В тесте слишком много сложных случаев: как взвесить приёмку ML-модели
Для приёмки модели команда вручную проверила двести документов. Половина — обычные сканы, половина — фотографии с телефона: на них чаще возникают ошибки. В производстве фотографии составляют только десятую часть потока. Общая accuracy на таком тесте полезна для сравнения трудных случаев, но завышает их вклад в ожидаемую долю ошибок сервиса.
Разделим набор для поиска ошибок и оценку потока
Целевой отбор сложных документов оправдан. Сотня фотографий позволит увидеть больше проблем с бликами и перспективой, чем случайная двадцатка. Ошибка начинается в момент, когда все двести примеров усредняют с одинаковым весом и подписывают результат «точность в продакшне». Мы изменили состав выборки, поэтому для такого вывода нужно вернуть сегментам их производственные доли.
Сначала задайте сегменты до просмотра результата модели. В нашем случае они определяются источником документа, который известен из входящего запроса. Нельзя объявлять сегментом «сложные ошибки», найденные после проверки, а затем приписывать ему частоту в потоке: для остальных документов принадлежность к такой группе неизвестна без разметки. Группы должны покрывать весь целевой поток и не пересекаться.
Внутри каждой группы примеры должны представлять её обычное разнообразие. Если среди фотографий вручную оставили только самые размытые, вес 10% не превратит эту коллекцию в случайную выборку фотографий. Взвешивание исправляет известную долю групп; скрытый отбор внутри группы оно не отменяет.
Каждая строка представляет разное число документов
Пусть модель верна на 98 из 100 сканов и на 70 из 100 фотографий. Обычное среднее даст 84%. В целевом потоке 90% сканов и 10% фотографий. Мы можем сначала взвесить точности групп их долями. Другой эквивалентный путь — каждой проверенной строке выдать вес доля_группы / размер_группы и сложить веса правильных ответов.
groups = {
"scan": {"n": 100, "correct": 98, "share": 0.90},
"photo": {"n": 100, "correct": 70, "share": 0.10},
}
assert abs(sum(g["share"] for g in groups.values()) - 1) < 1e-9
raw = sum(g["correct"] for g in groups.values()) / sum(
g["n"] for g in groups.values())
weighted = sum(g["share"] * g["correct"] / g["n"]
for g in groups.values())
row_weights = []
correctness = []
for g in groups.values():
assert 0 <= g["correct"] <= g["n"] and g["n"] > 0
row_weights.extend([g["share"] / g["n"]] * g["n"])
correctness.extend([1] * g["correct"] + [0] * (g["n"] - g["correct"]))
from_rows = sum(w * ok for w, ok in zip(row_weights, correctness)) / sum(row_weights)
assert abs(from_rows - weighted) < 1e-12
print(f"raw={raw:.1%} weighted={weighted:.1%}")
for name, g in groups.items():
error_share = g["share"] * (1 - g["correct"] / g["n"])
print(name, "expected_errors_per_10000", round(10_000 * error_share))
Получается 95,2% взвешенной accuracy. В расчёте на десять тысяч документов ожидаются 180 ошибок на сканах и 300 на фотографиях. Именно фотографии дают большую часть ожидаемых ошибок, хотя встречаются в девять раз реже. Общая метрика выглядит лучше исходных 84%, но потребность работать с фотографиями никуда не исчезла.
Это хороший повод разделить два решения. Для оценки общей нагрузки используйте целевые веса. Для инженерного плана сохраняйте качество и количество размеченных примеров каждой группы. Иначе высокая итоговая точность позволит пропустить сегмент, который создаёт большую часть ручной работы. Если стоимость ошибки различается, дополнительно умножайте ожидаемые ошибки на согласованную стоимость по типу случая.
Откуда взять доли и что сохранить в отчёте
Доли групп берите из входящего потока за период, соответствующий решению о запуске. Считайте все документы до отбора моделью. Доля фотографий среди уже отклонённых документов не подходит: это результат работы системы, а не состав исходного потока. Отдельно укажите период наблюдения и способ определения источника.
Если запланирован запуск в новой компании, её структура потока может отличаться. Полезно пересчитать несколько сценариев, например при 10%, 30% и 50% фотографий. Они показывают чувствительность результата к изменению состава, но не заменяют проверку качества на данных нового клиента. Внутри знакомой группы могут появиться другие языки, формы и условия съёмки.
Для интервала неопределённости сохраняйте стратификацию: повторно выбирайте документы внутри каждой группы, а затем применяйте фиксированные целевые доли. Если много страниц принадлежит одному документу, переносите в повторную выборку целые документы. Вес не увеличивает фактическое количество независимых наблюдений; сто сканов остаются сотней проверок даже при большом производственном потоке.
Оставляйте в отчёте исходные количества, а не только взвешенные проценты. Одинаковая оценка 95,2% может получиться из двухсот проверок или из двух тысяч, и уверенность в результате будет различаться. Особенно опасна группа с большой производственной долей и несколькими проверенными документами: её вес велик, а знание качества слабое. При планировании следующей разметки сначала увеличивайте именно такие группы, если их ошибки способны изменить решение о запуске.
Если для одной группы нет ни одного размеченного примера, не присваивайте ей качество соседней группы и не перераспределяйте её вес автоматически. В таком случае итоговая оценка полного потока ещё не определена. Можно честно рассчитать качество покрытой части и отдельно указать её долю, затем заказать недостающую разметку. Для рабочего решения это полезнее точного процента, который получился после незаметного исключения неизвестных случаев.
В платном курсе «Data Scientist: от данных до деплоя моделей» вы отрабатываете выбор метрик, валидацию, качество данных и анализ ошибок. Эти навыки помогают превратить отобранный тестовый набор в обоснованный отчёт для решения о запуске. Посмотрите программу курса и дальнейший переход от оценки к сервису предсказаний.
Продолжить на практике
- Data Scientist: от данных до деплоя моделей ↗
Практический курс по Data Science: от постановки ML-задачи и работы с данными до inference API, CI/CD и мониторинга моделей в продакшне. Вы пройдёте EDA, feature engineering, обучение и оценку моделей, experiment tracking, error analysis и deployment — чтобы собирать полноценный рабочий контур вокруг ML-решения.
