πŸ”ΉΠšΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠ° вСроятностСй#

ΠŸΠΎΡΡ‚Π°Π½ΠΎΠ²ΠΊΠ° ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹#

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

from sklearn.preprocessing import StandardScaler
from sklearn.metrics import log_loss, brier_score_loss, roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.svm import LinearSVC
from sklearn.linear_model import LogisticRegression
from sklearn.calibration import CalibratedClassifierCV, calibration_curve
from scipy.special import expit

sns.set_style("whitegrid")
plt.rcParams.update({"font.size": 14})

Π’ этом Π·Π°Π΄Π°Π½ΠΈΠΈ ΠΌΡ‹ Π±ΡƒΠ΄Π΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Π΄Π°Π½Π½Ρ‹Π΅ ΠΎ Π±ΠΈΠ½Π°Ρ€Π½ΠΎΠΉ классификации риса.

import kagglehub

path = (
    kagglehub.dataset_download("mssmartypants/rice-type-classification")
    +
    "/riceClassification.csv"
)


data = pd.read_csv(path)
data.head()
id Area MajorAxisLength MinorAxisLength Eccentricity ConvexArea EquivDiameter Extent Perimeter Roundness AspectRation Class
0 1 4537 92.229316 64.012769 0.719916 4677 76.004525 0.657536 273.085 0.764510 1.440796 1
1 2 2872 74.691881 51.400454 0.725553 3015 60.471018 0.713009 208.317 0.831658 1.453137 1
2 3 3048 76.293164 52.043491 0.731211 3132 62.296341 0.759153 210.012 0.868434 1.465950 1
3 4 3073 77.033628 51.928487 0.738639 3157 62.551300 0.783529 210.657 0.870203 1.483456 1
4 5 3693 85.124785 56.374021 0.749282 3802 68.571668 0.769375 230.332 0.874743 1.510000 1

ΠžΡ‚ΠΌΠ°ΡΡˆΡ‚Π°Π±ΠΈΡ€ΡƒΠ΅ΠΌ Π΄Π°Π½Π½Ρ‹Π΅ ΠΈ Ρ€Π°Π·Π΄Π΅Π»ΠΈΠΌ Π½Π° ΠΎΠ±ΡƒΡ‡Π΅Π½ΠΈΠ΅, Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΡŽ (Π½Π° Π½Π΅ΠΉ ΠΌΡ‹ Π±ΡƒΠ΄Π΅ΠΌ ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²Π°Ρ‚ΡŒ вСроятности) ΠΈ тСст.

X = data.drop(columns=["id", "Class"])
y = data.Class

X_train, X_temp, y_train, y_temp = train_test_split(
    X, y, test_size=0.40, random_state=999, stratify=y
)


X_cal, X_test, y_cal, y_test = train_test_split(
    X_temp, y_temp, test_size=0.50, random_state=999, stratify=y_temp
)


scaler = StandardScaler().fit(X_train)
X_train = scaler.transform(X_train)
X_cal = scaler.transform(X_cal)
X_test = scaler.transform(X_test)

ΠŸΠΎΡΠΌΠΎΡ‚Ρ€ΠΈΠΌ Π½Π° баланс классов Π² Π΄Π°Π½Π½Ρ‹Ρ….

print("Class balance:", y_train.mean())
Class balance: 0.5490789111905416

ΠšΠ»Π°ΡΡΡ‹ ΠΌΠΎΠΆΠ½ΠΎ Π½Π°Π·Π²Π°Ρ‚ΡŒ сбалансированными. ΠžΠ±ΡƒΡ‡ΠΈΠΌ ΠΌΠ΅Ρ‚ΠΎΠ΄ ΠΎΠΏΠΎΡ€Π½Ρ‹Ρ… Π²Π΅ΠΊΡ‚ΠΎΡ€ΠΎΠ² (SVC β€” Support Vector Classification) ΠΈ Π»ΠΎΠ³ΠΈΡΡ‚ΠΈΡ‡Π΅ΡΠΊΡƒΡŽ Ρ€Π΅Π³Ρ€Π΅ΡΡΠΈΡŽ, Π² качСствС ΠΌΠ΅Ρ‚Ρ€ΠΈΠΊΠΈ возьмСм ROC-AUC.

Для SVC Π² качСствС скоров Π±ΡƒΠ΄Π΅ΠΌ Ρ€Π°ΡΡΠΌΠ°Ρ‚Ρ€ΠΈΠ²Π°Ρ‚ΡŒ Π²Ρ‹Ρ…ΠΎΠ΄ decision_function, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΏΡ€ΠΎΠΏΠΎΡ€Ρ†ΠΈΠΎΠ½Π°Π»Π΅Π½ Ρ€Π°ΡΡΡ‚ΠΎΡΠ½ΠΈΡŽ Π΄ΠΎ Ρ€Π°Π·Π΄Π΅Π»ΡΡŽΡ‰Π΅ΠΉ гипСрплоскости, взятого со Π·Π½Π°ΠΊΠΎΠΌ. Π’Π°ΠΊ ΠΊΠ°ΠΊ расстояниС ΠΏΠΎ ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½ΠΈΡŽ ΠΏΡ€ΠΈΠ½Π°Π΄Π»Π΅ΠΆΠΈΡ‚ всСм вСщСствСнным значСниям, Ρ‚ΠΎ Ρ‡Ρ‚ΠΎΠ±Ρ‹ значСния ΠΎΡ‚Ρ€Π°ΠΆΠ°Π»ΠΈ вСроятности ΠΌΡ‹ ΠΏΠ΅Ρ€Π΅Π²Π΅Π΄Π΅ΠΌ ΠΈΡ… Π² ΠΏΡ€ΠΎΠΌΠ΅ΠΆΡƒΡ‚ΠΎΠΊ \([0, 1]\) ΠΌΠ°ΡΡˆΡ‚Π°Π±ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ Ρ‡Π΅Ρ€Π΅Π· ΠΌΠΈΠ½ΠΈΠΌΡƒΠΌ-максимум ΠΈΠ»ΠΈ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠ΅ΠΉ сигмоиды.

Π’Π°ΠΆΠ½ΠΎ! Для Π½Π°ΡˆΠΈΡ… Ρ†Π΅Π»Π΅ΠΉ Π½ΡƒΠΆΠ΅Π½ ΠΈΠΌΠ΅Π½Π½ΠΎ LinearSVC, Π° Π½Π΅ SVC!. Π’ послСднСм ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠ° ΡƒΠΆΠ΅ дСлаСтся ΠΏΠΎ-ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ.

Для логистичСской рСгрСссии Π±ΡƒΠ΄Π΅ΠΌ сразу ΠΏΡ€ΠΎΠ³Π½ΠΎΠ·ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ вСроятности.

svc = LinearSVC(max_iter=100000, C=0.1).fit(X_train, y_train)
svc_pred = svc.decision_function(X_test)
svc_pred = (svc_pred - svc_pred.min()) / (svc_pred.max() - svc_pred.min())

print("SVC ROC-AUC:", roc_auc_score(y_test, svc_pred))
SVC ROC-AUC: 0.9994134494424565
lr = LogisticRegression(max_iter=100000, C=0.1).fit(X_train, y_train)
lr_pred = lr.predict_proba(X_test)[:, 1]  # Π²Π΅Ρ€ΠΎΡΡ‚Π½ΠΎΡΡ‚ΡŒ ΠΏΠΎΠ»ΠΎΠΆΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠ³ΠΎ класса
print("Logistic regression ROC-AUC:", roc_auc_score(y_test, lr_pred))
Logistic regression ROC-AUC: 0.9992525373425992

ROC-AUC ΠΏΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚, Ρ‡Ρ‚ΠΎ ΠΌΡ‹ практичСски идСально прСдсказываСм Ρ†Π΅Π»Π΅Π²ΡƒΡŽ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½ΡƒΡŽ. ΠŸΠΎΡΠΌΠΎΡ‚Ρ€ΠΈΠΌ Ρ‚Π΅ΠΏΠ΅Ρ€ΡŒ Π½Π° распрСдСлСниС скоров для тСстовых ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ².

fig, axs = plt.subplots(1, 2, figsize=(10, 5))

axs[0].hist(svc_pred, bins=20, color="blue", density="True")
axs[1].hist(lr_pred, bins=20, color="orange", density="True")

axs[0].set_title("SVC")
axs[1].set_title("Logistic regression")

plt.suptitle("Outputs distribution")
plt.show()
../_images/5db607a03bf0449ca4a875e451e733eac25852c1637d39ea04a7610e85adbfc7.png

По ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½ΠΈΡŽ, ΠΌΡ‹ Π½Π°Π·Ρ‹Π²Π°Π΅ΠΌ ΠΎΡ‚ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²Π°Π½Π½Ρ‹ΠΌΠΈ ΠΏΡ€ΠΎΠ³Π½ΠΎΠ·Ρ‹ \(\hat{y}\), Ρ‚Π°ΠΊΠΈΠ΅ Ρ‡Ρ‚ΠΎ:

  • Ссли ΠΌΡ‹ зафиксируСм ΡΠΏΡ€ΠΎΠ³Π½ΠΎΠ·ΠΈΡ€ΠΎΠ²Π°Π½Π½ΡƒΡŽ Π²Π΅Ρ€ΠΎΡΡ‚Π½ΠΎΡΡ‚ΡŒ \(\hat{p}\) ΠΈ возьмСм всС ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Ρ‹ с этой Π²Π΅Ρ€ΠΎΡΡ‚Π½ΠΎΡΡ‚ΡŒΡŽ, Ρ‚ΠΎ срСди Π½ΠΈΡ… \(\hat{p}*100\) ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ² Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ Π±ΡƒΠ΄ΡƒΡ‚ ΠΏΡ€ΠΈΠ½Π°Π΄Π»Π΅ΠΆΠ°Ρ‚ΡŒ ΠΏΠΎΠ»ΠΎΠΆΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠΌΡƒ классу.

ΠœΡ‹ Π²ΠΈΠ΄ΠΈΠΌ, Ρ‡Ρ‚ΠΎ β€œΡƒΠ²Π΅Ρ€Π΅Π½Π½ΠΎΡΡ‚ΡŒβ€ логистичСской рСгрСссии ΠΏΠΎ классам сильнСС, Ρ‚ΠΎΠ³Π΄Π° ΠΊΠ°ΠΊ для SVC вСроятности ΠΏΠΎΠ»ΠΎΠΆΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠ³ΠΎ класса ΠΈΠΌΠ΅ΡŽΡ‚ Ρ€Π°Π·Π½Ρ‹Π΅ значСния. ΠŸΠΎΡΠΌΠΎΡ‚Ρ€ΠΈΠΌ, ΠΊΠ°ΠΊ это отраТаСтся Π½Π° ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΎΡ‡Π½Ρ‹Ρ… ΠΊΡ€ΠΈΠ²Ρ‹Ρ….

plt.figure(figsize=(10, 5))

svc_true_prob, svc_pred_prob = calibration_curve(y_test, svc_pred, n_bins=15)
lr_true_prob, lr_pred_prob = calibration_curve(y_test, lr_pred, n_bins=15)

plt.plot(svc_pred_prob, svc_true_prob, label="SVC", color="blue")
plt.plot(lr_pred_prob, lr_true_prob, label="LR", color="orange")
plt.plot([0, 1], [0, 1], label="Perfect", linestyle="--", color="green")

plt.xlabel("Mean predicted probability")
plt.ylabel("Fraction of positives")
plt.title("Calibration curves")
plt.legend()
plt.show()
../_images/fcc4d30be83b6a904faa0543c7736e9b7468b80641d1d70cf1cc6720107c0deb.png

ΠšΡ€ΠΈΠ²Π°Ρ для логистичСской рСгрСссии ΠΏΡ€ΠΈΠ±Π»ΠΈΠΆΠ°Π΅Ρ‚ диагональ Π»ΡƒΡ‡ΡˆΠ΅, Ρ‡Π΅ΠΌ кривая SVC. Π§Ρ‚ΠΎΠ±Ρ‹ ΠΏΠΎΠ½ΡΡ‚ΡŒ, ΠΏΠΎΡ‡Π΅ΠΌΡƒ Ρ‚Π°ΠΊ, рассмотрим ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΡƒΡŽ Ρ‚ΠΎΡ‡ΠΊΡƒ β€”Β 0.6.

Для всСх ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ², Π²Π΅Ρ€ΠΎΡΡ‚Π½ΠΎΡΡ‚ΡŒ ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… Π±Ρ‹Ρ‚ΡŒ ΠΏΡ€ΠΈΠ½Π°Π΄Π»Π΅ΠΆΠ°Ρ‰ΠΈΠΌΠΈ классу 1 Ρ€Π°Π²Π½Π° 0.6:

  • для SVC - 100% ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ² ΠΏΡ€ΠΈΠ½Π°Π΄Π»Π΅ΠΆΠ°Ρ‚ ΠΏΠΎΠ»ΠΎΠΆΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠΌΡƒ классу;

  • для LR - 50% ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ² ΠΏΡ€ΠΈΠ½Π°Π΄Π»Π΅ΠΆΠ°Ρ‚ ΠΏΠΎΠ»ΠΎΠΆΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠΌΡƒ классу.

ΠŸΠΎΠΏΡ€ΠΎΠ±ΡƒΠ΅ΠΌ ΠΎΡ‚ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²Π°Ρ‚ΡŒ классификаторы ΠΈ ΠΏΠΎΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ Π½ΠΎΠ²Ρ‹Π΅ ΠΊΡ€ΠΈΠ²Ρ‹Π΅.

Говоря Π² Ρ‚Π΅Ρ€ΠΌΠΈΠ½Π°Ρ… событий:

  • ΠΊΠΎΠ³Π΄Π° SVC ΡƒΠ²Π΅Ρ€Π΅Π½ Π² событии Π½Π° 60%, ΠΎΠ½ΠΎ всСгда происходит;

  • ΠΊΠΎΠ³Π΄Π° LR ΡƒΠ²Π΅Ρ€Π΅Π½Π½Π° Π² событии Π½Π° 60%, ΠΎΠ½ΠΎ происходит Π² 50% случаСв.

Π‘Π»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ, Π²ΠΈΠ΄Π½Π° ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΠ°

ОбС ΠΌΠΎΠ΄Π΅Π»ΠΈ ΠΈΠΌΠ΅ΡŽΡ‚ Ρ…ΠΎΡ€ΠΎΡˆΡƒΡŽ ΠΏΡ€Π΅Π΄ΡΠΊΠ°Π·Π°Ρ‚Π΅Π»ΡŒΠ½ΡƒΡŽ Ρ‚ΠΎΡ‡Π½ΠΎΡΡ‚ΡŒ, Π½ΠΎ соотвСтствиС ΠΈΡ… вСроятностСй эмпиричСским Π΄Π°Π½Π½Ρ‹ΠΌ Ρ€Π°Π·Π»ΠΈΡ‡Π½ΠΎ. ИмСнно эту ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΡƒ ΠΌΡ‹ Π±ΡƒΠ΄Π΅ΠΌ ΡƒΠ±ΠΈΡ€Π°Ρ‚ΡŒ ΠΌΠ΅Ρ‚ΠΎΠ΄Π°ΠΌΠΈ ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠΈ.

Π§Ρ‚ΠΎΠ±Ρ‹ Π»ΡƒΡ‡ΡˆΠ΅ ΠΎΡ†Π΅Π½ΠΈΡ‚ΡŒ Ρ‡ΡƒΠ²ΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠΈ ΠΌΠΎΠ΄Π΅Π»ΠΈ ΠΊ эмпиричСским вСроятностям, рассмотрим синтСтичСский ΠΏΡ€ΠΈΠΌΠ΅Ρ€ с Ρ‚Π΅ΠΌΠΈ ΠΆΠ΅ Π΄Π°Π½Π½Ρ‹ΠΌΠΈ Π² условиях дисбаланса.

minority_class = 0
majority_class = 1

X_min = X[y == minority_class]
X_maj = X[y == majority_class]
y_min = y[y == minority_class]
y_maj = y[y == majority_class]

# Π’ΠΎΠ·ΡŒΠΌΡ‘ΠΌ ΠΌΠ°Π»ΠΎ класса 0 ΠΈ ΠΌΠ½ΠΎΠ³ΠΎ класса 1
X_sub = pd.concat(
    [X_min.sample(frac=0.3, random_state=42), X_maj.sample(frac=1.0, random_state=42)]
)
y_sub = pd.concat(
    [y_min.sample(frac=0.3, random_state=42), y_maj.sample(frac=1.0, random_state=42)]
)


# Train Test
X_train_imb, X_test_imb, y_train_imb, y_test_imb = train_test_split(
    X_sub, y_sub, test_size=0.4, random_state=42, stratify=None
)

# ΠœΠ°ΡΡˆΡ‚Π°Π±ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅
scaler = StandardScaler().fit(X_train_imb)
X_train_imb = scaler.transform(X_train_imb)
X_test_imb = scaler.transform(X_test_imb)

# ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅ΠΌ распрСдСлСниС классов
print("Class distributions")
display(
    pd.concat(
        [
            y_train_imb.value_counts(normalize=True),
            y_test_imb.value_counts(normalize=True),
        ],
        axis=1,
        keys=["Train", "Test"],
    )
)
Class distributions
Train Test
Class
1 0.804473 0.799116
0 0.195527 0.200884

Π‘ΡƒΠ΄Π΅ΠΌ Π·Π°ΠΏΠΈΡΡ‹Π²Π°Ρ‚ΡŒ всС ΠΌΠ΅Ρ‚Ρ€ΠΈΠΊΠΈ Π² Π΄Π°Ρ‚Π°Ρ„Ρ€Π΅ΠΉΠΌ для красоты.

cal_metrics = []
probas = []
# ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅ΠΌ ΠΌΠΎΠ΄Π΅Π»ΠΈ
svc_imb = LinearSVC(max_iter=100000, C=0.1).fit(X_train_imb, y_train_imb)
svc_pred_imb = svc_imb.decision_function(X_test_imb)
svc_pred_imb = (
    (svc_pred_imb - svc_pred_imb.min())
    /
    (svc_pred_imb.max() - svc_pred_imb.min())
)

