1. 前言
本文使用飞桨(PaddlePaddle)复现卷积神经网络VGGNet。
本人全部文章请参见:博客文章导航目录
本文归属于:卷积神经网络复现系列
前文:AlexNet
2. VGGNet模型结构
VGGNet是当前最流行的CNN模型之一,由Simonyan和Zisserman在2014年提出,其命名来源于论文作者所在的实验室Visual Geometry Group。VGGNet通过使用一些列大小为3x3的小尺寸卷积核的卷积层和池化层构建深度卷积神经网络,并在深度学习图像分类实践中取得了比较好的效果。VGGNet模型结构简单,至今仍具有较强的应用性,同时其网络结构设计方法和思想也仍具有重要参考价值。
VGGNet系列模型中比较流行的有VGG11、VGG13、VGG16、VGG19,VGGNet模型均由5个卷积模块和3个全连接层组成,其严格使用核大小为3x3步长1的卷积层和核大小为2x2步长2的池化层来提取特征,并在网络的最后面使用三层全连接层,将最后一层全连接层的输出作为分类的预测。 在VGGNet中每层卷积均使用ReLU作为激活函数,在全连接层之后添加dropout来抑制过拟合。不同系列的VGGNet模型各卷积模块输入和输出通道数均相同,只有卷积模块中包含的卷积层数不同,其中VGG16模型结构如下图所示。
VGG16包含13个卷积层和3个全连接层,共16层。其5个卷积模块中卷积层数量分别是
[
2
,
2
,
3
,
3
,
3
]
[2,2,3,3,3]
[2,2,3,3,3]。VGG11的5个卷积模块中卷积层数量分别是
[
1
,
1
,
2
,
2
,
2
]
[1,1,2,2,2]
[1,1,2,2,2],VGG13中分别是
[
2
,
2
,
2
,
2
,
2
]
[2,2,2,2,2]
[2,2,2,2,2],VGG19中分别是
[
2
,
2
,
4
,
4
,
4
]
[2,2,4,4,4]
[2,2,4,4,4]。
VGGNet模型的成功证明了增加网络的深度,可以更好的学习图像中的特征模式,从而在图像分类等任务中取得更好的效果。
3. VGGNet核心思想
VGGNet使用多个3x3卷积核叠加代替更大的卷积核,从而减少卷积层参数或增大感受野(Receptive Field)。
感受野:卷积神经网络每一层输出的特征图(feature map)上的像素点在输入图片上映射的区域大小。
如果不能理解为什么叫【感受野】这么怪的名字,请查看这个词【视野】。
两层3x3的卷积核进行步长为1的卷积操作之后,输出特征图上每一个点的感受野是5x5,与一层5x5大小的卷积核进行卷积操作之后得到的特征图上一个点的感受野一致。但是两层3x3的卷积参数共3x3x2=18个,而一层5x5的卷积参数共5x5=25个(均在只有一个输出通道情况下)。因此使用更小的卷积核叠加代替大的卷积核,可以减少模型参数。
如果使用两层3x3的卷积核进行步长为2的卷积操作,输出特征图上每一个点的感受野是15x15。具体计算感受野的公式如下:
R
F
i
=
(
R
F
i
−
1
)
×
S
t
r
i
d
e
i
−
1
+
K
_
S
i
z
e
i
RF_i=(RF_i-1)\times Stride_{i-1}+K\_Size_i
RFi=(RFi−1)×Stridei−1+K_Sizei
- R F i RF_i RFi:第 i i i层的感受野
- S t r i d e i Stride_i Stridei:第 i i i层的卷积核移动步长
- K _ S i z e i K\_Size_i K_Sizei:第 i i i层的卷积核大小
4. VGGNet模型复现
使用飞桨(PaddlePaddle)复现VGGNet,首先定义继承自paddle.nn.Layer
的VGGBlock
类,在__init__
方法中根据传入参数定义各卷积、池化层,在forward
函数中实现VGGBlock
前向计算流程。具体代码如下:
# -*- coding: utf-8 -*-
# @Time : 2021/8/8 21:33
# @Author : He Ruizhi
# @File : vggnet.py
# @Software: PyCharm
import paddle
# VGGNet模型卷积层config
# key: VGGNet网络深度
# value: 每个VGGBlock中卷积层数
VGG_CONFIG = {
11: [1, 1, 2, 2, 2],
13: [2, 2, 2, 2, 2],
16: [2, 2, 3, 3, 3],
19: [2, 2, 4, 4, 4]
}
class VGGBlock(paddle.nn.Layer):
"""
VGGNet模型由多个VGGBlock + 多层全连接层组成,每个VGGBlock只有卷积层层数不同,其余均相同
一个VGGBlock中卷积层输出通道数均相同
input_channels: VGGBlock输入通道数
output_channels: VGGBlock各卷积层之间的输入及输出通道数
groups: VGGBlock包含的卷积层数
"""
def __init__(self, input_channels, output_channels, groups):
super(VGGBlock, self).__init__()
self.groups = groups
# 类似ReLU、MaxPool2D等无训练参数,且完全相同的模块可以只初始化一次,然后多次复用
# 甚至可以不用初始化,直接在forward前向计算函数中通过paddle.nn.functional.*调用相关函数
# 但是如果不在__init__函数中多次显式地定义出来,则在paddle.summary中无法清晰地看到模型结构
# 为了清晰地在paddle.summary中清晰地看到模型结构,这里会多次显式第初始化ReLU、MaxPool2D等模块
self.conv1 = paddle.nn.Conv2D(in_channels=input_channels, out_channels=output_channels,
kernel_size=3, stride=1, padding=1, bias_attr=False)
self.relu1 = paddle.nn.ReLU()
if groups in [2, 3, 4]:
self.conv2 = paddle.nn.Conv2D(in_channels=output_channels, out_channels=output_channels,
kernel_size=3, stride=1, padding=1, bias_attr=False)
self.relu2 = paddle.nn.ReLU()
if groups in [3, 4]:
self.conv3 = paddle.nn.Conv2D(in_channels=output_channels, out_channels=output_channels,
kernel_size=3, stride=1, padding=1, bias_attr=False)
self.relu3 = paddle.nn.ReLU()
if groups == 4:
self.conv4 = paddle.nn.Conv2D(in_channels=output_channels, out_channels=output_channels,
kernel_size=3, stride=1, padding=1, bias_attr=False)
self.relu4 = paddle.nn.ReLU()
self.max_pool = paddle.nn.MaxPool2D(kernel_size=2, stride=2, padding=0)
def forward(self, x):
x = self.relu1(self.conv1(x))
if self.groups in [2, 3, 4]:
x = self.relu2(self.conv2(x))
if self.groups in [3, 4]:
x = self.relu3(self.conv3(x))
if self.groups == 4:
x = self.relu4(self.conv4(x))
x = self.max_pool(x)
return x
设置input_channels=4
、output_channels=64
、groups=4
,实例化VGGBlock
对象,并使用paddle.summary
查看VGGBlock
结构:
vgg_block = VGGBlock(3, 64, 4)
paddle.summary(vgg_block, input_size=(None, 3, 224, 224))
打印VGGBlock
结构信息如下:
---------------------------------------------------------------------------
Layer (type) Input Shape Output Shape Param #
===========================================================================
Conv2D-1 [[1, 3, 224, 224]] [1, 64, 224, 224] 1,728
ReLU-1 [[1, 64, 224, 224]] [1, 64, 224, 224] 0
Conv2D-2 [[1, 64, 224, 224]] [1, 64, 224, 224] 36,864
ReLU-2 [[1, 64, 224, 224]] [1, 64, 224, 224] 0
Conv2D-3 [[1, 64, 224, 224]] [1, 64, 224, 224] 36,864
ReLU-3 [[1, 64, 224, 224]] [1, 64, 224, 224] 0
Conv2D-4 [[1, 64, 224, 224]] [1, 64, 224, 224] 36,864
ReLU-4 [[1, 64, 224, 224]] [1, 64, 224, 224] 0
MaxPool2D-1 [[1, 64, 224, 224]] [1, 64, 112, 112] 0
===========================================================================
Total params: 112,320
Trainable params: 112,320
Non-trainable params: 0
---------------------------------------------------------------------------
Input size (MB): 0.57
Forward/backward pass size (MB): 202.12
Params size (MB): 0.43
Estimated Total Size (MB): 203.13
---------------------------------------------------------------------------
定义继承自paddle.nn.Layer
的VGGNet
类,在__init__
方法中定义卷积模块和全连接层模块,在forward
函数中实现网络前向计算流程。具体代码如下:
class VGGNet(paddle.nn.Layer):
"""
VGGNet
config: VGGNet config,定义VGGNet种类
num_classes: 输出分类数
"""
def __init__(self, config, num_classes=1000):
super(VGGNet, self).__init__()
self.conv_block = paddle.nn.Sequential(
VGGBlock(3, 64, config[0]),
VGGBlock(64, 128, config[1]),
VGGBlock(128, 256, config[2]),
VGGBlock(256, 512, config[3]),
VGGBlock(512, 512, config[4])
)
self.flatten = paddle.nn.Flatten()
self.fc_block = paddle.nn.Sequential(
paddle.nn.Linear(7*7*512, 4096),
paddle.nn.ReLU(),
paddle.nn.Dropout(0.5),
paddle.nn.Linear(4096, 4096),
paddle.nn.ReLU(),
paddle.nn.Dropout(0.5),
paddle.nn.Linear(4096, num_classes)
)
def forward(self, x):
x = self.conv_block(x)
x = self.flatten(x)
x = self.fc_block(x)
return x
设置config=VGG_CONFIG[16]
,创建VGG16
模型对象,并使用paddle.summary
查看VGG16
结构信息:
if __name__ == '__main__':
# vgg_block = VGGBlock(3, 64, 4)
# paddle.summary(vgg_block, input_size=(None, 3, 224, 224))
vgg16 = VGGNet(VGG_CONFIG[16])
paddle.summary(vgg16, input_size=(None, 3, 224, 224))
打印VGG16
模型结构信息如下:
---------------------------------------------------------------------------
Layer (type) Input Shape Output Shape Param #
===========================================================================
Conv2D-1 [[1, 3, 224, 224]] [1, 64, 224, 224] 1,728
ReLU-1 [[1, 64, 224, 224]] [1, 64, 224, 224] 0
Conv2D-2 [[1, 64, 224, 224]] [1, 64, 224, 224] 36,864
ReLU-2 [[1, 64, 224, 224]] [1, 64, 224, 224] 0
MaxPool2D-1 [[1, 64, 224, 224]] [1, 64, 112, 112] 0
VGGBlock-1 [[1, 3, 224, 224]] [1, 64, 112, 112] 0
Conv2D-3 [[1, 64, 112, 112]] [1, 128, 112, 112] 73,728
ReLU-3 [[1, 128, 112, 112]] [1, 128, 112, 112] 0
Conv2D-4 [[1, 128, 112, 112]] [1, 128, 112, 112] 147,456
ReLU-4 [[1, 128, 112, 112]] [1, 128, 112, 112] 0
MaxPool2D-2 [[1, 128, 112, 112]] [1, 128, 56, 56] 0
VGGBlock-2 [[1, 64, 112, 112]] [1, 128, 56, 56] 0
Conv2D-5 [[1, 128, 56, 56]] [1, 256, 56, 56] 294,912
ReLU-5 [[1, 256, 56, 56]] [1, 256, 56, 56] 0
Conv2D-6 [[1, 256, 56, 56]] [1, 256, 56, 56] 589,824
ReLU-6 [[1, 256, 56, 56]] [1, 256, 56, 56] 0
Conv2D-7 [[1, 256, 56, 56]] [1, 256, 56, 56] 589,824
ReLU-7 [[1, 256, 56, 56]] [1, 256, 56, 56] 0
MaxPool2D-3 [[1, 256, 56, 56]] [1, 256, 28, 28] 0
VGGBlock-3 [[1, 128, 56, 56]] [1, 256, 28, 28] 0
Conv2D-8 [[1, 256, 28, 28]] [1, 512, 28, 28] 1,179,648
ReLU-8 [[1, 512, 28, 28]] [1, 512, 28, 28] 0
Conv2D-9 [[1, 512, 28, 28]] [1, 512, 28, 28] 2,359,296
ReLU-9 [[1, 512, 28, 28]] [1, 512, 28, 28] 0
Conv2D-10 [[1, 512, 28, 28]] [1, 512, 28, 28] 2,359,296
ReLU-10 [[1, 512, 28, 28]] [1, 512, 28, 28] 0
MaxPool2D-4 [[1, 512, 28, 28]] [1, 512, 14, 14] 0
VGGBlock-4 [[1, 256, 28, 28]] [1, 512, 14, 14] 0
Conv2D-11 [[1, 512, 14, 14]] [1, 512, 14, 14] 2,359,296
ReLU-11 [[1, 512, 14, 14]] [1, 512, 14, 14] 0
Conv2D-12 [[1, 512, 14, 14]] [1, 512, 14, 14] 2,359,296
ReLU-12 [[1, 512, 14, 14]] [1, 512, 14, 14] 0
Conv2D-13 [[1, 512, 14, 14]] [1, 512, 14, 14] 2,359,296
ReLU-13 [[1, 512, 14, 14]] [1, 512, 14, 14] 0
MaxPool2D-5 [[1, 512, 14, 14]] [1, 512, 7, 7] 0
VGGBlock-5 [[1, 512, 14, 14]] [1, 512, 7, 7] 0
Flatten-1 [[1, 512, 7, 7]] [1, 25088] 0
Linear-1 [[1, 25088]] [1, 4096] 102,764,544
ReLU-14 [[1, 4096]] [1, 4096] 0
Dropout-1 [[1, 4096]] [1, 4096] 0
Linear-2 [[1, 4096]] [1, 4096] 16,781,312
ReLU-15 [[1, 4096]] [1, 4096] 0
Dropout-2 [[1, 4096]] [1, 4096] 0
Linear-3 [[1, 4096]] [1, 1000] 4,097,000
===========================================================================
Total params: 138,353,320
Trainable params: 138,353,320
Non-trainable params: 0
---------------------------------------------------------------------------
Input size (MB): 0.57
Forward/backward pass size (MB): 230.46
Params size (MB): 527.78
Estimated Total Size (MB): 758.81
---------------------------------------------------------------------------