模型校准详解-初学者视觉指南及代码示例

模型校准详解:初学者视觉指南及代码示例

原文:towardsdatascience.com/model-calibration-explained-a-visual-guide-with-code-examples-for-beginners-55f368bafe72/

模型评估与优化

您已经训练了几个分类模型,并且它们似乎都表现出良好的高准确度得分。恭喜!

但等等——是否有一个模型真的比其他模型更好?仅仅准确度并不能说明全部。如果一个模型持续高估其置信度,而另一个模型低估其置信度,那会怎样?这就是模型校准发挥作用的地方。

在这里,我们将了解模型校准是什么,并探讨如何评估模型预测的可靠性——使用视觉和实用的代码示例向您展示如何识别校准问题。准备好超越准确度,激发机器学习模型的真正潜力吧!

所有视觉图像:作者使用 Canva Pro 创建。针对移动设备优化;在桌面设备上可能显示过大。

所有视觉图像:作者使用 Canva Pro 创建。针对移动设备优化;在桌面设备上可能显示过大。

理解校准

模型校准衡量模型预测概率与其实际性能匹配的程度。一个给出 70%概率分数的模型应该在相似预测中正确 70%的时间。这意味着其概率分数应该反映其预测正确的真实可能性。

为什么校准很重要

虽然准确度告诉我们模型整体正确性的频率,但校准告诉我们我们是否可以信赖其概率分数。两个模型可能都有 90%的准确度,但一个可能给出合理的概率分数,而另一个可能给出过于自信的预测。在许多实际应用中,拥有可靠的概率分数与拥有正确的预测一样重要。

两个准确度相同的模型(70%正确)在预测中表现出不同的置信度水平。模型 A 使用平衡的概率分数(0.3 和 0.7),而模型 B 仅使用极端概率(0.0 和 1.0),表明它对每个预测要么完全确定,要么完全不确定。

两个准确度相同的模型(70%正确)在预测中表现出不同的置信度水平。模型 A 使用平衡的概率分数(0.3 和 0.7),而模型 B 仅使用极端概率(0.0 和 1.0),表明它对每个预测要么完全确定,要么完全不确定。

完美校准与现实的对比

一个完美校准的模型会在其预测概率和实际成功率之间显示直接匹配:当它以 90%的概率预测时,它应该有 90%的时间是正确的。同样适用于所有概率级别。

然而,大多数模型并不是完美校准的。它们可以是:

  • 过于自信:给出的概率评分高于其实际表现

  • 缺乏自信:给出的概率评分低于其实际表现

  • 两者都有:在某些范围内过于自信,在其他方面缺乏自信

四个具有相同准确率(70%)但校准模式不同的模型。过于自信的模型做出极端预测(0.0 或 1.0),而缺乏自信的模型保持在 0.5 附近。过于和缺乏自信的模型在极端值和中值之间切换。校准良好的模型使用合理的概率(‘NO’为 0.3 和‘YES’为 0.7),这些概率与其实际表现相匹配。

四个具有相同准确率(70%)但校准模式不同的模型。过于自信的模型做出极端预测(0.0 或 1.0),而缺乏自信的模型保持在 0.5 附近。过于和缺乏自信的模型在极端值和中值之间切换。校准良好的模型使用合理的概率(‘NO’为 0.3 和‘YES’为 0.7),这些概率与其实际表现相匹配。

预测概率与实际正确性之间的这种不匹配,当在实际应用中使用这些模型时可能会导致决策不当。这就是为什么理解和改进模型校准对于构建可靠的机器学习系统是必要的。

📊 使用的数据集

为了探索模型校准,我们将继续使用我在之前关于分类算法的文章中使用的相同数据集:根据天气条件预测某人是否会打高尔夫。

列:'多云(独热编码为 3 列)','温度'(华氏度),'湿度'(百分比),'有风'(是/否)和'玩'(是/否,目标特征)

列:‘多云(独热编码为 3 列)’,‘温度’(华氏度),‘湿度’(百分比),‘有风’(是/否)和‘玩’(是/否,目标特征)

import pandas as pd
import numpy as np
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split

