1 简介

Transformers GitHub 上一个开源项目,提供了上千种 state-of-the-art NLP 预训练模型(BERT 类等),同时支持 PyTorch 和 TensorFlow 2.0. 我们用它,主要是它提供了很多 API,可以让我们:

  • 快速下载预训练的模型
  • 对模型在我们自己的数据集上 fine-tune
  • 将模型应用在 Transform 支持的 NLP 任务上(目前包括文本分类、NER、问答、摘要等)
  • 可以与 PyTorch 和 TenforFlow 无缝衔接,将模型直接用在这两个框架中

2 快速入门

翻译 Transformer 官方教程

2.1 使用 pipeline 完成指定任务

使用预训练模型完成给定任务最简单的方法就是使用 pipeline() 。Transformers 目前支持以下任务:

  • 情感分析:给定文本的情感是积极的还是消极的?
  • 生成文本(英语):提供一个句子,模型会生成它的后续。
  • 命名实体识别(NER):在输入句子中,给每一个单词标记词性
  • 问答:给模型一些上下文和一个问题,让模型从上下文语境中提取答案
  • 填充被遮住的文本(Filling masked text):给定文本中有一些词被遮盖(比如使用 [MASK] 替换),模型需要填充这个空白
  • 摘要:对一段长文本生成摘要
  • 特征提取:返回文本的张量表示

下面以情感分析举例(其他任务都在 task summary 章节中介绍):

  1. >>> from transformers import pipeline
  2. >>> classifier = pipeline('sentiment-analysis')

第一次输入的时候会将预训练模型以及模型使用的分词器(tokenizer)下载并缓存。分词器的任务就是帮助模型对文本进行预处理,后者的任务那就是预测啦。 pipeline 将二者结合在一起,并且对输出结果进一步处理,是它们更具有可读性。

  1. >>> classifier('We are very happy to show you the 🤗 Transformers library.')
  2. [{'label': 'POSITIVE', 'score': 0.9997795224189758}]

我们也可以输入一个有不同句子组成的列表,它们会被当作一个 batch 喂给模型,模型返回一个字典的列表

  1. >>> results = classifier(["We are very happy to show you the 🤗 Transformers library.",
  2. ... "We hope you don't hate it."])
  3. >>> for result in results:
  4. ... print(f"label: {result['label']}, with score: {round(result['score'], 4)}")
  5. label: POSITIVE, with score: 0.9998
  6. label: NEGATIVE, with score: 0.5309

我们看到第二个句子被分类为消极的(模型只能从积极和消极中选一个),它的得分很中立。默认情况下, pipeline() 下载使用的模型是“distilbert-base-uncased-finetuned-sst-2-english”。

如果我们想使用另一个模型,比如一个在法语数据集上训练的模型。我们可以在 model hub 上搜索模型,这些模型大都是一些实验室或者社区预训练的模型。在搜索栏中搜索“French”和“text-classification”会给出建议使用的模型“nlptown/bert-base-multilingual-uncased-sentiment”。

下面直接在 pipeline() 中传入模型名字就可以使用了:

  1. classifier = pipeline('sentiment-analysis', model="nlptown/bert-base-multilingual-uncased-sentiment")

不仅如此,你还可以将模型的名字替换成存有预训练模型的本地文件夹的名字(我们在下面将会看到)。你也可以将模型对象和它对应的 tokenizer 作为参数传进去。

上面设计了两个类,一个是 AutoTokenizer ,用来下载模型使用的 tokenizer 并且对其初始化;另一个是 AutoModelForSequenceClassification (TensorFlow 版本为 TFAutoModelForSequenceClassification ),用来下载模型本身。当然如果我们想进行其他任务,那么使用模型的类也会发生变化。具体看 task summary 教程。

  1. >>> model_name = "nlptown/bert-base-multilingual-uncased-sentiment"
  2. >>> model = AutoModelForSequenceClassification.from_pretrained(model_name)
  3. >>> tokenizer = AutoTokenizer.from_pretrained(model_name)
  4. >>> classifier = pipeline('sentiment-analysis', model=model, tokenizer=tokenizer)

