Hugging Face Transformers 学习1

宦琪
2023-12-01

我们来学习一下 Hugging Face Transformers 这个很方便的NLP library。本节我们首先看一下官方的 Quick Tour: https://huggingface.co/transformers/quicktour.html#

Pipeline

官方的解释从pipeline开始。它把所有的东西全部打包到了一起为一个端到端的工具,对初学者非常友好。好,首先加载pipeline

from transformers import pipeline

加载完之后我们来创建第一个模型

classifier = pipeline('sentiment-analysis')

非常简单的操作,直接告诉 pipeline我们要干啥便能得到 classifier 这个model。那么这里的 task 是 sentiment-analysis, 就是给一句话分析语气是 positive 还是 negative.

注意:

  1. 运行上面那句话之后,系统会自动下载一个 pretrained model 和 一个 tokenizer.
  2. pretrained model 就是别人已经帮你预训练好并且上载到他们的model hub的一个模型。比如说这里我们做的是 sentiment-analysis 这个任务,默认的下载模型叫 distilbert-base-uncased-finetuned-sst-2-english. 名字有点长但是很是 self-explanatory,就是使用了 DistilBERT 架构的,并且已经在 SST-2 这个 dataset 上 fine-tuned 过的一个model,那这个数据集本身就是做文本情感分析的,所以很适合我们自己的 sentiment-analysis task。
  3. 另一个tokenizer 是什么尼?他就是一个对文本的预处理器。每个model都有一个对应的tokenizer,来确保你使用时候对文本的预处理和他训练时候对文本的预处理是一致的。
  4. pipeline这个类的作用就是把 model 和 tokenizer 打包在一起 并 post-process 模型输出的 predictions 为可读的形式,这样直接输出的结果你就知道是positive还是negative了。
  5. 除了pretrained model 和 tokenizer,实际上pipeline还打包了一个模型配置configuration,我们暂且用不到,在本文最后修改模型架构时候再说。

使用pipeline

下面我们可以尝试一下用 classifier 这个模型了,看看表现如何

classifier('We are very happy to show you the 珞 Transformers library.')

# output: {'label': 'POSITIVE', 'score': 0.9997795224189758}]

上例中,我们输入了一句话给classifier,然后它直接输出了这句话是pos还是nega还有对应的打分,这个打分实际上是最后softmax输出的概率值。我们也可以直接输入一个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)}")

# output: label: POSITIVE, with score: 0.9998
# output: NEGATIVE, with score: 0.5309

可以看到,pipeline 实现的是一个端到端的model,使用非常方便。

其他模型

model hub 上的其他模型

如果想使用其他的模型,可以在 model hub 里面找,比如说我们想用 nlptown/bert-base-multilingual-uncased-sentiment” 这个模型, 那就直接把名字输入进pipeline

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

先下载后从文件中加载

pipeline也支持我们自己的模型和自定义的tokenizer,但是我们额外需要两个类 AutoTokenizer 和 AutoModelForSequenceClassification (若使用tensorflow则是TFAutoModelForSequenceClassification)

from transformers import AutoTokenizer, AutoModelForSequenceClassification

AutoTokenizer 的作用是 下载我们选取model对应的 tokenizer 并且实例化这个 tokenizer

AutoModelForSequenceClassification 作用是下载modek身,注意不同任务我们需要使用不同的class,具体对应关系在 task summary tutorial中

model_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)

如果model hub上没找到匹配你的数据的模型,你将需要 fine-tune a pretrained model on your data. 官网上有例子该怎么做,训练完之后别忘了share with other people

pipeline中的模块

紧接着我们挨个分析分析pipeline里面的各个模块

Tokenizer: 它的作用是预处理text,主要分为以下几步

  1. 划分一段text in words (或一部分words或标点符号等),每一个划分后的部分就叫做 tokens.
    具体的划分方式见 tokenizer summary, 每一个模型都对应一个tokenizer是因为我们要确保你在使用时tokenize的方式和训练时一致
  2. 第二步是把 tokens 转换为 numbers 从而变为Tensor扔进model中,因此,tokenizer 都还有一个 vocab
inputs = tokenizer("We are very happy to show you the 珞 Transformers library.")

输入11个words,一个标点,开始和结束,总共输出14个数字

>>> 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]}

输入一个batch,tokenizer会自动truncate,pad,调整 attention mask

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") # pt = pytorch

输出

for key, value in pt_batch.items():
    print(f"{key}: {value.numpy().tolist()}")

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]]

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]]

tokenize 之后的batch可以直接放进model中,注意pytorch需要加上一个额外的符号 ** before pt_batch

pt_outputs = pt_model(**pt_batch)

注意,所有的输出都是 tuples

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

这里我们只要求输出model的final activations,因此得到的是 a tuple with one element. in general,model反馈的可能不止 final activations

注意,输出的final activations是在final activation function (like SoftMax) 之前的!因为通常final activation function 会和 loss function 定义在一起.

因此,我们在用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>)

可以看到,此时我们得到的 pt_predictions 和之前直接用pipeline得到的一致

如果我们有label,我们可以直接提供给model,它将直接输出loss和final activations

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

注意这些模型就是标准的 torch.nn.Module 或 tf.keras.Model,你们可以直接用常规方式训练

我们有提供一个额外的 Trainer 类 (如果用TF则是 TFTrainer) 帮助你训练 (处理 distributed training, mixed precision 等问题)。详见 the training tutorial

在你的模型 fine-tuned 之后,你可以保存它和它的tokenizer

tokenizer.save_pretrained(save_directory)
model.save_pretrained(save_directory)

如果想load back,那就用 from_pretrained() 方法输入 directory name (instead of the model name).

珞 Transformers 一个 cool 的特点是你可以随意在 PyTorch 和 TensorFlow 之间切换

任何一个存储的模型可以 loaded back either in PyTorch or TensorFlow.

比如说,我们要 如果要把一个存储的pytorch model load 称为 TF, then:

tokenizer = AutoTokenizer.from_pretrained(save_directory)
model = TFAutoModel.from_pretrained(save_directory, from_pt=True)

反过来,如果要把一个存储的TF model load 成pytorch,则

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

你也可以让 model返回所有的 hidden states 和 attention weights

pt_outputs = pt_model(**pt_batch, output_hidden_states=True, output_attentions=True)
all_hidden_states, all_attentions = pt_outputs[-2:]

在我们之前的例子中,model的名字是 distilbert-base-uncased-finetuned-sst-2-english, 这意味着说它使用the DistilBERT architecture.

因为我们使用了 AutoModelForSequenceClassification 类, 自动创建的model便是 DistilBertForSequenceClassification.

如果我们不想要这种自动创建的行为,可以直接load 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)

自定义模型

如果你想改变model本身的架构,那你可以自定义model (比如any of the hidden dimension, dropout rate, etc.)

实际上,每一个model都有一个自己的 configuration. 比如说DistilBERT这个model,它的configuration就是 DistilBertConfig

但是如果你要做核心的修改,比如改变hidden size,那你只能从头开始训练了。此时你可以从 configuration 中直接实例化这个model开始训练

下面我们给一个从头开始实例化的例子,即我们将从configuration中 (而非from_pretrained()中) 实例化model

多import一个DistilBertConfig class

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)

对于一些小修改,比如只是改变model head (比如label个数),你仍然可以使用pretrained model

一种方式是改configuration中number of labels

另一种更简单的方式是在from_pretrained()方法中直接输入修改(只要是configuration能改的都可以),他将自动修改config中的配置

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)
 类似资料: