一种训练神经网络的方法

李飞飞的高徒-Andrej Karpathy 于4月25日发表了一篇文章剖析了神经网络训练的问题和如何完成神经网络的方法。

Andrej Karpathy何许人也?

Andrej Karpathy是深度学习和计算机视觉专家,任职于特斯拉,并担任人工智能和Autopilot Vision部门主管(Director of AI and Autopilot Vision),直接向 Elon Musk 汇报(多少人梦寐以求的机会)。

Andrej毕业于斯坦福人工智能实验室,博士师从李飞飞教授,在谷歌大脑、DeepMind 实过习,与吴恩达一起共事,业界几大深度学习实验室都待过,更重要的是,他乐于而且善于分享自己的经验和见解,在推特和Medium上非常活跃,有AI “网红”之称。

比较重要的贡献:Andrej 通过在 ImageNet 的工作让计算机拥有视觉,通过生成模型的开发给了计算机想象力,以及通过强化学习的工作给了计算机浏览互联网的能力。

神经网络训练是一种漏洞抽象

据称很容易开始训练神经网络。许多图书馆和框架都以展示解决您的数据问题的30行奇迹片段而自豪,给人以错误的印象,即这些东西即插即用。常见的事情如下:

>>> your_data = # plug your awesome dataset here
>>> model = SuperCrossValidator(SuperDuper.fit, your_data, ResNet50, SGDOptimizer)
# conquer world here

这些库和示例激活了我们熟悉标准软件的大脑部分 - 通常可以获得干净的API和抽象的地方。请求库演示:

>>> r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
>>> r.status_code
200

这很酷!一个勇敢的开发人员已经承担了理解查询字符串,URL,GET / POST请求,HTTP连接等等的负担,并且在很大程度上隐藏了几行代码背后的复杂性。这是我们熟悉和期待的。不幸的是,神经网不是那样的。它们不是“现成的”技术,第二个与训练ImageNet分类器略有不同。我试图在我的帖子中提出这一点“是的你应该理解backprop”通过选择反向传播并将其称为“漏洞抽象”,但不幸的是情况更加可怕。Backprop + SGD并没有神奇地让你的网络运作。批量规范不会神奇地使其收敛得更快。RNN不会神奇地让你“插入”文本。只是因为你可以制定你的问题,因为RL并不意味着你应该。如果您坚持使用该技术而不了解其工作原理,则可能会失败。这让我想到......

神经网络训练无声地失败

当您破坏或错误配置代码时,您通常会遇到某种异常。你插入了一个预期字符串的整数。该函数只需要3个参数。此导入失败。那把钥匙不存在。两个列表中的元素数量不相等。此外,通常可以为特定功能创建单元测试。

这只是训练神经网络的一个开始。一切都可以在语法上正确,但整个事情没有妥善安排,而且很难说清楚。“可能的错误表面”是大的,逻辑的(与语法相反),并且对单元测试非常棘手。例如,在数据增强期间左右翻转图像时,您可能忘记翻转标签。您的网络仍然可以(令人震惊地)工作得非常好,因为您的网络可以在内部学习检测翻转的图像,然后左右翻转其预测。或许你的自回归模型会因为一个错误的错误而意外地将它想要预测的东西作为输入。或者你试图剪切你的渐变但是修剪了损失,导致在训练期间忽略异常值的例子。或者您从预训练检查点初始化了您的权重,但没有使用原始均值。或者你只是搞砸了正则化强度,学习率,衰减率,模型大小等设置。因此,错误配置的神经网络只有在你运气好的时候才会抛出异常; 大部分时间它会训练,但默默地工作有点糟糕。

结果,(并且这在过去很难过分强调),训练神经网络的“快速和激烈”方法不起作用,只会导致痛苦。现在,痛苦是让神经网络运作良好的一个非常自然的部分,但它可以通过彻底,防御,偏执和痴迷基本上所有可能的事物的可视化来减轻。在我的经验中,与深度学习成功最相关的品质是耐心和对细节的关注。

实际方法

鉴于上述两个事实,我已经为自己开发了一个特定的过程,我将神经网络应用于新问题,我将尝试描述。你会发现它非常重视上述两个原则。特别是,它从简单到复杂构建,并且在每一步我们对将要发生的事情做出具体假设,然后通过实验验证它们或进行调查直到我们发现某些问题。我们试图非常努力地防止的是同时引入了许多“未经验证的”复杂性,这必然会引入需要永远查找的错误/错误配置(如果有的话)。如果编写您的神经网络代码就像训练一样,您需要使用非常小的学习速率并猜测,然后在每次迭代后评估完整的测试集。

1、与数据成为一体

