Motivation

论文:Very deep convolutional networks for large-scale image recognition

VGG对于Alexnet来说,改进并不是很大,主要改进就在于使用了小卷积核,网络是分段卷积网络,通过max pooling过度,同时网络更深更宽。分别在定位和分类问题中获得了第一和第二名。

VGG结构

VGG的网络结构图


由上图所知,VGG一共有五段卷积,每段卷积之后紧接着最大池化层,作者一共实验了6种网络结构。分别是VGG-11,VGG-13,VGG-16,VGG-19,网络的输入是$224\times 224$大小的图像,输出是图像分类结果(本文只针对网络在图像分类任务上,图像定位任务上暂不做分析)

A-LRN 增加了 LRN 层,但在评估的时候可以看到 LRN (Local Response Normalisation)层并没有起到多大的作用,文章认为 LRN 并没有提升模型在 ILSVRC 数据集上的表现,反而增加了内存消耗和计算时间。模型 C 和 D 的层数一样,但 C 层使用了 1×1 的卷积核,用于对输入的线性转换,增加非线性决策函数,而不影响卷积层的接受视野。后面的评估阶段也有证明,使用增加的 1×1 卷积核不如添加 3×3 的卷积核。池化层的核数变小且为偶数,AlexNet 使用的是3×3 stride 为 2,VGG 为2×2 stride 也是 2 。全连接层形式上完全平移AlexNet的最后三层,超参数上只有最后一层fc有变化:bias的初始值,由AlexNet的0变为0.1,该层初始化高斯分布的标准差,由AlexNet的0.01变为0.005。

VGG16结构

1. VGG16相比AlexNet的一个改进是采用连续的几个3x3的卷积核代替AlexNet中的较大卷积核(11x11,5x5),ZFNet中的较大卷积核(7x7)。对于给定的感受野(与输出有关的输入图片的局部大小),采用堆积的小卷积核是优于采用大的卷积核,因为多层非线性层ReLU可以增加网络深度来保证学习更复杂的模式,而且代价还比较小(参数更少)。

在224x224x3的RGB图上(设置pad=1,stride=4,output_channel=96)做 conv3x3、conv5x5、conv7x7、conv9x9和conv11x11 卷积,卷积层的参数规模和得到的feature map的大小如下:

2. 在VGG中,使用了3个3x3卷积核来代替7x7卷积核,使用了2个3x3卷积核来代替5x5卷积核,这样做的主要目的是在保证具有相同感知野的条件下,提升了网络的深度,在一定程度上提升了神经网络的效果

3. conv filter的参数减少。比如,3个步长为1的3x3卷积核的一层层叠加作用可看成一个大小为7的感受野(其实就表示3个3x3连续卷积相当于一个7x7卷积),其参数总量为$3\times 9\times C^2$,如果直接使用7x7卷积核,其参数总量为 $49\times C^2$ ,这里 C指的是输入和输出的通道数。很明显,$27\times C^2$小于$49\times C^2$,即减少了参数;而且3x3卷积核有利于更好地保持图像性质。

VGG参数

```Text
INPUT: [224x224x3] memory: 224*224*3=150K weights: 0
CONV3-64: [224x224x64] memory: 224*224*64=3.2M weights: (3*3*3)*64 = 1,728
CONV3-64: [224x224x64] memory: 224*224*64=3.2M weights: (3*3*64)*64 = 36,864
POOL2: [112x112x64] memory: 112*112*64=800K weights: 0
CONV3-128: [112x112x128] memory: 112*112*128=1.6M weights: (3*3*64)*128 = 73,728
CONV3-128: [112x112x128] memory: 112*112*128=1.6M weights: (3*3*128)*128 = 147,456
POOL2: [56x56x128] memory: 56*56*128=400K weights: 0
CONV3-256: [56x56x256] memory: 56*56*256=800K weights: (3*3*128)*256 = 294,912
CONV3-256: [56x56x256] memory: 56*56*256=800K weights: (3*3*256)*256 = 589,824
CONV3-256: [56x56x256] memory: 56*56*256=800K weights: (3*3*256)*256 = 589,824
POOL2: [28x28x256] memory: 28*28*256=200K weights: 0
CONV3-512: [28x28x512] memory: 28*28*512=400K weights: (3*3*256)*512 = 1,179,648
CONV3-512: [28x28x512] memory: 28*28*512=400K weights: (3*3*512)*512 = 2,359,296
CONV3-512: [28x28x512] memory: 28*28*512=400K weights: (3*3*512)*512 = 2,359,296
POOL2: [14x14x512] memory: 14*14*512=100K weights: 0
CONV3-512: [14x14x512] memory: 14*14*512=100K weights: (3*3*512)*512 = 2,359,296
CONV3-512: [14x14x512] memory: 14*14*512=100K weights: (3*3*512)*512 = 2,359,296
CONV3-512: [14x14x512] memory: 14*14*512=100K weights: (3*3*512)*512 = 2,359,296
POOL2: [7x7x512] memory: 7*7*512=25K weights: 0
FC: [1x1x4096] memory: 4096 weights: 7*7*512*4096 = 102,760,448
FC: [1x1x4096] memory: 4096 weights: 4096*4096 = 16,777,216
FC: [1x1x1000] memory: 1000 weights: 4096*1000 = 4,096,000

TOTAL memory: 24M * 4 bytes ~= 93MB / image (only forward! ~*2 for bwd)
TOTAL params: 138M parameters
```

