Перейти к содержанию

Мониторинг

Модель не падает громко, когда мир сдвигается. Она продолжает уверенно отвечать про данные, которых никогда не видела, а метрика, которая это поймала бы, — accuracy по разметке — в продакшене не существует: разметка и есть то, чего вы ждёте.

Что доступно с первого запроса — это вход. Сдвиг его распределения — самый ранний честный сигнал, что модели задают вопросы, под которые её не обучали.

Две половины

Обучение уже записывает то, на чём оно шло. Каждая зарегистрированная версия несёт профиль своего обучающего сплита:

Sentiment.load()._version.baseline
# {'text': {'kind': 'text', 'mean': 37.5, 'edges': [...], 'counts': [...]},
#  'label': {'kind': 'categorical', 'values': {'neg': 162, 'pos': 155}}}

Вторую половину записывает сервинг — если её включить:

myproject/settings.py
PREDICTION_LOG = {
    "ENABLED": True,
    "SAMPLE": 0.05,        # 5% достаточно, чтобы увидеть сдвиг распределения
    "MAX_ROWS": 100_000,   # старые строки обрезаются за этим порогом
}

По умолчанию выключено намеренно. Лог предсказаний — это копия пользовательского ввода в базе: решение, которое проект принимает сам, а не обнаруживает утром. Сэмплирование идёт построчно, а не по запросу, поэтому батчевый эндпоинт записывает долю каждого батча, а не все целиком одни и ни одного другие.

Измерение расхождения

python manage.py drift reviews.Sentiment
python manage.py drift reviews.Sentiment --stage production --since 24h
python manage.py drift reviews.Sentiment --against reviews.Incoming
reviews.Sentiment@v4 vs 2841 logged predictions over the last 7d

Column             Kind         PSI     Verdict
-----------------  -----------  ------  -----------
text               text         0.4132  significant
label (predicted)  categorical  0.1801  moderate

PSI below 0.1 is stable, 0.1–0.25 moderate, above 0.25 significant.
A column has moved significantly. Retraining is likely overdue.

Сравниваются две вещи, и вторая важнее всего тогда, когда первая мало что может сказать:

  • Дрейф входа — каждый признак против того же признака на обучении.
  • Дрейф предсказаний — то, что модель отвечает, против меток, на которых её учили. Модель, чей выход раньше делился поровну, а теперь на 90% один класс, стоит посмотреть — и для этого вообще не нужна разметка.
Флаг Что делает
--version N / --stage NAME С профилем какой версии сравнивать
--since 24h\|7d\|4w Окно логов предсказаний. По умолчанию 7d
--against LABEL Сравнить датасет вместо лога — для батча, который вы собираетесь скорить
-n N Ограничить число прочитанных строк. По умолчанию 10 000
--json Отдать оценки
--fail-on moderate\|significant Выйти с ненулевым кодом. Для регулярной задачи

Страница модели в админке показывает ту же таблицу за последние семь дней — и не показывает ничего, когда лог выключен: пустая таблица дрейфа на каждой странице учит людей не смотреть на таблицу дрейфа.

Что такое PSI

Population stability index — стандарт в кредитном скоринге ровно для этой задачи:

PSI = Σ (actual_i − expected_i) · ln(actual_i / expected_i)

по корзинам i, в долях от своих сумм. Одно число на колонку, сравнимое между колонками разных типов, с порогами, которые не надо выдумывать: ниже 0.1 — стабильно, 0.1–0.25 — умеренно, выше 0.25 — значимо.

Как колонка разбивается на корзины, зависит от того, что в ней лежит, и решается один раз на обучении:

Вид Разбиение Замечания
Числовая Децили обучающего сплита Квантили, а не равные ширины: равные ширины на скошенной колонке сложат всё в одну корзину и не заметят ничего
Категориальная Корзина на значение Значения, не встречавшиеся на обучении, получают свою
Текстовая Децили длины Грубый прокси — и честный: значения свободного текста не повторяются, поэтому их частоты ничего не говорят

Наблюдаемые данные всегда читаются как тот вид, который определил baseline. Определять вид дважды кажется безобидным, но это не так: продакшн-текст, схлопнувшийся до горстки повторов, был бы прочитан как категориальный, и сравнение вернуло бы пустоту ровно в тот момент, который стоило поймать.

В регулярной задаче

--fail-on превращает дрейф из отчёта в проверку:

- name: Check for drift
  run: python manage.py drift reviews.Sentiment --since 24h --fail-on significant

Чем это не является

Это не продукт для мониторинга и не пытается им быть. Здесь нет алертов, нет дашбордов во времени, нет детекции аномалий. Он отвечает на один вопрос — ушёл ли вход от того, на чём обучалась эта версия, — по данным, которые у фреймворка уже были, и без всякой дополнительной инфраструктуры. Когда вы это перерастёте, лог предсказаний останется обычной таблицей, которую можно отгрузить куда угодно.