Huggingface Transformers 教程

本教程基于 Huggingface 的 Transformers (4.10.0),其中大部分来自于官方文档 ## 1.pipeline

首先是最简单的使用方法 pipeline 其可以直接利用制定的任务

pipeline 中指定了以下几种任务:情绪分析,文本生成,命名实体识别,回答问题,Mask预测,总结文本,翻译,文本特征向量提取

其使用也很简单

1
2
3
4
from transformers import pipeline
classifier = pipeline('sentiment-analysis')
classifier('We are very happy')
# {'label': 'POSITIVE', 'score': 0.9998835325241089}

在这里由于模型没有指定使用什么预训练模型,因此默认调用 “ distilbert-base-uncased-finetuned-sst-2-english” ,可以注意到这是一个基于 DistilBERT 的架构,并在 sst-2 上针对情感预测进行了微调,当然你也可以指定想要使用的模型,可以在 model hub 中寻找你想要使用的模型。这里使用一下中文模型 “uer/roberta-base-finetuned-chinanews-chinese”,需要注意的是,这个模型是这是文本分类的任务,但也可以使用 'sentiment-analysis'pipeline

1
2
3
4
from transformers import pipeline
clasifier = pipeline("sentiment-analysis", model="uer/roberta-base-finetuned-chinanews-chinese")
print(clasifier("中国国足今天将和越南比赛"))
# {'label': 'sports', 'score': 0.999755322933197}

除了网上的模型,我们还可以调用本地的模型,这里我们需要两个类 AutoTokenizer,AutoModelForMaskedLM,这里笔者由于使用了自己训练的本地模型,因此使用的是 BertTokenizerAutoModelForMaskedLM

1
2
3
4
5
6
from transformers import BertTokenizer, AutoModelForMaskedLM, pipeline
model = AutoModelForMaskedLM.from_pretrained("Huggingface/output/checkpoint-884000/")
tokenizer = BertTokenizer(vocab_file="/Huggingface/vocab.txt")
unmask = pipeline("fill-mask", model=model, tokenizer=tokenizer)
print(unmask("实验室规[MASK]管理制度"))
# {'sequence': '实 验 室 规 范 管 理 制 度', 'score': 0.46887779235839844, 'token': 6457, 'token_str': '范'}, {'sequence': '实 验 室 规 章 管 理 制 度', 'score': 0.39199674129486084, 'token': 5512, 'token_str': '章'}

至于其它的 pipeline 的使用可以参考官方文档

2.tokenizer

tokenizertransformers 的核心组件之一,负责将句子数字化和分词

对于使用 model hub 中的预训练模型来说,其必须要使用对应的 tokenizer ,可以通过 AutoTokenizer.from_pretrained() 获得

这里我们下载一下中文 BERT-BASE 的 tokenizer,一般会得到四个文件

1
2
3
from transformers import AutoTokenizer, AutoModelForMaskedLM, pipeline
tokenizer = AutoTokenizer.from_pretrained("./save")
tokenizer.save_pretrained("./save")
image-20211116144002979

其中 special_token_map.json指定了特殊token的形式,如 [CLS],[UNK],[MASK]

tokener.json和vocab.txt,则分别是词对应的数字和的词表,其中json文件还多了一部分信息用于描述 tokenzier 中的特殊token

tokenizer_config.json 则同样描述了特殊token和下载的tokenizer信息

在得到 tokenizer 后就可以对文本进行预处理,这里以中文为例

1
2
3
4
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("hfl/chinese-bert-wwm-ext")
print(tokenizer("实验室规章管理制度"))
# {'input_ids': [101, 2141, 7741, 2147, 6226, 4995, 5052, 4415, 1169, 2428, 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]}

经过 tokenizer 编码后会输出三项,input_ids 是句子向量化以后的数字,token_type_ids 是描述句子上下关系的,如果两个句子有上下文关联则会使用0和1加以区分,attention_mask 则是描述句子的 padding 结构以指示哪些部分是 padding

1
2
3
4
5
6
7
from transformers import AutoTokenizer, AutoModelForMaskedLM, pipeline
tokenizer = AutoTokenizer.from_pretrained("hfl/chinese-bert-wwm-ext")
sentence_1 = ["我喜欢吃香菜", "去实验室吗"]
sentence_2 = ["我也喜欢吃", "去"]
print(tokenizer(sentence_1, sentence_2, padding=True, truncation=True))
# {'input_ids': [[101, 2769, 1599, 3614, 1391, 7676, 5831, 102, 2769, 738, 1599, 3614, 1391, 102], [101, 1343, 2141, 7741, 2147, 1408, 102, 1343, 102, 0, 0, 0, 0, 0]],
#'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 0, 0, 0, 0, 0]]}

