Как выбрать порог классификации по стоимости ошибок, а не по 0,5

Модель оценивает риск отказа устройства. По умолчанию вы отправляете технику на проверку, когда оценка не ниже 0,5. Так удобно получить метку «проверять / не проверять», но порог ничего не знает о стоимости выезда и цене пропущенной поломки. Если пропуск обходится дороже лишней проверки, порог может оказаться ниже. Выбрать его помогают не впечатления от отдельных прогнозов, а ошибки на отложенных данных и их цена.

Сначала договоритесь, какую ошибку оплачиваете

Обозначим поломку как класс 1. Если устройство помечено как рискованное и действительно сломалось, это TP. Лишний выезд к исправному устройству — FP. Пропущенная поломка — FN. Допустим, лишняя проверка стоит 3 условные единицы, пропуск — 5. Тогда стоимость решений на проверочной выборке равна 3 × FP + 5 × FN. Числа здесь заданы для учебного примера; в своей задаче их надо согласовать с теми, кто отвечает за обслуживание.

Понижение порога обычно находит больше поломок, то есть повышает полноту (recall), но может добавить ложные тревоги и снизить точность положительных сигналов (precision). Поэтому одного слова «качество» мало: команда должна знать, сколько проверок сможет сделать и какую ошибку ей дороже оставить.

Посчитайте несколько вариантов на одних наблюдениях

Ниже — восемь устройств из валидационной выборки. score — оценка уже обученной модели, y_true — наблюдавшаяся затем поломка. Код ничего не обучает: он переводит готовые оценки в решения при трёх порогах и считает ошибки.

y_true = [1, 0, 1, 0, 1, 0, 0, 1]
score = [0.90, 0.80, 0.65, 0.55, 0.45, 0.35, 0.20, 0.10]

for threshold in (0.50, 0.40, 0.30):
    predicted = [int(s >= threshold) for s in score]
    tp = sum(y == 1 and p == 1 for y, p in zip(y_true, predicted))
    fp = sum(y == 0 and p == 1 for y, p in zip(y_true, predicted))
    fn = sum(y == 1 and p == 0 for y, p in zip(y_true, predicted))
    precision = tp / (tp + fp)
    recall = tp / (tp + fn)
    cost = 3 * fp + 5 * fn
    print(f'{threshold:.2f}: TP={tp} FP={fp} FN={fn} '
          f'precision={precision:.2f} recall={recall:.2f} cost={cost}')

Код напечатает:

0.50: TP=2 FP=2 FN=2 precision=0.50 recall=0.50 cost=16
0.40: TP=3 FP=2 FN=1 precision=0.60 recall=0.75 cost=11
0.30: TP=3 FP=3 FN=1 precision=0.50 recall=0.75 cost=14

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

При пороге 0,4 из пяти отправленных на проверку устройств три действительно сломались: precision = 3/5. Всего в выборке сломались четыре, модель нашла три: recall = 3/4. Эти дроби помогают объяснить результат людям, которые планируют выезды, а стоимость добавляет к ним цену каждого типа ошибки.

Где выбирать порог, а где оценивать результат

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

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

Здесь оценки score используются как ранжирование риска. Если хотите читать 0,4 как «40% вероятности поломки», отдельно проверьте калибровку модели. Для выбора направления обучения есть разбор курсов ML после Python, а о том, как малая выборка влияет на уверенность в доле, — пример с доверительным интервалом.

От порога к полноценному эксперименту

В курсе «Классическое машинное обучение: алгоритмы и практика на Python» вы сравните модели на одинаковом разбиении, настроите подготовку признаков в Pipeline и разберёте ROC- и PR-кривые, калибровку и стоимость ошибок. В проекте классификации прогноз отказа устройства доводится до выбора порога и финальной проверки. Откройте программу и пробный урок, чтобы пройти весь путь от данных и модели до решения, которое можно объяснить сервисной команде.