Собесов

Сценарий ML: выбор threshold по бизнес-метрике

ML / Data ScienceМетрики и оценкаСредняяMiddle

Условие

Модель fraud-детекции с предсказанной вероятностью. Bool FN = пропущенный фрод = 5000 ₽ убытка. FP = ложный блок = 50 ₽ убытка (поддержка + злой клиент). На каком пороге запускать?

Решение

Подход

Оптимизируем expected cost, не F1 / AUC. Cost-sensitive threshold:

Cost(t) = C_FN · FN(t) + C_FP · FP(t)

В пересчёте на одного позитива:

Cost = C_FN · (1 − recall(t)) · P + C_FP · (1 − precision(t)) · positives_predicted(t)

Или эквивалентно через cost-aware decision:

Predict positive iff p(x) > C_FP / (C_FN + C_FP)

В нашем случае: t* = 50 / (5000 + 50) ≈ 0.0099. То есть блокируем при p > ~1%.

Реализация

import numpy as np
 
def optimal_threshold(y_true, y_prob, c_fn=5000, c_fp=50):
    thresholds = np.linspace(0.001, 0.999, 999)
    costs = []
    for t in thresholds:
        pred = (y_prob >= t).astype(int)
        fn = ((pred == 0) & (y_true == 1)).sum()
        fp = ((pred == 1) & (y_true == 0)).sum()
        costs.append(c_fn*fn + c_fp*fp)
    best_t = thresholds[np.argmin(costs)]
    return best_t, min(costs)
 
# Алгебраический shortcut (для калиброванной модели):
t_star = c_fp / (c_fn + c_fp)

Когда shortcut работает

  • Модель калибрована (предсказывает истинные вероятности).
  • Costs линейны (не зависят от величины ошибки).
  • Не учитываем opportunity cost (например, удерживаем клиента).

Если модель не калибрована — используйте grid search.

Метрики для отчёта

  • Precision@t, Recall@t.
  • Confusion matrix в денежных единицах.
  • Expected cost per transaction.
  • Saved fraud / cost of investigation.
def business_report(y_true, y_prob, t, c_fn, c_fp):
    pred = (y_prob >= t).astype(int)
    fn = ((pred == 0) & (y_true == 1)).sum()
    fp = ((pred == 1) & (y_true == 0)).sum()
    tp = ((pred == 1) & (y_true == 1)).sum()
    return {
        'threshold': t,
        'precision': tp / (tp + fp),
        'recall': tp / (tp + fn),
        'expected_cost_per_tx': (c_fn*fn + c_fp*fp) / len(y_true),
        'fraud_saved_rub': c_fn * tp,
    }

Подводные камни

  1. F1 / accuracy игнорируют асимметрию costs — не используйте на cost-sensitive задачах.
  2. Калибровка обязательна для shortcut формулы. Без неё — grid search.
  3. Stationarity: cost_FN может меняться сезонно (праздники → выше) — пересчитывайте threshold.
  4. Multi-class: формула расширяется до cost matrix, выбираем класс с минимальным expected cost.
  5. Если threshold очень малый (1%), precision крошечная — большая нагрузка на support. Это бизнес-trade-off.

Эталонный ответ

Threshold = argmin expected_cost = C_FP / (C_FN + C_FP) для калиброванной модели. Иначе grid search. F1 не подходит для асимметричных cost. Здесь t ≈ 1%, что даёт максимум сохранённого фрода — но большой precision tradeoff.*

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти