1. 前言
本文使用飞桨(PaddlePaddle)复现卷积神经网络AlexNet。
本人全部文章请参见:博客文章导航目录
本文归属于:卷积神经网络复现系列
2. AlexNet模型结构
2012年,Alex Krizhevsky等人提出的AlexNet以很大优势获得了ImageNet比赛的冠军。这一成果极大的激发了产业界对神经网络的兴趣,开创了使用深度神经网络解决图像问题的途径。
AlexNet包含5层卷积、2层LRN(Local Response Normalization)和3个全连接层。除最后一个全连接层外,每个卷积层和全连接层激活函数均为ReLU。同时,AlexNet使用了Dropout抑制过拟合,模型具体结构如下图所示。(示意图图中不包含LRN层,具体参见模型复现代码)
AlexNet共包含8个模块,对输入227x227x3的图片依次进行如下处理:
- 11x11的96通道步长为4的卷积 + local_size为5的LRN +步长为2的3x3池化;
- 5x5的256通道padding为2group为2步长为1的卷积 + local_size为5的LRN + 步长为2的3x3池化;
- 3x3的384通道padding为1步长为1的卷积;
- 3x3的384通道padding为1步长为1的卷积;
- 3x3的256通道padding为1步长为1的卷积 + 步长为2的3x3池化;
- 4096个节点的全连接层 + 概率为0.5的Dropout;
- 4096个节点的全连接层 + 概率为0.5的Dropout;
- 节点数等于输出类别数的全连接输出层。
其中除输出层外,所有卷积层和全链接层激活函数均为ReLU。
3. AlexNet中的结构组件
3.1 ReLU激活函数
线性整流函数(Rectified Linear Unit, ReLU),又称修正线性单元,是一种人工神经网络中常用的激活函数。其数学表达式如下:
f
(
x
)
=
m
a
x
(
0
,
x
)
f(x) = max(0, x)
f(x)=max(0,x)
ReLU函数的导数在
x
x
x小于或等于0时等于0,在
x
x
x大于0时等于1.因此在自变量
x
x
x大于0时,神经网络上一层的梯度能够直接传导到下一层,避免了梯度消失现象。
ReLU函数把自变量小于0时的函数值置为0,假设某一层神经元的输出中大于0和小于0的个数差不多,则使用ReLU函数作为激活函数可以关闭一半左右的神经元,使之处于非激活状态。因此ReLU函数保证了每一次网络中只有少部分神经元的参数被更新,这样做可以促进深度神经网络的收敛。
3.2 最大池化(MaxPooling)
池化(Pooling):把邻近的像素作为一个“池子”来重新考虑。
最大池化(Max Pooling):对每一个邻近的像素组成的池子,选取最大值作为输出。
实践证明,相比平均池化(Average Pooling,将每一个邻近的像素组成的池子,将池子中所有像素点的平均值作为输出),最大池化能够促进网络收敛。原因是在最大池化下,梯度直接传到到最大值,而不是平均分布到池子中的每一个元素,这样每次更新的参数将会进一步减少。
深度学习实践发现有重叠的最大池化能够很好的克服过拟合问题,提升深度学习模型性能。可能的原因有两个:1. 最大池化相当于同时做了降采样和非线性操作;2. 池化层激活的神经元变少。
3.3 随机丢弃(Dropout)
为了避免系统参数更新过快导致过拟合,在利用训练样本更新参数时候,随机“丢弃”一定比例的神经元。被丢弃的神经元将不参加训练过程,这些神经元的输入和输出权重系数也不做更新,这样每次训练时,训练的网络架构都不一样,而这些不同的网络架构分享共通的权重系数。
假设每个不同的网络均存在一定的过拟合情况,而这些过拟合方向基本是随机的。综合多个网络结构,可以使得整体网络的过拟合情况在一定程度上相互抵消,是的整体网络效果更好。
3.4 局部响应归一化(LRN)
LRN对局部神经元的活动创建竞争机制,使得其中响应比较大的值变得相对更大,并抑制其他反馈较小的神经元,增强了模型的泛化能力。LRN通过在相邻卷积核生成的特征图之间引入竞争,从而有些本来在特征图中显著的特征更显著,而在相邻的其他特征图中的特征被抑制,这样让不同卷积核产生的特征之间的相关性变小。
深度学习实践确实证明LRN可以提高模型的泛化能力,但是提升的很少,甚至有一些研究者觉得LRN是一个“伪命题”,因而饱受争议,以至于后面不再使用。在PddlePaddle框架并没有实现LRN层(2021年11月3日更新:PaddlePaddle框架实现了LRN层,使用paddle.nn.LocalResponseNorm()即可创建LRN层,具体参见链接),本文为了还原AlexNet论文中提出的模型,参考了资料链接[4]中的实现方法。
4. AlexNet模型复现
使用飞桨(PaddlePaddle)复现AlexNet
,首先实现LRN
层。定义继承自paddle.nn.Layer
的LRN
类,在__init__
方法中初始化各模块,并在forward
函数中实现LRN
层计算流程。具体代码如下:
# -*- coding: utf-8 -*-
# @Time : 2021/8/8 10:51
# @Author : He Ruizhi
# @File : alexnet.py
# @Software: PyCharm
import paddle
class LRN(paddle.nn.Layer):
"""
目前该网络已经很少使用了,这里为了原生的AlexNet而实现
LRN实现参考链接:https://github.com/sloth2012/AlexNet/blob/master/AlexNet.ipynb
"""
def __init__(self, local_size=1, alpha=1.0, beta=0.75, bias=1.0, ACROSS_CHANNELS=False):
super(LRN, self).__init__()
self.alpha = paddle.to_tensor(alpha)
self.beta = paddle.to_tensor(beta)
self.bias = paddle.to_tensor(bias)
self.ACROSS_CHANNELS = ACROSS_CHANNELS
if self.ACROSS_CHANNELS:
self.average = paddle.nn.AvgPool3D(kernel_size=(local_size, 1, 1), stride=1,
padding=(int((local_size - 1.0) / 2), 0, 0))
else:
self.average = paddle.nn.AvgPool3D(kernel_size=local_size, stride=1,
padding=int((local_size - 1.0) / 2))
def forward(self, x):
if self.ACROSS_CHANNELS:
div = x.pow(2).unsqueeze(1)
div = self.average(div).squeeze(1)
div = div.multiply(self.alpha).add(self.bias).pow(self.beta)
else:
div = x.pow(2)
div = self.average(div)
div = div.multiply(self.alpha).add(self.bias).pow(self.beta)
x = x.divide(div)
return x
搭建AlexNet
,定义继承自paddle.nn.Layer
的AlexNet
类,在__init__
方法中定义各卷积、池化、LRN
和全连接层,在forward
函数中实现网络前向计算流程。具体代码如下:
class AlexNet(paddle.nn.Layer):
"""AlexNet模型搭建"""
def __init__(self, num_classes=1000):
super(AlexNet, self).__init__()
# 第一个模块:11x11的96通道步长为4的卷积 + local_size为5的LRN +步长为2的3x3池化
self.block1 = paddle.nn.Sequential(
paddle.nn.Conv2D(in_channels=3, out_channels=96, kernel_size=11, stride=4),
paddle.nn.ReLU(),
LRN(local_size=5, alpha=1e-4, beta=0.75, ACROSS_CHANNELS=True),
paddle.nn.MaxPool2D(kernel_size=3, stride=2)
)
# 第二个模块:5x5的256通道padding为2group为2步长为1的卷积 + local_size为5的LRN + 步长为2的3x3池化
self.block2 = paddle.nn.Sequential(
paddle.nn.Conv2D(in_channels=96, out_channels=256, kernel_size=5, groups=2, stride=1, padding=2),
paddle.nn.ReLU(),
LRN(local_size=5, alpha=1e-4, beta=0.75, ACROSS_CHANNELS=True),
paddle.nn.MaxPool2D(kernel_size=3, stride=2)
)
# 第三个模块:3x3的384通道padding为1步长为1的卷积
self.block3 = paddle.nn.Sequential(
paddle.nn.Conv2D(in_channels=256, out_channels=384, kernel_size=3, stride=1, padding=1),
paddle.nn.ReLU()
)
# 第四个模块:3x3的384通道padding为1步长为1的卷积
self.block4 = paddle.nn.Sequential(
paddle.nn.Conv2D(in_channels=384, out_channels=384, kernel_size=3, stride=1, padding=1),
paddle.nn.ReLU()
)
# 第五个模块:33x3的256通道padding为1步长为1的卷积 + 步长为2的3x3池化
self.block5 = paddle.nn.Sequential(
paddle.nn.Conv2D(in_channels=384, out_channels=256, kernel_size=3, stride=1, padding=1),
paddle.nn.ReLU(),
paddle.nn.MaxPool2D(kernel_size=3, stride=2)
)
self.flatten = paddle.nn.Flatten()
# 全连接层1
self.block6 = paddle.nn.Sequential(
paddle.nn.Linear(in_features=6*6*256, out_features=4096),
paddle.nn.ReLU(),
paddle.nn.Dropout(0.5)
)
# 全连接层2
self.block7 = paddle.nn.Sequential(
paddle.nn.Linear(in_features=4096, out_features=4096),
paddle.nn.ReLU(),
paddle.nn.Dropout(0.5)
)
# 输出层
self.out_fc = paddle.nn.Linear(in_features=4096, out_features=num_classes)
def forward(self, x):
# 实现前向计算流程
x = self.block1(x)
x = self.block2(x)
x = self.block3(x)
x = self.block4(x)
x = self.block5(x)
x = self.flatten(x)
x = self.block6(x)
x = self.block7(x)
self.out_fc(x)
return x
- 你也许会问我,搭建AlexNet为什么不在一个
paddle.nn.Sequential
中堆到底。原因有二:
- 将各个模块用
paddle.nn.Sequential
打包起来,而不将整个网络如此堆叠,可以更加清晰地查看和理解AlexNet网络结构;- 建议使用这种在
__init__
方法中初始化各模块,在forward
方法中实现前向计算流程的方式搭建神经网络,而不建议使用在paddle.nn.Sequential
中堆叠网络,因为第一种方法网络搭建灵活度更高。
- 类似
ReLU
、MaxPool2D
等无训练参数,且完全相同的模块可以只初始化一次,然后多次复用。甚至可以不用初始化,直接在forward
前向计算函数中通过paddle.nn.functional.*
调用相关函数。但是如果不在__init__
函数中多次显式地定义出来,则在paddle.summary
中无法清晰地看到模型结构。为了清晰地在paddle.summary
中清晰地看到模型结构,本系列文章均会多次显式地初始化ReLU
、MaxPool2D
等模块。
实例化AlexNet
,使用paddle.summary
函数打印模型结构信息:
if __name__ == '__main__':
model = AlexNet()
paddle.summary(model, input_size=(None, 3, 227, 227))
模型结构信息如下:
----------------------------------------------------------------------------
Layer (type) Input Shape Output Shape Param #
============================================================================
Conv2D-1 [[1, 3, 227, 227]] [1, 96, 55, 55] 34,944
ReLU-1 [[1, 96, 55, 55]] [1, 96, 55, 55] 0
AvgPool3D-1 [[1, 1, 96, 55, 55]] [1, 1, 96, 55, 55] 0
LRN-1 [[1, 96, 55, 55]] [1, 96, 55, 55] 0
MaxPool2D-1 [[1, 96, 55, 55]] [1, 96, 27, 27] 0
Conv2D-2 [[1, 96, 27, 27]] [1, 256, 27, 27] 307,456
ReLU-2 [[1, 256, 27, 27]] [1, 256, 27, 27] 0
AvgPool3D-2 [[1, 1, 256, 27, 27]] [1, 1, 256, 27, 27] 0
LRN-2 [[1, 256, 27, 27]] [1, 256, 27, 27] 0
MaxPool2D-2 [[1, 256, 27, 27]] [1, 256, 13, 13] 0
Conv2D-3 [[1, 256, 13, 13]] [1, 384, 13, 13] 885,120
ReLU-3 [[1, 384, 13, 13]] [1, 384, 13, 13] 0
Conv2D-4 [[1, 384, 13, 13]] [1, 384, 13, 13] 1,327,488
ReLU-4 [[1, 384, 13, 13]] [1, 384, 13, 13] 0
Conv2D-5 [[1, 384, 13, 13]] [1, 256, 13, 13] 884,992
ReLU-5 [[1, 256, 13, 13]] [1, 256, 13, 13] 0
MaxPool2D-3 [[1, 256, 13, 13]] [1, 256, 6, 6] 0
Flatten-1 [[1, 256, 6, 6]] [1, 9216] 0
Linear-1 [[1, 9216]] [1, 4096] 37,752,832
ReLU-6 [[1, 4096]] [1, 4096] 0
Dropout-1 [[1, 4096]] [1, 4096] 0
Linear-2 [[1, 4096]] [1, 4096] 16,781,312
ReLU-7 [[1, 4096]] [1, 4096] 0
Dropout-2 [[1, 4096]] [1, 4096] 0
Linear-3 [[1, 4096]] [1, 1000] 4,097,000
============================================================================
Total params: 62,071,144
Trainable params: 62,071,144
Non-trainable params: 0
----------------------------------------------------------------------------
Input size (MB): 0.59
Forward/backward pass size (MB): 18.40
Params size (MB): 236.78
Estimated Total Size (MB): 255.77
----------------------------------------------------------------------------
5. 参考资料链接
- https://aistudio.baidu.com/aistudio/projectdetail/2169490
- https://www.icourse163.org/learn/ZJU-1206573810?tid=1206902211#/learn/content?type=detail&id=1235286004&cid=1254982007
- https://papers.nips.cc/paper/2012/file/c399862d3b9d6b76c8436e924a68c45b-Paper.pdf
- https://github.com/sloth2012/AlexNet/blob/master/AlexNet.ipynb
- https://blog.csdn.net/qq_27825451/article/details/88745034
- https://zhuanlan.zhihu.com/p/29786939
- https://blog.csdn.net/sunbaigui/article/details/39938097