# Réponse aux questions

Les tâches de question-réponse renvoient une réponse à partir d'une question. Si vous avez déjà demandé à un assistant virtuel comme Alexa, Siri ou Google quel est le temps qu'il fait, vous avez déjà utilisé un modèle de question-réponse. Il existe deux types courants de tâches de question-réponse :

- Extractive : extraire la réponse du contexte donné.
- Abstractive : générer une réponse à partir du contexte qui répond correctement à la question.

Ce guide vous montre comment :

1. Effectuer le fine-tuning de [DistilBERT](https://huggingface.co/distilbert/distilbert-base-uncased) sur le jeu de données [SQuAD](https://huggingface.co/datasets/rajpurkar/squad) pour la question-réponse extractive.
2. Utilisez votre modèle affiné pour l'inférence.

Pour consulter toutes les architectures et points de contrôle compatibles avec cette tâche, nous vous recommandons de consulter la [page de la tâche](https://huggingface.co/tasks/question-answering)

Avant de commencer, assurez-vous d'avoir installé toutes les bibliothèques nécessaires :

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

Nous vous encourage à vous connecter à votre compte Hugging Face afin de pouvoir téléverser et partager votre modèle avec la communauté. Lorsque cela vous est demandé, saisissez votre jeton pour vous connecter :

```py
>>> from huggingface_hub import notebook_login

>>> notebook_login()
```

## Charger le jeu de données SQuAD

Commencez par charger un sous-ensemble plus petit du jeu de données SQuAD à partir de la bibliothèque 🤗 Datasets. Cela vous permettra de faire des essais et de vous assurer que tout fonctionne correctement avant de consacrer plus de temps à l'entraînement sur l'ensemble du jeu de données.

```py
>>> from datasets import load_dataset

>>> squad = load_dataset("rajpurkar/squad", split="train[:5000]")
```

Divisez le split `train` du jeu de données en un ensemble d'entraînement et un ensemble de test à l'aide de la méthode [train_test_split](https://huggingface.co/docs/datasets/main/en/package_reference/main_classes#datasets.Dataset.train_test_split) :

```py
>>> squad = squad.train_test_split(test_size=0.2)
```

Puis, jetez un œil à un exemple :

```py
>>> 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'
}
```

Plusieurs champs importants figurent ici :

- `answers` : la position de départ du jeton de réponse et le texte de la réponse.
- `context`: background information from which the model needs to extract the answer.
- `question` : la question à laquelle le modèle doit répondre.

## Prétraitement

L'étape suivante consiste à charger un tokenizer DistilBERT pour traiter les champs `question` et `context` :

```py
>>> from transformers import AutoTokenizer

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

Il existe quelques étapes de prétraitement spécifiques aux tâches de réponse à des questions dont vous devez tenir compte :

1. Certains exemples d’un jeu de données peuvent comporter un `context` très long qui dépasse la longueur d’entrée maximale du modèle. Pour traiter des séquences plus longues, tronquez uniquement le `context` en définissant `truncation="only_second"`.
2. Ensuite, mappez les positions de début et de fin de la réponse sur le `context` d'origine en définissant `return_offset_mapping=True`.
3. With the mapping in hand, now you can find the start and end tokens of the answer. Use the `sequence_ids` method to find which part of the offset corresponds to the `question` and which corresponds to the `context`.

Voici comment créer une fonction pour tronquer et mapper les jetons de début et de fin de la `answer` vers le `context` :

```py
>>> 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
```

Pour appliquer la fonction de prétraitement à l'ensemble du jeu de données, utilisez la fonction [map](https://huggingface.co/docs/datasets/main/en/package_reference/main_classes#datasets.Dataset.map) de 🤗 Datasets. Vous pouvez accélérer la fonction `map` en définissant `batched=True` pour traiter plusieurs éléments du jeu de données à la fois. Supprimez les colonnes dont vous n'avez pas besoin :

```py
>>> tokenized_squad = squad.map(preprocess_function, batched=True, remove_columns=squad["train"].column_names)
```

Créez maintenant un lot d’exemples à l’aide de [DefaultDataCollator](/docs/transformers/main/fr/main_classes/data_collator#transformers.DefaultDataCollator). Contrairement aux autres data collators de 🤗 Transformers, le [DefaultDataCollator](/docs/transformers/main/fr/main_classes/data_collator#transformers.DefaultDataCollator) n’applique aucun prétraitement supplémentaire tel que le padding.

```py
>>> from transformers import DefaultDataCollator

>>> data_collator = DefaultDataCollator()
```

## Entraînement

Si vous n'êtes pas familier avec l'ajustement fin d'un modèle à l'aide du [Trainer](/docs/transformers/main/fr/main_classes/trainer#transformers.Trainer), consultez le tutoriel de base [ici](../training) !

Vous êtes prêt à commencer l’entraînement de votre modèle ! Chargez DistilBERT avec [AutoModelForQuestionAnswering](/docs/transformers/main/fr/model_doc/auto#transformers.AutoModelForQuestionAnswering) :

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

>>> model = AutoModelForQuestionAnswering.from_pretrained("distilbert/distilbert-base-uncased")
```

À ce stade, il ne reste que trois étapes :

1. Définissez vos hyperparamètres d'entraînement dans [TrainingArguments](/docs/transformers/main/fr/main_classes/trainer#transformers.TrainingArguments). Le seul paramètre obligatoire est `output_dir`, qui spécifie l'emplacement de sauvegarde de votre modèle. Vous publierez ce modèle sur le Hub en définissant `push_to_hub=True` (vous devez être connecté à Hugging Face pour téléverser votre modèle).
2. Transmettez les arguments d’entraînement à [Trainer](/docs/transformers/main/fr/main_classes/trainer#transformers.Trainer) en même temps que le modèle, le jeu de données, le tokenizer et le data collator.
3. Appel de [train()](/docs/transformers/main/fr/main_classes/trainer#transformers.Trainer.train) pour l’ajustement fin de votre modèle.

```py
>>> 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"],
...     processing_class=tokenizer,
...     data_collator=data_collator,
... )

>>> trainer.train()
```

Une fois l’entraînement terminé, partagez votre modèle sur le Hub à l’aide de la méthode [push_to_hub()](/docs/transformers/main/fr/main_classes/trainer#transformers.Trainer.push_to_hub) afin que chacun puisse l’utiliser :

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

Pour un exemple plus approfondi sur la façon d’affiner un modèle pour la réponse à des questions, consultez le [notebook PyTorch](https://colab.research.google.com/github/huggingface/notebooks/blob/main/examples/question_answering.ipynb) correspondant.

## Évaluer

L'évaluation pour le question answering nécessite une quantité importante de post-traitement. Pour ne pas vous faire perdre trop de temps, ce guide passe l'étape d'évaluation. Le [Trainer](/docs/transformers/main/fr/main_classes/trainer#transformers.Trainer) calcule néanmoins la perte d'évaluation pendant l'entraînement, afin que vous ne soyez pas totalement dans l'ignorance des performances de votre modèle.

Si vous avez plus de temps et que vous souhaitez savoir comment évaluer votre modèle pour le question answering, consultez le chapitre [Question answering](https://huggingface.co/course/chapter7/7?fw=pt#post-processing) du cours 🤗 Hugging Face !

## Inférence

Parfait, maintenant que vous avez affiné un modèle, vous pouvez l'utiliser pour l'inférence !

Formulez une question et un contexte que vous souhaitez que le modèle prédise :

```py
>>> 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."
```

Tokeniser le texte et retourner des tenseurs PyTorch :

```py
>>> from transformers import AutoTokenizer

>>> tokenizer = AutoTokenizer.from_pretrained("my_awesome_qa_model")
>>> inputs = tokenizer(question, context, return_tensors="pt")
```

Transmettez vos entrées au modèle et renvoyez les `logits` :

```py
>>> import torch
>>> from transformers import AutoModelForQuestionAnswering

>>> model = AutoModelForQuestionAnswering.from_pretrained("my_awesome_qa_model")
>>> with torch.no_grad():
...     outputs = model(**inputs)
```

Obtenez la probabilité la plus élevée de la sortie du modèle pour les positions de début et de fin :

```py
>>> answer_start_index = outputs.start_logits.argmax()
>>> answer_end_index = outputs.end_logits.argmax()
```

Décodez les jetons prédits pour obtenir la réponse :

```py
>>> 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'
```