# Create and prepare dataset
dataset_dict = {
    'Outlook': ['sunny', 'sunny', 'overcast', 'rainy', 'rainy', 'rainy', 'overcast', 
                'sunny', 'sunny', 'rainy', 'sunny', 'overcast', 'overcast', 'rainy',
                'sunny', 'overcast', 'rainy', 'sunny', 'sunny', 'rainy', 'overcast',
                'rainy', 'sunny', 'overcast', 'sunny', 'overcast', 'rainy', 'overcast'],
    'Temperature': [85.0, 80.0, 83.0, 70.0, 68.0, 65.0, 64.0, 72.0, 69.0, 75.0, 75.0,
                   72.0, 81.0, 71.0, 81.0, 74.0, 76.0, 78.0, 82.0, 67.0, 85.0, 73.0,
                   88.0, 77.0, 79.0, 80.0, 66.0, 84.0],
    'Humidity': [85.0, 90.0, 78.0, 96.0, 80.0, 70.0, 65.0, 95.0, 70.0, 80.0, 70.0,
                 90.0, 75.0, 80.0, 88.0, 92.0, 85.0, 75.0, 92.0, 90.0, 85.0, 88.0,
                 65.0, 70.0, 60.0, 95.0, 70.0, 78.0],
    'Wind': [False, True, False, False, False, True, True, False, False, False, True,
             True, False, True, True, False, False, True, False, True, True, False,
             True, False, False, True, False, False],
    'Play': ['No', 'No', 'Yes', 'Yes', 'Yes', 'No', 'Yes', 'No', 'Yes', 'Yes', 'Yes',
             'Yes', 'Yes', 'No', 'No', 'Yes', 'Yes', 'No', 'No', 'No', 'Yes', 'Yes',
             'Yes', 'Yes', 'Yes', 'Yes', 'No', 'Yes']
}
# Prepare data
df = pd.DataFrame(dataset_dict)

在训练我们的模型之前,我们通过标准缩放对数值天气测量进行归一化,并将分类特征通过独热编码进行转换。这些预处理步骤确保所有模型都能有效地使用数据,同时在它们之间保持公平的比较。

from sklearn.preprocessing import StandardScaler
df = pd.get_dummies(df, columns=['Outlook'], prefix='', prefix_sep='', dtype=int)
df['Wind'] = df['Wind'].astype(int)
df['Play'] = (df['Play'] == 'Yes').astype(int)

# Rearrange columns
column_order = ['sunny', 'overcast', 'rainy', 'Temperature', 'Humidity', 'Wind', 'Play']
df = df[column_order]

# Prepare features and target
X,y = df.drop('Play', axis=1), df['Play']
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.5, shuffle=False)

# Scale numerical features
scaler = StandardScaler()
X_train[['Temperature', 'Humidity']] = scaler.fit_transform(X_train[['Temperature', 'Humidity']])
X_test[['Temperature', 'Humidity']] = scaler.transform(X_test[['Temperature', 'Humidity']])

模型和训练

为了这次探索,我们训练了四个分类模型以达到相似的准确率分数:

  • K-Nearest Neighbors (kNN)

  • 伯努利朴素贝叶斯

  • 逻辑回归

  • 多层感知器(MLP)

对于那些对那些算法如何进行预测及其概率感到好奇的人,可以参考这篇文章:

预测概率解释:初学者视觉指南及代码示例

尽管这些模型在这个简单问题中达到了相同的准确率,但它们计算预测概率的方式不同。

尽管这四个模型有 85.7%的时间是正确的,但它们在预测中的信心水平不同。在这里,MLP 模型倾向于对其答案非常有信心(给出接近 1.0 的值),而 kNN 模型则更为谨慎,给出更多变化的信心分数。

即使这四个模型有 85.7%的时间是正确的,它们在预测中的信心水平也不同。在这里,MLP 模型倾向于对其答案非常有信心(给出接近 1.0 的值),而 kNN 模型则更为谨慎,给出更多变化的信心分数。

import numpy as np
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.neural_network import MLPClassifier
from sklearn.metrics import accuracy_score
from sklearn.naive_bayes import BernoulliNB

