Aspiring Data Science
274 subscribers
346 photos
9 videos
5 files
1.14K links
Заметки экономиста о программировании, прогнозировании и принятии решений, научном методе познания.
Контакт: @fingoldo

I call myself a data scientist because I know just enough math, economics & programming to be dangerous.
Download Telegram
#optimisation #sklearn #python #numba #codegems

Итак, то, что у кудесников машинного обучения считается 15 СЕКУНД, на самом деле должно занимать 50 МИЛЛИСЕКУНД. Но зато они сотни строк качественного кода написали, наверное, покрытого тестами, и прочим. Почему я считаю это важным? Да потому что мне предстоит обсчитывать сотни моделей, и оказалось, что качество обучения (classification report, roc auc, калибрация по всему сету + в разбивке по группам) на test set оценивается для 1 модели.. 5 минут, и основным тормозом был classification_report. Мне вовсе не улыбается платить за кластер, ожидающий сотни и тысячи раз по 5 минут. Спасибо, что у нас хотя бы есть numpy и numba. Без нумбы, кстати, этот код на языке программирования, созданном "гениальным" Ван Россумом, выполняется даже не знаю сколько, я прождал минут 5-10 и забил.

Скорее всего, напрасная трата времени, но всё же запостил issue. Как обычно, похерят, видимо.
#optimisation #numba #numpy #auc #fastauc

Ещё немного про оптимизацию. В попытке найти быструю реализацию roc_auc набрёл на библу factauc, где автор не поленился и сделал numba-оптимизированную, и даже сишную реализации. В сишную он явно вложился, вон сколько кода, и не напрасно: она получилась самой быстрой, почти вдвое быстрее нумбовской (что меня уже насторожило). Проверил на своём массивчике 8M float-ов, действительно самые тормозные catboost и sklearn (больше 2 секунд), фастаук уже позволяет прыгнуть до 0.6 секунды с нумба и до 0.4 с Си++. Глянул нумбовскую реализацию, а там argsort закомпилирован. Вспомнилось, что раньше нумба замедляла эту функцию. Вынес argsort "за скобки" njit-компилятора, и вуаля, С++ реализация побита, 0.3 секунды )) Даже неловко было сообщать автору, но что поделаешь.

P.S. Всеволод сказал, что на неделе предлагаемое улучшение потестит и, если что, в fastauc замёрджит )
#optimization #ml #metrics #python #numba #codegems

В общем, sklearn-овские метрики оказались слишком медленными, пришлось их переписать на numba. Вот пример classification_report, который работает в тысячу раз быстрее и поддерживает почти всю функциональность (кроме весов и микровзвешивания). Также оптимизировал метрики auc (алгоритм взят из fastauc) и calibration (считаю бины предсказанные vs реальные, потом mae/std от их разностей). На 8M сэмплов всё работает за ~30 миллисекунд кроме auc, та ~300 мс. Для сравнения, scikit-learn-овские работают от нескольких секунд до нескольких десятков секунд.

@njit()
def fast_classification_report(
y_true: np.ndarray, y_pred: np.ndarray, nclasses: int = 2, zero_division: int = 0
):
"""Custom classification report, proof of concept."""

N_AVG_ARRAYS = 3 # precisions, recalls, f1s

# storage inits
weighted_averages = np.empty(N_AVG_ARRAYS, dtype=np.float64)
macro_averages = np.empty(N_AVG_ARRAYS, dtype=np.float64)
supports = np.zeros(nclasses, dtype=np.int64)
allpreds = np.zeros(nclasses, dtype=np.int64)
misses = np.zeros(nclasses, dtype=np.int64)
hits = np.zeros(nclasses, dtype=np.int64)

# count stats
for true_class, predicted_class in zip(y_true, y_pred):
supports[true_class] += 1
allpreds[predicted_class] += 1
if predicted_class == true_class:
hits[predicted_class] += 1
else:
misses[predicted_class] += 1

# main calcs
accuracy = hits.sum() / len(y_true)
balanced_accuracy = np.nan_to_num(hits / supports, copy=True, nan=zero_division).mean()

recalls = hits / supports
precisions = hits / allpreds
f1s = 2 * (precisions * recalls) / (precisions + recalls)

# fix nans & compute averages
i=0
for arr in (precisions, recalls, f1s):
np.nan_to_num(arr, copy=False, nan=zero_division)
weighted_averages[i] = (arr * supports).sum() / len(y_true)
macro_averages[i] = arr.mean()
i+=1

