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