lr_imb = LogisticRegression(max_iter=100000, C=0.1).fit(X_train_imb, y_train_imb)
lr_pred_imb = lr_imb.predict_proba(X_test_imb)[:, 1]  # Π’Π΅Ρ€ΠΎΡΡ‚Π½ΠΎΡΡ‚ΡŒ ΠΏΠΎΠ»ΠΎΠΆΠΈΡ‚Π΅Π»Π½ΡŒΠΎΠ³ΠΎ класса

cal_metrics.extend(
    [
        {
            "model": "SVC",
            "metric": "ROC-AUC",
            "calibration": "None",
            "score": roc_auc_score(y_test_imb, svc_pred_imb),
        },
        {
            "model": "LogReg",
            "metric": "ROC-AUC",
            "calibration": "None",
            "score": roc_auc_score(y_test_imb, lr_pred_imb),
        },
    ]
)

probas.extend(
    [
        {"model": "SVC", "calibration": "None", "proba": svc_pred},
        {"model": "LogReg", "calibration": "None", "proba": lr_pred},
    ]
)
pd.DataFrame(cal_metrics)
model metric calibration score
0 SVC ROC-AUC None 0.998224
1 LogReg ROC-AUC None 0.998038

ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ ΠΏΠΎ AUC ΠΎΡΡ‚Π°Π»ΠΎΡΡŒ Π½Π΅ΠΈΠ·ΠΌΠ΅Π½Π½ΠΎ Ρ…ΠΎΡ€ΠΎΡˆΠΈΠΌ. Π’Π΅ΠΏΠ΅Ρ€ΡŒ посмотрим Π½Π° вСроятности ΠΈ ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΎΡ‡Π½Ρ‹Π΅ ΠΊΡ€ΠΈΠ²Ρ‹Π΅ зная, Ρ‡Ρ‚ΠΎ наши эмприричСскиС частоты Π² тСстовой Π²Ρ‹Π±ΠΎΡ€ΠΊΠ΅ Ρ‚Π°ΠΊΠΎΠ²Ρ‹:

  • 1 - 0.799116

  • 0 - 0.200884

fig, axs = plt.subplots(1, 3, figsize=(20, 6))

svc_true_prob_imb, svc_pred_prob_imb = calibration_curve(
    y_test_imb, svc_pred_imb, n_bins=15
)
lr_true_prob_imb, lr_pred_prob_imb = calibration_curve(
    y_test_imb, lr_pred_imb, n_bins=15
)

axs[0].hist(svc_pred_imb, bins=20, color="blue", density="True")
axs[1].hist(lr_pred_imb, bins=20, color="orange", density="True")
axs[2].plot(svc_pred_prob_imb, svc_true_prob_imb, label="SVC", color="blue")
axs[2].plot(lr_pred_prob_imb, lr_true_prob_imb, label="LR", color="orange")
axs[2].plot([0, 1], [0, 1], label="Perfect", linestyle="--", color="green")


axs[0].set_title("SVC outputs")
axs[1].set_title("Logistic regression outputs")
axs[2].set_title("Calibration curves")

plt.show()
../_images/70571bbb0967b56e3ba212080c59262f9edc61243d6a9fa96877788adfd58d5d.png

Битуация Π°Π½Π°Π»ΠΎΠ³ΠΈΡ‡Π½Π° сбалансированному ΡΠ»ΡƒΡ‡Π°ΡŽ, с ΠΏΠΎΠΏΡ€Π°Π²ΠΊΠΎΠΉ Π½Π° эмпиричСскиС частоты.

ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²Π΅Π½Π½Π°Ρ ΠΎΡ†Π΅Π½ΠΊΠ° ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠΈ: Log Loss, Brier Score#

Как Π²ΠΈΠ΄Π½ΠΎ Π²Ρ‹ΡˆΠ΅, roc-auc Π½Π΅ Π΄Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ качСствС ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠΈ вСроятностСй. Π§Ρ‚ΠΎΠ±Ρ‹ Π½Π΅ ΠΏΡ€ΠΈΠ±Π΅Π³Π°Ρ‚ΡŒ всСгда ΠΊ Π²ΠΈΠ·ΡƒΠ°Π»ΡŒΠ½ΠΎΠΉ ΠΎΡ†Π΅Π½ΠΊΠ΅ (хотя это наглядно), ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ количСствСнныС ΠΌΠ΅Ρ‚Ρ€ΠΈΠΊΠΈ. ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΈΠ· Ρ‚Π°ΠΊΠΈΡ… β€”Β Log Loss ΠΈ Brier Score.

LogLoss

\[ L = -\frac{1}{n}\sum_{i=1}^n \Big(y_i \log p_i + (1-y_i)\log (1-p_i)\Big), \]
  • Π’ идСальном случаС Log Loss Ρ€Π°Π²Π½Π° 0.

  • Π’ Ρ…ΡƒΠ΄ΡˆΠ΅ΠΌ случаС Log Loss стрСмится ΠΊ \(∞\).

Brier Score

\[ \mbox{Brier_score} = \frac{1}{n} \left(\sum_{i} (y_i-p_i)^2 \right) \]
  • Π’ Ρ…ΡƒΠ΄ΡˆΠ΅ΠΌ случаС Brier score Ρ€Π°Π²Π½Π° 1.

  • Π’ Π»ΡƒΡ‡ΡˆΠ΅ΠΌ случаС Brier score \(\to\) 0.

cal_metrics.extend(
    [
        {
            "model": "LogReg",
            "metric": "BrierScore",
            "calibration": "None",
            "score": brier_score_loss(y_test, lr_pred),
        },
        {
            "model": "SVC",
            "metric": "BrierScore",
            "calibration": "None",
            "score": brier_score_loss(y_test, svc_pred),
        },
        {
            "model": "LogReg",
            "metric": "LogLoss",
            "calibration": "None",
            "score": log_loss(y_test, lr_pred),
        },
        {
            "model": "SVC",
            "metric": "LogLoss",
            "calibration": "None",
            "score": log_loss(y_test, svc_pred),
        },
    ]
)
metrics = pd.DataFrame(cal_metrics)
metrics[metrics.metric.isin(["BrierScore", "LogLoss"])]
model metric calibration score
2 LogReg BrierScore None 0.009350
3 SVC BrierScore None 0.099743
4 LogReg LogLoss None 0.035032
5 SVC LogLoss None 0.364177

Как ΠΌΠΎΠΆΠ½ΠΎ Π·Π°ΠΌΠ΅Ρ‚ΠΈΡ‚ΡŒ, SVC Π½Π° порядок ΠΏΡ€ΠΎΠΈΠ³Ρ€Ρ‹Π²Π°Π΅Ρ‚ ΠΏΠΎ ΠΎΠ±Π΅ΠΈΠΌ ΠΌΠ΅Ρ‚Ρ€ΠΈΠΊΠ°ΠΌ.

ΠœΠ΅Ρ‚ΠΎΠ΄Ρ‹ ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠΈ#

ΠšΠ»Π°ΡΡΠΈΡ‡Π΅ΡΠΊΠΈΠΌΠΈ ΠΌΠ΅Ρ‚ΠΎΠ΄Π°ΠΌΠΈ ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠΈ ΡΠ²Π»ΡΡŽΡ‚ΡΡ ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠ° ΠŸΠ»Π°Ρ‚Ρ‚Π° (сигмоидная) ΠΈ изотоничСская рСгрСссия. Оба этих ΠΌΠ΅Ρ‚ΠΎΠ΄Π° Ρ€Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Π½Ρ‹ Π² sklearn.

ΠšΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠ° ΠŸΠ»Π°Ρ‚Ρ‚Π°

Допустим, Ρƒ нас Π΅ΡΡ‚ΡŒ ΠΎΠ±ΡƒΡ‡Π΅Π½Π½Ρ‹ΠΉ класификатор \(b(x)\), ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π²Ρ‹Π΄Π°Π΅Ρ‚ ΡƒΠ²Π΅Ρ€Π΅Π½Π½ΠΎΡΡ‚ΡŒ (скор) Π² Ρ‚ΠΎΠΌ, Ρ‡Ρ‚ΠΎ \(x\) относится ΠΊ ΠΏΠΎΠ»ΠΎΠΆΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠΌΡƒ классу. ΠœΠ΅Ρ‚ΠΎΠ΄ ΠΏΡ€ΠΈΠ±Π»ΠΈΠΆΠ°Π΅Ρ‚ Π²Π΅Ρ€ΠΎΡΡ‚Π½ΠΎΡΡ‚ΡŒ ΠΏΠΎΠ»ΠΎΠΆΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠ³ΠΎ класса с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ сигмоидной Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ:

\[ p\big(y = +1 \big| b(x)\big) = \frac{1}{1 + \exp (A \cdot b(x) + C)} \]