# Initialize the models with the found parameters
knn = KNeighborsClassifier(n_neighbors=4, weights='distance')
bnb = BernoulliNB()
lr = LogisticRegression(C=1, random_state=42)
mlp = MLPClassifier(hidden_layer_sizes=(4, 2),random_state=42, max_iter=2000)

# Train all models
models = {
    'KNN': knn,
    'BNB': bnb,
    'LR': lr,
    'MLP': mlp
}

for name, model in models.items():
    model.fit(X_train, y_train)

# Create predictions and probabilities for each model
results_dict = {
    'True Labels': y_test
}

for name, model in models.items():
#    results_dict[f'{name} Pred'] = model.predict(X_test)
    results_dict[f'{name} Prob'] = model.predict_proba(X_test)[:, 1]

# Create results dataframe
results_df = pd.DataFrame(results_dict)

# Print predictions and probabilities
print("nPredictions and Probabilities:")
print(results_df)

# Print accuracies
print("nAccuracies:")
for name, model in models.items():
    accuracy = accuracy_score(y_test, model.predict(X_test))
    print(f"{name}: {accuracy:.3f}")

通过这些差异,我们将探讨为什么我们需要超越准确率去寻找其他因素。

校准测量

为了评估一个模型的预测概率与其实际性能匹配得有多好,我们使用了多种方法和指标。这些测量帮助我们了解我们的模型信心水平是否可靠。

Brier 分数

Brier 分数衡量预测概率与实际结果之间的均方差异。其范围从 0 到 1,分数越低表示校准越好。这个分数特别有用,因为它同时考虑了校准和准确率。

分数(0.148)显示了模型的信心与其实际性能匹配得有多好。这是通过比较模型的预测概率与实际发生的情况('NO'为 0,'YES'为 1)来计算的,差异越小表示预测越好。

分数(0.148)显示了模型的信心与其实际性能匹配得有多好。这是通过比较模型的预测概率与实际发生的情况(‘NO’为 0,‘YES’为 1)来计算的,差异越小表示预测越好。

对数损失

对数损失计算正确预测的负对数概率。这个指标对自信但错误的预测特别敏感——当模型说它有 90%的把握但错了,它受到的惩罚比它有 60%的把握但错了要大得多。较低的值表示校准更好。

对于每个预测,它查看模型对正确答案的信心程度。当模型非常自信但错误(如索引 26 所示)时,它会受到更大的惩罚。最终得分 0.455 是所有这些惩罚的平均值,其中数字越低表示预测越好。

对于每个预测,它查看模型对正确答案的信心程度。当模型非常自信但错误(如索引 26 所示)时,它会受到更大的惩罚。最终得分 0.455 是所有这些惩罚的平均值,其中数字越低表示预测越好。

预期校准误差(ECE)

ECE衡量预测概率与实际概率(取标签的平均值)之间的平均差异,并按每个概率组中预测的数量进行加权。此指标有助于我们了解我们的模型在概率估计中是否存在系统性偏差。

根据模型信心程度将预测分为 5 个组。对于每个组,我们比较模型的平均信心与它实际上正确的频率。最终得分(0.1502)告诉我们这些匹配得有多好,其中数字越低表示越好。"

根据模型信心程度将预测分为 5 个组。对于每个组,我们比较模型的平均信心与它实际上正确的频率。最终得分(0.1502)告诉我们这些匹配得有多好,其中数字越低表示越好。"

可靠性图

类似于 ECE,可靠性图(或校准曲线)通过将预测分组并与之比较实际结果来可视化模型校准。虽然 ECE 给我们一个衡量校准误差的单个数字,但可靠性图以图形方式展示了相同的信息。我们使用相同的分组方法,并计算每个分组中实际积极结果的频率。当绘制时,这些点显示我们的模型预测与完美校准的偏差,这会表现为一条对角线。

与 ECE 类似,根据置信水平将预测分为 5 个组。每个点显示模型实际上正确的频率(上下)与它自信的程度(左右)相比。虚线表示完美匹配 - 模型的曲线显示它有时认为它比实际情况更好或更差。

