大数据文摘出品
编译:周素云、宋欣仪、熊琰、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个参数,所以输入失败。对此我们通常可以为特定功能创建一个单元测试。
这只是训练神经网络的一个开始。但可能出现所有语法正确,整个事情就是不对的情况,而且很难说清楚哪里不对。
“可能的错误“的覆盖面非常大,而且是逻辑性的(与语法相反),这很难通过单元测试判断出来。例如,在数据增强期间需要左右翻转图像时,你可能忘记翻转标签。你的网络仍然可以继续工作得非常好,因为它可以在内部学习检测翻转的图像,然后左右翻转其预测。
这之后,或许你的自回归模型会因为一个错误的错误而将它想要预测的东西作为输入。或者你希望裁剪你的梯度但是模型裁剪了缺失值,导致模型忽略异常值。或者你会从预训练检查点初始化权重,但没有使用原始均值。或者你只是搞砸了正则化强度,学习率,衰减率,模型大小等设置。因此,错误配置的神经网络只有在你运气好的时候才会让你发现异常,大部分时间它会自己训练,默默工作,然后越来越糟糕。
过犹不及,训练神经网络的“快速和大强度”的方法不起作用,只能带来一系列麻烦,这在过去是对的。但现在,这些麻烦可以成为让神经网络运作良好的一个部分,主要通过可视化来达到。深度学习要想成功,最需要的品质是耐心和对细节的关注。
基于上文讲述的两个问题,我为自己开发了一套神经网络训练”套路”。本文中我将尝试描述这个套路。这个套路非常重视上述两个原则,并且从简到繁,在每一步都对将要发生的事情做出具体假设,然后通过实验验证或进行检查,直到问题出现。
避免一次性地引入多个“未经验证的”复杂因素,这会导致你长时间的查找错误配置(如果有的话)。如果编写神经网络代码就像训练一样,最好控制学习速率,作出猜测,然后在每次迭代后评估完整的测试集。
训练神经网络的第一步不是研究神经网络代码,而是从彻底检查数据开始。这一步至关重要。我喜欢花费大量时间(以小时为单位)浏览数千个示例,了解它们的分布并寻找规律。幸运的是,我们的大脑非常擅长这一点。
有一次我发现数据集中包含重复的例子,还有一次我发现了损坏的图像/标签。我会尝试寻找数据的不平衡和偏见。我通常也会关注我自己的数据分类过程,从中可以看到我们最终要探索的各种架构。
这个数据集的背景是什么?有多少变化,它采取什么形式?什么变化是假的,可以预处理?空间位置是否重要,或者我们是否想要将其平均化?细节有多重要,我们可以在多大程度上对图像进行缩减采样?标签有多少?
此外,由于神经网络实际上是数据集的压缩/编译版本,因此你将能够查看网络(错误)预测并了解它们的来源。如果你的网络给你的预测看起来与你在数据中看到的内容不一致,那么就会有所收获。
一旦获得定性意义,编写一些简单的代码来搜索/过滤/排序也是一个好主意(例如标签的类型,注释的大小,注释的数量等),你可以可视化它们的分布,发现沿任何轴的异常值,注意,异常值几乎总能揭示数据质量或预处理中的一些错误。
当你了解数据就可以利用多尺度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,或者错误配置。
这一步的一些提示与技巧:
4.正则化
理想情况下,我们现在已经拥有一个有效的模型,至少对于训练集来说是有效的。现在是时候放弃一些训练精度,使它更规范并且具有更高的测算精度了。下面是一些提示和技巧:
最后,为了让你更确信自己的神经网络已经是一个合理的分类器了,我建议你可视化网络的第一层权重,并确保你的结果是有意义的。如果你的第一层过滤器看起来像噪音,那么也许哪里是有问题的。同样,网络内的激活函数有时会显示奇怪的效应,你可以利用这些信息去追踪问题所在。
你现在应该把你的数据集放在“循环迭代中”,为模型探索更宽泛的空间,以实现低验证成本的体系结构。以下是关于这一步的一些提示和技巧:
一旦你找到体系结构和超参数的最佳方法,你仍然可以使用一些技巧,从系统中提炼出一些精髓和方法:
一旦你做到了以上所有这些,你会对技术,数据集和问题有更加深刻的理解,因为你已经建立了整个神经网络训练的逻辑,并了解了提高准确性的信心与把握,而且你已经探索了越来越复杂的模型,模型可以每一步都能按照你预测的方法途径进行训练优化并且得到相应的进步。
现在你可以阅读大量的论文,尝试大量实验,并获得你的SOTA结果。
祝好运!
相关报道:
http://karpathy.github.io/2019/04/25/recipe/