特斯拉AI主管给你的33条深度学习训练建议

精翻版Andrej Karpathy博客

 

30行代码就能训练神经网络?

几周前,我在发了一条”最常见的神经网络错误”的微博(•̀ᴗ•́)و ̑̑ ,列举了一些与训练神经网络相关的常见错误,这条微博引发了大家热烈的讨论。相信很多人都曾亲身经历过”卷积层的工作原理”和”训练的实际结果”之间的巨大差距。

所以我想,就这个话题写一篇长文。不过,我不打算详细列举常见的错误,而是更深入地探讨一下如何能够完全避免这些错误(或快速修复它们)。这样做的诀窍是遵循一个特定的过程,据我所知,很少有文字记录如何修复训练错误的这个过程。

下面从两个日常碰到的问题开始。

1) 神经网络训练是一种”抽象泄漏”(leaky abstraction)

现在训练神经网络很容易,许多库和框架都以可以用30行代码解决你的数据问题而自豪,给人一种即插即用的(错误)印象。常见的做法是:

>>> your_data = # plug your awesome dataset here

这些库和示例类似我们之前熟悉的标准软件的接口流程———抽象、简洁的API,如http请求库API所示:

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

这很酷!开发者勇敢地承担了理解查询字符串、url、GET/POST请求、HTTP连接等重担,并在很大程度上隐藏了几行代码背后的复杂性。

不幸的是,神经网络不是这样的。如果你稍微偏离了训练ImageNet分类器,那么它们就不是”现成的”技术。

在我以前介绍反向传播的博文中,我试图通过使用反向传播并将其称为”抽象泄漏”来说明这一点,但不幸的是,实际情况要糟糕得多。Backprop + SGD并不会神奇地让你的网络工作,Batch norm并不能神奇地使其更快地收敛,RNN不会神奇地让你”插入”文本;你可以用RL表示你的问题,但仅仅是这样并不意味着你应该这样做。如果你坚持使用这种技术而不了解它的工作原理,那么你很可能会失败。

2) 神经网络训练常常无声无息地失败

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

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

因此,用”快速而激烈”的方法来训练神经网络是行不通的,只会导致痛苦。痛苦虽然是让神经网络正常工作的一个非常自然的部分,但它是可以通过彻底的、防御性的、偏执的,以及对几乎所有可能的事情进行可视化来减轻的。根据我的经验,与深度学习成功最相关的品质是耐心和关注细节。

如何训练一个神经网络

基于以上两个事实,我为自己开发了一个特定的过程,当我将神经网络应用到一个新的问题时,都遵循这个过程。本文中将尝试描述这个过程。

你会看到,它是非常重视上述两个原则的。特别是,它是从简单到复杂构建的,每一步我们都对将要发生的事情做出具体的假设,然后通过实验验证它们,或者进行调查,直到发现问题。需要极力避免的是同时引入大量”未经验证”的复杂性,这必然会引入错误/错误配置,而这些错误/错误配置将永远无法找到。如果像训练一样编写神经网络代码,你会想要使用非常小的学习率并猜测,然后在每次迭代之后评估完整的测试集。

1. 梳理数据

训练神经网络的第一步是完全不用接触任何神经网络代码,而是从彻底检查数据开始。这一步至关重要,花大量时间(以小时为单位)浏览数千个示例,了解它们的分布并寻找模式。幸运的是,人类的大脑非常擅长做这个。有一次,我发现数据中包含重复的例子,还有一次发现了损坏的图像/标签。我会寻找数据中的不平衡和偏差,也会关注自己是如何对数据进行分类的,这个过程会提醒了最终要探索怎样的网络网络结构类型。

举个例子,只有局部的特性是否足够,还是需要全局上下文?有多少变量,它们以什么形式出现?哪些变量是假的,可以预先处理的?空间位置是否重要,是否想要将其平均池化?细节有多重要,能在多大程度上对图像进行降采样?标签有多少噪音?

