[关闭]
@wanghuijiao 2021-07-07T17:17:23.000000Z 字数 7296 阅读 717

调参技巧1:大神 Andrej Karpathy - A Recipe for Training Neural Networks

模型训练


前言

起因&两个重要观察

几周前,我(以下均指Andrej Karpathy)发布了一条关于“最常见的神经网络错误”的推文,列出了一些与训练神经网络相关的常见错误。这条推文获得了比我预期更多的关注(包括一个网络研讨会)。显然,很多人都曾亲身经历过“卷积神经网络就是这么工作的”和“我们的convnet实现了最先进的结果”之间的巨大差距。

所以我想,把我tweet的这个话题扩展到应该有的篇幅,可能会很好玩。然而,我不想详细列举更多常见的错误或充实它们,我想更深入地探讨一下如何能够完全避免这些错误(或快速修复它们)。这样做的诀窍是遵循一个特定的过程,据我所知,这个过程并不经常被记录下来。让我们从两个重要的观察开始。

1) 神经网络训练是一个有漏洞的抽象概念

据说开始训练神经网络很容易。许多库和框架都觉得使用30行代码来解决数据问题很了不起,这给人一种即插即用的(错误的)印象。常见的做法是:

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

在我们的脑子里,标准的软件就应该是这样的,通常可以获得干净的api和抽象。使用Requests库演示一下:

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

那样是很酷!开发人员承担了理解查询字符串、url、GET/POST请求、HTTP连接等重担,并在很大程度上隐藏了几行代码背后的复杂性。这是我们所熟悉和期望的。不幸的是,神经网络不是这样的。如果你稍微和训练ImageNet分类器不太一样的话,那么它们就不是“现成的”技术了。我试图在我的帖子" Yes you should understand backprop "中指出这一点,通过选择反向传播并将其称为“泄漏的抽象”,但真实情况要糟糕得多。Backprop + SGD不会神奇地让你的网络工作。Batch norm并不能神奇地使其更快地收敛。RNNs也不会神奇地让你的文本即插即用。仅仅因为你可以用增强学习表示你的问题并不意味着你应该这样做。如果你坚持使用这种技术而不了解它的工作原理,你很可能会失败。这使我想到……

2) 神经网络训练默默的失败了

当你破坏或配置代码错误时,通常会得到某些异常。你输入了一个整数,但是期望输入是一个字符串。这个函数只需要3个参数。导入失败。这个key不存在。这两个列表中的元素数量不相等。此外,通常可以为某个功能创建单元测试。

这只是训练神经网络的一个开始。从语法上来说,一切都是正确的,但整件事的安排并不正确,而且这真的很难判断。“可能的错误面”很大,符合逻辑(与语法相反),并且很难进行单元测试。例如,在数据增强过程中,当你从左到右翻转图像时,可能忘记了翻转标签。你的网络仍然可以(令人震惊地)很好地工作,因为你的网络可以在内部学会检测翻转的图像,然后左右翻转它的预测。或者你的自回归模型不小心把它试图预测的东西作为输入,因为有一个减一的错误。或者你试着修剪你的梯度,但结果却减少了损失,导致异常值的样本在训练中被忽略。或者你从一个预训练的检查点初始化你的权重,但没有使用原始平均值。或者你只是搞砸了正则化强度、学习率、衰减率、模型大小等的设置。因此,只有在幸运的情况下,错误配置的神经网络才会抛出异常,大多数情况下,它会训练,但是,这样默默地工作更糟。

因此,用“简单粗暴”的方法来训练神经网络是行不通的,只会导致痛苦。现在,痛苦是让神经网络正常工作的一个非常自然的部分,但是它可以通过彻底的,防御性的,偏执的,以及对几乎所有可能的事情的可视化着迷的方式来减轻。根据我的经验,耐心和对细节的关注是深度学习成功最重要的因素。

方案

基于以上两个事实,我为自己开发了一个特定的过程,当我将神经网络应用到一个新的问题时,我将遵循这个过程,我将尝试描述它。你将看到,它非常认真地对待上述两个原则。特别是,它从简单到复杂,每一步我们都对将要发生的事情做出具体的假设,然后通过实验验证它们,或者进行调查,直到我们发现一些问题。我们极力避免的是同时引入大量“未经验证”的复杂性,这必然会引入错误/错误配置,而这些错误/错误配置将永远无法找到(如果有的话)。如果编写你的神经网络代码就像训练代码一样,你会想要使用非常小的学习率,然后去猜测,再在每次迭代之后评估完整的测试集。

