秘籍在手,训练不愁!特斯拉Karpathy的超全神经网络训练套路
大数据文摘出品
编译:周素云、宋欣仪、熊琰、ZoeY、顾晨波
训练神经网络到底有诀窍和套路吗?
Andrej Karpathy认为,还的确有。
这位特斯拉的人工智能研究负责人、李飞飞的斯坦福高徒刚刚难得更新了博客,推出了一篇长文《神经网络的训练秘籍》,详细讲述了我们在训练神经网络时候可以遵循的套路。
据Andrej Karpathy推特说,他本来是在推特上写了一些自己训练神经网络的经验教训,结果网友们反响强烈,所以他决定把相关内容更完整的在一篇文章中呈现给大家。
在这篇长文中,Andrej Karpathy像一个操心的老父亲一样,详细且循循善诱地对所有机器学习从业者讲述了构建神经网络的难处,以及如何才能循序渐进地构造神经网络。讲述细致,逻辑清晰,非常值得一看。
文摘菌也在第一时间对文章做了翻译,以下是翻译原文,enjoy~
以下内容翻译至Andrej Karpathy的博客:A Recipe for Training Neural Networks。
神经网络训练是一个漏洞百出的抽象概念
都说万事开头难,但随着训练神经网络的一些即插即用的工具的出现,很多30行代码解决问题的案例让人误以为训练神经网络很简单,就像这样:
>>> your_data = # plug your awesome dataset here >>> model = SuperCrossValidator(SuperDuper.fit, your_data, ResNet50, SGDOptimizer) # conquer world here
这些库和例子是不是对你来说很熟悉?比如Request库。
>>> r = requests.get('https://api.github.com/user', auth=('user', 'pass')) >>> r.status_code 200
这些分享非常酷炫, 一些开发人员提供了理解查询字符串,URL,GET / POST请求,HTTP连接等等,并且在很大程度上隐藏了几行代码背后的复杂性。
但不幸的是,神经网络并不是这样的。它们不是“现成的”技术,这个可以在我之前写的“你该知道backprop"一文中有介绍。
文章链接
https://medium.com/@karpathy/yes-you-should-understand-backprop-e2f06eab496b
Backprop + SGD并没有神奇地让你的网络运作,批量规范也不会神奇地使其收敛得更快。RNN也不会地让轻而易举地你“插入”文本。你可以用RL制定问题也不意味你应该这么做。
如果你坚持使用该神经网络训练而不了解其工作原理,就很容易会失败。
训练失败的神经网络
当你错误配置代码时,通常会遇到某种异常。比如你在一个预期字符串的位置插入了整数。因为该函数只需要3个参数,所以输入失败。对此我们通常可以为特定功能创建一个单元测试。
这只是训练神经网络的一个开始。但可能出现所有语法正确,整个事情就是不对的情况,而且很难说清楚哪里不对。
“可能的错误“的覆盖面非常大,而且是逻辑性的(与语法相反),这很难通过单元测试判断出来。例如,在数据增强期间需要左右翻转图像时,你可能忘记翻转标签。你的网络仍然可以继续工作得非常好,因为它可以在内部学习检测翻转的图像,然后左右翻转其预测。
这之后,或许你的自回归模型会因为一个错误的错误而将它想要预测的东西作为输入。或者你希望裁剪你的梯度但是模型裁剪了缺失值,导致模型忽略异常值。或者你会从预训练检查点初始化权重,但没有使用原始均值。或者你只是搞砸了正则化强度,学习率,衰减率,模型大小等设置。因此,错误配置的神经网络只有在你运气好的时候才会让你发现异常,大部分时间它会自己训练,默默工作,然后越来越糟糕。
过犹不及,训练神经网络的“快速和大强度”的方法不起作用,只能带来一系列麻烦,这在过去是对的。但现在,这些麻烦可以成为让神经网络运作良好的一个部分,主要通过可视化来达到。深度学习要想成功,最需要的品质是耐心和对细节的关注。
秘籍在手,训练不愁
基于上文讲述的两个问题,我为自己开发了一套神经网络训练”套路”。本文中我将尝试描述这个套路。这个套路非常重视上述两个原则,并且从简到繁,在每一步都对将要发生的事情做出具体假设,然后通过实验验证或进行检查,直到问题出现。
避免一次性地引入多个“未经验证的”复杂因素,这会导致你长时间的查找错误配置(如果有的话)。如果编写神经网络代码就像训练一样,最好控制学习速率,作出猜测,然后在每次迭代后评估完整的测试集。
1.开始训练前,先对数据了熟于心
训练神经网络的第一步不是研究神经网络代码,而是从彻底检查数据开始。这一步至关重要。我喜欢花费大量时间(以小时为单位)浏览数千个示例,了解它们的分布并寻找规律。幸运的是,我们的大脑非常擅长这一点。
有一次我发现数据集中包含重复的例子,还有一次我发现了损坏的图像/标签。我会尝试寻找数据的不平衡和偏见。我通常也会关注我自己的数据分类过程,从中可以看到我们最终要探索的各种架构。
这个数据集的背景是什么?有多少变化,它采取什么形式?什么变化是假的,可以预处理?空间位置是否重要,或者我们是否想要将其平均化?细节有多重要,我们可以在多大程度上对图像进行缩减采样?标签有多少?
此外,由于神经网络实际上是数据集的压缩/编译版本,因此你将能够查看网络(错误)预测并了解它们的来源。如果你的网络给你的预测看起来与你在数据中看到的内容不一致,那么就会有所收获。
一旦获得定性意义,编写一些简单的代码来搜索/过滤/排序也是一个好主意(例如标签的类型,注释的大小,注释的数量等),你可以可视化它们的分布,发现沿任何轴的异常值,注意,异常值几乎总能揭示数据质量或预处理中的一些错误。
2. 设置端到端的评估框架
当你了解数据就可以利用多尺度ASPP FPN ResNet并开始训练模型了么?那你真是想多了。
下一步应该做的是建立一个完整的训练模型+评估框架,并通过一系列实验获得对其正确性的信任。在这个阶段, 你最好选择一种有把握的简单模型,例如线性分类器或非常小的ConvNet。训练的内容通常包括可视化损失、准确度、模型预测等,并在此过程中使用伴有明确假设的一系列消融实验。
固定随机种子
始终使用固定的随机种子来确保当你运行代码两次时,还可以获得相同的结果。这种方法可以消除差异因素的影响。
简化
不要野心太大加入过多数据, 这个阶段一定要关闭其他数据库的扩充,在我们以后的正规训练中可能会尝试扩充数据, 但现在加入无疑是给自己找麻烦。
在评估中添加有效数字
当你在整个大的测试集进行评估并出现失败时, 不要继续进行批量的测试然后指望在Tensorboard进行平滑处理。我们需要追求准确,但也需要在适当的时候保持理智的放弃。
验证损失函数
用正确的损失值来验证损失函数, 例如,如果要保证初始化最后一层的正确, 你需要在softmax初始化时测试log(1/n_classes), 相同的默认值可以是L2 回归、Huber losses等。
初始化正确
确定初始化最终图层权重正确。例如,如果你回归一些平均值为50的值,则将最终偏差初始化为50。如果你有一个比例为1:10的不平衡数据集:正数:负数,请设置对数的偏差,以便你的网络预测概率在初始化时为0.1。在最初的几次迭代中,你的网络只是基本地学习偏差,正确设置这些将加速收敛并消除“曲棍球棒”损失曲线。
人为设置基准
监控除人为可解释和可检查的损失之外的指标,例如准确性。尽可能评估你自己的准确性并与之进行比较。或者,对测试数据进行两次注解,将一个视为预测,将第二个作为基础事实。
输入-独立基准
训练一个输入-独立的基准,最简单的方法是将所有输入设置为零。如果不清零,当你插入数据时就变得很糟糕,因为你的模型可能会从输入中提取信息。
先过拟合一部分数据
我们可以增加模型的容量(例如添加层或过滤器)以验证我们可以达到可实现的最低损失(例如零)。然后可以在同一个图中同时显示标签和预测,并确保一旦达到最小损失,它们就会完美对齐。如果没有对齐,那么就意味着哪里有一个错误,我们将无法进入下一个阶段。
自我验证
在使用玩具模型的阶段,数据集和你的模型越不合适越好。尝试稍微增加容量,然后看看你的训练损失是否随之下降了。
提前可视化数据
在运行y_hat = model(x)或sess.run在tf指令之前,最好先明确数据的位置,也就是说可视化网络中的内容,将原始的大量数据和标签可视化。使它成为唯一的事实来源”。这个步骤无数次地节省了我的时间,并且为我揭示了数据预处理和扩充中的问题。
可视化预测动态
我喜欢在训练模型过程中对固定测试批次上的模型预测进行可视化。这些预测的“动态”可以让你直观地了解到模型训练的进展情况。如果你看到网络剧烈摆动,显示出不稳定性,那就可能是你选择的模型不适合这套数据。学习率非常低或非常高地情况下抖动量也会很明显。
使用反向传播来绘制依赖关系
深度学习代码通常包含复杂的,矢量化的和工作量巨大的操作。一个相对常见的错误是人们弄错指令(例如在应该使用transpose permute到地方使用了view并且无意中在不同维度上混合信息。
令人沮丧的是,机器学习模型仍然可以正常训练,因为它会学习忽略其他示例中的数据。调试此问题(以及其他相关问题)的一种方法是将一个案例的的缺失值设定为1.0,然后反向传递一直运行到输入,确保在这个案例到其他案例上获得一个非零梯度值。梯度值可以提供网络中关键内容的信息,这对调试很有用。
使用特例
编写特例是一个通用的编码技巧,但我经常看到人们写下一个非常复杂的例子。我建议先从相对一般的功能开始。我喜欢为我现在正在做的事情编写一个非常具体的函数,让它运行,之后概括它得出的的结果。这非常适用于矢量化代码,我一般都是先写出一个完全循环的版本,然后一次一个循环地将它转换为矢量化代码。
3.过拟合
到这个阶段,我们应该对数据集有了很好的理解,同时我们必须保证我们的模型能够满足训练与验证结果的要求。对于任意模型,我们能够计算得出一个我们足以信任的指标。同时,我们也对我们的模型性能提出不基于输入的性能指标(模型性能不应受输入影响),我们模型的性能应当胜过傻瓜模型(比如随机分类)的性能,我们也应当对于人工的性能有一定的了解(我们希望我们的模型能够达到人类智能的层次)。到了处理拟合的这个阶段,我们将对模型进行迭代,从而提高模型的质量。
我用来寻找高质量模型的方法就是两步:首先找一个过拟合的模型(比如说,过拟合的判定标准可以是训练损失),然后我对这个模型进行规范化(regularize)处理从而使这个过拟合的模型变成一个高质量的模型(以降低部分训练损失的代价提高验证损失的质量——即以提高一点训练误差的代价降低较多的验证误差)。我喜欢用两步法的原因很简单,如果我们不能在第一步中根本不能使用任何模型取得较低的误差,这意味着我们的机器学习存在着一些问题,或者bug,或者错误配置。
这一步的一些提示与技巧:
- 挑选模型。为了取得较好的训练损失(较低的训练误差),你应当根据数据选取合适的网络结构。在选择模型时,我的第一条建议是:不要想着一口吃能胖子。我看到了好多人,如同堆乐高玩具一般,使用神经网络工具箱疯狂调整网络结构,狂热地妄想着创造诡异的神经网络,一步取得高质量的模型。在你项目的初期阶段,千万要抑制自己产生这样的想法。我经常建议人们就简简单单地查查和自己项目相关的论文,然后把他们模型的简化版应用在自己的项目来取得较好的性能。比如说,你想对图片分类,那就别先急着创建自己的神经网络,简简单单把ResNet-50抄过来试一试。在这之后,你就可以在这个网络上做一些自己的调整,并且用调整后的新网络,告诉ResNet-50谁才是真的爸爸。
- Adam优化是靠谱的。在最初的训练阶段,我会使用学习速率为3e-4的Adam作为网络参数的优化/迭代方法。对于ConvNets(卷积神经网络)而言,精准调参的随机梯度下降(Stochastic Gradient Descent)会比Adam拥有更好地性能,但是最优学习速率的区间会更窄,对于不同的问题会有不同的最优学习速率区间。(注意:如果你在使用递归神经网络或者相关的序列模型,Adam的使用就更加广泛了。在你项目的初期,我再强调一次,不要想着一步登天,跟着论文依样画瓢。)
- 提高模型复杂程度时,每次只改动一处。如果你有多个想要加入到分类器里的东西,我建议你依次加入它们(比如先加入dropout再加入batch normalization),来保证你得到你期待的性能。不要把所有东西都一股脑地一下塞到自己的模型里。先用较少的数据集进行训练,然后再加入更多的数据集,循序渐进,一步步提高模型的性能。
- 不要相信默认的学习速率衰减率。如果你将原有的训练代码运用在一些新的领域时,你应该对学习速率衰减率万分警惕。对于不同的问题,你不仅仅应该用不同的学习速率衰减方法,更应该注意的是,对于特定的问题,衰减速率应当基于当前的epoch数(epoch number),这会基于你当前数据集的尺寸。比如说,ImageNet的学习速率在第30个epoch的时候,会减少10。如果你不在训练ImageNet,那你最好就不要那么做。如果你改代码调参的时候不小心,然后让你模型参数的学习速率下降过快,你的模型参数可能会不收敛。在我自己的工作中,我会完全地取消学习速率的衰减率(我就用常值学习速率),然后在最后的最后来调节学习速率衰减率。
4.正则化
理想情况下,我们现在已经拥有一个有效的模型,至少对于训练集来说是有效的。现在是时候放弃一些训练精度,使它更规范并且具有更高的测算精度了。下面是一些提示和技巧:
- 获取更多数据。首先,目前为止,在任何实际环境中规范模型的最佳和首选方法是添加更多真实的训练数据。一个很常见的错误是,当你可以收集更多的数据时,你却绞尽脑汁花大量的工程周期在一个小数据集中提高效率。据我所知,添加更多的数据几乎是唯一能够保证提高配置良好的神经网络性能的方法。另一种方法则是集成学习器(如果你能负担得起的话),但它只有在集成5个个体学习器以上才能展现比较好的效果。
- 数据扩增。除了真正数据,你还可以使用半真半假的数据-尝试更具有挑战性的数据增强。
- 创意性地造数据。如果半真半假的数据不起作用,那么可以尝试假数据。人们正在寻找扩展数据集的创造性方法;例如,域随机化、模拟、甚至是GAN。
- 预训练。即使你有足够的数据,如果可以的话,建议使用一个经过预先训练的网络。
- 坚持有监督学习。不要太执着于无监督的预先训练。据我所知,与2008年的博客文章所告诉你的不同,目前为止还没有任何一个现代计算机视觉领域的无监督学习网络呈现了好的效果(尽管近几天BERT模型表现不俗,但这很可能是由于文本的谨慎性和更高的信噪比)。
- 减小输入维度。删除可能包含虚假信号的输入。如果数据集很小,任何的伪输入都将是一个过拟合的机会。同样,如果低层次的细节不太重要,尝试输入较小的图像。
- 缩减模型大小。在许多情况下,你可以使用域知识来约束并缩减模型大小。例如,过去流行在ImageNet的主干网顶部使用完全连接层,但是这些层后来被简单的平均池化所取代,这个过程消除了大量的参数使用。
- 减小batch大小。由于batch内部的规范化,较小的batch在一定程度上对应着较强的规范化。这是因为相对于完全平均值-标准差比,batch的经验平均值-标准差比更加有效,所以比例和偏移对于你的batch影响更大
- 防止过拟合。添加dropout。对ConvNets使用dropout2d(一种dropout方法)。当然,请谨慎地使用,因为dropout似乎不能很好地处理批处理规范化。
- 权重衰减。增加权重衰减惩罚力度。
- 及时停止训练。基于已测量验证的损失,及时停止训练,防止模型过拟合。
- 尝试大一点的模型。我在最后,并且是在“及时停止”之后提到这一点,是因为我在过去发现过几次,更大的模型最终会有更大程度的过拟合,但它们的“及时停止”性能往往比较小的模型好得多。
最后,为了让你更确信自己的神经网络已经是一个合理的分类器了,我建议你可视化网络的第一层权重,并确保你的结果是有意义的。如果你的第一层过滤器看起来像噪音,那么也许哪里是有问题的。同样,网络内的激活函数有时会显示奇怪的效应,你可以利用这些信息去追踪问题所在。
5.开始调参
你现在应该把你的数据集放在“循环迭代中”,为模型探索更宽泛的空间,以实现低验证成本的体系结构。以下是关于这一步的一些提示和技巧:
- 随机网格搜索。为了同时调整多个超参数,使用网格搜索来确保能够覆盖所有设置参数,这显然听起来很诱人,但请记住,最好使用随机搜索。直观地说,这是因为神经网络通常对某些参数比其他参数更敏感。在极限情况下,如果一个参数很重要但是改变参数b并没有效果。那么你应该多次采样,因为这比简单采样几个固定点更好。
- 超级参数优化。目前有很多贝叶斯超参数优化工具箱可以供我们参考使用,当然,我的一些朋友也有成功使用他们的案例,但我的个人经验是,探索一个非常好的、应用广泛的模型以及高级别的训练方法是使用实习生:)。哈哈哈…只是开玩笑。
6.再“榨”点东西出来
一旦你找到体系结构和超参数的最佳方法,你仍然可以使用一些技巧,从系统中提炼出一些精髓和方法:
- 集合/合并。模型集合是一种非常有保证非常靠谱的方法,可以在任何事情上提升2%的精度。如果你在测试时无法承受计算的成本,请考虑使用“黑匣子”进行整体提升。
- 自行训练。把模型放在一边然后让它自己一直训练。有些人在模型的验证损失几乎趋于平稳时就直接停止了训练。这是不对的,根据我的经验,模型的网络可以长时间不间断地进行训练并不断优化提升。有一次我在寒假期间,在训练模型的时候不小心离开了,然后模型自己一直训练,当我1月份回来时,它是SOTA(达到了最好的状态)
结论
一旦你做到了以上所有这些,你会对技术,数据集和问题有更加深刻的理解,因为你已经建立了整个神经网络训练的逻辑,并了解了提高准确性的信心与把握,而且你已经探索了越来越复杂的模型,模型可以每一步都能按照你预测的方法途径进行训练优化并且得到相应的进步。
现在你可以阅读大量的论文,尝试大量实验,并获得你的SOTA结果。
祝好运!
相关报道:
http://karpathy.github.io/2019/04/25/recipe/