Hidden text

Спойлер: это тизер. Конечно же, ни о каком импортозамещении речи нет. Просто мы хайпуем на возросшей волне популярности DA и DS, получаем фан, и при этом пытаемся утилизировать преимущества C++. No cringe, no fear.

О чем говорим?

Речь пойдет о библиотеках-аналогах numpy, pandas, scipy и sklearn на C++ (np, pd, scipy, sklearn соответственно). Эти проекты изначально задумывались как хорошее дополнение к портфолио, однако затем наступило всё более и более плотное вовлечение в процесс работы над ними, челенджи становились всё более и более существенными, и проект превратился в несколько отдельных проектов, содержащих десятки тысяч строк кода.

Предыстория

С момента написания моей прошлой статьи на Хабре прошло 6 лет. Со временем стало понятно, в какой плоскости стоит развиваться дальше бывалому программисту: интересно было бы связать свою карьеру с темой DA/DS, а точнее попробовать поразрабатывать инструменты для дата-аналитиков и датасайентистов.

Мне хотелось не только и не столько попробовать себя собственно в качестве датасайентиста, а более всего пощупать нутрянку многомерных массивов, реализовать слайсы, датафреймы, статистические методы и методы машинного обучения самостоятельно, ощутив на своей шкуре всю прелесть бесконечных челенджей разработчиков библиотек – борьбы за перформанс, юзабилити и функциональность. Потом опять перформанс, юзабилити и функциональность. Потом опять. Цикл “пока не надоест”. И все это на чистом C++.

Цели

Вы спросите: какова цель всего этого мероприятия? Отвечаю:

  • получить фан.

  • принести пользу пользователям и комьюнити.

  • и вообще, у самурая нет цели, есть только путь.

Принципы

  • Использовать оригинальный API соответствующих библиотек, так как миллионы пользователей уже к нему привыкли.

  • Не смотреть оригинальную имплементацию numpy, pandas и т.д. и не использовать идеи оттуда. Все с чистого листа, мы же самураи.

  • Не использовать сторонние библиотеки без крайней на это необходимости, все велосипеды руками (либо ногами).

  • Перформанс, перформанс, перформанс. У нас, у самураев C++, есть возможность затьюнить код так хорошо, как только можно, и этой возможностью нужно пользоваться.

Как и что имплементировать?

Итак, API у нас уже есть. А что же мы делаем в плане имплементации? Очень просто:

  1. Открываем блокнотик любого дата сайентиста. Например, https://github.com/adityakumar529/Coursera_Capstone/blob/master/KNN.ipynb

  2. Идем в каждую ячейку последовательно, сверху вниз, и пытаемся заимплементить соответствующую фичу в верхнеуровневой библиотеке sklearn.

  3. Заимплементили? Мы молодцы. Теперь оказывается, что чего-то нехватает в scipy.

  4. Теперь в pandas.

  5. Теперь в numpy.

  6. If (your grade <= junior developer) {

    goto 2;

    } else {

    // think more. What if implement the most popular features all at once?

    goto 7;

    }

  7. Имплементим сразу много.

  8. goto 2 while not tired, otherwise break

Мы молодцы!

Что уже сделано?

Numpy:

Pandas:

  • Датафреймы с операциями над ними

  • Series с операциями над ними

Scipy:

  • Модальные значения в массивах и датафреймах

Sklearn:

  • KNN на массивах и датафреймах

  • Метрики

  • Методы препроцессинга датасетов

Что в планах?

  • Допиливать sklearn и остальные библиотеки до победного (как минимум до полного имплемента сheatsheet-ов и как максимум для покрытия самых популярных сценариев датасайентистов)

  • Библиотека-аналог pytorch и/или tensorflow. Или что-то совсем другое, но по DL

  • Performance. Рукописные оптимизации под SSE, AVX2, AVX512.

А что же с перформансом?

Не всё так радужно, но мы работаем над этим.

Рассмотрим следующий пример на python (читатель, конечно же, узнал метод Монте-Карло для вычисления числа “пи”):

import numpy as np

size = 100000000

rx = np.random.rand(size)
ry = np.random.rand(size)

dist = rx * rx + ry * ry

inside = (dist[dist<1]).size

print ("PI=", 4 * inside / size)

Делаем замеры:

$ time python monte_carlo.py
PI= 3.14185536

real 0m2.108s
user 0m2.055s
sys 0m0.525s

Пример для нашей имплементации:

#include <iostream>

#include <np/Array.hpp>
 
int main(int, char **) {

     using namespace np;

     static const constexpr Size size = 100000000;

     auto rx = random::rand(size);
     auto ry = random::rand(size);

     auto dist = rx * rx + ry * ry;

     auto inside = (dist["dist<1"]).size();

     std::cout << "PI=" << 4 * static_cast<double>(inside) / size;

     return 0;
 }

