VGG16网络架构是由K. Simonyan和A. Zisserman在其论文《Very Deep Convolutional Networks for Large-Scale Image Recognition》中提出的。该架构在ImageNet数据集上达到了92.7%的top-5测试准确率,该数据集包含超过1400万张图像,涵盖1000个类别。VGG16因其在深度学习领域的卓越表现而闻名,其通过将大核尺寸的滤波器替换为第一层和第二层的11和5,相较于AlexNet架构有所改进,并且在多个3×3核尺寸的滤波器连续排列后进行训练,使用了NVIDIA Titan Black GPU进行数周的训练。
VGG16网络的输入是一个固定大小的224×224 RGB图像。预处理步骤仅包括从每个像素中减去在训练数据集上计算出的平均RGB值。然后,图像通过一系列卷积层(Conv.),其中包含非常小的感受野,即3×3,这是捕捉左右、上下和中心部分概念的最小尺寸。在某些配置中,它还使用1×1卷积滤波器,可以看作是输入通道的线性变换,随后是非线性。卷积步长固定为1像素;卷积层输入的空间填充使得卷积后空间分辨率得以保持,即对于3×3卷积层,填充为1像素。
然后通过五个最大池化层进行空间池化,这些层跟随一些卷积层但并非所有卷积层后都跟随最大池化层。这种最大池化是在2×2像素窗口上进行的,步长为2。该架构包含一系列卷积层,不同架构的深度不同,随后是三个全连接(FC)层:前两个FC层各有4096个通道,第三个FC层执行1000类分类,因此包含1000个通道,每个类别一个。最终层是softmax层。全连接层的配置在所有网络中都相似。所有隐藏层都配备了ReLU非线性。此外,其中一个网络包含局部响应归一化(LRN),这种归一化不会提高在训练数据集上的性能,但使用它会导致内存消耗和计算时间增加。
模型的输入是一个固定大小的224×224 RGB图像。预处理是从每个像素中减去训练集RGB值的平均值。共有17个卷积层,步长固定为1像素,对于3×3卷积层,填充为1像素。空间池化层不计入网络深度,使用最大池化层进行空间池化,窗口大小为2×2,步长固定为2,共使用了5个最大池化层。全连接层包括:第一个4096(ReLU),第二个4096(ReLU),第三个1000(Softmax)。
下图包含了VGG网络的卷积神经网络配置,包括VGG-11、VGG-11(LRN)、VGG-13、VGG-16(Conv1)、VGG-16和VGG-19。这些网络遵循传统设计,只在深度上有所不同:从网络A的11个权重层(8个卷积层和3个全连接层)到网络E的19个权重层(16个卷积层和3个全连接层)。每个卷积层的宽度,即通道数,相当小,从第一层的64开始,然后在每个最大池化层后增加一倍,直到达到512。
损失函数是多项式逻辑回归,学习算法是基于反向传播的mini-batch随机梯度下降(SGD),带有动量。批量大小为256,动量为0.9。正则化包括L2权重衰减(罚分乘数为0.0005)和前两个全连接层的dropout设置为0.5。学习率初始为0.01,当验证集准确率停止提高时,降低到0.01。尽管与Alexnet相比,CNN的参数数量和深度更大,但由于小卷积核和更多正则化以及某些层的预初始化,CNN所需的训练周期更少。训练图像大小是等比例缩放图像的最小边S,有两种设置S的方法:固定S(称为单尺度训练)和变化S(称为多尺度训练)。
import torch
import torch.nn as nn
VGG_types = {
"VGG11": [64, "M", 128, "M", 256, 256, "M", 512, 512, "M", 512, 512, "M"],
"VGG13": [64, 64, "M", 128, 128, "M", 256, 256, "M", 512, 512, "M", 512, 512, "M"],
"VGG16": [64, 64, "M", 128, 128, "M", 256, 256, 256, "M", 512, 512, 512, "M", 512, 512, 512, "M"],
"VGG19": [64, 64, "M", 128, 128, "M", 256, 256, 256, 256, "M", 512, 512, 512, 512, "M", 512, 512, 512, 512, "M"],
}
VGGType = "VGG16"
class VGGnet(nn.Module):
def __init__(self, in_channels=3, num_classes=1000):
super(VGGnet, self).__init__()
self.in_channels = in_channels
self.conv_layers = self.create_conv_layers(VGG_types[VGGType])
self.fcs = nn.Sequential(
nn.Linear(512 * 7 * 7, 4096),
nn.ReLU(),
nn.Dropout(p=0.5),
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(p=0.5),
nn.Linear(4096, num_classes),
)
def forward(self, x):
x = self.conv_layers(x)
x = x.reshape(x.shape[0], -1)
x = self.fcs(x)
return x
def create_conv_layers(self, architecture):
layers = []
in_channels = self.in_channels
for x in architecture:
if type(x) == int:
out_channels = x
layers += [
nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),
nn.BatchNorm2d(x),
nn.ReLU(),
]
in_channels = x
elif x == "M":
layers += [nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2))]
return nn.Sequential(*layers)
if __name__ == "__main__":
device = "cuda" if torch.cuda.is_available() else "cpu"
model = VGGnet(in_channels=3, num_classes=500).to(device)
x = torch.randn(1, 3, 224, 224).to(device)
print(model(x).shape)