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 статистически.