压缩和加速模型

  • 参数剪枝:通过删除神经网络中不必要的连接来减少计算量的方法
    • 减少模型中的参数数量,加速模型的运行速度,节省存储空间
  • 量化:降低神经网络中权重和激活值精度的方法
    • 通过将float32/int32转换为int8,可以减少存储空间和计算量
  • 网络结构优化:通过改变神经网络的结构来减少计算量
    • 包括减少网络输入/层数、减少神经元数量、更加高效的卷积操作和矩阵乘法等
  • 模型蒸馏:通过训练一个较小的模型来压缩一个较大的模型的方法。
    • 先训练一个较大的模型,然后使用训练数据集来训练一个较小的模型,使得较小的模型能够学到较大模型的知识。
    • 不仅能压缩模型,还能保留大部分性能
  • 低秩分解:通过将权重矩阵分解为两个低秩矩阵来压缩深度学习模型的方法。
    • 不仅能压缩模型,还能提高模型的泛化能力,降低过拟合的风险

参数剪枝

剪枝的类型

按剪枝粒度分类

类型 描述 示例 特点
权重剪枝 移除单个权重 将W₁₂=0.003→0 粒度最细,稀疏性高
神经元剪枝 移除整个神经元 删除某一隐藏层节点 减少层宽度
通道剪枝 移除整个特征通道 CNN中删除一个卷积核 硬件友好
层剪枝 移除整个网络层 删除某个残差块 大幅减少计算量

剪枝时机分类

  • 训练前剪枝:在模型训练前进行剪枝(较少使用)
  • 训练中剪枝:在训练过程中动态剪枝
  • 训练后剪枝:先训练完整模型,再进行剪枝(最常见)

是否结构化分类

  • 非结构化剪枝:任意位置权重置零(高稀疏性但硬件不友好)
  • 结构化剪枝:移除整个结构单元(如通道、层,硬件友好)

剪枝策略

  1. 通常对卷积层(Conv2d)和全连接层(Linear)进行剪枝,因为这些层包含了模型的大部分参数和计算量。
  2. 批归一化层(BatchNorm)通常与卷积层一起剪枝,因为它们是成对出现的。当我们剪枝一个卷积层时,下一个批归一化层的通道数也会相应减少。
  3. 激活函数(如ReLU)和池化层(如MaxPool2d)通常不进行剪枝,因为它们没有可学习的参数,而且它们的输出形状直接由输入决定,剪枝它们不会减少参数和计算量。
    • 第一个卷积层(输入层)和最后一个卷积层(输出层)通常剪枝比例要小一些,因为输入层对图像特征提取很关键,输出层直接关系到预测结果。

resnet34剪枝建议

  1. 主要剪枝卷积层(包括残差块中的卷积层)和全连接层
  2. 批归一化层(BN)需要与卷积层同步剪枝
    • 即当剪枝一个卷积层的输出通道时,下一个BN层的通道数也要相应减少,同时该卷积层后面连接的另一个卷积层的输入通道数也要减少
    • 如果是残差结构,注意 shortcut 的连接
  3. 不剪枝的层:
    • ReLU:没有参数,且输出形状与输入相同
    • MaxPool2d:没有参数,只改变特征图尺寸,不改变通道数
    • 跳跃连接(shortcut)中的卷积层:如果残差块中有1x1卷积(用于调整通道数),这些卷积层也需要剪枝,并且要确保主分支和shortcut分支的输出通道数一致。
  4. 谨慎剪枝的层:
    • 整个模型的输入层(如conv1):保留较高比例(建议剪枝率 < 20%),直接处理原始图像,提取基础特征,对图像特征提取至关重要
    • 整个模型的输出层(检测头中的卷积层):分类/回归分支的最后一层,建议剪枝率 < 30%,过度剪枝会显著影响精度
    • 残差块内部的第一层卷积:这些层通常改变通道数(尤其是下采样的残差块),剪枝时要注意与shortcut连接的通道匹配,剪枝比例可以适当高一些(例如30%-50%)。
    • 残差块内部的中间层和最后一层卷积:这些层可以应用较高的剪枝比例(例如40%-60%),因为它们不直接与残差连接匹配,且对模型容量的影响相对较小
层类型 示例 剪枝优先级 建议剪枝率
中间卷积 body.layer2.1.conv2 ★★★★★ 40-70%
残差块首层 body.layer1.0.conv1 ★★★★☆ 30-60%
残差块末层 body.layer3.2.conv3 ★★★☆☆ 20-50%
输入卷积 conv1 ★★☆☆☆ 10-20%
输出检测头 ClassHead.conv1 ★★☆☆☆ 20-40%
BN层 bn1 ★★★★☆ 跟随卷积
ReLU/MaxPool N/A 0%

注意事项:

  • 剪枝可能会破坏残差连接的结构,因为两个相加的分支必须有相同的通道数。因此,在剪枝残差块时,要确保两个分支的通道数匹配。
  • 第一个卷积层(输入层)和最后一个卷积层(输出层)通常剪枝比例要小一些,因为输入层对图像特征提取很关键,输出层直接关系到预测结果。
  • 对于 RetinaFace 有多个输出分支(如分类、回归、关键点等),这些分支的卷积层也需要剪枝,但要谨慎,以免影响检测精度。

剪枝后问题

Q1:通过torch_pruning进行通道剪枝后,模型大小变了,通过profile_macs获取的flops降低了30%,但是推理速度没有变化

(1) 硬件利用率瓶颈

  • 根本原因:现代GPU的并行计算能力远超小模型需求,当计算密度不足时会出现「内存墙」问题
  • 验证方法
nvidia-smi -l 1  # 观察GPU利用率是否达到100%,低于70%,说明瓶颈不在计算

(2) 剪枝后结构问题

  • 典型表现:
    • 剪枝后产生非结构化稀疏(不规则零值)
    • 未移除实际计算分支
  • 验证方法
for name, module in model.named_modules():
    if isinstance(module, nn.Conv2d):
        print(f"{name}: {module.weight.sum().item():.2f}")  # 查看权重稀疏度

Q2:通过torch_pruning进行非结构剪枝后,输出通道的维度发生了变化,加载模型失败:Target sizes: [10, 5376, 4]. Tensor sizes: [10, 10752, 1]

  • 验证方法
        ignored_layers = []
        for name, m in model.named_modules():
            print(f"name:{name}")
            if any(x in name for x in ['fpn', 'SSH','ClassHead','BboxHead','LandmarkHead']):
                ignored_layers.append(m)
                print(f"ignored_layers name:{m}")
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