3.1 温度系数:把暗知识调到看得见 本节摘要:温度系数是蒸馏损失里唯一一个"为传递知识而设"的旋钮:它控制教师概率分布被软化的程度,决定暗知识显形多少。本节从带温度的 softmax 出发讲清机理,给出温度平方的梯度补偿结论,用信息论视角解释温度在"传信息"里的角色,最后落到常见任务的经验温度带与系统调试法。 第一章 1.2 给过温度的直觉——放大镜;本节把这只放大镜的焦距讲成可操作的旋钮。本章是全册的机理核心,本节又是本章的第一块基石:3.2 的配比、3.3 的泛化解释都建立在"温度改变了什么"之上。 温度到底改了什么 把 logits 除以 T 再 softmax,数学上只有一步,信息论上的后果却很具体:分布的熵被抬高了。
本节摘要:温度系数是蒸馏损失里唯一一个"为传递知识而设"的旋钮:它控制教师概率分布被软化的程度,决定暗知识显形多少。本节从带温度的 softmax 出发讲清机理,给出温度平方的梯度补偿结论,用信息论视角解释温度在"传信息"里的角色,最后落到常见任务的经验温度带与系统调试法。
第一章 1.2 给过温度的直觉——放大镜;本节把这只放大镜的焦距讲成可操作的旋钮。本章是全册的机理核心,本节又是本章的第一块基石:3.2 的配比、3.3 的泛化解释都建立在"温度改变了什么"之上。
把 logits 除以 T 再 softmax,数学上只有一步,信息论上的后果却很具体:分布的熵被抬高了。T 等于 1 时,训练充分的教师输出接近 one-hot,熵接近零——一份不含多余信息的答案;T 升高,小概率类别逐级抬升,熵变大,分布开始"说话":它告诉你猫 0.50、狗 0.34、鸟 0.09、车 0.07,类间关系一览无余。
换句话说,硬标签是答案,温度软化后的教师分布是答案加解题人的犹豫。学生模型本来只能从 one-hot 里学到每样本 log C 比特的信息(C 是类别数,one-hot 的自信息),软化后可学的信息上限大幅抬升,而且这些信息与"哪些类别相似"直接相关——这正是从头训练拿不到的部分。
梯度侧还有一个必须知道的结论:除以 T 之后,softmax 梯度的量级大约缩小 T 平方倍。因此 KL 损失要乘 T 平方做补偿,否则温度越高软损失越"没声音"。这不是数值技巧,而是配比正确性的前提——3.2 里所有 alpha 的经验值都默认补偿已做。
import torch import torch.nn.functional as F # 验证梯度随温度缩小:对同一 logits 观察软化交叉熵对输入的梯度范数 logits = torch.tensor([[8.0, 6.5, 1.2, 0.3]], requires_grad=True) target = torch.tensor([[0.0, 1.0, 0.0, 0.0]]) # 以"狗"为软目标做演示 for T in [1.0, 4.0, 8.0]: if logits.grad is not None: logits.grad = None logp = F.log_softmax(logits / T, dim=-1) (-(target * logp).sum()).backward() print(f"T={T}: 梯度范数 = {logits.grad.norm().item():.4f}") # 输出示例: # T=1.0: 梯度范数 = 0.8175 # T=4.0: 梯度范数 = 0.0630 # T=8.0: 梯度范数 = 0.0152 # 梯度约按 T 平方缩小:8 度时只有常温的约 1/54。 # 这就是实现里 * T * T 补偿项的来历。
没有普适的最优温度,但有可用的经验带:
调试不要凭感觉扫全空间,按下面的流程走四步:
# 温度扫描实验:一次训练循环里只动温度,其他全部固定 import torch def temperature_sweep(student_fn, teacher, loader, temps=(2, 3, 4, 6, 8)): results = {} for T in temps: student = student_fn() # 每个温度重新初始化学生 opt = torch.optim.SGD(student.parameters(), lr=0.05, momentum=0.9) for epoch in range(20): # 短程训练足够比较相对优劣 for x, y in loader: s = student(x) with torch.no_grad(): t = teacher(x) loss = 0.1 * F.cross_entropy(s, y) + 0.9 * F.kl_div( F.log_softmax(s / T, dim=-1), F.softmax(t / T, dim=-1), reduction="batchmean") * T * T opt.zero_grad(); loss.backward(); opt.step() results[T] = evaluate(student, loader) # 常温评测,别用高温评测 print(sorted(results.items(), key=lambda kv: -kv[1])) # 输出示例:[(4, 0.926), (6, 0.923), (3, 0.921), (8, 0.917), (2, 0.914)] # 取常温评测最高的温度进入正式训练;注意评测永远在 T=1 下做。

背景。 团队给商品识别模型做蒸馏,学生加软目标后反而比纯硬标签基线低 0.8 个点,一度怀疑蒸馏不适合自家数据。
操作。 按三步排查。第一步查实现,发现软损失没乘温度平方,当时温度取 8——补偿缺失让软损失的有效权重缩小了约 64 倍,等于白蒸。第二步补上补偿后重跑,仍比基线低 0.2 个点;再做温度扫描,发现他们从论文里抄的 T=8 并不适合自己这个教师——该校准检查显示教师过度自信,理应升温,但扫描显示 6 到 10 一段全面走低,反常。第三步追根,发现教师的训练数据里混入了测试分布的同类样本,过自信其实是过拟合的表现,升温软化救不了过拟合。
结果。 换一个数据干净的同任务开源教师,温度扫描后取 4,学生反超基线 2.6 个点。
解读。 这条案例浓缩了温度调试的三条纪律:先验算梯度补偿是否在位,再做单变量扫描(只动温度),最后把"教师为什么过自信"当成独立问题查——温度是显影液,底片本身有问题时显影液救不了。也不要迷信论文里的温度值:它绑定在那个论文的教师校准状态上,你的教师不同,甜点位就不同。
变式。 教师欠自信时(比如标签本身带噪声)温度可以降到 1 到 2,靠分布的尖锐度把可靠判断凸显出来;序列任务上温度常配 token 置信过滤一起用;多教师场景(4.2)每个教师可各配各的温度,先各自扫描再合成。
⚠️ 常见坑:用高温分布做评测。评测时必须回到温度 1;见过有团队拿 T=4 的准确率去报成绩,上线用常温推理,成绩当场蒸发。
💡 关键直觉:温度的调节对象是"信息的显形程度",不是"学习率"。如果你发现升温后训练发散或收敛变慢,先查补偿项和学习率,别急着怪温度——那是梯度量级问题,不是知识问题。