前言
2014年,GoogLeNet和VGG是当年ImageNet挑战赛(ILSVRC14)的双雄,GoogLeNet获得了图片分类大赛第一名、VGG紧随其后,这两类模型结构的共同特点是网络深度更深了。VGG继承了LeNet以及AlexNet的一些框架结构,而GoogLeNet则做了更加大胆的网络结构尝试,虽然深度只有22层,但大小却比AlexNet和VGG小很多,GoogleNet参数为500万个,AlexNet参数个数是GoogleNet的12倍,VGGNet参数又是AlexNet的3倍,因此在内存或计算资源有限时,GoogleNet是比较好的选择;从模型结果来看,GoogLeNet的性能却更加优越。
GoogLeNetV1论文下载链接:
https://www.cv-foundation.org/openaccess/content_cvpr_2015/papers/Szegedy_Going_Deeper_With_2015_CVPR_paper.pdf
GoogLeNetV2论文下载链接:
http://proceedings.mlr.press/v37/ioffe15.pdf
GoogLeNetV3论文下载链接:
https://www.cv-foundation.org/openaccess/content_cvpr_2016/papers/Szegedy_Rethinking_the_Inception_CVPR_2016_paper.pdf
GoogLeNetV4论文下载链接:
https://arxiv.org/pdf/1602.07261.pdf
GoogLeNetV5论文下载链接:
https://openaccess.thecvf.com/content_cvpr_2017/papers/Chollet_Xception_Deep_Learning_CVPR_2017_paper.pdf
一、GoogLeNet V1
1、Motivation
一般来说,提升网络性能最直接的办法就是增加网络深度和宽度,或者输入数据的大小;但这种方式存在以下问题:
(1)参数太多,如果训练数据集有限,很容易产生过拟合;
(2)网络越大、参数越多,计算复杂度越大,难以应用;
(3)网络越深,容易出现梯度弥散问题(梯度越往后穿越容易消失),难以优化模型。
文章认为解决上述缺点的根本方法是将全连接甚至一般的卷积都转化为稀疏连接。一方面现实生物神经系统的连接也是稀疏的(即,神经系统传递某种信息时,只有少部分神经元被激活,大部分处于不反应状态);另一方面有文献表明:对于大规模稀疏的神经网络,可以通过分析激活值的统计特性,和对高度相关的输出进行聚类来逐层构建出一个最优网络。这点表明臃肿的稀疏网络可能被不失性能地被简化。
早些的时候,一方面为了打破网络对称性和提高学习能力,另一方面硬件设备能力有限,传统的卷积网络(LeNet时代)都使用了随机稀疏连接(每次卷积得到的feature maps随机选取一部分送入后续计算)。但是,计算机软硬件对非均匀稀疏数据的计算效率很差,所以在AlexNet中又重新启用了常规的卷积(每次卷积得到的feature maps全部送入后续计算),目的是为了更好地优化并行运算。
所以,现在的问题是有没有一种方法,既能保持网络结构的稀疏性,又能利用密集矩阵的高计算性能。大量的文献表明可以将稀疏矩阵聚类为较为密集的子矩阵集合来提高计算性能,据此论文提出了名为Inception 的结构来实现此目的。
2、Architectural Details
作者提出的inception结构如下图所示,将四个不同卷积核尺寸的卷积操作聚类成一个集合。
具体来说,就是将输入信息复制四份,分别送入四个不同的分支,分支中是卷积核尺寸不同的卷积操作,四个分支卷积计算后的feature maps在channel维度上合并,得到一组feature map送入后续操作。如下图所示:
对上图做以下说明:
(1)卷积核的大小在神经网络里是一种超参数,没有一种严格的数学理论证明那种尺寸的卷积核更适合提取特征,因此GoogLeNet选择了成年人的方式:我全都要。
(2)采用不同大小的卷积核意味着不同大小的计算感受野,最后拼接操作意味着不同尺度特征的融合;
(3)之所以卷积核大小采用1、3和5,主要是为了方便对齐。设定卷积步长stride=1之后,只要分别设定pad=0、1、2,那么卷积之后便可以得到相同维度的特征,然后这些特征就可以直接在channel维度拼接在一起了;
(4)文章说很多地方都表明pooling挺有效,所以Inception里面也嵌入了pooling操作。
(5)网络越到后面,特征越抽象,而且每个特征所涉及的感受野也更大了,因此随着层数的增加,3x3和5x5卷积的比例也要增加。
但是,使用5x5的卷积核仍然会带来相对较大的计算量。 为此,作者先采用1x1卷积核来进行降维。
例如:上一层的输出数据形状为(100x100x128),经过具有256个输出的5x5卷积层之后(stride=1,pad=2),输出数据形状为(100x100x256)。其中,卷积层的参数为128x5x5x256。假如上一层输出先经过具有32个输出的1x1卷积层,再经过具有256个输出的5x5卷积层,那么最终的输出数据的形状仍为(100x100x256),但卷积参数量已经减少为128x1x1x32 + 32x5x5x256,大约减少了4倍。
改进后的网络模型子结构inception如下:
完整的GoogLeNet就是由这种inception块堆叠而成的;如下图所示:
对上图做以下说明:
(1) 显然GoogLeNet采用了模块化的结构(stage,block,layer),如上图红黄绿圈出来的方框是不同的stage,每个stage中由很多block(这里的block是inception)堆叠而成,每个block中由很多神经网络层组成。这样模块化结构的优点是方便增添和修改模型结构;
(2) 网络最后采用了average pooling来代替全连接层 ,事实证明可以将TOP1 accuracy提高百分之0.6,而且,average pooling允许网络接收不同大小的图片输入了,在最后还是加了一个全连接层,主要是为了方便以后大家finetune(微调);
(3)最后一个全连接层前依然使用了Dropout ;
(4)为了避免梯度消失,网络额外增加了2个softmax辅助分类器(图中黄色stage的第二个和第四个block处),用于帮助模型反前传导梯度。此外,实际测试的时候,这两个额外的softmax会被去掉。
(5)下图给出网络的详细参数,红黄蓝三个模块与上图相对应。
3. 小结
(1)提出inception块增加网络宽度,在卷积操作时可以提取不同尺度的特征。
(2)提出辅助分类器,为网络训练提供更多梯度信息。
(3)将网络模块化为三个阶段(stage),每个stage内部的feature maps不变,结束后下采样送入下个stage,这种模式在之后的模型中经常出现:例如resnet,mobilenet,shufflenet等等。
二、GoogLeNetV2
GoogLeNetV2最大的贡献就是提出了BatchNormalization数据归一化方法。
1、Motivation
首先,GoogLeNet V1出现的同期,性能与之接近的大概只有VGGNet了,并且二者在图像分类之外的很多领域都得到了成功的应用。但是相比之下,GoogLeNet的计算效率明显高于VGGNet,大约只有500万参数,只相当于Alexnet的1/12(GoogLeNet的caffemodel大约50M,VGGNet的caffemodel则要超过600M)。
而且,二者的发展方向不同;从某种角度可以这样理解:Vgg追求的是网络深度;GoogLeNet追求的是网络宽度
GoogLeNet的表现很好,但是,如果想要通过简单地放大Inception结构来构建更大的网络,则会导致计算过程中的数值不稳定性问题。 为了提升GoogLeNetv1训练速度和稳健性,提出对模型结构的部分做归一化处理,也就是对每个训练的mini-batch做归一化,叫做Batch Normalization(BN)。BN在之后的网络模型中频繁出现,成为神经网络中必不可少的一环,BN的主要好处如下:
(1)BN使得模型可以使用较大的学习率而不用特别关心诸如梯度爆炸或消失等优化问题;
(2)BN降低了模型效果对初始权重的依赖;
(3)BN不仅可以加速收敛,还起到了正则化作用,提高了模型泛化性;
作者认为:网络训练过程中参数不断改变导致后续每一层输入的分布也发生变化,而学习的过程又要使每一层适应输入的分布,因此我们不得不降低学习率、小心地初始化。作者将分布发生变化称之为internal covariate shift。
解决这个问题的方法是在训练网络时将输入减去均值,目的是为了加快训练。为什么减均值可以加快训练呢,这里做一个简单地说明:
首先,图像数据是高度相关的,相似的图像抽象到高维空间的数据分布是接近的。假设其分布如下图a所示(一个点代表一个图像,简化为2维)。由于初始化的时候,我们的参数一般都是0均值的,因此开始的拟合y=Wx+b,基本过原点附近,如图b红色虚线。因此,网络需要经过多次学习才能逐步达到如紫色实线的拟合,即收敛的比较慢。如果我们对输入数据先作减均值操作,如图c,显然可以加快学习。
再举一个可视化的解释:BN就是对神经网络每层输入数据的数据分布,做了下面左图不规律的数据分布到右图规则的数据分布的归一化操作。箭头表示模型寻找最优解的过程,显然右图的方式更方便,更容易。
最后, BN的公式如下:
算法过程:
(1)、沿着通道计算每个batch的均值u
(2)、沿着通道计算每个batch的方差σ^2
(3)、对x做归一化,x’=(x-u)/开根号(σ^2+ε)
(4)、加入缩放和平移变量γ和β ,归一化后的值,y=γx’+β,加入缩放平移变量的原因是: 不一定每次都是标准正态分布,也许需要偏移或者拉伸。保证每一次数据经过归一化后还保留原有学习来的特征,同时又能完成归一化操作,加速训练。这两个参数是用来学习的参数。
2、Architectural Details
GoogLeNetV2网络详细参数如下,除了BN,基本没有什么太大的变动。
3. 小结
提出了batch normalization数据归一化操作。
三、GoogLeNetV3
GoogLeNet Inception V3在《Rethinking the Inception Architecture for Computer Vision》中提出,该论文的亮点在于:
(1)提出四个通用的网络结构设计准则
(2)引入卷积分解提高效率(空间可分离卷积)
(3)引入高效的feature map降维方法
(4)平滑样本标签
1、Motivation
在V1版本中,文章也没给出有关构建Inception结构注意事项的清晰描述。因此,在文章中作者首先给出了一些已经被证明有效的用于放大网络的通用准则和优化方法。这些准则和方法适用但不局限于Inception结构。
2 、General Design Principles
(1)避免特征表示上的瓶颈,尤其在神经网络的前若干层
神经网络包含一个自动提取特征的过程,例如多层卷积。一个直观并符合常识的理解是:如果在网络初期特征提取的太粗,细节已经丢了,后续即使结构再精细也没法做有效表示了;
举个例子,刚开始就直接从35×35×320被抽样降维到了17×17×320,特征细节被大量丢失,即使后面有Inception去做各种特征提取和组合也没用,因此,在对feature map进行降维的同时,一般会对channel通道进行升维。
所以feature map的大小应该是随着层数的加深逐步变小,但为了保证特征能得到有效表示和组合,其通道数量会逐渐增加。一个简单的理解是:我们希望卷积操作在图像的空间维度上进行特征提取,并把提取到的特征转移到channel维度上。
(2)增加卷积次数可以解耦更多特征,帮助网络的收敛
相互独立的输出特征越多,输入的信息就被分解的越彻底,子特征内部相关性高,把相关性强的特征聚集在了一起会更容易收敛。简单点说就是,提取到的特征越多,对下游任务的帮助就越大(例如,相比较于只知道眼睛这一种特征,如果知道五官的所有特征,那么对识别某个人来说越简单,即问题越容易收敛)。
对于神经网络的某一层,通过更多的输出分支,可以产生互相解耦的特征表示,从而产生更多高阶稀疏特征,而加速收敛,具体方法如下:
首先,一个前置小知识:在保证计算感受野相同的前提下,5X5大小的卷积核可以使用两个3X3的小卷积核代替。3X3 大小的卷积核可以使用一个3X1 和一个 1X3的小卷积核代替。具体图例如下所示:
所以,为了在网络中增加更多的卷积次数,GoogLeNetV3对inception做了如下改进:首先将inception中的5X5卷积使用两个3X3卷积进行替代,再组合使用1X3和3X1的卷积来替代3X3的卷积。
值得一提的是:一个n×n卷积核可以分解为通过顺序相连的两个1×n和n×1的卷积,这种操作也称空间可分离卷积(有点像矩阵分解),如果n=3,计算性能可以提升1-(3+3)/9=33%。当然,它的缺点也很明显,并不是所有的卷积核都可以拆成两个1×n和n×1卷积核相乘的形式。实际上,作者发现在网络的前期使用这种分解效果并不好,只有在中度大小的feature map上使用效果才会更好。(对于mxm大小的feature map,建议m在12到20之间)。
至于Figure7中的1 x n和 n x 1 的卷积组合方式是并联,是因为作者希望模型变得更宽而不是更深,以解决表征性瓶颈。如果该模块没有被拓展宽度,而是变得更深,那么维度会过多减少,造成信息损失。在General Design Principles的1和2中也有解释。
(3) 合理的压缩特征维度数,来减少计算量
inception-v1中提出的用1x1卷积先降维再作特征提取就是利用这点。其原因是相邻单元之间的强关联性导致在降维过程中信息的损失要小得多,如果输出被用于空间聚合的情况下。鉴于这些信号应该是容易压缩的,降维甚至会促进更快的学习。
整个网络结构的深度和宽度(特征维度数)要做到平衡
只有等比例的增大深度和维度才能最大限度的提升网络的性能。很有道理,也很模糊,没有给出具体的参数指导,后期的模型如 EfficientNet(V1, V2)就填了这个坑。
3 . 优化辅助分类器
作者发现V1中的辅助分类器有点问题:auxiliary classifiers在训练初期的时候并不能加速收敛,只有当训练快结束的时候它才会略微提高网络精度。于是就把第一个auxiliary classifiers去掉了。(又回去了…)。不过,作者认为auxiliary classifiers能够起到regularizer的作用。
4. 优化下采样操作
一般情况下,如果想让图像缩小,可以有如下两种方式:
先池化再作Inception卷积,或者先作Inception卷积再作池化。
方法一(左图)先作pooling(池化)会导致特征表示遇到瓶颈(特征缺失),
方法二(右图)是正常的缩小,但计算量很大。
为了同时保持特征表示且降低计算量,将网络结构改为下图,使用两个并行化的模块来降低计算量(卷积、池化并行执行,再进行合并):
5. 优化标签
深度学习用的分类labels一般都是one hot向量【one-hot向量为有且只有一个元素为1,其余元素都为0的向量。one-hot向量是在数字电路中的一种状态编码,指对任意给定的状态,状态寄存器中只有1位为1,其余位都为0。】,用来指示classifier的唯一结果,这样的labels有点类似信号与系统里的脉冲函数,或者叫“Dirac delta”,即只在某一位置取1,其它位置都是0。
有文献证明Labels的脉冲性质会引发两个不良后果:一是over-fitting,另外一个是降低了网络的适应性。
一种解决方法是加正则项,即对样本标签给个概率分布做调节:
在网络实现的时候,令 label_smoothing = 0.1,num_classes = 1000。Label smooth提高了网络精度0.2%。
Label smooth 把原来很突兀的one_hot_labels稍微的平滑了一点,避免了网络过度学习labels而产生的弊端。
6、Architectural Details
四、GoogLeNetV4
《Inception-v4, Inception-ResNet and the Impact of Residual Connections on Learning》一文中的亮点是:提出了效果更好的GoogLeNet Inception v4网络结构;与残差网络融合,提出效果不逊于v4但训练速度更快的GoogLeNet Inception ResNet结构。
1、 GoogLeNet Inception V4网络结构
2、GoogLeNet Inception Residual V4网络结构
整体来看,模型变的更加复杂了,有点让人眼花缭乱的感觉。这样复杂的模型设计实际上融合了大量的人为先验知识;不如像resne这样简单的模型,通过大量的数据来让模型自己学习总结知识。模型自己学习到的知识往往比人为赋予的归纳偏置上限更高。
3 、小结
(1)提出了第四代GoogLeNet
(2)与残差网络ResNet相结合
五、GoogLeNetV5
深度可分离卷积(Depthwise Separable Convolution)率先是由 Laurent Sifre在其博士论文《Rigid-Motion Scattering For Image Classification》中提出。经典的MobileNet系列算法便是采用深度可分离卷积作为其核心结构。
这篇文章主要从Inception 的角度出发,探讨了Inception和深度可分离卷积的关系,从一个全新的角度解释了深度可分离卷积。再结合经典的残差网络,一个新的架构Xception应运而生。Xception取义自Extreme Inception,即Xception是一种极端的Inception,下面我们来看看它是怎样的一种极端法。
1. inception回顾
Inception的核心思想是将channel分成若干个不同感受野大小的通道,除了能获得不同的感受野,Inception还能大幅的降低参数数量。我们看下图1中一个简单版本的Inception模型:
2. inception改进
对于一个输入的Feature Map,首先通过三组 1X1 卷积得到三组Feature Map,它和先使用一组 1X1 卷积得到Feature Map,再将这组Feature Map分成三组是完全等价的(图2)。假设图1中 1X1 卷积核的个数都是 K1 ,3X3 的卷积核的个数都是 K2,输入Feature Map的通道数为 m ,那么这个简单版本的参数个数为:
对比相同通道数,但是没有分组的普通卷积,普通卷积的参数数量为:
m x k1 + 3 x 3 x k1 x k2
参数数量约为图2Inception的三倍。
值得注意的是,像图2中这种基于分组的卷积(不含图中1×1卷积部分)称为“Group convolution”,即分组卷积。分组卷积中的K有两个极端值,一个是1,一个是N。经典的ShuffleNet系列算法便是采用深度可分离卷积作为其核心结构。
3. Xception
如果Inception是将 3x3 卷积分成3组,那么考虑一种极端的情况,我们如果将Inception的 1x1 得到的 k1 个通道的Feature Map完全分开呢?也就是使用 k1 个不同的 3x3 卷积分别在每个通道上进行卷积,它的参数数量是 m x k1 + k1 x 3 x 3
这个的参数数量是普通卷积的 1/k ,我们把这种形式的Inception叫做Extreme Inception,如图3所示。
4. 对比深度可分离卷积
深度可分离卷积的详细介绍请移步MobileNet系列文章,下面是深度可分离卷积的操作简图:
可以看出,两者非常类似。唯一的区别是 1X1 卷积的执行顺序是在 Depthwise Conv之前还是之后而已。 两个算法的提出时间近似,不存在谁抄袭谁的问题。他们从不同的角度揭示了深度可分离卷积的强大作用,MobileNet的思路是通过将普通卷积拆分的形式来减少参数数量,而Xception是通过对Inception的充分解耦来完成的。
5. Architectural Details
6. 小结
Xception 作为GoogLeNet系列文章的终章,竟然放弃了 InceptionV1,V2,V3,V4里面的11、33、5*5卷积核并列的结构。这是实验结果导向的,因为Xception的效果比之前版本的效果都好, 与V4中复杂的模型结构相比,Xception这种简单的模型结构反而达到了更好的性能。其实,这是因为V4的设计中作者给模型设计的结构体现了太多人为的思想,即,我们过分的想把人类自己对图像理解的归纳偏置赋予模型了,但是,模型跟人的思维方式毕竟是不同的体系,不如设计一个简单的结构,让模型自己从数据中来学习更加有效。古人早就说过了:大道至简。
六、代码实现
这里给出V1模型搭建的python代码(基于pytorch实现)。
import torch.nn as nn
import torch
import torch.nn.functional as F
class BasicConv2d(nn.Module):
def __init__(self, in_channels, out_channels, **kwargs):
super(BasicConv2d, self).__init__()
self.conv = nn.Conv2d(in_channels, out_channels, **kwargs)
self.relu = nn.ReLU(inplace=True)
def forward(self, x):
x = self.conv(x)
x = self.relu(x)
return x
class Inception(nn.Module):
def __init__(self, in_channels, ch1x1, ch3x3red, ch3x3, ch5x5red, ch5x5, pool_proj):
super(Inception, self).__init__()
self.branch1 = BasicConv2d(in_channels, ch1x1, kernel_size=1)
self.branch2 = nn.Sequential(
BasicConv2d(in_channels, ch3x3red, kernel_size=1),
BasicConv2d(ch3x3red, ch3x3, kernel_size=3, padding=1) # 保证输出大小等于输入大小
)
self.branch3 = nn.Sequential(
BasicConv2d(in_channels, ch5x5red, kernel_size=1),
BasicConv2d(ch5x5red, ch5x5, kernel_size=5, padding=2) # 保证输出大小等于输入大小
)
self.branch4 = nn.Sequential(
nn.MaxPool2d(kernel_size=3, stride=1, padding=1),
BasicConv2d(in_channels, pool_proj, kernel_size=1)
)
def forward(self, x):
branch1 = self.branch1(x)
branch2 = self.branch2(x)
branch3 = self.branch3(x)
branch4 = self.branch4(x)
outputs = [branch1, branch2, branch3, branch4]
return torch.cat(outputs, 1)
class InceptionAux(nn.Module):
def __init__(self, in_channels, num_classes):
super(InceptionAux, self).__init__()
self.averagePool = nn.AvgPool2d(kernel_size=5, stride=3)
self.conv = BasicConv2d(in_channels, 128, kernel_size=1) # output[batch, 128, 4, 4]
self.fc1 = nn.Linear(2048, 1024)
self.fc2 = nn.Linear(1024, num_classes)
def forward(self, x):
# aux1: N x 512 x 14 x 14, aux2: N x 528 x 14 x 14
x = self.averagePool(x)
# aux1: N x 512 x 4 x 4, aux2: N x 528 x 4 x 4
x = self.conv(x)
# N x 128 x 4 x 4
x = torch.flatten(x, 1)
x = F.dropout(x, 0.5, training=self.training)
# N x 2048
x = F.relu(self.fc1(x), inplace=True)
x = F.dropout(x, 0.5, training=self.training)
# N x 1024
x = self.fc2(x)
# N x num_classes
return x
class GoogLeNet(nn.Module):
def __init__(self, num_classes=1000, aux_logits=False, init_weights=False):
super(GoogLeNet, self).__init__()
self.aux_logits = aux_logits
self.conv1 = BasicConv2d(3, 64, kernel_size=7, stride=2, padding=3)
self.maxpool1 = nn.MaxPool2d(3, stride=2, ceil_mode=True)
self.conv2 = BasicConv2d(64, 64, kernel_size=1)
self.conv3 = BasicConv2d(64, 192, kernel_size=3, padding=1)
self.maxpool2 = nn.MaxPool2d(3, stride=2, ceil_mode=True)
self.inception3a = Inception(192, 64, 96, 128, 16, 32, 32)
self.inception3b = Inception(256, 128, 128, 192, 32, 96, 64)
self.maxpool3 = nn.MaxPool2d(3, stride=2, ceil_mode=True)
self.inception4a = Inception(480, 192, 96, 208, 16, 48, 64)
self.inception4b = Inception(512, 160, 112, 224, 24, 64, 64)
self.inception4c = Inception(512, 128, 128, 256, 24, 64, 64)
self.inception4d = Inception(512, 112, 144, 288, 32, 64, 64)
self.inception4e = Inception(528, 256, 160, 320, 32, 128, 128)
self.maxpool4 = nn.MaxPool2d(3, stride=2, ceil_mode=True)
self.inception5a = Inception(832, 256, 160, 320, 32, 128, 128)
self.inception5b = Inception(832, 384, 192, 384, 48, 128, 128)
if self.aux_logits:
self.aux1 = InceptionAux(512, num_classes)
self.aux2 = InceptionAux(528, num_classes)
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
self.dropout = nn.Dropout(0.4)
self.fc = nn.Linear(1024, num_classes)
if init_weights:
self._initialize_weights()
def forward(self, x):
# N x 3 x 224 x 224
x = self.conv1(x)
# N x 64 x 112 x 112
x = self.maxpool1(x)
# N x 64 x 56 x 56
x = self.conv2(x)
# N x 64 x 56 x 56
x = self.conv3(x)
# N x 192 x 56 x 56
x = self.maxpool2(x)
# N x 192 x 28 x 28
x = self.inception3a(x)
# N x 256 x 28 x 28
x = self.inception3b(x)
# N x 480 x 28 x 28
x = self.maxpool3(x)
# N x 480 x 14 x 14
x = self.inception4a(x)
# N x 512 x 14 x 14
if self.training and self.aux_logits: # eval model lose this layer
aux1 = self.aux1(x)
x = self.inception4b(x)
# N x 512 x 14 x 14
x = self.inception4c(x)
# N x 512 x 14 x 14
x = self.inception4d(x)
# N x 528 x 14 x 14
if self.training and self.aux_logits: # eval model lose this layer
aux2 = self.aux2(x)
x = self.inception4e(x)
# N x 832 x 14 x 14
x = self.maxpool4(x)
# N x 832 x 7 x 7
x = self.inception5a(x)
# N x 832 x 7 x 7
x = self.inception5b(x)
# N x 1024 x 7 x 7
x = self.avgpool(x)
# N x 1024 x 1 x 1
x = torch.flatten(x, 1)
# N x 1024
x = self.dropout(x)
x = self.fc(x)
# N x 1000 (num_classes)
if self.training and self.aux_logits: # eval model lose this layer
return x, aux2, aux1
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)
def googlenet(num_classes):
model = GoogLeNet( num_classes=num_classes)
return model