此外,由于神经网络实际上是数据集的压缩版本,因此你能够查看你的网络预测并了解它们可能出错来自何处。如果你的网络给你的预测与你在数据中看到的不一致,那就是有什么地方错了。

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

2. 建立端到端的训练/评估框架+获取简单的基线

现在我们已经对数据有了一定了解,那么就可以实现炫酷的多尺度ASPP FPN ResNet,并开始训练超棒的模型了吗?肯定不是,首先应该建立一个完整的训练+评估的框架,通过一系列的实验评估模型的正确性。在这个阶段,最好选择一些你不可能搞砸的简单模型———例如线性分类器,或者一个非常小的卷积网络。通过训练这个网络,可视化损失值及其他指标(例如准确性),并在此过程中执行一系列带有明确假设的消融实验。

这个阶段的提示和技巧:

-固定随机种子(random seed)。使用固定的随机seed来保证当你运行同一代码两次时,能得到相同的结果。消除了变量因素,将有助于对模型的判断。

-简化。在这个阶段一定要关闭所有数据增强,数据增强是一种正则化策略,我们稍后可能会将其合并进来,但在初始阶段它只是会引入一些愚蠢的bug。

-评估整个测试集。当绘制测试损失时,在整个测试集上进行评估,不要只在batches上绘制测试损失,然后依赖于在Tensorboard中平滑它们。为了要追求正确,需要牺牲一定的时间。

-在初始化时验证损失。验证你的损失是否以正确的损失值开始。例如,如果你正确地初始化了最后一层,你应该在初始化时测量softmax的-log(1/n_classes)。同样的缺省值可以用于L2回归、Huber损失等。

-正确地初始化。正确初地始化最后一层权重。例如,如果你要回归一些平均值为50的值,那么要将最终偏差初始化为50。如果你有一个不平衡的数据集,其正负比为1:10,那么在你的日志上设置偏差,使你的网络预测概率在初始化时为0.1。正确设置这些参数将加快收敛速度,并消除”hockey stick”损失曲线,在最初几次迭代中,你的网络基本上只是学习偏差。

-human baseline。监控人类可解释和可检查的损失以外的指标(例如准确性)。尽可能地评估你自己(人类)的准确性,并与之进行比较。或者,对测试数据进行两次注释,对于每个示例,将一个注释作为预测,将第二个注释作为ground truth。

-input-indepent baseline。训练一个独立于输入的基线(例如,最简单的方法就是将所有输入设置为零)。这应该比实际插入数据而不将其归零的情况更糟。也就是说,可以知道你的模型是否学会从输入中提取任何信息?

-在单批数据上过拟合。过拟合包含少量例子(例如只有两个)的一个batch。为此,我们需要增加模型的容量(例如添加层或过滤器),并验证我们可以达到的最低损失值(例如0)。我也喜欢在同一个图中可视化标签和预测,并确保一旦达到最小损失,它们最终会完美地对齐。如果没有完美对齐,那么在某个地方就有一个bug,无法继续到下一个阶段。

-验证训练损失的下降。在这个阶段,你可能希望在数据集上实现欠拟合,因为该阶段的模型是一个玩具模型。试着增加一点它的容量,在看看训练损失是否下降了。

-在输入网络前对数据进行可视化。在运行y_hat = model(x)(或tf中运行sess.run)之前,是可视化数据的正确时间。我们需要准确地可视化输入给你的网络的东西,把原始的数据张量和标签解码并可视化。这个过程为我节省了大量时间,并揭示了数据预处理和增强方面的问题。

-可视化预测动态。在训练过程中,我喜欢可视化固定测试batch模型的预测结构。这些预测动态移动将为你提供关于训练进展的良好直觉。很多时候,如果网络以某种方式太过波动,显示出不稳定性,你可能会觉得网络”难以”拟合你的数据。非常低或非常高的学习率在波动的数量中也很容易被注意到。

