1.简介
VGG是在2014年由牛顿大学著名研究组VGG(Visual Geometry Group)提出,斩获该年ImageNet竞赛中Localization Task(定位任务)第一名和Classfication Task(分类任务第二名)。
2. 网络结构
一般情况下,都会采用D、E这两种规模的网络,也就是VGG16和VGG19。
- VGG16:13个卷积层和3个全连接层
- VGG19:16个卷积层和3个全连接层
以VGG16为例
- 卷积层参数:卷积核大小(3*3),步长(Stride)为1,填充(padding)为1。
- 池化层参数:最大池化maxpooling参数均为(2*2)
在全连接层(蓝色)之前所有部分,即卷积和池化的操作,可以理解为提取特征,而在全连接层及之后,实现分类。
网络接收大小为(224x224)的图像,channels为3的RGB图,每一次卷积均不改变图片的长宽(224-3+2)/1+1=224
Out_size=(In_size-Kernel_size+2Padding)/Sride+1
而每次最大池化会使得图片长宽减半。而最后全连接层部分,因为目标任务imagenet是1000分类,所以最后值是1000,再加上softmax激活函数,求最大的概率分布。
3.优缺点
- 优点
与传统网络结构不同的是,VGGnet采用两个小的3x3的卷积核代替一个5x5的卷积核,采用三个3x3的卷积核代替一个7x7的卷积核。
之所以可以这样做的原因是因为其具有相同的感受野。
5x5卷积可以看作先做一个3x3卷积,然后再用全连接层链接这个3x3的卷积输出。
感受野计算公式:
F(i)=(F(i+1)-1) * Stride +Ksize
F(i):第i层感受野
Feature map=1
F=(1-1)*1+3=3
F=(3-1)*2+3=5
F=(5-1)*2+3=7
而通过小尺寸的卷积核去替代大尺寸的卷积核的目的是减少参数
假定输入channel和输出channel都为C
则7x7大小的参数为:7x7xCxC=49C²
而三个3x3大小的参数为:3x3xCxC+3x3xCxC+3x3xCxC=27C² - 缺点
耗费资源,使用了更多的参数,全连接层就有3层,第一个全连接层还是1x1x4096,且训练时间过长,存储容量大。
4. 网络复现
采用pytorch实现网络复现
class Vgg16(nn.Module):
def __init__(self, num_classes):
super(Vgg16, self).__init__()
self.conv_unit = nn.Sequential(
# BLOCK1
nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.MaxPool2d(2, 2),
# BLOCK2
nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.MaxPool2d(2, 2),
# BLOCK3
nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.MaxPool2d(2, 2),
# BLOCK 4
nn.Conv2d(in_channels=256, out_channels=512, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.MaxPool2d(2, 2),
# BLOCK5
nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.MaxPool2d(2, 2),
)
self.fn_unit = nn.Sequential(
nn.Linear(7*7*512, 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_unit(x)
x = x.view(x.size(0), -1)
x = self.fn_unit(x)
return x
网络是逐层写的,尽管其中有很多层都是一样的,略显有点冗长,不过基本功能还是能实现。