Logits蒸馏实战:软标签与硬标签、KL散度损失函数、蒸馏损失的权重平衡
好,咱们进入第三章。前两章我们把蒸馏的宏观概念和教师-学生架构聊透了。这一章,直接上手最核心的实操环节——Logits蒸馏。
说白了,Logits蒸馏就是让学生模型去模仿教师模型的“思考过程”。这个“思考过程”体现在哪?就体现在教师模型最后一层输出的Logits上。不是那个已经softmax过的概率,是原始的、没归一化的分数。
我个人习惯把Logits蒸馏比作“老厨师带徒弟”。老厨师(教师)做一道菜,不光告诉徒弟(学生)最终味道是咸是甜,还会把火候、调料比例、下锅顺序这些中间感觉都传给他。Logits就是这些中间感觉的数值化表达。
核心要点:Logits蒸馏的目标,是让学生输出的Logits分布,尽可能接近教师输出的Logits分布。分布越像,学生学到的“暗知识”就越多。
3.1 软标签 vs 硬标签:到底差在哪?
很多初学者会问:直接用教师预测的类别标签(硬标签)去训练学生不行吗?当然可以,但效果差很多。为什么?
咱们看个例子。假设一个三分类任务,教师模型对某张图片的Logits输出是:[15.2, 8.7, -3.1]。
- 硬标签:经过argmax,变成 [1, 0, 0]。学生只知道“第一类是对的”,完全不知道“第二类也有点可能,第三类基本没戏”。
- 软标签:经过带温度的softmax,变成 [0.95, 0.04, 0.01]。学生不仅知道第一类最像,还知道第二类和第三类的相对关系。这个“相对关系”就是暗知识。
我在项目中遇到过这样的情况:用硬标签蒸馏一个轻量级模型,在测试集上准确率死活上不去,比直接从头训练还差。后来换成软标签,同样的学生结构,准确率直接涨了3个点。你想想看,这差距就是信息量的差距。
| 对比维度 | 硬标签 | 软标签 |
|---|---|---|
| 信息量 | 仅包含类别排名 | 包含类别间相似度 |
| 训练难度 | 容易过拟合到噪声 | 更平滑,泛化性好 |
| 对教师质量要求 | 低,只要分类正确即可 | 高,需要教师输出分布合理 |
| 典型应用场景 | 教师模型很弱时 | 教师模型强且稳定时 |
我的小技巧:如果教师模型在某个类别上Logits分布特别尖锐(比如一个类别概率接近1,其他接近0),可以适当调高蒸馏温度T,让分布更平滑。T越大,软标签携带的暗知识越丰富。但T太大也会引入噪声,一般我习惯在2-8之间调参。
3.2 KL散度损失函数:怎么衡量“像不像”?
好,现在我们知道要让学生分布去拟合教师分布。那怎么量化这个“拟合程度”呢?KL散度(Kullback-Leibler Divergence)就是最常用的工具。
KL散度的公式长这样:
KL(P || Q) = Σ P(x) * log(P(x) / Q(x))
其中P是教师分布,Q是学生分布。这个值越小,说明两个分布越接近。当P和Q完全一样时,KL散度为0。
嗯,这里要注意。KL散度是非对称的。什么意思?KL(P||Q) 和 KL(Q||P) 结果不一样。在蒸馏里,我们固定教师分布P作为目标,让学生分布Q去靠近它,所以用 KL(P||Q)。
我曾经踩过一个坑:一开始图省事,用了对称的JS散度。结果训练出来的学生模型,在教师模型置信度高的样本上学得不错,但在教师犹豫不决的样本上表现很差。后来换回KL散度,问题就解决了。因为KL散度对教师分布中概率小的区域惩罚更重,强迫学生去关注那些“模糊地带”。
实际代码实现,用PyTorch的话很简单:
import torch
import torch.nn.functional as F
def kl_div_loss(student_logits, teacher_logits, temperature=4.0):
"""
计算蒸馏的KL散度损失
"""
# 用温度软化分布
soft_student = F.log_softmax(student_logits / temperature, dim=-1)
soft_teacher = F.softmax(teacher_logits / temperature, dim=-1)
# KL散度,注意reduction='batchmean'是标准做法
loss = F.kl_div(soft_student, soft_teacher, reduction='batchmean')
# 乘以温度平方,保持梯度尺度
return loss * (temperature ** 2)
避坑指南:我曾经忘记在KL散度后面乘以 temperature^2,结果损失值小得离谱,模型几乎不更新。这个缩放因子不是可加可不加的,它保证了不同温度下梯度尺度的一致性。你想想看,温度T把Logits缩小了T倍,梯度也会缩小,所以要用T^2补回来。
3.3 蒸馏损失的权重平衡:α和T怎么调?
蒸馏损失从来不是单独用的。实际工程中,我们通常把蒸馏损失和硬标签的交叉熵损失结合起来:
total_loss = α * distillation_loss + (1 - α) * hard_label_loss
这里α就是蒸馏损失的权重。α=0就是纯硬标签训练,α=1就是纯蒸馏。我一般从0.5开始调。
但光调α还不够,温度T也是关键。这两个参数是联动的。我总结了一个经验表格:
| 场景 | α建议 | T建议 | 说明 |
|---|---|---|---|
| 教师模型很强,学生容量小 | 0.7 - 0.9 | 4 - 8 | 高温让软标签更平滑,学生更容易学 |
| 教师和学生容量接近 | 0.3 - 0.5 | 2 - 4 | 低温保留更多细节,学生有能力学 |
| 训练数据量很大 | 0.1 - 0.3 | 1 - 3 | 数据本身已经提供了足够信息 |
| 训练数据量很小 | 0.8 - 0.95 | 5 - 10 | 依赖教师知识防止过拟合 |
我个人习惯的做法是:先固定T=4,在验证集上扫一遍α从0.1到0.9。找到最优α后,再固定这个α,扫一遍T从1到10。这样两轮调参基本就能找到不错的组合。
一个实用建议:不要同时调α和T。这两个参数相互影响,同时调参空间太大,容易陷入局部最优。分步调参,每次只动一个,结果更可控。
下面这张图,是我自己总结的Logits蒸馏核心流程,画出来方便你对照理解:
最后说一个我踩过的坑。有一次我在一个图像分类项目里,把α设到了0.9,T设到了10,想着最大化利用教师知识。结果学生模型训练到一半,损失开始震荡,最后发散。排查了半天,发现是温度太高,软标签几乎变成了均匀分布,学生学到的全是噪声。后来把T降到6,α降到0.7,训练就稳定了。
所以记住:高温不是万能的。T太高,软标签的信息熵太大,反而会淹没教师模型真正有用的判断。找到一个平衡点,才是工程落地的关键。