代码地址
https://github.com/xmy0916/DLNetwork

简介

GoogLeNet是2014年谷歌团队提出的,获得当时ImageNet竞赛分类任务第一名,该网络名称的命名中字母L大写为了致敬LeNet的工作。

重点

1、 引入Inception结构,可以融合不同尺度的特征信息。
2、 使用1 * 1的卷积核进行降维以及映射处理。
3、 添加两个辅助分类器帮助训练。
4、 丢弃全连接层,使用平均池化层大大减少模型参数。

Inception结构

初始版本:

对比与之前的LeNet、AlexNet、Vgg等,他们都是一个串形的结构,将卷积层、池化层进行串联得到网络结构,但是这里的Inception结构将上一层得到的特征图输出到四个分支同时进行处理,将四个分支拿到的特征图在channel上进行拼接得到最后的特征图。因为要对特征图进行channel上的拼接所以需要保证每个分支得到的特征图的长宽相同。
这里参考pytorch官方实现的GoogLeNet中inception结构的代码进行解释:

class Inception(nn.Module):
    def __init__(...):
        ...

    def _forward(self, x: Tensor) -> List[Tensor]:
        branch1 = self.branch1(x)
        branch2 = self.branch2(x)
        branch3 = self.branch3(x)
        branch4 = self.branch4(x)

        outputs = [branch1, branch2, branch3, branch4]
        return outputs

    def forward(self, x: Tensor) -> Tensor:
        outputs = self._forward(x)
        return torch.cat(outputs, 1)

直接看forward部分,pytorch的Module类底层每次调用 _ _ call _ _ 方法的时候都会调用forward函数,在_forward函数中首先拿到了四个分支输出的特征图branch1,branch2,branch3,branch4,最后调用torch.cat函数将四个特征图拼接再返回出去。


降维版本:

在降维版本的Inception中,对比与之前的多了一些1 * 1的卷积核,对于1 * 1卷积核如何进行降维进行一个说明:

1 * 1卷积核减少参数原理

首先假设inception网络的输入shape为:
28 * 28 * 192
对于原始版本的inception的卷积核尺寸依次为:
1 * 1 * 64——3 * 3 * 128——5 * 5 * 32
对于降维版本的inception结构相同的卷积核尺寸不变,新增的1 * 1的卷积核的尺寸从左往右依次为:
1 * 1 * 96——1 * 1 * 16——1 * 1 * 32

那么初始版本的卷积核参数为:1x1x192x64 + 3x3x192x128 + 5x5x192x32
加上降维版本的卷积核参数为:1x1x192x64 + (1x1x192x96 + 3x3x96x128) + (1x1x192x16 +5x5x16x32) + 1x1x192x32
降维后参数量小于初始版本!

辅助分类器


在GoogLeNet中一共有两个辅助分类器,他们的结构如图上框起来的位置所示,这两个辅助分类器的结构完全一样,这两个辅助分类起分别来自Inception4a和Inception4d的输出,下图为原文网络结构的详细数据:

引入原文的对这个辅助分类器结构的描述:

The exact structure of the extra network on the side, including the auxiliary classifier, is as follows:
• An average pooling layer with 5×5 filter size and stride 3, resulting in an 4×4×512 output for the (4a), and 4×4×528 for the (4d) stage.
• A 1×1 convolution with 128 filters for dimension reduction and rectified linear activation.
• A fully connected layer with 1024 units and rectified linear activation.
• A dropout layer with 70% ratio of dropped outputs.
• A linear layer with softmax loss as the classifier (predicting the same 1000 classes as the main classifier, but removed at inference time).

从下往上:
1、 平均池化下采样层,池化核大小 5 * 5,stride为3,那么输出的4 * 4 * 512中的4怎么来的呢?根据表格知道inception4a的输出大小size是14,
根据公式out = (in - F + 2P) / S + 1可以计算出4 = (14 - 5 + 2 * 0)/ 3 + 1。另一个4 * 4 * 528同理。
2、 卷积层,卷积核1 * 1 * 128来降维减少参数
3、全连接层,1024,70%比例的dropout
4、全连接层,1000,对应1000分类
5、SoftMax层,得到概率分布


