文章目录
论文阅读方法
第一遍(摘要+简介+结论)
初步结论:由于大量开放的数据图像库(ImgNet)以及好性能的计算系统(GPU)的存在,目前卷积神经网络在大尺度图像和视频分类取得了很大的成功。这篇论文主要探索了‘深度’对神经网络性能的影响,并且验证了在卷积核大小为3x3的网络上,把网络层数叠加到16-19层会使得网络性能得到显著的提升。这种方法也能推广到其他数据集上。
第二遍(分段阅读)
Part 2:Convnet Configurations
其实网络的基本配置和AlexNet差不多,只是相比于AlexNet,使用的卷积核更小(3x3)并且没有应用LRN(局部相应归一化)。关于ALexNet的具体网络配置可以看我另一篇博文:AlexNet
基于上面的网络配置,作者建立了A-E几个模型,图中省略了ReLU层。
然后作者就配置进行了讨论:
1)7x7的卷积和3个3x3的卷积感受野实际上是一样的,那为什么要用小卷积来代替大卷积呢?
- 首先,增加了非线性单元,因为每个卷积核后面都会接一个ReLU层,这会使得判别函数更具有判断性;
- 其次,减少了参数量一个7x7卷积的参数量为:77,而3个33的卷积为3 * 3 * 3;
- 这样的操作相当于对7x7的卷积强行加了正则化,迫使其分解。
2)1x1的卷积用来干嘛的? - 在之前1x1卷积都是用来在不改变视图的情况下增加非线性的,但是我们为了对比多一层ReLU的作用,所以仅仅作为线性映射,非线性又ReLU来提供。
- 实际上在后面的应用中,1x1的卷积通常用来融合通道间的信息并改变通道数。
Part3. Classification Framework
这里主要就是讲训练和测试的细节了,首先是训练:
- 相比于ALexNet,VGG所需要的训练epoch更少,作者认为有两点原因:主要是用了更深的网络和更小的卷积核;其次就是作者的训练策略,使得某些层用了预训练参数(模型A网络不够深,所以使用随机初始化训练,在训练更深的网络之前,前四层卷积层和后三层全连接层使用A的参数来代替)。
- 在训练时,令图像的最小边长为S,使用两种方法来设定,一个是固定其大小:256/384,另一个方法是从[Smin, Smax]中随机采样。
接来下是预测:
- 预测时,作者将全连接层全部改为了卷积层(第一个卷积层为7x7卷积,后两个卷积层为1x1卷积),输出将会得到一个score map得分图,对得分图做平均从而得到最后的类别概率。
- 这个地方我一开始有点不明白:在测试的时候才把全连接层改成了卷积层,那么卷积层的参数哪里来的呢? 其实,卷积层的参数就是来自于全连接层,第一个全连接层得到7x7x512的feature map,然后参数应该是7x7x512x4096,将其改为7x7x512卷积(4096个filters),参数也是7x7x512x4096,这时卷积相当于执行一个全连接操作,直接将全连接层对应的参数复制到卷积层即可。后面的两个层也是类似的思路。
- 接下来作者还提到了一个dense evaluation和multi-crop evaluation两种方法,并认为这两个方法是互补的:multi-crop的卷积特征图是由0填充的,然而dense是来自图像上相邻位置的自然填充,这增加了感受野,捕获了更多上下文。
- multi-crop的卷积特征图是由0填充的,然而dense是来自图像上相邻位置的自然填充,这增加了感受野,捕获了更多上下文;dense evaluation 就是利用FCN得到一个score map对其平均,也就是上面介绍的方法。
Part4. Classification Experiments
这一段主要就是Show Time了,实验对比,主要介绍在ILSVRC数据集上的结果:
- 首先是单规模实验,实验结果说明了三点:1. LRN局部响应归一化的确没用,还平白浪费计算资源;2. 随着网络深度的增加错误率下降(引入额外非线性单元,提取空间特征的卷积层能提高,并且两个3x3优于一个5x5);训练时的尺度抖动有助于提高精度。
- 多尺度实验说明,测试时候的尺度抖动也有助于精度的提升;
- Multi-crop实验说明,Multi-crop和Dense两种方案确实是互补的,两者的组合好于任何一个单个方法;
- Convnet fustion模型融合实验,就是把几个模型的结果融合在一起做平均;
与其他的SOTA模型做对比。
第三遍
VGG是牛津大学的VGG视觉组的产物,所以命名为VGG。虽然它没有获得那年ILSVRC分类组的冠军,但他获得了定位的冠军和分类的亚军。对后面的研究也很有影响:引入小卷积核 && 在网络的深度上探索。
3x3的卷积核在之后的网络基本上成为了标配,为什么不是2x2?或者4x4呢?因为3x3是一个捕捉局部信息的最小单元,它能捕捉区域中心以及四周的信息。
但VGG绝大多数的参数都是来自于第一个全连接层,并且如果只是单纯的增加神经网络的深度,会给训练带来困难,会出现梯度消失、梯度爆炸、不收敛等问题。
代码复现:
基于Pytorch 1.0.0
import torch
import torch.nn as nn
class VGG19(nn.Module):
def __init__(self, num_classes, init_weights=True):
super().__init__()
self.features = nn.Sequential(
# Block 1
nn.Conv2d(3, 64, 3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(64, 64, 3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(2, stride=2),
# Block 2
nn.Conv2d(64, 128, 3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(128, 128, 3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(2, stride=2),
# Block 3
nn.Conv2d(128, 256, 3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(256, 256, 3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(256, 256, 3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(256, 256, 3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(2, stride=2),
# Block 4
nn.Conv2d(256, 512, 3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, 3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, 3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, 3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(2, stride=2),
# Block 5
nn.Conv2d(512, 512, 3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, 3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, 3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, 3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(2, stride=2),
)
self.avgpool = nn.AdaptiveAvgPool2d((7, 7))
self.classifier = nn.Sequential(
nn.Linear(512*7*7, 4096),
nn.ReLU(inplace=True),
nn.Dropout(),
nn.Linear(4096, 4096),
nn.ReLU(inplace=True),
nn.Dropout(),
)
# 因为前面可以用预训练模型参数,所以单独把最后一层提取出来
self.classifier2 = nn.Linear(4096, num_classes)
if init_weights:
self._initialize_weights()
def forward(self, x):
x = self.features(x)
x = self.avgpool(x)
# torch.flatten 推平操作
x = torch.flatten(x, 1)
x = self.classifier(x)
x = self.classifier2(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.Linear):
nn.init.normal_(m.weight, 0, 0.01)
nn.init.constant_(m.bias, 0)