Π—Π΄Π΅ΡΡŒ ΠΎΠ±ΡƒΡ‡Π°Π΅ΠΌΡ‹ΠΌΠΈ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Π°ΠΌΠΈ ΡΠ²Π»ΡΡŽΡ‚ΡΡ \(A, C \in \mathbb{R}\), ΠΈΡ… ΠΏΠΎΠ΄Π±ΠΈΡ€Π°ΡŽΡ‚ с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ ΠΌΠ΅Ρ‚ΠΎΠ΄Π° максимального правдоподобия (Ρ‚ΠΎΡ‡Π½ΠΎ Ρ‚Π°ΠΊ ΠΆΠ΅, ΠΊΠ°ΠΊ Π² логистичСской рСгрСссии). Π‘Π΄Π΅Π»Π°Ρ‚ΡŒ это ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎ кросс-Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ ΠΈΠ»ΠΈ Π½Π° ΠΎΡ‚Π»ΠΎΠΆΠ΅Π½Π½ΠΎΠΉ Π²Ρ‹Π±ΠΎΡ€ΠΊΠ΅.

Π˜Π·ΠΎΡ‚ΠΎΠ½ΠΈΡ‡Π΅ΡΠΊΠ°Ρ рСгрСссия

Π”Ρ€ΡƒΠ³ΠΎΠΉ ΠΌΠ΅Ρ‚ΠΎΠ΄ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ Ρ‚Π°ΠΊ Π½Π°Π·Ρ‹Π²Π°Π΅ΠΌΡƒΡŽ ΠΈΠ·ΠΎΡ‚ΠΎΠ½ΠΈΡ‡Π΅ΡΠΊΡƒΡŽ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ β€” кусочно-Π»ΠΈΠ½Π΅ΠΉΠ½ΡƒΡŽ Π²ΠΎΠ·Ρ€Π°ΡΡ‚Π°ΡŽΡ‰ΡƒΡŽ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ \(f: \mathbb{R} \rightarrow \mathbb{R}\). Ѐункция подбираСтся Ρ‚Π°ΠΊ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΌΠΈΠ½ΠΈΠΌΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ MSE ΠΏΠΎ Π²Ρ‹Π±ΠΎΡ€ΠΊΠ΅:

\[ \frac{1}{\ell} \sum_{i=1}^\ell \big(y_i - f(b(x_i))\big)^2 \rightarrow \min_f \]

Π­Ρ‚ΠΎΡ‚ ΠΌΠ΅Ρ‚ΠΎΠ΄ склонСн ΠΊ ΠΏΠ΅Ρ€Π΅ΠΎΠ±ΡƒΡ‡Π΅Π½ΠΈΡŽ, поэтому Π΅Π³ΠΎ рСкомСндуСтся ΠΏΡ€ΠΈΠΌΠ΅Π½ΡΡ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ для Π±ΠΎΠ»ΡŒΡˆΠΈΡ… Π²Ρ‹Π±ΠΎΡ€ΠΎΠΊ.

ΠšΡ€ΠΎΠΌΠ΅ Ρ‚ΠΎΠ³ΠΎ, ΠΌΠΎΠΆΠ΅Ρ‚ Π½Π΅ Π±Ρ‹Ρ‚ΡŒ большого смысла ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²Π°Ρ‚ΡŒ Π»ΠΎΠ³ΠΈΡΡ‚ΠΈΡ‡Π΅ΡΠΊΡƒΡŽ Ρ€Π΅Π³Ρ€Π΅ΡΡΠΈΡŽ, Π½ΠΎ ΠΌΡ‹ ΠΏΡ€ΠΎΠ²Π΅Π΄Π΅ΠΌ нСбольшой экспСримСнт.

ΠšΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΡƒ проводят Π½Π° ΠΎΡ‚Π»ΠΎΠΆΠ΅Π½Π½ΠΎΠΉ Π²Ρ‹Π±ΠΎΡ€ΠΊΠ΅, Π²Ρ‹Π΄Π΅Π»Π΅Π½Π½ΠΎΠΉ ΠΏΠΎΠ΄ Π½Π΅Ρ‘. Иногда присутствуСт ΠΏΡ€Π°ΠΊΡ‚ΠΈΠΊΠ° ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠΈ Π½Π° Ρ‚Ρ€Π΅Π½ΠΈΡ€ΠΎΠ²ΠΎΡ‡Π½Ρ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ…. ΠœΡ‹ ΠΎΡ‚Π»ΠΎΠΆΠΈΠ»ΠΈ Π²Ρ‹Π±ΠΎΡ€ΠΊΡƒ Π²Ρ‹ΡˆΠ΅, Ρ‚Π°ΠΊ Ρ‡Ρ‚ΠΎ Π±ΡƒΠ΄Π΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Π΅Ρ‘.

Дляя Π°Π½Π°Π»ΠΈΠ·Π°, посмотрим Π½Π° ΠΌΠ΅Ρ‚Ρ€ΠΈΠΊΠΈ, Π² Ρ‚ΠΎΠΌ числС Ρ‚Π΅, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΠΎΡ‚ вСроятностСй Π½Π΅ зависят ΠΈ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΈΠΌ, Π΅ΡΡ‚ΡŒ Π»ΠΈ Π½Π°Π±Π»ΡŽΠ΄Π°Π΅ΠΌΡ‹ΠΉ эффСкт

def log_metric(model_name, method, y_test, calibrated_model_proba):

    cal_metrics.extend(
        [
            {
                "model": model_name,
                "metric": "ROC-AUC",
                "calibration": method,
                "score": roc_auc_score(y_test, calibrated_model_proba),
            },
            {
                "model": model_name,
                "metric": "LogLoss",
                "calibration": method,
                "score": log_loss(y_test, calibrated_model_proba),
            },
            {
                "model": model_name,
                "metric": "BrierScore",
                "calibration": method,
                "score": brier_score_loss(y_test, calibrated_model_proba),
            },
        ]
    )

    probas.extend(
        [
            {
                "model": model_name,
                "calibration": method,
                "proba": calibrated_model_proba,
            },
        ]
    )
for model, model_name in zip([svc, lr], ("SVC", "LogReg")):
    for method in ["Sigmoid", "Isotonic"]:

        calibrated_model = CalibratedClassifierCV(model, cv=3, method=method.lower()).fit(X_cal, y_cal)
        calibrated_model_proba = calibrated_model.predict_proba(X_test)[:, 1]

        log_metric(model_name, method, y_test, calibrated_model_proba)
metrics_df = pd.DataFrame(cal_metrics)
probas_df = pd.DataFrame(probas)

print("SVC Metrics")
metrics_df[metrics_df.model == "SVC"].pivot(
    columns=["calibration"], index=["metric"], values=["score"]
)["score"][["None", "Sigmoid", "Isotonic"]]
SVC Metrics
calibration None Sigmoid Isotonic
metric
BrierScore 0.099743 0.008806 0.008632
LogLoss 0.364177 0.032401 0.030417
ROC-AUC 0.998224 0.999347 0.999328

Из интСрСсного здСсь ΡΠ»Π΅Π΄ΡƒΡŽΡ‰Π΅Π΅:

  • ROC-AUC практичСски Π½Π΅ измСнился ΠΏΡ€ΠΈ ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠ΅, нСсмотря Π½Π° Ρ‚ΠΎ Ρ‡Ρ‚ΠΎ эта ΠΌΠ΅Ρ‚Ρ€ΠΈΠΊΠ° ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ вСроятности. ΠŸΡ€ΠΈΡ‡ΠΈΠ½Π° Π² Ρ‚ΠΎΠΌ, Ρ‡Ρ‚ΠΎ ROC-AUC зависит Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΎΡ‚ порядка прСдсказаний (Ρ€Π°Π½Π³ΠΎΠ²), Π° ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠ° β€” это ΠΌΠΎΠ½ΠΎΡ‚ΠΎΠ½Π½ΠΎΠ΅ ΠΏΡ€Π΅ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Π½ΠΈΠ΅ вСроятностСй. Π‘Π»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ, ΠΎΡ‚Π½ΠΎΡΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ порядок ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ² Π½Π΅ мСняСтся, ΠΈ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΌΠ΅Ρ‚Ρ€ΠΈΠΊΠΈ остаётся ΠΏΡ€Π΅ΠΆΠ½ΠΈΠΌ. Π’ΠΎ ΠΆΠ΅ самоС Π±ΡƒΠ΄Π΅Ρ‚ Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½ΠΎ для Π»ΡŽΠ±Ρ‹Ρ… ΠΌΠ΅Ρ‚Ρ€ΠΈΠΊ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΠΎΠΏΠΈΡ€Π°ΡŽΡ‚ΡΡ ΠΈΡΠΊΠ»ΡŽΡ‡ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ Π½Π° ΠΌΠ΅Ρ‚ΠΊΠΈ классов ΠΈΠ»ΠΈ порядок вСроятностСй, Π° Π½Π΅ Π½Π° ΠΈΡ… Π°Π±ΡΠΎΠ»ΡŽΡ‚Π½Ρ‹Π΅ значСния.

  • ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠ° ΠŸΠ»Π°Ρ‚Ρ‚Π° сильно ΡƒΠ»ΡƒΡ‡ΡˆΠΈΠ»Π° ΠΈ BrierScore, ΠΈ LogLoss

  • изотоничСская рСгрСссия Ρ‚ΠΎΠΆΠ΅, ΠΏΡ€ΠΈΡ‡Π΅ΠΌ ΠΏΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ Π»ΡƒΡ‡ΡˆΠ΅Π΅ качСство, хотя ΠΈ Π½Π΅Π·Π½Π°Ρ‡ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ

Π’Π΅ΠΏΠ΅Ρ€ΡŒ взглянСм Π½Π° Π»ΠΎΠ³ΠΈΡΡ‚ΠΈΡ‡Π΅ΡΠΊΡƒΡŽ Ρ€Π΅Π³Ρ€Π΅ΡΡΠΈΡŽ

print("LogReg Metrics")
metrics_df[metrics_df.model == "LogReg"].pivot(
    columns=["calibration"], index=["metric"], values=["score"]
)["score"][["None", "Sigmoid", "Isotonic"]]
LogReg Metrics
calibration None Sigmoid Isotonic
metric
BrierScore 0.009350 0.009643 0.009460
LogLoss 0.035032 0.035662 0.035123
ROC-AUC 0.998038 0.999182 0.999133

Как Π²ΠΈΠ΄ΠΈΠΌ, Ρ‚ΡƒΡ‚ практичСски Π½Π΅Ρ‚ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ, Ρ‡Ρ‚ΠΎ Π² ROC-AUC, Ρ‡Ρ‚ΠΎ Π² Π΄Ρ€ΡƒΠ³ΠΈΡ… ΠΌΠ΅Ρ‚Ρ€ΠΈΠΊΠ°Ρ… ΠΏΠΎΡΠΊΠΎΠ»ΡŒΠΊΡƒ модСль ΠΈΠ·Π½Π°Ρ‡Π°ΡŒΠ½ΠΎ Π½Π΅ΠΏΠ»ΠΎΡ…ΠΎ ΠΎΡ†Π΅Π½ΠΈΠ²Π°Π΅Ρ‚ вСроятности.

ΠŸΠΎΡΡ‚Ρ€ΠΎΠΈΠΌ Π½ΠΎΠ²Ρ‹Π΅ ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΎΡ‡Π½Ρ‹Π΅ ΠΊΡ€ΠΈΠ²Ρ‹Π΅:

fig, axs = plt.subplots(1, 3, figsize=(16, 5))

for idx, calibration in enumerate(["None", "Sigmoid", "Isotonic"]):

    for model in ["SVC", "LogReg"]:

        true_prob, pred_prob = calibration_curve(
            y_test,
            probas_df[(probas_df.model == model) & (probas_df.calibration == calibration)]["proba"].item(),
            n_bins=15,
        )

        axs[idx].plot(pred_prob, true_prob, label=model)

    axs[idx].plot([0, 1], [0, 1], label="Perfect", linestyle="--", color="green")
    if calibration == "None":
        calibration = "No"
    axs[idx].set_title(f"{calibration} Calibration")

for ax in axs:
    ax.set_xlabel("Mean predicted probability")
    ax.set_ylabel("Fraction of positives")
    ax.legend()

plt.show()
../_images/953ee231710c02ecee88fa69a896261c38c27e7a33f0fd8a105297cb37774637.png

Как ΠΌΡ‹ Π²ΠΈΠ΄ΠΈΠΌ, ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠ° ΠŸΠ»Π°Ρ‚Ρ‚Π° Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ ΡƒΠ»ΡƒΡ‡ΡˆΠΈΠ»Π° вСроятности, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΠΏΠΎΠ»ΡƒΡ‡Π°ΡŽΡ‚ΡΡ Ρƒ SVC ΠΈ сильнСС ΠΏΡ€ΠΈΠ±Π»ΠΈΠ·ΠΈΠ»Π° ΠΊ ΠΈΠ΄Π΅Π°Π»ΡŒΠ½Ρ‹ΠΌ вСроятности логистичСской ΠΌΠΎΠ΄Π΅Π»ΠΈ.

Π’ Ρ‚ΠΎ ΠΆΠ΅ врСмя изотоничСская рСгрСссия ΡƒΠ»ΡƒΡ‡ΡˆΠΈΠ»Π° вСроятности SVC, Π½ΠΎ ΡƒΡ…ΡƒΠ΄ΡˆΠΈΠ»Π° ΠΊΡ€ΠΈΠ²ΡƒΡŽ логистичСской рСгрСссии. Π­Ρ‚ΠΎ ΠΌΠΎΠΆΠ΅Ρ‚ ΡΠΈΠ³Π½Π°Π»ΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΠΎ Ρ‚ΠΎΠΌ, Ρ‡Ρ‚ΠΎ ΠΌΠ΅Ρ‚ΠΎΠ΄ Π½Π΅ΠΌΠ½ΠΎΠ³ΠΎ пСрСобучился, Ρ…ΠΎΡ‚ΡŒ Ρƒ нас ΠΈ Π±Ρ‹Π»Π° достаточно большая Π²Ρ‹Π±ΠΎΡ€ΠΊΠ°.

ΠœΡƒΠ»ΡŒΡ‚ΠΈΠΊΠ»Π°ΡΡΠΎΠ²Ρ‹ΠΉ случай#

Π’ Π·Π°Π΄Π°Ρ‡Π°Ρ… с Π±ΠΎΠ»Π΅Π΅ Ρ‡Π΅ΠΌ двумя классами ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΠ° ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠΈ модифицируСтся. А ΠΈΠΌΠ΅Π½Π½ΠΎ, Ρ‚Π΅ΠΏΠ΅Ρ€ΡŒ Π½Π°ΠΌ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½Ρ‹Π΅ вСроятности для всСх ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€ΠΈΠΉ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΡΡƒΠΌΠΌΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒΡΡ Π² Π΅Π΄ΠΈΠ½ΠΈΡ†Ρƒ. Один ΠΈΠ· ΠΏΠΎΠ΄Ρ…ΠΎΠ΄ΠΎΠ² ΠΊ ΠΌΡƒΠ»ΡŒΡ‚ΠΈΠΊΠ»Π°ΡΡΠΎΠ²ΠΎΠΉ ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠ΅ β€” свСдСниС Π·Π°Π΄Π°Ρ‡ΠΈ ΠΊ Π½Π°Π±ΠΎΡ€Ρƒ Π±ΠΈΠ½Π°Ρ€Π½Ρ‹Ρ… классификаций с ΠΏΠΎΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠΌ объСдинСниСм Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ².

БущСствуСт нСсколько стандартных способов разлоТСния многоклассовой Π·Π°Π΄Π°Ρ‡ΠΈ Π½Π° Π±ΠΈΠ½Π°Ρ€Π½Ρ‹Π΅ ΠΏΠΎΠ΄Π·Π°Π΄Π°Ρ‡ΠΈ. Π‘Π°Π·ΠΎΠ²Ρ‹Π΅:

  • One-vs-One (all pairs). Для классов \(c_1, c_2, ..., c_n\) строим \(\frac{n(n-1)}{2}\) классификаторов ΠΈ ΠΊΠ°Π»ΠΈΠ±Ρ€ΡƒΠ΅ΠΌ ΠΊΠ°ΠΆΠ΄Ρ‹ΠΉ ΠΈΠ· Π½ΠΈΡ….

  • One-vs-All: для \(n\) классов ΠΎΠ±ΡƒΡ‡Π°Π΅ΠΌ \(n\) классификаторов Β«ΠΎΠ΄ΠΈΠ½ класс ΠΏΡ€ΠΎΡ‚ΠΈΠ² всСх ΠΎΡΡ‚Π°Π»ΡŒΠ½Ρ‹Ρ…Β».

Π’ sklearn ΠΈ Π΄Ρ€ΡƒΠ³ΠΈΡ… Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ°Ρ… (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, SplineCalib) Ρ€Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Π½Π° ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠ° Π½Π° основС One-vs-All.

ΠŸΠΎΡΠΌΠΎΡ‚Ρ€ΠΈΠΌ Π½Π° ΠΏΡ€ΠΈΠΌΠ΅Ρ€. Π§Ρ‚ΠΎΠ±Ρ‹ ΠΏΠ΅Ρ€Π΅ΠΉΡ‚ΠΈ ΠΊ многоклассовой классификации β€” ΡƒΠΉΠ΄Π΅ΠΌ ΠΎΡ‚ риса ΠΈ возьмСм ирисы. Π’ качСствС классификатора возьмСм случайный лСс, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Ρ‚Π°ΠΊΠΆΠ΅ ΠΌΠΎΠΆΠ΅Ρ‚ ΠΈΠ· ΠΊΠΎΡ€ΠΎΠ±ΠΊΠΈ Π΄Π°Π²Π°Ρ‚ΡŒ вСроятости.

multiclass_results = []
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import label_binarize

X_multiclass, y_multiclass = load_iris(return_X_y=True)

X_train_multiclass, X_temp_multiclass, y_train_multiclass, y_temp_multiclass = (
    train_test_split(
        X_multiclass,
        y_multiclass,
        test_size=0.4,
        random_state=42,
        stratify=y_multiclass,
    )
)
X_valid_multiclass, X_test_multiclass, y_valid_multiclass, y_test_multiclass = (
    train_test_split(
        X_temp_multiclass,
        y_temp_multiclass,
        test_size=0.5,
        random_state=42,
        stratify=y_temp_multiclass,
    )
)

clf = RandomForestClassifier(random_state=999, max_depth=1)

