YOLOv10剪枝|稀疏训练、基于torch-pruning剪枝以及微调实践

一、稀疏训练

一句话结论

稀疏训练就是通过对BN γ(gamma)添加 L1 正则,让模型自动学出"不重要的通道",从而为后续结构化剪枝提供依据。

稀疏训练原理

稀疏训练最核心的数学原理:在 BN γ 上施加 L1 正则。

YOLOv10 每个 Conv 后都有以下结构:

复制代码
Conv → BN → SiLU

其中:BN也就是 BatchNorm 的计算公式简述如下:

复制代码
output = gamma * normalized_input + beta

其中重要参数 γ(gamma) 控制当前通道的"放大/缩小"权重

那么稀疏训练做了什么呢?

在 loss 中额外加一项 L1正则项:

最终 loss:

其中:

  • L_{detection} 是原始任务的损失(例如目标检测);

  • γ 通常表示网络中某些可学习的缩放因子(例如在 BatchNorm 层中的可学习参数,或通道/神经元的门控权重);

  • λ 是控制稀疏正则化强度的超参数;

  • ∑∣γ∣ 是对这些参数施加的 L1 正则项。

解释: 这个 λ * |γ| 会迫使部分通道的 BN gamma 值变小 ,(为什么?因为L2 正则只会让权重变小,但不会变为 0;因为靠近 0 时梯度接近 0。L1 正则(不可导,无论多小梯度不变)会产生恒定的推力,使不重要的权重直接变成 0,从而诱导稀疏性。)如此,会对模型的表达能力会带来轻微的干扰

优化后:

  • 不重要的通道 → γ 被推向 0 (γ 极小 → 通道几乎被关闭)

  • 重要的通道 → γ 仍保持较大(γ 较大 → 通道被保留)

这给剪枝提供了"通道可删性"的依据。

适合稀疏化的结构

在 YOLOv10 的 Backbone 与 Neck 中,大量使用了以下结构:

  • C2f 模块

  • C2fCIB 模块

  • SCDown 模块

这些结构具有一些共同特点:

  1. 多分支设计、跨层特征融合频繁

  2. 中宽通道数量大(如 128 / 256 / 512)

  3. 跨层特征存在一定重复性

  4. Bottleneck / CIB 等模块堆叠较深

上述特征共同带来的结果是:网络中部分通道天然冗余 ,其对应的 BatchNorm γ 参数会在训练中自然趋向于较小值 。因此,在进行稀疏化与通道剪枝时,这些模块非常容易被"压缩"而不影响整体性能------也正是 YOLOv10 特别适合稀疏剪枝的重要原因

上手实操

在训练中加入 L1 稀疏化最简单的方式,是利用回调机制对 BN γ 参数施加 L1 正则 。具体做法是:重写 优化器 的执行流程,在梯度 unscale 之后、参数 step 之前,对 BatchNorm 的梯度额外加上 L1 项。这样即可在不改动主干训练逻辑的前提下,实现对 BN 通道的稀疏化约束。

二、(可选)基于BN的γ进行稀疏处理

GammaPruner 基于Gamma进行稀疏化处理

这个步骤一般在第一步之后,效果好些。

核心是对于bn的权重进行排序,然后按一个比例去把对应的bn层的参数置为0,完成稀疏化。

剪枝后通常需要微调(fine-tune),否则精度可能大幅下降。

三、结构化剪枝以及微调

利用torch-pruning工具对yolov10进行结构化剪枝以及微调操作。

参考官方的示例代码:Torch-Pruning/examples/yolov8/yolov8_pruning.py at master · VainF/Torch-Pruning

主要剪枝的模块如yolov8一样(事实上yolov10是在yolov8上改进而来),都主要是优化C2f结构。

但是 需要注意的是 yolov10网络上还有C2f的衍生模块C2fCIB,这个模块继承C2f,实际上就是把C2f的Bottleneck结构换成了CIB结构,那么在替换C2f结构以及infer_shortcut部分特别要注意。

除此以外,这里还要额外解释下:为什么要重写C2f结构?

对于YOLOv8/v10中剪枝时重写C2f结构的解释

显浅的解释:主要是为了让 C2f 变成更容易 剪枝 **的结构。**您肯定会说,这不就是一句废话,那么接下来分析YOLO原版 C2f 的问题是:

原始 C2f 不适合 剪枝 (特别是 channel pruning

先重点看原 C2f 结构:

复制代码
self.cv1 = Conv(c1, 2 * self.c, 1, 1)
y = list(self.cv1(x).chunk(2, 1))

观察到:cv1 输出了 2*self.c 个通道,之后劈成两半用于不同分支

这意味着:

  1. 通道必须能被 2 整除

  2. 两个分支共享一个 conv 输出

  3. 一旦剪掉某些通道,会破坏 chunk 的对齐

  4. 剪枝后 channel 不再对称 → 网络结构直接错位、维度错误

因此:原版 C2f 是典型的"不剪不行,剪了就炸"的结构

所以各大剪枝框架(torch-pruning、slimmable、模型稀疏化)对 YOLO 的 C2f 都特别难处理。

接下来,看修改后的 C2f_v2 结构:

复制代码
self.cv0 = Conv(c1, self.c, 1, 1)
self.cv1 = Conv(c1, self.c, 1, 1)

也就是:用两个独立的 conv **来替代 原来 cv1 的"2c 输出 + chunk 切半"。**这带来以下4个剪枝优势:

  1. 每个分支现在可以独立剪枝(而不是绑定在一起)

  2. 移除 chunk,所有结构变为标准 Conv,便于剪枝工具解析

  3. 两个独立输入分支,更容易加稀疏正则(L1 on gamma)

  4. 新 C2f 的结构更像 ConvNeXt / ResNet 的典型可剪枝结构

  5. 保留原 C2f 的特性,逻辑一致,推理行为相近

最后看下 forward

复制代码
y = [self.cv0(x), self.cv1(x)]
y.extend(m(y[-1]) for m in self.m)
return self.cv2(torch.cat(y, 1))

推理行为与原版是一致:

  1. 第一条分支类似于原来的 y[0]

  2. 第二条分支相当于原来的 y[1]

  3. 后面的 m 都是堆叠 residual blocks

  4. 最后 concat → cv2 聚合

只是把 chunk 改成了更适合剪枝的版本。

最后,总结一句话:为了剪枝,把 C2f 中原本的"2c 输出 + chunk"改成了两个独立卷积 cv0/cv1,使得每个分支可以独立剪枝,不再受 chunk 对齐限制,从而让整个结构完全可剪、完全可稀疏化。

剪枝过程中遇到的坑

剪枝后微调会报错:

"RuntimeError: Inplace update to inference tensor outside InferenceMode is not allowed.You can make a clone to get a normal tensor before doing inplace update.See https://github.com/pytorch/rfcs/pull/17 for more details."

经查资料,inference_mode()在torch 1.9.0之后的版本引入,torch-pruning工具的yolov8示例代码如果不报错那可能torch版本在这之前。

最后定位在self.scaler.step(self.optimizer)报错了,但其根本原因是因为tensor处于推理模式,这些 inference tensor 极大概率是在运行 val() 验证时,在 torch.inference_mode() 下生成并缓存到模型内部,但又继续用这个实例去训练,导致的报错。

最后解决问题的方式是:

不要用剪枝以及评估后的model去微调,重新加载这个模型,并修改更新相应的方法然后进行微调。

未完待续... 会提供完整的代码解析(可催更)

相关推荐
!停8 分钟前
C语言单链表
c语言·数据结构·算法
闻缺陷则喜何志丹19 分钟前
【回文 字符串】3677 统计二进制回文数字的数目|2223
c++·算法·字符串·力扣·回文
Tisfy26 分钟前
LeetCode 0085.最大矩形:单调栈
算法·leetcode·题解·单调栈
mit6.82427 分钟前
出入度|bfs|状压dp
算法
hweiyu0028 分钟前
强连通分量算法:Kosaraju算法
算法·深度优先
源代码•宸28 分钟前
Golang语法进阶(定时器)
开发语言·经验分享·后端·算法·golang·timer·ticker
mit6.82435 分钟前
逆向思维|memo
算法
机器学习之心36 分钟前
MATLAB灰狼优化算法(GWO)改进物理信息神经网络(PINN)光伏功率预测
神经网络·算法·matlab·物理信息神经网络
代码游侠40 分钟前
学习笔记——ESP8266 WiFi模块
服务器·c语言·开发语言·数据结构·算法
倦王40 分钟前
力扣日刷26110
算法·leetcode·职场和发展