4. 中间层特征迁移:FitNet方法、注意力迁移(AT)、基于MSE的隐层对齐、层选择策略
前面我们聊了logits层面的蒸馏,说白了就是让学生的输出分布去模仿老师。但你想过没有,老师肚子里那些“中间思考过程”其实更有价值。就像学数学,光看答案没用,得看解题步骤。
中间层特征迁移,就是让学生模型去学习教师模型隐层中的“中间表征”。我自己的经验是,很多时候logits蒸馏做到一定程度就上不去了,加上中间层对齐往往能再涨1-2个点。今天我们就来拆解这几种主流方法。
4.1 FitNet:让学生的隐层直接模仿老师
FitNet是2015年提出的方法,思路非常直接——把教师和学生某个中间层的输出拉出来,算一个MSE损失,让两者尽可能接近。
但这里有个坑:教师和学生模型的隐层维度通常不一样。比如教师可能是768维,学生只有384维。怎么办?加一个线性映射层做适配。
核心公式:
LFitNet = || ft(x) - W · fs(x) ||²
其中 ft 是教师隐层输出,fs 是学生隐层输出,W 是可学习的线性映射矩阵。
我在项目中遇到过一个问题:直接对齐最后一层效果反而不好。后来发现,教师最后一层已经高度抽象,学生根本学不动。建议选中间偏后的层,大概在总层数的60%-70%位置。
# FitNet 对齐损失示例(PyTorch风格)
def fitnet_loss(teacher_hidden, student_hidden, adaptor):
"""
teacher_hidden: [batch, seq_len, d_t]
student_hidden: [batch, seq_len, d_s]
adaptor: 线性层 d_s -> d_t
"""
student_mapped = adaptor(student_hidden)
loss = F.mse_loss(student_mapped, teacher_hidden.detach())
return loss
我的小技巧:训练初期先只跑logits蒸馏,等loss降不动了再加入FitNet损失。两个损失一起上容易让学生“顾此失彼”。
4.2 注意力迁移(AT):抓住模型“看哪里”
Transformer的核心是自注意力机制。说白了,注意力图就是模型在说“我觉得这句话里这几个词比较重要”。如果我们能让学生的注意力图和老师的一样,那学生的“注意力分配能力”就学到手了。
注意力迁移(Attention Transfer, AT)最早由Zagoruyko在2017年提出,但当时是针对CNN的。在NLP中,我们直接对齐Transformer每一层的注意力矩阵。
具体做法:
- 取出教师和学生对应层的注意力权重矩阵 At 和 As
- 计算两者之间的KL散度或MSE损失
- 对多头注意力,可以对所有头取平均,也可以逐头对齐
我曾经踩过的坑:逐头对齐时,教师和学生头数不一样怎么办?我试过用线性映射把学生头数映射到教师头数,但效果一般。后来发现直接取平均后对齐更稳定。你想想看,头数不同说明模型容量不同,强行对齐每个头反而限制了学生的自由度。
# 注意力迁移损失示例
def attention_transfer_loss(teacher_attn, student_attn):
"""
teacher_attn: [batch, heads, seq_len, seq_len]
student_attn: [batch, heads, seq_len, seq_len]
"""
# 对头维度取平均
t_avg = teacher_attn.mean(dim=1) # [batch, seq_len, seq_len]
s_avg = student_attn.mean(dim=1)
# 计算KL散度
loss = F.kl_div(
F.log_softmax(s_avg.view(-1, s_avg.size(-1)), dim=-1),
F.softmax(t_avg.view(-1, t_avg.size(-1)), dim=-1),
reduction='batchmean'
)
return loss
4.3 基于MSE的隐层对齐:简单但有效
除了注意力图,我们还可以直接对齐隐层输出。这里有几个变种:
| 对齐方式 | 对齐对象 | 适用场景 | 我的评价 |
|---|---|---|---|
| 隐层输出对齐 | 每一层的输出向量 | 通用场景 | 简单直接,但计算量大 |
| 隐层梯度对齐 | 隐层输出的梯度 | 小模型训练不稳定时 | 收敛更快,但实现复杂 |
| 隐层相关性对齐 | 隐层之间的相关性矩阵 | 教师学生结构差异大时 | 鲁棒性好,我比较常用 |
我个人习惯用隐层相关性对齐。为什么?因为直接对齐输出向量太“死板”了。教师和学生隐层维度不同、分布不同,硬对齐反而让学生学得别扭。相关性对齐只关心“层与层之间的关系模式”,给了学生更多灵活性。
相关性对齐公式:
Gt = ft(x) · ft(x)T (教师层的Gram矩阵)
Gs = fs(x) · fs(x)T (学生层的Gram矩阵)
Lcorr = || Gt - Gs ||²
4.4 层选择策略:不是所有层都值得对齐
这是最容易被忽视的问题。教师模型可能有12层、24层甚至更多,学生可能只有6层。该对齐哪些层?
我总结了几种策略:
- 均匀采样法:把教师层和学生层按比例对应。比如教师12层,学生6层,那就教师第2层对学生第1层,第4层对第2层……
- 关键层对齐法:只对齐教师的前几层和后几层。前几层学的是基础特征,后几层学的是任务相关特征。
- 动态选择法:训练过程中动态选择对齐的层。这个实现复杂,但效果最好。
嗯,这里要注意。均匀采样法虽然简单,但有个问题:教师的前几层和学生前几层学的东西可能不一样。我建议先用一个小的验证集跑一下,看看教师各层输出的分布,再决定对齐策略。
我的经验:对于BERT-base蒸馏到BERT-tiny(12层到4层),我试过多种策略。最后发现对齐教师第2、5、8、11层到学生的第1、2、3、4层效果最好。说白了,就是跳过教师的第一层(太浅)和最后一层(太专),选中间那些“成熟但不偏科”的层。
4.5 本章知识结构图
下面这张图总结了中间层特征迁移的几种方法和它们之间的关系:
4.6 实践建议与避坑指南
最后,我把自己这些年踩过的坑和总结的经验列出来:
我曾经犯过的错:
- 一开始把所有层都加上对齐损失,结果学生模型直接不收敛了。后来发现损失权重太大,学生光顾着模仿中间层,忘了学任务本身。
- 对齐层选得太靠前,学生学到了很多“噪声”特征。建议从中间层开始试。
- 忘记detach教师的输出。如果不detach,梯度会回传到教师模型,但教师应该是冻结的。
推荐配置(经验值):
- FitNet损失权重:0.1 - 0.5(视任务而定)
- 注意力迁移损失权重:0.5 - 1.0
- 相关性对齐损失权重:0.01 - 0.1(数值范围大,需要调)
- 总损失 = Llogits + α · Lhidden + β · Lattn