深度学习利用篇-计算机视觉-图像分类[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 paddleimport numpy as npfrom 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 paddleimport numpy as npfrom 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中应用了一系列大小为3x3的小尺寸卷积核和池化层结构深度卷积神经网络,因为其构造简略、应用性极强而广受研究者欢送,尤其是它的网络结构设计办法,为构建深度神经网络提供了方向。
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 npimport paddle#from paddle.nn import Conv2D, MaxPool2D, BatchNorm, Linearfrom 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)中的设计形式,在每个3x3和5x5的卷积层之前,减少1x1的卷积层来管制输入通道数;在最大池化层前面减少1x1卷积层减小输入通道数。基于这一设计思维,造成了上图(b)中所示的构造。上面这段程序是Inception块的具体实现形式,能够对照图(b)和代码一起浏览。
提醒:
可能有读者会问,通过3x3的最大池化之后图像尺寸不会减小吗,为什么还能跟另外3个卷积输入的特色图进行拼接?这是因为池化操作能够指定窗口大小$k_h = k_w = 3$,stride=1和padding=1,输入特色图尺寸能够放弃不变。
Inception模块的具体实现如下代码所示:
#GoogLeNet模型代码import numpy as npimport paddlefrom 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 paddlefrom paddle import ParamAttrimport paddle.nn as nnimport paddle.nn.functional as Ffrom paddle.nn import Conv2D, BatchNorm, Linear, Dropoutfrom paddle.nn import AdaptiveAvgPool2D, MaxPool2D, AvgPool2Dfrom paddle.nn.initializer import Uniformimport 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_attrclass 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 catclass 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,那么卷积之后便能够失去雷同维度的特色,而后这些特色就能够间接拼接在一起了;
- 网络越到前面,特色越形象,而且每个特色所波及的感触野也更大了,因而随着层数的减少,3x3和5x5卷积的比例也要减少。然而,应用5x5的卷积核依然会带来微小的计算量。 为此,文章采纳1x1卷积核来进行降维。
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 paddlefrom paddle import ParamAttrimport paddle.nn as nnimport paddle.nn.functional as Ffrom paddle.nn import Conv2D, BatchNorm, Linear, Dropoutfrom paddle.nn import AdaptiveAvgPool2D, MaxPool2D, AvgPool2Dfrom paddle.nn.initializer import Uniformimport 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