复现论文实验一直是一大难题,或者说复现实验达到论文中精度是一大难题。绝大部分时候我们很难复现到媲美原文的精度,这很大原因是神经网络的参数众多,优化技巧众多,参数初始化等一些列问题,一般论文中对不会对小细节的处理进行描述或者他们有着一些不为人知的“trick”,所以我们很多时候我们达不到他们的实验效果。
之前我曾尝试复现几篇论文的实验,花了很大力气总算搞出来的,但是结果实验结果与原文中精度总有2-3%左右的差距。由于原文没开源代码,按照他论文来所描述的来写代码,难以发现自己哪里出问题了,后来就放弃了。
最近看到一篇有关网络优化的文章,感觉挺有用的,对复现实验代码比较有用,所以写下来了。
一、加快模型训练
主要有两块内容:一块是选用更大的batch size,另一块是采用16位浮点型进行训练。
选用更大的batch size能够在整体上加快模型的训练,但是一般而言如果只增大batch size,效果不会太理想,这部分目前有比较多的研究论文,比如Facebook的这篇:Accurate, Large Minibatch SGD:Training ImageNet in 1 Hour,作者也总结了主要的几个解决方案。
- 增大学习率,因为更大的batch size意味着基于每个batch数据计算得到的梯度更加贴近整个数据集(数学上来讲就是方差更小),因此当更新方向更加准确后,迈的步子也可以更大了,一般而言将batch size修改为原来的几倍,那么初始学习率也需要修改为原来的几倍。
- 用一个小的学习率先训几个epoch(warmup) 。因为网络的参数是随机初始化的,假如一开始就采用较大的学习率容易出现数值不稳定,这是使用warmup的原因。等到训练过程基本稳定了就可以使用原先设定的初始学习率进行训练了。作者在实现warmup的过程中采用线性增加的策略,举例而言,假设warmup阶段的初始学习率是0,warmup阶段共需要训练m个batch的数据(实现中m个batch共5个epoch),假设训练阶段的初始学习率是L,那么在batch i的学习率就设置为i*L/m。
- 每个残差块的最后一个BN层的γ参数初始化为0,我们知道BN层的γ、β参数是用来对标准化后的输入做线性变换的,也就是γx^+β,一般γ参数都会初始化为1,作者认为初始化为0更有利于模型的训练。
- 不对bias参数执行weight decay操作,weight decay主要的作用就是通过对网络层的参数(包括weight和bias)做约束(L2正则化会使得网络层的参数更加平滑)达到减少模型过拟合的效果。
采用低精度(16位浮点型)训练是从数值层面来做加速。一般而言现在大部分的深度学习网络的输入、网络参数、网络输出都采用32位浮点型,现在随着GPU的迭代更新(比如V100支持16为浮点型的模型训练),如果能使用16位浮点型参数进行训练,就可以大大加快模型的训练速度,这是作者加速训练最主要的措施,不过目前来看应该只有V100才能支持这样的训练。
二、优化网络结构部分
这部分的优化是以ResNet为例的,Figure1是ResNet网络的结构示意图,简单而言是一个input stem结构、4个stage和1个output部分,input stem和每个stage的内容在第二列展示,每个residual block的结构在第三列展示,整体而言这个图画得非常清晰了。
关于residual block的改进可以参考Figure2,主要有3点。
- ResNet-B,改进部分就是将stage中做downsample的residual block的downsample操作从第一个11卷积层换成第二个33卷积层,如果downsample操作放在stride为2的11卷积层,那么就会丢失较多特征信息(默认是缩减为1/4),可以理解为有3/4的特征点都没有参与计算,而将downsample操作放在33卷积层则能够减少这种损失,因为即便stride设置为2,但是卷积核尺寸够大,因此可以覆盖特征图上几乎所有的位置。
- ResNet-C,改进部分就是将Figure1中input stem部分的77卷积层用3个33卷积层替换。这部分借鉴了Inception v2的思想,主要的考虑是计算量,毕竟大尺寸卷积核带来的计算量要比小尺寸卷积核多不少,不过读者如果仔细计算下会发现ResNet-C中3个33卷积层的计算量并不比原来的少,这也是Table5中ResNet-C的FLOPs反而增加的原因。
- ResNet-D,改进部分是将stage部分做downsample的residual block的支路从stride为2的11卷积层换成stride为1的卷积层,并在前面添加一个池化层用来做downsample。这部分我个人理解是虽然池化层也会丢失信息,但至少是经过选择(比如这里是均值操作)后再丢失冗余信息,相比stride设置为2的1*1卷积层要好一些。
三、模型训练调优部分
主要4个调优技巧:
- 学习率衰减策略采用cosine函数,这部分的实验结果对比可以参考Figure3,其中(a)是cosine decay和step decay的示意图,step decay是目前比较常用的学习率衰减方式,表示训练到指定epoch时才衰减学习率。(b)是2种学习率衰减策略在效果上的对比。
- 采用label smoothing,网络中softmax的输出可以直接理解成概率,而one-hot向量只有0与1。所以这部分是将原来常用的one-hot类型标签做软化,这样在计算损失值时能够在一定程度上减少过拟合,可以增强模型的泛化能力。从交叉熵损失函数可以看出,只有真实标签对应的类别概率才会对损失值计算有所帮助,全概率和零概率将鼓励所属类别和非所属类别之间的差距尽可能拉大,而由于以上可知梯度有界,因此很难adapt。
因此label smoothing相当于减少真实标签的类别概率在计算损失值时的权重,同时增加其他类别的预测概率在最终损失函数中的权重。这样真实类别概率和其他类别的概率均值之间的gap(倍数)就会下降一些,会造成模型过于相信预测的类别。
label smoothing的具体: 为了使得模型less confident,提出将label真实标签从 变成 ,其中
对于以Dirac函数分布的真实标签,我们将它变成分为两部分获得(替换)
1.第一部分:将原本Dirac分布的标签变量替换为 (1 - ϵ)的Dirac函数;
2.第二部分:以概率 ϵ ,在u(k)u(k) 中份分布的随机变量。(在文章中,作者采用先验概率也就是均布概率,而K取值为num_class = 1000)
从而交叉熵被替换为: 可以认为:Loss 函数为分别对【预测label与真实label】【预测label与先验分布】进行惩罚。 - **知识蒸馏(knowledge distillation)**这部分其实是模型加速压缩领域的一个重要分支,表示用一个效果更好的teacher model训练student model,使得student model在模型结构不改变的情况下提升效果。作者采用ResNet-152作为teacher model,用ResNet-50作为student model,代码上通过在ResNet网络后添加一个蒸馏损失函数实现,这个损失函数用来评价teacher model输出和student model输出的差异,因此整体的损失函数原损失函数和蒸馏损失函数的结合:
其中p表示真实标签,z表示student model的全连接层输出,r表示teacher model的全连接层输出,T是超参数,用来平滑softmax函数的输出 - 引入mixup,mixup其实也是一种数据增强方式,假如采用mixup训练方式,那么每次读取2张输入图像,假设用(xi,yi)和(xj,yj)表示,那么通过下面这两个式子就可以合成得到一张新的图像(x,y),然后用这张新图像进行训练,需要注意的是采用这种方式训练模型时要训更多epoch。式子中的λ是一个超参数,用来调节合成的比重,取值范围是[0,1]。