分类任务中,softmax层产生的数值常被当作概率使用,例如模型输出0.92就被解读为有92%的把握。这种解读隐含了一个前提:输出值要能反映真实频率。然而深度神经网络在训练过程中倾向于产生过度自信的预测,softmax输出并不会天然满足这一点。校准的目标就是让模型输出的概率与经验频率对齐,即如果模型对100个样本都给出约0.8的置信度,那么其中大约80个样本应该被正确分类。本文围绕置信度校准不良问题,介绍温度调节和集成学习两种实用校准手段,并给出实现方案。

一、置信度校准不良的来源与评估方式
深度网络为什么容易校准不良?可以从损失函数和优化过程来理解。交叉熵损失在训练时会不断增大正确类别的logit、压低错误类别的logit,直到正确类别的输出概率接近1。如果训练数据没有足够的噪声,或者模型容量很大,网络就有能力记住几乎所有训练样本,从而在训练集上输出近乎极端的概率。即使使用权重衰减、早停和数据增强,现代网络在验证集上仍然可能表现出系统性过度自信。这是因为softmax本身是一个指数函数,对logit的微小差异会产生明显的概率放大,而网络并没有机制去主动学习不确定性。
评估校准质量通常使用可靠性图和期望校准误差。可靠性图把验证集按预测置信度分桶,例如0到0.1、0.1到0.2等,每个桶比较平均置信度与真实准确率。理想情况下,点应该落主对角线上。期望校准误差ECE则把每个桶的平均置信度与准确率的差值做加权求和,权重是该桶样本占比。公式可以写成:ECE = Σ(|B_m| / n) × |acc(B_m) − conf(B_m)|。这个值越低说明模型输出越接近真实概率。很多实际模型在ECE上可以达到0.05到0.15,说明概率估计并不好用。
下面这段代码演示如何计算ECE,输入为预测概率和真实标签,分成15个等距区间。
import numpy as np
def expected_calibration_error(y_true, probs, n_bins=15):
bins = np.linspace(0.0, 1.0, n_bins + 1)
ece = 0.0
n = len(y_true)
for i in range(n_bins):
left, right = bins[i], bins[i + 1]
mask = (probs >= left) & (probs < right)
if right == 1.0:
mask = (probs >= left) & (probs <= right)
if np.sum(mask) > 0:
avg_conf = np.mean(probs[mask])
avg_acc = np.mean(y_true[mask])
ece += (np.sum(mask) / n) * np.abs(avg_acc - avg_conf)
return ece
这段代码的意义在于,把校准问题变成可量化、可比较的指标。没有ECE,开发者只能依靠感觉判断模型是否过度自信。有了ECE之后,温度调节和集成学习的效果就能被客观评估。
二、温度调节:只改一个参数就能提升概率质量
温度调节的思路很直接。普通softmax的公式是 p_i = exp(z_i) / Σ_j exp(z_j),其中z_i是第i类的logit。如果在指数运算前引入一个温度参数T,公式就变为 p_i = exp(z_i / T) / Σ_j exp(z_j / T)。当T大于1时,输出的概率分布变得更平缓,峰值被压低;当T小于1时,分布变得更尖锐。对于已经训练好的模型,温度调节不会改变argmax结果,也就是预测类别不变,只会改变这个预测类别对应的置信度大小。
实际操作中,温度T是在一个独立的校准集上优化的。通常把验证集单独切出一部分作为校准集,固定模型权重,只调整T,使得校准集上的负对数似然或者ECE最小。因为T只是一个标量,优化过程非常稳定,也不容易过拟合。如果校准集足够大,几行代码就能完成。下面是一个基于NumPy的网格搜索实现,从多个候选温度中挑选ECE最低的值。
import numpy as np
def softmax_with_temperature(logits, T):
z = logits / T
z_max = np.max(z, axis=-1, keepdims=True)
exp_z = np.exp(z - z_max)
return exp_z / np.sum(exp_z, axis=-1, keepdims=True)
def find_best_temperature(logits, labels, candidates):
best_t = 1.0
best_ece = float('inf')
for T in candidates:
probs = softmax_with_temperature(logits, T)
conf = np.max(probs, axis=-1)
pred = np.argmax(probs, axis=-1)
acc = (pred == labels).astype(np.float32)
ece = expected_calibration_error(acc, conf)
if ece < best_ece:
best_ece = ece
best_t = T
return best_t, best_ece
温度调节的一个明显局限是它只能对整个输出分布做统一的平滑或锐化。如果模型的校准误差在不同置信区间内表现不一致,例如低置信度时过度自信、高置信度时又不够自信,单靠一个T就无法完全修正。还需要注意,温度调节不能替代模型训练,它只是在已有logits上做线性变换。对于结构性的校准偏差,需要从数据、损失函数或模型容量层面进一步处理。
三、集成学习校准:多个模型的平均预测更可靠
集成学习对校准的帮助来自方差下降。单个模型在训练过程中可能因为随机初始化、数据采样或优化路径不同,对某些样本产生偶然的极端预测。如果训练多个结构相同但初始化不同的模型,并对它们的输出概率取平均,那么个别模型的偏差会在平均中被稀释。深度集成是这类方法中效果最稳定的一种,通常只需要改变随机种子,训练5到10个模型,就能获得明显的ECE下降。
集成预测的方式可以是对概率做算术平均,也可以对logit做平均后再过softmax。概率平均更常用,因为每个模型输出的概率已经在0到1之间,平均后的结果仍然是一个合理的分布。对logit平均则可能导致数值尺度问题,但有时在模型差异较小时也能工作。假设有M个模型,第m个模型对样本x的输出概率为p_m,则集成概率为 p_ens = (1/M) Σ p_m。下面代码展示对一批logits进行概率平均和温度调节的完整流程。
import numpy as np
def ensemble_probs(logits_list, T=1.0):
probs = []
for logits in logits_list:
probs.append(softmax_with_temperature(logits, T))
avg_probs = np.mean(np.stack(probs, axis=0), axis=0)
return avg_probs
# 假设有5个模型的logits,形状均为 (batch_size, num_classes)
logits_models = [np.random.randn(128, 10) for _ in range(5)]
calibrated_probs = ensemble_probs(logits_models, T=1.5)
集成学习校准相比单一温度调节有一个额外优势:它还能改善预测准确率,而温度调节不会改变准确率。多个模型在多数样本上会给出相似判断,但在困难样本上可能产生分歧,平均概率会让最终预测偏向更一致的类别。工程上的代价是训练和推理成本翻倍。实践中可以通过并行训练、知识蒸馏或使用较小的子模型来控制成本。如果资源有限,也可以使用快照集成、多分支结构或随机权重平均等方式,在一定程度上模拟多模型的校准效果。
四、组合策略与落地流程
实际项目中,温度调节和集成学习并不冲突,反而可以叠加使用。比较推荐的做法是:先训练多个模型形成集成,再把这些模型的logits或概率取平均,最后在独立的校准集上优化一个温度参数,对平均后的logits做缩放。这样集成负责降低模型层面的方差,温度调节负责修正聚合后输出的整体平滑度。整个流程对原始模型没有任何侵入性,非常适合已经训练完成并且无法重新训练的场景。
落地时有几个容易忽略的点。第一,校准集必须与训练集和最终测试集保持独立,如果直接把训练集拿来选温度,会高估校准效果。第二,温度参数在验证集上选择时,如果候选区间过细且校准集太小,仍然可能过拟合,一般候选温度从中选取5到10个即可。第三,分布偏移会破坏校准结果,上线后需要定期监控ECE或可靠性图。第四,使用集成时要注意成员模型之间不能完全相同,否则平均没有意义。不同随机种子、不同训练数据顺序或者不同数据增强策略都能制造足够多样性。
下面是一个完整的封装示例,把集成预测、温度搜索和ECE计算串起来,方便在项目中直接调用。
import numpy as np
def calibrate_ensemble(logits_list, labels, candidates):
logits_avg = np.mean(np.stack(logits_list, axis=0), axis=0)
best_t = 1.0
best_ece = float('inf')
for T in candidates:
probs = softmax_with_temperature(logits_avg, T)
conf = np.max(probs, axis=-1)
pred = np.argmax(probs, axis=-1)
acc = (pred == labels).astype(np.float32)
ece = expected_calibration_error(acc, conf)
if ece < best_ece:
best_ece = ece
best_t = T
return best_t, best_ece
总结起来,置信度校准不是一个可有可无的后处理步骤。当模型概率被用于拒绝低置信样本、触发人工复核或者与其他系统做阈值交互时,校准质量直接影响业务风险。温度调节提供了一个低成本、易部署的校准工具,集成学习则从模型层面提高概率估计的稳定性。两者配合后,即使不重新训练模型,也能显著改善模型输出的可信度。对于医疗、金融和安全等高风险领域,这套流程应该成为模型上线前的固定评估环节。