Средняя метрика скрывает провал редкого сегмента: проверяем ML-модель на срезах

До публикации карточки проходят через классификатор: подозрительные попадают к модератору, остальные идут дальше.
Команда подключает новый партнёрский фид и готовит новую версию ML-модели к релизу.
На контрольном наборе accuracy выросла с 93% до 95,5%, а число карточек, отправленных на ручную проверку, сократилось со 136 до 113.
Для продукта это выглядит как два улучшения сразу.
Однако на карточках нового фида модель стала находить только 8 из 20 нарушений вместо прежних 16. Ещё восемь нарушений на том же наборе прошли мимо маршрута ручной проверки.
Итог зависит от того, заметит ли их другой контроль, но для решения о релизе одной общей accuracy уже недостаточно.
Это моделируемый продуктовый сценарий, а не данные работающего сервиса. Числа и обе версии предсказаний заданы ниже в воспроизводимом коде. Через них удобно показать процедуру проверки, которую затем можно применить к своему полностью размеченному набору.
Я Иннокентий Бодров, Product Leader с опытом более 18 лет в технологических продуктах.
В статье покажу, как общая метрика может скрыть провал нового источника данных, какие срезы и счётчики проверить перед релизом и как связать результат с решением о выпуске модели.
Общий отчёт показывает рост accuracy и сокращение очереди
В тестовом наборе 1000 карточек: 900 из основного потока и 100 из нового партнёрского API.
Независимая разметка пометила 90 нарушений в основном потоке и 20 в партнёрском.
Положительный класс означает «нарушение подтверждено».
Положительный прогноз — «отправить на ручную проверку».
Обе версии модели оцениваем на одних и тех же карточках.
Сегмент | Модель | Карточек | Нарушений | TP | FP | FN | TN | Accuracy | Recall |
|---|---|---|---|---|---|---|---|---|---|
Основной поток | A | 900 | 90 | 72 | 40 | 18 | 770 | 93,6% | 80% |
Основной поток | B | 900 | 90 | 81 | 20 | 9 | 790 | 96,8% | 90% |
Партнёрский API | A | 100 | 20 | 16 | 8 | 4 | 72 | 88% | 80% |
Партнёрский API | B | 100 | 20 | 8 | 4 | 12 | 76 | 84% | 40% |
Весь набор | A | 1000 | 110 | 88 | 48 | 22 | 842 | 93% | 80% |
Весь набор | B | 1000 | 110 | 89 | 24 | 21 | 866 | 95,5% | 80,9% |
Здесь:
TP— верно найденные нарушения.FN— пропущенные нарушения.FP— чистые карточки, напрасно отправленные на проверку.TN— чистые карточки, которые модель не отправила модератору.
Accuracy = (TP + TN) / N, а Recall = TP / (TP + FN).
На всём наборе выросла даже recall: с 80% до 80,9%. Очередь ручной проверки на этих 1000 карточках уменьшилась на 23 позиции: число положительных прогнозов равно TP + FP. Но в партнёрском фиде число пропущенных нарушений выросло с 4 до 12.
Новая модель разгружает модераторов и ухудшает контроль именно там, где появился новый источник данных. Решение о релизе требует оценки обоих эффектов.
Это не особенность accuracy. Агрегированные precision, recall или F1 тоже могут скрывать ухудшение отдельной группы.
Для модерации FN означает нарушение, которое модель не направила на проверку, а FP добавляет работу команде и задерживает добросовестного продавца. При выборе метрики нужно определить цену каждого исхода и доступную мощность модерации. Google подробно разбирает смысл и ограничения метрик классификации.
Опубликованный кейс: классификатор токсичных комментариев
Похожую проверку провели исследователи Jigsaw на двух публично доступных версиях классификатора Perspective API.
Они оценили модели на корпусе из 1,8 млн комментариев; у 450 тысяч из них разметили упоминаемые в тексте группы.
На коротких комментариях с упоминанием геев или лесбиянок метрика BPSN AUC у первой версии составила 0,57, а у версии после коррекции обучающих данных — 0,82.
BPSN AUC сравнивает оценки нетоксичных комментариев этого среза с токсичными комментариями вне среза: низкое значение показывает риск ложных срабатываний при общем пороге.
Это результаты офлайн-оценки моделей в исследовании, а не измерение ошибок модерации в действующем сервисе.
Кейс показывает другой тип провала, чем наш партнёрский фид: здесь страдают добросовестные комментарии, которые могут получить слишком высокий балл токсичности.
Авторы также обнаружили, что коррекция лучше помогла коротким комментариям и не сняла проблему полностью на всём корпусе.
Значит, после исправления полезно повторно проверить не только выбранный срез, но и соседние по длине и содержанию группы.
Наши 93% и 95,5% из начала статьи — учебные числа; исследование Jigsaw их не подтверждает.
Пересчитываем результат по каждой карточке
Код ниже работает на стандартной библиотеке Python. Он не обучает модель: два столбца предсказаний задают результаты условных версий A и B.
В рабочем проекте эти столбцы нужно получить от моделей на одном и том же наборе карточек. Здесь мы проверяем процедуру сравнения, а не способ обучения.
from collections import defaultdict
def segment(name, positives, negatives, a_tp, a_fp, b_tp, b_fp):
rows = []
for i in range(positives):
rows.append((name, 1, int(i < a_tp), int(i < b_tp)))
for i in range(negatives):
rows.append((name, 0, int(i < a_fp), int(i < b_fp)))
return rows
# Поля строки: source, confirmed_violation, prediction_A, prediction_B.
rows = (
segment("main", 90, 810, a_tp=72, a_fp=40, b_tp=81, b_fp=20)
+ segment("partner_api", 20, 80, a_tp=16, a_fp=8, b_tp=8, b_fp=4)
)
def evaluate(data, prediction_column):
counts = defaultdict(int)
for _, actual, pred_a, pred_b in data:
predicted = (pred_a, pred_b)[prediction_column]
counts[(actual, predicted)] += 1
tp = counts[(1, 1)]
fp = counts[(0, 1)]
fn = counts[(1, 0)]
tn = counts[(0, 0)]
n = tp + fp + fn + tn
return {
"n": n,
"positives": tp + fn,
"tp": tp,
"fp": fp,
"fn": fn,
"tn": tn,
"accuracy": (tp + tn) / n if n else None,
"recall": tp / (tp + fn) if tp + fn else None,
"precision": tp / (tp + fp) if tp + fp else None,
}
for name in ("all", "main", "partner_api"):
part = rows if name == "all" else [row for row in rows if row[0] == name]
for model, column in (("A", 0), ("B", 1)):
m = evaluate(part, column)
print(
name, model,
f"n={m['n']}", f"positives={m['positives']}",
f"TP={m['tp']}", f"FP={m['fp']}",
f"FN={m['fn']}", f"TN={m['tn']}",
f"accuracy={m['accuracy']:.1%}",
f"recall={m['recall']:.1%}",
f"precision={m['precision']:.1%}",
)
Для собственного набора данных вместо функции segment() понадобятся строки вида (source, confirmed_violation, prediction_A, prediction_B).
Добавьте идентификатор карточки и время её создания. Так можно проверить, что обе версии получили одинаковый вход и что разметка была доступна для каждой карточки, а не только для тех, которые старая модель отправила модератору.
В демонстрации предсказания уже бинарные.
Если модели возвращают вероятность или score, порог для каждой версии выбирают на отдельной валидационной выборке и фиксируют до итогового сравнения.
Для анализа компромисса между очередью модерации и пропусками полезны PR-кривые.
При редком положительном классе PR-кривая может быть информативнее ROC-кривой. Google: ROC, AUC и PR-кривые.
Разметка только проверенных карточек скроет пропуски
Качество оценки зависит от источника истины. Если модератор видит только карточки, отобранные текущей моделью, для остальных карточек нет подтверждённого класса.
Приравнять «не проверяли» к «нарушения нет» нельзя: именно среди непроверенных объектов находятся возможные
FN.
Поэтому в нашем сценарии контрольный набор полностью размечен независимо от решений обеих моделей.
На практике команда может организовать выборочную проверку карточек из обоих маршрутов и сохранить вероятность попадания в выборку.
Если проверять преимущественно подозрительные карточки, сырые доли на размеченном наборе не будут оценкой качества на всём потоке. При разных вероятностях отбора оценке общей метрики понадобятся веса; отсутствие меток в части потока требует отдельной проверки допущений.
Это вариант проблемы selective labels: решение системы определяет, для каких объектов вообще появляется метка. Она изучена, в частности, в работе о выборочно наблюдаемых метках; исследование взаимодействия автоматической и ручной модерации рассматривает одновременно отбор карточек и очередь людей.
Здесь есть ещё две проверки качества данных. Зафиксируйте версию правил модерации: если за время сбора набора они поменялись, одинаковые карточки могли получить разные метки.
А при спорных случаях проверьте согласованность решений нескольких модераторов. Большее количество строк не поможет, если сам целевой класс определён непоследовательно.
Срезы выбираем по маршрутам продукта
Если заранее не определить срезы, всегда можно найти группу, где кандидат выглядит плохо из-за случайности. Для сервиса объявлений я бы начал с маршрутов, которые используют разные входные данные или несут разную цену ошибки:
Источник карточки: основной интерфейс, партнёрский API, пакетный импорт.
Категория товара, если у категорий разные правила или цена пропуска.
Версия клиента и формат заполнения карточки: свободное описание, структурированные атрибуты, короткий текст.
Пересечения источника и категории, если в каждом пересечении хватает размеченных нарушений.
Первый список должен опираться на известные маршруты продукта, а не на поиск наиболее эффектной просадки после расчёта.
Для каждого среза нужны N, число нарушений, матрица ошибок и доля среза в потоке.
Если тестовый набор собран с намеренным переизбытком нарушений, его precision и размер очереди нельзя переносить на реальный трафик без поправки на распространённость класса.
Исследование Oakden-Rayner и соавторов о hidden stratification показывает, что значимые подгруппы могут не быть размечены как отдельные классы.
Авторы изучали медицинские изображения и обнаружили заметные различия качества в важных подгруппах. Это хороший повод искать предметно значимые срезы; численные результаты исследования нельзя объявлять типичным эффектом для других задач.
Есть ещё один риск: малый срез даёт нестабильную долю. В партнёрском API нашего примера всего 20 размеченных нарушений; один дополнительный пропуск изменит recall на пять процентных пунктов.
Падение с 16 до 8 найденных нарушений видно в исходных счётчиках, но для решения о релизе понадобятся оценки неопределённости и новые наблюдения.
Версии нужно сравнивать на парных карточках; для такой оценки можно использовать парный bootstrap в SciPy. Если один продавец создал много карточек, при пересэмплировании нужно сохранять группировку по продавцу.
Интервал не исправит смещённую выборку, ошибочную разметку или срез, выбранный после просмотра результатов.
Релизный порог привязываем к риску и очереди
Одной таблицы в отчёте недостаточно, если решение всё равно принимают по общей accuracy.
Для нашего моделируемого сервиса владелец продукта и команда модерации могли бы заранее согласовать условия:
общая accuracy не должна быть ниже accuracy текущей версии;
для партнёрского API модель должна находить минимум 75% подтверждённых нарушений на отдельном контрольном наборе;
очередь ручной проверки не должна превышать доступную мощность модераторов на ожидаемом объёме трафика;
сравнение проводится на одном зафиксированном тестовом наборе; пороги для моделей выбирают на отдельной валидационной выборке и фиксируют до финальной проверки;
если разметки по важному срезу мало, команда увеличивает контрольную выборку и не трактует шумную оценку как доказательство улучшения.
По этим правилам модель B не проходит проверку: в партнёрском API она находит 8 из 20 нарушений, или 40%, при требовании 75%. Число 75% взято только для иллюстрации; это не отраслевой норматив.
В рабочей системе порог выводят из допустимого числа пропусков, последствий нарушения и мощности ручной проверки.
Если тестовый набор не отражает будущий поток, отдельно пересчитывают ожидаемую очередь и долю нарушений для целевой популяции.
Условия можно автоматизировать: проверять абсолютный минимум метрики на срезе и изменение относительно текущей модели.
Именно такие два типа порогов поддерживает TensorFlow Model Analysis.
Инструмент не решает за команду, какие срезы и пороги считать важными; он делает согласованное правило воспроизводимым.
Если общий тест пройден, а один новый маршрут не прошёл, есть промежуточное решение: оставить на нём модель A, отправлять его карточки на усиленную ручную проверку или отложить подключение фида.
Выбор зависит от того, можно ли маршрутизировать карточки по источнику, выдержит ли очередь дополнительную нагрузку и не создаст ли отдельный порог для фида неожиданных эффектов.
Эти варианты требуют измерения, а не автоматического применения после одной таблицы.
Провал на партнёрском фиде требует отдельного расследования
Плохая метрика на срезе не указывает причину. Для партнёрского API я бы проверил четыре гипотезы:
карточки из API содержат другие тексты, категории или пропуски атрибутов;
модераторы применяли к партнёрским карточкам другую версию правил;
конвейер обработки признаков преобразует поля API иначе, чем поля основного интерфейса;
выбранный порог создаёт для фида невыгодный обмен между пропусками и объёмом ручной проверки.
Порядок проверки:
выгрузить восемь дополнительных пропусков модели B с идентификаторами карточек;
сверить тексты и атрибуты до и после обработки;
перепроверить спорные метки;
сравнить распределение score по источникам.
Лишь после этого выбирать исправление — разметку, преобразование, модель или порог. Его качество придётся измерить на новых данных: подбор решения на уже просмотренных 20 положительных карточках создаст завышенную оценку.
В других продуктах срез может описывать не источник данных, а группы людей. Тогда анализ качества затрагивает и справедливость решений. Равная recall сама по себе ещё не доказывает справедливость системы: нужны критерии, связанные с конкретным решением и его последствиями. Google: оценка смещений и fairness.
Релизный отчёт дополняем численностью среза и ценой ошибки
На 1000 смоделированных карточек модель B подняла accuracy с 93% до 95,5% и сократила число направлений на ручную проверку со 136 до 113. В партнёрском API она пропустила 12 из 20 нарушений вместо 4. Увеличение общей метрики и разгрузка очереди здесь сосуществуют с конкретным продуктовым риском.
В релизном отчёте я бы держал рядом общую метрику, матрицу ошибок по заранее выбранным маршрутам, число размеченных нарушений, ожидаемую очередь модерации и условие приемки каждого критичного среза.
Построчные предсказания обеих версий позволяют вернуться от процента к карточкам, на которых модели разошлись. Без этого шага таблица с красивой общей accuracy не объясняет, какие случаи новая версия перестала замечать.
Выводы
Общая accuracy выросла с 93% до 95,5%, но на партнёрском фиде recall упала с 80% до 40%. Релизная проверка должна показывать оба результата.
Для каждого важного среза нужны исходные счётчики ошибок, объём разметки и оценка последствий пропуска или ложного срабатывания. Один процент без численности среза вводит в заблуждение.
Срезы и условия приемки стоит определить до финального сравнения моделей. Если критичный маршрут не проходит порог, его выпуск требует отдельного решения.
Причину просадки устанавливают по карточкам, на которых версии разошлись, а исправление проверяют на новых данных. Плохая метрика сама по себе причину не объясняет.
Даже модель с хорошими метриками может со временем начать ошибаться из-за изменений во входных данных.
Как обнаруживать такие изменения и контролировать качество моделей в продакшене, разберём 26 октября в 20:00 на бесплатном демо-уроке «Data Drift в машинном обучении: почему модели деградируют в продакшене и как это контролировать».
Рассмотрим типы смещения данных, метрики и инструменты мониторинга на практике.
Больше практических тем по ML, аналитике и разработке — в октябрьском дайджесте вебинаров OTUS.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.