Если говорить интуитивно, feature drift означает, что модель смотрит на другой набор входных состояний, чем тот, на котором училась. Например, в антифроде выросла доля мобильного трафика, в рекомендательной системе пришла новая аудитория, в скоринге сместились доходы и длительности заявок. Concept drift — это другой случай. Признаки могут выглядеть очень похоже, но сама зависимость между ними и целевой переменной уже изменилась. То, что раньше было хорошим сигналом конверсии, риска или оттока, больше не работает так же.
Эту тему полезно рассматривать не как отдельный alert в monitoring, а как часть зрелой production-работы с моделью. Тогда drift перестает быть абстрактным “модель стала хуже” и начинает читаться как конкретный сигнал о том, что именно изменилось во входных данных или в самой зависимости между признаками и target.
Это различие важно не только теоретически. Оно определяет, что именно мы будем делать дальше. При feature drift иногда достаточно восстановить источник данных, обновить preprocessing или включить новые окна агрегации. При concept drift этого почти всегда недостаточно: меняется сама задача, и модель уже оптимизирует устаревшую картину мира.
Почему одна и та же деградация качества может иметь две разные причины
Полезно мыслить о любой ML-системе как о связке из двух слоев. Первый слой — распределение входных признаков. Это вопрос о том, какие объекты вообще приходят в систему. Второй слой — зависимость между объектом и ответом. Это вопрос о том, как устроена реальность, которую мы пытаемся предсказывать. В production-контуре здесь быстро встаёт и второй инженерный вопрос: как меняется latency в ML API, когда модель и данные начинают вести себя иначе, чем на обучении.
(P(X,Y)=P(Y \mid X)\cdot P(X))
Раздел математики: теория вероятностей.
Что означает каждый символ:
(X) — вектор признаков объекта.
(Y) — целевая переменная.
(P(X,Y)) — совместное распределение признаков и ответа.
(P(X)) — распределение входных признаков.
P(Y \mid X) — условная вероятность target при фиксированных признаках.
Формула появляется из базового правила разложения совместной вероятности на вероятность входа и условную вероятность ответа.
Численный пример: пусть (X) означает сегмент “новый пользователь после рекламной кампании”, и доля такого сегмента равна (P(X)=0.30). Если вероятность целевого действия внутри этого сегмента равна P(Y=1 \mid X)=0.20, то совместная вероятность такого объекта и положительного исхода равна (0.30 \cdot 0.20 = 0.06). Если меняется (P(X)), это feature drift. Если меняется P(Y \mid X), это concept drift.
Эта запись важна не ради формальности. Она показывает, что у продовой деградации действительно два разных источника. Иногда изменилась аудитория, поток заявок, сезонность, канал или поведение пользователей на входе. Иногда изменился сам механизм, по которому эти признаки начинают указывать на target.
Что такое feature drift на человеческом языке
Feature drift — это ситуация, в которой статистика признаков в production перестает быть похожей на train. Геометрически можно представить это так: облако точек в пространстве признаков сдвинулось. Оно могло стать шире, уже, плотнее в одном сегменте или разреженнее в другом. Но пока мы говорим именно о входах, а не о правильных ответах.
Представим задачу предсказания оттока. На обучении среднее число сессий в неделю было около 4, а в production после запуска нового onboarding стало около 8. Даже если сама связь между числом сессий и оттоком не изменилась, модель уже получает входы из другого диапазона. Это может исказить вероятности, confidence и работу downstream-правил.
Feature drift бывает не только у числовых признаков. Он встречается и в категориальных полях: появляются новые тарифы, каналы, города, устройства, статусы. Иногда drift вызван бизнесом, а иногда — тихой инженерной поломкой. Например, поле стало записываться в другой шкале, единицы измерения поменялись, часть значений стала заполняться дефолтом.
Именно здесь очень помогает отдельный разбор data quality: он хорошо показывает, почему drift может быть не только свойством реального мира, но и симптомом того, что сама система сбора данных начала работать иначе.
P_{\mathrm{train}}(X_j) \neq P_{\mathrm{prod}}(X_j)
Раздел математики: математическая статистика.
Что означает каждый символ:
(X_j) — отдельный признак с индексом (j).
P_{\mathrm{train}}(X_j) — распределение этого признака на обучающих данных.
P_{\mathrm{prod}}(X_j) — распределение того же признака в production.
Неравенство означает, что статистическая форма признака изменилась: по среднему, дисперсии, долям категорий или хвостам распределения.
Численный пример: если на train доля клиентов с признаком X_j=\text{mobile} была (0.40), а в production стала (0.68), то распределение признака явно сдвинулось. Модель видит другой мир, даже если сама функция ответа еще не успела измениться.
В production feature drift обычно диагностируют раньше, чем concept drift, потому что для него не нужны метки. Мы можем сравнивать признаки train и prod почти сразу. Именно поэтому мониторинг входных распределений считается leading indicator для деградации модели.
А чтобы такие сигналы вообще были интерпретируемыми, полезно понимать и feature lineage: когда происхождение признака прозрачно, заметно легче отделить реальный сдвиг в поведении пользователей от поломки конкретного вычислительного пути.
Что такое concept drift и почему он опаснее
Concept drift возникает тогда, когда меняется сама зависимость между признаками и target. Это более глубокая проблема. Признаки могут выглядеть почти как раньше, но предиктивный смысл этих признаков уже стал другим. В геометрическом смысле облако точек может остаться на месте, а вот разделяющая поверхность между классами уже повернулась, сдвинулась или вообще изменила форму.
Классический пример — кредитный скоринг в период резкой макроэкономической перестройки. До изменения рынка высокий доход, короткий стаж работы и отсутствие просрочек могли вести к одной вероятности дефолта. После изменения внешней среды та же комбинация признаков может давать совсем другой риск. Признаки почти не изменились, но их связь с target уже новая.
(P_{\mathrm{train}}(Y \mid X) \neq P_{\mathrm{prod}}(Y \mid X))
Раздел математики: теория вероятностей и статистическое моделирование.
Что означает каждый символ:
(Y) — целевая переменная.
(X) — вектор признаков.
(P_{\mathrm{train}}(Y \mid X)) — условная вероятность target при фиксированных признаках на train.
(P_{\mathrm{prod}}(Y \mid X)) — та же зависимость в production.
Формула означает, что для одного и того же объекта правильный ответ или его вероятность уже меняются по новой логике.
Численный пример: на train для пользователей с (X), равным “5 сессий за неделю и 2 покупки за месяц”, вероятность оттока могла быть (0.18). После смены подписочной модели для того же профиля пользователей эта вероятность стала (0.41). Это уже не feature drift, а изменение самой зависимости между признаками и целевой переменной.
Именно concept drift чаще всего отвечает за ощущение, что “модель вроде видит знакомые данные, но принимает неправильные решения”. Это связано с тем, что сама задача перестала быть той же самой, даже если название таргета осталось прежним.
Почему feature drift и concept drift нельзя лечить одинаково
Если drift обнаружен во входах, сначала нужно задать инженерный вопрос: это реальное изменение потока или ошибка данных? Может быть, новый сегмент пришел в продукт естественно. А может быть, витрина стала записывать сумму в копейках вместо рублей. В первом случае надо адаптировать систему к новому миру. Во втором — срочно чинить pipeline.
С concept drift логика другая. Здесь “почистить данные” недостаточно. Если поменялась связь между признаками и целевой переменной, значит, модель выучила уже не тот механизм. Иногда помогает быстрый retraining. Иногда этого недостаточно, и приходится пересматривать набор признаков, горизонт прогнозирования, целевую метку и даже саму business постановку задачи.
Поэтому зрелый monitoring обычно устроен в два уровня. Сначала смотрят на признаки: распределения, доли категорий, статистики по сегментам, PSI, расстояния между train и prod. Потом, когда доезжают метки или proxy-target, смотрят на то, не изменилась ли сама условная зависимость и не поползли ли calibration, ranking и бизнес-метрики.
Как измеряют feature drift на практике
Один из самых популярных способов — Population Stability Index. Это не магическая метрика, а вполне интуитивный способ сравнить, насколько различаются доли объектов по бинам между train и production. Чем сильнее перестроилась масса объектов по корзинам, тем выше PSI.
(\mathrm{PSI} = \sum_{b=1}^{B}(p_b-q_b)\ln\frac{p_b}{q_b})
Раздел математики: математическая статистика и элементы информационной теории.
Что означает каждый символ:
(B) — число бинов, на которые мы разбили признак.
(p_b) — доля объектов train в бине (b).
(q_b) — доля объектов production в том же бине.
\ln — натуральный логарифм.
Формула суммирует локальные различия по всем бинам и показывает, насколько сильно prod-поток перестроился относительно train.
Численный пример: пусть у признака всего два бина. На train доли равны (p_1=0.60) и (p_2=0.40), а в production — (q_1=0.30) и (q_2=0.70). Тогда PSI равен (0.60-0.30)\ln\frac{0.60}{0.30} + (0.40-0.70)\ln\frac{0.40}{0.70} \approx 0.254</code></span>. Это уже заметное смещение распределения.</p> </div> <p>PSI удобен тем, что связывает статистику и eng \ineer \ing. Его легко посчитать автоматически, положить в мониторинг и сравнивать по окнам времени. Но он отвечает только на вопрос о входах. Он не умеет диагностировать concept drift сам по себе.</p> <h2>Почему concept drift тяжелее мониторить</h2> <p>Потому что для него обычно нужны delayed labels. Модель принимает решение сегодня, а правильный ответ становится известен через неделю, месяц или квартал. В кредитном скоринге метка дефолта запаздывает. В churn-задачах пользователь должен сначала реально уйти. В fraud-задачах подтверждение часто приходит после ручной проверки.</p> <p>Именно поэтому drift-moni \tor \ing почти всегда связан и с инженерией выкладки: статья про <a href="https://sena \torovai.com/ci-cd-dlya-ml-servisov-kak-vykatyvat-modeli-i-api-bez-ruchnogo-khaosa/">CI/CD для ML-сервисов</a> полезна здесь как продолжение мысли о том, что реакция на drift должна быть не разовой паникой, а воспроизводимым процессом обновления моделей.</p> <p>Это означает, что concept drift почти всегда диагностируется позже. Поэтому сильная production-система использует несколько прокси-сигналов: сдвиг calibration, падение rank \ing-метрик на последних размеченных окнах, изменение качества в стабильных сегментах, рост бизнес-ошибок определенного типа, резкое изменение оптимального threshold.</p> <p>Интуитивно можно сказать так: feature drift — это проблема зрения модели, а concept drift — проблема понимания мира. В одном случае мир на входе выглядит иначе. В другом — мир на входе похож, но смысл увиденного уже другой.</p> <h2>Python: как раздельно посмотреть на feature drift и concept drift</h2> <pre><code class="langua\ge-python">import numpy as np # Подключаем NumPy для работы с массивами и гистограммами. import pandas as pd # Подключаем pandas для табличной диагностики. tra \in = pd.DataFrame({ # Создаем маленький train-срез. 'sessions': [2, 3, 4, 5, 6, 7, 8, 9], # Числовой признак активности пользователя. 'segment': ['old', 'old', 'old', 'old', 'new', 'new', 'new', 'new'], # Категориальный сегмент. 'tar \get': [0, 0, 0, 1, 0, 1, 1, 1], # Исторический исход, например churn или conversion. }) prod = pd.DataFrame({ # Создаем production-срез за новый период. 'sessions': [5, 6, 7, 8, 9, 10, 11, 12], # Видим сдвиг признака вверх по шкале. 'segment': ['old', 'old', 'old', 'new', 'new', 'new', 'new', 'new'], # Сегменты тоже могли измениться по частотам. 'tar \get': [0, 0, 1, 0, 0, 1, 1, 1], # После прихода меток проверяем и изменение связи с tar \get. }) def psi_numeric(tra \in_values, prod_values, b \ins=4): # Определяем функцию для простого PSI по числовому признаку. ed \ges = np.his \togram_b \in_ed \ges(tra \in_values, b \ins=b \ins) # Строим границы бинов по train, чтобы сохранить опорную шкалу. tra \in_hist, _ = np.his \togram(tra \in_values, b \ins=ed \ges) # Считаем train-частоты по этим бинам. prod_hist, _ = np.his \togram(prod_values, b \ins=ed \ges) # Считаем prod-частоты по тем же бинам. tra \in_share = np.clip(tra \in_hist / tra \in_hist.\sum(), 1e-6, None) # Переводим в доли и защищаемся от нулей. prod_share = np.clip(prod_hist / prod_hist.\sum(), 1e-6, None) # Переводим в доли и тоже защищаемся от нулей. return np.\sum((tra \in_share - prod_share) * np.\log(tra \in_share / prod_share)) # Возвращаем суммарный PSI. sessions_psi = psi_numeric(train['sessions'], prod['sessions']) # Оцениваем feature drift по числовому признаку. segment_shift = pd.concat([ # Сравниваем доли категорий между tra \in и prod. train['segment'].value_counts(normalize=True).rename('tra \in_share'), # Доли сегментов на train. prod['segment'].value_counts(normalize=True).rename('prod_share'), # Доли сегментов в prod. ], axis=1).fillna(0) # Заполняем нулями те категории, которых нет в одном из периодов. tra \in_relation = train.groupby('segment', as_ \index=False)['tar \get'].mean() # Считаем tar \get rate по сегментам на train. prod_relation = prod.groupby('segment', as_ \index=False)['tar \get'].mean() # Считаем tar \get rate по тем же сегментам в prod. concept_tab\le = tra \in_relation.mer\ge( # Соединяем обе таблицы, чтобы увидеть изменение условной зависимости. prod_relation, on='segment', suffixes=('_train', '_prod') ) # После mer\ge у нас одна строка на сегмент и две tar \get rate. pr \int('PSI for sessions:', round(sessions_psi, 4)) # Печатаем PSI как индикатор feature drift. pr \int(segment_shift) # Показываем, как изменились частоты категорий. pr \int(concept_tab\le) # Показываем, изменилась ли связь segment -> tar \get. </code></pre> <p>Этот пример не претендует на промышленный moni \tor \ing, но он очень хорошо фиксирует логику. По <span class="b\log-artic\le__math" data-latex="\mathrm{PSI}"><code>(\mathrm{PSI}) и долям категорий мы диагностируем, изменился ли входной мир модели. По таблице \text{segment} \rightarrow \text{target rate} мы смотрим, сохранился ли predictive смысл признака. Именно раздельность этих двух шагов и помогает не путать drift-ы.
Какие ошибки делают чаще всего
Первая ошибка — считать любой drift автоматическим поводом для retraining. Если проблема в ETL, retraining просто закрепит плохие данные в новой версии модели. Вторая ошибка — смотреть только на входные признаки и думать, что этого достаточно. Это помогает поймать feature drift, но почти ничего не говорит о concept drift. Третья ошибка — мониторить только общую метрику качества без разрезов по сегментам. Концептуальные изменения часто начинают проявляться локально, а агрегированная метрика маскирует их слишком долго.
Еще одна частая ошибка — не различать бизнес-изменение и поломку данных. Для продовой команды эти сценарии внешне похожи: метрика уехала, распределения сместились, alerts загорелись. Но решение в одном случае инженерное, а в другом — продуктово-модельное. Именно поэтому drift monitoring всегда должен жить рядом с предметным контекстом, а не отдельно от него.
На этапе реального реагирования это почти всегда означает осторожную выкладку новой версии, а не мгновенную замену всего трафика. Поэтому здесь особенно органично продолжить чтение через canary deployment и затем через latency в ML API, если обновление модели начинает влиять уже не только на качество, но и на поведение сервиса под нагрузкой.
Что важно вынести из темы
Feature drift и concept drift — это две разные формы изменения реальности вокруг модели. Feature drift означает, что изменилась статистика входов: другой поток, другие сегменты, другие распределения. Concept drift означает, что изменилась сама связь между входом и правильным ответом: мир начал отвечать по новым правилам.
Для Data Science это одна из базовых production-идей. Если мы не различаем эти два случая, мы будем принимать правильные инженерные действия слишком поздно или вообще не по адресу. А если различаем, то начинаем мыслить о модели не как о статичном файле с весами, а как о системе, живущей во времени вместе с данными, бизнесом и изменяющейся предметной средой.
Kaggle notebook по теме:
https://www.kaggle.com/code/drsachingupta/drift-concept-on-electricity-dataset/notebook