AI-深度学习

嵌入式视觉模型进阶 29:量化、蒸馏与剪枝的实际取舍

展开合集目录
  1. 嵌入式视觉模型进阶 01:怎样读懂一个实际模型
  2. 嵌入式视觉模型进阶 02:卷积进阶:分组、深度、逐点与空洞卷积
  3. 嵌入式视觉模型进阶 03:MobileNet:深度可分离卷积与倒残差
  4. 嵌入式视觉模型进阶 04:通道注意力与 MobileNetV3
  5. 嵌入式视觉模型进阶 05:ShuffleNet:通道拆分与重排
  6. 嵌入式视觉模型进阶 06:结构重参数化:训练分支怎样合为一个卷积
  7. 嵌入式视觉模型进阶 07:Backbone、Neck、Head 与多尺度特征
  8. 嵌入式视觉模型进阶 08:FPN 与 PAN:怎样融合不同尺度的信息
  9. 嵌入式视觉模型进阶 09:检测头:分类、位置与置信度
  10. 嵌入式视觉模型进阶 10:检测训练:标签分配与损失计算
  11. 嵌入式视觉模型进阶 11:完整 YOLOv8 推理:从图片到原图目标框
  12. 嵌入式视觉模型进阶 12:训练自己的检测器与错误分析
  13. 嵌入式视觉模型进阶 13:检测器导出对照与其他结构路线
  14. 嵌入式视觉模型进阶 14:跟踪基础:运动预测、匹配与轨迹生命周期
  15. 嵌入式视觉模型进阶 15:ByteTrack 思路:用低分框恢复已有轨迹
  16. 嵌入式视觉模型进阶 16:外观特征与 ReID:怎样比较两个目标
  17. 嵌入式视觉模型进阶 17:目标跟随:从轨迹到云台控制接口
  18. 嵌入式视觉模型进阶 18:视频系统:队列、时间戳与异步流水线
  19. 嵌入式视觉模型进阶 19:人脸处理流程:检测、关键点与对齐
  20. 嵌入式视觉模型进阶 20:特征识别:人脸向量、相似度与阈值
  21. 嵌入式视觉模型进阶 21:人体关键点与姿态估计
  22. 嵌入式视觉模型进阶 22:OCR 检测:从文字区域到矫正图片
  23. 嵌入式视觉模型进阶 23:OCR 识别:序列模型与 CTC
  24. 嵌入式视觉模型进阶 24:完整 OCR 流程与识别错误分析
  25. 嵌入式视觉模型进阶 25:轻量语义分割与实例分割
  26. 嵌入式视觉模型进阶 26:工业异常检测:缺陷样本少时怎么办
  27. 嵌入式视觉模型进阶 27:深度估计与图像恢复:两个专题入口
  28. 嵌入式视觉模型进阶 28:面向 NPU 的计算图与算子兼容
  29. 嵌入式视觉模型进阶 29:量化、蒸馏与剪枝的实际取舍
  30. 嵌入式视觉模型进阶 30:综合项目:检测、跟踪与云台跟随验收
嵌入式视觉模型进阶 29:量化、蒸馏与剪枝的实际取舍的封面

第 29 / 30 课。 前置:上一课。本篇的运行范围以实验说明和验证记录为准,构造样例不代表真实任务精度或板端性能。

三种方法改变不同的东西

量化减少数值表示精度;蒸馏用教师信号指导学生;结构化剪枝改变网络宽度等结构。它们可以组合,但应分别建立基线,否则很难判断精度变化来自哪里。

本课的量化部分手工模拟对称 int8 权重,逐输出通道计算 scale,并反量化比较误差;不模拟激活量化,也不宣称它等价于目标 NPU 的完整量化方案。校准样本、量化轴和舍入规则需与实际工具对齐。

蒸馏怎样传递软信息

教师输出经温度 T 调整后作为软目标,学生使用 KL 损失学习分布,同时保留真实标签的交叉熵。常见缩放使用 T² 补偿梯度尺度。本课先在构造二分类数据上训练较宽教师,再训练较窄学生,教师保持 eval 并关闭梯度。

学生对教师的拟合下降不自动表示真实精度提升;教师也可能犯错。要与相同学生结构、相同训练预算、只用硬标签的基线比较,并使用独立验证数据。

剪枝为何需要重建后续层

把权重设为零通常不改变稠密执行形状。真实裁掉中间通道时,需要同步裁掉当前层输出和下一层输入,以及相关 BN、残差和分组约束。本课用两层线性网络展示重建,并证明其输出等于“被裁通道显式置零”的参考网络。

