深度学习Pytorch中组卷积的参数存储方式与剪枝的问题

发布时间 2023-04-15 23:01:39作者: 爱敲代码的自闭杭

写这个主要是因为去年做项目的时候 需要对网络进行剪枝 普通卷积倒没问题 涉及到组卷积通道的裁剪就对应不上 当时没时间钻研 现在再看pytorch 钻研了一下 仔细研究了一下卷积的weight.data的存储

1.搭建网络

这里先随便搭建一下网络 放几个深度可分离卷积和普通卷积

import torch.nn as nn


def autopad(k, p=None):  # kernel, padding
    # Pad to 'same'
    if p is None:
        p = k // 2 if isinstance(k, int) else [x // 2 for x in k]  # auto-pad
    return p


class DWConv(nn.Module):
    def __init__(self, in_plane, out_plane):
        super(DWConv, self).__init__()
        self.depth_conv = nn.Conv2d(in_channels=in_plane,
                                    out_channels=in_plane,
                                    kernel_size=3,
                                    stride=1,
                                    padding=1,
                                    groups=in_plane)
        self.point_conv = nn.Conv2d(in_channels=in_plane,
                                    out_channels=out_plane,
                                    kernel_size=1,
                                    stride=1,
                                    padding=0,
                                    groups=1)
 
    def forward(self, x):
        x = self.depth_conv(x)
        x = self.point_conv(x)
        return x



class Conv(nn.Module):
    # Standard convolution
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups
        super(Conv, self).__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())

    def forward(self, x):
        return self.act(self.bn(self.conv(x)))

    def fuseforward(self, x):
        return self.act(self.conv(x))

class TestModule(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv = Conv(3, 64, 3, 2, 1)
        self.dwconv = DWConv(64, 256)
        self.conv1 = DWConv(256, 4)
        self.conv2 = Conv(4, 512, 8, 2, 1)
    def forward(self, x):
        x = self.conv(x)
        x = self.dwconv(x)
        x = self.conv1(x)
        x = self.conv2(x)
        return x

2.测试代码,查看网络层

model.modules()迭代遍历模型的所有子层,所有子层即指nn.Module子类,nn.xxx构成的卷积,池化,ReLU, Linear, BN, 等都是nn.Module子类,也就是model.modules()会迭代的遍历它们所有对象。

model.named_modules() 就是有名字的model.modules()。

import torch.nn as nn
import testM
model = testM.TestModule()
for modelName, layer in model.named_modules():
    if isinstance(layer, nn.Conv2d):
        print(modelName)
        print(layer)
        print(layer.weight.data.shape)
        print(1)

这里只输出了有关于卷积层的内容

conv.conv
Conv2d(3, 64, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
torch.Size([64, 3, 3, 3])
1
dwconv.depth_conv
Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), groups=64)
torch.Size([64, 1, 3, 3])
1
dwconv.point_conv
Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1))
torch.Size([256, 64, 1, 1])
1
conv1.depth_conv
Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), groups=256)
torch.Size([256, 1, 3, 3])
1
conv1.point_conv
Conv2d(256, 4, kernel_size=(1, 1), stride=(1, 1))
torch.Size([4, 256, 1, 1])
1
conv2.conv
Conv2d(4, 512, kernel_size=(8, 8), stride=(2, 2), padding=(1, 1), bias=False)
torch.Size([512, 4, 8, 8])
1

3.普通卷积

拿conv.conv来说, 参数的size是[64, 3, 3, 3] 第二个是卷积前的通道数 ,第一个是卷积后的通道数。正如图中所示,中间部分就是卷积核,共有64个3×3×3的卷积核,每一个进行卷积操作,最终生成64个通道的特征图。

conv2.conv 就是有512个4×8×8的卷积核进行卷积操作,最终生成512个通道的特征图。

剪枝裁剪通道的时候 直接根据上层卷积修改第二个卷积前的通道数再留下本层有效的卷积通道数即可。

4.深度可分离卷积


拿conv1来说,先说depth_conv,参数的size是[256, 1, 3, 3],这里第一个参数就是卷积后的通道数,如深度可分离卷积图1所展示,由原来的256个通道各自与对用的3×3大小的卷积核进行卷积操作,最终得到256通道的特征图,这里每个通道都只和自己对应的一个3×3大小的卷积核进行一次卷积操作,可以当作普通卷积中filter为1的情况。所以第二个参数就是1,这里进行剪枝操作的时候要根据上层卷积裁剪后的通道数修改第一个参数和group即可(当时就是卡在这里了)。

然后是point_conv,这里就和普通的卷积原理一样了,修改通道也是根据普通卷积那里修改即可。