训练神经网络的第一步是根本不接触任何神经网络代码,而是从彻底检查数据开始。这一步至关重要。我喜欢花费大量时间(以小时为单位)扫描数千个示例,了解它们的分布并寻找模式。幸运的是,你的大脑非常擅长这一点。有一次我发现数据包含重复的例子。另一次我发现损坏的图像/标签。我寻找数据不平衡和偏见。我通常也会关注我自己的数据分类过程,这些过程暗示了我们最终会探索的各种架构。作为一个例子 - 是非常本地的功能还是我们需要全球背景?有多少变化,它采取什么形式?什么变化是假的,可以预处理?空间位置是否重要,或者我们是否想要将其平均化?细节有多重要,我们可以在多大程度上对图像进行缩减采样?标签有多吵?

此外,由于神经网络实际上是数据集的压缩/编译版本,因此您将能够查看网络(错误)预测并了解它们的来源。如果你的网络给你的预测看起来与你在数据中看到的内容不一致,那么就会有所收获。

一旦获得定性意义,编写一些简单的代码来搜索/过滤/排序也是一个好主意,无论你能想到什么(例如标签的类型,注释的大小,注释的数量等),并可视化它们的分布。和沿任何轴的异常值。异常值尤其几乎总能揭示数据质量或预处理中的一些错误。

2、设置端到端的培训/评估框架+获得哑基线

既然我们了解了我们的数据,我们可以找到我们超级精彩的多尺度ASPP FPN ResNet并开始培训真棒模型吗?肯定没有。这是痛苦的道路。我们的下一步是建立一个完整的培训+评估框架,并通过一系列实验获得对其正确性的信任。在这个阶段,最好选择一些你不可能以某种方式搞砸的简单模型 - 例如线性分类器或非常小的ConvNet。我们希望对其进行训练,可视化损失,任何其他指标(例如准确度),模型预测,并在此过程中使用明确的假设执行一系列消融实验。

这个阶段的提示和技巧:

  • 修复随机种子。始终使用固定的随机种子来保证当您运行代码两次时,您将获得相同的结果。这消除了变异因素,并将帮助您保持理智。
  • 简化。确保禁用任何不必要的幻想。例如,在此阶段肯定会关闭任何数据扩充。数据扩充是一种我们可能在以后合并的正规化策略,但是现在它只是引入一些愚蠢错误的另一个机会。
  • 在您的评估中添加有效数字。在绘制测试损失时,对整个(大)测试集进行评估。不要只是在批量上绘制测试损失,然后依靠在Tensorboard中平滑它们。我们追求正确,并且非常愿意放弃保持理智的时间。
  • 验证丢失@ init。验证您的损失是否以正确的损失值开始。例如,如果正确初始化最后一层,则应-log(1/n_classes)在初始化时测量softmax。可以为L2回归,Huber损失等导出相同的默认值。
  • 初衷很好。正确初始化最终图层权重。例如,如果您回归一些平均值为50的值,则将最终偏差初始化为50.如果您有一个比例为1:10的不平衡数据集:正数:负数,请设置对数的偏差,以便您的网络预测概率在初始化时为0.1。正确设置这些将加速收敛并消除“曲棍球棒”损失曲线,在最初的几次迭代中,您的网络基本上只是学习偏差。
  • 人类基线。监控除人为可解释和可检查的损失之外的指标(例如准确性)。尽可能评估您自己(人类)的准确性并与之进行比较。或者,对测试数据进行两次注释,并且对于每个示例,将一个注释视为预测,将第二个注释视为基础事实。
  • 输入独立基线。训练一个独立于输入的基线,(例如,最简单的方法是将所有输入设置为零)。这应该比实际插入数据时更糟糕,而不会将其清零。可以?即你的模型是否学会从输入中提取任何信息?
  • 装一批。只需几个例子(例如少至两个)就可以完成一批。为此,我们增加了模型的容量(例如添加层或过滤器)并验证我们可以达到可实现的最低损失(例如零)。我还想在同一个图中同时显示标签和预测,并确保一旦达到最小损失,它们就会完美对齐。如果他们不这样做,那么某个地方就会出现一个错误,我们无法继续下一个阶段。
  • 验证减少训练损失。在这个阶段,你希望你的数据集不合适,因为你正在使用玩具模型。尝试稍微增加容量。您的训练损失是否应该下降?
  • 在网前可视化。在您y_hat = model(x)(或sess.run在tf)之前,明确正确地显示您的数据的位置。也就是说 - 您希望可视化确切地了解网络中的内容,将原始张量的数据和标签解码为可视化。这是唯一的“事实来源”。我无法计算这个节省了我的次数,并揭示了数据预处理和扩充中的问题。
  • 可视化预测动态。我喜欢在培训过程中对固定测试批次上的模型预测进行可视化。这些预测如何发生的“动态”将为您提供令人难以置信的良好直觉,使其了解培训的进展情况。很多时候,如果网络以某种方式摆动太多,显示出不稳定性,就有可能感觉网络“难以”适合您的数据。学习率非常低或非常高,抖动量也很明显。
  • 使用backprop来绘制依赖关系。您的深度学习代码通常包含复杂的,矢量化的和广播的操作。我遇到的一个相对常见的错误是人们弄错了(例如他们使用view而不是transpose/permute某处)并且无意中在批量维度上混合信息。令人沮丧的是,您的网络通常仍然可以正常训练,因为它会学会忽略其他示例中的数据。调试此问题(以及其他相关问题)的一种方法是将某个示例i的损失设置为1.0,将反向传递一直运行到输入,并确保仅在i-上获得非零梯度日例。更一般地说,渐变为您提供有关什么取决于网络中的内容的信息,这对调试很有用。
  • 概括一个特例。这是一个更通用的编码技巧,但我经常看到人们在咬人时比他们咀嚼更多,从头开始编写相对一般的功能。我喜欢为我现在正在做的事情编写一个非常具体的函数,让它工作,然后在以后概括它以确保我得到相同的结果。这通常适用于矢量化代码,其中我几乎总是首先写出完全循环的版本,然后一次一个循环地将它转换为矢量化代码。

