关于机器学习:深度学习应用篇计算机视觉图像分类2LeNetAlexNetVGG模型结构实现模型特点详细介绍

7次阅读

共计 23337 个字符,预计需要花费 59 分钟才能阅读完成。

深度学习利用篇 - 计算机视觉 - 图像分类[2]:LeNet、AlexNet、VGG、GoogleNet、DarkNet 模型构造、实现、模型特点具体介绍

1.LeNet(1998)

LeNet 是最早的卷积神经网络之一[1],其被提出用于辨认手写数字和机器印刷字符。1998 年,Yann LeCun 第一次将 LeNet 卷积神经网络应用到图像分类上,在手写数字辨认工作中获得了巨大成功。算法中论述了图像中像素特色之间的相关性可能由参数共享的卷积操作所提取,同时应用卷积、下采样(池化)和非线性映射这样的组合构造,是以后风行的大多数深度图像识别网络的根底。

1.1 LeNet 模型构造

LeNet 通过间断应用卷积和池化层的组合提取图像特色,其架构如 图 1 所示,这里展现的是用于 MNIST 手写体数字辨认工作中的 LeNet- 5 模型:

  • 第一模块:蕴含 5×5 的 6 通道卷积和 2×2 的池化。卷积提取图像中蕴含的特色模式(激活函数应用 Sigmoid),图像尺寸从 28 减小到 24。通过池化层能够升高输入特色图对空间地位的敏感性,图像尺寸减到 12。
  • 第二模块:和第一模块尺寸雷同,通道数由 6 减少为 16。卷积操作使图像尺寸减小到 8,通过池化后变成 4。
  • 第三模块:蕴含 4×4 的 120 通道卷积。卷积之后的图像尺寸减小到 1,然而通道数减少为 120。将通过第 3 次卷积提取到的特色图输出到全连贯层。第一个全连贯层的输入神经元的个数是 64,第二个全连贯层的输入神经元个数是分类标签的类别数,对于手写数字辨认的类别数是 10。而后应用 Softmax 激活函数即可计算出每个类别的预测概率。

提醒:

卷积层的输入特色图如何当作全连贯层的输出应用呢?

卷积层的输入数据格式是 $[N, C, H, W]$,在输出全连贯层的时候,会主动将数据拉平,

也就是对每个样本,主动将其转化为长度为 $K$ 的向量,

其中 $K = C \times H \times W$,一个 mini-batch 的数据维度变成了 $N\times K$ 的二维向量。


1.2 LeNet 模型实现

LeNet 网络的实现代码如下:

# 导入须要的包
import paddle
import numpy as np
from paddle.nn import Conv2D, MaxPool2D, Linear

## 组网
import paddle.nn.functional as F

#定义 LeNet 网络结构
class LeNet(paddle.nn.Layer):
    def __init__(self, num_classes=1):
        super(LeNet, self).__init__()
        # 创立卷积和池化层
        # 创立第 1 个卷积层
        self.conv1 = Conv2D(in_channels=1, out_channels=6, kernel_size=5)
        self.max_pool1 = MaxPool2D(kernel_size=2, stride=2)
        # 尺寸的逻辑:池化层未扭转通道数;以后通道数为 6
        # 创立第 2 个卷积层
        self.conv2 = Conv2D(in_channels=6, out_channels=16, kernel_size=5)
        self.max_pool2 = MaxPool2D(kernel_size=2, stride=2)
        # 创立第 3 个卷积层
        self.conv3 = Conv2D(in_channels=16, out_channels=120, kernel_size=4)
        # 尺寸的逻辑:输出层将数据拉平[B,C,H,W] -> [B,C*H*W]
        # 输出 size 是[28,28],通过三次卷积和两次池化之后,C*H* W 等于 120
        self.fc1 = Linear(in_features=120, out_features=64)
        # 创立全连贯层,第一个全连贯层的输入神经元个数为 64,第二个全连贯层输入神经元个数为分类标签的类别数
        self.fc2 = Linear(in_features=64, out_features=num_classes)
    #网络的前向计算过程
    def forward(self, x):
        x = self.conv1(x)
        # 每个卷积层应用 Sigmoid 激活函数,前面跟着一个 2x2 的池化
        x = F.sigmoid(x)
        x = self.max_pool1(x)
        x = F.sigmoid(x)
        x = self.conv2(x)
        x = self.max_pool2(x)
        x = self.conv3(x)
        #尺寸的逻辑:输出层将数据拉平[B,C,H,W] -> [B,C*H*W]
        x = paddle.reshape(x, [x.shape[0], -1])
        x = self.fc1(x)
        x = F.sigmoid(x)
        x = self.fc2(x)
        return x

1.3 LeNet 模型特点

  • 卷积网络应用一个 3 层的序列组合:卷积、下采样(池化)、非线性映射(LeNet- 5 最重要的个性,奠定了目前深层卷积网络的根底);
  • 应用卷积提取空间特色;
  • 应用映射的空间均值进行下采样;
  • 应用 $tanh$ 或 $sigmoid$ 进行非线性映射;
  • 多层神经网络(MLP)作为最终的分类器;
  • 层间的稠密连贯矩阵以防止微小的计算开销。

1.4 LeNet 模型指标

LeNet- 5 在 MNIST 手写数字辨认工作上进行了模型训练与测试,论文中提供的模型指标如 图 2 所示。应用 distortions 办法解决后,error rate 可能达到 0.8%。

  • 参考文献

[1] Gradient-based learn- ing applied to document recognition.

2.AlexNet(2012)

AlexNet[1]是 2012 年 ImageNet 比赛的冠军模型,其作者是神经网络畛域三巨头之一的 Hinton 和他的学生 Alex Krizhevsky。

AlexNet 以极大的劣势当先 2012 年 ImageNet 比赛的第二名,也因而给过后的学术界和工业界带来了很大的冲击。尔后,更多更深的神经网络相继被提出,比方优良的 VGG,GoogLeNet,ResNet 等。

2.1 AlexNet 模型构造

AlexNet 与此前的 LeNet 相比,具备更深的网络结构,蕴含 5 层卷积和 3 层全连贯,具体构造如 图 1 所示。

1)第一模块:对于 $224\times 224$ 的彩色图像,先用 96 个 $11\times 11\times 3$ 的卷积核查其进行卷积,提取图像中蕴含的特色模式(步长为 4,填充为 2,失去 96 个 $54\times 54$ 的卷积后果(特色图);而后以 $2\times 2$ 大小进行池化,失去了 96 个 $27\times 27$ 大小的特色图;

2)第二模块:蕴含 256 个 $5\times 5$ 的卷积和 $2\times 2$ 池化,卷积操作后图像尺寸不变,通过池化后,图像尺寸变成 $13\times 13$;

3)第三模块:蕴含 384 个 $3\times 3$ 的卷积,卷积操作后图像尺寸不变;

4)第四模块:蕴含 384 个 $3\times 3$ 的卷积,卷积操作后图像尺寸不变;

5)第五模块:蕴含 256 个 $3\times 3$ 的卷积和 $2\times 2$ 的池化,卷积操作后图像尺寸不变,通过池化后变成 256 个 $6\times 6$ 大小的特色图。

将通过第 5 次卷积提取到的特色图输出到全连贯层,失去原始图像的向量表白。前两个全连贯层的输入神经元的个数是 4096,第三个全连贯层的输入神经元个数是分类标签的类别数(ImageNet 较量的分类类别数是 1000),而后应用 Softmax 激活函数即可计算出每个类别的预测概率。

2.2 AlexNet 模型实现

基于 Paddle 框架,AlexNet 的具体实现的代码如下所示:

#-*- coding:utf-8 -*-

#导入须要的包
import paddle
import numpy as np
from paddle.nn import Conv2D, MaxPool2D, Linear, Dropout
## 组网
import paddle.nn.functional as F

#定义 AlexNet 网络结构
class AlexNet(paddle.nn.Layer):
    def __init__(self, num_classes=1):
        super(AlexNet, self).__init__()
        # AlexNet 与 LeNet 一样也会同时应用卷积和池化层提取图像特色
        # 与 LeNet 不同的是激活函数换成了‘relu’self.conv1 = Conv2D(in_channels=3, out_channels=96, kernel_size=11, stride=4, padding=5)
        self.max_pool1 = MaxPool2D(kernel_size=2, stride=2)
        self.conv2 = Conv2D(in_channels=96, out_channels=256, kernel_size=5, stride=1, padding=2)
        self.max_pool2 = MaxPool2D(kernel_size=2, stride=2)
        self.conv3 = Conv2D(in_channels=256, out_channels=384, kernel_size=3, stride=1, padding=1)
        self.conv4 = Conv2D(in_channels=384, out_channels=384, kernel_size=3, stride=1, padding=1)
        self.conv5 = Conv2D(in_channels=384, out_channels=256, kernel_size=3, stride=1, padding=1)
        self.max_pool5 = MaxPool2D(kernel_size=2, stride=2)

        self.fc1 = Linear(in_features=12544, out_features=4096)
        self.drop_ratio1 = 0.5
        self.drop1 = Dropout(self.drop_ratio1)
        self.fc2 = Linear(in_features=4096, out_features=4096)
        self.drop_ratio2 = 0.5
        self.drop2 = Dropout(self.drop_ratio2)
        self.fc3 = Linear(in_features=4096, out_features=num_classes)
    
    def forward(self, x):
        x = self.conv1(x)
        x = F.relu(x)
        x = self.max_pool1(x)
        x = self.conv2(x)
        x = F.relu(x)
        x = self.max_pool2(x)
        x = self.conv3(x)
        x = F.relu(x)
        x = self.conv4(x)
        x = F.relu(x)
        x = self.conv5(x)
        x = F.relu(x)
        x = self.max_pool5(x)
        x = paddle.reshape(x, [x.shape[0], -1])
        x = self.fc1(x)
        x = F.relu(x)
        # 在全连贯之后应用 dropout 克制过拟合
        x = self.drop1(x)
        x = self.fc2(x)
        x = F.relu(x)
        # 在全连贯之后应用 dropout 克制过拟合
        x = self.drop2(x)
        x = self.fc3(x)
        return x

2.3 AlexNet 模型特点

AlexNet 中蕴含了几个比拟新的技术点,也首次在 CNN 中胜利利用了 ReLU、Dropout 和 LRN 等 Trick。同时 AlexNet 也应用了 GPU 进行运算减速。

