因果语言模型¶
因果语言模型是语言模型的一种类型,它用于预测序列中下一个标记,并且模型只能关注左侧的标记。这意味着模型无法看到未来的标记。GPT-2 就是一个因果语言模型的例子。
本指南将向您展示如何:
- 在 ELI5 数据集的 r/askscience 子集上微调 DistilGPT2。
- 使用您微调的模型进行推理。
在开始之前,请确保您已安装所有必要的库:
pip install transformers datasets evaluate
我们建议您登录到您的 Hugging Face 账户,这样您就可以上传并与社区分享您的模型。当提示时,输入您的令牌以登录:
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 字段。语言建模任务酷的地方在于您不需要标签(也称为无监督任务),因为下一个词就是标签。
预处理¶
下一步是加载 DistilGPT2 分词器来处理 text 子字段:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("distilbert/distilgpt2")
您会注意到上面的示例中,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定义的较短块,block_size应该既小于最大输入长度,又短到足以适合您的 GPU RAM。
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()
}
result["labels"] = result["input_ids"].copy()
return result
在整个数据集上应用 group_texts 函数:
lm_dataset = tokenized_eli5.map(group_texts, batched=True, num_proc=4)
现在使用 DataCollatorForLanguageModeling 创建一个示例批次。在整理过程中,将句子动态填充到批次中最长的长度比将整个数据集填充到最大长度更有效。
Pytorch
使用序列结束标记作为填充标记,并将 mlm=False。这将使用输入作为向右移动一个元素作为标签:
from transformers import DataCollatorForLanguageModeling
tokenizer.pad_token = tokenizer.eos_token
data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)
TensorFlow
使用序列结束标记作为填充标记,并将 mlm=False。这将使用输入作为向右移动一个元素作为标签:
from transformers import DataCollatorForLanguageModeling
data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False, return_tensors="tf")
from transformers import AutoModelForCausalLM, TrainingArguments, Trainer
model = AutoModelForCausalLM.from_pretrained("distilbert/distilgpt2")
在这一点上,只剩下三个步骤:
- 在 TrainingArguments 中定义您的训练超参数。唯一需要的参数是
output_dir,它指定保存模型的位置。您可以通过设置push_to_hub=True将模型推送到 Hub(您需要登录 Hugging Face 才能上传模型)。 - 将训练参数传递给 Trainer,以及模型、数据集和数据整理器。
- 调用 train() 来微调您的模型。
training_args = TrainingArguments(
output_dir="my_awesome_eli5_clm-model",
eval_strategy="epoch",
learning_rate=2e-5,
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)
然后您可以使用 TFAutoModelForCausalLM 加载 DistilGPT2:
from transformers import TFAutoModelForCausalLM
model = TFAutoModelForCausalLM.from_pretrained("distilbert/distilgpt2")
使用 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 配置模型进行训练。请注意,Transformers 模型都有一个默认的与任务相关的损失函数,因此除非您想要指定一个,否则不需要指定损失函数:
import tensorflow as tf
model.compile(optimizer=optimizer) # 无需指定损失函数
您可以通过在 PushToHubCallback 中指定模型和分词器的推送位置来实现:
from transformers.keras_callbacks import PushToHubCallback
callback = PushToHubCallback(
output_dir="my_awesome_eli5_clm-model",
tokenizer=tokenizer,
)
最后,您准备好开始训练您的模型了!使用您的训练和验证数据集、纪元数和回调来微调模型:
model.fit(x=tf_train_set, validation_data=tf_test_set, epochs=3, callbacks=[callback])
训练完成后,您的模型将自动上传到 Hub,以便每个人都可以使用它!
有关如何微调模型进行因果语言建模的更深入示例,请查看相应的 PyTorch 笔记本 或 TensorFlow 笔记本。
推理¶
太好了,现在您已经微调了一个模型,您可以使用它进行推理!
想出一个您想从中生成文本的提示:
prompt = "Somatic hypermutation allows the immune system to"
尝试使用您微调的模型进行推理的最简单方法是将其用于 pipeline()。使用您的模型实例化一个用于文本生成的 pipeline,并将您的文本传递给它:
from transformers import pipeline
generator = pipeline("text-generation", model="username/my_awesome_eli5_clm-model")
generator(prompt)
Pytorch
对文本进行分词并将 input_ids 作为 PyTorch 张量返回:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("username/my_awesome_eli5_clm-model")
inputs = tokenizer(prompt, return_tensors="pt").input_ids
使用 generate() 方法生成文本。有关控制生成的不同文本生成策略和参数的更多详细信息,请查看 文本生成策略 页面。
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained("username/my_awesome_eli5_clm-model")
outputs = model.generate(inputs, max_new_tokens=100, do_sample=True, top_k=50, top_p=0.95)
将生成的标记 ID 解码回文本:
tokenizer.batch_decode(outputs, skip_special_tokens=True)
TensorFlow
对文本进行分词并将 input_ids 作为 TensorFlow 张量返回:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("username/my_awesome_eli5_clm-model")
inputs = tokenizer(prompt, return_tensors="tf").input_ids
使用 ~transformers.generation_tf_utils.TFGenerationMixin.generate 方法创建摘要。有关控制生成的不同文本生成策略和参数的更多详细信息,请查看 文本生成策略 页面。
from transformers import TFAutoModelForCausalLM
model = TFAutoModelForCausalLM.from_pretrained("username/my_awesome_eli5_clm-model")
outputs = model.generate(input_ids=inputs, max_new_tokens=100, do_sample=True, top_k=50, top_p=0.95)
将生成的标记 ID 解码回文本:
tokenizer.batch_decode(outputs, skip_special_tokens=True)