Training-serving skew

Модель показала 0.95 на валидации, а в проде ведёт себя как монетка — почти всегда виноват skew.

Training-serving skew — расхождение между тем, как данные/признаки готовятся при обучении и при инференсе, из-за которого модель в проде работает хуже, чем на валидации.

Почему это коварно

Skew не вызывает ошибок и не падает в логах. Модель просто тихо предсказывает хуже, чем должна. Это одна из самых частых и трудноуловимых проблем продакшн-ML, потому что код обучения и код инференса обычно пишут разные люди в разное время.

Три источника skew

  • Разная логика признаков. При обучении «возраст аккаунта» считали в днях через SQL, а в проде — в месяцах на Python. Модель получает не то, что ожидала.
  • Разная предобработка. При обучении нормализовали по среднему трейна, а в проде забыли применить тот же scaler — или применили с другими константами.
  • Разные данные. При обучении пропуски заполняли медианой, а в проде они приходят как null и ломают расчёт.

Демонстрация: что делает забытая нормализация

Смоделируем skew: модель ожидает нормализованный признак (вычесть среднее, поделить на std), но в проде ей дали сырой. Посмотрим, как искажается результат простого линейного решающего правила.

import statistics

train = [10, 12, 11, 13, 9, 10, 14, 8]
mu = statistics.mean(train)
sd = statistics.pstdev(train)

def norm(x):
    return (x - mu) / sd

# модель обучена на нормализованных значениях: решает "1", если z > 0.5
def predict(z):
    return 1 if z > 0.5 else 0

x = 13  # пример из прода
correct = predict(norm(x))      # как задумано
skewed = predict(x)             # skew: подали сырое значение
print(f"mu={mu:.2f} sd={sd:.2f} norm(x)={norm(x):.2f}")
print(f"Правильно (с нормализацией): {correct}")
print(f"Со skew (без нормализации): {skewed}")

Вывод:

mu=10.88 sd=1.90 norm(x)=1.12
Правильно (с нормализацией): 1
Со skew (без нормализации): 1

Здесь предсказание совпало случайно, но norm(x)=1.12 против сырого 13 — модель видела бы радикально другой масштаб; на большинстве входов решения разойдутся. Именно так skew портит качество, не вызывая ошибок.

Как бороться

  • Единый код признаков. Один и тот же модуль трансформаций вызывается и в обучении, и в инференсе. Лучший вариант — feature store или общая библиотека.
  • Сериализовать препроцессинг вместе с моделью. Pipeline (scaler + модель) пакуется как единый артефакт, чтобы нельзя было «забыть» шаг.
  • Skew-детектор. Логировать распределения признаков на входе модели в проде и сравнивать с обучающими: если разъехались — тревога.

Как работает под капотом

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

Частые ошибки

  • Переписывать препроцессинг в проде «по памяти». Любое расхождение константы рождает skew.
  • Не логировать входные признаки прода. Без этого skew невозможно обнаружить.
  • Считать, что хорошая офлайн-метрика гарантирует прод. Skew рушит именно это допущение.

Итог

  • Training-serving skew — тихое расхождение подготовки данных между обучением и инференсом, бьющее по качеству без ошибок.
  • Главное лекарство — единый код признаков и сериализация препроцессинга вместе с моделью.
  • Логирование входных распределений в проде позволяет обнаружить skew статистически.
Проверьте себя
1. Почему training-serving skew особенно опасен?
AОн сразу роняет сервер с ошибкой
BОн не вызывает ошибок — модель просто тихо предсказывает хуже
CОн виден в каждом логе
DОн влияет только на обучение
2. Какой приём надёжнее всего устраняет skew препроцессинга?
AПереписать препроцессинг в проде заново
BСериализовать препроцессинг вместе с моделью как единый pipeline
CУбрать нормализацию совсем
DИгнорировать проблему
3. Как обнаружить skew в проде?
AПерезагрузить сервер
BЛогировать распределения входных признаков и сравнивать с обучающими
CУменьшить размер модели
DУвеличить learning rate