如果你找不到一个在与你相似数据集上预训练的模型,你可以在你的数据上 fine-tune 一个预训练的模型。Transformer 提供了示例代码。一旦你整完了,可以根据教程在社区分享你的模型。

2.2 pipeline 背后的预训练模型

前面我们已经看到,我们通过 from_pretrained() 方法创建了模型及其 tokenizer。

  1. >>> from transformers import AutoTokenizer, AutoModelForSequenceClassification
  2. >>> model_name = "distilbert-base-uncased-finetuned-sst-2-english"
  3. >>> pt_model = AutoModelForSequenceClassification.from_pretrained(model_name) # PyTorch model
  4. >>> tokenizer = AutoTokenizer.from_pretrained(model_name)

2.2.1 使用 tokenizer

Tokenizer 首先将文本进行分词(token),然后将每一个 token 替换为其在 word embedding 中的序号。为此,tokenizer 维护一个词汇表,这一部分也在我们调用 from_pretrain() 方法的时候下载了。

  1. >>> inputs = tokenizer("We are very happy to show you the 🤗 Transformers library.")

返回值是一个 string 到 list 的字典,包括 token idsattention masks

  1. >>> print(inputs)
  2. {'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]}

你也可以直接将文本组成的 list 传给 tokenizer。如果你想将这些文本作为一个 batch 喂给模型,那么可能还需要把它们填充(padding)到同样的长度,或者截断(truncate)到最大长度。

  1. pt_batch = tokenizer(
  2. ["We are very happy to show you the 🤗 Transformers library.", "We hope you don't hate it."],
  3. padding=True,
  4. truncation=True,
  5. max_length=512,
  6. return_tensors="pt"
  7. )
  1. for key, value in pt_batch.items():
  2. print(f"{key}: {value.numpy().tolist()}")
  3. input_ids: [[101, 2057, 2024, 2200, 3407, 2000, 2265, 2017, 1996, 100, 19081, 3075, 1012, 102], [101, 2057, 3246, 2017, 2123, 1005, 1056, 5223, 2009, 1012, 102, 0, 0, 0]]
  4. 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, 0, 0, 0]]

更多关于 tokenizer,请看链接

2.2.2 使用模型

一旦你的输入被 tokenizer 预处理过了,你就可以直接把它喂给模型了。就像上面我们提到的,它包含了模型需要的所有相关信息。如果你使用的是 TensorFlow 模型,你可以直接将这个字典传递给 tensors;如果你使用的是 PyTorch 模型,你需要使用 ** 来把字典解包。

  1. >>> pt_outputs = pt_model(**pt_batch)

在 Transformers 中,所有的输出都是元组(只有一个元素)。这里我们拿到了一个元组,装有模型的最后一层激活值。

  1. >>> print(pt_outputs)
  2. (tensor([[-4.0833, 4.3364],
  3. [ 0.0818, -0.0418]], grad_fn=<AddmmBackward>),)

模型可以不只返回最后一层激活值,这就是为什么输出是一个元组。但是上面我们只要求返回最后一层的激活值,所以这个元组只有一个元素。

接下来我们在激活值上应用 SoftMax 函数,得到最后的预测值:

  1. >>> import torch.nn.functional as F
  2. >>> pt_pred = F.softmax(pt_outputs[0], dim=-1)
  3. >>> print(pt_pred)
  4. tensor([[2.2043e-04, 9.9978e-01],
  5. [5.3086e-01, 4.6914e-01]], grad_fn=<SoftmaxBackward>)

如果你还有标签,你也可以将它们喂给模型,它会返回一个装有损失值和最后一层激活值的元组。

  1. >>> import torch
  2. >>> pt_outputs = pt_model(**pt_batch, torch.tensor([1,0]))

上面的模型都是 torch.nn.Module 或者 tf.keras.Model 的子类,所以你可以直接在你的训练 loop 中使用这些模型。Transformers 也提供了 Trainer (TensorFlow 版本为 TFTrainer )来帮助你进行训练。可以查看训练教程以了解更多。

一旦你的模型完成了 fine-tune,你就可以通过以下的方式保存模型及其 tokenizer:

  1. tokenizer.save_pretrained(save_directory)
  2. model.save_pretrained(save_directory)

然后可以将保存模型的目录传给 from_pretrained() 方法重新加载模型,而不是像上面输入模型的名字。Transformers 支持模型在 PyTorch 和 TensorFlow 之间切换:不管你之前是使用 PyTorch 还是 TensorFlow 保存的模型,你都可以重新加载到 TF 或者 PyTorch 中。如果是使用 PyTorch 保存的模型,可以按照如下方式加载到 TF 中:

  1. tokenizer = AutoTokenizer.from_pretrained(save_directory)
  2. model = TFAutoModel.from_pretrained(save_directory, from_tf=True)

反之,将 TF 保存的加载到 PyTorch 中:

  1. tokenizer = AutoTokenizer.from_pretrained(save_directory)
  2. model = AutoModel.from_pretrained(save_directory, from_tf=True)

最后,你也可以让模型返回所有的隐藏状态和 attention 权重:

  1. >>> pt_outputs = pt_model(**pt_batch, output_hidden_state=True, output_attentions=True)
  2. >>> all_hidden_states, all_attentions = pt_outputs[-2:]

2.2.3 获取代码

AutoModelAutoTokenizer 类都只是自动加载预训练模型的简便写法。在这两个类背后,Transformers 库为每一种模型的结构创建单独的一个类,所以你可以轻松获得代码并对它进行修改。

在之前的示例中,名为 “distilbert-base-uncased-finetuned-sst-2-english” 的模型代表着它使用 DistilBERT 架构。就像之前用的 AutoModelForSequenceClassification 一样,自动创建的模型是一个 DistilBertForSequenceClassification 。你可以查看文档以获得更多细节。

  1. from transformers import DistilBertTokenizer, DistilBertForSequenceClassification
  2. model_name = "distilbert-base-uncased-finetuned-sst-2-english"
  3. model = DistilBertForSequenceClassification.from_pretrained(model_name)
  4. tokenizer = DistilBertTokenizer.from_pretrained(model_name)

2.2.4 修改模型

如果你想修改模型的结构,你可以定义自己的配置 class。每一个结构都有自己的配置(DistilBertConfig 中为 DistilBertConfig ),它可以让你指定隐藏状态的维度以及 dropout 的概率。但如果你想更改模型的结构,比如 hidden size,你就不能再使用这个预训练的模型了,而要从零开始训练。你可以直接从配置中初始化模型。

这里我们使用预定义的 DistilBERT 词汇表(即使用 from_pretrained() 方法读取 tokenizer)并且从零初始化模型(所以要使用 configuration 配置模型,而不是使用 from_pretrained() 方法来读取)。

  1. from transformers import DistilBertConfig, DistilBertTokenizer, DistilBertForSequenceClassication
  2. config = DistilBertConfig(n_heads=8, dim=512, hidden_dim=4*512)
  3. tokenzier = DistilBertTokenizer.from_pretrained('distilbert-base-uncased')
  4. model = DistilBertForSequenceClassification(config)

如果只是改变模型的头部(比如标签的数量),那我们还是可以使用预训练好的模型的。比如,下面我们定义一个 10 分类的分类器。

  1. from transformers import DistilBertConfig, DistilBertTokenizer, DistilBertForSequenceClassification
  2. model_name = "distilbert-base-uncased"
  3. model = DistilBertForSequenceClassification.from_pretrained(model_name, num_labels=10)
  4. tokenizer = DistilBertTokenizer.from_pretrained(model_name)