2014年VggNet被推出,获取了ILSVRC2014比赛分类项目的第二名,第一名是GoogleNet,该网络在下节介绍,本节主要介绍VggNet。
VggNet可以称为是一个家族,根据层数的不同包括了A、A-LRN、B、C、D等网络结构,其中D、E也就是常见的VggNet16、VggNet19,网络层从初始的11层增加到了19层,但参数并没有明显的增加,因为参数主要集中在全连接层。以下是VggNet网络家族网络层介绍(增加的网络层,用粗体标注),参数表示“conv〈receptive field size〉-〈number of channels〉”。
每个网络结构参数,如下:
网络结构
以VggNet16为例,各层网络结构如下:
-
输入图像3*224*224-》使用64个3*3的卷积核做两次卷积-》经过ReLU,输出为64*224*224
-
使用Maxpooling进行池化,池化大小为2*2,输出64*112*112
-
输入64*112*112-》使用128个3*3的卷积核做两次卷积-》经过ReLU,输出为128*112*112
-
使用Maxpooling进行池化,池化大小为2*2,输出128*56*56
-
输入128*56*56-》使用256个3*3的卷积核做三次卷积-》经过ReLU,输出为256*56*56
-
使用Maxpooling进行池化,池化大小为2*2,输出256*28*28
-
输入256*28*28-》使用512个3*3的卷积核进行三次卷积-》经过ReLU,输出512*28*28
-
使用Maxpooling进行池化,池化大小为2*2,输出512*14*14
-
输入512*14*14-》使用512个3*3的卷积核进行三次卷积-》经过ReLU,输出512*14*14
-
使用Maxpooling进行池化,池化大小为2*2,输出512*7*7
-
连接两层1*1*4096全连接层
-
连接一层1*1*1000进行全连接-》经过ReLU
-
通过softmax输出1000个预测结果的概率值
通过上面网络层介绍,可以发现,卷积核从64个,陆续进行翻倍增加,64-》128-》256-》512,专注于通道数增加;池化层采用较小的2*2。整体小卷积核、小池化核使得模型架构更深更宽的同时,计算量的增加放缓。
在网络测试阶段,将训练阶段的三个全连接层替换为三个卷积,测试重用训练时的参数,使得测试得到的全卷积网络因为没有全连接的限制,因而可以接收任意宽或高为的输入。
网络亮点
数据增强
-
对图像进行镜像翻转和random RGB colour shift ,扩大样本数量
-
对数据进行随机的裁剪,将图片裁剪为224*224大小
-
多种尺寸训练:1)分别使用256*256和384*384固定大小的图片进行模型的训练;2)多尺度的模型训练,对图像进行缩放,缩放范围为256~512,可以理解为一种数据的抖动,增加了数据量。
下面是采用不同尺度后,模型top1和top5的效果。
数据预处理
-
对图像每个像素值减去RGB的平均值,得到新的像素值。
-
在某些网络结构中,使用了Local Response Normalisation,发现并没有带来效果的提升,反而增加了内存和计算时间,因此后续网络结构中并未继续使用该处理方法。
参数初始化
网络权重的初始化很重要,因为糟糕的初始化会因为深度网络中的梯度不稳定而导致学习停滞。当网络层较少时,参数值使用随机初始化;当网络层较多时,需要更加合理的参数初始化方法。
VggNet在训练A网络结构时,采用了随机方法,通过训练得到了一组实验参数。在后续的网络结构如D、E网络,利用A网络中的参数,初始化了前4层网络结构和最后三层全连接层,中间的网络层使用随机初始化进行训练。初始化参数满足均值=0,方差=0.01的正态分布,biases初始化0.
不过作者后续发现,可以不使用预训练来确定参数,可以使用Golort&Bengio(2010),进行初始化。目前有更加多的初始化方法。
多个小卷积核代替大卷积核
用多个小的卷积代替了较大的卷积核,例如,两个3*3的卷积核叠加等价于5*5卷积核的视野,三个3*3的卷积核叠加等价于7*7的卷积核视野。
多个小卷积核叠加为什么会等于大卷积核视野?
假设图像是28*28,卷积核的大小为5*5,s=1,p=0,则输出大小为(28-5+2*0)/1+1 = 24;
同样使用两个3*3的卷积核,s=1,p=0,第一个卷积核作用后,输出(28-3+2*0)/1+1=26;继续进行第二个卷积核,输出大小为(26-3+2*0)/1+1=24,从结果上看,两者处理的结果是一致的。
上面计算过程,可简化为如下图形。3个3*3卷积核替代7*7卷积核,同理。
多个小卷积核代替大卷积核,有什么好处?
-
多个小卷积核,相对于单一的使用一个卷积核。多个卷积核具有更强的非线性映射,可以获得更多的图像特征,从而使得决策函数有更好的识别力。
-
多个小卷积核的总参数,小于单个大卷积核的参数。假设输入通道数和输出通道数分别为input_channel 和output_channel,对于两个3*3的参数量为2*3*3*input_channel *output_channel,一个5*5卷积核的参数量为1*5*5*input_channel *output_channel,两个参数量比较5*5的参数量是多个小卷积核的1.39倍(25/18)。小卷积核替代大卷积核,参数量级的减少是其优势的一部分,更重要的是计算量的减少更加明显。
-
多个小卷积核代替大卷积核,例如3个3*3的卷积核,替代7*7的卷积核。相当于一定程度上进行了正则化,同时通过多个卷积核进行分解,每个卷积核之后加入了ReLU激活函数,增加了非线性表示,使得整体特征提取能力得到了提升。
多个小卷积核代替大卷积核,有什么坏处?
-
在进行反向传播时,中间的卷积层可能会占用更多的内存。
1*1卷积核
1*1卷积核,可以增加decision function的非线性能力,同时不影响卷积核的视野。1*1卷积核,可以起到以下作用。
-
起到降维和升维的作用
-
增加非线性:可以在保持feature map尺度不变的(即不损失分辨率)的前提下大幅增加非线性特性(利用后接的非线性激活函数),把网络做的很deep。
-
跨通道信息交互:1*1的卷积核,实际上是对不同channel的信息,进行了线性组合,实现了通道间的信息交互。
应用
网络结构定义
import torch.nn as nn
import torch# official pretrain weights,官网直接下载模型权重
model_urls = {'vgg11': 'https://download.pytorch.org/models/vgg11-bbd30ac9.pth','vgg13': 'https://download.pytorch.org/models/vgg13-c768596a.pth','vgg16': 'https://download.pytorch.org/models/vgg16-397923af.pth','vgg19': 'https://download.pytorch.org/models/vgg19-dcbb9e9d.pth'
}# 定义网络
class VGG(nn.module):"""features: 定义网络结构,num_classes: 分类数init_weights: 权重是否初始化"""def __init__(self, features, num_classes=1000, init_weights=False):super(VGG, self).__init__()# 特征提取网络层self.features = features# 分类层self.classifier = nn.Sequential(nn.Linear(512*7*7, 4096),nn.ReLU(True),nn.Dropout(p=0.5),nn.Linear(4096, 4096),nn.ReLU(True),nn.Dropout(p=0.5),nn.Linear(4096, num_classes))if init_weights:self._initialize_weights()# 前向传播def forward(self, x):# barch * 3 * 224 * 224x = self.features(x)x = torch.flatten(x, start_dim=1)x = self.classifier(x)return x# 定义初始化方法def _initialize_weights(self):# 读取每层网络for m in self.modules():if isinstance(m, nn.Conv2d):# 用均匀分布进行填充nn.init.xavier_uniform_(m.weight)if m.bias is not None:# 进行数值填充nn.init.constant_(m.bias, 0)elif isinstance(m, nn.Linear):nn.init.xavier_uniform_(m.weight)nn.init.constant_(m.bias, 0)# 将网络配置参数,转变为对应的网络结构
def make_features(cfg:list):layers = []in_channels = 3for v in cfg:if v == "M":# 网络中池化层,size=2,stride=2layers += [nn.MaxPool2d(kernel_size=2, stride=2)]else:# 卷积层 stride=1, padding=1conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)layers += [conv2d, nn.ReLU(True)]in_channels = vreturn nn.Sequential(*layers)# 配置网络参数,64代表卷积核的个数,M代表池化层
cfgs = {'vgg11': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],'vgg13': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],'vgg16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],'vgg19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'],
}