与 ECE 类似,根据置信水平将预测分为 5 个组。每个点显示模型实际上正确的频率(上下)与它自信的程度(左右)相比。虚线表示完美匹配 – 模型的曲线显示它有时认为它比实际情况更好或更差。

比较校准指标

这些指标展示了校准问题的不同方面:

  • 高 Brier 分数表明整体概率估计较差。

  • 高对数损失表明过度自信的错误预测。

  • 高 ECE 表示概率估计存在系统性偏差。

这些指标共同为我们提供了关于我们的模型概率分数如何反映其真实性能的完整图景。

我们的模型

对于我们的模型,让我们计算校准指标并绘制它们的校准曲线:

from sklearn.metrics import brier_score_loss, log_loss
from sklearn.calibration import calibration_curve
import matplotlib.pyplot as plt

# Initialize models
models = {
    'k-Nearest Neighbors': KNeighborsClassifier(n_neighbors=4, weights='distance'),
    'Bernoulli Naive Bayes': BernoulliNB(),
    'Logistic Regression': LogisticRegression(C=1.5, random_state=42),
    'Multilayer Perceptron': MLPClassifier(hidden_layer_sizes=(4, 2), random_state=42, max_iter=2000)
}

# Get predictions and calculate metrics
metrics_dict = {}
for name, model in models.items():
    model.fit(X_train, y_train)
    y_prob = model.predict_proba(X_test)[:, 1]
    metrics_dict[name] = {
        'Brier Score': brier_score_loss(y_test, y_prob),
        'Log Loss': log_loss(y_test, y_prob),
        'ECE': calculate_ece(y_test, y_prob),
        'Probabilities': y_prob
    }

# Plot calibration curves
fig, axes = plt.subplots(2, 2, figsize=(8, 8), dpi=300)
colors = ['orangered', 'slategrey', 'gold', 'mediumorchid']

for idx, (name, metrics) in enumerate(metrics_dict.items()):
    ax = axes.ravel()[idx]
    prob_true, prob_pred = calibration_curve(y_test, metrics['Probabilities'], 
                                           n_bins=5, strategy='uniform')

    ax.plot([0, 1], [0, 1], 'k--', label='Perfectly calibrated')
    ax.plot(prob_pred, prob_true, color=colors[idx], marker='o', 
            label='Calibration curve', linewidth=2, markersize=8)

    title = f'{name}nBrier: {metrics["Brier Score"]:.3f} | Log Loss: {metrics["Log Loss"]:.3f} | ECE: {metrics["ECE"]:.3f}'
    ax.set_title(title, fontsize=11, pad=10)
    ax.grid(True, alpha=0.7)
    ax.set_xlim([-0.05, 1.05])
    ax.set_ylim([-0.05, 1.05])
    ax.spines[['top', 'right', 'left', 'bottom']].set_visible(False)
    ax.legend(fontsize=10, loc='upper left')

plt.tight_layout()
plt.show()

图片

现在,让我们根据这些指标分析每个模型的校准性能:

k-最近邻(KNN)模型在估计其预测的确定性方面表现良好。其图形线紧贴虚线,显示出良好的性能。它有稳定的分数——Brier 分数为 0.148,最佳 ECE 分数为 0.090。虽然它有时在中间范围表现出过多的自信,但它通常对其确定性做出可靠的估计

图片

伯努利朴素贝叶斯模型在其线条上显示出不寻常的阶梯状模式。这意味着它在不同的确定性水平之间跳跃,而不是平滑变化。虽然它与 KNN 有相同的 Brier 分数(0.148),但其更高的 ECE 为 0.150 表明它在估计其确定性方面不太准确。该模型在过于自信和不够自信之间切换

图片

逻辑回归模型在其预测中显示出明显的问题。其线条远离虚线,这意味着它经常错误地判断其应该有多大的确定性。它有最差的 ECE 分数(0.181)和较差的 Brier 分数(0.164)。该模型始终表现出对其预测过多的自信,使其不可靠。

图片