1. 与数据融为一体

训练神经网络的第一步是完全不接触任何神经网络代码,而是从彻底检查数据开始。这一步至关重要。我喜欢花大量的时间(以小时为单位)浏览数千个样本,理解它们的分布并寻找模式。幸运的是,你的大脑非常擅长这个。有一次,我发现数据中包含重复的样本。另一次我发现了损坏的图像/标签。我寻找数据的不平衡和偏差。我通常还会注意我自己对数据进行分类的过程,这暗示了我们最终将探索的架构类型。举个例子,非常局部的特征是否足够,或者我们是否需要全局上下文?有多少多样性,它以什么形式出现?哪些多样性是假的,是不是可以可以通过预处理挑出来?空间位置重要吗,我们用不用平均池化?细节有多重要,我们能在对图像降采样多少倍?标签的噪声程度怎么样?

此外,由于神经网络实际上是对数据集的压缩/编译版本,你可以查看你的网络的预测,并了解它们可能来自何处。如果你的网络给你的预测与你在数据中看到的不一致,那就错了。

一旦你有了一个定性的感觉,写一些简单的代码来搜索/过滤/排序也是一个好主意,不管你能想到什么(例如标签的类型,标注的尺寸,标签的数量,等等),然后可视化它们的分布,看看在每一个维度上的异常值。异常值几乎总是会发现数据质量或预处理中的一些bug。

2. 配置端到端的训练/评估框架+获取一个基线

现在我们了解了我们的数据了,那我们是不是就可以使用我们的超炫的多尺度ASPP FPN ResNet,并开始训练非常牛逼的模型了呢?肯定是不行的,那样做是自寻死路。我们的下一步是建立一个完整的训练+评估框架,通过一系列的实验获得对其正确性的信任。在这个阶段,最好选择一些你不可能搞砸的简单模型——例如一个线性分类器,或者一个非常小的卷积网络。我们想要训练它,可视化损失,所有的其他指标(例如准确性),模型预测,并在此过程中执行一系列带有明确假设的消融实验。

这个阶段的小贴士和技巧:

3. 过拟合

在这个阶段,我们应该对数据集有一个很好的理解,我们有完整的训练+评估流程。对于任何给定的模型,我们都可以(可重复地)计算我们可信任的度量。我们还准备好了一个独立于输入的基线,一些简单基线的性能(我们最好战胜这些基线),并且我们对人类的性能有一个粗略的感觉(我们希望达到这个目标)。现在已经为迭代一个好的模型做好了准备。

我喜欢采用的找到一个好的模型的方法有两个阶段:首先获得一个足够大的模型,它可以过拟合(即关注训练损失),然后适当地对其进行正则化化(放弃一些训练损失,以改进验证损失)。我喜欢这两个阶段的原因是,如果我们不能达到任何模型的低错误率,这可能再次表明一些问题、bug或错误配置。

这一阶段的一些建议和技巧:

4. 正则化

理想情况下,我们现在拥有一个至少能在训练集上拟合的很好的大型模型。现在是时候对其进行正则化,并通过放弃一些训练精度来获得一些验证精度。以下是一些小贴士和技巧:

最后,为了进一步确信你的网络是一个合理的分类器,我喜欢可视化网络的第一层权重,并确保得到有意义的好边缘。如果你的第一层过滤器看起来像噪音,那么有些东西可能会出问题。类似地,网络内的激活有时会显示奇怪的纹理,这也提示有问题。

5. 调试

你现在应该“循环”使用你的数据集,探索广阔的模型空间,为的是实现低验证损失。这一步的一些小贴士和技巧:

6. 再压榨一波

一旦你找到了最好的架构类型和超参数,你仍然可以使用更多的技巧来再压榨一下:

结论

一旦你看到这儿了,你会得到所有成功的配方:你有一个深入了解的技术,数据集和问题,你建立了整个训练/评估的基础设施并且对其准确性有很高的信心,你探索了越来越复杂的模型,你的每一步都得到了预期的性能改进。你也已经准备好阅读大量的论文,尝试大量的实验,并获得SOTA结果。好运!

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注