AlexNet 将 LeNet 的思维发扬光大,把 CNN 的基本原理利用到了很深很宽的网络中。AlexNet 次要应用到的新技术点如下:

  • 胜利应用 ReLU 作为 CNN 的激活函数,并验证其成果在较深的网络超过了 Sigmoid,胜利解决了 Sigmoid 在网络较深时的梯度弥散问题。尽管 ReLU 激活函数在很久之前就被提出了,然而直到 AlexNet 的呈现才将其发扬光大。
  • 训练时应用 Dropout 随机疏忽一部分神经元,以防止模型过拟合。Dropout 虽有独自的论文阐述,然而 AlexNet 将其实用化,通过实际证实了它的成果。在 AlexNet 中次要是最初几个全连贯层应用了 Dropout。
  • 在 CNN 中应用重叠的 最大池化。此前 CNN 中广泛应用均匀池化,AlexNet 全副应用最大池化,防止均匀池化的模糊化成果。并且 AlexNet 中提出退让长比池化核的尺寸小的观点,这样池化层的输入之间会有重叠和笼罩,晋升了特色的丰富性。
  • 提出了LRN 部分响应归一化层,对部分神经元的流动创立竞争机制,使得其中响应比拟大的值变得绝对更大,并克制其余反馈较小的神经元,加强了模型的泛化能力。
  • 应用 CUDA 减速深度卷积网络的训练,利用 GPU 弱小的并行计算能力,解决神经网络训练时大量的矩阵运算。AlexNet 应用了两块 GTX 580 GPU 进行训练,单个 GTX 580 只有 3GB 显存,这限度了可训练的网络的最大规模。因而作者将 AlexNet 散布在两个 GPU 上,在每个 GPU 的显存中贮存一半的神经元的参数。因为 GPU 之间通信不便,能够相互拜访显存,而不须要通过主机内存,所以同时应用多块 GPU 也是十分高效的。同时,AlexNet 的设计让 GPU 之间的通信只在网络的某些层进行,管制了通信的性能损耗。
  • 应用 数据加强,随机地从 $256\times 256$ 大小的原始图像中截取 $224\times 224$ 大小的区域(以及程度翻转的镜像),相当于减少了 $2\times (256-224)^2=2048$ 倍的数据量。如果没有数据加强,仅靠原始的数据量,参数泛滥的 CNN 会陷入过拟合中,应用了数据加强后能够大大加重过拟合,晋升泛化能力。进行预测时,则是取图片的四个角加两头共 5 个地位,并进行左右翻转,一共取得 10 张图片,对他们进行预测并对 10 次后果求均值。同时,AlexNet 论文中提到了会对图像的 RGB 数据进行 PCA 解决,并对主成分做一个标准差为 0.1 的高斯扰动,减少一些噪声,这个 Trick 能够让错误率再降落 1%。

2.4 AlexNet 模型指标

AlexNet 作为 ImageNet 2012 较量的冠军算法,在 ImageNet 测试集上达到了 15.3% 的 top-5 error rate,远远超过第二名(SIFT+FVs)的 26.2%。如 图 2 所示。

  • 参考文献

[1] Imagenet classification with deep convolutional neural networks.

3.VGG(2012)

随着 AlexNet 在 2012 年的 ImageNet 大赛上大放异彩后,卷积神经网络进入了飞速发展的阶段。2014 年,由 Simonyan 和 Zisserman 提出的 VGG[1]网络在 ImageNet 上获得了亚军的问题。VGG 的命名来源于论文作者所在的实验室 Visual Geometry Group,其对卷积神经网络进行了改进,摸索了网络深度与性能的关系,用更小的卷积核和更深的网络结构,获得了较好的成果,成为了 CNN 发展史上较为重要的一个网络。VGG 中应用了一系列大小为 3 ×3 的小尺寸卷积核和池化层结构深度卷积神经网络,因为其构造简略、应用性极强而广受研究者欢送,尤其是它的网络结构设计办法,为构建深度神经网络提供了方向。

3.1 VGG 模型构造

图 1 是 VGG-16 的网络结构示意图,有 13 层卷积和 3 层全连贯层。VGG 网络的设计严格应用 $3\times 3$ 的卷积层和池化层来提取特色,并在网络的最初面应用三层全连贯层,将最初一层全连贯层的输入作为分类的预测。

VGG 中还有一个显著特点:每次通过池化层(maxpooling)后特色图的尺寸减小一倍,而通道数增加一倍(最初一个池化层除外)。

在 VGG 中每层卷积将应用 ReLU 作为激活函数,在全连贯层之后增加 dropout 来克制过拟合。应用小的卷积核可能无效地缩小参数的个数,使得训练和测试变得更加无效。比方应用两层 $3\times 3$ 卷积层,能够失去感触野为 5 的特色图,而比应用 $5 \times 5$ 的卷积层须要更少的参数。因为卷积核比拟小,能够重叠更多的卷积层,加深网络的深度,这对于图像分类工作来说是无利的。VGG 模型的胜利证实了减少网络的深度,能够更好的学习图像中的特色模式。

3.2 VGG 模型实现

基于 Paddle 框架,VGG 的具体实现如下代码所示:

#-*- coding:utf-8 -*-

#VGG 模型代码
import numpy as np
import paddle
#from paddle.nn import Conv2D, MaxPool2D, BatchNorm, Linear
from paddle.nn import Conv2D, MaxPool2D, BatchNorm2D, Linear