这种对照只证明裁剪索引正确,不证明与原始未裁网络相同。任务精度可能需要微调恢复。

练习与验收

给一个输出通道放入离群权重,比较逐张量与逐通道误差;改变蒸馏温度,记录硬标签损失与 KL;裁掉更大比例通道,比较学生或剪枝后的验证质量。

最终报告至少包含原始、量化、结构改变三个明确版本的文件哈希、质量、内存和端到端延迟。只有真实部署运行库使用了低精度或更窄结构,才可能获得对应资源收益。

配套实验与运行记录

实验:量化、蒸馏与剪枝的实际取舍

查看完整代码:main.py
"""运行逐通道量化误差、教师学生训练和通道裁剪对照,理解三种方法的不同作用。"""

import copy
import torch
from torch import nn
import torch.nn.functional as F


def main():
    torch.manual_seed(7)
    torch.set_num_threads(2)
    w = torch.randn(4, 8)
    scale = w.abs().amax(1, keepdim=True).clamp_min(1e-8) / 127
    q = torch.round(w / scale).clamp(-127, 127)
    restored = q * scale
    assert bool(((w - restored).abs() <= scale / 2 + 1e-6).all())
    print("per-channel simulated weight max error", float((w - restored).abs().max()))
    x = torch.randn(128, 4)
    y = (x[:, 0] + x[:, 1] > 0).long()
    teacher = nn.Sequential(nn.Linear(4, 16), nn.ReLU(), nn.Linear(16, 2))
    opt = torch.optim.Adam(teacher.parameters(), lr=0.03)
    for _ in range(60):
        opt.zero_grad()
        loss = F.cross_entropy(teacher(x), y)
        loss.backward()
        opt.step()
    teacher.eval()
    with torch.no_grad():
        soft = (teacher(x) / 2).softmax(1)
    student = nn.Sequential(nn.Linear(4, 4), nn.ReLU(), nn.Linear(4, 2))
    baseline = copy.deepcopy(student)
    opt = torch.optim.Adam(student.parameters(), lr=0.03)
    plain = torch.optim.Adam(baseline.parameters(), lr=0.03)
    initial = float(F.cross_entropy(student(x), y).detach())
    for _ in range(80):
        logits = student(x)
        loss = 0.5 * F.cross_entropy(logits, y) + 0.5 * 4 * F.kl_div(
            (logits / 2).log_softmax(1), soft, reduction="batchmean"
        )
        opt.zero_grad()
        loss.backward()
        opt.step()
        plain.zero_grad()
        F.cross_entropy(baseline(x), y).backward()
        plain.step()
    final = float(F.cross_entropy(student(x), y).detach())
    assert final < initial
    test = torch.randn(256, 4)
    truth = (test[:, 0] + test[:, 1] > 0).long()
    with torch.no_grad():
        print(
            "synthetic held-out accuracy teacher/student/hard-only",
            *[
                float((m(test).argmax(1) == truth).float().mean())
                for m in [teacher, student, baseline]
            ]
        )
    first = nn.Linear(4, 6)
    last = nn.Linear(6, 2)
    keep = torch.tensor([0, 2, 5])
    small_first = nn.Linear(4, 3)
    small_last = nn.Linear(3, 2)
    with torch.no_grad():
        small_first.weight.copy_(first.weight[keep])
        small_first.bias.copy_(first.bias[keep])
        small_last.weight.copy_(last.weight[:, keep])
        small_last.bias.copy_(last.bias)
        hidden = torch.relu(first(x))
        mask = torch.zeros(6)
        mask[keep] = 1
        reference = last(hidden * mask)
        actual = small_last(torch.relu(small_first(x)))
        torch.testing.assert_close(actual, reference, atol=1e-6, rtol=1e-5)
    print(
        "distillation train CE",
        initial,
        final,
        "channel-pruning index equivalence passed",
    )


if __name__ == "__main__":
    main()
下载代码

在项目根目录执行;下载包解压后,在解压目录执行 python3 main.py。

cd 'content/posts/AI-深度学习/嵌入式视觉模型进阶/29-量化、蒸馏与剪枝的实际取舍/code' && python3 main.py

在本篇代码目录执行 python3 main.py。依赖安装、可选入口见包内 README.md;实际运行结果见 verification.md。下载包包含本课使用的辅助模块与样例。

参考与进一步阅读

原始论文或官方文档用于核对结构、接口和适用范围。本篇代码是围绕教学目标编写的最小实验;除明确注明的完整入口外,不视为对应论文模型的完整复现。


完整进阶目录 · 入门系列 · 上一篇 · 下一篇