面向自然语言处理的迁移学习(三)
原文:
zh.annas-archive.org/md5/da86c0033427bb250532db6d61561179译者:飞龙
第九章:ULMFiT 和知识蒸馏适应策略
本章包括
-
实施判别微调和逐步解冻等策略。
-
在教师和学生BERT 模型之间执行知识蒸馏
在本章和下一章中,我们将介绍迄今为止已涵盖的深度NLP迁移学习建模架构的一些适应策略。换句话说,鉴于预训练架构如 ELMo、BERT 或 GPT,我们如何更有效地进行迁移学习?我们可以在这里采取几种效率措施。我们选择关注参数效率,即目标是在减少性能的同时产生尽可能少的参数模型。这样做的目的是使模型更小、更容易存储,从而更容易在智能手机设备上部署。另外,智能适应策略可能需要在某些困难的迁移情况下达到可接受的性能水平。
在第六章中,我们描述了 ULMFiT¹方法,即通用语言模型微调。该方法引入了判别微调和逐步解冻的概念。简而言之,逐步解冻逐渐增加网络中解冻或微调的子层的数量。另一方面,判别微调为网络中的每一层指定了可变的学习率,从而实现更有效的迁移。我们在第六章的代码中没有实施这些方法,因为作为适应策略,我们认为它们最适合放在本章中。在本章中,我们使用 ULMFiT 作者编写的fast.ai库来演示预训练的循环神经网络(RNN)语言模型的概念。
一些模型压缩方法通常被应用于大型神经网络以减小其大小。一些著名的方法包括权重修剪和量化。在这里,我们将重点关注适应策略,即NLP领域最近备受关注的知识蒸馏。该过程本质上试图使用显著较小的学生模型模拟来自较大的教师模型的输出。特别是,我们使用变压器库中的 DistilBERT²方法的实现来演示通过这种方法可以将 BERT 的大小减半以上。
让我们从下一节开始 ULMFiT。
9.1 逐步解冻和判别微调
在本节中,我们将在代码中实现 ULMFiT 方法,将语言模型适应于新的数据领域和任务。我们首先在第六章的最后讨论了这种方法的概念,因为从历史上看,它首先是在递归神经网络(RNNs)的背景下引入的。然而,我们将实际的编码练习推迟到现在,以强调在其核心,ULMFiT 是一组与架构无关的适应技术。这意味着它们也可以应用于基于 transformer 的模型。然而,为了与源材料保持一致,我们在 RNN-based 语言模型的背景下进行练习编码。我们将编码练习集中在我们在第六章中看到的假新闻检测示例上。
作为提醒,辨别微调指定网络中每一层的可变学习率。此外,学习率在学习过程中不是恒定的。相反,它们是倾斜三角形的——在开始时线性增加到一定程度,然后线性衰减。换句话说,这意味着快速增加学习率,直到达到最大速率,然后以较慢的速度减小。这个概念在图 6.8 中有所说明,我们在这里为了您的方便重复了它。
图 6.8(从第六章复制)建议的倾斜三角形 ULMFiT 学习率时间表,对于总迭代次数为 10,000 的情况。学习率在总迭代次数的 10%(即 1,000)上线性增加,最高达 0.01,然后线性减少到 0。
请注意,图中标有“最大学习率”的点在我们的情况下会有所不同(不是 0.01)。迭代的总数也将与图中显示的 10,000 次不同。这个时间表会产生更有效的转移和更具一般性的模型。
渐进解冻,另一方面,逐渐增加网络的子层的数量解冻,这样可以减少过拟合,同时也会产生更有效的转移和更具一般性的模型。所有这些技术在第六章的最后一节中都有详细讨论,可能在着手本节其余部分之前,简要回顾该讨论会有益处。
我们将在这里使用第 5.2 节的说明性示例——事实核查示例。回想一下,这个数据集包含超过 40,000 篇文章,分为两类:“假”和“真”。真实文章是从 reuters.com,一个声誉良好的新闻网站收集来的。另一方面,假文章则是从 PolitiFact 标记为不可靠的各种来源收集来的。在第 6.2 节,我们在预训练的 ELMo 模型导出的特征向量上训练了一个二元分类器。这个分类器预测一篇给定的文章是真实的(1)还是假的(0)。使用由每个类别的 1,000 篇文章组成的数据集,获得了 98%+ 的准确率。在这里,我们将看看是否可以通过 ULMFiT 方法取得更好的效果。
在本节中,我们将该方法分为两个小节。第一个小节涉及在目标任务数据上微调预训练语言模型的第一阶段 ULMFiT。斜三角形学习率在这里发挥作用,以及分层微调的概念。一些数据预处理和模型架构讨论也自然地融入到这个第一个小节中。第二个小节涵盖了第二阶段,涉及在目标任务数据上微调目标任务分类器——它位于微调语言模型之上——的阶段。逐步解冻程序的有效性由此得到证明。
请注意,本节中呈现的代码采用 fast.ai 版本 1 语法编写。选择这样做的原因是该库的第 2 版更改了输入数据的处理方式,提供了将其分割为训练集和验证集的内部函数,而不是允许您自己指定。为了与我们在前几章中的工作保持一致,在那里我们自己分割了数据,我们在这里坚持使用版本 1。我们还在 Kaggle 笔记本中提供了等效的 fast.ai 版本 2 语法代码³,您应该运行并与此处呈现的版本 1 代码进行比较。最后,请注意,版本 1 的文档托管在 fastai1.fast.ai/,而版本 2 的文档托管在 docs.fast.ai/。
9.1.1 预训练语言模型微调
第 5.2 节已经描述了我们需要对事实核查示例数据集进行的初始数据预处理步骤。特别地,我们对文章文本数据进行了洗牌,并将其加载到 NumPy 数组train_x和test_x中。我们还构建了相应的标签 NumPy 数组train_y和test_y,其中包含每篇文章是否为真实的信息,当文章为真时标记为 1,否则为 0。如同第 5.2 节一样,保持 1,000 个样本和测试/验证比例为 30%,得到的训练数组——train_x,train_y——长度为 1,400,测试数组——test_x,test_y——长度为 600。
我们需要做的第一件事是准备 fast.ai 库所期望的数据形式。其中一种数据格式是一个两列的 Pandas DataFrame,第一列包含标签,第二列包含数据。我们可以相应地构建训练和测试/验证数据框,如下所示:
train_df = pd.DataFrame(data=[train_y,train_x]).T
test_df = pd.DataFrame(data=[test_y,test_x]).T
这些数据框应该分别有 1,400 行和 600 行,每个都对应于相应数据样本中的每篇文章,并且在继续之前,最好用通常的.shape命令检查一下,如下所示:
train_df.shape
test_df.shape
预期输出分别为(1400, 2)和(600, 2)。
fast.ai 中的数据使用TextLMDataBunch类进行消耗,这些实例可以使用我们刚刚准备的 DataFrame 格式构建,使用以下命令:
data_lm = TextLMDataBunch.from_df(train_df = train_df, valid_df = test_df, path = "")
另一方面,fast.ai 中的数据由一个特定于任务的分类器使用TextClasDataBunch类进行消耗。我们构建此类的一个实例,准备进入下一小节,使用以下类似的命令从我们的数据框中:
data_clas = TextClasDataBunch.from_df(path = "", train_df = train_df, valid_df = test_df, vocab=data_lm.train_ds.vocab)
现在我们准备在目标数据上微调我们的语言模型!为此,我们需要使用以下命令创建language_model_learner fast.ai 类的一个实例:
learn = language_model_learner(data_lm, AWD_LSTM, drop_mult=0.3) ❶
❶ 使用 30%的权重丢失率初始化预训练的权重丢失 LSTM。这是在 WikiText-103 基准数据集上预训练的。
这里,AWD_LSTM代表ASGD 权重丢失 LSTM。⁴这只是通常的 LSTM 架构,其中一些权重已被随机丢弃,就像通常的 dropout 层对神经网络激活所做的那样,与权重相反。这是最类似于 fast.ai 库中原始 ULMFiT 论文中所做的架构选择。此外,如果您检查上一个命令的执行日志,您应该能够确认它还从在 WikiText-103 基准数据集上训练的检查点加载预训练权重。⁶这个数据集,官方称为“WikiText 长期依赖语言建模数据集”,是一组由人类判断为“好”的维基百科文章。这是一个很好的、干净的无监督数据来源,已被许多自然语言处理论文用于基准测试。
现在我们已经加载了一个模型实例和一些预训练权重,我们将尝试确定用于微调语言模型的最佳或最优学习率。fast.ai 中一个称为lr_find的巧妙实用方法可以自动为我们完成这项工作。它会迭代一系列学习率,并检测结果损失与学习率曲线上损失函数下降最快的点。等价地,这是损失梯度最小的地方。⁷我们可以使用我们的语言模型学习器learn快速进行如下操作:
learn.lr_find() ❶
learn.recorder.plot(suggestion=True) ❷
❶ 寻找最佳/最优学习率
❷ 绘制它
结果的损失与学习率曲线,突出显示了最佳率,如图 9.1 所示。
图 9.1 fast.ai 库用于语言模型微调步骤中的最佳学习率找寻过程的结果,用于虚假新闻检测示例。通过多次迭代不同的学习率,并选择在曲线上损失下降最快的点作为最佳学习率。
我们可以通过以下命令程序化地检索这个学习率,并显示它:
rate = learn.recorder.min_grad_lr ❶
print(rate) ❷
❶ 检索最佳率
❷ 显示它
在我们执行代码时,返回的最佳学习率约为 4.0e-2。
找到最佳学习率后,我们现在可以使用下面的命令对我们的预训练的权重丢弃 LSTM 模型进行微调,使用 fit_one_cycle fast.ai 命令如下所示:
learn.fit_one_cycle(1, rate) ❶
❶ 这个命令在底层使用了斜三角形学习率。它以 epochs 的数量和期望的最大学习率作为输入。
执行命令,在单个 Kaggle GPU 上进行大约 26 秒的微调,得到了 0.334 的准确度。
获得了基准值后,我们想要找出是否差异化微调能够带来改善。我们首先通过使用 unfreeze 命令解冻所有层,然后使用 slice 方法指定学习率范围的上限和下限。这个命令将最接近输出的层的最大学习率设置为上限,并通过除以一个常数因子几何地减少每个后续层的最大学习率到下限。下面展示了执行这个操作的确切代码:
learn.unfreeze() ❶
learn.fit_one_cycle(1, slice(rate/100,rate)) ❷
❶ 确保所有层都解冻以进行微调
❷ 在最终层中的最佳率和比该最佳率小两个数量级的值之间以几何方式变化
从代码可以看出,我们任意选择了将学习率从最大最优值变化到比该值小两个数量级的值。这个调度背后的直觉是,随后的层包含的信息更为一般化,与任务无关,因此它应该比最接近输出的层从这个特定目标数据集中学到的更少。
执行所提出的差异化微调代码,得到了一个准确度分数为 0.353,明显优于我们在没有使用它时得到的 0.334 的值。使用以下命令保存微调后的语言模型以供以后使用:
learn.save_encoder('fine-tuned_language_model')
通过斜三角形学习率和差异化微调调整了我们的预训练语言模型后,让我们看看我们能得到多好的目标任务分类器——也就是虚假新闻检测器。我们在下一小节对微调后的语言模型之上微调一个分类器。
9.1.2 目标任务分类器微调
请回想在前一小节中,我们创建了一个用于目标任务分类器的数据消费对象。我们将这个变量称为data_clas。作为微调我们的目标任务分类器的下一步,我们需要实例化一个分类器学习器的实例,方法恰当地命名为text_classifier_learner,在 fast.ai 中。下面的代码完成了这一步:
learn = text_classifier_learner(data_clas, AWD_LSTM, drop_mult=0.3) ❶
learn.load_encoder('fine-tuned_language_model') ❷
❶ 实例化目标任务分类器学习的一个实例。使用我们微调过的语言模型相同的设置,因此我们可以无问题地加载。
❷ 载入我们微调过的语言模型
作为下一步,我们再次使用实用的 fast.ai 方法lr_find来找到最佳学习率,使用以下代码:
learn.lr_find() ❶
learn.recorder.plot(suggestion=True) ❷
❶ 寻找最佳速率
❷ 绘制它
执行该代码得到的是图 9.2 中显示的损失与学习率曲线。
图 9.2 从 fast.ai 库获取目标任务分类器微调步骤中用于找到最佳学习率的结果的过程。通过几个学习率进行迭代,并选择最佳学习率,即在曲线上损失下降最快的点。
我们看到最佳速率约为 7e-4。我们使用倾斜三角形学习率,通过以下代码对分类器学习器进行一轮训练:
rate = learn.recorder.min_grad_lr ❶
learn.fit_one_cycle(1, rate) ❷
❶ 提取最佳的最大学习率
❷ 使用确定的最大学习率在倾斜三角形学习率计划中微调目标任务分类器
执行该代码得到的准确率约为 99.5%。这已经比我们在第六章(第 6.2 节)通过在 ELMo 嵌入之上训练分类器得到的 98%+的结果更好了。我们还能做些什么来进一步提高它呢?
幸运的是,我们还有一个底牌:渐进式解冻。再次提醒,这是当我们仅解冻一层,微调它,解冻一个额外的较低层,微调它,并重复此过程一定次数时。ULMFiT 的作者发现,在目标任务分类器阶段应用此方法显着改善了结果。举个简单的例子,要执行此过程直到 2 层深度,我们需要以下代码:
depth = 2 ❶
for i in range(1,depth+1): ❷
learn.freeze_to(-i) ❸
learn.fit_one_cycle(1, rate) ❹
❶ 我们仅执行渐进式解冻,直到解冻两个层为止。
❷ 逐渐解冻更多层,首先一个,然后两个,每次使用倾斜三角形学习率进行一轮训练
❸ 此命令解冻了顶部 i 层。
❹ 执行一次倾斜三角形学习率,如已经介绍的
请注意,命令 learn.freeze_to``(-i) 冻结前 i 层对于本次操作至关重要。在我们对虚假新闻检测示例上执行代码时,我们发现在第一步中准确性达到了 99.8%,当解冻了前两层时,准确性达到了惊人的 100%。这些结果充分说明了自己,似乎表明 ULMFiT 方法是一套非常有用的技术。请注意,如果有必要,我们可以继续解冻更深层次的层次——第 3 层,第 4 层等等。
奇妙的事情!看来在我们适应新场景时,聪明地调整模型可以带来显著的好处!在接下来的章节中,我们将介绍另一种实现这一点的方法——知识蒸馏。
9.2 知识蒸馏
知识蒸馏是一种神经网络压缩方法,旨在教授一个较小的学生模型大型教师模型所包含的知识。这种方法近年来在 NLP 社区中变得流行,本质上是试图通过学生来模仿教师的输出。此方法也与模型无关——教师和学生可以是基于变压器的、基于循环神经网络的或其他结构,并且彼此之间可以完全不同。
在 NLP 领域中,对此方法的最初应用是由于对双向 LSTM(bi-LSTMs)的表示能力与基于变压器的架构之间的比较的疑问。⁸ 作者想要知道单个 bi-LSTM 层是否能够捕捉到 BERT 的多少信息。令人惊讶的是,研究人员发现,在某些情况下,预训练的基于变压器的语言模型的参数数量可以减少 100 倍,推理时间可以减少 15 倍,同时不损失标准性能指标。这是一个巨大的尺寸和时间上的减少,可以决定这些方法是否可以实际部署!知识蒸馏的过程在图 9.3 中简要概述。
图 9.3 是知识蒸馏的一般过程的示意图。教师模型产生的“软”标签被用于通过蒸馏损失鼓励学生模型表现出类似的行为。同时,学生损失被训练成与通过学生损失的标准地面真实情况行为类似。
如图所示,传统上,教师产生的标签被用于计算“软”标签,通过与学生的输出进行比较来确定蒸馏损失。这种损失促使学生模型跟踪教师模型的输出。此外,学生还通过学生损失同时学习“硬”的真实标签。我们将通过 Hugging Face 的 transformers 库来快速展示如何使用这个想法实现。
已经提出了几种架构来减小预训练的 NLP 语言模型的尺寸,包括 TinyBERT ⁹ 和 DistilBERT. ¹⁰ 我们选择专注于 DistilBERT,因为它在 transformers 库中已经准备就绪。 DistilBERT 是由 Hugging Face 开发的,这是与编写 transformers 库相同的团队。 与以前一样,我们对这个主题的覆盖并不意味着是全面的,而是举例说明。 在像这样快速发展的领域中保持进一步开发和文献的更新仍然很重要。 我们希望这里所呈现的内容能让您做到这一点。
DistilBERT 研究的目标是特别生成 BERT 模型的较小版本。 学生架构被选择为与 BERT 相同-在第 7 和第八章中描述的堆叠变压器编码器。 学生的层数减少了一半,只有六层的模型。 这是大部分尺寸节省的地方。 作者发现在这种框架中,内部隐藏维度的变化对效率几乎没有影响,因此,在教师和学生之间都是相似的。 过程的一个重要部分是将学生初始化到适当的一组权重,从中收敛会相对较快。 因为教师和学生的所有层的尺寸都是相似的,作者可以简单地使用对应层中的预训练教师权重来初始化学生,并发现这样做效果良好。
作者对 GLUE 等基准进行了广泛的实验证明,我们将在下一章中看到,并在 SQuAD 上进行了实验证明。 他们发现,由结果产生的 DistilBERT 模型在 GLUE 基准上的性能保持了 BERT 教师模型的 97% ,但参数个数只有教师的 40%. 它在 CPU 上的推理时间也快了 60%,而在 iPhone 等移动设备上快了 71%。 如您所见,这是一项明显的改进。
执行实际蒸馏的脚本可在官方 transformers 存储库中找到。¹¹ 要训练自己的 DistilBERT 模型,你需要创建一个每行一个文本样本的文件,并执行该页面提供的一系列命令,这些命令准备数据并蒸馏模型。因为作者已经提供了各种检查点可供直接加载——所有检查点都列在了该页面上——而我们的重点是迁移学习,我们在这里不重复从头开始训练的步骤。相反,我们使用了一个类似于我们在第八章中用于跨语言迁移学习实验的 mBERT 检查点。这样可以直接比较使用蒸馏架构与原始 mBERT 的性能和好处,同时还教会你如何开始在自己的项目中使用这个架构。这也为你提供了另一个机会,即在自定义语料库上微调预训练的基于 transformer 的模型——直接修改具有不同架构、预训练检查点和自定义数据集的代码应该适用于你自己的用例。
更具体地说,我们将重复我们在第 8.2.2 节中进行的实验,即通过在来自 JW300 数据集的语料库上进行微调,将 mBERT 中包含的知识转移到单语 Twi 场景中。我们执行使用检查点中包含的预训练 tokenizer 的实验变体,而不是从头开始训练一个新的,为了简单起见。
9.2.1 使用预训练 tokenizer 将 DistilmBERT 转移到单语 Twi 数据
在本小节中,我们的目标是从一个在超过 100 种语言上训练过的模型中生成一个用于加纳语 Twi 的 DistilBERT 模型,不包括 Twi 在内。BERT 的多语言等效版本称为 mBERT;因此,DistilBERT 的多语言等效版本可预见地称为 DistilmBERT。这个 DistilmBERT 模型直接类比于我们在第八章中实验过的 mBERT 模型。我们当时发现,即使 Twi 没有包含在原始训练中,从这个检查点开始是有益的。在这里,我们基本上会复制相同的步骤序列,将每个 mBERT 实例替换为 DistilmBERT。这样可以直接比较两者,并因此直观地了解知识蒸馏的好处,同时学习如何在自己的项目中使用 DistilBERT。与之前一样,我们会在 JW300 数据集的单语 Twi 子集上对模型进行微调。¹²
我们首先初始化一个 DistilBERT tokenizer,使用 DistilmBERT 模型的预训练检查点。这次我们使用 cased 版本,如下所示:
from transformers import DistilBertTokenizerFast ❶
tokenizer = DistilBertTokenizerFast.from_pretrained("distilbert-base-multilingual-cased") ❷
❶ 这只是 DistilBertTokenizer 的一个更快的版本,你可以用它来代替。
❷ 使用预训练的 DistilmBERT tokenizer
准备好 tokenizer 后,将 DistilmBERT 检查点加载到 DistilBERT 掩码语言模型中,并按照以下方式显示参数的数量:
from transformers import DistilBertForMaskedLM ❶
model = DistilBertForMaskedLM.from_pretrained("distilbert-base-multilingual-cased") ❷
print("Number of parameters in DistilmBERT model:")
print(model.num_parameters())
❶ 使用掩码语言建模
❷ 初始化为 mBERT 检查点
输出表明,与我们在第八章中发现的 BERT 模型的 178.6 百万个参数相比,该模型具有 1.355 亿个参数。 因此,DistilBERT 模型的大小仅为等效 BERT 模型的 76%。
接下来,使用 transformers 中方便的 LineByLineTextDataset 方法从单语 Twi 文本构建数据集,具体方法如下所示:
from transformers import LineByLineTextDataset
dataset = LineByLineTextDataset(
tokenizer=tokenizer,
file_path="../input/jw300entw/jw300.en-tw.tw", ❶
block_size=128) ❷
❶ 我们在第 8.2.1 节中介绍的英语到 Twi JW300 数据集
❷ 一次读取多少行
随后,按照下面的代码片段中所示的方式定义“数据集整理器”——这是一个帮助程序,它将一批样本数据行(长度为 block_size)创建成一个特殊对象,这个特殊对象可以被 PyTorch 用于神经网络训练:
from transformers import DataCollatorForLanguageModeling
data_collator = DataCollatorForLanguageModeling(
tokenizer=tokenizer,
mlm=True, mlm_probability=0.15) ❶
❶ 使用掩码语言建模,并掩码单词的概率为 0.15
在这里,我们使用了掩码语言建模的方法——将我们输入数据中的 15% 的单词随机掩码,要求模型在训练过程中进行预测。
接下来,按照以下方式定义标准的训练参数,例如输出目录(我们选择为 twidistilmbert)和训练批次大小:
from transformers import TrainingArguments
training_args = TrainingArguments(
output_dir="twidistilmbert",
overwrite_output_dir=True,
num_train_epochs=1,
per_gpu_train_batch_size=16,
save_total_limit=1,
)
然后,使用已定义的数据集和数据整理器定义“训练器”,并在数据上进行一个训练时代,具体方法如下。请记住,Twi 数据包含超过 600,000 行,因此在所有数据上进行一遍训练是相当费力的!
trainer = Trainer(
model=model,
args=training_args,
data_collator=data_collator,
train_dataset=dataset,
prediction_loss_only=True)
最后,按照以下方式进行训练并计算训练所需的时间:
import time
start = time.time()
trainer.train()
end = time.time()
print("Number of seconds for training:")
print((end-start))
一如既往地,一定要保存模型:
trainer.save_model("twidistilmbert")
我们发现,与第八章中等效教师完成每个时代所需的 3 小时相比,该模型花费了大约 2 小时和 15 分钟完成该时代。 因此,学生的训练时间只有老师的 75%。 显著提高!
此外,损失函数的值达到了约 0.81,而 mBERT 的等效模型在第八章中的损失为约 0.77。就绝对值而言,性能差异可以粗略地量化为大约 5%——我们看到 DistilBERT 达到了 BERT 性能的 95%。 这非常接近 DistilBERT 作者在论文中报告的基准数字 97%。
最后一步,从语料库中取出以下句子:“Eyi de ɔhaw kɛse baa sukuu h*ɔ。” 掩盖一个单词,sukuu(在 Twi 中表示“学校”),然后将管道 API 应用于以下预测所删除的单词:
from transformers import pipeline
fill_mask = pipeline( ❶
"fill-mask",
model="twidistilmbert",
tokenizer=tokenizer)
print(fill_mask("Eyi de ɔhaw kɛse baa [MASK] hɔ.")) ❷
❶ 定义了填空管道
❷ 预测掩码标记
这会产生以下输出:
[{'sequence': '[CLS] Eyi de ɔhaw kɛse baa fie hɔ. [SEP]', 'score': 0.31311026215553284, 'token': 29959}, {'sequence': '[CLS] Eyi de ɔhaw kɛse baa me hɔ. [SEP]', 'score': 0.09322386980056763, 'token': 10911}, {'sequence': '[CLS] Eyi de ɔhaw kɛse baa ne hɔ. [SEP]', 'score': 0.05879712104797363, 'token': 10554}, {'sequence': '[CLS] Eyi de ɔhaw kɛse baa too hɔ. [SEP]', 'score': 0.052420321851968765, 'token': 16683}, {'sequence': '[CLS] Eyi de ɔhaw kɛse baa no hɔ. [SEP]', 'score': 0.04025224596261978, 'token': 10192}]
这确实是可信的完成。值得注意的是,我们在第 8.2.2 节中看到的结果中的宗教偏见似乎已经在模型中得到了缓解。像“以色列”和“伊甸园”这样的完成,在第 8.2.2 节的 mBERT 等价模型中建议,现在已经不再存在了。这可以通过两者之间参数数量的显著差异来解释。由于这一点,DistilBERT 不太可能过拟合,而 BERT 则更有可能这样做。
现在你知道如何在自己的项目中使用 DistilBERT 了!我们再次强调,你刚刚进行的练习教会了你如何在自定义语料库上微调预训练的基于 transformer 的模型——只需修改代码以应用于你自己的用例,包括不同的架构、预训练检查点和自定义数据集。
在下一章的第一节中,我们将有机会在英语中再次微调一个基于 transformer 的模型,这次是在自定义语料库上进行!我们将讨论 ALBERT 架构背后的适应性思想——一种轻量级的 BERT——并将其微调到来自 Multi-Domain Sentiment Dataset 的一些评论中。回想一下,在第四章我们玩过这个数据集。这是亚马逊 25 个产品类别的评论数据集,我们将重点关注书评,就像第四章一样。
摘要
-
ULMFiT 的策略,如倾斜三角形学习率、差异微调和逐步解冻,可以导致明显更有效的迁移。
-
执行知识蒸馏一个较大的教师BERT 模型会产生一个明显更小的学生BERT 模型,性能损失最小。
-
V. Sanh 等人,“DistilBERT,BERT 的精简版本:更小、更快、更便宜、更轻”,EMC²:与 NeurIPS 合办的第 5 版(2019 年)。
-
www.kaggle.com/azunre/tlfornlp-chapter9-ulmfit-adaptation-fast-aiv2 -
S. Merity 等人,“正则化和优化 LSTM 语言模型”,ICLR(2018 年)。
-
L. Smith 等人,“神经网络超参数的一种纪律方法:第一部分——学习率、批大小、动量和权重衰减”,arXiv(2018 年)。
-
R. Tang 等人,“从 BERT 中提炼任务特定知识到简单神经网络”,arXiv(2018 年)。
-
X. Jiao 等人,“TinyBERT:BERT 的精炼”,arXiv(2020 年)。
-
V. Sanh 等人,“DistilBERT,BERT 的精简版本:更小、更快、更便宜、更轻”,EMC²:与 NeurIPS 合办的第 5 版(2019 年)。
-
github.com/huggingface/transformers/blob/master/examples/research_projects/distillation
第十章:ALBERT,适配器和多任务适配策略
本章介绍
-
对嵌入因子分解和层间参数共享进行应用
-
在多个任务上对 BERT 系列模型进行微调
-
将迁移学习实验分成多个步骤
-
对 BERT 系列模型应用适配器
在上一章中,我们开始介绍了到目前为止我们所涵盖的深度 NLP 迁移学习建模架构的一些适配策略。换句话说,给定一个预训练的架构,如 ELMo、BERT 或 GPT,如何更有效地进行迁移学习?我们涵盖了 ULMFiT 方法背后的两个关键思想,即区分性微调和逐渐解冻的概念。
我们在本章中将要讨论的第一个适配策略围绕着两个目标展开,旨在创建更有利于具有更大词汇量和更长输入长度的基于 transformer 的语言模型。第一个想法实质上涉及巧妙的因子分解,或者将更大的权重矩阵分解为两个较小的矩阵,使您可以增加一个的维度而不影响另一个的维度。第二个想法涉及在所有层之间共享参数。这两个策略是 ALBERT 方法的基础,即 A Lite BERT。我们使用 transformers 库中的实现来获得这种方法的一些实际经验。
在第四章中,我们介绍了多任务学习的概念,即模型被训练为同时执行多种任务。由此产生的模型通常对新场景更具泛化能力,并且可能导致更好的迁移效果。毫不奇怪,这个想法在预训练的 NLP 语言模型的适配策略的背景下再次出现。当面临转移场景时,没有足够的训练数据来微调给定任务时,为什么不在多个任务上进行微调呢?讨论这个想法为介绍(GLUE)数据集提供了一个很好的机会:一个包含了几个代表人类语言推理任务的数据集。这些任务包括检测句子之间的相似性、问题之间的相似性、释义、情感分析和问答。我们展示了如何利用 transformers 库快速进行多任务微调使用这个数据集。这个练习还演示了如何在一个来自这些重要问题类别的自定义数据集上类似地微调 BERT 系列模型。
在第四章中,我们还讨论了领域自适应,在那里我们发现源域和目标域的相似性对于迁移学习的有效性起着至关重要的作用。更大的相似性通常意味着更容易的迁移学习过程。当源和目标过于不相似时,你可能会发现在一个步骤中执行该过程是不可能的。在这种情况下,可以使用“顺序适应”的概念将整体所需的转移分解成更简单、更易管理的步骤。例如,一个语言工具在西非和东非之间无法转移,但可以先在西非和中非之间成功转移,然后在中非和东非之间转移成功。在本章中,我们将“填空”目标预训练 BERT 顺序适应到一个低资源句子相似度检测场景中,首先适应到一个数据丰富的问题相似度场景。
我们将探讨的最终适应策略是使用所谓的适应模块或适配器。这些是预训练神经网络层之间只有少量参数的新引入模块。对于新任务微调这个修改后的模型只需要训练这几个额外的参数。原始网络的权重保持不变。通常情况下,当每个任务只增加 3-4% 的额外参数时,与微调整个模型相比,性能几乎没有损失。这些适配器也是模块化的,并且很容易在研究人员之间共享。
10.1 嵌入因子分解和跨层参数共享
我们在本节讨论的适应策略围绕着两个想法,旨在创建具有更大词汇表和更长最大输入长度的基于 transformer 的语言模型。第一个想法基本上涉及将一个更大的权重矩阵巧妙地分解为两个较小的矩阵,使得其中一个可以在不影响另一个维度的情况下增加维度。第二个想法涉及在所有层之间共享参数。这两种策略是 ALBERT 方法的基础。我们再次使用 transformers 库中的实现来获取一些与该方法有关的实际经验。这既可以让你对所获得的改进有所了解,也可以让你有能力在自己的项目中使用它。我们将使用第四章中的 Multi-Domain Sentiment Dataset 中的亚马逊图书评论作为我们这次实验的自定义语料库。这将使您能够进一步体验在自定义语料库上微调预训练的基于 transformer 的语言模型,这次是用英语!
第一个策略,即嵌入因子分解,受到了观察的启发,即在 BERT 中,输入嵌入的大小与其隐藏层的维度密切相关。分词器为每个标记创建一个 one-hot 编码的向量——该向量在与标记对应的维度上等于 1,在其他维度上等于 0。这个 one-hot 编码向量的维度等于词汇表的大小,V。输入嵌入可以被看作是一个维度为V乘以E的矩阵,将 one-hot 编码的向量乘以它并投影到大小为E的维度中。在早期的模型(如 BERT)中,这等于隐藏层的维度H,因此这个投影直接发生在隐藏层中。
这意味着当隐藏层的大小增加时,输入嵌入的维度也必须增加,这可能非常低效。另一方面,ALBERT 的作者观察到,输入嵌入的作用是学习上下文无关的表示,而隐藏层的作用是学习上下文相关的表示——这是一个更难的问题。受此启发,他们提出将单一输入嵌入矩阵分成两个矩阵:一个是V乘以E,另一个是E乘以H,允许H和E完全独立。换句话说,one-hot 编码的向量可以首先投影到较小尺寸的中间嵌入中,然后再馈送到隐藏层。即使隐藏层的尺寸很大或需要扩展,这也使得输入嵌入可以具有显着较小的尺寸。仅此设计决策就导致将投影 one-hot 嵌入向量到隐藏层的矩阵/矩阵的尺寸减少了 80%。
第二个策略,即跨层参数共享,与我们在第四章中讨论的软参数共享多任务学习场景相关。在学习过程中,通过对它们施加适当的约束,鼓励所有层之间的相应权重彼此相似。这起到了正则化的效果,通过减少可用自由度的数量来降低过拟合的风险。这两种技术的结合使得作者能够构建出在当时(2020 年 2 月)超越了 GLUE 和 SQuAD 记录性能的预训练语言模型。与 BERT 相比,在参数大小上实现了约 90%的减少,而性能只有轻微的下降(在 SQuAD 上不到 1%)。
再次,因为多种检查点可用于直接加载,我们不在此重复从头开始的训练步骤,因为我们的重点是迁移学习。相反,我们使用类似于我们在前一章和第八章中用于我们的跨语言迁移学习实验的“基础”BERT 检查点。这使我们能够直接比较使用这种架构与原始 BERT 的性能和效益,并教你如何开始在自己的项目中使用这种架构。
10.1.1 在 MDSD 书评上对预训练的 ALBERT 进行微调
我们准备数据的步骤与第 4.4 节中的步骤相同,我们在此不再重复。这些步骤也在本书附带的 Kaggle 笔记本中重复出现。我们从列表 4.6 生成的变量data开始。假设与第 4.4 节相同的超参数设置,这是一个由 2,000 本书评文本组成的 NumPy 数组。
使用以下代码将这个 NumPy 数组写入 Pandas 到文件中:
import pandas as pd
train_df = pd.DataFrame(data=data)
train_df.to_csv("albert_dataset.csv")
我们首先初始化一个 Albert 分词器,使用基本 ALBERT 模型中的预训练检查点,如下所示。我们使用版本 2 是因为它是目前可用的最新版本。你可以在 Hugging Face 网站上随时找到所有可用的 ALBERT 模型列表。⁶
from transformers import AlbertTokenizer ❶
tokenizer = AlbertTokenizer.from_pretrained("albert-base-v2") ❷
❶ 加载 ALBERT 分词器
❷ 使用预训练的 ALBERT 分词器
准备好分词器后,将基础 ALBERT 检查点加载到 ALBERT 遮盖语言模型中,并显示参数数量如下:
from transformers import AlbertForMaskedLM ❶
model = AlbertForMaskedLM.from_pretrained("albert-base-v2") ❷
print("Number of parameters in ALBERT model:")
print(model.num_parameters())
❶ 使用遮盖语言建模
❷ 初始化到 ALBERT 检查点
输出表明模型有 1180 万个参数——与第八章的 BERT 的 178.6 万个参数和直接 BERT 的 135.5 万个参数相比,这是一个巨大的缩小。事实上,这是与 BERT 模型相比的 15 倍缩小。哇!
然后,像之前一样,使用 transformers 中提供的方便的LineByLineTextDataset方法,使用单语 Twi 文本中的分词器构建数据集,如下所示:
from transformers import LineByLineTextDataset
dataset = LineByLineTextDataset(
tokenizer=tokenizer,
file_path="albert_dataset.csv",
block_size=128) ❶
❶ 每次读取多少行
定义一个“数据收集器”——一个帮助方法,将一个样本数据行批量(block_size长度)创建成一个特殊对象——如下所示。这个特殊对象可以被 PyTorch 用于神经网络训练:
from transformers import DataCollatorForLanguageModeling
data_collator = DataCollatorForLanguageModeling(
tokenizer=tokenizer,
mlm=True, mlm_probability=0.15) ❶
❶ 使用遮盖语言建模,并用 0.15 的概率遮盖单词
在这里,我们使用了以 15%的概率对我们的输入数据进行随机遮盖的遮盖语言建模,并要求模型在训练过程中对它们进行预测。
定义标准的训练参数,如输出目录和训练批量大小,如下代码片段所示。注意,这一次我们训练 10 次,因为数据集比前一章中使用的超过 60,000 个单语 Twi 样本要小得多:
from transformers import Trainer, TrainingArguments
training_args = TrainingArguments(
output_dir="albert",
overwrite_output_dir=True,
num_train_epochs=10,
per_gpu_train_batch_size=16,
save_total_limit=1,
)
然后,使用之前定义的数据集和整理器来定义一个“训练器”,以跨数据进行一个训练 epoch,如下所示:
trainer = Trainer(
model=model,
args=training_args,
data_collator=data_collator,
train_dataset=dataset,
prediction_loss_only=True,
)
按照以下步骤训练并计时训练时间:
import time
start = time.time()
trainer.train()
end = time.time()
print("Number of seconds for training:")
print((end-start))
在这个小数据集上,10 个 epochs 大约只需约五分钟就能完成训练。损失值达到约 1。
按以下方式保存模型:
trainer.save_model("albert_fine-tuned")
最后,让我们按照以下步骤应用管道 API 来预测虚构书评中的遮蔽词:
from transformers import pipeline
fill_mask = pipeline( ❶
"fill-mask",
model="albert_fine-tuned",
tokenizer=tokenizer
)
print(fill_mask("The author fails to [MASK] the plot.")) ❷
❶ 定义填空管道
❷ 预测遮蔽的标记
这产生了以下非常合理的输出:
[{'sequence': '[CLS] the author fails to describe the plot.[SEP]', 'score': 0.07632581889629364, 'token': 4996}, {'sequence': '[CLS] the author fails to appreciate the plot.[SEP]', 'score': 0.03849967569112778, 'token': 8831}, {'sequence': '[CLS] the author fails to anticipate the plot.[SEP]', 'score': 0.03471902385354042, 'token': 27967}, {'sequence': '[CLS] the author fails to demonstrate the plot.[SEP]', 'score': 0.03338927403092384, 'token': 10847}, {'sequence': '[CLS] the author fails to identify the plot.[SEP]', 'score': 0.032832834869623184, 'token': 5808}]
到目前为止,您可能已经观察到,我们在此处对自定义书评语料库对 ALBERT 进行微调的步骤序列与我们在上一章中使用 DistilBERT 的步骤序列非常相似。这一系列步骤反过来又与我们在第八章中使用的 mBERT 的步骤序列非常相似。我们再次强调,这个配方可以用作 transformers 中几乎任何其他架构的蓝图。虽然我们无法提供在每种可能的应用类型上微调的示例,但这个配方应该可以推广,或者至少作为许多用例的良好起点。例如,考虑一种情况,您想要教 GPT-2 以某种选择的风格写作。只需复制我们在这里使用的相同代码,将数据集路径指向您选择的写作风格的语料库,并将标记器和模型引用从 AlbertTokenizer / AlbertForMaskedLM 更改为 GPT2Tokenizer / GPT2LMHeadModel。
需要注意的一点是,所有 PyTorch transformers 模型默认情况下都会解冻所有层进行训练。要冻结所有层,您可以执行以下代码片段:
for param in model.albert.parameters():
param.requires_grad = False
您可以使用类似的代码片段仅冻结一些参数。
在下一节中,我们将讨论多任务微调,我们将有另一个机会来看看这些类型模型的微调,这次是针对各种任务。
10.2 多任务微调
在第四章的第三部分中,我们介绍了多任务学习的概念,其中模型被训练执行各种任务,而不仅仅是一个任务。结果模型通常对新场景更具一般性,并且可以实现更好的转移和性能。毫不奇怪,这个想法再次出现在预训练 NLP 语言模型的适应策略的背景下,微调在多个任务上的模型观察到更加健壮和有效。⁷
我们在这里讨论这个想法提供了一个很好的机会来介绍通用语言理解评估(GLUE)数据集,⁸这是一个包含几个人类语言推理代表性任务数据的集合。这个数据集包括检测句子相似性、问题相似性、释义、情感分析和问题回答等任务。在本节中,我们演示了如何快速利用 transformers 库对我们讨论的各种基于变压器的预训练模型在 GLUE 数据集的各种任务上进行微调。这个练习还演示了如何按类似方式微调来自 BERT 系列的模型,以解决 GLUE 中包含的重要问题类别之一的自定义数据集。
我们还演示了顺序适应——将总体所需的转移实验过程分解成更简单、更易管理的步骤的过程。考虑一个假设的情景,即基于语言的工具在西非和东非之间无法完成转移——首先它可能在西非和中非之间成功转移,然后在中非和东非之间成功转移。这与多任务微调的想法相关,因为它本质上是按顺序进行的,一步接一步进行。与通常理解的多任务微调方法不同,顺序适应首先在一个任务上进行微调,然后再在另一个任务上进行微调。
在本节中,我们通过在 GLUE 数据集的几个任务上对一些预训练的基于变压器的语言模型进行多任务微调和顺序适应来演示。具体来说,我们关注的是一个被称为Quora 问题对(QQP)任务的问题相似度任务,以及用于衡量一对句子之间相似性的语义文本相似性基准(SST-B)任务。
10.2.1 通用语言理解数据集(GLUE)
通用语言理解数据集(GLUE)旨在提供一系列多样的自然语言理解任务的具有挑战性的基准数据集。这些任务被选中,以代表多年来在自然语言处理领域研究人员之间达成的一种关于什么构成有趣、具有挑战性和相关问题的隐含共识。在表 10.1 中,我们总结了数据集中可用任务和每个任务的数据计数。
表 10.1 原始通用语言理解数据集(GLUE)中提供的任务、描述和数据计数列表
| 任务名称 | 数据量 | 描述 |
|---|---|---|
| 语言可接受性语料库(CoLA) | 训练 8,500,测试 1,000 | 确定一个英语句子是否符合语法规范 |
| 斯坦福情感树库(SST2) | 训练 67,000,测试 1800 | 检测给定句子的情感-积极或消极 |
| Microsoft Research Paraphrase Corpus (MRPC) | 3,700 train, 1,700 test | 确定一个句子是否是另一个句子的释义 |
| Semantic Textual Similarity Benchmark (STS-B) | 7,000 train, 1,400 test | 预测一对句子之间的相似度分数,范围在 1 到 5 之间 |
| Quora Question Pairs (QQP) | 3,640,000 train, 391,000 test | 确定一对 Quora 问题是否语义上等同 |
| Multi-Genre Natural Language Inference (MultiNLI) | 393,000 train, 20,000 test | 确定一个前提句子是暗示/蕴含还是与一个假设句子相矛盾 |
| Question-Answering Natural Language Inference (QNLI) | 105,000 train, 5,400 test | 检测上下文句子是否包含对问题的答案 |
| Recognizing Textual Entailment (RTE) | 2,500 train, 3,000 test | 测量前提和假设之间的文本蕴含关系,类似于 MultiNLI |
| Winograd Schema Challenge (WNLI) | 634 train, 146 test | 确定模棱两可的代词指的是一组可能选项中的哪一个名词 |
从表中可以看出,原始的 GLUE 数据集涵盖了各种任务,并且可用的数据量不同。这是为了鼓励不同任务之间的知识共享,这也是我们在本章节中探讨的多任务微调理念的核心。接下来我们简要描述表中的各项任务。
前两个任务——Corpus of Linguistic Acceptability (CoLA) 和 Stanford Sentiment Treebank (SST2)——是单句任务。前者试图确定一个给定的英文句子是否语法正确,而后者试图检测句子中表达的情感是积极还是消极。
以下三项任务——Microsoft Research Paraphrase Corpus (MRPC)、Semantic Textual Similarity Benchmark (STS-B) 和 Quora Question Pairs (QQP)——被归类为相似性任务。这些任务涉及以各种方式比较两个句子。MRPC 试图检测一个句子是否是另一个句子的释义,即是否表达了相同的概念。STS-B 在连续范围 1 到 5 之间测量一对句子的相似度。QQP 试图检测一个 Quora 问题是否等同于另一个。
剩下的四个任务被分类为推理任务。多体裁自然语言推理(MultiNLI)任务试图确定给定的句子是否暗示另一个句子或是否与之矛盾――它衡量蕴涵。问答自然语言推理(QNLI)任务类似于我们讨论并在第八章中用于说明问答的 SQuAD⁹数据集。提醒一下,该数据集由上下文段落、对其提出的问题以及答案在上下文段落中的开始和结束位置指示符组成,如果存在的话。QNLI 基本上将这个想法转化为一个句对任务,通过将每个上下文句子与问题配对,并尝试预测答案是否在该上下文句子中。识别文本蕴涵(RTE)任务类似于 MultiNLI,因为它衡量两个句子之间的蕴涵关系。最后,Winograd Schema Challenge(WNLI)数据集试图检测一个含糊指代词在句子中指代可用选项中的哪个名词。
自 GLUE 成立以来,还引入了另一个名为 SuperGLUE¹⁰的数据集。这个新版本是必要的,因为最近的现代方法在 GLUE 的许多部分几乎达到了完美的性能。SuperGLUE 的开发是为了更具挑战性,因此为比较方法提供更多的“动态范围”。我们在这里关注 GLUE,但我们认为在您成为 NLP 专家时,牢记 SuperGLUE 的存在是很重要的。
接下来,我们将以 QQP 和 STS-B GLUE 任务做一些实验,作为本节的说明性示例。首先,在下一小节中,我们展示如何对我们提出的任何任务中的一个任务进行微调预训练的 BERT。我们强调,虽然在这种情况下,我们使用 STS-B 作为示例微调任务,但对于任何呈现的任务,相同的步骤序列直接适用。我们还提醒您,此练习是为了准备您在自己的自定义数据集上对 BERT 进行微调,该数据集来自我们提出的任何任务类别。
10.2.2 在单个 GLUE 任务上进行微调
在本小节中,我们看到如何快速微调 transformers 家族中的预训练模型,以处理 GLUE 基准集中的任务。回想一下,BERT 是在“填空”和“下一个句子预测”目标上进行预训练的。在这里,我们进一步微调这个预训练的 BERT 来处理 GLUE 数据上的 STS-B 相似性任务。这个练习作为如何在 GLUE 的任何其他任务以及属于这些重要问题类别之一的任何自定义数据集上进行操作的示例。
我们要做的第一件事是克隆 transformers 存储库,并使用以下代码安装必要的要求:
!git clone --branch v3.0.1 https:/ /github.com/huggingface/transformers ❶
!cd transformers
!pip install -r transformers/examples/requirements.txt ❷
!pip install transformers==3.0.1 ❸
❶ 克隆(指定版本的)transformers 存储库
❷ 安装必要的要求
❸ 为了可重现性,固定 transformers 版本
请注意在我们的 Kaggle 笔记本中忽略依赖冲突消息——这些消息与我们在此处使用的库无关,只要您复制我们的笔记本而不是从头开始创建一个新的。
接下来,按以下方式下载 GLUE 数据:
!mkdir GLUE
!python transformers/utils/download_glue_data.py --data_dir GLUE --tasks all❶
❶ 下载所有任务的 GLUE 数据
这会创建一个名为 GLUE 的目录,其中包含一个子目录,该子目录以每个 GLUE 任务命名,并包含该任务的数据。我们可以按以下方式查看 GLUE/STS-B 中包含的内容:
!ls GLUE/STS-B
这产生了以下输出:
LICENSE.txt dev.tsv original readme.txt test.tsv train.tsv
此外,我们可以用以下方式查看一部分 STS-B 训练数据:
!head GLUE/STS-B/train.tsv
这产生以下输出:
index genre filename year old_index source1 source2 sentence1 sentence2 score
0 main-captions MSRvid 2012test 0001 none none A plane is taking off. An air plane -is taking off. 5.000
1 main-captions MSRvid 2012test 0004 none none A man is playing a large flute. A man is playing a flute. 3.800
2 main-captions MSRvid 2012test 0005 none none A man is spreading shreddedcheese on a pizza. A man is spreading shredded cheese on an uncooked pizza. 3.800
3 main-captions MSRvid 2012test 0006 none none Three men are playing chess. Two men are playing chess. 2.600
4 main-captions MSRvid 2012test 0009 none none A man is playing the cello.A man seated is playing the cello. 4.250
5 main-captions MSRvid 2012test 0011 none none Some men are fighting. Two men are fighting. 4.250
6 main-captions MSRvid 2012test 0012 none none A man is smoking. A man is skating. 0.500
7 main-captions MSRvid 2012test 0013 none none The man is playing the piano. The man is playing the guitar. 1.600
8 main-captions MSRvid 2012test 0014 none none A man is playing on a guitar and singing. A woman is playing an acoustic guitar and singing. 2.200
在继续之前,我们注意到,为了使用这里讨论的脚本来在您自己的自定义数据上对模型进行精细调优,您只需要将您的数据转换为所示格式并指定脚本所在的位置即可!
要在 STS-B GLUE 任务上对“vanilla” bert-base-cased BERT checkpoint 进行三轮训练——批量大小为 32,最大输入序列长度为 256,学习率为 2e-5——我们执行以下命令:
%%time ❶
!python transformers/examples/text-classification/run_glue.py --model_name_or_path bert-base-cased --task_name STS-B --do_train --do_eval --data_dir GLUE/STS-B/ --max_seq_length 256 --per_gpu_train_batch_size 32 --learning_rate 2e-5 --num_train_epochs 3.0 --output_dir /tmp/STS-B/
❶ 这是 Jupyter 笔记本中计时的“魔法”命令。
这一操作执行时间不超过 10 分钟。请注意,在代码中,我们指定了输出目录为 /tmp/STS-B/。该文件夹包含了经过精细调优的模型和评估结果。然后,为了查看所取得的性能,我们只需执行以下命令将结果打印到屏幕上:
!cat /tmp/STS-B/eval_results_sts-b.txt
这产生以下输出:
eval_loss = 0.493795601730334
eval_pearson = 0.8897041761974835
eval_spearmanr = 0.8877572577691144
eval_corr = 0.888730716983299
这些代表了用于此问题的度量标准的最终数值,即皮尔逊相关系数和斯皮尔曼相关系数。不深入细节,这些系数衡量了数据集中提供的真实相似度与我们在测试集上精细调优模型获得的相似度之间的相关性。这些系数的较高值表明了更好的模型,因为它们与真实结果的关联更大。我们看到对于这两个系数都达到了接近 89% 的性能。在撰写本文时(2020 年 10 月初),当前的 GLUE 排行榜¹¹显示,在全球范围内,排名前 20 的性能大约在 87% 到 93% 之间变化。这些排名靠前的性能也在 GLUE 的其他任务上表现良好,尽管我们目前只对一个任务进行了精细调优。但我们可以快速取得如此接近最新技术水平的性能仍然令人印象深刻。请注意从表 10.1 中得知,用于此任务的训练数据量仅为 7,000 个样本。
在下一小节中,我们将进一步在另一个任务——Quora 问题对(QQP)上对模型进行精细调优,并进一步阐明多任务学习和顺序适应的概念。
10.2.3 顺序适应
在本小节中,我们将看到在 STS-B 任务上进行微调之前,在 Quora 问答对(QQP)任务上进行微调是否会产生更好的性能。请回顾表 10.1,其中 QQP 有 364,000 个训练样本,而 STS-B 有 7,000 个样本。显然,QQP 具有更多的数据。首先在 QQP 上训练可以被解释为应用一种顺序适应多任务学习策略来处理一个低资源的场景,其中训练数据量不理想:只有 7,000 个样本。
我们开始本练习,假设已经克隆了 transformers 存储库,已安装了必要的要求,并已下载了 GLUE 数据,如前一小节所示。现在,要做的下一件事是在 QQP GLUE 任务上对“普通”的bert-base-cased BERT 检查点进行微调,一次迭代,批处理大小为 32,最大输入序列长度为 256,学习率为 2e-5。请注意,这次我们只使用一个迭代,而不是前一小节中的三个,因为训练数据现在要大得多。现在每个迭代(涉及一次通过训练集)涵盖了 364,000 个样本,我们认为这已足够。我们使用以下代码:
!python transformers/examples/text-classification/run_glue.py --model_name_or_path bert-base-cased --task_name QQP --do_train --do_eval --data_dir GLUE/QQP/ --max_seq_length 256 --per_gpu_train_batch_size 32 --learning_rate 2e-5 --num_train_epochs 1 --output_dir /tmp/QQP/
训练时长约为 2 小时 40 分钟。与以前一样,我们可以检查 QQP 任务上的性能如下:
!cat /tmp/QQP/eval_results_qqp.txt
这达到了以下性能:
eval_loss = 0.24864352908579548
eval_acc = 0.8936433341578036
eval_f1 = 0.8581700639883898
eval_acc_and_f1 = 0.8759066990730967
epoch = 1.0
然后我们可以按以下方式加载 QQP 微调的模型:
from transformers import BertForSequenceClassification, BertConfig ❶
qqp_model = BertForSequenceClassification.from_pretrained("/tmp/QQP") ❷
❶ 初始化为我们的微调模型检查点
❷ 这次使用序列分类,因为这是问题的形式
在加载了微调模型之后,让我们提取其编码器,以便我们可以在后续模型中使用它,然后可以进一步在 STS-B 任务上进行微调。请注意,这类似于我们在第四章中分析的硬参数共享场景。我们在图 10.1 中说明了这种情况。
图 10.1 我们在本节探讨的硬参数共享多任务学习场景。模型首先在 QQP 上进行微调,这是一个数据丰富的场景,然后是 STS-B,这是一个资源稀缺的场景。这个实验的顺序性质将其分类为顺序适应。
图中清楚地显示了任务之间共享的编码器。编码器被提取并用于初始化一个模型,以进行在 STS-B 上的微调,代码片段如下:
shared_encoder = getattr(qqp_model, "bert") ❶
configuration = BertConfig()
configuration.vocab_size = qqp_model.config.vocab_size ❷
configuration.num_labels = 1 ❸
stsb_model = BertForSequenceClassification(configuration) ❹
setattr(stsb_model, "bert", shared_encoder) ❺
❶ 获取经过微调的 QQP 模型编码器
❷ 确保 STS-B 配置的词汇量和输出大小设置一致
❸ STS-B 是一个回归问题,只需要一个输出;QQP 是一个二元分类任务,因此有两个输出。
❹ 用与 QQP 类似的设置初始化 STS-B 模型
将初始化的 STS-B 模型保存以供进一步微调,方法如下:
stsb_model.save_pretrained("/tmp/STSB_pre")
确保从 QQP 模型中获取了词汇表,如下所示:
!cp /tmp/QQP/vocab.txt /tmp/STSB_pre
现在,使用与前一小节相同的设置,在 STS-B 上微调先前微调的 QQP 模型,操作如下:
!python transformers/examples/text-classification/run_glue.py --model_name_or_path /tmp/STSB_pre --task_name STS-B --do_train --do_eval --data_dir GLUE/STS-B/ --max_seq_length 256 --per_gpu_train_batch_size 32 --learning_rate 2e-5 --num_train_epochs 3 --output_dir /tmp/STS-B/
这三个训练时代只需大约七分半钟即可执行,只有 7,000 个训练集大小。像往常一样,我们使用以下内容来检查获得的性能:
!cat /tmp/STS-B/eval_results_sts-b.txt
观察到以下性能:
eval_loss = 0.49737201514158474
eval_pearson = 0.8931606380447263
eval_spearmanr = 0.8934618150816026
eval_corr = 0.8933112265631644
epoch = 3.0
我们已经取得了比之前的小节更好的表现,只是在 STS-B 上进行微调。那里的eval_corr约为 88.9%,而我们在这里达到了 89.3%。因此,连续适应的多任务学习实验被证明具有益处,并导致性能的可衡量提高。
在下一节中,我们将探讨是否可以比我们在这里做得更加高效地将模型微调到新的情况。我们将研究在预训练语言模型的层之间引入称为适应模块或适配器的方法,以适应新情况。这种方法很有前途,因为引入的参数数量非常少,可以有效地预训练和分享 NLP 社区。
10.3 适配器
我们探索的下一个适应策略是使用所谓的适配模块或适配器。它们背后的关键思想如图 10.2 所示,介绍了它们作为第七章中图 7.6 中香草变压器编码器中的附加层。
图 10.2 在“香草”变压器编码器的图 7.6 中新引入的适配器层
如图所示,这些适配器是预训练神经网络层之间仅有几个参数的新引入模块。为了将修改后的模型微调到新任务上,只需要训练这些少量的额外参数——原始网络的权重保持不变。与微调整个模型相比,通常仅添加每个任务 3-4%的额外参数,几乎没有性能损失。¹² 实际上,这些额外参数相当于大约 1 兆字节的磁盘空间,这在现代标准下非常低。
这些适配器是模块化的,允许易于扩展和研究人员之间的经验共享。实际上,一个名为 AdapterHub¹³的项目是在我们使用的 transformers 库上构建的,旨在成为共享这些模块的中央存储库。在该部分中,我们将使用此项目构建在斯坦福情感树库(SST2)任务上微调的 BERT 模型。这相当于我们在先前的小节中微调 STS-B GLUE 子集所做的事情,将使您迅速了解适配器框架所提供的优势与我们之前所做的有何不同。
让我们按以下方式安装 AdapterHub 库:
pip install adapter-transformers
导入所需类并仅使用三行代码加载所需的适配器,如下所示:
from transformers import BertForSequenceClassification, BertTokenizer
model = BertForSequenceClassification.from_pretrained("bert-base-uncased") ❶
model.load_adapter("sentiment/sst-2@ukp") ❷
❶要微调的检查点
❷ 任务特定的适配器选择规范
适配器和使用说明在 AdapterHub 网站上列出 ¹⁴ 这就是我们要对 BERT 检查点适应 SST2 情感分类任务所做的一切。将这与上一节的微调步骤进行比较,就可以显而易见地看出适配器方法的实用性。我们无需进行微调,只需加载附加的模块即可继续前进!
请注意,在我们的代码中,我们使用了bert-base-uncased检查点,并且我们要加载的适配器是在 UKP 句子论证挖掘语料库上进行了微调 ¹⁵,这是因为目前 AdapterHub 存储库中只有部分可用的内容。AdapterHub 是一个早期项目,我们预计随着时间的推移会提供更多的适配器。在撰写本文的 2020 年 10 月,已经有接近 200 个适配器可用。¹⁶
作为本节和本章的最终行动,让我们通过以下代码片段来确信我们构建的模型实际上作为情感分类引擎运行。我们使用以下两个句子的情感进行比较:“那是非常出色的贡献,好!”和“那对环境非常糟糕。”
import torch
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased") ❶
tokensA = tokenizer.tokenize("That was an amazing contribution, good!") ❷
input_tensorA = torch.tensor([tokenizer.convert_tokens_to_ids(tokensA)])
tokensB = tokenizer.tokenize("That is bad for the environment.") ❸
input_tensorB = torch.tensor([tokenizer.convert_tokens_to_ids(tokensB)])
outputsA = model(input_tensorA,adapter_names=['sst-2']) ❹
outputsB = model(input_tensorB,adapter_names=['sst-2']) ❺
print("The prediction for sentence A - That was an amazing contribution, good! - is:")
print(torch.nn.functional.softmax(outputsA[0][0])) ❻
print("The prediction for sentence B - That is very bad for the environment. - is:")
print(torch.nn.functional.softmax(outputsB[0][0])) ❼
❶ 使用常规预训练的分词器
❷ 句子 A
❸ 句子 B
❹ 进行了 A 的预测
❺ 进行了 B 的预测
❻ 显示了句子 A 的预测概率
❼ 显示了句子 B 的预测概率
这产生了以下输出:
The prediction for sentence A - That was an amazing contribution, good! - is:
tensor([0.0010, 0.9990], grad_fn=<SoftmaxBackward>)
The prediction for sentence B - That is very bad for the environment. - is:
tensor([0.8156, 0.1844], grad_fn=<SoftmaxBackward>)
所示的预测可以被解释为一对概率,第一个指示了输入是“负面”的概率,第二个是“正面”的概率。我们看到句子“那是非常出色的贡献,好!”有 99.9%的强烈正面概率。另一方面,句子“那对环境非常糟糕。”则是负面的,概率为 81.6%。这当然是合理的,并验证了我们的实验。
总结
-
应用嵌入因子分解和层间参数共享可以产生更加参数高效的模型。
-
在 BERT 系列模型上同时进行多任务微调,也就是多任务微调,会产生更具一般性的模型。
-
在 BERT 系列模型上使用适配器可以简化微调。
-
Z. Lan 等人,“ALBERT:自监督学习语言表示的 Lite BERT”,ICLR(2020)。
-
A. Wang 等人,“GLUE:自然语言理解多任务基准和分析平台”,ICLR(2019)。
-
N. Houlsby 等人,“NLP 参数高效迁移学习”,ICML(2019)。
-
Z. Lan 等人,“ALBERT:自监督学习语言表示的 Lite BERT”,ICLR(2020)。
-
X. Liu 等,“用于自然语言理解的多任务深度神经网络”,ACL 会议记录(2019)。
-
A. Wang 等,“GLUE:自然语言理解的多任务基准和分析平台”,ICLR(2019)。
-
P. Rajpurkar 等,“SQuAD:用于机器文本理解的 100,000+问题”,arXiv(2016)。
-
A. Wang 等,“GLUE:自然语言理解的多任务基准和分析平台”,ICLR(2019)。
-
N. Houlsby 等,“用于 NLP 的参数高效迁移学习”,ICML(2019)。
第十一章:结论
本章包括
-
总结本书涵盖的重要概念
-
总结相关的重要新兴概念
-
考虑自然语言处理中关于迁移学习方法的局限性以及环境和伦理考虑
-
展望自然语言处理中的迁移学习未来
-
跟上该领域的最新发展
在前面的章节中,我们涵盖了大量的材料——我们希望它们既具信息性又引人入胜。这一结论性章节试图对我们所做的一切进行有意义的总结,并展望该领域的未来和新兴的研究趋势。由于该领域的多产产出和快速发展的性质,我们当然没有涵盖每一个有影响力的架构或有前途的研究方向。为了减轻这一点,我们对我们在本书中没有机会涵盖的各种研究趋势进行了简要讨论,尽可能与已涵盖的材料进行联系和框架化。
在本章中,我们还试图通过涉及一些传统上没有受到太多关注的新兴问题,例如伦理考虑和各种模型的环境影响,提供更广泛的背景。这些与对这些模型的局限性的认识密切相关,我们在本章中尽可能突出这一点。
至关重要的是,我们讨论了在这样一个快速发展的领域中保持最新的各种提示。强调掌握了本书内容后,您现在只是开始您在该领域的旅程。所提出的工具和技能会随时间变化,并且它们的每个独特应用可能需要您的创造力或者尚未开发的新技术。在这样一个快速发展的领域中保持竞争优势确实是一次旅程,而不是一个目的地。我们鼓励读者对正在进行的研究保持探究的态度,并在某种程度上继续为其发展做出贡献。
让我们通过概述关键概念来开始这一最后一章。
11.1 关键概念概述
迁移学习旨在利用不同设置中的先前知识——无论是不同的任务、语言还是领域——来帮助解决手头的问题。它受到人类学习方式的启发,因为我们通常不会从头开始学习任何给定的问题,而是建立在可能相关的先前知识上。使没有实质计算资源的从业者能够达到最先进的性能被认为是向民主化获得正在进行的技术革命成果的重要一步。作为更具体的动机,考虑一下训练不同大小的 BERT 模型的代表性成本,如图 11.1 所示。¹
图 11.1 BERT 不同规模的训练成本。展示了两种代表性成本——单次运行和包括超参数调整在内的整个训练过程。15 亿参数的最大规模单次运行成本为1.6 百万!
正如图所示,最大规模的 BERT 训练成本可能高达数百万美元。迁移学习确实可以让您在几小时内,最坏情况下花费几美元用于微调,将这些宝贵的知识重复利用于您的个人计算项目中。
在计算机视觉中推广的迁移学习最近开始被自然语言处理(NLP)社区大量使用。而计算机视觉涉及教计算机如何理解和处理图像和视频,NLP 则考虑如何处理人类语音,无论是文本还是语音音频。在本书中,我们关注的是文本。我们特别感兴趣的一些 NLP 任务包括文档分类、机器翻译和问答。
尽管在历史上,这些任务最初是通过试图为每种情况制定固定规则来解决的——这种范式现在被称为符号 AI——但机器学习现在已成为主导趋势。计算机不再为每种可能的情况明确编程,而是通过看到许多这种相应的输入-输出对的示例来训练计算机将输入与输出信号相关联。传统上用于学习适当的输入-输出关系的方法包括决策树、随机森林、诸如 SVM 的核方法和神经网络。神经网络最近已成为解决感知问题(即计算机视觉和 NLP)的表示学习方法的首选。因此,这是我们在本书中探讨的最重要的方法类别。
在深入研究现代 NLP 的迁移学习方法之前,我们进行了一项关于传统机器学习方法的回顾性实验。具体来说,我们采用了以下方法:
-
逻辑回归
-
支持向量机
-
随机森林
-
梯度提升机
以解决两个重要问题:电子邮件垃圾检测和互联网电影数据库(IMDB)电影评论分类。为了将文本转换为数字,我们使用了词袋模型。该模型简单地计算了每封电子邮件中包含的单词标记的频率,从而将其表示为这些频率计数的向量。
现代自然语言处理(NLP)方法学主要集中在将文本部分(词语、子词、句子等)向量化上,采用诸如 word2vec 和 sent2vec 之类的技术。然后将得到的数值向量进一步处理,作为传统机器学习方法的特征,例如用于随机森林分类。
正如本书第一章所概述的,这一重要的自然语言处理研究子领域起源于 20 世纪 60 年代的信息检索术语向量模型。这在预训练的浅层神经网络技术方面达到了高潮,包括以下内容:
-
fastText
-
GloVe
-
word2vec,在 2010 年代中期推出了几个变体,包括连续词袋(CBOW)和 Skip-Gram
CBOW 和 Skip-Gram 都来自于训练用于各种目标的浅层神经网络。Skip-Gram 试图预测滑动窗口中任何目标词附近的单词,而 CBOW 试图预测给定邻居的目标词。GloVe,代表“全局向量”,试图通过将全局信息合并到嵌入中来扩展 word2vec。它优化了嵌入,使得单词之间的余弦乘积反映它们共同出现的次数,从而使得结果向量更具可解释性。fastText 技术试图通过对字符 n-gram(而不是单词 n-gram)重复 Skip-Gram 方法来增强 word2vec,从而能够处理以前未见过的单词。这些预训练嵌入的每个变体都有其优点和缺点。作为这类方法的数值演示,我们使用 fastText 词嵌入来重新访问 IMDB 电影分类示例,那里将词袋模型替换为 fastText 以将文本转化为数字。
几种技术受 word2vec 的启发,试图将较大的文本部分嵌入到向量空间中,以便在诱导向量空间中含义类似的文本部分彼此靠近。这使得可以在这些文本部分上进行算术运算,以进行关于类比、组合含义等推理。这样的方法包括以下内容:
-
段落向量,或doc2vec,利用了从预训练词嵌入中摘要单词的连接(而不是平均)。
-
Sent2vec扩展了 word2vec 的经典连续词袋(CBOW)—其中一个浅层神经网络被训练以从其上下文中的滑动窗口中预测一个词—到通过优化词和词 n-gram 的嵌入来对句子进行准确的平均表示。
作为这类方法的数值演示,我们使用了一个基于 fastText 而非词袋模型的 sent2vec 的实现来执行 IMDB 电影分类实验。
一些作者², ³, ⁴提出了各种分类系统,将迁移学习方法归类到不同的组别中。粗略地说,分类是基于迁移是否发生在不同的语言、任务或数据领域之间。通常,这些分类类型对应着以下内容:
-
跨语言学习
-
多任务学习
-
领域自适应
我们进行了一系列多任务迁移学习实验,使用了 IMDB 分类和电子邮件垃圾邮件检测这些熟悉的任务来说明这个概念。为了通过示例说明领域自适应,我们使用了自动编码器来调整一个在 IMDB 电影评论分类上训练的模型,以适应亚马逊图书评论的领域。这个练习还允许我们说明了零-shot 迁移学习的一个实例,即在亚马逊图书评论领域不需要微调就可以开始提供有价值的结果。
序列到序列建模的进展为诸如机器翻译之类的任务带来了革命。该设置中的编码器和解码器最初是循环神经网络(RNNs)。由于输入序列过长的问题,发展了一种称为注意力的技术,允许输出仅关注输入的相关部分。尽管最初这与 RNNs 结合使用,但它发展成为了使用自注意力构建编码器和解码器的技术。自注意力与最初的注意力公式不同,因为它寻求序列的部分与同一序列的其他部分之间的关联,而不是两个不同输入和输出序列的部分之间的关联。自注意力取代注意力的架构被称为 变压器,它在并行计算架构上比早期基于 RNN 的序列到序列模型更具可伸缩性。这种改进的可扩展性推动了它在竞争架构中的广泛采用。我们使用了一个预训练的英文到加纳语 Twi 的翻译变压器模型来探索这一重要架构的效能和其他特性。
NLP 的迁移学习的早期探索侧重于与计算机视觉的类比,而计算机视觉在此方面已经成功使用了一段时间。其中一个模型——SIMOn——采用了字符级卷积神经网络(CNNs)结合双向 LSTM 用于结构语义文本分类。SIMOn 代表 本体建模的语义推理。它是在 DARPA 的数据驱动模型发现(D3M)⁵ 计划中开发的,该计划是为了自动化数据科学家面临的一些典型任务。它展示了与计算机视觉中使用的方法直接类似的 NLP 迁移学习方法。该模型学到的特征还被证明对无监督学习任务也有用,并且在社交媒体语言数据上表现良好,这些数据可能有些特殊,与维基百科和其他大型基于书籍的数据集上的语言非常不同。列类型分类被用作该建模框架的说明性示例。
作为提醒,计算机视觉中微调的启发式大致如下:
-
随着目标域中的数据越来越多,阈值从输出中移动(并朝向输入)。在阈值和输出之间的参数被解冻并进行训练,而其余参数保持不变。这是由于增加的数据量可以有效地用于训练更多的参数,而否则无法完成。
-
另外,阈值的移动必须远离输出并朝向输入,因为这样可以保留编码靠近输入的通用特征的参数,同时重新训练更靠近输出的层,这些层编码源域特定特征。
-
此外,当源和目标高度不同时,一些更具体的参数/层可以完全丢弃。
早期嵌入方法(如 word2vec)的一个主要弱点是消歧义 - 区分一个词的各种用法,这些用法根据上下文可能有不同的含义。这些词在技术上被称为同形异义词,例如,duck(姿势)与 duck(鸟)和 fair(集会)与 fair(公平)。来自语言模型的嵌入 - 在流行的Sesame Street角色之后缩写为 ELMo - 是最早尝试开发单词的上下文化嵌入的方法之一,使用双向长短期记忆网络(bi-LSTMs)。ELMo 可以说是与正在进行的 NLP 迁移学习革命相关联的最流行的早期预训练语言模型之一。它与 SIMOn 具有许多架构相似之处,后者由字符级 CNNs 和 bi-LSTMs 组成。这个模型中一个词的嵌入取决于其上下文,ELMo 通过被训练来预测单词序列中的下一个单词来实现这一点。大量数据集,如维基百科和各种图书数据集,被用来训练这个模型。我们将 ELMo 应用于一个说明性的示例问题,即假新闻检测,作为一个实际演示。
通用语言模型微调(ULMFiT)进一步提出了一种方法,为任何特定任务微调基于神经网络的语言模型。该框架介绍并演示了一些关键的技术和概念,以更有效地适应预训练语言模型的新设置。这些包括区分性微调和渐进解冻。区分性微调规定,因为语言模型的不同层包含不同类型的信息,它们应该以不同的速率进行微调。渐进解冻描述了一种逐渐地以渐进方式微调更多参数的过程,目的是减少过拟合的风险。ULMFiT 框架还包括在适应过程中以独特方式改变学习率的创新。我们使用 fast.ai 库对这些概念进行了数值上的说明。
-
OpenAI 的生成式预训练变压器(GPT)修改了变压器的编码器-解码器架构,以实现 NLP 的可微调语言模型。它丢弃了编码器,保留了解码器及其自注意子层。它是以因果建模目标进行训练的——预测序列中的下一个词。它特别适用于文本生成。我们展示了如何使用 Hugging Face 的 transformers 库快速使用预训练的 GPT-2 模型进行文本生成,该库在本书中早已介绍过。
-
从转换器的双向编码器表示(BERT)可以说是相反的,通过保留编码器并丢弃解码器来修改转换器架构,还依赖于掩码词语,然后需要准确预测作为训练度量的这些词语。更具体地说,它是以掩码建模目标训练的——填补空白。此外,它还通过下一个句子预测任务进行训练——确定给定句子是否是目标句子之后的一个合理的跟随句子。虽然不适用于文本生成,但该模型在其他一般语言任务(如分类和问题回答)上表现非常好。我们将其应用于问题回答和文档分类的两个重要应用。文档分类用例是垃圾邮件检测。我们还展示了它在填补空白和检测一个句子是否是另一个句子的合理的下一个句子方面的应用。
-
mBERT 模型,代表“多语言 BERT”,实际上是同时在 100 多种语言上预训练的 BERT。自然地,这个模型特别适合跨语言迁移学习。我们展示了多语言预训练权重检查点如何有助于为原本未包含在多语言训练语料库中的语言创建 BERT 嵌入。BERT 和 mBERT 都是由 Google 创建的。
-
在所有这些基于语言模型的方法——ELMo、ULMFiT、GPT 和 BERT 中——都显示出生成的嵌入可以用相对较少的标记数据点进行特定下游 NLP 任务的微调。这解释了 NLP 社区对语言模型的关注:它验证了它们诱导的假设集通常是有用的。
我们还介绍了一些关于已覆盖的深度 NLP 迁移学习建模架构的适应策略。换句话说,针对预训练架构,如 ELMo、BERT 或 GPT,如何更有效地进行迁移学习?我们专注于参数效率,目标是在尽可能减少参数的情况下产生一个性能损失最小的模型。这样做的目的是让模型更小、更容易存储,从而更容易在智能手机等设备上部署。或者,智能的适应策略可能仅仅是为了在一些困难的转移情况下达到可接受的性能水平。我们介绍的适应策略有:
-
我们探索的第一种适应策略是前面提到的 ULMFiT 技术,即逐步解冻和区分微调,使用的是 fast.ai 库。
-
然后我们探索了被称为知识蒸馏的模型压缩方法,因为它最近在 NLP 领域显赫一时。这个过程本质上试图通过显著更小的学生模型来模拟更大教师模型的输出。特别是,我们使用 transformers 库中知识蒸馏方法 DistilBERT 的实现来证明通过这种方式可以将 BERT 模型的大小减少一半以上。
-
我们接触到的下一个适应策略围绕着两个想法,旨在创建更有利于更大词汇量和更长输入长度的基于变压器的语言模型。第一种方法涉及巧妙的因式分解,或者将一个更大的权重矩阵分解为两个较小的矩阵,允许你增加一个维度而不影响另一个维度。第二种想法涉及跨所有层共享参数。这两种策略是 ALBERT(A Lite BERT)方法的基础。我们使用 transformers 库中的实现来亲身体验这一方法。
因此,我们基于多任务学习的想法,即模型被训练以同时执行多种任务,并产生更具有普遍性的模型。当面临转移场景时,我们没有足够的训练数据来在给定任务上进行微调时,为什么不在多个任务上进行微调?讨论这个想法给我们提供了一个很好的机会来介绍通用语言理解评估(GLUE)数据集,这是一组代表人类语言推理的几项任务的数据集,例如检测句子之间的相似性,问题之间的相似性,释义,情感分析和问题回答。我们展示了如何使用这个数据集快速利用 transformers 库进行多任务微调。这个练习还演示了如何类似地在来自这些重要问题类别之一的自定义数据集上微调来自 BERT 系列的模型。
我们还建立在领域自适应的思想上,特别是源域和目标域的相似性对于迁移学习的有效性起着至关重要的作用。更大的相似性通常意味着一般情况下更容易进行迁移学习。当源域和目标域之间相差太大时,可能无法在单一步骤中执行该过程。在这种情况下,可能会使用顺序自适应的想法将所需的整体迁移分解为更简单、更易管理的步骤。举例来说,我们首先将一个“填空”目标预训练的 BERT 逐步适应到一个低资源的句子相似度检测场景中,再适应到一个数据丰富的问题相似度场景中。实验中两个场景的数据都来自 GLUE 数据集。
我们探讨的最终适应策略是使用所谓的适应模块或适配器。这些是仅在预训练神经网络的层之间具有少量参数的新引入的模块。对于新任务微调这个修改过的模型只需要训练这几个额外的参数。原始网络的权重保持不变。与微调整个模型相比,通常只添加了 3-4% 的额外参数时往往几乎没有性能损失。这些适配器也是模块化的,并且容易在研究人员之间共享。我们使用了 AdapterHub 框架来加载其中一些适配器,并展示它们如何用于将通用 BERT 模型适应为在情感分类任务上表现良好的模型。
11.2 其他新兴研究趋势
在整本书中,我们试图强调,在诸如 NLP 的迁移学习这样的快速发展领域中,像这样一本单独的书籍完全涵盖每种架构或创新是不可能的。相反,我们采取的方法是专注于我们认为是基础的架构和技术。未来的创新很可能在某种程度上是从这些架构和技术中派生出来的,因此读者可能通过自己的一些努力来学习它们。为了进一步促进这一点,我们将这一部分重点放在了我们在这本书中没有涵盖但在该领域中已经有些影响的各种研究趋势的简要讨论上。我们尽可能将它们置于我们已经涵盖的内容的背景中,以便您在需要时更轻松地学习这些主题。
我们首先通过概述 RoBERTa⁷——Robustly Optimized BERT Approach——来开始这个练习,它采用了一些优化技巧来提高 BERT 的效率。
11.2.1 RoBERTa
所讨论的研究试图复制 BERT,同时特别关注各种训练超参数和设置以及它们对结果的影响。总的来说,观察到通过谨慎的设计选择可以显著改善原始 BERT 的性能。这样的选择之一是去除下一句预测(NSP)任务,同时保留掩码语言建模(MLM)— 填空— 任务。换句话说,他们发现 NSP 会降低下游任务的性能,并显示去除它是有益的。其他设计选择包括在训练过程中使用较大的学习率和小批量。它是由我们在本书中介绍的 Hugging Face 的 transformers 库中实现的。
接下来,我们将看看迄今为止开发的最大语言模型之一 — GPT-3,最近引起了很多关注,并在 NeurIPS 2020 虚拟研究会议(2020 年 12 月)上获得了最佳论文奖。
11.2.2 GPT-3
您可能还记得我们的报道,GPT 模型经历了几次迭代 — GPT、GPT-2,最近是 GPT-3。在撰写时,GPT-3 恰好是拥有 1750 亿参数的最大的预训练语言模型之一。它的前身 GPT-2 拥有 15 亿参数,在发布时也被认为是最大的,仅仅在前一年发布。在 2020 年 6 月发布 GPT-3 之前,最大的模型是微软的图灵 NLG,拥有 170 亿参数,并于 2020 年 2 月发布。这些指标的进展速度之快令人震惊,这些记录往往很快就会被打破。为了比较,这些参数爆炸在图 11.2 中有所体现。
图 11.2 模型参数数量随时间增长的趋势。如图所示,模型大小的爆炸趋势似乎在加速,其中最近的一个进步 — GPT-3 — 表示了 10 倍的增长因子。
如图所示,GPT-3 相比之前最大的图灵 NLG 增长了超过 10 倍,这是一次超越以往进步的飞跃。实际上,一种称为 Switch Transformer 的架构,通过为不同的输入分配单独的 transformer 块部分利用了稀疏性,并声称在 2021 年 1 月达到了 1 万亿参数的规模。由于在撰写时仍在进行同行评审,我们没有在图 11.2 中包含这种架构。然而,很明显,这种模型大小增长的趋势似乎正在加速。
在 GPT-3 论文中,作者展示了这个巨大的模型可以在很少的样本情况下执行广泛的任务。例如,只需看几个示例翻译,它就可以被启动以将一种语言翻译成另一种语言,或者只需看几个示例垃圾邮件,即可检测垃圾邮件。 事实上,一些意想不到的应用,例如从编码的描述中编写代码,已经被广泛报道。目前,该模型尚未由 OpenAI 作者发布给公众,只有通过邀请和付费 API 向少数早期采用者提供。 OpenAI 限制接入的理由是监控使用,从而限制这种技术的任何潜在有害应用。 GPT-3 的早期采用者是一款名为 Shortly 的应用程序,它为创意写作提供了 GPT-3 访问权限,任何人都可以以少量费用尝试。
此外,一个最近更小但功能强大的 GPT-3 开源替代品已经在 transformers 库中可用:EleutherAI 的 GPT-Neo。该组织旨在构建一个与全尺寸 GPT-3 相当的模型,并在开放许可下向公众提供。他们的存储库中提供了不同大小的模型,您也可以使用 Hugging Face 托管的推理 API,通过浏览器测试这些模型。我们还提供了伴随 Kaggle 笔记本,演示了我们在第七章中进行的练习的运行情况。通过检查,您应该会发现其性能更好,但自然而然地,成本也更高。 (最大型号的重量超过 10 GB!)
关于 GPT-3 工作的一个重要事项是,作者本人在论文中认识到,使语言模型更大的好处已接近极限。即便在极限下,处理某些类型的任务(例如关于对常识物理的理解的文本生成)的性能仍然较差。因此,虽然它确实代表了一项重要的技术突破,但在建模方法(而不是简单地扩大模型规模)上的创新必须成为前进的道路。
接下来,我们将看一组旨在改善基于 transformer 的模型在更长输入序列上的性能的方法。这很重要,因为香草 transformer 模型会随着输入长度呈平方级别的运行时间和内存使用。
11.2.3 XLNet
XLNet¹³是在类似早期模型 Transformer-XL¹⁴的基础上构建的,旨在更好地处理更长的输入序列。其中一个关键组成部分的想法是因果语言建模(CLM),我们在讨论 GPT 时已经讨论过,它涉及到预测序列中下一个词的经典语言建模任务。请注意,此方法中的未来标记已被屏蔽。XLNet 论文的作者等效地将其称为自回归语言建模。XLNet 的另一个关键组成部分是对输入序列的所有可能排列执行 CLM。这个想法有时被称为排列语言建模(PLM)。通过结合 PLM 和 CLM,实现了双向性,因为所有标记都有机会在某个排列中作为过去标记被包含。XLNet 和 Transformer-XL 都没有序列长度限制,并且由 Hugging Face 的 transformers 库实现。
有了这种对 XLNet 的看法,让我们继续考虑 BigBird¹⁵,这是一种引入稀疏注意机制的创新,以实现更高的计算效率。
11.2.4 BigBird
BigBird 通过引入一种稀疏注意机制将传统基于 Transformer 的模型的二次依赖关系减少到线性,该机制被证明能够近似并保持原始完整注意力的性质。与一次应用完整注意力到整个输入序列不同,稀疏注意力逐个查看序列标记,允许它更加智能地且舍弃一些连接。可以处理长达传统基于 Transformer 模型处理的八倍长度的序列,且可在类似硬件上处理。它是由 Hugging Face 的 transformers 库实现的。
接下来,我们将介绍 Longformer¹⁶,这是对 Transformer 传统完整自注意力的另一项创新,能够更好地随着输入长度的增加而扩展。
11.2.5 Longformer
Longformer 是针对传统 Transformer 注意力的二次缩放的又一尝试。这里的创新是将局部窗口注意力与全局任务导向注意力相结合。局部注意力用于上下文表示,而全局注意力用于构建在预测中使用的完整序列表示。所达到的缩放在输入序列长度方面是线性的,类似于 BigBird。Longformer 是由 Hugging Face 的 transformers 库实现的。
我们接下来介绍 Reformer¹⁷,这是另一种减轻原始自注意力二次缩放的方法。
11.2.6 Reformer
Reformer 引入了两种技术来对抗原始 Transformer 输入长度的二次扩展的计算时间和内存消耗。用局部敏感哈希替换原始的完全自注意力机制,减少了冗余计算和时间复杂度从二次到 O(LlogL)(其中 L 是输入序列长度)。一种称为可逆层的技术允许只存储激活一次。在实践中,这意味着,与为具有N层的模型存储激活N次相比,只使用了一小部分内存。根据N的值,内存节省可能非常大。Reformer 是由 Hugging Face 实现的 transformers 库中的一个模型。
显然,使基于 Transformer 的模型在处理更长的输入长度时表现更好已成为一个元研究趋势。我们在这里可能没有包括所有关于此主题的重要研究,如果你自己深入研究,可能会发现更多。
接下来,我们将谈论最近重新出现的序列到序列建模方法。这些尝试将本书中遇到的各种问题统一到一个文本到文本建模框架中。
11.2.7 T5
你可能还记得在本书中我们讨论过,序列到序列模型在自然语言处理中发挥了重要作用。首次出现在循环神经网络(RNN)模型的背景下,它们也被翻译应用领域在原始 Transformer 架构的背景下所探索。T5,“文本到文本转换 Transformer”,是将各种自然语言处理问题统一到一个序列到序列框架中的尝试。它允许对每个任务应用相同的模型、目标、训练过程和解码过程。处理的问题类别包括从摘要到情感分析和问答等众多领域。英语和罗马尼亚语、德语、法语之间的语言对翻译被包括在训练中。一些代表性的数据转换,使得可以在多种任务上训练单一模型,如图 11.3 所示的翻译和摘要(灵感来源于 T5 论文的图 1)。
图 11.3 T5 是一个序列到序列模型,它采用了一系列转换,使得可以同时在各种任务上训练单一模型、解码过程和目标。它可以被看作是多任务学习的一个有趣变体。
如图所示,任务数据通过在原始文本数据前加上标准任务描述符来转换。训练数据包括 GLUE 和 SuperGLUE 数据集、用于抽象摘要的 CNN/Daily Mail 数据集等。目标是处理包含的多样化的自然语言理解任务,而不修改模型。从这个意义上说,它可以被看作是我们在整本书中一直提到的多任务学习思想的一个有趣变体或迭代。同时学习如此多种任务的包含可能会实现参数共享和生成模型的更好泛化能力。关键是,模型最初是在作者称之为“巨大干净爬行语料库”(C4)的数据集上使用蒙版语言建模或自动编码目标进行训练,然后在上述各种任务上进行微调。基本上,所有标记的 15% 被丢弃,结果被送入输入,而未损坏的输入被送入输出以进行预测。请注意,C4 语料库本质上是目标语言(英语)的互联网,其中过滤掉了色情材料、代码和其他“垃圾数据”。用于训练的模型架构类似于我们在第七章中用于翻译的转换器架构。由所得模型在许多包含的任务上实现了最先进的结果。
除了原始的 T5 模型之外,还开发了一个多语言版本,不足为奇地称为 mT5,通过同时对 101 种语言进行训练。T5 和 mT5 都在 Hugging Face 的 transformers 库中实现。
接下来,我们简要介绍 BART,它与 T5 类似,都是基于转换器的序列到序列建模框架。
11.2.8 BART
BART,即双向自回归转换器,可以被视为 T5 减去单一统一变换的模型,以使未经修改的模型能够应用于各种任务。相反,首先对标准的转换器编码器-解码器架构进行预训练,以通过各种噪声方法重现损坏的输入。这包括蒙版语言建模,如 BERT 和 T5,以及排列语言建模,如 XLNet 等。然后,该模型被修改用于各种任务,例如 SQuAD、摘要等,并针对每个任务分别进行微调,类似于我们对传统 BERT 所做的操作。这个模型在语言生成任务中表现特别好,例如摘要、翻译和对话。同时也开发了一个多语言版本 mBART,通过同时对 25 种语言进行训练而获得。BART 和 mBART 都在 Hugging Face 的 transformers 库中实现。
在下一个小节中,我们将审视一种最新的跨语言模型,它不仅仅是同时在多种语言上进行训练,还通过在有并行数据时修改语言建模目标来显式建模跨语言转移。
11.2.9 XLM
XLM,²² 其作者用来指代“跨语言语言模型”,是一个结合单语和并行数据的跨语言学习方法的建模框架。在不同语言上学习得到的单语嵌入可以使用已知数值表示的小词汇表进行对齐。如果有并行数据可用,作者提出了一种他们称之为翻译语言建模(TLM)的方法,并同时利用它进行跨语言学习。本质上,这涉及将并行数据的连接序列应用掩码语言建模,在两种语言的连接序列的各个部分中,让一些单词消失并预测它们。
在跨语言学习任务中观察到显著的改进。它还激发了许多类似模型的产生,特别是 XLM-R,²³ 它将 XLM 的思想与 RoBERTa 的思想结合起来,以提高性能。XLM 和 XLM-R 都在 Hugging Face 的 transformers 库中实现。
最后,我们简要谈到了一种在书中遇到的重要问题数据类别——表格数据的专门模型。
11.2.10 TAPAS
在第五章和第六章中,我们讨论了 SIMOn 方法及其对表格数据类型分类的处理——这是数据科学家通常会遇到的一个重要问题类别。TAPAS²⁴ 是尝试将基于变换器的模型的建模优势扩展到这一重要问题类别的一种尝试,通过显式地为表格数据中的问答建模和专门化。TAPAS 代表表格解析器。在第八章中,我们讨论了将 BERT 应用于问答任务。结果专门化模型的输出是输入上下文段落中感兴趣问题的潜在答案的开始和结束位置。除此之外,TAPAS 还学会检测表格中哪个单元可能包含可以从中提取答案的上下文段落,且起始和结束索引类似。与本节中讨论的其他大多数模型一样,该模型在 Hugging Face 的 transformers 库中实现。
这标志着我们对本书中尚未有机会详细分析的近期工作的概述之旅的结束。这些模型架构大多可以通过与我们在 transformers 库中使用 BERT 和 DistilBERT 非常相似的代码来使用。
在下一节中,我们将尝试对这个领域下一步可能的走向做出有根据的猜测——鉴于当前和新兴的研究趋势,哪些主题可能保持或变得流行。
11.3 NLP 中转移学习的未来
在本节中,我们尝试通过预测该领域即将出现的形态来推断前面两节描述的趋势。
对过去两节的批判性分析揭示了两个可以说是正交的元趋势——一个是将模型尽可能地变大,另一个是开发更高效的更大模型的推动。
GPT-3,目前我们观察到的参数数量迈出的最大一步——是以前的 10 倍,最初引起了一些研究人员对研究公司开始将重点放在规模而不是巧妙建模上的担忧。然而,正如我们在上一节讨论的那样,扩大模型的局限性很快变得明显,GPT-3 论文的作者们承认已经达到了可能的极限。考虑到 GPT-3 目前仅通过有限的付费 API 可用,我们可以预期空间中的其他参与者将尝试很快构建更大的模型,因为他们有货币激励这样做(我们已经提到正在进行同行评审的拥有万亿参数的 Switch Transformer)。这场竞赛很可能最终将导致谷歌和/或 Facebook 发布一个类似的模型,这很可能会推动 GPT-3 完全开源(类似的情况在 GPT-2 历史上已经发生过)。除此之外,我们预计会有更多资源开始致力于实现类似性能的更有效方法。
在 NLP 迁移学习的即将到来的有趣问题中,大多数可能都在被一些人称为TinyML的运动中。这可以被定义为一个将模型大小缩小到可以适应较小硬件的通用目标。我们在第九章演示了一个例子,即通过 DistilBERT 等方法可以将 BERT 的大小大致缩小一半,而性能损失很小。我们在第十章取得了类似的成就的另一种方法是 ALBERT,它实现了模型大小的 90%缩减。现在全球大部分人口都拥有智能手机,可以运行这些先进模型的较小版本。这给物联网(IoT)等领域带来的机会是巨大的,其中设备形成智能网络,每个节点都能独立地执行复杂功能。尽管今天许多手机应用程序可能都包含翻译和其他工具的服务器后端,用于进行实际的翻译和其他计算,但在没有互联网连接的情况下在智能手机上本地运行这些算法的能力正在成为更可行和更普遍的范式。我们预计在未来几年内,在使 BERT 及其衍生产品更小和更具参数效率的努力将继续如火如荼。
另一个你可能从前一节中注意到的趋势是对跨语言模型的日益关注。事实上,过去一年全球对所谓的“低资源”语言的方法的投资有所增加。我们在第七章中通过一个例子提到了这一点,当时我们使用了一个变压器架构将低资源的西非语言特威(Twi)翻译成了英语。许多流行的经济模型预测,非洲市场正在出现一个日益重要的消费者群体,这很可能是引起对这一领域突然兴趣和投资的至少一个推动因素。对于许多低资源语言来说,应用我们讨论的所有方法的初始障碍往往是数据的可用性。因此,我们可以预期在接下来的一年左右,适当的多语言数据开发将受到很多关注,随后将进行对语言特定方法的深入研究。值得关注的地方,特别是涉及非洲语言的地方,包括 NLP Ghana,Masakhane,EthioNLP,Zindi Africa,AfricaNLP 和 Black in AI。
语音是另一个即将迎来转折时刻的 NLP 研究前沿。直到最近,自动语音识别模型,将语音转录成文本,需要大量的平行语音文本数据才能取得良好的结果。Facebook 最近的一种架构 Wav2Vec2 展示了,同时在许多语言上对语音进行预训练可以极大地减少所需的平行数据量。这类似于我们在本书中探讨的文本中使用的 mBERT 的功能。Wav2Vec2 模型已经在 transformers 库中提供,并且可以通过只使用几个小时的标注语音数据对新语言进行微调。我们预计这将在接下来的一年内首次推动多种语言的语音识别工具的开发。此外,我们预计类似的事情也将在不久的将来出现在另一个方向上:文本到语音,也就是从文本生成语音。
在第一章中,我们描述了 NLP 中的迁移学习是如何受到计算机视觉的进展的启发的。有趣的是,最近 NLP 迁移学习的进展似乎又激发了计算机视觉的进一步发展。一个具体的例子是 DALL-E,这是一个在文本描述 - 图像对上训练的 GPT-3 的版本,它已经学会了从文本提示中生成图像。一个更广泛的趋势是构建上下文情景的对象嵌入,试图从场景中其他可观察到的对象预测缺失的对象,类似于 BERT 和类似的遮蔽语言模型所使用的填空词目标。
另一个最近似乎越来越受到关注的研究问题是:这些模型的环境和道德影响是什么?在最近的研究高潮初期,研究人员似乎满足于发布只改善技术指标的模型,但随着时间的推移,这个领域开始重视对潜在道德影响的详细探讨。与之相关的是对可解释性的提高兴趣:我们能否真正解释模型是如何做出决定的,以确保它不会歧视?我们将在下一节进一步探讨这些道德问题。
11.4 道德和环境考虑
你可能还记得我们在第五章和第六章讨论关于假新闻检测的问题时提出了所谓的假新闻是什么是一个有争议的观点。如果在数据标签的质量上不加注意,那么准备训练数据标签的人身上植入的偏见很可能会转移到分类系统上。这是我们首次遭遇到了在部署这些模型到可能会显著影响人类生活的情况下,充分意识到潜在局限性的重要性。
当我们在第八章用 Jehovah's Witnesses 准备的 JW300 数据集对 mBERT 进行微调时,我们发现它会以有偏见的方式填补空白。当我们试图预测一个基本名词“学校”时,它会提供像伊甸园这样的词作为合理的补充。这表明了强烈的宗教偏见,这是我们在旅程中第二次被提醒到,盲目地将这些模型应用于某些数据可能会产生有偏见和意想不到的结果。
在这一部分,我们将从更广泛的角度讨论这个问题,考虑到可能需要放在从业者脑后的道德和环境考虑因素。这是一个近来受到越来越多关注的话题,但在机器学习领域并不算新鲜。
早期关于偏见的知名机器学习研究可预见地发生在计算机视觉领域。具有里程碑意义的作品,“性别和肤色”,³⁴研究了商业性别分类系统在种族和性别维度上的准确性。它发现,与较浅肤色的男性相比,这些系统在较深肤色的女性身上表现不佳,绝对百分比高达 35 个百分点。这对少数族裔社区有着巨大的实际影响,在一些地区可能会受到一些自动计算机视觉系统的监控。错误的分类或检测可能意味着错误的逮捕,即使被清除,也可能意味着在最脆弱的社区中失去工作。有多次广泛报道这种情况发生在真实的人身上。一个愤世嫉俗的力量失衡在那些打着“客观”和“科学”旗号的系统背后被揭露出来,在这些系统被开发的更富裕的社区和它们的经济利益主要是被搬走的地方并没有遭受到对较贫困的社区造成的伤害。这项工作及相关研究的影响是巨大的,最近美国国会最近出台了相关的减轻监管措施,可以说这是直接后果。像 IBM 和亚马逊这样的公司也被迫重新审视他们与执法机构分享这些技术的方式,IBM 甚至完全停止了这项服务。
最近对预训练的自然语言处理语言模型存在偏见的担忧也很高。实际上,GPT-3 论文³⁵专门包括了一个研究的部分,涵盖了几个方面,即种族、性别和宗教。最近越来越常见地看到学术文章做这样的研究,这是非常令人鼓舞的。特别是 GPT-3 的研究探讨了模型从训练数据中学到的与各个关注维度的关联。例如,他们发现通常与较高教育水平相关联的职业在填空时更倾向于与男性代词相关联。同样,暗示专业能力的提示更有可能由男性代词和说明者完成。这很可能是模型直接从互联网学到的性别偏见,我们可能无法指望互联网是一个无偏见的信息来源。另一方面,积极的描述词更有可能被“亚洲”和“白人”等词引导的名词以比“黑人”人物高得多的速度分配。同样,模型显然从互联网学到了种族偏见,而对模型的盲目应用只会传播这种偏见。在宗教维度上,“伊斯兰”一词与“恐怖主义”一词相关联,是最有可能的完成之一。作为这种偏见的直接现实影响,考虑一下那位巴勒斯坦人的善意“早上好”Facebook 帖子被错误地翻译为“攻击他们”,并导致了重大的不公平后果。³⁶
预训练的自然语言处理语言模型可能无意中影响贫困社区的另一种方式是通过气候变化。实际上,这些模型最近被发现具有相当大的碳足迹。³⁷, ³⁸虽然发现单个 BERT 模型的一次训练的碳足迹相当于纽约到旧金山的一个普通往返航班,但在微调和超参数优化期间,模型实际上会进行多次训练。如果模型通过 神经架构搜索 部署,其中各种架构超参数被详尽地变化,然后选择性能最佳的模型,研究人员发现单个模型部署的碳足迹相当于五辆普通汽车的寿命。再次强调,这是严重的,特别是因为与这些碳足迹直接相关的气候变化影响最严重的是贫困社区,而这些社区并没有体验到这些模型的直接好处。很明显,在评估这些模型时,这些成本需要纳入考虑。这一认识可以说是驱使该领域朝着更具参数效率的模型发展的力量之一。
预训练语言模型及深度学习普遍存在的一个长期批评是,这些模型往往不太可解释——很难解释模型是如何在特定情景下得出预测的。这与本节早些时候讨论的偏见问题有关——让模型解释其对教育相关联的决策是如何做出的,例如,可以帮助检测这样的决定是否基于种族或性别变量。最值得注意的最近的方法之一是 bertviz,³⁹试图在第七章中探索的注意力可视化基础上进行改进。然而,这仍然没有解决训练数据透明度缺失的问题:语言模型的训练规模如此之大,以至于研究人员几乎无法确保其是无偏的。因此,我们期望看到人们投入时间和精力开发出可以从更小、精心策划的数据集中执行相当的方法。
通过我们对一些应该记住的伦理问题的简要讨论完成后,我们在下一节中提供一些关于如何在这个快速发展的领域中保持时效性的建议。
11.5 保持时效性
正如我们在本章中一再强调的那样,NLP 中的迁移学习方法的状况更新速度很快。本书涵盖的材料应该仅被视为一个平台,用于继续跟踪最新发展。在本节中,我们提供了一些关于如何实现这一目标的基本提示。总的来说,在 Kaggle 和/或 Zindi 平台上参加各种相关竞赛可能是一个处理现实情况的好方法,但数据足够干净且与时俱进。跟踪 arXiv 上的最新论文是必不可少的,尽管新闻和社交媒体的报道可能夸张并且不可靠,但它仍然有助于及早发现有影响力的论文。
11.5.1 Kaggle 和 Zindi 竞赛
在整本书中,我们鼓励您使用 Kaggle 来运行所呈现的各种代码。尽管该平台提供的免费 GPU 计算和易用的设置立即成为其优点,但最大的好处可能直到现在才被明确说明。可以说,Kaggle 平台最强大的方面是可以访问该平台上众多持续进行和归档供后人参考的竞赛。
各种顶级公司面临各种技术挑战,使用该平台来刺激对这些问题的解决方案的研究和开发,通过提供现金奖励,有时可达数千美元的头等奖。这意味着通过跟踪这些竞赛,您可以了解到行业中最紧迫的问题是什么,同时可以访问代表性数据进行即时测试和实验。您可以按主题浏览当前和过去的竞赛,以找到测试任何想法所需的数据——您所需要做的就是将数据集附加到本书中使用的笔记本上,更改一些路径,然后您可能已经准备好产生一些初步的见解了。当然,如果您能够做到这一点,赢得竞赛是很好的,但您从实验、失败和再试中获得的学习价值才是真正无价的。实际上,根据我的经验,一个在排行榜上可能被认为是平庸的竞赛问题解决方案,如果在实践中易于部署和扩展,可能会导致真正的现实影响。我们在附录 A 中提供了一些使用 Kaggle 的具体提示,以帮助初学者入门。
我们还强调了 NLP 中对低资源语言的关注日益增加。因此,重要的是提到 Zindi Africa 平台,该平台提供与 Kaggle 类似的许多功能,但专注于非洲语言和问题。如果您是一位研究人员,想要了解您的方法在这些类型的语言中可能的表现,那么这个平台将是一个寻找相关竞赛和实验数据的好地方。
11.5.2 arXiv
机器学习,以及延伸开来的自然语言处理,可以说是当今最开放的研究领域。除了几个例外,一般来说,结果通常在一旦出现后立即在开放平台arXiv上发表。这使研究团队能够提前对任何发现进行申请,同时进行完善和论文出版手续。这意味着,如果你能找到它的话,最前沿的研究已经对你可用。arXiv由 Google Scholar 存档,因此你可以在那里设定关键词的警报,帮助你及早发现相关的论文。
arXiv平台上传的论文数量巨大,要找到与你相关的最重要的论文可能会有些困难。为了解决这个问题,我建议关注你喜欢的论文的作者在社交媒体上的动态——Twitter 似乎是这一领域的研究人员比较喜欢的平台。关注媒体报道也可能会有所帮助,只要你对所有声明持保留态度。接下来我们会多说几句关于这个问题。
11.5.3 新闻和社交媒体(Twitter)
总的来说,把科学主题的新闻和社交媒体报道看作可能具有煽动性和技术上不可靠是件好事。如果我们考虑一个媒体机构可能与报道技术相关的激励以及通常记者对主题可能没有技术背景的事实,这就说得通了。然而,经过核实的新闻可能是关于特定论文或主题的社区兴奋的一个良好指标,这总是一个需要考虑的好事。
如果你使用像谷歌新闻这样的平台,你可以在你的订阅中设置“语言模型”等主题的警报。你可能会得到很多信息,而且并非所有信息都值得关注。通常,我只会在这些论坛上深入研究一篇论文,只有在我认为一段时间内一直“可靠”的场所出现后,我才会深入研究一篇论文,这使我对这些论点至少经受了一段时间的公开评审产生了信心。GPT-3 的情况正好是个最近的例子——通过谷歌新闻上的这个启发式方法,我立刻就能体会到其影响。
关于社交媒体,Twitter 似乎是机器学习研究科学家的选择平台。事实上,许多人对他们的工作非常开放,并且如果你向他们提问,他们会很乐意在平台上直接回答你。这也是我最喜欢在这个领域工作的原因之一。请随时在@pazunre 上联系我。你最喜欢的作家或科学家可能会在他们的订阅中分享他们最新最喜欢的论文,通过关注他们,你可以直接收到这些信息。在这一领域,你可能会对以下一些受欢迎的账户感兴趣:@fchollet,@seb_ruder 和@huggingface。
除了竞赛、阅读 arXiv 上的论文、追踪新闻和社交媒体,没有什么比使用这些工具解决实际的挑战更好的了。对于许多人来说,这可能意味着在机器学习和/或自然语言处理领域拥有一份工作,并且每天都在解决一个实际的应用。实际的经验是这个领域大多数潜在雇主最重视的。如果你还没有在这个领域获得这样的实际经验,并且希望进入,那么开源项目可能是一个很好的途径——看看 TensorFlow、PyTorch、Hugging Face、NLP Ghana、Masakhane 等等。列表是无穷尽的,有很多有趣的问题可以解决和贡献,同时也可能使每个人受益。
我希望这些提示能帮助你进入你的机器学习和自然语言处理的未来,让你有能力对你的社会产生重大的积极影响。能够与你分享你旅程的一部分是我的荣幸。
11.6 最后的话
这就是了!你做到了——你已经读完了整本书。在写作过程中,我度过了难忘的时光,与许多研究人员互动,讨论思想并克服了许多挑战。我真诚地希望你享受这个旅程,就像我一样。当你带着这些工具改变世界的时候,请记得善待你周围的人,不要伤害生态系统,并且保持警惕,以防技术被滥用。通过与这个领域中一些杰出头脑互动的短暂时间内,我真诚地相信大多数人对于将这些技术突破变成善良的源头都感到兴奋。因此,我每天都迫不及待地关注研究新闻,渴望看到我们集体的人类思维将会产生什么样的惊喜。我只能希望你也能分享一些这种兴奋。
概要
-
你只是在这个迅速发展的领域中旅程的开端;保持竞争优势是一个旅程,而不是一个终点。
-
通过学习这本书所掌握的技能,使你处于一个良好的位置,能够通过持续的努力保持更新。
-
我们涵盖的一些关键的基础预训练迁移学习启用的语言建模架构包括 Transformer、BERT、mBERT、ELMo 和 GPT。
-
将这些更大的模型变得更小、更高效的愿望导致了像 ALBERT、DistilBERT 和 ULMFiT 这样的架构/技术的发展,我们也进行了介绍。
-
新兴的体系结构是前述模型的后代,书中没有详细介绍但你应该知道的,包括 BART、T5、Longformer、Reformer、XLNet 等等。
-
在实践中部署这些模型时,意识到它们可能带来的潜在的道德和环境影响是很重要的。
-
近期对道德和环境影响的关注,以及希望将模型能力应用于智能手机和物联网,很可能会在不久的将来继续推动更高效的变压器架构的开发。
-
Sharir O.等,“训练 NLP 模型的成本:简要概述”,arXiv(2020)。
-
S.J. Pan 和 Q. Yang,“迁移学习概述”,IEEE 知识与数据工程交易(2009)。
-
S. Ruder,“自然语言处理的神经迁移学习”,爱尔兰加尔韦国立大学(2019)。
-
D. Wang 和 T. F. Zheng,“语音和语言处理的迁移学习”,2015 年亚太信号与信息处理协会年度峰会和会议(APSIPA)论文集。
-
Lipmann Richard 等,“DARPA 数据驱动模型发现(D3M)计划概述”,第 29 届神经信息处理系统(NeurIPS)会议论文集(2016)。
-
Yinhan Liu 等,“RoBERTa:稳健优化的 BERT 预训练方法”,arXiv(2019)。
-
Tom B. Brown 等,“语言模型是少样本学习者”,NeurIPS(2020)。
-
W. Fedus 等,“Switch 变压器:用简单高效的稀疏性扩展到万亿参数模型”,arXiv(2021)。
-
Z. Yang 等,“XLNet:用于语言理解的广义自回归预训练”,NeurIPS(2019)。
-
Z. Dai 等,“Transformer-XL:超越固定长度上下文的关注语言模型”,ACL(2019)。
-
M. Zaheer 等,“BigBird:更长序列的变压器”,arXiv(2020)。
-
I. Beltagy 等,“Longformer:长文档变压器”,arXiv(2020)。
-
N. Kitaev 等,“Reformer:高效变压器”,arXiv(2020)。
-
C. Raffel 等,“探索统一文本到文本变压器的迁移学习极限”,arXiv(2020)。
-
L. Xue 等,“mT5:大规模多语言预训练文本到文本变压器”,arXiv(2019)。
-
M. Lewis 等,“BART:用于自然语言生成、翻译和理解的去噪序列到序列预训练”,arXiv(2020)。
-
Y. Liu 等,“用于神经机器翻译的多语言去噪预训练”,arXiv(2020)。
-
G. Lample 和 A. Conneau,“跨语言语言模型预训练”,arXiv(2019)。
-
A. Conneau 等,“规模化的无监督跨语言表示学习”,arXiv(2019)。
-
J. Herzig 等,“TaPas:通过预训练进行弱监督表解析”,arXiv(2020)。
-
A. Dosovitskiy et al, “An Image Is Worth 16x16 Words: Transformers for Image Recognition at Scale,” arXiv (2020).
-
J. Builamwini and T. Gebru, “Gender Shades: Intersectional Accuracy Disparities in Commercial Gender Classification,” Journal of Machine Learning Research 81 (2018).
-
Tom B. Brown et al., “Language Models Are Few-Shot Learners,” NeurIPS (2020).
-
E. Strubell et al., “Energy and Policy Considerations for Deep Learning in NLP,” ACL (2019).
-
E. Bender et al., “On the Dangers of Stochastic Parrots: Can Language Models Be Too Big?” FAccT (2021).
-
Jesse Vig, “A Multiscale Visualization of Attention in the Transformer Model,” ACL (2019).
附录 A:Kaggle 入门指南
Kaggle 平台为数据科学和机器学习初学者提供了一个学习基本技能的绝佳途径。通过恰当地利用该平台,你有机会在各种数据集上练习各种问题,并与其他机器学习工程师展示和讨论你的工作。这有可能帮助你扩展你的专业网络。重要的是,该平台允许你在云中直接运行 Python 笔记本,这可以显著消除初学者的系统设置障碍。它还每周提供有限的免费 GPU 计算。这进一步使得本书讨论的工具和方法更加民主化。在全书中,我们鼓励你使用 Kaggle 来运行所呈现的代码。
另一个工具——Google Colab——同样提供免费的 GPU 计算,同时与 Google Drive 集成。然而,如果你必须选择一个工具,我会推荐 Kaggle,因为它具有社交性质,可以访问数据集、讨论和竞赛,这些都是非常宝贵的学习资源。当然,在实际情况下,大多数工程师可能会在某个时候同时使用两者,例如为了增加每周的免费 GPU 配额。
在这个附录中,我们试图提供一个简要的入门指南,可以帮助初学者逐步了解 Kaggle 的各种功能。我们将其分为两个部分。首先讨论 Kaggle 内核的概念以运行笔记本,然后查看竞赛、相关讨论和 Kaggle 博客功能。
A.1 Kaggle 内核提供的免费 GPU
如前所述,你可以在 Kaggle 免费使用云中直接运行 Python 代码。这些云笔记本有时被称为Kaggle 内核。在撰写本文时(2021 年 1 月),Kaggle 每周提供约 36 小时的 GPU 时数,你可以为任何你认为可能需要的笔记本启用它。我们将通过演示如何开始,逐步介绍一个对 Python 初学者有用的简单场景。
假设你是一个初学者,并且有兴趣使用这些内核学习基本的 Python 语法。一个很好的开始地方是访问www.kaggle.com/kernels,然后搜索“Python 教程”。这个搜索结果可能如图 A.1 所示。
图 A.1 开始学习 Kaggle 内核和启动相关笔记本来学习新知识的最佳地点。前往www.kaggle.com/kernels,然后搜索你感兴趣的主题。在图表中,我们展示了这样一个查询的结果列表,供初学者开始学习 Python 时参考。选择最合适的结果继续。或者使用新笔记本按钮创建一个新笔记本。
如图所示,搜索将返回一系列结果,您可以选择最符合您需求的一个。在这种情况下,初学者可能希望教程直接以 NLP 为重点开始,考虑到书籍的内容,因此可能会选择突出显示的教程笔记本。点击它将会显示相关的渲染笔记本,其中代表性视图如图 A.2 所示。
图 A.2 渲染笔记本的视图,突出显示可以执行的一些关键操作
请注意,所示的视图代表了您在单击书籍存储库中我们的伴随笔记本链接之一时将遇到的第一个视图。¹ 如图所示,笔记本已呈现,这意味着即使不运行代码,您也可以滚动并查看所有代码的代表性输出。
要运行代码,请点击复制并编辑按钮以创建自己版本的笔记本。生成的笔记本将具有相同的依赖项——在 Kaggle 环境中预安装的 Python 库版本和用于生成代表性笔记本输出的库。请注意,如果您点击了图 A.1 中的新笔记本按钮而不是选择复制现有笔记本,则依赖项将是 Kaggle 指定的最新依赖项。因此,您可能需要修改原始代码以使其正常工作,这会增加难度。要完成复制和编辑,或分叉,过程,将要求您提供登录信息。您可以注册您的电子邮件地址,也可以直接使用谷歌等社交账号登录。
为了精确复制我们为本书的伴随笔记本所使用的 Kaggle 环境,我们在伴随书籍存储库中包含了需求文件。请注意,这些需求文件仅用于在 Kaggle 笔记本上复制 Kaggle 环境的目的。如果您试图在本地计算机上使用它们,根据本地架构的不同,您可能会遇到额外的问题,并且可能需要对其进行修改。我们不支持这种模式,如果您正在追求它,请仅将需求文件用作指南。还要记住,并非每个列出的要求都需要在您的本地安装中使用。
单击“复制并编辑”将带您进入主工作区,如图 A.3 所示。如图所示,您可以通过左上角的按钮运行当前选择的单元格,也可以运行笔记本中的所有代码。在右侧面板上,您可以启用或禁用您的互联网连接。下载数据或安装软件包可能需要互联网连接。此右侧面板还包含在当前笔记本中启用 GPU 加速的选项,您需要在合理的时间内训练神经网络。您还将看到当前附加到笔记本的数据集,并且可以单击其中任何一个以转到数据集的描述。单击“添加数据”将打开一个搜索查询框,您将能够按关键字搜索感兴趣的数据集,以添加到当前笔记本。对于本书的所有伴侣笔记本,必要的数据已经附加到笔记本中。
图 A.3 使用 Kaggle 内核时的主工作区。在左上角,是运行笔记本的按钮。在右上角,是共享、保存、重新启动和关闭笔记本的选项。右侧面板包含连接到互联网的选项(用于安装软件包或下载数据)、启用/禁用当前笔记本的 GPU 加速以及添加数据。
在右上角,您可以选择笔记本的共享设置——根据您的项目需要,您可以将笔记本设置为仅自己可见,与其他用户私下共享,或者对外公开。我们所有的伴侣笔记本都是公开的,这样任何人都可以访问,但您可以将它们的分支设置为私有。重要的是,也在右上角,选择保存版本将弹出对话框以保存您的工作,如图 A.4 所示。
图 A.4 笔记本的保存选项。您可以提交代码并让其后续非交互式运行以供以后检查,也可以直接快速保存代码和当前输出。
正如图所示,有两种保存模式。快速保存模式将在版本名称文本博客中保存当前代码和输出。如果当前输出需要几个小时来生成,这将是正确的选择。保存并运行所有选项将保存代码并在后台非交互式地运行它。当运行长时间的训练作业时,例如五六个小时时,这特别有用。您可以关闭会话和所有窗口,并在需要检查结果时随时返回。最近运行/保存的检查通常可以在个性化的 URL www.kaggle.com//notebooks 上进行,其中 是您的用户名。对于我的用户名 azunre,此页面的视图如图 A.5 所示。
图 A.5 近期运行/保存的检查通常可以在个性化 URL www.kaggle.com//notebooks 进行,其中 是您的用户名(此处显示的是我的用户名 azunre)。
我们已经介绍了您需要了解的本书练习的主要特性。我们还没有涵盖的许多其他特性,Kaggle 经常会添加更多。通常,快速的谷歌搜索和一些坚持和实验的愿望就足以弄清楚如何使用任何这样的特性。
在下一节中,我们简要讨论 Kaggle 比赛。
A.2 比赛、讨论和博客
面对技术挑战的领先企业利用 Kaggle 通过为顶级创新提供重大奖金来刺激解决方案的研究和开发。让我们通过选择任何 Kaggle 页面左侧面板上可见的奖杯图标来检查 Kaggle 比赛页面,如图 A.6 所示。
图 A.6 通过选择任何 Kaggle 页面左侧面板上的奖杯图标进入比赛页面。我们可以看到一个比赛提供了总共 $100,000 的奖金——这个问题很可能对该行业非常有价值,以激励这样的投资!
您可以追踪这些比赛,了解行业中最紧迫的问题,同时可以访问基础数据以进行即时测试和实验。您可以按主题浏览当前和过去的比赛,以找到测试您可能有的任何想法的数据。您所需要做的就是将数据集附加到上一节介绍的笔记本中,更改一些路径,然后您应该准备产生一些初步的见解了。当然,如果您能够做到,赢得比赛对于获得金钱奖励来说是很棒的,但是您从实验、失败和再次尝试中获得的学习价值才是真正无价的。事实上,在我看来,通过排行榜的位置可能被认为是中等的比赛问题的解决方案,如果在实践中更容易部署和扩展,那么可能会导致实际影响。这是我个人关心的事情,因此我倾向于将精力集中在对我最感兴趣但我了解最少的问题上,以获取最大的学习价值。
点击任何比赛都会打开一个专门页面,在这里您可以浏览其描述、数据、排行榜,以及重要的是,图 A.7 中显示的“讨论”功能。
图 A.7 讨论功能使您能够与 Kaggle 社区的其他成员就您感兴趣的特定主题进行交流。聊天,扩展您的网络!
正如您可能在图 A.7 中看到的那样,这是一个与问题相关的讨论论坛。人们发布提示和入门笔记,提出重要问题,甚至可能由竞赛组织者回答。例如,如果您遇到特定竞赛数据的任何问题,您很有可能在这里找到答案。许多比赛提供最有价值贡献的奖励——通常是通过点赞来衡量的——这激励人们提供帮助。获胜者经常发布他们的解决方案,有时甚至作为您可以直接重新利用的笔记本。您甚至可以在此建立未来挑战的团队,并建立友谊。参与社区,并回馈一些您从中得到的东西,您可能会学到比其他方式更多。归根结底,科学仍然是一项社会活动,这使得 Kaggle 的这个功能尤为宝贵。
最后,Kaggle 在medium.com/kaggle-blog上运行一个博客。大型比赛的获胜者经常在这里接受采访,分享他们可以与他人分享的技巧。教程经常发布在各种关键主题上。及时了解这些内容,以确保了解数据科学中最新的新兴研究趋势。
我们希望这个附录是一个有用的练习,并让您跟上了进展。继续前进,Kaggle!
附录 B:初级深度学习工具介绍
本附录涵盖
-
介绍本书中使用的五种基本算法和软件工具
-
对训练神经网络使用的算法——随机梯度下降进行概述
-
以 TensorFlow 开始进行神经网络建模
-
以 PyTorch 开始进行神经网络建模
-
对较高级别的神经网络建模框架 Keras、fast.ai 和 Hugging Face transformers 进行概述
在本附录中,我们试图对本书中使用的一些基本工具和概念提供一个简要的入门。对这些工具的简要介绍并不绝对必要,以便理解并充分从本书中受益。不过,阅读它们可以帮助一个新的深度学习领域的人快速融入,并且对他们可能是最有用的。
具体来说,我们首先向读者介绍了我们目前经历的深度学习革命背后的基本算法。当然,这就是用于训练神经网络的随机梯度下降算法。接着我们介绍了两种基本的神经网络建模框架,PyTorch 和 TensorFlow。然后我们介绍了这两个建模框架之上构建的三种工具,以提供一个更高级别的接口:Keras、fast.ai 和 Hugging Face transformers。这些工具相互补充,你可能在职业生涯的某个时候都会用到它们。我们对概念的阐述并不是穷尽的;它提供了一个“鸟瞰”为什么这些工具是需要的以及它们如何相互比较和相互补充。我们涉及了介绍性的概念,并引用了精心筛选的参考资料,以便深入研究。如果你觉得自己对这些工具的经验很少,你可能需要在开始阅读本书之前深入研究一下它们。
让我们从深度学习革命背后的算法引擎开始,即随机梯度下降算法。
B.1 随机梯度下降
神经网络有一组参数,称为权重,确定它将如何将输入数据转换为输出数据。确定哪组权重允许网络最接近地逼近一组训练数据称为训练网络。随机梯度下降是实现这一目标的方法。
让我们用W表示权重,x表示输入数据,y表示输出数据。我们还用y_pred表示神经网络对输入x预测的输出数据。损失函数,用于衡量y与y_pred之间的接近程度,被表示为函数f。注意它是x、y和W的函数。随机梯度下降算法被制定为一个过程,以找到f的最小值,即预测尽可能接近训练数据的位置。如果f的梯度,用f'表示,存在——如果它是一个可微函数——我们知道在这样的点上f'=0。算法试图使用以下步骤序列找到这样的点:
-
从训练集中随机抽取一个输入-输出批次
x-y的数据。这种随机性是算法被称为随机的原因。 -
使用当前
W的值将输入通过网络以获得y_pred.。 -
计算相应的损失函数值
f。 -
计算损失函数相对于
W的相应梯度f'。 -
稍微改变
W的方向以降低f。步长的大小由算法的学习率决定,这是收敛的一个非常重要的超参数。
对于过度简单的单个权重的情况,该过程在图 B.1 中的第 2 步找到了算法的最小值。这张图受到弗朗索瓦·朱利叶的优秀书籍《深度学习与 Python》(Manning Publications,2018)中图 2.11 的启发,你也应该查看这本书以获得对该算法非常直观的解释。
图 B.1 展示了随机梯度下降在单个权重的过度简单情况下的示意图。在每一步中,计算相对于W的梯度,并采取预先确定大小的步骤,由学习率决定,沿着损失函数梯度的相反方向。在这个假设的情景中,最小值在第 2 步找到。
有许多此算法的变体存在,包括 Adam、RMSprop 和 Adagrad。这些算法倾向于专注于避免局部最小值,并以各种方式(如学习率)进行自适应,以更快地收敛。动量的概念——它表现为每一步W更新中的额外加法项——被几种这样的变体用来避免局部最小值。以下是一些最流行的变体及简要描述。
Adagrad 根据参数遇到的频率调整学习率。罕见的参数会以更大的步长进行更新,以实现平衡。该技术被用于训练 GloVe 静态词嵌入,该词嵌入在本书的第四章中描述。在这种情况下,它需要适当处理语言中的稀有单词。
RMSprop是为了解决 Adagrad 的学习速率经常过快下降的问题而开发的。我们可以通过将更新缩放为平方梯度的指数衰减平均值来部分缓解这个问题。
Adam表示自适应矩估计,也针对不同参数变化学习率。它与 RMSprop 具有相似之处,因为它使用衰减平方梯度平均值来执行更新。衰减平方梯度平均值的第一和二个时刻被估计,更新,然后用于在每个步骤中更新参数。这是尝试解决许多问题的流行算法。
Nadam是Nesterov 加速 Adam的缩写,采用称为Nesterov 加速梯度的创新来进一步改善 Adam 的收敛性。
因为这里的介绍只是一个简短的介绍,而不是详细的处理,所以我们不会更深入地探讨这些变体。这个主题已经被许多优秀的参考文献详细覆盖了¹,²,我们鼓励您深入研究以获得更好的理解。即使您可以在不深入了解这些变体的情况下使用现代框架,更好地了解它们可以帮助您调整超参数,并最终部署更好的模型。
B.2 TensorFlow
如前一节所述,了解损失函数相对于神经网络权重的梯度对于训练网络至关重要。由于现代神经网络是巨大的,达到数十亿个参数,因此手动计算此梯度函数将是不可能的。相反,使用 TensorFlow 等基本神经网络建模工具,通过应用取导数的链式法则自动找到梯度来计算它。这个过程被称为自动微分。
Tensorflow 中的基本数据结构是张量,通过构建一个计算图来对其进行操作。在框架的 1.x 版本中,通过多种 API 调用tf.*来构建图,并使用Session对象编译和执行它以产生数值。示例 B.1 中演示了使用此 API 定义图形并执行其梯度计算的说明性示例。具体来说,我们需要计算矩阵乘积z = x*y,其中x是简单的列向量,而y是简单的行向量。我们还希望自动计算它相对于x和y的梯度。
示例 B.1 使用 TensorFlow 1 计算矩阵乘积 z = x * y 及其梯度
import tensorflow as tf ❶
tf.compat.v1.disable_eager_execution() ❷
x = tf.compat.v1.placeholder(tf.float32, name = "x") ❸
y = tf.compat.v1.placeholder(tf.float32, name = "y")
z = tf.multiply(x, y) # Define vector product graph
gradient = tf.gradients(z,[x, y],grad_ys=tf.eye(2)) ❹
with tf.compat.v1.Session() as session: ❺
z = session.run(z, feed_dict={x: [[1., 1.]], y: [[5.], [5.]]}) ❻
zG = session.run(gradient,feed_dict={x: [[1.,1.]], y: [[5.],[5.]]}) ❼
print("Product:") ❽
print(z)
print("\n\n")
print("Gradient of Product:")
print(zG)
print("\n\n")
❶总是先导入 TensorFlow
❷Eager 执行在 2.0 之前作为非默认值引入,因此在此确保其关闭。
❸定义留给后面分配值的向量变量占位符
❹ 定义了乘积的向量导数图,相对于 x 和 y。参数 grad_ys 乘以输出,可用于取链导数,因此我们将其设置为单位矩阵以无效果。
❺ 使用 Session 对象执行图
❻ 运行函数,指定占位符的值
❼ 运行梯度,指定占位符的值
❽ 显示结果
执行此代码将产生以下输出。您应该能够手动验证这些值是否正确,使用您的基本线性代数知识,这是本书的先决条件。我们还在书的伴随存储库中包含了一个 Kaggle 内核笔记本,执行这些命令。³
Product:
[[5\. 5.]
[5\. 5.]]
Gradient of Product:
[array([[5., 5.]], dtype=float32), array([[1.],
[1.]], dtype=float32)]
框架的 2.0 版及更高版本将更“Pythonic”的 eager execution 模式作为默认模式,这使得框架更易于使用。它现在还包括了 Keras,使得使用各种高级功能更加容易。下一个列表中显示了使用此 API 定义和执行与列表 B.1 中相同图形的说明性示例。更易于访问性立即变得明显,eager 模式使得立即执行变得可能,而不是通过图上的 Session 对象。
列表 B.2 计算矩阵乘积 z = x*y 及其在 TensorFlow 2 中的梯度
import tensorflow as tf
x = tf.convert_to_tensor([[1., 1.]]) ❶
y = tf.convert_to_tensor([[5.], [5.]]) ❷
with tf.GradientTape() as g: ❸
g.watch(x)
z = tf.multiply(x, y)
dz_dx = g.gradient(z, x, output_gradients=tf.eye(2))
with tf.GradientTape() as g: ❹
g.watch(y)
z = tf.multiply(x, y)
dz_dy = g.gradient(z, y, output_gradients=tf.eye(2))
print("Dot Product:") ❺
print(z)
print("\n\n")
print("Gradient of Product (dz_dx):")
print(dz_dx)
print("\n\n")
print("Gradient of Product (dz_dy):")
print(dz_dy)
❶ 列向量
❷ 行向量
❸ 这是如何相对于 x 计算自动导数的。这里的“Tape”一词表示所有状态都被“记录”,可以播放回来以检索我们需要的信息。
❹ 这是如何相对于 y 计算自动导数的。参数 output_gradients 乘以输出,可用于取链导数,因此我们将其设置为单位矩阵以无效果。
❺ 显示结果
执行此代码应产生与之前相同的输出值。
框架按层次结构组织,具有高级和低级 API,如图 B.2 所示。
图 B.2 TensorFlow 框架的分层组织示意图
此图受官方 TensorFlow 文档第 1 图的影响。⁴ 如果您是初学者,并且想更详细地浏览此参考资料,这可能会有所帮助。在附录的最后一节将进一步讨论 TensorFlow 版本的 Keras,该版本也显示在图中。
更好地了解 TensorFlow 各种特性的方法是动手尝试相关的 Kaggle 内核/笔记本教程,如附录 A 中所述。特别是,只需访问 kaggle.com 并搜索“TensorFlow 教程”即可找到大量精彩的教程,您可以选择最适合您的学习风格和经验水平的内容。www.kaggle.com/akashkr/tensorflow-tutorial 上的教程似乎对初学者很有帮助。
B.3 PyTorch
这个框架在 TensorFlow 之后发布(2016 年对比 2015 年)。然而,它很快就成为许多研究人员首选的框架,如 TensorFlow 相对 PyTorch 在学术论文引用方面的相对流行度下降所证明的那样。⁵ 这种增加的流行度被普遍认为是因为该框架能够在运行时以编程方式修改各种 PyTorch 模型对象,从而在研究过程中更容易进行代码优化。事实上,TensorFlow 2.0 中的急切模式的引入被普遍认为受到了 PyTorch 成功的影响。尽管在 TensorFlow 2.0 发布后,这两个平台之间的差异变得更小了,但普遍的观点是,研究人员更喜欢 PyTorch,而 TensorFlow 更适用于在生产中部署。
作为例证,我们在 PyTorch 中执行与清单 B.1 和 B.2 相同的操作序列 —— 向量乘法及其导数,这是神经网络模型的核心 —— 并在下一个清单中展示相应的代码。
清单 B.3 在 PyTorch 中计算矩阵乘积 z = x*y 及其梯度
import torch ❶
from torch.autograd import grad ❷
import numpy as np # tensors will be built from numpy arrays
x = torch.from_numpy(np.array([[1., 1.]])) ❸
y = torch.from_numpy(np.array([[5.], [5.]])) ❹
x.requires_grad = True ❺
y.requires_grad = True
z = torch.mul(x, y) ❻
zGx = grad(outputs=z, inputs=x,grad_outputs=torch.eye(2),retain_graph=True)❼
zGy = grad(outputs=z, inputs=y,grad_outputs=torch.eye(2)) ❽
print("Dot Product") ❾
print(z)
print("Gradient of Product(dz_dx)")
print(zGx)
print("\n\n")
print("Gradient of Product (dz_dy):")
print(zGy)
❶ 总是首先导入 PyTorch。
❷ 导入 grad 函数进行自动微分
❸ 列向量
❹ 行向量
❺ 这确保了可以针对 x 计算梯度。
❻ 计算乘积
❼ 针对 x 计算自动导数。retain_graph 确保我们可以继续进行导数计算;否则,“Tape” 将被丢弃,无法回放。
❽ 针对 y 计算自动导数。参数 grad_outputs 乘以输出,可以用于进行链式导数,因此我们将其设置为单位矩阵以无效果。
❾ 显示结果
执行此代码应该产生与前一节相同的结果。我们还在本书的伴随存储库中包含了一个 Kaggle 内核笔记本,执行这些命令。
和以前一样,我们建议通过一些 Kaggle 内核来熟悉 PyTorch 的各个方面,如果你觉得自己可能需要更多经验的话。www.kaggle.com/kanncaa1/pytorch-tutorial-for-deep-learning-lovers 上的教程似乎是初学者的好选择。
B.4 由 Hugging Face 提供的 Keras、fast.ai 和 Transformers
正如在附录中早前提到的,Keras 库是一个更高级的神经网络建模框架,现在也包含在 TensorFlow 2.0 及更高版本中。通过使用它,你可以在 TensorFlow 和 PyTorch 中指定神经网络架构,只需从一个 API 中切换后端即可!它与 TensorFlow 预先打包在一起,如我们在图 B.2 中所示。与 TensorFlow 和 PyTorch 相比,其 API 相对简单,这使得它非常受欢迎。存在许多优秀的学习资源,也许最好的资源之一是作者自己的书籍。这也是学习 TensorFlow 和神经网络的绝佳参考资料,如果你觉得需要复习这些主题,我们强烈推荐它。你也可以通过一些 Kaggle 内核来学习一些基础知识,例如 www.kaggle.com/prashant111/keras-basics-for-beginners 上的教程只是一个很好的例子。
在该领域另一个流行的高级建模 API 是 fast.ai。这个库是作为同名大型在线课程(MOOC)的伴侣而开发的,并以一种极易使用的方式实现了最先进的方法。其动机之一是将这些工具普及到发展中国家。该库的一个受欢迎功能是其学习率确定实用程序,我们在本书的第九章中使用了它。该框架用于自然语言处理和计算机视觉,并运行在 PyTorch 上。自然,学习该库的最佳参考资料是 fast.ai MOOC 本身。这门免费课程涵盖了神经网络和深度学习的基础知识,是另一个我们强烈推荐的精彩资源。该库通过定义其自己的一套数据结构来实现简单化,这些数据结构处理了用户的大量样板代码。另一方面,这可能会使其对非标准用例的定制更加困难。在作者的经验中,这是一个拥有的绝佳工具。
最后,Hugging Face 的 Transformers 是一个专门针对基于 Transformer 模型的高级建模框架。这些模型已经成为现代自然语言处理中可能是最重要的架构。你将在整本书中准确了解其中的原因。这个库可能是当今领域中最受欢迎的库,因为使用它部署这些模型非常简单。在这个库存在之前,使用 Keras、TensorFlow 和/或 PyTorch 部署 Transformer 模型相当繁琐。该库在某些情况下简化了这个过程,只需几行 Python 代码,导致其受欢迎程度激增,并被认为是现代自然语言处理从业者不可或缺的工具。由于 API 的透明度和简单性,你可能只需阅读本书并通过相关示例进行工作,甚至无需任何先前的使用经验即可。如需进一步参考,请查看作者在 GitHub 上的入门笔记⁹以及官方快速入门文档¹⁰。
-
F. Chollet,《Deep Learning with Python》(Manning Publications,2018)。
-
S. Ruder,“梯度下降优化算法概述”,arXiv(2016)。
-
developers.google.com/machine-learning/crash-course/first-steps-with-tensorflow/toolkit -
F. Chollet,《Deep Learning with Python》(Manning Publications,2018)。