1. YOLOv8预训练模型迁移学习
1. YOLOv8源码下载和安装
(1)克隆YOLOv8并安装
克隆项目到本地(如d:):
git clone https://github.com/ultralytics/ultralytics
在自己创建的虚拟环境下执行:
cd ultralytics
pip install -e .
(2)下载预训练权重文件
下载yolov8预训练权重文件,并放置在新建立的weights文件夹下
例如:D:\ultralytics\ultralytics\weights
(3)安装测试
预测图片
yolo predict model=模型路径 source=图片路径
PyTorch 2.6 版本的安全机制导致的。PyTorch 2.6默认启用了 weights_only=True 参数,只允许加载包含张量数据的权重文件,而你的YOLOv8权重文件包含了自定义的类定义。 修改ultralytics/ultralytics/nn/tasks.py
# 原始代码(约716行)
ckpt = torch.load(file, map_location="cpu")
# 修改为:
ckpt = torch.load(file, map_location="cpu", weights_only=False)
预测图片并存储推理结果:
yolo predict model=模型路径 source=图片路径 save_txt
2. 准备自己的数据集
准备数据集脚本:prepare_data.py
原始数据(VOC格式)
↓
1. JPEGImages/ (原始图片)
2. Annotations/ (XML标注)→ in_file读取
↓
转换过程(convert_annotation函数)
↓
YOLOLabels/ (临时YOLO格式标签)→ out_file写入
↓
复制和分配(按比例分配训练/验证集)
↓
最终结构(YOLOv8格式)
↓
1. images/train/ (训练集图片)
2. images/val/ (验证集图片)
3. labels/train/ (训练集标签)
4. labels/val/ (验证集标签)
3. 修改配置文件
修改文件cfg/datasets/VOC.yaml
4. 正常训练自己的数据集
(1)训练命令
yolo detect train data=配置文件路径 model=模型路径 epochs=100 imgsz=640 batch=16 workers=4
注意:如果出现显存溢出,可减小batch size
断点续训:resume
yolo detect train data=配置文件路径 model=模型路径 epochs=100 imgsz=640 batch=16 workers=4 resume
(2)查看训练结果
查看ultralytics/runs/detect/train/weights目录下的文件
(3)训练步骤
使用yolov8在COCO数据集上训练的预训练模型,迁移学习。
迁移学习:
- 第一步:使用YOLOv8n.pt做混合精度测试,真正训练还是用的YOLOs.pt,
- 第二步:把训练集和验证集的标签文件(文本文件)转换成二进制文件(cahe缓冲文件),加载和读取就比较快
- 第三步:训练验证微调,迁移学习
Transferred 349/355 items from pretrained weights
Freezing layer 'model.22.dfl.conv.weight'
AMP: running Automatic Mixed Precision (AMP) checks with YOLOv8n...
F:\ultralytics\ultralytics\utils\checks.py:639: FutureWarning: `torch.cuda.amp.autocast(args...)` is deprecated. Please use `torch.amp.autocast('cuda', args...)` instead.
with torch.cuda.amp.autocast(True):
AMP: checks passed ✅
(4)混合精度测试的作用
1. 核心目的:低成本验证
验证配置正确性:在投入大量时间用大模型正式训练之前,先用最小的模型(nano版,即 YOLOv8n.pt)跑1-2个epoch,快速检查你的整个训练管道是否畅通。
- 数据路径:YAML配置文件、图片和标签路径是否正确。
- 数据格式:标注文件是否能被正确读取和解析。
- 环境依赖:CUDA、PyTorch、Ultralytics等库的版本是否兼容。
- 硬件配置:GPU内存是否足够,batch size设置是否合理。
避免重大浪费:如果直接用 YOLOv8s.pt 训练,跑了几十轮后才发现数据有误或配置错误,浪费的时间和计算资源是巨大的。用 v8n 测试,几分钟就能发现问题。
2. 理解“混合精度”
你提到的“混合精度测试”可能更侧重于测试 AMP(自动混合精度) 是否正常工作。这是PyTorch的一项技术,允许模型同时使用 float16 和 float32 精度进行计算。
- 测试作用:用
v8n快速验证在你的机器上开启amp=True参数后,训练是否能正常进行而不报错(如NaN loss),并且能带来预期的速度提升和内存节省。 - 对最终训练的影响:在
v8n上测试成功后,你就可以放心地对v8s也开启amp=True,从而加速正式训练。其作用和流程可以总结为下图:
5. 测试训练出的网络模型
性能测试
yolo detect val model=模型路径 data=配置文件路径 batch=16 conf=0.001 iou=0.6
注意:性能评估结果和conf和iou阈值的设置有关
测试图片
yolo detect predict model=模型路径 data=配置文件路径 source=测试图片路径
2. 知识点总结
损失函数
1. DepGraph剪枝中的剪枝对象是什么?
卷积层输出通道和对应的依赖参数
2. 剪枝粒度是什么级别?为什么选择这个粒度?
通道级(Channel-level)结构化剪枝
3. 剪枝依据(重要性评估准则)?
L2幅度重要性(Magnitude Importance)
为何选择L2:
1. 简单有效:计算复杂度O(n),适合大规模网络,
2. 理论支持:大权重通常对应重要特征,
3. 与训练兼容:L2正则化后,不重要通道权重自然变小,
4. 实证成功:在ResNet、YOLO等网络上验证有效
4. 剪枝策略是什么?为什么采用这个策略?
采用迭代渐进式剪枝 + 依赖感知分组 + 局部微调的三阶段策略:分16次小步剪枝,剪枝后微调30论。
5.如何处理YOLOv8的特殊结构(如C2f模块)?
采用了结构转换 + DepGraph自动处理的方法
6.如果剪枝后精度损失过大,有哪些恢复策略?
多级精度恢复策略。
轻度损失(<5%):短期微调
中度损失(5-10%):强化微调 + 知识蒸馏
严重损失(>10%): "回退剪枝 + 重新设计
7. YOLOv8为什么选择DepGraph剪枝方法,而不选择其他的剪枝方法?
L1/L2 Norm:基于权重范数剪枝。简单快速,可能破坏网络结构。 BN缩放因子:依赖BN层,但是YOLOv8某些层没有BN
结构化剪枝方法对比
# 几种主流结构化剪枝方法对比
剪枝方法 = {
'Network_Slimming': {
'原理': '基于BN层γ系数的通道重要性',
'问题': '忽略层间依赖,YOLOv8的C2f会出问题'
},
'ThiNet': {
'原理': '基于下一层重建误差',
'问题': '只考虑局部依赖,忽略跨层连接'
},
'Channel_Pruning': {
'原理': 'LASSO回归选择通道',
'问题': '计算复杂,不适合复杂网络'
},
'DepGraph': {
'原理': '构建全局依赖图,确保结构完整性',
'优势': '✅ 适合YOLOv8的复杂连接结构'
}
}
YOLOv8的关键结构特性
全局依赖分析能力
YOLOv8的依赖链示例:
Conv1 → Split → [Branch1, Branch2] → Concat → Conv2
↓ ↑
└──→ Bottleneck →──────────┘
DepGraph可以:
1. 自动发现Conv1和Conv2的依赖关系
2. 识别Split和Concat的通道映射
3. 确保剪枝后网络结构完整
自动剪枝组发现
# DepGraph自动发现YOLOv8中的剪枝组
剪枝组 = {
'C2f_模块': {
'layers': ['cv1', 'cv2', 'bottleneck.conv1', 'bottleneck.conv2'],
'dependency': '必须同时剪枝这些层的对应通道'
},
'PAN-FPN_路径': {
'layers': ['cbs_p5p4', 'cbs_p4p3', 'upsample_p5', 'upsample_p4'],
'dependency': 'P5->P4->P3路径的完整依赖'
}
}
处理复杂连接的能力
1. 残差连接(Add操作):
输入 → 处理 → Add → 输出
↘___________↗
DepGraph: 必须保持输入输出通道一致
2. 拼接连接(Concat操作):
[特征A, 特征B] → Concat → 处理
DepGraph: 识别每个输入来源,分别处理
3. 分割连接(Split操作):
特征 → Split → [分支A, 分支B]
DepGraph: 跟踪每个分割后的通道去向
DepGraph的独特优势
1. 多分支结构:C2f模块有并行分支
2. 跨层连接:PAN-FPN有上下采样和跳跃连接
3. 权重共享:Detect头共享卷积权重
4. 多尺度处理:3个不同尺度的检测头
3. YOLOv8结构及转换过程
3.1 C2f模块转换为C2f_v2
C2f 是 YOLOv8 系列(以及后续一些 YOLO 变体)中引入的一种高效特征提取模块,名字常被解释为 “C2-fusion” 或 “C2 with better gradient Flow”。它属于 CSP(Cross Stage Partial,跨阶段部分残差连接)家族的改良版本,用来取代 YOLOv5/YOLOv7 风格的 C3 / BottleneckCSP 模块。
通俗说:C2f 是一个“把输入特征拆多路、依次加工、再把所有中间特征拼回来然后压缩融合”的结构。它的目标是:
- 强化梯度流(gradient flow),让很深的网络也能很好的训练;
- 提高特征多样性(多分支、多阶段特征同时保留);
- 降低参数量和计算量(FLOPs),尤其在中小模型里;
- 保持导出友好(ONNX/TensorRT),适合工业部署。
C2f 最常出现的位置是 YOLOv8 的 Backbone(主干)和 Neck(特征融合颈部)中的主堆叠单元。可以把它当成 YOLOv8 的“主力积木块”。
CBS:C = Conv,B = BatchNorm,S = SiLU。也就是:卷积层 (Conv) → 批归一化 (Batch Normalization) → SiLU 激活函数
卷积输出(256通道):
[通道0, 通道1, ..., 通道127, 通道128, ..., 通道255]
前128通道 后128通道
↓ ↓
分支1(y0) 分支2(y1)
直接连接 Bottleneck输入
3.1.1 原C2f结构(一个CBS卷积 + Split)
输入 → [CV1] → 分割 → [Bottleneck×N] → 拼接 → CV2 → 输出
│ ├──→ 分支1 (直接连接)
│ └──→ 分支2 (通过多个Bottleneck)
输入(c1) → [单个CBS卷积: 输出2×c2] → 256通道 → Split分割 → [c2] + [c2]
优点:计算高效,一次卷积完成
缺点:剪枝困难
为什么CBS输出2×c2? 因为后面要Split成两份来满足C2f的双分支
3.1.2 转换后的C2f_v2结构(两个CBS卷积)
输入 → [CV0, CV1] → [Bottleneck×N] → 拼接 → CV2 → 输出
│ │ ├──→ 分支1 (通过CV0)
│ │ └──→ 分支2 (通过CV1→Bottleneck)
输入(c1) → [CBS1: 输出c2] → 分支1(128通道)
→ [CBS2: 输出c2] → 分支2(128通道)
优点:便于独立剪枝
缺点:两次卷积,略微低效
关键差异对比表
剪枝影响对比图
3.1.3 为什么需要转换?
因为经过CBS层之后会有两个通道两个分支,而CBS层并不知道哪些通道会去哪个分支。 CBS输出被分割为两部分,两部分通道存在依赖关系。如果剪掉CBS的某些通道,会影响两个分支
CBS输出通道索引:0, 1, 2, ..., 2c2-1
↓
Split分配:前c2个 → 分支1
后c2个 → 分支2
剪枝困境:如果剪掉通道50(属于分支1),不影响分支2
如果剪掉通道150(属于分支2),不影响分支1
但剪枝算法很难知道这个映射关系!
3.1.4 适合DepGraph剪枝的原因:
- 解耦依赖:原结构CV1同时为两个分支提供特征,通道相互依赖。
- 独立剪枝:CV0和CV1现在可以独立剪枝各自通道。
- 依赖图清晰:输入到两个分支的依赖关系变得明确。
3.1.5 总结
C2f_v2的转换核心思想是将耦合的通道分割解耦为独立的卷积路径,这使得:
- 每个卷积层可以独立评估通道重要性
- DepGraph可以构建清晰的依赖关系
- 剪枝决策可以沿着依赖图正确传播
- 避免了原结构中CV1层输出通道必须成对剪枝的限制
3.2 PAN-FPN颈部转换
3.2.1 原PAN-FPN结构
原PAN-FPN有复杂的跨层连接和权重共享,对DepGraph剪枝不友好:
- C2f中的CBS卷积权重共享(如图所示)
- 上/下采样操作共享
- 权重共享导致层间依赖分析困难,一个层的剪枝会影响多个输出路径
代码中的实现方式
# ========== 原结构(有共享权重问题)==========
class PANFPN_Original(nn.Module):
"""原PAN-FPN结构(DepGraph剪枝困难)"""
def __init__(self, ch=(256, 512, 1024)):
super().__init__()
c3, c4, c5 = ch # 输入通道数
# 预处理(各尺度独立)
self.cv3 = Conv(c3, 256, 1) # P3
self.cv4 = Conv(c4, 256, 1) # P4
self.cv5 = Conv(c5, 256, 1) # P5
# 问题1:共享的上采样层(原代码中可能共享)
# 实际上YOLOv8中上采样是函数调用,但依赖分析时仍有关联
# 问题2:共享的卷积层(简化示例)
self.cv_td = Conv(512, 256, 3) # 自顶向下共享
self.cv_bu = Conv(512, 256, 3) # 自底向上共享
def forward(self, x):
# x = [p3, p4, p5]
p3, p4, p5 = x
# 预处理
p3 = self.cv3(p3) # 80×80×256
p4 = self.cv4(p4) # 40×40×256
p5 = self.cv5(p5) # 20×20×256
# 自顶向下(共享cv_td导致问题)
p5_up = F.interpolate(p5, scale_factor=2) # 40×40
p4_td = self.cv_td(torch.cat([p5_up, p4], 1)) # 第一次使用cv_td
p4_up = F.interpolate(p4_td, scale_factor=2) # 80×80
p3_out = self.cv_td(torch.cat([p4_up, p3], 1)) # 第二次使用cv_td(共享权重!)
# 自底向上(共享cv_bu导致问题)
p3_down = F.interpolate(p3_out, scale_factor=0.5, mode='bilinear') # 40×40
p4_out = self.cv_bu(torch.cat([p3_down, p4_td], 1)) # 第一次使用cv_bu
p4_down = F.interpolate(p4_out, scale_factor=0.5, mode='bilinear') # 20×20
p5_out = self.cv_bu(torch.cat([p4_down, p5], 1)) # 第二次使用cv_bu(共享权重!)
return [p3_out, p4_out, p5_out]
3.2.2 转换后的结构(DepGraph友好)
# ========== 转换后结构(DepGraph友好)==========
class PANFPN_Converted(nn.Module):
"""转换后的PAN-FPN(每个路径独立)"""
def __init__(self, ch=(256, 512, 1024)):
super().__init__()
c3, c4, c5 = ch
# 预处理(保持独立)
self.cv3 = Conv(c3, 256, 1) # P3
self.cv4 = Conv(c4, 256, 1) # P4
self.cv5 = Conv(c5, 256, 1) # P5
# ===== 关键转换:每个融合路径都有独立的卷积 =====
# 自顶向下路径(两个独立的卷积)
self.cv_p5p4 = Conv(512, 256, 3) # P5->P4融合专用
self.cv_p4p3 = Conv(512, 256, 3) # P4->P3融合专用(不再共享权重)
# 自底向上路径(两个独立的卷积)
self.cv_p3p4 = Conv(512, 256, 3) # P3->P4融合专用
self.cv_p4p5 = Conv(512, 256, 3) # P4->P5融合专用(不再共享权重)
def forward(self, x):
"""前向传播(与源码格式一致)"""
p3, p4, p5 = x
# 1. 预处理
p3 = self.cv3(p3) # [B,256,80,80]
p4 = self.cv4(p4) # [B,256,40,40]
p5 = self.cv5(p5) # [B,256,20,20]
# 2. 自顶向下路径(使用独立的卷积)
# P5 -> P4
p5_up = F.interpolate(p5, scale_factor=2) # 20×20→40×40
p4_td = self.cv_p5p4(torch.cat([p5_up, p4], 1)) # 使用专用卷积cv_p5p4
# P4 -> P3
p4_up = F.interpolate(p4_td, scale_factor=2) # 40×40→80×80
p3_out = self.cv_p4p3(torch.cat([p4_up, p3], 1)) # 使用专用卷积cv_p4p3
# 3. 自底向上路径(使用独立的卷积)
# P3 -> P4(使用原始p4,避免特征重用)
p3_down = F.interpolate(p3_out, scale_factor=0.5, mode='bilinear') # 80×80→40×40
p4_out = self.cv_p3p4(torch.cat([p3_down, p4], 1)) # 使用专用卷积cv_p3p4
# P4 -> P5
p4_down = F.interpolate(p4_out, scale_factor=0.5, mode='bilinear') # 40×40→20×20
p5_out = self.cv_p4p5(torch.cat([p4_down, p5], 1)) # 使用专用卷积cv_p4p5
return [p3_out, p4_out, p5_out]
3.2.3 转换的详细步骤
步骤1:拆分共享卷积层
# 原结构(问题)
self.cv_td = Conv(512, 128, 1) # 用于P4和P3的顶部卷积(共享)
self.cv_bu = Conv(256, 256, 3) # 用于P4和P3的底部卷积(共享)
# 新结构(独立)
self.cv_p4_td = Conv(512, 128, 1) # P4专用
self.cv_p3_td = Conv(512, 128, 1) # P3专用(独立权重)
self.cv_p4_bu = Conv(256, 256, 3) # P4专用
self.cv_p3_bu = Conv(256, 256, 3) # P3专用(独立权重)
步骤2:拆分上/下采样
# 原结构(共享)
self.upsample = nn.Upsample(scale_factor=2) # P5和P4共享
self.downsample = Conv(128, 128, 3, stride=2) # P3和P4共享
# 新结构(独立)
self.upsample_p5 = nn.Upsample(scale_factor=2) # P5专用
self.upsample_p4 = nn.Upsample(scale_factor=2) # P4专用(可独立优化)
self.downsample_p3 = Conv(128, 128, 3, stride=2) # P3专用
self.downsample_p4 = Conv(256, 256, 3, stride=2) # P4专用
3.2.3 转换关键点:
- 拆分共享层:将同时服务于多个路径的卷积层拆分为独立实例
- 明确连接:为每个连接路径创建独立的节点
- 避免循环依赖:重构数据流为更线性的依赖关系
3.2.4 适合DepGraph的原因:
- 复杂依赖可视化:DepGraph能清晰展示跨层连接
- 组剪枝支持:相关层的通道可以被分组剪枝
- 依赖传播:剪枝决策可以沿依赖图正确传播
3.2.5 DepGraph依赖分析示例
# DepGraph需要分析的复杂依赖
dependencies = {
'CBS_1': {
'直接下游': ['P4_out1'],
'间接下游': [
'P3_out'(通过上采样和CBS_2),
'P4_out2'(通过直接使用),
'P5_out'(通过P4_out2和CBS_4)
]
},
'CBS_2': {
'直接下游': ['P3_out'],
'间接下游': [
'P4_out2'(通过下采样和CBS_3),
'P5_out'(通过P4_out2和CBS_4)
]
},
'CBS_3': {
'直接下游': ['P4_out2'],
'间接下游': [
'P5_out'(通过下采样和CBS_4)
]
}
}
3.2.6 转换后的DepGraph友好结构
转换核心思想:创建独立的处理路径,避免特征重用
路径1(P5→P4融合):
P5 → 上采样 → Concat + P4 → CBS_P5P4 → P4_out1_A
路径2(P4→P3融合):
P4_out1_A → 上采样 → Concat + P3 → CBS_P4P3 → P3_out_A
路径3(P3→P4融合):
P3_out_A → 下采样 → Concat + P4_out1_A → CBS_P3P4 → P4_out2_A
路径4(P4→P5融合):
P4_out2_A → 下采样 → Concat + P5 → CBS_P4P5 → P5_out_A
关键改进:
1. 每个CBS只服务于单一输出
2. 避免中间特征的多重使用
3. 依赖关系线性化
3.3 Detect检测头转换
3.3.1 原Detect头(权重共享)
输入特征图 [P3, P4, P5]
│
共享Conv层 (权重共享)
├──→ 分类分支 → P3_cls
├──→ 回归分支 → P3_reg
├──→ 分类分支 → P4_cls
├──→ 回归分支 → P4_reg
├──→ 分类分支 → P5_cls
└──→ 回归分支 → P5_reg
3.3.2 转换后的独立检测头
P3特征图 ── Conv_P3_cls ── P3_cls输出
└─ Conv_P3_reg ── P3_reg输出
P4特征图 ── Conv_P4_cls ── P4_cls输出
└─ Conv_P4_reg ── P4_reg输出
P5特征图 ── Conv_P5_cls ── P5_cls输出
└─ Conv_P5_reg ── P5_reg输出
转换原理:
# 原共享权重结构
class SharedDetect(nn.Module):
def __init__(self, num_classes):
super().__init__()
self.cls_conv = nn.Conv2d(256, num_classes, 1) # 共享分类头
self.reg_conv = nn.Conv2d(256, 4, 1) # 共享回归头
def forward(self, features): # features: [P3, P4, P5]
outputs = []
for feat in features:
cls_out = self.cls_conv(feat)
reg_out = self.reg_conv(feat)
outputs.append((cls_out, reg_out))
return outputs
# 转换后独立结构
class IndependentDetect(nn.Module):
def __init__(self, num_classes, num_scales=3):
super().__init__()
# 为每个尺度创建独立的卷积层
self.cls_convs = nn.ModuleList([
nn.Conv2d(256, num_classes, 1) for _ in range(num_scales)
])
self.reg_convs = nn.ModuleList([
nn.Conv2d(256, 4, 1) for _ in range(num_scales)
])
3.3.3 适合DepGraph的原因:
- 消除共享约束:权重共享限制了通道的独立剪枝
- 尺度特异性剪枝:不同尺度特征图可以有不同的剪枝策略
- 依赖简化:每个头的输入输出关系变得简单明确
4. DepGraph剪枝优势图示与总结
传统剪枝的问题:
Layer1 ──→ Layer2 ──→ Layer3
│ │ │
剪枝↓ 剪枝↓ 剪枝↓
? ? ? # 相互依赖导致问题
DepGraph剪枝:
Layer1 ──→ Layer2 ──→ Layer3
│ │ │
节点A 节点B 节点C
│ │ │
依赖边 依赖边 依赖边
│ │ │
剪枝组←─────┐ └─────→剪枝组 # 依赖传播确保一致性
3. DepGraph剪枝训练
安装torch-pruning
pip install torch-pruning
yolov8_pruning.py代码运行全流程
这是一个专业级的DepGraph剪枝实现,特点包括:
- 完整的C2f结构适配:专门针对YOLOv8的C2f模块优化
- 迭代剪枝策略:分多次小剪枝 + 微调,避免精度骤降
- 自动依赖处理:通过DepGraph保证剪枝后模型可运行
- 详细性能监控:实时记录MACs、参数量、mAP变化
- 可视化输出:自动生成性能对比图表
第一部分:导入与配置
import argparse # 命令行参数解析
import math # 数学计算
import os # 系统操作
from copy import deepcopy # 深度复制(内存消耗大)
from datetime import datetime # 时间戳
# ... 其他导入
from ultralytics import YOLO # YOLOv8主库
import torch_pruning as tp # DepGraph剪枝库
第二部分:辅助函数定义
# 1. 可视化函数
def save_pruning_performance_graph(x, y1, y2, y3): # 绘制性能曲线图
# 2. C2f结构转换(核心)
def infer_shortcut(bottleneck): # 判断Bottleneck是否有shortcut
class C2f_v2(nn.Module): # 新的C2f结构定义
def transfer_weights(c2f, c2f_v2): # 权重转移函数
def replace_c2f_with_c2f_v2(module): # 替换模型中的C2f
# 3. 训练函数重写(解决PyTorch 2.6兼容性)
def save_model_v2(self: BaseTrainer): # 禁用半精度保存
def final_eval_v2(self: BaseTrainer): # 修改最终评估
def strip_optimizer_v2(f): # 修改优化器剥离
def train_v2(self: YOLO, pruning=False, **kwargs): # 训练适配剪枝
C2f结构转换(关键步骤)
将YOLOv8的C2f模块转换为更易剪枝的C2f_v2:
原C2f结构: 输入 → [CV1] → 分割 → Bottleneck*N → 拼接 → CV2 → 输出
转换为C2f_v2: 输入 → [CV0, CV1] → Bottleneck*N → 拼接 → CV2 → 输出
目的:使CV0和CV1独立,便于通道剪枝
第三部分:主剪枝函数
def prune(args): # 主剪枝流程控制器
# 这里包含了之前流程图的所有步骤
关键步骤详解
1. DepGraph剪枝核心
pruner = tp.pruner.GroupNormPruner(
model.model,
example_inputs,
importance=tp.importance.MagnitudeImportance(p=2), # L2范数重要性
iterative_steps=1,
pruning_ratio=pruning_ratio, # 每次剪枝比例
ignored_layers=ignored_layers, # 保护检测头
unwrapped_parameters=unwrapped_parameters
)
# DepGraph会自动分析层间依赖,保证剪枝后模型可运行
2. 迭代剪枝策略
# 计算每次迭代的剪枝比例
pruning_ratio = 1 - math.pow((1 - args.target_prune_rate),
1 / args.iterative_steps)
# 例如:目标剪50%,分16次迭代 → 每次剪约4.4%
3. 微调恢复精度
# 剪枝后立即微调
model.train_v2(pruning=True, **pruning_cfg)
# 使用少量epoch(你的设置:30)恢复精度
性能监控与输出
# 1. 实时显示剪枝效果
print(f"After pruning iter {i+1}: MACs={pruned_macs/1e9}G, "
f"Params={pruned_nparams/1e6}M, mAP={pruned_map}")
# 2. 生成性能图表
save_pruning_performance_graph(nparams_list, map_list, macs_list, pruned_map_list)
# 输出:pruning_perf_change.png
最终输出文件
输出目录结构:
runs/detect/
├── baseline_val/ # 基准验证结果
├── step_0_pre_val/ # 第1次剪枝前验证
├── step_0_finetune/ # 第1次微调过程
├── step_0_post_val/ # 第1次微调后验证
├── step_1_pre_val/ # 第2次...
└── ...
最终文件:
best_pruned.pt # PyTorch模型权重
best_pruned.onnx # ONNX格式模型
pruning_perf_change.png # 性能变化图
实际执行时的代码路径
执行顺序:
1. 解析命令行参数 (第433行: parser.parse_args())
2. 调用 prune(args) 函数 (第449行)
3. prune() 内部按阶段执行:
a. 加载模型
b. C2f转换
c. 基准测试
d. 迭代剪枝
e. 结果保存
torch_pruning 库
DepGraph剪枝算法已经集成在 torch_pruning 库中,不需要从零实现。
1. 已经集成的DepGraph功能
import torch_pruning as tp
# DepGraph核心类已经存在:
tp.DependencyGraph() # 依赖图分析
tp.pruner.GroupNormPruner() # 基于DepGraph的剪枝器
tp.pruner.MagnitudePruner() # 同样使用DepGraph
# 你的代码只是调用这些现成功能
pruner = tp.pruner.GroupNormPruner(
model.model,
example_inputs,
importance=tp.importance.MagnitudeImportance(p=2),
iterative_steps=1,
pruning_ratio=pruning_ratio,
ignored_layers=ignored_layers, # ← 这里依赖DepGraph
unwrapped_parameters=unwrapped_parameters
)
2. 代码职责
虽然DepGraph算法是现成的,但还需要实现关键的三件事:
# 1. YOLOv8特定结构适配(核心难点)
replace_c2f_with_c2f_v2(model.model) # 将C2f转为可剪枝的C2f_v2
# 2. 完整的剪枝工作流设计
# 基准测试 → 迭代剪枝 → 微调恢复 → 验证 → 保存
# 3. 性能监控与可视化
save_pruning_performance_graph() # 生成专业图表
4. 最终实验结果
Before Pruning: MACs= 14.27201 G, #Params= 11.13599 M, mAP= 0.82984 14.27G MACs ≈ 28.4G FLOPs(MACs约是FLOPs的一半,正常)