#定义 vgg 网络
class VGG(paddle.nn.Layer):
    def __init__(self):
        super(VGG, self).__init__()

        in_channels = [3, 64, 128, 256, 512, 512]
        # 定义第一个卷积块,蕴含两个卷积
        self.conv1_1 = Conv2D(in_channels=in_channels[0], out_channels=in_channels[1], kernel_size=3, padding=1, stride=1)
        self.conv1_2 = Conv2D(in_channels=in_channels[1], out_channels=in_channels[1], kernel_size=3, padding=1, stride=1)
        # 定义第二个卷积块,蕴含两个卷积
        self.conv2_1 = Conv2D(in_channels=in_channels[1], out_channels=in_channels[2], kernel_size=3, padding=1, stride=1)
        self.conv2_2 = Conv2D(in_channels=in_channels[2], out_channels=in_channels[2], kernel_size=3, padding=1, stride=1)
        # 定义第三个卷积块,蕴含三个卷积
        self.conv3_1 = Conv2D(in_channels=in_channels[2], out_channels=in_channels[3], kernel_size=3, padding=1, stride=1)
        self.conv3_2 = Conv2D(in_channels=in_channels[3], out_channels=in_channels[3], kernel_size=3, padding=1, stride=1)
        self.conv3_3 = Conv2D(in_channels=in_channels[3], out_channels=in_channels[3], kernel_size=3, padding=1, stride=1)
        # 定义第四个卷积块,蕴含三个卷积
        self.conv4_1 = Conv2D(in_channels=in_channels[3], out_channels=in_channels[4], kernel_size=3, padding=1, stride=1)
        self.conv4_2 = Conv2D(in_channels=in_channels[4], out_channels=in_channels[4], kernel_size=3, padding=1, stride=1)
        self.conv4_3 = Conv2D(in_channels=in_channels[4], out_channels=in_channels[4], kernel_size=3, padding=1, stride=1)
        # 定义第五个卷积块,蕴含三个卷积
        self.conv5_1 = Conv2D(in_channels=in_channels[4], out_channels=in_channels[5], kernel_size=3, padding=1, stride=1)
        self.conv5_2 = Conv2D(in_channels=in_channels[5], out_channels=in_channels[5], kernel_size=3, padding=1, stride=1)
        self.conv5_3 = Conv2D(in_channels=in_channels[5], out_channels=in_channels[5], kernel_size=3, padding=1, stride=1)

        # 应用 Sequential 将全连贯层和 relu 组成一个线性构造(fc + relu)# 当输出为 224x224 时,通过五个卷积块和池化层后,特色维度变为[512x7x7]
        self.fc1 = paddle.nn.Sequential(paddle.nn.Linear(512 * 7 * 7, 4096), paddle.nn.ReLU())
        self.drop1_ratio = 0.5
        self.dropout1 = paddle.nn.Dropout(self.drop1_ratio, mode='upscale_in_train')
        # 应用 Sequential 将全连贯层和 relu 组成一个线性构造(fc + relu)self.fc2 = paddle.nn.Sequential(paddle.nn.Linear(4096, 4096), paddle.nn.ReLU())

        self.drop2_ratio = 0.5
        self.dropout2 = paddle.nn.Dropout(self.drop2_ratio, mode='upscale_in_train')
        self.fc3 = paddle.nn.Linear(4096, 1)

        self.relu = paddle.nn.ReLU()
        self.pool = MaxPool2D(stride=2, kernel_size=2)

    def forward(self, x):
        x = self.relu(self.conv1_1(x))
        x = self.relu(self.conv1_2(x))
        x = self.pool(x)

        x = self.relu(self.conv2_1(x))
        x = self.relu(self.conv2_2(x))
        x = self.pool(x)

        x = self.relu(self.conv3_1(x))
        x = self.relu(self.conv3_2(x))
        x = self.relu(self.conv3_3(x))
        x = self.pool(x)

        x = self.relu(self.conv4_1(x))
        x = self.relu(self.conv4_2(x))
        x = self.relu(self.conv4_3(x))
        x = self.pool(x)

        x = self.relu(self.conv5_1(x))
        x = self.relu(self.conv5_2(x))
        x = self.relu(self.conv5_3(x))
        x = self.pool(x)

        x = paddle.flatten(x, 1, -1)
        x = self.dropout1(self.relu(self.fc1(x)))
        x = self.dropout2(self.relu(self.fc2(x)))
        x = self.fc3(x)
        return x

3.3 VGG 模型特点

  • 整个网络都应用了同样大小的卷积核尺寸 $3\times3$ 和最大池化尺寸 $2\times2$。
  • $1\times1$ 卷积的意义次要在于线性变换,而输出通道数和输入通道数不变,没有产生降维。
  • 两个 $3\times3$ 的卷积层串联相当于 1 个 $5\times5$ 的卷积层,感触野大小为 $5\times5$。同样地,3 个 $3\times3$ 的卷积层串联的成果则相当于 1 个 $7\times7$ 的卷积层。这样的连贯形式使得网络参数量更小,而且多层的激活函数令网络对特色的学习能力更强。
  • VGGNet 在训练时有一个小技巧,先训练浅层的的简单网络 VGG11,再复用 VGG11 的权重来初始化 VGG13,如此重复训练并初始化 VGG19,可能使训练时收敛的速度更快。
  • 在训练过程中应用多尺度的变换对原始数据做数据加强,使得模型不易过拟合。

3.4 VGG 模型指标

VGG 在 2014 年的 ImageNet 较量上获得了亚军的好问题,具体指标如 图 2 所示。图 2 第一行为在 ImageNet 较量中的指标,测试集的 Error rate 达到了 7.3%,在论文中,作者对算法又进行了肯定的优化,最终能够达到 6.8% 的 Error rate。

  • 参考文献

[1] Very deep convolutional networks for large-scale image recognition.

4.GoogLeNet(2014)

GoogLeNet[1]是 2014 年 ImageNet 较量的冠军,它的次要特点是网络不仅有深度,还在横向上具备“宽度”。从名字 GoogLeNet 能够晓得这是来自谷歌工程师所设计的网络结构,而名字中 GoogLeNet 更是致敬了 LeNet。GoogLeNet 中最外围的局部是其外部子网络结构 Inception,该构造灵感来源于 NIN(Network In Network)。

4.1 GoogLeNet 模型构造

因为图像信息在空间尺寸上的微小差别,如何抉择适合的卷积核来提取特色就显得比拟艰难了。空间散布范畴更广的图像信息适宜用较大的卷积核来提取其特色;而空间散布范畴较小的图像信息则适宜用较小的卷积核来提取其特色。为了解决这个问题,GoogLeNet 提出了一种被称为 Inception 模块的计划。如 图 1 所示:


阐明:

  • Google 的钻研人员为了向 LeNet 致敬,顺便将模型命名为 GoogLeNet。
  • Inception 一词来源于电影《盗梦空间》(Inception)。

图 1(a)是 Inception 模块的设计思维,应用 3 个不同大小的卷积核查输出图片进行卷积操作,并附加最大池化,将这 4 个操作的输入沿着通道这一维度进行拼接,形成的输入特色图将会蕴含通过不同大小的卷积核提取进去的特色,从而达到捕获不同尺度信息的成果。Inception 模块采纳多通路 (multi-path) 的设计模式,每个支路应用不同大小的卷积核,最终输入特色图的通道数是每个支路输入通道数的总和,这将会导致输入通道数变得很大,尤其是应用多个 Inception 模块串联操作的时候,模型参数量会变得十分大。