多层感知器显示出明显的问题。尽管它有最佳的 Brier 分数(0.129),但其线条揭示它主要做出极端预测——要么非常确定,要么非常不确定,中间很少。它的高 ECE(0.167)和中间范围的平坦线条表明它难以做出平衡的确定性估计。

图片

在检查了所有四个模型之后,k-最近邻在估计预测确定性方面表现最佳。它在不同确定性水平上保持一致的性能,并在其预测中显示出最可靠的模式。虽然其他模型可能在某些指标(如多层感知器的 Brier 分数)上得分较高,但它们的图形揭示,当我们需要信任它们的确定性估计时,它们并不那么可靠。

最后的备注

在选择不同的模型时,我们需要考虑它们的准确性和校准质量。一个准确度略低但校准质量更好的模型可能比一个准确度高但概率估计差的模型更有价值。

通过理解校准及其重要性,我们可以构建更可靠的机器学习系统,用户不仅信任其预测,也信任其对预测的信心。

🌟 模型校准代码总结(1 个模型)

import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import BernoulliNB
from sklearn.metrics import brier_score_loss, log_loss
from sklearn.calibration import calibration_curve
import matplotlib.pyplot as plt

# Define ECE
def calculate_ece(y_true, y_prob, n_bins=5):
    bins = np.linspace(0, 1, n_bins + 1)
    ece = 0
    for bin_lower, bin_upper in zip(bins[:-1], bins[1:]):
        mask = (y_prob >= bin_lower) &amp; (y_prob < bin_upper)
        if np.sum(mask) > 0:
            bin_conf = np.mean(y_prob[mask])
            bin_acc = np.mean(y_true[mask])
            ece += np.abs(bin_conf - bin_acc) * np.sum(mask)
    return ece / len(y_true)

# Create dataset and prepare data
dataset_dict = {
    'Outlook': ['sunny', 'sunny', 'overcast', 'rainy', 'rainy', 'rainy', 'overcast','sunny', 'sunny', 'rainy', 'sunny', 'overcast', 'overcast', 'rainy','sunny', 'overcast', 'rainy', 'sunny', 'sunny', 'rainy', 'overcast','rainy', 'sunny', 'overcast', 'sunny', 'overcast', 'rainy', 'overcast'],
    'Temperature': [85.0, 80.0, 83.0, 70.0, 68.0, 65.0, 64.0, 72.0, 69.0, 75.0, 75.0,72.0, 81.0, 71.0, 81.0, 74.0, 76.0, 78.0, 82.0, 67.0, 85.0, 73.0,88.0, 77.0, 79.0, 80.0, 66.0, 84.0],
    'Humidity': [85.0, 90.0, 78.0, 96.0, 80.0, 70.0, 65.0, 95.0, 70.0, 80.0, 70.0,90.0, 75.0, 80.0, 88.0, 92.0, 85.0, 75.0, 92.0, 90.0, 85.0, 88.0,65.0, 70.0, 60.0, 95.0, 70.0, 78.0],
    'Wind': [False, True, False, False, False, True, True, False, False, False, True,True, False, True, True, False, False, True, False, True, True, False,True, False, False, True, False, False],
    'Play': ['No', 'No', 'Yes', 'Yes', 'Yes', 'No', 'Yes', 'No', 'Yes', 'Yes', 'Yes','Yes', 'Yes', 'No', 'No', 'Yes', 'Yes', 'No', 'No', 'No', 'Yes', 'Yes','Yes', 'Yes', 'Yes', 'Yes', 'No', 'Yes']
}

# Prepare and encode data
df = pd.DataFrame(dataset_dict)
df = pd.get_dummies(df, columns=['Outlook'], prefix='', prefix_sep='', dtype=int)
df['Wind'] = df['Wind'].astype(int)
df['Play'] = (df['Play'] == 'Yes').astype(int)
df = df[['sunny', 'overcast', 'rainy', 'Temperature', 'Humidity', 'Wind', 'Play']]

