VGG网络是在2014年由牛津大学著名研究组VGG (Visual Geometry Group) 提出,斩获该年ImageNet竞赛中 Localization Task (定位任务) 第一名 和 Classification Task (分类任务) 第二名。原论文名称是《Very Deep Convolutional Networks For Large-Scale Image Recognition》,在原论文中给出了一系列VGG模型的配置,下面这幅图是VGG16模型的结构简图。
该网络中的亮点: 通过堆叠多个3x3的卷积核来替代大尺度卷积核(在拥有相同感受野的前提下能够减少所需参数)。
论文中提到,可以通过堆叠两层3x3的卷积核替代一层5x5的卷积核,堆叠三层3x3的卷积核替代一层7x7的卷积核。下面给出一个示例:使用7x7卷积核所需参数,与堆叠三个3x3卷积核所需参数(假设输入输出特征矩阵深度channel都为C)
如果使用一层卷积核大小为7的卷积所需参数(第一个C代表输入特征矩阵的channel,第二个C代表卷积核的个数也就是输出特征矩阵的深度):
如果使用三层卷积核大小为3的卷积所需参数:
经过对比明显使用3层大小为3x3的卷积核比使用一层7x7的卷积核参数更少。
下表是从原论文中截取的几种VGG模型的配置表,作者尝试了不同深度的配置(11层,13层,16层,19层),是否使用LRN(Local Response Normalization)以及卷积核1x1与卷积核3x3的差异。
表中的卷积层(conv3-kernels,其中kernels代表卷积核的个数)全部都是大小为3x3,步距为1,padding为1的卷积操作(经过卷积后不会改变特征矩阵的高和宽)。最大池化下采样层全部都是池化核大小为2,步距为2的池化操作,每次通过最大池化下采样后特征矩阵的高和宽都会缩减为原来的一半。我们通常使用的VGG模型是表格中的VGG16(D)配置。根据表格中的配置信息以及刚刚讲的卷积层和池化层的详细参数就能搭建出VGG网络了。
我们通常看到别人在搭建VGG网络时,图像预处理的第一步会将图像的RGB分量分别减去[123.68, 116.78, 103.94]这三个参数。这三个参数是对应着ImageNet分类数据集中所有图像的R、G、B三个通到的均值分量。如果你要使用别人在ImageNet数据集上训练好的模型参数进行fine-train操作(也就是迁移学习)那么你需要在在图像预处理过程中减去这[123.68, 116.78, 103.94]三个分量,如果你是从头训练一个数据集(不使用在ImageNet上的预训练模型)那么就可以忽略这一步。
pytorch模型代码如下:
关于模型的预训练模型,在这个地方
import torch.nn as nn
import torch
class VGG(nn.Module):
def __init__(self, features, num_classes=1000, init_weights=False):
super(VGG, self).__init__()
self.features = features
self.classifier = nn.Sequential(
nn.Dropout(p=0.5),
nn.Linear(512*7*7, 2048),
nn.ReLU(True),
nn.Dropout(p=0.5),
nn.Linear(2048, 2048),
nn.ReLU(True),
nn.Linear(2048, num_classes)
)
if init_weights:
self._initialize_weights()
def forward(self, x):
# N x 3 x 224 x 224
x = self.features(x)
# N x 512 x 7 x 7
x = torch.flatten(x, start_dim=1)
# N x 512*7*7
x = self.classifier(x)
return x
def _initialize_weights(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
# nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
nn.init.xavier_uniform_(m.weight)
if m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
nn.init.xavier_uniform_(m.weight)
# nn.init.normal_(m.weight, 0, 0.01)
nn.init.constant_(m.bias, 0)
def make_features(cfg: list):
layers = []
in_channels = 3
for v in cfg:
if v == "M":
layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
else:
conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
layers += [conv2d, nn.ReLU(True)]
in_channels = v
return nn.Sequential(*layers)
cfgs = {
'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'],
}
def vgg(model_name="vgg16", **kwargs):
try:
cfg = cfgs[model_name]
except:
print("Warning: model number {} not in cfgs dict!".format(model_name))
exit(-1)
model = VGG(make_features(cfg), **kwargs)
return model
如果用基于迁移学习的方法来做,图像处理需要减去 [123.68, 116.78, 103.94] 这个值,这是ImageNet数据集三个通道的平均值。