三类分类的概率校准

Machine LearningMachine LearningBeginner
立即练习

This tutorial is from open-source community. Access the source code

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

本实验演示了如何使用 scikit-learn 在 Python 中对三类分类进行 sigmoid 校准。它展示了 sigmoid 校准如何改变预测概率,以及如何用它来提高模型性能。

虚拟机使用提示

虚拟机启动完成后,点击左上角切换到“笔记本”标签,以访问 Jupyter Notebook 进行练习。

有时,你可能需要等待几秒钟让 Jupyter Notebook 完成加载。由于 Jupyter Notebook 的限制,操作验证无法自动化。

如果你在学习过程中遇到问题,随时向 Labby 提问。课程结束后提供反馈,我们会及时为你解决问题。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL sklearn(("Sklearn")) -.-> sklearn/CoreModelsandAlgorithmsGroup(["Core Models and Algorithms"]) sklearn(("Sklearn")) -.-> sklearn/ModelSelectionandEvaluationGroup(["Model Selection and Evaluation"]) sklearn(("Sklearn")) -.-> sklearn/UtilitiesandDatasetsGroup(["Utilities and Datasets"]) ml(("Machine Learning")) -.-> ml/FrameworkandSoftwareGroup(["Framework and Software"]) sklearn/CoreModelsandAlgorithmsGroup -.-> sklearn/ensemble("Ensemble Methods") sklearn/ModelSelectionandEvaluationGroup -.-> sklearn/metrics("Metrics") sklearn/ModelSelectionandEvaluationGroup -.-> sklearn/calibration("Probability Calibration") sklearn/UtilitiesandDatasetsGroup -.-> sklearn/datasets("Datasets") ml/FrameworkandSoftwareGroup -.-> ml/sklearn("scikit-learn") subgraph Lab Skills sklearn/ensemble -.-> lab-49074{{"三类分类的概率校准"}} sklearn/metrics -.-> lab-49074{{"三类分类的概率校准"}} sklearn/calibration -.-> lab-49074{{"三类分类的概率校准"}} sklearn/datasets -.-> lab-49074{{"三类分类的概率校准"}} ml/sklearn -.-> lab-49074{{"三类分类的概率校准"}} end

数据

我们生成了一个包含 2000 个样本、2 个特征和 3 个目标类别的分类数据集。然后,我们按如下方式划分数据:

  • 训练集:600 个样本(用于训练分类器)
  • 验证集:400 个样本(用于校准预测概率)
  • 测试集:1000 个样本
import numpy as np
from sklearn.datasets import make_blobs

np.random.seed(0)

X, y = make_blobs(
    n_samples=2000, n_features=2, centers=3, random_state=42, cluster_std=5.0
)
X_train, y_train = X[:600], y[:600]
X_valid, y_valid = X[600:1000], y[600:1000]
X_train_valid, y_train_valid = X[:1000], y[:1000]
X_test, y_test = X[1000:], y[1000:]

拟合与校准

我们在拼接的训练集和验证集(1000 个样本)上训练一个具有 25 个基估计器(树)的随机森林分类器。这就是未校准的分类器。

from sklearn.ensemble import RandomForestClassifier

clf = RandomForestClassifier(n_estimators=25)
clf.fit(X_train_valid, y_train_valid)

为了训练校准后的分类器,我们从相同的随机森林分类器开始,但仅使用训练数据子集(600 个样本)进行训练,然后在一个两阶段过程中,使用验证数据子集(400 个样本),通过 method='sigmoid' 进行校准。

from sklearn.calibration import CalibratedClassifierCV

clf = RandomForestClassifier(n_estimators=25)
clf.fit(X_train, y_train)
cal_clf = CalibratedClassifierCV(clf, method="sigmoid", cv="prefit")
cal_clf.fit(X_valid, y_valid)

比较概率

我们绘制一个二维单纯形,并使用箭头展示测试样本预测概率的变化。

import matplotlib.pyplot as plt

plt.figure(figsize=(10, 10))
colors = ["r", "g", "b"]

clf_probs = clf.predict_proba(X_test)
cal_clf_probs = cal_clf.predict_proba(X_test)
## 绘制箭头
for i in range(clf_probs.shape[0]):
    plt.arrow(
        clf_probs[i, 0],
        clf_probs[i, 1],
        cal_clf_probs[i, 0] - clf_probs[i, 0],
        cal_clf_probs[i, 1] - clf_probs[i, 1],
        color=colors[y_test[i]],
        head_width=1e-2,
    )

## 在每个顶点绘制完美预测
plt.plot([1.0], [0.0], "ro", ms=20, label="类别 1")
plt.plot([0.0], [1.0], "go", ms=20, label="类别 2")
plt.plot([0.0], [0.0], "bo", ms=20, label="类别 3")

## 绘制单位单纯形的边界
plt.plot([0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], "k", label="单纯形")

