Каскад двух ML-моделей: где теряются положительные случаи

Компания проверяет заявки на нарушение правил. Дешёвая модель быстро отбрасывает очевидные случаи, а дорогая изучает оставшиеся. У второй модели высокий recall, поэтому команда ждёт такого же качества всей системы. После запуска оказывается, что важные заявки до неё просто не доходят.

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

Сохранить единицу наблюдения

Для проверки нужна одна таблица на исходную заявку. В ней есть правильная метка, решение первого этапа и решение второго. У заявок, которые первый этап отбросил, второй этап в реальном процессе не выполняется. В расчёте итогового положительного решения такие строки считаются отрицательными.

Не собирайте отчёт только из логов дорогой модели. В них отсутствует именно та часть потерь, которую предстоит найти. Сохраняйте идентификатор заявки ещё на входе и связывайте решения обоих этапов с ним.

Проверим каскад на маленьком потоке

truth = [1, 1, 1, 1, 1, 0, 0, 0]
passed_first = [1, 1, 1, 1, 0, 1, 0, 0]
positive_second = [1, 1, 1, 0, 0, 1, 0, 0]

positive_total = sum(truth)
positive_after_first = sum(
    y for y, passed in zip(truth, passed_first) if passed
)
found = sum(
    y for y, passed, predicted in zip(
        truth, passed_first, positive_second
    ) if passed and predicted
)
first_recall = positive_after_first / positive_total
second_conditional_recall = found / positive_after_first
cascade_recall = found / positive_total
print(first_recall, second_conditional_recall, cascade_recall)
assert (first_recall, second_conditional_recall, cascade_recall) == (0.8, 0.75, 0.6)

Первый фильтр сохранил 4 из 5 положительных заявок: recall равен 0,8. Второй нашёл 3 из этих 4: условный recall равен 0,75. Весь процесс нашёл 3 из исходных 5, поэтому итог — 0,6.

На одном и том же наборе произведение 0,8 и 0,75 даёт 0,6. Здесь второй множитель измерен именно среди положительных случаев, которые прошли первый этап. Для этого равенства не нужна независимость моделей: оно следует из общих числителей и знаменателей.

Когда произведение метрик вводит в заблуждение

Допустим, вторую модель раньше тестировали отдельно на всех заявках и получили recall 0,9. Нельзя просто умножить его на 0,8 первого фильтра и объявить итоговое качество 0,72. После отбора меняется состав потока. Первый этап может оставить как раз те трудные случаи, на которых вторая модель ошибается чаще.

Поэтому пересчитайте её метрику на реальном входе каскада, сохранив независимый тест. Полезно разложить исходные положительные заявки на три группы: потеряны на первом этапе, потеряны на втором и успешно найдены. В примере размеры групп — 1, 1 и 3. Это сразу показывает, какой этап способен дать выигрыш.

Не ограничивайтесь полнотой. Если первый фильтр пропускает почти всё, высокая полнота достигается ценой дорогих вызовов. Считайте долю заявок, дошедших до второго этапа, его ложные срабатывания и общую нагрузку. В нашем входе до дорогой модели дошли 5 из 8 заявок, а среди её положительных решений есть один ложноположительный случай.

Как выбрать следующее изменение

Меняя порог первого этапа, пересчитывайте одновременно итоговую полноту и размер очереди. Если снятый фильтр возвращает много важных заявок при небольшом росте нагрузки, это один сценарий. Если он пропускает огромный поток с несколькими полезными находками, понадобится другой компромисс.

Сравнивайте версии на одних заявках. Иначе рост recall может отражать более простой тестовый период. Сохранённые идентификаторы и решения обоих этапов позволяют построить такой отчёт без повторного выполнения всего процесса.

Потренировать подсчёт пропущенных положительных случаев можно в подборке по классификации, precision и recall. После этого добавьте первый фильтр к одному из примеров и посчитайте итог вручную.

В курсе про табличные модели в продакшн вы строите корректную валидацию, анализируете причины провалов качества и собираете batch-инференс и API. Эти навыки нужны, чтобы связать качество отдельной модели с поведением рабочего процесса. Откройте программу и выберите раздел про оценку и диагностику моделей.

Продолжить на практике

  • ML Engineer / Data Scientist: табличные модели в продакшн ↗

    Практический курс для тех, кто хочет получить навыки ML Engineer / Data Scientist на реальных табличных задачах. В курсе Вы не просто обучите CatBoost/LightGBM, а соберёте полноценный production-style пайплайн: подготовка данных, feature engineering (генерация признаков), корректная валидация, защита от leakage (утечек), тюнинг через Optuna, интерпретация через SHAP, batch-инференс и REST API для предсказаний.