遮蔽语言模型¶
遮蔽语言模型预测序列中的遮蔽标记,并且模型可以双向关注标记。这意味着模型可以完全访问左右两边的标记。遮蔽语言模型非常适合需要对整个序列有良好上下文理解的任务。BERT 就是一个遮蔽语言模型的例子。
本指南将向你展示如何:
- 在 ELI5 数据集的 r/askscience 子集上微调 DistilRoBERTa。
- 使用你的微调模型进行推理。
要查看与此任务兼容的所有架构和检查点,我们推荐查看任务页面。
开始之前,请确保你已安装所有必要的库:
pip install transformers datasets evaluate
我们鼓励你登录你的 Hugging Face 账户,这样你就可以上传并与社区分享你的模型。当提示时,输入你的 token 以登录:
from huggingface_hub import notebook_login
notebook_login()
加载 ELI5 数据集¶
首先使用 🤗 Datasets 库加载 ELI5-Category 数据集的前 5000 个示例。这将给你一个机会进行实验并确保一切工作正常,然后再花更多时间在完整数据集上训练。
from datasets import load_dataset
eli5 = load_dataset("eli5_category", split="train[:5000]")
使用 train_test_split 方法将数据集的 train 分割成训练集和测试集:
eli5 = eli5.train_test_split(test_size=0.2)
然后查看一个示例:
eli5["train"][0]
虽然这看起来很多,但你真正感兴趣的是 text 字段。语言建模任务的酷之处在于你不需要标签(也称为无监督任务),因为下一个词就是标签。
预处理¶
对于遮蔽语言建模,下一步是加载一个 DistilRoBERTa 分词器来处理 text 子字段:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("distilbert/distilroberta-base")
你会从上面的示例中注意到,text 字段实际上嵌套在 answers 里面。这意味着你需要使用 flatten 方法从其嵌套结构中提取 text 子字段:
eli5 = eli5.flatten()
eli5["train"][0]
现在每个子字段都是一个单独的列,如 answers 前缀所示,text 字段现在是一个列表。不要单独对每个句子进行分词,将列表转换为字符串,以便你可以一起对它们进行分词。
这是一个预处理函数,用于将每个示例的字符串列表连接起来并分词结果:
def preprocess_function(examples):
return tokenizer([" ".join(x) for x in examples["answers.text"]])
要在整个数据集上应用这个预处理函数,使用 🤗 Datasets 的 map 方法。你可以通过设置 batched=True 来加速 map 函数,以便一次处理数据集中的多个元素,并使用 num_proc 增加进程数。删除你不需要的任何列:
tokenized_eli5 = eli5.map(
preprocess_function,
batched=True,
num_proc=4,
remove_columns=eli5["train"].column_names,
)
这个数据集包含了标记序列,但其中一些序列比模型的最大输入长度还要长。
现在你可以使用第二个预处理函数来:
- 连接所有的序列
- 将连接的序列分割成由
block_size定义的更短的块,它应该比模型的最大输入长度短,并且足够短以适应你的 GPU 内存。
block_size = 128
def group_texts(examples):
# 连接所有文本。
concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()}
total_length = len(concatenated_examples[list(examples.keys())[0]])
# 我们可以添加填充而不是这种丢弃,如果模型支持的话,你可以根据自己的需要定制这部分。
if total_length >= block_size:
total_length = (total_length // block_size) * block_size
# 按 block_size 分割块。
result = {
k: [t[i : i + block_size] for i in range(0, total_length, block_size)]
for k, t in concatenated_examples.items()
}
return result
lm_dataset = tokenized_eli5.map(group_texts, batched=True, num_proc=4)
现在使用 DataCollatorForLanguageModeling 创建一批示例。在整理过程中动态填充句子到批次中的最长长度,而不是将整个数据集填充到最大长度,这样做更有效。
Pytorch
使用序列结束标记作为填充标记,并指定 mlm_probability 以在每次遍历数据时随机遮蔽标记:
from transformers import DataCollatorForLanguageModeling
tokenizer.pad_token = tokenizer.eos_token
data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15)
TensorFlow
使用序列结束标记作为填充标记,并指定 mlm_probability 以在每次遍历数据时随机遮蔽标记:
from transformers import DataCollatorForLanguageModeling
data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15, return_tensors="tf")
训练¶
Pytorch
如果你不熟悉如何使用 Trainer 微调模型,请查看这里的基本教程!
现在你可以开始训练你的模型了!使用 AutoModelForMaskedLM 加载 DistilRoBERTa:
from transformers import AutoModelForMaskedLM
model = AutoModelForMaskedLM.from_pretrained("distilbert/distilroberta-base")
此时,只剩下三个步骤:
- 在 TrainingArguments 中定义你的训练超参数。唯一必需的参数是
output_dir,它指定了保存模型的位置。你可以通过设置push_to_hub=True(需要登录 Hugging Face 才能上传你的模型)将此模型推送到 Hub。 - 将训练参数传递给 Trainer 以及模型、数据集和数据整理器。
- 调用 train() 微调你的模型。
training_args = TrainingArguments(
output_dir="my_awesome_eli5_mlm_model",
eval_strategy="epoch",
learning_rate=2e-5,
num_train_epochs=3,
weight_decay=0.01,
push_to_hub=True,
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=lm_dataset["train"],
eval_dataset=lm_dataset["test"],
data_collator=data_collator,
tokenizer=tokenizer,
)
trainer.train()
训练完成后,使用 evaluate() 方法评估你的模型并获取它的困惑度:
import math
eval_results = trainer.evaluate()
print(f"Perplexity: {math.exp(eval_results['eval_loss']):.2f}")
然后使用 push_to_hub() 方法将你的模型分享到 Hub,这样每个人都可以使用你的模型:
trainer.push_to_hub()
from transformers import create_optimizer, AdamWeightDecay
optimizer = AdamWeightDecay(learning_rate=2e-5, weight_decay_rate=0.01)
然后你可以使用 TFAutoModelForMaskedLM 加载 DistilRoBERTa:
from transformers import TFAutoModelForMaskedLM
model = TFAutoModelForMaskedLM.from_pretrained("distilbert/distilroberta-base")
使用 prepare_tf_dataset() 将你的数据集转换为 tf.data.Dataset 格式:
tf_train_set = model.prepare_tf_dataset(
lm_dataset["train"],
shuffle=True,
batch_size=16,
collate_fn=data_collator,
)
tf_test_set = model.prepare_tf_dataset(
lm_dataset["test"],
shuffle=False,
batch_size=16,
collate_fn=data_collator,
)
使用 compile 配置模型进行训练。注意,Transformer 模型都有一个默认的任务相关损失函数,所以你不需要指定一个,除非你想:
import tensorflow as tf
model.compile(optimizer=optimizer) # No loss argument!
这可以通过指定在哪里推送你的模型和分词器来完成 PushToHubCallback:
from transformers.keras_callbacks import PushToHubCallback
callback = PushToHubCallback(
output_dir="my_awesome_eli5_mlm_model",
tokenizer=tokenizer,
)
最后,你准备好开始训练你的模型了!用你的训练和验证数据集、训练周期数和你的回调来调用 fit 微调模型:
model.fit(x=tf_train_set, validation_data=tf_test_set, epochs=3, callbacks=[callback])
一旦训练完成,你的模型会自动上传到 Hub,这样每个人都可以使用它!
有关如何微调遮蔽语言模型的更深入示例,请查看相应的 PyTorch 笔记本 或 TensorFlow 笔记本。
推理¶
太好了,现在你已经微调了一个模型,你可以用它进行推理了!
想出一些你想让模型填补空白的文本,并使用特殊的 <mask> 标记来表示空白:
text = "The Milky Way is a <mask> galaxy."
尝试你的微调模型进行推理的最简单方法是使用它在 pipeline() 中。为你的模型实例化一个 pipeline 进行填充掩码,并传递你的文本给它。如果喜欢,你可以使用 top_k 参数来指定返回多少个预测:
from transformers import pipeline
mask_filler = pipeline("fill-mask", "username/my_awesome_eli5_mlm_model")
mask_filler(text, top_k=3)
Pytorch
将文本分词并返回 PyTorch 张量的 input_ids。你还需要指定 <mask> 标记的位置:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("username/my_awesome_eli5_mlm_model")
inputs = tokenizer(text, return_tensors="pt")
mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1]
from transformers import AutoModelForMaskedLM
model = AutoModelForMaskedLM.from_pretrained("username/my_awesome_eli5_mlm_model")
logits = model(**inputs).logits
mask_token_logits = logits[0, mask_token_index, :]
然后返回三个可能性最高的遮蔽标记并打印它们:
top_3_tokens = torch.topk(mask_token_logits, 3, dim=1).indices[0].tolist()
for token in top_3_tokens:
print(text.replace(tokenizer.mask_token, tokenizer.decode([token])))
TensorFlow
将文本分词并返回 TensorFlow 张量的 input_ids。你还需要指定 <mask> 标记的位置:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("username/my_awesome_eli5_mlm_model")
inputs = tokenizer(text, return_tensors="tf")
mask_token_index = tf.where(inputs["input_ids"] == tokenizer.mask_token_id)[0, 1]
from transformers import TFAutoModelForMaskedLM
model = TFAutoModelForMaskedLM.from_pretrained("username/my_awesome_eli5_mlm_model")
logits = model(**inputs).logits
mask_token_logits = logits[0, mask_token_index, :]
然后返回三个可能性最高的遮蔽标记并打印它们:
top_3_tokens = tf.math.top_k(mask_token_logits, 3).indices.numpy()
for token in top_3_tokens:
print(text.replace(tokenizer.mask_token, tokenizer.decode([token])))
这样,你就可以使用微调后的模型来预测遮蔽词,并得到最有可能的三个填充词。这种方法可以应用于任何需要上下文理解的任务,例如文本生成、问答系统等。