关于AI温度缩放的几点记录

最近在项目里又碰了一次AI温度缩放,有几处判断值得留下来。

背景和动机

最近在做模型部署的时候,遇到一个很现实的问题:模型给出的"置信度"和实际准确率不匹配。

举个例子:模型说某个样本是"猫"的概率是95%,但实际上它经常搞错;有时候模型说某个样本是"狗"的概率只有55%,结果它还真对了。这种"嘴上说不要,身体很诚实"的情况,在生产环境中很危险——你可能误以为模型很自信,结果实际表现很糟糕。

这就是典型的置信度校准问题。而温度缩放就是解决这个问题的一个简单而有效的技术。

写这篇的动机很直接:自己踩过坑,网上资料又多公式少落地,留给后面做部署的同学当备忘。

什么是置信度校准问题

先搞清楚我们在说什么。

置信度校准的核心思想很简单:模型给出的概率值,应该和实际准确率一致。

  • 如果模型说"这是猫"的概率是80%,那么在所有模型给出80%置信度的预测中,应该有80%确实是猫
  • 如果模型说"这是狗"的概率是90%,那么在所有模型给出90%置信度的预测中,应该有90%确实是狗

但现实往往很残酷。现代深度学习模型(尤其是那些用了Softmax的)普遍存在过度自信的问题——模型给出的概率值往往偏高。

用一个比喻:这就像考试估分,你感觉自己能考90分,结果实际只考了70分。这种"过度自信"在日常考试中只是有点尴尬,但在医疗诊断、金融风控等场景中,后果就很严重了。

import matplotlib.pyplot as plt
import numpy as np

# 可靠性图示例
plt.figure(figsize=(8, 6))
confidence = np.array([0.5, 0.6, 0.7, 0.8, 0.9])
accuracy_uncalibrated = np.array([0.3, 0.4, 0.55, 0.65, 0.75])  # 过度自信
accuracy_calibrated = np.array([0.48, 0.62, 0.68, 0.82, 0.88])   # 校准后

plt.plot(confidence, accuracy_uncalibrated, 'ro--', label='未校准 (过度自信)')
plt.plot(confidence, accuracy_calibrated, 'go-', label='已校准')
plt.plot([0, 1], [0, 1], 'k--', label='理想校准线')

plt.xlabel('模型置信度')
plt.ylabel('实际准确率')
plt.title('可靠性图:置信度 vs 准确率')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

可靠性图

上图很直观地展示了过度自信的问题:红色虚线表示模型的表现,明显低于理想的黑色对角线。绿色实线是校准后的效果,更接近理想状态。

温度缩放的核心原理

温度缩放的公式非常简单:

scaled_probs = softmax(logits / temperature)

就这么简单。temperature 是一个超参数,通常通过在验证集上优化来找到最佳值。

关键点:

  • temperature = 1:不做任何改变,就是原始的Softmax
  • temperature > 1:让概率分布更平缓(更"不确定")
  • temperature < 1:让概率分布更尖锐(更"自信")

直觉上理解:

  • 模型过度自信 → 用 temperature > 1 来"降温"
  • 模型不够自信 → 用 temperature < 1 来"加热"
import matplotlib.pyplot as plt
import numpy as np

# 不同温度对概率分布的影响
logits = np.array([2.0, 1.5, 0.5, -0.5])
temperatures = [0.5, 1.0, 2.0]

plt.figure(figsize=(10, 6))
x = np.arange(len(logits))
width = 0.25

for i, temp in enumerate(temperatures):
    exp_x = np.exp(logits / temp)
    probs = exp_x / np.sum(exp_x)
    plt.bar(x + i * width, probs, width, label=f'T={temp}', alpha=0.7)

plt.xlabel('类别')
plt.ylabel('概率')
plt.title('不同温度对概率分布的影响')
plt.xticks(x + width, ['类别A', '类别B', '类别C', '类别D'])
plt.legend()
plt.grid(True, alpha=0.3, axis='y')
plt.show()

温度对概率分布的影响

从上图可以看到:

  • T=0.5(蓝色):概率分布很尖锐,模型非常自信
  • T=1.0(橙色):原始Softmax,中等分布
  • T=2.0(绿色):概率分布更平缓,置信度更低

实现步骤

温度缩放的整体流程可以概括为:

graph TD A[原始模型输出] --> B[准备验证集] B --> C[搜索最佳温度] C --> D[应用温度缩放] D --> E[评估校准效果] E --> F{ECE是否改善?} F -->|是| G[部署使用] F -->|否| H[调整搜索范围或尝试其他方法] H --> C G --> I[定期重新校准]

第一步:准备数据

你需要一个有标签的验证集,这是计算temperature的基础。

import numpy as np
from sklearn.metrics import log_loss

# 假设我们有验证集
logits_val = np.array([[2.0, 1.0, 0.5], [1.0, 2.0, 0.5], [0.5, 1.0, 2.0]])  # 模型输出logits
labels_val = np.array([0, 1, 2])  # 真实标签

第二步:搜索最佳温度

通常我们通过最小化负对数似然(NLL)来寻找最佳temperature。

def softmax(x, temperature=1.0):
    exp_x = np.exp(x / temperature)
    return exp_x / np.sum(exp_x, axis=1, keepdims=True)

def find_best_temperature(logits_val, labels_val):
    temperatures = np.logspace(-3, 3, 100)  # 从0.001到1000
    best_temp = 1.0
    best_nll = float('inf')

    for temp in temperatures:
        probs = softmax(logits_val, temp)
        nll = log_loss(labels_val, probs, labels=np.arange(logits_val.shape[1]))
        if nll < best_nll:
            best_nll = nll
            best_temp = temp

    return best_temp