# Split and scale data
X, y = df.drop('Play', axis=1), df['Play']
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.5, shuffle=False)
scaler = StandardScaler()
X_train[['Temperature', 'Humidity']] = scaler.fit_transform(X_train[['Temperature', 'Humidity']])
X_test[['Temperature', 'Humidity']] = scaler.transform(X_test[['Temperature', 'Humidity']])

# Train model and get predictions
model = BernoulliNB()
model.fit(X_train, y_train)
y_prob = model.predict_proba(X_test)[:, 1]

# Calculate metrics
metrics = {
    'Brier Score': brier_score_loss(y_test, y_prob),
    'Log Loss': log_loss(y_test, y_prob),
    'ECE': calculate_ece(y_test, y_prob)
}

# Plot calibration curve
plt.figure(figsize=(6, 6), dpi=300)
prob_true, prob_pred = calibration_curve(y_test, y_prob, n_bins=5, strategy='uniform')

plt.plot([0, 1], [0, 1], 'k--', label='Perfectly calibrated')
plt.plot(prob_pred, prob_true, color='slategrey', marker='o', 
        label='Calibration curve', linewidth=2, markersize=8)

title = f'Bernoulli Naive BayesnBrier: {metrics["Brier Score"]:.3f} | Log Loss: {metrics["Log Loss"]:.3f} | ECE: {metrics["ECE"]:.3f}'
plt.title(title, fontsize=11, pad=10)
plt.grid(True, alpha=0.7)
plt.xlim([-0.05, 1.05])
plt.ylim([-0.05, 1.05])
plt.gca().spines[['top', 'right', 'left', 'bottom']].set_visible(False)
plt.legend(fontsize=10, loc='lower right')

plt.tight_layout()
plt.show()

🌟 模型校准代码总结(4 个模型)

import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import BernoulliNB
from sklearn.linear_model import LogisticRegression
from sklearn.neural_network import MLPClassifier
from sklearn.metrics import brier_score_loss, log_loss
from sklearn.calibration import calibration_curve
import matplotlib.pyplot as plt

# Define ECE
def calculate_ece(y_true, y_prob, n_bins=5):
    bins = np.linspace(0, 1, n_bins + 1)
    ece = 0
    for bin_lower, bin_upper in zip(bins[:-1], bins[1:]):
        mask = (y_prob >= bin_lower) &amp; (y_prob < bin_upper)
        if np.sum(mask) > 0:
            bin_conf = np.mean(y_prob[mask])
            bin_acc = np.mean(y_true[mask])
            ece += np.abs(bin_conf - bin_acc) * np.sum(mask)
    return ece / len(y_true)

# Create dataset and prepare data
dataset_dict = {
    'Outlook': ['sunny', 'sunny', 'overcast', 'rainy', 'rainy', 'rainy', 'overcast','sunny', 'sunny', 'rainy', 'sunny', 'overcast', 'overcast', 'rainy','sunny', 'overcast', 'rainy', 'sunny', 'sunny', 'rainy', 'overcast','rainy', 'sunny', 'overcast', 'sunny', 'overcast', 'rainy', 'overcast'],
    'Temperature': [85.0, 80.0, 83.0, 70.0, 68.0, 65.0, 64.0, 72.0, 69.0, 75.0, 75.0,72.0, 81.0, 71.0, 81.0, 74.0, 76.0, 78.0, 82.0, 67.0, 85.0, 73.0,88.0, 77.0, 79.0, 80.0, 66.0, 84.0],
    'Humidity': [85.0, 90.0, 78.0, 96.0, 80.0, 70.0, 65.0, 95.0, 70.0, 80.0, 70.0,90.0, 75.0, 80.0, 88.0, 92.0, 85.0, 75.0, 92.0, 90.0, 85.0, 88.0,65.0, 70.0, 60.0, 95.0, 70.0, 78.0],
    'Wind': [False, True, False, False, False, True, True, False, False, False, True,True, False, True, True, False, False, True, False, True, True, False,True, False, False, True, False, False],
    'Play': ['No', 'No', 'Yes', 'Yes', 'Yes', 'No', 'Yes', 'No', 'Yes', 'Yes', 'Yes','Yes', 'Yes', 'No', 'No', 'Yes', 'Yes', 'No', 'No', 'No', 'Yes', 'Yes','Yes', 'Yes', 'Yes', 'Yes', 'No', 'Yes']
}

