Data Science | Machinelearning [ru]
19.9K subscribers
783 photos
54 videos
28 files
3.7K links
Все о Data Science, машинном обучении и искусственном интеллекте: от базовой теории до cutting-edge исследований и LLM.

Личный блог автора - @just_genych
По вопросам рекламы или разработки - @g_abashkin


РКН: https://vk.cc/cJPGXD
Download Telegram
Граница Дженсена-Шеннона для обнаружения дрейфа эмбеддингов без лейблов

В production-рекомендательных системах скрытый дрейф часто остается незамеченным до падения бизнес-метрик. Когда лейблы недоступны из-за privacy ограничений или задержки накопления ground truth, единственный сигнал — изменение распределения эмбеддингов. Типичная ошибка: визуально сравнивать UMAP или PCA проекции, хотя JSD дает численную, статистически обоснованную метрику.

Почему JSD, а не KL или MMD
JSD симметрична, ограничена [0, log(2)] и имеет интерпретируемый порог. Для эмбеддингов размерностью 128+ значение JSD > 0.01-0.03 после нормировки на размерность — надежный индикатор дрейфа. В отличие от KL, JSD не требует выбора референсного распределения, а в отличие от MMD — имеет понятную шкалу. На практике JSD на батчах эмбеддингов из разных временных окон хорошо коррелирует с последующим падением offline-метрик.

KNN-based оценка без плотности
Прямая оценка JSD через KDE на 256-мерных эмбеддингах — ошибка: curse of dimensionality убивает KDE. Рабочий подход — использовать энтропийную оценку через расстояния до k-го соседа:

import numpy as np
from sklearn.neighbors import NearestNeighbors

def jsd_knn(X, Y, k=5):
n, d = X.shape
m = Y.shape[0]
Z = np.vstack([X, Y])

# Энтропия смеси
nbrs_mix = NearestNeighbors(n_neighbors=k+1).fit(Z)
dist_mix = nbrs_mix.kneighbors(Z, return_distance=True)[0][:, -1]
H_mix = np.log(n+m) - np.log(k) + d * np.mean(np.log(np.maximum(dist_mix, 1e-10)))

# Энтропия X
nbrs_X = NearestNeighbors(n_neighbors=k+1).fit(X)
dist_X = nbrs_X.kneighbors(X, return_distance=True)[0][:, -1]
H_X = np.log(n) - np.log(k) + d * np.mean(np.log(np.maximum(dist_X, 1e-10)))

# Энтропия Y
nbrs_Y = NearestNeighbors(n_neighbors=k+1).fit(Y)
dist_Y = nbrs_Y.kneighbors(Y, return_distance=True)[0][:, -1]
H_Y = np.log(m) - np.log(k) + d * np.mean(np.log(np.maximum(dist_Y, 1e-10)))

jsd = H_mix - 0.5 * (H_X + H_Y)
return max(0.0, jsd)

# Пример на 128d эмбеддингах
X_old = np.random.randn(10000, 128)
X_new = X_old + np.random.randn(10000, 128) * 0.15
print(jsd_knn(X_old, X_new)) # ~0.008 — норма
X_drifted = X_old + np.random.randn(10000, 128) * 0.4
print(jsd_knn(X_old, X_drifted)) # ~0.04 — дрейф


Практический совет: для production выбирайте k в диапазоне [5, 20] и фиксируйте seed. Предупреждение: JSD через k-NN чувствительна к выбросам — обязательно preprocess: центрируйте (убирая среднее), clip граничные значения, и мониторьте разницу в числе наблюдений между окнами.

Trade-offs и валидация порога
Главный риск — ложные срабатывания при высоком k или low-density областях эмбеддингового пространства. На практике порог подбирается эмпирически: возьмите исторические данные без дрейфа, вычислите JSD между соседними временными окнами (например, днями), возьмите 99-й перцентиль. Для 128-мерных эмбеддингов в рекомендательных системах часто получается 0.01-0.02. Если JSD между текущим и референсным окном превышает это значение — запускайте углубленную диагностику: смотрите на per-feature drift, k ближайших соседей, проверяйте на данных позже с лейблами.

Вывод: JSD на эмбеддингах через k-NN — это production-ready, unsupervised алерт дрейфа, который дает численный порог без накопления лейблов, но требует калибровки под конкретную размерность и архитектуру модели.
Streaming-адаптация контекстных бандитов с мета-обучением на сдвигах распределения в recommendation serving

Реальные рекомендательные сервисы живут в условиях distribution shift — пользовательские паттерны, тренды и поведение ботов постоянно меняются. Классические контекстные бандиты вроде LinUCB или NeuralUCB требуют полного ретренинга на исторических данных, и в стриминговой среде это превращается в дорогой и нестабильный процесс. Основная ошибка — полагаться на статичные модели, которые не справляются с non-stationary окружением, где online-learning через SGD страдает от дрейфа градиентов.

Почему это critical для production ML

В реальных системах — новостные ленты, e-commerce, реклама — распределение наград и контекстов дрейфует каждые несколько часов. Полный ретренинг LinUCB на всем датасете каждые 10 минут непозволительно дорог: latency растет, метрики проседают. Online-обновления через stochastic gradient descent могут стабилизироваться, но после резкого сдвига (например, алгоритмической смены аудитории) градиенты рассинхронизируются, и CTR резко падает. Вместо этого нужна быстрая адаптация с минимальным числом шагов — мета-обучение здесь дает explicit преимущество.

Архитектура: MAML-style мета-обучение на потоке данных

Подход заключается в комбинировании прошлых сдвигов как отдельных tasks. Каждый новый батч с shift-зашумленными данными — это отдельная задача. Мета-сеть (например, RNN) кодирует историю контекстов и наград, а для адаптации используется один градиентный шаг на стриминговых данных с регуляризацией на предыдущие сдвиги. Вот упрощенный код на JAX:

def meta_update(theta, batch_context, rewards, alpha=0.01):
inner_grad = grad(loss_function)(theta, batch_context, rewards)
theta_adapted = theta - alpha * inner_grad
meta_grad = grad(loss_function)(theta_adapted, batch_context, rewards)
return theta - 0.1 * meta_grad


Key insight: first-order MAML ускоряет вычисления — нет необходимости в heavy second-order дифференцировании, что критично для real-time serving. FTRL-обновление мета-параметров добавляет регуляризацию против шума. Trade-off: требуются GPU для инференса мета-сети, но latency остается в пределах десятков миллисекунд.

Пример из production

Допустим, у вас recommendation system для новостной ленты. В 10:00 фиксируем shift из-за смены аудитории (переход на мобильных юзеров). Мета-параметры запоминают похожие паттерны с прошлой недели — например, сезонное падение CTR на развлекательном контенте в утренние часы. Агент адаптируется за 2 градиентных шага на батче в 1000 запросов, что занимает 50 мс на GPU. По данным из arXiv:2206.04137, CTR растет на 20% против онлайн-LinUCB за счет быстрой перестройки политики. Важно: без мета-обучения LinUCB показал бы падение на 10-15% в первые 30 минут после shift.

Типичная ошибка и практический совет

Ошибка: использовать классический MAML с full-batch обновлениями в стриминге — это ломается из-за высокой вариативности mini-batch градиентов. Градиенты могут уводить параметры в неправильную сторону на шумных данных. Совет: добавляйте FTRL-проксимальный шаг к мета-обновлению — это стабилизирует обучения и предотвращает катастрофическое забывание предыдущих сдвигов. Также проверяйте distribution shift на этапе feature engineering: внезапное изменение контекстных признаков (например, падение числа просмотров категории) часто индицирует дрейф, и мета-обучение должно активироваться только при явном детектировании.

Применение в production

Подходит для high-stakes scenarios: Black Friday в e-commerce, рекламные кампании с резкой сменой interest таргетинга, news feeds с трендовыми темами. Но готовьтесь к GPU costs — мета-сеть требует инференса, и latency превышает simple LinUCB на 15-20 мс. Если требования к latency до 10 мс, этот подход заменяйте на lightweight версию с одним gradient step.

Вывод: Мета-обучение на сдвигах решает проблему non-stationary в рекомендательных системах быстрее, чем offline ретренинг, и стабильнее, чем naive online-learning, но требует GPU-поддержки и внимательной регуляризации против шума градиентов.
Локальная LLM на Windows 11: среда, модель и развёртывание

Это третья статья из цикла, посвящённого созданию системы круглосуточной ситуационной осведомлённости. Материал является подготовительным этапом перед запуском такой системы.

В статье рассматривается пошаговое развёртывание локальной большой языковой модели на Windows 11. Описывается выбор среды выполнения и конкретной модели для инференса. Приводится последовательность действий для установки и настройки всех необходимых компонентов.

Автор делится практическим опытом запуска LLM на персональном компьютере под управлением Windows 11. Инструкция нацелена на пользователей, желающих получить работающий локальный чат-бот без обращения к облачным сервисам.

Читать статью полностью
🔥5
Distributed SHAP-агрегация для real-time интерпретации предсказаний GBDT в сервинге с гарантированной консистентностью

Когда GBDT-модель висит под 100k RPS в продакшене, интерпретация каждого предсказания — не опция, а необходимость. Особенно если регулятор или compliance просят объяснить, почему клик засчитали именно этому юзеру. Но стандартный SHAP для GBDT — это O(T * 2^max_depth). Для глубины дерева 10 — 1024 комбинации на одно дерево. В real-time это не взлетит.

Я часто вижу, как команды кладут SHAP-векторы в Kafka, а потом Spark их аггрегирует. И тут начинается классическая проблема: временные метки расходятся, значения теряются, и вместо объяснения получается каша. Особенно когда на разных worker-ах модель чуть по-разному обновляется.

Архитектура агрегации

Мы пошли другим путём. Каждый worker считает локальный SHAP через TreeSHAP (сложность O(T * D²), для глубоких деревьев всё ещё дорого, но терпимо, если резать глубину). Для real-time используем приближение: берём глобальный baseline (среднее предсказание по train), и worker отправляет в Redis Cluster не весь вектор, а агрегат: feature_name → (sum_shap, count, глобальный timestamp).

Проблема консистентности

Главная проблема — консистентность. Если два worker-а видят модель в разном состоянии, их SHAP-ы несопоставимы. Тут в игру входят Hybrid Logical Clocks (HLC). Они дают causal consistency: мы знаем, какое событие произошло раньше, и можем детерминированно смержить данные без конфликтов. Никаких векторных часов вручную — HLC встроен в Redis Cluster через CRDT.

Детали агрегатора

На стороне Reducer (у нас Flink) мы считаем: avg_shap[i] = sum_shap[i] / count[i], но только если временные метки всех worker-ов для этого фичи сходятся в пределах окна. Если расходятся — дропаем точку. Вот пример кода агрегатора:

// Псевдокод агрегации в Flink
select feature_name,
sum(shap_value) / count(*) as avg_shap,
max(timestamp) as ts
from shap_stream
group by feature_name, tumble(ts, interval '1' minute)
having count(distinct worker_id) = expected_worker_count;


Типичная ошибка: не проверять количество worker-ов в окне. Без этого вы мержите SHAP-ы от разных версий модели, и интерпретация становится бессмысленной.

Production-oriented результаты

Что получилось на практике. При 50k RPS задержка интерпретации — меньше 50 мс. Глобальная важность фич обновляется в реальном времени, без переобучения модели. Потеря в точности SHAP — около 0.05 единиц при 95% аппроксимации. Для прода это нормально. Регулятору плевать на сотые доли, ему важно — почему.