3、过度适应

在这个阶段,我们应该对数据集有一个很好的理解,我们有完整的培训+评估管道工作。对于任何给定的模型,我们可以(可重复地)计算我们信任的度量。我们还有一个独立于输入基线的表现,一些哑基线的表现(我们更好地击败了这些基线),我们粗略地了解人类的表现(我们希望达到这一点)。舞台现在设置为迭代一个好的模型。

我喜欢寻找一个好模型的方法有两个阶段:首先得到一个足够大的模型,它可以过度拟合(即专注于训练损失),然后适当地调整它(放弃一些训练损失以改善验证损失)。我喜欢这两个阶段的原因是,如果我们根本无法使用任何模型达到低错误率,这可能再次表明一些问题,错误或错误配置。

这个阶段的一些提示和技巧:

  • 挑选模型。为了获得良好的培训损失,您需要为数据选择合适的架构。当谈到选择这个我的#1建议是:不要成为英雄。我见过很多人都渴望疯狂和创造性地将神经网络工具箱的乐高积木堆叠在各种对他们有意义的异域架构中。在项目的早期阶段强烈抵制这种诱惑。我总是建议人们简单地找到最相关的纸张并复制粘贴他们最简单的架构,以获得良好的性能。例如,如果您要分类图像不是英雄,只需复制粘贴ResNet-50为您的第一次运行。你可以稍后做一些更自定义的事情并击败它。
  • adam是安全的。在设定基线的早期阶段,我喜欢使用学习率为3e-4的 Adam 。根据我的经验,亚当对超参数更加宽容,包括不良的学习率。对于ConvNets来说,一个经过良好调整的SGD几乎总会略微优于Adam,但最佳学习率区域要窄得多,而且特定于问题。(注意:如果您使用的是RNN和相关的序列模型,那么使用Adam更为常见。在项目的初始阶段,再次,不要成为英雄并遵循最相关的论文所做的事情。)
  • 一次只复杂化一个。如果您有多个信号插入分类器,我会建议您逐个插入它们,每次都确保您获得预期的性能提升。一开始不要把厨房水槽扔到你的模型上。还有其他方法可以增加复杂性 - 例如,您可以尝试先插入较小的图像,然后再将它们放大,等等。
  • 不相信学习率衰减默认值。如果你重新使用来自其他领域的代码,那么学习速度衰减时一定要非常小心。您不仅希望针对不同的问题使用不同的衰减计划,而且 - 更糟糕的是 - 在典型的实施中,计划将基于当前的纪元数,其可以根据数据集的大小而广泛变化。例如,ImageNet将在30年内衰减10点。如果你没有训练ImageNet那么你几乎肯定不会想要这个。如果你不小心,你的代码可能会过早地将你的学习率提高到零,不允许你的模型收敛。在我自己的工作中,我总是完全禁用学习率衰减(我使用常数LR)并在最后调整这一点。

4、规范

理想情况下,我们现在所处的地方是一个大型模型,至少适合训练集。现在是时候通过放弃一些训练准确性来规范它并获得一些验证准确性。

