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层。该对齐哪些层?

我总结了几种策略:

  1. 均匀采样法:把教师层和学生层按比例对应。比如教师12层,学生6层,那就教师第2层对学生第1层,第4层对第2层……
  2. 关键层对齐法:只对齐教师的前几层和后几层。前几层学的是基础特征,后几层学的是任务相关特征。
  3. 动态选择法:训练过程中动态选择对齐的层。这个实现复杂,但效果最好。

嗯,这里要注意。均匀采样法虽然简单,但有个问题:教师的前几层和学生前几层学的东西可能不一样。我建议先用一个小的验证集跑一下,看看教师各层输出的分布,再决定对齐策略。

我的经验:对于BERT-base蒸馏到BERT-tiny(12层到4层),我试过多种策略。最后发现对齐教师第2、5、8、11层到学生的第1、2、3、4层效果最好。说白了,就是跳过教师的第一层(太浅)和最后一层(太专),选中间那些“成熟但不偏科”的层。

4.5 本章知识结构图

下面这张图总结了中间层特征迁移的几种方法和它们之间的关系:

中间层特征迁移方法总览 教师模型 学生模型 隐层输出 隐层输出 注意力矩阵 注意力矩阵 Logits蒸馏(上一章) FitNet: 隐层MSE对齐 + 线性映射 注意力迁移: 注意力矩阵KL对齐 相关性对齐: Gram矩阵MSE对齐 层选择策略 均匀采样 | 关键层对齐 | 动态选择

4.6 实践建议与避坑指南

最后,我把自己这些年踩过的坑和总结的经验列出来:

我曾经犯过的错:

  • 一开始把所有层都加上对齐损失,结果学生模型直接不收敛了。后来发现损失权重太大,学生光顾着模仿中间层,忘了学任务本身。
  • 对齐层选得太靠前,学生学到了很多“噪声”特征。建议从中间层开始试。
  • 忘记detach教师的输出。如果不detach,梯度会回传到教师模型,但教师应该是冻结的。

推荐配置(经验值):

  • FitNet损失权重:0.1 - 0.5(视任务而定)
  • 注意力迁移损失权重:0.5 - 1.0
  • 相关性对齐损失权重:0.01 - 0.1(数值范围大,需要调)
  • 总损失 = Llogits + α · Lhidden + β · Lattn