best_temperature = find_best_temperature(logits_val, labels_val)
print(f"最佳温度: {best_temperature}")

第三步:应用温度缩放

找到最佳temperature后,在预测时使用它。

def predict_with_temperature(logits, temperature):
    probs = softmax(logits, temperature)
    return probs

# 使用
logits_test = np.array([[1.5, 1.0, 0.5], [0.5, 1.5, 1.0]])
final_probs = predict_with_temperature(logits_test, best_temperature)

第四步:评估校准效果

怎么知道温度缩放有没有用?我们可以用**期望校准误差(ECE)**来衡量。

def compute_ece(probs, labels, n_bins=10):
    # 将概率分成n_bins个区间
    bin_boundaries = np.linspace(0, 1, n_bins + 1)
    bin_lowers = bin_boundaries[:-1]
    bin_uppers = bin_boundaries[1:]

    # 获取最大概率和对应预测
    confidences = np.max(probs, axis=1)
    predictions = np.argmax(probs, axis=1)
    accuracies = predictions == labels

    ece = 0.0
    for bin_lower, bin_upper in zip(bin_lowers, bin_uppers):
        # 找到落在当前bin的样本
        in_bin = (confidences > bin_lower) & (confidences <= bin_upper)
        prop_in_bin = in_bin.mean()

        if prop_in_bin > 0:
            accuracy_in_bin = accuracies[in_bin].mean()
            avg_confidence_in_bin = confidences[in_bin].mean()
            ece += np.abs(avg_confidence_in_bin - accuracy_in_bin) * prop_in_bin

    return ece

# 对比校准前后
original_probs = softmax(logits_val)
calibrated_probs = softmax(logits_val, best_temperature)

original_ece = compute_ece(original_probs, labels_val)
calibrated_ece = compute_ece(calibrated_probs, labels_val)

print(f"校准前ECE: {original_ece:.4f}")
print(f"校准后ECE: {calibrated_ece:.4f}")

ECE越小,说明校准效果越好。

实际踩坑记录

坑1:温度搜索范围太窄

一开始我只搜索temperature从0.1到10,结果发现最佳temperature经常在边缘。后来改成了从0.001到1000,效果好很多。

# 推荐的搜索范围
temperatures = np.logspace(-3, 3, 100)

坑2:验证集太小

温度缩放需要验证集来计算最佳temperature。如果验证集太小,结果就不稳定。我的经验是至少需要几百个样本,最好上千个。

坑3:只看准确率不看校准

有一个陷阱:温度缩放有时会让准确率略微下降,但置信度变得更准确。如果你只看准确率,可能会误以为温度缩放没用。

重要提醒: 置信度校准和准确率是两个不同的指标。在决策阈值场景(如医疗诊断),校准可能比准确率更重要。

坑4:忘记保存temperature

训练时计算了最佳temperature,但部署时忘记保存和使用。这个坑很简单,但很常见。我习惯把temperature和其他模型参数一起保存。

model_params = {
    'model_weights': ...,
    'temperature': best_temperature,
    # 其他参数...
}

实战结果

在一个图像分类任务上,我尝试了温度缩放:

指标校准前校准后
准确率92.3%92.1%
ECE0.0850.023
温度1.02.3

可以看到:

  • 准确率几乎不变(略微下降0.2%)
  • ECE显著降低(从0.085降到0.023)
  • 最佳温度是2.3,说明模型确实存在过度自信问题

这个结果符合预期:温度缩放主要改善置信度校准,对准确率影响很小。

更复杂的场景

多任务学习的温度缩放

如果你的模型同时做多任务(比如分类+检测),你可能需要为每个任务计算不同的temperature。

# 为每个任务计算不同的温度
temps = {}
for task_name, (logits, labels) in task_data.items():
    temps[task_name] = find_best_temperature(logits, labels)

在线学习场景

对于数据分布不断变化的情况,你可能需要定期重新计算temperature。

def定期更新_temperature(new_data):
    # 用新数据重新计算温度
    new_temp = find_best_temperature(new_data['logits'], new_data['labels'])
    # 平滑更新(避免突变)
    current_temp = 0.9 * current_temp + 0.1 * new_temp

什么时候不需要温度缩放

温度缩放不是万能的,有些场景可能不需要:

  1. 只关心预测结果,不关心置信度:比如纯推荐系统,只需要知道推荐什么,不需要知道推荐有多确定
  2. 模型已经很好校准:如果你的模型本来就没过度自信,温度缩放可能反而有害
  3. 没有足够的验证数据:没有足够的数据来稳定地计算temperature

延伸

温度缩放实现简单、开销小,适合当部署流程里的第一道校准。它只能拉置信度的尺度,改不了分布形状;验证集不够或模型本身已校准得不错时,收益有限。

效果不够时可以再看 Platt Scaling、Isotonic Regression 或贝叶斯校准——数据量和任务类型决定用哪种。

最后的思考

模型部署不能只盯训练准确率。模型还得"诚实"——输出的置信度得和实际命中率对得上。

温度缩放是往这个方向迈的一步:实现简单,部署流程里加一道校准不亏。

可以把它想成医生除了给诊断,还得说明把握有多大。模型预测也一样,概率数字得能当真用。

版权声明: 本文首发于 指尖魔法屋-关于AI温度缩放的几点记录https://blog.thinkmoon.cn/post/348-ai-temperature-scaling-confidence-calibration-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!