## 在单纯形周围注释 6 个点,并在单纯形内部注释中点
plt.annotate(
    r"($\frac{1}{3}$, $\frac{1}{3}$, $\frac{1}{3}$)",
    xy=(1.0 / 3, 1.0 / 3),
    xytext=(1.0 / 3, 0.23),
    xycoords="数据",
    arrowprops=dict(facecolor="黑色", shrink=0.05),
    horizontalalignment="中心",
    verticalalignment="中心",
)
plt.plot([1.0 / 3], [1.0 / 3], "ko", ms=5)
plt.annotate(
    r"($\frac{1}{2}$, $0$, $\frac{1}{2}$)",
    xy=(0.5, 0.0),
    xytext=(0.5, 0.1),
    xycoords="数据",
    arrowprops=dict(facecolor="黑色", shrink=0.05),
    horizontalalignment="中心",
    verticalalignment="中心",
)
plt.annotate(
    r"($0$, $\frac{1}{2}$, $\frac{1}{2}$)",
    xy=(0.0, 0.5),
    xytext=(0.1, 0.5),
    xycoords="数据",
    arrowprops=dict(facecolor="黑色", shrink=0.05),
    horizontalalignment="中心",
    verticalalignment="中心",
)
plt.annotate(
    r"($\frac{1}{2}$, $\frac{1}{2}$, $0$)",
    xy=(0.5, 0.5),
    xytext=(0.6, 0.6),
    xycoords="数据",
    arrowprops=dict(facecolor="黑色", shrink=0.05),
    horizontalalignment="中心",
    verticalalignment="中心",
)
plt.annotate(
    r"($0$, $0$, $1$)",
    xy=(0, 0),
    xytext=(0.1, 0.1),
    xycoords="数据",
    arrowprops=dict(facecolor="黑色", shrink=0.05),
    horizontalalignment="中心",
    verticalalignment="中心",
)
plt.annotate(
    r"($1$, $0$, $0$)",
    xy=(1, 0),
    xytext=(1, 0.1),
    xycoords="数据",
    arrowprops=dict(facecolor="黑色", shrink=0.05),
    horizontalalignment="中心",
    verticalalignment="中心",
)
plt.annotate(
    r"($0$, $1$, $0$)",
    xy=(0, 1),
    xytext=(0.1, 1),
    xycoords="数据",
    arrowprops=dict(facecolor="黑色", shrink=0.05),
    horizontalalignment="中心",
    verticalalignment="中心",
)
## 添加网格
plt.grid(False)
for x in [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]:
    plt.plot([0, x], [x, 0], "k", alpha=0.2)
    plt.plot([0, 0 + (1 - x) / 2], [x, x + (1 - x) / 2], "k", alpha=0.2)
    plt.plot([x, x + (1 - x) / 2], [0, 0 + (1 - x) / 2], "k", alpha=0.2)

plt.title("sigmoid 校准后测试样本预测概率的变化")
plt.xlabel("类别 1 的概率")
plt.ylabel("类别 2 的概率")
plt.xlim(-0.05, 1.05)
plt.ylim(-0.05, 1.05)
_ = plt.legend(loc="最佳")

对数损失比较

我们比较未校准和校准后的分类器在 1000 个测试样本预测结果上的对数损失。

from sklearn.metrics import log_loss

score = log_loss(y_test, clf_probs)
cal_score = log_loss(y_test, cal_clf_probs)

print("对数损失为")
print(f" * 未校准分类器:{score:.3f}")
print(f" * 校准后分类器:{cal_score:.3f}")

生成网格并绘图

我们在二维单纯形上生成一组可能的未校准概率网格,计算相应的校准概率,并为每个概率绘制箭头。箭头根据最高的未校准概率进行着色。这展示了学习到的校准映射:

plt.figure(figsize=(10, 10))
## 生成概率值网格
p1d = np.linspace(0, 1, 20)
p0, p1 = np.meshgrid(p1d, p1d)
p2 = 1 - p0 - p1
p = np.c_[p0.ravel(), p1.ravel(), p2.ravel()]
p = p[p[:, 2] >= 0]

## 使用三个类别校准器计算校准概率
calibrated_classifier = cal_clf.calibrated_classifiers_[0]
prediction = np.vstack(
    [
        calibrator.predict(this_p)
        for calibrator, this_p in zip(calibrated_classifier.calibrators, p.T)
    ]
).T

## 重新归一化校准后的预测结果,以确保它们保持在单纯形内。在多类别问题上,CalibratedClassifierCV 的 predict 方法内部也会执行相同的归一化步骤。
prediction /= prediction.sum(axis=1)[:, None]

## 绘制校准器引起的预测概率变化
for i in range(prediction.shape[0]):
    plt.arrow(
        p[i, 0],
        p[i, 1],
        prediction[i, 0] - p[i, 0],
        prediction[i, 1] - p[i, 1],
        head_width=1e-2,
        color=colors[np.argmax(p[i])],
    )

## 绘制单位单纯形的边界
plt.plot([0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], "k", label="单纯形")

plt.grid(False)
for x in [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]:
    plt.plot([0, x], [x, 0], "k", alpha=0.2)
    plt.plot([0, 0 + (1 - x) / 2], [x, x + (1 - x) / 2], "k", alpha=0.2)
    plt.plot([x, x + (1 - x) / 2], [0, 0 + (1 - x) / 2], "k", alpha=0.2)

plt.title("学习到的 sigmoid 校准映射")
plt.xlabel("类别 1 的概率")
plt.ylabel("类别 2 的概率")
plt.xlim(-0.05, 1.05)
plt.ylim(-0.05, 1.05)

plt.show()

总结

本实验展示了如何在 Python 中使用 scikit-learn 对三类分类问题进行 sigmoid 校准。它展示了校准对预测概率的影响,以及如何使用校准来提高模型性能。