目录

一、背景

二、剪枝

1. Network Slimming

1.0 代码准备

1.1 稀疏化训练

1.2 剪枝

1.3 微调

1.4 测试总结

2. Torch Pruning(TP)

2.1 MagnitudePruner

2.1.1 剪枝

2.1.2 retrain

2.1.3 测试总结

2.2 SlimmingPruner

2.2.1 定义重要性评估

2.2.2 定义剪枝器

2.2.3 初始化

2.2.4 稀疏化训练

2.2.5 剪枝

2.2.6 微调

2.2.7 测试总结

三、总结

参考资料


一、背景

        书接上回,本文记录一下上文中介绍的三种剪枝方法的使用,以剪枝 yolov5n6 为例,对比一下剪枝前后模型体积和准确率变化,以及不同剪枝方法之间的对比。

二、剪枝

        [1] 官方没有提供代码,放到后面使用 Torch Pruning 工具实现,[2] 的官方代码是对 VGG 和 ResNet 进行的剪枝,有需要的朋友可以参考,这里使用的是 midasklr 大佬提供的 yolov5 实现[5],也是使用比较多的。

1. Network Slimming

        主要剪枝流程如下,作为比较,可以先训练一个原始 yolo 模型作为 baseline,之后再进行稀疏化训练。

剪枝流程图

原始 yolov5n6 模型精度测试结果

1.0 代码准备

        首先本文测试的模型是有 4 个检测尺度分支的 yolov5n6,所以需要先对源码进行修改已适配模型结构。

(1)在 models/yolo.py 的 Line538 后添加一行

from_to_map[named_m_base + ".m.3"] = fromlayer[f[3]]

(2)prune.py 从 Line432 开始的 pruned_yaml["backbone"] 和 pruned_yaml["head"] 改成 yolov5n6 的 backbone 和 head

(3)Line554 的 "model.24" 改成 “model.33”

1.1 稀疏化训练

        train_sparity.py 提供了稀疏化训练代码,这里的稀疏率 s 没有像官方一样使用定值,而是根据训练轮次增加逐渐减小,通常 s 越大稀疏化程度越高。

python train_sparity.py --st --sr 0.0005

训练完成后可以进入 http://localhost:6006/ 查看 BN 层权重分布直方图的变化情况

tensorboard --logdir=./runs/train
稀疏困难
稀疏过快
平稳变化

        s 如果设置过小,BN 层很难稀疏化,可剪枝空间很小;如果设置过大,BN 层会过于稀疏化,难以学习到有效权重,造成精度损失和过度剪枝。所以需要根据具体任务和训练结果不断调整 s,直到 BN 层的权重在训练过程中平稳变化至 0 点。

除了监控 BN 层,还可以通过 mAP 的变化情况判断模型的训练效果。

稀疏模型的精度测试

1.2 剪枝

稀疏训练完成后对得到的稀疏模型进行剪枝

python prune.py --percent 0.5 --weights /path/to/your/sparse_model

可以看到根据计算得到的 \gamma 阈值对各层进行了剪枝。

        当剪枝之后剩余通道数为 1 时,剪枝过程可能会报错。上图中 model.8.cv3.bn 剪枝后剩余 1 个通道,然后就会报错。

这表明对该层来说 \gamma 阈值偏大了,所以需要针对该层调整 \gamma,确保剪枝后剩余通道数>1,具体需要修改 utils/prune_utils.py 的 obtain_bn_mask(),每次剪完之后判断剩余通道数,如果余 1,则按1% 的步长降低 \gamma,这样能保证剪枝通道尽可能多,循环剪枝判断,直至剩余通道数>1。

此时再剪枝就没有问题了,可以看到 model.8.cv3.bn 层剩余通道数变2了。

剪枝 50% 后的精度测试

可以看到跟剪枝前的稀疏模型相比没有精度损失,说明还有剪枝空间,剪枝率可以再调大一些。

剪枝 70% 后的模型和精度测试:

可以看到准确率明显下降,需要微调来恢复精度。 

1.3 微调
python finetune.py --weights pruned_model.pt