-使用反向传播来绘制依赖关系图。深度学习代码通常包含复杂、向量化和broadcast操作。我遇到过的一个比较常见的bug是,使用view而不是进行transpose/permute,无意中混淆了批处理的维度信息。然而,你的网络通常仍然训练良好,因为它将学会忽略来自其他样本的数据。一种debug的方法是将某个样本i的损失设置为1.0,然后运行反向传播一直到输入,并确保只在i-thexample上得到一个非零梯度。也就是说,梯度提供了网络中的依赖关系信息,这对于debug非常有用。

-代码通用化。这是一种更为通用的编码技巧,但是我经常看到人们在使用这个技巧时产生bug,尤其是从头编写一个相对通用的函数时。我喜欢为我正在做的事情写非常具体的函数,让这个函数能work,然后再一般化,确保得到相同的结果。这通常适用于向量化代码,我会先写出完整的循环版本,然后再将它转换为向量化代码。

3.过拟合

在这个阶段,我们应该对数据集有一个很好的理解,已经有完整的训练和测试pipeline。对于任何给定的模型,可以重复地计算出一个可信的量度。这一阶段的任务是对一个性能不错的模型进行迭代。

找到一个好模型可以分为两个极端:首先得到一个足够大的模型,它可以是过拟合的,然后在适当调整(放弃一些训练损失,以改善验证损失)。

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

-挑选模型。 为了获得良好的训练损失,需要为数据选择合适的网络结构。我的建议是:不要逞英雄。我见过很多人疯狂的人,将神经网络工具箱的各种工具像堆乐高积木一样堆叠在各种奇异的网络结构中,应该在项目早期阶段尽力避免这样做。

我总是建议先找到最相关的一些论文,先把其中最简单的网络结构原样照搬过来,以获得良好的性能。比如你做得是图像分类,不要想着当英雄,先把ResNet-50拿过来用。后面可以做一些更自定义设置和改进,并实现比它更好的性能。

-选Adam总没错。 在设定baseline方法的早期阶段,我喜欢使用学习率为3e-4的Adam网络结构。根据我的经验,Adam对超参数更加宽容,包括不良的学习率。对于ConvNets来说,一个经过适当调整的SGD几乎总会略优于Adam,但前者最佳学习率区域要窄得多,而且受到具体问题所限。(如果使用的是RNN和相关的序列模型,那么在项目早期阶段更常用Adam。再说一遍,不要逞英雄,多参考相关论文。)

-一次只针对一个对象进行复杂化。 如果有多个信号进入分类器,建议逐个引入,确保每次信号的引入都获得预期的性能提升。一开始不要一股脑地把全部信号都喂给模型。增加复杂度还有别的方法,比如可以尝试先插入较小的图像,然后再放大,诸如此类。

-不要过于相信默认的学习率衰减。 如果是重用来自其他领域的代码,那么在处理学习速度衰减时一定要非常小心。您不仅希望针对不同的问题使用不同的衰减计划,而且 - 更糟糕的是 - 在典型的实施中,计划将基于当前的epoch数,该值会根据数据集的大小而广泛变化。

比如,ImageNet将在30个epoch上衰减10倍。如果你没有训练ImageNet那么这肯定不是想要的结果。如果你不小心,代码可能会过早地将学习率趋零,导致模型无法收敛。在我自己的研究中总是完全禁用学习率衰减(学习速率为常数)并在最后进行调整。

4.正则化

理想情况下,我们要处理的是大型模型,至少能够拟合训练集。现在是时候通过放弃一些训练准确性,进行一些正则化处理,以获得一些验证准确性。以下是这方面一些提示和技巧:

-获取更多的数据。首先,在任何实际环境中,最佳和首选方法是添加更多真实的训练数据。当可以收集更多数据时,就不要再花费大量时间尝试从小型数据集中挤性能了。据我所知,添加更多数据几乎是唯一无限改善配置良好的神经网络性能的方式。

