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
Комментарии (30)
Emulyator
06.08.2023 11:17+3Интернет говорит, что вроде как есть библиотеки по типу pandas с применением gpu, например https://github.com/rapidsai/cudf (сам не использовал)
funca
06.08.2023 11:17Что в планах?
Пожалуй, оставлю себе в будущем этот коммент. Вдруг через 100500 лет пригодится (или сколько там было суммарно вложено в разработку оригиналов).
Actaeon
06.08.2023 11:17+1Ииии ??
Ну, вот просто интересно, как вы думаете, через сколько лет вашей библиотеке начнут доверять ??mike1 Автор
06.08.2023 11:17Если сравняюсь с numpy по некоторым бенчмаркам, то может быть и станут доверять, но и то не факт. В любом случае, пока я работаю над этим один, всё это скорее похоже на авантюру и ресерч-проект в стол, поскольку ресурсов сделать что-то конкурентоспособное нет. Можно попробовать не распыляться, и что-то одно хотя бы довести до ума, но у меня пока даже нет идей, на чем лучше сконцентрироваться - перф дожимать или функциональность наращивать.
N-Cube
06.08.2023 11:17Можно и одному делать конкурентоспособное, только нишу выбрать. Скажем, библиотека для легкого создания numpy-совместимых функций, исполняемых на GPU с поддержкой, в том числе, Apple Silicon платформы будет куда как интереснее ваших матричных операций на плюсах (кстати, в фортране это давно уже есть нативное и отлично оптимизированное, до появления numpy проще было именно на фортране написать).
mike1 Автор
06.08.2023 11:17+1Спасибо за идею @N-Cube! Нужна ниша, но-таки да, я не ориентируюсь в конкретных потребностях пользователей. А не скинете конкретный example чего бы вы хотели? Поподробнее о numpy-совместимых функциях? Лучше в личку. Спасибо!
N-Cube
06.08.2023 11:17-1Почему в личку, это же много кому интересно. Мой открытый проект это процессор радарных спутниковых снимков, написан на питоне с поддержкой отложенных вычислений, работает намного быстрее сишных реализаций и требует на порядки меньше памяти (имеется в виду десятичный порядок, на докерхабе есть пример, который на питоне работает в контейнере с 16 гигабайт оперативной памяти, а на си половина этого примера не работает при наличии 256 гигабайт). Так вот, было бы прекрасно иметь возможность просто подгрузить библиотеку, которая заменит часть базовых операций numpy на gpu-версии и выполнит их на любой ОС (макос эппл силикон и интел, линукс, опционально - виндоус). Как вариант похуже, иметь возможность самому реализовать нужные функции для выполнения на gpu - это потребует лишней работы, но тоже в определенных ситуациях годится. В принципе, уже есть подобные библиотеки, но заточенные на определенные видеокарты, так что проекту для широкой аудитории они бесполезны. Даже какие-то простые вещи типа аффинного преобразования больших растров будут полезны, если будут работать везде и стабильно.
fongostev
06.08.2023 11:17А как же cupy? Конечно, там реализованы далеко не все функции numpy, но приличное количество. Рискну предположить, что для отложенных вычислений Вы используете dask, тогда перевести алгоритмы на GPU можно просто заменив бекэнд с numpy на cupy.
ArtemWernon
06.08.2023 11:17библиотека для легкого создания numpy-совместимых функций, исполняемых на GPU с поддержкой, в том числе, Apple Silicon платформы
Звучит как jax, в эппл силикон вроде тоже умеет: https://github.com/google/jax/discussions/9847
Gadd
06.08.2023 11:17Как минимум не раньше, чем с этим можно будет работать в Jupiyer Notebook или "импортозамещенном" аналоге.
mike1 Автор
06.08.2023 11:17+1В Jupyter можно работать и с C++ (https://github.com/jupyter-xeus/xeus-cling), и с Java, и с многими другими популярными языками, так что это не проблема.
tzlom
06.08.2023 11:17+2У вас в статье опечатка, или рельно С++ версия сильно медленнее питона?
Претензии на AVX есть, но внутри для этого ничего нет, так что оптимизации там постольку-поскольку. numpy этим увы тоже страдает.
BlazeCpp гораздо быстрее и удобнее вашего np.
Вообщем импортозамещение в его классическом представлении.
На практике - берите Julia и забейте на плюсы, даже с крутейшим Blaze писать математику на плюсах все равно сложно и муторно.
mike1 Автор
06.08.2023 11:17В numpy и sklearn под капотом C и Fortran, если я не ошибаюсь, так что ничего удивительного. Но у меня есть шанс догнать и перегнать их.
Возможно, моя библиотека/ки будут полезны тем, кто страдает от неудобной интеграции с Python-ом и готовы немного пожертвовать перформансом.
А вообще посмотрим куда всё это пойдет. Пока не знаю.
tzlom
06.08.2023 11:17Еще как удивительно, ведь уделать numpy по производительности на вашем примере задача тривиальная, попробуйте написать то же самое с использованием Blaze или на Julia и будете приятно удивлены
N-Cube
06.08.2023 11:17-1Если вы так рекламируете свое, как насчет сравнить производительность с питоном: https://pythonspeed.com/articles/speeding-up-numba/
mike1 Автор
06.08.2023 11:17Спс за ссылку. Цифры перфа с примерами можно использовать как бейзлайн. В статье говорится о вещах известных - кэш-миссы, branch misprediction, SIMD, но правильно вкрутить все эти вещи в софт (в смысле SIMD и побороться с первыми двумя) - та еще задачка. SIMD делает и компилятор как умеет, но он может делать это не оптимально. И можно вырулить на том, что ещё не очень многие хорошо научились делать это оптимально. И спасибо Intel VTune/Intel Advisor!
N-Cube
06.08.2023 11:17-1Главное, поменьше слушайте всех тех, кто громко кричит про разные модные вещи типа джулия, а как речь заходит о тестах производительности или реальных задачах, тут же исчезает.
economist75
06.08.2023 11:17Сейчас, с появлением Pandas 2.0+, появилась новая точка отсчета - бэкенд pyarrow (Apache Arrow), который кратно быстрее читается, в разы быстрее пишет в файл и фильтрует чем numpy- и pandas-объекты. И все это почти не меняя код. Другое дело что его написаны тонны, но ведь и переходить никто не торопит.
Есть места где pyarrow медленнее чем Numpy/Pandas (работа с categorical-данными, агрегирование и по мелочи). Но в целом апачевские колоночные БД, форматы и DS-платформы уже стали промышленным стандартом и однозначно они скоро станут основными в DS и BI (года через 3).
N-Cube
06.08.2023 11:17-1Нет, конечно. Многомерные растры с ним будут только медленнее, когда вы попытаетесь произвольный многомерный блок извлечь. Эта штука только для одномерных данных, для которых и так бэкенды типа NetCDF и zarr хорошо работают.
vagon333
06.08.2023 11:17+2Импортозамещаем numpy, pandas, scipy и sklearn
Импортозамещать не корректно т.к. нет ограничения в использовании на территории РФ.Если выбирать либу - между старой, с открытым доступом и огромной community поддержкой, либо вашу, которую еще нужно изучать, а гарантии поддержки лет через 5 нет никакой, то выбор очевиден.
mike1 Автор
06.08.2023 11:17Я постарался сделать интерфейс максимально, так близко как только возможно, приближенным к numpy/pandas/scipy/sklearn. То есть по интерфейсу это по сути то же самое. Так что "переобучиться" для тех, кто хоть немного понимает в C++ - не проблема.
vagon333
06.08.2023 11:17Я читал ваше описание, но всегда есть разница реализации функционала, когда одна и та-же функция в разных либах работает по-разному.
VladimirFarshatov
06.08.2023 11:17Когда понадобилось применять математику для вращения матрицами представления дрона о пространстве, не стал применять ни одну "библиотеку", а тупо перемножил руками. Матрицы 3х3 это всего ничего операций.. ;)
vba
06.08.2023 11:17пытаемся утилизировать преимущества C++
Не совсем понятно, зачем утилизировать преимущества. Чем они вам не угодили? Наоборот их нужно использовать, разве не так?
N-Cube
Вроде не первое апреля, а статья похожа на первоапрельскую шутку… В numpy/scipy уйма функций, и все они кому-то нужны. Ладно, давайте на примерах - вот у меня код собственной реализации robust trend estimator (для выравнивания стэка радарных космоснимков функция работает быстрее и точнее библиотечных) на питоне с использованием библиотек: https://github.com/mobigroup/gmtsar/blob/pygmtsar/todo/PRM.robust_trend2d.ipynb Покажите вашу реализацию без использования сторонних библиотек и сравнимой производительности. Сразу скажу, что понадобится целая пачка спецфункций, каждая из которых описана несколько по-разному в разных научных статьях, и во всех вариантах могут быть свои ограничения, ошибки или просто недочеты.