一文读不懂——VGGNets
题外话: 封面是之前拍的人民公园,疫情赶紧过去吧.
自从resnet之后,这个系列的文章还没有更新过,是一个有些浮躁且有点忙的状态,趁着有时间把VGGNet论文给读一下,做个总结再复现一下提高代码能力.
1 介绍
VGG是Oxford的Visual Geometry Group的组提出的,取名也是用实验室的前几个字母组成,该网络是在ILSVRC 2014上的相关工作。在AlexNet基础上通过对网络结构进行改进和加深,证明了增加网络的深度能够在一定程度上影响网络最终的性能。VGG有两种结构,分别是VGG16和VGG19,主要是网络层数上的不同。
2 原理
1)用3x3卷积代替5x5以及7x7。网络在不断地加深,模型的参数也越来越多,给训练带来很多阻碍。VGGNet一个很重要的改进就是用更小的比如3x3的卷积层去代替5x5以及7x7的卷积,经过计算可以发现两个3x3的卷积在感受野上相当于一个5x5的卷积,但是有两方面的提升:更少的参数(2x3x3/5x5),以及多加了分线性变化层,提高模型的学习能力.这里假设输入通道和输出通道分别是C1和C2,那两个3x3的卷积就相当于2x3x3xC1xC2,同理计算其他的;
2)使用1x1卷积。这里使用的1x1卷积是在同一纬度上进行特征的融合,多引入了非线性变换,增强模型学习能力。
3 结构
结构其实也很简单,就是卷积和maxpool的堆叠,各种结构如图3.1所示.
图 3.1 VGGNet网络结构参数
4 Pytorch复现
这里虽然有很多结构的VGG,但是我们经过分析之后可以发现,都是成组的,所以可以把所有的模型集成在一起,这种写法自己平常也写的不多,学到了.
import torch.nn as nn
import torch.nn.functional as F
首先,这些包的引入就不用多说了.
接下来定义VGGNets
class VGG(nn.Module):
def __init__(self, arch: object, num_classes=1000) -> object:
super(VGG, self).__init__()
self.in_channels = 3
self.conv3_64 = self.__make_layer(64, arch[0])
self.conv3_128 = self.__make_layer(128, arch[1])
self.conv3_256 = self.__make_layer(256, arch[2])
self.conv3_512a = self.__make_layer(512, arch[3])
self.conv3_512b = self.__make_layer(512, arch[4])
self.fc1 = nn.Linear(7*7*512, 4096)
self.bn1 = nn.BatchNorm1d(4096)
self.bn2 = nn.BatchNorm1d(4096)
self.fc2 = nn.Linear(4096, 4096)
self.fc3 = nn.Linear(4096, num_classes)
def __make_layer(self, channels, num):
layers = []
for i in range(num):
layers.append(nn.Conv2d(self.in_channels, channels, 3, stride=1, padding=1, bias=False)) # same padding
layers.append(nn.BatchNorm2d(channels))
layers.append(nn.ReLU())
self.in_channels = channels
return nn.Sequential(*layers)
def forward(self, x):
out = self.conv3_64(x)
out = F.max_pool2d(out, 2)
out = self.conv3_128(out)
out = F.max_pool2d(out, 2)
out = self.conv3_256(out)
out = F.max_pool2d(out, 2)
out = self.conv3_512a(out)
out = F.max_pool2d(out, 2)
out = self.conv3_512b(out)
out = F.max_pool2d(out, 2)
out = out.view(out.size(0), -1)
out = self.fc1(out)
out = self.bn1(out)
out = F.relu(out)
out = self.fc2(out)
out = self.bn2(out)
out = F.relu(out)
return F.softmax(self.fc3(out))
可以看到我们在每一个block的地方用了一个Sequential,接下来我们把不同结构网络的参数写进去.
def VGG_11():
return VGG([1, 1, 2, 2, 2], num_classes=1000)
def VGG_13():
return VGG([1, 1, 2, 2, 2], num_classes=1000)
def VGG_16():
return VGG([2, 2, 3, 3, 3], num_classes=1000)
def VGG_19():
return VGG([2, 2, 4, 4, 4], num_classes=1000)
接下来进行简单测试, 并打印模型结构
if __name__=='__main__':
model = VGG_16()
print(model)
另外,在看论文的时候,提到说Conv+MaxPooling 可以增强平移不变性,应该是Pooling层的一个用处,好久没看这些有点忘了,不严谨的可以理解为,Conv提取特征之后,经过MaxPooling的操作,局部的特征有些被融合了一部分.
5 总结
VGGNet在AlexNet上进行了改进,这个改进只是说依旧用卷积和最大池化堆叠的方式进行网络设计,总结其优缺点如下:
- 优点:就是用更小更多的卷积代替大卷积,减少模型参数的同时提升模型学习能力;
- 缺点:最后加了四层FC,参数也太多了,就很离谱,后面有人实验将其中的几个FC去掉之后,效果没有啥变化.
看论文还不是很细,理解有误的地方,欢迎交流指正~