简介这份资源是面向毕业设计与模型压缩入门者的识别算法Python源码包围绕知识蒸馏与剪枝两条主线展开帮助读者在有限算力下完成识别模型的轻量化改造与对比实验。包内共185个文件以79个py源码为核心辅以60个pyc编译文件、7个txt说明、2个json配置及若干训练日志与记录文件压缩包约4.03MB体量轻便便于本地复现与二次修改。内容覆盖不同数据集上的模型对比、蒸馏与剪枝策略的配置切换以及将模型转换为Apple Silicon架构的实践路径技术栈基于Python与PyTorch等深度学习框架。已有113人学习适合需要完整实验流程、排错思路与目录结构参考的本科或研究生读者可据此快速搭建自己的压缩识别实验基线。1. 从一份毕设代码仓库说起模型压缩到底压的是什么如果你正在做识别方向的毕业设计大概率会遇到一个很尴尬的局面导师要求模型精度不能掉太多但实验室的显卡只有一张 8G 显存的 3060甚至有人拿 MacBook 的 M 系列芯片跑训练。这时候「模型压缩」这四个字就会反复出现在你的检索记录里。这份名为「基于模型压缩的识别算法 python 源码蒸馏和剪枝」的仓库本质上就是一套围绕人脸/图像识别任务、把大模型变小模型的可运行代码集合核心手段是知识蒸馏和剪枝附带不同数据集上的对比实验以及把模型转换到 Apple Silicon 架构的脚本。它适合三类人一是毕设题目直接跟模型压缩、轻量化识别相关的同学二是想把一个已经训好的识别模型塞进边缘设备、但又不想从头设计网络的工程师三是想搞明白蒸馏和剪枝在真实训练流程里怎么接、参数怎么调、日志怎么看的人。仓库里能看到arcfaceTrainLog这类训练日志文件说明它跑的是 ArcFace 系的人脸识别训练流程不是那种只贴一个 demo 的玩具工程。接下来我会按「先搞清楚它在干什么再动手跑起来最后把坑填上」的顺序拆一遍。2. 蒸馏与剪枝的代码骨架先看懂再动手2.1 知识蒸馏在识别任务里的落点知识蒸馏的核心逻辑不复杂用一个已经训好的、精度较高的大模型teacher去指导一个小模型student训练让小模型不仅学真实标签还学大模型输出的软标签分布。在识别任务里这个软标签往往比 one-hot 标签信息量更大因为人脸识别里不同身份之间的相似度是有层次的teacher 输出的 logits 或 embedding 能把这个层次关系传递下去。这份仓库里蒸馏相关的代码常见做法是定义一个DistillLoss把 student 的 logits 和 teacher 的 logits 做 KL 散度再和 student 自己的分类损失加权求和。温度系数 T 是关键参数T 越大软标签分布越平滑student 能学到的类间关系越丰富但 T 太大也会让 teacher 的置信度被抹平。我一般会从 T4 开始试配合 alpha0.7 的蒸馏损失权重先看 student 的收敛曲线是否比单独训练更稳。import torch import torch.nn as nn import torch.nn.functional as F class DistillLoss(nn.Module): def __init__(self, temperature4.0, alpha0.7): super().__init__() self.T temperature self.alpha alpha self.ce nn.CrossEntropyLoss() def forward(self, student_logits, teacher_logits, labels): # 硬标签损失student 自己学真实类别 hard_loss self.ce(student_logits, labels) # 软标签损失student 模仿 teacher 的分布 soft_student F.log_softmax(student_logits / self.T, dim1) soft_teacher F.softmax(teacher_logits / self.T, dim1) soft_loss F.kl_div(soft_student, soft_teacher, reductionbatchmean) * (self.T ** 2) # 加权求和alpha 控制蒸馏强度 return self.alpha * soft_loss (1 - self.alpha) * hard_loss这段代码里temperature控制软标签平滑程度alpha控制蒸馏损失占比。注意soft_loss后面乘了T**2这是 Hinton 原论文里的做法目的是让梯度尺度在温度变化时保持稳定。如果你发现 student 训练初期 loss 震荡很大先把 alpha 降到 0.5 试试等模型稳定后再慢慢加回去。2.2 剪枝的两种粒度与代码入口剪枝分结构化剪枝和非结构化剪枝。非结构化剪枝是把权重矩阵里绝对值小的元素置零模型体积能压下来但普通硬件上不一定能加速因为稀疏矩阵的运算需要专门支持。结构化剪枝是直接砍掉整个通道或卷积核模型结构真的变小推理速度提升明显但精度损失也更直接。这份仓库里两种都有涉及常见做法是先做非结构化剪枝拿到一个稀疏模型再用蒸馏把精度拉回来或者直接对 student 做结构化剪枝后再微调。剪枝的代码入口一般是一个prune_model函数接收模型、剪枝比例和剪枝方式。下面是一个基于 PyTorch 的 L1 非结构化剪枝示例import torch.nn.utils.prune as prune def prune_model(model, amount0.3): # 只对卷积层和全连接层做剪枝 for name, module in model.named_modules(): if isinstance(module, (nn.Conv2d, nn.Linear)): # L1 非结构化剪枝按权重绝对值大小裁掉最小的 amount 比例 prune.l1_unstructured(module, nameweight, amountamount) # 把剪枝掩码固化到权重里否则推理时还是原模型 prune.remove(module, weight) return modelamount0.3表示剪掉 30% 的权重。prune.remove这一步很多人会漏掉不调用它的话剪枝只是加了一个 mask模型实际参数量没变导出 ONNX 或转 CoreML 时会发现体积根本没降。结构化剪枝要用prune.ln_structured指定dim参数决定按通道还是按卷积核剪这个后面在避坑章节会细说。2.3 训练日志与实验对比怎么看仓库里arcfaceTrainLog这个文件是训练日志里面通常记录了每个 epoch 的 loss、accuracy、学习率变化。看日志的时候不要只盯最后一行精度重点看三件事一是 student 单独训练的 baseline 和蒸馏后的曲线对比如果蒸馏后收敛更快但最终精度只高 0.2 个点那蒸馏的性价比就要打问号二是剪枝后微调的第一个 epoch精度通常会掉 5 到 15 个点这是正常的关键看后面能不能爬回来三是不同数据集上的表现差异仓库里提到「不同数据集上的模型对比」说明它至少跑了两个数据集你要确认这两个数据集的类别数和图片尺寸是否一致不一致的话对比结论不能直接下。3. 把仓库跑起来环境、数据与训练命令3.1 环境配置与依赖安装这份代码是 Python 写的深度学习框架是 PyTorch。如果你用的是 Windows 或者 Linux先确认显卡驱动和 CUDA 版本然后建一个干净的虚拟环境。我一般用 conda 建环境避免和系统里的包打架。conda create -n model_compress python3.8 -y conda activate model_compress # 根据你的 CUDA 版本装 PyTorch下面以 CUDA 11.3 为例 pip install torch1.12.1cu113 torchvision0.13.1cu113 -f https://download.pytorch.org/whl/torch_stable.html # 装其他依赖仓库里一般有 requirements.txt pip install -r requirements.txt如果你用的是 Apple Silicon 的 MacPyTorch 从 1.12 开始支持 MPS 后端但仓库里提到的「模型转换为 Apple Silicon 架构」通常是指把训练好的模型转成 CoreML 格式用coremltools这个库。注意 MPS 后端在部分算子上的支持和 CUDA 不完全一致蒸馏里的 KL 散度计算在 MPS 上可能遇到精度问题遇到报错先切回 CPU 跑通再换设备。3.2 数据准备与目录结构识别任务的数据集一般按类别分文件夹每个文件夹里放同一个人的多张图片。仓库里没有附带数据集你需要自己准备。常见做法是用 LFW 或者自己采集的人脸数据按 8:2 划分训练集和验证集。目录结构大概是这样dataset/ train/ id_001/ img_001.jpg img_002.jpg id_002/ ... val/ id_001/ ...如果你的数据是 CSV 标注格式需要改一下Dataset类的__getitem__把图片路径和标签对应上。注意 ArcFace 训练时一般要做人脸对齐仓库里如果有align相关的脚本先跑一遍对齐否则识别精度会受姿态影响。3.3 蒸馏训练与剪枝微调的命令行仓库里通常会有train_distill.py和train_prune.py两个入口或者一个main.py通过参数切换模式。下面是一个典型的蒸馏训练命令python train_distill.py \ --teacher_ckpt ./weights/teacher.pth \ --student_arch mobilenet_v2 \ --data_root ./dataset \ --batch_size 64 \ --lr 0.01 \ --epochs 60 \ --temperature 4.0 \ --alpha 0.7 \ --gpu 0teacher_ckpt是预训练好的大模型权重student_arch指定小模型结构temperature和alpha对应蒸馏损失里的参数。剪枝微调的命令类似多一个--prune_amount参数python train_prune.py \ --ckpt ./weights/student_best.pth \ --prune_amount 0.3 \ --prune_type l1_unstructured \ --finetune_epochs 20 \ --lr 0.001剪枝后微调的学习率要比正常训练小一个数量级因为模型结构已经被破坏过学习率太大会把剩下的权重也带偏。我一般用正常训练学习率的十分之一跑 15 到 20 个 epoch看验证集精度是否回到剪枝前的 95% 以上。3.4 模型转换到 Apple Silicon 的流程仓库里提到的「模型转换为 Apple Silicon 架构」常见做法是用coremltools把 PyTorch 模型转成 CoreML 格式然后在 Mac 上直接用 ANE 推理。转换前要先把模型切到 eval 模式并且用torch.jit.trace做一次追踪。import torch import coremltools as ct model.eval() example_input torch.randn(1, 3, 112, 112) traced torch.jit.trace(model, example_input) mlmodel ct.convert( traced, inputs[ct.TensorType(nameinput, shapeexample_input.shape)], compute_unitsct.ComputeUnit.ALL # 让 CoreML 自动选 CPU/GPU/ANE ) mlmodel.save(student_pruned.mlmodel)compute_unitsALL会让 CoreML 自己决定用哪个计算单元如果你发现推理结果和 PyTorch 对不上先改成CPU_ONLY排查是不是算子精度问题。转换后建议用几张验证集图片跑一遍对比 PyTorch 和 CoreML 的输出余弦相似度低于 0.99 就说明转换过程中有精度损失需要检查是否有不支持的算子被替换了。4. 避坑与排查那些日志里不会写的翻车现场4.1 蒸馏 loss 不下降student 精度反而比单独训练低现象加了蒸馏损失后student 的训练 loss 一直在高位震荡验证集精度比不加蒸馏还低 2 到 3 个点。原因通常是 teacher 和 student 的任务不匹配比如 teacher 是在大数据集上训的student 的数据集类别数少很多teacher 输出的软标签里大量类别是 student 根本没见过的这些噪声把 student 带偏了。解决方法是先确认 teacher 和 student 的分类头类别数一致不一致的话只对共享的类别做蒸馏或者把 alpha 降到 0.3 以下让硬标签损失占主导。4.2 剪枝后模型体积没变导出 ONNX 还是原大小现象调了prune.l1_unstructured训练也正常但保存的.pth文件大小和剪枝前一模一样转 ONNX 后体积也没降。原因就是前面提到的没有调用prune.remove。PyTorch 的剪枝默认是「加 mask」而不是「真删权重」mask 是一个额外的 buffer不占多少空间但权重还在。解决是在剪枝后立刻调用prune.remove(module, weight)把 mask 固化到权重里再保存。注意prune.remove之后不能再对同一层重复剪枝要剪多次就分阶段做每次剪完 remove 再重新剪。4.3 结构化剪枝后模型直接报维度不匹配现象用prune.ln_structured剪了卷积层的输出通道前向传播时报RuntimeError: Given groups1, weight of size [64, 128, 3, 3], expected input[1, 96, 56, 56] to have 128 channels。原因是结构化剪枝砍了某一层的输出通道但下一层的输入通道没跟着改PyTorch 的剪枝工具不会自动帮你改下一层。解决方法是结构化剪枝后手动调整下一层的in_channels或者用torch.nn.utils.prune的依赖剪枝功能但那个用起来更复杂。我一般建议新手先做非结构化剪枝等熟悉流程后再碰结构化剪枝。4.4 CoreML 转换后推理结果和 PyTorch 对不上现象PyTorch 上验证集精度 98%转成 CoreML 后在 Mac 上跑同一批图片精度掉到 90% 以下。原因可能是 CoreML 对某些算子做了近似计算或者输入图片的预处理方式不一致。PyTorch 里图片归一化用的均值和方差CoreML 转换时如果没显式指定会默认用 ImageNet 的统计量。解决是在ct.convert里加上preprocessing_args把归一化参数写清楚或者干脆在 PyTorch 里把预处理做完再送进模型CoreML 只负责推理。4.5 训练日志里 accuracy 突然掉到 0.1现象训练到一半日志里 accuracy 从 0.95 直接掉到 0.1 左右loss 飙升。原因通常是学习率设太大或者数据加载时标签错位了。先检查学习率调度器是不是在某个 epoch 后把 lr 调得过高再看Dataset的__getitem__返回的标签和图片是否对应。ArcFace 训练里如果标签错位模型会学到完全错误的映射精度直接崩。解决是固定随机种子重新跑一遍如果还是掉把学习率降到原来的十分之一再试。5. 进阶技巧用蒸馏剪枝组合把模型压到极致前面几章把蒸馏和剪枝分开讲了实际做毕设或者项目落地时单用其中一种往往达不到体积和精度的双重要求。我一般会走「先蒸馏、再剪枝、再蒸馏」的三段式流程第一步用大模型蒸馏出一个小模型让小模型精度尽量接近 teacher第二步对小模型做非结构化剪枝剪掉 30% 到 50% 的权重第三步用同一个 teacher 对剪枝后的模型再做一轮蒸馏微调把剪枝损失的精度拉回来。这套流程在识别任务上通常能把模型体积压到原来的 1/5精度只掉 1 到 2 个点。验证这套流程是否有效不能只看最终精度还要看三个指标模型参数量、推理延迟、以及在不同设备上的内存占用。参数量用sum(p.numel() for p in model.parameters())算推理延迟在目标设备上跑 100 次取平均内存占用用torch.cuda.max_memory_allocated()看。下面是一个对比表格你可以照着填自己的实验数据阶段参数量(M)推理延迟(ms)验证集精度(%)Teacher24.545.299.1Student 单独训练3.48.797.3Student 蒸馏3.48.798.2Student 蒸馏 剪枝1.85.197.6剪枝后再蒸馏1.85.198.0最后一步「剪枝后再蒸馏」的收益往往被低估。很多人剪完枝直接微调就结束了但微调只用了硬标签teacher 的软标签信息没用上。我试过在剪枝后加一轮温度 T2 的蒸馏学习率设成 1e-4跑 10 个 epoch精度能比纯微调高 0.5 到 1 个点。注意这一轮蒸馏的 alpha 要调低0.3 左右就够了因为剪枝后的模型已经比较脆弱蒸馏损失太强反而会干扰它学硬标签。还有一个技巧是分层剪枝。识别模型里浅层卷积提取的是通用特征深层卷积和全连接层才是任务相关的。我一般对浅层剪 20%对深层剪 40% 到 50%这样能在精度损失可控的前提下把参数量压得更低。代码上就是遍历model.named_modules()时根据层名判断是浅层还是深层分别设不同的amount。这个策略在 ArcFace 系模型上比较稳换成其他识别网络需要重新试比例。从那以后我每次做模型压缩都会先把 baseline 跑通再按「蒸馏→剪枝→再蒸馏」的顺序走一遍每一步都存 checkpoint 和日志绝不跳步。希望帮到你。本文还有配套的精品资源点击获取