A Recipe for Training Neural Networks [中文翻译, part 1]

最近拜读大神Karpathy的经验之谈 A Recipe for Training Neural Networks  https://karpathy.github.io/2019/04/25/recipe/,这个秘籍对不少深度学习算法训练过程当中遇到的各自问题进行了总结,并提出了不少很好的建议,翻译于此,但愿可以帮助更多人学到这些内容。python

译文以下:git

几周前,我发布了一条关于“最多见的神经网络错误”的推文,列出了一些与训练神经网络相关的常见问题。这条推文获得了比我预期的要多得多的认同(包括网络研讨会:))。显然,不少人我的遇到了“卷积层是这样工做的”和“咱们的卷积网络达到最早进结果”之间的巨大差距。github

因此我认为若是清空我尘土飞扬的博客并将这个推文扩展到这个主题应该获得的长篇形式应该是有趣的事情。然而,与其列举常见的错误或深刻分析它们,我更想深刻挖掘并讨论如何避免出现这些错误(或者很是快速地修复它们)。这样作的关键是遵循某个过程,据我所知,这个过程并无文档记录下来。让咱们从促使我作这个的两个重要发现开始吧。算法

1. 神经网络训练的困难是一种假象

据称很容易开始训练神经网络。许多图书和框架都以展现了若是采用30行代码解决您的数据问题,并以此而自豪。这给你们一个很是错误的印象,即这些东西是即插即用。常见的示例代码以下:api

>>> your_data = # 导入数据
>>> model = SuperCrossValidator(SuperDuper.fit, your_data, ResNet50, SGDOptimizer)
# 由此征服世界这些

这些库和示例激活了咱们大脑里面熟悉标准软件的部分 - 一般它们认为干净和抽象的API是能够轻易得到的。 好比,后面的功能能够采用以下调用:网络

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

  这很酷! 一个勇敢的开发人员已经承担了理解查询字符串,URL,GET / POST请求,HTTP链接等等的负担,而且在很大程度上隐藏了几行代码背后的复杂性。 这偏偏是咱们熟悉和期待的。 不幸的是,神经网不是那样的。 它们不是“现成的”技术,第二个与训练ImageNet分类器略有不一样。 我试图在个人帖子“是的你应该理解反向传播”中经过反向传播的例子说明这一点,并将其称为“漏洞抽象”,但不幸的是状况更加可怕。 Backprop + SGD并无神奇地让你的网络正常工做。 Batch Norm也不会神奇地使其收敛得更快。RNN不会神奇地让你“插入”文本。 只是由于你能够将你的问题采用加强学习来建模,并不意味着你应该如此。 若是您坚持使用该技术而不了解其工做原理,则可能会失败。 这让我想到......架构

2. 神经网络训练无声地失败

当您写错或错误配置代码时,您一般会遇到某种异常。你输入了一个整数,但这原本应该是一个字符串的!某函数只须要3个参数!导入失败!键值不存在!两个列表中的元素个数不相等。此外,一般不会为特定功能建立单元测试。框架

解决了这些问题,也只是训练神经网络的开始。一切均可以在语法上正确,但整个训练没有妥善安排,并且很难说清楚。 “可能的错误面”是很大的,逻辑(与语法相反)层面的问题,而且对单元测试很是棘手。例如,在数据加强期间左右翻转图像时,您可能忘记翻转数据标签。您的网络仍然能够(使人震惊地)工做得很是好,由于您的网络能够在内部学习检测翻转的图像,而后左右翻转其预测。或许你的自回归模型会由于一个偶然错误而意外地将它想要预测的东西做为输入。或者你想要修剪你的梯度可是修剪了损失,致使在训练期间异常样本被忽略。或者您从预训练模型初始化了您的权重,但没有使用原始均值。或者你只是用错了正则化权重,学习率,衰减率,模型大小等。所以,错误设置的神经网络只有在你运气好的时候才会抛出异常;大部分时间它会训练,但默默地输出看起来有点糟糕的结果。函数

所以,“快速和激烈”方法训练的神经网络并不能发挥其做用,咱们只会遭受其痛苦。 如今,痛苦是让神经网络运做良好的一个很是天然的过程,但能够经过对训练过程当中的全部细节了然于胸来减轻训练过程的折磨。 在个人经验中,与深度学习成功最相关的品质是耐心和对细节的关注。单元测试

秘方

鉴于上述两个事实,我已经为本身开发了一个特定的过程,使我可以将神经网络应用于新问题。稍后我会竭力描述如何作到的。 你会发现它很是重视上述两个原则。 特别是,它遵循从简单到复杂的规律,而且在每一步咱们对将要发生的事情作出具体假设,而后经过实验验证它们或进行检查直到咱们发现了问题。 咱们试图防止的是同时引入了许多复杂“未经验证的”问题,这必然会致使须要花不少时间去查找的错误/错误配置。 若是编写您的神经网络代码就像训练同样,您须要使用很是小的学习速率并猜想,而后在每次迭代后评估整个的测试集。

1.了解你的数据

训练神经网络的第一步是根本不是接触任何神经网络代码,而是从完全检查数据开始。这一步相当重要。我喜欢花费大量时间(以小时为单位)扫描数千个示例,了解它们的分布并寻找模式。幸运的是,你的大脑很是擅长这一点。有一次我发现了数据中包含重复的例子。另外一次我发现了错误的图像/标签对。我一般会寻找不均衡的数据,也会关注本身对数据的分类过程,这些过程暗示了咱们最终会尝试的各类架构。例如 -咱们须要局部特征仍是全局上下文?数据有多少变化,这些变化采起什么形式?什么变化是假的,是能够预处理的?空间位置是否重要,或者咱们是否想要将其平均化?细节有多重要,咱们能够在多大程度上对图像进行下采样?标签有多少噪声?

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

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

2.设置端到端的训练/评估框架+得到基准

当咱们了解了数据以后,咱们能够采用咱们超级精彩的多尺度ASPP FPN ResNet训练牛X的模型吗? 答案是不。 这是一条充满痛苦的道路。 咱们的下一步是创建一个完整的训练+评估框架,并经过一系列实验验证其正确性。在这个阶段,最好选择一些你能正确使用的简单模型 - 例如 线性分类器,或很是小的ConvNet。 咱们但愿对其进行训练,可视化损失,任何其余指标(例如准确度),模型预测,并在此过程当中使用明确的假设进行一系列实验。

这个阶段的须要注意的地方和建议主要包括:

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