这里写目录标题
Part1 论文阅读与视频学习:
传统卷积神经网络,内存需求大、运算量大 导致无法在移动设备以及嵌入式设备上运行,提出了Mobilenet网络。
1 Mobilenet系列
1.1 Mobilenet V1
网络中的亮点:
- 提出了深度可分离卷积(Depthwise Convolution),它将标准卷积分解成深度卷积以及一个1x1的卷积即逐点卷积,大幅度减少了运算量和参数量。
- 增加超参数α、β,这两个超参数是人为设定的,并非学习得到的。
下面看一下普通卷积和深度可分卷积的对比:
传统(普通)卷积:- 卷积核 c h a n n e l = 输入特征矩阵 c h a n n e l 卷积核channel = 输入特征矩阵channel 卷积核channel=输入特征矩阵channel
-
输出特征矩阵
c
h
a
n
n
e
l
=
卷积核个数
输出特征矩阵channel = 卷积核个数
输出特征矩阵channel=卷积核个数
DW卷积: - 卷积核 c h a n n e l = 1 卷积核channel=1 卷积核channel=1
-
输入特征矩阵
c
h
a
n
n
e
l
=
卷积核个数
=
输出特征矩阵
c
h
a
n
n
e
l
输入特征矩阵channel=卷积核个数=输出特征矩阵channel
输入特征矩阵channel=卷积核个数=输出特征矩阵channel
一般是 DW+PW 一起使用,理论上 普通卷积的 计算量是 DW+PW 的8到9倍 ~
注意:PW 的 filter 大小为 1 × 1 × C i n 1\times 1\times C_{in} 1×1×Cin
DW + PW
depthwise部分的卷积核容易费掉,即卷积核参数大部分为零。
1.2 Mobilenet V2
MobileNet v2网络是由google团队在2018年提出的,相比MobileNet V1网 络,准确率更高,模型更小。
网络中的亮点:
- Inverted Residuals(倒残差结构)
- Linear Bottlenecks
Residual block使用ReLu激活函数,Inverted Residuals block使用ReLu6激活函数。
y = R e L U 6 ( x ) = m i n ( m a x ( x , 0 ) , 6 ) y = ReLU6(x) = min(max(x, 0), 6) y=ReLU6(x)=min(max(x,0),6)
注意:
当 stride=1 且 输入特征矩阵shape = 输出特征矩阵shape时 才有shortcut连接。
Mobilenet V2
并且在倒残差结构最后一个1×1的卷积层,使用了线性的激活函数,因为ReLu6激活函数对于低维特征信息造成大量损失,而对高维特征信息损失很小。
1.3 Mobilenet V3
Mobilenet V3
网络中的亮点:
- 更新了 block(bneck)
- 加入 SE 模块 (注意力机制):一个池化两个全连接,详情请见下文SELet部分~
- 更新了激活函数,使用 h − s w i s h [ x ] = x ⋅ R e L U 6 ( x + 3 ) 6 h-swish[x]=x ·\frac{ReLU6(x+3)}{6} h−swish[x]=x⋅6ReLU6(x+3)
- 使用 NAS 搜索参数
- 重新设计耗时层结构
- 减少第一个卷积层的卷积核个数(32->16)
- 精简Last Stage
2 SENet
2.1 什么是注意力机制?
总的来说,注意力机制能够灵活的捕捉全局信息和局部信息之间的联系。它的目的就是让模型获得需要重点关注的目标区域,并对该部分投入更大的权重,突出显著有用特征,抑制和忽略无关特征。
2.2 综述
是属于 通道域 (改变的是channel) 的注意力机制~
一个目的: 得到一个权重矩阵(核心),对特征进行重构
两个重要操作: Squeeze 和 Excitation
四步走: Transformation、Squeeze 、Excitation、Scale
(它是一个可以用来衡量通道重要性的数值,上图中用不同颜色展示)
2.3 过程
2.3.1 第一步 Transformation映射
![](https://i-blog.csdnimg.cn/blog_migrate/33e53c76b1a13b496a99284f0ea5e5c8.png)
2.3.2 第二步 Squeeze 压缩
全局平均池化:维度由
H
×
W
×
C
H\times W\times C
H×W×C ——>
1
×
1
×
C
1\times 1\times C
1×1×C
2.3.3 第三步 Excitation
![](https://i-blog.csdnimg.cn/blog_migrate/129ddaa8f31039ded91b4e1d1471ee83.png)
w1:第一个全连接层
w2:第二个全连接层
为了节省参数,使用r去衰减一下神经元的个数,经实验发现发现 r=16 特别好
2.3.4 第四步 Scale
X
~
\widetilde{X}
X
就是最终得到的哪儿重要哪儿不重要的特征图了~
2.4 应用
本模块的基本原则是不改变原来CNN结构,做到即插即用
2.5 代码
完整代码见笔记本: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 %
Part2 代码作业
阅读论文《HybridSN: Exploring 3-D–2-DCNN Feature Hierarchy for Hyperspectral Image Classification》,通过HybridSN混合网络实现高光谱图像分类,平台使用Google Colab平台。
3 高光谱图像分类 HybridSN混合网络
3.1 相关知识
3.1.1 高光谱图像
- 即包含光谱信息特别长的图像(包含红外线、紫外线等不可见光光谱),比如:普通照片只有三个通道,即RGB —— 红、绿、蓝。数据类型是一个 m ∗ n ∗ 3 m ∗ n ∗ 3 m∗n∗3的矩阵,而高光谱图像则是 M ∗ N ∗ B M ∗ N ∗ B M∗N∗B (B是光谱的层数:3层RGB + 其他光谱层…)。
- 光谱图像是一个立体的三维结构,x、y表示二维平面像素信息坐标轴,第三维是波长信息坐标轴。
3.1.2 高光谱图像成像原理
空间中的一维信息通过镜头和狭缝后,不同波长的光按照不同程度的弯散传播,一维图像上的每个点,再通过光栅进行衍射分光,形成一个谱带,照射到探测器上,探测器上的每个像素位置和强度表征光谱和强度。
3.2 卷积网络处理高光谱图像时的技术问题
- 2-D-CNN无法处理数据的第三维度——光谱信息(前两维度是图像本身的x轴和y轴)。
- 传统的2D-卷积处理不好这种三维的高光谱图像;
- 若只使用3D-卷积,虽然可以提取第三维——光谱维度的特征,能同时进行空间和空间特征表示,但数据计算量特别大,且对特征的表现能力比较差(因为许多光谱带上的纹理是相似的)
所以,作者做了以下工作:
- 提出HybirdSN模型(全称是Hybrid SpectralNet——混合了2D、3D卷积的光谱网络):
将空间光谱和光谱的互补信息分别以3D-CNN和2D-CNN层组合到了一起,从而充分利用了光谱和空间特征图,来克服以上缺点。 - HybirdSN模型比3D-CNN模型的计算效率更高。在小的训练数据上也显示出了优越的性能。
3.3 实现步骤
3.3.1 PCA主成分分析
- 首先,对于输入的高光谱图像,进行了主成分分析(PCA),减少了第三维数据的一些光谱波段,只保留了对识别物体重要的空间信息;
- 将数据的输入规范化为 M ∗ N ∗ B M ∗ N ∗ B M∗N∗B;
3.3.2 将数据划分为三维小块
- 随后将数据划分为重叠的三维小块
S
∗
S
∗
B
S ∗ S ∗ B
S∗S∗B(“厚度”不变),小块的label由中心像素的label决定.
3.3.3 三维卷积提取光谱维度特征
- 之后,使用3D-卷积获取光谱维度和图像间的特征;
- 三次三维卷积中,卷积核的尺寸依次为:
- 8×3×3×7×1(8是他自己设计的卷积核的个数,3 ∗ 3 ∗ 7 是一个三维卷积核的大小,1是因为一共只有1组图,所以再乘个1)
- 16×3×3×5×8
- 32×3×3×3×16。
- 在三维卷积中,生成第 i 层第 j 个 feature map 在空间位置(x, y, z)的激活值,记为 v i , j x , y , z v_{i , j}^{x,y,z} vi,jx,y,z,公式2如下图所示:
3.3.4 二维卷积卷图像特征
- 使用2D-卷积获取图像本身的特征;
- 二维卷积中,卷积核的尺寸为64×3×3×576(576为二维输入特征图的数量)
- 在二维卷积中,第 i 层第 j 个feature map在空间位置 (x, y) 处的值,记为
v
i
,
j
x
,
y
,
z
v_{i , j}^{x,y,z}
vi,jx,y,z,其生成公式1如下:
3.3.5 全连接输出
- 接下来是一个 flatten 操作(为了输出给全连接层,所以必须是一条二维数据),变为 18496 维的向量;
- 接下来依次为256,128节点的全连接层,都使用比例为0.4的 Dropout(就是扔掉40%的数据,防止模型过拟合);
- 最后输出为 16 个节点,是分类的种数
4 阅读代码
三维卷积部分:
- 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,感觉分类的效果很一般,图像效果不太好
5 思考题
5.1 思考3D卷积和2D卷积的区别
3D卷积使用的数据和2D卷积最大的不同就在于数据的时序性。3D卷积和2D卷积相比,多了一个深度通道,这个深度通道可以获取更多的信息,比如视频的连续帧和立体图像的分割等。3D卷积中的数据通常是视频的多个帧或者是一张医学图像的多个分割图像堆叠在一起,这样每帧图像之间就有时间或者空间上的联系。
5.2 训练网络,然后多测试几次,会发现每次分类的结果都不一样,这是为什么?
在训练模式中,采用了dropout,使得网络在训练的时候,抗噪声能力更强,防止过拟合。但是在测试模型的时候,随机的drop,就会导致最终结果的不一致。
5.3 如何进一步提升高光谱图像的分类性能
1、在网络的最后加一个LogSoftMax函数(这个方法是从其他博主的博文里看到的),尝试后发现准确率的确有明显的提高~
2、可以尝试加入注意力机制模块。比如加入上面的SENet模块,让模型获得需要重点关注的目标区域,并对该部分投入更大的权重,突出显著有用特征,从而使网络更加有侧重的学习,以此提高网络的学习能力。