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 у нас уже есть. А что же мы делаем в плане имплементации? Очень просто:
Открываем блокнотик любого дата сайентиста. Например, https://github.com/adityakumar529/Coursera_Capstone/blob/master/KNN.ipynb
Идем в каждую ячейку последовательно, сверху вниз, и пытаемся заимплементить соответствующую фичу в верхнеуровневой библиотеке sklearn.
Заимплементили? Мы молодцы. Теперь оказывается, что чего-то нехватает в scipy.
Теперь в pandas.
Теперь в numpy.
If (your grade <= junior developer) {
goto 2;
} else {
// think more. What if implement the most popular features all at once?
goto 7;
}
Имплементим сразу много.
goto 2 while not tired, otherwise break
Мы молодцы!
Что уже сделано?
Numpy:
N-dimensional массивы, заточенные для использования на стеке и хипе
Слайсы над ними
Популярные операции над ними
Подробнее: все, что изображено здесь, за исключением fancy indexing: https://s3.amazonaws.com/assets.datacamp.com/blog_assets/Numpy_Python_Cheat_Sheet.pdf
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