微调 60 个 epochs 之后准确率恢复到可接受程度,相比剪枝前会有轻微下降。

1.4 测试总结

        上述剪枝流程还是比较清晰的,实际使用时很难说一次剪枝成功,根据效果多调参数,设置合适的剪枝比例基本不会有太大问题。下面对比总结一下剪枝前后模型准确率、体积、推理速度等方面的差异。

model

mAP

@.5

mAP

@.5:.95

Speed / CPU

 (ms/image)

Speed / GPU

(ms/image)

Params

(M)

FLOPs

(G)

Size

(MB)

baseline0.9950.92136.311.73.094.313.2@32FP

sparse

training

0.995   0.89737.612.03.094.313.2
pruned 50%0.9950.89728.311.61.283.05.5
pruned 70%0.6940.55

25.6 (↑29.4%)

10.4 (↑11.1%)0.672.23.0
finetune0.9940.87724.9 (↑31.4%)11.0 (↑6.0%)0.672.23.0

        从推理速度上看,剪枝后的模型在 GPU 上的加速效果不明显,在 CPU 上能加速 30% 左右,模型体积有明显降低。对于 GPU 加速不明显的问题,有人说是因为剪枝后的通道数不满足 2^n,不利于 GPU 的并行计算。

有小伙伴提出控制剪枝时的通道数为 2^n,实测下来提升效果不明显。

2. Torch Pruning(TP)

        下面介绍使用剪枝库 TP 实现两种 [1-2] 不同的剪枝器。建议安装最新版本,新旧版本的代码结构差别比较大。TP 的基本使用可以参考官方文档,里面说的很清楚。

2.1 MagnitudePruner

MagnitudePruner 不需要稀疏化训练,直接对训练后的模型剪枝即可。

2.1.1 剪枝

        TP 内部实现了基于权值重要性的剪枝算法,我们只需要调用相关接口生成一个自己的剪枝器就可以使用了。

def l1norm_filter_pruner(model, fake_input, iterative_steps, ratio, device='cuda:0'):for p in model.parameters():p.requires_grad_(True)# 定义重要性标准,p=2表示使用L2范数计算重要性分数imp = tp.importance.MagnitudeImportance(p=2)ignored_layer_outputs = []    # 忽略剪枝的输出通道ignored_layer_inputs = []     # 忽略剪枝的输入通道ignored_module = []# 自定义忽略剪枝的层,如果某些层对精度影响较大可以不剪,如果全剪则跳过本部分for k, m in model.named_modules():k_sp = k.split('.')if len(k_sp) <= 1: continuemodule_idx = int(k_sp[1])# 忽略对backbone的剪枝if module_idx < 12 and f"model.{module_idx}" not in ignored_module:ignored_module.append(f"model.{module_idx}")ignored_layer_outputs.append(m)# 忽略对Detect输出层的剪枝if isinstance(m, (Detect, )):ignored_layer_outputs.append(m)# 忽略对Detect输入层的剪枝if k in ['model.23.cv3', 'model.26.cv3', 'model.29.cv3', 'model.32.cv3']:ignored_layer_inputs.append(m)ignored_layers = ignored_layer_inputs + ignored_layer_outputs# 定义剪枝器pruner = tp.pruner.MagnitudePruner(model,fake_input,    # 样例输入,用于自动生成依赖图,shape: [bs, ch, h, w]importance=imp,    # 重要性标准iterative_steps=iterative_steps,    # 剪枝迭代次数(=1:一次剪枝;>1:迭代剪枝,每次剪枝比例=ratio/iterative_steps)pruning_ratio=ratio,    # 剪枝比例ignored_layers=ignored_layer_outputs,    #忽略层)return model, pruner

        注意剪枝比例是对于所有需要剪枝的层来说的,比如模型一共 20 层,需要剪枝 15 层,剪枝比例 70%,则实际剪枝 15\times 0.7\approx 11 层。

        有了剪枝器之后就可以剪枝了,这里使用了两种方式剪枝,一种是直接对训练完成的权重文件进行剪枝,另一种是 prune-retrain 迭代式剪枝。

