Skip to content

分类网络

Le-Net

第一个卷积神经网络,但是受到硬件条件的影响难以发挥出神经网络的优势。

Alex-Net

12年的Imagenet冠军算法。

亮点:

  1. 首次利用GPU进行网络加速训练
  2. 使用ReLU激活函数
  3. 使用LRN局部响应归一化
  4. 在全连接层中使用了Dropout随机失活,减少过拟合

VGG

14年Imagenet的定位冠军和分类第二。

亮点:通过堆叠多个3*3的卷积核替代更大的卷积核,感受野的大小是一样的但是参数量比较小。

Google-Net

14年Imagenet的分类第一。

亮点:

  1. 引入Inception结构(融合不同尺度的特征信息)
  2. 使用1*1的卷积核进行降维和映射处理
  3. 添加两个辅助分类器帮助训练
  4. 丢弃全连接层,使用平均池化层,达到减参的效果

Inception结构:有两个版本。将特征矩阵同时输入到不同尺度的卷积核和一个下采样中,并融合在一起。第二个版本即时在输入时通过1*1的卷积核进行降维操作达成减参目的.

python
class Inception(nn.Module):
# 这是一个v2版本的Inception
    def __init__(self, in_channels, conv_1, conv_3_re, conv_3, conv_5_re, conv_5, pooling):
        super(Inception, self).__init__()
        self.branch_1 = ConvReLU(in_channels, conv_1, kernel_size=1)
        self.branch_2 = nn.Sequential(
            ConvReLU(in_channels, conv_3_re, kernel_size=1),
            ConvReLU(in_channels, conv_3, kernel_size=3)
        )
        self.branch_3 = nn.Sequential(
            ConvReLU(in_channels, conv_5_re, kernel_size=1),
            ConvReLU(in_channels, conv_5, kernel_size=5)
        )
        self.branch_4 = nn.Sequential(
            nn.MaxPool2d(kernel_size=3, stride=1, padding=1),
            ConvReLU(in_channels, pooling, kernel_size=1)
        )

    def forward(self, x):
        branch_1 = self.branch_1(x)
        branch_2 = self.branch_2(x)
        branch_3 = self.branch_3(x)
        branch_4 = self.branch_4(x)
        out = [branch_1, branch_2, branch_3, branch_4]
        outputs = torch.cat(out)
        return outputs

辅助分类器:平均池化下采样(5*5)==>Conv 1==> 2FC softmax

LRN作用并不明显所以可以去掉。

python
class InceptionAux(nn.Module):
# 辅助分类器
	def __init__(self, in_channels, classe_num, **kwargs):
		super(InceptionAux, self).__init__()
		self.averagePool = nn.AvgPool2d(kernel_size=5, stride=3)
		self.conv = ConvReLU(in_channels, 128, kernel_size=1)

        # 可以接入展开层
        # self.f = nn.Flatten()

        self.fc_1 = nn.Sequential(
            nn.Linear(2048, 1024),
            nn.ReLU(inplace=True)
        )
        self.fc_2 = nn.Linear(1024, classe_num)

    def forward(self, x):
        # Aux_1 input is B * 512 * 14 * 14
        # Aux_2 input is B * 528 * 14 * 14
        x = self.averagePool(x)
        # Aux_1 pool is B * 512 * 4 * 4
        # Aux_2 pool is B * 528 * 4 * 4
        x = self.conv(x)
        # Aux_1 conv is B * 128 * 14 * 14
        # 展开
        x = torch.flatten(x, 1)
        x = f.dropout(x, 0.5, training=self.training)
        x = self.fc_1(x)
        x = f.dropout(x, 0.5, training=self.training)
        x = self.fc_2(x)
        return x

Res-Net

15年冠军出自微软(何凯明)。

亮点:

  1. 超级深的网络结构(突破了1000层),但是不可能无限叠加,在上千层的时候还是出现了梯度(爆炸和消失)和退化问题
  2. 提出残差结构(residual模块)
  3. 舍弃Dropout,使用BN(Batch Normalization)

**Residual模块:**残差结构,是将前面输入加入到经过处理的里面,是相加需要保持channel一样,不同于Inception的concat计算。主要有两种残差结构。注意虚线的残差结构,在这个结构中进行通道改变和下采样。

python
class Residual(nn.Module):
    expansion = 1

    def __init__(self, in_channels, out_channels, stride=1, downsample=None):
        super(Residual, self).__init__()
        self.conv_1 = nn.Conv2d(in_channels, out_channels, kernel_size=3,
       				 stride=stride, padding=1, bias=False)
        self.BN_1 = nn.BatchNorm2d(out_channels)

        self.conv_2 = nn.Conv2d(out_channels, out_channels, kernel_size=3,
        			stride=stride, padding=1, bias=False)
        self.BN_2 = nn.BatchNorm2d(out_channels)

        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample

    def forward(self, x):
        identity = x
        if self.downsample is not None:
        	identity = self.downsample(x)

        out = self.conv_1(x)
        out = self.BN_1(out)
        out = self.relu(out)

        out = self.conv_2(out)
        out = self.BN_2(out)

        # 注意这里+是残差在激活
        out += identity
        out = self.relu(out)

        return out

BN(Batch Normalization)批归一化 :使得一个batch的feature map 满足均值为0方差为1的分布规律。加速网络收敛和提升准确率。注意:只需要在训练时使用就可以,通过model.train 和modl.pred去调整,这个使用和batch_size的大小,这个是加入在conv和激活函数之间,conv是不需加入偏置。

迁移学习:使用预训练模型的时候注意预处理的方式。

  1. 载入权重后训练所有参数
  2. 载入权重后只训练最后几层参数
  3. 载入权重后冻结参数,只训练我们在后续添加的模块参数