为了减小参数量,Inception 模块应用了图 (b) 中的设计形式,在每个 3 ×3 和 5 ×5 的卷积层之前,减少 1 ×1 的卷积层来管制输入通道数;在最大池化层前面减少 1 ×1 卷积层减小输入通道数。基于这一设计思维,造成了上图 (b) 中所示的构造。上面这段程序是 Inception 块的具体实现形式,能够对照图 (b) 和代码一起浏览。


提醒:

可能有读者会问,通过 3 ×3 的最大池化之后图像尺寸不会减小吗,为什么还能跟另外 3 个卷积输入的特色图进行拼接?这是因为池化操作能够指定窗口大小 $k_h = k_w = 3$,stride= 1 和 padding=1,输入特色图尺寸能够放弃不变。


Inception 模块的具体实现如下代码所示:

#GoogLeNet 模型代码
import numpy as np
import paddle
from paddle.nn import Conv2D, MaxPool2D, AdaptiveAvgPool2D, Linear
## 组网
import paddle.nn.functional as F

#定义 Inception 块
class Inception(paddle.nn.Layer):
    def __init__(self, c0, c1, c2, c3, c4, **kwargs):
        '''
        Inception 模块的实现代码,c1, 图 (b) 中第一条支路 1x1 卷积的输入通道数,数据类型是整数
        c2, 图 (b) 中第二条支路卷积的输入通道数,数据类型是 tuple 或 list, 
               其中 c2[0]是 1x1 卷积的输入通道数,c2[1]是 3x3
        c3, 图 (b) 中第三条支路卷积的输入通道数,数据类型是 tuple 或 list, 
               其中 c3[0]是 1x1 卷积的输入通道数,c3[1]是 3x3
        c4, 图 (b) 中第一条支路 1x1 卷积的输入通道数,数据类型是整数
        '''
        super(Inception, self).__init__()
        # 顺次创立 Inception 块每条支路上应用到的操作
        self.p1_1 = Conv2D(in_channels=c0,out_channels=c1, kernel_size=1)
        self.p2_1 = Conv2D(in_channels=c0,out_channels=c2[0], kernel_size=1)
        self.p2_2 = Conv2D(in_channels=c2[0],out_channels=c2[1], kernel_size=3, padding=1)
        self.p3_1 = Conv2D(in_channels=c0,out_channels=c3[0], kernel_size=1)
        self.p3_2 = Conv2D(in_channels=c3[0],out_channels=c3[1], kernel_size=5, padding=2)
        self.p4_1 = MaxPool2D(kernel_size=3, stride=1, padding=1)
        self.p4_2 = Conv2D(in_channels=c0,out_channels=c4, kernel_size=1)

    def forward(self, x):
        # 支路 1 只蕴含一个 1x1 卷积
        p1 = F.relu(self.p1_1(x))
        # 支路 2 蕴含 1x1 卷积 + 3x3 卷积
        p2 = F.relu(self.p2_2(F.relu(self.p2_1(x))))
        # 支路 3 蕴含 1x1 卷积 + 5x5 卷积
        p3 = F.relu(self.p3_2(F.relu(self.p3_1(x))))
        # 支路 4 蕴含 最大池化和 1x1 卷积
        p4 = F.relu(self.p4_2(self.p4_1(x)))
        # 将每个支路的输入特色图拼接在一起作为最终的输入后果
        return paddle.concat([p1, p2, p3, p4], axis=1)

GoogLeNet 的架构如 图 2 所示,在主体卷积局部中应用 5 个模块(block),每个模块之间应用步幅为 2 的 3 ×3 最大池化层来减小输入高宽。

  • 第一模块应用一个 64 通道的 7 × 7 卷积层。
  • 第二模块应用 2 个卷积层: 首先是 64 通道的 1 × 1 卷积层,而后是将通道增大 3 倍的 3 × 3 卷积层。
  • 第三模块串联 2 个残缺的 Inception 块。
  • 第四模块串联了 5 个 Inception 块。
  • 第五模块串联了 2 个 Inception 块。
  • 第五模块的前面紧跟输入层,应用全局均匀池化层来将每个通道的高和宽变成 1,最初接上一个输入个数为标签类别数的全连贯层。

阐明:
在原作者的论文中增加了图中所示的 softmax1 和 softmax2 两个辅助分类器,如下图所示,训练时将三个分类器的损失函数进行加权求和,以缓解梯度隐没景象。

4.2 GoogLeNet 模型实现

GoogLeNet 的具体实现如下代码所示:

#GoogLeNet 模型代码
import paddle
from paddle import ParamAttr
import paddle.nn as nn
import paddle.nn.functional as F
from paddle.nn import Conv2D, BatchNorm, Linear, Dropout
from paddle.nn import AdaptiveAvgPool2D, MaxPool2D, AvgPool2D
from paddle.nn.initializer import Uniform
import math

#全连贯层参数初始化
def xavier(channels, filter_size, name):
    stdv = (3.0 / (filter_size**2 * channels))**0.5
    param_attr = ParamAttr(initializer=Uniform(-stdv, stdv), name=name + "_weights")
    return param_attr


class ConvLayer(nn.Layer):
    def __init__(self,
                 num_channels,
                 num_filters,
                 filter_size,
                 stride=1,
                 groups=1,
                 act=None,
                 name=None):
        super(ConvLayer, self).__init__()

        self._conv = Conv2D(
            in_channels=num_channels,
            out_channels=num_filters,
            kernel_size=filter_size,
            stride=stride,
            padding=(filter_size - 1) // 2,
            groups=groups,
            weight_attr=ParamAttr(name=name + "_weights"),
            bias_attr=False)

    def forward(self, inputs):
        y = self._conv(inputs)
        return y