一次剪枝

def prune_model(pruner, model, inputs, iters):# 原始模型的计算量和参数量base_macs, base_params = tp.utils.count_ops_and_params(model, example_inputs=inputs)# 开始剪枝for _ in range(iters):pruner.step()# 剪枝后的计算量和参数量macs, params = tp.utils.count_ops_and_params(model, example_inputs=inputs)print(model)print("Before Pruning: MACs=%.2f G, #Params=%.2f M" % (base_macs/1e9, base_params/1e6))print("After Pruning: MACs=%.2f G, #Params=%.2f M" % (macs/1e9, params/1e6))return modelif __name__ == '__main__':weights = 'runs/train/exp/weights/last.pt'model = attempt_load(weights, map_location='cuda:0')fake_input = torch.randn((1, 3, 640, 640), device='cuda:0')model, pruner = l1norm_filter_pruner(model, fake_input,1, 0.5, 'cuda:0')pruned_model = prune_model(pruner, model, fake_input, 1)

测试剪枝后准确率,直接归零

剪枝率 50%

        实测不同剪枝比例,仅剪枝 20% 准确率依然是 0,剪枝 1% 时 mAP 也有明显下降,感觉这种剪枝方式不好用。

剪枝率 1%
2.1.2 retrain

        [1]中提供了两种 retrain 策略恢复精度,一次剪枝+retrain 和 剪枝+retrain 交替。

(1)One Shot

        对于一次剪枝+retrain,重新加载剪枝模型后从头开始训练,实测训练 120 个 epochs 模型基本收敛,精度不再有明显提升,最终恢复结果如下:

retrain 60 epochs
retrain 120 epochs

(2)迭代剪枝(Iter)

        逐层剪枝和 retrain 交替进行,进行下一次剪枝之前先 retrain。之前 TP 作者有说过还不支持该方式,我也看了一下自该回答之后更新过的版本,也没有看到支持该方式的提示,所以目前来说应该还不支持。(如果说的不对请大佬指正)

2.1.3 测试总结

        目前测试来看基于权值重要性的剪枝方式效果不如基于 BN 层的剪枝,前者剪枝比例稍大一些对于精度损失就很明显,而且 retrain 基本恢复不到原始精度,但是模型压缩比和 CPU 加速比更明显,具体对比测试结果如下:

Model

(prune method)

mAP

@.5

mAP

@.5:.95

Speed / CPU

 (ms/image)

Speed / GPU

(ms/image)

Params

(M)

FLOPs

(G)

Size

(MB)

baseline0.9950.92136.311.73.094.313.2@32FP
MagPru+50%00//0.781.23.4
SlimPru+50%0.9950.89728.311.61.283.05.5
SlimPru+70%0.6940.55

25.6 (↑29.4%)

10.4 (↑11.1%)0.672.23.0

MagPru+50%+retrain

(OneShot/120epochs) 

0.9950.86

20.6

(↑43.2%)

10.9

(↑6.8%)

0.781.23.4

SlimPru+70%+finetune

(60 epochs)

0.9940.87724.9 (↑31.4%)11.0 (↑6.0%)0.672.23.0
2.2 SlimmingPruner

        基于 TP 实现 Slimming 剪枝比较简单,[6] 中原作者也给了比较清晰的例子,定义好重要性评估和剪枝器,将 [5] 中的稀疏化训练代码替换一下就可以,后面的剪枝和微调操作不变。

2.2.1 定义重要性评估
class MySlimmingImportance(tp.importance.Importance):def __call__(self, group, **kwargs):group_imp = []for dep, idxs in group:layer = dep.target.moduleif isinstance(layer, nn.BatchNorm2d):local_imp = torch.abs(layer.weight.data)group_imp.append(local_imp)if not len(group_imp):return Nonegroup_imp = torch.stack(group_imp, dim=0).mean(dim=0)return group_imp
2.2.2 定义剪枝器
class MySlimmingPruner(tp.pruner.BasePruner):def regularize(self, model, sr):for m in model.modules():if isinstance(m, nn.BatchNorm2d):m.weight.grad.data.add_(sr * torch.sign(m.weight.data))     # L1正则
2.2.3 初始化

初始化重要性评估和剪枝器

imp = MySlimmingImportance()ignored_layer_outputs = []
ignored_layer_inputs = []
for k, m in model.named_modules():if isinstance(m, Detect):ignored_layer_outputs.append(m)if k in ['model.23.cv3', 'model.26.cv3', 'model.29.cv3', 'model.32.cv3']:ignored_layer_inputs.append(m)
ignored_layers = ignored_layer_inputs + ignored_layer_outputsiterative_steps = 1
pruner = MySlimmingPruner(model,fake_input,global_pruning=False,importance=imp,iterative_steps=iterative_steps,pruning_ratio=0.7,ignored_layers=ignored_layers,    # 忽略对Detect层的剪枝
)
2.2.4 稀疏化训练

        在反向传播和梯度更新之间插入一行代码即可。tp.Pruner.MetaPruner 提供了一个 regularize 接口,用于稀疏训练。

loss.backward()
srtmp = opt.sr*(1 - 0.9*epoch/epochs)
slpruner.regularize(model, srtmp)    # 稀疏化训练
optimizer.step()

        训练过程没有区别,训练完之后使用 TensorBoard 查看 BN 层权重变化,可以看到权重随着训练慢慢变得稀疏

2.2.5 剪枝

2.2.6 微调

微调 60 个 epochs 后基本恢复,测试精度上与直接使用 Network Slimming 没啥区别。

2.2.7 测试总结

        Network Slimming 的两种实现方式在最终的剪枝效果上没有啥区别,主要还是使用方式上的不同。

Model

mAP

@.5

mAP

@.5:.95

Speed / CPU

 (ms/image)

Speed / GPU

(ms/image)

Params

(M)

FLOPs

(G)

Size

(MB)

base0.9950.92136.311.73.094.313.2@32FP

Slimming

(prune 70%)

0.6940.55

25.6 (↑29.4%)

10.4 (↑11.1%)0.672.23.0

Slimming+ft

(60 epochs)

0.9940.87724.9 (↑31.4%)

11.0

(↑6.0%)

0.672.23.0

TP-Slimming

(prune 70%)

00////2.9

TP-Slimming+ft

(60 epochs)

0.9950.877

25.4

(↑30%)

11.1

(↑5.1%)

0.672.22.9

三、总结

        本文是基于之前学习的几篇剪枝论文,参考网上代码做的本地实现,并对比测试了不同剪枝方法之间的效果差异。目前的工作只能算是对模型剪枝的入门,剪枝的模型也是使用比较多的yolov5,实现难度上相对小一些。后面有时间也会慢慢尝试对其他更多模型的剪枝,学习更多的剪枝方法。

        现在使用的剪枝和量化操作都是独立进行的,而且剪枝对模型体积的压缩效果更明显,可以试着把两者结合起来,对剪枝后的模型做量化,进一步压缩模型体积的同时借助 NPU 加速模型推理。此外,除了模型剪枝和模型量化,模型蒸馏也是模型优化的一种方法,后面有时间也会去学习。

参考资料

[1] [1608.08710] Pruning Filters for Efficient ConvNets

[2] ICCV 2017 Open Access Repository

[3] yolov5s模型剪枝详细过程(v6.0)_yolov5剪枝-CSDN博客

[4] YOLOv5-6.1剪枝实战_yolov5通道剪枝-CSDN博客 

[5] https://github.com/midasklr/yolov5prune/tree/v6.0

[6] https://github.com/YINYIPENG-EN/Pruning_for_YOLOV5_pytorch 

[7] Torch-Pruning | 轻松实现结构化剪枝算法 - 知乎 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/95264.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/95264.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/95264.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI入门学习--AI模型评测

一、AI模型评测目标传统质量主要关注功能、性能、安全、兼容性等。 AI模型评测在此基础上,引入了全新的、更复杂的评估维度: 1.性能/准确性:这是基础,在一系列复杂的评测基准上评价个性能指标。 2.安全性:模型是否可能被用于恶意目的?是否会生成有害、违法或有毒的内容?是否容…

