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

2ee76616a7dc4fab28cafd4ef265d16f.jpg

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

Scipy

Sklearn

Что в планах?

  • Допиливать 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 

#include 
 
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(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 
#include 
#include 
#include 

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{{.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

© Habrahabr.ru