第二次作业:卷积神经网络 part 2

天涯浪子 提交于 2020-08-10 08:38:00

【第一部分】问题总结

创建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实现高光谱分类

  1. 三维卷积部分:
  • 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)
  1. 把前面的 32*18 reshape 一下,得到 (576, 19, 19)
  2. 二维卷积:(576, 19, 19) 64个 3x3 的卷积核,得到 (64, 17, 17)
  3. flatten 操作,变为 18496 维的向量
  4. 接下来依次为256,128节点的全连接层,都使用比例为0.4的 Dropout
  5. 最后输出为 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=1的时候 1. point-wise升维 2. depth-wise提取特征 3. 通过Linear的point-wise降维 4. input与结果 相加(残差结构)
  • 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 个节点,是最终的分类类别数。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!