Главный trade-off, как всегда: точность против latency. Если вам нужны доли процента — готовьтесь к latency в секунды. Но для fraud detection или credit scoring 50 мс — это разница между блокировкой мошеннического перевода и его пропуском.

Код аггрегатора — простой, как грабли. Redis Streams, пара ключей, HLC в payload. Ничего сложного. Сложность в том, чтобы не сломать консистентность, когда модель обновляется на лету.

Вывод: В production ML для real-time интерпретации GBDT используйте распределённую SHAP-агрегацию с HLC и окном по timestamp, чтобы гарантировать causal consistency без потери в точности, приемлемой для регулятора.
👍1
Почему дорогая LLM дороже: экономика инференса, которую видно в твоём 5-часовом лимите

Каждый, кто работал с Claude или ChatGPT, сталкивался с лимитами и задавался вопросом, как один запрос может съесть 10% от лимита. Автор потратил неделю на изучение того, что отображают эти лимиты, и написал третью статью из серии «А как вообще работают современные LLM». После её прочтения вы узнаете, что скрыто за 5-часовым лимитом Claude и других LLM, из каких примитивов состоят лимиты и какая физика вычислений за этим стоит. Для тех, кто работает с моделями по API, материал особенно полезен.

Читать на Habr
3
Автоматическая настройка аугментации для GBDT через обратную связь от дрейфа остаточных ошибок в production

Стандартные детекторы дрейфа смотрят на распределение фич или предсказаний, но пропускают важный сигнал — изменение структуры остаточных ошибок. В production, когда ground truth поступает с задержкой, остатки могут указать на сдвиг раньше, чем классические методы, и это открывает путь к адаптивной аугментации без переобучения модели.

Почему residuals чувствительнее фич
При сдвиге распределения дерево часто попадает в локальную область, где предсказания застывают. Дрейф по фичам может оставаться незамеченным (например, из-за корреляции), но остатки на fresh батчах сразу показывают систематическое смещение. Используйте KS-тест между остатками текущего среза и эталонными (с валидации). Порог 0.1-0.2 на практике работает лучше, чем PSI для предсказаний — меньше ложных срабатываний.

Адаптивная аугментация через градиент по residuals
После детекции дрейфа запускается мини-оптимизация: параметры аугментации (например, масштаб гауссовского шума) подбираются под текущий срез данных. Формула проста — noise_scale = min(0.5, ks_stat * 2). Это не случайная augmentation, а инженерный трюк: дерево, обученное на зашумлённых точках в зоне дрейфа, быстрее выходит из локального минимума. В production для GBDT с init_model реакция занимает менее секунды.

residuals_current = y_true - y_pred
ks_stat, _ = ks_2samp(residuals_current, reference_residuals)
if ks_stat > 0.1:
noise_scale = min(0.5, ks_stat * 2)
X_aug = current_X + np.random.normal(0, noise_scale, current_X.shape)
model.fit(X_aug, y_true, init_model=model)


Типичная ошибка и trade-offs
Ошибка — использовать фиксированный порог KS для всех задач. В рекомендациях порог 0.05 даст ложные срабатывания на каждый чих, а в финскорингах 0.15 может пропустить критический сдвиг. Подбирайте порог по частоте дрейфа на исторических данных. Ещё важное: метод требует ground truth каждые 5-10 батчей. Если метки приходят реже, residuals теряют смысл. И помните — при концептуальном дрейфе change in P(Y|X) не виден в остатках, здесь нужна переоценка структуры модели.

Практический совет и production-пример
В онлайн-рекомендациях GBDT часто страдает от дрейфа в поведении пользователей после изменения UI. После детекции по residuals в течение 2 минут вы автоматически аугментируете последний батч и доучиваете модель — без даунтайма и пересоздания пайплайна. Это снижает latency для адаптации с часов до секунд. В IoT с быстрыми метками (например, сенсорные данные с обратной связью через 1 минуту) метод стабилизирует качество на 15-20% по RMSE по сравнению с обычным инференсом.

Вывод: Дрейф остаточных ошибок — недооценённый сигнал для адаптации GBDT в production, а аугментация под его статистику позволяет автоматически корректировать модель без full retraining, с trade-off между чувствительностью и затратами на ground truth.
Нелинейная интерполяция пропусков в timeseries: INRs для real-time serving с bounded latency

Классическая линейная интерполяция или сплайны ломаются, когда в данных нелинейные паттерны: резкие скачки, осцилляции, аномалии. В production ML это критично для real-time систем, где задержка на предсказание строго ограничена, а ошибки интерполяции приводят к сбоям в мониторинге или A/B тестах. Типичная ошибка — использовать глобальную модель на всю историю, теряя контроль над latency.

Почему INRs, а не просто сплайны
Implicit Neural Representations (INRs) отображают timestamp в значение сигнала через крошечную MLP. Это дает аппроксимацию любой гладкой функции, включая резкие переходы и шумные тренды. Для production ключевое — bounded latency. Если тренировать INR на всей истории, время обучения неконтролируемо растет. Решение — patch-based подход: делим историю на фиксированные окна, например, по 128 точек, и для каждого окна обучаем малую сеть за 30-50 итераций.

Реализация с нулевым оверхедом
Пример кода на PyTorch для окна:

class TinyINR(nn.Module):
def __init__(self, hidden_dim=64):
super().__init__()
self.net = nn.Sequential(
nn.Linear(1, hidden_dim), nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim), nn.ReLU(),
nn.Linear(hidden_dim, 1))

def forward(self, t):
return self.net(t)


Обучаем на наблюдаемых точках окна за 50 шагов, предсказываем пропуски. Предсказание на CPU занимает микросекунды. Ключевой trade-off: при фиксированном размере окна и числе итераций latency детерминировано, но нужно подбирать гиперпараметры под конкретный датасет.

