🚧 为初学者构建高级神经网络的简单注意事项

发布于 2021 年 2 月 25 日
在 GitHub 上更新

Builders

照片由 Henry & Co. 拍摄于 Unsplash

随着机器学习不断渗透到行业的各个方面,神经网络从未如此被热议。例如,像 GPT-3 这样的模型在过去几周内席卷了社交媒体,并继续以耸人听闻的标题出现在科技新闻之外的媒体头条。

Builders

《卫报》的一篇文章

与此同时,深度学习框架、工具和专门的库通过使最先进的研究比以往任何时候都更容易使用,从而推动了机器学习研究的普及。我们经常看到那些几乎神奇/即插即用的 5 行代码,它们承诺(接近)最先进的结果。作为 Hugging Face 🤗 的一员,我承认我对此负有部分责任。😅 这可能会给没有经验的用户带来误导,让他们以为神经网络现在是一项成熟的技术,而实际上,这个领域仍在不断发展中。

实际上,构建和训练神经网络通常是一种极其令人沮丧的体验

  • 有时很难判断你的性能不佳是由于模型/代码中的错误,还是仅仅受限于模型的表达能力。
  • 在流程的每一步,你都可能犯下无数微小的错误,而起初并未察觉,但你的模型仍然会训练并给出不错的性能。

在这篇文章中,我将尝试重点介绍我在构建和调试神经网络时的一些心路历程。 我所说的“调试”,是指确保你构建的东西和你脑海中的想法是一致的。我还会指出一些在你不知道下一步该怎么做时可以查看的东西,并列出我通常会问自己的问题。

这些想法很多源于我在自然语言处理领域的研究经验,但其中大部分原则也适用于机器学习的其他领域。

1. 🙈 从搁置机器学习开始

这可能听起来有悖常理,但构建神经网络的第一步是将机器学习放在一边,专注于你的数据。查看样本、它们的标签、如果你处理的是文本,则看看词汇的多样性、长度分布等等。你应该深入研究数据,初步了解你正在处理的原始产品,并专注于提取模型可能能够捕捉到的通用模式。希望通过查看几百个样本,你将能够识别出高层次的模式。你可以问自己一些标准问题:

  • 标签是否均衡?
  • 是否有你不同意的黄金标签?
  • 数据是如何获得的?这个过程中可能有哪些噪声来源?
  • 有没有看起来很自然的预处理步骤(如分词、移除 URL 或话题标签等)?
  • 样本的多样性如何?
  • 什么样的基于规则的算法可以在这个问题上表现得不错?

对你的数据集有一个宏观的(定性的)感觉和微观的(定量的)分析同样重要。如果你正在使用一个公开的数据集,可能已经有人深入研究过数据并报告了他们的分析(例如,这在 Kaggle 竞赛中很常见),所以你绝对应该去看看这些分析!

2. 📚 像机器学习新手一样继续

一旦你对数据有了深入而广泛的了解,我总是建议把自己想象成刚开始学习机器学习时的样子,那时你还在 Coursera 上看 Andrew Ng 的入门课程。从最简单的方法开始,以了解你的任务的难度以及标准基线模型的表现如何。 例如,如果你处理文本,二元文本分类的标准基线可以包括一个在 word2vec 或 fastText 嵌入之上训练的逻辑回归。使用当前的工具,运行这些基线与运行 BERT 一样容易(如果不是更容易的话),而 BERT 可以说被认为是许多自然语言处理问题的标准工具之一。如果还有其他基线可用,运行(或实现)其中一些。这将帮助你更加熟悉数据。

作为开发者,我们很容易在构建一些花哨的东西时感到满足,但如果它只比简单的基线高出几个百分点,有时就很难从理性上证明其合理性,所以确保你有合理的比较点是至关重要的。

  • 随机预测器的表现会怎样(尤其是在分类问题中)?数据集可能是不平衡的……
  • 随机预测器的损失会是什么样子?
  • 衡量我的任务进展的最佳指标是什么?
  • 这个指标的局限性是什么?如果它完美无缺,我能得出什么结论?我不能得出什么结论?
  • “简单方法”中缺少了什么才能达到完美的分数?
  • 在我的神经网络工具箱中,是否有适合模拟数据归纳偏置的架构?

