目标检测 YOLOv5 - 模型压缩
flyfish
1 什么是剪枝
YOLOv5自带的模型压缩是怎样的呢?就是剪枝。 在一棵树中,把不重要的枝条剪掉,就是剪枝
园丁的手艺是不同的,不同的园丁剪的效果不同。
做模型的剪枝与园丁干得工作是一模一样,先看一个回归实例
拟合数据的结果有正合适,欠拟合,过拟合 直线就是欠拟合,一个每个数据点都经过的曲线就是过拟合了 再看他们的数学表达式,多项式的最高次数是不同的,剪枝就像去掉上图中3次方项和4次方项
剪枝方法多,因为把不重要的东西去掉,在定义什么东西不重要,各家有各家的方法 如果把项剪的太多了,曲线就变成值线了,这不是我们想要的。 当过拟合的时候,剪太多就把树枝剪的没剩几根,了就变成欠拟合;如果已经拟合的很合适了,再剪也欠拟合了。 期望是精度和召回率都不降低,降低的只有计算量
剪枝的生物学启示
深度学习的剪枝被认为是人脑中突触剪枝的一个想法,在人脑中之间发生的突触消除。修剪突触从出生时开始,一直持续到20多岁左右。
看一个神经网络
圈与圈之间的连线就是权重,权重也就是一堆堆的数
剪节点的可以叫 pruning node 或者 pruning neurons 剪神经元 剪线的可以叫 pruning connections 或者叫 pruning synapses 剪突触,剪权重
剪的结果 左图是原始权重矩阵,右图是阈值为0.1的修剪后的矩阵。高亮显示的权重将被删除或者置零。
weight剪枝的实现
怎么实现呢
原始的矩阵 * weight_mask = 新的矩阵 这样知道了代码里的weight_mask 和 bias_mask是个什么意思
彩票假说:寻找稀疏的、可训练的神经网络 彩票假说简单说就是是主要是随机初始化的密集神经网络包含一个初始化的子网,通过随机初始化权重的子网络仍可以达到原始网络的精度.
剪枝有unstructured和structured,这两者有什么区别
非结构化(左)和结构化(右)剪枝的区别:结构化剪枝去除卷积滤波器和内核行,而不仅仅是剪枝连接。 结构化剪枝和非结构化剪枝的主要区别在于剪枝权重的粒度。 非结构化剪枝主要是对单个权重进行裁剪 结构化剪枝的粒度较大,主要是把整行整列的权重移除掉(即把一个神经元去掉),对channel和Filter维度进行裁剪。 可以反过来说因为对单个权重进行裁剪的结果是unstructured,看上去有点乱。对channel和Filter裁剪的结果是structured的,根据裁剪之后是否保持了structe起了一个名字
YOLOv5的剪枝是怎么操作的
YOLOv5提供的一段剪枝代码
def sparsity(model):
a, b = 0., 0.
for p in model.parameters():
a += p.numel()
b += (p == 0).sum()
return b / a
def prune(model, amount=0.3):
import torch.nn.utils.prune as prune
print('Pruning model... ', end='')
for name, m in model.named_modules():
if isinstance(m, nn.Conv2d):
prune.l1_unstructured(m, name='weight', amount=amount)
prune.remove(m, 'weight')
print(' %.3g global sparsity' % sparsity(model))
写一段代码把YOLOv5的剪枝代码用上去,查看剪枝前和剪枝后的区别
import torch
from torch import nn
import torch.nn.utils.prune as prune
import torch.nn.functional as F
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
def sparsity(model):
a, b = 0., 0.
for p in model.parameters():
a += p.numel()
b += (p == 0).sum()
return b / a
def prune(model, amount=0.3):
import torch.nn.utils.prune as prune
print('Pruning model... ', end='')
for name, m in model.named_modules():
if isinstance(m, nn.Conv2d):
prune.l1_unstructured(m, name='weight', amount=amount)
prune.remove(m, 'weight')
print(' %.3g global sparsity' % sparsity(model))
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.conv1 = nn.Conv2d(1, 6, 3)
self.conv2 = nn.Conv2d(6, 16, 3)
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
x = x.view(-1, int(x.nelement() / x.shape[0]))
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
model = LeNet().to(device=device)
module = model.conv1
print(list(module.named_parameters()))
prune(module, amount=0.3)
print(list(module.named_parameters()))
剪枝前
[('weight', Parameter containing:
tensor([[[[-0.2032, -0.0269, -0.0981],
[-0.1920, -0.2737, 0.2451],
[ 0.1116, 0.1331, 0.0147]]],
...
[[[ 0.3109, 0.0082, -0.0080],
[-0.3009, -0.0805, -0.0308],
[-0.0347, -0.2851, 0.1614]]]], device='cuda:0', requires_grad=True)), ('bias', Parameter containing:
tensor([-0.0874, 0.2916, 0.2522, 0.2425, -0.2085, 0.2855], device='cuda:0',
requires_grad=True))]
剪枝后
Pruning model... 0.267 global sparsity
[('bias', Parameter containing:
tensor([-0.0874, 0.2916, 0.2522, 0.2425, -0.2085, 0.2855], device='cuda:0',
requires_grad=True)), ('weight', Parameter containing:
tensor([[[[-0.2032, -0.0000, -0.0981],
[-0.1920, -0.2737, 0.2451],
[ 0.1116, 0.1331, 0.0000]]],
...
[[[ 0.3109, 0.0000, -0.0000],
[-0.3009, -0.0805, -0.0000],
[-0.0000, -0.2851, 0.1614]]]], device='cuda:0', requires_grad=True))]
我们看到不重要的权重变成了0
|