Запускаем:

$ time ./monte_carlo

PI=3.14152

real	0m2.871s
user	0m12.610s
sys	    0m1.184s

Читерство? Читерство. У нас используется OpenMP (поэтому и user time такой) + встроенная векторизация для AVX2, чего нельзя сказать о "ванильном" numpy. Короче говоря, есть над чем работать.

Хотите еще примеров? Их есть у меня!

Пример по мотивам https://github.com/adityakumar529/Coursera_Capstone/blob/master/KNN.ipynb

#include <sklearn/metrics/f1_score.hpp>
#include <sklearn/model_selection/train_test_split.hpp>
#include <sklearn/neighbors/KNeighborsClassifier.hpp>
#include <sklearn/preprocessing/StandardScaler.hpp>

int main(int, char **) {
    using namespace pd;
    using namespace sklearn::model_selection;
    using namespace sklearn::neighbors;
    using namespace sklearn::preprocessing;
    using namespace sklearn::metrics;

    auto data = read_csv("https://raw.githubusercontent.com/adityakumar529/Coursera_Capstone/master/diabetes.csv");
    const char *non_zero[] = {"Glucose", "BloodPressure", "SkinThickness", "Insulin", "BMI"};
    for (const auto &column: non_zero) {
        data[column] = data[column].replace(0L, np::NaN);
        auto mean = data[column].mean(true);
        data[column] = data[column].replace(np::NaN, mean);
    }

    auto X = data.iloc(":", "0:8");
    auto y = data.iloc(":", "8");
    auto [X_train, X_test, y_train, y_test] = train_test_split({.X = X, .y = y, .test_size = 0.2, .random_state = 42});

    auto sc_X = StandardScaler{};
    X_train = sc_X.fit_transform(X_train);
    X_test = sc_X.transform(X_test);

    auto classifier = KNeighborsClassifier<pd::DataFrame>{{.n_neighbors = 13,
                                                           .p = 2,
                                                           .metric = sklearn::metrics::DistanceMetricType::kEuclidean}};
    classifier.fit(X_train, y_train);
    auto y_pred = classifier.predict(X_test);
    std::cout << "Prediction: " << y_pred << std::endl;
    auto cm = confusion_matrix({.y_true = y_test, .y_pred = y_pred});
    std::cout << cm << std::endl;
    std::cout << f1_score({.y_true = y_test, .y_pred = y_pred}) << std::endl;
    std::cout << accuracy_score(y_test, y_pred) << std::endl;

    return 0;
}

Вывод примера:

$ ./neighbors_diabetes
Prediction: 	0
0	0
1	0
2	0
3	0
4	1
...
149	1
150	0
151	0
152	0
153	1
154 rows x 1 columns

[[85 15]
 [19 35]]
0.673077
0.779221

Про перформанс скромно умолчим.

Еще примеры здесь: https://github.com/mgorshkov/sklearn/tree/main/samples

Заключение

Мы сделали краткий обзор библиотек np, pd, scipy, sklearn.

Присоединяйтесь к разработке!

Для желающих поконтрибьютить начинающих разрабов проекты будут хорошим, а главное, совершенно бесплатным, приложением к вашему портфолио, а для желающих все больше и больше фич и их кастомизации дата сайентистов – может быть станут и рабочим инструментом.

Если есть заинтересовавшиеся поконтрибьютить, обращайтесь в личку. Если есть желающие заюзать библиотеку датасайентисты – тоже обращайтесь, кастомизируем проект под ваши нужды.

И еще: кому нужно обучить или зафайнтьюнить свою модель на GPU – обращайтесь. Дам поюзать свою GPU в обмен на возможность ознакомиться с вашим проектом.

Ссылки

C++ numpy-like template-based array implementation: https://github.com/mgorshkov/np

Methods from pandas library on top of NP library: https://github.com/mgorshkov/pd

Scientific methods on top of NP library: https://github.com/mgorshkov/scipy

ML Methods from scikit-learn library: https://github.com/mgorshkov/sklearn