Подводные камни и практические советы
- Overfitting: без weight decay сеть запоминает шум. Используйте L2-регуляризацию.
- Если target latency < 1 мс на CPU, siren-активации (синус) работают лучше ReLU — требуют меньше итераций для сходимости.
- Bounded latency держится только при фиксированном размере окна и числе итераций. Если окно растет, время уходит. В production явно задавайте максимальный размер окна и прерывайте обучение после превышения лимита.
- На реальных данных избегайте глобальных INR без Fourier features: они плохо аппроксимируют высокие частоты, что критично для HFT или медицинских сигналов.

Где применяю в production
- HFT: заполнение микро-пропусков между тиками, где линейная интерполяция дает артефакты (ошибка до 5% на осцилляциях).
- IoT: потеря пакетов от сенсоров — надо вставить значение без задержки, иначе срабатывает ложный alarm.
- Медицинские сигналы (ЭКГ): пропуск в пару отсчетов ломает детекцию аномалий (например, экстрасистол). INR восстанавливают форму волны с MSE < 0.001 при latency 200 мкс на CPU.

Вывод: INRs дают нелинейную гибкость интерполяции, но в real-time serving bounded latency достигается только через patch-based подход с фиксированным окном и числом итераций, иначе вы рискуете получить недетерминированное время ответа.
1
Обучение early-exit GBDT-ансамбля по метрике latency-aware inference с прунингом глубины на уровне пайплайна

Когда в продакшене модель должна дать ответ за 5 миллисекунд на edge-устройстве, а каждое дерево стандартного GBDT проходит полную глубину просто по инерции, это ломает SLA. Частая ошибка — использовать ванильный градиентный бустинг без контроля времени инференса, полагаясь только на accuracy. В real-time ML системах latency становится первой метрикой, а точность — второй.

Архитектура early-exit ансамбля
Идея простая: вместо того чтобы каждое дерево в ансамбле вычислять до максимальной глубины (например, 10 уровней), на каждом уровне вводим точку выхода — после 1, 2, 4, 8 листьев. На каждом exit ставим классификатор, который по состоянию текущей гипотезы решает: "хватит, ответ готов" или "копнём глубже". На практике такие ансамбли не собираются из коробки — CatBoost и XGBoost не предоставляют готового early-exit, так что реализация кастомная через кастомные хуки (например, на базе LightGBM).

Latency-aware loss и тренировка
Берём стандартный loss (например, logloss для классификации) и добавляем штраф за среднюю задержку — количество реально пройденных листьев на валидации, усреднённое по батчу. Пусть L_orig — базовый loss, L_lat — среднее число пройденных узлов. Тогда целевая метрика: L_total = L_orig + lambda * L_lat. lambda подбираем так, чтобы при росте глубины penalty сильно рос, но не убивал качество. Первый шаг — деревья обучаются с жёстким лимитом на число листьев (max_depth=2 или 4). Второй — на каждом уровне вешается линейный классификатор (например, логистическая регрессия), обученный предсказывать, стоит ли выходить. Третий — после обучения удаляем тупиковые ветви, которые никогда не активируются на валидации — это даёт дополнительный прунинг без потери качества.

Production пример и trade-offs
Пример из моего опыта: рекомендательный сервис с лимитом latency 4 мс на запрос. Изначально GBDT из 100 деревьев глубиной 8 работал 7 мс — не проходил SLA. После обучения early-exit ансамбля с max_depth=4 и lambda=0.1 latency упала до 2.4 мс (выигрыш 40%), а loss вырос на 0.8% (logloss 0.12 -> 0.121). Но ключевая деталь: на этапе инференса пришлось добавить адаптивный мониторинг — если на валидации доля выходов на первом уровне падает ниже 20%, это сигнал к переобучению, так как распределение могло сместиться. Типичная ошибка — не учитывать, что early-exit классификаторы чувствительны к дрифту: на новых данных модель может внезапно начать чаще уходить вглубь, ломая latency. Практический совет — оценивать не только среднюю задержку, но и перцентиль P99 по exit-уровням.

Вывод: Early-exit GBDT с latency-aware loss и прунингом глубины на уровне пайплайна даёт выигрыш в latency до 40% при потере качества менее 1%, но требует кастомной реализации и обязательного мониторинга стабильности exit-порогов на production данных.
2😁1
Мониторинг дрейфа в GBDT: контрастивное обучение на эмбеддингах листьев

Обычно дрейф в GBDT ловят через PSI на предсказаниях или KS-тест на фичах. Но эти методы не видят внутреннюю структуру ансамбля — сдвиг в распределении leaf-эмбеддингов часто проявляется раньше, чем упадет качество или поплывут предсказания. Типичная ошибка: доверять только output-метрикам и пропускать фундаментальные изменения в том, как модель "читает" данные.

Идея: контрастивное обучение на leaf-эмбеддингах
Когда объект проходит через GBDT, каждый decision tree возвращает индекс листа. Собирая бинарные индикаторы (попал/не попал) по всем деревьям, получаем разреженный эмбеддинг размерности num_trees * max_leaves. Контрастивное обучение (SimCLR или SupCon) проецирует эти векторы в dense латентное пространство. В проде средний эмбеддинг батча сравнивается с референсным — резкий рост расстояния сигнализирует о дрейфе. Пример кода:

def get_leaf_embeddings(model, X):
leaf_idx = model.predict(X, pred_leaf=True)
emb = ...
return emb

def contrastive_loss(emb_pos, emb_neg, margin=1.0):
pos_dist = torch.sum((emb_pos - emb_neg)**2, dim=1)
loss = torch.mean(F.relu(pos_dist - margin))
return loss

# На проде:
mean_emb = get_leaf_embeddings(model, batch_X).mean(0)
drift_score = torch.dist(mean_emb_ref, mean_emb_new)


Преимущества для production
Во-первых, раннее обнаружение: leaf-структура меняется до того, как target поплывет — это дает время на reaction, например, переобучение или fallback-модель. Во-вторых, подход работает с любым GBDT (LightGBM, XGBoost, CatBoost) без доступа к таргету — достаточно признакового пространства. В-третьих, не нужна разметка: контрастивная пара формируется из аугментированных эмбеддингов того же батча, что утилизирует дрейф без ручного контроля.

