← Назад к вопросам
Как работает ROC-кривая?
1.3 Junior🔥 201 комментариев
#Машинное обучение#Метрики и оценка моделей
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
ROC-кривая: как она работает и что она показывает
ROC (Receiver Operating Characteristic) — одна из самых важных метрик в классификации. Расскажу как она устроена, почему она так полезна, и как её интерпретировать.
Основная идея
РОК-кривая показывает trade-off между ловлей положительных примеров и случайными срабатываниями при разных порогах решения.
TPR (True Positive Rate)
↑
1 | /
| /
0.8 / ← ROC-кривая
| /
0.5|/________
|/
0 |________→ FPR (False Positive Rate)
0 0.5 1
Ключевые компоненты
TPR (True Positive Rate) = Recall
Из всех положительных примеров, сколько мы правильно предсказали:
TPR = TP / (TP + FN)
# Пример:
# 100 больных людей
# Модель правильно определила 80 → TPR = 80/100 = 0.80
FPR (False Positive Rate) = 1 - Specificity
Из всех отрицательных примеров, сколько мы неправильно предсказали как положительные:
FPR = FP / (FP + TN)
# Пример:
# 100 здоровых людей
# Модель неправильно определила 10 как больных → FPR = 10/100 = 0.10
Как строится ROC-кривая
Модель выдаёт вероятности, а не жёсткие классы. Изменяя порог решения (threshold), мы получаем разные TPR и FPR.
from sklearn.metrics import roc_curve, roc_auc_score
import numpy as np
import matplotlib.pyplot as plt
# Истинные метки
y_true = np.array([1, 1, 1, 1, 0, 0, 0, 0])
# Вероятности модели
y_proba = np.array([0.9, 0.8, 0.7, 0.3, 0.6, 0.5, 0.2, 0.1])
print('Истинные метки (1=болезнь, 0=здоров):')
print(y_true)
print('\nВероятности модели (вероятность болезни):')
print(y_proba)
print('\n' + '='*70)
print('ВЫЧИСЛЯЕМ TPR И FPR ДЛЯ РАЗНЫХ ПОРОГОВ')
print('='*70)
thresholds = [0.9, 0.8, 0.7, 0.6, 0.5, 0.3, 0.2, 0.0]
for threshold in thresholds:
# Предсказываем: 1 если вероятность >= threshold
y_pred = (y_proba >= threshold).astype(int)
# Вычисляем TP, FP, TN, FN
TP = ((y_true == 1) & (y_pred == 1)).sum()
FP = ((y_true == 0) & (y_pred == 1)).sum()
TN = ((y_true == 0) & (y_pred == 0)).sum()
FN = ((y_true == 1) & (y_pred == 0)).sum()
TPR = TP / (TP + FN) if (TP + FN) > 0 else 0
FPR = FP / (FP + TN) if (FP + TN) > 0 else 0
print(f'\nПороговое значение: {threshold}')
print(f' Предсказания: {y_pred} → TP={TP}, FP={FP}, TN={TN}, FN={FN}')
print(f' TPR (чувствительность): {TPR:.2f}')
print(f' FPR (ложные срабатывания): {FPR:.2f}')
Вывод:
- При низком пороге (0.1): много положительных предсказаний → TPR↑, FPR↑
- При высоком пороге (0.9): мало положительных предсказаний → TPR↓, FPR↓
Визуализация ROC-кривой
from sklearn.metrics import roc_curve, auc
import matplotlib.pyplot as plt
# Используем встроенную функцию
fpr, tpr, thresholds = roc_curve(y_true, y_proba)
auc_score = auc(fpr, tpr)
print('FPR значения:', fpr)
print('TPR значения:', tpr)
print('\nПороги для каждой точки:')
for f, t, th in zip(fpr, tpr, thresholds):
print(f' FPR={f:.2f}, TPR={t:.2f}, Threshold={th:.2f}')
# Рисуем
plt.figure(figsize=(10, 8))
plt.plot(fpr, tpr, marker='o', linewidth=2, markersize=8, label=f'ROC (AUC = {auc_score:.3f})')
plt.plot([0, 1], [0, 1], 'k--', label='Random Classifier (AUC = 0.5)')
plt.xlabel('False Positive Rate (чем меньше - лучше)', fontsize=12)
plt.ylabel('True Positive Rate (чем больше - лучше)', fontsize=12)
plt.title('ROC-кривая', fontsize=14)
plt.legend(fontsize=11)
plt.grid(alpha=0.3)
plt.xlim([-0.05, 1.05])
plt.ylim([-0.05, 1.05])
plt.show()
Интерпретация ROC-кривой
TPR
1 | Идеальная модель (верхний левый угол)
| /|
|/ | ← Хорошая модель
0.5| |
| | ← Средняя модель
| |
0 |___|________
0 0.5 1
↑
Случайная (диагональ)
Три примера:
# МОДЕЛЬ 1: Идеальная
y_true_1 = np.array([1, 1, 1, 1, 0, 0, 0, 0])
y_proba_1 = np.array([1.0, 0.9, 0.8, 0.7, 0.1, 0.0, 0.0, 0.0]) # Идеально разделены!
# МОДЕЛЬ 2: Хорошая
y_proba_2 = np.array([0.9, 0.8, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1]) # Хороший порядок
# МОДЕЛЬ 3: Случайная
y_proba_3 = np.random.rand(8) # Случайные значения
for name, proba in [('Идеальная', y_proba_1), ('Хорошая', y_proba_2), ('Случайная', y_proba_3)]:
auc_score = roc_auc_score(y_true_1, proba)
print(f'{name:12} → AUC = {auc_score:.3f}')
AUC (Area Under the Curve)
Это площадь под ROC-кривой. Она имеет красивую интерпретацию:
# AUC = вероятность того, что модель правильно рангирует
# случайно выбранный положительный пример выше, чем отрицательный
from sklearn.metrics import roc_auc_score
y_true = np.array([1, 1, 0, 0])
y_proba = np.array([0.8, 0.6, 0.4, 0.2]) # Модель хорошо разделяет
auc = roc_auc_score(y_true, y_proba)
print(f'AUC = {auc}') # 1.0 (идеально)
# Визуально:
# Все положительные примеры (0.8, 0.6) выше, чем отрицательные (0.4, 0.2)
# Пары: (0.8 > 0.4), (0.8 > 0.2), (0.6 > 0.4), (0.6 > 0.2)
# Все 4 пары правильны → AUC = 4/4 = 1.0
Интерпретация AUC:
AUC = 1.0 → Идеальная модель (невозможно в реальности)
AUC = 0.9 → Отличная модель (используй её!)
AUC = 0.8 → Хорошая модель (приемлемо)
AUC = 0.7 → Приемлемая модель (на границе)
AUC = 0.5 → Случайный классификатор (не используй)
AUC < 0.5 → Хуже случайного (проверь код!)
Практический пример: медицинская диагностика
from sklearn.metrics import roc_curve, roc_auc_score, confusion_matrix
import matplotlib.pyplot as plt
# Тесты на заболевание
y_true = np.array([ # 0 = здоров, 1 = болен
1, 1, 1, 1, 1, # 5 больных
0, 0, 0, 0, 0 # 5 здоровых
])
y_proba = np.array([
0.95, 0.88, 0.75, 0.62, 0.51, # Больные (большие вероятности)
0.40, 0.35, 0.22, 0.15, 0.05 # Здоровые (малые вероятности)
])
# Вычислим для разных порогов
fpr, tpr, thresholds = roc_curve(y_true, y_proba)
auc_score = roc_auc_score(y_true, y_proba)
print('='*70)
print('АНАЛИЗ ПОРОГОВ ДЛЯ МЕДИЦИНСКОГО ТЕСТА')
print('='*70)
for threshold in [0.3, 0.5, 0.7]:
y_pred = (y_proba >= threshold).astype(int)
tn, fp, fn, tp = confusion_matrix(y_true, y_pred).ravel()
sensitivity = tp / (tp + fn) # TPR: «поймали ли больных?»
specificity = tn / (tn + fp) # 1-FPR: «не обидели ли здоровых?»
ppv = tp / (tp + fp) # Precision: «сколько предсказаний верны?»
print(f'\nПороговое значение: {threshold}')
print(f' Чувствительность (TPR): {sensitivity:.1%} — ловим {int(tp)} из {tp+fn} больных')
print(f' Специфичность (1-FPR): {specificity:.1%} — не беспокоим {int(tn)} из {tn+fp} здоровых')
print(f' Положительное предсказание (PPV): {ppv:.1%} — верны {int(tp)} из {tp+fp} тестов')
print(f'\n\nAUC-ROC = {auc_score:.3f}\n')
# Визуализация
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(fpr, tpr, 'b-', linewidth=2, label=f'Наш тест (AUC={auc_score:.3f})')
plt.plot([0, 1], [0, 1], 'r--', label='Случайный тест (AUC=0.5)')
plt.fill_between(fpr, tpr, alpha=0.2)
plt.xlabel('FPR (доля ложных срабатываний)')
plt.ylabel('TPR (доля правильных срабатываний)')
plt.title('ROC-кривая медицинского теста')
plt.legend()
plt.grid(alpha=0.3)
plt.subplot(1, 2, 2)
plt.plot(thresholds, tpr[:len(thresholds)], 'b-', marker='o', label='TPR (ловим больных)')
plt.plot(thresholds, 1-fpr[:len(thresholds)], 'g-', marker='s', label='Specificity (не беспокоим здоровых)')
plt.xlabel('Пороговое значение')
plt.ylabel('Процент')
plt.title('Sensitivity vs Specificity')
plt.legend()
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()
ROC vs PR-кривая
Когда что использовать?
# ROC-кривая: независима от дисбаланса
# Хороша когда классы примерно одинаковые (50-50)
# PR-кривая: более чувствительна к редкому классу
# Хороша когда большой дисбаланс (95%-5%)
from sklearn.metrics import precision_recall_curve, auc
precision, recall, _ = precision_recall_curve(y_true, y_proba)
pr_auc = auc(recall, precision)
print(f'ROC-AUC: {auc_score:.3f}')
print(f'PR-AUC: {pr_auc:.3f}')
print(f'\nEсли датасет дисбалансирован → используй PR-AUC')
print(f'Если данные относительно сбалансированы → используй ROC-AUC')
Выбор оптимального порога
РОК-кривая помогает выбрать лучший порог:
from sklearn.metrics import f1_score
# Для максимизации F1-score
f1_scores = []
for threshold in thresholds:
y_pred = (y_proba >= threshold).astype(int)
f1 = f1_score(y_true, y_pred)
f1_scores.append(f1)
best_threshold = thresholds[np.argmax(f1_scores)]
print(f'Оптимальный порог для F1: {best_threshold:.2f}')
# Для максимизации Youden's Index (TPR - FPR)
youden = tpr - fpr
best_idx = np.argmax(youden)
best_threshold_youden = thresholds[best_idx]
print(f'Оптимальный порог (Youden): {best_threshold_youden:.2f}')
Реальный пример: fraud detection
# Базовая история
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_curve, auc, confusion_matrix
# Финансовые транзакции (95% обычные, 5% мошеннические)
X, y = make_classification(
n_samples=10000,
weights=[0.95, 0.05],
n_features=20,
n_informative=10,
random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
# Модель
model = LogisticRegression()
model.fit(X_train, y_train)
y_proba_test = model.predict_proba(X_test)[:, 1]
# ROC-кривая
fpr, tpr, thresholds = roc_curve(y_test, y_proba_test)
auc_score = auc(fpr, tpr)
print('FRAUD DETECTION СИСТЕМА')
print(f'AUC-ROC: {auc_score:.3f}')
print(f'\nДля разных стратегий:')
# Стратегия 1: Ловим много, но false positives
thresh_aggressive = 0.2
y_pred = (y_proba_test >= thresh_aggressive).astype(int)
tn, fp, fn, tp = confusion_matrix(y_test, y_pred).ravel()
print(f'\nАгрессивная (порог={thresh_aggressive}): Ловим {tp}/{tp+fn} мошеннических, ложно срабатываем на {fp} честных')
# Стратегия 2: Консервативная
thresh_conservative = 0.7
y_pred = (y_proba_test >= thresh_conservative).astype(int)
tn, fp, fn, tp = confusion_matrix(y_test, y_pred).ravel()
print(f'Консервативная (порог={thresh_conservative}): Ловим {tp}/{tp+fn} мошеннических, ложно срабатываем на {fp} честных')
Вывод
ROC-кривая показывает:
- Как меняется качество при разных порогах решения
- Trade-off между ловлей положительного класса и ложными срабатываниями
- AUC как общий показатель качества модели
Используй ROC для:
- Сравнения разных моделей
- Выбора оптимального порога
- Оценки качества на сбалансированных данных
Помни:
- ROC-кривая всегда монотонна (растёт слева направо)
- Идеальная кривая идёт через точку (0, 1)
- Случайная модель — диагональная линия
- AUC интерпретируется как вероятность правильного ранжирования