问答
问答任务根据给定的问题返回答案。如果您曾经问过 Alexa、Siri 或 Google 这样的虚拟助手天气如何,那么您之前就已经使用过问答模型。问答任务有两种常见类型:
- 提取式:从给定的上下文中提取答案。
- 摘要式:从上下文中生成一个正确回答问题的答案。
本指南将向您展示如何:
- 在 DistilBERT 上微调 SQuAD 数据集以进行提取式问答。
- 将您微调的模型用于推理。
要查看与该任务兼容的所有架构和检查点,我们建议查看 任务页面
在开始之前,请确保已安装所有必要的库
pip install transformers datasets evaluate
我们鼓励您登录您的 Hugging Face 帐户,以便您可以上传模型并与社区分享。在提示时,输入您的令牌登录
>>> from huggingface_hub import notebook_login
>>> notebook_login()
加载 SQuAD 数据集
首先从 🤗 Datasets 库加载 SQuAD 数据集的一个较小子集。这将让您有机会进行实验,并确保一切正常,然后再花更多时间在完整数据集上进行训练。
>>> from datasets import load_dataset
>>> squad = load_dataset("squad", split="train[:5000]")
使用 train_test_split 方法将数据集的 train
拆分拆分为训练集和测试集
>>> squad = squad.train_test_split(test_size=0.2)
然后查看一个示例
>>> squad["train"][0]
{'answers': {'answer_start': [515], 'text': ['Saint Bernadette Soubirous']},
'context': 'Architecturally, the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend "Venite Ad Me Omnes". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette Soubirous in 1858. At the end of the main drive (and in a direct line that connects through 3 statues and the Gold Dome), is a simple, modern stone statue of Mary.',
'id': '5733be284776f41900661182',
'question': 'To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France?',
'title': 'University_of_Notre_Dame'
}
这里有几个重要的字段:
answers
:答案标记的起始位置和答案文本。context
:模型需要从中提取答案的背景信息。question
:模型应该回答的问题。
预处理
下一步是加载 DistilBERT 标记器来处理 question
和 context
字段
>>> from transformers import AutoTokenizer
>>> tokenizer = AutoTokenizer.from_pretrained("distilbert/distilbert-base-uncased")
您应该了解一些特定于问答任务的预处理步骤
- 数据集中的某些示例可能具有非常长的
context
,超过了模型的最大输入长度。为了处理更长的序列,仅通过设置truncation="only_second"
来截断context
。 - 接下来,通过设置
return_offset_mapping=True
将答案的起始和结束位置映射到原始context
。 - 有了映射,现在您可以找到答案的起始和结束标记。使用
sequence_ids
方法找出哪个偏移部分对应于question
,哪个部分对应于context
。
以下是如何创建用于截断并将 answer
的起始和结束标记映射到 context
的函数
>>> def preprocess_function(examples):
... questions = [q.strip() for q in examples["question"]]
... inputs = tokenizer(
... questions,
... examples["context"],
... max_length=384,
... truncation="only_second",
... return_offsets_mapping=True,
... padding="max_length",
... )
... offset_mapping = inputs.pop("offset_mapping")
... answers = examples["answers"]
... start_positions = []
... end_positions = []
... for i, offset in enumerate(offset_mapping):
... answer = answers[i]
... start_char = answer["answer_start"][0]
... end_char = answer["answer_start"][0] + len(answer["text"][0])
... sequence_ids = inputs.sequence_ids(i)
... # Find the start and end of the context
... idx = 0
... while sequence_ids[idx] != 1:
... idx += 1
... context_start = idx
... while sequence_ids[idx] == 1:
... idx += 1
... context_end = idx - 1
... # If the answer is not fully inside the context, label it (0, 0)
... if offset[context_start][0] > end_char or offset[context_end][1] < start_char:
... start_positions.append(0)
... end_positions.append(0)
... else:
... # Otherwise it's the start and end token positions
... idx = context_start
... while idx <= context_end and offset[idx][0] <= start_char:
... idx += 1
... start_positions.append(idx - 1)
... idx = context_end
... while idx >= context_start and offset[idx][1] >= end_char:
... idx -= 1
... end_positions.append(idx + 1)
... inputs["start_positions"] = start_positions
... inputs["end_positions"] = end_positions
... return inputs
要将预处理函数应用于整个数据集,请使用 🤗 Datasets map 函数。您可以通过设置 batched=True
来加速 map
函数,以便一次处理数据集的多个元素。删除任何不需要的列
>>> tokenized_squad = squad.map(preprocess_function, batched=True, remove_columns=squad["train"].column_names)
现在使用 DefaultDataCollator 创建一批示例。与 🤗 Transformers 中的其他数据整理器不同,DefaultDataCollator 不执行任何额外的预处理,例如填充。
>>> from transformers import DefaultDataCollator
>>> data_collator = DefaultDataCollator()
>>> from transformers import DefaultDataCollator
>>> data_collator = DefaultDataCollator(return_tensors="tf")
训练
您现在准备开始训练您的模型!使用 AutoModelForQuestionAnswering 加载 DistilBERT
>>> from transformers import AutoModelForQuestionAnswering, TrainingArguments, Trainer
>>> model = AutoModelForQuestionAnswering.from_pretrained("distilbert/distilbert-base-uncased")
此时,只剩下三个步骤
- 在 TrainingArguments 中定义您的训练超参数。唯一需要的参数是
output_dir
,它指定保存模型的位置。您将通过设置push_to_hub=True
将此模型推送到 Hub(您需要登录 Hugging Face 才能上传您的模型)。 - 将训练参数传递给 Trainer 以及模型、数据集、标记器和数据整理器。
- 调用 train() 来微调您的模型。
>>> training_args = TrainingArguments(
... output_dir="my_awesome_qa_model",
... eval_strategy="epoch",
... learning_rate=2e-5,
... per_device_train_batch_size=16,
... per_device_eval_batch_size=16,
... num_train_epochs=3,
... weight_decay=0.01,
... push_to_hub=True,
... )
>>> trainer = Trainer(
... model=model,
... args=training_args,
... train_dataset=tokenized_squad["train"],
... eval_dataset=tokenized_squad["test"],
... tokenizer=tokenizer,
... data_collator=data_collator,
... )
>>> trainer.train()
训练完成后,使用 push_to_hub() 方法将您的模型分享到 Hub,以便每个人都可以使用您的模型
>>> trainer.push_to_hub()
如果您不熟悉使用 Keras 微调模型,请查看 这里 的基本教程!
>>> from transformers import create_optimizer
>>> batch_size = 16
>>> num_epochs = 2
>>> total_train_steps = (len(tokenized_squad["train"]) // batch_size) * num_epochs
>>> optimizer, schedule = create_optimizer(
... init_lr=2e-5,
... num_warmup_steps=0,
... num_train_steps=total_train_steps,
... )
然后,您可以使用 TFAutoModelForQuestionAnswering 加载 DistilBERT
>>> from transformers import TFAutoModelForQuestionAnswering
>>> model = TFAutoModelForQuestionAnswering.from_pretrained("distilbert/distilbert-base-uncased")
使用 prepare_tf_dataset() 将您的数据集转换为 tf.data.Dataset
格式
>>> tf_train_set = model.prepare_tf_dataset(
... tokenized_squad["train"],
... shuffle=True,
... batch_size=16,
... collate_fn=data_collator,
... )
>>> tf_validation_set = model.prepare_tf_dataset(
... tokenized_squad["test"],
... shuffle=False,
... batch_size=16,
... collate_fn=data_collator,
... )
使用 compile
为训练配置模型
>>> import tensorflow as tf
>>> model.compile(optimizer=optimizer)
在开始训练之前要设置的最后一件事是提供一种将模型推送到 Hub 的方法。这可以通过在 PushToHubCallback 中指定推送到 Hub 的模型和标记器的位置来完成
>>> from transformers.keras_callbacks import PushToHubCallback
>>> callback = PushToHubCallback(
... output_dir="my_awesome_qa_model",
... tokenizer=tokenizer,
... )
最后,您准备开始训练您的模型!使用您的训练集和验证集、时期数和回调调用 fit
来微调模型
>>> model.fit(x=tf_train_set, validation_data=tf_validation_set, epochs=3, callbacks=[callback])
训练完成后,您的模型会自动上传到 Hub,以便每个人都可以使用它!
有关如何为问答微调模型的更深入示例,请查看相应的 PyTorch 笔记本 或 TensorFlow 笔记本。
评估
问答的评估需要大量的后处理。为了避免占用您过多的时间,本指南跳过了评估步骤。在训练期间,Trainer 仍然会计算评估损失,因此您不会完全不知道模型的性能。
如果您有更多时间,并且您对如何评估问答模型感兴趣,请查看 🤗 Hugging Face 课程的 问答 章节!
推理
太好了,既然您已经微调了模型,就可以将其用于推理!
提出一个您希望模型预测的问题和一些上下文
>>> question = "How many programming languages does BLOOM support?"
>>> context = "BLOOM has 176 billion parameters and can generate text in 46 languages natural languages and 13 programming languages."
尝试使用微调模型进行推理的最简单方法是在 pipeline() 中使用它。使用您的模型实例化一个用于问答的 pipeline
,并将您的文本传递给它
>>> from transformers import pipeline
>>> question_answerer = pipeline("question-answering", model="my_awesome_qa_model")
>>> question_answerer(question=question, context=context)
{'score': 0.2058267742395401,
'start': 10,
'end': 95,
'answer': '176 billion parameters and can generate text in 46 languages natural languages and 13'}
如果您愿意,您还可以手动复制 pipeline
的结果
标记文本并返回 PyTorch 张量
>>> from transformers import AutoTokenizer
>>> tokenizer = AutoTokenizer.from_pretrained("my_awesome_qa_model")
>>> inputs = tokenizer(question, context, return_tensors="pt")
将您的输入传递给模型并返回 logits
>>> import torch
>>> from transformers import AutoModelForQuestionAnswering
>>> model = AutoModelForQuestionAnswering.from_pretrained("my_awesome_qa_model")
>>> with torch.no_grad():
... outputs = model(**inputs)
从模型输出中获取开始和结束位置的最高概率
>>> answer_start_index = outputs.start_logits.argmax()
>>> answer_end_index = outputs.end_logits.argmax()
解码预测的标记以获得答案
>>> predict_answer_tokens = inputs.input_ids[0, answer_start_index : answer_end_index + 1]
>>> tokenizer.decode(predict_answer_tokens)
'176 billion parameters and can generate text in 46 languages natural languages and 13'
对文本进行分词并返回 TensorFlow 张量
>>> from transformers import AutoTokenizer
>>> tokenizer = AutoTokenizer.from_pretrained("my_awesome_qa_model")
>>> inputs = tokenizer(question, text, return_tensors="tf")
将您的输入传递给模型并返回 logits
>>> from transformers import TFAutoModelForQuestionAnswering
>>> model = TFAutoModelForQuestionAnswering.from_pretrained("my_awesome_qa_model")
>>> outputs = model(**inputs)
从模型输出中获取开始和结束位置的最高概率
>>> answer_start_index = int(tf.math.argmax(outputs.start_logits, axis=-1)[0])
>>> answer_end_index = int(tf.math.argmax(outputs.end_logits, axis=-1)[0])
解码预测的标记以获得答案
>>> predict_answer_tokens = inputs.input_ids[0, answer_start_index : answer_end_index + 1]
>>> tokenizer.decode(predict_answer_tokens)
'176 billion parameters and can generate text in 46 languages natural languages and 13'