Инженерные trade-offs и типичная ошибка
На практике возникает компромисс: latency vs sensitivity. В real-time инференсе батч из N объектов может не успеть пройти через embedder внутри decision path — требуется асинхронный пайплайн (например, ставить мониторинг на отдельном sidecar-процессе). Ошибка — использовать универсальный порог для разных моделей. Подбирать чувствительность нужно эмпирически на production-данных через validation на исторических дрифтах. Также не забывайте про data quality: если в батче 50% missing values, leaf-индексы исказятся, и эмбеддинг укажет на дрейф там, где его нет.

Практический совет
Для production внедрения используйте window-based мониторинг: считайте средний leaf-эмбеддинг на скользящем окне (например, 1000 объектов) и сравнивайте с эталонным, полученным на train-данных. В качестве метрики дрейфа берите cosine distance между эмбеддингами — она менее чувствительна к масштабу, чем L2. Если distance превышает 95-й перцентиль на baseline — запускайте alert. Это позволит не дожидаться, пока target упадет на 2%.

Вывод: Leaf-эмбеддинги с контрастивным обучением дают интерпретируемый, быстрый и независимый от target способ детекции дрейфа в GBDT, но требуют аккуратного подбора порога и учета latency в real-time пайплайнах.
😁1
Mix Hub в свой VST3-плагин: анализ конфликтов между дорожками

Привет, Хабр! Меня зовут Артур Валиев. Я продолжаю делать свой VST3-плагин Mix Teacher AI. В прошлый раз я рассказывал про идею плагина: поставить его на дорожку, посмотреть уровни, пики, RMS, примерный LUFS, частотные зоны и получить простую подсказку человеческим языком. Но довольно быстро стало понятно, что анализировать только одну дорожку мало. Потому что в сведении часто проблема не в одной дорожке. Кик сам по себе нормальный. Бас сам по себе нормальный. Вокал сам по себе нормальный. Барабаны вроде тоже нормальные. А вместе всё почему-то не звучит. И вот тут начинается самая интересная часть: конфликты между дорожками.

Читать далее
3🔥1
Оптимизация калибровки GBDT в production: как адаптировать вероятности через мониторинг скользящих ошибок второго типа

Калибровка вероятностей моделей градиентного бустинга — это не разовая операция, а непрерывный процесс. На serving-данных распределения целевой переменной и признаков могут дрифтовать, и статичная калибровка, выполненная на валидации, теряет точность. Типичная ошибка — полагаться на среднюю log-loss или Brier score, не отслеживая динамику ошибок по классам.

Почему FN rate — а не accuracy или AUC
В задачах с дисбалансом классов (fraud detection, CVR, credit scoring) глобальные метрики скрывают ухудшение калибровки для редкого класса. FN rate — доля false negatives среди реальных положительных — чувствителен к дрифту, при котором модель начинает систематически недооценивать вероятность редкого события. Именно это поведение критически важно для бизнеса: пропущенный фрод или кредитный дефолт обходится дороже, чем ложное срабатывание.

Как организовать мониторинг и онлайн-коррекцию
Процесс выглядит так:
* На этапе обучения фиксируем эталонный FN rate на валидации при пороге 0.5.
* В serving каждые N инференсов (например, 10 тыс. событий) собираем буфер сырых предсказаний (raw logits) и реальных меток (с учетом лагов агрегации).
* На каждом окне вычисляем текущий FN rate.
* Если отклонение от эталона превышает порог δ (например, 0.05), запускаем адаптивную калибровку на буфере.

Реализация Platt scaling на лету
Лучший выбор — логистическая регрессия с L2-регуляризацией на буфере последних logits. Алгоритм:
* Хранится FIFO-буфер buffer_preds (raw scores) и buffer_labels.
* При достижении окна window_size=10000 старые элементы вытесняются.
* Функция check_drift(ref_fn_rate) вычисляет FN rate в окне:

buffer_preds, buffer_labels = [], []
window_size = 10000

def update(raw_pred, true_label):
buffer_preds.append(raw_pred)
buffer_labels.append(true_label)
if len(buffer_preds) > window_size:
buffer_preds.pop(0)
buffer_labels.pop(0)

def check_drift(ref_fn_rate):
fn = (np.array(buffer_preds) > 0.5) & (np.array(buffer_labels) == 0)
current_fn = fn.sum() / max((labels == 0).sum(), 1)
if abs(current_fn - ref_fn_rate) > 0.05:
calibrator.fit(buffer_preds, buffer_labels)


Инженерные trade-offs и предупреждения
* Isotonic regression на малом окне склонна к переобучению и нестабильна — Platt scaling с регуляризацией надежнее.
* Для отложенного таргета (рекомендации, конверсии) окно нужно синхронизировать по времени, а не по событиям, чтобы избежать смещения от старых меток.
* Корректируйте только post-hoc калибровку, не трогая веса модели и не нарушая бэкап-совместимость.
* Предупреждение: не используйте этот метод, если данные метятся с обратной связью с дрейфом меток — сначала требуется объединение по времени.

Когда это особенно полезно
* CVR, fraud detection, credit scoring — где дрифт паттернов частотен.
* Системы с регуляторными требованиями к точности вероятностей (например, Basel или IFRS 9).
* Как дешевая альтернатива ежедневному или еженедельному переобучению модели — метод позволяет жить между ретрайнингами, снижая cost на MLOps.

Альтернатива: полный retrain с обновленными данными — более затратно, но гарантирует воспроизводимость. Предложенный подход — компромисс между стабильностью и адаптивностью.

Вывод: Мониторинг скользящего FN rate на serving и онлайн-калибровка через Platt scaling — это низкозатратный способ держать вероятности модели GBDT честными без переписывания пайплайна, особенно при дрифте в редком классе.
🔥2
Инференс и Unity в контейнере: почему X11 стал главным узким местом