# ΠΎΠ±ΡƒΡ‡Π°Π΅ΠΌ Π±Π΅Π· ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠΈ
clf.fit(X_train_multiclass, y_train_multiclass)
probs_base = clf.predict_proba(X_test_multiclass)

multiclass_results.extend(
    [
        {
            "Calibration": None,
            "LogLoss": log_loss(y_test_multiclass, probs_base),
            "BrierScore": brier_score_loss(
                label_binarize(y_test_multiclass, classes=[0, 1, 2]).ravel(),
                probs_base.ravel(),
            ),
        }
    ]
)

pd.DataFrame(multiclass_results)
Calibration LogLoss BrierScore
0 None 0.477278 0.098811

ΠŸΠΎΡΠΌΠΎΡ‚Ρ€ΠΈΠΌ Π½Π° эмприричСскиС распрСдСлСния Π² Π΄Π°Π½Π½Ρ‹Ρ….

for y, name in zip(
    [y_train_multiclass, y_valid_multiclass, y_test_multiclass],
    ["Train", "Calibration", "Test"],
):
    data = pd.DataFrame(np.unique(y, return_counts=True))
    print(name)
    display(data)
    print("\n")
Train
0 1 2
0 0 1 2
1 30 30 30
Calibration
0 1 2
0 0 1 2
1 10 10 10
Test
0 1 2
0 0 1 2
1 10 10 10

ΠšΠ»Π°ΡΡΡ‹ идСально сбалансированны. Как выглядят вСроятности лСса?

import matplotlib.pyplot as plt

probas_unc_multiclass = clf.predict_proba(X_test_multiclass)
n_classes = probas_unc_multiclass.shape[1]

fig, axes = plt.subplots(1, n_classes, figsize=(14, 4), sharey=True)

for i in range(n_classes):
    axes[i].hist(probas_unc_multiclass[:, i], bins=10, alpha=0.7, color=f"C{i}")
    axes[i].set_title(f"Класс {i}", fontsize=15)
    axes[i].set_xlabel("Π’Π΅Ρ€ΠΎΡΡ‚Π½ΠΎΡΡ‚ΡŒ", fontsize=10)

    axes[i].axvline(x=0.0, color="black", linestyle="--", linewidth=1)
    axes[i].axvline(x=1.0, color="black", linestyle="--", linewidth=1)

    axes[i].set_xlim(-0.05, 1.05)

    if i == 0:
        axes[i].set_ylabel("Частота", fontsize=15)
../_images/2aeecdcccc63de898cf350d5b26f130a0702a373eec3646cb8c8ee65908f0b87.png

Π’Π΅ΠΏΠ΅Ρ€ΡŒ Π²Ρ‹ΠΏΠΎΠ»Π½ΠΈΠΌ ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΡƒ.

from sklearn.frozen import FrozenEstimator

cal_clf = CalibratedClassifierCV(FrozenEstimator(clf), method="sigmoid")
cal_clf.fit(X_valid_multiclass, y_valid_multiclass)

probs_cal = cal_clf.predict_proba(X_test_multiclass)


multiclass_results.extend(
    [
        {
            "Calibration": "sigmoid",
            "LogLoss": log_loss(y_test_multiclass, probs_cal),
            "BrierScore": brier_score_loss(
                label_binarize(y_test_multiclass, classes=[0, 1, 2]).ravel(),
                probs_cal.ravel(),
            ),
        }
    ]
)


pd.DataFrame(multiclass_results)
Calibration LogLoss BrierScore
0 None 0.477278 0.098811
1 sigmoid 0.377704 0.074093
import matplotlib.pyplot as plt

n_classes = probas_unc_multiclass.shape[1]

fig, axes = plt.subplots(1, n_classes, figsize=(14, 4), sharey=True)

for i in range(n_classes):
    axes[i].hist(probs_cal[:, i], bins=10, alpha=0.7, color=f"C{i}")
    axes[i].set_title(f"Класс {i}", fontsize=15)
    axes[i].set_xlabel("Π’Π΅Ρ€ΠΎΡΡ‚Π½ΠΎΡΡ‚ΡŒ", fontsize=10)

    axes[i].axvline(x=0.0, color="black", linestyle="--", linewidth=1)
    axes[i].axvline(x=1.0, color="black", linestyle="--", linewidth=1)

    axes[i].set_xlim(-0.05, 1.05)

    if i == 0:
        axes[i].set_ylabel("Частота", fontsize=15)
../_images/f708667b36d436c48dc778f679be3cc9ec4a5c53e0333b6d759b2a690cf21124.png

ΠšΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠ° Π½Π΅Π·Π½Π°Ρ‡ΠΈΠΌΠΎ, Π½ΠΎ ΡƒΠ»ΡƒΡ‡ΡˆΠΈΠ»Π° вСроятности. Если ΠΎΡ‚ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Π° трСбуСтся ΠΈΠ΄Π΅Π°Π», Ρ‚ΠΎ, ΠΊΠ°ΠΊ ΠΌΡ‹ упомянули Π² сСминарС, стоит Ρ€Π°ΡΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ использованиС ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΠΈΠ·Π½Π°Ρ‡Π°Π»ΡŒΠ½ΠΎ строят многоклассовоС распрСдСлСниС.

Π’Π°ΠΊΠΆΠ΅ ΠΎΠ±Ρ€Π°Ρ‚ΠΈΡ‚Π΅ Π²Π½ΠΈΠΌΠ°Π½ΠΈΠ΅, Ρ‡Ρ‚ΠΎ распрСдСлСниС вСроятностСй, Π΄Π°ΠΆΠ΅ послС ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠΈ, Π½Π΅ всСгда ΠΈΠΌΠ΅Π΅Ρ‚ значСния, Π±Π»ΠΈΠ·ΠΊΠΈΠ΅ ΠΊ Π΅Π΄ΠΈΠ½ΠΈΡ†Π΅ Π² ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½Ρ‹Ρ… классах. Π”Π°ΠΆΠ΅ Π² случаС Π±ΠΈΠ½Π°Ρ€Π½ΠΎΠΉ классификации ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠ° Π½Π΅Ρ€Π΅Π΄ΠΊΠΎ ΠΏΡ€ΠΈΠ²ΠΎΠ΄ΠΈΡ‚ ΠΊ ситуации, ΠΊΠΎΠ³Π΄Π°, максимальная Π²Π΅Ρ€ΠΎΡΡ‚Π½ΠΎΡΡ‚ΡŒ ΠΏΠΎΠ»ΠΎΠΆΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠ³ΠΎ класса, выдаваСмая ΠΎΡ‚ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²Π°Π½Π½ΠΎΠΉ модСлью, мСньшС 1. Π­Ρ‚ΠΎ Π½Π΅ Π±Π°Π³, Π° Ρ„ΠΈΡ‡Π°, которая лишь Π³ΠΎΠ²ΠΎΡ€ΠΈΡ‚ ΠΎ Ρ‚ΠΎΠΌ, Ρ‡Ρ‚ΠΎ Π΄Π°ΠΆΠ΅ скалиброванная модСль Π½Π΅ Π±Ρ‹Π²Π°Π΅Ρ‚ ΡƒΠ²Π΅Ρ€Π΅Π½Π° Π½Π° 100% ΠΏΡ€ΠΎΡ†Π΅Π½Ρ‚ΠΎΠ² ΠΈ слСдуСт ΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Π² Ρ‚ΠΎΠΌ числС Π½Π° Π΄Ρ€ΡƒΠ³ΠΈΠ΅ Π½Π΅ ΠΌΠ΅Π½Π΅Π΅ Π²Π°ΠΆΠ½Ρ‹Π΅ ΠΌΠ΅Ρ‚Ρ€ΠΈΠΊΠΈ

ИдСальноС ΠΏΡ€ΠΈΠ±Π»ΠΈΠΆΠ΅Π½ΠΈΠ΅#

Π’ Ρ…ΠΎΠ΄Π΅ сСминара ΠΌΡ‹ Π½Π°Π·Ρ‹Π²Π°Π»ΠΈ ΠΏΡ€ΠΈΠ΅ΠΌΠ»Π΅ΠΌΡ‹ΠΌΠΈ Π΄Π°ΠΆΠ΅ Ρ‚Π΅ случаи, Π³Π΄Π΅ Π±Ρ‹Π»ΠΈ отклонСния ΠΎΡ‚ идСальной ΠΊΡ€ΠΈΠ²ΠΎΠΉ. ΠŸΡ€ΠΈΡ‡ΠΈΠ½Π° кроСтся Π² Π΄ΠΎΠ²Π΅Ρ€ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠΌ ΠΈΠ½Ρ‚Π΅Ρ€Π²Π°Π»Π΅ - это допустимый разброс, Π² ΠΏΡ€Π΅Π΄Π΅Π»Π°Ρ… ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ³ΠΎ прСдсказанныС вСроятности всё Π΅Ρ‰Ρ‘ ΡΡ‡ΠΈΡ‚Π°ΡŽΡ‚ΡΡ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½Ρ‹ΠΌΠΈ.

Быстро ΠΎΡ†Π΅Π½ΠΈΡ‚ΡŒ Π΄ΠΎΠ²Π΅Ρ€ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ ΠΈΠ½Ρ‚Π΅Ρ€Π²Π°Π» ΠΌΠΎΠΆΠ½ΠΎ с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠΈ ml_insights. Π₯отя этот инструмСнт ΠΈ Π½Π΅ Π½ΠΎΠ²Ρ‹ΠΉ, для нашСй Π·Π°Π΄Π°Ρ‡ΠΈ ΠΎΠ½ ΠΎΡ‚Π»ΠΈΡ‡Π½ΠΎ ΠΏΠΎΠ΄ΠΎΠΉΠ΄Ρ‘Ρ‚. Π•ΡΡ‚ΡŒ Ρ‚Π°ΠΊΠΆΠ΅ Π°Π»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π° Π² Π²ΠΈΠ΄Π΅ Π±ΠΎΠ»Π΅Π΅ ΠΌΠΎΠ΄Π½ΠΎΠ³ΠΎ relplot ΠΎΡ‚ ΠΊΠΎΠΌΠΏΠ°Π½ΠΈΠΈ Apple

!pip install -qU ml_insights relplot
WARNING: Ignoring invalid distribution ~lotly (/usr/local/lib/python3.12/dist-packages)
WARNING: Ignoring invalid distribution ~lotly (/usr/local/lib/python3.12/dist-packages)

Π’ MLI Π΅ΡΡ‚ΡŒ Π΄Π²Π° способа рассчСта Π”Π˜, ΠΎΡ‚Π½ΠΎΡΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ Ρ‚ΠΎΡ‡ΠΊΠΈ (ci_ref='point') ΠΈ ΠΎΡ‚Π½ΠΎΡΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ идСальной оси (ci_ref='axis').

ΠŸΠ΅Ρ€Π²Π°Ρ стратСгия ci_ref='point' ΠΏΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚:

  • с Π²Π΅Ρ€ΠΎΡΡ‚Π½ΠΎΡΡ‚ΡŒΡŽ \(1-\alpha\) эмпиричСская Π²Π΅Ρ€ΠΎΡΡ‚Π½ΠΎΡΡ‚ΡŒ ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ Π² этих Π΄ΠΈΠΏΠ°Π·ΠΎΠ½Π°Ρ…. Π’ этом случаС смотрим, ΠΏΠΎΠΏΠ°Π΄Π°Π΅Ρ‚ Π»ΠΈ Π”Π˜ Π½Π° ΠΈΠ΄Π΅Π°Π»ΡŒΠ½ΡƒΡŽ ΠΊΡ€ΠΈΠ²ΡƒΡŽ;

Вторая стратСгия ci_ref='axis' ΠΏΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚:

  • с Π²Π΅Ρ€ΠΎΡΡ‚Π½ΠΎΡΡ‚ΡŒΡŽ \(1-\alpha\) эмпиричСская частота Π»Π΅ΠΆΠΈΡ‚ Π² этом ΠΈΠ½Ρ‚Π΅Ρ€Π²Π°Π»Π΅. Π’ этом случаС смотрим, ΠΏΠΎΠΏΠ°Π΄Π°Π΅Ρ‚ Π»ΠΈ Ρ‚ΠΎΡ‡ΠΊΠ° Π² Π”Π˜.

import ml_insights as mli

plt.figure(figsize=(14, 5))
fig = mli.plot_reliability_diagram(
    y_test, lr_pred, show_histogram=True, ci_ref="axis", error_bar_alpha=0.05
)
../_images/e3db593025d0d5b53ec8a6d0042999815feffbcf5a6354b814686c7c5fa4613f.png

Как Π²ΠΈΠ΄ΠΈΠΌ, для рСгрСссии ΠΏΡ€ΠΎΠ³Π½ΠΎΠ·Ρ‹ (всС Ρ‚ΠΎΡ‡ΠΊΠΈ) находятся Π² ΠΎΠΆΠΈΠ΄Π°Π΅ΠΌΡ‹Ρ… Π”Π˜.

plt.figure(figsize=(14, 5))
fig = mli.plot_reliability_diagram(
    y_test, svc_pred, show_histogram=True, ci_ref="axis", error_bar_alpha=0.05
)
../_images/bb6f732ece34b5ba956a50da3d17415f8dc12085d2ca96b2b9488e70eddd676e.png

Для SVC это ΡƒΠΆΠ΅ Π½Π΅ Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½ΠΎ.

Π’ relplot Π΅ΡΡ‚ΡŒ ΠΊΠ°ΠΊ ΠΏΡ€ΠΈΠ²Ρ‹Ρ‡Π½Ρ‹Π΅ ΠΎΡ†Π΅Π½ΠΊΠΈ, Ρ‚Π°ΠΊ ΠΈ Π±ΠΎΠ»Π΅Π΅ интСрСсныС, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, бинаризованная калибровочная кривая, сглаТСнная ΠΈ Π΄Ρ€ΡƒΠ³ΠΈΠ΅, ΠΎ ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… ΠΌΠΎΠΆΠ΅Ρ‚Π΅ ΠΏΠΎΡ‡ΠΈΡ‚Π°Ρ‚ΡŒ Π² Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°Ρ†ΠΈΠΈ

import relplot.diagrams as rd
from relplot.estimators import Binning
from sklearn.model_selection import GridSearchCV


def plot_compare(f, y, **kwargs):
    fig, axs = plt.subplots(1, 2, figsize=(15, 6), sharey=True)
    f, y = map(lambda x: np.array(x, dtype=np.double).reshape(-1).copy(), [f, y])

    plot_params = dict(
        split_densities=False,
        plot_density_ticks=True,
        plot_density=True,
        plot_confidence_band=False,
    )
    if kwargs is not None:
        plot_params.update(kwargs)
    num_bootstrap = 500

    binning = GridSearchCV(
        Binning(),
        param_grid={"bins_cnt": range(10, 25, 1)},
        cv=5,
        scoring="neg_mean_squared_error",
        verbose=1,
    )
    binning.fit(f.reshape(-1, 1), y - f)
    nbins = binning.best_params_["bins_cnt"]
    # binning = Binning(15)

    _ = rd.rel_diagram_binned(f, y, fig=fig, ax=axs[0], nbins=nbins)
    axs[0].set_title("BinnedECE")

    _ = rd.rel_diagram(
        f, y, fig=fig, ax=axs[1], num_bootstrap=num_bootstrap, **plot_params
    )
    axs[1].set_title("SmoothECE")
    return fig, axs
fig, axs = plot_compare(lr_pred, y_test)
Fitting 5 folds for each of 15 candidates, totalling 75 fits
../_images/73d5c884972f539236d1d32df615be195a284f4e0684cba9e8381a47eca608e3.png
fig, axs = plot_compare(svc_pred, y_test)
Fitting 5 folds for each of 15 candidates, totalling 75 fits
../_images/ce6657ac4b980f1f0f6fec4cdf31fc800c52a4cad312e41b5685b11f1e981f01.png

Π”Ρ€ΡƒΠ³ΠΈΠ΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠΈ для ознакомлСния#

На ΠΏΡ€Π°ΠΊΡ‚ΠΈΠΊΠ΅ часто достаточно изотоничСской рСгрСссии ΠΈΠ»ΠΈ ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠΈ ΠŸΠ»Π°Ρ‚Ρ‚Π°. Однако, Ссли ΠΎΠ½ΠΈ ΠΏΠΎΠΊΠ°Π·Ρ‹Π²Π°ΡŽΡ‚ Π½Π΅ΡƒΠ΄ΠΎΠ²Π»Π΅Ρ‚Π²ΠΎΡ€ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ Π½Π° Π΄Π°Π½Π½Ρ‹Ρ…, ΠΌΠΎΠΆΠ½ΠΎ Ρ‚Π°ΠΊΠΆΠ΅ Ρ€Π°ΡΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Π΄Ρ€ΡƒΠ³ΠΈΠ΅ Π²Π°Ρ€ΠΈΠ°Π½Ρ‚Ρ‹:

  1. Beta Calubration, которая, ΠΊΠ°ΠΊ ΠΌΡ‹ ΠΊΡ€Π°Ρ‚ΠΊΠΎ обсудили Π² Π»Π΅ΠΊΡ†ΠΈΠΎΠ½Π½ΠΎΠΌ ΠΌΠ°Ρ‚Π΅Ρ€ΠΈΠ°Π»Π΅, ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ°Π΅Ρ‚ ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΡƒ ΠŸΠ»Π°Ρ‚Ρ‚Π°

  2. SplineCalib ΠΈΠ· Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠΈ ML insights.

  3. Venn-Abers, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π·Π°Ρ‡Π°ΡΡ‚ΡƒΡŽ ΠΏΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ Π»ΡƒΡ‡ΡˆΠΈΠΉ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ Π·Π° Ρ‚Π΅ ΠΆΠ΅ дСньги

Π•Ρ‰Π΅ большС ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠ² ΠΌΠΎΠΆΠ½ΠΎ Π½Π°ΠΉΡ‚ΠΈ Π² ΡΠΎΠΎΡ‚Π²Π΅Ρ‚ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΡ… ΠΏΠ°ΠΊΠ΅Ρ‚Π°Ρ…, ΡΡ‚Π°Ρ‚ΡŒΡΡ… ΠΈΠ»ΠΈ курсах

!pip install -qU betacal venn-abers
WARNING: Ignoring invalid distribution ~lotly (/usr/local/lib/python3.12/dist-packages)
WARNING: Ignoring invalid distribution ~lotly (/usr/local/lib/python3.12/dist-packages)

model = svc
model_name = "SVC"
from venn_abers import VennAbersCV

method = "Venn-Abers"

# чСрная магия, Ρ‡Ρ‚ΠΎΠ±Ρ‹ venn-abers Π·Π°Ρ€Π°Π±ΠΎΡ‚Π°Π» Π² классах Π±Π΅Π· predict_proba
model.predict_proba = lambda x: np.vstack(
    [model.decision_function(x), model.decision_function(x)]
).T
va = VennAbersCV(
    estimator=model, inductive=True, cal_size=0.2, random_state=101, shuffle=False
)
va.fit(X_train, y_train.values)
calibrated_model_proba = va.predict_proba(X_test)[:, 1]

log_metric(model_name, method, y_test, calibrated_model_proba)
from betacal import BetaCalibration

method = "Beta"

# чСрная магия, Ρ‡Ρ‚ΠΎΠ±Ρ‹ va Π·Π°Ρ€Π°Π±ΠΎΡ‚Π°Π» Π² классах Π±Π΅Π· predict_proba
bc = BetaCalibration()
bc.fit(model.predict_proba(X_train)[:, 1], y_train.values)
calibrated_model_proba = bc.predict(model.predict_proba(X_test)[:, 1])

log_metric(model_name, method, y_test, calibrated_model_proba)
from ml_insights import SplineCalib

method = "Spline"

# чСрная магия, Ρ‡Ρ‚ΠΎΠ±Ρ‹ va Π·Π°Ρ€Π°Π±ΠΎΡ‚Π°Π» Π² классах Π±Π΅Π· predict_proba
sc = SplineCalib()
sc.fit(model.predict_proba(X_train)[:, 1], y_train.values)
calibrated_model_proba = sc.calibrate(model.predict_proba(X_test)[:, 1])

log_metric(model_name, method, y_test, calibrated_model_proba)
# проститС, Π»ΡŽΠ±ΠΈΡ‚Π΅Π»ΠΈ пандаса, Ρ‚Π°ΠΊ Π±ΡƒΠ΄Π΅Ρ‚ Π³ΠΎΡ€Π°Π·Π΄ΠΎ ΠΏΡ€ΠΎΡ‰Π΅
import polars as pl

metrics = pd.DataFrame(cal_metrics)
probas_df = pd.DataFrame(probas)
print("SVC Metrics")

display(
    pl.from_pandas(metrics)
    .filter(pl.col("model") == "SVC")
    .sort("score")
    .group_by("metric")
    .agg(pl.col("score", "calibration").first())
)

print("SVC LogLoss")
display(
    pl.from_pandas(metrics)
    .filter((pl.col("model") == "SVC") & (pl.col("metric") == "LogLoss"))
    .sort("score")
)
SVC Metrics
shape: (3, 3)
metricscorecalibration
strf64str
"LogLoss"0.030417"Isotonic"
"BrierScore"0.008632"Isotonic"
"ROC-AUC"0.995556"Beta"
SVC LogLoss
shape: (6, 4)
modelmetriccalibrationscore
strstrstrf64
"SVC""LogLoss""Isotonic"0.030417
"SVC""LogLoss""Sigmoid"0.032401
"SVC""LogLoss""Venn-Abers"0.036094
"SVC""LogLoss""Spline"0.044212
"SVC""LogLoss""Beta"0.046303
"SVC""LogLoss""None"0.364177

Как Π²ΠΈΠ΄Π½ΠΎ, Π½ΠΎΠ²Ρ‹Π΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ ΠΊΠ°Ρ€Π΄ΠΈΠ½Π°Π»ΡŒΠ½ΠΎ Π½Π΅ ΡƒΠ»ΡƒΡ‡ΡˆΠΈΠ»ΠΈ ΡΠΈΡ‚ΡƒΠ°Ρ†ΠΈΡŽ, Π½ΠΎ, ΠΊΠ°ΠΊ ΠΈ всСгда Π² ΠΌΠΈΡ€Π΅ машинного обучСния, Π½ΠΈΠΊΠΎΠ³Π΄Π° Π½Π΅ извСстно, Ρ‡Ρ‚ΠΎ ΠΈΠΌΠ΅Π½Π½ΠΎ Π·Π°ΠΉΠ΄Π΅Ρ‚ Π² ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠΉ Π·Π°Π΄Π°Ρ‡Π΅, поэтому Π½Π΅ стоит сразу ΡΠΏΠΈΡΡ‹Π²Π°Ρ‚ΡŒ ΠΈΡ… со счСтов

НаконСц, построим Ρ„ΠΈΠ½Π°Π»ΡŒΠ½Ρ‹ΠΉ Π³Ρ€Π°Ρ„ΠΈΠΊ ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΎΡ‡Π½Ρ‹Ρ… ΠΊΡ€ΠΈΠ²Ρ‹Ρ…. Π›ΠΈΠ½ΠΈΠΉ Ρ‚Π°ΠΌ довольно ΠΌΠ½ΠΎΠ³ΠΎ, поэтому ΠΎΠ½ сдСлан ΠΈΠ½Ρ‚Π΅Ρ€Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹ΠΌ, ΠΏΡ€ΠΈ ΠΆΠ΅Π»Π°Π½ΠΈΠΈ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ ΠΏΠΎΡ‚Ρ‹ΠΊΠ°Ρ‚ΡŒ ΠΈ ΡƒΠ±Ρ€Π°Ρ‚ΡŒ Ρ‡Π°ΡΡ‚ΡŒ ΠΈΠ· Π½ΠΈΡ…

!pip install -qU plotly
WARNING: Ignoring invalid distribution ~lotly (/usr/local/lib/python3.12/dist-packages)
WARNING: Ignoring invalid distribution ~lotly (/usr/local/lib/python3.12/dist-packages)
WARNING: Ignoring invalid distribution ~lotly (/usr/local/lib/python3.12/dist-packages)

import plotly.graph_objects as go

fig = go.Figure()

fig.add_trace(
    go.Scatter(
        x=[0, 1], y=[0, 1], line=dict(dash="dash", color="green"), name="Perfect"
    )
)


for idx, method in enumerate(
    ["None", "Sigmoid", "Isotonic", "Spline", "Beta", "Venn-Abers"]
):

    svc_true_prob, svc_pred_prob = calibration_curve(
        y_test,
        probas_df[(probas_df.model == "SVC") & (probas_df.calibration == method)][
            "proba"
        ].item(),
        n_bins=15,
    )

    fig.add_trace(go.Scatter(x=svc_pred_prob, y=svc_true_prob, name=method))

fig.update_layout(
    height=800,
    title="Calibration Comparison",
    xaxis=dict(title=dict(text="Mean predicted probability")),
    yaxis=dict(title=dict(text="Fraction of positives")),
    legend=dict(title=dict(text="Calibration")),
)
fig.show()

Какого-Ρ‚ΠΎ Π»ΡƒΡ‡ΡˆΠ΅Π³ΠΎ ΠΌΠ΅Ρ‚ΠΎΠ΄Π°, ΡƒΠ²Ρ‹ Π½Π΅ сущСствуСт. Π’Ρ‹Π±ΠΈΡ€Π°Ρ‚ΡŒ ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΎΡ‡Π½ΡƒΡŽ ΠΊΡ€ΠΈΠ²ΡƒΡŽ ΠΏΠΎΠ΄ Π²Π°ΡˆΡƒ Π·Π°Π΄Π°Ρ‡Ρƒ - это ΡƒΠΆΠ΅ искусство. МоТно лишь Ρ€Π°ΡΡΡƒΠ΄ΠΈΡ‚ΡŒ, Ρ‡Ρ‚ΠΎ ΠΊΠ°ΠΊΠΈΠ΅-Ρ‚ΠΎ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, сплайн-ΠΊΠ°Π»ΠΈΠ±Ρ€ΠΎΠ²ΠΊΠ° ΠΏΠ»ΠΎΡ…ΠΎ ΠΏΠΎΠΊΠ°Π·Ρ‹Π²Π°ΡŽΡ‚ сСбя Π½Π° ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π°Ρ… с Π½ΠΈΠ·ΠΊΠΎΠΉ Π²Π΅Ρ€ΠΎΡΡ‚Π½ΠΎΡΡ‚ΡŒΡŽ, ΠΊΠ°ΠΊΠΈΠ΅-Ρ‚ΠΎ Π½Π°ΠΎΠ±ΠΎΡ€ΠΎΡ‚, Π½Π° ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π°Ρ… с высокой. Π§Ρ‚ΠΎ ΠΈΠ· этого Π»ΡƒΡ‡ΡˆΠ΅ ΡƒΠΆΠ΅ зависит ΠΎΡ‚ постановки Π·Π°Π΄Π°Ρ‡ΠΈ. Π”ΡƒΠΌΠ°ΠΉΡ‚Π΅ ΠΈ исслСдуйтС!