当然既然可以使用 tokenizer 对句子进行编码处理,那也可以使用 tokenizer 对已经编码的句子进行还原

1
2
3
encoded_inputs = tokenizer(sentence_1, sentence_2, padding=True, truncation=True)
print(tokenizer.decode(encoded_inputs["input_ids"][1]))
# [CLS] 去 实 验 室 吗 [SEP] 去 [SEP] [PAD] [PAD] [PAD] [PAD] [PAD]

当然我们也可以自己训练一个自己的 tokenizer,这里就要用到 Tokenizers 库了,这里以训练 BPE 编码的tokenizer为例

Tokenizers 中提供了四种算法,包括 BPE,Unigram,WordLevel,WordPiece 这里仅以 BPE 为例

首先需要准备好训练的语料库,每个句子一行

image-20211117141652919

随后指定训练方法,预分词方法,预计词表大小,预先定义的特殊 token,之后就可以开始训练模型并存储下来

1
2
3
4
5
tokenizer = Tokenizer(BPE())
tokenizer.pre_tokenizer = Whitespace()
trainer = BpeTrainer(special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"], vocab_size=9000)
tokenizer.train(files=["News2016/valid.txt"], trainer=trainer)
tokenizer.save('tokenizer.json')

最后就可以得到 tokenizer 文件

加载的时候需要注意的是,在 tokenizers 中的 tokenizerTransformers 中的 tokenizer 不一样

在tokenizers中加载只需要之前保存的 tokenizer.json 但在 Transformers 中需要使用 PreTrainedTokenizerFast 来加载,且两者在后续的使用中虽然功能都一样但无法混用,即由 Tokenizer 初始化的 tokenizer 无法应用到 Transformers

1
2
3
4
# tokenizers
tokenizer = Tokenizer.from_file("Huggingface/tokenizer.json")
# Transformers
tokenizer = PreTrainedTokenizerFast(tokenizer_file="Huggingface/tokenizer.json")

3.Models

Transformers 支持众多预训练模型,这里主要可以分为:

  1. 解码器或自回归模型(GPT)
  2. 编码器或自编码模型(BERT)
  3. 序列到序列模型(T5)
  4. 多模态模型
  5. 基于检索的模型

具体支持的模型可以在官方文档下查看

具体使用的时候可以如同第一节所述直接加载训练好的模型,也可以自己重新训练一个模型,这里主要讲述如何训练一个模型

首先需要准备的是语料库,和 tokenizer,注意这里的 tokenizer 不可以是之前第二节中说的从 tokenizers 中初始化的模型,必须从 Transformers 中初始化

接下来就是对所选模型的 config 进行改写,这里以 BERT 为例,训练一个掩码模型MLM

1
2
3
4
5
from transformers import BertModel, BertConfig
# 对config进行自定义修改,这里仅修改了词表大小
configuration = BertConfig(vocab_size=9468)
# 初始化BERT模型
model = BertForMaskedLM(configuration)

在准备训练数据集部分还需要用到另一个 huggingface 的库 datasets 同样在 datasets hub 上有许多开源的库,可以直接调用

1
2
from datasets import load_dataset
dataset = load_dataset('glue', 'mrpc', split='train')

但其中的纯中文语料库并不多,可能还是需要读者自行准备相应的训练语料库

1
2
3
from datasets import load_dataset, Dataset
dataset = load_dataset('text', data_files={'train': 'News2016/train.txt',
'validation': 'News2016/valid.txt'})

接下来需要对dataset使用 tokenizer 进行处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def tokenize_function(examples):
# 去除空行
examples["text"] = [line for line in examples["text"] if len(line) > 0 and not line.isspace()]
return tokenizer(
# 上面初始化的时候将数据集中起名为 text
examples["text"],
padding="max_length", # 进行填充
truncation=True, # 进行截断
max_length=100, # 设置句子的长度
return_special_tokens_mask=True,
)

tokenized_datasets = dataset.map(
tokenize_function,
batched=True,
num_proc=24,
remove_columns=['text'],
)
train_dataset = tokenized_datasets["train"]
eval_dataset = tokenized_datasets["validation"]

之后就是设置 trainer 的相关参数

1
2
3
4
5
6
7
datacollator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=True, mlm_probability=0.15)
train_args = TrainingArguments(output_dir='Huggingface/output/', overwrite_output_dir=True,
num_train_epochs=10, save_steps=2000,
learning_rate=5e-5, per_device_train_batch_size=300, save_total_limit=3)
trainer = Trainer(model=model, args=train_args, data_collator=datacollator, train_dataset=train_dataset)
trainer.train(resume_from_checkpoint=None)
trainer.save_model('Huggingface/output/')