Казалось бы, что сложного: в YTsaurus есть GPU, джобы пробрасывают /dev/nvidia0. Закинул бинарник, запустил, получил видео. На практике — всё сломалось на этапе инициализации.

Проблема: Window System Integration

Unity и Unreal Engine используют WSI. Даже для offscreen-рендеринга им нужен X11-сервер. Без него библиотеки XCB/Xlib не создадут VkSurfaceKHR — поверхность для финального кадра.

Цепочка зависимостей выглядит так:
1. Vulkan ICD обращается к /dev/dri/renderD128 для аллокации буферов.
2. Движок рендерит кадр и передаёт dma-buf в X-сервер через DRI3.
3. Xorg должен быть запущен с DDX-драйвером nvidia_drv.so на том же GPU.
4. Только тогда драйвер импортирует dma-buf и выведет их на виртуальный монитор.

Что нужно пробросить помимо /dev/nvidia0

Для ML достаточно трёх устройств. Для графики список шире:

/dev/nvidiactl
/dev/nvidia0
/dev/nvidia-uvm
/dev/nvidia-modeset
/dev/dri/card0
/dev/dri/renderD128


Плюс в контейнере должны быть библиотеки NVIDIA, DDX-драйвер для Xorg и nvidia-smi для дебага.

Как дошли до стабильного запуска


Шаг 1. Docker с --gpus all и --privileged. vkcube работает — X11 внутри контейнера поднимается.
Шаг 2. Убрали --privileged. Userspace-библиотеки драйвера и DDX ставим вручную.
Шаг 3. Переехали в Porto. Экспортировали Docker-контейнер в Porto-слой. Porto поддерживает вложенные контейнеры — это критично для YTsaurus.
Шаг 4. «Ванильная» операция в YTsaurus. Драйверы накладываются отдельным слоем, их нельзя класть в образ. Ловили ошибки и понимали, чего не хватает.
Шаг 5. Unity с 3DGS-аватарами. После стабильного vkcube перешли к реальной практической задаче.

Типичные ошибки

- Запускать Xorg без DDX-драйвера nvidia_drv.so. X11 падает с "no screens found".
- Не прокидывать /dev/nvidia-modeset. Управление виртуальным дисплеем недоступно.
- Забыть про dbus. Unity требует работающую шину для инициализации.

Как починили в инфраструктуре

Когда пример собрали, стало ясно: без изменений на платформе не обойтись. Передали наработки коллегам из Yandex Infrastructure. Они за несколько часов добавили поддержку Xorg для GPU-хостов, через пару недель раскатили на ML-кластера.

Вывод: Графический рендеринг в контейнерах — это не только про проброс GPU. Это про X11, DRM, dma-buf и драйверы. Собери пазл один раз — и рендеринг становится обычной batch-задачей без ручных запусков.
👍1
Как измерять качество рекомбинации сплитов в GBDT при динамическом изменении числа деревьев в serving: три online-метрики для production

Когда в serving число деревьев меняется динамически — из-за A/B-тестов, калибровки композитных моделей или адаптации под latency — классические offline-метрики пасуют. Рекомбинация сплитов, где разные деревья используют одинаковые разбиения, становится скрытой точкой отказа, которую не видят ни AUC, ни logloss.

Gradient Overlap

Процент деревьев с совпадающими сплитами по признакам на последовательных итерациях. Если overlap выше 70%, рекомбинация стабильна. Ниже — шум или переобучение. Практический совет: используйте Gradient Overlap как триггер остановки добавления деревьев — при overlap менее 50% увеличение числа деревьев скорее всего ухудшит обобщение.

Split Importance Drift

Корреляция Спирмена топ-10 сплитов по gain при n и n+1 деревьях. Дрифт выше 0.3 — сигнал к корректировке hyperparams. Типичная ошибка: игнорировать этот дрифт и продолжать наращивать деревья, ухудшая как latency, так и качество.

Online-AUC на скользящем окне

AUC на окне из N=20 деревьев показывает, как рекомбинация влияет на качество при добавлении или удалении. Production-oriented пример: в fraud detection или real-time bidding каждое дерево критично — online-AUC выявляет момент, когда рекомбинация ломается, раньше, чем offline-метрики.

import numpy as np
from scipy.stats import spearmanr

class OnlineSplitTracker:
def __init__(self, window_size=20):
self.window_size = window_size
self.split_gains = []

def update(self, tree_splits, tree_gains):
self.split_gains.append(tree_gains)
if len(self.split_gains) > self.window_size:
current_top = self._get_top_features(-1)
prev_top = self._get_top_features(-2)
drift, _ = spearmanr(current_top, prev_top)
print(f"Split drift: {drift:.3f}")

def _get_top_features(self, idx):
agg = {}
for gains in self.split_gains[idx]:
for feat, gain in gains.items():
agg[feat] = agg.get(feat, 0) + gain
return sorted(agg.keys(), key=lambda x: agg[x], reverse=True)[:10]


Вывод: Online-метрики рекомбинации сплитов позволяют вовремя остановить рост числа деревьев и избежать degradation в serving, но требуют учёта overhead по памяти для градиентов и ограничены по глубине деревьев (более 10 — теряют точность).
Динамический выбор алгоритма ветвления GBDT на основе аппаратных счетчиков производительности

Когда production GBDT внезапно начинает тормозить на одинаковой нагрузке, корень часто не в модели, а в том, как она ветвится на конкретном железе. CPU последовательно долбит if-else с промахами в prefetch, а GPU простаивает из-за дивергенции варпов. Типичная ошибка — фиксировать стратегию ветвления (left-heavy, depth-first) без учета аппаратных счетчиков.

