传统卷积神经网络,内存需求大、运算量大 导致无法在移动设备以及嵌入式设备上运行,提出了Mobilenet网络。
网络中的亮点:
DW + PW
depthwise部分的卷积核容易费掉,即卷积核参数大部分为零。
MobileNet v2网络是由google团队在2018年提出的,相比MobileNet V1网 络,准确率更高,模型更小。
网络中的亮点:
Mobilenet V2
并且在倒残差结构最后一个1×1的卷积层,使用了线性的激活函数,因为ReLu6激活函数对于低维特征信息造成大量损失,而对高维特征信息损失很小。
Mobilenet V3
网络中的亮点:
总的来说,注意力机制能够灵活的捕捉全局信息和局部信息之间的联系。它的目的就是让模型获得需要重点关注的目标区域,并对该部分投入更大的权重,突出显著有用特征,抑制和忽略无关特征。
是属于 通道域 (改变的是channel) 的注意力机制~
一个目的: 得到一个权重矩阵(核心),对特征进行重构
两个重要操作: Squeeze 和 Excitation
四步走: Transformation、Squeeze 、Excitation、Scale
(它是一个可以用来衡量通道重要性的数值,上图中用不同颜色展示)
全局平均池化:维度由
H
×
W
×
C
H\times W\times C
H×W×C ——>
1
×
1
×
C
1\times 1\times C
1×1×C
w1:第一个全连接层
w2:第二个全连接层
为了节省参数,使用r去衰减一下神经元的个数,经实验发现发现 r=16 特别好
X
~
\widetilde{X}
X
就是最终得到的哪儿重要哪儿不重要的特征图了~
本模块的基本原则是不改变原来CNN结构,做到即插即用
完整代码见笔记本:colab代码-ResNet18-SENet
# 定义BasicBlock
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)
#定义网络结构---网络结构参考了ResNet18
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的卷积核(1)in_channel:图片RGB三通道(2)out_channel:filter组数(3)filter大小kernel_size:3x3
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)
#全连接---最终分类数为10
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
Accuracy of the network on the 10000 test images: 85.26 %
阅读论文《HybridSN: Exploring 3-D–2-DCNN Feature Hierarchy for Hyperspectral Image Classification》,通过HybridSN混合网络实现高光谱图像分类,平台使用Google Colab平台。
空间中的一维信息通过镜头和狭缝后,不同波长的光按照不同程度的弯散传播,一维图像上的每个点,再通过光栅进行衍射分光,形成一个谱带,照射到探测器上,探测器上的每个像素位置和强度表征光谱和强度。
所以,作者做了以下工作:
三维卷积部分:
- 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 个节点,是最终的分类类别数。
代码链接 并补全网络,网络结构如下:
class_num = 16
class HybridSN(nn.Module):
def __init__(self, class_num=16):
super(HybridSN, self).__init__()
# conv1:(1, 30, 25, 25), 8个 7x3x3 的卷积核 ==>(8, 24, 23, 23)
self.conv1 = nn.Conv3d(in_channels=1, out_channels=8, kernel_size=(7, 3, 3))
# conv2:(8, 24, 23, 23), 16个 5x3x3 的卷积核 ==>(16, 20, 21, 21)
self.conv2 = nn.Conv3d(8, 16, (5, 3, 3))
# conv3:(16, 20, 21, 21),32个 3x3x3 的卷积核 ==>(32, 18, 19, 19)
self.conv3 = nn.Conv3d(16, 32, (3, 3, 3))
# conv3_2d (576, 19, 19),64个 3x3 的卷积核 ==>(64, 17, 17)
self.conv3_2d = nn.Conv2d(576, 64, 3)
# 全连接层(256个节点)
self.fc1 = nn.Linear(18496, 256)
# 全连接层(128个节点)
self.fc2 = nn.Linear(256, 128)
# fc1,fc2使用比例为0.4的Dropout
self.dropout = nn.Dropout(0.4)
self.soft = nn.Softmax(dim=1)
# 最终输出层(16个节点)
self.fc3 = nn.Linear(128, class_num)
# 加入BN归一化数据
self.bn1 = nn.BatchNorm3d(8)
self.bn2 = nn.BatchNorm3d(16)
self.bn3 = nn.BatchNorm3d(32)
self.bn4 = nn.BatchNorm2d(64)
# 定义激活函数
self.relu = nn.ReLU()
def forward(self, x):
# 第一次,没加bn
out = self.relu(self.conv1(x))
out = self.relu(self.conv2(out))
out = self.relu(self.conv3(out))
# 接下来要进行二维卷积,因此把前面的 32*18 reshape 一下,得到 (576, 19, 19)
out = out.reshape(out.shape[0], -1, 19, 19)
out = self.relu(self.conv3_2d(out))
# flatten 操作,变为 18496 维的向量
out = out.view(out.size(0), -1)
out = self.dropout(self.fc1(out))
out = self.dropout(self.fc2(out))
out = self.relu(self.dropout(self.fc2(out)))
out = self.relu(self.fc3(out))
return out
# 随机输入,测试网络结构是否通
# x = torch.randn(1, 1, 30, 25, 25)
# net = HybridSN()
# y = net(x)
# print(y.shape)
第一次训练,准确率很低23%,没什么分类效果,可能哪里写错了
去掉softMax函数,准确率是93%
第二次训练,把softMax函数改成LogSoftmax函数,准确率达到96%
最后,加上BN,感觉分类的效果很一般,图像效果不太好
3D卷积使用的数据和2D卷积最大的不同就在于数据的时序性。3D卷积和2D卷积相比,多了一个深度通道,这个深度通道可以获取更多的信息,比如视频的连续帧和立体图像的分割等。3D卷积中的数据通常是视频的多个帧或者是一张医学图像的多个分割图像堆叠在一起,这样每帧图像之间就有时间或者空间上的联系。
在训练模式中,采用了dropout,使得网络在训练的时候,抗噪声能力更强,防止过拟合。但是在测试模型的时候,随机的drop,就会导致最终结果的不一致。
1、在网络的最后加一个LogSoftMax函数(这个方法是从其他博主的博文里看到的),尝试后发现准确率的确有明显的提高~
2、可以尝试加入注意力机制模块。比如加入上面的SENet模块,让模型获得需要重点关注的目标区域,并对该部分投入更大的权重,突出显著有用特征,从而使网络更加有侧重的学习,以此提高网络的学习能力。