-数据增强。 仅次于真实数据的半真实的数据,需要尝试更积极的数据增强。

-创造性数据增强。 如果数据增强不能做到这一点,假数据也许可以。人们正在寻找扩展数据集的创新方法;例如,域随机化,模拟的使用,巧妙的混合,比如将(可能模拟的)数据引入场景,甚至是GAN中。

-预训练。 即使有足够的数据,如果可以,使用预训练网络也几乎没坏处。

-坚持监督式学习。 不要对无监督的预训练抱有过分的信心。据我所知,没有任何一种无监督学习在现代计算机视觉任务上有很强的表现(虽然现在NLP领域诞生了BERT等优秀模型,但这很可能是因为文本数据更成熟的形式,以及更高的信噪比)。

-更小的输入维度。 去除可能包含虚假信号的特征。如果数据集很小,任何添加的虚假输入都可能造成过拟合。同样,如果低级细节无关紧要,请尝试输入更小的图像。

-较小的模型。 很多时候可以在网络上使用先验知识来减小模型大小。例如,过去在ImageNet骨干网顶部使用全连接层很流行,但现在已改用简单的平均池化,从而去掉了过程中的大量参数。

-减小批大小。 由于归一化是基于批大小的,更小的batch size会具有更明显的正则化效果。这是因为批量经验均值/标准是完整均值/标准的更近似版本,因此标度和偏移量”摆动”您的批次更多。

-Dropout。添加dropout。对ConvNets使用dropout2d。建议谨慎使用dropout,因为似乎不太不适合批归一化。

-权重衰减。 增加权重衰减惩罚。

-“早停”:提早停止训练。 根据验证损失停止训练,在出现过拟合之前获得模型。

-尝试更大的模型。 我最后提到这一点,并且只是在提前停止之后,但我在过去曾经发现过几次大模型最终过拟合,但是它们的”早停”性能往往比小模型的性能要好得多。

最后,为了进一步确保网络是一个合理的分类器,我喜欢对网络第一层权重进行可视化,并确保获得有意义的边缘。如果第一层滤波器看起来像噪音,那么可能需要去掉一些东西。类似地,网络中的激活函数有时也会有异常出现,暴露出一些问题。

5.精细调整

现在应该使用数据集探索宽泛的模型空间,以获得低验证损失的体系结构。下面是一些提示和技巧:

-随机网格搜索。为了同时调整多个超参数,使用网格搜索确保覆盖所有设置,这听起来很诱人,但请记住,最好使用随机搜索。直观地说,这是因为神经网络通常对某些参数比其他参数更敏感。在极限情况下,如果一个参数很重要,但是改变另一个参数没有效果,那还不如对第一个参数进行彻底采样。

-超参数优化。目前有很多花哨的贝叶斯超参数优化工具箱,也有一些成功应用的实例,但我个人的经验是,探索优质大宽度模型和超参数空间的最好方式是找个实习生。哈哈,开句玩笑。

6.性能压榨

在确定了最佳类型的体系结构和超参数后,仍然可以利用一些技巧最后”压榨”一下系统的性能:

-集成。模型是一种非常有保证的方法,可以在任何事情上获得2%的准确率。如果您在测试时无法负担计算,请考虑使用黑暗知识将您的整体提升到网络中。

-保持训练。我经常看到人们在验证损失趋于平稳时就想停止模型训练。根据我的经验,网络会长时间不间断地进行训练。有一次我在冬假期间不小心没有停止训练,当我第二1月份回来时,发现模型性能达到了SOTA水平。

最后

走到这一步,相信你已经获得了神经网络训练成功的要素:对技术,数据集和问题定位有了更加深刻的理解,建立了模型的训练/测试基础框架,并对其准确性有了很高的信心,且进一步探索了更加复杂的模型,以预测每一步的调试获得性能改进。现在是时候开始阅读大量论文,尝试大量实验,准备好获得SOTA结果吧,祝你好运!

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值