精华内容
下载资源
问答
  • 深度学习 CNN卷积神经网络 LeNet-5详解

    万次阅读 多人点赞 2017-10-18 16:04:35
    是一种常见的深度学习架构,受生物自然视觉认知机制(动物视觉皮层细胞负责检测光学信号)启发而来,是一种特殊的多层前馈神经网络。它的人工神经元可以响应一部分覆盖范围内的周围单元,对于大型图像处理有出色表现...

    文章首发于公众号【编程求职指南】
    卷积神经网络( Convolutional Neural Network, CNN):
    是一种常见的深度学习架构,受生物自然视觉认知机制(动物视觉皮层细胞负责检测光学信号)启发而来,是一种特殊的多层前馈神经网络。它的人工神经元可以响应一部分覆盖范围内的周围单元,对于大型图像处理有出色表现。
    一般神经网络VS卷积神经网络:
    相同点:卷积神经网络也使用一种反向传播算法(BP)来进行训练
    不同点:网络结构不同。卷积神经网络的网络连接具有局部连接、参数共享的特点。
    局部连接:是相对于普通神经网络的全连接而言的,是指这一层的某个节点只与上一层的部分节点相连。
    参数共享:是指一层中多个节点的连接共享相同的一组参数。
    这里写图片描述
    全连接:连接个数nm 局部连接:连接个数im
    参数不共享:参数个数n*m+m 参数共享:参数个数i+1

    卷积神经网络的主要组成:
    卷积层(Convolutional layer),卷积运算的目的是提取输入的不同特征,第一层卷积层可能只能提取一些低级的特征如边缘、线条和角等层级,更多层的网络能从低级特征中迭代提取更复杂的特征。

    池化层(Pooling),它实际上一种形式的向下采样。有多种不同形式的非线性池化函数,而其中最大池化(Max pooling)和平均采样是最为常见的

    全连接层(Full connection), 与普通神经网络一样的连接方式,一般都在最后几层

    pooling层的作用:
    Pooling层相当于把一张分辨率较高的图片转化为分辨率较低的图片;
    pooling层可进一步缩小最后全连接层中节点的个数,从而达到减少整个神经网络中参数的目的。

    LeNet-5卷积神经网络模型
    LeNet-5:是Yann LeCun在1998年设计的用于手写数字识别的卷积神经网络,当年美国大多数银行就是用它来识别支票上面的手写数字的,它是早期卷积神经网络中最有代表性的实验系统之一。

    LenNet-5共有7层(不包括输入层),每层都包含不同数量的训练参数,如下图所示。
    这里写图片描述
    LeNet-5中主要有2个卷积层、2个下抽样层(池化层)、3个全连接层3种连接方式

    卷积层
    卷积层采用的都是5x5大小的卷积核/过滤器(kernel/filter),且卷积核每次滑动一个像素(stride=1),一个特征图谱使用同一个卷积核.
    每个上层节点的值乘以连接上的参数,把这些乘积及一个偏置参数相加得到一个和,把该和输入激活函数,激活函数的输出即是下一层节点的值
    这里写图片描述

    LeNet-5的下采样层(pooling层)
    下抽样层采用的是2x2的输入域,即上一层的4个节点作为下一层1个节点的输入,且输入域不重叠,即每次滑动2个像素,下抽样节点的结构如下:
    这里写图片描述
    每个下抽样节点的4个输入节点求和后取平均(平均池化),均值乘以一个参数加上一个偏置参数作为激活函数的输入,激活函数的输出即是下一层节点的值。

    卷积后输出层矩阵宽度的计算:
    Outlength=
    (inlength-fileterlength+2*padding)/stridelength+1

    Outlength:输出层矩阵的宽度
    Inlength:输入层矩阵的宽度
    Padding:补0的圈数(非必要)
    Stridelength:步长,即过滤器每隔几步计算一次结果

    LeNet-5第一层:卷积层C1
    C1层是卷积层,形成6个特征图谱。卷积的输入区域大小是5x5,每个特征图谱内参数共享,即每个特征图谱内只使用一个共同卷积核,卷积核有5x5个连接参数加上1个偏置共26个参数。卷积区域每次滑动一个像素,这样卷积层形成的每个特征图谱大小是(32-5)/1+1=28x28。C1层共有26x6=156个训练参数,有(5x5+1)x28x28x6=122304个连接。C1层的连接结构如下所示。
    这里写图片描述

    LeNet-5第二层:池化层S2
    S2层是一个下采样层(为什么是下采样?利用图像局部相关性的原理,对图像进行子抽样,可以减少数据处理量同时保留有用信息)。C1层的6个28x28的特征图谱分别进行以2x2为单位的下抽样得到6个14x14((28-2)/2+1)的图。每个特征图谱使用一个下抽样核。5x14x14x6=5880个连接。S2层的网络连接结构如下右图
    这里写图片描述

    LeNet-5第三层:卷积层C3
    C3层是一个卷积层,卷积和和C1相同,不同的是C3的每个节点与S2中的多个图相连。C3层有16个10x10(14-5+1)的图,每个图与S2层的连接的方式如下表 所示。C3与S2中前3个图相连的卷积结构见下图.这种不对称的组合连接的方式有利于提取多种组合特征。该层有(5x5x3+1)x6 + (5x5x4 + 1) x 3 + (5x5x4 +1)x6 + (5x5x6+1)x1 = 1516个训练参数,共有1516x10x10=151600个连接。
    这里写图片描述

    LeNet-5第四层:池化层S4
    S4是一个下采样层。C3层的16个10x10的图分别进行以2x2为单位的下抽样得到16个5x5的图。5x5x5x16=2000个连接。连接的方式与S2层类似,如下所示。
    这里写图片描述

    LeNet-5第五层:全连接层C5
    C5层是一个全连接层。由于S4层的16个图的大小为5x5,与卷积核的大小相同,所以卷积后形成的图的大小为1x1。这里形成120个卷积结果。每个都与上一层的16个图相连。所以共有(5x5x16+1)x120 = 48120个参数,同样有48120个连接。C5层的网络结构如下所示。
    这里写图片描述

    LeNet-5第六层:全连接层F6
    F6层是全连接层。F6层有84个节点,对应于一个7x12的比特图,该层的训练参数和连接数都是(120 + 1)x84=10164.
    这里写图片描述

    LeNet-5第七层:全连接层Output
    Output层也是全连接层,共有10个节点,分别代表数字0到9,如果节点i的输出值为0,则网络识别的结果是数字i。采用的是径向基函数(RBF)的网络连接方式。假设x是上一层的输入,y是RBF的输出,则RBF输出的计算方式是:
    这里写图片描述
    yi的值由i的比特图编码(即参数Wij)确定。yi越接近于0,则标明输入越接近于i的比特图编码,表示当前网络输入的识别结果是字符i。该层有84x10=840个设定的参数和连接。连接的方式如上图.

    以上是LeNet-5的卷积神经网络的完整结构,共约有60,840个训练参数,340,908个连接。一个数字识别的效果如图所示

    这里写图片描述

    LeNet-5的训练算法
    训练算法与传统的BP算法差不多。主要包括4步,这4步被分为两个阶段:
    第一阶段,向前传播阶段:
    a)从样本集中取一个样本(X,Yp),将X输入网络;
    b)计算相应的实际输出Op。
    在此阶段,信息从输入层经过逐级的变换,传送到输出 层。这个过程也是网络在完成训练后正常运行时执行的过程。在此过程中,网络执行的是计算(实际上就是输入与每层的权值矩阵相点乘,得到最后的输出结果):
    Op=Fn(…(F2(F1(XpW(1))W(2))…)W(n))
    第二阶段,向后传播阶段
    a)算实际输出Op与相应的理想输出Yp的差;
    b)按极小化误差的方法反向传播调整权矩阵。

    卷积神经网络的优点
    卷积网络较一般神经网络在图像处理方面有 如下优点
    a)输入图像和网络的拓扑结构能很好的吻
    合;
    b)特征提取和模式分类同时进行,并同时在
    训练中产生;
    c)权重共享可以减少网络的训练参数,使神
    经网络结构变得更简单,适应性更强。
    总结
    卷积网络在本质上是一种输入到输出的映射,它能够学习大量的输入与输出之间的映射关系,而不需要任何输入和输出之间的精确的数学表达式。

    通过对LeNet-5的网络结构的分析,可以直观地了解一个卷积神经网络的构建方法,可以为分析、构建更复杂、更多层的卷积神经网络做准备。

    LaNet-5的局限性
    CNN能够得出原始图像的有效表征,这使得CNN能够直接从原始像素中,经过极少的预处理,识别视觉上面的规律。然而,由于当时缺乏大规模训练数据,计算机的计算能力也跟不上,LeNet-5 对于复杂问题的处理结果并不理想。

    2006年起,人们设计了很多方法,想要克服难以训练深度CNN的困难。其中,最著名的是 Krizhevsky et al.提出了一个经典的CNN 结构,并在图像识别任务上取得了重大突破。其方法的整体框架叫做 AlexNet,与 LeNet-5 类似,但要更加深一些。

    展开全文
  • 用于高光谱图像分类的深度卷积神经网络     摘要:近来,卷积神经网络已经在各种视觉任务中表现出优异的性能,包括常见二维图像的分类。 在本文中,采用深卷积神经网络直接在光谱域中对高光谱图像进行分类。更...

    用于高光谱图像分类的深度卷积神经网络

     

     

    摘要:近来,卷积神经网络已经在各种视觉任务中表现出优异的性能,包括常见二维图像的分类。 在本文中,采用深卷积神经网络直接在光谱域中对高光谱图像进行分类。更具体地,所提出的分类器的架构包含权重的五层,其中是输入层,卷积层,最大池层,完整连接层和输出层。 在每个光谱签名上实现这五个层以区别于其他光谱。 基于几个高光谱图像数据集的实验结果表明,所提出的方法可以比一些传统方法(如支持向量机和传统的基于深度学习的方法)实现更好的分类性能。

     

     

    1、introduction

    高光谱图像(HSI)[1]由遥感器采集,其特征在于数百个具有高光谱分辨率的观测通道。利用丰富的光谱信息,已经开发了诸如

    展开全文
  • 卷积神经网络 ③循环神经网络 ④深度信念网络(DBN) 深度信念网络(Deep Belief Netword, DBN)是一种深层概率有向图模型,其图结构有多层节点构成。每层节点内部没有连接,相邻两层节点之间为全连接 ...

    常见的五种神经网络

    ①前馈神经网络

    ②卷积神经网络

    ③循环神经网络

    ④深度信念网络(DBN)

          深度信念网络(Deep Belief Netword, DBN)是一种深层的概率有向图模型,其图结构有多层的节点构成。每层节点的内部没有连接,相邻两层的节点之间为全连接

    •     玻尔兹曼机
    •     受限玻尔兹曼机

          DBN的组成原件是玻尔兹曼机

    ⑤生成对抗网络

     

     

     

     

     

    展开全文
  • 动手学深度学习:卷积神经网络,LeNet,卷积神经网络进阶 卷积神经网络基础 目录: 1、卷积神经网络的基础概念 2、卷积层和池化层 3、填充、步幅、输入通道和输出通道 4、卷积层简洁实现 5、池化层简洁实现 1、...
  • 卷积神经网络基础;leNet;卷积神经网络进阶卷积神经网络基础二位互相关运算二维卷积层互相关运算与卷积运算特征图与感受野填充和步幅填充:在输入高宽两侧填充元素,通常填充0。步幅:卷积核在输入数组上每次滑动...
  • 文章目录几种常见的卷积神经网络结构卷积神经网络的基本组成部分常见的神经网络结构走向深度:VGGNet改进神经网络结构代码实现从横交错:Inception(GoogLeNet)改进神经网络结构代码实现里程碑:ResNet改进神经网络...

    几种常见的卷积神经网络结构

    卷积神经网络的基本组成

    部分常见的神经网络结构

    走向深度:VGGNet

    2014年的ImageNet亚军,探索了网络深度与性能的关系,用更小的卷积核与更深的网络结构,取得了较好的效果,成为卷积结构发展史上较为重要的一个网络。

    VGGNet采用了五组卷积与三个全连接层,最后使用Softmax做分类。VGGNet有一个显著的特点:每次经过池化层(maxpool)后特征图的尺寸减小一倍,而通道数则增加一 倍(最后一个池化层除外)。

    AlexNet中有使用到5×5的卷积核,而在VGGNet中,使用的卷积核 基本都是3×3,而且很多地方出现了多个3×3堆叠的现象,这种结构的优 点在于,首先从感受野来看,两个3×3的卷积核与一个5×5的卷积核是一 样的;其次,同等感受野时,3×3卷积核的参数量更少。更为重要的 是,两个3×3卷积核的非线性能力要比5×5卷积核强,因为其拥有两个激 活函数,可大大提高卷积网络的学习能力。

    改进

    在感受野上两个3x3的卷积核等价于一个5x5的卷积核,使用了两个3x3的卷积核来代替5x5的卷积核,减少了参数,同时也增加了非线性能力,提高了学习能力

    dropout通过随机删减神经元来减少神经网络的过拟合,在训练时,每个神经元以概率p保留,即以1-p的概率停止工作,每次前向传播保留下来的神经元都不 同,这样可以使得模型不太依赖于某些局部特征,泛化性能更强。在测 试时,为了保证相同的输出期望值,每个参数还要乘以p。

    神经网络结构

    代码实现

    VGG-16

    import torch
    from torch import nn
    
    
    class VGG(nn.Module):
        def __init__(self, num_classes=1000):
            super(VGG, self).__init__()
            layers = []
            in_dim = 3
            out_dim = 64
    
            # 构造13个卷积层
            for i in range(13):
                layers += [
                    nn.Conv2d(in_dim, out_dim, 3, 1, 1),
                    nn.ReLU(inplace=True)  # 可以实现inplace操作,即可以直接将运算结果覆盖到输入中,以节省内存
                ]
                in_dim = out_dim
                # 在第2、4、7、10、13个卷积层后增加池化层
                if i == 1 or i == 3 or i == 6 or i == 9 or i == 12:
                    layers += [
                        nn.MaxPool2d(2, 2)
                    ]
                    # 第10个卷积层后保持与第9层第通道数一致,都为512,其余加倍
                    if i != 9:
                        out_dim *= 2
    
            self.features = nn.Sequential(*layers)
            # VGGNet的3个全连接层,中间添加ReLU与Dropout
            self.classifier = nn.Sequential(
                nn.Linear(512 * 7 * 7, 4096),
                nn.ReLU(True),
                nn.Dropout(),
                nn.Linear(4096, 4096),
                nn.ReLU(True),
                nn.Dropout(),
                nn.Linear(4096, num_classes)
            )
    
        def forward(self, x):
            x = self.features(x)
            # 特征图的维度从[1,512,7,7]变到[1,512*7*7]
            x = x.view(x.size(0), -1)
            x = self.classifier(x)
            return x
    
    
    if __name__ == '__main__':
        vgg = VGG(21)
        input = torch.randn([1, 3, 224, 224])
        print('input.shape:', input.shape)
        scores = vgg(input)
        print('score.shape:', scores.shape)
        features = vgg.features(input)
        print('features.shape:', features.shape)
        print('vgg.features:', features.shape)
        print('vgg.classifier:', vgg.classifier)
    
    

    输出:

    input.shape: torch.Size([1, 3, 224, 224])
    score.shape: torch.Size([1, 21])
    features.shape: torch.Size([1, 512, 7, 7])
    vgg.features: torch.Size([1, 512, 7, 7])
    vgg.classifier: Sequential(
      (0): Linear(in_features=25088, out_features=4096, bias=True)
      (1): ReLU(inplace=True)
      (2): Dropout(p=0.5, inplace=False)
      (3): Linear(in_features=4096, out_features=4096, bias=True)
      (4): ReLU(inplace=True)
      (5): Dropout(p=0.5, inplace=False)
      (6): Linear(in_features=4096, out_features=21, bias=True)
    )
    
    

    从横交错:Inception(GoogLeNet)

    出自Google听说其命名是为了致敬LeNet,神经网络向广度发展的一个典型

    Inception v1网络一共有9个上述堆叠的模块,共有22层,在最后的 Inception模块处使用了全局平均池化。为了避免深层网络训练时带来的 梯度消失问题,作者还引入了两个辅助的分类器,在第3个与第6个 Inception模块输出后执行Softmax并计算损失,在训练时和最后的损失一 并回传。其中1×1的模块可以先将特征图 降维,再送给3×3和5×5大小的卷积核,由于通道数的降低,参数量也有了较大的减少。

    Inception v2增加了 BN层,同时利用两个级联的3×3卷积取代了Inception v1版本中的5×5卷 积,这种方式既减少了卷积参数量,也增加了网络的非
    线性能力。

    改进

    使用BN层

    BN层首先对每一个batch的输入特征进行白化操作,即去均值方差 过程。假设一个batch的输入数据为x:B=x1,,xmB={x_1,…,x_m},首先求该batch 数据的均值与方差
    uB1mi=1mxi u_B\gets\frac{1}{m}\sum_{i=1}^{m}x_i

    σB21mi=1m(xiuB)2 \sigma_B^2\gets\frac{1}{m}\sum_{i=1}^{m}(x_i-u_B)^2

    以上公式中,m代表batch的大小,μBμ_B为批处理数据的均值,σB2σ^2_B为 批处理数据的方差。在求得均值方差后,利用下面公式进行去均值方差操作:
    x^ixiuiσ2+ε \widehat{x}_i\gets\frac{x_i-u_i}{\sqrt{\sigma^2+\varepsilon}}
    白化操作可以使输入的特征分布具有相同的均值与方差,固定了每 一层的输入分布,从而加速网络的收敛。然而,白化操作虽然从一定程 度上避免了梯度饱和,但也限制了网络中数据的表达能力,浅层学到的 参数信息会被白化操作屏蔽掉,因此,BN层在白化操作后又增加了一 个线性变换操作,让数据尽可能地恢复本身的表达能力
    yiγx^i+β y_i\gets \gamma\widehat{x}_i+\beta
    γ与β为新引进的可学习参数,最终的输出为yi。 BN层可以看做是增加了线性变换的白化操作,在实际工程中被证 明了能够缓解神经网络难以训练的问题。BN层的优点主要有以下3点:

    • 缓解梯度消失,加速网络收敛。BN层可以让激活函数的输入数据 落在非饱和区,缓解了梯度消失问题。此外,由于每一层数据的均值与 方差都在一定范围内,深层网络不必去不断适应浅层网络输入的变化, 实现了层间解耦,允许每一层独立学习,也加快了网络的收敛。
    • 简化调参,网络更稳定。在调参时,学习率调得过大容易出现震 荡与不收敛,BN层则抑制了参数微小变化随网络加深而被放大的问 题,因此对于参数变化的适应能力更强,更容易调参。
    • 防止过拟合。BN层将每一个batch的均值与方差引入到网络中,由 于每个batch的这两个值都不相同,可看做为训练过程增加了随机噪音,可以起到一定的正则效果,防止过拟合。

    在测试时,由于是对单个样本进行测试,没有batch的均值与方差, 通常做法是在训练时将每一个batch的均值与方差都保留下来,在测试时 使用所有训练样本均值与方差的平均值。

    神经网络结构

    inceptionV1

    inceptionV2

    代码实现

    Inception v1

    import torch
    from torch import nn
    import torch.nn.functional as F
    
    
    # 定义一个conv+relu的类
    class BasicConv2d(nn.Module):
        def __init__(self, in_channels, out_channels, kernel_size, padding=0):
            super(BasicConv2d, self).__init__()
            self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, padding=padding)
    
        def forward(self, x):
            x = self.conv(x)
            return F.relu(x, inplace=True)
    
    
    # 定义googLeNet
    class Inceptionv1(nn.Module):
        def __init__(self, in_dim, hid_1_1, hid_2_1, hid_2_3, hid_3_1, out_3_5, out_4_1):
            super(Inceptionv1, self).__init__()
            # 定义四个子模块
            self.branch1x1 = BasicConv2d(in_dim, hid_1_1, 1)
            self.branch3x3 = nn.Sequential(
                BasicConv2d(in_dim, hid_2_1, 1),
                BasicConv2d(hid_2_1, hid_2_3, 3, padding=1)
            )
            self.branch5x5 = nn.Sequential(
                BasicConv2d(in_dim, hid_3_1, 1),
                BasicConv2d(hid_3_1, out_3_5, 5, padding=2)
            )
            self.branch_pool = nn.Sequential(
                nn.MaxPool2d(3, stride=1, padding=1),
                BasicConv2d(in_dim, out_4_1, 1)
            )
    
        def forward(self, x):
            b1 = self.branch1x1(x)
            b2 = self.branch3x3(x)
            b3 = self.branch5x5(x)
            b4 = self.branch_pool(x)
            # 连接子模块
            output = torch.cat((b1, b2, b3, b4), dim=1)
            return output
    
    
    if __name__ == '__main__':
        net_inceptionv1 = Inceptionv1(3, 63, 32, 64, 64, 96, 32)
        print('net_inceptionv1:', net_inceptionv1)
        input = torch.randn(1, 3, 256, 256)
        print(input.shape)
        output = net_inceptionv1(input)
        print('output.shape', output.shape)
    

    输出

    net_inceptionv1: Inceptionv1(
      (branch1x1): BasicConv2d(
        (conv): Conv2d(3, 63, kernel_size=(1, 1), stride=(1, 1))
      )
      (branch3x3): Sequential(
        (0): BasicConv2d(
          (conv): Conv2d(3, 32, kernel_size=(1, 1), stride=(1, 1))
        )
        (1): BasicConv2d(
          (conv): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        )
      )
      (branch5x5): Sequential(
        (0): BasicConv2d(
          (conv): Conv2d(3, 64, kernel_size=(1, 1), stride=(1, 1))
        )
        (1): BasicConv2d(
          (conv): Conv2d(64, 96, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
        )
      )
      (branch_pool): Sequential(
        (0): MaxPool2d(kernel_size=3, stride=1, padding=1, dilation=1, ceil_mode=False)
        (1): BasicConv2d(
          (conv): Conv2d(3, 32, kernel_size=(1, 1), stride=(1, 1))
        )
      )
    )
    torch.Size([1, 3, 256, 256])
    output.shape torch.Size([1, 255, 256, 256])
    

    Inception v2

    import torch
    from torch import nn
    import torch.nn.functional as F
    
    
    class BasicConv2d(nn.Module):
        def __init__(self, in_channels, out_channels, kernel_size, padding=0):
            super(BasicConv2d, self).__init__()
            self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, padding=padding)
            self.bn = nn.BatchNorm2d(out_channels, eps=0.001)
    
        def forward(self, x):
            x = self.conv(x)
            x = self.bn(x)
            return F.relu(x, inplace=True)
    
    
    class Inceptionv2(nn.Module):
        def __init__(self):
            super(Inceptionv2, self).__init__()
            self.branch1 = BasicConv2d(192, 96, 1, 0)
            self.branch2 = nn.Sequential(
                BasicConv2d(192, 48, 1, 0),
                BasicConv2d(48, 64, 3, 1)
            )
            self.branch3 = nn.Sequential(
                BasicConv2d(192, 64, 1, 0),
                BasicConv2d(64, 96, 3, 1),
                BasicConv2d(96, 96, 3, 1)
            )
            self.branch4 = nn.Sequential(
                nn.AvgPool2d(3, stride=1, padding=1, count_include_pad=False),
                BasicConv2d(192, 64, 1, 0)
            )
    
        def forward(self, x):
            x0 = self.branch1(x)
            x1 = self.branch2(x)
            x2 = self.branch3(x)
            x3 = self.branch4(x)
            out = torch.cat((x0, x1, x2, x3), 1)
            return out
    
    
    if __name__ == '__main__':
        net_inceptionv2 = Inceptionv2()
        print('net_inceptionv2:', net_inceptionv2)
        input = torch.randn(1, 192, 32, 32)
        print('input.shape:', input.shape)
        output = net_inceptionv2(input)
        print('output.shape:', output.shape)
    

    输出

    net_inceptionv2: Inceptionv2(
      (branch1): BasicConv2d(
        (conv): Conv2d(192, 96, kernel_size=(1, 1), stride=(1, 1))
        (bn): BatchNorm2d(96, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)
      )
      (branch2): Sequential(
        (0): BasicConv2d(
          (conv): Conv2d(192, 48, kernel_size=(1, 1), stride=(1, 1))
          (bn): BatchNorm2d(48, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)
        )
        (1): BasicConv2d(
          (conv): Conv2d(48, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (bn): BatchNorm2d(64, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)
        )
      )
      (branch3): Sequential(
        (0): BasicConv2d(
          (conv): Conv2d(192, 64, kernel_size=(1, 1), stride=(1, 1))
          (bn): BatchNorm2d(64, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)
        )
        (1): BasicConv2d(
          (conv): Conv2d(64, 96, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (bn): BatchNorm2d(96, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)
        )
        (2): BasicConv2d(
          (conv): Conv2d(96, 96, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (bn): BatchNorm2d(96, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)
        )
      )
      (branch4): Sequential(
        (0): AvgPool2d(kernel_size=3, stride=1, padding=1)
        (1): BasicConv2d(
          (conv): Conv2d(192, 64, kernel_size=(1, 1), stride=(1, 1))
          (bn): BatchNorm2d(64, eps=0.001, momentum=0.1, affine=True, track_running_stats=True)
        )
      )
    )
    input.shape: torch.Size([1, 192, 32, 32])
    output.shape: torch.Size([1, 320, 32, 32])
    

    里程碑:ResNet

    何凯明大神的作品,较好的解决了梯度下降的问题,获得了2015年ImageNet分类任务的第一名

    ResNet(Residual Network,残差网络)较好的解决了梯度下降的问题,获得了2015年ImageNet分类任务的第一名。此后的分类、检测、分割等 任务也大规模使用ResNet作为网络骨架。

    改进

    ResNet的思想在于引入了一个深度残差框架来解决梯度消失问题, 即让卷积网络去学习残差映射,而不是期望每一个堆叠层的网络都完整 地拟合潜在的映射(拟合函数)。如图所示,对于神经网络,如果 我们期望的网络最终映射为H(x),左侧的网络需要直接拟合输出H(x), 而右侧由ResNet提出的子模块,通过引入一个shortcut(捷径)分支,将 需要拟合的映射变为残差F(x):H(x)-x。ResNet给出的假设是:相较于 直接优化潜在映射H(x),优化残差映射F(x)是更为容易的。由于F(x)+x是逐通道进行相加,因此根据两者是否通道数相同,存 在两种Bottleneck结构。对于通道数不同的情况,比如每个卷积组的第 一个Bottleneck,需要利用1×1卷积对x进行Downsample操作,将通道数变为相同,再进行加操作。对于相同的情况下,两者可以直接进行相 加。

    神经网络结构

    代码实现

    import torch
    import torch.nn as nn
    
    
    class Bottleneck(nn.Module):
        def __init__(self, in_dim, out_dim, stride=1):
            super(Bottleneck, self).__init__()
            # 网络堆叠层是由1x1、3x3、1x1这3个卷积层组成,中间包含BN层
            self.bottleneck = nn.Sequential(
                nn.Conv2d(in_dim, in_dim, 1, bias=False),
                nn.BatchNorm2d(in_dim),
                nn.ReLU(inplace=True),
                nn.Conv2d(in_dim, in_dim, 3, stride, 1, bias=False),
                nn.BatchNorm2d(in_dim),
                nn.ReLU(inplace=True),
                nn.Conv2d(in_dim, out_dim, 1, bias=False),
                nn.BatchNorm2d(out_dim)
            )
            self.relu = nn.ReLU(inplace=True)
            # Downsample部分由一个包含BN层的1x1的卷积组成
            self.downsample = nn.Sequential(
                nn.Conv2d(in_dim, out_dim, 1, 1),
                nn.BatchNorm2d(out_dim),
            )
    
        def forward(self, x):
            identity = x
            out = self.bottleneck(x)
            identity = self.downsample(x)
            # 将identity(恒等映射)与网络堆叠层进行相加,并经过ReLU输出
            out += identity
            out = self.relu(out)
            return out
    
    
    if __name__ == '__main__':
        bottleneck_1_1 = Bottleneck(64, 256)
        print('bottleneck_1_1', bottleneck_1_1)
        input = torch.randn(1, 64, 56, 56)
        output = bottleneck_1_1(input)
        print('input.shape', input.shape)
        print('output.shape', output.shape)
        
    

    输出

    bottleneck_1_1 Bottleneck(
      (bottleneck): Sequential(
        (0): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
        (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (2): ReLU(inplace=True)
        (3): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (4): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (5): ReLU(inplace=True)
        (6): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
        (7): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
      (relu): ReLU(inplace=True)
      (downsample): Sequential(
        (0): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1))
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    input.shape torch.Size([1, 64, 56, 56])
    output.shape torch.Size([1, 256, 56, 56])
    
    

    继往开来:DenseNet

    2017CVPR的best论文奖

    ResNet通过前层与后层的“短路连接”(Shortcuts),加强 了前后层之间的信息流通,在一定程度上缓解了梯度消失现象,从而可 以将神经网络搭建得很深。DenseNet最大化了这 种前后层信息交流,通过建立前面所有层与后面层的密集连接,实现了 特征在通道维度上的复用,使其可以在参数与计算量更少的情况下实现 比ResNet更优的性能。

    改进

    FPN将深层的语义信息传到底层,来补充浅层的语义信息,从而获 得了高分辨率、强语义的特征,在小物体检测、实例分割等领域有着非 常不俗的表现。

    相比ResNet,DenseNet提出了一个更激进的密集连接机制:即互相连接所有的层,具体来说就是每个层都会接受其前面所有层作为其额外的输入。图1为ResNet网络的连接机制,作为对比,图2为DenseNet的密集连接机制。可以看到,ResNet是每个层与前面的某层(一般是2~3层)短路连接在一起,连接方式是通过元素级相加。而在DenseNet中,每个层都会与前面所有层在channel维度上连接(concat)在一起(这里各个层的特征图大小是相同的,后面会有说明),并作为下一层的输入。对于一个 L层的网络,DenseNet共包含L(L+1)2\frac{L(L+1)}{2} 个连接,相比ResNet,这是一种密集连接。而且DenseNet是直接concat来自不同层的特征图,这可以实现特征重用,提升效率,这一特点是DenseNet与ResNet最主要的区别。

    神经网络结构

    网络由多个Dense Block与中间 的卷积池化组成,核心就在Dense Block中。Dense Block中的黑点代表 一个卷积层,其中的多条黑线代表数据的流动,每一层的输入由前面的 所有卷积层的输出组成。注意这里使用了通道拼接(Concatnate)操 作,而非ResNet的逐元素相加操作。

    DenseNet的结构有如下两个特性:

    • 神经网络一般需要使用池化等操作缩小特征图尺寸来提取语义特 征,而Dense Block需要保持每一个Block内的特征图尺寸一致来直接进 行Concatnate操作,因此DenseNet被分成了多个Block。Block的数量一 般为4。
    • 两个相邻的Dense Block之间的部分被称为Transition层,具体包括 BN、ReLU、1×1卷积、2×2平均池化操作。1×1卷积的作用是降维,起 到压缩模型的作用,而平均池化则是降低特征图的尺寸, 具体的Block实现细节如图3.20所示,每一个Block由若干个 Bottleneck的卷积层组成,对应图3.19中的黑点。Bottleneck由BN、 ReLU、1×1卷积、BN、ReLU、3×3卷积的顺序构成。

    关于Block,有以下4个细节需要注意:

    • 每一个Bottleneck输出的特征通道数是相同的,例如这里的32。同 时可以看到,经过Concatnate操作后的通道数是按32的增长量增加的, 因此这个32也被称为GrowthRate。
    • 这里1×1卷积的作用是固定输出通道数,达到降维的作用。当几十 个Bottleneck相连接时,Concatnate后的通道数会增加到上千,如果不增 加1×1的卷积来降维,后续3×3卷积所需的参数量会急剧增加。1×1卷积 的通道数通常是GrowthRate的4倍。
    • 特征传递方式是直接将前面所有层的特征Concatnate后 传到下一层,这种方式与具体代码实现的方式是一致的,而不像图3.19 中,前面层都要有一个箭头指向后面的所有层。
    • Block采用了激活函数在前、卷积层在后的顺序,这与一般的网络 上是不同的。

    代码实现

    import torch
    from torch import nn
    import torch.nn.functional as F
    
    
    class Bottleneck(nn.Module):
        def __init__(self, nChannels, growthRate):
            super(Bottleneck, self).__init__()
            interChannels = 4 * growthRate
            self.bn1 = nn.BatchNorm2d(nChannels)
            self.conv1 = nn.Conv2d(nChannels, interChannels, kernel_size=1, bias=False)
            self.bn2 = nn.BatchNorm2d(interChannels)
            self.conv2 = nn.Conv2d(interChannels, growthRate, kernel_size=3, padding=1, bias=False)
    
        def forward(self, x):
            out = self.conv1(F.relu(self.bn1(x)))
            out = self.conv2(F.relu(self.bn2(out)))
            out = torch.cat((x, out), 1)
            return out
    
    
    class Denseblock(nn.Module):
        def __init__(self, nChannels, growthRate, nDenseBlock):
            super(Denseblock, self).__init__()
            layers = []
            for i in range(int(nDenseBlock)):
                layers.append(Bottleneck(nChannels, growthRate))
                nChannels += growthRate
            self.denseblock = nn.Sequential(*layers)
    
        def forward(self, x):
            return self.denseblock(x)
    
    
    if __name__ == '__main__':
        denseblock = Denseblock(64, 32, 6)
        print('denseblock:', denseblock)
        input = torch.randn(1, 64, 256, 256)
        output = denseblock(input)
        print('output.shape:', output.shape)
    

    输出

    denseblock: Denseblock(
      (denseblock): Sequential(
        (0): Bottleneck(
          (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (conv1): Conv2d(64, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
          (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (conv2): Conv2d(128, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        )
        (1): Bottleneck(
          (bn1): BatchNorm2d(96, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (conv1): Conv2d(96, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
          (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (conv2): Conv2d(128, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        )
        (2): Bottleneck(
          (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (conv1): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
          (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (conv2): Conv2d(128, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        )
        (3): Bottleneck(
          (bn1): BatchNorm2d(160, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (conv1): Conv2d(160, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
          (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (conv2): Conv2d(128, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        )
        (4): Bottleneck(
          (bn1): BatchNorm2d(192, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (conv1): Conv2d(192, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
          (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (conv2): Conv2d(128, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        )
        (5): Bottleneck(
          (bn1): BatchNorm2d(224, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (conv1): Conv2d(224, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
          (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (conv2): Conv2d(128, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        )
      )
    )
    output.shape: torch.Size([1, 256, 256, 256])
    
    

    特征金字塔:FPN

    为了解决多尺度问题

    为了增强语义性,传统的物体检测模型通常只在深度卷积网络的最 后一个特征图上进行后续操作,而这一层对应的下采样率(图像缩小的 倍数)通常又比较大,如16、32,造成小物体在特征图上的有效信息较 少,小物体的检测性能会急剧下降,这个问题也被称为多尺度问题。

    解决多尺度问题的关键在于如何提取多尺度的特征。传统的方法有 图像金字塔(Image Pyramid),主要思路是将输入图片做成多个尺度, 不同尺度的图像生成不同尺度的特征,这种方法简单而有效,大量使用 在了COCO等竞赛上,但缺点是非常耗时,计算量也很大。

    改进

    2017年的FPN(Feature Pyramid Network)方法融合了不同层的特征,较好地改善了多尺度检测问题。

    神经网络结构

    • 自下而上:最左侧为普通的卷积网络,默认使用ResNet结构,用 作提取语义信息。C1代表了ResNet的前几个卷积与池化层,而C2至C5 分别为不同的ResNet卷积组,这些卷积组包含了多个Bottleneck结构, 组内的特征图大小相同,组间大小递减。
    • 自上而下:首先对C5进行1×1卷积降低通道数得到P5,然后依次进 行上采样得到P4、P3和P2,目的是得到与C4、C3与C2长宽相同的特 征,以方便下一步进行逐元素相加。这里采用2倍最邻近上采样,即直 接对临近元素进行复制,而非线性插值。
    • 横向连接(Lateral Connection):目的是为了将上采样后的高语义 特征与浅层的定位细节特征进行融合。高语义特征经过上采样后,其长 宽与对应的浅层特征相同,而通道数固定为256,因此需要对底层特征 C2至C4进行1x1卷积使得其通道数变为256,然后两者进行逐元素相加得 到P4、P3与P2。由于C1的特征图尺寸较大且语义信息不足,因此没有 把C1放到横向连接中。
    • 卷积融合:在得到相加后的特征后,利用3×3卷积对生成的P2至P4 再进行融合,目的是消除上采样过程带来的重叠效应,以生成最终的特 征图。

    代码实现

    import torch
    import torch.nn as nn
    import torch.nn.functional as F
    
    
    class Bottleneck(nn.Module):
        expansion = 4  # 通道倍增数
    
        def __init__(self, in_place, planes, stride=1, downsample=None):
            super(Bottleneck, self).__init__()
            self.bottleneck = nn.Sequential(
                nn.Conv2d(in_place, planes, 1, bias=True),
                nn.BatchNorm2d(planes),
                nn.ReLU(inplace=True),
                nn.Conv2d(planes, planes, 3, stride, 1, bias=False),
                nn.BatchNorm2d(planes),
                nn.ReLU(inplace=True),
                nn.Conv2d(planes, self.expansion * planes, 1, bias=True),
                nn.BatchNorm2d(self.expansion * planes)
            )
            self.relu = nn.ReLU(inplace=True)
            self.downsample = downsample
    
        def forward(self, x):
            identity = x
            out = self.bottleneck(x)
            if self.downsample is not None:
                identity = self.downsample(x)
            out += identity
            out = self.relu(out)
            return out
    
    
    # layers代表每一个阶段的Bottleneck的数量
    class FPN(nn.Module):
        def __init__(self, layers):
            super(FPN, self).__init__()
            self.inplanes = 64
            # 处理输入的C1模块
            self.conv1 = nn.Conv2d(3, 64, 7, 2, 3, bias=False)
            self.bn1 = nn.BatchNorm2d(64)
            self.relu = nn.ReLU(inplace=True)
            self.maxpool = nn.MaxPool2d(3, 2, 1)
            # 搭建自上而下的C2、C3、C4、C5模块
            self.layer1 = self._make_layer(64, layers[0])
            self.layer2 = self._make_layer(128, layers[1], 2)
            self.layer3 = self._make_layer(256, layers[2], 2)
            self.layer4 = self._make_layer(512, layers[3], 2)
            # 对C5减少通道数,得到P5
            self.toplayer = nn.Conv2d(2048, 256, 1, 1, 0)
            # 3x3卷积特征融合
            self.smooth1 = nn.Conv2d(256, 256, 3, 1, 1)
            self.smooth2 = nn.Conv2d(256, 256, 3, 1, 1)
            self.smooth3 = nn.Conv2d(256, 256, 3, 1, 1)
            # 横向连接,保证通道数相同
            self.latlayer1 = nn.Conv2d(1024, 256, 1, 1, 0)
            self.latlayer2 = nn.Conv2d(512, 256, 1, 1, 0)
            self.latlayer3 = nn.Conv2d(256, 256, 1, 1, 0)
    
        # 构建C2到C5,注意区分stride=1或2的情况
        def _make_layer(self, planes, blocks, stride=1):
            downsample = None
            if stride != 1 or self.inplanes != Bottleneck.expansion * planes:
                downsample = nn.Sequential(
                    nn.Conv2d(self.inplanes, Bottleneck.expansion * planes, 1, stride, bias=False),
                    nn.BatchNorm2d(Bottleneck.expansion * planes)
                )
            layers = []
            layers.append(Bottleneck(self.inplanes, planes, stride, downsample))
            self.inplanes = planes * Bottleneck.expansion
            for i in range(1, blocks):
                layers.append(Bottleneck(self.inplanes, planes))
            return nn.Sequential(*layers)
    
        # 自上而下的上采样模块
        def _upsample_add(self, x, y):
            _, _, H, W = y.shape
            return F.upsample(x, size=(H, W), mode='bilinear') + y
    
        def forward(self, x):
            # 自下而上
            c1 = self.maxpool(self.relu(self.bn1(self.conv1(x))))
            c2 = self.layer1(c1)
            c3 = self.layer2(c2)
            c4 = self.layer3(c3)
            c5 = self.layer4(c4)
            # 自上而下
            p5 = self.toplayer(c5)
            p4 = self._upsample_add(p5, self.latlayer1(c4))
            p3 = self._upsample_add(p4, self.latlayer2(c3))
            p2 = self._upsample_add(p3, self.latlayer3(c2))
            # 卷积融合,平滑处理
            p4 = self.smooth1(p4)
            p3 = self.smooth2(p3)
            p2 = self.smooth3(p2)
    
            return p2, p3, p4, p5
    
    
    if __name__ == '__main__':
        net_fpn = FPN([3, 4, 6, 3])
        print('net_fpn.conv1:', net_fpn.conv1)
        print('net_fpn.bn1:', net_fpn.bn1)
        print('net_fpn.relu:', net_fpn.relu)
        print('net_fpn.maxpool:', net_fpn.maxpool)
        print('net_fpn.layer1:', net_fpn.layer1)
        print('net_fpn.layer2:', net_fpn.layer2)
        print('net_fpn.toplayer:', net_fpn.toplayer)
        print('net_fpn.smooth1:', net_fpn.smooth1)
        print('net_fpn.latlayer1', net_fpn.latlayer1)
        input = torch.randn(1, 3, 224, 224)
        output = net_fpn(input)
        print('output[0].shape:', output[0].shape)
        print('output[1].shape:', output[1].shape)
        print('output[2].shape:', output[2].shape)
        print('output[3].shape:', output[3].shape)
    

    输出

    net_fpn.conv1: Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
    net_fpn.bn1: BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    net_fpn.relu: ReLU(inplace=True)
    net_fpn.maxpool: MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
    net_fpn.layer1: Sequential(
      (0): Bottleneck(
        (bottleneck): Sequential(
          (0): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1))
          (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (2): ReLU(inplace=True)
          (3): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (4): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (5): ReLU(inplace=True)
          (6): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1))
          (7): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (relu): ReLU(inplace=True)
        (downsample): Sequential(
          (0): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
          (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
      )
      (1): Bottleneck(
        (bottleneck): Sequential(
          (0): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1))
          (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (2): ReLU(inplace=True)
          (3): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (4): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (5): ReLU(inplace=True)
          (6): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1))
          (7): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (relu): ReLU(inplace=True)
      )
      (2): Bottleneck(
        (bottleneck): Sequential(
          (0): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1))
          (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (2): ReLU(inplace=True)
          (3): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (4): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (5): ReLU(inplace=True)
          (6): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1))
          (7): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (relu): ReLU(inplace=True)
      )
    )
    net_fpn.layer2: Sequential(
      (0): Bottleneck(
        (bottleneck): Sequential(
          (0): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1))
          (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (2): ReLU(inplace=True)
          (3): Conv2d(128, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
          (4): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (5): ReLU(inplace=True)
          (6): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1))
          (7): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (relu): ReLU(inplace=True)
        (downsample): Sequential(
          (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)
          (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
      )
      (1): Bottleneck(
        (bottleneck): Sequential(
          (0): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1))
          (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (2): ReLU(inplace=True)
          (3): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (4): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (5): ReLU(inplace=True)
          (6): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1))
          (7): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (relu): ReLU(inplace=True)
      )
      (2): Bottleneck(
        (bottleneck): Sequential(
          (0): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1))
          (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (2): ReLU(inplace=True)
          (3): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (4): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (5): ReLU(inplace=True)
          (6): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1))
          (7): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (relu): ReLU(inplace=True)
      )
      (3): Bottleneck(
        (bottleneck): Sequential(
          (0): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1))
          (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (2): ReLU(inplace=True)
          (3): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (4): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (5): ReLU(inplace=True)
          (6): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1))
          (7): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (relu): ReLU(inplace=True)
      )
    )
    net_fpn.toplayer: Conv2d(2048, 256, kernel_size=(1, 1), stride=(1, 1))
    net_fpn.smooth1: Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    net_fpn.latlayer1 Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1))
    output[0].shape: torch.Size([1, 256, 56, 56])
    output[1].shape: torch.Size([1, 256, 28, 28])
    output[2].shape: torch.Size([1, 256, 14, 14])
    output[3].shape: torch.Size([1, 256, 7, 7])
    
    

    为检测而生:DetNet

    如VGGNet和ResNet等,虽从各个角度出发 提升了物体检测性能,但究其根本是为ImageNet的图像分类任务而设计 的。而图像分类与物体检测两个任务天然存在着落差,分类任务侧重于 全图的特征提取,深层的特征图分辨率很低;而物体检测需要定位出物 体位置,特征图分辨率不宜过小,因此造成了以下两种缺陷:

    • 大物体难以定位:对于FPN等网络,大物体对应在较深的特征图上 检测,由于网络较深时下采样率较大,物体的边缘难以精确预测,增加 了回归边界的难度。
    • 小物体难以检测:对于传统网络,由于下采样率大造成小物体在 较深的特征图上几乎不可见;FPN虽从较浅的特征图来检测小物体,但 浅层的语义信息较弱,且融合深层特征时使用的上采样操作也会增加物 体检测的难度。

    针对以上问题,旷视科技提出了专为物体检测设计的DetNet结构, 引入了空洞卷积,使得模型兼具较大感受野与较高分辨率,同时避免了FPN的多次上采样,实现了较好的检测效果。

    DetNet的网络结构如图3.22所示,仍然选择性能优越的ResNet-50作 为基础结构,并保持前4个stage与ResNet-50相同,具体的结构细节有以 下3点:

    • 引入了一个新的Stage 6,用于物体检测。Stage 5与Stage 6使用了 DetNet提出的Bottleneck结构,最大的特点是利用空洞数为2的3×3卷积 取代了步长为2的3×3卷积。
    • Stage 5与Stage 6的每一个Bottleneck输出的特征图尺寸都为原图的116\frac{1}{16} ,通道数都为256,而传统的Backbone通常是特征图尺寸递减,通道数递增。
    • 在组成特征金字塔时,由于特征图大小完全相同,因此可以直接 从右向左传递相加,避免了上一节的上采样操作。为了进一步融合各通 道的特征,需要对每一个阶段的输出进行1×1卷积后再与后一Stage传回 的特征相加。

    改进

    采用了空洞卷积

    图a是普通的卷积过程,在卷积核紧密排列在特征图上滑动计算,而图b代表了空洞数为2的空洞 卷积,可以看到,在特征图上每2行或者2列选取元素与卷积核卷积。类 似地,图c代表了空洞数为3的空洞卷积。

    在代码实现时,空洞卷积有一个额外的超参数dilation rate,表示空
    洞数,普通卷积dilation rate默认为1,图中的b与c的dilation rate分别 为2与3。

    在图3.11中,同样的一个3×3卷积,却可以起到5×5、7×7等卷积的 效果。可以看出,空洞卷积在不增加参数量的前提下,增大了感受野。 假设空洞卷积的卷积核大小为k,空洞数为d,则其等效卷积核大小k’计 算如式所示
    k=k+(k1)×(d1) k'=k+(k-1)\times (d-1)
    空洞卷积的优点显而易见,在不引入额外参数的前提下可以任意扩 大感受野,同时保持特征图的分辨率不变。这一点在分割与检测任务中 十分有用,感受野的扩大可以检测大物体,而特征图分辨率不变使得物 体定位更加精准。

    当然,空洞卷积也有自己的一些缺陷,主要表现在以下3个方面:

    • 网格效应(Gridding Effect):由于空洞卷积是一种稀疏的采样方
      式,当多个空洞卷积叠加时,有些像素根本没有被利用到,会损失信息 的连续性与相关性,进而影响分割、检测等要求较高的任务。
    • 远距离的信息没有相关性:空洞卷积采取了稀疏的采样方式,导 致远距离卷积得到的结果之间缺乏相关性,进而影响分类的结果。
    • 不同尺度物体的关系:大的dilation rate对于大物体分割与检测有 利,但是对于小物体则有弊无利,如何处理好多尺度问题的检测,是空 洞卷积设计的重点。

    神经网络结构

    代码实现

    Bottlenck A与Bottlenck B

    import torch
    from torch import nn
    
    
    class DetBottleneck(nn.Module):
        def __init__(self, inplanes, planes, stride=1, extra=False):
            super(DetBottleneck, self).__init__()
            self.bottleneck = nn.Sequential(
                nn.Conv2d(inplanes, planes, 1, bias=True),
                nn.BatchNorm2d(planes),
                nn.ReLU(inplace=True),
                nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=2, dilation=2, bias=False),
                nn.BatchNorm2d(planes),
                nn.ReLU(inplace=True),
                nn.Conv2d(planes, planes, 1, bias=False),
                nn.BatchNorm2d(planes)
            )
            self.relu = nn.ReLU(inplace=True)
            self.extra = extra
            if self.extra:
                self.extra_conv = nn.Sequential(
                    nn.Conv2d(inplanes, planes, 1, bias=False),
                    nn.BatchNorm2d(planes)
                )
    
        def forward(self, x):
            if self.extra:
                identity = self.extra_conv(x)
            else:
                identity = x
            out = self.bottleneck(x)
            out += identity
            out = self.relu(out)
            return out
    
    
    if __name__ == '__main__':
        bottleneck_b = DetBottleneck(1024, 256, 1, True)
        print('bottleneck_b:', bottleneck_b)
        bottleneck_a1 = DetBottleneck(256, 256)
        print('bottleneck_a1:', bottleneck_a1)
        bottleneck_a2 = DetBottleneck(256, 256)
        print('bottleneck_a2:', bottleneck_a2)
        input = torch.randn(1, 1024, 14, 14)
        output1 = bottleneck_b(input)
        output2 = bottleneck_a1(output1)
        output3 = bottleneck_a2(output2)
        print('output1.shape:', output1.shape)
        print('output2.shape:', output2.shape)
        print('output3.shape:', output3.shape)
    
    展开全文
  • 文章目录1 卷积神经网络基础1.1 二维卷积层1.2 填充和步幅1.3 多输入通道和多输出通道1.4 卷积层与全连接层对比1.4 池化2 LeNet2.1 LeNet 模型2.2 获取数据和训练模型3 卷积神经网络进阶3.1 深度卷积神经网络...
  • 池化二、LeNet三、常见的一些卷积神经网络1.AlexNet2.VGG3.NiN4.GoogLeNet   一、卷积神经网络基础 1.基础知识 二维互相关(cross-correlation)运算:输入一个二维数组和核数组(卷积核或过滤器),卷积核在输入数组...
  • 这次学习《动手学深度学习》打卡主要内容有三:卷积神经网络基础,leNet和卷积神经网络进阶 一、卷积神经网络基础 本节介绍卷积神经网络的基础概念,主要是卷积层和池化层,并解释填充、步幅、输入通道和输出通道...
  • 卷积神经网络基础 内容摘自伯禹人工智能AI公益课程 目录: 1、卷积神经网络的基础概念 2、卷积层和池化层 3、填充、步幅、输入通道和输出通道 1、卷积神经网络的基础概念 最常见的二维卷积层,常用于处理图像数据。 ...
  • 文章目录一、LeNet二、AlexNet2.1 AlexNet优点三:VGGNet3.1 VGG特点...LeNet诞生于1994年,由深度学习三巨头之一Yan LeCun提出,他也被称为卷积神经网络之父。        LeNe
  • 几个具有代表性的深度卷积神经网络的设计思路: 1、最早提出的AlexNet 2、后来使用重复元素的网络VGG 3、网络中的网络NiN 4、含并行连结的网络GoogleNet 5、残差网络ResNet 6、稠密连接网络DenseNet 本章中...
  • 最近在整理深度学习中用到的算法、常见的模型和优化技巧,于是,决定整理成一个深度学习模型和一个深度学习优化算法单元,大家一起学习,有什么问题可以在微信群里讨论,同时也热烈欢迎大家加入微信群“深度学习与...
  • 深度学习总结:常见卷积神经网络2Inception v1Inception v2BN层Inception v3非对称卷积分解Inception v4总结  上一篇博客主要回顾了VGG和Resnet,这一篇主要回顾一下GoogLeNet系列。 Inception v1 Inception v1...
  • 深度学习网络与更常见的单一隐藏层神经网络的区别在于深度深度学习网络中,每一个节点层在前一层输出的基础上学习识别一组特定的特征。随着神经网络深度增加,节点所能识别的特征也就越来越复杂。 2、卷积神经网络...
  • 正是靠着卷积和卷积神经网络深度学习才超越了几乎其他所有的机器学习手段。这期我们一起学习下深度学习中常见的卷积有哪些?1. 一般卷积卷积在数学上用通俗的话来说就是输入矩阵与卷积核(卷积核也是矩阵)进行对应...
  • 人工智能原理基于Python语言和TensorFlow张明 副教授 第六章卷积神经网络卷积神经网络卷积神经网络的模型架构卷积运算卷积常见层TensorFlow和图像模型训练模型评估多GPU模型训练6.1卷积神经网络应用是深度学习技术...
  • CNN 卷积神经网络卷积神经网络(Convolutional Neural Network,CNN)是图像处理领域最为常见的一种深度学习方法。在图像处理领域,网络的输入是数万个像素点组成的高维特征,如果仍然使用传统的前馈神经网络主要存在...
  • 卷积神经网络概述 一般一个卷积神经网络由多个卷积层构成,在卷基层内部通常会有如下几个操作: 1)图像通过多个卷积核滤波,添加偏置,提取局部特征每个卷积核会映射出一个新2D图像。 2)卷积核滤波结果输出...
  • 深度学习网络与更常见的单一隐藏层神经网络的区别在于深度深度学习网络中,每一个节点层在前一层输出的基础上学习识别一组特定的特征。随着神经网络深度增加,节点所能识别的特征也就越来越复杂。 卷积神经网络 ...
  • 1.1 卷积神经网络与简单全连接神经网络比较 全连接神经网络缺点 参数太多,在cifar-10数据集中,只有32 x 32 x 3,就会有这么多权重,如果说更大图片,比如200 x 200 x 3就需要120000多个,这完全是浪费...
  • 卷积神经网络(Convolutional Neural Network, CNN)用于图像识别,在CNN 中有卷积层(Convolution层)、池化层(Pooling层...常见的卷积神经网络有两种: (1)Input -> 卷积层 -> 池化层 -> 卷积层 ->...
  • 之前的推送介绍了一些深度学习中一些...01卷积层卷积神经网络中最常见的是二维卷积层,常用于处理图像数据。在介绍卷积层之前,先介绍一下互相关运算,以二维互相关运算为例。二维互相关(cross-correlation)运算的输...
  • 卷积神经网络基础 本节我们介绍卷积神经网络的基础概念,主要是卷积层和池化层,并解释填充、步幅、输入通道和输出通道的含义。二维卷积层 本节介绍的是最常见的二维卷积层,常用于处理图像数据。二维互相关运算 二...
  • 正是靠着卷积和卷积神经网络深度学习才超越了几乎其他所有的机器学习手段。这期我们一起学习下深度学习中常见的卷积有哪些?1. 一般卷积卷积在数学上用通俗的话来说就是输入矩阵与卷积核(卷积核也是矩阵)进行对应...
  • 卷积神经网络基础 本节我们介绍卷积神经网络的基础概念,主要是卷积层和池化层,并解释填充、步幅、输入通道和输出通道的含义。 二维卷积层 本节介绍的是最常见的二维卷积层,常用于处理图像数据。 二维互相关...
  • 卷积神经网络基础 本节我们介绍卷积神经网络的基础概念,主要是卷积层和池化层,并解释填充、步幅、输入通道和输出通道的含义。 二维卷积层 本节介绍的是最常见的二维卷积层,常用于处理图像数据。 二维互相关运算 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 472
精华内容 188
关键字:

常见的深度卷积神经网络