本文主要是介绍如何快速高效的训练ResNet,各种奇技淫巧(八):一大波技巧,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
点击上方“AI公园”,关注公众号,选择加“星标“或“置顶”
作者:David Page
编译:ronghuaiyang
导读
这个系列介绍了如何在CIFAR10上高效的训练ResNet,这是第八篇,给大家总结了一大波的技巧,这些技巧同样可以用到提升准确率上。
在本系列的最后一篇文章中,我们绕了一圈,加快了我们的单gpu训练,与多gpu竞争。我们推出了一系列标准和不太标准的技巧,通过增加测试时间将训练时间缩短到34秒或26秒。
到目前为止我们已经占领的地盘包括,超参数调优,权值衰减,batch norm,我们的CIFAR10训练到94%的测试准确率的位置在DAWNBench排行榜上已经下滑了5个位置。
前六个都使用了9层resnet,它们是我们在之前的blog中开发的网络的近亲。第一个是来自Kakao Brain的4-GPU实现,它在37秒内完成。同样的单gpu版本以68s排在第三,比去年的单gpu版本明显提高了7s,尽管仔细检查显示这些提交使用的是测试时间增强(TTA)。我们将在文章的最后讨论这种方法的有效性(我们的结论是,任何合理的限制都应该基于总推断成本,这里使用的轻度TTA的形式以及轻量级的网络将在这方面进行传递)。请注意,我们之前的提交,允许相同的TTA,将在没有进一步更改的情况下,在19个epoch的训练策略上实现了60秒的时间。
在文章的末尾,我们的单gpu实现轻松地超越了顶级多gpu,以34秒的时间夺回了梦寐以求的DAWNBench皇冠,并在系列开始时实现了10倍于单gpu最先进水平的改进!使用与Kakao Brain提交的相同的TTA,这个数字下降到26s。我们通过积累一系列小的改进(通常是0.1-0.3%的绝对测试精度)来达到这些时间,这些改进可以用更短的训练时间来交换。这些改进是基于一组标准和不那么标准的技巧。
我们的主要武器是统计显著性。一次训练的测试准确度的标准偏差约为0.15%,在两次训练之间进行比较时,我们需要将其乘以√2。这比我们正在测量的许多影响都要大。考虑到训练时间很快就会降到一分钟以下,我们可以进行10-100次的实验,以确保改进是真实的,这使我们能够取得一致的进展。
尖锐的实验结果对于推进这个领域是至关重要的,但是如果基线调整得不好或者运行的次数太少,实验验证就没有什么价值。今天这篇文章的主要目标是提供一个良好的调优的基线,在此基础上测试新技术,允许在一个GPU上在几分钟内完成统计上显著数量的训练运行。我们在文章结束时确认,如果训练进程收敛一致,训练速度的改进将转化为最终准确性的改进。
GPU上的预处理 (70s)
我们从一些代码优化的实际问题开始。我们早期DAWNBench提交的日志显示,在数据预处理上浪费了3秒,这将计入训练时间。回想一下,我们在训练前对数据集进行标准化、转置和填充,以避免在每个epochs重复工作。
我们可以做得更好,通过传输数据到GPU,在那里预处理,然后传输回CPU随机数据增加和批处理。将整个数据集(uint8格式)移动到GPU只需微不足道的40毫秒,而在GPU上完成预处理则更快,大约只需15毫秒。大部分时间花在将预处理数据集传输回CPU上,这需要近半秒的时间。这比我们之前的3s有了很大的改进,但似乎也有点浪费,因为数据需要在批量处理和增强之后再次传输到GPU,在每一个训练步骤都会导致进一步的延迟。我们可以通过在GPU上做数据增强来消除这个开销吗?
答案是肯定的,但这需要一点小心。如果我们天真地将增强应用于单个训练示例,比如在CPU上,我们会导致启动多个GPU内核来处理每个项的大量开销。我们可以通过对一组示例应用相同的增强来避免这种情况,并且我们可以通过预先打乱数据来保持随机性。
例如,考虑对CIFAR10图像应用8×8 cutout增强。在一张32×32的图像中有625个可能的8×8个裁剪区域,因此我们可以通过打乱数据集并将其分成625个组,每个组对应一个可能的裁剪区域,从而实现随机增强。如果我们选择大小均匀的组,这与为每个示例随机选择不同(这会导致组大小不规则),但已经足够接近了。作为进一步的优化,如果用于增加的组的数量变得过大,我们可以考虑将其限制在一个合理的限制范围内——比如每个epoch随机选择200个组。
我们的基本实现相当简单,只需要大约35行代码(没有任何Pytorch dataloader)。下面是这4张图片的两个随机放大,以显示它的实际效果:
更重要的是,它的速度很快,只需不到400毫秒就可以遍历24个周期的训练数据,并应用随机裁剪、水平翻转和裁剪数据增强、随机打乱和批处理。这比将数据集一次传输到CPU所花费的时间还少!此外,由于我们不再与GPU竞争CPU预处理队列,我们可以完全停止担心数据加载,即使训练变得更快。
注意:我们这样做的依赖是数据集足够小,存储和操作可以作为一个整体在GPU内存中实现,但更复杂的实现将解决这个—或者可以切换到一个工业强度的解决方案如(Nvidia DALI)。
如果我们用新的GPU数据处理重新运行网络并从我们的DAWNBench提交训练,训练时间将下降到70秒以下,使我们在排行榜上上升两位!
混合精度训练
在我们最初DAWNBench提交和在上面测试的代码中,我们简单把模型转换为float16,并没有在意没有所谓的细节(混合精度训练,虽然我们用了一个基本的“损失缩放”,把batch中所有的损失加起来,而不是平均。实现适当的混合精度训练是很简单的,但是这增加了大约一秒钟的总训练时间,我们发现它对最终的精度没有影响,所以我们下面没有继续做它。
去掉最大池化(64s)
把最大池化与一个单调递增的激活函数(如ReLU)交换。先使用池化应该更高效。好的编译器也可能做到这点,但是现在让我们手动切换顺序。这是一个典型的conv-pool块:
交换之后:
切换顺序将在24个epochs的训练时间内进一步减少3秒,而网络的计算完全没有变化!也许我们应该尝试一些更激进的方法,把最大池化移动到batch norm之前。这将获得进一步的效率提高,但会改变网络,所以我们需要测试训练的效果。
结果对测试准确度有一个小的负面影响,与我们的基线94.1%相比,测试准确度变成了94.0%(平均50次)。更积极的是,这大大减少了5秒的训练时间。我们可以通过在训练中增加额外的时间来恢复以前的准确性。这是这篇文章中唯一一次我们选择了一个导致更糟的准确性的“改进”!一个更高效的网络带来的5s的好处,超过了额外训练时间带来的2.5秒的损失。净效应使我们的时间达到64秒,在排行榜上名列第三。
标签平滑(59s)
标签平滑是一种在分类问题中提高神经网络训练速度和泛化能力的成熟技巧。它包括混合独热目标概率与均匀分布在类标签内的交叉熵损失。这有助于稳定的产生分布,并防止网络做出过度自信的预测,而这种预测可能会抑制进一步的训练。让我们试一试,标签平滑参数0.2已经是非常粗的手工优化了,但结果对一系列选择不是太敏感。
测试精度提高到94.2%(平均运行50次)。我们可以通过减少epoch的数量来换取训练时间。根据经验,每提高0.1%的测试准确度,我们就减少一个训练周期,这是大致跟踪了额外训练周期的收益得到的。我们减少了热身阶段——在此期间,学习率线性增长——与整个epochs的数量成比例。
23个epochs的正确率为94.1%,训练时间低于1分钟!
CELU激活函数(52s)
我们希望通过使用一个光滑的激活函数来帮助优化过程,而不是使用在原点处不连续的ReLU。这也可能有助于泛化,因为平滑的函数能表达的函数类别较少——在较大的平滑极限下,我们恢复了线性网络。
除此之外,我们对ReLU很满意,所以我们将选择一个简单的平滑选项。我们选择的是连续可微指数线性单元或CELU激活函数,因为它是平滑的(不像ELU),而且PyTorch的实现比其他的Softplus激活要快。除了平滑之外,CELU还对ReLU使用了x-和y-shift,如下所示,但是考虑到我们使用的batch norm,这些无关紧要。
我们手动调整平滑参数值α为0.075,注意,这是远低于默认值为1。这样达到了94.3%的测试精度(平均50次),有了显著的提高,从而进一步减少了3个epochs的训练,20个epochs的时间为52秒,达到了94.1%的准确性。
Ghost batch norm (46s)
Batch norm似乎在batch size为32左右时工作得最好。原因可能与batch统计中的噪声有关,特别是在中间大小的batch的有益调节效果和小batch的过量噪声之间的平衡。
我们的batch size为512,如果不严重影响训练时间,我们无法减少它们,但是我们可以将batch norm单独应用于训练batch的子集。这种技术称为“ghost” batch norm,通常在分布式设置中使用,但在单个节点上使用大量batch norm时同样有用。在PyTorch中没有直接支持它,但是我们可以很容易地自己实现它。
这对epoch测试的20个epochs的准确率(94.2%)提供了一个提升。随着训练时间变得越来越短,偶尔提高学习率来弥补这一不足是有帮助的。如果将最大学习率提高50%,则可以在18个epochs和46秒的训练时间内达到94.1%的正确率。
冻结batch norm的缩放(43s)
Batch norm对每个通道的均值和方差进行标准化,但随后是可学习的缩放和偏差。我们的Batch norm层由(平滑的)ReLUs继承,因此可学习的偏差可以允许网络优化每个通道的稀疏程度。另一方面,如果通道尺度有很大变化,这可能会减少有效通道的数量,并带来瓶颈。让我们来看看这些参数在训练中的动态变化:
图中发生了很多事情,但有一件事很突出,那就是缩放参数并没有做太多的学习,在很大程度上是在权值衰减的控制下进化的。让我们试着把它们冻结在一个恒定的值1/4——大概是它们在训练中点的平均值。最后一层的可学习的缩放尺度要大一些,但是我们可以调整网络输出的规模来进行补偿。
实际上我们可以固定batch norm的缩放参数为1,或者我们也可以通过因子4调节CELU的α,通过因子42和1/42来调节学习率和权值衰减。我们更喜欢这样做,因为它使通道尺度对偏差的学习率动态的影响更加明显。
18个epochs的测试精度提高到了94.2%。有趣的是,如果我们没有提高batch norm偏差的学习率,我们将获得一个相当低的准确率。这表明,可学习的bias确实在做一些有用的事情——要么学习适当程度的稀疏性,要么只是添加正则化噪声。实际上,我们可以通过将偏差的学习率提高4倍,并将权值衰减除以相应的因子,来略微改善这种情况。
最后,我们可以使用增加的精度,把训练减少到17个epochs。新的测试精度为94.1%,最重要的是我们以43秒的速度超过了BaiduNet9P的8个gpu,排名第二!
输入块的白化 (36s)
batch norm在控制单个通道的分布方面做得很好,但不能处理通道与像素之间的协方差。在内部层控制协方差,使用batch norm的“白化”版本,可能会有帮助,但需要额外的计算和比较复杂的实现工作。我们将重点放在输入层的简单问题上。
消除输入相关性的经典方法是执行全局PCA(或ZCA)白化。我们提出了一种基于patch的方法,它不受图像大小的限制,更符合卷积网络的结构。我们将PCA白化应用于3×3的块输入,作为初始的3×3卷积与固定(非学习)权重。接下来是一个可学习的1×1卷积。这一层的27个输入通道是原3×3×3输入patch的转换版本,其协方差矩阵近似为恒等式,这将使优化变得更容易。
首先绘制输入数据的3×3个patch的协方差矩阵的主特征向量。方括号中的数字是对应特征值的平方根,表示沿着这些方向的相对变化幅度,我们用两个符号绘制特征向量来说明变化的方向。正如我们所预料的,局部亮度的变化占主导地位。
现在我们用一个固定的3×3白化卷积替换网络的第一个3×3卷积,使上述特征patch的尺度相等,然后是一个可学习的1×1卷积,看看对训练的效果。
17个epochs的测试准确度跃升至94.4%,令训练时间进一步缩短2个epochs。15个epochs带来的测试精度为94.1%时间是39秒,接近4 gpu,测试时间增强帮助了我们!如果我们进一步增加最大学习率的~50%,并减少cutout增强,从8×8变为5×5,以弥补额外的高学习率带来的正则化,我们可以删除一个epoch,在36s内达到测试精度达到94.1%,我们勉强进入排行榜榜首!
指数移动平均(34s)
高学习率是快速训练的必要条件,因为它允许随机梯度下降在有限的时间内在参数空间中通过必要的距离。另一方面,学习率需要在训练结束时进行退火,以便在参数空间中沿着更陡峭和更嘈杂的方向进行优化。参数平均方法允许以更高的速度继续训练,同时通过多次迭代进行平均,可以沿着有噪声或振荡的方向接近最小值。
我们将研究参数的指数移动平均,这是一种标准方法。出于效率的原因,我们每5个批次更新一次移动平均,因为我们发现更频繁的更新并不能改善情况。我们需要选择一个新的学习率策略,在培训练接近尾声时提高学习率,并为移动平均提供动力。学习率的一个简单的选择是坚持我们一直使用的分段线性策略,比过去的两个epochs的固定低学习率要好,我们选择0.99的momentum ,这样平均大约发生在时间尺度的最后一个epoch上。
测试精度提高到94.3%,允许我们进一步削减epochs。13个epoch训练达到94.1%的测试精度,训练时间低于34s,比系列开始时的单gpu水平提高了10倍!
测试时间增强(26s)
假设你希望网络在对输入进行水平翻转的时候,对图像输出相同的分类。一种可能性是,我们现在一直在使用的,是向网络提供大量的数据,可能通过保存左右翻转的标签来扩充,并希望网络最终通过大量的训练来学习不变性。
第二种方法是同时给网络输入图像和水平翻转后的图像,并通过对两个版本的网络输出进行平均来达成一致,从而保证不变性。这种非常明智的方法被称为测试时间增强。
在训练时,我们仍然向网络呈现每个图像的单一版本——可能会受到随机翻转的影响,作为数据增强,以便在不同的训练阶段呈现不同的版本。另一种方法是,在训练时使用与测试时相同的程序,并将每个图像及其镜像显示出来。在这种情况下,我们可以通过将网络分成两个相同的分支来改变网络,其中一个分支可以看到翻转后的图像,然后在最后合并。通过这一视角,原始训练可以被看作是一个权重固定的随机训练过程,即两个分支网络,其中每个训练示例都有一个分支被“删除”。
这种dropout-training的观点清楚地表明,如果不允许引入TTA的话,基准测试的尝试会有很多困难。从这个角度看,我们刚刚介绍了一个更大的网络,我们有一个有效的随机训练方法。另一方面,如果我们不限制我们在测试时的工作量,那么就会出现一些明显的退化解决方案,其中训练所需的时间与存储数据集所需的时间一样少!
这些参数不仅与人工基准测试相关,而且与终端用户用例有关。在一些应用中,分类精度是所有的要求,在这种情况下,TTA绝对应该被使用。在其他情况下,推理时间也是一种约束,明智的做法是在这种约束下最大化准确性。这可能也是一个很好的训练基准测试方法。
在目前的案例中,Kakao Brain团队应用了这里描述的TTA的简单形式——在推断时呈现图像及其左右镜像,从而使计算量加倍。当然,对于其他对称(如平移对称、亮度/颜色的变化等)来说,TTA的更广泛形式是可能的,但这将付出更高的计算成本。
现在,由于这些都是基于一个计算能力较轻的9层ResNet, 包括TTA在内的总推断时间可能比在比赛早期阶段进入的100多个层网络中的一些要低得多!根据我们上面的讨论,任何限制这种方法的合理规则都应该基于推理时间约束,而不是任意的实现,因此从这个角度来看,我们应该接受这种方法。
让我们看看TTA带来了哪些改进。为了与当前DAWNBench提交的一致,我们将限制自己只使用水平翻转TTA,因为这似乎是准确性和推理成本之间的最佳点。在我们现有的网络和13个epoch训练设置下,TTA的测试精度提高到94.6%,这是我们今天研究的最大的单个提升效果。
如果我们去掉剩余的cutout增强——在如此短的训练时间中,这是一个障碍——我们可以将训练减少到10个epochs,并在26秒内实现TTA测试精度94.1% !
训练到收敛
这里有一个简单的实验来研究我们所收集的训练速度的提高如果训练到最后收敛的话是否也转化为模型的最终精度的提高。我们有充分的理由相信这应该是这样的情况因为我们今天使用的许多技术最初本来就是作为提高ImageNet收敛精度的技术而提出的!如果同样的技术可以将CIFAR10上的训练时间提高到94%的精度,同样也可以提高ImageNet上的收敛精度,那么这就为后一个问题的研究提供了一种相当有效的方法!
不像之前的实验,这是个非常粗的实验,我们留到未来去把这个实验做的更仔细。我们选择一个固定的学习率策略,学习率较低,适合较长时间的训练,并将cutout增强的数量增加到12×12 patch,使训练时间更长,而不会过拟合。我们将恢复上面提到的其他超参数,并对基线网络和最终网络进行24到100个不同epochs的训练。最后我们要打破所有的规则,每个实验只运行5次!结果如下:
尽管缺少针对较长时间运行的最终训练设置的各种额外超参数的调优,但即使在训练的100个epochs和近似收敛的情况下,它似乎也能保持对基线的领先。我们的9层ResNet在80个epochs的最终TTA准确度是96.1%,尽管我们从未优化过任何训练精度高于94%的东西!我们可以通过适当的超参数优化得到更高的结果。
在大约70个epochs和3分钟的总训练时间内,准确率达到96%,回答了一个我已经被问过好几次的问题,这些人(也许是正确的)认为DAWNBench的94%的门槛太低了。请注意,我们几乎没有尝试优化96%的时间,我们预计它会大幅下降。
最后的一点想法
在这个项目中工作是非常有趣的,探索神经网络训练的动态,并扩展其他人的工作,将训练时间带到一个可以进行快速实验的水平。我希望读者能在他们的工作中发现这一点,并相信通过进一步的算法开发,训练时间还有很长的路要走(如果你喜欢的话,也可以提高精确度!)
在本系列文章的开头,我曾半开玩笑地说,如果我们能够实现100%的计算效率,那么训练应该花费40s。如果我发现这个目标在本系列的末尾被超越了,而计算效率却没有比以前好多少,我会感到惊讶的!在这方面还有很大的改进空间。
—END—
英文原文:https://myrtle.ai/how-to-train-your-resnet-8-bag-of-tricks/
请长按或扫描二维码关注本公众号
喜欢的话,请给我个好看吧!
这篇关于如何快速高效的训练ResNet,各种奇技淫巧(八):一大波技巧的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!