Комментарии (30)


  1. N-Cube
    06.08.2023 11:17
    +14

    Вроде не первое апреля, а статья похожа на первоапрельскую шутку… В numpy/scipy уйма функций, и все они кому-то нужны. Ладно, давайте на примерах - вот у меня код собственной реализации robust trend estimator (для выравнивания стэка радарных космоснимков функция работает быстрее и точнее библиотечных) на питоне с использованием библиотек: https://github.com/mobigroup/gmtsar/blob/pygmtsar/todo/PRM.robust_trend2d.ipynb Покажите вашу реализацию без использования сторонних библиотек и сравнимой производительности. Сразу скажу, что понадобится целая пачка спецфункций, каждая из которых описана несколько по-разному в разных научных статьях, и во всех вариантах могут быть свои ограничения, ошибки или просто недочеты.


  1. Emulyator
    06.08.2023 11:17
    +3

    Интернет говорит, что вроде как есть библиотеки по типу pandas с применением gpu, например https://github.com/rapidsai/cudf (сам не использовал)


  1. funca
    06.08.2023 11:17

    Что в планах?

    Пожалуй, оставлю себе в будущем этот коммент. Вдруг через 100500 лет пригодится (или сколько там было суммарно вложено в разработку оригиналов).


  1. Actaeon
    06.08.2023 11:17
    +1

    Ииии ??
    Ну, вот просто интересно, как вы думаете, через сколько лет вашей библиотеке начнут доверять ??


    1. mike1 Автор
      06.08.2023 11:17

      Если сравняюсь с numpy по некоторым бенчмаркам, то может быть и станут доверять, но и то не факт. В любом случае, пока я работаю над этим один, всё это скорее похоже на авантюру и ресерч-проект в стол, поскольку ресурсов сделать что-то конкурентоспособное нет. Можно попробовать не распыляться, и что-то одно хотя бы довести до ума, но у меня пока даже нет идей, на чем лучше сконцентрироваться - перф дожимать или функциональность наращивать.


      1. N-Cube
        06.08.2023 11:17

        Можно и одному делать конкурентоспособное, только нишу выбрать. Скажем, библиотека для легкого создания numpy-совместимых функций, исполняемых на GPU с поддержкой, в том числе, Apple Silicon платформы будет куда как интереснее ваших матричных операций на плюсах (кстати, в фортране это давно уже есть нативное и отлично оптимизированное, до появления numpy проще было именно на фортране написать).


        1. mike1 Автор
          06.08.2023 11:17
          +1

          Спасибо за идею @N-Cube! Нужна ниша, но-таки да, я не ориентируюсь в конкретных потребностях пользователей. А не скинете конкретный example чего бы вы хотели? Поподробнее о numpy-совместимых функциях? Лучше в личку. Спасибо!


          1. N-Cube
            06.08.2023 11:17
            -1

            Почему в личку, это же много кому интересно. Мой открытый проект это процессор радарных спутниковых снимков, написан на питоне с поддержкой отложенных вычислений, работает намного быстрее сишных реализаций и требует на порядки меньше памяти (имеется в виду десятичный порядок, на докерхабе есть пример, который на питоне работает в контейнере с 16 гигабайт оперативной памяти, а на си половина этого примера не работает при наличии 256 гигабайт). Так вот, было бы прекрасно иметь возможность просто подгрузить библиотеку, которая заменит часть базовых операций numpy на gpu-версии и выполнит их на любой ОС (макос эппл силикон и интел, линукс, опционально - виндоус). Как вариант похуже, иметь возможность самому реализовать нужные функции для выполнения на gpu - это потребует лишней работы, но тоже в определенных ситуациях годится. В принципе, уже есть подобные библиотеки, но заточенные на определенные видеокарты, так что проекту для широкой аудитории они бесполезны. Даже какие-то простые вещи типа аффинного преобразования больших растров будут полезны, если будут работать везде и стабильно.


            1. fongostev
              06.08.2023 11:17

              А как же cupy? Конечно, там реализованы далеко не все функции numpy, но приличное количество. Рискну предположить, что для отложенных вычислений Вы используете dask, тогда перевести алгоритмы на GPU можно просто заменив бекэнд с numpy на cupy.


        1. ArtemWernon
          06.08.2023 11:17

          библиотека для легкого создания numpy-совместимых функций, исполняемых на GPU с поддержкой, в том числе, Apple Silicon платформы

          Звучит как jax, в эппл силикон вроде тоже умеет: https://github.com/google/jax/discussions/9847


          1. N-Cube
            06.08.2023 11:17
            -1

            Как вы с этим напишите, скажем, аффинное преобразование растров?


    1. Gadd
      06.08.2023 11:17

      Как минимум не раньше, чем с этим можно будет работать в Jupiyer Notebook или "импортозамещенном" аналоге.


      1. mike1 Автор
        06.08.2023 11:17
        +1

        В Jupyter можно работать и с C++ (https://github.com/jupyter-xeus/xeus-cling), и с Java, и с многими другими популярными языками, так что это не проблема.


  1. tzlom
    06.08.2023 11:17
    +2

    У вас в статье опечатка, или рельно С++ версия сильно медленнее питона?

    Претензии на AVX есть, но внутри для этого ничего нет, так что оптимизации там постольку-поскольку. numpy этим увы тоже страдает.

    BlazeCpp гораздо быстрее и удобнее вашего np.

    Вообщем импортозамещение в его классическом представлении.

    На практике - берите Julia и забейте на плюсы, даже с крутейшим Blaze писать математику на плюсах все равно сложно и муторно.


    1. mike1 Автор
      06.08.2023 11:17

      В numpy и sklearn под капотом C и Fortran, если я не ошибаюсь, так что ничего удивительного. Но у меня есть шанс догнать и перегнать их.

      Возможно, моя библиотека/ки будут полезны тем, кто страдает от неудобной интеграции с Python-ом и готовы немного пожертвовать перформансом.

      А вообще посмотрим куда всё это пойдет. Пока не знаю.


      1. tzlom
        06.08.2023 11:17

        Еще как удивительно, ведь уделать numpy по производительности на вашем примере задача тривиальная, попробуйте написать то же самое с использованием Blaze или на Julia и будете приятно удивлены


        1. mike1 Автор
          06.08.2023 11:17

          ok, спс, посмотрю как и что у них там сделано.


        1. N-Cube
          06.08.2023 11:17
          -1

          Если вы так рекламируете свое, как насчет сравнить производительность с питоном: https://pythonspeed.com/articles/speeding-up-numba/


          1. mike1 Автор
            06.08.2023 11:17

            Спс за ссылку. Цифры перфа с примерами можно использовать как бейзлайн. В статье говорится о вещах известных - кэш-миссы, branch misprediction, SIMD, но правильно вкрутить все эти вещи в софт (в смысле SIMD и побороться с первыми двумя) - та еще задачка. SIMD делает и компилятор как умеет, но он может делать это не оптимально. И можно вырулить на том, что ещё не очень многие хорошо научились делать это оптимально. И спасибо Intel VTune/Intel Advisor!


            1. N-Cube
              06.08.2023 11:17
              -1

              Главное, поменьше слушайте всех тех, кто громко кричит про разные модные вещи типа джулия, а как речь заходит о тестах производительности или реальных задачах, тут же исчезает.


  1. economist75
    06.08.2023 11:17

    Сейчас, с появлением Pandas 2.0+, появилась новая точка отсчета - бэкенд pyarrow (Apache Arrow), который кратно быстрее читается, в разы быстрее пишет в файл и фильтрует чем numpy- и pandas-объекты. И все это почти не меняя код. Другое дело что его написаны тонны, но ведь и переходить никто не торопит.

    Есть места где pyarrow медленнее чем Numpy/Pandas (работа с categorical-данными, агрегирование и по мелочи). Но в целом апачевские колоночные БД, форматы и DS-платформы уже стали промышленным стандартом и однозначно они скоро станут основными в DS и BI (года через 3).


    1. N-Cube
      06.08.2023 11:17
      -1

      Нет, конечно. Многомерные растры с ним будут только медленнее, когда вы попытаетесь произвольный многомерный блок извлечь. Эта штука только для одномерных данных, для которых и так бэкенды типа NetCDF и zarr хорошо работают.


  1. vagon333
    06.08.2023 11:17
    +2

    Импортозамещаем numpy, pandas, scipy и sklearn


    Импортозамещать не корректно т.к. нет ограничения в использовании на территории РФ.

    Если выбирать либу - между старой, с открытым доступом и огромной community поддержкой, либо вашу, которую еще нужно изучать, а гарантии поддержки лет через 5 нет никакой, то выбор очевиден.


    1. mike1 Автор
      06.08.2023 11:17

      Я постарался сделать интерфейс максимально, так близко как только возможно, приближенным к numpy/pandas/scipy/sklearn. То есть по интерфейсу это по сути то же самое. Так что "переобучиться" для тех, кто хоть немного понимает в C++ - не проблема.


      1. vagon333
        06.08.2023 11:17

        Я читал ваше описание, но всегда есть разница реализации функционала, когда одна и та-же функция в разных либах работает по-разному.


  1. kivsiak
    06.08.2023 11:17

    Надо было на Rust писать. Тогда хотя-бы с контрибутерами проблем не будет.


    1. ShaggyCat2
      06.08.2023 11:17
      +1

      Уже есть, Polars:

      https://www.pola.rs/


  1. VladimirFarshatov
    06.08.2023 11:17

    Когда понадобилось применять математику для вращения матрицами представления дрона о пространстве, не стал применять ни одну "библиотеку", а тупо перемножил руками. Матрицы 3х3 это всего ничего операций.. ;)


  1. js_n00b
    06.08.2023 11:17

    Осталось импортозаместить Github


  1. vba
    06.08.2023 11:17

    пытаемся утилизировать преимущества C++

    Не совсем понятно, зачем утилизировать преимущества. Чем они вам не угодили? Наоборот их нужно использовать, разве не так?