2. 软标签与硬标签:温度系数、KL散度与蒸馏损失调节
知识蒸馏里,最核心的一步就是怎么把大模型(Teacher)的知识传给小模型(Student)。说白了,就是让Student学Teacher的输出。但这里有个关键问题——学什么?是学它预测的类别,还是学它预测时的“犹豫”和“倾向”?
这就引出了我们今天的主角:硬标签和软标签。
2.1 硬标签 vs 软标签:一个例子就懂
先看一个直观的例子。假设我们做一个情感分类,输入是“这部电影太棒了”。
- 硬标签:直接告诉你“这是正面”。(one-hot向量:[1, 0])
- 软标签:告诉你“我90%确定是正面,10%觉得是负面”。(概率分布:[0.9, 0.1])
你想想看,哪个信息量更大?显然是软标签。它不光告诉了你答案,还告诉了你模型在决策时的“置信度”和“类间关系”。
核心观点:蒸馏的本质,就是让Student去模仿Teacher在软标签里表达的“暗知识”。
我在项目里遇到过一种情况:直接用硬标签训Student,效果很差。后来换成软标签,准确率直接涨了3个点。为什么?因为硬标签把Teacher的“犹豫”信息全丢了。
2.2 温度系数T:让软标签更“软”
那软标签怎么生成呢?直接用Softmax的输出?
嗯,这里要注意。原始的Softmax输出往往太“尖锐”了。比如一个分类任务,Teacher可能对正确类输出0.99,对其他类输出0.01。这种分布,Student学起来跟学硬标签区别不大。
所以我们需要一个温度系数T来调节。
公式很简单:
q_i = exp(z_i / T) / Σ_j exp(z_j / T)
其中z_i是logits(模型最后一层的输出,还没过Softmax)。T就是温度系数。
T的作用:
- T=1:就是标准的Softmax。
- T>1:概率分布变得更平滑,小概率类的值被放大,类间关系更清晰。
- T→0:趋近于one-hot,也就是硬标签。
我的经验:T一般取2-8之间。我习惯先从T=4开始试。如果Student太小,可以适当提高T,让Teacher的“暗知识”更明显。
举个例子,假设Teacher对三个类输出的logits是[10, 3, 1]:
| T值 | 类别A | 类别B | 类别C | 特点 |
|---|---|---|---|---|
| T=1 | 0.999 | 0.001 | 0.000 | 太尖锐,几乎就是硬标签 |
| T=4 | 0.836 | 0.114 | 0.050 | 平滑,保留了类间关系 |
| T=8 | 0.635 | 0.229 | 0.136 | 更平滑,但可能丢失主要信息 |
你看,T=4的时候,类别B和C的概率明显提升了。Student就能学到“类别A和B更相似,和C差异更大”这种知识。
2.3 KL散度损失函数:衡量两个分布的差异
有了Teacher的软标签,怎么让Student去学呢?我们需要一个损失函数来衡量两个概率分布之间的差异。
这里用的就是KL散度(Kullback-Leibler Divergence)。
公式:
KL(P || Q) = Σ P(x) * log(P(x) / Q(x))
其中P是Teacher的分布,Q是Student的分布。
KL散度的特点:
- 非对称性:KL(P||Q) ≠ KL(Q||P)。蒸馏时我们用KL(Teacher||Student),意思是“让Student去拟合Teacher”。
- 值非负:当两个分布完全一致时,KL=0。
- 对概率敏感:Teacher认为概率大的类,Student必须学得准,否则惩罚很大。
我曾经踩过的坑:直接用KL散度训Student,发现loss降不下去。后来检查发现,Teacher的logits里有些类概率极小(接近0),导致log(P/Q)出现数值不稳定。解决办法是给logits加一个很小的epsilon(比如1e-7)。
在PyTorch里实现蒸馏的KL损失,代码很简单:
import torch
import torch.nn.functional as F
def distillation_loss(student_logits, teacher_logits, T=4.0):
"""
计算蒸馏损失
Args:
student_logits: Student模型的logits
teacher_logits: Teacher模型的logits
T: 温度系数
Returns:
loss: KL散度损失
"""
# 用温度T软化两个分布
soft_student = F.log_softmax(student_logits / T, dim=1)
soft_teacher = F.softmax(teacher_logits / T, dim=1)
# 计算KL散度,然后乘以T^2
loss = F.kl_div(soft_student, soft_teacher, reduction='batchmean') * (T ** 2)
return loss
注意最后乘了T²。这是Hinton论文里的做法,目的是让梯度的大小不受T的影响。我刚开始做蒸馏时没注意这个细节,调T半天没效果,后来才发现是忘了乘T²。
2.4 蒸馏损失的权重调节
实际训练时,我们通常不只使用蒸馏损失。还会加上Student和真实标签之间的交叉熵损失。
总损失公式:
L_total = α * L_CE(student, true_labels) + (1 - α) * L_KL(student, teacher, T)
这里的α就是蒸馏损失的权重调节参数。
α怎么选?我个人的经验:
- α=0.7:适合大部分场景,蒸馏损失占主导,但保留一部分真实标签信息。
- α=0.5:两者均衡,适合Teacher和Student规模差距不大的情况。
- α=0.9:几乎只用真实标签,蒸馏效果弱。适合Teacher质量不高时。
- α=0.1:几乎全靠蒸馏,适合Teacher非常强、数据量少的情况。
我的建议:先固定T=4,α=0.7跑一个baseline。然后调α,观察验证集效果。如果Student过拟合了,就增大α(多用真实标签);如果欠拟合,就减小α(多学Teacher)。
另外,α和T是联动的。T越大,软标签越平滑,蒸馏损失的影响相对变小。所以调大T时,可以适当减小α来补偿。
2.5 本章知识结构图
下面这张图总结了软标签蒸馏的核心流程:
2.6 实践中的几个要点
最后,总结几个我在项目中反复踩过的坑:
- Teacher和Student的logits尺度要匹配:如果Teacher的logits数值范围很大(比如几百),直接除以T可能效果不好。可以先对Teacher的logits做LayerNorm或标准化。
- 温度T和α要一起调:不要只调一个。我习惯用网格搜索,T在[2,4,6,8]里选,α在[0.3,0.5,0.7]里选。
- 蒸馏不是万能的:如果Teacher本身效果就不好,蒸馏出来的Student也不会好。先确保Teacher的质量。
- 注意数值稳定性:KL散度里log(0)的问题,记得加epsilon。
一句话总结:软标签是Teacher的“暗知识”,温度T让暗知识更清晰,KL散度让Student去模仿,α调节模仿的力度。这四个东西配合好了,蒸馏就成功了一大半。