#定义 Inception 块
class Inception(nn.Layer):
    def __init__(self,
                 input_channels,
                 output_channels,
                 filter1,
                 filter3R,
                 filter3,
                 filter5R,
                 filter5,
                 proj,
                 name=None):
          '''
        Inception 模块的实现代码,c1, 图 (b) 中第一条支路 1x1 卷积的输入通道数,数据类型是整数
        c2, 图 (b) 中第二条支路卷积的输入通道数,数据类型是 tuple 或 list, 
               其中 c2[0]是 1x1 卷积的输入通道数,c2[1]是 3x3
        c3, 图 (b) 中第三条支路卷积的输入通道数,数据类型是 tuple 或 list, 
               其中 c3[0]是 1x1 卷积的输入通道数,c3[1]是 3x3
        c4, 图 (b) 中第一条支路 1x1 卷积的输入通道数,数据类型是整数
        '''
        super(Inception, self).__init__()
                # 顺次创立 Inception 块每条支路上应用到的操作
        self._conv1 = ConvLayer(input_channels, filter1, 1, name="inception_" + name + "_1x1")
        self._conv3r = ConvLayer(input_channels, filter3R, 1, name="inception_" + name + "_3x3_reduce")
        self._conv3 = ConvLayer(filter3R, filter3, 3, name="inception_" + name + "_3x3")
        self._conv5r = ConvLayer(input_channels, filter5R, 1, name="inception_" + name + "_5x5_reduce")
        self._conv5 = ConvLayer(filter5R, filter5, 5, name="inception_" + name + "_5x5")
        self._pool = MaxPool2D(kernel_size=3, stride=1, padding=1)
        self._convprj = ConvLayer(input_channels, proj, 1, name="inception_" + name + "_3x3_proj")

    def forward(self, inputs):
          # 支路 1 只蕴含一个 1x1 卷积
        conv1 = self._conv1(inputs)
                # 支路 2 蕴含 1x1 卷积 + 3x3 卷积
        conv3r = self._conv3r(inputs)
        conv3 = self._conv3(conv3r)
                # 支路 3 蕴含 1x1 卷积 + 5x5 卷积
        conv5r = self._conv5r(inputs)
        conv5 = self._conv5(conv5r)
                # 支路 4 蕴含 最大池化和 1x1 卷积
        pool = self._pool(inputs)
        convprj = self._convprj(pool)
                # 将每个支路的输入特色图拼接在一起作为最终的输入后果
        cat = paddle.concat([conv1, conv3, conv5, convprj], axis=1)
        cat = F.relu(cat)
        return cat


class GoogLeNet(nn.Layer):
    def __init__(self, class_dim=1000):
        super(GoogLeNetDY, self).__init__()
        # GoogLeNet 蕴含五个模块,每个模块前面紧跟一个池化层
        # 第一个模块蕴含 1 个卷积层
        self._conv = ConvLayer(3, 64, 7, 2, name="conv1")
        # 3x3 最大池化
        self._pool = MaxPool2D(kernel_size=3, stride=2)
        # 第二个模块蕴含 2 个卷积层
        self._conv_1 = ConvLayer(64, 64, 1, name="conv2_1x1")
        self._conv_2 = ConvLayer(64, 192, 3, name="conv2_3x3")
                # 第三个模块蕴含 2 个 Inception 块
        self._ince3a = Inception(192, 192, 64, 96, 128, 16, 32, 32, name="ince3a")
        self._ince3b = Inception(256, 256, 128, 128, 192, 32, 96, 64, name="ince3b")
                # 第四个模块蕴含 5 个 Inception 块
        self._ince4a = Inception(480, 480, 192, 96, 208, 16, 48, 64, name="ince4a")
        self._ince4b = Inception(512, 512, 160, 112, 224, 24, 64, 64, name="ince4b")
        self._ince4c = Inception(512, 512, 128, 128, 256, 24, 64, 64, name="ince4c")
        self._ince4d = Inception(512, 512, 112, 144, 288, 32, 64, 64, name="ince4d")
        self._ince4e = Inception(528, 528, 256, 160, 320, 32, 128, 128, name="ince4e")
                # 第五个模块蕴含 2 个 Inception 块
        self._ince5a = Inception(832, 832, 256, 160, 320, 32, 128, 128, name="ince5a")
        self._ince5b = Inception(832, 832, 384, 192, 384, 48, 128, 128, name="ince5b")
                # 全局池化
        self._pool_5 = AvgPool2D(kernel_size=7, stride=7)

        self._drop = Dropout(p=0.4, mode="downscale_in_infer")
        self._fc_out = Linear(
            1024,
            class_dim,
            weight_attr=xavier(1024, 1, "out"),
            bias_attr=ParamAttr(name="out_offset"))
        self._pool_o1 = AvgPool2D(kernel_size=5, stride=3)
        self._conv_o1 = ConvLayer(512, 128, 1, name="conv_o1")
        self._fc_o1 = Linear(
            1152,
            1024,
            weight_attr=xavier(2048, 1, "fc_o1"),
            bias_attr=ParamAttr(name="fc_o1_offset"))
        self._drop_o1 = Dropout(p=0.7, mode="downscale_in_infer")
        self._out1 = Linear(
            1024,
            class_dim,
            weight_attr=xavier(1024, 1, "out1"),
            bias_attr=ParamAttr(name="out1_offset"))
        self._pool_o2 = AvgPool2D(kernel_size=5, stride=3)
        self._conv_o2 = ConvLayer(528, 128, 1, name="conv_o2")
        self._fc_o2 = Linear(
            1152,
            1024,
            weight_attr=xavier(2048, 1, "fc_o2"),
            bias_attr=ParamAttr(name="fc_o2_offset"))
        self._drop_o2 = Dropout(p=0.7, mode="downscale_in_infer")
        self._out2 = Linear(
            1024,
            class_dim,
            weight_attr=xavier(1024, 1, "out2"),
            bias_attr=ParamAttr(name="out2_offset"))

    def forward(self, inputs):
        x = self._conv(inputs)
        x = self._pool(x)
        x = self._conv_1(x)
        x = self._conv_2(x)
        x = self._pool(x)

        x = self._ince3a(x)
        x = self._ince3b(x)
        x = self._pool(x)

        ince4a = self._ince4a(x)
        x = self._ince4b(ince4a)
        x = self._ince4c(x)
        ince4d = self._ince4d(x)
        x = self._ince4e(ince4d)
        x = self._pool(x)

        x = self._ince5a(x)
        ince5b = self._ince5b(x)

        x = self._pool_5(ince5b)
        x = self._drop(x)
        x = paddle.squeeze(x, axis=[2, 3])
        out = self._fc_out(x)

        x = self._pool_o1(ince4a)
        x = self._conv_o1(x)
        x = paddle.flatten(x, start_axis=1, stop_axis=-1)
        x = self._fc_o1(x)
        x = F.relu(x)
        x = self._drop_o1(x)
        out1 = self._out1(x)

        x = self._pool_o2(ince4d)
        x = self._conv_o2(x)
        x = paddle.flatten(x, start_axis=1, stop_axis=-1)
        x = self._fc_o2(x)
        x = self._drop_o2(x)
        out2 = self._out2(x)
        return [out, out1, out2]

