Advertisement

模型剪枝-ICLR2017-Pruning Filters for Efficient ConvNets

阅读量:

Filter 修剪

由于CNN通常在不同的 Filter 和特征信道之间具有显着的冗余,论文中通过修剪 Filter 来减少CNN的计算成本。与在整个网络中修剪权重相比, Filter 修剪是一种自然结构化的修剪方法,不会引入稀疏性,因此不需要使用稀疏库或任何专用硬件。通过减少矩阵乘法的次数,修剪 Filter 的数量与加速度直接相关,这很容易针对目标加速进行调整。

卷积核层面的模型剪枝

本质方法。就是计算每一个filter上权重绝对值之和,去掉m个权重最小的filters,并同时去掉与其相关的特征图及下一层的所有相关的输入filters;
在这里插入图片描述
筛选需要裁减的卷积核步骤为:
1.对每个Filter,使用L1-Norm来计算每一个filter上权重绝对值之和;
2.对所得权重之和进行排序,和大小反映了相关filter的重要性;
3.选择前k个较大的权重之和保留,建立一个mask,大保留的部分为1,小于阈值的部分为0。

复制代码
    cfg_mask = []
    layer_id = 0 # 统计层数
    for m in model.modules(): # 遍历vgg的每个module
    if isinstance(m, nn.Conv2d): # 如果发现卷积层
        out_channels = m.weight.data.shape[0]
    
        # cfg[layer_id]: 每一层要保留的通道数量
        if out_channels == cfg[layer_id]:
            # 如果这一层的通道数已经满足,直接进入下一层循环
            cfg_mask.append(torch.ones(out_channels))
            layer_id += 1
            continue
    
        # 克隆所有卷积层的权重
        weight_copy = m.weight.data.abs().clone()
        weight_copy = weight_copy.cpu().numpy()
    
        # weight_copy: [c_out, c_in, kernal, kernal]
        # L1_norm : [c_out]
        L1_norm = np.sum(weight_copy, axis=(1, 2, 3))
    
        # arg_max为从大到小排序后的下标
        arg_max = np.argsort(L1_norm)[::-1]
    
        # 取前cfg[layer_id]个较大值
        arg_max_rev = arg_max[:cfg[layer_id]]
        assert arg_max_rev.size == cfg[layer_id], "size of arg_max_rev not correct"
        
        # 删除的通道mask=0,保留的通道mask=1
        mask = torch.zeros(out_channels)
        mask[arg_max_rev.tolist()] = 1
    
        # 记录每个卷积层保留的权重
        cfg_mask.append(mask)
        layer_id += 1
    
    elif isinstance(m, nn.MaxPool2d):
        layer_id += 1
    
    
    
    AI生成项目python
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-07-13/otDMwlAufXGp6ExeJkH38mQvhj9Z.png)

之后需要进行BN2D层的剪枝,即需要丢弃刚才被抛弃的卷积核。

复制代码
    start_mask = torch.ones(3)
    layer_id_in_cfg = 0
    end_mask = cfg_mask[layer_id_in_cfg]
    for [m0, m1] in zip(model.modules(), newmodel.modules()):
    
    # 对BN2d层进行剪枝
    if isinstance(m0, nn.BatchNorm2d):
    
        # 获取大于0的所有数据的索引,使用squeeze变成向量
        idx1 = np.squeeze(np.argwhere(np.asarray(end_mask.cpu().numpy())))
        if idx1.size == 1:
            idx1 = np.resize(idx1,(1,))
    
        # 用经过剪枝后的层参数的替换原来的
        # [c]
        m1.weight.data = m0.weight.data[idx1.tolist()].clone()
        m1.bias.data = m0.bias.data[idx1.tolist()].clone()
        m1.running_mean = m0.running_mean[idx1.tolist()].clone()
        m1.running_var = m0.running_var[idx1.tolist()].clone()
    
        # 下一层
        layer_id_in_cfg += 1
    
        # 当前在处理的层的mask
        start_mask = end_mask
    
        # 全连接层不做处理
        if layer_id_in_cfg < len(cfg_mask):  # do not change in Final FC
            end_mask = cfg_mask[layer_id_in_cfg]
    
    
    
    AI生成项目python
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-07-13/mIe1GsoWxNiSKr74lHA9vYup5jCw.png)

