
Автор: Мацера Максим, главный исследователь данных в Страховом Доме ВСК
Представьте: ваша модель машинного обучения, блестяще прошедшая все A/B-тесты, через полгода начинает тихо, но уверенно деградировать. Предсказания становятся менее точными, бизнес-метрики ползут вниз, а вы не понимаете, почему. Знакомо? Скорее всего, вы столкнулись с Data Drift — смещением данных.
Data Drift — это изменение распределения входных данных модели с течением времени. Мир не статичен: меняются привычки клиентов, экономическая ситуация, законодательство. Модель, обученная на «старых» данных, оказывается не готова к «новым». В страховой сфере, где риски и деньги напрямую связаны, это особенно критично. Ошибка в оценке убыточности полиса может стоить компании миллионов.
В этой статье я на реальном примере разберу, как:
Диагностировать дрифт с помощью метрики PSI.
Выявлять проблемные и «протекающие» признаки.
Перестраивать модель, чтобы она оставалась точной в будущем.
Оценивать эффект от проведенной работы не на валидации, а на реальных «свежих» данных.
Мы будем использовать открытые данные по автострахованию и библиотеки OutboxML и SHAP. Весь код доступен в репозитории на GitHub.
Данные и условия эксперимента
Набор данных: Открытый датасет от Университета Валенсии, содержащий анонимизированную информацию о ~105k страховых полисов за 3 года (2015-2018). В данных 30 признаков: от характеристик автомобиля и водителя до истории страховых случаев.
Ключевое условие для честной оценки: Мы строго разделили данные по времени.
Обучающая выборка: Полисы с 2015 по 2017 год (~52k записей).
Тестовая выборка: Полисы за 2018 год (~11k записей).
Целевая переменная: Общая сумма выплат по полису (TARGET). Задача — регрессия.
Модель: CatBoostRegressor с фиксированными гиперпараметрами (глубина деревьев = 6, learning_rate = 0.03 и т.д.) для чистоты эксперимента.
# Инициализация DataDrift с настройками
datadrift = DataDrift(
full_calc=True, # Включить полный расчет с дополнительными метриками
columns_to_exclude=[], # Столбцы для исключения из расчета
n_bins=50, # Количество бинов для численных признаков
dif_len_string=100 # Максимальная длина строки для различий в категориальных признаках
)
# Расчет датадрифта
drift_report = datadrift.report(
train_data=df_train[0],
test_data=df_test[0],
base_data=df_train[0],
control_data=df_test[0]
Отчёт, возвращаемый библиотекой:
|
PSI |
KL |
JS |
TYPE |
NaN_train |
NaN_test |
uniq_train |
uniq_test |
mode_train |
mode_test |
mean_train |
mean_test |
Seniority |
3,29 |
44,37 |
1,82 |
NUMERICAL |
0,00 |
0,00 |
38,00 |
37,00 |
2,00 |
1,00 |
4,45 |
3,15 |
Premium |
1,54 |
4410,80 |
111,40 |
NUMERICAL |
0,00 |
0,00 |
29012,00 |
9651,00 |
41,13 |
41,13 |
321,42 |
299,94 |
Lapse |
0,37 |
1,27 |
0,10 |
NUMERICAL |
0,00 |
0,00 |
4,00 |
4,00 |
0,00 |
0,00 |
0,24 |
0,08 |
Cylinder_capacity |
0,24 |
22589,71 |
547,35 |
NUMERICAL |
0,00 |
0,00 |
596,00 |
455,00 |
1896,00 |
1896,00 |
1579,34 |
1598,21 |
Driving_experience |
0,05 |
241,78 |
8,14 |
NUMERICAL |
0,00 |
0,00 |
570,00 |
562,00 |
10,30 |
0,00 |
23,48 |
22,77 |
Max_policies |
0,03 |
11,08 |
0,62 |
NUMERICAL |
0,00 |
0,00 |
10,00 |
10,00 |
1,00 |
1,00 |
1,83 |
1,99 |
Year_matriculation |
0,03 |
19878,47 |
694,51 |
NUMERICAL |
0,00 |
0,00 |
58,00 |
57,00 |
2005,00 |
2005,00 |
2005,36 |
2005,77 |
Type_fuel |
0,00 |
NaN |
NaN |
NUMERICAL |
0,00 |
0,00 |
3,00 |
3,00 |
6,39 |
6,39 |
-0,20 |
0,04 |
Методология: Две модели — два подхода
Чтобы показать влияние дрифта, мы сравнили две стратегии:
Первая: Модель «С Дрифтом» (Drift): Используются все признаки, кроме откровенных «утечек» данных (data leakage). Утечки — это признаки, которые напрямую или косвенно содержат информацию о целевой переменной (сумме выплат) и которых не будет в момент предсказания нового полиса. Мы исключили:
Cost_claims_year_new, Cost_claims_year, Claim_to_premium_ratio, R_Claims_history, N_claims_year, N_claims_history
ID (идентификатор)
Вторая: Модель «Без Дрифта» (NoDrift): Сначала мы провели детектирование дрифтующих признаков, а затем заново выполнили feature selection, полностью исключив их. К утечкам добавились признаки, показавшие значительное смещение:
Seniority (стаж)
Premium (премия)
Lapse (отток)
Вопрос на засыпку: Почему Premium (премия) может быть дрифтующим признаком? Ведь это базовый параметр полиса!
Ответ кроется в бизнес-процессах. Если компания за год поменяла тарифную политику или на рынке появились агрессивные конкуренты, вынудившие снизить цены, распределение премий на новых данных будет кардинально отличаться от обучающей выборки. Модель, завязанная на этот признак, даст сбой.
Диагностика: Ищем дрифт с помощью PSI-метрики
Population Stability Index (PSI) — наш главный инструмент. Эта метрика отвечает на вопрос: «Насколько сильно изменилось распределение признака в новой выборке по сравнению с обучающей?».
Как считается PSI (простыми словами):
Разбиваем значения признака на интервалы (бины) по обучающей выборке.
Смотрим, какой процент наблюдений попадает в каждый бин в обучающей (базовая популяция) и тестовой (текущая популяция) выборках.
Для каждого бина считаем:
(Текущий % - Базовый %) * ln(Текущий % / Базовый %)
.PSI — сумма этих значений по всем бинам.
Эмпирическое правило:
PSI < 0.1: Изменения незначительны.
0.1 < PSI < 0.25: Небольшое смещение, требуется мониторинг.
PSI > 0.25: Значительный дрифт! Признак нужно исследовать.
Мы использовали модуль DataDrift
из библиотеки OutboxML
. Вот что показал анализ:
Признак |
PSI |
TYPE |
mean_train |
mean_test |
Seniority |
3.29 |
NUMERICAL |
4.45 |
3.15 |
Premium |
1.54 |
NUMERICAL |
321.42 |
299.94 |
Lapse |
0.37 |
NUMERICAL |
0.24 |
0.08 |
Cylinder_capacity |
0.24 |
NUMERICAL |
1579.34 |
1598.21 |
Driving_experience |
0.05 |
NUMERICAL |
23.48 |
22.77 |
Вывод очевиден: Seniority, Premium и Lapse имеют критически высокий PSI. Распределения этих признаков в 2018 году сильно ушли от образца 2015-2017 гг. Именно они были исключены из второй модели.
Что внутри? SHAP-анализ до и после
Интересно посмотреть, как изменилась логика модели после исключения дрифтующих признаков. Для этого мы использовали SHAP (SHapley Additive exPlanations). Графики показывают ранжирование признаков по их важности для модели.

В модели «С Дрифтом»: Ведущую роль в прогнозе играли исключенные нами Premium и Seniority. Модель сильно на них завязывалась.
В модели «Без Дрифта»: После их удаления, модель перераспределила важность на другие, более стабильные признаки, такие как Year_matriculation (год выпуска авто), Cylinder_capacity (объем двигателя) и Driving_experience (стаж вождения).
Вывод: Модель успешно переучилась, найдя новые, более устойчивые во времени взаимосвязи для прогноза.
Цифры не врут: Сравнение метрик
Вот ради чего все затевалось. Давайте сравним, как модели справились с данными 2018 года. Модель, идеальная на случайном сплите, оказалась нежизнеспособной при столкновении с реальностью. Объясняю:
На обучающей выборке (2015-2017):
Метрика |
Модель «С Дрифтом» |
Модель «Без Дрифта» |
MAE |
137.37 |
134.56 |
RMSE |
531.07 |
512.63 |
R² |
0.820 |
0.832 |
Разница минимальна. На исторических данных обе модели показывают отличную и почти идентичную точность.
На тестовой выборке (2018 год, реалии жизни):
Метрика |
Модель «С Дрифтом» |
Модель «Без Дрифта» |
Улучшение |
MAE |
141.25 |
129.20 |
~8.5% |
RMSE |
614.33 |
480.86 |
~21.7% |
R² |
-1.115 |
-0.296 |
- |
А вот здесь — прорыв. Модель, избавленная от дрифтующих признаков:
На 8.5% точнее по средней абсолютной ошибке (MAE).
На 21.7% точнее по корню из среднеквадратичной ошибки (RMSE), что особенно важно, так как эта метрика сильнее штрафует за крупные ошибки.
Отрицательный R² говорит о том, что обе модели справляются хуже, чем просто предсказание среднего значения по всем полисам, но модель «Без Дрифта» делает это катастрофически лучше.
Data Drift — это не дефект модели, а вызов системе мониторинга
Результаты нашего эксперимента однозначно подтверждают то, о чем все чаще пишут в исследованиях ML-инженеры ведущих tech-компаний: смещение данных — одна из главных причин «старения» моделей в продакшене. Это не теоретическая угроза, а ежедневная реальность.
От выводов к практике: выстраиваем надежный ML-мониторинг
Выявление дрифта в нашем эксперименте — это не разовая акция, а прототип процесса, который должен стать неотъемлемой частью MLOps.
1. Приоритизация и постоянный мониторинг
Начните с мониторинга топ-10-15 наиболее важных фичей, определенных через SHAP. Именно их смещение оказывает наибольшее влияние на предсказания. Расчет PSI для этого узкого набора признаков можно легко автоматизировать.
2. Определение порогов срабатывания
Наш эксперимент предлагает использовать пороги для PSI:
PSI < 0.1: Изменения незначительны.
0.1 ≤ PSI < 0.25: Умеренный дрифт. Необходим анализ.
PSI ≥ 0.25: Значительный дрифт. Требует немедленного расследования.
3. Интеграция анализа в процесс переобучения
Любое плановое переобучение модели должно начинаться с анализа стабильности данных. Сравните распределения признаков в новом тренировочном наборе с предыдущим. Если ключевые признаки показывают высокий PSI, это красный флаг. Возможно, пришло время не просто переобучить модель, а провести полный рефакторинг признакового пространства, как мы это сделали в модели «NoDrift».
Опыт борьбы со смещением данных уникален для каждой доменной области. Расскажите в комментариях, сталкивались ли вы с подобными проблемами в своих проектах? Какие метрики, помимо PSI, и инструменты оказались наиболее полезными в вашей практике?
ChePeter
Причина дрифта в том, что методы исследования субъектов и объектов существенно отличаются.
Это электроны одинаковые, что у Резерфорда, что в розетке.
А люди разные и нет ещё такой математики, что может научить их предсказывать.
Именно по этой причине все нынешние AI со временем будут врать. А у того, что они врут сразу после обучения, другая причина.
Как то стало существенно чувстоваться, что в школе и ВУЗ перестали нормально преподавать математику