4、知识蒸馏:教师-学生网络架构、蒸馏损失函数设计、蒸馏在边缘设备上的实践

知识蒸馏,说白了就是「大模型教小模型」。

我刚开始接触这个方向时,心里也犯嘀咕:既然都要训练,为什么不直接训练一个小模型?后来在项目里踩了坑才明白——小模型自己学,上限太低了。你想想看,一个只有几百万参数的模型,让它从零开始理解复杂的视觉特征,太难了。

蒸馏的核心思路很简单:让一个已经训练好的大模型(教师网络)去指导一个小模型(学生网络)学习。学生不仅看数据本身的标签,还要模仿教师的「思考过程」。

教师-学生网络架构

教师网络通常是一个参数量巨大的模型,比如 ResNet-152、ViT-Large 甚至更大的。学生网络则是一个轻量级模型,比如 MobileNet、ShuffleNet 或者 TinyBERT。

我个人习惯把蒸馏分为两种模式:

  • 离线蒸馏:教师提前训练好,固定参数。学生只从教师的输出中学习。这是最常用的方式,稳定、可控。
  • 在线蒸馏:教师和学生同时训练,互相影响。这种方式适合教师本身也在不断更新的场景,但训练不稳定,我一般不太推荐。

架构上,教师和学生的输入是相同的。教师输出「软标签」(soft label),也就是经过 softmax 后的概率分布。学生则同时学习硬标签(ground truth)和软标签。

关键点:软标签里包含了教师对类别之间相似性的理解。比如一张猫的图片,教师可能输出「猫 0.9,老虎 0.05,狗 0.03,汽车 0.02」。这个分布告诉学生:猫和老虎更像,和汽车差得远。这种信息是硬标签给不了的。

我曾经在一个工业质检项目里试过直接用 MobileNet 训练,准确率只有 82%。后来用 ResNet-152 做教师蒸馏,同样的 MobileNet 结构,准确率直接跳到 91%。差距就是这么大。

蒸馏损失函数设计

蒸馏损失函数一般由两部分组成:

  1. 硬标签损失:学生输出与真实标签之间的交叉熵损失。
  2. 软标签损失:学生输出与教师输出之间的 KL 散度或 MSE 损失。

总损失公式如下:

L_total = α * L_hard + (1 - α) * L_soft

其中 α 是一个平衡系数,我一般设 0.3 到 0.5 之间。α 太大,学生就只学硬标签,失去了蒸馏的意义;α 太小,学生可能过度拟合教师的错误。

软标签损失里还有一个关键参数——温度 T。公式是这样的:

softmax(z_i / T)

温度 T 越高,输出的概率分布越平滑,类别之间的差异越小。T 越低,分布越尖锐,接近 one-hot 编码。

温度 T 效果 适用场景
T = 1 正常 softmax 常规蒸馏
T > 1 分布平滑,信息丰富 教师输出噪声大时
T < 1 分布尖锐,聚焦主要类别 学生能力弱时

我建议新手从 T=4 开始试。温度太高,学生学到的都是模糊信息,反而不好。

避坑指南:我曾经在蒸馏时忘记对教师的输出也使用相同的温度缩放,结果损失一直降不下去。记住,教师和学生必须用同一个 T 计算 softmax,否则分布不匹配。

蒸馏在边缘设备上的实践

边缘设备上跑蒸馏,说白了就是「怎么把蒸馏后的模型部署到小芯片上」。这里我分三步讲:

第一步:选择合适的教师

教师不是越大越好。我见过有人拿 GPT-3 去蒸馏一个 TinyBERT,结果教师输出太复杂,学生根本学不会。教师的能力应该比学生高一个量级,但不要高太多。

第二步:设计蒸馏策略

边缘设备上,我常用两种策略:

  • 中间层蒸馏:不仅让学生的输出模仿教师,还让学生的中间特征图也模仿教师。这能让学生学到更丰富的表示。
  • 注意力蒸馏:让学生模仿教师的注意力图。适合 Transformer 结构的模型。

我个人更推荐中间层蒸馏,因为它对 CNN 和 Transformer 都适用,而且实现起来简单。

第三步:部署与量化

蒸馏后的学生模型通常还需要量化才能跑在边缘设备上。我一般用 INT8 量化,精度损失控制在 1% 以内。

# 伪代码示例:蒸馏训练流程
teacher = load_model('resnet152.pth')
student = MobileNetV2(num_classes=10)

for batch in dataloader:
    images, labels = batch
    
    # 教师输出(不计算梯度)
    with torch.no_grad():
        teacher_logits = teacher(images)
    
    # 学生输出
    student_logits = student(images)
    
    # 计算损失
    loss_hard = cross_entropy(student_logits, labels)
    loss_soft = kl_divergence(
        softmax(student_logits / T),
        softmax(teacher_logits / T)
    )
    loss = alpha * loss_hard + (1 - alpha) * loss_soft
    
    loss.backward()
    optimizer.step()

注意:蒸馏训练时,教师模型一定要设置为 eval 模式,关闭 dropout 和 batch norm 的更新。否则教师输出不稳定,学生学到的都是噪声。

嗯,这里还要提一句。边缘设备上的推理框架,比如 TensorRT、ONNX Runtime、TFLite,都支持蒸馏后的模型。我建议导出 ONNX 格式,兼容性最好。

最后,我画了一张图,帮你理清蒸馏的整体流程:

教师网络 ResNet-152 / ViT-Large 学生网络 MobileNet / TinyBERT 输入数据 软标签(Soft Label) 学生输出 硬标签(Ground Truth) 蒸馏损失函数 L_total 教师网络 学生网络 输入/标签 损失函数

这张图展示了蒸馏的核心流程:输入数据同时送入教师和学生,教师输出软标签,学生输出自己的预测,两者与硬标签一起计算蒸馏损失,然后反向传播更新学生参数。

在实际部署中,我建议先在小数据集上验证蒸馏效果,再全量训练。我曾经在一个语音识别项目里,直接用全量数据蒸馏,结果跑了三天发现温度参数设错了,白白浪费了时间。

我的经验:蒸馏后的学生模型,参数量可以压缩到教师的 1/10 甚至 1/100,推理速度提升 5-10 倍,精度损失控制在 2% 以内。这在边缘设备上是非常划算的买卖。

好了,蒸馏的核心内容就这些。记住一句话:教师是灯塔,学生是航船。灯塔不需要跟着船走,但船必须看着灯塔的方向。

专注资料整理