最后给出训练的完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
tokenizer = BertTokenizer(vocab_file='vocab.txt')
dataset = load_dataset('text', data_files={'train': 'News2016/train.txt',
'validation': 'News2016/valid.txt'})


def tokenize_function(examples):
examples["text"] = [line for line in examples["text"] if len(line) > 0 and not line.isspace()]
return tokenizer(
examples["text"],
padding="max_length", # 进行填充
truncation=True, # 进行截断
max_length=100, # 设置句子的长度
return_special_tokens_mask=True,
)


tokenized_datasets = dataset.map(
tokenize_function,
batched=True,
num_proc=24,
remove_columns=['text'],
)
# 得到训练集和验证集
train_dataset = tokenized_datasets["train"]
eval_dataset = tokenized_datasets["validation"]

config = BertConfig(vocab_size=9468)
model = BertForMaskedLM(config)

datacollator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=True, mlm_probability=0.15)
train_args = TrainingArguments(output_dir='Huggingface/output/', overwrite_output_dir=True,
num_train_epochs=10, save_steps=2000,
learning_rate=5e-5, per_device_train_batch_size=300, save_total_limit=3)
trainer = Trainer(model=model, args=train_args, data_collator=datacollator, train_dataset=train_dataset)
trainer.train(resume_from_checkpoint=None)
trainer.save_model('Huggingface/output/')

4.Fine-tuning

有了预训练模型后肯定还需要在具体任务上进行微调,这里以在IMDB上评价为正面还是负面为例(其实就是官方文档的例子)

这里主要介绍如何在pytorch上进行微调,其核心还是利用 Transformers 中已经构建好的微调任务模型或者自己重写如何利用预训练模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
from torch.utils.data import DataLoader
from transformers import AdamW
from transformers import AutoModelForSequenceClassification
from transformers import get_scheduler
from tqdm.auto import tqdm
from transformers import AutoTokenizer
from datasets import load_dataset

# 获取数据集
raw_datasets = load_dataset("imdb")
# 获取tokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")

# 将tokenizer自动应用到数据集上
def tokenize_function(examples):
return tokenizer(examples["text"], padding="max_length", truncation=True)
tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)

# 去除数据集中没用的标签,并将输出设为torch的tensor格式
tokenized_datasets = tokenized_datasets.remove_columns(["text"])
tokenized_datasets = tokenized_datasets.rename_column("label", "labels")
tokenized_datasets.set_format("torch")

# 取一部分数据集做演示
small_train_dataset = tokenized_datasets["train"].shuffle(seed=42).select(range(1000))
small_eval_dataset = tokenized_datasets["test"].shuffle(seed=42).select(range(1000))

# 构建dataloader
train_dataloader = DataLoader(small_train_dataset, shuffle=True, batch_size=8)
eval_dataloader = DataLoader(small_eval_dataset, batch_size=8)

# 构建优化器
optimizer = AdamW(model.parameters(), lr=5e-5)
# 直接使用Transformer中有的模型
model = AutoModelForSequenceClassification.from_pretrained("bert-base-cased", num_labels=2)

# 设置变量和定义学习率变化方法
num_epochs = 3
num_training_steps = num_epochs * len(train_dataloader)
lr_scheduler = get_scheduler(
"linear",
optimizer=optimizer,
num_warmup_steps=0,
num_training_steps=num_training_steps
)

device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
model.to(device)

progress_bar = tqdm(range(num_training_steps))

# 开始训练
model.train()
for epoch in range(num_epochs):
for batch in train_dataloader:
batch = {k: v.to(device) for k, v in batch.items()}
outputs = model(**batch)
loss = outputs.loss
loss.backward()

optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
progress_bar.update(1)