为问答任务微调 DistilBERT

transformers 库为许多流行的 Transformer 模型提供了一个干净且文档齐全的接口。它不仅使源代码更易于阅读和理解,还提供了一种与模型交互的标准方法。在前一篇文章中,您已经看到了如何使用 DistilBERT 等模型进行自然语言处理任务。在这篇文章中,您将学习如何为您自己的目的微调该模型。这会将模型的使用从推理扩展到训练。具体来说,您将学习:

  • 如何准备用于训练的数据集
  • 如何使用辅助库训练模型

通过我的书籍《Hugging Face Transformers中的NLP》快速启动您的项目。它提供了带有工作代码的自学教程

让我们开始吧。

为问答任务微调 DistilBERT
照片由 Lea Fabienne 拍摄。保留部分权利。

概述

这篇博文分为三部分;它们是:

  • 为自定义问答微调 DistilBERT
  • 数据集和预处理
  • 运行训练

为自定义问答微调 DistilBERT

在 transformers 库中使用模型的最简单方法是创建 pipeline,它隐藏了许多与模型交互的细节。

您可能不想创建 pipeline 而要单独设置模型的一个原因是,您希望在自己的数据集上微调模型。这在使用 pipeline 时是不可能的,因为您需要检查模型在损失函数下的原始输出,而这通常是隐藏在 pipeline 中的。

通常,预训练模型是使用通用数据集创建的。但是,它可能无法在特定领域上很好地工作,尤其是当领域中的语言与通用用法显著不同时。这就是可以尝试微调的地方。

微调的难点可能在于一个好的数据集的可用性。这通常非常昂贵且耗时。为说明起见,下面我们将使用一个通用的、公开可用的数据集,称为 SQuAD(斯坦福问答数据集)。

得益于 transformers 库高度通用和干净的设计,微调模型非常简单。下面是如何在 SQuAD 数据集上微调模型的示例:

这段代码有点复杂。让我们一步一步地分解。

数据集和预处理

SQuAD 数据集是用于问答的流行数据集,可以在 Hugging Face hub 上找到。您可以使用 Hugging Face 的 datasets 库中的 load_dataset() 函数来加载它。

每个数据集都不同。但这个特定的数据集是类似字典的,包含“title”、“context”、“question”和“answers”键。“context”是一段中等长度的文本。“question”是一个问题句子。“answers”是一个字典,其中包含“text”和“answer_start”键。“text”映射到问题的简短答案字符串。“answer_start”映射到答案在上下文中的起始位置。“title”可以忽略,因为它提供了上下文摘录的文章标题。

要将数据集用于训练,您需要了解模型期望的输入以及它产生的输出类型。对于 DistilBERT 进行问答,模型通过 DistilBertForQuestionAnswering 类的实现进行固定,除非您决定编写自己的模型实现。在此类中,模型将序列整数 token ID 作为输入,输出是两个 logits 向量,一个用于答案的开始位置,一个用于结束位置。

您可以在上一篇文章中找到模型输入和输出格式的详细信息。或者,您可以在 DistilBertForQuestionAnswering 类文档中找到详细信息。

为了将数据集用于训练,您需要进行一些预处理。这是为了将数据集转换为与模型的输入和输出格式匹配的格式。从 Hugging Face hub 加载的数据集对象允许您使用 map() 方法进行此操作,其中转换实现为自定义函数 preprocess_function()

请注意,preprocess_function() 接受来自数据集的批次,因为您在 map() 方法中使用了 batched=True

preprocess_function() 中,分词器使用来自 examples["question"] 的问题和来自 examples["context"] 的上下文来调用。问题会去除额外的空格,上下文会截断以适应 384 个 token 的最大长度。此函数中使用分词器的方式与您在前一篇文章中看到的不同。

首先,分词器使用问题和上下文的批次进行调用。对于可能不规则的输入,分词器会将输入填充到批次的最大长度。其次,使用 return_offsets_mapping=True,分词器返回一个包含“input_ids”、“attention_mask”和“offset_mapping”键的字典。“input_ids”是整数 token ID 的序列。“attention_mask”是一个二进制掩码,指示哪些 token 是真实的(1),哪些是填充的(0)。“offset_mapping”是通过设置 return_offsets_mapping=True 添加的,它是一个元组列表,指示每个 token 在原始文本中的字符位置(开始和结束偏移量)。

分词器输出的 input_ids 以以下格式连接问题和上下文:

这就是模型所期望的。来自数据集的答案是一个字符串,以及答案在原始上下文中可以找到的字符偏移量。这与模型生成的 token 位置的 logits 不同。因此,您在 preprocess_function() 中使用了一个 for 循环来重新创建答案的开始和结束 token 位置。

在此代码中,分词器使用其他参数进行调用。设置 return_offsets_mapping=True 将使返回的对象包含 offset_mapping,这是一个元组列表,用于识别每个输入文本中每个 token 的开始和结束位置。

首先,offset_mapping 从分词器返回的对象中弹出,因为它对于训练不是必需的。然后,对于每个答案,您需要从上下文中识别字符的开始和结束偏移量。您可以使用类似以下的代码来验证这一点:

即使您知道字符偏移量,模型也是根据 token 位置进行操作的。

请记住,分词器连接了问题和上下文。幸运的是,分词器提供了线索来识别输出中的上下文的开始和结束。在 inputs.sequence_ids(i) 中,它是一个整数或 None 的 Python 列表,对应于批次中的元素 i。列表包含 None 表示特殊 token 的位置,包含一个整数表示实际输入的 token 位置。在您的用例中,您先调用了问题分词器,然后是上下文分词器,因此整数 0 对应于问题,1 对应于上下文。

因此,您可以通过检查 sequence_ids 列表中整数 1 第一次和最后一次出现的位置来识别上下文的 token 开始和结束偏移量。

知道了上下文的开始和结束 token 位置后,您仍然需要检查答案是否被任何 token 覆盖。这通过逐个检查 token 来完成。您使用 for 循环遍历每一对偏移量,并检查答案的开始和结束字符位置是否在任何 token 内。如果是,则 token 的位置将被记为 start_positionsend_positions。对于未找到的答案(例如,由于上下文被裁剪),则将其设置为 0。

preprocess_function() 的末尾,将返回 inputs 对象。它是一个类似字典的对象,键为 input_idsattention_masksstart_positionsend_positions。您必须不要更改这些键的名称,因为 DistilBertForQuestionAnswering 类期望在 forward() 方法中使用这些参数。

DistilBERT 模型期望您使用 input_ids 参数来调用它。如果您使用大批次调用,还需要 attention_masks 来告知输入中的哪些 token 是填充的。如果您使用可选的开始和结束位置调用,还将计算交叉熵损失。这就是 transformers 库的设计方式,它可以帮助您使用相同的接口在推理和训练中调用模型。

运行训练

要运行此代码,您需要安装以下软件包:

虽然您可以预期 torchtransformersdatasets 是必需的。accelerate 包是使用 transformers 库中的 Trainer 类时的依赖项。

您可能期望训练像 DistilBERT 这样的复杂模型需要大量代码。确实,这并不容易,因为您需要决定使用什么优化器、训练的 epoch 数以及批次大小、学习率、权重衰减等超参数。您甚至需要处理检查点,以便在中断时可以恢复训练。

这就是 Trainer 类被引入的原因。您只需设置训练参数,然后使用数据集设置 Trainer,然后运行训练。

Trainer 将在一次函数调用中处理检查点、日志记录和评估。训练完成后,您只需将微调后的模型(以及分词器,因为它们是一起加载的)以 Hugging Face 格式保存即可。

您只需要做这些。即使您没有指定使用 GPU 进行训练,Trainer 也会自动检测您系统上的 GPU 并使用它来加速进程。上面的代码虽然不长,但它是 DistilBERT 在 SQuAD 数据集上进行微调的完整代码。

如果运行此代码,您将期望看到以下输出:

即使使用性能不错的 GPU,这仍然需要一些时间来运行。然而,您正在对新数据集进行预训练模型的微调。这比从头开始训练要快得多,也容易得多。

训练完成后,您可以在其他项目中通过以下路径加载模型:

请确保 model_path 是您项目中用于查找已保存模型文件的正确路径。

进一步阅读

以下是本帖中使用到的类和方法的文档链接:

总结

在本帖中,您学习了如何为自定义问答任务微调 DistilBERT。即使 DistilBERT 和问答被用作示例,您也可以将相同的流程应用于其他模型和任务。特别是,您学习了:

  • 如何准备用于训练的数据集
  • 如何使用 transformers 库中的 Trainer 接口来训练或微调模型

 

想在您的NLP项目中使用强大的语言模型吗?

NLP with Hugging Face Transformers

在您自己的机器上运行最先进的模型

...只需几行Python代码

在我的新电子书中探索如何实现
使用 Hugging Face Transformers 进行自然语言处理

它涵盖了在以下任务上的实践示例实际用例文本分类、摘要、翻译、问答等等...

最终将高级NLP带入
您自己的项目

没有理论。只有实用的工作代码。

查看内容

暂无评论。

留下回复

Machine Learning Mastery 是 Guiding Tech Media 的一部分,Guiding Tech Media 是一家领先的数字媒体出版商,专注于帮助人们了解技术。访问我们的公司网站以了解更多关于我们的使命和团队的信息。