23暑假深度学习week4

本文最后更新于:2023年8月6日 晚上

MobileNet V1

MobileNet网络是由google团队在2017年提出的,专注于移动端或者嵌入式设备中的轻量级CNN网络。相比传统卷积神经网络,在准确率小幅降低的前提下大大减少模型参数与运算量。(相比VGG16准确率减少了0.9%,但模型参数只有VGG的1/32)

MobileNet_v1的亮点:

  1. Depthwise Convolution( 大大减少运算量和参数数量)
  2. 增加超参数 增加超参数α 、β(其中α是控制卷积层卷积核个数的超参数,β是控制输入图像的大小

传统卷积:

传统卷积

  • 卷积核channel=输入特征矩阵channel(图中为3)
  • 输出特征矩阵channel=卷积核个数(图中为4)

DW卷积(Depthwise Conv)

DWConv

  • 卷积核channel=1
  • 输入特征矩阵channel=卷积核个数=输出特征矩阵channel(图中为3)

DW卷积中的每一个卷积核,只会和输入特征矩阵的一个channel进行卷积计算,所以输出的特征矩阵就等于输入的特征矩阵。

PW卷积(Pointwise Conv)

PWConv

PW卷积和普通的卷积类似,只是采用了1x1的卷积核,输出的特征矩阵channel的个数与使用的卷积核数相等,而输入特征矩阵的channel的个数与卷积核的channel数相等。所以其就是一个普通的卷积。

一般来说,以上的PW卷积与DW卷积是放在一起操作的,共同组成深度可分卷积操作。

深度可分卷积操作(Depthwise Separable Conv)

深度可分卷积操作是有两步分组成,一部分是DW卷积(Depthwise Conv),另外一部分是PW卷积(Pointwise Conv)

DSConv

两者的计算量对比:

  • DSConv:$D_k * D_k * M * D_f * D_f + M * N * D_f * D_f$
  • 普通:$D_k * D_k * M * N * D_f * D_f$
    理论上普通卷积计算量是 DW+PW 的8到9倍

MobileNet V2

MobileNet v1 的不足

ReLU 和数据坍缩

如图所示,Input是一个2维数据,其中兴趣流形是其中的蓝色螺旋线。本例使用矩阵$T$将数据嵌入到n维空间中,后接ReLU,再使用$T^{-1}$将其投影回2D平面。

可以看到设置n=2,3时信息丢失严重,中心点坍塌掉了。当n=15..30之间,恢复的信息明显多了。

实验发现当n很小的时候,后面接ReLU非线性变换的话会导致很多信息的丢失,而维度越高还原的图片和原图越相似。

ResNet 、ReLU 和神经元死亡

在神经网络训练中如果节点的值变为0就会“死掉”。因为ReLU对0值的梯度是0,后续无论怎么迭代这个节点的值都不会恢复了。而通过ResNet结构的特征复用,可以很大程度上缓解这种特征退化问题(这也从一个侧面说明ResNet为何好于VGG)。另外,一般情况训练网络使用的是float32浮点数;当使用低精度的float16时,这种特征复用可以更加有效的减缓退化。

Linear Bottleneck

降维的问题

论文中称网络层中的激活特征为兴趣流形(mainfold of interest),我们认为深度神经网络是由n个$L_i$层构成,每层经过激活输出的张量为$h_iw_id_i$。我们认为一连串的卷积和激活层形成一个兴趣流形(manifold of interest,这就是我们感兴趣的数据内容),现阶段还无法定量的描述这种流行,这里以经验为主的研究这些流行性质。

长期以来我们认为:在神经网络中兴趣流行可以嵌入到低维子空间,通俗点说,我们查看的卷积层中所有单个d通道像素时,这些值中存在多种编码信息,兴趣流行位于其中的。我们可以通过变换,进一步嵌入到下一个低维子空间中(例如通过 1×1 卷积变换维数,转换兴趣流行所在空间维度)。

乍一看,这样的想法比较容易验证,可通过减少层维度从而降低激活空间的维度。MobileNetv1是通过宽度因子(width factor)在计算量和精度之间取折中。用上面的理论来说,宽度因子控制激活空间的维度,直到兴趣流形横跨整个空间。

然而,由于深度卷积神经网络的层是具有非线性激活函数的。以ReLU变换为例,会出现上面说到的数据坍塌问题

我们设计网络结构的时候,想要减少运算量,就需要尽可能将网络维度设计的低一些但是维度如果低的话,激活变换ReLU函数可能会滤除很多有用信息。然后我们就想到了,反正ReLU另外一部分就是一个线性映射。那么如果我们全用线性分类器,会不会就不会丢失一些维度信息,同时可以设计出维度较低的层呢?

解决方法

论文针对这个问题使用linear bottleneck(即不使用ReLU激活,做了线性变换)的来代替原本的非线性激活变换。到此,优化网络架构的思路也出来了:通过在卷积模块中后插入linear bottleneck来捕获兴趣流形。 实验证明,使用linear bottleneck可以防止非线性破坏太多信息。

具体思想如下图:

  1. 相比于MobileNetV1,先进行了1x1的卷积进行升维,目的在于获得更多特征,然后用3x3的空间卷积,最后再用1x1降维。核心思想是升维再降维,参数量更少。

  2. 为了避免ReLU对特征的破坏,在3x3网络结构后,再利用1x1卷积降维后,不再进行ReLU6层,直接进行残差网络的加法。

从linear bottleneck到深度卷积之间的的维度比称为Expansion factor(扩展系数),该系数控制了整个block的通道数。下一部分介绍就要用到这个Expansion了。

Inverted residuals

MobileNetV2的网络模块样子是这样的:

MobileNetV1网络主要思路就是深度可分离卷积的堆叠。在V2的网络设计中,我们除了继续使用深度可分离(中间那个)结构之外,还使用了Expansion layerProjection layer

  • Projection layer也是使用 1×1 的网络结构,他的目的是希望把高维特征映射到低维空间去。另外说一句,使用 1×1 的网络结构将高维空间映射到低纬空间的设计有的时候我们也称之为Bottleneck layer。

  • Expansion layer的功能正相反,使用 1×1 的网络结构,目的是将低维空间映射到高维空间。这里Expansion有一个超参数是维度扩展几倍。可以根据实际情况来做调整的,默认值是6,也就是扩展6倍。

上图更详细的展示了整个模块的结构。我们输入是24维,最后输出也是24维。但这个过程中,我们扩展了6倍,然后应用深度可分离卷积进行处理。

bottleneck residual block(ResNet论文中的)是中间窄两头胖,在MobileNetV2中正好反了过来,所以,在MobileNetV2的论文中我们称这样的网络结构为Inverted residuals。需要注意的是residual connection是在输入和输出的部分进行连接。另外,我们之前已经花了很大篇幅来讲Linear Bottleneck,因为从高维向低维转换,使用ReLU激活函数可能会造成信息丢失或破坏(不使用非线性激活数数)。所以在Projection convolution这一部分,我们不再使用ReLU激活函数而是使用线性激活函数。

MobileNet V3

主要特点

  1. 论文推出两个版本:Large 和 Small,分别适用于不同的场景;
  2. 使用NetAdapt算法获得卷积核和通道的最佳数量;
  3. 继承V1的深度可分离卷积;
  4. 继承V2的具有线性瓶颈的残差结构;
  5. 引入SE通道注意力结构;
  6. 使用了一种新的激活函数h-swish(x)代替ReLU6,h的意思表示hard;
  7. 使用了$\frac{\operatorname{ReLU} 6(x+3)}{6}$来近似SE模块中的sigmoid;
  8. 修改了MobileNetV2后端输出head;

整体结构

large和small的整体结构一致,区别就是基本单元bneck的个数以及内部参数上,主要是通道数目。

通道可分离卷积

通道分离卷积是MobileNet系列的主要特点,也是其发挥轻量级作用的主要因素。如下图,通道可分离卷积分为两个过程:1.channel方向通道可分离卷积;2.正常的1X1卷积输出指定的channel个数。

SE模块

SE通道注意力机制,老生常谈的话题。值得注意的是,这里利用1X1卷积实现的FC操作,本质上和FC是一样的。这里利用hsigmoid模拟sigmoid操作。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SeModule(nn.Module):
def __init__(self, in_size, reduction=4):
super(SeModule, self).__init__()
self.se = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Conv2d(in_size, in_size // reduction, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(in_size // reduction),
nn.ReLU(inplace=True),
nn.Conv2d(in_size // reduction, in_size, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(in_size),
hsigmoid())

def forward(self, x):
return x * self.se(x)

h-swish 和 h-sigmoid

利用近似操作模拟swish和relu,公式如下:

$\text { h-swish }[x]=x \frac{\operatorname{ReLU} 6(x+3)}{6}$

代码实现

1
2
3
4
5
6
7
8
9
class hswish(nn.Module):
def forward(self, x):
out = x * F.relu6(x + 3, inplace=True) / 6
return out

class hsigmoid(nn.Module):
def forward(self, x):
out = F.relu6(x + 3, inplace=True) / 6
return out

bneck

核心模块,也是网络的基本模块。主要实现了通道可分离卷积+SE通道注意力机制+残差连接。结构图如下:

代码实现

1
2
3
4
5
6
7
8
def forward(self, x):
out = self.nolinear1(self.bn1(self.conv1(x))) # 降维
out = self.nolinear2(self.bn2(self.conv2(out))) # 通道可分离卷积
out = self.bn3(self.conv3(out)) # 1X1卷积聚合特征
if self.se != None:
out = self.se(out) # 通道注意力机制
out = out + self.shortcut(x) if self.stride == 1 else out # 残差连接
return out

网络输出

移除之前的瓶颈层连接,进一步降低网络参数。可以有效降低11%的推理耗时,而性能几乎没有损失。修改结构如下:

代码实现

1
2
3
4
5
out = F.avg_pool2d(out, 7)
out = out.view(out.size(0), -1)
out = self.hs3(self.bn3(self.linear3(out)))
out = self.linear4(out)
return out

ShuffleNet

ShuffleNet是旷视科技最近提出的一种计算高效的CNN模型,其和MobileNet和SqueezeNet等一样主要是想应用在移动端。所以,ShuffleNet的设计目标也是如何利用有限的计算资源来达到最好的模型精度,这需要很好地在速度和精度之间做平衡。

ShuffleNet的核心是采用了两种操作:pointwise group convolution和channel shuffle,这在保持精度的同时大大降低了模型的计算量。

设计理念

ShuffleNet的核心设计理念是对不同的channels进行shuffle来解决group convolution带来的弊端。Group convolution是将输入层的不同特征图进行分组,然后采用不同的卷积核再对各个组进行卷积,这样会降低卷积的计算量。因为一般的卷积都是在所有的输入特征图上做卷积,可以说是全通道卷积,这是一种通道密集连接方式(channel dense connection)。而group convolution相比则是一种通道稀疏连接方式(channel sparse connection)。

使用group convolution的网络如Xception,MobileNet,ResNeXt等。Xception和MobileNet采用了depthwise convolution,这其实是一种比较特殊的group convolution,此时分组数恰好等于通道数,意味着每个组只有一个特征图。但是这些网络存在一个很大的弊端是采用了密集的1x1卷积,或者说是dense pointwise convolution,这里说的密集指的是卷积是在所有通道上进行的。所以,实际上比如ResNeXt模型中1x1卷积基本上占据了93.4%的乘加运算。

那么不如也对1x1卷积采用channel sparse connection,那样计算量就可以降下来了。但是group convolution存在另外一个弊端,如下图(a)所示,其中GConv是group convolution,这里分组数是3。可以看到当堆积GConv层后一个问题是不同组之间的特征图是不通信的,这就好像分了三个互不相干的路,大家各走各的,这目测会降低网络的特征提取能力。这样你也可以理解为什么Xception,MobileNet等网络采用密集的1x1卷积,因为要保证group convolution之后不同组的特征图之间的信息交流。

但是达到上面那个目的,我们不一定非要采用dense pointwise convolution。如下图(b)所示,你可以对group convolution之后的特征图进行“重组”,这样可以保证接下了采用的group convolution其输入来自不同的组,因此信息可以在不同组之间流转。这个操作等价于下图(c),即group convolution之后对channels进行shuffle,但并不是随机的,其实是“均匀地打乱”。

在程序上实现channel shuffle是非常容易的:假定将输入层分为$g$组,总通道数为$g*n$,首先你将通道那个维度拆分为$(g,n)$两个维度,然后将这两个维度转置变成$(n,g)$,最后重新reshape成一个维度。仅需要简单的维度操作和转置就可以实现均匀的shuffle。利用channel shuffle就可以充分发挥group convolution的优点,而避免其缺点。

ShuffleNet基本单元

SENet

Squeeze-and-Excitation Networks(简称 SENet)是 Momenta 胡杰团队(WMW)提出的新的网络结构,利用SENet,一举取得最后一届 ImageNet 2017 竞赛 Image Classification 任务的冠军,在ImageNet数据集上将top-5 error降低到2.251%,原先的最好成绩是2.991%。

作者在文中将SENet block插入到现有的多种分类网络中,都取得了不错的效果。作者的动机是希望显式地建模特征通道之间的相互依赖关系。另外,作者并未引入新的空间维度来进行特征通道间的融合,而是采用了一种全新的「特征重标定」策略。具体来说,就是通过学习的方式来自动获取到每个特征通道的重要程度,然后依照这个重要程度去提升有用的特征并抑制对当前任务用处不大的特征。

通俗的来说SENet的核心思想在于通过网络根据loss去学习特征权重,使得有效的feature map权重大,无效或效果小的feature map权重小的方式训练模型达到更好的结果。SE block嵌在原有的一些分类网络中不可避免地增加了一些参数和计算量,但是在效果面前还是可以接受的 。Sequeeze-and-Excitation(SE) block并不是一个完整的网络结构,而是一个子结构,可以嵌到其他分类或检测模型中。

听起来像是注意力机制

SENet 结构

上述结构中,Squeeze 和 Excitation 是两个非常关键的操作,下面进行详细说明。

上图是SE 模块的示意图。给定一个输入 $x$,其特征通道数为 $c_1$,通过一系列卷积等一般变换后得到一个特征通道数为$c_2$ 的特征。与传统的 CNN 不一样的是,接下来通过三个操作来重标定前面得到的特征。

首先是 Squeeze 操作,顺着空间维度来进行特征压缩,将每个二维的特征通道变成一个实数,这个实数某种程度上具有全局的感受野,并且输出的维度和输入的特征通道数相匹配。它表征着在特征通道上响应的全局分布,而且使得靠近输入的层也可以获得全局的感受野,这一点在很多任务中都是非常有用的。

其次是 Excitation 操作,它是一个类似于循环神经网络中门的机制。通过参数$w$来为每个特征通道生成权重,其中参数$ w $被学习用来显式地建模特征通道间的相关性。

最后是一个 Reweight 的操作,将 Excitation 的输出的权重看做是进过特征选择后的每个特征通道的重要性,然后通过乘法逐通道加权到先前的特征上,完成在通道维度上的对原始特征的重标定。

HybridSN

通常,HybridSN是频谱空间3-D CNN,然后是空间2-D-CNN。
  论文中提到,仅使用2-D-CNN或3-D-CNN有一些缺点,例如缺少频道 关系信息或模型非常复杂。这也阻止了这些方法在HSI空间上获得更高的准确性。主要原因是由于HSI是体积数据,也有光谱维度。
  单独的2-D-CNN无法从光谱维度上提取良好的区分特征,同样,深 3-D-CNN的计算更加复杂,而论文中提出的HybridSN模型,克服了先前模型的这些缺点。3-D-CNN和2-D-CNN层以推荐的模型组装成合适的网络,充分利用光谱图和空间特征图,最大限度地提高精度。

网络结构

在论文中使用的高光谱图像数据集有一个是Indian Pines,它是最早的用于高光谱图像分类的测试数据。
  该数据集总共有21025个像素,但是其中只有10249个像素是地物像素,其余10776个像素均为背景像素,我们需要剔除。最后,我们对着10249个像素进行16分类。

定义 HybridSN 类

根据Fig. 1. 可以写出HybridSN类的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class HybridSN(nn.Module):
def __init__(self):
super(HybridSN, self).__init__()

# 三维卷积部分
self.conv1 = nn.Conv3d(1, 8, kernel_size=(7, 3, 3))
self.conv2 = nn.Conv3d(8, 16, kernel_size=(5, 3, 3))
self.conv3 = nn.Conv3d(16, 32, kernel_size=(3, 3, 3))

# 二维卷积部分
self.conv4 = nn.Conv2d(32*18, 64, kernel_size=3)

# 全连接部分
self.fc1 = nn.Linear(18496, 256)
self.fc2 = nn.Linear(256, 128)
self.fc3 = nn.Linear(128, 16)

# Dropout
self.dropout = nn.Dropout(p=0.4)

# Flatten操作
self.flatten = nn.Flatten()

# 激活函数
self.activation = nn.ReLU()

# softmax
self.softmax = nn.LogSoftmax(dim=1)

def forward(self, x):
# 三维卷积部分
x = self.conv1(x)
x = self.activation(x)
x = self.conv2(x)
x = self.activation(x)
x = self.conv3(x)
x = self.activation(x)

# reshape
x = x.reshape(-1,32*18, 19, 19)

# 二维卷积部分
x = self.conv4(x)
x = self.activation(x)

# flatten操作
x = self.flatten(x)

# 全连接部分
x = self.fc1(x)
x = self.activation(x)
x = self.dropout(x)
x = self.fc2(x)
x = self.activation(x)
x = self.dropout(x)
x = self.fc3(x)
x = self.softmax(x)

return x

注意这个数据集目前需要自己去官网下载之后放到目录下

结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
96.22367215494994 Kappa accuracy (%)
96.69376693766938 Overall accuracy (%)
95.12790082818768 Average accuracy (%)

precision recall f1-score support

Alfalfa 1.00 0.88 0.94 41
Corn-notill 0.96 0.95 0.95 1285
Corn-mintill 0.94 0.97 0.96 747
Corn 0.94 0.92 0.93 213
Grass-pasture 0.98 0.98 0.98 435
Grass-trees 0.99 0.97 0.98 657
Grass-pasture-mowed 1.00 0.92 0.96 25
Hay-windrowed 0.98 1.00 0.99 430
Oats 0.90 1.00 0.95 18
Soybean-notill 0.98 0.96 0.97 875
Soybean-mintill 0.96 0.99 0.98 2210
Soybean-clean 0.97 0.89 0.93 534
Wheat 0.99 0.98 0.99 185
Woods 0.98 1.00 0.99 1139
Buildings-Grass-Trees-Drives 0.99 0.88 0.93 347
Stone-Steel-Towers 0.81 0.93 0.87 84

accuracy 0.97 9225
macro avg 0.96 0.95 0.96 9225
weighted avg 0.97 0.97 0.97 9225

混淆矩阵

分类结果

可以看出分类准确度比较好,还可以增加注意力机制等方法来进一步提高准确度

思考

训练HybridSN,然后多测试几次,会发现每次分类的结果都不一样?

启用dropout后(默认开启了model.train()),会进行随机采样,可能会导致网络在测试时分类结果不一样,准确率可能会受到影响。加入model.eval()可以固定住dropout(有时候,我们的模型会产生随机数,可能也会导致分类结果不同)。

如果想要进一步提升高光谱图像的分类性能,可以如何改进?

最开始我是按照基本网络流程进行训练,OA在94%,后来分别进行了两个优化操作:

  • 更换softmax层为LogSoftmax

    1
    2
    # softmax
    self.softmax = nn.LogSoftmax(dim=1)
  • 使用BatchNorm

    1
    2
    3
    4
    5
    #加入BN归一化数据
    self.bn1=nn.BatchNorm3d(8)
    self.bn2=nn.BatchNorm3d(16)
    self.bn3=nn.BatchNorm3d(32)
    self.bn4=nn.BatchNorm2d(64)

前者将OA提升到96.69%,在此基础上后者将OA提升至97.15%,但是在加入BN操作之后,分类效果图惨不忍睹:

分类结果

depth-wise conv 和 分组卷积有什么区别与联系?

“Depth-wise conv”(深度卷积)是将输入特征的每一通道分为一组,即分组数为通道数(Cin)。在深度卷积中,每个卷积核都与输入特征的每个通道进行卷积操作,从而收集每个通道的特征。

DW卷积

“分组卷积”是将输入特征按通道分为g组,每组特征中的通道数为Cin/g。相应的卷积核大小也变了,通道数变少。每次卷积后的特征按通道concat输出特征。当分组数量和输出特征的通道相同时,即只有Cout个卷积核时,卷积分别对输入特征的每一组卷积1次;当输出特征的通道大于分组数量时,需要Cout个卷积核对输入特征的每一组卷积Cout/g次。

分组卷积(右)

“depth-wise conv”是分组卷积的一种特例,而分组卷积则是普通卷积的一种变体,通过减少卷积核的数量和调整卷积核的大小来提高计算的效率。

SENet 的注意力是不是可以加在空间位置上?

SENet的注意力机制可以通过在空间位置上应用SE模块来实现。SE模块中的全局平均池化操作可以捕获每个通道的全局特征,而这也适用于空间位置。因此,可以在SE模块中添加空间位置信息,以便更好地捕捉特征在空间上的关系。

在 ShuffleNet 中,通道的 shuffle 如何用代码实现?

ShuffleNet 中的通道 shuffle 可以使用 PyTorch 实现。在 ShuffleNet 的卷积层中使用 nn.Shuffle 模块来实现通道 shuffle。

以下是使用 PyTorch 实现通道 shuffle 的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import torch
import torch.nn as nn

class ShuffleBlock(nn.Module):
def __init__(self, groups):
super(ShuffleBlock, self).__init__()
self.groups = groups

def forward(self, x):
# 将通道维度分成 groups 个组
batch_size, channels, height, width = x.size()
channels_per_group = channels // self.groups

# 将通道重新分组
x = x.view(batch_size, self.groups, channels_per_group, height, width)

# 在每个组内进行通道 shuffle
for i in range(self.groups):
start = i * channels_per_group
end = (i + 1) * channels_per_group
x[..., start:end, :, :] = x[..., start:end, :, :].permute(0, 2, 1, 3, 4)

# 将通道重新合并
x = x.view(batch_size, channels, height, width)

return x

在上述代码中,定义了一个 ShuffleBlock 模块,其中 groups 参数表示通道分组的数量。在 forward 方法中,首先将通道维度分成 groups 个组,并将通道重新分组。然后,对于每个组,将其内部的通道进行通道 shuffle,即按照通道维度交换顺序。最后,将通道重新合并,并返回输出。

这样,就实现了 ShuffleNet 中的通道 shuffle,并可以使用该模块来构建 ShuffleNet 模型。


23暑假深度学习week4
https://jialiangz.github.io/2023/08/04/23暑假深度学习week4/
作者
爱吃菠萝
发布于
2023年8月4日
更新于
2023年8月6日
许可协议