【第一部分】问题总结
创建MobileNet V2时,第二个基本单元的步长与论文中不一致:
MobileNet V2中使用了BatchNorm来优化网络,归一化来加速网络,HybridSN中添加后分类结果变差。
【第二部分】代码练习
使用MobileNet V1对CIFAR10进行分类
可分离卷积的实现:
class Block(nn.Module):
'''Depthwise conv + Pointwise conv'''
def __init__(self, in_planes, out_planes, stride=1):
super(Block, self).__init__()
# Depthwise 卷积,3*3 的卷积核,分为 in_planes,即各层单独进行卷积
# group这个参数是用做分组卷积的,但是现在用的比较多的是groups = in_channel
# 当groups = in_channel时,是在做的depth-wise conv的
self.conv1 = nn.Conv2d(in_planes, in_planes, kernel_size=3, stride=stride, padding=1, groups=in_planes, bias=False)
# 在卷积神经网络的卷积层之后总会添加BatchNorm2d进行数据的归一化处理,这使得数据在进行Relu之前不会因为数据过大而导致网络性能的不稳定
self.bn1 = nn.BatchNorm2d(in_planes)
# Pointwise 卷积,1*1 的卷积核
self.conv2 = nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=1, padding=0, bias=False)
self.bn2 = nn.BatchNorm2d(out_planes)
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = F.relu(self.bn2(self.conv2(out)))
return out
创建 MobileNetV1 网络:
class MobileNetV1(nn.Module):
# (128,2) means conv planes=128, stride=2
cfg = [(64,1), (128,2), (128,1), (256,2), (256,1), (512,2), (512,1),
(1024,2), (1024,1)]
def __init__(self, num_classes=10):
super(MobileNetV1, self).__init__()
self.conv1 = nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(32)
self.layers = self._make_layers(in_planes=32)
self.linear = nn.Linear(1024, num_classes)
# 用来构建MobileNet V1网络中的4个blocks
def _make_layers(self, in_planes):
layers = []
for x in self.cfg:
out_planes = x[0]
stride = x[1]
# 将每个blocks的第一个residual结构保存在layers列表中
layers.append(Block(in_planes, out_planes, stride))
in_planes = out_planes
return nn.Sequential(*layers)
# forward方法中主要是定义数据在层之间的流动顺序,也就是层的连接顺序
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = self.layers(out)
out = F.avg_pool2d(out, 2)
#将多维的数据平铺成一维
# view()函数的功能根reshape类似,用来转换size大小。
#x = x.view(batchsize, -1)中batchsize指转换后有几行,
# 而-1指在不告诉函数有多少列的情况下,根据原tensor数据和batchsize自动分配列数。
out = out.view(out.size(0), -1)
out = self.linear(out)
return out
训练网络:
模型测试:
使用MobileNet V2对CIFAR10进行分类
创建基本Block单元:
class Block(nn.Module):
'''expand + depthwise + pointwise'''
def __init__(self, in_planes, out_planes, expansion, stride):
super(Block, self).__init__()
self.stride = stride
# 通过 expansion 增大 feature map 的数量
planes = expansion * in_planes
self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, stride=1, padding=0, bias=False)
self.bn1 = nn.BatchNorm2d(planes)
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, groups=planes, bias=False)
self.bn2 = nn.BatchNorm2d(planes)
self.conv3 = nn.Conv2d(planes, out_planes, kernel_size=1, stride=1, padding=0, bias=False)
self.bn3 = nn.BatchNorm2d(out_planes)
# 步长为 1 时,如果 in 和 out 的 feature map 通道不同,用一个卷积改变通道数
if stride == 1 and in_planes != out_planes:
self.shortcut = nn.Sequential(
nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(out_planes))
# 步长为 1 时,如果 in 和 out 的 feature map 通道相同,直接返回输入
if stride == 1 and in_planes == out_planes:
self.shortcut = nn.Sequential()
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = F.relu(self.bn2(self.conv2(out)))
out = self.bn3(self.conv3(out))
# 步长为1,加 shortcut 操作
if self.stride == 1:
return out + self.shortcut(x)
# 步长为2,直接输出
else:
return out
创建MobileNet V2网络:
class MobileNetV2(nn.Module):
# (expansion, out_planes, num_blocks, stride)
cfg = [(1, 16, 1, 1),
(6, 24, 2, 1),
(6, 32, 3, 2),
(6, 64, 4, 2),
(6, 96, 3, 1),
(6, 160, 3, 2),
(6, 320, 1, 1)]
def __init__(self, num_classes=10):
super(MobileNetV2, self).__init__()
self.conv1 = nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(32)
self.layers = self._make_layers(in_planes=32)
self.conv2 = nn.Conv2d(320, 1280, kernel_size=1, stride=1, padding=0, bias=False)
self.bn2 = nn.BatchNorm2d(1280)
self.linear = nn.Linear(1280, num_classes)
def _make_layers(self, in_planes):
layers = []
for expansion, out_planes, num_blocks, stride in self.cfg:
# ??????????????????
strides = [stride] + [1]*(num_blocks-1)
for stride in strides:
layers.append(Block(in_planes, out_planes, expansion, stride))
in_planes = out_planes
return nn.Sequential(*layers)
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = self.layers(out)
out = F.relu(self.bn2(self.conv2(out)))
out = F.avg_pool2d(out, 4)
out = out.view(out.size(0), -1)
out = self.linear(out)
return out
训练网络:
模型测试:
HybridSN实现高光谱分类
- 三维卷积部分:
- conv1:(1, 30, 25, 25), 8个 7x3x3 的卷积核 ==>(8, 24, 23, 23)
- conv2:(8, 24, 23, 23), 16个 5x3x3 的卷积核 ==>(16, 20, 21, 21)
- conv3:(16, 20, 21, 21),32个 3x3x3 的卷积核 ==>(32, 18, 19, 19)
- 把前面的 32*18 reshape 一下,得到 (576, 19, 19)
- 二维卷积:(576, 19, 19) 64个 3x3 的卷积核,得到 (64, 17, 17)
- flatten 操作,变为 18496 维的向量
- 接下来依次为256,128节点的全连接层,都使用比例为0.4的 Dropout
- 最后输出为 16 个节点,是最终的分类类别数
下面是 HybridSN 类的代码:
class_num = 16
class HybridSN(nn.Module):
def __init__(self):
super(HybridSN, self).__init__()
self.conv1_3d = nn.Conv3d(1, 8, kernel_size=(7, 3, 3), stride=1, padding=0)
self.conv2_3d = nn.Conv3d(8, 16, kernel_size=(5, 3, 3), stride=1, padding=0)
self.conv3_3d = nn.Conv3d(16, 32, kernel_size=(3, 3, 3), stride=1, padding=0)
self.conv4_2d = nn.Conv2d(576, 64, kernel_size=3, stride=1, padding=0)
self.fc1 = nn.Linear(18496,256)
self.fc2 = nn.Linear(256,128)
self.fc3 = nn.Linear(128,16)
self.dropout = nn.Dropout(p = 0.4)
def forward(self, x):
out = F.relu(self.conv1_3d(x))
out = F.relu(self.conv2_3d(out))
out = F.relu(self.conv3_3d(out))
out = out.reshape(out.shape[0], out.shape[1]*out.shape[2], out.shape[3], out.shape[4])
out = F.relu(self.conv4_2d(out))
#out = out.flatten()
out = out.reshape(-1, 18496)
out = F.relu(self.fc1(out))
out = self.dropout(out)
out = F.relu(self.fc2(out))
out = self.dropout(out)
out = self.fc3(out)
return out
# 随机输入,测试网络结构是否通
x = torch.randn(1, 1, 30, 25, 25)
net = HybridSN()
y = net(x)
print(y.shape)
测试结果:
可视化:
对比作者的结果:
只是简单搭建了网络,好像分类效果不是特别好…
【第三部分】论文阅读心得
MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications
论文链接:MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications
深度可分离卷积
MobileNet模型基于深度可分离卷积建立,这是一种分解卷积的形式,把标准卷积分解成了一种深度卷积(depthwise convolution)和一个1*1的卷积,1*1卷积又称为逐点卷积(pointwise convolution)。
对于MobileNets而言,深度卷积在每个通道(channel)上应用一个卷积核。逐点卷积接着应用1*1卷积把输出和深度卷积结合起来。
标准卷积在一个步骤把卷积核和输入结合为新的输出。深度可分离卷积将其分为两层,一层用于滤波(filtering),一层用于结合。这个分解过程极大地减少了计算量和模型的大小。
下面看具体的卷积过程,首先是标准卷积:
假设输入是一个$$D_F$$ x \(D_F\) x M的feature map,经过standard convolution之后,生成一个$$D_G \times D_G \times N$$的feature map。
卷积核K的大小为$$D_K \times D_K \times M \times N$$ ,如上图(a)所示。
则standard convolution的cost为:\(D_K \cdot D_K \cdot M \cdot N \cdot D_F \cdot D_F\)
再来看深度可分离卷积:
深度可分离卷积由两层组成:深度卷积和逐点卷积。作者使用深度卷积把一个卷积核应用到输入通道上。逐点卷积接着被用来创建depthwise layer的输出的线性叠加。MobileNets对两个层都使用batchnorm和ReLU。
卷积核K的大小为$$D_K \times D_K \times M$$,如图(b)所示。
Depthwise convolution的cost为:\(D_K \cdot D_K \cdot M \cdot D_F \cdot D_F\)
深度卷积与标准卷积相比极其高效。然而它只过滤输入通道,不把他们结合成新的特征。
因此还需要一个计算深度卷积输出的线性结合的layer,需要1*1卷积生成新特征,卷积核如上图(c)所示。
经过这两层卷积,最终,Depthwise separable convolutions的cost为:\(D_K \cdot D_K \cdot M \cdot D_F \cdot D_F + M \cdot N \cdot D_F \cdot D_F\)
Depthwise separable convolution一个更直观的理解
常规卷积
假设有shape为5×5×3的图片,对于常规的卷积,经过shape为3×3×3×4的卷积核,最终输出4个feature map,如果有same padding则尺寸与输入层相同(5×5),如果没有则为尺寸变为3×3。
此时,卷积层共4个Filter,每个Filter包含了3个Kernel,每个Kernel的大小为3×3。因此卷积层的参数数量可以用如下公式来计算:
N_std = 4 × 3 × 3 × 3 = 108
深度可分离卷积
对于深度可分离卷积来说,先进行depthwise convolution,再进行pointwise convolution。
Depthwise Convolution
Depthwise Convolution的一个卷积核负责一个通道,一个通道只被一个卷积核卷积。上面所提到的常规卷积每个卷积核是同时操作输入图片的每个通道。
同样是对于一张5×5像素、三通道彩色输入图片(shape为5×5×3),Depthwise Convolution首先经过第一次卷积运算,不同于上面的常规卷积,DW完全是在二维平面内进行。卷积核的数量与上一层的通道数相同(通道和卷积核一一对应)。所以一个三通道的图像经过运算后生成了3个Feature map(如果有same padding则尺寸与输入层相同为5×5),如下图所示。
其中一个Filter只包含一个大小为3×3的Kernel,卷积部分的参数个数计算如下:
N_depthwise = 3 × 3 × 3 = 27
Depthwise Convolution完成后的Feature map数量与输入层的通道数相同,无法扩展Feature map。而且这种运算对输入层的每个通道独立进行卷积运算,没有有效的利用不同通道在相同空间位置上的feature信息。因此需要Pointwise Convolution来将这些Feature map进行组合生成新的Feature map。
Pointwise Convolution
Pointwise Convolution的运算与常规卷积运算非常相似,它的卷积核的尺寸为 1×1×M,M为上一层的通道数。所以这里的卷积运算会将上一步的map在深度方向上进行加权组合,生成新的Feature map。有几个卷积核就有几个输出Feature map。如下图所示。
由于采用的是1×1卷积的方式,此步中卷积涉及到的参数个数可以计算为:
N_pointwise = 1 × 1 × 3 × 4 = 12
参数对比
回顾一下,常规卷积的参数个数为: N_std = 4 × 3 × 3 × 3 = 108
Separable Convolution的参数由两部分相加得到: N_depthwise = 3 × 3 × 3 = 27 N_pointwise = 1 × 1 × 3 × 4 = 12 N_separable = N_depthwise + N_pointwise = 39
相同的输入,同样是得到4张Feature map,Separable Convolution的参数个数是常规卷积的约1/3。因此,在参数量相同的前提下,采用Separable Convolution的神经网络层数可以做的更深。
网络结构
MobileNet的网络结构如上图所示,除了第一层的全卷积层和最后的全连接层,其余均为深度可分离卷积层,共有1+13*2+1=28层。除了最后的全连接层直接输入到softmax层进行分类之外,其余所有层后面都使用batchnorm和ReLU的非线性激活函数,最后的平均池化层在全连接层前将空间维度减少为1。
Width Multiplier(通道乘子):Thinner Models
作者引入了一个简单的参数:width multiplier α。它的作用是在每层均匀地减负网络。对于一个给定的层和width multiplier α,输入通道M变成了αM,输出通道变成了αN。
α∈(0,1],常用的数值为1、0.75、0.5、0.25。α=1是标准MobileNet,α<1是reduced MobileNets。
一个depthwise separable convo- lution with width multiplier α的cost是:\(D_K \cdot D_K \cdot \alpha M \cdot D_F \cdot D_F + \alpha M \cdot \alpha N \cdot D_F \cdot D_F\)
Resolution Multiplier(分辨率乘子):Reduced Representation
作者引入了第二个超参数resolution multiplier ρ。作者把这个参数应用在输入图片,每个层的内部特征随后被减去相同的乘数。
ρ ∈(0,1],典型隐式设置以使网络输入分辨率是224,192,160,128。ρ=1是基准MobileNet,ρ<1是computation MobileNets。
一个depthwise separable convo- lution with α & ρ 的cost是:\(D_K \cdot D_K \cdot \alpha M \cdot \rho D_F \cdot \rho D_F + \alpha M \cdot \alpha N \cdot \rho D_F \cdot \rho D_F\)
总结
作者基于深度可分离卷积提出了一个叫做MobileNets的新的模型架构,探索了一些重要的设计高效模型的决定思路,然后演示了使用width multiplier和resolution multiplier怎么建立更小和更快的MobileNets,来权衡一个合理的准确度减小大小和延迟度。接着作者比较了不同的MobileNets和流行的模型,演示了其大小、速度和准确度的特性。最后,在应用到一系列任务上应用MobileNets验证了其高效性。
MobileNetV2:Inverted Residuals and Linear BottleNecks
与前面所讲的MobileNet V1相比,一方面,沿用了再MobileNet V1中采用的depthwise separable convolution。另一方面,新提出了inverted residual结构和linear bottleneck结构来改进模型。
Linear Bottlenecks
首先看一个概念**“manifold of interest“**,它是指真实的样本输入集,经过一系列的激活层,得到的结果,它的大小与输入相同,假设为$$h_i \times w_i \times d_i$$
一般认为,manifold of interest可以压缩到低维子空间,在MobileNet V1中,利用了Width Multiplier parameter来降低激活空间的维度数,从而使得manifold of interest充满整个空间。
但此时有一个问题,在使用ReLU函数进行激活的时候,负数直接变为0,这样就失去了很多有用的信息从而使得网络的性能降低。但这个问题在manifold of interest占激活空间较小时不存在,即如果我们有许多通道,并且有一个结构在激活通道分流的时候信息任然保留在其他通道中。
总结一下,有以下四点:
- 如果manifold of interest在ReLU变换后任然保持非零值,那么对应线性变换。
- ReLU可以在输入manifold分布在输入空间的低维子空间时保持完整的信息。
- Linear Bottlenecks就是,去掉ReLU,减小对特征的破坏,尽可能保留输入的信息,本质是一个去掉ReLU的1×1的卷积层。
- Linear Bottlenecks后面会与Inverted residuals组合起来使用。
Inverted residuals
从传统卷积到MobileNet V2:
(a)是传统的3*3卷积操作,假设channel为n,则(a)中卷积核(红色立方体)的维度就是3*3*n。
(b)是MobileNet V1中采用的depthwise separable convolution,添加了一个1*1的卷积核。
(c)在(b)的基础上添加了一个类似bottleneck的操作。
(d)主要描述了MobileNet V2的invert residual block结构。
普通的residual block与inverted residual block之间的对比:
在residual block中是先降维再升维,在inverted residual block中是先升维再降维。
(a)是传统的residual block,它的结构是:1*1卷积(降维)+ReLU → 3*3卷积+ReLU → 1*1卷积(升维)+ReLU
- (b)是invert residual block,它的结构是:1*1卷积(升维)+ReLU → 3*3 depthwise separable 卷积+ReLU → 1*1卷积(降维),最后一层是不经过ReLU激活函数的,而是用到了前面提到了linear bottleneck。
网络结构
MobileNet V2的基本组成结构
- 第一行的升维叫 Expansion Layer
- 第二行的卷积叫 Depthwise Layer
- 第三行的降维叫 Projection Layer
MobileNet V2的总体结构:
表中的参数:
n:Table 2 的每一行都描述了Table 1中展示的一个基本单元,或者一些更基本典型的结构,重复n次。
c:每一层的输出通道数。
S:每个单元中,第一层的步长为s,其余均为1。
t:所有空间卷积都使用3*3的卷积核,t是一个扩展因子,用于调节输入的大小。
- stride为2时,因为input与output的大小不同,所以没有添加shortcut结构
HybridSN- Exploring 3-D–2-DCNN Feature Hierarchy for Hyperspectral Image Classification
网络结构:
首先对高光谱数据实施PCA降维,经过neighborhood extraction,在$$ (\alpha, \beta)\(处得到一个\) S \times S \times B$$ 的临近3D patches,包含大小为$$ S \times S$$ 的窗口,和所有的光谱$$B$$
HybridSN包含3个3D卷积层,1个2D卷积层,和三个全连接层。
3D卷积内核的shape为8×3×3×7×1,16×3×3×5×8,和32×3×3×3×16。例如,16×3×3×5×8意为共有16个3×3×5的卷积核(两个空间和一个光谱尺寸),输入的feature map的个数为8。
2D卷积核的shape是64×3×3×576,即64个3×3的卷积核,输入的feature map的个数为576。
接下来是一个 flatten 操作,变为 18496 维的向量,
接下来依次为256,128节点的全连接层,都使用比例为0.4的 Dropout,
最后输出为 16 个节点,是最终的分类类别数。
来源:oschina
链接:https://my.oschina.net/u/4332858/blog/4480664