最后需要进行卷积层剪枝,根据前后BN层的保留层,可以计算得到卷积层保留的卷积核大小(上层BN层输出,下层BN层输入),保留前后BN的对应保留的元素,其余剪枝。

复制代码
       # 对卷积层进行剪枝
    elif isinstance(m0, nn.Conv2d):
        # 卷积后面会接bn
        idx0 = np.squeeze(np.argwhere(np.asarray(start_mask.cpu().numpy())))
        idx1 = np.squeeze(np.argwhere(np.asarray(end_mask.cpu().numpy())))
        print('In shape: {:d}, Out shape {:d}.'.format(idx0.size, idx1.size))
        if idx0.size == 1:
            idx0 = np.resize(idx0, (1,))
        if idx1.size == 1:
            idx1 = np.resize(idx1, (1,))
        # 剪枝
        # [c_out, c_in, kernal, kernal]
        w1 = m0.weight.data[:, idx0.tolist(), :, :].clone()
        w1 = w1[idx1.tolist(), :, :, :].clone()
        m1.weight.data = w1.clone()
    
    
    
    AI生成项目python
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-07-13/prsR3cCntXjx0aSfHTomyNPiBd9h.png)

最后对FC层进行剪枝,由于最后一层FC层的输出是固定的(分类类数),因此只对FC层的输入维度进行剪枝,也是根据上一层BN层的输出,对应保留的元素,其余剪枝。

复制代码
    # 对全连接层进行剪枝
    elif isinstance(m0, nn.Linear):
    
        # 最后一层全连接层进行剪枝
        if layer_id_in_cfg == len(cfg_mask):
            idx0 = np.squeeze(np.argwhere(np.asarray(cfg_mask[-1].cpu().numpy())))
            if idx0.size == 1:
                idx0 = np.resize(idx0, (1,))
            # [c_out, c_in]
            m1.weight.data = m0.weight.data[:, idx0].clone()
            m1.bias.data = m0.bias.data.clone()
            layer_id_in_cfg += 1
            continue
    
        # 其余全连接层不剪枝
        m1.weight.data = m0.weight.data.clone()
        m1.bias.data = m0.bias.data.clone()
    
    
    
    AI生成项目python
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-07-13/AWeQ4hB91cjUR2Xw7i0lTvuIbn8G.png)

对BN1d层不剪枝

复制代码
    # 对BN1d层不进行剪枝,直接使用原始模型参数
    elif isinstance(m0, nn.BatchNorm1d):
        m1.weight.data = m0.weight.data.clone()
        m1.bias.data = m0.bias.data.clone()
        m1.running_mean = m0.running_mean.clone()
        m1.running_var = m0.running_var.clone()
    
    
    
    AI生成项目python
    
    

多层同时裁剪

多层同时修剪:
作者给出了2中修剪思路:
1)独立修剪:修剪时每一层是独立的。
2)贪心修剪:修剪时考虑之前图层中删除的 Filter 。
两种方法的区别:独立修剪在计算(求权重绝对值之和)时不考虑上一层的修剪情况,所以计算时下图中的黄点仍然参与计算;贪心修剪计算时不计算已经修剪过的,即黄点不参与计算。
结果证明第二种方法的精度高一些。
在这里插入图片描述

卷积核层面的剪枝的优点

删除整个不重要的过滤器的好处有很多:1)修剪后的模型在网络结构上没有区别,因此它可以被任何现成的深度学习库完美支持。2)内存占用将显著减少。这种记忆的减少不仅来自于模型参数本身,还来自于中间激活,这在以往的研究中很少被考虑。3)由于修剪后的网络结构没有受到破坏,可以通过其他压缩方法进一步压缩和加速,如参数量化方法。4)修剪后的模型可以极大地加速更多的视觉任务,如目标检测、语义分割等。

全部评论 (0)

还没有任何评论哟~