结果分析

单一尺度评估

结论:
1. 使用局部响应归一化(A-LRN网络)在没有任何归一化层的模型A上没有提升;
2. 分类误差随着ConvNet的深度的增加而减小:从A中的11层到E中的19层;
3. 训练时候的尺度抖动(S∈[256,512])比在具有固定最小边(S=256或S=384)的图像上训练产生明显的更好的结果;

多尺度评估

结论:
1. 结果表明测试时候的尺度抖动会导致更好的性能
2. 尺度抖动的训练比用固定最小边S训练效果要好

多尺度裁剪


结论:

1. 使用多种剪裁表现要略好于密集评估;
2. 并且这两种方法确实是互补的,因为它们的结合优于他们中的每一种;

注:方法1: multi-crop,即对图像进行多样本的随机裁剪,然后通过网络预测每一个样本的结构,最终对所有结果平均; 方法2: densely, 利用FCN的思想,将原图直接送到网络进行预测,将最后的全连接层改为1x1的卷积,这样最后可以得出一个预测的score map,再对结果求平均;

ConvNet融合

结论:多种模型进行融合,效果更好

与现有技术的比较

结论:与其它模型相比,VGG效果也很好

Pytorch实现

```python

import torch
import torch.nn as nn
import torchvision

def Conv3x3BNReLU(in_channels,out_channels):
return nn.Sequential(
nn.Conv2d(in_channels=in_channels,out_channels=out_channels,kernel_size=3,stride=1,padding=1),
nn.BatchNorm2d(out_channels),
nn.ReLU6(inplace=True)
)

class VGGNet(nn.Module):
def __init__(self, block_nums,num_classes=1000):
super(VGGNet, self).__init__()

self.stage1 = self._make_layers(in_channels=3, out_channels=64, block_num=block_nums[0])
self.stage2 = self._make_layers(in_channels=64, out_channels=128, block_num=block_nums[1])
self.stage3 = self._make_layers(in_channels=128, out_channels=256, block_num=block_nums[2])
self.stage4 = self._make_layers(in_channels=256, out_channels=512, block_num=block_nums[3])
self.stage5 = self._make_layers(in_channels=512, out_channels=512, block_num=block_nums[4])

self.classifier = nn.Sequential(
nn.Linear(in_features=512*7*7,out_features=4096),
nn.Dropout(p=0.2),
nn.Linear(in_features=4096, out_features=4096),
nn.Dropout(p=0.2),
nn.Linear(in_features=4096, out_features=num_classes)
)

self._init_params()

def _make_layers(self, in_channels, out_channels, block_num):
layers = []
layers.append(Conv3x3BNReLU(in_channels,out_channels))
for i in range(1,block_num):
layers.append(Conv3x3BNReLU(out_channels,out_channels))
layers.append(nn.MaxPool2d(kernel_size=2,stride=2, ceil_mode=False))
return nn.Sequential(*layers)

def _init_params(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
elif isinstance(m, nn.BatchNorm2d):
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)

def forward(self, x):
x = self.stage1(x)
x = self.stage2(x)
x = self.stage3(x)
x = self.stage4(x)
x = self.stage5(x)
x = x.view(x.size(0),-1)
out = self.classifier(x)
return out

def VGG16():
block_nums = [2, 2, 3, 3, 3]
model = VGGNet(block_nums)
return model

def VGG19():
block_nums = [2, 2, 4, 4, 4]
model = VGGNet(block_nums)
return model

if __name__ == '__main__':
model = VGG16()
print(model)

input = torch.randn(1,3,224,224)
out = model(input)
print(out.shape)

```

References

  1. Very Deep Convolutional Networks for Large-Scale Image Recognition
  2. 2014-VGG-《用于大规模图像识别的非常深的卷积网络》翻译
  3. VGG网络结构分析
  4. VGG 论文阅读记录
  5. Convolutional Neural Networks - Basics
  6. VGG ILSVRC