一些提示和技巧:

  • 获得更多数据。首先,在任何实际环境中规范模型的最佳和首选方法是添加更多真实的训练数据。如果您可以收集更多数据,那么花费大量工程周期尝试从小型数据集中挤出汁液是一个非常常见的错误。据我所知,添加更多数据几乎是唯一无限期单调改善配置良好的神经网络性能的唯一保证方式。另一个是合奏(如果你能负担得起的话),但是在5个模型之后最高。
  • 数据增加。实际数据的下一个最好的事情是半假数据 - 尝试更积极的数据增强。
  • 创造性的增强。如果半假数据不能做到这一点,假数据也可能会有所作为。人们正在寻找扩展数据集的创新方法; 例如,域随机化,模拟的使用,巧妙的混合,例如将(可能模拟的)数据插入场景,甚至是GAN。
  • 预训练。即使您有足够的数据,如果可以,也很少使用预训练网络。
  • 坚持有监督的学习。对于无人监督的预训练,不要过度兴奋。不像2008年的博客文章告诉你的那样,据我所知,没有任何一个版本在现代计算机视觉中报告了强有力的结果(虽然现在NLP似乎与BERT和朋友做得很好,很可能是因为更多故意性质的文本,以及更高的信噪比)。
  • 较小的输入维度。删除可能包含虚假信号的功能。如果您的数据集很小,任何添加的虚假输入都是过度拟合的另一个机会。同样,如果低级细节无关紧要,请尝试输入较小的图像。
  • 较小的型号。在许多情况下,您可以在网络上使用域知识约束来减小其大小。例如,过去使用ImageNet骨干网顶部的完全连接层是时髦的,但这些已经被简单的平均池替换,从而消除了过程中的大量参数。
  • 减少批量大小。由于批次规范内的归一化,较小的批量大小有点对应于较强的正则化。这是因为批次经验均值/标准是完整均值/标准的更近似版本,因此标度和偏移量“摆动”您的批次更多。
  • 下降。添加辍学。对ConvNets使用dropout2d(空间丢失)。谨慎/谨慎地使用这个,因为丢失似乎不适合批量标准化。
  • 体重衰减。增加减重惩罚。
  • 提早停止。根据您测量的验证损失停止培训,以便抓住您的模型,就像它过度拟合一样。
  • 尝试更大的模型。我最后提到这一点并且只是在早期停止之后但我在过去曾经发现过几次,大型模型当然会最终过度装配,但是他们的“早期停止”性能往往比小型号更好。

最后,为了让您更加确信您的网络是一个合理的分类器,我喜欢可视化网络的第一层权重,并确保您获得有意义的优质边缘。如果您的第一层滤镜看起来像噪音,那么可能会有一些关闭。类似地,网内的激活有时可以显示奇怪的伪像并暗示问题。

5、调整

您现在应该“在循环中”使用您的数据集探索宽泛的模型空间,以获得实现低验证损失的体系结构。

这一步的一些提示和技巧:

  • 随机网格搜索。为了同时调整多个超参数,使用网格搜索确保覆盖所有设置听起来很诱人,但请记住,最好使用随机搜索。直观地说,这是因为神经网络通常对某些参数比其他参数更敏感。在极限情况下,如果一个参数一个问题,但改变b没有效果,那么你宁愿品尝一个不是在几个固定点多次更throughly。
  • 超参数优化。有很多花哨的贝叶斯超参数优化工具箱,我的一些朋友也报告了它们的成功,但我个人的经验是,最先进的方法是探索一个漂亮而宽广的模型和超参数空间是使用实习生:)。

6、挤出果汁

一旦找到最佳类型的体系结构和超参数,您仍然可以使用一些技巧将最后一些果汁挤出系统:

  • 合奏。模型集合是一种非常有保证的方法,可以在任何事情上获得2%的准确率。如果您在测试时无法负担计算,请考虑使用黑暗知识将您的整体提升到网络中。
  • 留下训练。我经常看到人们试图在验证损失似乎趋于平稳时停止模型训练。根据我的经验,网络会长时间不间断地进行培训。有一次我在冬假期间不小心离开了模特训练,当我1月份回来时,它是SOTA(“最先进的”)。

结论

一旦你在这里成功,你将拥有成功的所有要素:你对技术,数据集和问题有深刻的理解,你已经建立了整个培训/评估基础设施,并对其准确性有了很高的信心,您已经探索了越来越复杂的模型,以预测每一步的方式获得性能改进。您现在已经准备好阅读大量论文,尝试大量实验,并获得您的SOTA结果。祝好运!

本文转自:Data Intelligence的AI分享空间,原作者:Andrej Karpathy,转载此文目的在于传递更多信息,版权归原作者所有。

最新文章