4.3 GoogLeNet 模型特色

  • 采纳不同大小的卷积核意味着不同大小的感触野,最初通过拼接实现不同尺度特色的交融;
  • 之所以卷积核大小采纳 1、3 和 5,次要是为了不便对齐。设定卷积步长 stride= 1 之后,只有别离设定 pad=0、1、2,那么卷积之后便能够失去雷同维度的特色,而后这些特色就能够间接拼接在一起了;
  • 网络越到前面,特色越形象,而且每个特色所波及的感触野也更大了,因而随着层数的减少,3×3 和 5 ×5 卷积的比例也要减少。然而,应用 5 ×5 的卷积核依然会带来微小的计算量。为此,文章采纳 1 ×1 卷积核来进行降维。

4.4 GoogLeNet 模型指标

GoogLeNet 在 2014 年的 ImageNet 较量上获得了冠军的好问题,具体指标如 图 3 所示。在测试集上 Error rate 达到了 6.67%。

  • 参考文献

[1] Going deeper with convolutions.

5.DarkNet(YOLOv2、3)

在指标检测畛域的 YOLO 系列算法中,作者为了达到更好的分类成果,本人设置并训练了 DarkNet 网络作为骨干网络。其中,YOLOv2[1]首次提出 DarkNet 网络,因为其具备 19 个卷积层,所以也称之为 DarkNet19。起初在 YOLOv3[2]中,作者持续排汇了以后优良算法的思维,如残差网络和特色交融等,提出了具备 53 个卷积层的骨干网络 DarkNet53。作者在 ImageNet 上进行了试验,发现相较于 ResNet-152 和 ResNet-101,DarkNet53 在分类精度差不多的前提下,计算速度获得了当先。

5.1 DarkNet 模型构造

5.1.1 DarkNet19

DarkNet19 中,借鉴了许多优良算法的教训,比方:借鉴了 VGG 的思维,应用了较多的 $3\times 3$ 卷积,在每一次池化操作后,将通道数翻倍;借鉴了 network in network 的思维,应用全局均匀池化(global average pooling)做预测,并把 $1\times 1$ 的卷积核置于 $3\times 3$ 的卷积核之间,用来压缩特色;同时,应用了批归一化层稳固模型训练,减速收敛,并且起到正则化作用。DarkNet19 的网络结构如 图 1 所示。

DarkNet19 精度与 VGG 网络相当,但浮点运算量只有其 $\frac{1}{5}$ 左右,因而运算速度极快。

5.1.2 DarkNet53

DarkNet53 在之前的根底上,借鉴了 ResNet 的思维,在网络中大量应用了残差连贯,因而网络结构能够设计的很深,并且缓解了训练中梯度隐没的问题,使得模型更容易收敛。同时,应用步长为 2 的卷积层代替池化层实现降采样。DarkNet53 的网络结构如 图 2 所示。

思考到以后 Darknet19 网络应用频率较低,接下来次要针对 Darknet53 网络进行实现与解说。

5.2 DarkNet 模型实现

基于 Paddle 框架,DarkNet53 的具体实现的代码如下所示:

import paddle
from paddle import ParamAttr
import paddle.nn as nn
import paddle.nn.functional as F
from paddle.nn import Conv2D, BatchNorm, Linear, Dropout
from paddle.nn import AdaptiveAvgPool2D, MaxPool2D, AvgPool2D
from paddle.nn.initializer import Uniform
import math

#将卷积和批归一化封装为 ConvBNLayer,不便后续复用
class ConvBNLayer(nn.Layer):
    def __init__(self,
                 input_channels,
                 output_channels,
                 filter_size,
                 stride,
                 padding,
                 name=None):
          # 初始化函数
        super(ConvBNLayer, self).__init__()
                # 创立卷积层
        self._conv = Conv2D(
            in_channels=input_channels,
            out_channels=output_channels,
            kernel_size=filter_size,
            stride=stride,
            padding=padding,
            weight_attr=ParamAttr(name=name + ".conv.weights"),
            bias_attr=False)
                # 创立批归一化层
        bn_name = name + ".bn"
        self._bn = BatchNorm(
            num_channels=output_channels,
            act="relu",
            param_attr=ParamAttr(name=bn_name + ".scale"),
            bias_attr=ParamAttr(name=bn_name + ".offset"),
            moving_mean_name=bn_name + ".mean",
            moving_variance_name=bn_name + ".var")

    def forward(self, inputs):
          # 前向计算
        x = self._conv(inputs)
        x = self._bn(x)
        return x

#定义残差块
class BasicBlock(nn.Layer):
    def __init__(self, input_channels, output_channels, name=None):
          # 初始化函数
        super(BasicBlock, self).__init__()
                # 定义两个卷积层
        self._conv1 = ConvBNLayer(input_channels, output_channels, 1, 1, 0, name=name + ".0")
        self._conv2 = ConvBNLayer(output_channels, output_channels * 2, 3, 1, 1, name=name + ".1")

    def forward(self, inputs):
          # 前向计算
        x = self._conv1(inputs)
        x = self._conv2(x)
        # 将第二个卷积层的输入和最后的输出值相加
        return paddle.add(x=inputs, y=x)


