3. 经典蒸馏损失函数:KL散度与温度系数、均方误差(MSE)与L2损失、对比学习损失(InfoNCE)、蒸馏损失与任务损失的联合优化

损失函数,说白了就是蒸馏过程的指挥棒。你选什么损失,学生模型就朝哪个方向学。我这些年调过不下二十种蒸馏方案,最后发现真正常用的其实就那几类。今天咱们把它们的底牌翻出来看看。

3.1 KL散度与温度系数:软标签的精髓

KL散度是蒸馏的基石。它衡量两个概率分布之间的差异。在蒸馏场景里,就是让学生的输出分布尽量贴近老师的输出分布。

公式长这样:

KL(P || Q) = Σ P(x) * log(P(x) / Q(x))

但直接算KL散度有个问题——老师的输出往往太“硬”了。比如分类任务,老师对正确类别的预测可能是0.99,其他类别接近0。学生根本学不到类别间的相似关系。

温度系数就是来解决这个的。Hinton老爷子在原始论文里提的:把logits除以温度T,再做softmax。T越大,分布越平滑,类别间的相对关系就保留得越好。

核心公式:

软化后的概率:q_i = exp(z_i / T) / Σ_j exp(z_j / T)

蒸馏损失:L_KD = KL(q_teacher || q_student) * T²

注意那个T²,是梯度尺度补偿,很多人会漏掉。

我刚开始做蒸馏时,T设成1,结果学生学出来跟直接训练差不多。后来调到4,效果明显好了。但T也不是越大越好——T太大,所有类别概率都趋同,信息就没了。

温度T 分布特点 适用场景
T=1 原始分布,较尖锐 任务损失为主,蒸馏为辅
T=2~5 平滑分布,保留类间关系 通用蒸馏,大部分场景
T>10 接近均匀分布 极少使用,信息稀释严重

我的经验:T从3开始调,观察学生输出的熵值。如果熵值太小(分布太尖),就增大T;如果熵值太大(太平坦),就减小T。一般3~5之间能找到甜点。

3.2 均方误差(MSE)与L2损失:特征层的对齐

KL散度管的是输出层。但很多时候,我们希望学生在中间特征层也学得像老师。这时候MSE就上场了。

MSE损失就是算老师和学生特征图之间的欧氏距离:

L_MSE = 1/N * Σ ||f_teacher - f_student||²

这里有个坑——老师和学生的特征图尺寸往往不一样。我遇到过好几次,学生网络浅,特征图分辨率大,直接算MSE维度都对不上。

避坑指南:我曾经在一个检测模型蒸馏项目里,直接用MSE对齐特征图,结果训练loss降得很快,但验证指标反而掉了。后来发现是特征图没做归一化——老师和学生的特征尺度差了一个数量级。加上LayerNorm或者BN后再算MSE,问题就解决了。

L2损失和MSE本质是一回事,只是L2不取平均。实践中我更喜欢用MSE,因为梯度尺度更稳定,不用频繁调学习率。

特征层对齐有几个常见策略:

  • 直接对齐:选某个中间层,MSE硬对齐。简单,但容易过拟合。
  • 注意力对齐:对齐特征图的注意力图,而不是特征本身。更鲁棒。
  • 关系对齐:对齐样本间的距离关系矩阵。适合小样本场景。

3.3 对比学习损失(InfoNCE):拉近正样本,推开负样本

InfoNCE损失是从对比学习里借来的。它的思路很直接:让学生的表征和老师的表征在特征空间里靠近,同时和其他样本的表征拉开距离。

公式:

L_InfoNCE = -log( exp(sim(z_s, z_t)/τ) / Σ exp(sim(z_s, z_i)/τ) )

其中sim是余弦相似度,τ是温度系数(注意这个τ和KL散度的T不是一回事)。

我为什么喜欢InfoNCE?因为它比MSE更灵活。MSE强制特征完全一致,但InfoNCE只要求相对关系一致——学生特征和老师特征的距离,要比和其他样本的距离近就行。这给了学生更多自由度。

适用场景对比:

  • MSE:特征维度固定,老师和学生结构相似,要求精确对齐。
  • InfoNCE:特征维度可不同,老师和学生结构差异大,只要求相对关系。

我在做跨架构蒸馏(比如ResNet蒸馏到MobileNet)时,InfoNCE的效果明显好于MSE。因为两个网络的特征空间本身就不一样,硬对齐反而有害。

3.4 蒸馏损失与任务损失的联合优化

光用蒸馏损失不行,任务损失也得带上。否则学生可能学了一堆特征,但分类/检测任务本身没做好。

联合损失的标准形式:

L_total = α * L_task + β * L_distill

α和β是权重系数。怎么调?我见过有人用网格搜索,有人用贝叶斯优化。我的习惯是:

  1. 先把α设成1,β设成0,跑一个baseline。
  2. 然后β从0.1开始试,每次翻倍,观察验证集指标。
  3. 如果β太大(比如>10),任务损失被淹没,学生学不到任务知识。
  4. 如果β太小(比如<0.01),蒸馏没效果。

我的经验:大部分场景下,α:β = 1:1 到 1:5 之间效果最好。但如果你用KL散度做输出层蒸馏,记得把KL散度乘以T²,否则梯度尺度会偏小。

还有一个细节:蒸馏损失和任务损失的梯度尺度可能差很多。我遇到过KL散度的梯度是0.01量级,而交叉熵的梯度是1量级。这时候不调整权重,蒸馏根本不起作用。解决办法是:

  • 监控两个损失的数值,让它们在同一量级。
  • 或者用梯度裁剪,防止某个损失主导。
  • 或者用learnable weight,让网络自己学α和β。

嗯,说到learnable weight,我试过一次,效果不稳定。后来还是老老实实手动调了。

蒸馏损失函数体系与联合优化流程 教师模型 学生模型 特征图 特征图 Logits Logits 损失函数联合优化 KL散度 + T MSE / L2 InfoNCE 任务损失 L_total = α × L_task + β × (L_KL + L_MSE + L_InfoNCE) | 权重α、β需根据梯度尺度调整

最后说一句:损失函数没有银弹。我见过有人用KL散度加MSE加InfoNCE三管齐下,效果反而不如只用KL散度。我的建议是:从最简单的开始,加一个损失看一次效果,不要贪多。