利用L1范数的CNN模型剪枝

利⽤L1范数的CNN 模型剪枝人脸识别门
⽂章⽬录
1.原理
缩放因⼦和稀疏性引起的惩罚。我们的想法是为每个通道引⼊⼀个缩放因⼦ γ,它与该通道的输出相乘。然后我们联合训练⽹络权重和这些缩放因⼦,并对后者进⾏稀疏正则化。最后,我们⽤⼩因⼦修剪那些通道,并对修剪后的⽹络进⾏微调。具体来说,我们⽅法的训练⽬标由下式给出其中(x ,y )表⽰训练输⼊和⽬标,W  表⽰可训练权重,第⼀个求和项对应于 CNN  的正常训练损失,g (·)
是稀疏性引起的尺度因⼦惩罚,λ 平衡这两个项。在我们的实验中,我们选择 g (s )=| s |,这被称为 L1 范数,⼴泛⽤于实现稀疏性。
由于修剪通道本质上对应于删除该通道的所有传⼊和传出连接,因此我们可以直接获得⼀个狭窄的⽹络(见图 1),⽽⽆需借助任何特殊的稀疏计算包。缩放因⼦充当通道选择的代表。由于它们与⽹络权重联合优化,⽹络可以⾃动识别⽆关紧要的通道,从⽽安全地删除这些通道,⽽不会很⼤地影响泛化性能。
图 1:我们将缩放因⼦(从批量归⼀化层复⽤)与卷积层中的每个通道相关联。在训练期间对这些缩放因⼦施加稀疏正则化以⾃动识别不重要的通道。具有⼩⽐例因⼦值(橙⾊)的通道将被修剪(左侧)。修剪后,我们获得紧凑模型(右侧),然后对其进⾏微调以达到与正常训练的完整⽹络相当(甚⾄更⾼)的精度。
利⽤ BN  层中的缩放因⼦。批量归⼀化已被⼤多数现代 CNN  ⽤作实现快速收敛和更好泛化性能的标准⽅法。 BN  对激活进⾏归⼀化的⽅式促使我们设计⼀种简单有效的⽅法来合并通道缩放因⼦。特别是,BN  层使⽤⼩批量统计对内部激活进⾏归⼀化。设 zin  和 zout  为⼀个BN  层的输⼊和输出,B  表⽰当前的 minibatch ,BN  层进⾏如下变换:
其中  和  是  上输⼊激活的均值和标准差值,γ 和 β 是可训练的仿射变换参数(尺度和位移),它提供了将归⼀化激活线性转换回任何尺度的可能性。在归⼀化后会进⾏线性变换,那么当系数 γ 很⼩时候,对应的激活(Zout )会相应很⼩。这些响应很⼩的输出可以裁剪掉,这样就实现了BN  层的通道剪枝。通过在损失函数中添加 γ
的 L1 正则约束,可以实现 γ 的稀疏化。公式(1)等号右边第⼀项是原始的损失函数,第⼆项是约束,其中 g(s) = |s|,λ 是正则系数,根据数据集调整实际训练的时候,就是在优化损失函数最⼩,依据梯度下降算法:  ′=∑ ′+ ∑ ′( )=∑ ′+ ∑| |′=∑ ′+ ∑ ∗    ( )所以只需要在反向传播时,在 BN  层权重乘以权重的符号函数输出和系数即可。
图 2:⽹络瘦⾝过程流程图。虚线⽤于多通道/迭代⽅案。 通常的做法是在卷积层之后插⼊⼀个 BN  层,并带有通道级缩放/移位参数。因此,我们可以直接利⽤ BN  层中的 γ 参数作为⽹络瘦⾝所需的缩放因⼦。它的巨⼤优势在于不会给⽹络增加开销。事实上,这可能也是我们学习有意义的通道剪枝缩放因⼦的最有效⽅式。 1)如果我们在没有 BN  层的 CNN  中添加缩放层,缩放因⼦的值对于评估通道的重要性没有意义,因为卷积层和缩放层都是线性变换。可以通过减⼩缩放因⼦值同时放⼤卷积层中的权重来获得相同的结果。 2)如果我们在 BN  层之前插⼊⼀个缩放层,缩放层的缩放效果将被 BN  中的归⼀化过程完全抵消。 3)如果我们在 BN  层之后插⼊缩放层,则每个通道有两个连续的缩放因⼦。
L =l (f (x ,W ),y )+(x ,y )∑λg (γ)————(1)γ∈Γ∑
=z ^;z =σ+ϵ)(B 2z −μin B out γ+z ^β————(2)
μB σB B
通道修剪和微调。在通道级稀疏诱导正则化下训练后,我们获得了⼀个模型,其中许多缩放因⼦接近于零(见图 1)。然后我们可以通过删除所有传⼊和传出连接以及相应的权重来修剪具有接近零缩放
因⼦的通道。我们使⽤跨所有层的全局阈值修剪通道,该阈值定义为所有缩放因⼦值的某个百分位数。例如,我们通过选择百分⽐阈值为 70% 来修剪具有较低缩放因⼦的 70% 通道。通过这样做,我们获得了⼀个更紧凑的⽹络,具有更少的参数和运⾏时内存,以及更少的计算操作。
当修剪率很⾼时,修剪可能会暂时导致⼀些精度损失。但这可以在很⼤程度上通过修剪后的⽹络上的微调过程得到补偿。在我们的实验中,经过微调的窄⽹络在很多情况下甚⾄可以达到⽐原始未剪枝⽹络更⾼的精度。
多程⽅案。我们还可以将所提出的⽅法从单程学习⽅案(使⽤稀疏正则化、修剪和微调进⾏训练)扩展到多程⽅案。具体来说,⽹络瘦⾝过程会导致⽹络变窄,我们可以再次应⽤整个训练过程来学习更紧凑的模型。这由图 2 中的虚线说明。实验结果表明,这种多通道⽅案可以在压缩率⽅⾯产⽣更好的结果。
处理跨层连接和预激活结构。上⾯介绍的⽹络瘦⾝过程可以直接应⽤于⼤多数普通的 CNN 架构,例如 AlexNet 和 VGGNet。虽然将其应⽤于具有跨层连接和预激活设计(例如 ResNet 和 DenseNet)的现代⽹络时需要进⾏⼀些调整。对于这些⽹络,⼀层的输出可以作为后续多个层的输⼊,其中在卷积层之前放置⼀个 BN 层。在这种情况下,稀疏性是在层的传⼊端实现的,即该层有选择地使⽤它接收到的信道⼦集。为了在测试时获得参数和计算节省,我们需要放置⼀个通道选择层来屏蔽我们已经确定的⽆关紧要的通道。
2.修改模型
根据训练的数据集的需要,修改 yaml ⽂件中的 nc 即可。
例如需要训练 coco 数据集中的车类⽬标检测,就将 coco 中的车类图像选出来(),[‘bicycle’, ‘car’, ‘motorcycle’, ‘bus’, truck] ⼀共 5 类⽬标。需要修剪的模型是 yolov5s ,就将 yolov5s.yaml 中的 nc 改为 5。
3.数据集
从已有数据集中提取出⾃⼰想要的图像和 label,或者制作⾃⼰需要的数据集。我是从 coco 数据集中提取想要的图像,可以参考。
4.代码实现
4.1.正常训练
训练 yolov5 模型作为基准,⽤ yolov5 的源码和权重⽂件。
4.2.稀疏训练
反向传播时,在 BN 层的权重乘以权重的符号函数和学习稀疏系数。因为对 BN 层进⾏剪枝,所有的 BN 层都接在卷积层之后,这⾥选择没有 shortcut 的层进⾏剪枝。
在 yolov5 项⽬原有的 train.py ⽂件中加⼊下⾯内容:
# Backward
loss.backward()
# scaler.scale(loss).backward()
# # ============================= sparsity training ========================== #
srtmp = opt.sr*(1-0.9*epoch/epochs)# L1系数逐渐减⼩
if opt.st:
ignore_bn_list =[]
for k, m in model.named_modules():
if isinstance(m, Bottleneck):# 有shortcut的Bottleneck层不剪枝三板模
if m.add:
ignore_bn_list.append(k.rsplit(".",2)[0]+".cv1.bn")
ignore_bn_list.append(k +'.cv1.bn')
ignore_bn_list.append(k +'.cv2.bn')
if isinstance(m, nn.BatchNorm2d)and(k not in ignore_bn_list):
ad.data.add_(srtmp * torch.sign(m.weight.data))# L1
ad.data.add_(opt.sr*10* torch.sign(m.bias.data))# L1
# # ============================= sparsity training ========================== #
optimizer.step()
# scaler.step(optimizer)  # optimizer.step
# scaler.update()
<_grad()
4.3.剪枝
将 BN 层前的卷积层对应通道的卷积核裁剪掉,BN 层后对应的特征图裁剪掉。
(1)将稀疏化的参数升序排列,根据修剪百分⽐设置参数的阈值
# =========================================== prune model ====================================# model_list ={}
ignore_bn_list =[]
for i, layer in model.named_modules():
if isinstance(layer, Bottleneck):
if layer.add:
ignore_bn_list.append(i.rsplit(".",2)[0]+".cv1.bn")
ignore_bn_list.append(i +'.cv1.bn')
ignore_bn_list.append(i +'.cv2.bn')
if isinstance(layer, nn.BatchNorm2d):
if i not in ignore_bn_list:
model_list[i]= layer
print(i, layer)
model_list ={k:v for k,v in model_list.items()if k not in ignore_bn_list}
bn_weights = gather_bn_weights(model_list)
sorted_bn = torch.sort(bn_weights)[0]
# 避免剪掉所有channel的最⾼阈值(每个BN层的gamma的最⼤值的最⼩值即为阈值上限)
highest_thre =[]
for bnlayer in model_list.values():
highest_thre.append(bnlayer.weight.data.abs().max().item())
highest_thre =min(highest_thre)
# 到highest_thre对应的下标对应的百分⽐
percent_limit =(sorted_bn == highest_thre).nonzero()[0,0].item()/len(bn_weights)
print(f'Suggested Gamma threshold should be less than {highest_thre:.4f}.')
print(f'The corresponding prune ratio is {percent_limit:.3f}, but you can set higher.')
assert opt.percent < percent_limit,f"Prune ratio should less than {percent_limit}, otherwise it may cause error"
# model_copy = deepcopy(model)
thre_index =int(len(sorted_bn)* opt.percent)
thre = sorted_bn[thre_index]
print(f'Gamma value that less than {thre:.4f} are set to zero!')
收获时间到print("="*94)
print(f"|\t{'layer name':<25}{'|':<10}{'origin channels':<20}{'|':<10}{'remaining channels':<20}|")
remain_num =0
modelstate = model.state_dict()
(2)根据指定的修剪阈值,设定掩码,将较⼩的参数置 0,其余不变
# ============================================================================== #冷凝水回收装置
maskbndict ={}
氯离子含量测定方法
for bnname, bnlayer in model.named_modules():
if isinstance(bnlayer, nn.BatchNorm2d):
bn_module = bnlayer
mask = obtain_bn_mask(bn_module, thre)
if bnname in ignore_bn_list:
mask = s(bnlayer.weight.data.size()).cuda()
maskbndict[bnname]= mask
# print("mask:",mask)
remain_num +=int(mask.sum())
bn_module.weight.data.mul_(mask)
拉紧装置bn_module.bias.data.mul_(mask)
# print("bn_module:", bn_module.bias)
print(f"|\t{bnname:<25}{'|':<10}{bn_module.weight.data.size()[0]:<20}{'|':<10}{int(mask.sum()):<20}|")
print("="*94)
# print(maskbndict.keys())
pruned_model = ModelPruned(maskbndict=maskbndict, cfg=pruned_yaml, ch=3).cuda()
# Compatibility updates
for m in dules():
if type(m)in[nn.Hardswish, nn.LeakyReLU, nn.ReLU, nn.ReLU6, nn.SiLU, Detect, Model]:
m.inplace =True# pytorch 1.7.0 compatibility
elif type(m)is Conv:
m._non_persistent_buffers_set =set()# pytorch 1.6.0 compatibility
from_to_map = pruned_model.from_to_map
pruned_model_state = pruned_model.state_dict()
assert pruned_model_state.keys()== modelstate.keys()
# ======================================================================================= #
(3)修剪参数,只保留⾮ 0 参数,然后保存 pt ⽂件,代码太长不贴了
4.4.微调
与 yolov5 正常训练基本⼀致,把权重⽂件换成修剪后的即可,最终 mAP@.5 是0.64、权重⽂件⼤⼩是 5.4MB,⽽正常训练的 mAP@.5是0.636、权重⽂件⼤⼩是 14.4MB。
参考⽂献

本文发布于:2024-09-22 17:18:41,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/3/314717.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:通道   修剪   缩放   训练   权重
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议