Проблема статического ветвления
Классические GBDT-библиотеки (XGBoost, LightGBM) используют фиксированный порядок обхода дерева. На CPU это приводит к cache misses при холодных данных или branch miss penalties при нерегулярных паттернах. На GPU — к warp divergence, когда разные треды в варпе идут по разным веткам. В одном production-кейсе с XGBoost инференс на CPU vs GPU давал разницу в 2x из-за структуры дерева, хотя сама модель была идентична.

PMC-управляемое ветвление на CPU
Решение — на лету переключать алгоритм ветвления, используя Performance Monitoring Counters (PMC). Следим за:
- INSTRUCTIONS RETIRED — нагрузка на ядро;
- BRANCH MISS PREDICT — если >5%, переходим на предикаты (вычисляем обе ветки, выбираем результат);
- CACHE MISS (L1/L2) — при высоких значениях включаем prefetch и column-wise layout.

Пример: если cache miss >10% и branch miss predict <3%, оставляем row-wise traversal. Иначе — переключаемся на column-wise с prefetch-инструкциями через __builtin_prefetch. Мониторинг через libpfc или perf_event_open добавляет 1-3% overhead, но стабильно выигрывается 10-25% latency на стриминге.

GPU адаптация: occupancy и дивергенция
На GPU ключевой параметр — warp divergence. Порог — 30%: при превышении реорганизуем дерево в SIMD-дружественную структуру. Листья одного уровня упаковываются в плоский массив, а ветвление заменяется на gather/scatter из __shfl_sync. Работает через NVML или cupti для чтения счетчиков. Выигрыш 15-40% времени при стриминге, но портировать на ARM сложно (PMC там другие).

Trade-offs и ML-управление
Static-библиотеки не учитывают реальную нагрузку. Использование PMC добавляет overhead, но окупается на горячих путях. В production я добавил маленький регрессор (20 признаков из PMC) для предсказания оптимальной стратегии. Overhead тот же 1-3%, но точность подбора выше — снижает branch miss еще на 5%. Минус: портирование между x86 и ARM требует переписывать парсеры счетчиков.

Вывод:
Динамический выбор алгоритма ветвления GBDT на основе аппаратных счетчиков позволяет выжать 15-40% производительности на CPU и GPU, но требует учета overhead мониторинга и архитектурных различий.
👍21
Многорукие бандиты с контекстом в микросервисном ранжировании: как не утонуть в сервисах

Цепочка ранжирования из нескольких сервисов — это не просто конвейер, а распределенная задача обучения с подкреплением. Типичная ошибка — внедрять бандита на каждый узел изолированно, не учитывая сквозной эффект решений.

Проблема фрагментации контекста
Каждый микросервис видит только свой контекст: сервис кандидатов знает время суток, сервис фич — нагрузку на вычисления, модель — идентификатор эксперимента, пост-процессинг — бизнес-правила. Если каждый из них принимает жадное или исследовательское решение независимо, цепочка накапливает шум. Контекстуальный бандит на финальном шаге бесполезен, если предыдущие узлы сгенерировали нерепрезентативные состояния.

Архитектура с общей обратной связью
Рабочее решение — цепочка контекстуальных бандитов, где каждый узел получает два сигнала: глобальное вознаграждение (например, CTR) и локальное (latency, ошибки). Ключевой трюк — смешивание их с фиксированными весами:
reward = 0.7 * глобальный CTR + 0.3 * локальный latency

Пример из продакшена:
- Сервис A выбирает источники кандидатов. Контекст: время суток, тип устройства.
- Сервис B решает, делать полный пересчет фич или инкрементальный — это влияет на latency и свежесть признаков.
- Сервис C выбирает версию модели: explore или exploit.
- Сервис D применяет бусты/дебусты в пост-процессинге.

Все бандиты работают с одним session_id (через OpenTelemetry) для трекинга сквозных цепочек. Вознаграждение выплачивается после завершения всего запроса — используем delayed rewards и policy gradient, а не Q-learning, чтобы избежать переобучения на локальных паттернах.

Практический совет и типичная ошибка
Реализация элементарна: класс с альфой на скользящее среднее и двумя полями под reward. Но главное — не добавлять бандита на узел, который не генерирует измеримого изменения в глобальной метрике. Иначе получите фоновый шум, который только увеличит variance A/B теста.

В нашем эксперименте на production: рост CTR +15% при сохранении latency. Сервисы остались автономными, но скоординированными. Добавление нового узла в цепочку — просто протянуть session_id и определить свой reward.

Вывод: Цепочка контекстуальных бандитов с общей обратной связью позволяет координировать микросервисы ранжирования без нарушения их изоляции, но требует явного смешивания глобальных и локальных метрик для стабильного обучения.
2👍2
Вечная тема: все хотят "говорить свободно", но в реальном диалоге впадают в ступор через 10 секунд. А продаваны курсов с радостью впарят тебе очередного "AI-тьютора", который отвечает через раз и с пятисекундной задержкой.

Парень на Хабре расписал свою Aisha — голосового репетитора английского. Это не просто обертка над GPT. Он реально запарился над архитектурой: голосовой ввод, стриминг, обработка ошибок в моменте, чтобы диалог не превращался в пытку лагами.

Суть проблемы, которую он решил: если тупо гонять аудио туда-сюда через API, живой диалог сдохнет на старте. Надо обрабатывать поток, резать payload и не проваливаться в задержки под контекст. Это hard mode, а не игрушки.

Новичкам: не пытайтесь сразу клепать таких монстров. Сначала научитесь гонять через whisper одну фразу и получать осмысленный ответ. Это база.

Для тех, кто шарит: статья — норм разбор того, как резать лаги в реалтайм-диалоге. Полезно, если пилите своего бойца.


Короче, идем читать, а не сохраняем в закладки до лучших времен. Пора разобраться, как это работает, а не просто пользоваться сырым говном от стартапов.

Тута ссылка на разбор архитектуры, payload и модели. Поехали.