辅助分类器的作用:

在GoogLeNet的结构中,我们可以发现共有3个softmax层,这是为了避免梯度消失,作者们额外增加了2个辅助的softmax(softmax0、softmax1)用于向前传导梯度。辅助分类器是将中间某一层的输出用作分类,并按一个较小的权重(比如0.3)加到最终分类结果中,这样相当于做了模型融合,同时给网络增加了反向传播的梯度信号,也提供了额外的正则化,对于整个网络的训练很有好处。而在实际测试的时候,这两个额外的softmax会被去掉。
但是实际上在后续的研究中,Google团队研究人员发现辅助分类器在训练早期并没有改善收敛:在两个模型达到高精度之前,有无侧边网络的训练进度看起来几乎相同;接近训练结束,有辅助分支的网络才开始超越没有任何分支的网络的准确性,达到了更高的稳定水平,因此辅助分类器更多的还是起到了一个正则化的作用。

结构

代码

参考pytorch官方实现的GoogLeNet代码,修改到我的工程,官方代码链接:
https://github.com/pytorch/vision/tree/master/torchvision/models
项目链接:
https://github.com/xmy0916/DLNetwork

项目简介

本项目全部在cifar10数据集上测试网络,使用torch vision中的cifar10数据集接口测试,不用自行下载数据集,该数据集包含十类彩色数据飞机( airplane )、汽车( automobile )、鸟类( bird )、猫( cat )、鹿( deer )、狗( dog )、蛙类( frog )、马( horse )、船( ship )和卡车( truck )。每张图片尺寸为32 * 32,对于pytorch官方实现的主干网络代码的预训练模型都是基于ImageNet数据集进行训练的,所以主干网络的代码最后的全连接层输出为1000分类,而我们需要修改成10分类。修改后加载官方预训练模型时需要将不匹配的参数给删除掉。
代码结构如下:



这里和LeNet的一样在cifar10上进行测试,首先采用两种策略进行测试:
1、 Training from scratch
2、 finetune on official pretrained model

策略一比较简单,基本不用修改代码结构,不多赘述,策略二进行了简单的修改:
首先在判断分类类别时如果不等于1000需要对torch.load加载的字典的值进行处理,就是把最后的全连接层的参数给删了赋值我们自己随机初始化的tensor即可,代码对应:

model = GoogLeNet(**kwargs)
        state_dict = load_state_dict_from_url(model_urls['googlenet'],
                                              progress=progress)
        if num_class != 1000:
                weight = torch.Tensor(random.rand(num_class, 1024))
                bias = torch.Tensor(random.rand(num_class))
                state_dict["fc.weight"] = weight
                state_dict["fc.bias"] = bias
                state_dict["aux1.fc2.weight"] = weight
                state_dict["aux1.fc2.bias"] = bias
                state_dict["aux2.fc2.weight"] = weight
                state_dict["aux2.fc2.bias"] = bias


        model.load_state_dict(state_dict)

运行实验

训练

python3 train.py --model googlenet

测试

python3 predict.py --model googlenet

打印网络参数量

python3 params_print.py --model googlenet

参数量对比:

vgg16:
Model Summary: 32 layers, 1.34302e+08 parameters, 1.34302e+08 gradients
googlenet:
Model Summary: 173 layers, 5.61015e+06 parameters, 5.61015e+06 gradients

就此可以看出googlenet因为加入大量的1 * 1卷积核大大的减少了网络的参数量!

实验结果

模型 LeNet AlexNet VggNet GoogLeNet
轮数 5 5 5 5
精度 0.622 0.800、0.860(加pretrain) 0.911(加pretrain) 0.932(加pretrain)
日志 lenet.log alexnet.logalexnet_pretrain.log vggnet.log googlenet.log