精通 Transformers(一)(2)https://developer.aliyun.com/article/1511461
使用应用程序编程接口访问数据集
datasets
库通过 Hugging Face hub 为加载、处理和共享数据集提供了非常高效的实用工具。与 TensorFlow 数据集一样,它使直接从原始数据集主机下载、缓存和动态加载集合变得更加容易。该库还提供了与数据一起的评估指标。事实上,hub 不持有或分发数据集。相反,它保留了关于数据集的所有信息,包括所有者、预处理脚本、描述和下载链接。我们需要检查我们是否有权限使用相应许可证下的数据集。要查看其他功能,请查看相应数据集在 GitHub 存储库下的dataset_infos.json
和DataSet-Name.py
文件,网址为github.com/huggingface/datasets/tree/master/datasets
。
让我们从安装dataset
库开始,如下所示:
pip install datasets
以下代码自动使用 Hugging Face hub 加载cola
数据集。datasets.load_dataset()
函数如果数据尚未缓存,则从实际路径下载加载脚本:
from datasets import load_dataset cola = load_dataset('glue', 'cola') cola['train'][25:28]
重要提示
数据集的可重用性:当您多次运行代码时,datasets
库会开始缓存您的加载和操作请求。它首先存储数据集,并开始缓存您在数据集上的操作,例如拆分、选择和排序。您会看到一条警告消息,例如reusing dataset xtreme (/home/savas/.cache/huggingface/dataset…)或loading cached sorted…。
在前面的示例中,我们从 GLUE 基准测试中下载了cola
数据集,并从其中的train
拆分中选择了一些示例。
目前,有 661 个 NLP 数据集和 21 个度量标准用于各种任务,如以下代码片段所示:
from pprint import pprint from datasets import list_datasets, list_metrics all_d = list_datasets() metrics = list_metrics() print(f"{len(all_d)} datasets and {len(metrics)} metrics exist in the hub\n") pprint(all_d[:20], compact=True) pprint(metrics, compact=True)
这是输出结果:
661 datasets and 21 metrics exist in the hub. ['acronym_identification', 'ade_corpus_v2', 'adversarial_qa', 'aeslc', 'afrikaans_ner_corpus', 'ag_news', 'ai2_arc', 'air_dialogue', 'ajgt_twitter_ar', 'allegro_reviews', 'allocine', 'alt', 'amazon_polarity', 'amazon_reviews_multi', 'amazon_us_reviews', 'ambig_qa', 'amttl', 'anli', 'app_reviews', 'aqua_rat'] ['accuracy', 'BERTscore', 'bleu', 'bleurt', 'comet', 'coval', 'f1', 'gleu', 'glue', 'indic_glue', 'meteor', 'precision', 'recall', 'rouge', 'sacrebleu', 'sari', 'seqeval', 'squad', 'squad_v2', 'wer', 'xnli']
数据集可能具有多个配置。例如,作为一个聚合基准的 GLUE 有许多子集,如前面提到的 CoLA、SST-2 和 MRPC。要访问每个 GLUE 基准数据集,我们传递两个参数,第一个是glue
,第二个是其示例数据集(cola
或sst2
)中的特定数据集。同样,维基百科数据集提供了几种语言的几种配置。
数据集带有DatasetDict
对象,包括多个Dataset
实例。当使用拆分选择(split='...')
时,我们会得到Dataset
实例。例如,CoLA
数据集带有DatasetDict
,其中包含三个拆分:train、validation和test。虽然训练和验证数据集包括两个标签(1
表示可接受,0
表示不可接受),但测试拆分的标签值为-1
,表示无标签。
让我们看一下CoLA
数据集对象的结构,如下所示:
>>> cola = load_dataset('glue', 'cola') >>> cola DatasetDict({ train: Dataset({ features: ['sentence', 'label', 'idx'], num_rows: 8551 }) validation: Dataset({ features: ['sentence', 'label', 'idx'], num_rows: 1043 }) test: Dataset({ features: ['sentence', 'label', 'idx'], num_rows: 1063 }) }) cola['train'][12] {'idx': 12, 'label':1,'sentence':'Bill rolled out of the room.'} >>> cola['validation'][68] {'idx': 68, 'label': 0, 'sentence': 'Which report that John was incompetent did he submit?'} >>> cola['test'][20] {'idx': 20, 'label': -1, 'sentence': 'Has John seen Mary?'}
数据集对象具有一些额外的元数据信息,这可能对我们有所帮助:split
、description
、citation
、homepage
、license
和info
。让我们运行以下代码:
>>> print("1#",cola["train"].description) >>> print("2#",cola["train"].citation) >>> print("3#",cola["train"].homepage) 1# GLUE, the General Language Understanding Evaluation benchmark(https://gluebenchmark.com/) is a collection of resources for training,evaluating, and analyzing natural language understanding systems.2# @article{warstadt2018neural, title={Neural Network Acceptability Judgments}, author={Warstadt, Alex and Singh, Amanpreet and Bowman, Samuel R}, journal={arXiv preprint arXiv:1805.12471}, year={2018}}@inproceedings{wang2019glue, title={{GLUE}: A Multi-Task Benchmark and Analysis Platform for Natural Language Understanding}, author={Wang, Alex and Singh, Amanpreet and Michael, Julian and Hill, Felix and Levy, Omer and Bowman, Samuel R.}, note={In the Proceedings of ICLR.}, year={2019}}3# https://nyu-mll.github.io/CoLA/
GLUE 基准提供了许多数据集,如前所述。让我们下载 MRPC 数据集,如下所示:
>>> mrpc = load_dataset('glue', 'mrpc')
类似地,要访问其他 GLUE 任务,我们将更改第二个参数,如下所示:
>>> load_dataset('glue', 'XYZ')
为了对数据可用性进行合理性检查,运行以下代码片段:
>>> glue=['cola', 'sst2', 'mrpc', 'qqp', 'stsb', 'mnli', 'mnli_mismatched', 'mnli_matched', 'qnli', 'rte', 'wnli', 'ax'] >>> for g in glue: _=load_dataset('glue', g)
XTREME(使用跨语言数据集)是另一个我们已经讨论过的流行跨语言数据集。让我们从 XTREME 集中选择MLQA
示例。MLQA 是 XTREME 基准的子集,专为评估跨语言问答模型的性能而设计。它包括约 5,000 个基于 SQuAD 格式的抽取式问答实例,涵盖七种语言,即英语、德语、阿拉伯语、印地语、越南语、西班牙语和简体中文。
例如,MLQA.en.de
是一个英德问答示例数据集,可以按如下方式加载:
>>> en_de = load_dataset('xtreme', 'MLQA.en.de') >>> en_de \ DatasetDict({ test: Dataset({features: ['id', 'title', 'context', 'question', 'answers'], num_rows: 4517 }) validation: Dataset({ features: ['id', 'title', 'context', 'question', 'answers'], num_rows: 512})})
将其视为 pandas DataFrame 可能更方便,如下所示:
>>> import pandas as pd >>> pd.DataFrame(en_de['test'][0:4])
以下是前述代码的输出:
图 2.15 – 英德跨语言问答数据集
使用 datasets 库进行数据操作
数据集带有许多子集的字典,其中split
参数用于决定要加载哪些子集或子集的哪一部分。如果默认情况下为none
,它将返回所有子集(train
、test
、validation
或任何其他组合)的数据集字典。如果指定了split
参数,它将返回单个数据集而不是字典。对于以下示例,我们只检索cola
数据集的train
子集:
>>> cola_train = load_dataset('glue', 'cola', split ='train')
我们可以得到一个train
和validation
子集的混合,如下所示:
>>> cola_sel = load_dataset('glue', 'cola', split = 'train[:300]+validation[-30:]')
split
表达式意味着获取train
的前 300 个示例和validation
的最后 30 个示例,结果为cola_sel
。
我们可以应用不同的组合,如下所示的拆分示例:
- 如下所示是来自
train
和validation
的前 100 个示例:
split='train[:100]+validation[:100]'
train
的 50%和validation
的最后 30%,如下所示:
split='train[:50%]+validation[-30%:]'
train
的前 20%和从validation
的切片 [30:50] 中的示例,如下所示:
split='train[:20%]+validation[30:50]'
排序、索引和洗牌
以下执行调用 cola_sel
对象的 sort()
函数。我们看到前 15 个和最后 15 个标签:
>>> cola_sel.sort('label')['label'][:15] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] >>> cola_sel.sort('label')['label'][-15:] [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
我们已经熟悉 Python 的切片表示法。同样,我们也可以使用类似的切片表示法或索引列表来访问多行,如下所示:
>>> cola_sel[6,19,44] {'idx': [6, 19, 44], 'label': [1, 1, 1], 'sentence':['Fred watered the plants flat.', 'The professor talked us into a stupor.', 'The trolley rumbled through the tunnel.']}
我们按以下方式洗牌数据集:
>>> cola_sel.shuffle(seed=42)[2:5] {'idx': [159, 1022, 46], 'label': [1, 0, 1], 'sentence': ['Mary gets depressed if she listens to the Grateful Dead.', 'It was believed to be illegal by them to do that.', 'The bullets whistled past the house.']}
重要提示
种子值:在洗牌时,我们需要传递一个种子值来控制随机性,实现作者和读者之间的一致输出。
缓存和可重用性
使用缓存文件可以通过内存映射(如果数据集适合驱动器)使用快速后端加载大型数据集。这种智能缓存有助于保存和重用在驱动器上执行的操作结果。要查看关于数据集的缓存日志,运行以下代码:
>>> cola_sel.cache_files [{'filename': '/home/savas/.cache/huggingface...,'skip': 0, 'take': 300}, {'filename': '/home/savas/.cache/huggingface...','skip': 1013, 'take': 30}]
数据集过滤和映射函数
我们可能想要处理数据集的特定选择。例如,我们可以仅检索具有 cola
数据集中包含术语 kick
的句子,如下面的执行所示。datasets.Dataset.filter()
函数返回包含 kick
的句子,其中应用了匿名函数和 lambda
关键字:
>>> cola_sel = load_dataset('glue', 'cola', split='train[:100%]+validation[-30%:]') >>> cola_sel.filter(lambda s: "kick" in s['sentence'])["sentence"][:3] ['Jill kicked the ball from home plate to third base.', 'Fred kicked the ball under the porch.', 'Fred kicked the ball behind the tree.']
以下过滤用于从集中获取正面(可接受的)示例:
>>> cola_sel.filter(lambda s: s['label']== 1 )["sentence"][:3] ["Our friends won't buy this analysis, let alone the next one we propose.", "One more pseudo generalization and I'm giving up.", "One more pseudo generalization or I'm giving up."]
在某些情况下,我们可能不知道类标签的整数代码。假设我们有许多类,而 culture
类的代码难以记住在 10 个类中。我们可以在我们之前的示例中传递一个 acceptable
标签给 str2int()
函数,代替在我们之前的示例中给出整数代码 1
,即 acceptable
的代码,如下所示:
>>> cola_sel.filter(lambda s: s['label']== cola_sel.features['label'].str2int('acceptable'))["sentence"][:3]
这产生与之前执行相同的输出。
用映射函数处理数据
datasets.Dataset.map()
函数在数据集上迭代,对集合中的每个示例应用处理函数,并修改示例的内容。以下执行显示添加一个新的 'len'
特征,表示句子的长度:
>>> cola_new=cola_sel.map(lambda e:{'len': len(e['sentence'])}) >>> pd.DataFrame(cola_new[0:3])
这是前面代码片段的输出:
图 2.16 – 带有附加列的 Cola 数据集
作为另一个示例,以下代码片段在 20 个字符后剪切句子。我们不创建新特征,而是更新句子特性的内容,如下所示:
>>> cola_cut=cola_new.map(lambda e: {'sentence': e['sentence'][:20]+ '_'})
输出如下所示:
图 2.17 – 带有更新的 Cola 数据集
使用本地文件工作
要从本地文件加载数据集(在csv
、text
或json
中),以及加载脚本load_dataset()
到通用加载脚本。如下代码片段所示,在../data/
文件夹中,有三个 CSV 文件(a.csv
、b.csv
和c.csv
),这些文件是从 SST-2 数据集中随机选择的玩具示例。我们可以加载单个文件,如data1
对象所示,合并多个文件,如data2
对象所示,或进行数据集分割,如data3
所示:
from datasets import load_dataset data1 = load_dataset('csv', data_files='../data/a.csv', delimiter="\t") data2 = load_dataset('csv', data_files=['../data/a.csv','../data/b.csv', '../data/c.csv'], delimiter="\t") data3 = load_dataset('csv', data_files={'train':['../data/a.csv','../data/b.csv'], 'test':['../data/c.csv']}, delimiter="\t")
为了以其他格式获取文件,我们传递json
或text
而不是csv
,如下所示:
>>> data_json = load_dataset('json', data_files='a.json') >>> data_text = load_dataset('text', data_files='a.txt')
到目前为止,我们已经讨论了如何加载、处理和操作数据集,这些数据集要么已经托管在 Hub 上,要么在我们的本地驱动器上。现在,我们将研究如何为 Transformer 模型训练准备数据集。
准备数据集以进行模型训练
让我们从标记化过程开始吧。每个模型都有自己的标记化模型,在实际的语言模型之前进行了训练。我们将在下一章节详细讨论这个问题。为了使用标记器,我们应该已经安装了Transformer
库。下面的示例从预训练的distilBERT-base-uncased
模型加载了标记器模型。我们使用map
和带有lambda
的匿名函数将标记器应用于data3
中的每个拆分。如果在map
函数中选择了batched
为True
,它会将一批例子传递给tokenizer
函数。batch_size
值默认为1000
,这是传递给函数的每批例子的数量。如果没有选择,则整个数据集作为单个批次传递。代码可以在这里看到:
from Transformer import DistilBERTTokenizer tokenizer = \ DistilBERTTokenizer.from_pretrained('distilBERT-base-uncased') encoded_data3 = data3.map(lambda e: tokenizer( e['sentence'], padding=True, truncation=True, max_length=12), batched=True, batch_size=1000)
如下输出所示,我们看到了data3
和encoded_data3
之间的区别,这里添加了两个额外特征——attention_mask
和input_ids
——并相应地添加到了数据集中。我们已经在本章的前面部分介绍了这两个特征。简而言之,input_ids
是与句子中每个标记对应的索引。这些特征是 Transformer 的Trainer
类需要的特征,我们将在接下来的微调章节中讨论。
我们通常一次传递多个句子(称为max_length
参数,在这个玩具示例中为12
)。我们还截断较长的句子以符合最大长度。代码可以在下面的代码片段中看到:
>>> data3 DatasetDict({ train: Dataset({ features: ['sentence','label'], num_rows: 199 }) test: Dataset({ features: ['sentence','label'], num_rows: 100 })}) >>> encoded_data3 DatasetDict({ train: Dataset({ features: ['attention_mask', 'input_ids', 'label', 'sentence'], num_rows: 199 }) test: Dataset({ features: ['attention_mask', 'input_ids', 'label', 'sentence'], num_rows: 100 })}) >>> pprint(encoded_data3['test'][12]) {'attention_mask': [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0], 'input_ids': [101, 2019, 5186, 16010, 2143, 1012, 102, 0, 0, 0, 0, 0], 'label': 0, 'sentence': 'an extremely unpleasant film . '}
我们已经完成了datasets
库的使用。到目前为止,我们已经评估了数据集的所有方面。我们涵盖了类似于 GLUE 的基准测试,在这里,我们考虑了分类指标。在接下来的部分中,我们将专注于如何对速度和内存的计算性能进行基准测试,而不是分类。
速度和内存基准测试
仅仅比较大型模型在特定任务或基准上的分类性能已经不再足够。现在,我们必须关注特定环境对于特定模型的计算成本(Transformer
库,PyTorchBenchmark
和TensorFlowBenchmark
使得可以为 TensorFlow 和 PyTorch 的模型进行基准测试。
在我们开始实验之前,我们需要检查我们的 GPU 能力,采用以下执行:
>>> import torch >>> print(f"The GPU total memory is {torch.cuda.get_device_properties(0).total_memory /(1024**3)} GB") The GPU total memory is 2.94921875 GB
输出是从 NVIDIA GeForce GTX 1050 (3 Transformer
库当前仅支持单设备基准测试。当我们在 GPU 上进行基准测试时,我们需要指示 Python 代码将在哪个 GPU 设备上运行,这是通过设置CUDA_VISIBLE_DEVICES
环境变量来完成的。例如,export CUDA_VISIBLE_DEVICES=0
。 0
表示将使用第一个cuda
设备。
在接下来的代码示例中,我们探索了两个网格。我们比较了四个随机选择的预训练 BERT 模型,如models
数组中所列。要观察的第二个参数是sequence_lengths
。我们将批量大小保持为4
。如果您有更好的 GPU 容量,可以将批量值扩展到 4-64 的范围以及其他参数的搜索空间:
from Transformer import PyTorchBenchmark, PyTorchBenchmarkArguments models= ["BERT-base-uncased","distilBERT-base-uncased","distilroBERTa-base", "distilBERT-base-german-cased"] batch_sizes=[4] sequence_lengths=[32,64, 128, 256,512] args = PyTorchBenchmarkArguments(models=models, batch_sizes=batch_sizes, sequence_lengths=sequence_lengths, multi_process=False) benchmark = PyTorchBenchmark(args)
重要注意事项
TensorFlow 的基准测试:本部分的代码示例是用于 PyTorch 基准测试的。对于 TensorFlow 的基准测试,我们简单地使用TensorFlowBenchmarkArguments
和TensorFlowBenchmark
相应的类。
我们已准备通过运行以下代码进行基准测试实验:
>>> results = benchmark.run()
这可能会花费一些时间,这取决于您的 CPU/GPU 容量和参数选择。如果遇到内存不足的问题,您应该采取以下措施来解决问题:
- 重新启动您的内核或操作系统。
- 在开始之前删除内存中所有不必要的对象。
- 设置较低的批处理大小,例如 2,甚至 1。
以下输出显示了推理速度性能。由于我们的搜索空间有四种不同的模型和五种不同的序列长度,我们在结果中看到了 20 行:
图 2.18 – 推理速度性能
同样,我们将看到 20 种不同情景的推理内存使用情况,如下所示:
图 2.19 – 推理内存使用情况
为了观察跨参数的内存使用情况,我们将使用存储统计数据的results
对象进行绘制。以下执行将绘制模型和序列长度的推理时间性能:
import matplotlib.pyplot as plt plt.figure(figsize=(8,8)) t=sequence_lengths models_perf=[list(results.time_inference_result[m]['result'][batch_sizes[0]].values()) for m in models] plt.xlabel('Seq Length') plt.ylabel('Time in Second') plt.title('Inference Speed Result') plt.plot(t, models_perf[0], 'rs--', t, models_perf[1], 'g--.', t, models_perf[2], 'b--^', t, models_perf[3], 'c--o') plt.legend(models) plt.show()
如下截图所示,两个 DistillBERT 模型表现出近似的结果,并且比其他两个模型表现更好。BERT-based-uncased
模型在特别是序列长度增加时表现不佳:
图 2.20 – 推理速度结果
要绘制内存性能,请使用results
对象的memory_inference_result
结果,而不是前述代码中显示的time_inference_result
。
欲了解更多有趣的基准测试示例,请查看以下链接:
huggingface.co/transformers/benchmarks.html
github.com/huggingface/transformers/tree/master/notebooks
现在我们完成了本节,成功地完成了本章。恭喜您完成安装,运行第一个hello-world
变换器程序,使用datasets
库并进行基准测试!
总结
在本章中,我们涵盖了各种入门主题,并且也亲手操作了hello-world
变换器应用程序。另一方面,这一章在将迄今所学应用于即将到来的章节方面起着关键作用。那么,到目前为止学到了什么呢?我们迈出了第一小步,设置了环境和系统安装。在这个背景下,anaconda
软件包管理器帮助我们安装了主要操作系统所需的模块。我们还介绍了语言模型、社区提供的模型和分词过程。此外,我们介绍了多任务(GLUE)和跨语言基准测试(XTREME),这使得这些语言模型变得更强大和更准确。我们介绍了datasets
库,该库为社区提供的 NLP 数据集提供了高效的访问方式。最后,我们学会了如何评估特定模型在内存使用和速度方面的计算成本。变换器框架使得可以为 TensorFlow 和 PyTorch 的模型进行基准测试。
本节中使用的模型已由社区预先训练并与我们共享。现在,轮到我们训练语言模型并将其传播给社区了。
在下一章中,我们将学习如何训练 BERT 语言模型以及分词器,并了解如何与社区共享它们。
第二部分:变换模型-从自编码到自回归模型
在本节中,您将了解自编码模型(如 BERT)和自回归模型(如 GPT)的架构。您将学习如何为各种自然语言理解和生成问题训练、测试和微调模型。您还将学习如何与社区共享模型,以及如何微调社区共享的其他预训练语言模型。
本节包括以下章节:
- 第三章, 自编码语言模型
- 第四章, 自回归和其他语言模型
- 第五章, 文本分类的语言模型微调
- 第六章, 标记分类的语言模型微调
- 第七章, 文本表示
第三章:自动编码语言模型
在上一章中,我们查看并研究了如何使用 HuggingFace 的 Transformers 的典型 Transformer 模型。到目前为止,所有主题都包括如何使用预定义或预构建模型,而对于特定模型及其训练的信息较少。
在本章中,我们将了解如何从头开始在任何给定语言上训练自动编码语言模型。这种训练将包括模型的预训练和任务特定训练。首先,我们将从 BERT 模型的基本知识和其工作原理开始。然后,我们将使用一个简单且小型的语料库来训练语言模型。之后,我们将看看如何将该模型用于任何 Keras 模型内。
为了了解本章将学到的内容,我们将讨论以下主题:
- BERT——其中之一自动编码语言模型
- 任何语言的自动编码语言模型训练
- 与社区共享模型
- 了解其他自动编码模型
- 使用标记化算法工作
技术要求
本章的技术要求如下:
- Anaconda
- Transformers >= 4.0.0
- PyTorch >= 1.0.2
- TensorFlow >= 2.4.0
- 数据集 >= 1.4.1
- 标记器
请还要检查第三章
对应的 GitHub 代码:
github.com/PacktPublishing/Advanced-Natural-Language-Processing-with-Transformers/tree/main/CH03
.
查看以下链接以查看代码实战视频:bit.ly/3i1ycdY
BERT——其中之一自动编码语言模型
来自变换器的双向编码器表示,也被称为BERT,是最早使用编码器 Transformer 堆栈的自动编码语言模型之一,稍作修改用于语言建模。
BERT 架构是基于 Transformer 原始实现的多层 Transformer 编码器。Transformer 模型本身最初用于机器翻译任务,但 BERT 所做的主要改进是利用该体系结构的这一部分来提供更好的语言建模。这种语言模型在预训练之后,能够提供对其训练语言的全局理解。
BERT 语言模型预训练任务
要清楚了解 BERT 所使用的遮罩语言建模,让我们更详细地定义它。遮罩语言建模是训练模型的任务,输入是一句话,其中有一些遮罩标记,输出是填满遮罩标记的完整句子。但是这样做为什么能帮助模型在分类等下游任务中获得更好的结果呢?答案很简单:如果模型能够完成完形填空测试(一种通过填写空白来评估语言理解能力的语言测试),那么它就对语言本身有了一般的理解。对于其他任务,它已经进行了预训练(通过语言建模),并且将表现更好。
这是一道完形填空的例子:
乔治·华盛顿是 ___ 州的第一任总统。
预期 United 应该填入空白处。对于遮罩语言模型,应用了同样的任务,需要填补遮罩标记。不过,遮罩标记是从一句话中随机选择的。
BERT 受训的另一个任务是下一句预测(NSP)。这个预训练任务确保 BERT 不仅学习了预测遮罩标记中所有令牌之间的关系,还帮助其理解两个句子之间的关系。会选择一对句子,并在它们之间放上一个*[SEP]* 分隔符令牌。数据集中还知道第二个句子是在第一个句子之后还是之前。
以下是 NSP 的示例:
读者需要填写空白。比特币价格相比其他替代币高得太多了。
在这个例子中,模型需要预测为否定(这两个句子之间没有关联)。
这两种预训练任务使 BERT 能够对语言本身有所了解。BERT 令牌嵌入为每个令牌提供上下文嵌入。上下文嵌入意味着每个令牌的嵌入与周围令牌完全相关。与 Word2Vec 和其他模型不同,BERT 为每个令牌嵌入提供更好的信息。另一方面,NSP 任务使 BERT 能够为*[CLS]* 令牌提供更好的嵌入。正如在第一章中讨论的那样,此令牌提供关于整个输入的信息。[CLS] 用于分类任务,并且在预训练部分学习整个输入的总体嵌入。下图显示了 BERT 模型的整体外观。图 3.1 显示了 BERT 模型的相应输入和输出:
图 3.1 – BERT 模型
让我们继续下一部分!
深入了解 BERT 语言模型
标记器是许多 NLP 应用程序中各自流水线中最重要的部分之一。 对于 BERT,使用的是 WordPiece 标记。 通常,WordPiece,SentencePiece和BytePairEncoding(BPE)是最广为人知的三种标记器,由不同的基于 Transformer 的架构使用,也将在接下来的部分中介绍。 BERT 或任何其他基于 Transformer 的架构使用子词标记化的主要原因是这些标记器处理未知标记的能力。
BERT 还使用位置编码来确保将标记的位置提供给模型。如果您还记得章节 1,从词袋模型到 Transformer,BERT 和类似的模型使用非顺序操作,如密集神经层。 传统模型,如基于 LSTM 和 RNN 的模型,通过序列中标记的顺序获得位置。 为了为 BERT 提供这些额外信息,位置编码非常有用。
BERT 的预训练(如自动编码模型)为模型提供了语言信息,但在实践中,当处理不同的问题,如序列分类,标记分类或问题回答时,会使用模型输出的不同部分。
例如,在序列分类任务(如情感分析或句子分类)的情况下,原始 BERT 文章提出了必须使用最后一层的*[CLS]嵌入。然而,还有其他研究使用 BERT 进行分类,使用不同的技术(使用所有标记的平均标记嵌入,在最后一层部署 LSTM,甚至在最后一层之上使用 CNN)。 序列分类的最后一个[CLS]*嵌入可以被任何分类器使用,但提出的,也是最常见的方法是使用具有输入大小等于最终标记嵌入大小和输出大小等于类数量的 softmax 激活函数的密集层。 当输出可能是多标签并且问题本身是多标签分类问题时,使用 sigmoid 也是另一种选择。
为了给您更详细的关于 BERT 如何实际工作的信息,以下说明显示了一个 NSP 任务的示例。请注意,这里对标记化进行了简化,以便更好地理解:
图 3.2 - 用于 NSP 任务的 BERT 示例
BERT 模型有不同的变体,具有不同的设置。例如,输入大小是可变的。在前面的示例中,它被设置为512,而模型可以接受的最大序列大小是512。但是,这个大小包括特殊标记*[CLS]和[SEP],因此它会被缩减为510*。另一方面,使用 WordPiece 作为标记器会产生子词标记,作为序列输入之前可以有较少的词,标记化之后,大小会增加,因为标记器会将词分解为子词,如果在预训练语料库中没有看到它们常见。
以下图显示了 BERT 用于不同任务的示例。对于 NER 任务,使用每个令牌的输出,而不是*[CLS]。在问答情景中,使用[SEP]分隔符令牌将问题和答案连接起来,然后使用最后一层的Start/End和Span输出标记答案。在这种情况下,Paragraph是Question所询问的Context*:
图 3.3 – 用于各种 NLP 任务的 BERT 模型
不管这些任务如何,BERT 最重要的能力是对文本的上下文表示。它成功的原因在于 Transformer 编码器架构,它以密集向量的形式表示输入。这些向量可以通过非常简单的分类器轻松转换为输出。
到目前为止,您已经了解了 BERT 以及它的工作原理。您已经详细了解了 BERT 可以用于的各种任务的重要信息以及这种架构的重要要点。
在下一节中,您将学习如何预先训练 BERT,并在训练后使用它。
任何语言的自编码语言模型训练
我们已经讨论了 BERT 的工作原理以及可以使用 HuggingFace 库提供的预训练版本。在本节中,您将学习如何使用 HuggingFace 库来训练您自己的 BERT。
在开始之前,有一个很重要的问题,那就是需要有良好的训练数据,这将用于语言建模。这些数据称为语料库,通常是一大堆数据(有时经过预处理和清理)。这些无标签的语料库必须适合您希望训练语言模型的用例;例如,如果您尝试为英语单独创建一个特殊的 BERT。尽管有成千上万的巨大优秀数据集,比如 Common Crawl(commoncrawl.org/
),我们更倾向于一个小一点的数据集,以便更快地训练。
50K 电影评论的 IMDB 数据集(可在www.kaggle.com/lakshmi25npathi/imdb-dataset-of-50k-movie-reviews
找到)是一个用于情感分析的大型数据集,但如果您将其用作语料库来训练语言模型,则算是小型的:
- 你可以使用以下代码轻松下载并保存为
.txt
格式,用于语言模型和分词器训练:
import pandas as pd imdb_df = pd.read_csv("IMDB Dataset.csv") reviews = imdb_df.review.to_string(index=None) with open("corpus.txt", "w") as f: f.writelines(reviews)
- 在准备语料库之后,必须训练分词器。
tokenizers
库提供了快速简单的 WordPiece 分词器训练。为了在你的语料库上训练它,需要运行以下代码:
>>> from tokenizers import BertWordPieceTokenizer >>> bert_wordpiece_tokenizer =BertWordPieceTokenizer() >>> bert_wordpiece_tokenizer.train("corpus.txt")
- 这将训练分词器。你可以通过使用训练好的
tokenizer
对象的get_vocab()
函数来访问训练好的词汇表。你可以通过以下代码获取词汇表:
>>> bert_wordpiece_tokenizer.get_vocab()
- 以下是输出:
{'almod': 9111, 'events': 3710, 'bogart': 7647, 'slapstick': 9541, 'terrorist': 16811, 'patter': 9269, '183': 16482, '##cul': 14292, 'sophie': 13109, 'thinki': 10265, 'tarnish': 16310, '##outh': 14729, 'peckinpah': 17156, 'gw': 6157, '##cat': 14290, '##eing': 14256, 'successfully': 12747, 'roomm': 7363, 'stalwart': 13347,...}
- 保存分词器以供以后使用是必不可少的。使用对象的
save_model()
函数并提供目录将保存分词器词汇表供以后使用:
>>> bert_wordpiece_tokenizer.save_model("tokenizer")
- 你可以使用
from_file()
函数重新加载它:
>>> tokenizer = \ BertWordPieceTokenizer.from_file("tokenizer/vocab.txt")
- 你可以按照以下示例使用分词器:
>>> tokenized_sentence = \ tokenizer.encode("Oh it works just fine") >>> tokenized_sentence.tokens ['[CLS]', 'oh', 'it', 'works', 'just', 'fine','[SEP]']
- 特殊的标记
[CLS]
和[SEP]
将自动添加到标记列表中,因为 BERT 需要它们来处理输入。 - 让我们尝试使用我们的分词器来另一个句子:
>>> tokenized_sentence = \ tokenizer.encode("ohoh i thougt it might be workingg well") ['[CLS]', 'oh', '##o', '##h', 'i', 'thoug', '##t', 'it', 'might', 'be', 'working', '##g', 'well', '[SEP]']
- 对于嘈杂和拼写错误的文本,似乎是一个很好的分词器。现在你已经准备好并保存了你的分词器,你可以训练你自己的 BERT。第一步是使用
Transformers
库中的BertTokenizerFast
。你需要使用以下命令加载上一步训练好的分词器:
>>> from Transformers import BertTokenizerFast >>> tokenizer = \ BertTokenizerFast.from_pretrained("tokenizer")
- 我们使用了
BertTokenizerFast
,因为它是由 HuggingFace 文档建议使用的。还有BertTokenizer
,根据库文档中的定义,它没有实现快速版本那么快。在大多数预训练模型的文档和卡片中,强烈建议使用BertTokenizerFast
版本。 - 下一步是通过以下命令准备语料库以加快训练速度:
>>> from Transformers import LineByLineTextDataset >>> dataset = \ LineByLineTextDataset(tokenizer=tokenizer, file_path="corpus.txt", block_size=128)
- 并且需要为掩码语言建模提供数据收集器:
>>> from Transformers import DataCollatorForLanguageModeling >>> data_collator = DataCollatorForLanguageModeling( tokenizer=tokenizer, mlm=True, mlm_probability=0.15)
- 数据收集器获取数据并为训练准备好。例如,上面的数据收集器获取数据并准备好使用概率为
0.15
的掩码语言建模。使用这种机制的目的是在运行时进行预处理,这样可以使用更少的资源。另一方面,它会减慢训练过程,因为每个样本都必须在训练时动态进行预处理。 - 训练参数还为训练器在训练阶段提供信息,可以使用以下命令设置:
>>> from Transformers import TrainingArguments >>> training_args = TrainingArguments( output_dir="BERT", overwrite_output_dir=True, num_train_epochs=1, per_device_train_batch_size=128)
- 现在我们将创建 BERT 模型本身,我们将使用默认配置(注意力头数、Transformer 编码器层数等):
>>> from Transformers import BertConfig, BertForMaskedLM >>> bert = BertForMaskedLM(BertConfig())
- 最后一步是创建一个训练器对象:
>>> from Transformers import Trainer >>> trainer = Trainer(model=bert, args=training_args, data_collator=data_collator, train_dataset=dataset)
- 最后,你可以使用以下命令训练你的语言模型:
>>> trainer.train()
- 它会显示一个进度条,指示训练的进度:
图 3.4 - BERT 模型训练进度
在模型训练过程中,将使用名为runs
的日志目录存储步骤检查点:
图 3.5 – BERT 模型检查点 - 训练结束后,您可以使用以下命令轻松保存模型:
>>> trainer.save_model("MyBERT")
- 直到目前为止,您已经学会了如何训练您希望的任何特定语言的 BERT。您已经学会了如何训练标记器和 BERT 模型,使用您准备的语料库。
- 您提供的 BERT 默认配置是此训练过程中最关键的部分,它定义了 BERT 的架构和超参数。您可以使用以下代码查看这些参数:
>>> from Transformers import BertConfig >>> BertConfig()
- 输出如下:
精通 Transformers(一)(4)https://developer.aliyun.com/article/1511463