👉 Data Science | Machinelearning [ru]
Please open Telegram to view this post
VIEW IN TELEGRAM
Оптимизация распределённого жадного поиска сплитов в GBDT: вычислительное бюджетирование

Узкое место любого GBDT под нагрузкой — жадный поиск порогов для сплитов. Когда фич под тысячу, а строк миллионы, строить гистограмму для каждой на каждом узле — взрывной рост стоимости. В распределённой среде это буквально трата времени на заведомо мусорные сплиты.

Почему это важно
В production ML сценариях с высокой нагрузкой — мониторинг потоковых данных, обучение на Spark, CatBoost в real-time — каждая микросекунда на сплит умножается на число фич и глубину дерева. Типичная ошибка: строить плотные гистограммы для всех фич, даже для разреженных или с низким градиентом, что перегружает сеть и CPU.

Идея вычислительного бюджетирования
Вводим явный бюджет — лимит на количество гистограмм на узел. Фичи ранжируются по эвристике: сумма абсолютных градиентов или плотность ненулевых значений. Для разреженных фич это особенно спасает — они отсекаются до передачи данных между узлами.

Пример псевдокода для распределённой среды:
def allocate_budget(feature_stats, budget=100):
scores = {feat: len(nonzero_grads[feat]) for feat in feature_stats}
sorted_feats = sorted(scores, key=scores.get, reverse=True)
return sorted_feats[:budget]

for tree_level in range(max_depth):
budget_features = allocate_budget(current_stats)
parallel_build_histograms(budget_features)


Production-кейс и trade-offs
Из опыта внедрения в streaming-обучении: фаза сплита ускоряется на 40-60% при том же качестве. Сетевого трафика меньше — передаём гистограммы только по отобранным фичам. Однако есть нюанс: если бюджет задан без учёта gain от корня, можно отсечь хорошие сплиты в глубине. Слишком агрессивное бюджетирование (меньше 5% фич) — потеря информативности, особенно на сложных узлах с низкой чистотой.

Практический совет
Используйте адаптивный бюджет под сложность узла: для корневых узлов с большим gain можно увеличить бюджет, для глубоких — уменьшить. Это даёт баланс между скоростью и качеством, особенно в streaming-сценариях с ограничением по latency.

Типичная ошибка
Фиксированный бюджет на все уровни дерева без учёта распределения градиентов. На ранних узлах это может отсечь потенциально сильные фичи, которые станут доминантными глубже. Лучше динамически пересчитывать budget_features на каждой глубине.

Вывод: Вычислительное бюджетирование — простой и эффективный способ снизить latency в GBDT под нагрузкой, но требует адаптивного управления, чтобы не потерять качество из-за преждевременного отсечения фич.
👍1🔥1
Koda Pro решила поджарить Claude-sonnet

Новая версия Koda Pro на GLM 5.2 выстрелила в SWE-bench Verified. Старая версия показывала 59% — норм, но без фанатизма. Новая — 77.4%. Это на 10% выше старичка и почти догоняет Claude-sonnet-5 с его 78.8%.

Что сделали: перелопатили агентные инструменты, натянули системный промпт и докрутили обучение. Результат — дерзкий скачок.

Новичкам: не тупите в бездну абстрактных курсов. Откройте SWE-bench, посмотрите, как такие штуки тестируют код. Это сразу покажет, что вы умеете, а что нет.

Опытным: если ваши pet-projects не проходят даже половину таких бенчмарков, вы что-то делаете не так. Время подтянуть инструментарий.

Читать статью.

👉 Data Science | Machinelearning [ru]
Please open Telegram to view this post
VIEW IN TELEGRAM
1👎1
Сравнение методов дообучения LLM: главный показатель качества

Исследователи лаборатории научных исследований Т-Технологий представили на ICML 2026 единый подход к сравнению методов дообучения больших языковых моделей, которые учатся на заранее подготовленных парах ответов. Разные методы часто сравниваются в разных условиях: этапы обучения, настройки и способы оценки ответов. Чтобы привести методы к сопоставимым условиям, исследователи разложили одноэтапные подходы (ORPO, ASFT) на два шага: supervised fine-tuning и отдельное выравнивание на парах ответов, а также ввели параметр β, регулирующий силу дообучения на человеческих предпочтениях. Исследователи пришли к выводу: качество сильнее всего зависит не от алгоритма, а от того, сравнивает ли модель ответы напрямую или оценивает их по отдельности.

«Один из главных выводов в том, что модели лучше учатся выбирать ответ, когда напрямую сравнивают два варианта между собой, а не оценивают каждый по отдельности»,

— рассказал руководитель лаборатории Даниил Гаврилов.

Так, попарные методы (pairwise) чаще дают результаты лучше, чем поточечные (pointwise), особенно на задачах средней сложности. В работе использовались Llama 3.2 3B, Llama 3.1 8B, Mistral 7B, Qwen 2.5 7B и 14B, обученные на данных Reddit TL;DR, UltraChat и UltraFeedback.

👉 Data Science | Machinelearning [ru]
Please open Telegram to view this post
VIEW IN TELEGRAM
5
Слышал новость? Роботы уже бегают быстрее людей, а ходить ровно всё ещё не могут.

За 2026 год двуногие машины пробежали полумарафон быстрее человеческого рекорда, CEO публично резал роботу ногу ножницами, а на презентации за миллионы долларов механизм тупо завалился на ровном месте. Сюрприз: походка теперь делается за 20 минут на одной видеокарте — это дешёвая инженерия. Но "живой" робот упирается в ватты, переполняющийся контекст и отсутствие непрерывного обучения.

Простыми словами: база есть, а мозгов нет. Всё упирается не в железо, а в то, как оно учится не падать.

Кому вкатываться — сначала разберись, как работает контекст и continuous learning. Без этого любая походка превратится в цирк на собеседовании.

Читать разбор

👉 Data Science | Machinelearning [ru]
Please open Telegram to view this post
VIEW IN TELEGRAM
4