MobileNets学习
MobileNetV1
网络亮点
MobileNet是由谷歌提出的用于移动端设备的网络架构,其在大幅减少参数个数的情况下可以做到尽量不影响准确度
网络结构
MobileNetV1中最为重要的部分是Depthwise Separable Convolution,其可以做到极大较少参数量
对于普通卷积而言,如下图所示,如果在步长为1,输出维度和输入相同时,对于每一个卷积核而言都有
K
i
∈
N
D
K
∗
D
K
∗
M
K_i\in N^{D_K*D_K*M}
Ki∈NDK∗DK∗M,其中
i
∈
(
0
,
N
)
i\in(0,N)
i∈(0,N),在步长为1的情况下,卷积核每次移动一格,这样得到最终的计算代价为
D
K
∗
D
K
∗
M
∗
N
∗
D
F
∗
D
F
D_K*D_K*M*N*D_F*D_F
DK∗DK∗M∗N∗DF∗DF
接下来将传统卷积变换的一步变成两步,分别为Depthwise和PointWise,在Depthwise的情况下,对于每一个卷积核而言
K
i
∈
D
K
∗
D
K
∗
1
K_i\in D_K*D_K*1
Ki∈DK∗DK∗1,其中
i
∈
(
0
,
M
)
i\in(0,M)
i∈(0,M),之后直接拼接,于是这个步骤中计算代价为
D
K
∗
D
K
∗
1
∗
M
∗
D
F
∗
D
F
D_K*D_K*1*M*D_F*D_F
DK∗DK∗1∗M∗DF∗DF,而后面的pointwise中,其计算代价为
1
∗
1
∗
M
∗
N
∗
D
F
∗
D
F
1*1*M*N*D_F*D_F
1∗1∗M∗N∗DF∗DF,故总计算带代价为
D
K
∗
D
K
∗
M
∗
D
F
∗
D
F
+
M
∗
N
∗
D
F
∗
D
F
D_K*D_K*M*D_F*D_F+M*N*D_F*D_F
DK∗DK∗M∗DF∗DF+M∗N∗DF∗DF,与传统卷积相比,比率为
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
=
1
N
+
1
D
K
2
\frac{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}=\frac{1}{N}+\frac{1}{D_{K}^{2}}
DK∗DK∗M∗N∗DF∗DFDK∗DK∗M∗DF∗DF+M∗N∗DF∗DF=N1+DK21
最后总的卷积替换可以用原论文中的图片表示
在具体使用中,网络结构对比如下
与上文一样,将传统
3
∗
3
3*3
3∗3卷积分为两个部分,其中BN层和ReLU层并不改变channel。
MobileNetV2
网络结构
MobileNetV2借用resnet结构对MobileNetV1进行改造,其中最为重要的部分为Linear Bottlenecks。对于输入图片,我们也许只需要较少的channel便可以存储其中有作用的部分(manifolds of interest),但在这种情况下使用ReLU函数便不可避免地丢失一些信息,但如果我们一开始就有很多channel,那么这其中的一些信息仍然可以在保存下来的channel中存储下去。正如原论文所展示的两点结论:
- If the manifold of interest remains non-zero volume after ReLU transformation, it corresponds to a linear transformation
- ReLU is capable of preserving complete information about the input manifold, but only if the input manifold lies in a low-dimensional subspace of the input space.
于是可以设计出一种结构,在原MobileNetV1的基础上,首先对原输入进行pointwise卷积进行升维,之后进行depthwise卷积,最后再进行pointwise降维至想要的维度,这里如果在输入和输出维度、尺度相同的情况下可以使用resnet中的residual结构,将输入和输出相加。具体结构如下
MobileNetV3
网络结构
在block中,最重要的部分是对MobileNetV2中的depthwise卷积和pointwise降维卷积之间加入SENet注意力模块,关于SENet注意力模块具体见下面的SENets。
其他部分便是对一些结构的精减,具体设计如下:
除此以外MobileNetV3还重新设计了激活函数,如下图
SENets学习
SENet结构
在具体操作中,主要有两个部分–squeeze和excitation
squeeze部分主要是对图片每一个channel都采用pooling计算,得到一个一维tensor,例如maxpooling具体操作如下
在sequeeze之后得到一维tensor,之后便是对tensor进行excitation操作,具体操作如下:
excitation操作便是对一维tensor进行全连接,以此学习参数得到不同channel的一个比例,该数值可以看成对channel的一个重要性排序,再将比例和原图片每个channel的pixel相乘。
SENet代码
class BasicBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride=1):
super(BasicBlock, self).__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(out_channels)
self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(out_channels)
# shortcut的输出维度和输出不一致时,用1*1的卷积来匹配维度
self.shortcut = nn.Sequential()
if stride != 1 or in_channels != out_channels:
self.shortcut = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(out_channels))
# 在 excitation 的两个全连接
self.fc1 = nn.Conv2d(out_channels, out_channels//16, kernel_size=1)
self.fc2 = nn.Conv2d(out_channels//16, out_channels, kernel_size=1)
#定义网络结构
def forward(self, x):
#feature map进行两次卷积得到压缩
out = F.relu(self.bn1(self.conv1(x)))
out = self.bn2(self.conv2(out))
# Squeeze 操作:global average pooling
w = F.avg_pool2d(out, out.size(2))
# Excitation 操作: fc(压缩到16分之一)--Relu--fc(激到之前维度)--Sigmoid(保证输出为 0 至 1 之间)
w = F.relu(self.fc1(w))
w = F.sigmoid(self.fc2(w))
# 重标定操作: 将卷积后的feature map与 w 相乘
out = out * w
# 加上浅层特征图
out += self.shortcut(x)
#R elu激活
out = F.relu(out)
return out
class SENet(nn.Module):
def __init__(self):
super(SENet, self).__init__()
#最终分类的种类数
self.num_classes = 10
#输入深度为64
self.in_channels = 64
#先使用64*3*3的卷积核
self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(64)
#卷积层的设置,BasicBlock
#2,2,2,2为每个卷积层需要的block块数
self.layer1 = self._make_layer(BasicBlock, 64, 2, stride=1)
self.layer2 = self._make_layer(BasicBlock, 128, 2, stride=2)
self.layer3 = self._make_layer(BasicBlock, 256, 2, stride=2)
self.layer4 = self._make_layer(BasicBlock, 512, 2, stride=2)
#全连接
self.linear = nn.Linear(512, self.num_classes)
#实现每一层卷积
#blocks为大layer中的残差块数
#定义每一个layer有几个残差块,resnet18是2,2,2,2
def _make_layer(self, block, out_channels, blocks, stride):
strides = [stride] + [1]*(blocks-1)
layers = []
for stride in strides:
layers.append(block(self.in_channels, out_channels, stride))
self.in_channels = out_channels
return nn.Sequential(*layers)
#定义网络结构
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = self.layer1(out)
out = self.layer2(out)
out = self.layer3(out)
out = self.layer4(out)
out = F.avg_pool2d(out, 4)
out = out.view(out.size(0), -1)
out = self.linear(out)
return out
HybridSN代码学习
论文学习
这里同时使用了3D卷积和2D卷积,正如原论文所说,高光谱图片是一类立体数据,其同时拥有光谱维度,如果只有2D卷积则不能从光谱维度去提取好的区分特征,而深的3D卷积计算量过大,并且只有3D卷积似乎在分类共谱带相似纹理的类别时效果并不理想。故这里同时使用3D卷积和2D卷积,如下图所示。
网络结构补充
class_num=16
class HybridSN(nn.Module):
def __init__(self):
super(HybridSN,self).__init__()
self.conv1=nn.Conv3d(1,8,kernel_size=(7,3,3))
self.bn1=nn.BatchNorm3d(8)
self.conv2=nn.Conv3d(8,16,kernel_size=(5,3,3))
self.bn2=nn.BatchNorm3d(16)
self.conv3=nn.Conv3d(16,32,kernel_size=(3,3,3))
self.bn3=nn.BatchNorm3d(32)
self.conv4=nn.Conv2d(576,64,kernel_size=(3,3))
self.bn4=nn.BatchNorm2d(64)
self.fc1=nn.Linear(18496,256)
self.fc2=nn.Linear(256,128)
self.fc3=nn.Linear(128,class_num)
self.dropout=nn.Dropout(p=0.4)
def forward(self,x):
x=F.relu(self.bn1(self.conv1(x)))
x=F.relu(self.bn2(self.conv2(x)))
x=F.relu(self.bn3(self.conv3(x)))
x=x.reshape(x.shape[0],-1,19,19)
x=F.relu(self.bn4(self.conv4(x)))
x=x.reshape(x.shape[0],-1)
x=F.relu(self.dropout(self.fc1(x)))
x=F.relu(self.dropout(self.fc2(x)))
x=self.fc3(x)
return x
网络结果
第二次测试
可以看到两次测试结果并不完全相同
问题回答
- 每次分类结果不同原因?
在网络中使用了dropout方法,所以在网络训练中每次会丢弃一写结点,这会导致每次结果不同 - 改进方法
在网络中可以加入各种网络模块,例如可以尝试加入group convolution甚至depthwise convolution等,也可以加入本次学习中的SENet模块,对网络模块进行改变