# 多项选择

多项选择题任务类似于问答任务，不同之处在于，除了上下文之外，还会提供多个候选答案，模型被训练以选择正确的答案。

本指南将向您展示如何：

1. 在 [SWAG](https://huggingface.co/datasets/allenai/swag) 数据集的 `regular` 配置上微调 [BERT](https://huggingface.co/google-bert/bert-base-uncased)，根据多个选项和部分上下文选择最佳答案。
2. 使用微调后的模型进行推理。

在开始之前，请确保已安装所有必要的库：

```bash
pip install transformers datasets evaluate
```

我们建议您登录 Hugging Face 账户，以便上传模型并与社区共享。当系统提示时，请输入您的令牌以完成登录：

```py
>>> from huggingface_hub import notebook_login

>>> notebook_login()
```

## 加载 SWAG 数据集

首先，从 🤗 Datasets 库中加载 SWAG 数据集的 `regular` 配置：

```py
>>> from datasets import load_dataset

>>> swag = load_dataset("allenai/swag", "regular")
```

然后来看一个示例：

```py
>>> swag["train"][0]
{'ending0': 'passes by walking down the street playing their instruments.',
 'ending1': 'has heard approaching them.',
 'ending2': "arrives and they're outside dancing and asleep.",
 'ending3': 'turns the lead singer watches the performance.',
 'fold-ind': '3416',
 'gold-source': 'gold',
 'label': 0,
 'sent1': 'Members of the procession walk down the street holding small horn brass instruments.',
 'sent2': 'A drum line',
 'startphrase': 'Members of the procession walk down the street holding small horn brass instruments. A drum line',
 'video-id': 'anetv_jkn6uvmqwh4'}
```

虽然这里看起来有很多字段，但实际上非常简单明了：

- `sent1` 和 `sent2`：这些字段展示了句子的开头部分，将两者组合起来即可得到 `startphrase` 字段。
- `ending`：提示句子可能的结尾方式，但其中只有一种是正确的。
- `label`：标识正确的句子结尾。

## 预处理

下一步是加载一个 BERT 分词器，以处理句子开头和四种可能的结尾：

```py
>>> from transformers import AutoTokenizer

>>> tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-uncased")
```

您想要创建的预处理函数需要：

1. 创建 `sent1` 字段的四个副本，并将每个副本与 `sent2` 组合，以重现句子的开头。
2. 将 `sent2` 与四种可能的句子结尾分别组合。
3. 将这两个列表展平以便进行分词，之后再将其恢复原状，使每个示例都拥有对应的 `input_ids`、`attention_mask` 和 `labels` 字段。

```py
>>> ending_names = ["ending0", "ending1", "ending2", "ending3"]

>>> def preprocess_function(examples):
...     first_sentences = [[context] * 4 for context in examples["sent1"]]
...     question_headers = examples["sent2"]
...     second_sentences = [
...         [f"{header} {examples[end][i]}" for end in ending_names] for i, header in enumerate(question_headers)
...     ]

...     first_sentences = sum(first_sentences, [])
...     second_sentences = sum(second_sentences, [])

...     tokenized_examples = tokenizer(first_sentences, second_sentences, truncation=True)
...     return {k: [v[i : i + 4] for i in range(0, len(v), 4)] for k, v in tokenized_examples.items()}
```

要对整个数据集应用预处理函数，请使用 🤗 Datasets 的 [map](https://huggingface.co/docs/datasets/main/en/package_reference/main_classes#datasets.Dataset.map) 方法。你可以通过设置 `batched=True` 来一次处理数据集中的多个元素，从而加速 `map` 函数：

```py
>>> tokenized_swag = swag.map(preprocess_function, batched=True)
```

要创建一个示例批次，在数据整理（collation）阶段将句子*动态填充*到批次内的最长长度，比将整个数据集填充到最大长度更高效。[DataCollatorForMultipleChoice](/docs/transformers/main/zh/main_classes/data_collator#transformers.DataCollatorForMultipleChoice) 会展平所有模型输入，应用填充，然后对结果进行反展平。

```py
>>> from transformers import DataCollatorForMultipleChoice
>>> collator = DataCollatorForMultipleChoice(tokenizer=tokenizer)
```

## 评估

在训练过程中包含一个指标通常有助于评估模型的性能。你可以使用 🤗 [Evaluate](https://huggingface.co/docs/evaluate/index) 库快速加载一个评估方法。对于此任务，请加载 [accuracy](https://huggingface.co/spaces/evaluate-metric/accuracy) 指标（请参阅 🤗 Evaluate 的 [快速入门](https://huggingface.co/docs/evaluate/a_quick_tour) 以了解如何加载和计算指标）：

```py
>>> import evaluate

>>> accuracy = evaluate.load("accuracy")
```

然后创建一个函数，将你的预测值和标签传递给 [compute](https://huggingface.co/docs/evaluate/main/en/package_reference/main_classes#evaluate.EvaluationModule.compute) 以计算准确率：

```py
>>> import numpy as np

>>> def compute_metrics(eval_pred):
...     predictions, labels = eval_pred
...     predictions = np.argmax(predictions, axis=1)
...     return accuracy.compute(predictions=predictions, references=labels)
```

您的 `compute_metrics` 函数现已准备就绪，您将在配置训练时再次使用它。

## 训练

如果您不熟悉使用 [Trainer](/docs/transformers/main/zh/main_classes/trainer#transformers.Trainer) 对模型进行微调，请查看 [此处](../training) 的基础教程！

现在您可以开始训练模型了！使用 [AutoModelForMultipleChoice](/docs/transformers/main/zh/model_doc/auto#transformers.AutoModelForMultipleChoice) 加载 BERT：

```py
>>> from transformers import AutoModelForMultipleChoice, TrainingArguments, Trainer

>>> model = AutoModelForMultipleChoice.from_pretrained("google-bert/bert-base-uncased")
```

此时，只剩下三个步骤：

1. 在 [TrainingArguments](/docs/transformers/main/zh/main_classes/trainer#transformers.TrainingArguments) 中定义训练超参数。唯一必需的参数是 `output_dir`，用于指定模型的保存位置。通过设置 `push_to_hub=True`，你可以将该模型推送到 Hub（上传模型需要登录 Hugging Face 账户）。在每个 epoch 结束时，[Trainer](/docs/transformers/main/zh/main_classes/trainer#transformers.Trainer) 会评估准确率并保存训练检查点。
2. 将训练参数与模型、数据集、分词器、数据整理器以及 `compute_metrics` 函数一起传递给 [Trainer](/docs/transformers/main/zh/main_classes/trainer#transformers.Trainer)。
3. 调用 [train()](/docs/transformers/main/zh/main_classes/trainer#transformers.Trainer.train) 来微调你的模型。

```py
>>> training_args = TrainingArguments(
...     output_dir="my_awesome_swag_model",
...     eval_strategy="epoch",
...     save_strategy="epoch",
...     load_best_model_at_end=True,
...     learning_rate=5e-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_swag["train"],
...     eval_dataset=tokenized_swag["validation"],
...     processing_class=tokenizer,
...     data_collator=collator,
...     compute_metrics=compute_metrics,
... )

>>> trainer.train()
```

训练完成后，使用 [push_to_hub()](/docs/transformers/main/zh/main_classes/trainer#transformers.Trainer.push_to_hub) 方法将您的模型共享到 Hub，以便所有人都能使用您的模型：

```py
>>> trainer.push_to_hub()
```

如需了解如何针对多项选择任务微调模型的更详细示例，请参阅对应的 [PyTorch notebook](https://colab.research.google.com/github/huggingface/notebooks/blob/main/examples/multiple_choice.ipynb)。

## 推理

很好，现在你已经微调了一个模型，可以使用它进行推理了！

想出一些文本和两个候选答案：

```py
>>> prompt = "France has a bread law, Le Décret Pain, with strict rules on what is allowed in a traditional baguette."
>>> candidate1 = "The law does not apply to croissants and brioche."
>>> candidate2 = "The law applies to baguettes."
```

对每个提示和候选答案对进行分词，并返回 PyTorch 张量。您还应该创建一些 `labels`：

```py
>>> from transformers import AutoTokenizer

>>> tokenizer = AutoTokenizer.from_pretrained("username/my_awesome_swag_model")
>>> inputs = tokenizer([[prompt, candidate1], [prompt, candidate2]], return_tensors="pt", padding=True)
>>> labels = torch.tensor(0).unsqueeze(0)
```

将输入和标签传入模型，并返回 `logits`：

```py
>>> from transformers import AutoModelForMultipleChoice

>>> model = AutoModelForMultipleChoice.from_pretrained("username/my_awesome_swag_model")
>>> outputs = model(**{k: v.unsqueeze(0) for k, v in inputs.items()}, labels=labels)
>>> logits = outputs.logits
```

获取概率最高的类别：

```py
>>> predicted_class = logits.argmax().item()
>>> predicted_class
0
```