3. 🦸‍♀️ 不要害怕深入了解这些 5 行代码模板的背后

接下来,你可以根据之前获得的见解和理解开始构建你的模型。如前所述,实现神经网络很快就会变得相当棘手:有很多相互配合的活动部件(优化器、模型、输入处理管道等),在实现这些部件并将它们相互连接时,很多小事情都可能出错。挑战在于,你可能会犯这些错误,训练一个模型而它永远不会崩溃,并且仍然能得到不错的性能……

然而,当你认为你已经完成了实现时,一个好习惯是对一小批样本进行过拟合(例如 16 个)。如果你的实现是(基本)正确的,你的模型将能够过拟合并记住这些样本,显示出 0 损失(确保你移除了任何形式的正则化,如权重衰减)。如果不能,很可能你在实现中做错了什么。在少数情况下,这意味着你的模型表达能力不足或容量不够。再次强调,从一个小规模的模型开始(例如更少的层):你的目标是调试模型,所以你需要快速的反馈循环,而不是高性能。

专家提示:根据我使用预训练语言模型的经验,将嵌入模块冻结为其预训练值对微调任务的性能影响不大,但可以显著加快训练速度。

一些常见的错误包括:

  • 错误的索引……(这些真的是最糟糕的 😅)。例如,确保你在正确的维度上收集张量……
  • 在评估模式下忘记调用 `model.eval()`(在 PyTorch 中)或 `model.zero_grad()` 来清除梯度
  • 输入预处理中出了问题
  • 损失函数收到了错误的参数(例如,在需要 logits 时传递了概率)
  • 初始化没有打破对称性(通常发生在你用单个常量值初始化整个矩阵时)
  • 一些参数在前向传播过程中从未被调用(因此没有收到梯度)
  • 学习率一直取到像 0 这样的奇怪值
  • 你的输入被以次优的方式截断了

专家提示:当你处理语言时,要认真检查分词器的输出。我数不清有多少个小时是因为分词出了问题,而我却在努力复现结果(有时是我自己以前的结果)上浪费了。🤦‍♂️

另一个有用的工具是深入研究训练动态并在(例如 Tensorboard 中)绘制多个标量在训练过程中的演变。至少,你应该关注你的损失、参数及其梯度的动态。

随着损失的减少,你还想查看模型的预测:要么在你的开发集上进行评估,要么,我个人最喜欢的是,打印出几个模型的输出。例如,如果你正在训练一个机器翻译模型,看到生成结果在训练过程中变得越来越令人信服是相当令人满意的。你需要特别注意过拟合:你的训练损失持续下降,而你的评估损失却在飞速上升。💫

4. 👀 调优但不要盲目调优

一旦你的一切都准备就绪,你可能想调整你的超参数,为你的设置找到最佳配置。我通常坚持使用随机网格搜索,因为事实证明它在实践中相当有效。

有些人报告说使用像贝叶斯优化这样的高级超参数调整方法取得了成功,但根据我的经验,在一个合理的手动定义网格上进行随机搜索仍然是一个难以超越的基线。

最重要的是,用不同的超参数(或像激活函数这样的架构调整)启动 1000 次运行是没有意义的:比较几次不同超参数的运行,以了解哪些超参数影响最大,但总的来说,期望通过简单地调整几个值来获得最大的性能提升是不切实际的。例如,如果你性能最好的模型是用 4e2 的学习率训练的,那么你的神经网络内部可能发生了更根本的事情,你需要识别和理解这种行为,以便在当前特定情境之外重用这些知识。

平均而言,专家们使用更少的资源来找到更好的解决方案。

总而言之,一个帮助我更好地构建神经网络的通用建议是,尽可能地深入理解神经网络的每个组件,而不是盲目地(更不用说神奇地)调整架构。保持简单,避免那些即使经过努力尝试也无法合理解释的小调整。显然,在“试错法”和“分析法”之间需要找到适当的平衡,但随着你积累实践经验,很多这些直觉会变得更加自然。你也在训练你自己的内部模型。 🤯

一些相关的阅读资料以补充你的阅读

社区

注册登录 以发表评论