return hits, misses, accuracy, balanced_accuracy, supports, precisions, recalls, f1s, macro_averages, weighted_averages
#sklearn #metrics #optimization #numba

В гитхабе sklearn-а началась некая дискуссия о том, нужны ли быстрые метрики или даже использование Numba в sklearn. Возможно, у Вас тоже есть своё мнение?
#numba #codegems #shuffle #random #numpy

На удивление, нумба ускоряет и функции нампай для работы со случайными числами. Пользуйтесь!
#numpy #numba #codegems #calloc

Итак, выяснилось, что numpy.zeros делегирует вызов сишной calloc, и на самом деле читит. Если тестировать инициализацию массива с реальной записью хотя бы 1 элемента, всё стаёт на свои места. .zeros() чуть медленнее остальных, .fill(0) несущественно быстрее двоеточий. Но удивительно, что нумба медленнее в 2-8 раз.

shape = (10000, 10000)
a = np.zeros(shape, dtype=np.int64)

def alloc_new(a):
a = np.zeros(shape, dtype=np.int64)
a[500, 500] = 1
return a

def numpy_fancy_assign(a):
a[:, :] = 0
a[500, 500] = 1
return a

def numpy_fill(a):
a.fill(0)
a[500, 500] = 1
return a

def cyclces_assign(a):
for i in range(a.shape[0]):
for j in range(a.shape[1]):
a[i, j] = 0
a[500, 500] = 1
return a

njitted_funcs = []
funcs = (alloc_new, numpy_fancy_assign, numpy_fill, cyclces_assign)
for func in funcs:
njitted_func = njit(func)
njitted_func(a) # test call
njitted_funcs.append(njitted_func)
#numpy #numba #codegems #zeros

История с zeros не закончилась )) Открылись новые факты. Я подумал, нумба показалась медленной из-за переключения контекста, поэтому внутри каждой функции выше просто сделал цикл до 10, чтобы основную работ вести внутри контекста. К примеру,

def numpy_fancy_assign(a):
for _ in range(10):
a[:, :] = 0
a[500, 500] = 1
return a

и т.д.
Выводы из прошлого поста подтвердились: numba-версии действительно медленнее numpy-евских, КРОМЕ a[:, :] = 0, которая одна-единственная при выполнении в контексте numba в 5 раз быстрее зануляет numpy-массив, чем сам numpy.

Оптимальная тактика на сегодня: массив создавать надо вне numba с помощью .zeros(), а обнулять его вызовом a[:, :] = 0 внутри numba (если, конечно, это надо делать много раз). Feature request чтобы нумба редиректила на np.zeros.
#numba #gpt #optimization #python #ai

Такая история: в процессе оптимизации питоновской функции с помощью numba выяснилось, что njit-нуть её не получится - она использует модуль itertools для получения списка комбинаций индексов массива. Думаю, была, не была, попросил чат ГПТ сгенерить нативный питоновский код для combinations без модуля itertools. ИИ справился. Потом попросил функцию модифицировать чтоб она njit-тилась. И это без проблем выполнилось, и сразу рабочий код. Будущее уже наступает. Или наступило.

import numpy as np
from numba import njit

@njit
def generate_combinations_recursive_njit(sequence, r):
if r == 0:
return np.empty((1, 0), dtype=sequence.dtype)
if sequence.size == 0:
return np.empty((0, r), dtype=sequence.dtype)

first, rest = sequence[0], sequence[1:]

without_first = generate_combinations_recursive_njit(rest, r)
with_first = generate_combinations_recursive_njit(rest, r - 1)

result = np.empty((without_first.shape[0] + with_first.shape[0], r), dtype=sequence.dtype)

result[:without_first.shape[0], :] = without_first
for i in range(with_first.shape[0]):
result[i + without_first.shape[0], 0] = first
result[i + without_first.shape[0], 1:] = with_first[i, :]

return result

sequence = np.arange(4)
r = 2

combinations_array = generate_combinations_recursive_njit(sequence, r)
combinations_list = sorted(combinations_array.tolist())
print(combinations_list)


A Skynet funding bill is passed in the United States Congress, and the system goes online on August 4, 1997, removing human decisions from strategic defense. Skynet begins to learn rapidly and eventually becomes self-aware at 2:14 a.m., EDT, on August 29, 1997.
#stats #numpy #numba

Набрёл на вот такую библиотечку быстрых вычислений статистик bottleneck. Мне надо было считать скользящую среднюю, так эта библа вдвое заруливает мою реализацию на numba!

PS. Ах, нет, заруливает только в некоторых частных случаях ) В большинстве случаев нумба король.