# Prepare and encode data
df = pd.DataFrame(dataset_dict)
df = pd.get_dummies(df, columns=['Outlook'], prefix='', prefix_sep='', dtype=int)
df['Wind'] = df['Wind'].astype(int)
df['Play'] = (df['Play'] == 'Yes').astype(int)
df = df[['sunny', 'overcast', 'rainy', 'Temperature', 'Humidity', 'Wind', 'Play']]

# Split and scale data
X, y = df.drop('Play', axis=1), df['Play']
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.5, shuffle=False)
scaler = StandardScaler()
X_train[['Temperature', 'Humidity']] = scaler.fit_transform(X_train[['Temperature', 'Humidity']])
X_test[['Temperature', 'Humidity']] = scaler.transform(X_test[['Temperature', 'Humidity']])

# Initialize models
models = {
    'k-Nearest Neighbors': KNeighborsClassifier(n_neighbors=4, weights='distance'),
    'Bernoulli Naive Bayes': BernoulliNB(),
    'Logistic Regression': LogisticRegression(C=1.5, random_state=42),
    'Multilayer Perceptron': MLPClassifier(hidden_layer_sizes=(4, 2), random_state=42, max_iter=2000)
}

# Get predictions and calculate metrics
metrics_dict = {}
for name, model in models.items():
    model.fit(X_train, y_train)
    y_prob = model.predict_proba(X_test)[:, 1]
    metrics_dict[name] = {
        'Brier Score': brier_score_loss(y_test, y_prob),
        'Log Loss': log_loss(y_test, y_prob),
        'ECE': calculate_ece(y_test, y_prob),
        'Probabilities': y_prob
    }

# Plot calibration curves
fig, axes = plt.subplots(2, 2, figsize=(8, 8), dpi=300)
colors = ['orangered', 'slategrey', 'gold', 'mediumorchid']

for idx, (name, metrics) in enumerate(metrics_dict.items()):
    ax = axes.ravel()[idx]
    prob_true, prob_pred = calibration_curve(y_test, metrics['Probabilities'], 
                                           n_bins=5, strategy='uniform')

    ax.plot([0, 1], [0, 1], 'k--', label='Perfectly calibrated')
    ax.plot(prob_pred, prob_true, color=colors[idx], marker='o', 
            label='Calibration curve', linewidth=2, markersize=8)

    title = f'{name}nBrier: {metrics["Brier Score"]:.3f} | Log Loss: {metrics["Log Loss"]:.3f} | ECE: {metrics["ECE"]:.3f}'
    ax.set_title(title, fontsize=11, pad=10)
    ax.grid(True, alpha=0.7)
    ax.set_xlim([-0.05, 1.05])
    ax.set_ylim([-0.05, 1.05])
    ax.spines[['top', 'right', 'left', 'bottom']].set_visible(False)
    ax.legend(fontsize=10, loc='upper left')

plt.tight_layout()
plt.show()

技术环境

本文使用 Python 3.7 和 scikit-learn 1.5。虽然讨论的概念通常适用,但具体代码实现可能因不同版本略有差异。

关于插图

除非另有说明,所有图像均由作者创建,并融合了 Canva Pro 的授权设计元素。

𝙎𝙚𝙚 𝙢𝙤𝙧𝙚 𝙈𝙤𝙙𝙚𝙡 𝙀𝙫𝙖𝙡𝙪𝙖𝙩𝙞𝙣 & 𝙊𝙥𝙩𝙞𝙢𝙞𝙯𝙖𝙩𝙞𝙣 𝙢𝙚𝙩𝙝𝙤𝙙𝙨 𝙝𝙚𝙧𝙚:

模型评估与优化

𝙔𝙤𝙪 𝙢𝙞𝙜𝙝𝙩 𝙖𝙡𝙨𝙤 𝙡𝙞𝙠𝙚:

集成学习

分类算法

posted @ 2026-03-27 10:48  布客飞龙V  阅读(1)  评论(0)    收藏  举报