class DarkNet53(nn.Layer):
    def __init__(self, class_dim=1000):
          # 初始化函数
        super(DarkNet, self).__init__()
                # DarkNet 每组残差块的个数,来自 DarkNet 的网络结构图
        self.stages = [1, 2, 8, 8, 4]
        # 第一层卷积
        self._conv1 = ConvBNLayer(3, 32, 3, 1, 1, name="yolo_input")
        # 下采样,应用 stride= 2 的卷积来实现
        self._conv2 = ConvBNLayer(32, 64, 3, 2, 1, name="yolo_input.downsample")
                # 增加各个层级的实现
        self._basic_block_01 = BasicBlock(64, 32, name="stage.0.0")
        # 下采样,应用 stride= 2 的卷积来实现
        self._downsample_0 = ConvBNLayer(64, 128, 3, 2, 1, name="stage.0.downsample")

        self._basic_block_11 = BasicBlock(128, 64, name="stage.1.0")
        self._basic_block_12 = BasicBlock(128, 64, name="stage.1.1")
        # 下采样,应用 stride= 2 的卷积来实现
        self._downsample_1 = ConvBNLayer(128, 256, 3, 2, 1, name="stage.1.downsample")

        self._basic_block_21 = BasicBlock(256, 128, name="stage.2.0")
        self._basic_block_22 = BasicBlock(256, 128, name="stage.2.1")
        self._basic_block_23 = BasicBlock(256, 128, name="stage.2.2")
        self._basic_block_24 = BasicBlock(256, 128, name="stage.2.3")
        self._basic_block_25 = BasicBlock(256, 128, name="stage.2.4")
        self._basic_block_26 = BasicBlock(256, 128, name="stage.2.5")
        self._basic_block_27 = BasicBlock(256, 128, name="stage.2.6")
        self._basic_block_28 = BasicBlock(256, 128, name="stage.2.7")
        # 下采样,应用 stride= 2 的卷积来实现
        self._downsample_2 = ConvBNLayer(256, 512, 3, 2, 1, name="stage.2.downsample")

        self._basic_block_31 = BasicBlock(512, 256, name="stage.3.0")
        self._basic_block_32 = BasicBlock(512, 256, name="stage.3.1")
        self._basic_block_33 = BasicBlock(512, 256, name="stage.3.2")
        self._basic_block_34 = BasicBlock(512, 256, name="stage.3.3")
        self._basic_block_35 = BasicBlock(512, 256, name="stage.3.4")
        self._basic_block_36 = BasicBlock(512, 256, name="stage.3.5")
        self._basic_block_37 = BasicBlock(512, 256, name="stage.3.6")
        self._basic_block_38 = BasicBlock(512, 256, name="stage.3.7")
        # 下采样,应用 stride= 2 的卷积来实现
        self._downsample_3 = ConvBNLayer(512, 1024, 3, 2, 1, name="stage.3.downsample")

        self._basic_block_41 = BasicBlock(1024, 512, name="stage.4.0")
        self._basic_block_42 = BasicBlock(1024, 512, name="stage.4.1")
        self._basic_block_43 = BasicBlock(1024, 512, name="stage.4.2")
        self._basic_block_44 = BasicBlock(1024, 512, name="stage.4.3")
                # 自适应均匀池化
        self._pool = AdaptiveAvgPool2D(1)

        stdv = 1.0 / math.sqrt(1024.0)
        # 分类层
        self._out = Linear(
            1024,
            class_dim,
            weight_attr=ParamAttr(name="fc_weights", initializer=Uniform(-stdv, stdv)),
            bias_attr=ParamAttr(name="fc_offset"))

    def forward(self, inputs):
        x = self._conv1(inputs)
        x = self._conv2(x)

        x = self._basic_block_01(x)
        x = self._downsample_0(x)

        x = self._basic_block_11(x)
        x = self._basic_block_12(x)
        x = self._downsample_1(x)

        x = self._basic_block_21(x)
        x = self._basic_block_22(x)
        x = self._basic_block_23(x)
        x = self._basic_block_24(x)
        x = self._basic_block_25(x)
        x = self._basic_block_26(x)
        x = self._basic_block_27(x)
        x = self._basic_block_28(x)
        x = self._downsample_2(x)

        x = self._basic_block_31(x)
        x = self._basic_block_32(x)
        x = self._basic_block_33(x)
        x = self._basic_block_34(x)
        x = self._basic_block_35(x)
        x = self._basic_block_36(x)
        x = self._basic_block_37(x)
        x = self._basic_block_38(x)
        x = self._downsample_3(x)

        x = self._basic_block_41(x)
        x = self._basic_block_42(x)
        x = self._basic_block_43(x)
        x = self._basic_block_44(x)

        x = self._pool(x)
        x = paddle.squeeze(x, axis=[2, 3])
        x = self._out(x)
        return x

5.3 DarkNet 模型特点

  • DarkNet53 模型应用了大量的残差连贯,缓解了训练中梯度隐没的问题,使得模型更容易收敛。
  • DarkNet53 模型应用步长为 2 的卷积层代替池化层实现降采样。

5.4 DarkNet 模型指标

在 YOLOv3 论文中,作者在 ImageNet 数据集上比照了 DarkNet 网络与 ResNet 网络的精度及速度,如图 3 所示。能够看到 DarkNet53 的 top- 5 准确率能够达到 93.8%,同时速度也显著超过了 ResNet101 和 ResNet152。

更多文章请关注公重号:汀丶人工智能

  • 参考文献

[1] YOLO9000: Better, Faster, Stronger

[2] YOLOv3: An Incremental Improvement

正文完
 0