Дрейф данных: KS-тест и PSI

«Кажется, данные изменились» нужно превратить в число — для этого есть KS-тест и PSI.

Дрейф данных (data drift) — изменение распределения входных признаков относительно обучающего; обнаруживается статистически сравнением текущего распределения с эталонным.

Зачем мерить дрейф числом

Глаз на гистограмме субъективен и не масштабируется на сотни признаков. Нужны метрики, дающие число и порог: сработал — алерт. Две самые ходовые — KS-тест и PSI.

KS-тест (Колмогорова — Смирнова)

Сравнивает два распределения по максимальному расстоянию между их функциями распределения (CDF). Статистика KS — это наибольший вертикальный зазор между эталонной и текущей CDF. Чем больше зазор, тем сильнее распределения разошлись. Удобен для непрерывных признаков, не требует предположений о форме распределения.

# Упрощённый KS: максимум |CDF_ref(x) - CDF_cur(x)| по объединённым точкам
def ks_statistic(ref, cur):
    points = sorted(set(ref) | set(cur))
    nr, nc = len(ref), len(cur)
    d = 0.0
    for x in points:
        cdf_r = sum(1 for v in ref if v <= x) / nr
        cdf_c = sum(1 for v in cur if v <= x) / nc
        d = max(d, abs(cdf_r - cdf_c))
    return d

reference = [10, 11, 12, 12, 13, 13, 14, 15, 11, 12]   # обучение
current_ok = [11, 11, 12, 12, 13, 13, 14, 15, 11, 12]  # похоже
current_drift = [20, 22, 21, 23, 24, 22, 25, 21, 23, 24]  # сдвиг вверх

print(f"KS (без дрейфа):  {ks_statistic(reference, current_ok):.2f}")
print(f"KS (с дрейфом):   {ks_statistic(reference, current_drift):.2f}")

Вывод:

KS (без дрейфа):  0.10
KS (с дрейфом):   1.00

Без дрейфа зазор мал (0.10), при явном сдвиге вверх — максимален (1.00: распределения не пересекаются). В реальности KS-статистику сопровождают p-value, но величина зазора уже наглядна.

PSI (Population Stability Index)

Любимая метрика в кредитном скоринге. Разбивает диапазон на корзины (bins) и суммирует, насколько доли наблюдений в корзинах сместились между эталоном и текущими данными. Формула по каждой корзине: (p_cur - p_ref) * ln(p_cur / p_ref), и всё суммируется.

import math

def psi(ref_counts, cur_counts):
    nr, nc = sum(ref_counts), sum(cur_counts)
    total = 0.0
    for r, c in zip(ref_counts, cur_counts):
        p_ref = max(r / nr, 1e-6)   # защита от деления на ноль
        p_cur = max(c / nc, 1e-6)
        total += (p_cur - p_ref) * math.log(p_cur / p_ref)
    return total

# доли по 4 корзинам
ref = [25, 25, 25, 25]            # эталон: равномерно
cur_stable = [24, 26, 25, 25]     # почти то же
cur_shifted = [10, 15, 30, 45]    # масса уехала вправо

print(f"PSI (стабильно): {psi(ref, cur_stable):.3f}")
print(f"PSI (сдвиг):     {psi(ref, cur_shifted):.3f}")

Вывод:

PSI (стабильно): 0.001
PSI (сдвиг):     0.315

Как читать PSI

PSIИнтерпретация
< 0.1дрейфа практически нет
0.1 – 0.25умеренный дрейф, присмотреться
> 0.25сильный дрейф, нужны меры

В примере 0.315 > 0.25 — сигнал к действию: проверить причину и, возможно, переобучить.

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

Оба метода сравнивают текущее окно данных с зафиксированным эталонным (обычно обучающая выборка или стабильный прошлый период). KS работает на сырых значениях через CDF, PSI — на бинированных долях. Детектор дрейфа в проде хранит эталонную статистику признаков, периодически собирает свежее окно из логов, считает метрику по каждому признаку и алертит при превышении порога. Важно: дрейф входов не всегда означает падение качества — это сигнал «присмотреться», а не автоматический приговор.

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

  • Считать любой дрейф катастрофой. Дрейф входов может не вредить качеству; это повод проверить, а не паниковать.
  • Брать слишком маленькое окно. На шумной выборке метрика дрейфа сама прыгает.
  • Один эталон навсегда. После переобучения эталон надо обновлять под новую норму.
  • Деление на ноль в PSI. Пустые корзины нужно сглаживать (как 1e-6 выше).

Итог

  • Дрейф данных измеряют числом: KS-тест (макс. зазор между CDF) и PSI (сдвиг долей по корзинам).
  • PSI < 0.1 — норма, 0.1–0.25 — присмотреться, > 0.25 — действовать.
  • Детектор сравнивает текущее окно с эталоном по каждому признаку; дрейф — сигнал проверить, а не приговор.
Проверьте себя
1. Что измеряет статистика KS-теста?
AСреднее значение признака
BМаксимальный зазор между функциями распределения (CDF) эталона и текущих данных
CРазмер выборки
DТочность модели
2. Какое значение PSI обычно считают сигналом к действию?
APSI < 0.1
BPSI > 0.25
CPSI = 0
DЛюбое PSI
3. Почему дрейф входных данных — не всегда приговор модели?
AПотому что KS всегда ошибается
BСдвиг входов может не вредить качеству; это сигнал проверить, а не автоматическая деградация
CДрейф невозможно измерить
DМодель сама исправится