作者:Andrej Karpathy,翻译:杰少
A Recipe for Training Neural Networks
简介
今天看到一篇非常非常棒的调节NN的文章,作者是特斯拉高级总监Andrej Karpathy,就翻译过来和大家一起分享,难免翻译有问题,有兴趣的朋友可以去引文阅读原文。
为了能更好地训练NN,karpathy为自己定制了一套具体的流程。在每次用NN处理一个新问题的时候,都会按照这套流程来,该流程的核心主要有两步骤。
- 从简单到复杂,每一步我们都会对将要发生的事情做出具体的假设,然后通过实验验证这些假设,或者进行研究,直到我们发现一些问题。我们努力防止的是一次引入大量“未经验证”的复杂假设,这必然会引入错误/错误配置,这将需要花费很长时间才能找到(如果有的话)。
- 编写你的神经网络代码并训练一个,可以使用一个非常小的学习率和猜测,然后在每次迭代后评估完整的测试集。
整体流程
01
熟悉数据
训练神经网络的第一步是不接触任何神经网络代码,而是从检查数据开始。
这一步至关重要。我喜欢花大量的时间(以小时为单位)浏览数千个示例,了解它们的分布并寻找模式。幸运的是,你的大脑在这方面很在行。有一次,我发现数据中包含重复的样本。另一次我发现损坏的图像/标签。我寻找数据不平衡和Bias。我通常还会注意我自己对数据进行分类的过程,这暗示了我们将最终探索的体系结构类型。例如,
- 非常局部的特征是否足够?
- 或者我们是否需要全局上下文的信息?
- 有多少变化,它采取什么形式?
- 什么变化是虚假的,是否可以被预处理掉?
- 空间位置重要吗?还是我们想把它平均化?
- 细节有多重要?我们能承受多大程度的减少图像采样?
- 标签存在多少噪音?
此外,神经网络实际上是数据集的压缩/编译版本,您将能够查看网络(mis)预测并了解它们可能来自何处。如果你的网络给了你一些与你在数据中看到的不一致的预测,那么就有问题了。
一旦你有了一个定性的感觉,写一些简单的代码来搜索/过滤/排序你能想到的任何东西(例如标签的类型、注释的大小、注释的数量等),并可视化它们的分布和任何轴上的异常值也是一个好主意。异常值几乎总是会暴露出数据质量或预处理中的一些缺陷。
02
端到端训练/评估框架 获取基线
在我们了解我们数据之后,下一步任务便是建立一个完整的**“训练 评估”**框架,并通过一系列实验获得对其正确性。在这个阶段,最好选择一些比较擅长的简单模型,例如线性分类器,或者较小的卷积网络等。之后我们便可以进行训练,可视化损失函数,评估指标(如准确性)等,在进行模型预测,并在过程中使用明确的假设进行消融实验。
这一步的核心技巧有:
- 固定随机种子。始终使用固定的随机种子,以确保当您运行代码两次时,将得到相同的结果。
- 尽可能简单,确保禁用任何不必要的假设。在此阶段,请务必关闭任何数据扩充的策略。
- 数据扩充是一种正则化策略,我们以后可能会采用这种策略,但刚开始就引入则经常会犯一些愚蠢的错误。
- 在评估中添加重要的数字。绘制测试损失图时,对整个(大型)测试集运行评估。不要只绘制批次的测试损失图,然后依靠在Tensorboard中平滑它们。我们追求正确,非常愿意为了保持理智而放弃时间。
- 验证损失@init。确保loss从正确的损失值开始。例如,如果正确初始化最后一层,则应在初始化时在softmax上测量-log(1/n_class)。对于L2回归、Huber损失等,可以导出相同的默认值。
- init well。正确初始化最终层的权重。例如,如果您对一些平均值为50的值进行回归,则将最终偏差初始化为50。如果您有一个正:负比率为1:10的不平衡数据集,则在您的登logits上设置偏差,以便网络在初始化时预测概率为0.1。正确设置这些参数将加快收敛速度并消除“hockey stick”损失曲线,在最初的几次迭代中,您的网络基本上只是在学习偏差。
- human基线。监控除损失以外的人类可解释和可检查的指标(例如准确性)。尽可能评估自己(人类)的准确性并与之进行比较。或者,对测试数据进行两次注释,对于每个示例,将一次注释视为预测,第二次注释视为基本真理。
- 输入相关的基线。训练输入独立的基线(例如,最简单的方法是将所有输入设置为零)。这应该比实际插入数据而不将其归零的情况更糟糕。即:您的模型是否学习从输入中提取任何信息?
- 过拟合一个batch。使用少数几个样本(例如,仅两三个样本)对单个批次进行过拟合。为此,我们增加了模型的容量(例如添加层或filters),并验证我们可以达到最低的可实现损耗(例如零)。我还喜欢在同一个图中可视化标签和预测,并确保一旦我们达到最小损失,它们最终会完美对齐。如果他们不这样做,那么肯定在某个地方存在一个bug,我们无法继续下一阶段。
- 验证是否减少了训练loss,在这个阶段,我们更加希望看到在数据集上欠拟合,因为你正在使用一个玩具模型。试着稍微增加它的容量。你的训练损失有没有像应该的那样减少?
- 在net之前可视化,可视化数据的明确正确位置就在y_hat=model(x)(或sess.run in tf)之前。也就是说,您希望准确地可视化进入网络的内容,将数据和标签的原始张量解码为可视化。这是唯一的“真理之源”。我无法计算这节省了我多少时间,并暴露了数据预处理和扩充方面的问题。
- 可视化预测动态。在训练过程中,我喜欢在固定的测试批次上可视化模型的预测。这些预测的“动态”会让你对训练的进展有非常好的直觉。很多时候,如果网络以某种方式摆动过多,暴露出不稳定性,人们可能会感觉到网络在努力适应数据。非常低或非常高的学习率在抖动量上也很容易被注意到。
- 使用backprop来图表来依赖关系。深度学习代码通常会包含复杂的、矢量化的和广播式的操作。我曾经遇到过的一个相对常见的错误是,人们错误地理解了这一点(例如,他们在某处使用视图而不是转置/置换),无意中在批处理维度中混合了信息。这是一个令人沮丧的事实,您的网络通常仍能正常训练,因为它将学会忽略其他样本中的数据。调试此问题(以及其他相关问题)的一种方法是将损耗设置为微不足道的值,如样本的所有输出之和,运行反向传递到输入,并确保仅在第个输入上获得非零梯度。例如,可以使用相同的策略来确保时间t时的自回归模型仅取决于。更一般地说,梯度为您提供了有关网络中哪些依赖于哪些的信息,这对于调试非常有用。
- 泛化一个特例。这更像是一个通用的编码技巧,从头开始编写一个相对通用的功能。我喜欢为我现在正在做的事情编写一个非常具体的函数,让它工作起来。
03
过拟合
在这一步,我们应该对数据集有很好的理解,并且我们有完整的训练 评估流程。
对于任何给定的模型,我们都可以(重复地)计算出我们信任的度量。我们还拥有独立于输入的基线的表现,一些愚蠢基线的性能(我们最好击败这些基线),我们对人类的表现有一个粗略的感觉(我们希望达到这一点)。现在可以在一个好的模型上进行迭代了。
我喜欢采用的寻找一个好模型的方法有两个阶段:
- 首先获得一个足够大的模型,使其能够过拟合(即,关注训练损失),然后适当地调整它(放弃一些训练损失以改善验证损失)。
我喜欢这两个阶段的原因是,如果我们在任何模型上都无法达到低错误率,那么这可能再次表明一些问题、bug或错误配置。
这一步的一些建议:
- 选择模型。要达到良好的训练效果,您需要为数据选择合适的结构。在选择这个问题上,我的第一条建议是:不要做英雄。我见过很多人,他们热衷于疯狂和创造性地将神经网络工具箱中的乐高积木堆积在各种对他们认为有意义的结构中。在项目的早期阶段强烈抵制这种诱惑。我总是建议人们简单地找到最相关的论文,然后复制粘贴他们最简单的体系结构,以获得良好的性能。例如,如果您正在对图像进行分类,请不要成为英雄,只需在第一次运行时复制粘贴ResNet-50即可。你可以在以后做一些更习惯的事情,并战胜它;
- Adam会相对安全。在设定基线的早期阶段,我喜欢使用学习率为3e-4的Adam。根据我的经验,Adam对超参数(包括糟糕的学习率)更为宽容。对于ConvNets,经过良好调整的SGD几乎总是略优于Adam,但最佳学习速率区域要窄得多,且针对具体问题(注意:如果您使用RNN和相关序列模型,Adam则更常用。在项目的初始阶段,再次强调,不要做英雄,而要遵循最相关的论文。)
- 一次只复杂化一个。如果您有多个信号要插入分类器,我建议您一个接一个地插入它们,每次都要确保获得预期的性能提升。不要一开始就把厨房的水槽扔向你的模型。还有其他增加复杂性的方法-例如,您可以尝试先插入较小的图像,然后再将其放大,等等。
- 不要相信学习速率衰减默认值。如果您打算从其他领域重新编写代码,请务必非常小心使用学习率衰减。您不仅希望针对不同的问题使用不同的衰减计划,而且更糟糕的是,在典型schedule实现中,该计划将基于当前epoch,而当前epoch数仅取决于数据集的大小,可能会有很大的变化。例如,ImageNet将在第30 epoch时衰减10。如果您不训练ImageNet,那么您几乎肯定不希望这样。如果您不小心,您的代码可能会过早地秘密地将您的学习率降至零,从而导致您的模型无法收敛。在我自己的工作中,我总是禁用学习速率完全衰减(我使用一个常数LR),并在最后一直调整它。
04
正则化
理想情况下,我们现在所处的位置是,我们有一个至少拟合训练集的大模型。现在需要对其进行正则化,并通过放弃一些训练精度来获得更好的验证精度。这边有一些技巧:
- 获取更多数据。首先,到目前为止,在任何实际环境中正华化模型的最佳和首选方法是添加更多真实的训练数据。当您可以收集更多数据时,花费大量的工程周期试图从一个小数据集中榨取数据,这是一个非常常见的错误。据我所知,添加更多数据几乎是单调地提高配置良好的神经网络几乎无限期性能的唯一保证。另一种可能是集成(如果你能负担得起的话),但在5个模型之后,这种继承就最为流行了。
- 数据扩充。与真实数据相比,下一个最好的方法是半假数据——尝试更激进的数据扩充。
- 创造性的数据增加。如果有一半的假数据不起作用,假数据也可能起到作用。人们正在寻找扩展数据集的创造性方法;例如,域随机化、模拟的使用、巧妙的混合,例如将(可能模拟的)数据插入场景,甚至是GANs。
- 预训练:如果可以的话,即使你有足够的数据,使用预先训练好的网络也不会有什么坏处。
- 坚持监督学习。不要对无监督的预训练过度兴奋。据我所知,与2008年的那篇博文所告诉你的不同,目前还没有一个版本的NLP在现代计算机视觉方面取得了很好的效果(尽管这段时间来使用BERT处理NLP问题非常好,很可能是因为文本到特殊性质,以及更高的信噪比)。
- 较小的输入维度。删除可能包含虚假信号的功能。如果您的数据集很小,任何添加的虚假输入都只是另一个过拟合的机会。同样,如果低级细节无关紧要,请尝试输入较小的图像。
- 更小的模型size。在许多情况下,您可以使用网络上的领域知识约束来减小其大小。例如,过去流行在ImageNet主干的顶部使用完全连接的层,但后来这些层被简单的平均池所取代,从而消除了过程中的大量参数。
- 减少batch大小。由于batch范数内的标准化,较小的batch大小在某种程度上对应于更强的正则化。这是因为batch经验平均值/std是完整平均值/std的更近似版本,因此比例和偏移量会使批次更容易“摆动”。
- 加上drop。将dropout2d(空间dropout)用于CONVnet。谨慎使用此选项,因为辍学似乎不能很好地处理批处理规范化。
- 权重衰减。增加weight衰减惩罚。
- 早停。根据验证损失停止训练,以便在模型即将过度拟合时捕捉模型。
- 试试大一点的模型。我最后一次提到这一点,而且是在提前停止之后,但我发现在过去的几次中,大型车型当然最终会过度拟合,但它们的“提前停止”性能通常会比小型车型好得多。
最后,为了使您的网络成为一个合理的分类器,我喜欢可视化网络的第一层权重,并确保获得有意义的好边。如果您的第一层过滤器看起来像噪音,那么可能会有问题。类似地,网络内部的激活有时会显示奇怪的工件并提示问题。
05
调模型
现在,您应该“in the loop”使用数据集,为达到低验证损失的结构需要探索更广阔的模型空间。此步骤的一些提示和技巧:
- 随机网格搜索。为了同时调整多个超参数,使用网格搜索来确保覆盖所有的设置听起来很诱人,但请记住,最好使用随机搜索。直觉上,这是因为神经网络通常比其他网络对某些参数更敏感。在极限情况下,如果参数a很重要,但更改b没有效果,那么您宁愿更全面地对a进行采样,而不是多次在几个固定点进行采样。
- 超参数优化。目前有大量fancy的贝叶斯超参数优化工具箱,我的一些朋友也说出了他们的成功,但我个人的经验是,探索模型和超参数的美好和广阔空间的最先进方法是使用实习生:)。只是开玩笑......
06
再挤挤
一旦您找到了最佳的结构和超参数,仍然可以使用更多的技巧从结果中榨出最后的汁液:
- 集成。模型集成是一种几乎可以保证在任何情况下获得2%准确率的方法。如果您在测试时负担不起计算,请考虑使用暗知识将您的集成提取到网络中。
- 留着训练。我经常看到人们试图在验证损失趋于平稳时停止模型培训。根据我的经验,网络会持续很长时间的训练。有一次,我在寒假期间不小心离开了,留着一个模特训练,当我在一月份回来的时候,那是SOTA。
07
结论
一旦你来到这里,你将拥有成功的所有要素:你对技术、数据集和问题有着深刻的理解,你建立了整个训练/评估结构,对其准确性有着高度的信心,你探索了越来越复杂的模型,通过预测每一步的方式获得性能改进。您现在已经准备好阅读大量论文,尝试大量实验,并获得SOTA结果。祝你好运!
参考文献
- A Recipe for Training Neural Networks