ResNext

新的Block,这个Block使用分组卷积。分组数是32

分组卷积就是卷积通道进行分组管理,这样能减参数。当每一个通道为一组时就是深度可分离卷积(DWconv)对于卷积层数少于3的效果是不是很好。实现的话加入groud约束好分组就好

Mobile-Net

这个是为嵌入式设备和移动设备提供推理能力的一种分类网络。

谷歌提出的,在嵌入式设备和移动设备的卷积神经网络。在效果相当的时候,参数量少了很多。

第一个版本

亮点:

  1. Deepwise Conv(DWconv)。卷积核的channel是为1的
  2. 增加超参数 a(卷积核格式) b(图像大小)

DW卷积就是输入的每一个通道有一个通道为1的卷积核负责,输出一组maps。

PW卷积是将DW的map,通过1*1卷积核进行整理

深度可分离卷积DW+PW卷积。

DW卷积核容易浪费,即卷积核参数大部分为0。

第二个版本

亮点:

  1. Inverted Residuals(倒残差结构)
  2. Linner Bottlenecks

倒残差结构

残差与倒残差

在激活函数中也有变化,在最后一个倒残差结构最后一个地方使用的是线性激活。这个结构并不是每一个结构都有分支连接shotcut。

python
# 倒残差
class InveredResidual:
    def __init__(self, in_channels, out_channels, stride, expand_ratio) -> None:
        hidden_channel = in_channels * expand_ratio
        self.use_shotcut = stride == 1 and in_channels == out_channels

        layers = []
        # 针对第一个层,有没有拓展
        if expand_ratio != 1:
            # 1 * 1
            layers.append(CBRonv(in_channels, hidden_channel, kernel_size = 1))
        layers.extend([
            # 3 * 3
            CBRonv(hidden_channel, hidden_channel, kernel_size=3, stride=stride, groups=hidden_channel),
            # 1 * 1 and liner 激活 
            nn.Conv2d(hidden_channel, out_channels, kenerl_size=1,bias=False),
            nn.BatchNorm2d(out_channels),
        ])

        self.conv = nn.Sequential(*layers)

    def forward(self,x):
        if self.use_shotcut:
            return x + self.conv(x)
        else:
            return self.conv(x)

第三个版本

亮点:

  1. 更新了Block,加入SE注意力和更换激活函数
  2. 使用了NAS搜索参数(Neural Architecture Search)
  3. 重新设计了耗时层结构

SE注意力通过avgpooling采样每个通道,得到一个向量,经过两个linner(channel-->channel/4-->channel)出来一个同纬的向量,这个向量表示的通道将的影响和相关性,将对应的权值与通道特征图相乘。

重新设计耗时层减少了第一层卷积核的个数,精减Last Stage

NAS搜索参数即在给定的search space(卷积核大小, 数量,步长)中搜索出网络合适的结构和超参数

随机搜索、RNN+RL、Datr方法(将每一层的选择融合在网络中,构建一个可微的loss)

Shuffle-Net

一个轻量化网络。

第一个版本亮点:

  1. 通道混淆 (channel shuffle)。
  2. 分组卷积和深度可分离。

通道混淆:将分组卷积和深度可分离卷积的每个groups进行关联。

python
def channel_shuffle(x: Tensor, groups: int): 
    batch_size, num_channels, height, width = x.size()
    channels_pre_group = num_channels // groups

    # 这里就是把序列分组,然后以每个组按序在重组的方式混洗,最后按照新的组展
    # reshape
    # [B[C[H,W],[]......]] ==> [B[G[C_1[H,W]...],[C_2[H,W]...]]]
    x = x.view(batch_size, groups, channels_pre_group, height, width)
  
    # 调换第一个维度和第二个维度的数据,然后在内存连续
    x = torch.transpose(x, 1, 2).contiguous()

    # flatten 
    x = x.view(batch_size, -1, height, width)

    return x

第二个版本:4个准则和新的块结构.。更关注内存访问开销,不只关注FloPs

轻量化的思路:(FLOPs不变,即层的参数量是确定的)

  1. 当conv的输入特征矩阵和输出特征矩阵channel相同是MAC最小(保持FLOPs不变)。
  2. 当Gconv的groups增大时(保持FLOPs不变),MAC也会增大。
  3. 网络设计的碎片化结构(多分枝)程度越高,速度越慢
  4. Element-wise操作(元素操作,+ 激活 shotcut等等)会带来的影响

根据这些准则,对v1的bock进行了调整

Efficient-Net

通过nas,得到考虑输入分辨率、网络深度和宽度(channel)对准确率的影响。博客

第一个版本

以往的经验:

  1. 增加深度能够获得更加丰富、复杂的特征并且能够很好的应用到其他任务但网络的训练更加困难,面临梯度消失等问题。
  2. 增加宽度能够获得更高细粒度的特征且容易训练,但宽度大深度浅的网络很难学到更深层次的特征。
  3. 增加网络输入的图像分辨率能够获取更高细粒度的特征模板,但对于非常高的输入分辨率,准确率的增加也会减小。并且增加计算量。

采用类似mobilenet的block结构,在细节上有变化。具体可以看源码。占用显存!

问题

  1. 训练图像分辨率很高时,训练速度很慢。
  2. 浅层使用DW卷积速度会变慢,是因为在浅层的dw与一些优化器不适应。
  3. 同等方法每个stage是次优的。

第二个版本

亮点:

  1. 引入Fused-MBConv模块
  2. 引入渐进式学习策略(训练更快)
  3. 使用更小的倍率因子和卷积核

Fused-MBConv的shotcut和dropout是同时存在。

这里的dropout随机失活是有点点不同。

渐进式学习可以好好利用一下。

文章更新时间: