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蒸馏核心流程,画出来方便你对照理解:

Logits蒸馏核心流程 教师模型 学生模型 教师Logits 学生Logits 温度 T 软标签 KL散度损失 交叉熵损失(硬标签) α · KL + (1-α) · CE 总损失 → 反向传播 α控制蒸馏强度 T控制软标签平滑度

最后说一个我踩过的坑。有一次我在一个图像分类项目里,把α设到了0.9,T设到了10,想着最大化利用教师知识。结果学生模型训练到一半,损失开始震荡,最后发散。排查了半天,发现是温度太高,软标签几乎变成了均匀分布,学生学到的全是噪声。后来把T降到6,α降到0.7,训练就稳定了。

所以记住:高温不是万能的。T太高,软标签的信息熵太大,反而会淹没教师模型真正有用的判断。找到一个平衡点,才是工程落地的关键。