1. VGG简介
VGGNet是由牛津大学视觉几何小组(Visual Geometry Group, VGG)提出的一种深层卷积网络结构,他们以7.32%的错误率赢得了2014年ILSVRC分类任务的亚军(冠军由GoogLeNet以6.65%的错误率夺得)和25.32%的错误率夺得定位任务(Localization)的第一名(GoogLeNet错误率为26.44%),网络名称VGGNet取自该小组名缩写。VGGNet是首批把图像分类的错误率降低到10%以内模型,同时该网络所采用的3×33\times33×3卷积核的思想是后来许多模型的基础,该模型发表在2015年国际学习表征会议(International Conference On Learning Representations, ICLR)后至今被引用的次数已经超过1万4千余次。
2. 模型结构
在原论文中的VGGNet包含了6个版本的演进,分别对应VGG11、VGG11-LRN、VGG13、VGG16-1、VGG16-3和VGG19,不同的后缀数值表示不同的网络层数(VGG11-LRN表示在第一层中采用了LRN的VGG11,VGG16-1表示后三组卷积块中最后一层卷积采用卷积核尺寸为1×11\times11×1,相应的VGG16-3表示卷积核尺寸为3×33\times33×3),本节介绍的VGG16为VGG16-3。图中的VGG16体现了VGGNet的核心思路,使用3×33\times33×3的卷积组合代替大尺寸的卷积(2个3×3卷积即可与3\times3卷积即可与3×3卷积即可与5×55\times55×5卷积拥有相同的感受视野),网络参数设置如表所示。
网络层 | 输入尺寸 | 核尺寸 | 输出尺寸 | 参数个数 |
---|---|---|---|---|
卷积层C11C_{11}C11 | 224×224×3224\times224\times3224×224×3 | 3×3×64/13\times3\times64/13×3×64/1 | 224×224×64224\times224\times64224×224×64 | (3×3×3+1)×64(3\times3\times3+1)\times64(3×3×3+1)×64 |
卷积层C12C_{12}C12 | 224×224×64224\times224\times64224×224×64 | 3×3×64/13\times3\times64/13×3×64/1 | 224×224×64224\times224\times64224×224×64 | (3×3×64+1)×64(3\times3\times64+1)\times64(3×3×64+1)×64 |
下采样层Smax1S_{max1}Smax1 | 224×224×64224\times224\times64224×224×64 | 2×2/22\times2/22×2/2 | 112×112×64112\times112\times64112×112×64 | 000 |
卷积层C21C_{21}C21 | 112×112×64112\times112\times64112×112×64 | 3×3×128/13\times3\times128/13×3×128/1 | 112×112×128112\times112\times128112×112×128 | (3×3×64+1)×128(3\times3\times64+1)\times128(3×3×64+1)×128 |
卷积层C22C_{22}C22 | 112×112×128112\times112\times128112×112×128 | 3×3×128/13\times3\times128/13×3×128/1 | 112×112×128112\times112\times128112×112×128 | (3×3×128+1)×128(3\times3\times128+1)\times128(3×3×128+1)×128 |
下采样层Smax2S_{max2}Smax2 | 112×112×128112\times112\times128112×112×128 | 2×2/22\times2/22×2/2 | 56×56×12856\times56\times12856×56×128 | 000 |
卷积层C31C_{31}C31 | 56×56×12856\times56\times12856×56×128 | 3×3×256/13\times3\times256/13×3×256/1 | 56×56×25656\times56\times25656×56×256 | (3×3×128+1)×256(3\times3\times128+1)\times256(3×3×128+1)×256 |
卷积层C32C_{32}C32 | 56×56×25656\times56\times25656×56×256 | 3×3×256/13\times3\times256/13×3×256/1 | 56×56×25656\times56\times25656×56×256 | (3×3×256+1)×256(3\times3\times256+1)\times256(3×3×256+1)×256 |
卷积层C33C_{33}C33 | 56×56×25656\times56\times25656×56×256 | 3×3×256/13\times3\times256/13×3×256/1 | 56×56×25656\times56\times25656×56×256 | (3×3×256+1)×256(3\times3\times256+1)\times256(3×3×256+1)×256 |
下采样层Smax3S_{max3}Smax3 | 56×56×25656\times56\times25656×56×256 | 2×2/22\times2/22×2/2 | 28×28×25628\times28\times25628×28×256 | 000 |
卷积层C41C_{41}C41 | 28×28×25628\times28\times25628×28×256 | 3×3×512/13\times3\times512/13×3×512/1 | 28×28×51228\times28\times51228×28×512 | (3×3×256+1)×512(3\times3\times256+1)\times512(3×3×256+1)×512 |
卷积层C42C_{42}C42 | 28×28×51228\times28\times51228×28×512 | 3×3×512/13\times3\times512/13×3×512/1 | 28×28×51228\times28\times51228×28×512 | (3×3×512+1)×512(3\times3\times512+1)\times512(3×3×512+1)×512 |
卷积层C43C_{43}C43 | 28×28×51228\times28\times51228×28×512 | 3×3×512/13\times3\times512/13×3×512/1 | 28×28×51228\times28\times51228×28×512 | (3×3×512+1)×512(3\times3\times512+1)\times512(3×3×512+1)×512 |
下采样层Smax4S_{max4}Smax4 | 28×28×51228\times28\times51228×28×512 | 2×2/22\times2/22×2/2 | 14×14×51214\times14\times51214×14×512 | 000 |
卷积层C51C_{51}C51 | 14×14×51214\times14\times51214×14×512 | 3×3×512/13\times3\times512/13×3×512/1 | 14×14×51214\times14\times51214×14×512 | (3×3×512+1)×512(3\times3\times512+1)\times512(3×3×512+1)×512 |
卷积层C52C_{52}C52 | 14×14×51214\times14\times51214×14×512 | 3×3×512/13\times3\times512/13×3×512/1 | 14×14×51214\times14\times51214×14×512 | (3×3×512+1)×512(3\times3\times512+1)\times512(3×3×512+1)×512 |
卷积层C53C_{53}C53 | 14×14×51214\times14\times51214×14×512 | 3×3×512/13\times3\times512/13×3×512/1 | 14×14×51214\times14\times51214×14×512 | (3×3×512+1)×512(3\times3\times512+1)\times512(3×3×512+1)×512 |
下采样层Smax5S_{max5}Smax5 | 14×14×51214\times14\times51214×14×512 | 2×2/22\times2/22×2/2 | 7×7×5127\times7\times5127×7×512 | 000 |
全连接层FC1FC_{1}FC1 | 7×7×5127\times7\times5127×7×512 | (7×7×512)×4096(7\times7\times512)\times4096(7×7×512)×4096 | 1×40961\times40961×4096 | (7×7×512+1)×4096(7\times7\times512+1)\times4096(7×7×512+1)×4096 |
全连接层FC2FC_{2}FC2 | 1×40961\times40961×4096 | 4096×40964096\times40964096×4096 | 1×40961\times40961×4096 | (4096+1)×4096(4096+1)\times4096(4096+1)×4096 |
全连接层FC3FC_{3}FC3 | 1×40961\times40961×4096 | 4096×10004096\times10004096×1000 | 1×10001\times10001×1000 | (4096+1)×1000(4096+1)\times1000(4096+1)×1000 |
3. 模型特性
- 整个网络都使用了同样大小的卷积核尺寸3×33\times33×3和最大池化尺寸2×22\times22×2。
- 1×11\times11×1卷积的意义主要在于线性变换,而输入通道数和输出通道数不变,没有发生降维。
- 两个3×33\times33×3的卷积层串联相当于1个5×55\times55×5的卷积层,感受野大小为5×55\times55×5。同样地,3个3×33\times33×3的卷积层串联的效果则相当于1个7×77\times77×7的卷积层。这样的连接方式使得网络参数量更小,而且多层的激活函数令网络对特征的学习能力更强。
- VGGNet在训练时有一个小技巧,先训练浅层的的简单网络VGG11,再复用VGG11的权重来初始化VGG13,如此反复训练并初始化VGG19,能够使训练时收敛的速度更快。
- 在训练过程中使用多尺度的变换对原始数据做数据增强,使得模型不易过拟合。
4. VGG16模型PyTorch代码实现
import torch
import torch.nn as nn
from torchvision.models.utils import load_state_dict_from_url
#--------------------------------------#
# VGG16的结构
#--------------------------------------#
class VGG(nn.Module):
def __init__(self, features, num_classes=1000, init_weights=True):
super(VGG, self).__init__()
self.features = features
#--------------------------------------#
# 平均池化到7x7大小
#--------------------------------------#
self.avgpool = nn.AdaptiveAvgPool2d((7, 7))
#--------------------------------------#
# 分类部分
#--------------------------------------#
self.classifier = nn.Sequential(
nn.Linear(512 * 7 * 7, 4096),
nn.ReLU(True),
nn.Dropout(),
nn.Linear(4096, 4096),
nn.ReLU(True),
nn.Dropout(),
nn.Linear(4096, num_classes),
)
if init_weights:
self._initialize_weights()
def forward(self, x):
#--------------------------------------#
# 特征提取
#--------------------------------------#
x = self.features(x)
#--------------------------------------#
# 平均池化
#--------------------------------------#
x = self.avgpool(x)
#--------------------------------------#
# 平铺后
#--------------------------------------#
x = torch.flatten(x, 1)
#--------------------------------------#
# 分类部分
#--------------------------------------#
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')
if m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.BatchNorm2d):
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
nn.init.normal_(m.weight, 0, 0.01)
nn.init.constant_(m.bias, 0)
'''
假设输入图像为(600, 600, 3),随着cfg的循环,特征层变化如下:
600,600,3 -> 600,600,64 -> 600,600,64 -> 300,300,64 -> 300,300,128 -> 300,300,128 -> 150,150,128 -> 150,150,256 -> 150,150,256 -> 150,150,256
-> 75,75,256 -> 75,75,512 -> 75,75,512 -> 75,75,512 -> 37,37,512 -> 37,37,512 -> 37,37,512 -> 37,37,512
到cfg结束,我们获得了一个37,37,512的特征层
'''
cfg = [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M']
#--------------------------------------#
# 特征提取部分
#--------------------------------------#
def make_layers(cfg, batch_norm=False):
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)
if batch_norm:
layers += [conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)]
else:
layers += [conv2d, nn.ReLU(inplace=True)]
in_channels = v
return nn.Sequential(*layers)
def decom_vgg16(pretrained = False):
model = VGG(make_layers(cfg))
if pretrained:
state_dict = load_state_dict_from_url("https://download.pytorch.org/models/vgg16-397923af.pth", model_dir="./model_data")
model.load_state_dict(state_dict)
#----------------------------------------------------------------------------#
# 获取特征提取部分,最终获得一个37,37,1024的特征层
#----------------------------------------------------------------------------#
features = list(model.features)[:30]
#----------------------------------------------------------------------------#
# 获取分类部分,需要除去Dropout部分
#----------------------------------------------------------------------------#
classifier = list(model.classifier)
del classifier[6]
del classifier[5]
del classifier[2]
features = nn.Sequential(*features)
classifier = nn.Sequential(*classifier)
return features, classifier