论文:Going deeper with convolutions
Google Inception Net 首次出现在 ILSVRC 2014的比赛中(和VGGNet 同年),就以较大优势取得了第一名。它最大的特点就是控制了计算量和参数量的同时,获得了非常好的分类性能——top-5 错误率 6.67%。Inception V1 有22 层深,比 AlexNet的8层或者 VGGNet的19层还要更深。但其大小却比AlexNet和VGG小很多,计算量只有 15亿次浮点运算,同时只有500万的参数量,仅为 AlexNet 参数量(6000万)的 1/12。
我们在ImageNet大规模视觉识别挑战赛2014(ILSVRC14)上提出了一种代号为Inception的深度卷积神经网络结构,并在分类和检测上取得了新的最好结果。这个架构的主要特点是提高了网络内部计算资源的利用率。通过精心的手工设计,我们在增加了网络深度和广度的同时保持了计算预算不变。为了优化质量,架构的设计以赫布理论和多尺度处理直觉为基础。我们在ILSVRC14提交中应用的一个特例被称为GoogLeNet,一个22层的深度网络,其质量在分类和检测的背景下进行了评估。
过去三年中,由于深度学习和卷积网络的发展,我们的目标分类和检测能力得到了显著提高。一个令人鼓舞的消息是,大部分的进步不仅仅是更强大硬件、更大数据集、更大模型的结果,而主要是新的想法、算法和网络结构改进的结果。例如,ILSVRC 2014竞赛中最靠前的输入除了用于检测目的的分类数据集之外,没有使用新的数据资源。我们在ILSVRC 2014中的GoogLeNet提交实际使用的参数只有两年前Krizhevsky等人获胜结构参数的1/12,而结果明显更准确。在目标检测前沿,最大的收获不是来自于越来越大的深度网络的简单应用,而是来自于深度架构和经典计算机视觉的协同,像Girshick等人的R-CNN算法那样。
另一个显著因素是随着移动和嵌入式设备的推动,我们的算法的效率很重要——尤其是它们的电力和内存使用。值得注意的是,正是包含了这个因素的考虑才得出了本文中呈现的深度架构设计,而不是单纯的为了提高准确率。对于大多数实验来说,模型被设计为在一次推断中保持15亿乘加的计算预算,所以最终它们不是单纯的学术好奇心,而是能在现实世界中应用,甚至是以合理的代价在大型数据集上使用。
在本文中,我们将关注一个高效的计算机视觉深度神经网络架构,代号为Inception,它的名字来自于Lin等人网络论文中的Network与著名的“we need to go deeper”网络迷的结合。在我们的案例中,单词“deep”用在两个不同的含义中:首先,在某种意义上,我们以“Inception module”的形式引入了一种新层次的组织方式,在更直接的意义上增加了网络的深度。一般来说,可以把Inception模型看作论文的逻辑顶点同时从Arora等人的理论工作中受到了鼓舞和引导。这种架构的好处在ILSVRC 2014分类和检测挑战赛中通过实验得到了验证,它明显优于目前的最好水平。
从LeNet-5开始,卷积神经网络(CNN)通常有一个标准结构——堆叠的卷积层(后面可以选择有对比归一化和最大池化)后面是一个或更多的全连接层。这个基本设计的变种在图像分类著作流行,并且目前为止在MNIST,CIFAR和更著名的ImageNet分类挑战赛中的已经取得了最佳结果。对于更大的数据集例如ImageNet来说,最近的趋势是增加层的数目和层的大小,同时使用丢弃来解决过拟合问题。
尽管担心最大池化层会引起准确空间信息的损失,但与相同的卷积网络结构也已经成功的应用于定位,目标检测和行人姿态估计。
受灵长类视觉皮层神经科学模型的启发,Serre等人使用了一系列固定的不同大小的Gabor滤波器来处理多尺度。我们使用一个了类似的策略。然而,与Thomas Serre的固定的2层深度模型相反,Inception结构中所有的滤波器是学习到的。此外,Inception层重复了很多次,在GoogleNet模型中得到了一个22层的深度模型。
Network-in-Network是Lin等人为了增加神经网络表现能力而提出的一种方法。在他们的模型中,网络中添加了额外的1 × 1卷积层,增加了网络的深度。我们的架构中大量的使用了这个方法。但是,在我们的设置中,1 × 1卷积有两个目的:最关键的是,它们主要是用来作为降维模块来移除卷积瓶颈,否则将会限制我们网络的大小。这不仅允许了深度的增加,而且允许我们网络的宽度增加但没有明显的性能损失。
最后,目前最好的目标检测是Girshick等人的基于区域的卷积神经网络(R-CNN)方法。R-CNN将整个检测问题分解为两个子问题:利用低层次的信号例如颜色,纹理以跨类别的方式来产生目标位置候选区域,然后用CNN分类器来识别那些位置上的对象类别。这样一种两个阶段的方法利用了低层特征分割边界框的准确性,也利用了目前的CNN非常强大的分类能力。我们在我们的检测提交中采用了类似的方式,但探索增强这两个阶段,例如对于更高的目标边界框召回使用多盒预测,并融合了更好的边界框候选区域分类方法。
提高深度神经网络性能最直接的方式是增加它们的尺寸。这不仅包括增加深度——网络层次的数目——也包括它的宽度:每一层的单元数目。这是一种训练更高质量模型容易且安全的方法,尤其是在可获得大量标注的训练数据的情况下。但是这个简单方案有两个主要的缺点。更大的尺寸通常意味着更多的参数,这会使增大的网络更容易过拟合,尤其是在训练集的标注样本有限的情况下。这是一个主要的瓶颈,因为要获得强标注数据集费时费力且代价昂贵,经常需要专家评委在各种细粒度的视觉类别进行区分,例如图1中显示的ImageNet中的类别(甚至是1000类ILSVRC的子集)。
统一增加网络尺寸的另一个缺点是计算资源使用的显著增加。例如,在一个深度视觉网络中,如果两个卷积层相连,它们的滤波器数目的任何统一增加都会引起计算量平方式的增加。如果增加的能力使用时效率低下(例如,如果大多数权重结束时接近于0),那么会浪费大量的计算能力。由于计算预算总是有限的,计算资源的有效分布更偏向于尺寸无差别的增加,即使主要目标是增加性能的质量。
解决这两个问题的一个基本的方式就是引入稀疏性并将全连接层替换为稀疏的全连接层,甚至是卷积层。除了模仿生物系统之外,由于Arora等人的开创性工作,这也具有更坚固的理论基础优势。他们的主要成果说明如果数据集的概率分布可以通过一个大型稀疏的深度神经网络表示,则最优的网络拓扑结构可以通过分析前一层激活的相关性统计和聚类高度相关的神经元来一层层的构建。虽然严格的数学证明需要在很强的条件下,但事实上这个声明与著名的赫布理论产生共鸣——神经元一起激发,一起连接——实践表明,基础概念甚至适用于不严格的条件下。
遗憾的是,当碰到在非均匀的稀疏数据结构上进行数值计算时,现在的计算架构效率非常低下。即使算法运算的数量减少100倍,查询和缓存丢失上的开销仍占主导地位:切换到稀疏矩阵可能是不可行的。随着稳定提升和高度调整的数值库的应用,差距仍在进一步扩大,数值库要求极度快速密集的矩阵乘法,利用底层的CPU或GPU硬件的微小细节。非均匀的稀疏模型也要求更多的复杂工程和计算基础结构。目前大多数面向视觉的机器学习系统通过采用卷积的优点来利用空域的稀疏性。然而,卷积被实现为对上一层块的密集连接的集合。为了打破对称性,提高学习水平,从论文开始,ConvNets习惯上在特征维度使用随机的稀疏连接表,然而为了进一步优化并行计算,论文中趋向于变回全连接。目前最新的计算机视觉架构有统一的结构。更多的滤波器和更大的批大小要求密集计算的有效使用。
这提出了下一个中间步骤是否有希望的问题:一个架构能利用滤波器水平的稀疏性,正如理论所建议的那样,但能通过利用密集矩阵计算来利用我们目前的硬件。稀疏矩阵乘法的大量文献认为对于稀疏矩阵乘法,将稀疏矩阵聚类为相对密集的子矩阵会有更佳的性能。在不久的将来会利用类似的方法来进行非均匀深度学习架构的自动构建,这样的想法似乎并不牵强。
Inception架构开始是作为案例研究,用于评估一个复杂网络拓扑构建算法的假设输出,该算法试图近似中所示的视觉网络的稀疏结构,并通过密集的、容易获得的组件来覆盖假设结果。尽管是一个非常投机的事情,但与基于的参考网络相比,早期可以观测到适度的收益。随着一点点调整加宽差距,作为基础网络,Inception被证明在定位上下文和目标检测中尤其有用。有趣的是,虽然大多数最初的架构选择已被质疑并分离开进行全面测试,但结果证明它们是局部最优的。然而必须谨慎:尽管Inception架构在计算机上领域取得成功,但这是否可以归因于构建其架构的指导原则仍是有疑问的。确保这一点将需要更彻底的分析和验证。
Inception架构的主要想法是考虑怎样近似卷积视觉网络的最优稀疏结构并用容易获得的密集组件进行覆盖。注意假设转换不变性,这意味着我们的网络将以卷积构建块为基础。我们所需要做的是找到最优的局部构造并在空间上重复它。Arora等人提出了一个层次结构,其中应该分析最后一层的相关统计并将它们聚集成具有高相关性的单元组。这些聚类形成了下一层的单元并与前一层的单元连接。我们假设较早层的每个单元都对应输入层的某些区域,并且这些单元被分成滤波器组。在较低的层(接近输入的层)相关单元集中在局部区域。因此,我们最终会有许多聚类集中在单个区域,它们可以通过下一层的1×1卷积层覆盖。然而也可以预期,将存在更小数目的在更大空间上扩展的聚类,其可以被更大块上的卷积覆盖,在越来越大的区域上块的数量将会下降。为了避免块校正的问题,目前Inception架构形式的滤波器的尺寸仅限于1×1、3×3、5×5,这个决定更多的是基于便易性而不是必要性。这也意味着提出的架构是所有这些层的组合,其输出滤波器组连接成单个输出向量形成了下一阶段的输入。另外,由于池化操作对于目前卷积网络的成功至关重要,因此建议在每个这样的阶段添加一个替代的并行池化路径应该也应该具有额外的有益效果(看图2(a))。
由于这些“Inception模块”在彼此的顶部堆叠,其输出相关统计必然有变化:由于较高层会捕获较高的抽象特征,其空间集中度预计会减少。这表明随着转移到更高层,3×3和5×5卷积的比例应该会增加。
上述模块的一个大问题是在具有大量滤波器的卷积层之上,即使适量的5×5卷积也可能是非常昂贵的,至少在这种朴素形式中有这个问题。一旦池化单元添加到混合中,这个问题甚至会变得更明显:输出滤波器的数量等于前一阶段滤波器的数量。池化层输出和卷积层输出的合并会导致这一阶段到下一阶段输出数量不可避免的增加。虽然这种架构可能会覆盖最优稀疏结构,但它会非常低效,导致在几个阶段内计算量爆炸。
这导致了Inception架构的第二个想法:在计算要求会增加太多的地方,明智地减少维度。这是基于嵌入的成功:甚至低维嵌入可能包含大量关于较大图像块的信息。然而嵌入以密集、压缩形式表示信息并且压缩信息更难处理。这种表示应该在大多数地方保持稀疏(根据[2]中条件的要求】)并且仅在它们必须汇总时才压缩信号。也就是说,在昂贵的3×3和5×5卷积之前,1×1卷积用来计算降维。除了用来降维之外,它们也包括使用线性修正单元使其两用。最终的结果如图2(b)所示。
通常,Inception网络是一个由上述类型的模块互相堆叠组成的网络,偶尔会有步长为2的最大池化层将网络分辨率减半。出于技术原因(训练过程中内存效率),只在更高层开始使用Inception模块而在更低层仍保持传统的卷积形式似乎是有益的。这不是绝对必要的,只是反映了我们目前实现中的一些基础结构效率低下。
该架构的一个有用的方面是它允许显著增加每个阶段的单元数量,而不会在后面的阶段出现计算复杂度不受控制的爆炸。这是在尺寸较大的块进行昂贵的卷积之前通过普遍使用降维实现的。此外,设计遵循了实践直觉,即视觉信息应该在不同的尺度上处理然后聚合,为的是下一阶段可以从不同尺度同时抽象特征。
计算资源的改善使用允许增加每个阶段的宽度和阶段的数量,而不会陷入计算困境。可以利用Inception架构创建略差一些但计算成本更低的版本。我们发现所有可用的控制允许计算资源的受控平衡,导致网络比没有Inception结构的类似执行网络快3—10倍,但是在这一点上需要仔细的手动设计。
通过“GoogLeNet”这个名字,我们提到了在ILSVRC 2014竞赛的提交中使用的Inception架构的特例。我们也使用了一个稍微优质的更深更宽的Inception网络,但将其加入到组合中似乎只稍微提高了结果。我们忽略了该网络的细节,因为经验证据表明确切架构的参数影响相对较小。表1说明了竞赛中使用的最常见的Inception实例。这个网络(用不同的图像块采样方法训练的)使用了我们组合中7个模型中的6个。
所有的卷积都使用了修正线性激活,包括Inception模块内部的卷积。在我们的网络中感受野是在均值为0的RGB颜色空间中,大小是224×224。“#3×3 reduce”和“#5×5 reduce”表示在3×3和5×5卷积之前,降维层使用的1×1滤波器的数量。在pool proj列可以看到内置的最大池化之后,投影层中1×1滤波器的数量。所有的这些降维/投影层也都使用了线性修正激活。
网络的设计考虑了计算效率和实用性,因此推断可以单独的设备上运行,甚至包括那些计算资源有限的设备,尤其是低内存占用的设备。当只计算有参数的层时,网络有22层(如果我们也计算池化层是27层)。构建网络的全部层(独立构建块)的数目大约是100。确切的数量取决于机器学习基础设施对层的计算方式。分类器之前的平均池化是基于[12]的,尽管我们的实现有一个额外的线性层。线性层使我们的网络能很容易地适应其它的标签集,但它主要是为了方便使用,我们不期望它有重大的影响。我们发现从全连接层变为平均池化,提高了大约top-1 0.6%的准确率,然而即使在移除了全连接层之后,丢失的使用还是必不可少的。
给定深度相对较大的网络,有效传播梯度反向通过所有层的能力是一个问题。在这个任务上,更浅网络的强大性能表明网络中部层产生的特征应该是非常有识别力的。通过将辅助分类器添加到这些中间层,可以期望较低阶段分类器的判别力。这被认为是在提供正则化的同时克服梯度消失问题。这些分类器采用较小卷积网络的形式,放置在Inception (4a)和Inception (4b)模块的输出之上。在训练期间,它们的损失以折扣权重(辅助分类器损失的权重是0.3)加到网络的整个损失上。在推断时,这些辅助网络被丢弃。后面的控制实验表明辅助网络的影响相对较小(约0.5),只需要其中一个就能取得同样的效果。
包括辅助分类器在内的附加网络的具体结构如下:
GoogLeNet网络使用DistBelief分布式机器学习系统进行训练,该系统使用适量的模型和数据并行。尽管我们仅使用一个基于CPU的实现,但粗略的估计表明GoogLeNet网络可以用更少的高端GPU在一周之内训练到收敛,主要的限制是内存使用。我们的训练使用异步随机梯度下降,动量参数为0.9,固定的学习率计划(每8次遍历下降学习率4%)。Polyak平均在推断时用来创建最终的模型。
图像采样方法在过去几个月的竞赛中发生了重大变化,并且已收敛的模型在其他选项上进行了训练,有时还结合着超参数的改变,例如丢弃和学习率。因此,很难对训练这些网络的最有效的单一方式给出明确指导。让事情更复杂的是,受Andrew G的启发,一些模型主要是在相对较小的裁剪图像进行训练,其它模型主要是在相对较大的裁剪图像上进行训练。然而,一个经过验证的方案在竞赛后工作地很好,包括各种尺寸的图像块的采样,它的尺寸均匀分布在图像区域的8%–100%之间,方向角限制为 [ 3 4 , 4 3 ] [\frac{3}{4},\frac{4}{3}] [43,34]之间。另外,我们发现Andrew Howard的光度扭曲对于克服训练数据成像条件的过拟合是有用的。
ILSVRC 2014分类挑战赛包括将图像分类到ImageNet层级中1000个叶子结点类别的任务。训练图像大约有120万张,验证图像有5万张,测试图像有10万张。每一张图像与一个实际类别相关联,性能度量基于分类器预测的最高分。通常报告两个数字:top-1准确率,比较实际类别和第一个预测类别,top-5错误率,比较实际类别与前5个预测类别:如果图像实际类别在top-5中,则认为图像分类正确,不管它在top-5中的排名。挑战赛使用top-5错误率来进行排名。
我们参加竞赛时没有使用外部数据来训练。除了本文中前面提到的训练技术之外,我们在获得更高性能的测试中采用了一系列技巧,描述如下:
在本文的其余部分,我们分析了有助于最终提交整体性能的多个因素。
竞赛中我们的最终提交在验证集和测试集上得到了top-5 6.67%的错误率,在其它的参与者中排名第一。与2012年的SuperVision方法相比相对减少了56.5%,与前一年的最佳方法(Clarifai)相比相对减少了约40%,这两种方法都使用了外部数据训练分类器。表2显示了过去三年中一些表现最好的方法的统计。
我们也分析报告了多种测试选择的性能,当预测图像时通过改变表3中使用的模型数目和裁剪图像数目。
ILSVRC检测任务是为了在200个可能的类别中生成图像中目标的边界框。如果检测到的对象匹配的它们实际类别并且它们的边界框重叠至少50%(使用Jaccard索引),则将检测到的对象记为正确。无关的检测记为假阳性且被惩罚。与分类任务相反,每张图像可能包含多个对象或没有对象,并且它们的尺度可能是变化的。报告的结果使用平均精度均值(mAP)。GoogLeNet检测采用的方法类似于R-CNN,但用Inception模块作为区域分类器进行了增强。此外,为了更高的目标边界框召回率,通过选择搜索方法和多箱预测相结合改进了区域生成步骤。为了减少假阳性的数量,超分辨率的尺寸增加了2倍。这将选择搜索算法的区域生成减少了一半。我们总共补充了200个来自多盒结果的区域生成,大约使用60%的区域,同时将覆盖率从92%提高到93%。减少区域生成的数量,增加覆盖率的整体影响是对于单个模型的情况平均精度均值增加了1%。最后,等分类单个区域时,我们使用了6个GoogLeNets的组合。这导致准确率从40%提高到43.9%。注意,与R-CNN相反,由于缺少时间我们没有使用边界框回归。
我们首先报告了最好检测结果,并显示了从第一版检测任务以来的进展。与2013年的结果相比,准确率几乎翻了一倍。所有表现最好的团队都使用了卷积网络。我们在表4中报告了官方的分数和每个队伍的常见策略:使用外部数据、集成模型或上下文模型。外部数据通常是ILSVRC12的分类数据,用来预训练模型,后面在检测数据集上进行改善。一些团队也提到使用定位数据。由于定位任务的边界框很大一部分不在检测数据集中,所以可以用该数据预训练一般的边界框回归器,这与分类预训练的方式相同。GoogLeNet输入没有使用定位数据进行预训练。
在表5中,我们仅比较了单个模型的结果。最好性能模型是Deep Insight的,令人惊讶的是3个模型的集合仅提高了0.3个点,而GoogLeNet在模型集成时明显获得了更好的结果。
我们的结果取得了坚实的证据,即通过易获得的密集构造块来近似期望的最优稀疏结果是改善计算机视觉神经网络的一种可行方法。相比于较浅且较窄的架构,这个方法的主要优势是在计算需求适度增加的情况下有显著的质量收益。
我们的目标检测工作虽然没有利用上下文,也没有执行边界框回归,但仍然具有竞争力,这进一步显示了Inception架构优势的证据。
对于分类和检测,预期通过更昂贵的类似深度和宽度的非Inception类型网络可以实现类似质量的结果。然而,我们的方法取得了可靠的证据,即转向更稀疏的结构一般来说是可行有用的想法。这表明未来的工作将在先前基础上以自动化方式创建更稀疏更精细的结构,以及将Inception架构的思考应用到其他领域。
GoogLeNet的特点是提升了计算资源的利用率,可以在保持网络计算资源不变的前提下,通过工艺上的设计来增加网络的宽度和深度,基于Hebbian法则和多尺度处理来优化性能。 在深度学习中, 提升网络性能最直接的办法就是增加网络深度和宽度, 这也就意味着巨量的参数。但是, 巨量参数容易产生过拟合也会大大增加计算量。
一般来说,提升网络性能最直接的办法就是增加网络深度和宽度,深度指网络层次数量、宽度指神经元数量。但这种方式存在以下问题:
(1)参数太多,如果训练数据集有限,很容易产生过拟合;
(2)网络越大、参数越多,计算复杂度越大,难以应用;
(3)网络越深,容易出现梯度弥散问题(梯度越往后穿越容易消失),难以优化模型。
对于大规模稀疏的神经网络,可以通过分析激活值的统计特性和对高度相关的输出进行聚类来逐层构建出一个最优网络, 这点表明臃肿的稀疏网络可能被不失性能地简化。解决过拟合和计算成本的根本方法是将全连接甚至一般的卷积都转化为稀疏连接, 早在AlexNet中使用的Dropout就是将网络之间的连接转变为稀疏连接从而减少参数的数据以防止模型过拟合, 但是,计算机软硬件对非均匀稀疏数据的计算效率很差,所以在AlexNet中又重新启用了全连接层,目的是为了更好地优化并行运算。
GoogLeNet而非GoogleNet是为了向早期的LeNet致敬!
如何既能保持网络结构的稀疏性,又能利用密集矩阵的高计算性能呢?
在GoogLeNet中提出了Inception结构来实现的, 采用将多个稀疏矩阵合并成相关的稠密子矩阵的方法来解决:
网络结构解析
0、输入
原始输入图像为224x224x3,且都进行了零均值化的预处理操作(图像每个像素减去均值)。
1、第一层(卷积层)
使用7x7的卷积核(滑动步长2,padding为3),64通道,输出为112x112x64,卷积后进行ReLU操作
经过3x3的max pooling(步长为2),输出为((112 - 3+1)/2)+1=56,即56x56x64,再进行ReLU操作
2、第二层(卷积层)
使用3x3的卷积核(滑动步长为1,padding为1),192通道,输出为56x56x192,卷积后进行ReLU操作
经过3x3的max pooling(步长为2),输出为((56 - 3+1)/2)+1=28,即28x28x192,再进行ReLU操作
3a、第三层(Inception 3a层)
分为四个分支,采用不同尺度的卷积核来进行处理
(1)64个1x1的卷积核,然后RuLU,输出28x28x64
(2)96个1x1的卷积核,作为3x3卷积核之前的降维,变成28x28x96,然后进行ReLU计算,再进行128个3x3的卷积(padding为1),输出28x28x128
(3)16个1x1的卷积核,作为5x5卷积核之前的降维,变成28x28x16,进行ReLU计算后,再进行32个5x5的卷积(padding为2),输出28x28x32
(4)pool层,使用3x3的核(padding为1),输出28x28x192,然后进行32个1x1的卷积,输出28x28x32。
将四个结果进行连接,对这四部分输出结果的第三维并联,即64+128+32+32=256,最终输出28x28x256
3b、第三层(Inception 3b层)
(1)128个1x1的卷积核,然后RuLU,输出28x28x128
(2)128个1x1的卷积核,作为3x3卷积核之前的降维,变成28x28x128,进行ReLU,再进行192个3x3的卷积(padding为1),输出28x28x192
(3)32个1x1的卷积核,作为5x5卷积核之前的降维,变成28x28x32,进行ReLU计算后,再进行96个5x5的卷积(padding为2),输出28x28x96
(4)pool层,使用3x3的核(padding为1),输出28x28x256,然后进行64个1x1的卷积,输出28x28x64。
将四个结果进行连接,对这四部分输出结果的第三维并联,即128+192+96+64=480,最终输出输出为28x28x480
第四层(4a,4b,4c,4d,4e)、第五层(5a,5b)……,与3a、3b类似,在此就不再重复。
结构
import torch
import torch.nn as nn
import torchvision
def ConvBNReLU(in_channels,out_channels,kernel_size):
return nn.Sequential(
nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size, stride=1,padding=kernel_size//2),
nn.BatchNorm2d(out_channels),
nn.ReLU6(inplace=True)
)
class InceptionV1Module(nn.Module):
def __init__(self, in_channels,out_channels1, out_channels2reduce,out_channels2, out_channels3reduce, out_channels3, out_channels4):
super(InceptionV1Module, self).__init__()
self.branch1_conv = ConvBNReLU(in_channels=in_channels,out_channels=out_channels1,kernel_size=1)
self.branch2_conv1 = ConvBNReLU(in_channels=in_channels,out_channels=out_channels2reduce,kernel_size=1)
self.branch2_conv2 = ConvBNReLU(in_channels=out_channels2reduce,out_channels=out_channels2,kernel_size=3)
self.branch3_conv1 = ConvBNReLU(in_channels=in_channels, out_channels=out_channels3reduce, kernel_size=1)
self.branch3_conv2 = ConvBNReLU(in_channels=out_channels3reduce, out_channels=out_channels3, kernel_size=5)
self.branch4_pool = nn.MaxPool2d(kernel_size=3,stride=1,padding=1)
self.branch4_conv1 = ConvBNReLU(in_channels=in_channels, out_channels=out_channels4, kernel_size=1)
def forward(self,x):
out1 = self.branch1_conv(x)
out2 = self.branch2_conv2(self.branch2_conv1(x))
out3 = self.branch3_conv2(self.branch3_conv1(x))
out4 = self.branch4_conv1(self.branch4_pool(x))
out = torch.cat([out1, out2, out3, out4], dim=1)
return out
class InceptionAux(nn.Module):
def __init__(self, in_channels,out_channels):
super(InceptionAux, self).__init__()
self.auxiliary_avgpool = nn.AvgPool2d(kernel_size=5, stride=3)
self.auxiliary_conv1 = ConvBNReLU(in_channels=in_channels, out_channels=128, kernel_size=1)
self.auxiliary_linear1 = nn.Linear(in_features=128 * 4 * 4, out_features=1024)
self.auxiliary_relu = nn.ReLU6(inplace=True)
self.auxiliary_dropout = nn.Dropout(p=0.7)
self.auxiliary_linear2 = nn.Linear(in_features=1024, out_features=out_channels)
def forward(self, x):
x = self.auxiliary_conv1(self.auxiliary_avgpool(x))
x = x.view(x.size(0), -1)
x= self.auxiliary_relu(self.auxiliary_linear1(x))
out = self.auxiliary_linear2(self.auxiliary_dropout(x))
return out
class InceptionV1(nn.Module):
def __init__(self, num_classes=1000, stage='train'):
super(InceptionV1, self).__init__()
self.stage = stage
self.block1 = nn.Sequential(
nn.Conv2d(in_channels=3,out_channels=64,kernel_size=7,stride=2,padding=3),
nn.BatchNorm2d(64),
nn.MaxPool2d(kernel_size=3,stride=2, padding=1),
nn.Conv2d(in_channels=64, out_channels=64, kernel_size=1, stride=1),
nn.BatchNorm2d(64),
)
self.block2 = nn.Sequential(
nn.Conv2d(in_channels=64, out_channels=192, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(192),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1),
)
self.block3 = nn.Sequential(
InceptionV1Module(in_channels=192,out_channels1=64, out_channels2reduce=96, out_channels2=128, out_channels3reduce = 16, out_channels3=32, out_channels4=32),
InceptionV1Module(in_channels=256, out_channels1=128, out_channels2reduce=128, out_channels2=192,out_channels3reduce=32, out_channels3=96, out_channels4=64),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1),
)
self.block4_1 = InceptionV1Module(in_channels=480, out_channels1=192, out_channels2reduce=96, out_channels2=208,out_channels3reduce=16, out_channels3=48, out_channels4=64)
if self.stage == 'train':
self.aux_logits1 = InceptionAux(in_channels=512,out_channels=num_classes)
self.block4_2 = nn.Sequential(
InceptionV1Module(in_channels=512, out_channels1=160, out_channels2reduce=112, out_channels2=224,
out_channels3reduce=24, out_channels3=64, out_channels4=64),
InceptionV1Module(in_channels=512, out_channels1=128, out_channels2reduce=128, out_channels2=256,
out_channels3reduce=24, out_channels3=64, out_channels4=64),
InceptionV1Module(in_channels=512, out_channels1=112, out_channels2reduce=144, out_channels2=288,
out_channels3reduce=32, out_channels3=64, out_channels4=64),
)
if self.stage == 'train':
self.aux_logits2 = InceptionAux(in_channels=528,out_channels=num_classes)
self.block4_3 = nn.Sequential(
InceptionV1Module(in_channels=528, out_channels1=256, out_channels2reduce=160, out_channels2=320,
out_channels3reduce=32, out_channels3=128, out_channels4=128),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1),
)
self.block5 = nn.Sequential(
InceptionV1Module(in_channels=832, out_channels1=256, out_channels2reduce=160, out_channels2=320,out_channels3reduce=32, out_channels3=128, out_channels4=128),
InceptionV1Module(in_channels=832, out_channels1=384, out_channels2reduce=192, out_channels2=384,out_channels3reduce=48, out_channels3=128, out_channels4=128),
)
self.avgpool = nn.AvgPool2d(kernel_size=7,stride=1)
self.dropout = nn.Dropout(p=0.4)
self.linear = nn.Linear(in_features=1024,out_features=num_classes)
def forward(self, x):
x = self.block1(x)
x = self.block2(x)
x = self.block3(x)
aux1 = x = self.block4_1(x)
aux2 = x = self.block4_2(x)
x = self.block4_3(x)
out = self.block5(x)
out = self.avgpool(out)
out = self.dropout(out)
out = out.view(out.size(0), -1)
out = self.linear(out)
if self.stage == 'train':
aux1 = self.aux_logits1(aux1)
aux2 = self.aux_logits2(aux2)
return aux1, aux2, out
else:
return out
if __name__=='__main__':
model = InceptionV1()
print(model)
input = torch.randn(1, 3, 224, 224)
aux1, aux2, out = model(input)
print(aux1.shape)
print(aux2.shape)
print(out.shape)
人脑神经元的连接是稀疏的,因此研究者认为大型神经网络的合理连接方式应该也是稀疏的。稀疏结构是非常适合神经网络的一种结构,尤其是对非常大型,非常深的神经网络,可以减轻过拟合并降低计算量,例如卷积神经网络就是稀疏的连接。Inception Net的主要目标就是找到最优的稀疏结构单元(即Inception Module),论文中提到其稀疏结构基于 Hebbian原理,这里简单解释一下Hebbian原理:神经反射活动的持续与重复会导致神经元连接稳定性的持久提升,当两个神经元细胞 A 和B 距离很近,并且A 参与了对B重复,持续的兴奋,那么某些代谢会导致A将作为能使B兴奋的细胞。总结一下即“一起发射的神经元会连接一起”(Cells that fire together, were together),学习过程中的刺激会使神经元间的突触强度增加。受 Hebbian原理启发,另一篇文章 Provable Bounds for learning Some Deep Representations 提出,如果数据集的概率分布可以被一个很大很稀疏的神经网络所表达,那么构筑这个网络的最佳方法时逐层构筑网络:将上一层高度相关(correlated)的节点聚类,并将聚类出来的每一个小簇(cluster)连接到一起,如下图所示,这个相关性高的节点应该被连接在一起的结论,即使从神经网络的角度对 Hebbian 原理有效性的证明。
因此一个“好”的稀疏结构,应该是符合 Hebbian原理的,我们应该把相关性高的一簇神经元节点连接在一起。在普通的数据集中,这可能需要对神经元节点聚类,但是在图片数据中,天然的就是临近区域的数据相关性高,因此相邻的像素点被卷积操作连接在一起。而我们可能有多个卷积核,在同一空间位置但在不同通道的卷积核的输出结果相关性极高。因此,一个 1 × 1 1\times1 1×1的卷积就可以很自然的把这些相关性很高的,在同一个空间位置但是不同通道的特征连接在一起,这就是为什么 1 × 1 1\times1 1×1卷积这么频繁的被应用到 Inception Net 中的原因。 1 × 1 1\times1 1×1 卷积所连接的节点的相关性是最高的,而稍微大一点尺寸的卷积,比如 3 × 3 3\times3 3×3, 5 × 5 5\times5 5×5的卷积所连接的节点的相关性是最高的,而稍微大一点的卷积,比如 3 × 3 3\times3 3×3, 5 × 5 5\times5 5×5的卷积所连接的节点相关性也很高,因此也可以适当地使用一些大尺寸的卷积,增加多样性(diversity)。最后 Inception Module 通过4个分支中不同尺寸的 1 × 1 1\times1 1×1, 3 × 3 3\times3 3×3, 5 × 5 5\times5 5×5等小型卷积将相关性很高的节点连接在一起,就完成了其设计初衷,构建出了很高效的符合 Hebbian原理的稀疏结构。