nt!MmCreatePeb函数分析之peb中OSMajorVersion的由来

第一部分&#xff1a;NTSTATUS MmCreatePeb (IN PEPROCESS TargetProcess,IN PINITIAL_PEB InitialPeb,OUT PPEB *Base) {PPEB PebBase;PebBase->OSMajorVersion NtMajorVersion;PebBase->OSMinorVersion NtMinorVersion;PebBase->OSBuildNumber (USHORT)(NtBuildN…

Unity TimeLine使用教程

1.概述 Timeline 是一个基于时间轴的序列化编辑工具&#xff0c;主要用于控制游戏或动画中的 过场动画&#xff08;Cutscenes&#xff09;、剧情事件、角色动画混合、音频控制 等。它类似于视频编辑软件&#xff08;如 Adobe Premiere&#xff09;的时间线&#xff0c;但专门针…

数据分析基本内容(第二十节课内容总结)

1.pd.read_csv(一个文件.csv)&#xff1a;从本地文件加载数据&#xff0c;返回一个 DataFrame 对象&#xff0c;这是 pandas 中用于存储表格数据的主要数据结构2.df.head()&#xff1a;查看数据的前五行&#xff0c;帮助快速了解数据的基本结构和内容3.df.info()&#xff1a;查…

2025年最新原创多目标算法:多目标酶作用优化算法(MOEAO)求解MaF1-MaF15及工程应用---盘式制动器设计,提供完整MATLAB代码

一、酶作用优化算法 酶作用优化&#xff08;Enzyme Action Optimizer, EAO&#xff09;算法是一种2025年提出的新型仿生优化算法&#xff0c;灵感源于生物系统中酶的催化机制&#xff0c;发表于JCR 2区期刊《The Journal of Supercomputing》。其核心思想是模拟酶与底物的特异性…

用 COLMAP GUI 在 Windows 下一步步完成 相机位姿估计(SfM) 和 稀疏点云重建的详细步骤:

使用 COLMAP GUI 进行 SfM 和稀疏点云重建的步骤1. 打开 COLMAP GUI运行 colmap.bat&#xff0c;会弹出图形界面。2. 新建项目&#xff08;或打开已有项目&#xff09;点击菜单栏的 File > New Project&#xff0c;选择一个空文件夹作为项目目录&#xff08;建议新建一个空目…

天线设计 介质材料PEC和FR4有什么区别吗

在电磁仿真&#xff08;包括 CST 中&#xff09;&#xff0c;PEC 和 FR4 是两种完全不同的材料类型&#xff0c;主要区别如下&#xff1a;材料性质&#xff1a;PEC&#xff08;Perfect Electric Conductor&#xff0c;理想电导体&#xff09;&#xff1a;是一种理论上的理想材料…

mysql锁+索引

mysql锁按锁的粒度分类表级锁&#xff08;Table - level locks&#xff09;特点&#xff1a;对整张表进行锁定&#xff0c;实现简单&#xff0c;加锁和释放锁的速度快&#xff0c;但并发度较低。当一个事务对表加表级锁后&#xff0c;其他事务对该表的读写操作都可能被阻塞。应…

计算机视觉CS231n学习(7)

可视化和理解 这里主要是对CNN中间的层的结果可视化滤波器可视化 直接可视化网络各层的滤波器权重&#xff0c;高层滤波器的可视化结果趣味性较低&#xff0c;而底层滤波器通常对应边缘、纹理等基础视觉特征 &#xff08;“高层滤波器” 通常指的是网络中靠后的卷积层所包含的滤…

OpenBMC中工厂模式的简明工作流程解析

本文将以最简单直接的方式&#xff0c;从零开始讲解OpenBMC中工厂模式的完整工作流程&#xff0c;包括从设计到使用的全生命周期。 1. 工厂模式最简示例 我们先从一个最基础的工厂模式实现开始&#xff1a; // 产品接口 class GpioPin { public:virtual void setValue(bool val…

解决:Error updating changes: detected dubious ownership in repository at

在通过 Git Bash 提交项目代码时输入 git add . 命令后&#xff0c;报错&#xff1a;Error updating changes: detected dubious ownership in repository at ...这是因为 该项目的所有者 与 现在的用户 不一致 比如说&#xff1a; 该项目的所有者是 Administrator&#xff0c;…

DataEase V2 社区版安装部署

参考&#xff1a;使用外置 MySQL 部署 DataEase v2 - FIT2CLOUD 知识库 一、下载安装包 开源社区 - FIT2CLOUD 飞致云 选择社区版下载 下载后上传到 linux 的目录 &#xff08;要求至少200G&#xff09; 二、在MySQL8中创建数据库 # 创建DataEase库 CREATE DATABASE datae…

nginx高性能web服务器

web服务基础介绍 一、Web服务核心流程 #mermaid-svg-NCj4hbRIvvgMXmcK {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-NCj4hbRIvvgMXmcK .error-icon{fill:#552222;}#mermaid-svg-NCj4hbRIvvgMXmcK .error-text{fil…

_init__.py的作用

目录 场景1:没有 `__init__.py` 的情况 场景2:有 `__init__.py` 并导入模块 场景3:用 `__all__` 定义"推荐工具" 注意:工具箱内部的工具互相调用 情况1:在 `__init__.py` 中导入模块 情况2:在 `__init__.py` 中直接导入模块里的功能 关键原则 在 Python 中,__…

浏览器面试题及详细答案 88道(12-22)

《前后端面试题》专栏集合了前后端各个知识模块的面试题&#xff0c;包括html&#xff0c;javascript&#xff0c;css&#xff0c;vue&#xff0c;react&#xff0c;java&#xff0c;Openlayers&#xff0c;leaflet&#xff0c;cesium&#xff0c;mapboxGL&#xff0c;threejs&…

开发避坑指南(23):Tomcat高版本URL特殊字符限制问题解决方案(RFC 7230 RFC 3986)

异常信息 java.lang.IllegalArgumentException: 在请求目标中找到无效字符[/order/show?orderType01&orderTitle0xe50x8f0xa30xe50xb20xb80xe50x8a0xa80xe60x800x81&pageNum1 ]。有效字符在RFC 7230和RFC 3986中定义org.apache.coyote.http11.Http11InputBuffer.parse…

HTTPS的应用层协议

HTTPS的应用层协议 方案 5 - 非对称加密 对称加密 证书认证 在客户端和服务器刚一建⽴连接的时候, 服务器给客户端返回一个 证书&#xff0c;证书包含了之前服务端的公钥, 也包含了网站的身份信息. 客户端进行认证 当客户端获取到这个证书之后, 会对证书进行校验(防止证书是伪…

【SpringBoot】05 容器功能 - SpringBoot底层注解的应用与实战 - @Configuration + @Bean

文章目录前言一、创建两个组件二、使用传统方式源代码解释三、使用SpringBoot方法源代码解释四、查看是否添加到组件中查看自定义组件名配置类在容器中注册的是单实例组件配置类本身也是容器中的一个组件Configuration的proxyBeanMethods属性&#xff1a;代理bean的方法proxyBe…

c#联合Halcon进行OCR字符识别(含halcon-25.05 百度网盘)

1.下载安装halcon 通过网盘分享的文件&#xff1a;halcon-25.05.0.0-x64-win64 链接: https://pan.baidu.com/s/1XAx-8ZQM-ZHkgHIc-dhCYw 提取码: whek 2.c#环境配置 创建test_halcon_ocr项目 找到halcon的安装路径 我的&#xff1a; D:\halcon\HALCON-25.05-Progress\bin\x64…

丝杆支撑座怎样助力升降设备实现智能化?

丝杆支撑座作为传动系统中的关键支撑部件&#xff0c;凭借其高刚性、抗冲击及精准定位能力&#xff0c;广泛应用于重型机械与升降设备领域&#xff0c;为设备提供稳定可靠的轴向承载与径向支撑&#xff0c;确保高负荷工况下的安全运行。电梯 / 升降平台&#xff1a;液压电梯的辅…