论文地址:Going deeper with convolutions
------ 隔了两个月再来看这篇论文,不少地方依旧觉得晦涩难懂------
理解的不够深,但希望对大家有帮助,有问题的地方也希望大家能指出来
本篇论文由google团队提出,拿到了ILSVRC14图像分类赛道冠军,同年的亚军是VGG。我认为googlenet的提出,相当于给出了一个新的,区别于传统的单纯堆叠卷积层的卷积神经网络结构。其中也借鉴了许多其它经典论文中的idea,比如NIN,《Robust object recognition with cortex-like mechanisms》,《Provable Bounds for Learning Some Deep Representations》等,其中第二篇论文的主要结果表明,如果数据集的概率分布可由大的、非常稀疏的深度神经网络表示,通过分析最后一层激活情况的相关统计量,并聚类输出高度相关的神经元,逐层构建最优网络拓扑。,可以逐层构建最佳网络拓扑(这句话是照抄论文用软件翻译过来的)。大致可以认为使用稀疏结构可以让我们找到一个最优的拓扑结构。大家可以看一下原论文,我也打算去看看,似乎涉及到很多数学内容。
令人头疼的是,计算机在稀疏结构上的运算是低效的,即使将算数运算的次数减低100倍,也无法从稀疏结构中得到回报。另外,非均匀稀疏模型需要更复杂的工程和计算基础设施。意思就是说当时的计算机设备不利于稀疏数据结构的运算。于是作者另辟蹊径,希望中间步骤使用稀疏性,然后将稀疏矩阵聚类成相对稠密的子矩阵,最后用当前的计算机对这个稠密的子矩阵做运算。这样即应用了稀疏结构来构建最有结构又避免了计算机在稀疏数据结构上效率低的问题。
这篇论文提出的Inception块就是想要近似论文【2】中的稀疏结构,并通过实验发现有较好的表现。
在最初的Inception快中(结构由下图所述,navies version),应用了多个不同大小的滤波器(filter),随着网络层数的增加,会导致越往后的output feature的通道数越来越大(因为有个池化层,经过池化层后feature map的通道数量不变,每层叠加会导致通道数越来越大),导致参数量猛增,使得计算量也大了起来。
改进后的Inception结构称作Inception v1,它在原来的基础上添加了1乘1的卷积层用于降维,然后再通过3×3或5×5的卷积核升维,这样大大减少参数量,增加网络层数,激活函变多,使网络非线性表达能力增强。
举个栗子:
用5×5filter这条支路举例,假设输入是28×28,256通道的feature map,输出是28×28,32通道的feature map。
navies version所需参数:5×5×256×32≈20万
先降后升version所需参数:1×1×256×16 + 5×5×16×32≈2万
这一操作根本上地解决了参数量过大的问题,VGG网络过于臃肿的原因就是在卷积层与全连接层之间产生了1亿多个参数。这里保留了一层全连接层(linear层),方便之后的微调(fine-tuning)
由直觉,处理视觉信息应该利用不同大小的filter并行处理input feature,然后再集合起来,从而使后面的卷积层可以同时提取不同尺寸的特征。
作者发现了一个有趣的现象,相对较浅的网络有更好的表现,表明网络中间层产生的特征应该是非常有判别能力的。通过在网络中间部分加入辅助分类器,增强了分类表现,并促进了梯度的反向传播(尽管后面认为这个辅助分类器没什么用)。共有两个辅助分类器和一个主分类器,在训练阶段,对三个分类器的结果进行加权求和得到最后的分类结果(权重分别为0.3、0.3,0.4)。但是在inference阶段,仅通过主分类器进行分类。
代码有些长,但好理解
import sys
import numpy as np
import torch
from torch import nn
from torch.autograd import Variable
# 卷积层标配,卷积+relu
def conv_relu(in_channel, out_channel, kernel, stride=1, padding=0):
layer = nn.Sequential(
nn.Conv2d(in_channel, out_channel, kernel, stride, padding),
nn.ReLU(True)
)
return layer
# 定义inception块
class inception(nn.Module):
def __init__(self, in_channel, out1_1, out2_1, out2_3, out3_1, out3_5, out4_1):
super(inception, self).__init__()
# 第一条线路,filter大小为1×1
self.branch1x1 = conv_relu(in_channel, out1_1, 1)
# 第二条线路,filter大小为3×3
self.branch3x3 = nn.Sequential(
conv_relu(in_channel, out2_1, 1),
conv_relu(out2_1, out2_3, 3, padding=1)
)
# 第三条线路,filter大小为5×5
self.branch5x5 = nn.Sequential(
conv_relu(in_channel, out3_1, 1),
conv_relu(out3_1, out3_5, 5, padding=2)
)
# 第四条线路,直接池化层
self.branch_pool = nn.Sequential(
nn.MaxPool2d(3, stride=1, padding=1),
conv_relu(in_channel, out4_1, 1)
)
def forward(self, x):
f1 = self.branch1x1(x)
f2 = self.branch3x3(x)
f3 = self.branch5x5(x)
f4 = self.branch_pool(x)
output = torch.cat((f1, f2, f3, f4), dim=1)
return output
# 构建googlenet
class googlenet(nn.Module):
def __init__(self, in_channel, num_classes, verbose=False):
super(googlenet, self).__init__()
self.verbose = verbose
self.block1 = nn.Sequential(
conv_relu(in_channel, out_channel=64, kernel=7, stride=2, padding=3),
nn.MaxPool2d(3, 2)
)
self.block2 = nn.Sequential(
conv_relu(64, 64, kernel=1),
conv_relu(64, 192, kernel=3, padding=1),
nn.MaxPool2d(3, 2)
)
self.block3 = nn.Sequential(
inception(192, 64, 96, 128, 16, 32, 32),
inception(256, 128, 128, 192, 32, 96, 64),
nn.MaxPool2d(3, 2)
)
self.block4 = nn.Sequential(
inception(480, 192, 96, 208, 16, 48, 64),
inception(512, 160, 112, 224, 24, 64, 64),
inception(512, 128, 128, 256, 24, 64, 64),
inception(512, 112, 144, 288, 32, 64, 64),
inception(528, 256, 160, 320, 32, 128, 128),
nn.MaxPool2d(3, 2)
)
self.block5 = nn.Sequential(
inception(832, 256, 160, 320, 32, 128, 128),
inception(832, 384, 182, 384, 48, 128, 128),
nn.AvgPool2d(2)
)
self.classifier = nn.Linear(1024, num_classes)
def forward(self, x):
x = self.block1(x)
if self.verbose:
print('block 1 output: {}'.format(x.shape))
x = self.block2(x)
if self.verbose:
print('block 2 output: {}'.format(x.shape))
x = self.block3(x)
if self.verbose:
print('block 3 output: {}'.format(x.shape))
x = self.block4(x)
if self.verbose:
print('block 4 output: {}'.format(x.shape))
x = self.block5(x)
if self.verbose:
print('block 5 output: {}'.format(x.shape))
x = x.view(x.shape[0], -1)
x = self.classifier(x)
return x
test_net = googlenet(3, 10, True) # 3表示输入数据的通道数,10表示分类类别个数