前言Huggingface的Transformers库是一个很棒的项目,该库提供了用于自然语言理解(NLU)任务(如分析文本的情感)和自然语言生成(NLG)任务(如用新文本完成提示或用另一种语言翻译)的预先训练的模型。其收录了在100多种语言上超过32种预训练模型。这些先进的模型通过这个库可以非常轻松的调取。同时,也可以通过Pytorch和TensorFlow 2.0进行编写修改等。本系列学习资料来自于该库的官方文档(v4.4.2),链接为Transformers入门(Get Started)快速开始(Quick tour)该库下载了用于自然语言理解(NLU)任务(如分析文本的情感)和自然语言生成(NLG)任务(如用新文本完成提示或用另一种语言翻译)的预先训练的模型。首先,我们利用管道API来快速使用那些预先训练好的模型。然后,我们将进一步挖掘,看看这个库是如何访问这些模型并预处理数据。使用Pipeline快速上手(Getting started on a task with a pipeline)在给定任务上使用预训练模型的最简单方法是使用pipeline()方法。Transformers对以下任务提供了开箱即用的方法:情感分析:文本是积极的还是消极的?文本生成(英文):提供一个提示,模型将生成以下内容。名称实体识别(NER):在一个输入句子中,用它所代表的实体(人、地点等)标记每个单词。填空文本:给定一个带有填空单词的文本(例如,用[MASK]代替),填空。摘要:生成一篇长文章的摘要。翻译:用另一种语言翻译一篇文章。特征提取:返回文本的张量表示。对于一个简单的任务,例如对一个句子进行文本情感分类,利用Pipeline可以在几行代码之间完成这项工作:from transformers import pipeline # 导入pipeline classifier = pipeline('sentiment-analysis') # 使用文本情感分析任务的分类器 classifier('We are very happy to show you the 🤗 Transformers library.') # 进行文本情感分类输出如下:[{'label': 'POSITIVE', 'score': 0.9997795224189758}]可以看到,分类器很快给出了判别结果和得分。当然,也可以使用一个句子列表作为输入,来作为一个batch形式输入到模型中。results = classifier(["We are very happy to show you the 🤗 Transformers library.", "We hope you don't hate it."]) for result in results: print(f"label: {result['label']}, with score: {round(result['score'], 4)}")输出如下:label: POSITIVE, with score: 0.9998label: NEGATIVE, with score: 0.5309默认情况下,为该pipeline方法下载的模型称为“distilbert-base-uncased-finetuned-sst-2-english”。它使用蒸馏器架构,并在一个名为SST-2的数据集上进行了微调,用于情感分析任务。详细信息参考:huggingface.co/distilbert-…假设我们想使用另一个模型; 比如,一个接受过法语数据训练的文本情感分类模型。那么可以在模型中心搜索,链接为:model hub。然后限定左边的标签栏中选定fr和text classification标签,就可以看到符合任务需求的模型展示,这里可以看到第一个模型的下载量是143k,可以使用这个模型来做法语的文本情感分析。在使用时,可以直接在pipeline方法中指定model为“nlptown/bert-base-multilingual-uncased-sentiment”。classifier = pipeline('sentiment-analysis', model="nlptown/bert-base-multilingual-uncased-sentiment")这个分类器现在可以处理文本在英语,法语,也可以处理荷兰语,德语,意大利语和西班牙语! 我们还可以用一个保存了预训练模型的本地文件路径替换该名称。我们还可以传递一个模型对象及其相关的分词器(Tokenizer)。为此我们需要两个类。第一个是AutoTokenizer,我们将使用它来下载与我们选择的模型相关联的分词器并实例化它。第二个是AutoModelForSequenceClassification,我们将使用它来下载模型本身。首先我们导入这两个类from transformers import AutoTokenizer, AutoModelForSequenceClassification现在,要下载我们前面找到的模型和标记器,我们只需要使用from_pretrained()方法(可以随意用model hub中的任何其他模型替换model_nammodel_name = "nlptown/bert-base-multilingual-uncased-sentiment"model = AutoModelForSequenceClassification.from_pretrained(model_name) tokenizer = AutoTokenizer.from_pretrained(model_name) classifier = pipeline('sentiment-analysis', model=model, tokenizer=tokenizer)预训练模型的简要剖析(Under the hood: pretrained models)现在让我们看看在使用这些管道时,在引擎盖下面会发生什么。正如我们所看到的,模型和标记器是使用from_pretrained方法创建的。from transformers import AutoTokenizer, AutoModelForSequenceClassification model_name = "distilbert-base-uncased-finetuned-sst-2-english"pt_model = AutoModelForSequenceClassification.from_pretrained(model_name) tokenizer = AutoTokenizer.from_pretrained(model_name)使用分词器(Using the tokenizer)分词器负责文本的预处理。首先,它将以单词(或部分单词、标点符号等)分割给定的文本,通常称为token。有多种规则可以管理这个过程,因此需要使用模型的名称来实例化分词器,以确保使用与模型预先训练时相同的规则。第二步是将这些token转换为数字,以便能够利用它们构建一个张量并将它们提供给模型。因而,分词器有一个词汇表,在使用from_pretrained方法实例化它时,程序将自动下载。要确保使用与模型预训练时相同的词汇表。要在给定文本上应用这些步骤,只需将文本提供给分词器。inputs = tokenizer("We are very happy to show you the 🤗 Transformers library.") print(inputs)得到输出{'input_ids': [101, 2057, 2024, 2200, 3407, 2000, 2265, 2017, 1996, 100, 19081, 3075, 1012, 102], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}可以直接将句子列表传递给标记器。 如果我们的目标是将它们作为批处理发送到模型中,那么首先将它们填充到相同的长度,之后将它们截断到模型可以接受的最大长度,然后返回张量。pt_batch = tokenizer( ["We are very happy to show you the 🤗 Transformers library.", "We hope you don't hate it."], padding=True, truncation=True, max_length=512, return_tensors="pt")填充将自动应用到模型长度不够的那些句子上(如例子中的第二句,"We hope you don't hate it."),并使用模型预训练的填充token。 attention mask也适用。for key, value in pt_batch.items(): print(f"{key}: {value.numpy().tolist()}")使用模型(Using the model)输入被分词器预处理后,就可以直接发送到模型。对于PyTorch模型,需要通过添加**解包字典方式实现参数传递。关于**方式解包参数,请参考Python函数参数中的*与**运算符pt_outputs = pt_model(**pt_batch) print(pt_outputs) print(pt_outputs[0])输出为SequenceClassifierOutput(loss=None, logits=tensor([[-4.0833, 4.3364], [ 0.0818, -0.0418]], grad_fn=<AddmmBackward>), hidden_states=None, attentions=None) tensor([[-4.0833, 4.3364], [ 0.0818, -0.0418]], grad_fn=<AddmmBackward>)输出时是一个SequenceClassifierOutput对象,其中logoits属性即经过最后分类(如果config.num_labels==1,则回归)分数(在SoftMax之前)。之后,可以使用Softmax激活来获取最终的预测结果。import torch.nn.functional as F pt_predictions = F.softmax(pt_outputs[0], dim=-1) print(pt_predictions)得到输出为:tensor([[2.2043e-04, 9.9978e-01], [5.3086e-01, 4.6914e-01]], grad_fn=<SoftmaxBackward>)预训练模型本身都是torch.nn.Module或tensorflow.keras.Model类型的, 因此可以在PyTorch或TensorFlow的框架下进行训练。可以将标签提供给模型,它将返回一个包含loss和最终激活的元组。import torch pt_outputs = pt_model(**pt_batch, labels = torch.tensor([1, 0])) print(pt_outputs[0])得到输出为:SequenceClassifierOutput(loss=None, logits=tensor([[-4.0833, 4.3364], [ 0.0818, -0.0418]], grad_fn=<AddmmBackward>), hidden_states=(tensor([[[ 0.3549, -0.1386, -0.2253, ..., 0.1536, 0.0748, 0.1310], [-0.5773, 0.6791, -0.9738, ..., 0.8805, 1.1044, -0.7628], [-0.3451, -0.2094, 0.5709, ..., 0.3208, 0.0853, 0.4575], ..., [ 0.4431, 0.0931, -0.1034, ..., -0.7737, 0.0813, 0.0728], [-0.5605, 0.1081, 0.1229, ..., 0.4519, 0.2104, 0.2970], [-0.6116, 0.0156, -0.0555, ..., -0.1736, 0.1933, -0.0021]], [[ 0.3549, -0.1386, -0.2253, ..., 0.1536, 0.0748, 0.1310], [-0.5773, 0.6791, -0.9738, ..., 0.8805, 1.1044, -0.7628], [-0.7195, -0.0363, -0.6576, ..., 0.4434, 0.3358, -0.9249], ..., [ 0.0073, -0.5248, 0.0049, ..., 0.2801, -0.2253, 0.1293], [-0.0790, -0.5581, 0.2347, ..., 0.2370, -0.5104, 0.0770], [-0.0958, -0.5744, 0.2631, ..., 0.2453, -0.3293, 0.1269]]], grad_fn=<NativeLayerNormBackward>), tensor([[[ 5.0274e-02, 1.2093e-02, -1.1208e-01, ..., 6.2100e-02, 1.9892e-02, 3.6863e-02], …… # 中间结果太多,省略 …… [[3.4474e-02, 3.1367e-02, 2.3187e-01, ..., 0.0000e+00, 0.0000e+00, 0.0000e+00], [3.5311e-02, 3.3985e-02, 5.4093e-02, ..., 0.0000e+00, 0.0000e+00, 0.0000e+00], [1.0943e-02, 3.1733e-03, 2.9226e-01, ..., 0.0000e+00, 0.0000e+00, 0.0000e+00], ..., [9.0263e-03, 3.5084e-03, 2.9081e-01, ..., 0.0000e+00, 0.0000e+00, 0.0000e+00], [1.5904e-02, 8.0937e-03, 2.1290e-01, ..., 0.0000e+00, 0.0000e+00, 0.0000e+00], [1.3486e-02, 6.0674e-03, 2.1980e-01, ..., 0.0000e+00, 0.0000e+00, 0.0000e+00]]]], grad_fn=<SoftmaxBackward>)))可以看到各种隐藏层的状态也都在这个对象中包含。Note: 这里给不给标签都有输出的各个类别的概率,我觉得给了标签就相当于是训练所以可以得到loss,而不给标签相当于测试,所以没有loss。一旦模型经过了微调,就可以按照以下方式与分词器一同保存tokenizer.save_pretrained(save_directory)model.save_pretrained(save_directory)然后,可以使用from_pretrained()方法通过传递目录名而不是模型名来加载这个模型。from transformers import AutoModel, AutoTokenizer tokenizer = AutoTokenizer.from_pretrained(save_directory) model = AutoModel.from_pretrained(save_directory)最后,如果需要,也可以要求模型返回所有隐藏状态和所有注意权重。pt_outputs = pt_model(**pt_batch, output_hidden_states=True, output_attentions=True) all_hidden_states, all_attentions = pt_outputs[-2:]访问代码(Accessing the code)AutoModel和AutoTokenizer类只是快捷方式,可以自动地与任何预先训练过的模型一起工作。在幕后,每个体系结构加类的组合都有一个库模型类,所以如果需要,代码很容易访问和调整。在我们前面的例子中,“distilbert-base-uncased-finetuned-sst-2-english”模型使用DistilBERT架构。当使用 AutoModelForSequenceClassification时,自动创建的模型是 DistilBertForSequenceClassification。from transformers import DistilBertTokenizer, DistilBertForSequenceClassification model_name = "distilbert-base-uncased-finetuned-sst-2-english"model = DistilBertForSequenceClassification.from_pretrained(model_name) tokenizer = DistilBertTokenizer.from_pretrained(model_name)自定义模型(Customizing the model)如果想要改变模型本身的构建方式,可以定义自定义配置类。每个架构都有自己的相关配置(例如在DistilBERT模型中, 可以配置DistilBertConfig这个类),它允许设置隐藏层的维度、dropout概率等。如果做核心修改,比如改变隐藏的大小,将不能再使用一个预先训练的模型,需要从头开始训练。然后,可以直接从这个配置实例化模型。这里我们使用了DistilBERT的预定义词汇表(因此用from_pretrained()方法加载分词器))并从头初始化模型(因此从配置中实例化模型,而不是使用from_pretrained()方法)。from transformers import DistilBertConfig, DistilBertTokenizer, DistilBertForSequenceClassification config = DistilBertConfig(n_heads=8, dim=512, hidden_dim=4*512) tokenizer = DistilBertTokenizer.from_pretrained('distilbert-base-uncased') model = DistilBertForSequenceClassification(config)对于只更改模型头部的东西(例如,标签的数量),仍然可以为主体使用一个预先训练过的模型。 例如,我们使用一个预先训练的body为10个不同的标签定义一个分类器。可以用所有默认值创建一个配置,只更改标签的数量,但更简单的是,直接将配置需要的任何参数传递给from_pretrained()方法,它将用它更新默认配置。from transformers import DistilBertConfig, DistilBertTokenizer, DistilBertForSequenceClassification model_name = "distilbert-base-uncased"model = DistilBertForSequenceClassification.from_pretrained(model_name, num_labels=10) tokenizer = DistilBertTokenizer.from_pretrained(model_name)安装(Installation)安装部分比较简单,首先需要配置pytorch或TensorFlow 2.0环境。可以根据TensorFlow installation page,PyTorch installation page ,上的提示进行下载安装。之后直接pip install transformers即可开发理念(Philosophy)Transformers是一个固执己见的(opinionated)库,它被开发为了以下人群:那些寻求使用/研究/扩展大型Transformer模型的NLP研究人员和教育者想要微调这些模型和/或在生产中为它们服务的实践者只想下载一个预先训练过的模型并使用它来解决给定的自然语言处理任务的工程师。该库的设计目标有两个:尽可能的简单和能够快速使用该库强烈地限制了要学习的面向用户的抽象的数量,事实上,几乎没有抽象,每个模型只需要三个标准类: configuration, models 和tokenizer.。所有这些类可以被用一种简单和统一的方式初始化:通过使用一个共同from_pretrained()实例化方法。该方法将负责从Hugging Face Hub提供或你自己保存的pretrained checkpoint进行下载(如果需要)、缓存和加载相关的类实例和相关数据(配置hyper-parameters、分词器词汇和模型权重)在这三个基类之上,库提供了两个api: pipeline()用于在给定任务上快速使用模型(及其相关的标记器和配置),Trainer()/TFTrainer()用于快速训练或调优给定模型。因此,这个库不是神经网络构建模块的模块化工具箱。如果你想扩展/构建这个库,只需要使用常规的Python/PyTorch/TensorFlow/Keras模块,并从库的基类继承来重用模型加载/保存等功能。提供最先进的模型,使其性能尽可能接近原始模型我们为每个体系结构提供至少一个例子,它重现了该体系结构的官方作者提供的结果。代码通常尽可能接近原始代码基础,这意味着一些PyTorch代码可能由于被转换为TensorFlow代码而不像它可能的那样pytorchic,反之亦然。其他几个目标:尽可能一致地公开模型内部:我们使用一个API来访问完整的隐藏状态和注意力权重。标记器和基本模型的API都经过了标准化,可以方便地在模型之间切换。结合对这些模型进行微调/研究的有希望的工具的主观选择:一种简单/一致的方式向词汇表和用于微调的嵌入添加新标记。简单的方法来屏蔽和修剪变压器头。在PyTorch和TensorFlow 2.0之间轻松切换,允许使用一个框架进行训练,使用另一个框架进行推理。主要概念(Main concepts)这个库围绕每个模型的三种类型的类构建:Model classes,比如BertModel,它是30多个PyTorch模型(torch.nn.Module)或Keras模型(tf.keras.Model),使用库中提供的预先训练的权重。Configuration classes,比如BertConfig,它存储了构建模型所需的所有参数。你并不总是需要自己实例化这些。特别地,如果您使用一个未经任何修改的预先训练的模型,创建模型将自动地处理配置的实例化(它是模型的一部分)。Tokenizer classes,比如BertTokenizer,它存储每个模型的词汇表,并提供对输入到模型的token嵌入索引列表中的字符串进行编码/解码的方法。所有这些类都可以从预先训练的实例中实例化,并使用两种方法在本地保存。from_pretrained() 让您从库本身提供的(受支持的模型在这里的列表中提供)或用户本地存储(或服务器上)的预训练版本中实例化模型/配置/标记器save_pretrained() 允许你在本地保存模型/配置/标记器,以便可以使用from_pretrained()重新加载它。术语表(Glossary)一般术语(General terms)autoencoding models: 自编码器模型。 参考 MLMautoregressive models: 自回归模型。 参考 CLMCLM: 因果语言模型。 因果语言模型是一种训练前的任务,模型按顺序阅读文本,并预测下一个单词。这通常是通过阅读整个句子来完成的,但是在模型中使用一个掩码来隐藏将来某个时间步长的标记。causal language modeling, a pretraining task where the model reads the texts in order and has to predict the next word. It’s usually done by reading the whole sentence but using a mask inside the model to hide the future tokens at a certain timestep.deep learning: 深度学习。 使用多层神经网络的机器学习算法。machine learning algorithms which uses neural networks with several layers.MLM: 遮罩语言模型。 这是一种预训练任务,让模型看到文本的损坏版本,通常通过随机屏蔽一些token来完成,并且必须预测原始文本。masked language modeling, a pretraining task where the model sees a corrupted version of the texts, usually done by masking some tokens randomly, and has to predict the original text.multimodal: 多模态。 将文本与另一种输入(例如图像)组合在一起的任务。a task that combines texts with another kind of inputs (for instance images).NLG: 自然语言生成。 所有与生成文本相关的任务(例如与Transformers对话、翻译)。natural language generation, all tasks related to generating text (for instance talk with transformers, translation).NLP: 自然语言处理。 一种处理文本的通用方式。natural language processing, a generic way to say “deal with texts”.NLU: 自然语言理解。 所有与理解文本内容相关的任务(例如对整个文本和单个单词进行分类)。natural language understanding, all tasks related to understanding what is in a text (for instance classifying the whole text, individual words).pretrained model: 预训练模型。 在某些数据(例如Wikipedia的所有数据)上预先训练过的模型。预训练方法包括一个自我监督的目标,它可以是阅读文本并尝试预测下一个单词(参见CLM),或者屏蔽一些单词并尝试预测它们(参见MLM)。a model that has been pretrained on some data (for instance all of Wikipedia). Pretraining methods involve a self-supervised objective, which can be reading the text and trying to predict the next word (see CLM) or masking some words and trying to predict them (see MLM).RNN: 循环神经网络。 一种在层上使用循环来处理文本的模型。recurrent neural network, a type of model that uses a loop over a layer to process texts.self-attention: 自注意力。 输入的每个元素都找出它们应该“注意”的其他输入元素。each element of the input finds out which other elements of the input they should attend to.seq2seq or sequence-to-sequence: 序列到序列(模型)。 从输入中生成新序列的模型,如翻译模型或总结模型(如Bart或T5)。models that generate a new sequence from an input, like translation models, or summarization models (such as Bart or T5).token : 一个句子的一部分,通常是一个词,但也可以是一个子词(非常用词常被拆分在子词中)或标点符号。a part of a sentence, usually a word, but can also be a subword (non-common words are often split in subwords) or a punctuation symbol.transformer : 基于自我注意的深度学习模型体系结构。self-attention based deep learning model architecture.translation models, or summarization models (such as Bart or T5).token : 一个句子的一部分,通常是一个词,但也可以是一个子词(非常用词常被拆分在子词中)或标点符号。a part of a sentence, usually a word, but can also be a subword (non-common words are often split in subwords) or a punctuation symbol.transformer : 基于自我注意的深度学习模型体系结构。self-attention based deep learning model architecture.模型输入(Model inputs)每一种模型都是不同的,但也有相似之处。因此,大多数模型使用相同的输入,这里将详细介绍使用示例。输入ID(Input IDs)输入id通常是作为输入传递给模型的唯一必需参数。它们是token的索引,是构建序列的token的数字表示,这些序列将被模型用作输入。分词器负责将序列拆分为分词器词汇表中可用的token。每个分词器的工作方式不同,但底层机制是相同的。下面是一个使用BERT分词器的例子,这是一个Word-piece 分词器。分词器负责将序列拆分为分词器词汇表中可用的标记。from transformers import BertTokenizer tokenizer = BertTokenizer.from_pretrained("bert-base-cased") sequence = "A Titan RTX has 24GB of VRAM"tokenized_sequence = tokenizer.tokenize(sequence) print(tokenized_sequence)得到输出为['A', 'Titan', 'R', '##T', '##X', 'has', '24', '##GB', 'of', 'V', '##RA', '##M']可以看到,标记可以是单词,也可以是子单词。例如,在这里,VRAM不在模型词汇表中,所以它被分为V、RA和M。为了表明这些标记不是单独的单词,而是同一个单词的一部分,为RA和M添加了一个双哈希前缀(即#)然后可以将这些token转换为模型可以理解的id。这可以通过直接将句子提供给分词器来实现,该分词器利用huggingface/tokenizers的Rust实现峰值性能。标记器返回一个字典,其中包含其对应模型正常工作所需的所有参数。token的索引位于键input_ids之下。inputs = tokenizer(sequence) encoded_sequence = inputs["input_ids"] print(encoded_sequence)得到输出[101, 138, 18696, 155, 1942, 3190, 1144, 1572, 13745, 1104, 159, 9664, 2107, 102]注意,标记器会自动添加特殊的标记(如果关联的模型依赖它们的话),这些标记是模型有时使用的特殊id。如果我们对之前序列的id进行解码输出,可以看到decoded_sequence = tokenizer.decode(encoded_sequence) print(decoded_sequence)输出为[CLS] A Titan RTX has 24GB of VRAM [SEP]这个就是BERT模型所希望的输入格式。注意力掩码(Attention mask)Attention mask是一个可选参数,当将序列批处理在一起时使用。这个参数指示模型应该关注哪些token,哪些不应该关注。例如,考虑这两个序列from transformers import BertTokenizer tokenizer = BertTokenizer.from_pretrained("bert-base-cased") sequence_a = "This is a short sequence."sequence_b = "This is a rather long sequence. It is at least longer than the sequence A."encoded_sequence_a = tokenizer(sequence_a)["input_ids"] encoded_sequence_b = tokenizer(sequence_b)["input_ids"]两个序列编码后的长度并不一致。len(encoded_sequence_a), len(encoded_sequence_b)输出(8, 19)因此,我们不能把它们放在同一个张量中。除非第一个序列需要填充到第二个序列的长度,或者第二个序列需要截断到第一个序列的长度。在第一种情况下,id列表将由填充(Padding)索引扩展。我们可以传递一个列表给分词器,并要求它像这样填充。padded_sequences = tokenizer([sequence_a, sequence_b], padding=True) padded_sequences["input_ids"]输出[[101, 1188, 1110, 170, 1603, 4954, 119, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [101, 1188, 1110, 170, 1897, 1263, 4954, 119, 1135, 1110, 1120, 1655, 2039, 1190, 1103, 4954, 138, 119, 102]]我们可以看到,在第一个句子的右边添加了0,使它与第二个句子的长度相同,然后可以将其转换为PyTorch或TensorFlow中的张量。注意attention mask是一个二值张量,表示填充指标的位置,这样模型就不会注意到它们。对于BertTokenizer, 1表示应该处理的值,而0表示填充的值。这个attention mask位于标记器返回的键attention_mask下的字典中。padded_sequences["attention_mask"]输出[[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]Token类型ID(Token Type IDs)有些模型的目的是进行序列分类或回答问题。这需要将两个不同的序列连接到单个input_ids条目中,这通常需要特殊标记的帮助,例如分类器([CLS])和分隔符([SEP])。例如,BERT模型这样构建它的两个序列输入# [CLS] SEQUENCE_A [SEP] SEQUENCE_B [SEP]我们可以像这样使用分词器自动生成这样的句子,方法是将这两个序列作为两个参数(而不是像前面那样的列表)传递给分词器 from transformers import BertTokenizer tokenizer = BertTokenizer.from_pretrained("bert-base-cased") sequence_a = "HuggingFace is based in NYC"sequence_b = "Where is HuggingFace based?"encoded_dict = tokenizer(sequence_a, sequence_b) decoded = tokenizer.decode(encoded_dict["input_ids"])我们打印输出一下解码后的句子python复制代码 print(decoded)输出为[CLS] HuggingFace is based in NYC [SEP] Where is HuggingFace based? [SEP]对于一些模型来说,这足以理解一个序列在哪里结束,另一个序列在哪里开始。然而,其他模型,如BERT,也部署Token type id(也称为segment id)。它们被表示为一个二进制mask,标识模型中的两种序列类型。分词器将此mask作为token_type_ids条目返回。encoded_dict['token_type_ids']输出为[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1]第一个序列是用于问题的上下文,它的所有标记都用0表示,而第二个序列对应于问题,它的所有标记都用1表示。有些模型,如XLNetModel,使用了一个额外的token,由2表示。位置ID(Position IDs)与将每个token的位置嵌入其中的RNN相反,transformer不知道每个令牌的位置。因此,模型使用位置id (position_ids)来标识每个标记在标记列表中的位置。它们是一个可选参数。如果没有将position_ids传递给模型,则IDs将自动创建为绝对位置嵌入。绝对位置嵌入在范围[0,config.max_position_embeddings - 1)。一些模型使用其他类型的位置嵌入,如正弦位置嵌入或相对位置嵌入。标签(Labels)标签是一个可选参数,它可以被传递给模型来计算自己的损失。这些标签应该是模型的预期预测:它将使用标准损失来计算预测和期望值(标签)之间的损失。例如,这些标签根据模型头的类型而不同。对于序列分类模型(例如, BertForSequenceClassification),模型期望一个维度张量为(batch_size),其中每个batch的值都对应于整个序列的期望标签。对于token分类模型(例如,BertForTokenClassification),模型需要一个维度张量为(batch_size, seq_length),其中的每个值都对应于每个单个token的期望标签。对于遮罩语言模型(例如,BertForMaskedLM),模型需要一个维度张量(batch_size, seq_length),其中的每个值都对应于每个单个token的预期标签:标签是mask token的token ID,其余的值将被忽略(通常为-100)。对于seq2seq任务,(例如。 BartForConditionalGeneration, MBartForConditionalGeneration,模型期望一个维度张量(batch_size, tgt_seq_length),每个值对应于与每个输入序列相关联的目标序列。在训练期间,BART和T5都将在内部制作适当的decoder_input_ids和 decoder attention masks。它们通常不需要供应。这不适用于利用编码器-解码器框架的模型。基本模型(例如,BertModel)不接受标签,因为这些是基本transformer模型,只是输出特性。解码器输入ID(Decoder input IDs)这个输入是特定于编码器-解码器模型的,并且包含将被输入到解码器的输入id。这些输入应该用于序列到序列任务,例如翻译或摘要,并且通常以特定于每个模型的方式构建。大多数编码器-解码器模型(BART, T5)自己从标签中创建decoder_input_id。在这些模型中,传递标签是处理训练过程的首选方式。前馈模块(Feed Forward Chunking)在Transformer的每个残差块中,自我注意层通常后面跟着2个前馈层。前馈层的中间嵌入尺寸通常大于模型的隐藏尺寸(例如,bert-base-uncased)。对于input size为 [batch_size, sequence_length] 的输入,存储中间前馈嵌入所需的内存 [batch_size, sequence_length, config.intermediate_size] 可以占内存使用的很大一部分。Reformer: The Efficient Transformer的作者注意到,由于计算是独立于sequence_length维度的,它在数学上等价于计算两个前馈层的输出嵌入 _[batch_size, config.hidden_size]_0, ..., [batch_size, config.hidden_size]n 配置。然后将它们连接到 [batch_size, sequence_length, config.hidden_size] 中。其中n = sequence_length,这会增加计算时间,减少内存使用,但会产生数学上等价的结果。对于使用apply_chunking_to_forward()函数的模型,chunk_size定义了并行计算的输出嵌入的数量,从而定义了内存和时间复杂性之间的权衡。如果chunk_size设置为0,则不进行前馈分块。
前言本部分是Transformer库的基础部分的上半部分,主要包括任务汇总、模型汇总和数据预处理三方面内容,由于许多模型我也不太了解,所以多为机器翻译得到,错误再所难免,内容仅供参考。使用Transformers(Using Transformers)任务汇总(Summary of the tasks)该部分介绍了使用该库时最常见的用例。可用的模型允许许多不同的配置,并且在各个实际用例中具有很大的通用性。可用的模型允许许多不同的配置,并且在用例中具有很大的通用性。这些例子利用了auto-models,这些类将根据给定的checkpoint实例化一个模型,自动地选择正确的模型架构。为了让模型在任务上良好地执行,它必须从与该任务对应的checkpoint加载。这些checkpoint通常针对大量数据进行预先训练,并针对特定任务进行微调。这意味着以下内容并不是所有的模型都对所有的任务进行了微调。如果想对特定任务的模型进行微调,可以利用示例目录中的run_$ task .py脚本之一。微调模型是在特定数据集上进行微调的。这个数据集可能与我们要做的用例和域重叠,也可能不重叠。如前所述,可以利用示例脚本来微调模型,或者可以创建自己的训练脚本。为了对任务进行推理,这个库提供了几种机制:Pipelines: 非常容易使用的抽象,只需要两行代码。直接使用模型:抽象较少,但通过直接访问分词器(PyTorch/TensorFlow)和充分的推理能力,更灵活和强大。下面的具体应用中展示了这两种方法。序列分类(Sequence Classification)序列分类是根据给定的类别数目对序列进行分类的任务。序列分类的一个例子是GLUE数据集,它完全基于该任务。如果想在GLUE序列分类任务上对模型进行微调,可以利用run_glue.py、run_tf_glue.py、run_tf_text_classification.py或run_xnlib .py脚本。下面是一个使用Pipeline进行情感分析的例子:识别一个序列是积极的还是消极的。它在sst2上利用了一个经过微调的模型,这是一个GLUE任务。这将在分数旁边返回一个标签(正的或负的),如下所示from transformers import pipeline nlp = pipeline("sentiment-analysis") result = nlp("I hate you")[0] print(f"label: {result['label']}, with score: {round(result['score'], 4)}") result = nlp("I love you")[0] print(f"label: {result['label']}, with score: {round(result['score'], 4)}")输出为label: NEGATIVE, with score: 0.9991label: POSITIVE, with score: 0.9999下面是一个使用模型进行序列分类的例子,以确定两个序列是否相互转述(paraphrase)。过程如下:从checkpoint名称实例化分词器和模型。该模型被识别为BERT模型,并将存储在checkpoint中的权值加载到该模型中。从这两个句子构建一个序列,使用正确的特定于模型的分隔符、token类型id和注意掩码(encode()和__call__()负责这一点)。将这个序列传递到模型中,以便将其分类为两个可用类中的一个:0(不是释义)和1(是释义)。计算结果的softmax以得到所有类的概率。打印结果。相关代码如下:from transformers import AutoTokenizer, AutoModelForSequenceClassification import torch tokenizer = AutoTokenizer.from_pretrained("bert-base-cased-finetuned-mrpc") model = AutoModelForSequenceClassification.from_pretrained("bert-base-cased-finetuned-mrpc") classes = ["not paraphrase", "is paraphrase"] sequence_0 = "The company HuggingFace is based in New York City"sequence_1 = "Apples are especially bad for your health"sequence_2 = "HuggingFace's headquarters are situated in Manhattan"paraphrase = tokenizer(sequence_0, sequence_2, return_tensors="pt") not_paraphrase = tokenizer(sequence_0, sequence_1, return_tensors="pt") paraphrase_classification_logits = model(**paraphrase).logits not_paraphrase_classification_logits = model(**not_paraphrase).logits paraphrase_results = torch.softmax(paraphrase_classification_logits, dim=1).tolist()[0] not_paraphrase_results = torch.softmax(not_paraphrase_classification_logits, dim=1).tolist()[0] # Should be paraphrasefor i in range(len(classes)): print(f"{classes[i]}: {int(round(paraphrase_results[i] * 100))}%") print("---"*24) # Should not be paraphrasefor i in range(len(classes)): print(f"{classes[i]}: {int(round(not_paraphrase_results[i] * 100))}%")得到输出如下not paraphrase: 10%paraphrase: 90%------------------------------------------------------------------------not paraphrase: 94%paraphrase: 6%提取式问答(Extractive Question Answering)提取式问答是指从给定的文本中提取一个答案的任务。关于QA数据集的一个例子是SQuAD数据集,它完全基于该任务。如果想对一个任务的模型在SQuAD数据集上进行微调,可以使用run_qa.py和run_tf_team .py脚本。下面是一个使用pipeline进行问题回答的示例:从给定问题的文本中提取答案。它利用了一个在SQuAD上微调过的模型。from transformers import pipeline nlp = pipeline("question-answering") context = r"""Extractive Question Answering is the task of extracting an answer from a text given a question. An example of a question answering dataset is the SQuAD dataset, which is entirely based on that task. If you would like to fine-tune a model on a SQuAD task, you may leverage the examples/question-answering/run_squad.py script. """ result = nlp(question="What is extractive question answering?", context=context) print(f"Answer: '{result['answer']}', score: {round(result['score'], 4)}, start: {result['start']}, end: {result['end']}") print("---"*24) result = nlp(question="What is a good example of a question answering dataset?", context=context) print(f"Answer: '{result['answer']}', score: {round(result['score'], 4)}, start: {result['start']}, end: {result['end']}")输出如下Answer: 'the task of extracting an answer from a text given a question', score: 0.6226, start: 34, end: 95------------------------------------------------------------------------Answer: 'SQuAD dataset', score: 0.5053, start: 147, end: 160下面是一个使用模型和分词器回答问题的示例。流程如下:从checkpoint名称实例化一个分词器和模型。该模型被识别为BERT模型,并将存储在checkpoint中的权值加载到该模型中。定义一篇文章和一些问题。迭代问题并从文本和当前问题构建一个序列,使用正确的特定于模型的分隔符、token类型id和attention mask。将此序列传递给模型。这将在整个序列token(问题和文本)中输出开始位置和结束位置的分数范围。计算结果的softmax以获得token上的概率。从标识的start和stop值中获取token,将这些token转换为字符串。打印结果。相关代码如下:from transformers import AutoTokenizer, AutoModelForQuestionAnswering import torch tokenizer = AutoTokenizer.from_pretrained("bert-large-uncased-whole-word-masking-finetuned-squad") model = AutoModelForQuestionAnswering.from_pretrained("bert-large-uncased-whole-word-masking-finetuned-squad") text = r"""🤗 Transformers (formerly known as pytorch-transformers and pytorch-pretrained-bert) provides general-purpose architectures (BERT, GPT-2, RoBERTa, XLM, DistilBert, XLNet…) for Natural Language Understanding (NLU) and Natural Language Generation (NLG) with over 32+ pretrained models in 100+ languages and deep interoperability between TensorFlow 2.0 and PyTorch. """ questions = [ "How many pretrained models are available in 🤗 Transformers?", "What does 🤗 Transformers provide?", "🤗 Transformers provides interoperability between which frameworks?", ] for question in questions: inputs = tokenizer(question, text, add_special_tokens=True, return_tensors="pt") input_ids = inputs["input_ids"].tolist()[0] outputs = model(**inputs) answer_start_scores = outputs.start_logits answer_end_scores = outputs.end_logits answer_start = torch.argmax( answer_start_scores ) # Get the most likely beginning of answer with the argmax of the score answer_end = torch.argmax(answer_end_scores) + 1 # Get the most likely end of answer with the argmax of the score answer = tokenizer.convert_tokens_to_string(tokenizer.convert_ids_to_tokens(input_ids[answer_start:answer_end])) print(f"Question: {question}") print(f"Answer: {answer}")得到输出如下:Question: How many pretrained models are available in 🤗 Transformers?Answer: over 32 +Question: What does 🤗 Transformers provide?Answer: general - purpose architecturesQuestion: 🤗 Transformers provides interoperability between which frameworks?Answer: tensorflow 2. 0 and pytorch语言模型(Language Modeling)语言模型是将模型拟合到语料库中的任务,可以是特定领域的。所有流行的基于transformer的模型都是使用某种语言模型的变体进行训练的,例如BERT使用掩码语言建模,GPT-2使用因果语言建模。遮罩语言模型(Masked Language Modeling)屏蔽语言建模是用mask token屏蔽序列中的token,并提示模型用适当的token填充该屏蔽。这允许模型同时处理右上下文(掩码右边的token)和左上下文(掩码左边的token)。这样的训练为需要双向背景的下游任务创造了强大的基础,如SQuAD(问题回答,参见Lewis, Lui, Goyal等人,第4.2部分)。下面是一个使用pipeline从序列中替换掩码的示例。from transformers import pipeline from pprint import pprint nlp = pipeline("fill-mask") pprint(nlp(f"HuggingFace is creating a {nlp.tokenizer.mask_token} that the community uses to solve NLP tasks."))得到输出如下:[{'score': 0.17927521467208862, 'sequence': 'HuggingFace is creating a tool that the community uses to solve ' 'NLP tasks.', 'token': 3944, 'token_str': ' tool'}, {'score': 0.1134946271777153, 'sequence': 'HuggingFace is creating a framework that the community uses to ' 'solve NLP tasks.', 'token': 7208, 'token_str': ' framework'}, {'score': 0.05243523046374321, 'sequence': 'HuggingFace is creating a library that the community uses to ' 'solve NLP tasks.', 'token': 5560, 'token_str': ' library'}, {'score': 0.034935325384140015, 'sequence': 'HuggingFace is creating a database that the community uses to ' 'solve NLP tasks.', 'token': 8503, 'token_str': ' database'}, {'score': 0.028602493926882744, 'sequence': 'HuggingFace is creating a prototype that the community uses to ' 'solve NLP tasks.', 'token': 17715, 'token_str': ' prototype'}]下面是一个使用模型和分词器进行掩码语言建模的示例。流程如下:从checkpoint名称实例化一个分词器和模型。该模型被识别为DistilBERT,并使用存储在checkpoint中的权重加载该模型。定义一个带有mask token的序列,放置 tokenizer.mask_token遮盖住一个单词。将该序列编码到一个id列表中,并找到mask token在该列表中的位置。在mask token的索引处检索预测:这个张量与词汇表的大小相同,值是赋给每个token的分数。模型会给它认为在该上下文中可能出现的token更高的分数。使用PyTorch topk或TensorFlow的top_k方法检索前5个token。用token替换mask token并打印结果相关代码如下:from transformers import AutoModelWithLMHead, AutoTokenizer import torch tokenizer = AutoTokenizer.from_pretrained("distilbert-base-cased") model = AutoModelWithLMHead.from_pretrained("distilbert-base-cased") sequence = f"Distilled models are smaller than the models they mimic. Using them instead of the large versions would help {tokenizer.mask_token} our carbon footprint."inputs = tokenizer.encode(sequence, return_tensors="pt") mask_token_index = torch.where(inputs == tokenizer.mask_token_id)[1] token_logits = model(inputs).logits mask_token_logits = token_logits[0, mask_token_index, :] top_5_tokens = torch.topk(mask_token_logits, 5, dim=1).indices[0].tolist() for token in top_5_tokens: print(sequence.replace(tokenizer.mask_token, tokenizer.decode([token])))输出结果为:Distilled models are smaller than the models they mimic. Using them instead of the large versions would help reduce our carbon footprint. Distilled models are smaller than the models they mimic. Using them instead of the large versions would help increase our carbon footprint. Distilled models are smaller than the models they mimic. Using them instead of the large versions would help decrease our carbon footprint. Distilled models are smaller than the models they mimic. Using them instead of the large versions would help offset our carbon footprint. Distilled models are smaller than the models they mimic. Using them instead of the large versions would help improve our carbon footprint.输出打印了5个序列,其中包含模型预测的前5个token。因果语言模型(Causal Language Modeling)因果语言模型是在一系列token之后预测token的任务。在这种情况下,模型只处理左侧上下文(掩码左侧的token)。这样的训练对于生成任务特别有趣。如果想在因果语言建模任务上对模型进行微调,可以利用run_clm.py脚本。通常,通过对模型从输入序列中产生的最后一个隐藏状态的logit进行采样来预测下一个令牌。下面是一个使用分词器和模型的示例,并利用top_k_top_p_filter()方法对token输入序列之后的下一个token进行取样。from transformers import AutoModelWithLMHead, AutoTokenizer, top_k_top_p_filtering import torch from torch.nn import functional as F tokenizer = AutoTokenizer.from_pretrained("gpt2") model = AutoModelWithLMHead.from_pretrained("gpt2") sequence = f"Hugging Face is based in DUMBO, New York City, and "input_ids = tokenizer.encode(sequence, return_tensors="pt") # get logits of last hidden statenext_token_logits = model(input_ids).logits[:, -1, :] # filterfiltered_next_token_logits = top_k_top_p_filtering(next_token_logits, top_k=50, top_p=1.0) # sampleprobs = F.softmax(filtered_next_token_logits, dim=-1) next_token = torch.multinomial(probs, num_samples=1) generated = torch.cat([input_ids, next_token], dim=-1) resulting_string = tokenizer.decode(generated.tolist()[0]) print(resulting_string)输出结果为Hugging Face is based in DUMBO, New York City, and这将输出一个(希望)与原始序列一致的下一个标记,(这里我输出的结果还是原来的句子,不知道哪里出了问题)在下一节中,我们将展示如何在generate()中利用此功能生成多个用户定义长度的token。文本生成(Text Generation)在文本生成(也就是开放式文本生成)中,目标是创建文本的连贯部分,作为给定上下文的延续。下面的示例展示了如何在pipeline中使用GPT-2来生成文本。默认情况下,当在pipeline中使用时,所有的模型都应用Top-K采样,就像在它们各自的配置中配置的那样(参见gpt-2配置)。from transformers import pipeline text_generator = pipeline("text-generation") print(text_generator("As far as I am concerned, I will", max_length=50, do_sample=False))输出结果为:[{'generated_text': 'As far as I am concerned, I will be the first to admit that I am not a fan of the idea of a "free market." I think that the idea of a free market is a bit of a stretch. I think that the idea'}]在这里,模型从上下文“As far as I am concerned, I will”中生成一个最大长度为50个标记的随机文本。PreTrainedModel.generate()的默认参数可以在pipeline中直接覆盖,如上面所示的max_length参数。下面是一个使用XLNet及其分词器生成文本的示例。from transformers import AutoModelWithLMHead, AutoTokenizer model = AutoModelWithLMHead.from_pretrained("xlnet-base-cased") tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased") # Padding text helps XLNet with short prompts - proposed by Aman Rusia in https://github.com/rusiaaman/XLNet-gen#methodologyPADDING_TEXT = """In 1991, the remains of Russian Tsar Nicholas II and his family(except for Alexei and Maria) are discovered. The voice of Nicholas's young son, Tsarevich Alexei Nikolaevich, narrates the remainder of the story. 1883 Western Siberia, a young Grigori Rasputin is asked by his father and a group of men to perform magic. Rasputin has a vision and denounces one of the men as a horse thief. Although his father initially slaps him for making such an accusation, Rasputin watches as the man is chased outside and beaten. Twenty years later, Rasputin sees a vision of the Virgin Mary, prompting him to become a priest. Rasputin quickly becomes famous, with people, even a bishop, begging for his blessing. <eod> </s> <eos>""" prompt = "Today the weather is really nice and I am planning on "inputs = tokenizer.encode(PADDING_TEXT + prompt, add_special_tokens=False, return_tensors="pt") prompt_length = len(tokenizer.decode(inputs[0], skip_special_tokens=True, clean_up_tokenization_spaces=True)) outputs = model.generate(inputs, max_length=250, do_sample=True, top_p=0.95, top_k=60) generated = prompt + tokenizer.decode(outputs[0])[prompt_length:] print(generated)输出为:Today the weather is really nice and I am planning on anning on going out to see a new band in the next few days. I will see some young guys there when I get back. This will likely be the last time that I have been to the Twilight Zone for a long time. I have been wanting to go to the Twilight Zone for a long time but have not been able to go there. Maybe I will have文本生成目前可以在PyTorch中使用GPT-2, OpenAi-GPT, CTRL, XLNet, Transfo-XL和Reformer,以及Tensorflow中的大多数模型。从上面的例子中可以看出,XLNet和Transfo-XL经常需要进行填充才能很好地工作。GPT-2通常是开放式文本生成的一个很好的选择,因为它是在数百万个带有因果语言建模目标的网页上进行训练的。命名实体识别(Named Entity Recognition)命名实体识别(NER)是根据类对token进行分类的任务,例如,将token标识为人、组织或位置。命名实体识别数据集的一个例子是CoNLL-2003数据集,它完全基于该任务。如果想在NER任务上对模型进行微调,可以利用run_ner.py脚本。下面是一个使用pipeline进行命名实体识别的示例,具体来说,尝试将token标识为属于9个类中的一个:O, 在命名实体之外,Outside of a named entityB-MIS, 杂项实体开始,Beginning of a miscellaneous entity right after another miscellaneous entityI-MIS, 杂项实体,Miscellaneous entityB-PER, 人名实体开始,Beginning of a person’s name right after another person’s nameI-PER, 人名实体,Person’s nameB-ORG, 组织实体开始,Beginning of an organisation right after another organisationI-ORG, 组织实体,OrganisationB-LOC, 位置实体开始,Beginning of a location right after another locationI-LOC, 位置实体,Location它利用了在control -2003上经过微调的模型,由dbmdz的@stefan-it进行微调。这将输出一个所有单词的列表,这些单词被标识为上面定义的9个类中的一个实体。from transformers import pipeline nlp = pipeline("ner") sequence = "Hugging Face Inc. is a company based in New York City. Its headquarters are in DUMBO, therefore very" "close to the Manhattan Bridge which is visible from the window." pprint(nlp(sequence))输出为:{'word': 'Hu', 'score': 0.999578595161438, 'entity': 'I-ORG', 'index': 1, 'start': 0, 'end': 2} {'word': '##gging', 'score': 0.9909763932228088, 'entity': 'I-ORG', 'index': 2, 'start': 2, 'end': 7} {'word': 'Face', 'score': 0.9982224702835083, 'entity': 'I-ORG', 'index': 3, 'start': 8, 'end': 12} {'word': 'Inc', 'score': 0.9994880557060242, 'entity': 'I-ORG', 'index': 4, 'start': 13, 'end': 16} {'word': 'New', 'score': 0.9994345307350159, 'entity': 'I-LOC', 'index': 11, 'start': 40, 'end': 43} {'word': 'York', 'score': 0.9993196129798889, 'entity': 'I-LOC', 'index': 12, 'start': 44, 'end': 48} {'word': 'City', 'score': 0.9993793964385986, 'entity': 'I-LOC', 'index': 13, 'start': 49, 'end': 53} {'word': 'D', 'score': 0.9862582683563232, 'entity': 'I-LOC', 'index': 19, 'start': 79, 'end': 80} {'word': '##UM', 'score': 0.9514269828796387, 'entity': 'I-LOC', 'index': 20, 'start': 80, 'end': 82} {'word': '##BO', 'score': 0.933659017086029, 'entity': 'I-LOC', 'index': 21, 'start': 82, 'end': 84} {'word': 'Manhattan', 'score': 0.9761653542518616, 'entity': 'I-LOC', 'index': 28, 'start': 114, 'end': 123} {'word': 'Bridge', 'score': 0.9914628863334656, 'entity': 'I-LOC', 'index': 29, 'start': 124, 'end': 130}请注意,Hugging Face Inc.被确定为一个组织,New York City、DUMBO和Manhattan Bridge被确定为地点。下面是一个使用模型和分词器进行命名实体识别的示例。过程如下:从checkpoint名称实例化分词器和模型。该模型被识别为BERT模型,并将存储在checkpoint中的权值加载到该模型中。定义用于训练模型的标签列表。定义一个具有已知实体的序列,例如将“Hugging Face”作为组织,将“New York City”作为地点。将单词拆分为token,以便它们可以映射到预测。我们使用了一个小hack,首先,完全编码和解码序列,这样我们就得到了一个包含特殊token的字符串。将该序列编码到id中(自动添加特殊标记)。通过将输入传递给模型并获得第一个输出来检索预测。这将导致每个token分布在9个可能的类上。我们使用argmax来检索每个token最可能出现的类。将每个token与其预测打包并打印。代码如下:from transformers import AutoModelForTokenClassification, AutoTokenizer import torch model = AutoModelForTokenClassification.from_pretrained("dbmdz/bert-large-cased-finetuned-conll03-english") tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") label_list = [ "O", # Outside of a named entity "B-MISC", # Beginning of a miscellaneous entity right after another miscellaneous entity "I-MISC", # Miscellaneous entity "B-PER", # Beginning of a person's name right after another person's name "I-PER", # Person's name "B-ORG", # Beginning of an organisation right after another organisation "I-ORG", # Organisation "B-LOC", # Beginning of a location right after another location "I-LOC" # Location] sequence = "Hugging Face Inc. is a company based in New York City. Its headquarters are in DUMBO, therefore very" \ "close to the Manhattan Bridge."# Bit of a hack to get the tokens with the special tokenstokens = tokenizer.tokenize(tokenizer.decode(tokenizer.encode(sequence))) inputs = tokenizer.encode(sequence, return_tensors="pt") outputs = model(inputs).logits predictions = torch.argmax(outputs, dim=2) 输出为:[('[CLS]', 'O'), ('Hu', 'I-ORG'), ('##gging', 'I-ORG'), ('Face', 'I-ORG'), ('Inc', 'I-ORG'), ('.', 'O'), ('is', 'O'), ('a', 'O'), ('company', 'O'), ('based', 'O'), ('in', 'O'), ('New', 'I-LOC'), ('York', 'I-LOC'), ('City', 'I-LOC'), ('.', 'O'), ('Its', 'O'), ('headquarters', 'O'), ('are', 'O'), ('in', 'O'), ('D', 'I-LOC'), ('##UM', 'I-LOC'), ('##BO', 'I-LOC'), (',', 'O'), ('therefore', 'O'), ('very', 'O'), ('##c', 'O'), ('##lose', 'O'), ('to', 'O'), ('the', 'O'), ('Manhattan', 'I-LOC'), ('Bridge', 'I-LOC'), ('.', 'O'), ('[SEP]', 'O')]这将输出映射到相应预测的每个token的列表。与pipeline方法不同的是,这里每个token都有一个预测,因为我们没有删除第0个类,这意味着在该token上没有找到特定的实体。文本摘要(Summarization)摘要是将一份文件或一篇文章总结成较短的文本。如果想对汇总任务的模型进行微调,可以利用run_summary .py脚本。摘要数据集的一个例子是CNN /每日邮报数据集,它由长新闻文章组成,是为摘要任务而创建的。下面是一个使用pipeline进行汇总的示例。它利用了在CNN /每日邮报数据集上进行微调的Bart模型。因为文本摘要pipeline依赖于PreTrainedModel.generate()方法,所以我们可以在max_length和min_length的管道中直接覆盖PreTrainedModel.generate()的默认参数,如下所示。from transformers import pipeline summarizer = pipeline("summarization") ARTICLE = """ New York (CNN)When Liana Barrientos was 23 years old, she got married in Westchester County, New York.A year later, she got married again in Westchester County, but to a different man and without divorcing her first husband. Only 18 days after that marriage, she got hitched yet again. Then, Barrientos declared "I do" five more times, sometimes only within two weeks of each other. In 2010, she married once more, this time in the Bronx. In an application for a marriage license, she stated it was her "first and only" marriage. Barrientos, now 39, is facing two criminal counts of "offering a false instrument for filing in the first degree," referring to her false statements on the 2010 marriage license application, according to court documents. Prosecutors said the marriages were part of an immigration scam. On Friday, she pleaded not guilty at State Supreme Court in the Bronx, according to her attorney, Christopher Wright, who declined to comment further. After leaving court, Barrientos was arrested and charged with theft of service and criminal trespass for allegedly sneaking into the New York subway through an emergency exit, said Detective Annette Markowski, a police spokeswoman. In total, Barrientos has been married 10 times, with nine of her marriages occurring between 1999 and 2002. All occurred either in Westchester County, Long Island, New Jersey or the Bronx. She is believed to still be married to four men, and at one time, she was married to eight men at once, prosecutors say. Prosecutors said the immigration scam involved some of her husbands, who filed for permanent residence status shortly after the marriages. Any divorces happened only after such filings were approved. It was unclear whether any of the men will be prosecuted. The case was referred to the Bronx District Attorney\'s Office by Immigration and Customs Enforcement and the Department of Homeland Security\'s Investigation Division. Seven of the men are from so-called "red-flagged" countries, including Egypt, Turkey, Georgia, Pakistan and Mali. Her eighth husband, Rashid Rajput, was deported in 2006 to his native Pakistan after an investigation by the Joint Terrorism Task Force. If convicted, Barrientos faces up to four years in prison. Her next court appearance is scheduled for May 18. """ print(summarizer(ARTICLE, max_length=130, min_length=30, do_sample=False))输出结果为:[{'summary_text': ' Liana Barrientos, 39, is charged with two counts of "offering a false instrument for filing in the first degree" In total, she has been married 10 times, with nine of her marriages occurring between 1999 and 2002 . At one time, she was married to eight men at once, prosecutors say .'}]下面是一个使用模型和分词器进行汇总的示例。流程如下:从检查点名称实例化一个分词器和模型。摘要通常使用一个编码器-解码器模型来完成,例如Bart或T5。定义应该总结的文章。添加T5特有的前缀“summarize: “。使用PreTrainedModel.generate()方法生成摘要。在本例中,我们使用谷歌的T5模型。即使它只在多任务混合数据集(包括CNN /每日邮报)上进行了预先训练,它也会产生非常好的结果。from transformers import AutoModelWithLMHead, AutoTokenizer model = AutoModelWithLMHead.from_pretrained("t5-base") tokenizer = AutoTokenizer.from_pretrained("t5-base") # T5 uses a max_length of 512 so we cut the article to 512 tokens.inputs = tokenizer.encode("summarize: " + ARTICLE, return_tensors="pt", max_length=512) outputs = model.generate(inputs, max_length=150, min_length=40, length_penalty=2.0, num_beams=4, early_stopping=True) print(tokenizer.decode(outputs.tolist()[0]))输出为:<pad> prosecutors say the marriages were part of an immigration scam. if convicted, barrientos faces two criminal counts of "offering a false instrument for filing in the first degree" she has been married 10 times, nine of them between 1999 and 2002.</s>文本翻译(Translation)文本翻译是把文本从一种语言翻译成另一种语言的任务。如果想对翻译任务中的模型进行微调,可以利用run_translation.py脚本。翻译数据集的一个例子是WMT英语到德语数据集,它以英语句子作为输入数据,以相应的德语句子作为目标数据。下面是一个使用pipeline进行翻译的示例。它利用了仅在多任务混合数据集(包括WMT)上预先训练的T5模型,然而,产生了令人印象深刻的翻译结果。from transformers import pipeline translator = pipeline("translation_en_to_de") print(translator("Hugging Face is a technology company based in New York and Paris", max_length=40))**输出为:[{'translation_text': 'Hugging Face ist ein Technologieunternehmen mit Sitz in New York und Paris.'}因为文本翻译pipeline依赖于PreTrainedModel.generate()方法,所以我们可以直接在管道中覆盖PreTrainedModel.generate()的默认参数,如上文max_length所示。下面是一个使用模型和分词器进行翻译的示例。过程如下:从checkpoint名称实例化分词器和模型。摘要通常使用一个编码器-解码器模型来完成,例如Bart或T5。定义应该总结的文章。添加特定于T5的前缀“translate English to German:”使用PreTrainedModel.generate()方法来执行翻译。代码如下:from transformers import AutoModelWithLMHead, AutoTokenizer model = AutoModelWithLMHead.from_pretrained("t5-base") tokenizer = AutoTokenizer.from_pretrained("t5-base") inputs = tokenizer.encode("translate English to German: Hugging Face is a technology company based in New York and Paris", return_tensors="pt") outputs = model.generate(inputs, max_length=40, num_beams=4, early_stopping=True) print(tokenizer.decode(outputs.tolist()[0]))输出为:<pad> Hugging Face ist ein Technologieunternehmen mit Sitz in New York und Paris.</s>模型汇总(Summary of the models)下面总结了本库中的所有模型。这里假设读者熟悉原始的transformer模型。我们将重点放在模型之间的高层差异上。库中的每一个模型都可以分为以下几类:自回归模型自编码模型序列对序列模型多模态模型基于检索的模型自回归模型在经典的语言建模任务上进行了预先训练:在阅读了所有之前的token之后,猜测下一个token。它们对应于原Transformer模型的解码器,并且在整句话的顶部使用了掩码,以便注意头只能看到文本的前面,而不能看到后面。尽管这些模型可以进行微调,并在许多任务上取得良好的结果,但最自然的应用程序是文本生成。这种模型的典型例子是GPT。自编码模型的预训练是通过以某种方式破坏输入标记并试图重构原始句子。它们对应于原始Transformer模型的编码器,因为它们可以在没有任何掩码的情况下获得全部输入。这些模型通常是对整个句子的双向表示。它们可以进行微调,并在许多任务(如文本生成)上取得良好的结果,但它们最自然的应用是句子分类或token分类。这类模型的一个典型例子是BERT。请注意,自回归模型和自编码模型之间的唯一区别在于模型的预训练方式。因此,同样的体系结构可以用于自回归模型和自编码模型。当给定的模型被用于这两种类型的预训练时,我们将它放在与第一次介绍它的文章对应的类别中。序列到序列模型同时使用原始Transformer的编码器和解码器,用于转换任务或将其他任务转换为序列到序列问题。它们可以被微调到许多任务中,但它们最自然的应用是翻译、总结和回答问题。原始的transformer模型是这种模型的一个示例(仅用于翻译),T5是一个可以在其他任务上进行微调的示例。多模态模型将文本输入与其他类型(如图像)混合在一起,并且更特定于特定的任务。自回归模型(Autoregressive models)如前所述,这些模型依赖于原始Transformer的解码器部分,并使用注意掩码,以便在每个位置,模型只能看到注意头之前的标记。原始GPT(Original GPT)Improving Language Understanding by Generative Pre-Training, Alec Radford et al.第一个基于Transformer体系结构的自回归模型,在图书语料库数据集上进行预训练。该库提供了用于语言建模和多任务语言模型/多选择分类的模型版本。GPT-2Language Models are Unsupervised Multitask Learners, Alec Radford et al.GPT的一个更大更好的版本,在WebText(在Reddit网站上向外连接超过3karms的网页)。该库提供了用于语言建模和多任务语言建模/多项选择分类的模型版本。CTRLCTRL: A Conditional Transformer Language Model for Controllable Generation, Nitish Shirish Keskar et al.与GPT模型相同,但增加了控制代码的思想。文本由一个提示(可以是空的)和一个(或几个)控制代码生成,这些控制代码然后用于影响文本生成:以维基百科文章、一本书或电影评论的风格生成。这个库提供了一个仅用于语言建模的模型版本。Transformer-XLTransformer-XL: Attentive Language Models Beyond a Fixed-Length Context, Zihang Dai et al.与常规的GPT模型相同,但引入了两个连续段的递归机制(类似于具有两个连续输入的常规RNN)。在这个上下文中,一个段是一系列连续的token(例如512),它们可以跨多个文档,段是按照模型的顺序输入的。基本上,前一部分的隐藏状态与当前输入相连接,以计算注意力分数。这使得模型可以同时关注前一部分和当前部分中的信息。通过叠加多个注意层,感受野可以增加到多个先前的片段。这将位置嵌入改变为位置相对嵌入(因为常规的位置嵌入会在给定位置的当前输入和当前隐藏状态中给出相同的结果),并且需要在计算注意力分数的方式上做一些调整。这个库提供了一个仅用于语言建模的模型版本。ReformerReformer: The Efficient Transformer, Nikita Kitaev et al .一个具有许多技巧的自回归transformer模型,以减少内存占用和计算时间。这些技巧包括:使用轴向位置编码(参见下面了解更多细节)。它是一种通过分解成更小的矩阵来避免拥有一个巨大的位置编码矩阵(当序列长度非常大时)的机制。用LSH(本地敏感哈希)注意代替传统的注意(参见下面了解更多细节)。这是一种避免在注意层中计算完整产品查询键的技术。避免存储每一层的中间结果,方法是在反向过程中使用可逆变压器层获取中间结果(从下一层的输入中减去剩余将返回中间结果),或者为给定层中的结果重新计算中间结果(效率低于存储中间结果,但节省内存)。按块而不是整批计算前馈操作。利用这些技巧,该模型可以比传统的变压器自回归模型输入更大的句子。注意:这个模型可以很好地用于自动编码设置,但是对于这样的预训练还没有checkpoint。这个库提供了一个仅用于语言建模的模型版本。XLNetXLNet: Generalized Autoregressive Pretraining for Language Understanding, Zhilin Yang et al.XLNet不是传统的自回归模型,而是在此基础上使用了一种训练策略。它对句子中的记号进行排列,然后允许模型使用最后的n个记号来预测n+1个记号。由于这都是通过一个掩码完成的,所以句子实际上是按正确的顺序输入到模型中,但是XLNet没有屏蔽n+1的前n个标记,而是使用一个掩码,以序列长度为1的某些给定排列隐藏以前的标记。自编码模型(Autoencoding models)如前所述,这些模型依赖于原始Transformer的编码器部分,并且不使用掩码,因此模型可以查看注意头中的所有标记。对于预训练,目标是原始的句子,输入是它们的损坏版本。BERTBERT: Pre-training of Deep Bidirectional Transformers for Language Understanding, Jacob Devlin et al.通过使用随机mask破坏输入,更准确地说,在预训练期间,给定百分比的token(通常为15%)被mask80%的概率被mask token替换10%的概率被随机一个词替换10%的概率保持不动注意:这里的80%、10%是在15%的基础上划分的。即先有15%决定被mask,然后被mask的情况下有80%被mask token替换。模型必须预测原始句子,但有第二个目标:输入是两个句子A和B(中间有一个分隔符)。在语料库中,这些句子有50%的概率是连续的,剩下的50%的句子是不相关的。该模型必须预测句子是否连续。该库为语言建模(传统的或屏蔽的)、下一个句子预测、token分类、句子分类、多选择分类和问题回答都提供了一个版本的模型。ALBERTALBERT: A Lite BERT for Self-supervised Learning of Language Representations, Zhenzhong Lan et al.和BERT一样,只是做了些调整:嵌入大小E与隐藏大小H是不同的,因为嵌入是上下文独立的(一个嵌入向量代表一个标记),而隐藏状态是上下文相关的(一个隐藏状态代表一个标记序列),所以H>>E更符合逻辑。此外,嵌入矩阵很大,因为它是 V x E (V是词汇表大小)。如果E < H,它的参数更少层被分割成共享参数的组(以节省内存)。下一个句子预测被一个句子排序预测取代:在输入中,我们有两个句子A和B(连续的),我们要么输入A,然后输入B,要么输入B,然后输入A。模型必须预测它们是否被交换了。该库为遮罩语言模型、token分类、句子分类、多选择分类和问题回答都提供了一个版本的模型。RoBERTaRoBERTa: A Robustly Optimized BERT Pretraining Approach, Yinhan Liu et al.和BERT一样,但有更好的预训练技巧:动态mask:每个epoch中mask是不同的,而BERT则是一样的没有NSP(下一个句子预测)loss,不是将两个句子放在一起,而是将一组连续的文本放在一起以达到512个标记(这样句子的顺序就可以跨多个文档)更大的batch使用BPE字节作为一个单元,而不是字符(因为unicode)该库为遮罩语言模型、token分类、句子分类、多选择分类和问题回答都提供了一个版本的模型。DistilBERTDistilBERT, a distilled version of BERT: smaller, faster, cheaper and lighter, Victor Sanh et al.和BERT一样,但更小。通过对预先训练的BERT模型的蒸馏来训练,这意味着训练它预测的概率与较大模型相同。实际的目标是:找到与teacher model相同的概率正确预测masked token(没有下一句预测的目标)在student model和teacher model的隐层间的cosine similarity该库为遮罩语言模型、token分类、句子分类和问题回答都提供了一个版本的模型。ConvBERTConvBERT: Improving BERT with Span-based Dynamic Convolution, Zihang Jiang, Weihao Yu, Daquan Zhou, Yunpeng Chen, Jiashi Feng, Shuicheng Yan.像BERT这样的预先训练过的语言模型及其变体最近在各种自然语言理解任务中取得了令人印象深刻的成绩。然而,BERT严重依赖全局的自我注意块,因此内存占用和计算成本很大。虽然所有的注意头都是从全局角度查询整个输入序列来生成注意图,但我们观察到一些注意头只需要学习局部依赖关系,这意味着计算冗余的存在。因此,我们提出了一种新的基于区间的动态卷积来代替这些自我注意头,直接建模局部依赖关系。新的卷积头与其他的自我注意头一起形成新的混合注意块,在全局和局部语境学习中都更有效。我们给BERT配备了这种混合注意设计,并建立了ConvBERT模型。实验表明,ConvBERT在各种下游任务中显著优于BERT及其变体,其训练成本更低,模型参数更少。值得注意的是,ConvBERTbase模型的GLUE评分达到了86.4分,比ELECTRAbase高0.7分,而使用的培训成本不到1/4。该库为遮罩语言模型、token分类、句子分类和问题回答都提供了一个版本的模型。XLMCross-lingual Language Model Pretraining, Guillaume Lample and Alexis Conneau用几种语言训练的transformer模型。这个模型有三种不同的训练方式,库为所有这些类型提供了checkpoint。因果语言建模(CLM)是传统的自回归训练(所以这个模型也可以在前一节中介绍)。为每个训练样本选择一种语言,模型输入是一个包含256个标记的句子,它可以跨越使用其中一种语言的多个文档。遮罩语言模型(MLM)就像RoBERTa。为每个训练样本选择一种语言,模型输入是一个包含256个标记的句子,它可以跨越使用其中一种语言的多个文档,并对标记进行动态屏蔽。遮罩语言模型(MLM)与翻译语言建模(TLM)的结合。这包括用两种不同的语言连接一个句子,并使用随机屏蔽。为了预测其中一个被屏蔽的标记,模型可以同时使用语言1中的上下文和语言2给出的上下文。检查点指的是在名称中包含clm、mlm或mlm-tlm的方法用于预培训。在位置嵌入的基础上,该模型还具有语言嵌入。当使用MLM/CLM进行培训时,它会给模型一个使用语言的指示,而当使用MLM+TLM进行培训时,它会给每个部分使用的语言的指示。该库为语言建模、token分类、句子分类和问题回答都提供了一个版本的模型。XLM-RoBERTaUnsupervised Cross-lingual Representation Learning at Scale, Alexis Conneau et al.在XLM方法上使用RoBERTa的技巧,但不使用翻译语言建模目标。它只对来自一种语言的句子使用遮罩语言模型。然而,该模型训练了更多的语言(100种),并且没有使用语言嵌入,因此它能够自己检测输入语言。该库为掩码语言建模、token分类、句子分类、多项选择分类和问题回答都提供了一个版本的模型。FlauBERTFlauBERT: Unsupervised Language Model Pre-training for French, Hang Le et al.像RoBERTa,没有句子排序预测(所以只是训练在MLM目标)。该库提供了一个用于语言建模和句子分类的模型版本。该库提供了一个用于语言建模和句子分类的模型版本。ELECTRAELECTRA: Pre-training Text Encoders as Discriminators Rather Than Generators, Kevin Clark et al.ELECTRA是一个用另一个(小的)遮罩语言模型预训练的transformer模型。输入被该语言模型损坏,该模型接受一个随机mask的输入文本,并输出一个文本,ELECTRA必须在该文本中预测哪个token是原始的,哪个token被替换了。像GAN训练一样,小语言模型训练了几个步骤(但以原始文本为目标,而不是像传统GAN设置那样愚弄ELECTRA模型),然后ELECTRA模型训练了几个步骤。该库为遮罩语言模型、token分类和句子分类提供了一个版本的模型。Funnel TransformerFunnel-Transformer: Filtering out Sequential Redundancy for Efficient Language Processing, Zihang Dai et al.Funnel Transformer是一个使用池化的Transformer模型,有点像ResNet模型:层被分组在块中,在每个块的开始(除了第一个块),隐藏状态在序列维度中被池化。这样,它们的长度就除以2,这就加快了下一个隐藏状态的计算速度。所有预先训练的模型都有三个区块,这意味着最终隐藏状态的序列长度是原始序列长度的四分之一。对于分类这样的任务,这不是问题,但是对于掩藏语言建模或标记分类这样的任务,我们需要一个与原始输入具有相同序列长度的隐藏状态。在这些情况下,最终的隐藏状态被采样到输入序列长度,并经过两个额外的层。这就是为什么每个checkpoint有两个版本。带-base后缀的版本只包含三个块,而不带该后缀的版本包含三个块和带有附加层的上采样头。可用的预训练模型使用与ELECTRA相同的预训练目标。该库为遮罩语言模型、token分类、句子分类、多选择分类和问题回答提供了一个版本的模型。LongformerLongformer: The Long-Document Transformer, Iz Beltagy et al.为了加快速度而用稀疏矩阵代替注意力矩阵的Transformer模型。通常,本地上下文(例如,左边和右边的两个token是什么?)足以对给定的token采取行动。一些预先选定的输入token仍然得到全局关注,但注意矩阵的参数更少,导致加速。有关更多信息,请参阅本地注意部分。这和RoBERTa一样是预先训练好的。注意:这个模型可以很好地用于自回归模型设置,但是对于这样的预训练还没有checkpoint。该库为遮罩语言模型、token分类、句子分类、多选择分类和问题回答提供了一个版本的模型。序列到序列模型(Sequence-to-sequence models)如前所述,这些模型保留了原Transformer的编码器和解码器。BARTBART: Denoising Sequence-to-Sequence Pre-training for Natural Language Generation, Translation, and Comprehension, Mike Lewis et al.序列到序列模型,有一个编码器和一个解码器。编码器输入一个损坏版本的token,解码器输入原始token(但有一个掩码来隐藏未来的词,就像一个普通的transformer解码器)。以下转换的组合应用于编码器的预训练任务:随机mask掉一些token(如BERT)随机删除token将一段k个token的字段用一个mask token代替重新排列句子旋转文档,使其从特定token开始这个库为条件生成和序列分类提供了这个模型的一个版本。PegasusPEGASUS: Pre-training with Extracted Gap-sentences forAbstractive Summarization, Jingqing Zhang, Yao Zhao, Mohammad Saleh and Peter J. Liu on Dec 18, 2019.Pegasus在两个自我监督的目标函数上进行了联合训练:遮罩语言模型(MLM)和一种新的特定于摘要的预训练目标,称为间隙句生成(GSG)。MLM:编码器输入标记被一个mask token随机替换,并且必须由编码器预测(如BERT)GSG:整个编码器输入的句子被替换为第二个掩码令牌并输入到解码器,但它有一个因果掩码来隐藏未来的单词,就像一个常规的自回归Transformer解码器。与BART不同的是,Pegasus的预训练任务有意地类似于摘要:重要的句子被掩藏起来,并从剩余的句子中生成一个输出序列,类似于摘录摘要。这个库为条件生成提供了这个模型的一个版本,它应该用于摘要。MarianMTMarian: Fast Neural Machine Translation in C++, Marcin Junczys-Dowmunt et al.翻译模型的框架,使用与BART相同的模型。这个库为条件生成提供了这个模型的一个版本。T5Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer, Colin Raffel et al.使用传统的transformer模型(在每个层学习的位置嵌入中有微小的变化)。为了能够操作所有的自然语言处理任务,它通过使用特定的前缀:summarize:, question:, translate English To German:等等,将它们转换成文本到文本的问题。预训练包括有监督和自监督训练。有监督训练是通过由GLUE和SuperGLUE提供的下游任务(如上所述,将它们转换为文本到文本的任务)来进行推断。自监督训练则使用损坏的token方式,通过随机删除15%的token和替换他们为独立sentinel tokens(如果将多个连续token标记为要删除,则整个组将被替换为单个sentinel token)。编码器的输入是被破坏的句子,解码器的输入是原始的句子,然后目标是由它们的sentinel token分隔的被删除的标记。例如,如果我们有一个句子“My dog is very cute .”,我们决定删除标记:“dog”,“is”和“cute”,编码器输入变成了“My very .”目标输入变成了“ dog is cute .”这个库为条件生成提供了这个模型的一个版本。MT5mT5: A massively multilingual pre-trained text-to-text transformer, Linting Xue et al.模型架构与T5相同。mT5的培训前目标包括T5的自监督训练,但不包括T5的有监督训练。mT5接受101种语言的训练。这个库为条件生成提供了这个模型的一个版本。MBartMultilingual Denoising Pre-training for Neural Machine Translation by Yinhan Liu, Jiatao Gu, Naman Goyal, Xian Li, Sergey Edunov Marjan Ghazvininejad, Mike Lewis, Luke Zettlemoyer.模型体系结构和预训练目标与BART相同,但是BART是针对25种语言进行训练的,用于监督和非监督机器翻译。MBart是第一个通过对多语言全文去噪来预训练完整序列到序列模型的方法这个库为条件生成提供了这个模型的一个版本。ProphetNetProphetNet: Predicting Future N-gram for Sequence-to-Sequence Pre-training, by Yu Yan, Weizhen Qi, Yeyun Gong, Dayiheng Liu, Nan Duan, Jiusheng Chen, Ruofei Zhang, Ming Zhou.ProphetNet引入了一种新的序列到序列的预训练目标,称为未来n-gram预测。在未来的n-gram预测中,模型在每个时间步都根据之前的上下文标记同时预测下一个n个标记,而不是只预测单个的next token。未来的n-gram预测明确鼓励模型计划未来的token,并防止强局部相关性的过拟合。该模型架构是在原transformer的基础上,但以一种主要的自我注意机制取代了解码器中的“标准”自我注意机制。这个库为条件生成提供了这个模型的预训练版本,为摘要提供了一个经过微调的版本。XLM-ProphetNetProphetNet: Predicting Future N-gram for Sequence-to-Sequence Pre-training, by Yu Yan, Weizhen Qi, Yeyun Gong, Dayiheng Liu, Nan Duan, Jiusheng Chen, Ruofei Zhang, Ming Zhou.XLM-ProphetNet的模型体系结构和预训练目标与ProphetNet相同,但XLM-ProphetNet是在跨语言数据集XGLUE上进行预训练的。该库分别为多语言条件生成提供了该模型的预训练版本,为标题生成和问题生成提供了微调版本。多模态模型(Multimodal models)在这个库中有一个多模态模型,它没有像其他模型那样经过自我监督的预先训练。MMBTSupervised Multimodal Bitransformers for Classifying Images and Text, Douwe Kiela et al.一种用于多模态设置的Transformer模型,结合文本和图像进行预测。transformer模型将作为输入的映射进行标记化的文本,最后在一个预训练好的图像resnet上通过一个线性层(从特征的数量决定Transformer隐藏层的维度)。不同的输入被连接起来,并且在位置嵌入的基础上,添加一个分段嵌入,让模型知道输入向量的哪一部分对应于文本,哪一部分对应于图像。预先训练的模型只适用于分类。基于检索的模型(Retrieval-based models)有些模型在(前)训练和推理过程中使用文档检索来回答开放领域的问题。DPRDense Passage Retrieval for Open-Domain Question Answering, Vladimir Karpukhin et al.密集通道检索(DPR) -是最先进的开放领域问答研究的一套工具和模型。DPR包括三种模型:问题编码器:将问题编码为向量上下文编码器:将上下文编码为向量读者:在检索到的上下文中提取问题的答案,并给出一个相关性得分(如果推断的跨度确实回答了问题,那么得分就高)。DPR的pipeline(尚未实现)使用检索步骤查找给定某个问题的前k个上下文,然后用问题和检索到的文档调用阅读器以获得答案。RAGRetrieval-Augmented Generation for Knowledge-Intensive NLP Tasks, Patrick Lewis, Ethan Perez, Aleksandara Piktus, Fabio Petroni, Vladimir Karpukhin, Naman Goyal, Heinrich Küttler, Mike Lewis, Wen-tau Yih, Tim Rocktäschel, Sebastian Riedel, Douwe Kiela检索增强生成(RAG)模型结合了预先训练的密集检索(DPR)和Seq2Seq模型的功能。RAG模型检索文档,将它们传递给seq2seq模型,然后边缘化以生成输出。retriver和seq2seq模块由预先训练的模型初始化,并共同进行微调,允许检索和生成以适应下游任务。RAG-Token模型和RAG-Sequence模型可以进行生成。更多的技术内容(More technical aspects)Full vs sparse attention大多数Transformer模型在注意矩阵为方形的意义上使用了充分注意。当您有很长的文本时,它可能成为一个很大的计算瓶颈。Longformer和reformer是两种模型,它们试图提高效率,使用稀疏版本的注意力矩阵来加速训练。LSH attentionReformer使用LSH attention。在softmax(QKT)softmax(QK^T)softmax(QKT)中,只有最大的元素才会给出有用的贡献。所以对于q中的每一个查询q,我们可以只考虑k中接近q的键k。哈希函数用于确定q和k是否接近。注意掩码被修改为掩码当前标记(除了第一个位置),因为它将给出相同的查询和键(非常相似)。因为哈希可能有点随机,所以在实践中会使用几个哈希函数(由n_rounds参数确定),然后一起取平均值。Local attentionLongformer使用了局部注意:通常,局部上下文(例如,左边和右边的两个标记是什么?)足以对给定的标记采取行动。此外,通过叠加具有小窗口的注意层,最后一层将拥有一个接收域,而不仅仅是窗口中的标记,允许它们构建整个句子的表示。一些预先选择的输入token也会得到全局注意:对于那些少数的token,注意矩阵可以访问所有令牌,并且这个过程是对称的:所有其他token都可以访问这些特定的token(在其本地窗口的token之上)。如图本文的图2d所示,下面是注意罩的示例使用那些参数更少的注意矩阵可以让模型的输入具有更大的序列长度。Other tricksReformer使用轴向位置编码:在传统的Transformer模型中,位置编码E是一个大小为l × d的矩阵,l是序列长度,d是隐藏状态的维数。如果你有很长的文本,这个矩阵可能会很大,在GPU上占用太多的空间。为了减轻这种情况,轴向位置编码包括将大矩阵E分解为两个更小的矩阵E1和E2,维度为l1×d1l_1 \times d_1l1×d1和l2×d2l_2 \times d_2l2×d2,这样l1×l2=ll_1 \times l_2 = ll1×l2=l 和 d1+d2=dd_1 + d_2=dd1+d2=d(加上长度的乘积,这最终会小得多)。时间步长j在E中的嵌入是将时间步长j%l1j \% l_1j%l1在E1中的嵌入和j//l1j//l_1j//l1在E2中的嵌入连接起来得到的。数据预处理(Preprocessing data)在本教程中,我们将探讨如何使用 🤗 Transformers对数据进行预处理。这方面的主要工具是我们所说的分词器。我们可以使用与想要使用的模型相关联的tokenizer类,或者直接使用AutoTokenizer类来构建一个。正如我们在Quick tour中看到的,分词器首先将给定文本分割成通常称为token的单词(或部分单词、标点符号等)。然后,它将把这些token转换为数字,以便能够利用它们构建一个张量,并将它们提供给模型。它还将添加模型可能希望正常工作的任何额外输入。注意:如果你计划使用一个预训练模型,使用它相关联的预训练的分词器是很重要的。它将以与前训练语料库相同的方式分割输入文本成为token,并且它将使用与预训练期间相同的对应token索引(我们通常称之为词汇表)。要自动下载预训练期间使用的词汇表或对给定模型进行微调,可以使用from_pretraining()方法。from transformers import AutoTokenizer+ tokenizer = AutoTokenizer.from_pretrained('bert-base-cased')基本使用(Base use)一个 PreTrainedTokenizer 有很多方法,但是你需要记住的唯一一个预处理方法是它的__call__:你只需要把你的句子喂给你的分词器对象。Note:__call__是python的魔法方法,可以把类实例当做函数调用。调用时执行的函数就是类实例的__call__方法。encoded_input = tokenizer("Hello, I'm a single sentence!") print(encoded_input)输出为:{'input_ids': [101, 8667, 117, 146, 112, 182, 170, 1423, 5650, 106, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}这将返回一个字典,值就是字符串对应到整数的列表。input_id是与句子中每个标记对应的索引。下面我们将看到attention_mask的用途,下一节将看到token_type_ids的目标。分词器可以解码正确句子中的标记id列表。tokenizer.decode(encoded_input["input_ids"])输出为:"[CLS] Hello, I'm a single sentence! [SEP]"可以看到,分词器自动添加了模型所期望的一些特殊标记。并不是所有的模型都需要特殊的token。例如,如果我们使用gpt2-medium而不是bert-base-case来创建分词器,那么我们将看到与原来的句子相同的句子。你可以通过传递 add_special_tokens=False 来禁用这个行为(只有当你自己手动添加那些特殊的token时才会被建议)。如果有几个句子需要处理,可以通过将它们作为列表发送给分词器来有效地完成这一任务:batch_sentences = ["Hello I'm a single sentence", "And another sentence", "And the very very last one"] encoded_inputs = tokenizer(batch_sentences) print(encoded_inputs)输出为:{'input_ids': [[101, 8667, 146, 112, 182, 170, 1423, 5650, 102], [101, 1262, 1330, 5650, 102], [101, 1262, 1103, 1304, 1304, 1314, 1141, 102]], 'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]], 'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1]]}我们再次得到了一个字典,这次的值是整数列表的列表。如果一次向tokenizer发送几个句子的目的是构建batch处理为模型提供信息,那么您可能需要这样做:把每个句子填充到batch中最大的长度。将每个句子截断到模型所能接受的最大长度(如果适用的话)。返回张量。当将句子列表输入到分词器时,可以使用以下选项来完成所有这一切。batch = tokenizer(batch_sentences, padding=True, truncation=True, return_tensors="pt") print(batch)输出为:{'input_ids': tensor([[ 101, 8667, 146, 112, 182, 170, 1423, 5650, 102], [ 101, 1262, 1330, 5650, 102, 0, 0, 0, 0], [ 101, 1262, 1103, 1304, 1304, 1314, 1141, 102, 0]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1, 1, 0]])}它返回一个包含字符串键和张量值的字典。现在我们可以看到attention_mask是关于什么的:它指出模型应该注意哪些标记,哪些不应该注意(因为它们在本例中表示填充)。注意,如果您的模型没有与其关联的最大长度,那么上面的命令将抛出一个警告。你可以安全地忽略它。您还可以传递 verbose=False 来阻止分词器抛出此类警告。对句子对进行处理(Preprocessing pairs of sentences)有时你需要给你的模型提供一对句子。例如,如果您想对一对中的两个句子是否相似进行分类,或者对问句回答模型进行分类,它们接受一个上下文和一个问题。对于BERT模型,输入可以这样表示:[CLS] Sequence A [SEP] Sequence B [SEP]我们可以将两个句子作为两个参数提供(不是一个列表,因为两个句子的列表将被解释为两个单个句子的批处理,正如我们前面看到的那样),从而以模型所期望的格式编码一对句子。这将再次返回一个dict字符串到整数列表。encoded_input = tokenizer("How old are you?", "I'm 6 years old") print(encoded_input)输出为:{'input_ids': [101, 1731, 1385, 1132, 1128, 136, 102, 146, 112, 182, 127, 1201, 1385, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}这向我们展示了token_type_ids的用途:它们向模型指示输入的哪一部分对应第一个句子,哪一部分对应第二个句子。注意,token_type_ids不是所有模型都需要或处理的。默认情况下,分词器将只返回其关联模型所期望的输入。可以使用return_input_ids或return_token_type_ids强制返回(或不返回)任何这些特殊参数。如果我们解码我们获得的token id,我们将看到特殊的token被适当地添加了。tokenizer.decode(encoded_input["input_ids"])输出为:"[CLS] How old are you? [SEP] I'm 6 years old [SEP]"如果有一个需要处理的序列对列表,那么应该将它们作为两个列表提供给分词器:第一个句子列表和第二个句子列表。batch_sentences = ["Hello I'm a single sentence", "And another sentence", "And the very very last one"] batch_of_second_sentences = ["I'm a sentence that goes with the first sentence", "And I should be encoded with the second sentence", "And I go with the very last one"] encoded_inputs = tokenizer(batch_sentences, batch_of_second_sentences) print(encoded_inputs)输出为:{'input_ids': [[101, 8667, 146, 112, 182, 170, 1423, 5650, 102, 146, 112, 182, 170, 5650, 1115, 2947, 1114, 1103, 1148, 5650, 102], [101, 1262, 1330, 5650, 102, 1262, 146, 1431, 1129, 12544, 1114, 1103, 1248, 5650, 102], [101, 1262, 1103, 1304, 1304, 1314, 1141, 102, 1262, 146, 1301, 1114, 1103, 1304, 1314, 1141, 102]], 'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1]], 'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]}正如我们所看到的,它返回一个字典,其中每个值都是一个整型数列表的列表。为了再次检查输入到模型中的内容,我们可以逐个解码input_ids中的每个列表。for ids in encoded_inputs["input_ids"]: print(tokenizer.decode(ids))输出为:[CLS] Hello I'm a single sentence [SEP] I'm a sentence that goes with the first sentence [SEP][CLS] And another sentence [SEP] And I should be encoded with the second sentence [SEP][CLS] And the very very last one [SEP] And I go with the very last one [SEP]同样,可以自动将输入填充到批处理中的最大句子长度,截断到模型可以接受的最大长度,并使用以下方法直接返回张量batch = tokenizer(batch_sentences, batch_of_second_sentences, padding=True, truncation=True, return_tensors="pt") print(batch)输出为:{'input_ids': tensor([[ 101, 8667, 146, 112, 182, 170, 1423, 5650, 102, 146, 112, 182, 170, 5650, 1115, 2947, 1114, 1103, 1148, 5650, 102], [ 101, 1262, 1330, 5650, 102, 1262, 146, 1431, 1129, 12544, 1114, 1103, 1248, 5650, 102, 0, 0, 0, 0, 0, 0], [ 101, 1262, 1103, 1304, 1304, 1314, 1141, 102, 1262, 146, 1301, 1114, 1103, 1304, 1314, 1141, 102, 0, 0, 0, 0]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]])}关于填充和截断(Everything you always wanted to know about padding and truncation)我们已经看到了适用于大多数情况的命令(将批处理填充到最大句子的长度,并截断到模式可以接受的最大长度)。但是,如果需要的话,API支持更多的策略。为此需要知道的三个参数是padding、truncation和max_length。padding控制填充。它可以是一个布尔值或字符串,应该是:True或'longest'参数将使得每个句子都填充到batch中最长的序列(如果只提供一个序列,则不填充)。'max_length'参数将使得每个句子都填充到max_length参数指定的长度,或者如果没有提供max_length (max_length=None),则填充为模型可接受的最大长度(例如BERT为512个token)。如果你只提供一个序列,填充仍然会应用到它上面。False或'do_not_pad'不填充序列。正如我们之前看到的,这是默认行为。truncation控制截断。它可以是一个布尔值或字符串,应该是:True或'only_first'截断为max_length参数指定的最大长度,或者如果没有提供max_length,则为模型接受的最大长度(max_length=None)。如果提供了一对序列(或一批序列),这只会截断一对序列的第一个句子。'only_second'截断为max_length参数指定的最大长度,如果没有提供max_length,则截断为模型接受的最大长度(max_length=None)。如果提供了一对序列(或一批序列),这只会截断一对序列的第二个句子。'longest_first'截断为max_length参数指定的最大长度,或者如果没有提供max_length,则为模型接受的最大长度(max_length=None)。这将逐个截断token,从该对中的最长序列中删除一个标记,直到达到适当的长度。False或'do_not_truncate'不截断序列。正如我们之前看到的,这是默认行为。max_length来控制填充/截断的长度。它可以是一个整数或None,在这种情况下,它将默认为模型可以接受的最大长度。如果模型没有特定的最大输入长度,截断/填充到max_length将被禁用。下面是一个表,总结了设置填充和截断的建议方法。如果你用一双输入序列在下列例子中,你可以用一个在[‘only_first’,‘only_second’,' longest_first ']的STRATEGY替换truncation=True,即 truncation='only_second' or truncation= 'longest_first'来控制序列对中的两个序列如何截断。这里通过实验来验证一下这些指令,我使用的是一组pair的形式。首先定义这对pair的batch形式,注意,这里batch_sentences的第i个元素和batch_of_second_sentences的第i个元素才是一个pair。batch_sentences = ["Hello I'm a single sentence", "And another sentence", "And the very very last one"] batch_of_second_sentences = ["I'm a sentence that goes with the first sentence", "And I should be encoded with the second sentence", "And I go with the very last one"]No truncation首先是no truncation,这里分为no padding、padding to max sequence in batch、padding to max model input length、padding to specific length四种情况。no paddingbatch = tokenizer(batch_sentences, batch_of_second_sentences) for ids in batch['input_ids']: # 打印输出的代码全部一样,后续省略 print("===="*36) print(len(tokenizer.convert_ids_to_tokens(ids))) print(tokenizer.convert_ids_to_tokens(ids)) print()输出为:================================================================================================================================================ 21['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]']================================================================================================================================================ 15['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]']================================================================================================================================================ 17['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]']这里输出了经过tokenizer后的sentence pair,再分词得到的token基础上共新增了三个特殊的token。可以看到,在no padding、no truncation的情况下,并未有任何填充和截断的情形(除非句子长到模型最大输入才会截断)发生。由于打印输出的代码都一样,后续打印输出代码省略。padding to max sequence in batchbatch = tokenizer(batch_sentences, batch_of_second_sentences, padding=True) # 等价于# batch = tokenizer(batch_sentences, batch_of_second_sentences, padding='longest')输出为:================================================================================================================================================ 21['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]']================================================================================================================================================ 21['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']================================================================================================================================================ 21['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']在这种情况下,文本已经填充对齐到了最长的那个sentence。*padding to max model input length **batch = tokenizer(batch_sentences, batch_of_second_sentences, padding='max_length')这种情况下,文本会自动填充到模型能接受的最长输入,BERT这里是512. 输出(由于填充到了512个长度,故以下输出只展示了几个PAD)为:================================================================================================================================================ 512['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]', '[PAD]', ]================================================================================================================================================ 512['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]', '[PAD]',]================================================================================================================================================ 512['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]', '[PAD]',]padding to specific length同样,我们也可以自己设置max_length。batch = tokenizer(batch_sentences, batch_of_second_sentences, padding='max_length', max_length=24)================================================================================================================================================ 24['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]', '[PAD]', '[PAD]', '[PAD]']================================================================================================================================================ 24['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']================================================================================================================================================ 24['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']这里设置max_length=24,可以看到文本都被填充到了24。truncation to max model input lengthno paddingbatch = tokenizer(batch_sentences, batch_of_second_sentences, truncation=True)输出为:================================================================================================================================================ 21['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]']================================================================================================================================================ 15['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]']================================================================================================================================================ 17['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]']同时,在sentence pair的情况下,我们实验了以下三句代码输出都是一样的batch = tokenizer(batch_sentences, batch_of_second_sentences, truncation='only_first') batch = tokenizer(batch_sentences, batch_of_second_sentences, truncation='only_second') batch = tokenizer(batch_sentences, batch_of_second_sentences, truncation='longest_first')输出同上,不再展示。padding to max sequence in batchbatch = tokenizer(batch_sentences, batch_of_second_sentences, padding=True, truncation=True)输出为:================================================================================================================================================ 21['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]']================================================================================================================================================ 21['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']================================================================================================================================================ 21['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']这里的文本已经填充对齐,同时由于远远没有达到模型最大输入的长度限制,没有发生截断。然后测试batch = tokenizer(batch_sentences, batch_of_second_sentences, padding=True, truncation='only_first') batch = tokenizer(batch_sentences, batch_of_second_sentences, padding=True, truncation='only_second') batch = tokenizer(batch_sentences, batch_of_second_sentences, padding=True, truncation='longest_first')输出与上面完全一样。padding to max model input lengthbatch = tokenizer(batch_sentences, batch_of_second_sentences, padding='max_length', truncation=True)输出全部填充到了512长度。================================================================================================================================================ 512['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]', '[PAD]', ]================================================================================================================================================ 512['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]', '[PAD]',]================================================================================================================================================ 512['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]', '[PAD]',]同样测试以下三句代码结果也都相同batch = tokenizer(batch_sentences, batch_of_second_sentences, padding='max_length', truncation='only_first') batch = tokenizer(batch_sentences, batch_of_second_sentences, padding='max_length', truncation='only_second') batch = tokenizer(batch_sentences, batch_of_second_sentences, padding='max_length', truncation='longest_first')同时在truncation to max model input length的设置下无法实现padding to specific length。truncation to specific lengthno paddingbatch = tokenizer(batch_sentences, batch_of_second_sentences, truncation=True, max_length=20)这里设置最大长度为16,输出为:================================================================================================================================================ 16['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', '[SEP]']================================================================================================================================================ 15['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]']================================================================================================================================================ 16['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', '[SEP]']可以看到,第一三段文本被截断了,其他第二段既没有被截断也没有被填充。接着,我们分别测试一下[‘only_first’,‘only_second’,’ longest_first ']的差异:首先是batch = tokenizer(batch_sentences, batch_of_second_sentences, truncation='only_first', max_length=16)其输出为================================================================================================================================================ 16['[CLS]', 'Hello', 'I', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]']================================================================================================================================================ 15['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]']================================================================================================================================================ 16['[CLS]', 'And', 'the', 'very', 'very', 'last', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]']可以看到,在only_first设置下,第一个句子被优先截断。batch = tokenizer(batch_sentences, batch_of_second_sentences, truncation='only_second', max_length=16)输出为:================================================================================================================================================ 16['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', '[SEP]']================================================================================================================================================ 15['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]']================================================================================================================================================ 16['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', '[SEP]']可以看到,在only_second设置下,第二个句子被优先截断。在batch = tokenizer(batch_sentences, batch_of_second_sentences, truncation='longest_first', max_length=16) 输出为:================================================================================================================================================ 16['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', '[SEP]']================================================================================================================================================ 15['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]']================================================================================================================================================ 16['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', '[SEP]']此时,优先截断最长的那个(一个token一个token的截断,可能先截断第二句,删除几个token之后一二相同了,就继续截断第一句……)。需要注意的是,截断长度一定要保证两句话都有内容(最少要剩下1个token),否则会报错。batch = tokenizer(batch_sentences, batch_of_second_sentences, truncation='only_first', max_length=15) batch = tokenizer(batch_sentences, batch_of_second_sentences, truncation='only_first', max_length=14)例如上面第一句不会报错,因为这样截断还可以保证第一句有一个token为'I',而第二句会报错。padding to max sequence in batch设置padding=True后,达不到batch中最长长度的句子会被填充。如下代码batch = tokenizer(batch_sentences, batch_of_second_sentences, padding=True, truncation='only_first', max_length=18)输出为:================================================================================================================================================ 18['[CLS]', 'Hello', 'I', "'", 'm', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]']================================================================================================================================================ 18['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]', '[PAD]', '[PAD]', '[PAD]']================================================================================================================================================ 18['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]', '[PAD]']同时截断也正常发挥作用。使用[‘only_first’,‘only_second’,’ longest_first ']设置时也是截断相应的部分。这里不再展示。padding to specific length这里将确保PAD到特定的长度(通过设置max_length来实现)。注意一下代码的对比:batch = tokenizer(batch_sentences, batch_of_second_sentences, padding='max_length', truncation=True, max_length=22)输出:================================================================================================================================================ 22['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]', '[PAD]']================================================================================================================================================ 22['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']================================================================================================================================================ 22['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']而如下代码:batch = tokenizer(batch_sentences, batch_of_second_sentences, padding=True, truncation=True, max_length=22)输出为:================================================================================================================================================ 21['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]']================================================================================================================================================ 21['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']================================================================================================================================================ 21['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']Pre-tokenized输入(Pre-tokenized inputsPre-tokenized inputs)tokenizer还接受预标记化的输入。当想要计算命名实体识别(NER)或词性标记(POS标记)中的标签和提取预测时,这一点特别有用。警告:预标记化并不意味着您的输入已经分词好了(如果是这样的话,您不需要通过tokenizer传递它们),而是将它们分割成单词(这通常是子单词标记化算法(如BPE)的第一步)。如果您想使用预标记化的输入,只需在将输入传递给分词器时设置is_split_into_words=True。例如,我们有encoded_input = tokenizer(["Hello", "I'm", "a", "single", "sentence"], is_split_into_words=True) print(encoded_input)输出为:{'input_ids': [101, 8667, 146, 112, 182, 170, 1423, 5650, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]}注意,分词器仍然会添加特殊标记的id(如果适用),除非传递add_special_tokens=False。同时,tokenizer同样会进行分词操作,只是在设置is_split_into_words=True后,它不会在词和词直接进行分割。如下示例代码:batch = tokenizer(["Hello", "I'm", "a", "single", "sentence"], is_split_into_words=True) ids = batch['input_ids'] # 打印输出的代码全部一样,后续省略print("===="*36) print(len(tokenizer.convert_ids_to_tokens(ids))) print(tokenizer.convert_ids_to_tokens(ids)) print()输出为:================================================================================================================================================ 9['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]']可以看到,I'm这个词内部仍被切分为了I,', m三个token。这和之前的一组句子或一组句子对完全一样。你可以像这样编码一批句子batch_sentences = [["Hello", "I'm", "a", "single", "sentence"], ["And", "another", "sentence"], ["And", "the", "very", "very", "last", "one"]] encoded_inputs = tokenizer(batch_sentences, is_split_into_words=True)或者像这样的一组成对句子batch_of_second_sentences = [["I'm", "a", "sentence", "that", "goes", "with", "the", "first", "sentence"], ["And", "I", "should", "be", "encoded", "with", "the", "second", "sentence"], ["And", "I", "go", "with", "the", "very", "last", "one"]] encoded_inputs = tokenizer(batch_sentences, batch_of_second_sentences, is_split_into_words=True)你可以添加填充,截断以及像之前那样直接返回张量batch = tokenizer(batch_sentences, batch_of_second_sentences, is_split_into_words=True, padding=True, truncation=True, return_tensors="pt")
前言本部分是Transformer库的基础部分的下半部分,主要包括训练和微调、模型共享和上传、分词器汇总、多语言模型。使用Transformers(Using Transformers)训练和微调(Training and fine-tuning) Transformers 中的模型类被设计成符合PyTorch和TensorFlow 2书写习惯的方式,可以与之无缝衔接使用。在这个快速入门中,我们将展示如何使用两种框架中可用的标准培训工具来微调(或从头开始训练)模型。我们还将展示如何使用内含的Trainer()类,它可以处理很多复杂的训练。本指南假设您已经熟悉加载和使用我们的模型进行推理;否则,请参见任务摘要。我们还假设您熟悉在PyTorch或TF2中训练深度神经网络,并特别关注在🤗 Transformers 中进行模型训练的细微差别和工具。在本地PyTorch中进行微调(Fine-tuning in native PyTorch)在Transformer库中的模型类不以TF或PyTorch模块开始,这意味着您可以使用它们就像任何在PyTorch中的模型那样进行推理和优化。让我们考虑在序列分类数据集上微调像BERT这样的掩码语言模型的常见任务。当我们用from_pretrained()实例化模型时,将使用指定模型的模型配置和预先训练过的权值来初始化模型。该库还包括许多特定于任务的最终层或“头”,它们的权值在指定的预训练模型中不存在时被随机实例化。例如,用 BertForSequenceClassification.from_pretrained('bert-base-uncased', num_labels=2) 实例化一个模型将创建一个BERT模型实例的编码器,它的权值是从 bert-base-uncased 得到,同时位于编码器顶部的随机初始化序列分类头,其输出大小为2。默认情况下,模型以eval模式初始化。我们可以调用model.train()将它置于训练模式。from transformers import BertForSequenceClassification model = BertForSequenceClassification.from_pretrained('bert-base-uncased') model.train()输出为: BertForSequenceClassification( (bert): BertModel( (embeddings): BertEmbeddings( (word_embeddings): Embedding(30522, 768, padding_idx=0) (position_embeddings): Embedding(512, 768) (token_type_embeddings): Embedding(2, 768) (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True) (dropout): Dropout(p=0.1, inplace=False) ) (encoder): BertEncoder( (layer): ModuleList( (0): BertLayer( (attention): BertAttention( (self): BertSelfAttention( (query): Linear(in_features=768, out_features=768, bias=True) (key): Linear(in_features=768, out_features=768, bias=True) (value): Linear(in_features=768, out_features=768, bias=True) (dropout): Dropout(p=0.1, inplace=False) ) (output): BertSelfOutput( (dense): Linear(in_features=768, out_features=768, bias=True) (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True) (dropout): Dropout(p=0.1, inplace=False) ) ) (intermediate): BertIntermediate( (dense): Linear(in_features=768, out_features=3072, bias=True) ) (output): BertOutput( (dense): Linear(in_features=3072, out_features=768, bias=True) (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True) (dropout): Dropout(p=0.1, inplace=False) ) ) ## 省略中间(1)-(10)都是一样的结构# (11): BertLayer( (attention): BertAttention( (self): BertSelfAttention( (query): Linear(in_features=768, out_features=768, bias=True) (key): Linear(in_features=768, out_features=768, bias=True) (value): Linear(in_features=768, out_features=768, bias=True) (dropout): Dropout(p=0.1, inplace=False) ) (output): BertSelfOutput( (dense): Linear(in_features=768, out_features=768, bias=True) (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True) (dropout): Dropout(p=0.1, inplace=False) ) ) (intermediate): BertIntermediate( (dense): Linear(in_features=768, out_features=3072, bias=True) ) (output): BertOutput( (dense): Linear(in_features=3072, out_features=768, bias=True) (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True) (dropout): Dropout(p=0.1, inplace=False) ) ) ) ) (pooler): BertPooler( (dense): Linear(in_features=768, out_features=768, bias=True) (activation): Tanh() ) ) (dropout): Dropout(p=0.1, inplace=False) (classifier): Linear(in_features=768, out_features=2, bias=True) )输出展示了BERTbaseBERT_{base}BERTbase的内部结构,一共有12层Transformer的encoder层在其中。微调模型是有用的,因为它允许我们使用预先训练的BERT编码器,并且很容易地在我们选择的任何序列分类数据集上训练它。我们可以使用任何PyTorch优化器,但是我们的库也提供了AdamW()优化器,它实现了梯度偏差校正和权重衰减。from transformers import AdamW optimizer = AdamW(model.parameters(), lr=1e-5)优化器允许我们为特定的参数组应用不同的超参数。例如,我们可以对除偏差和层归一化项外的所有参数应用权重衰减:no_decay = ['bias', 'LayerNorm.weight'] optimizer_grouped_parameters = [ {'params': [p for n, p in model.named_parameters() if not any(nd in n for nd in no_decay)], 'weight_decay': 0.01}, {'params': [p for n, p in model.named_parameters() if any(nd in n for nd in no_decay)], 'weight_decay': 0.0} ] optimizer = AdamW(optimizer_grouped_parameters, lr=1e-5)这里any() 函数用于判断给定的可迭代参数 iterable 是否全部为 False,则返回 False,如果有一个为 True,则返回 True。元素除了是 0、空、FALSE 外都算 TRUE。现在我们可以使用__call__()设置一个简单的虚拟训练批处理。这将返回一个BatchEncoding()实例,该实例准备我们可能需要传递给模型的所有东西。from transformers import BertTokenizer tokenizer = BertTokenizer.from_pretrained('bert-base-uncased') text_batch = ["I love Pixar.", "I don't care for Pixar."] encoding = tokenizer(text_batch, return_tensors='pt', padding=True, truncation=True) input_ids = encoding['input_ids'] attention_mask = encoding['attention_mask']当我们使用labels参数调用分类模型时,第一个返回的元素是预测和传递的标签之间的交叉熵损失。在建立了优化器之后,我们可以向后传递并更新权重:labels = torch.tensor([1,0]).unsqueeze(0) outputs = model(input_ids, attention_mask=attention_mask, labels=labels) loss = outputs.loss loss.backward() optimizer.step()或者,你也可以直接得到logit,自己计算损失。下面的示例与前面的示例相同:from torch.nn import functional as F labels = torch.tensor([1,0]) outputs = model(input_ids, attention_mask=attention_mask) loss = F.cross_entropy(outputs.logits, labels) loss.backward() optimizer.step()当然,你可以像往常一样通过调用.to('cuda')将模型和输入放到GPU上来训练。我们还提供了一些学习速率调度工具。通过以下步骤,我们可以设置一个调度器,该调度器先为num_warmup_steps热身,然后在训练结束时线性衰减为0。from transformers import get_linear_schedule_with_warmup scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps, num_train_steps)然后,我们要做的就是在optimizer.step()之后调用scheduler.step()。loss.backward() optimizer.step() scheduler.step()我们强烈推荐使用以下即将讨论的Trainer(),它可以方便地处理训练模型时移动的部分,与诸如混合精度之类的功能和容易进行tensorboard日志记录。冻结编码器(Freezing the encoder)在某些情况下,我们可能对保持预训练编码器的权重不变和只优化头部层的权重感兴趣。要做到这一点,只需在编码器参数上将requires_grad属性设置为False,它可以通过库中任何特定于任务的模型的base_model子模块访问:for param in model.base_model.parameters(): param.requires_grad = False在本地TF-2中微调(Fine-tuning in native TensorFlow 2)暂时不太熟悉,略训练器(Trainer)我们还通过Trainer()和TFTrainer()提供了一个简单但功能完整的训练和评估接口。你可以训练、调整和评估任 Transformers 上的模型,使用广泛的训练选项和内置功能,如日志记录、梯度积累和混合精度。from transformers import BertForSequenceClassification, Trainer, TrainingArguments model = BertForSequenceClassification.from_pretrained("bert-large-uncased") training_args = TrainingArguments( output_dir='./results', # output directory num_train_epochs=3, # total # of training epochs per_device_train_batch_size=16, # batch size per device during training per_device_eval_batch_size=64, # batch size for evaluation warmup_steps=500, # number of warmup steps for learning rate scheduler weight_decay=0.01, # strength of weight decay logging_dir='./logs', # directory for storing logs ) trainer = Trainer( model=model, # the instantiated Transformers model to be trained args=training_args, # training arguments, defined above train_dataset=train_dataset, # training dataset eval_dataset=test_dataset # evaluation dataset )现在只需调用trainer.train()来进行培训,调用trainer.evaluate()来进行评估。也可以使用自己的模块,但是forward返回的第一个参数必须是希望优化的loss。Trainer()使用一个内置的默认函数来整理batch并准备它们以供输入模型。如果需要,还可以使用data_collator参数来传递自己的collator函数,该函数以数据集提供的格式接收数据,并返回准备提供给模型的批处理。注意,TFTrainer()期望传递的数据集是来自tensorflow_datasets的数据集对象。为了计算损失之外的额外指标,您还可以定义自己的compute_metrics函数并将其传递给训练器。from sklearn.metrics import accuracy_score, precision_recall_fscore_support def compute_metrics(pred): labels = pred.label_ids preds = pred.predictions.argmax(-1) precision, recall, f1, _ = precision_recall_fscore_support(labels, preds, average='binary') acc = accuracy_score(labels, preds) return { 'accuracy': acc, 'f1': f1, 'precision': precision, 'recall': recall }Trainer()类的初始化构造方法中是可以接受compute_metrics参数的,因而可以直接传入。最后,您可以通过在指定的logging_dir目录中启动tensorboard来查看结果,包括任何计算指标。模型共享和上传(Model sharing and uploading)在本页面中,我们将向您展示如何在model hub上与社区共享您培训过的模型或对新数据进行微调的模型。此处内容暂时省略,详细请参考Model sharing and uploading分词器汇总(Summary of the tokenizers)在本页中,我们将更仔细地研究分词器。正如我们在预处理教程中看到的,对文本进行标记就是将其分解为word或sub-word,然后通过查找表将它们转换为id。将word或sub word转换为ids非常简单,因此在本总结中,我们将重点关注将文本拆分为word或subword(即对文本进行tokenize)。更确切的说,我们将关注于Transformer中三种主要的分词器:Byte-Pair Encoding(BPE),WordPiece和SentencePiece,并展示这些类型的分词器被哪些模型所使用。注意:在每个模型页面上,您可以查看相关标记器的文档,以了解预先训练过的模型使用的是哪种标记器类型。例如,如果我们查看BertTokenizer,我们可以看到模型使用WordPiece。简介(Introduction)将文本分割成更小的块是一项比看起来更困难的任务,有多种方法可以做到这一点。例如,让我们来看看这句话“Don't you love🤗transformer ? We sure do.”标记这个文本的一个简单方法是用空格分隔它,这将给出:["Don't", "you", "love", "🤗", "Transformers?", "We", "sure", "do."]这是明智的第一步,但如果我们看看令牌“Transformer ?”和“do.”,我们会注意到标点被附加到单词“Transformer”和“do”上,这不是最优的。我们应该考虑到标点符号,这样模型就不必学习单词的不同表示形式以及它后面可能出现的每一个标点符号,否则模型必须学习的表示形式的数量就会激增。考虑到标点符号,我们将得到如下结果["Don", "'", "t", "you", "love", "🤗", "Transformers", "?", "We", "sure", "do", "."]更好了。然而,符号化如何处理“don't”这个词是不太恰当的。“Don't”代表“do not”,所以最好把它标记为[“do”,“n't”]。这就是事情开始变得复杂的地方,这也是每个模型都有自己的标记器类型的部分原因。根据我们申请对文本进行标记的规则,将为相同的文本生成不同的标记化输出。一个预先训练过的模型只有在输入一个标记化了的规则的情况下才能正常执行,这些标记化的规则与用于标记它的训练数据的规则相同。spaCy和Moses是两个流行的基于规则的分词器。将它们应用到我们的例子中,spaCy和Moses会输出如下内容:["Do", "n't", "you", "love", "🤗", "Transformers", "?", "We", "sure", "do", "."]可以看到,这里使用了空格和标点分词,以及基于规则的分词。空间、标点和规则化都是词的词素化,词的词素化被宽泛地定义为把句子分裂成词。虽然它是将文本分割成更小块的最直观的方法,但这种标记化方法可能会导致大量文本语料库出现问题。在这种情况下,空格和标点标记化通常会生成一个非常大的词汇表(使用的所有唯一单词和标记的集合)。例如,Transformer XL使用空格和标点符号化,导致词汇大小为267,735。如此大的词汇量迫使模型有一个巨大的嵌入矩阵作为输入和输出层,这导致了内存和时间复杂性的增加。一般来说,transformer模型的词汇量很少超过50,000个,特别是当它们仅被预先训练为一种语言时。因此,如果简单的空格和标点符号化不能令人满意,为什么不简单地对字符进行符号化呢? 虽然字符标记化非常简单,可以大大减少内存和时间复杂性,但它使模型更难学习有意义的输入表示。例如,学习字母“t”的有意义的语境无关的表达比学习单词“today”的语境无关的表达要困难得多。因此,字符标记化常常伴随着性能的损失。因此,为了两全其美,transformer模型使用了一种介于词级和字符级的标记化,称为sub word标记化。Sub-word 分词(Subword tokenization)subword 分词算法的原理是:频繁使用的词不应该被拆分为更小的子词,而罕见的词应该被拆分为有意义的子词。例如, "annoyingly" 可能被认为是一个罕见的词,它可以被分解为 "annoying" 和 "ly"。相比把"annoyingly" 单独作为一个subword来说,"annoying" 和 "ly"都是会更频繁出现的独立subword。这在黏着性语言中特别有用,比如土耳其语,你可以通过把subword串在一起来形成(几乎)任意长的复杂单词。subword分词允许模型拥有一个合理的词汇量,同时能够学习有意义的上下文无关的表示。此外,subword 分词使该模型能够通过将它们分解为已知子词来处理它以前从未见过的词。例如, BertTokenizer将把"I have a new GPU!"分词为如下形式from transformers import BertTokenizer tokenizer = BertTokenizer.from_pretrained("bert-base-uncased") tokenizer.tokenize("I have a new GPU!")输出为:["i", "have", "a", "new", "gp", "##u", "!"]因为我们考虑的是无大小写模型,所以句子首先是小写的。我们可以看到这些单词 ["i", "have", "a", "new"] 出现在分词器的词汇表中,但“gpu”这个词却不是。因此,分词器将“gpu”拆分为已知的子词:[“gp”和“##u”]。“##”表示该标记的其余部分应该附加到前一个标记上,不包含空格(用于解码或反转标记)。+作为另一个例子,XLNetTokenizer对我们之前的示例文本分词如下:from transformers import XLNetTokenizer tokenizer = XLNetTokenizer.from_pretrained("xlnet-base-cased") tokenizer.tokenize("Don't you love 🤗 Transformers? We sure do.")输出为:["▁Don", "'", "t", "▁you", "▁love", "▁", "🤗", "▁", "Transform", "ers", "?", "▁We", "▁sure", "▁do", "."]我们将在介绍SentencePiece时来介绍"__"的意思,我们可以看到,“Transformer”这个罕见的词已经被分成了更常用的子词“Transform”和“ers”。现在让我们看看不同的subword 分词算法是如何工作的。请注意,所有这些分词算法都依赖于某种形式的训练,而这种训练通常是在训练相应模型的语料库上进行的。字节对编码(Byte-Pair Encoding (BPE))字节对编码(BPE)是由 _ Neural Machine Translation of Rare Words with Subword Units (Sennrich et al., 2015)._ 引入。BPE依赖于将训练数据分解成单词的预标记器。预标记化可以像空间标记化一样简单,例如GPT-2、Roberta。更高级的预标记化包括基于规则的标记化,例如XLM,FlauBERT对大多数语言使用Moses,或使用Spacy和ftfy的GPT,来计算训练语料库中每个单词的频率。在预标记化之后,创建了一组唯一的单词,并确定了每个单词在训练数据中出现的频率。接下来,BPE创建一个基础词汇表,该词汇表包含出现在唯一词汇集中的所有符号,并学习合并规则,从基础词汇表的两个符号形成一个新符号。它一直这样进行下去,直到词汇表达到所需的词汇量为止。注意,所需的词汇表大小是在训练标记器之前定义的超参数。举个例子,假设在预标记化之后,已经确定了以下一组单词,包括它们的频率:("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)因此,基本词汇是“b”、“g”、“h”,“n”,“p”,“s”、“u”。将所有单词分解为基础词汇的符号,我们得到:("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5)然后BPE计算每个可能的符号对的频率,并选择出现频率最高的符号对。在这个例子中,前面是"h",后面跟着"u"的组合出现了10+5=15次(10次在"hug"中出现,5次在"hugs"中出现)。然而,最频繁的是"u"后面跟着"g",这个组合出现了10+5+5=20次。因此,分词器学习的第一个合并规则是将所有的“u”符号和一个“g”符号组合在一起。接下来,“ug”被添加到词汇表中。这组词就变成了:("h" "ug", 10), ("p" "ug", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "ug" "s", 5)然后BPE标识下一个最常见的符号对。就是"un"共出现16次,然后"u"和"n"合并加入到词表中。紧随其后的是“h”和“ug”,共出现了15次。再次将这两个词合并,"hug"也添加到词汇表中。在这个阶段,词汇表是["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"]我们的一组唯一的单词被表示为("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5)假设字节对编码训练在此停止,那么学习到的合并规则将应用于新单词(只要这些新单词不包括基础词汇表中不包含的符号)。例如,当单词"bug"被分词为["b","ug"],但"mug"将被分词为["<unk>", "ug"],这是因为符号'm"并不在基础词汇表中。通常来说,单个字母并不会被替换为"<unk>"符号因为训练数据中每个字母都会至少出现一次,但对于一些非常特殊的字符比如表情来说就不是这样了。如前所述,词汇量大小,即基本词汇量大小+合并次数,是一个可以选择的超参数。例如,GPT的词汇表大小为40,478,因为它们有478个基本字符,并且选择在40,000个合并后停止训练。字节级别BPE(Byte-level BPE)一个包含所有可能的基本字符的基本词汇表可能非常大,例如,如果所有unicode字符都被认为是基本字符。为了获得更好的基本词汇表,GPT-2使用字节作为基本词汇表,这是一种聪明的技巧,它强制基本词汇表的大小为256,同时确保每个基本字符都包含在词汇表中。通过一些处理标点符号的额外规则,GPT2的标记化器可以对每个文本进行标记,而不需要<unk>符号。GPT-2的词汇表大小为50257,它对应256字节的基本token、一个特殊的文本结束token和通过50,000个合并学习到的符号。WordPieceWordPiece是用于BERT、蒸馏BERT和Electra的子词标记化算法。算法概述在 Japanese and Korean Voice Search (Schuster et al., 2012),和BPE非常相似。WordPiece首先初始化词汇表,包括训练数据中出现的每个字符,并逐步学习给定数量的合并规则。与BPE不同,WordPiece并不选择最频繁的符号对,而是选择在将训练数据添加到词汇表后最大限度地提高其可能性的符号对。那么这到底意味着什么呢?参照前面的例子,最大化训练数据的似然性等同于找到符号对,其概率除以其第一个符号之后第二个符号的概率在所有符号对中最大。如。只有当“ug”除以“u”,“g”的概率大于任何其他符号对时,“u”和“g”才会被合并。直观地说,WordPiece与BPE略有不同,因为它通过合并两个符号来评估它的损失,以确保它是有价值的。UnigramUnigram是一种subword 分词算法,详情在_Subword Regularization: Improving Neural Network Translation Models with Multiple Subword Candidates (Kudo, 2018)._。与BPE或WordPiece不同,Unigram将其基本词汇表初始化为大量的符号,并逐步削减每个符号以获得更小的词汇表。例如,基本词汇表可以对应所有预先标记的单词和最常见的子字符串。Unigram不是直接用于Transformer中的任何模型,但是它与SentencePiece连用。在每个训练步骤中,Unigram算法在给定当前词汇表和Unigram语言模型的情况下定义训练数据的损失(通常定义为对数似然)。然后,对于词汇表中的每个符号,算法计算如果将该符号从词汇表中移除,总损失将增加多少。Unigram然后移除p (p通常是10%或20%)损失增加最少的符号的百分比,即那些对训练数据的整体损失影响最小的符号。重复这个过程,直到词汇表达到所需的大小。Unigram算法总是保留基本字符,以便任何单词都可以被标记。由于Unigram不基于合并规则(与BPE和WordPiece不同),该算法在训练后有多种对新文本进行标记的方法。例如,如果一个训练好的Unigram标记器显示词汇表: ["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"],"hugs"可以被分词为 ["hug", "s"], ["h", "ug", "s"]或者["h", "u", "g", "s"]。那么到底该选择哪一种呢?Unigram在保存词汇的基础上,保存了训练语料库中每个token的概率,这样在训练后就可以计算每个可能的token化概率。该算法只是在实践中选择最有可能的标记化,但也提供了根据其概率对可能的标记化进行抽样的可能性。这些概率是由标记器所受训练的损失定义的。假设训练数据由以下单词组成:x1x2⋯ xNx_1, x_2, \cdots, x_Nx1,x2,⋯,xN,并且单词xix_ixi的所有可能的标记化的集合定义为S(xi)S(x_i)S(xi),那么总的损失定义为SentencePiece到目前为止所描述的所有标记化算法都有相同的问题:假设输入文本使用空格来分隔单词。然而,并不是所有的语言都使用空格来分隔单词。一个可能的解决方案是使用特定语言的预分词(Pre-Tokenizer),例如,XLM使用特定的汉语、日语和泰国语预标记器)。为了更普遍地解决这个问题,_SentencePiece: A simple and language independent subword tokenizer and detokenizer for Neural Text Processing (Kudo et al., 2018) _ 将输入视为原始输入流,因此包括要使用的字符集中的空格。然后,它使用BPE或unigram算法构造适当的词汇表。例如,XLNetTokenizer使用了句子,这也是为什么在前面的例子中“__”字符被包含在词汇表中。使用SentencePiece进行解码非常简单,因为所有的符号都可以连接起来,而“__”被一个空格所取代。该库中所有使用句子块的transformer模型都将它与unigram结合使用。使用SentencePiece的例子有ALBERT、XLNet、Marian和T5。多语言模型(Multi-lingual models)这个库中的大多数模型都是单语言模型(英语、汉语和德语)。有一些多语言模型是可用的,它们具有与单语言模型不同的机制。此页面详细介绍了这些模型的使用。目前支持多种语言的两个模型是BERT和XLM。XLM不太了解,略BERTBERT有两个checkpoint,可以用于多语言任务:bert-base-multilingual- uncastion(掩码语言建模+下一句预测,102种语言)bert-base-multilingual(掩码语言建模+下一句预测,104种语言)这些checkpoint不需要在推理时进行语言嵌入。他们应该识别上下文中使用的语言,并据此进行推断。
Huggingface的Transformers库是一个很棒的项目,该库提供了用于自然语言理解(NLU)任务(如分析文本的情感)和自然语言生成(NLG)任务(如用新文本完成提示或用另一种语言翻译)的预先训练的模型。其收录了在100多种语言上超过32种预训练模型。这些先进的模型通过这个库可以非常轻松的调取。同时,也可以通过Pytorch和TensorFlow 2.0进行编写修改等