# Course

## Docs

- [Quiz de fin de chapitre](https://huggingface.co/learn/course/fr/chapter7/9.md)
- [Résumé de textes](https://huggingface.co/learn/course/fr/chapter7/5.md)
- [Introduction](https://huggingface.co/learn/course/fr/chapter7/1.md)
- [<i>Finetuner</i> un modèle de langage masqué](https://huggingface.co/learn/course/fr/chapter7/3.md)
- [Entraîner un modèle de langage causal à partir de zéro](https://huggingface.co/learn/course/fr/chapter7/6.md)
- [Maîtriser le <i>NLP</i>](https://huggingface.co/learn/course/fr/chapter7/8.md)
- [Traduction](https://huggingface.co/learn/course/fr/chapter7/4.md)
- [Réponse aux questions](https://huggingface.co/learn/course/fr/chapter7/7.md)
- [Classification de <i>tokens</i>](https://huggingface.co/learn/course/fr/chapter7/2.md)
- [Fin de la première partie du cours !](https://huggingface.co/learn/course/fr/chapter4/5.md)
- [Le <i>Hub</i> d'Hugging Face](https://huggingface.co/learn/course/fr/chapter4/1.md)
- [Partage de modèles pré-entraînés](https://huggingface.co/learn/course/fr/chapter4/3.md)
- [Quiz de fin de chapitre](https://huggingface.co/learn/course/fr/chapter4/6.md)
- [Construire une carte de modèle](https://huggingface.co/learn/course/fr/chapter4/4.md)
- [Utilisation de modèles pré-entraînés](https://huggingface.co/learn/course/fr/chapter4/2.md)
- [<i>Finetuning</i>, coché !](https://huggingface.co/learn/course/fr/chapter3/5.md)
- [Introduction](https://huggingface.co/learn/course/fr/chapter3/1.md)
- [<i>Finetuner</i> un modèle avec l'API Trainer](https://huggingface.co/learn/course/fr/chapter3/3.md)
- [Quiz de fin de chapitre](https://huggingface.co/learn/course/fr/chapter3/6.md)
- [<i>Finetuner</i> un modèle avec Keras](https://huggingface.co/learn/course/fr/chapter3/3_tf.md)
- [Un entraînement complet](https://huggingface.co/learn/course/fr/chapter3/4.md)
- [Préparer les données](https://huggingface.co/learn/course/fr/chapter3/2.md)
- [Comment rédiger une bonne <i>issue</i>](https://huggingface.co/learn/course/fr/chapter8/5.md)
- [Introduction](https://huggingface.co/learn/course/fr/chapter8/1.md)
- [Demander de l'aide sur les forums](https://huggingface.co/learn/course/fr/chapter8/3.md)
- [Partie 2 terminée !](https://huggingface.co/learn/course/fr/chapter8/6.md)
- [Débogage du pipeline d'entraînement](https://huggingface.co/learn/course/fr/chapter8/4.md)
- [Quiz de fin de chapitre](https://huggingface.co/learn/course/fr/chapter8/7.md)
- [Débogage du pipeline d'entraînement](https://huggingface.co/learn/course/fr/chapter8/4_tf.md)
- [Que faire lorsque vous obtenez une erreur](https://huggingface.co/learn/course/fr/chapter8/2.md)
- [Quiz de fin de chapitre](https://huggingface.co/learn/course/fr/chapter9/9.md)
- [Intégrations avec le <i>Hub d'Hugging Face</i>](https://huggingface.co/learn/course/fr/chapter9/5.md)
- [Introduction à <i>Gradio</i>](https://huggingface.co/learn/course/fr/chapter9/1.md)
- [Comprendre la classe <i>Interface</i>](https://huggingface.co/learn/course/fr/chapter9/3.md)
- [Fonctionnalités avancées de l'interface](https://huggingface.co/learn/course/fr/chapter9/6.md)
- [<i>Gradio</i>, coché !](https://huggingface.co/learn/course/fr/chapter9/8.md)
- [Partager ses démos avec les autres](https://huggingface.co/learn/course/fr/chapter9/4.md)
- [Introduction à la classe <i>Blocks</i>](https://huggingface.co/learn/course/fr/chapter9/7.md)
- [Construire votre première démo](https://huggingface.co/learn/course/fr/chapter9/2.md)
- [Sessions en direct et ateliers](https://huggingface.co/learn/course/fr/events/1.md)
- [Fête des blocs Gradio](https://huggingface.co/learn/course/fr/events/3.md)
- [Événement pour le lancement de la partie 2](https://huggingface.co/learn/course/fr/events/2.md)
- [<i>Tokenizer</i>, coché !](https://huggingface.co/learn/course/fr/chapter6/9.md)
- [Tokénisation <i>Byte-Pair Encoding</i>](https://huggingface.co/learn/course/fr/chapter6/5.md)
- [Introduction](https://huggingface.co/learn/course/fr/chapter6/1.md)
- [Pouvoirs spéciaux des <i>tokenizers</i> rapides](https://huggingface.co/learn/course/fr/chapter6/3.md)
- [Tokénisation <i>WordPiece</i>](https://huggingface.co/learn/course/fr/chapter6/6.md)
- [Quiz de fin de chapitre](https://huggingface.co/learn/course/fr/chapter6/10.md)
- [Construction d'un <i>tokenizer</i>, bloc par bloc](https://huggingface.co/learn/course/fr/chapter6/8.md)
- [Normalisation et prétokenization](https://huggingface.co/learn/course/fr/chapter6/4.md)
- [Tokenisation <i>Unigram</i>](https://huggingface.co/learn/course/fr/chapter6/7.md)
- [<i>Tokenizer</i> rapide dans le pipeline de QA](https://huggingface.co/learn/course/fr/chapter6/3b.md)
- [Entraîner un nouveau <i>tokenizer</i> à partir d'un ancien](https://huggingface.co/learn/course/fr/chapter6/2.md)
- [Création de votre propre jeu de données](https://huggingface.co/learn/course/fr/chapter5/5.md)
- [Introduction](https://huggingface.co/learn/course/fr/chapter5/1.md)
- [Il est temps de trancher et de découper](https://huggingface.co/learn/course/fr/chapter5/3.md)
- [Recherche sémantique avec FAISS](https://huggingface.co/learn/course/fr/chapter5/6.md)
- [Quiz de fin de chapitre](https://huggingface.co/learn/course/fr/chapter5/8.md)
- [Données massives ? 🤗 <i>Datasets</i> à la rescousse !](https://huggingface.co/learn/course/fr/chapter5/4.md)
- [🤗 <i>Datasets</i>, coché !](https://huggingface.co/learn/course/fr/chapter5/7.md)
- [Que faire si mon jeu de données n'est pas sur le <i>Hub</i> ?](https://huggingface.co/learn/course/fr/chapter5/2.md)
- [Glossaire](https://huggingface.co/learn/course/fr/glossary/1.md)
- [Manipulation de plusieurs séquences](https://huggingface.co/learn/course/fr/chapter2/5.md)
- [Introduction](https://huggingface.co/learn/course/fr/chapter2/1.md)
- [Les modèles](https://huggingface.co/learn/course/fr/chapter2/3.md)
- [Tout assembler](https://huggingface.co/learn/course/fr/chapter2/6.md)
- [Quiz de fin de chapitre](https://huggingface.co/learn/course/fr/chapter2/8.md)
- [Les <i>tokenizers</i>](https://huggingface.co/learn/course/fr/chapter2/4.md)
- [Utilisation de base terminée !](https://huggingface.co/learn/course/fr/chapter2/7.md)
- [Derrière le pipeline](https://huggingface.co/learn/course/fr/chapter2/2.md)
- [Introduction](https://huggingface.co/learn/course/fr/chapter0/1.md)
- [Résumé du chapitre](https://huggingface.co/learn/course/fr/chapter1/9.md)
- [Les modèles basés sur l'encodeur](https://huggingface.co/learn/course/fr/chapter1/5.md)
- [Introduction](https://huggingface.co/learn/course/fr/chapter1/1.md)
- [Que peuvent faire les <i>transformers</i> ?](https://huggingface.co/learn/course/fr/chapter1/3.md)
- [Les modèles basés sur le décodeur](https://huggingface.co/learn/course/fr/chapter1/6.md)
- [Quiz de fin de chapitre](https://huggingface.co/learn/course/fr/chapter1/10.md)
- [Biais et limitations](https://huggingface.co/learn/course/fr/chapter1/8.md)
- [Comment fonctionnent les <i>transformers</i> ?](https://huggingface.co/learn/course/fr/chapter1/4.md)
- [Les modèles de séquence-à-séquence](https://huggingface.co/learn/course/fr/chapter1/7.md)
- [Traitement du langage naturel (NLP pour <i>Natural Language Processing</i>)](https://huggingface.co/learn/course/fr/chapter1/2.md)

### Quiz de fin de chapitre
https://huggingface.co/learn/course/fr/chapter7/9.md

# Quiz de fin de chapitre

<CourseFloatingBanner
    chapter={7}
    classNames="absolute z-10 right-0 top-0"
/>

Testons ce que vous avez appris dans ce chapitre !

### 1. Laquelle des tâches suivantes peut être considérée comme un problème de classification de <i>tokens</i> ?

<Question
	choices={[
		{
			text: "Trouvez les composants grammaticaux d'une phrase.",
			explain: "Nous pouvons alors étiqueter chaque mot comme étant un nom, un verbe, etc.",
			correct: true
		},
		{
			text: "Trouvez si une phrase est grammaticalement correcte ou non.",
			explain: "C'est un problème de classification des séquences."
		},
		{
			text: "Trouvez les personnes mentionnées dans une phrase.",
			explain: "Nous pouvons étiqueter chaque mot comme personne ou non personne.",
            correct: true
		},
        {
			text: "Trouvez le groupe de mots dans une phrase qui répond à une question.",
			explain: "C'est un problème de réponse aux questions."
		}
	]}
/>

### 2. Quelle partie du prétraitement pour la classification de <i>tokens</i> diffère des autres pipelines de prétraitement ?

<Question
	choices={[
		{
			text: "Il n'y a rien à faire, les textes sont déjà tokenisés.",
			explain: "Les textes sont en effet donnés sous la forme de mots séparés mais nous devons encore appliquer le modèle de tokenisation en sous-mots."
		},
		{
			text: "Les textes sont donnés sous forme de mots, il suffit donc d'appliquer la tokénisation en sous-mots.",
			explain: "C'est différent du prétraitement habituel, où nous devons appliquer le pipeline complet de tokénisation. Pouvez-vous penser à une autre différence ?",
			correct: true
		},
		{
			text: "Nous utilisons <code>-100</code> pour étiqueter les <i>tokens</i> spéciaux.",
			explain: "Ce n'est pas spécifique à la classification de <i>tokens</i>. Nous utilisons toujours <code>-100</code> comme étiquette pour les <i>tokens</i> que nous voulons ignorer dans la perte."
		},
		{
			text: "Nous devons nous assurer que les étiquettes sont tronquées ou rembourrées à la même taille que les entrées, lorsque nous appliquons la troncature/le <i>padding</i>.",
			explain: "En effet mais ce n'est pas la seule différence.",
			correct: true
		}
	]}
/>

### 3. Quel problème se pose lorsque nous tokenisons les mots dans un problème de classification de <i>tokens</i> et que nous voulons étiqueter les <i>tokens</i> ?

<Question
	choices={[
		{
			text: "Le <i>tokenizer</i> ajoute des <i>tokens</i> spéciaux et nous n'avons pas d'étiquettes pour eux.",
			explain: "Nous les étiquetons par <code>-100</code> ils sont donc ignorés dans la perte."
		},
		{
			text: "Chaque mot peut produire plusieurs <i>tokens</i>, ce qui fait que nous nous retrouvons avec plus de <i>tokens</i> que d'étiquettes.",
			explain: "C'est le problème principal et nous devons aligner les étiquettes originales avec les <i>tokens</i>.",
			correct: true
		},
		{
			text: "Les <i>tokens</i> ajoutés n'ont pas d'étiquettes, il n'y a donc pas de problème.",
			explain: "Nous avons besoin d'autant d'étiquettes que de <i>tokens</i>, sinon nos modèles se tromperont."
		}
	]}
/>

### 4. Que signifie « adaptation au domaine » ?

<Question
	choices={[
		{
			text: "C'est lorsque nous exécutons un modèle sur un jeu de données et que nous obtenons les prédictions pour chaque échantillon de ce jeu de données.",
			explain: "C'est juste une inférence."
		},
		{
			text: "C'est quand on entraîne un modèle sur un jeu de données.",
			explain: "Il s'agit d'entraîner un modèle. Il n'y a pas d'adaptation ici."
		},
		{
			text: "C'est lorsque nous <i>finetunons</i> un modèle pré-entraîné sur un nouveau jeu de données et qu'il donne des prédictions qui sont plus adaptées à ce nouveau jeu de données.",
			explain: "Le modèle a adapté ses connaissances au nouveau jeu de données.",
            correct: true
		},
        {
			text: "C'est lorsque nous ajoutons des échantillons mal classés à un jeu de données pour rendre notre modèle plus robuste.",
			explain: "C'est certainement quelque chose que vous devriez faire si vous réentraînez votre modèle régulièrement, mais ce n'est pas une adaptation au domaine."
		}
	]}
/>

### 5. Quelles sont les étiquettes dans un problème de modélisation du langage masqué ?

<Question
	choices={[
		{
			text: "Certains des <i>tokens</i> de la phrase d'entrée sont masqués de manière aléatoire et les étiquettes sont les <i>tokens</i> d'entrée originaux.",
			explain: "C'est ça !",
            correct: true
		},
		{
			text: "Certains des <i>tokens</i> de la phrase d'entrée sont masqués de manière aléatoire et les étiquettes sont les <i>tokens</i> d'entrée originaux, décalés vers la gauche.",
			explain: "Non, le déplacement des étiquettes vers la gauche correspond à la prédiction du mot suivant, ce qui est une modélisation causale du langage."
		},
		{
			text: "Certains des <i>tokens</i> de la phrase d'entrée sont masqués de manière aléatoire et l'étiquette indique si la phrase est positive ou négative.",
			explain: "Il s'agit d'un problème de classification de séquences avec une certaine augmentation de données et non d'une modélisation du langage masqué."
		},
        {
			text: "Certains des <i>tokens</i> des deux phrases d'entrée sont masqués de manière aléatoire et l'étiquette indique si les deux phrases sont similaires ou non.",
			explain: "Il s'agit d'un problème de classification de séquences avec une certaine augmentation de données et non d'une modélisation du langage masqué."
		}
	]}
/>

### 6. Laquelle de ces tâches peut être considérée comme un problème de séquence à séquence ?

<Question
	choices={[
		{
			text: "Rédiger de courtes critiques de longs documents.",
			explain: "C'est un problème de résumé. Essayez une autre réponse !",
            correct: true
		},
		{
			text: "Répondre à des questions sur un document.",
			explain: "Cela peut être formulé comme un problème de séquence à séquence. Ce n'est cependant pas la seule bonne réponse.",
            correct: true
		},
		{
			text: "Traduire un texte en chinois en anglais.",
			explain: "C'est définitivement un problème de séquence à séquence. Pouvez-vous en trouver un autre ?",
            correct: true
		},
        {
			text: "Corriger les messages envoyés par mon neveu/ami pour qu'ils soient en anglais correct.",
			explain: "Il s'agit d'un problème de traduction donc d'une tâche de séquence à séquence. Mais ce n'est pas la seule bonne réponse !",
			correct: true
		}
	]}
/>

### 7. Quelle est la bonne façon de prétraiter les données pour un problème de séquence à séquence ?

<Question
	choices={[
		{
			text: "Les entrées et les cibles doivent être envoyées ensemble au <i>tokenizer</i> avec les éléments suivants <code>inputs=...</code> et <code>targets=...</code>.",
			explain: "Nous pourrions ajouter cette API à l'avenir mais ce n'est pas possible pour le moment."
		},
		{
			text: "Les entrées et les cibles doivent être prétraitées, en deux appels séparés au <i>tokenizer</i>.",
			explain: "C'est vrai, mais incomplet. Il y a quelque chose que vous devez faire pour vous assurer que le <i>tokenizer</i> traite les deux correctement."
		},
		{
			text: "Comme d'habitude, nous devons simplement tokeniser les entrées.",
			explain: "Pas dans un problème de classification de séquences. Les cibles sont aussi des textes que nous devons convertir en chiffres !"
		},
        {
			text: "Les entrées doivent être envoyées au <i>tokenizer</i>, et les cibles aussi, mais sous un gestionnaire de contexte spécial.",
			explain: "C'est exact, le <i>tokenizer</i> doit être mis en mode cible par ce gestionnaire de contexte.",
			correct: true
		}
	]}
/>

{#if fw === 'pt'}

### 8. Pourquoi existe-t-il une sous-classe spécifique de <code>Trainer</code> pour les problèmes de séquence à séquence ?

<Question
	choices={[
		{
			text: "Parce que les problèmes de séquence-à-séquence utilisent une perte personnalisée, pour ignorer les étiquettes définies à <code>-100</code>.",
			explain: "Ce n'est pas du tout une perte personnalisée mais la façon dont la perte est toujours calculée."
		},
		{
			text: "Parce que les problèmes de séquence à séquence nécessitent une boucle d'évaluation spéciale.",
			explain: "Les prédictions des modèles de séquence à séquence sont souvent exécutées en utilisant la méthode <code>generate()</code>.",
			correct: true
		},
		{
			text: "Parce que les cibles sont des textes dans des problèmes de séquence à séquence.",
			explain: "<code>Trainer</code> ne se soucie pas vraiment de cela puisqu'elles ont été prétraités auparavant."
		},
        {
			text: "Parce que nous utilisons deux modèles dans les problèmes de séquence à séquence.",
			explain: "Nous utilisons en quelque sorte deux modèles, un encodeur et un décodeur, mais ils sont regroupés dans un seul modèle."
		}
	]}
/>

{:else}

### 9. Pourquoi est-il souvent inutile de spécifier une perte quand on appelle <code>compile()</code> sur un <i>transformer</i> ?

<Question
	choices={[
		{
			text: "Parce que les <i>tranformers</i> sont entraînés avec un apprentissage autosupervisé.",
			explain: "Pas tout à fait. Même l'apprentissage autosupervisé a besoin d'une fonction de perte !"
		},
		{
			text: "Parce que la sortie de perte interne du modèle est utilisée par défaut.",
			explain: " ",
			correct: true
		},
		{
			text: "Parce que nous calculons les mesures après l'entraînement au lieu de le faire.",
			explain: "Nous le faisons souvent mais cela n'explique pas d'où vient la valeur de perte que nous optimisons dans l'entraînement."
		},
        {
			text: "Parce que la perte est spécifiée dans <code>model.fit()</code>.",
			explain: "La fonction de perte est toujours fixée une fois que vous exécutez <code>model.compile()</code> et ne peut pas être modifiée dans <code>model.fit()</code>."
		}
	]}
/>

{/if}

### 10. Quand devez-vous pré-entraîner un nouveau modèle ?

<Question
	choices={[
		{
			text: "Lorsqu'il n'y a pas de modèle pré-entraîné disponible pour votre langue spécifique.",
			explain: " ",
			correct: true
		},
		{
			text: "Lorsque vous disposez d'un grand nombre de données, même s'il existe un modèle pré-entraîné qui pourrait fonctionner sur ces données.",
			explain: "Dans ce cas, vous devriez probablement utiliser le modèle pré-entraîné et le <i>finetuner</i> sur vos données afin d'éviter d'énormes coûts de calcul."
		},
		{
			text: "Lorsque vous avez des doutes sur le biais du modèle pré-entraîné que vous utilisez.",
			explain: "C'est vrai mais vous devez vous assurer que les données que vous utiliserez pour l'entraînement sont vraiment meilleures.",
			correct: true
		},
        {
			text: "Lorsque les modèles pré-entraînés disponibles ne sont tout simplement pas assez bons.",
			explain: "Vous êtes sûr d'avoir bien débogué votre entraînement ?"
		}
	]}
/>

### 11. Pourquoi est-il facile de prétraîner un modèle de langage sur des batchs de textes ?

<Question
	choices={[
		{
			text: "Parce qu'il y a beaucoup de textes disponibles sur internet.",
			explain: "Bien que vrai, cela ne répond pas vraiment à la question. Essayez encore !"
		},
		{
			text: "Parce que l'objectif de pré-entraînement ne nécessite pas que les humains étiquettent les données.",
			explain: "La modélisation du langage est un problème autosupervisé.",
			correct: true
		},
		{
			text: "Parce que la bibliothèque 🤗 <i>Transformers</i> ne nécessite que quelques lignes de code pour démarrer l'entraînement.",
			explain: "Bien que vrai, cela ne répond pas vraiment à la question posée. Essayez une autre réponse !"
		}
	]}
/>

### 12. Quels sont les principaux défis lors du prétraitement des données pour une tâche de réponse à des questions ?

<Question
	choices={[
		{
			text: "Vous devez tokeniser les entrées.",
			explain: "Mais est-ce vraiment un défi majeur ?"
		},
		{
			text: "Vous devez faire face à des contextes très longs, qui donnent plusieurs caractéristiques d'entraînement qui peuvent ou non contenir la réponse.",
			explain: "C'est certainement l'un des défis à relever.",
			correct: true
		},
		{
			text: "Vous devez tokeniser les réponses à la question ainsi que les entrées.",
			explain: "Non, à moins que vous ne formuliez votre problème de réponse aux questions comme une tâche de séquence à séquence."
		},
       {
			text: "À partir de l'intervalle de réponse dans le texte, vous devez trouver le token de début et de fin dans l'entrée tokenisée.",
			explain: "C'est une des parties les plus difficiles, oui !",
			correct: true
		}
	]}
/>

### 13. Comment le post-traitement est-il généralement effectué dans les réponses aux questions ?

<Question
	choices={[
		{
			text: "Le modèle vous donne les positions de début et de fin de la réponse, et vous n'avez plus qu'à décoder la plage de <i>tokens</i> correspondant.",
			explain: "Ce pourrait être une façon de faire mais c'est un peu trop simpliste."
		},
		{
			text: "Le modèle vous donne les positions de début et de fin de la réponse pour chaque caractéristique créée par un exemple et il vous suffit de décoder la plage de <i>tokens</i> correspondant dans celui qui a le meilleur score.",
			explain: "C'est proche du post-traitement que nous avons étudié, mais ce n'est pas tout à fait exact."
		},
		{
			text: "Le modèle vous donne les positions de début et de fin de la réponse pour chaque caractéristique créée par un exemple et vous n'avez plus qu'à les faire correspondre à la portée dans le contexte de celui qui a le meilleur score.",
			explain: "C'est ça en résumé !",
			correct: true
		},
        {
			text: "Le modèle génère une réponse et il vous suffit de la décoder.",
			explain: "A moins que vous ne formuliez votre problème de réponse aux questions comme une tâche de séquence à séquence."
		}
	]}
/>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter7/9.mdx" />

### Résumé de textes
https://huggingface.co/learn/course/fr/chapter7/5.md

# Résumé de textes

{#if fw === 'pt'}
<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section5_pt.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter7/section5_pt.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section5_pt.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter7/section5_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section5_tf.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter7/section5_tf.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section5_tf.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter7/section5_tf.ipynb"},
]} />

{/if}


Dans cette section, nous allons voir comment les *transformers* peuvent être utilisés pour condenser de longs documents en résumés, une tâche connue sous le nom de _résumé de texte_. Il s'agit de l'une des tâches de NLP les plus difficiles car elle requiert une série de capacités, telles que la compréhension de longs passages et la génération d'un texte cohérent qui capture les sujets principaux d'un document. Cependant, lorsqu'il est bien fait, le résumé de texte est un outil puissant qui peut accélérer divers processus commerciaux en soulageant les experts du domaine de la lecture détaillée de longs documents.

<Youtube id="yHnr5Dk2zCI"/>

Bien qu'il existe déjà plusieurs modèles *finetunés* pour le résumé sur le [*Hub*](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), la plupart d'entre eux ne sont adaptés qu'aux documents en anglais. Ainsi, pour ajouter une touche d'originalité à cette section, nous allons entraîner un modèle bilingue pour l'anglais et l'espagnol. À la fin de cette section, vous disposerez d'un [modèle](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) capable de résumer les commentaires des clients comme celui présenté ici :

<iframe src="https://course-demos-mt5-small-finetuned-amazon-en-es.hf.space" frameBorder="0" height="400" title="Gradio app" class="block dark:hidden container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>
<iframe src="https://course-demos-mt5-small-finetuned-amazon-en-es-darkmode.hf.space" frameBorder="0" height="400" title="Gradio app" class="hidden dark:block container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Comme nous allons le voir, ces résumés sont concis car ils sont appris à partir des titres que les clients fournissent dans leurs commentaires sur les produits. Commençons par constituer un corpus bilingue approprié pour cette tâche.

## Préparation d'un corpus multilingue

Nous allons utiliser le [*Multilingual Amazon Reviews Corpus*](https://huggingface.co/datasets/amazon_reviews_multi) pour créer notre résumeur bilingue. Ce corpus est constitué de critiques de produits Amazon en six langues et est généralement utilisé pour évaluer les classifieurs multilingues. Cependant, comme chaque critique est accompagnée d'un titre court, nous pouvons utiliser les titres comme résumés cibles pour l'apprentissage de notre modèle ! Pour commencer, téléchargeons les sous-ensembles anglais et espagnols depuis le *Hub* :

```python
from datasets import load_dataset

spanish_dataset = load_dataset("amazon_reviews_multi", "es")
english_dataset = load_dataset("amazon_reviews_multi", "en")
english_dataset
```

```python out
DatasetDict({
    train: Dataset({
        features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'],
        num_rows: 200000
    })
    validation: Dataset({
        features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'],
        num_rows: 5000
    })
    test: Dataset({
        features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'],
        num_rows: 5000
    })
})
```

Comme vous pouvez le voir, pour chaque langue, il y a 200 000 critiques pour la partie entraînement et 5 000 critiques pour chacune des parties validation et test. Les informations qui nous intéressent sont contenues dans les colonnes `review_body` et `review_title`. Voyons quelques exemples en créant une fonction simple qui prend un échantillon aléatoire de l'ensemble d'entraînement avec les techniques apprises au [chapitre 5](/course/fr/chapter5) :

```python
def show_samples(dataset, num_samples=3, seed=42):
    sample = dataset["train"].shuffle(seed=seed).select(range(num_samples))
    for example in sample:
        print(f"\n'>> Title: {example['review_title']}'")
        print(f"'>> Review: {example['review_body']}'")


show_samples(english_dataset)
```

```python out
'>> Title: Worked in front position, not rear'
# Travaillé en position avant, pas arrière
'>> Review: 3 stars because these are not rear brakes as stated in the item description. At least the mount adapter only worked on the front fork of the bike that I got it for.'
# 3 étoiles car ce ne sont pas des freins arrière comme indiqué dans la description de l'article. Au moins, l'adaptateur de montage ne fonctionnait que sur la fourche avant du vélo pour lequel je l'ai acheté.

'>> Title: meh'
'>> Review: Does it’s job and it’s gorgeous but mine is falling apart, I had to basically put it together again with hot glue'
# Il fait son travail et il est magnifique mais le mien est en train de tomber en morceaux, j'ai dû le recoller avec de la colle chaude.

'>> Title: Can\'t beat these for the money' 
# On ne peut pas faire mieux pour le prix
'>> Review: Bought this for handling miscellaneous aircraft parts and hanger "stuff" that I needed to organize; it really fit the bill. The unit arrived quickly, was well packaged and arrived intact (always a good sign). There are five wall mounts-- three on the top and two on the bottom. I wanted to mount it on the wall, so all I had to do was to remove the top two layers of plastic drawers, as well as the bottom corner drawers, place it when I wanted and mark it; I then used some of the new plastic screw in wall anchors (the 50 pound variety) and it easily mounted to the wall. Some have remarked that they wanted dividers for the drawers, and that they made those. Good idea. My application was that I needed something that I can see the contents at about eye level, so I wanted the fuller-sized drawers. I also like that these are the new plastic that doesn\'t get brittle and split like my older plastic drawers did. I like the all-plastic construction. It\'s heavy duty enough to hold metal parts, but being made of plastic it\'s not as heavy as a metal frame, so you can easily mount it to the wall and still load it up with heavy stuff, or light stuff. No problem there. For the money, you can\'t beat it. Best one of these I\'ve bought to date-- and I\'ve been using some version of these for over forty years.'
# Je l'ai acheté pour manipuler diverses pièces d'avion et des "trucs" de hangar que je devais organiser ; il a vraiment fait l'affaire. L'unité est arrivée rapidement, était bien emballée et est arrivée intacte (toujours un bon signe). Il y a cinq supports muraux - trois sur le dessus et deux sur le dessous. Je voulais le monter sur le mur, alors tout ce que j'ai eu à faire était d'enlever les deux couches supérieures de tiroirs en plastique, ainsi que les tiroirs d'angle inférieurs, de le placer où je voulais et de le marquer ; j'ai ensuite utilisé quelques-uns des nouveaux ancrages muraux à vis en plastique (la variété de 50 livres) et il s'est facilement monté sur le mur. Certains ont fait remarquer qu'ils voulaient des séparateurs pour les tiroirs, et qu'ils les ont fabriqués. Bonne idée. Pour ma part, j'avais besoin de quelque chose dont je pouvais voir le contenu à hauteur des yeux, et je voulais donc des tiroirs plus grands. J'aime aussi le fait qu'il s'agisse du nouveau plastique qui ne se fragilise pas et ne se fend pas comme mes anciens tiroirs en plastique. J'aime la construction entièrement en plastique. Elle est suffisamment résistante pour contenir des pièces métalliques, mais étant en plastique, elle n'est pas aussi lourde qu'un cadre métallique, ce qui permet de la fixer facilement au mur et de la charger d'objets lourds ou légers. Aucun problème. Pour le prix, c'est imbattable. C'est le meilleur que j'ai acheté à ce jour, et j'utilise des versions de ce type depuis plus de quarante ans.
```

<Tip>

✏️ **Essayez !** Changez la graine aléatoire dans la commande `Dataset.shuffle()` pour explorer d'autres critiques dans le corpus. Si vous parlez espagnol, jetez un coup d'œil  à certaines des critiques dans `spanish_dataset` pour voir si les titres semblent aussi être des résumés raisonnables.

</Tip>

Cet échantillon montre la diversité des critiques que l'on trouve généralement en ligne, allant du positif au négatif (et tout ce qui se trouve entre les deux !). Bien que l'exemple avec le titre « meh » ne soit pas très informatif, les autres titres semblent être des résumés décents des critiques. Entraîner un modèle de résumé sur l'ensemble des 400 000 avis prendrait beaucoup trop de temps sur un seul GPU, nous allons donc nous concentrer sur la génération de résumés pour un seul domaine de produits. Pour avoir une idée des domaines parmi lesquels nous pouvons choisir, convertissons `english_dataset` en `pandas.DataFrame` et calculons le nombre d'avis par catégorie de produits :

```python
english_dataset.set_format("pandas")
english_df = english_dataset["train"][:]
# Afficher le compte des 20 premiers produits
english_df["product_category"].value_counts()[:20]
```

```python out
home                      17679   # maison
apparel                   15951   # vêtements
wireless                  15717   # sans fil
other                     13418   # autres
beauty                    12091   # beauté
drugstore                 11730   # pharmacie
kitchen                   10382   # cuisine
toy                        8745   # jouets
sports                     8277   # sports
automotive                 7506   # automobile
lawn_and_garden            7327   # pelouse_et_jardin
home_improvement           7136   # amélioration_de_la_maison
pet_products               7082   # produits_pour_animaux_de_compagnie
digital_ebook_purchase     6749   # achat_de_livres_numériques 
pc                         6401   # ordinateur_personnel
electronics                6186   # électronique
office_product             5521   # produits_de_bureau 
shoes                      5197   # chaussures 
grocery                    4730   # épicerie
book                       3756   # livre
Name: product_category, dtype: int64
```

Les produits les plus populaires du jeu de données anglais concernent les articles ménagers, les vêtements et l'électronique sans fil. Pour rester dans le thème d'Amazon, nous allons nous concentrer sur le résumé des critiques de livres. Après tout, c'est la raison d'être de l'entreprise ! Nous pouvons voir deux catégories de produits qui correspondent à nos besoins (`book` et `digital_ebook_purchase`). Nous allons donc filtrer les jeux de données dans les deux langues pour ces produits uniquement. Comme nous l'avons vu dans le [chapitre 5](/course/fr/chapter5), la fonction `Dataset.filter()` nous permet de découper un jeu de données de manière très efficace. Nous pouvons donc définir une fonction simple pour le faire :

```python
def filter_books(example):
    return (
        example["product_category"] == "book"
        or example["product_category"] == "digital_ebook_purchase"
    )
```

Maintenant, lorsque nous appliquons cette fonction à `english_dataset` et `spanish_dataset`, le résultat ne contient que les lignes impliquant les catégories de livres. Avant d'appliquer le filtre, changeons le format de `english_dataset` de `"pandas"` à `"arrow"` :

```python
english_dataset.reset_format()
```

Nous pouvons ensuite appliquer la fonction de filtrage et, à titre de vérification, inspecter un échantillon de critiques pour voir si elles portent bien sur des livres :

```python
spanish_books = spanish_dataset.filter(filter_books)
english_books = english_dataset.filter(filter_books)
show_samples(english_books)
```

```python out
'>> Title: I\'m dissapointed.' 
# Je suis déçu
'>> Review: I guess I had higher expectations for this book from the reviews. I really thought I\'d at least like it. The plot idea was great. I loved Ash but, it just didnt go anywhere. Most of the book was about their radio show and talking to callers. I wanted the author to dig deeper so we could really get to know the characters. All we know about Grace is that she is attractive looking, Latino and is kind of a brat. I\'m dissapointed.'
# Je suppose que j'avais de plus grandes attentes pour ce livre d'après les critiques. Je pensais vraiment que j'allais au moins l'aimer. L'idée de l'intrigue était géniale. J'aimais Ash, mais ça n'allait nulle part. La plus grande partie du livre était consacrée à leur émission de radio et aux conversations avec les auditeurs. Je voulais que l'auteur creuse plus profondément pour que nous puissions vraiment connaître les personnages. Tout ce que nous savons de Grace, c'est qu'elle est séduisante, qu'elle est latino et qu'elle est une sorte de garce. Je suis déçue.

'>> Title: Good art, good price, poor design' 
# Un bon art, un bon prix, un mauvais design
'>> Review: I had gotten the DC Vintage calendar the past two years, but it was on backorder forever this year and I saw they had shrunk the dimensions for no good reason. This one has good art choices but the design has the fold going through the picture, so it\'s less aesthetically pleasing, especially if you want to keep a picture to hang. For the price, a good calendar'
# J'ai eu le calendrier DC Vintage ces deux dernières années, mais il était en rupture de stock pour toujours cette année et j'ai vu qu'ils avaient réduit les dimensions sans raison valable. Celui-ci a de bons choix artistiques mais le design a le pli qui traverse l'image, donc c'est moins esthétique, surtout si vous voulez garder une image à accrocher. Pour le prix, c'est un bon calendrier.

'>> Title: Helpful'
# Utile
'>> Review: Nearly all the tips useful and. I consider myself an intermediate to advanced user of OneNote. I would highly recommend.'
# Presque tous les conseils sont utiles et. Je me considère comme un utilisateur intermédiaire à avancé de OneNote. Je le recommande vivement.
```

D'accord, nous pouvons voir que les critiques ne concernent pas strictement les livres et peuvent se référer à des choses comme des calendriers et des applications électroniques telles que OneNote. Néanmoins, le domaine semble approprié pour entraîner un modèle de résumé. Avant de regarder les différents modèles qui conviennent à cette tâche, nous avons une dernière préparation de données à faire : combiner les critiques anglaises et espagnoles en un seul objet `DatasetDict`. 🤗 *Datasets* fournit une fonction pratique `concatenate_datasets()` qui (comme son nom l'indique) va empiler deux objets `Dataset` l'un sur l'autre. Ainsi, pour créer notre jeu de données bilingue, nous allons boucler sur chaque division, concaténer les jeux de données pour cette division, et mélanger le résultat pour s'assurer que notre modèle ne s'adapte pas trop à une seule langue :

```python
from datasets import concatenate_datasets, DatasetDict

books_dataset = DatasetDict()

for split in english_books.keys():
    books_dataset[split] = concatenate_datasets(
        [english_books[split], spanish_books[split]]
    )
    books_dataset[split] = books_dataset[split].shuffle(seed=42)

# Quelques exemples
show_samples(books_dataset)
```

```python out
'>> Title: Easy to follow!!!!' 
# Facile à suivre!!!!
'>> Review: I loved The dash diet weight loss Solution. Never hungry. I would recommend this diet. Also the menus are well rounded. Try it. Has lots of the information need thanks.'
# J'ai adoré The dash diet weight loss Solution. Jamais faim. Je recommande ce régime. Les menus sont également bien arrondis. Essayez-le. Il contient beaucoup d'informations, merci.

'>> Title: PARCIALMENTE DAÑADO' 
# PARTIELLEMENT ENDOMMAGÉ
'>> Review: Me llegó el día que tocaba, junto a otros libros que pedí, pero la caja llegó en mal estado lo cual dañó las esquinas de los libros porque venían sin protección (forro).'
# Il est arrivé le jour prévu, avec d'autres livres que j'avais commandés, mais la boîte est arrivée en mauvais état, ce qui a endommagé les coins des livres car ils étaient livrés sans protection (doublure).

'>> Title: no lo he podido descargar' 
# Je n'ai pas pu le télécharger
'>> Review: igual que el anterior' 
# même chose que ci-dessus
```

Cela ressemble certainement à un mélange de critiques anglaises et espagnoles ! Maintenant que nous avons un corpus d'entraînement, une dernière chose à vérifier est la distribution des mots dans les critiques et leurs titres. Ceci est particulièrement important pour les tâches de résumé, où les résumés de référence courts dans les données peuvent biaiser le modèle pour qu'il ne produise qu'un ou deux mots dans les résumés générés. Les graphiques ci-dessous montrent les distributions de mots, et nous pouvons voir que les titres sont fortement biaisés vers seulement 1 ou 2 mots :

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/review-lengths.svg" alt="Word count distributions for the review titles and texts."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/review-lengths-dark.svg" alt="Word count distributions for the review titles and texts."/>
</div>

Pour y remédier, nous allons filtrer les exemples avec des titres très courts afin que notre modèle puisse produire des résumés plus intéressants. Puisque nous avons affaire à des textes anglais et espagnols, nous pouvons utiliser une heuristique grossière pour séparer les titres sur les espaces blancs, puis utiliser notre fidèle méthode `Dataset.filter()` comme suit :

```python
books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2)
```

Maintenant que nous avons préparé notre corpus, voyons quelques *transformers* possibles que l'on pourrait *finetuné* dessus !

## Modèles pour le résumé de texte

Si vous y pensez, le résumé de texte est une tâche similaire à la traduction automatique. Nous avons un corps de texte, comme une critique, que nous aimerions « traduire » en une version plus courte qui capture les caractéristiques saillantes de l'entrée. En conséquence, la plupart des *transformers* pour le résumé adoptent l'architecture encodeur-décodeur que nous avons rencontrée pour la première fois dans le [chapitre 1](/course/fr/chapter1), bien qu'il y ait quelques exceptions comme la famille de modèles GPT qui peut également être utilisée pour le résumé dans des contextes peu complexes. Le tableau suivant présente quelques modèles pré-entraînés populaires qui peuvent être *finetunés* pour le résumé.

| *Transformers* | Description                                                                                                                                                                                                    | Multilingue ? |
| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: |
|    [GPT-2](https://huggingface.co/gpt2-xl)    | Bien qu'il soit entraîné comme un modèle de langage autorégressif, vous pouvez faire en sorte que le GPT-2 génère des résumés en ajoutant `TL;DR` à la fin du texte d'entrée.                                                                          | ❌ |
|   [PEGASUS](https://huggingface.co/google/pegasus-large)   | Utilise un objectif de pré-entraînement pour prédire les phrases masquées dans les textes à plusieurs phrases. Cet objectif de pré-entraînement est plus proche du résumé que de la modélisation du langage standard et obtient des scores élevés sur des *benchmarks* populaires. | ❌ |
|     [T5](https://huggingface.co/t5-base)      | Une architecture universelle de *transformer* qui formule toutes les tâches dans un cadre texte à texte. Par exemple, le format d'entrée du modèle pour résumer un document est `summarize: ARTICLE`.                              | ❌ |
|     [mT5](https://huggingface.co/google/mt5-base)     | Une version multilingue de T5, pré-entraînée sur le corpus multilingue Common Crawl (mC4), couvrant 101 langues.                                                                                                | ✅ |
|    [BART](https://huggingface.co/facebook/bart-base)     | Une architecture de *transformer* avec une pile d'encodeurs et de décodeurs entraînés pour reconstruire l'entrée corrompue qui combine les schémas de pré-entraînement de BERT et GPT-2.                                    | ❌ |
|  [mBART-50](https://huggingface.co/facebook/mbart-large-50)   | Une version multilingue de BART, pré-entraînée sur 50 langues.                                                                                                                                                     |      ✅       |

Comme vous pouvez le voir dans ce tableau, la majorité des *transformers* pour le résumé (et en fait la plupart des tâches de NLP) sont monolingues. C'est une bonne chose si votre tâche se déroule dans une langue « à haute ressource » comme l'anglais ou l'allemand, mais moins pour les milliers d'autres langues utilisées dans le monde. Heureusement, il existe une catégorie de *transformers* multilingues, comme mT5 et mBART, qui viennent à la rescousse. Ces modèles sont pré-entraînés en utilisant la modélisation du langage mais avec une particularité : au lieu d'être entraîné sur un corpus d'une seule langue, ils sont entraînés conjointement sur des textes dans plus de 50 langues !

Nous allons nous concentrer sur mT5, une architecture intéressante basée sur T5 qui a été pré-entraînée dans un cadre texte à texte. Dans T5, chaque tâche de NLP est formulée en termes d'un préfixe de *prompt* comme `summarize:` qui conditionne le modèle à adapter le texte généré au *prompt*. Comme le montre la figure ci-dessous, cela rend le T5 extrêmement polyvalent car vous pouvez résoudre de nombreuses tâches avec un seul modèle !

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/t5.svg" alt="Different tasks performed by the T5 architecture."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/t5-dark.svg" alt="Different tasks performed by the T5 architecture."/>
</div>

mT5 n'utilise pas de préfixes mais partage une grande partie de la polyvalence de T5 et a l'avantage d'être multilingue. Maintenant que nous avons choisi un modèle, voyons comment préparer nos données pour l'entraînement.


<Tip>

✏️ **Essayez !** Une fois que vous aurez terminé cette section, comparez le mT5 à mBART en *finetunant* ce dernier avec les mêmes techniques. Pour des points bonus, vous pouvez aussi essayer de *finetuner* le T5 uniquement sur les critiques anglaises. Puisque le T5 a un préfixe spécial, vous devrez ajouter `summarize:` aux entrées dans les étapes de prétraitement ci-dessous.

</Tip>

## Prétraitement des données

<Youtube id="1m7BerpSq8A"/>

Notre prochaine tâche est de tokeniser et d'encoder nos critiques et leurs titres. Comme d'habitude, nous commençons par charger le *tokenizer* associé au *checkpoint* du modèle pré-entraîné. Nous utiliserons `mt5-small` comme *checkpoint* afin de pouvoir *finetuner* le modèle en un temps raisonnable :

```python
from transformers import AutoTokenizer

model_checkpoint = "google/mt5-small"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
```

<Tip>

💡 Aux premiers stades de vos projets de NLP, une bonne pratique consiste à entraîner une classe de « petits » modèles sur un petit échantillon de données. Cela vous permet de déboguer et d'itérer plus rapidement vers un flux de travail de bout en bout. Une fois que vous avez confiance dans les résultats, vous pouvez toujours faire évoluer le modèle en changeant simplement le *checkpoint* du modèle !

</Tip>

Testons le *tokenizer* de mT5 sur un petit exemple :

```python
inputs = tokenizer(
    "I loved reading the Hunger Games!"
)  # J'ai adoré lire les Hunger Games !
inputs
```

```python out
{'input_ids': [336, 259, 28387, 11807, 287, 62893, 295, 12507, 1], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]}
```

Ici nous pouvons voir les familiers `input_ids` et `attention_mask` que nous avons rencontrés dans nos premières expériences de *finetuning* au [chapitre 3](/course/fr/chapter3). Décodons ces identifiants d'entrée avec la fonction `convert_ids_to_tokens()` du *tokenizer* pour voir à quel type de *tokenizer* nous avons affaire :

```python
tokenizer.convert_ids_to_tokens(inputs.input_ids)
```

```python out
['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', '</s>']
```

Le caractère Unicode spécial `▁` et le *token* de fin de séquence `</s>` indiquent que nous avons affaire au *tokenizer* de SentencePiece, qui est basé sur l'algorithme de segmentation Unigram discuté dans le [chapitre 6](/course/chapter6). Unigram est particulièrement utile pour les corpus multilingues car il permet à SentencePiece d'être agnostique vis-à-vis des accents, de la ponctuation et du fait que de nombreuses langues, comme le japonais, n'ont pas de caractères d'espacement.

Pour tokeniser notre corpus, nous devons faire face à une subtilité associée au résumé : comme nos étiquettes sont également du texte, il est possible qu'elles dépassent la taille maximale du contexte du modèle. Cela signifie que nous devons appliquer une troncature à la fois aux critiques et à leurs titres pour nous assurer de ne pas transmettre des entrées trop longues à notre modèle. Les tokenizers de 🤗 *Transformers* fournissent une fonction très pratique `as_target_tokenizer()` qui vous permet de tokeniser les étiquettes en parallèle avec les entrées. Ceci est typiquement fait en utilisant un gestionnaire de contexte à l'intérieur d'une fonction de prétraitement qui encode d'abord les entrées, et ensuite encode les étiquettes comme une colonne séparée. Voici un exemple d'une telle fonction pour mT5 :

```python
max_input_length = 512
max_target_length = 30


def preprocess_function(examples):
    model_inputs = tokenizer(
        examples["review_body"],
        max_length=max_input_length,
        truncation=True,
    )
    labels = tokenizer(
        examples["review_title"], max_length=max_target_length, truncation=True
    )
    model_inputs["labels"] = labels["input_ids"]
    return model_inputs
```

Parcourons ce code pour comprendre ce qui se passe. La première chose que nous avons faite est de définir des valeurs pour `max_input_length` et `max_target_length`, qui fixent les limites supérieures de la longueur des commentaires et des titres. Comme le corps de la critique est généralement beaucoup plus long que le titre, nous avons mis ces valeurs à l'échelle en conséquence. Ensuite, dans la `preprocess_function()` elle-même, nous pouvons voir que les commentaires sont d'abord tokenizés, suivis par les titres avec `as_target_tokenizer()`.

Avec la fonction `preprocess_function()`, il est alors simple de tokeniser l'ensemble du corpus en utilisant la fonction pratique `Dataset.map()` que nous avons largement utilisée dans ce cours :

```python
tokenized_datasets = books_dataset.map(preprocess_function, batched=True)
```

Maintenant que le corpus a été prétraité, examinons certaines métriques couramment utilisées pour le résumé. Comme nous allons le voir, il n'existe pas de solution miracle pour mesurer la qualité d'un texte généré par une machine.

<Tip>

💡 Vous avez peut-être remarqué que nous avons utilisé `batched=True` dans notre fonction `Dataset.map()` ci-dessus. Cela permet de coder les exemples par lots de 1 000 (par défaut) et d'utiliser les capacités de *multithreading* des *tokenizers* rapides de 🤗 *Transformers*. Lorsque cela est possible, essayez d'utiliser `batched=True` pour tirer le meilleur parti de votre prétraitement !

</Tip>


## Métriques pour le résumé de texte

<Youtube id="TMshhnrEXlg"/>

Par rapport à la plupart des autres tâches que nous avons abordées dans ce cours, la mesure des performances des tâches de génération de texte comme le résumé ou la traduction n'est pas aussi simple. Par exemple, pour une critique telle que « J'ai adoré lire les Hunger Games », il existe plusieurs résumés valides, comme « J'ai adoré Hunger Games » ou « Hunger Games est une excellente lecture ». Il est clair que l'application d'une sorte de correspondance exacte entre le résumé généré et l'étiquette n'est pas une bonne solution. En effet, même les humains auraient de mauvais résultats avec une telle mesure, car nous avons tous notre propre style d'écriture.

Pour le résumé, l'une des métriques les plus couramment utilisées est le [score ROUGE](https://en.wikipedia.org/wiki/ROUGE_(metric)) (abréviation de *Recall-Oriented Understudy for Gisting Evaluation*). L'idée de base de cette métrique est de comparer un résumé généré avec un ensemble de résumés de référence qui sont généralement créés par des humains. Pour être plus précis, supposons que nous voulions comparer les deux résumés suivants :

```python
generated_summary = "I absolutely loved reading the Hunger Games"
# "J'ai absolument adoré lire les Hunger Games"
reference_summary = "I loved reading the Hunger Games"
# "J'ai adoré lire les Hunger Games"
```

Une façon de les comparer pourrait être de compter le nombre de mots qui se chevauchent, qui dans ce cas serait de 6. Cependant, cette méthode est un peu grossière, c'est pourquoi ROUGE se base sur le calcul des scores de _précision_ et de _rappel_ pour le chevauchement.

<Tip>

🙋 Ne vous inquiétez pas si c'est la première fois que vous entendez parler de précision et de rappel. Nous allons parcourir ensemble quelques exemples explicites pour que tout soit clair. Ces métriques sont généralement rencontrées dans les tâches de classification, donc si vous voulez comprendre comment la précision et le rappel sont définis dans ce contexte, nous vous recommandons de consulter les [guides de `scikit-learn`](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html).

</Tip>

Pour ROUGE, le rappel mesure la proportion du résumé de référence qui est capturée par le résumé généré. Si nous ne faisons que comparer des mots, le rappel peut être calculé selon la formule suivante :

$$ \mathrm{Recall} = \frac{\mathrm{Nombre\,de\,mots\,qui\,se\,chevauchent}}{\mathrm{Nombre\, total\, de\, mots\, dans\, le\, résumé\, de\, réference}} $$

Pour notre exemple simple ci-dessus, cette formule donne un rappel parfait de 6/6 = 1, c'est-à-dire que tous les mots du résumé de référence ont été produits par le modèle. Cela peut sembler génial, mais imaginez que le résumé généré ait été « J'ai vraiment aimé lire les Hunger Games toute la nuit ». Le rappel serait également parfait, mais le résumé serait sans doute moins bon puisqu'il serait verbeux. Pour traiter ces scénarios, nous calculons également la précision, qui dans le contexte de ROUGE, mesure la proportion du résumé généré qui est pertinente :

$$ \mathrm{Precision} = \frac{\mathrm{Nombre\,de\,mots\,qui\,se\,chevauchent}}{\mathrm{Nombre\, total\, de\, mots\, dans\, le\, résumé\, généré}} $$

En appliquant cela à notre résumé verbeux, on obtient une précision de 6/10 = 0,6, ce qui est considérablement moins bon que la précision de 6/7 = 0,86 obtenue par notre résumé plus court. En pratique, la précision et le rappel sont généralement calculés, puis le score F1 (la moyenne harmonique de la précision et du rappel) est indiqué. Nous pouvons le faire facilement dans 🤗 *Datasets* en installant d'abord le *package* `rouge_score` :

```py
!pip install rouge_score
```

et ensuite charger la métrique ROUGE comme suit :

```python
import evaluate

rouge_score = evaluate.load("rouge")
```

Ensuite, nous pouvons utiliser la fonction `rouge_score.compute()` pour calculer toutes les métriques en une seule fois :

```python
scores = rouge_score.compute(
    predictions=[generated_summary], references=[reference_summary]
)
scores
```

```python out
{'rouge1': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92)),
 'rouge2': AggregateScore(low=Score(precision=0.67, recall=0.8, fmeasure=0.73), mid=Score(precision=0.67, recall=0.8, fmeasure=0.73), high=Score(precision=0.67, recall=0.8, fmeasure=0.73)),
 'rougeL': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92)),
 'rougeLsum': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92))}
```

Whoa, il y a pas mal d'informations dans cette sortie. Qu'est-ce que ça veut dire ? Tout d'abord, 🤗 *Datasets* calcule des intervalles de confiance pour la précision, le rappel et le score F1. Ce sont les attributs `low`, `mid`, et `high` que vous pouvez voir ici. De plus, 🤗 *Datasets* calcule une variété de scores ROUGE qui sont basés sur différents types de granularité du texte lors de la comparaison des résumés générés et de référence. La variante `rouge1` est le chevauchement des unigrammes. C'est juste une façon fantaisiste de dire le chevauchement des mots et c'est exactement la métrique dont nous avons discuté ci-dessus. Pour vérifier cela, nous allons extraire la valeur `mid` de nos scores :

```python
scores["rouge1"].mid
```

```python out
Score(precision=0.86, recall=1.0, fmeasure=0.92)
```

Super, les chiffres de précision et de rappel correspondent ! Maintenant, qu'en est-il des autres scores ROUGE ? `rouge2` mesure le chevauchement entre les bigrammes (chevauchement des paires de mots), tandis que `rougeL` et `rougeLsum` mesurent les plus longues séquences de mots correspondants en recherchant les plus longues sous-souches communes dans les résumés générés et de référence. Le « sum » dans `rougeLsum` fait référence au fait que cette métrique est calculée sur un résumé entier, alors que `rougeL` est calculée comme une moyenne sur des phrases individuelles.

<Tip>

✏️ **Essayez !** Créez votre propre exemple de résumé généré et de référence et voyez si les scores ROUGE obtenus correspondent à un calcul manuel basé sur les formules de précision et de rappel. Pour des points bonus, divisez le texte en bigrammes et comparez la précision et le rappel pour la métrique `rouge2`.

</Tip>

Nous utiliserons ces scores ROUGE pour suivre les performances de notre modèle, mais avant cela, faisons ce que tout bon praticien de NLP devrait faire : créer une *baseline* solide, mais simple !

### Création d'une base de référence solide

Une *baseline* commune pour le résumé de texte consiste à prendre simplement les trois premières phrases d'un article, souvent appelée la *baseline* _lead-3_. Nous pourrions utiliser les points pour tracker les limites des phrases mais cela échouera avec des acronymes comme « U.S. » ou « U.N. ». Nous allons donc utiliser la bibliothèque `nltk`, qui inclut un meilleur algorithme pour gérer ces cas. Vous pouvez installer le *package* en utilisant `pip` comme suit :

```python
!pip install nltk
```

puis téléchargez les règles de ponctuation :

```python
import nltk

nltk.download("punkt")
```

Ensuite, nous importons le *tokenizer* de `nltk` et créons une fonction simple pour extraire les trois premières phrases d'une critique. La convention dans le résumé de texte est de séparer chaque résumé avec une nouvelle ligne, donc nous allons également inclure ceci et tester le tout sur un exemple d'entraînement :

```python
from nltk.tokenize import sent_tokenize


def three_sentence_summary(text):
    return "\n".join(sent_tokenize(text)[:3])


print(three_sentence_summary(books_dataset["train"][1]["review_body"]))
```

```python out
'I grew up reading Koontz, and years ago, I stopped,convinced i had "outgrown" him.' 
# J'ai grandi en lisant Koontz, et il y a des années, j'ai arrêté, convaincu que je l'avais "dépassé"
'Still,when a friend was looking for something suspenseful too read, I suggested Koontz.' 
# "Pourtant, quand une amie cherchait un livre à suspense, je lui ai suggéré Koontz."
'She found Strangers.' 
# Elle a trouvé Strangers.
```

Cela semble fonctionner, alors implémentons maintenant une fonction qui extrait ces résumés d'un jeu de données et calcule les scores ROUGE pour la ligne de base :

```python
def evaluate_baseline(dataset, metric):
    summaries = [three_sentence_summary(text) for text in dataset["review_body"]]
    return metric.compute(predictions=summaries, references=dataset["review_title"])
```

Nous pouvons ensuite utiliser cette fonction pour calculer les scores ROUGE sur l'ensemble de validation et les embellir un peu en utilisant Pandas :

```python
import pandas as pd

score = evaluate_baseline(books_dataset["validation"], rouge_score)
rouge_names = ["rouge1", "rouge2", "rougeL", "rougeLsum"]
rouge_dict = dict((rn, round(score[rn].mid.fmeasure * 100, 2)) for rn in rouge_names)
rouge_dict
```

```python out
{'rouge1': 16.74, 'rouge2': 8.83, 'rougeL': 15.6, 'rougeLsum': 15.96}
```

Nous pouvons voir que le score de `rouge2` est significativement plus bas que le reste. Ceci reflète probablement le fait que les titres des critiques sont typiquement concis et donc que la *baseline* *lead-3* est trop verbeuse. Maintenant que nous disposons d'une bonne *baseline*, concentrons-nous sur le *finetuning* du mT5 !

{#if fw === 'pt'}

## <i>Finetuning</i> de mT5 avec l'API `Trainer`

Le *finetuning* d'un modèle pour le résumé est très similaire aux autres tâches que nous avons couvertes dans ce chapitre. La première chose à faire est de charger le modèle pré-entraîné à partir du *checkpoint* `mt5-small`. Puisque la compression est une tâche de séquence à séquence, nous pouvons charger le modèle avec la classe `AutoModelForSeq2SeqLM`, qui téléchargera automatiquement et mettra en cache les poids :

```python
from transformers import AutoModelForSeq2SeqLM

model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)
```

{:else}

## <i>Finetuning</i> de mT5 avec Keras

Le *finetuning* d'un modèle pour le résumé est très similaire aux autres tâches que nous avons couvertes dans ce chapitre. La première chose à faire est de charger le modèle pré-entraîné à partir du *checkpoint* `mt5-small`. Puisque la compression est une tâche de séquence à séquence, nous pouvons charger le modèle avec la classe `TFAutoModelForSeq2SeqLM`, qui téléchargera automatiquement et mettra en cache les poids :

```python
from transformers import TFAutoModelForSeq2SeqLM

model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)
```

{/if}

<Tip>

💡 Si vous vous demandez pourquoi vous ne voyez aucun avertissement concernant le *finetuning* du modèle sur une tâche en aval, c'est parce que pour les tâches de séquence à séquence, nous conservons tous les poids du réseau. Comparez cela à notre modèle de classification de texte du [chapitre 3](/course/fr/chapter3) où la tête du modèle pré-entraîné a été remplacée par un réseau initialisé de manière aléatoire.

</Tip>

La prochaine chose que nous devons faire est de nous connecter au *Hub*. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante :

```python
from huggingface_hub import notebook_login

notebook_login()
```

qui affichera un *widget* où vous pourrez saisir vos informations d'identification. Vous pouvez également exécuter cette commande dans votre terminal et vous connecter à partir de là :

```
huggingface-cli login
```

{#if fw === 'pt'}

Nous aurons besoin de générer des résumés afin de calculer les scores ROUGE pendant l'entraînement. Heureusement, 🤗 *Transformers* fournit des classes dédiées `Seq2SeqTrainingArguments` et `Seq2SeqTrainer` qui peuvent faire cela pour nous automatiquement ! Pour voir comment cela fonctionne, définissons d'abord les hyperparamètres et autres arguments pour nos expériences :

```python
from transformers import Seq2SeqTrainingArguments

batch_size = 8
num_train_epochs = 8
# La perte d'entraînement à chaque époque
logging_steps = len(tokenized_datasets["train"]) // batch_size
model_name = model_checkpoint.split("/")[-1]

args = Seq2SeqTrainingArguments(
    output_dir=f"{model_name}-finetuned-amazon-en-es",
    evaluation_strategy="epoch",
    learning_rate=5.6e-5,
    per_device_train_batch_size=batch_size,
    per_device_eval_batch_size=batch_size,
    weight_decay=0.01,
    save_total_limit=3,
    num_train_epochs=num_train_epochs,
    predict_with_generate=True,
    logging_steps=logging_steps,
    push_to_hub=True,
)
```

Ici, l'argument `predict_with_generate` a été défini pour indiquer que nous devons générer des résumés pendant l'évaluation afin de pouvoir calculer les scores ROUGE pour chaque époque. Comme discuté au [chapitre 1](/course/fr/chapter1), le décodeur effectue l'inférence en prédisant les *tokens* un par un, et ceci est implémenté par la méthode `generate()`. Définir `predict_with_generate=True` indique au `Seq2SeqTrainer` d'utiliser cette méthode pour l'évaluation. Nous avons également ajusté certains des hyperparamètres par défaut, comme le taux d'apprentissage, le nombre d'époques, et le taux de décroissance des poids, et nous avons réglé l'option `save_total_limit` pour ne sauvegarder que jusqu'à trois *checkpoints* pendant l'entraînement. C'est parce que même la plus petite version de mT5 utilise environ 1 Go d'espace disque, et nous pouvons gagner un peu de place en limitant le nombre de copies que nous sauvegardons.

L'argument `push_to_hub=True` nous permettra de pousser le modèle vers le *Hub* après l'entraînement. Vous trouverez le dépôt sous votre profil utilisateur dans l'emplacement défini par `output_dir`. Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` à `Seq2SeqTrainingArguments`.

La prochaine chose que nous devons faire est de fournir à `Seq2SeqTrainer` une fonction `compute_metrics()` afin que nous puissions évaluer notre modèle pendant l'entraînement. Pour le résumé, c'est un peu plus compliqué que de simplement appeler `rouge_score.compute()` sur les prédictions du modèle, puisque nous devons _décoder_ les sorties et les étiquettes en texte avant de pouvoir calculer les scores ROUGE. La fonction suivante fait exactement cela, et utilise également la fonction `sent_tokenize()` de `nltk` pour séparer les phrases du résumé avec des nouvelles lignes :


```python
import numpy as np


def compute_metrics(eval_pred):
    predictions, labels = eval_pred
    # Décoder les résumés générés en texte
    decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True)
    # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder
    labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
    # Décoder les résumés de référence en texte
    decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)
    # ROUGE attend une nouvelle ligne après chaque phrase
    decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds]
    decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels]
    # Calcul des scores ROUGE
    result = rouge_score.compute(
        predictions=decoded_preds, references=decoded_labels, use_stemmer=True
    )
    # Extraire les scores médians
    result = {key: value.mid.fmeasure * 100 for key, value in result.items()}
    return {k: round(v, 4) for k, v in result.items()}
```

{/if}

Ensuite, nous devons définir un assembleur de données pour notre tâche de séquence à séquence. Comme mT5 est un *transformer* encodeur-décodeur, une des subtilités de la préparation de nos batchs est que, pendant le décodage, nous devons décaler les étiquettes d'une unité vers la droite. Ceci est nécessaire pour garantir que le décodeur ne voit que les étiquettes de vérité terrain précédentes et non les étiquettes actuelles ou futures, qui seraient faciles à mémoriser pour le modèle. Cela ressemble à la façon dont l'auto-attention masquée est appliquée aux entrées dans une tâche comme [la modélisation causale du langage](/course/fr/chapter7/6).

Heureusement, 🤗 *Transformers* fournit un assembleur `DataCollatorForSeq2Seq` qui rembourrera dynamiquement les entrées et les étiquettes pour nous. Pour instancier ce assembleur, nous devons simplement fournir le *tokenizer* et le *modèle* :

{#if fw === 'pt'}

```python
from transformers import DataCollatorForSeq2Seq

data_collator = DataCollatorForSeq2Seq(tokenizer, model=model)
```

{:else}

```python
from transformers import DataCollatorForSeq2Seq

data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="tf")
```

{/if}

Voyons ce que produit ce assembleur lorsqu'on lui donne un petit batch d'exemples. Tout d'abord, nous devons supprimer les colonnes contenant des chaînes de caractères, car le assembleur ne saura pas comment remplir ces éléments :

```python
tokenized_datasets = tokenized_datasets.remove_columns(
    books_dataset["train"].column_names
)
```

Comme le assembleur attend une liste de `dict`, où chaque `dict` représente un seul exemple du jeu de données, nous devons également mettre les données dans le format attendu avant de les transmettre au assembleur de données :

```python
features = [tokenized_datasets["train"][i] for i in range(2)]
data_collator(features)
```

```python out
{'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
         1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]), 'input_ids': tensor([[  1494,    259,   8622,    390,    259,    262,   2316,   3435,    955,
            772,    281,    772,   1617,    263,    305,  14701,    260,   1385,
           3031,    259,  24146,    332,   1037,    259,  43906,    305,    336,
            260,      1,      0,      0,      0,      0,      0,      0],
        [   259,  27531,  13483,    259,   7505,    260, 112240,  15192,    305,
          53198,    276,    259,  74060,    263,    260,    459,  25640,    776,
           2119,    336,    259,   2220,    259,  18896,    288,   4906,    288,
           1037,   3931,    260,   7083, 101476,   1143,    260,      1]]), 'labels': tensor([[ 7483,   259,  2364, 15695,     1,  -100],
        [  259, 27531, 13483,   259,  7505,     1]]), 'decoder_input_ids': tensor([[    0,  7483,   259,  2364, 15695,     1],
        [    0,   259, 27531, 13483,   259,  7505]])}
```

La principale chose à remarquer ici est que le premier exemple est plus long que le second, donc les `input_ids` et `attention_mask` du second exemple ont été complétés sur la droite avec un *token* `[PAD]` (dont l'identifiant est `0`). De même, nous pouvons voir que les `labels` ont été complétés par des `-100`, pour s'assurer que les *tokens* de remplissage sont ignorés par la fonction de perte. Et enfin, nous pouvons voir un nouveau `decoder_input_ids` qui a déplacé les étiquettes vers la droite en insérant un *token* `[PAD]` dans la première entrée.

{#if fw === 'pt'}

Nous avons enfin tous les ingrédients dont nous avons besoin pour l'entraînement ! Nous devons maintenant simplement instancier le `Seq2SeqTrainer` avec les arguments :

```python
from transformers import Seq2SeqTrainer

trainer = Seq2SeqTrainer(
    model,
    args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation"],
    data_collator=data_collator,
    tokenizer=tokenizer,
    compute_metrics=compute_metrics,
)
```

et lancer notre course d'entraînement :

```python
trainer.train()
```

Pendant l'entraînement, vous devriez voir la perte d'entraînement diminuer et les scores ROUGE augmenter à chaque époque. Une fois l'entraînement terminé, vous pouvez voir les scores ROUGE finaux en exécutant `Trainer.evaluate()` :

```python
trainer.evaluate()
```

```python out
{'eval_loss': 3.028524398803711,
 'eval_rouge1': 16.9728,
 'eval_rouge2': 8.2969,
 'eval_rougeL': 16.8366,
 'eval_rougeLsum': 16.851,
 'eval_gen_len': 10.1597,
 'eval_runtime': 6.1054,
 'eval_samples_per_second': 38.982,
 'eval_steps_per_second': 4.914}
```

D'après les scores, nous pouvons voir que notre modèle a largement surpassé notre *baseline* *lead-3*. Bien ! La dernière chose à faire est de pousser les poids du modèle vers le *Hub*, comme suit :

```
trainer.push_to_hub(commit_message="Training complete", tags="summarization")
```

```python out
'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0'
```

Ceci sauvegardera le *checkpoint* et les fichiers de configuration dans `output_dir`, avant de télécharger tous les fichiers sur le *Hub*. En spécifiant l'argument `tags`, nous nous assurons également que le *widget* sur le *Hub* sera celui d'un pipeline de résumé au lieu de celui de la génération de texte par défaut associé à l'architecture mT5 (pour plus d'informations sur les balises de modèle, voir la [documentation du *Hub*](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)). La sortie de `trainer.push_to_hub()` est une URL vers le hash du commit Git, donc vous pouvez facilement voir les changements qui ont été faits au dépôt de modèle !

Pour conclure cette section, voyons comment nous pouvons également *finetuner* mT5 en utilisant les fonctionnalités de bas niveau fournies par 🤗 *Accelerate*.

{:else}

Nous sommes presque prêts à nous entraîner ! Nous devons juste convertir nos jeux de données en `tf.data.Dataset` en utilisant le assembleur de données que nous avons défini ci-dessus, puis utiliser `compile()` et `fit()`. D'abord, les jeux de données :

```python
tf_train_dataset = model.prepare_tf_dataset(
    tokenized_datasets["train"],
    collate_fn=data_collator,
    shuffle=True,
    batch_size=8,
)
tf_eval_dataset = model.prepare_tf_dataset(
    tokenized_datasets["validation"],
    collate_fn=data_collator,
    shuffle=False,
    batch_size=8,
)
```

Maintenant, nous définissons nos hyperparamètres d'entraînement et nous compilons :

```python
from transformers import create_optimizer
import tensorflow as tf

# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch,
# puis multiplié par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un tf.data.Dataset,
# et non le jeu de données original donc son len() est déjà num_samples // batch_size.
num_train_epochs = 8
num_train_steps = len(tf_train_dataset) * num_train_epochs
model_name = model_checkpoint.split("/")[-1]

optimizer, schedule = create_optimizer(
    init_lr=5.6e-5,
    num_warmup_steps=0,
    num_train_steps=num_train_steps,
    weight_decay_rate=0.01,
)

model.compile(optimizer=optimizer)

# Entraîner en mixed-precision float16
tf.keras.mixed_precision.set_global_policy("mixed_float16")
```

Et enfin, nous *finetunons* le modèle. Nous utilisons un `PushToHubCallback` pour sauvegarder le modèle sur le *Hub* après chaque époque, ce qui nous permettra de l'utiliser pour l'inférence plus tard :

```python
from transformers.keras_callbacks import PushToHubCallback

callback = PushToHubCallback(
    output_dir=f"{model_name}-finetuned-amazon-en-es", tokenizer=tokenizer
)

model.fit(
    tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback], epochs=8
)
```

Nous avons obtenu quelques valeurs de perte pendant l'entraînement mais nous aimerions voir les métriques ROUGE que nous avons calculées plus tôt. Pour obtenir ces métriques, nous devons générer les sorties du modèle et les convertir en chaînes de caractères. Construisons une liste d'étiquettes et une liste de prédictions pour la métrique ROUGE pour comparer (notez que si vous obtenez des erreurs d'importation pour cette section, vous pouvez avoir besoin de faire `pip install tqdm`). Nous allons également utiliser une astuce qui augmente considérablement les performances : compiler notre code de génération avec [XLA](https://www.tensorflow.org/xla), le compilateur d'algèbre linéaire accéléré de TensorFlow. XLA applique diverses optimisations au graphe de calcul du modèle, ce qui permet d'améliorer considérablement la vitesse et l'utilisation de la mémoire. Comme décrit dans un article du [blog d’Hugging Face](https://huggingface.co/blog/tf-xla-generate), XLA fonctionne mieux lorsque nos formes d'entrée ne varient pas trop. Pour gérer cela, nous allons rembourrer nos entrées à des multiples de 128, et créer un nouveau jeu de données avec l’assembleur de rembourrage. Puis nous appliquerons le décorateur `@tf.function(jit_compile=True)` à notre fonction de génération, qui marque la fonction entière pour la compilation avec XLA. 

```python
from tqdm import tqdm
import numpy as np

generation_data_collator = DataCollatorForSeq2Seq(
    tokenizer, model=model, return_tensors="tf", pad_to_multiple_of=320
)
tf_generate_dataset = model.prepare_tf_dataset(
    tokenized_datasets["validation"],
    collate_fn=generation_data_collator,
    shuffle=False,
    batch_size=8,
    drop_remainder=True,
)


@tf.function(jit_compile=True)
def generate_with_xla(batch):
    return model.generate(
        input_ids=batch["input_ids"],
        attention_mask=batch["attention_mask"],
        max_new_tokens=32,
    )


all_preds = []
all_labels = []
for batch, labels in tqdm(tf_generate_dataset):
    predictions = generate_with_xla(batch)
    decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True)
    labels = labels.numpy()
    labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
    decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)
    decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds]
    decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels]
    all_preds.extend(decoded_preds)
    all_labels.extend(decoded_labels)
```

Une fois que nous avons nos listes d'étiquettes et de chaînes de prédiction, le calcul du score ROUGE est facile :

```python
result = rouge_score.compute(
    predictions=decoded_preds, references=decoded_labels, use_stemmer=True
)
result = {key: value.mid.fmeasure * 100 for key, value in result.items()}
{k: round(v, 4) for k, v in result.items()}
```

```
{'rouge1': 31.4815, 'rouge2': 25.4386, 'rougeL': 31.4815, 'rougeLsum': 31.4815}
```


{/if}

{#if fw === 'pt'}

## <i>Finetuning</i> de mT5 avec 🤗 <i>Accelerate</i>

Le *finetuning* de notre modèle avec 🤗 *Accelerate* est très similaire à l'exemple de classification de texte que nous avons rencontré dans le [chapitre 3](/course/fr/chapter3). Les principales différences seront la nécessité de générer explicitement nos résumés pendant l'entraînement et de définir comment nous calculons les scores ROUGE (rappelons que le `Seq2SeqTrainer` s'est occupé de la génération pour nous). Voyons comment nous pouvons mettre en œuvre ces deux exigences dans 🤗 *Accelerate* !

### Préparer tout pour l'entraînement

La première chose que nous devons faire est de créer un `DataLoader` pour chacun de nos échantillons. Puisque les chargeurs de données PyTorch attendent des batchs de tenseurs, nous devons définir le format à `"torch"` dans nos jeux de données :

```python
tokenized_datasets.set_format("torch")
```

Maintenant que nous avons des jeux de données constitués uniquement de tenseurs, la prochaine chose à faire est d'instancier à nouveau le `DataCollatorForSeq2Seq`. Pour cela, nous devons fournir une nouvelle version du modèle, donc chargeons-le à nouveau depuis notre cache :

```python
model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)
```

Nous pouvons ensuite instancier le assembleur de données et l'utiliser pour définir nos chargeurs de données :

```python
from torch.utils.data import DataLoader

batch_size = 8
train_dataloader = DataLoader(
    tokenized_datasets["train"],
    shuffle=True,
    collate_fn=data_collator,
    batch_size=batch_size,
)
eval_dataloader = DataLoader(
    tokenized_datasets["validation"], collate_fn=data_collator, batch_size=batch_size
)
```

La prochaine chose à faire est de définir l'optimiseur que nous voulons utiliser. Comme dans nos autres exemples, nous allons utiliser `AdamW`, qui fonctionne bien pour la plupart des problèmes :

```python
from torch.optim import AdamW

optimizer = AdamW(model.parameters(), lr=2e-5)
```

Enfin, nous introduisons notre modèle, notre optimiseur et nos chargeurs de données dans la méthode `accelerator.prepare()` :

```python
from accelerate import Accelerator

accelerator = Accelerator()
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, eval_dataloader
)
```

<Tip>

🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code ci-dessus dans une fonction d'entraînement dédiée. Voir le [chapitre 3](/course/fr/chapter3) pour plus de détails.

</Tip>

Maintenant que nous avons préparé nos objets, il reste trois choses à faire :

* définir le planificateur du taux d'apprentissage,
* implémenter une fonction pour post-traiter les résumés pour l'évaluation,
* créer un dépôt sur le *Hub* vers lequel nous pouvons pousser notre modèle.

Pour le planificateur de taux d'apprentissage, nous utiliserons le planificateur linéaire standard des sections précédentes :

```python
from transformers import get_scheduler

num_train_epochs = 10
num_update_steps_per_epoch = len(train_dataloader)
num_training_steps = num_train_epochs * num_update_steps_per_epoch

lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)
```

Pour le post-traitement, nous avons besoin d'une fonction qui divise les résumés générés en phrases séparées par des nouvelles lignes. C'est le format attendu par la métrique ROUGE et nous pouvons y parvenir avec le bout de code suivant :

```python
def postprocess_text(preds, labels):
    preds = [pred.strip() for pred in preds]
    labels = [label.strip() for label in labels]

    # ROUGE attend une nouvelle ligne après chaque phrase
    preds = ["\n".join(nltk.sent_tokenize(pred)) for pred in preds]
    labels = ["\n".join(nltk.sent_tokenize(label)) for label in labels]

    return preds, labels
```

Cela devrait vous sembler familier si vous vous rappelez comment nous avons défini la fonction `compute_metrics()` du `Seq2SeqTrainer`. 

Enfin, nous devons créer un dépôt de modèles sur le *Hub*. Pour cela, nous pouvons utiliser la bibliothèque 🤗 *Hub*, qui porte le nom approprié. Nous avons juste besoin de définir un nom pour notre dépôt, et la bibliothèque a une fonction utilitaire pour combiner l'identifiant du dépôt avec le profil de l'utilisateur :

```python
from huggingface_hub import get_full_repo_name

model_name = "test-bert-finetuned-squad-accelerate"
repo_name = get_full_repo_name(model_name)
repo_name
```

```python out
'lewtun/mt5-finetuned-amazon-en-es-accelerate'
```

Nous pouvons maintenant utiliser ce nom de dépôt pour cloner une version locale dans notre répertoire de résultats qui stockera les artefacts d'entraînement :

```python
from huggingface_hub import Repository

output_dir = "results-mt5-finetuned-squad-accelerate"
repo = Repository(output_dir, clone_from=repo_name)
```

Cela nous permettra de pousser les artefacts vers le *Hub* en appelant la méthode `repo.push_to_hub()` pendant l'entraînement ! Concluons maintenant notre analyse en écrivant la boucle d'entraînement.

### Boucle d'entraînement

La boucle d'entraînement pour le résumé est assez similaire aux autres exemples 🤗 *Accelerate* que nous avons rencontrés et est grossièrement divisée en quatre étapes principales :

1. entraîner le modèle en itérant sur tous les exemples dans `train_dataloader` pour chaque époque,
2. générer les résumés du modèle à la fin de chaque époque, en générant d'abord les *tokens* puis en les décodant (ainsi que les résumés de référence) en texte,
3. calculer les scores ROUGE en utilisant les mêmes techniques que nous avons vues précédemment,
4. sauvegarder les *checkpoints* et pousser le tout vers le *Hub*. Ici, nous nous appuyons sur l'argument `blocking=False` de l'objet `Repository` afin de pouvoir pousser les *checkpoints* par époque de manière _asynchrone_. Cela nous permet de poursuivre l'entraînement sans avoir à attendre le téléchargement quelque peu lent associé à un modèle de la taille d'1 Go !

Ces étapes peuvent être vues dans le bloc de code suivant :

```python
from tqdm.auto import tqdm
import torch
import numpy as np

progress_bar = tqdm(range(num_training_steps))

for epoch in range(num_train_epochs):
    # Entraînement
    model.train()
    for step, batch in enumerate(train_dataloader):
        outputs = model(**batch)
        loss = outputs.loss
        accelerator.backward(loss)

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)

    # Evaluation
    model.eval()
    for step, batch in enumerate(eval_dataloader):
        with torch.no_grad():
            generated_tokens = accelerator.unwrap_model(model).generate(
                batch["input_ids"],
                attention_mask=batch["attention_mask"],
            )

            generated_tokens = accelerator.pad_across_processes(
                generated_tokens, dim=1, pad_index=tokenizer.pad_token_id
            )
            labels = batch["labels"]

            # Si nous n'avons pas rempli la longueur maximale, nous devons également remplir les étiquettes
            labels = accelerator.pad_across_processes(
                batch["labels"], dim=1, pad_index=tokenizer.pad_token_id
            )

            generated_tokens = accelerator.gather(generated_tokens).cpu().numpy()
            labels = accelerator.gather(labels).cpu().numpy()

            # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder
            labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
            if isinstance(generated_tokens, tuple):
                generated_tokens = generated_tokens[0]
            decoded_preds = tokenizer.batch_decode(
                generated_tokens, skip_special_tokens=True
            )
            decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)

            decoded_preds, decoded_labels = postprocess_text(
                decoded_preds, decoded_labels
            )

            rouge_score.add_batch(predictions=decoded_preds, references=decoded_labels)

    # Calculer les métriques
    result = rouge_score.compute()
    # Extract the median ROUGE scores
    result = {key: value.mid.fmeasure * 100 for key, value in result.items()}
    result = {k: round(v, 4) for k, v in result.items()}
    print(f"Epoch {epoch}:", result)

    # Sauvegarder et télécharger
    accelerator.wait_for_everyone()
    unwrapped_model = accelerator.unwrap_model(model)
    unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
    if accelerator.is_main_process:
        tokenizer.save_pretrained(output_dir)
        repo.push_to_hub(
            commit_message=f"Training in progress epoch {epoch}", blocking=False
        )
```

```python out
Epoch 0: {'rouge1': 5.6351, 'rouge2': 1.1625, 'rougeL': 5.4866, 'rougeLsum': 5.5005}
Epoch 1: {'rouge1': 9.8646, 'rouge2': 3.4106, 'rougeL': 9.9439, 'rougeLsum': 9.9306}
Epoch 2: {'rouge1': 11.0872, 'rouge2': 3.3273, 'rougeL': 11.0508, 'rougeLsum': 10.9468}
Epoch 3: {'rouge1': 11.8587, 'rouge2': 4.8167, 'rougeL': 11.7986, 'rougeLsum': 11.7518}
Epoch 4: {'rouge1': 12.9842, 'rouge2': 5.5887, 'rougeL': 12.7546, 'rougeLsum': 12.7029}
Epoch 5: {'rouge1': 13.4628, 'rouge2': 6.4598, 'rougeL': 13.312, 'rougeLsum': 13.2913}
Epoch 6: {'rouge1': 12.9131, 'rouge2': 5.8914, 'rougeL': 12.6896, 'rougeLsum': 12.5701}
Epoch 7: {'rouge1': 13.3079, 'rouge2': 6.2994, 'rougeL': 13.1536, 'rougeLsum': 13.1194}
Epoch 8: {'rouge1': 13.96, 'rouge2': 6.5998, 'rougeL': 13.9123, 'rougeLsum': 13.7744}
Epoch 9: {'rouge1': 14.1192, 'rouge2': 7.0059, 'rougeL': 14.1172, 'rougeLsum': 13.9509}
```

Et c'est tout ! Une fois que vous l'aurez exécuté, vous aurez un modèle et des résultats assez similaires à ceux que nous avons obtenus avec le `Trainer`.

{/if}

## Utilisation de votre modèle <i>finetuné</i>

Une fois que vous avez poussé le modèle vers le *Hub*, vous pouvez jouer avec lui soit via le *widget* d'inférence, soit avec un objet `pipeline`, comme suit :

```python
from transformers import pipeline

hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es"
summarizer = pipeline("summarization", model=hub_model_id)
```

Nous pouvons alimenter notre pipeline avec quelques exemples de l'ensemble de test (que le modèle n'a pas vu) pour avoir une idée de la qualité des résumés. Tout d'abord, implémentons une fonction simple pour afficher ensemble la critique, le titre et le résumé généré :

```python
def print_summary(idx):
    review = books_dataset["test"][idx]["review_body"]
    title = books_dataset["test"][idx]["review_title"]
    summary = summarizer(books_dataset["test"][idx]["review_body"])[0]["summary_text"]
    print(f"'>>> Review: {review}'")
    print(f"\n'>>> Title: {title}'")
    print(f"\n'>>> Summary: {summary}'")
```

Examinons l'un des exemples anglais que nous recevons :

```python
print_summary(100)
```

```python out
'>>> Review: Nothing special at all about this product... the book is too small and stiff and hard to write in. The huge sticker on the back doesn’t come off and looks super tacky. I would not purchase this again. I could have just bought a journal from the dollar store and it would be basically the same thing. It’s also really expensive for what it is.'
# Ce produit n'a rien de spécial... le livre est trop petit et rigide et il est difficile d'y écrire. L'énorme autocollant au dos ne se détache pas et a l'air super collant. Je n'achèterai plus jamais ce produit. J'aurais pu simplement acheter un journal dans un magasin à un dollar et ce serait à peu près la même chose. Il est également très cher pour ce qu'il est.

'>>> Title: Not impressed at all... buy something else' 
# Pas du tout impressionné... achetez autre chose.

'>>> Summary: Nothing special at all about this product' 
# Rien de spécial à propos de ce produit
```

Ce n'est pas si mal ! Nous pouvons voir que notre modèle a été capable d'effectuer un résumé _abstractif_ en augmentant certaines parties de la critique avec de nouveaux mots. Et peut-être que l'aspect le plus cool de notre modèle est qu'il est bilingue, donc nous pouvons également générer des résumés de critiques en espagnol :

```python
print_summary(0)
```

```python out
'>>> Review: Es una trilogia que se hace muy facil de leer. Me ha gustado, no me esperaba el final para nada' 
# C'est une trilogie qui se lit très facilement. J'ai aimé, je ne m'attendais pas du tout à la fin.

'>>> Title: Buena literatura para adolescentes' 
# Bonne littérature pour les adolescents

'>>> Summary: Muy facil de leer' 
# Très facile à lire
```

Le résumé a été extrait directement de la critique. Néanmoins, cela montre la polyvalence du modèle mT5 et vous a donné un aperçu de ce que c'est que de traiter un corpus multilingue !

Ensuite, nous allons nous intéresser à une tâche un peu plus complexe : entraîner un modèle de langue à partir de zéro.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter7/5.mdx" />

### Introduction
https://huggingface.co/learn/course/fr/chapter7/1.md

# Introduction

<CourseFloatingBanner
    chapter={7}
    classNames="absolute z-10 right-0 top-0"
/>

Dans le [chapitre 3](/course/fr/chapter3), vous avez vu comment *finetuner* un modèle de classification de texte. Dans ce chapitre, nous nous attaquons aux tâches de NLP courantes suivantes :

- la classification de *tokens*,
- la modélisation du langage masqué (comme BERT),
- les résumés,
- la traduction,
- le pré-entraînement à la modélisation causale du langage (comme GPT-2),
- la réponse aux questions.

{#if fw === 'pt'}

Pour ce faire, vous devrez tirer parti de tout ce que vous avez appris sur l'API `Trainer`, sur la bibliothèque 🤗 *Accelerate* au [chapitre 3](/course/fr/chapitre3), sur la bibliothèque 🤗 *Datasets* au [chapitre 5](/course/fr/chapiter5) et sur la bibliothèque 🤗 *Tokenizers* au [chapitre 6](/course/fr/chapiter6). Nous téléchargerons également nos résultats sur le *Hub*, comme nous l'avons fait dans le [chapitre 4](/course/fr/chapiter4), donc c'est vraiment le chapitre où tout est réuni !

Chaque section peut être lue indépendamment et vous montrera comment entraîner un modèle avec l'API `Trainer` ou avec 🤗 *Accelerate* et votre propre boucle d'entraînement. N'hésitez pas à sauter l'une ou l'autre partie et à vous concentrer sur celle qui vous intéresse le plus. L'API `Trainer` est idéale pour *finetuner* ou entraîner votre modèle sans vous soucier de ce qui se passe en coulisses, tandis que la boucle d'entraînement avec `Accelerate` vous permettra de personnaliser plus facilement toutes les parties que vous souhaitez.

{:else}

Pour ce faire, vous devrez tirer parti de tout ce que vous avez appris sur l'entraînement des modèles avec l'API Keras dans le [chapitre 3](/course/fr/chapiter3), sur la bibliothèque 🤗 *Accelerate* au [chapitre 3](/course/fr/chapitre3), sur la bibliothèque 🤗 *Datasets* au [chapitre 5](/course/fr/chapiter5) et sur la bibliothèque 🤗 *Tokenizers* au [chapitre 6](/course/fr/chapiter6). Nous téléchargerons également nos résultats sur le *Hub*, comme nous l'avons fait dans le [chapitre 4](/course/fr/chapiter4), donc c'est vraiment le chapitre où tout est réuni !

Chaque section peut être lue indépendamment.

{/if}


<Tip>

Si vous lisez les sections dans l'ordre, vous remarquerez qu'elles ont beaucoup de code et de prose en commun. La répétition est intentionnelle, afin de vous permettre de vous plonger (ou de revenir plus tard) dans une tâche qui vous intéresse et de trouver un exemple fonctionnel complet.

</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter7/1.mdx" />

### <i>Finetuner</i> un modèle de langage masqué
https://huggingface.co/learn/course/fr/chapter7/3.md

# <i>Finetuner</i> un modèle de langage masqué

{#if fw === 'pt'}
<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section3_pt.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter7/section3_pt.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section3_pt.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter7/section3_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section3_tf.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter7/section3_tf.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section3_tf.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter7/section3_tf.ipynb"},
]} />

{/if}

Pour de nombreuses applications de NLP impliquant des *transformers*, vous pouvez simplement prendre un modèle pré-entraîné du *Hub* et le *finetuner* directement sur vos données pour la tâche à accomplir. Pour autant que le corpus utilisé pour le pré-entraînement ne soit pas trop différent du corpus utilisé pour le *finetuning*. L'apprentissage par transfert produira généralement de bons résultats. 

Cependant, il existe quelques cas où vous voudrez d'abord *finetuner* les modèles de langue sur vos données, avant d'entraîner une tête spécifique à la tâche. Par exemple, si votre jeu de données contient des contrats légaux ou des articles scientifiques, un *transformer* classique comme BERT traitera généralement les mots spécifiques au domaine dans votre corpus comme des *tokens* rares et les performances résultantes peuvent être moins que satisfaisantes. En *finetunant* le modèle de langage sur les données du domaine, vous pouvez améliorer les performances de nombreuses tâches en aval, ce qui signifie que vous ne devez généralement effectuer cette étape qu'une seule fois !

Ce processus de *finetuning* d'un modèle de langage pré-entraîné sur des données *dans le domaine* est généralement appelé _adaptation au domaine_. Il a été popularisé en 2018 par [ULMFiT](https://arxiv.org/abs/1801.06146) qui a été l'une des premières architectures neuronales (basées sur des LSTMs) à faire en sorte que l'apprentissage par transfert fonctionne réellement pour le NLP. Un exemple d'adaptation de domaine avec ULMFiT est présenté dans l'image ci-dessous. Dans cette section, nous ferons quelque chose de similaire mais avec un *transformer* au lieu d'une LSTM !

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/ulmfit.svg" alt="ULMFiT."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/ulmfit-dark.svg" alt="ULMFiT."/>
</div>

À la fin de cette section, vous aurez un [modèle de langage masqué](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.) sur le *Hub* qui peut autocompléter des phrases comme indiqué ci-dessous :

<iframe src="https://course-demos-distilbert-base-uncased-finetune-7400b54.hf.space" frameBorder="0" height="300" title="Gradio app" class="block dark:hidden container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>
<iframe src="https://course-demos-distilbert-base-uncased-finetuned-imdb-darkmode.hf.space" frameBorder="0" height="300" title="Gradio app" class="hidden dark:block container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Allons-y !

<Youtube id="mqElG5QJWUg"/>

<Tip>

🙋 Si les termes « modélisation du langage masqué » et « modèle pré-entraîné » ne vous sont pas familiers, consultez le [chapitre 1](/course/fr/chapiter1), où nous expliquons tous ces concepts fondamentaux, vidéos à l'appui !

</Tip>

## Choix d'un modèle pré-entraîné pour la modélisation du langage masqué

Pour commencer, nous allons choisir un modèle pré-entraîné approprié pour la modélisation du langage masqué. Comme le montre la capture d'écran suivante, vous pouvez trouver une liste de candidats en appliquant le filtre « *Fill-Mask* » sur le [*Hub*](https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads) :

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/mlm-models.png" alt="Hub models." width="80%"/>
</div>

Bien que les modèles de la famille BERT et RoBERTa soient les plus téléchargés, nous utiliserons un modèle appelé [DistilBERT](https://huggingface.co/distilbert-base-uncased) qui peut être entraîné beaucoup plus rapidement avec peu ou pas de perte de performance en aval. Ce modèle a été entraîné à l'aide d'une technique spéciale appelée [_distillation de connaissances_](https://en.wikipedia.org/wiki/Knowledge_distillation), où un grand modèle *enseignant* comme BERT est utilisé pour guider l'entraînement d'un modèle *étudiant* qui a beaucoup moins de paramètres. Une explication des détails de la distillation de connaissances nous mènerait trop loin dans cette section mais si vous êtes intéressé, vous pouvez lire tout cela dans le livre [_Natural Language Processing with Transformers_](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/).

{#if fw === 'pt'}

Allons-y et téléchargeons DistilBERT en utilisant la classe `AutoModelForMaskedLM` :

```python
from transformers import AutoModelForMaskedLM

model_checkpoint = "distilbert-base-uncased"
model = AutoModelForMaskedLM.from_pretrained(model_checkpoint)
```

Nous pouvons voir combien de paramètres ce modèle possède en appelant la méthode `num_parameters()` :

```python
distilbert_num_parameters = model.num_parameters() / 1_000_000
print(f"'>>> DistilBERT nombre de paramètres : {round(distilbert_num_parameters)}M'")
print(f"'>>> BERT nombre de paramètres : 110M'")
```

```python out
'>>> DistilBERT  nombre de paramètres : 67M'
'>>> BERT nombre de paramètres : 110M'
```

{:else}

Allons-y et téléchargeons DistilBERT en utilisant la classe `AutoModelForMaskedLM` :

```python
from transformers import TFAutoModelForMaskedLM

model_checkpoint = "distilbert-base-uncased"
model = TFAutoModelForMaskedLM.from_pretrained(model_checkpoint)
```

Nous pouvons voir combien de paramètres ce modèle possède en appelant la méthode `summary()` :

```python
model(model.dummy_inputs)  # Construire le modèle
model.summary()
```

```python out
Model: "tf_distil_bert_for_masked_lm"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
distilbert (TFDistilBertMain multiple                  66362880  
_________________________________________________________________
vocab_transform (Dense)      multiple                  590592    
_________________________________________________________________
vocab_layer_norm (LayerNorma multiple                  1536      
_________________________________________________________________
vocab_projector (TFDistilBer multiple                  23866170  
=================================================================
Total params: 66,985,530
Trainable params: 66,985,530
Non-trainable params: 0
_________________________________________________________________
```

{/if}

Avec environ 67 millions de paramètres, DistilBERT est environ deux fois plus petit que le modèle de base de BERT, ce qui se traduit approximativement par une accélération de l'entraînement d'un facteur deux. Voyons maintenant quels types de *tokens* ce modèle prédit comme étant les compléments les plus probables d'un petit échantillon de texte :

```python
text = "This is a great [MASK]."
```

En tant qu'êtres humains, nous pouvons imaginer de nombreuses possibilités pour le *token* `[MASK]`, telles que « jour », « promenade » ou « peinture ». Pour les modèles pré-entraînés, les prédictions dépendent du corpus sur lequel le modèle a été entraîné puisqu'il apprend à détecter les modèles statistiques présents dans les données. Comme BERT, DistilBERT a été pré-entraîné sur les jeux de données [*English Wikipedia*](https://huggingface.co/datasets/wikipedia) et [*BookCorpus*](https://huggingface.co/datasets/bookcorpus), nous nous attendons donc à ce que les prédictions pour `[MASK]` reflètent ces domaines. Pour prédire le masque, nous avons besoin du *tokenizer* de DistilBERT pour produire les entrées du modèle, alors téléchargeons-le également depuis le *Hub* :

```python
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
```

Avec un *tokenizer* et un modèle, nous pouvons maintenant passer notre exemple de texte au modèle, extraire les logits, et afficher les 5 meilleurs candidats :

{#if fw === 'pt'}

```python
import torch

inputs = tokenizer(text, return_tensors="pt")
token_logits = model(**inputs).logits
# Trouve l'emplacement de [MASK] et extrait ses logits
mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1]
mask_token_logits = token_logits[0, mask_token_index, :]
# Choisissez les candidats [MASK] avec les logits les plus élevés
top_5_tokens = torch.topk(mask_token_logits, 5, dim=1).indices[0].tolist()

for token in top_5_tokens:
    print(f"'>>> {text.replace(tokenizer.mask_token, tokenizer.decode([token]))}'")
```

{:else}

```python
import numpy as np
import tensorflow as tf

inputs = tokenizer(text, return_tensors="np")
token_logits = model(**inputs).logits
# Trouve l'emplacement de [MASK] et extrait ses logits
mask_token_index = np.argwhere(inputs["input_ids"] == tokenizer.mask_token_id)[0, 1]
mask_token_logits = token_logits[0, mask_token_index, :]
# On choisit les candidats [MASK] avec les logits les plus élevés
# Nous annulons le tableau avant argsort pour obtenir le plus grand, et non le plus petit, logits
top_5_tokens = np.argsort(-mask_token_logits)[:5].tolist()

for token in top_5_tokens:
    print(f">>> {text.replace(tokenizer.mask_token, tokenizer.decode([token]))}")
```

{/if}

```python out
'>>> This is a great deal.' # C'est une bonne affaire
'>>> This is a great success.' # C'est un grand succès
'>>> This is a great adventure.' # C'est une grande aventure
'>>> This is a great idea.' # C'est une bonne idée
'>>> This is a great feat.' # C'est un grand exploit
```

Nous pouvons voir dans les sorties que les prédictions du modèle se réfèrent à des termes de tous les jours, ce qui n'est peut-être pas surprenant étant donné le fondement de Wikipédia. Voyons comment nous pouvons changer ce domaine pour quelque chose d'un peu plus spécialisé : des critiques de films !


## Le jeu de données

Pour illustrer l'adaptation au domaine, nous utiliserons le célèbre [*Large Movie Review Dataset*](https://huggingface.co/datasets/imdb) (ou IMDb en abrégé), qui est un corpus de critiques de films souvent utilisé pour évaluer les modèles d'analyse de sentiments. En *finetunant* DistilBERT sur ce corpus, nous espérons que le modèle de langage adaptera son vocabulaire des données factuelles de Wikipédia sur lesquelles il a été pré-entraîné aux éléments plus subjectifs des critiques de films. Nous pouvons obtenir les données du *Hub* avec la fonction `load_dataset()` de 🤗 *Datasets* :

```python
from datasets import load_dataset

imdb_dataset = load_dataset("imdb")
imdb_dataset
```

```python out
DatasetDict({
    train: Dataset({
        features: ['text', 'label'],
        num_rows: 25000
    })
    test: Dataset({
        features: ['text', 'label'],
        num_rows: 25000
    })
    unsupervised: Dataset({
        features: ['text', 'label'],
        num_rows: 50000
    })
})
```

Nous pouvons voir que les parties `train` et `test` sont chacune composées de 25 000 critiques, alors qu'il y a une partie non étiquetée appelée `unsupervised` qui contient 50 000 critiques. Jetons un coup d'œil à quelques échantillons pour avoir une idée du type de texte auquel nous avons affaire. Comme nous l'avons fait dans les chapitres précédents du cours, nous allons enchaîner les fonctions `Dataset.shuffle()` et `Dataset.select()` pour créer un échantillon aléatoire :

```python
sample = imdb_dataset["train"].shuffle(seed=42).select(range(3))

for row in sample:
    print(f"\n'>>> Review: {row['text']}'")
    print(f"'>>> Label: {row['label']}'")
```

```python out

'>>> Review: This is your typical Priyadarshan movie--a bunch of loony characters out on some silly mission. His signature climax has the entire cast of the film coming together and fighting each other in some crazy moshpit over hidden money. Whether it is a winning lottery ticket in Malamaal Weekly, black money in Hera Pheri, "kodokoo" in Phir Hera Pheri, etc., etc., the director is becoming ridiculously predictable. Don\'t get me wrong; as clichéd and preposterous his movies may be, I usually end up enjoying the comedy. However, in most his previous movies there has actually been some good humor, (Hungama and Hera Pheri being noteworthy ones). Now, the hilarity of his films is fading as he is using the same formula over and over again.<br /><br />Songs are good. Tanushree Datta looks awesome. Rajpal Yadav is irritating, and Tusshar is not a whole lot better. Kunal Khemu is OK, and Sharman Joshi is the best.'
'>>> Label: 0'

'>>> Review: Okay, the story makes no sense, the characters lack any dimensionally, the best dialogue is ad-libs about the low quality of movie, the cinematography is dismal, and only editing saves a bit of the muddle, but Sam" Peckinpah directed the film. Somehow, his direction is not enough. For those who appreciate Peckinpah and his great work, this movie is a disappointment. Even a great cast cannot redeem the time the viewer wastes with this minimal effort.<br /><br />The proper response to the movie is the contempt that the director San Peckinpah, James Caan, Robert Duvall, Burt Young, Bo Hopkins, Arthur Hill, and even Gig Young bring to their work. Watch the great Peckinpah films. Skip this mess.'
'>>> Label: 0'

'>>> Review: I saw this movie at the theaters when I was about 6 or 7 years old. I loved it then, and have recently come to own a VHS version. <br /><br />My 4 and 6 year old children love this movie and have been asking again and again to watch it. <br /><br />I have enjoyed watching it again too. Though I have to admit it is not as good on a little TV.<br /><br />I do not have older children so I do not know what they would think of it. <br /><br />The songs are very cute. My daughter keeps singing them over and over.<br /><br />Hope this helps.'
'>>> Label: 1'
```

Oui, ce sont bien des critiques de films, et si vous êtes assez âgés, vous pouvez même comprendre le commentaire dans la dernière critique sur le fait de posséder une version VHS 😜 ! Bien que nous n'ayons pas besoin des étiquettes pour la modélisation du langage, nous pouvons déjà voir qu'un `0` dénote une critique négative, tandis qu'un `1` correspond à une critique positive.

<Tip>

✏️ **Essayez !** Créez un échantillon aléatoire de la répartition `unsupervised` et vérifiez que les étiquettes ne sont ni `0` ni `1`. Pendant que vous y êtes, vous pouvez aussi vérifier que les étiquettes dans les échantillons `train` et `test` sont bien `0` ou `1`. C'est un contrôle utile que tout praticien en NLP devrait effectuer au début d'un nouveau projet !

</Tip>

Maintenant que nous avons jeté un coup d'œil rapide aux données, plongeons dans leur préparation pour la modélisation du langage masqué. Comme nous allons le voir, il y a quelques étapes supplémentaires à suivre par rapport aux tâches de classification de séquences que nous avons vues au [chapitre 3](/course/fr/chapter3). Allons-y !

## Prétraitement des données

<Youtube id="8PmhEIXhBvI"/>

Pour la modélisation autorégressive et la modélisation du langage masqué, une étape commune de prétraitement consiste à concaténer tous les exemples, puis à diviser le corpus entier en morceaux de taille égale. C'est très différent de notre approche habituelle, où nous nous contentons de *tokenizer* les exemples individuels. Pourquoi tout concaténer ? La raison est que les exemples individuels peuvent être tronqués s'ils sont trop longs, ce qui entraînerait la perte d'informations qui pourraient être utiles pour la tâche de modélisation du langage !

Donc pour commencer, nous allons d'abord tokeniser notre corpus comme d'habitude, mais _sans_ mettre l'option `truncation=True` dans notre *tokenizer*. Nous allons aussi récupérer les identifiants des mots s'ils sont disponibles (ce qui sera le cas si nous utilisons un *tokenizer* rapide, comme décrit dans le [chapitre 6](/course/fr/chapter6/3)), car nous en aurons besoin plus tard pour faire le masquage de mots entiers. Nous allons envelopper cela dans une simple fonction, et pendant que nous y sommes, nous allons supprimer les colonnes `text` et `label` puisque nous n'en avons plus besoin :

```python
def tokenize_function(examples):
    result = tokenizer(examples["text"])
    if tokenizer.is_fast:
        result["word_ids"] = [result.word_ids(i) for i in range(len(result["input_ids"]))]
    return result


# Utilisation de batched=True pour activer le multithreading rapide !
tokenized_datasets = imdb_dataset.map(
    tokenize_function, batched=True, remove_columns=["text", "label"]
)
tokenized_datasets
```

```python out
DatasetDict({
    train: Dataset({
        features: ['attention_mask', 'input_ids', 'word_ids'],
        num_rows: 25000
    })
    test: Dataset({
        features: ['attention_mask', 'input_ids', 'word_ids'],
        num_rows: 25000
    })
    unsupervised: Dataset({
        features: ['attention_mask', 'input_ids', 'word_ids'],
        num_rows: 50000
    })
})
```

Comme DistilBERT est un modèle de type BERT, nous pouvons voir que les textes encodés sont constitués des `input_ids` et des `attention_mask` que nous avons vus dans d'autres chapitres, ainsi que des `word_ids` que nous avons ajoutés. 

Maintenant que nos critiques de films ont été tokenisées, l'étape suivante consiste à les regrouper et à diviser le résultat en chunks. Mais quelle taille doivent avoir ces *chunks* ? Cela sera finalement déterminé par la quantité de mémoire GPU dont vous disposez, mais un bon point de départ est de voir quelle est la taille maximale du contexte du modèle. Cela peut être déduit en inspectant l'attribut `model_max_length` du *tokenizer* :

```python
tokenizer.model_max_length
```


```python out
512
```

Cette valeur est dérivée du fichier *tokenizer_config.json* associé à un *checkpoint*. Dans ce cas, nous pouvons voir que la taille du contexte est de 512 *tokens*, tout comme avec BERT.

<Tip>

✏️ **Essayez !** Certains *transformers*, comme [BigBird](https://huggingface.co/google/bigbird-roberta-base) et [Longformer](hf.co/allenai/longformer-base-4096), ont une longueur de contexte beaucoup plus longue que BERT et les autres premiers *transformers*. Instanciez le *tokenizer* pour l'un de ces *checkpoints* et vérifiez que le `model_max_length` correspond à ce qui est indiqué sur sa carte.

</Tip>

Ainsi, pour réaliser nos expériences sur des GPUs comme ceux disponibles sur Google Colab, nous choisirons quelque chose d'un peu plus petit qui peut tenir en mémoire :

```python
chunk_size = 128
```

<Tip warning={true}>

Notez que l'utilisation d'une petite taille peut être préjudiciable dans les scénarios du monde réel. Vous devez donc utiliser une taille qui correspond au cas d'utilisation auquel vous appliquerez votre modèle.

</Tip>

Maintenant vient la partie amusante. Pour montrer comment la concaténation fonctionne, prenons quelques commentaires de notre ensemble d'entraînement et affichons le nombre de *tokens* par commentaire :

```python
# Le découpage produit une liste de listes pour chaque caractéristique
tokenized_samples = tokenized_datasets["train"][:3]

for idx, sample in enumerate(tokenized_samples["input_ids"]):
    print(f"'>>> Review {idx} length: {len(sample)}'")
```

```python out
'>>> Review 0 length: 200'
'>>> Review 1 length: 559'
'>>> Review 2 length: 192'
```

Nous pouvons ensuite concaténer tous ces exemples avec une simple compréhension du dictionnaire, comme suit :

```python
concatenated_examples = {
    k: sum(tokenized_samples[k], []) for k in tokenized_samples.keys()
}
total_length = len(concatenated_examples["input_ids"])
print(f"'>>> Longueur des critiques concaténées : {total_length}'")
```

```python out
'>>> Longueur des critiques concaténées : 951'
```

Super, la longueur totale est correcte. Donc maintenant, nous allons diviser les exemples concaténés en morceaux de la taille donnée par `block_size`. Pour ce faire, nous itérons sur les caractéristiques de `concatenated_examples` et utilisons une compréhension de liste pour créer des *chunks* de chaque caractéristique. Le résultat est un dictionnaire de *chunks* pour chaque caractéristique :

```python
chunks = {
    k: [t[i : i + chunk_size] for i in range(0, total_length, chunk_size)]
    for k, t in concatenated_examples.items()
}

for chunk in chunks["input_ids"]:
    print(f"'>>> Chunk length: {len(chunk)}'")
```

```python out
'>>> Chunk length: 128'
'>>> Chunk length: 128'
'>>> Chunk length: 128'
'>>> Chunk length: 128'
'>>> Chunk length: 128'
'>>> Chunk length: 128'
'>>> Chunk length: 128'
'>>> Chunk length: 55'
```

Comme vous pouvez le voir dans cet exemple, le dernier *chunk* sera généralement plus petit que la taille maximale des morceaux. Il y a deux stratégies principales pour gérer cela :

* Abandonner le dernier morceau s'il est plus petit que `chunk_size`.
* Rembourrer le dernier morceau jusqu'à ce que sa longueur soit égale à `chunk_size`.

Nous adopterons la première approche ici, donc nous allons envelopper toute la logique ci-dessus dans une seule fonction que nous pouvons appliquer à nos jeux de données tokenisés :

```python
def group_texts(examples):
    # Concaténation de tous les textes
    concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()}
    # Calcule la longueur des textes concaténés
    total_length = len(concatenated_examples[list(examples.keys())[0]])
    # Nous laissons tomber le dernier morceau s'il est plus petit que chunk_size
    total_length = (total_length // chunk_size) * chunk_size
    # Fractionnement par chunk de max_len
    result = {
        k: [t[i : i + chunk_size] for i in range(0, total_length, chunk_size)]
        for k, t in concatenated_examples.items()
    }
    # Créer une nouvelle colonne d'étiquettes
    result["labels"] = result["input_ids"].copy()
    return result
```

Notez que dans la dernière étape de `group_texts()` nous créons une nouvelle colonne `labels` qui est une copie de la colonne `input_ids`. Comme nous le verrons bientôt, c'est parce que dans la modélisation du langage masqué, l'objectif est de prédire des *tokens* masqués aléatoirement dans le batch d'entrée, et en créant une colonne `labels`, nous fournissons la vérité de base pour notre modèle de langage à apprendre. 

Appliquons maintenant `group_texts()` à nos jeux de données tokenisés en utilisant notre fidèle fonction `Dataset.map()` :

```python
lm_datasets = tokenized_datasets.map(group_texts, batched=True)
lm_datasets
```

```python out
DatasetDict({
    train: Dataset({
        features: ['attention_mask', 'input_ids', 'labels', 'word_ids'],
        num_rows: 61289
    })
    test: Dataset({
        features: ['attention_mask', 'input_ids', 'labels', 'word_ids'],
        num_rows: 59905
    })
    unsupervised: Dataset({
        features: ['attention_mask', 'input_ids', 'labels', 'word_ids'],
        num_rows: 122963
    })
})
```

Vous pouvez voir que le regroupement puis le découpage des textes a produit beaucoup plus d'exemples que nos 25 000 exemples initiaux pour les divisions `train` et `test`. C'est parce que nous avons maintenant des exemples impliquant des *tokens* contigus qui s'étendent sur plusieurs exemples du corpus original. Vous pouvez le voir explicitement en cherchant les *tokens* spéciaux `[SEP]` et `[CLS]` dans l'un des *chunks* :

```python
tokenizer.decode(lm_datasets["train"][1]["input_ids"])
```

```python out
".... at.......... high. a classic line : inspector : i'm here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn't! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless"
```

Dans cet exemple, vous pouvez voir deux critiques de films qui se chevauchent, l'une sur un film de lycée et l'autre sur les sans-abri. Voyons également à quoi ressemblent les étiquettes pour la modélisation du langage masqué :

```python out
tokenizer.decode(lm_datasets["train"][1]["labels"])
```

```python out
".... at.......... high. a classic line : inspector : i'm here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn't! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless"
```

Comme prévu par notre fonction `group_texts()` ci-dessus, cela semble identique aux `input_ids` décodés. Mais alors comment notre modèle peut-il apprendre quoi que ce soit ? Il nous manque une étape clé : insérer des *tokens* à des positions aléatoires dans les entrées ! Voyons comment nous pouvons le faire à la volée pendant le *finetuning* en utilisant un assembleur de données spécial.

## <i>Finetuning</i> de DistilBERT avec l'API `Trainer`

Le *finetuning* d'un modèle de langage masqué est presque identique au *finetuning* d'un modèle de classification de séquences, comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3). La seule différence est que nous avons besoin d'un collecteur de données spécial qui peut masquer de manière aléatoire certains des *tokens* dans chaque batch de textes. Heureusement, 🤗 *Transformers* est livré préparé avec un `DataCollatorForLanguageModeling` dédié à cette tâche. Nous devons juste lui passer le *tokenizer* et un argument `mlm_probability` qui spécifie quelle fraction des *tokens* à masquer. Nous choisirons 15%, qui est la quantité utilisée pour BERT et un choix commun dans la littérature :

```python
from transformers import DataCollatorForLanguageModeling

data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15)
```

Pour voir comment le masquage aléatoire fonctionne, nous allons donner quelques exemples à l'assembleur de données. Puisqu'il s'attend à une liste de `dict` où chaque `dict` représente un seul morceau de texte contigu, nous itérons d'abord sur le jeu de données avant de donner le batch à l'assembleur. Nous supprimons la clé `"word_ids"` pour cet assembleur de données car il ne l'attend pas :

```python
samples = [lm_datasets["train"][i] for i in range(2)]
for sample in samples:
    _ = sample.pop("word_ids")

for chunk in data_collator(samples)["input_ids"]:
    print(f"\n'>>> {tokenizer.decode(chunk)}'")
```

```python output
'>>> [CLS] bromwell [MASK] is a cartoon comedy. it ran at the same [MASK] as some other [MASK] about school life, [MASK] as " teachers ". [MASK] [MASK] [MASK] in the teaching [MASK] lead [MASK] to believe that bromwell high\'[MASK] satire is much closer to reality than is " teachers ". the scramble [MASK] [MASK] financially, the [MASK]ful students whogn [MASK] right through [MASK] pathetic teachers\'pomp, the pettiness of the whole situation, distinction remind me of the schools i knew and their students. when i saw [MASK] episode in [MASK] a student repeatedly tried to burn down the school, [MASK] immediately recalled. [MASK]...'

'>>> .... at.. [MASK]... [MASK]... high. a classic line plucked inspector : i\'[MASK] here to [MASK] one of your [MASK]. student : welcome to bromwell [MASK]. i expect that many adults of my age think that [MASK]mwell [MASK] is [MASK] fetched. what a pity that it isn\'t! [SEP] [CLS] [MASK]ness ( or [MASK]lessness as george 宇in stated )公 been an issue for years but never [MASK] plan to help those on the street that were once considered human [MASK] did everything from going to school, [MASK], [MASK] vote for the matter. most people think [MASK] the homeless'
```

Super, ça a marché ! Nous pouvons voir que le *token* `[MASK]` a été inséré de façon aléatoire à différents endroits dans notre texte. Ce seront les *tokens* que notre modèle devra prédire pendant l'entraînement. Et la beauté du collecteur de données est qu'il va rendre aléatoire l'insertion du `[MASK]` à chaque batch ! 

<Tip>

✏️ **Essayez** Exécutez le code ci-dessus plusieurs fois pour voir le masquage aléatoire se produire sous vos yeux ! Remplacez aussi la méthode `tokenizer.decode()` par `tokenizer.convert_ids_to_tokens()` pour voir que parfois un seul *token* d'un mot donné est masqué et pas les autres.

</Tip>

{#if fw === 'pt'}

Un effet secondaire du masquage aléatoire est que nos métriques d'évaluation ne seront pas déterministes lorsque nous utilisons la fonction `Trainer` puisque nous utilisons le même assembleur de données pour les échantillons d'entraînement et de test. Nous verrons plus tard, lorsque nous examinerons le *finetuning* avec 🤗 *Accelerate*, comment nous pouvons utiliser la flexibilité d'une boucle d'évaluation personnalisée pour geler le caractère aléatoire.

{/if}

Lors de l'entraînement des modèles pour la modélisation du langage masqué, une technique qui peut être utilisée est de masquer des mots entiers ensemble et pas seulement des *tokens* individuels. Cette approche est appelée _masquage de mots entiers_. Si nous voulons utiliser le masquage de mots entiers, nous devons construire nous-mêmes un assembleur de données. Un assembleur de données est simplement une fonction qui prend une liste d'échantillons et les convertit en un batch. Faisons-le ! Nous utiliserons les identifiants des mots calculés plus tôt pour faire une correspondance entre les indices des mots et les *tokens*, puis nous déciderons aléatoirement quels mots masquer et appliquerons ce masque sur les entrées. Notez que les étiquettes sont toutes `-100` sauf celles qui correspondent aux mots masqués.

{#if fw === 'pt'}

```py
import collections
import numpy as np

from transformers import default_data_collator

wwm_probability = 0.2


def whole_word_masking_data_collator(features):
    for feature in features:
        word_ids = feature.pop("word_ids")

        # Création d'une correspondance entre les mots et les indices des tokens correspondants
        mapping = collections.defaultdict(list)
        current_word_index = -1
        current_word = None
        for idx, word_id in enumerate(word_ids):
            if word_id is not None:
                if word_id != current_word:
                    current_word = word_id
                    current_word_index += 1
                mapping[current_word_index].append(idx)

        # Masquer des mots de façon aléatoire
        mask = np.random.binomial(1, wwm_probability, (len(mapping),))
        input_ids = feature["input_ids"]
        labels = feature["labels"]
        new_labels = [-100] * len(labels)
        for word_id in np.where(mask)[0]:
            word_id = word_id.item()
            for idx in mapping[word_id]:
                new_labels[idx] = labels[idx]
                input_ids[idx] = tokenizer.mask_token_id
        feature["labels"] = new_labels

    return default_data_collator(features)
```

{:else}

```py
import collections
import numpy as np

from transformers.data.data_collator import tf_default_data_collator

wwm_probability = 0.2


def whole_word_masking_data_collator(features):
    for feature in features:
        word_ids = feature.pop("word_ids")

        # Création d'une correspondance entre les mots et les indices des tokens correspondants
        mapping = collections.defaultdict(list)
        current_word_index = -1
        current_word = None
        for idx, word_id in enumerate(word_ids):
            if word_id is not None:
                if word_id != current_word:
                    current_word = word_id
                    current_word_index += 1
                mapping[current_word_index].append(idx)

        # Masquer des mots de façon aléatoire
        mask = np.random.binomial(1, wwm_probability, (len(mapping),))
        input_ids = feature["input_ids"]
        labels = feature["labels"]
        new_labels = [-100] * len(labels)
        for word_id in np.where(mask)[0]:
            word_id = word_id.item()
            for idx in mapping[word_id]:
                new_labels[idx] = labels[idx]
                input_ids[idx] = tokenizer.mask_token_id
        feature["labels"] = new_labels

    return tf_default_data_collator(features)
```

{/if}

Ensuite, nous pouvons l'essayer sur les mêmes échantillons que précédemment :

```py
samples = [lm_datasets["train"][i] for i in range(2)]
batch = whole_word_masking_data_collator(samples)

for chunk in batch["input_ids"]:
    print(f"\n'>>> {tokenizer.decode(chunk)}'")
```

```python out
'>>> [CLS] bromwell high is a cartoon comedy [MASK] it ran at the same time as some other programs about school life, such as " teachers ". my 35 years in the teaching profession lead me to believe that bromwell high\'s satire is much closer to reality than is " teachers ". the scramble to survive financially, the insightful students who can see right through their pathetic teachers\'pomp, the pettiness of the whole situation, all remind me of the schools i knew and their students. when i saw the episode in which a student repeatedly tried to burn down the school, i immediately recalled.....'

'>>> .... [MASK] [MASK] [MASK] [MASK]....... high. a classic line : inspector : i\'m here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn\'t! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless'
```

<Tip>

✏️ **Essayez** Exécutez le code ci-dessus plusieurs fois pour voir le masquage aléatoire se produire sous vos yeux ! Remplacez aussi la méthode `tokenizer.decode()` par `tokenizer.convert_ids_to_tokens()` pour voir que les *tokens* d'un mot donné sont toujours masqués ensemble.

</Tip>

Maintenant que nous avons deux assembleurs de données, les étapes restantes du *finetuning* sont standards. L'entraînement peut prendre un certain temps sur Google Colab si vous n'avez pas la chance de tomber sur un mythique GPU P100 😭. Ainsi nous allons d'abord réduire la taille du jeu d'entraînement à quelques milliers d'exemples. Ne vous inquiétez pas, nous obtiendrons quand même un modèle de langage assez décent ! Un moyen rapide de réduire la taille d'un jeu de données dans 🤗 *Datasets* est la fonction `Dataset.train_test_split()` que nous avons vue au [chapitre 5](/course/fr/chapter5) :

```python
train_size = 10_000
test_size = int(0.1 * train_size)

downsampled_dataset = lm_datasets["train"].train_test_split(
    train_size=train_size, test_size=test_size, seed=42
)
downsampled_dataset
```

```python out
DatasetDict({
    train: Dataset({
        features: ['attention_mask', 'input_ids', 'labels', 'word_ids'],
        num_rows: 10000
    })
    test: Dataset({
        features: ['attention_mask', 'input_ids', 'labels', 'word_ids'],
        num_rows: 1000
    })
})
```

Cela a automatiquement créé de nouvelles divisions `train` et `test` avec la taille du jeu d'entraînement fixée à 10.000 exemples et la validation fixée à 10% de cela. N'hésitez pas à augmenter la taille si vous avez un GPU puissant ! La prochaine chose que nous devons faire est de nous connecter au *Hub*. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction suivante :

```python
from huggingface_hub import notebook_login

notebook_login()
```

qui affichera un *widget* où vous pourrez saisir vos informations d'identification. Alternativement, vous pouvez exécuter : 

```
huggingface-cli login
```

dans votre terminal préféré et connectez-vous là. 

{#if fw === 'tf'}

Une fois que nous sommes connectés, nous pouvons créer nos jeux de données `tf.data`. Pour ce faire, nous utiliserons la méthode `prepare_tf_dataset()`, qui utilise notre modèle pour déduire automatiquement quelles colonnes doivent aller dans le jeu de données. Si vous voulez contrôler exactement les colonnes à utiliser, vous pouvez utiliser la méthode `Dataset.to_tf_dataset()` à la place. Pour garder les choses simples, nous n'utiliserons ici que le l’assembleur de données standard, mais vous pouvez aussi essayer l’assembleur masquant des mots entiers et comparer les résultats à titre d'exercice :

```python
tf_train_dataset = model.prepare_tf_dataset(
    downsampled_dataset["train"],
    collate_fn=data_collator,
    shuffle=True,
    batch_size=32,
)

tf_eval_dataset = model.prepare_tf_dataset(
    downsampled_dataset["test"],
    collate_fn=data_collator,
    shuffle=False,
    batch_size=32,
)
```

Ensuite, nous configurons nos hyperparamètres d'entraînement et compilons notre modèle. Nous utilisons la fonction `create_optimizer()` de la bibliothèque 🤗 *Transformers*, qui nous donne un optimiseur `AdamW` avec une décroissance linéaire du taux d'apprentissage. Nous utilisons également la perte intégrée au modèle, qui est la perte par défaut lorsqu'aucune perte n'est spécifiée comme argument de `compile()`, et nous définissons la précision d'entraînement à `"mixed_float16"`. Notez que si vous utilisez un GPU Colab ou un autre GPU qui n'a pas le support accéléré en float16, vous devriez probablement commenter cette ligne.

De plus, nous mettons en place un `PushToHubCallback` qui sauvegardera le modèle sur le *Hub* après chaque époque. Vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, pour pousser le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/distilbert-finetuned-imdb"`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc dans notre cas, ce sera `"lewtun/distilbert-finetuned-imdb"`.

```python
from transformers import create_optimizer
from transformers.keras_callbacks import PushToHubCallback
import tensorflow as tf

num_train_steps = len(tf_train_dataset)
optimizer, schedule = create_optimizer(
    init_lr=2e-5,
    num_warmup_steps=1_000,
    num_train_steps=num_train_steps,
    weight_decay_rate=0.01,
)
model.compile(optimizer=optimizer)

# Entraîner en mixed-precision float16
tf.keras.mixed_precision.set_global_policy("mixed_float16")

model_name = model_checkpoint.split("/")[-1]
callback = PushToHubCallback(
    output_dir=f"{model_name}-finetuned-imdb", tokenizer=tokenizer
)
```

Nous sommes maintenant prêts à exécuter `model.fit()`. Mais avant, regardons brièvement la _perplexité_ qui est une métrique commune pour évaluer la performance des modèles de langage.

{:else}

Une fois que nous sommes connectés, nous pouvons spécifier les arguments pour le `Trainer` :

```python
from transformers import TrainingArguments

batch_size = 64
# Montrer la perte d'entraînement à chaque époque
logging_steps = len(downsampled_dataset["train"]) // batch_size
model_name = model_checkpoint.split("/")[-1]

training_args = TrainingArguments(
    output_dir=f"{model_name}-finetuned-imdb",
    overwrite_output_dir=True,
    evaluation_strategy="epoch",
    learning_rate=2e-5,
    weight_decay=0.01,
    per_device_train_batch_size=batch_size,
    per_device_eval_batch_size=batch_size,
    push_to_hub=True,
    fp16=True,
    logging_steps=logging_steps,
)
```

Ici, nous avons modifié quelques options par défaut, y compris `logging_steps` pour s'assurer que nous suivons la perte d'entraînement à chaque époque. Nous avons également utilisé `fp16=True` pour activer l'entraînement en précision mixte, ce qui nous donne un autre gain de vitesse. Par défaut, `Trainer` va supprimer toutes les colonnes qui ne font pas partie de la méthode `forward()` du modèle. Cela signifie que si vous utilisez l'assembleur de masquage de mots entiers, vous devrez également définir `remove_unused_columns=False` pour vous assurer que nous ne perdons pas la colonne `word_ids` pendant l'entraînement.

Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/distilbert-finetuned-imdb"` `TrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"lewtun/distilbert-finetuned-imdb"`.

Nous avons maintenant tous les ingrédients pour instancier le `Trainer`. Ici, nous utilisons juste l'assembleur standard `data_collator`, mais vous pouvez essayer l'assembleur de masquage de mots entiers et comparer les résultats comme exercice : 

```python
from transformers import Trainer

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=downsampled_dataset["train"],
    eval_dataset=downsampled_dataset["test"],
    data_collator=data_collator,
    tokenizer=tokenizer,
)
```

Nous sommes maintenant prêts à exécuter `trainer.train()`. Mais avant, regardons brièvement la _perplexité_ qui est une métrique commune pour évaluer la performance des modèles de langage.

{/if}

### Perplexité pour les modèles de langage

<Youtube id="NURcDHhYe98"/>

Contrairement à d'autres tâches, comme la classification de textes ou la réponse à des questions, sur lesquelles nous disposons d'un corpus étiqueté pour entraîner, la modélisation du langage ne s'appuie sur aucune étiquette explicite. Alors comment déterminer ce qui fait un bon modèle de langage ? Comme pour la fonction de correction automatique de votre téléphone, un bon modèle de langage est celui qui attribue des probabilités élevées aux phrases grammaticalement correctes et des probabilités faibles aux phrases absurdes. Pour vous donner une meilleure idée de ce à quoi cela ressemble, vous pouvez trouver en ligne des séries entières de « ratés d'autocorrection » où le modèle d'un téléphone produit des compléments plutôt amusants (et souvent inappropriés) ! 

{#if fw === 'pt'}

En supposant que notre ensemble de test se compose principalement de phrases grammaticalement correctes, une façon de mesurer la qualité de notre modèle de langage est de calculer les probabilités qu'il attribue au mot suivant dans toutes les phrases de l'ensemble de test. Des probabilités élevées indiquent que le modèle n'est pas « surpris » ou « perplexe » vis-à-vis des exemples non vus, et suggèrent qu'il a appris les modèles de base de la grammaire de la langue. Il existe plusieurs définitions mathématiques de la perplexité. Celle que nous utiliserons la définit comme l'exponentielle de la perte d'entropie croisée. Ainsi, nous pouvons calculer la perplexité de notre modèle pré-entraîné en utilisant la fonction `Trainer.evaluate()` pour calculer la perte d'entropie croisée sur l'ensemble de test, puis en prenant l'exponentielle du résultat :

```python
import math

eval_results = trainer.evaluate()
print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}")
```

{:else}

En supposant que notre ensemble de test se compose principalement de phrases grammaticalement correctes, une façon de mesurer la qualité de notre modèle de langage est de calculer les probabilités qu'il attribue au mot suivant dans toutes les phrases de l'ensemble de test. Des probabilités élevées indiquent que le modèle n'est pas « surpris » ou « perplexe » vis-à-vis des exemples non vus, et suggèrent qu'il a appris les modèles de base de la grammaire de la langue. Il existe plusieurs définitions mathématiques de la perplexité. Celle que nous utiliserons la définit comme l'exponentielle de la perte d'entropie croisée. Ainsi, nous pouvons calculer la perplexité de notre modèle pré-entraîné en utilisant la fonction `model.evaluate()` pour calculer la perte d'entropie croisée sur l'ensemble de test, puis en prenant l'exponentielle du résultat :

```python
import math

eval_loss = model.evaluate(tf_eval_dataset)
print(f"Perplexité : {math.exp(eval_loss):.2f}")
```

{/if}

```python out
>>> Perplexité : 21.75
```

Un score de perplexité faible signifie un meilleur modèle de langue. Nous pouvons voir ici que notre modèle de départ a une valeur assez élevée. Voyons si nous pouvons la réduire en l'affinant ! Pour ce faire, nous commençons par exécuter la boucle d'entraînement :

{#if fw === 'pt'}

```python
trainer.train()
```

{:else}

```python
model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback])
```

{/if}

et ensuite calculer la perplexité résultante sur l'ensemble de test comme précédemment :

{#if fw === 'pt'}

```python
eval_results = trainer.evaluate()
print(f">>> Perplexité : {math.exp(eval_results['eval_loss']):.2f}")
```

{:else}

```python
eval_loss = model.evaluate(tf_eval_dataset)
print(f"Perplexité : {math.exp(eval_loss):.2f}")
```

{/if}

```python out
>>> Perplexité : 11.32
```

Joli. C'est une réduction considérable de la perplexité, ce qui nous indique que le modèle a appris quelque chose sur le domaine des critiques de films !

{#if fw === 'pt'}

Une fois l'entraînement terminé, nous pouvons pousser la carte de modèle avec les informations d'entraînement vers le *Hub* (les *checkpoints* sont sauvegardés pendant l'entraînement lui-même) :

```python
trainer.push_to_hub()
```

{/if}

<Tip>

✏️ **A votre tour !** Exécutez l'entraînement ci-dessus après avoir remplacé le collecteur de données par le collecteur de mots entiers masqués. Obtenez-vous de meilleurs résultats ?

</Tip>

{#if fw === 'pt'} 

Dans notre cas d'utilisation, nous n'avons pas eu besoin de faire quelque chose de spécial avec la boucle d'entraînement, mais dans certains cas, vous pourriez avoir besoin de mettre en œuvre une logique personnalisée. Pour ces applications, vous pouvez utiliser 🤗 *Accelerate*. Jetons un coup d'œil !

## <i>Finetuning</i> de DistilBERT avec 🤗 <i>Accelerate</i>

Comme nous l'avons vu, avec `Trainer` le *finetuning* d'un modèle de langage masqué est très similaire à l'exemple de classification de texte du [chapitre 3](/course/fr/chapter3). En fait, la seule subtilité est l'utilisation d'un assembleur de données spécial, et nous l'avons déjà couvert plus tôt dans cette section ! 

Cependant, nous avons vu que `DataCollatorForLanguageModeling` applique aussi un masquage aléatoire à chaque évaluation. Nous verrons donc quelques fluctuations dans nos scores de perplexité à chaque entrainement. Une façon d'éliminer cette source d'aléat est d'appliquer le masquage _une fois_ sur l'ensemble de test, puis d'utiliser l'assembleur de données par défaut dans 🤗 *Transformers* pour collecter les batchs pendant l'évaluation. Pour voir comment cela fonctionne, implémentons une fonction simple qui applique le masquage sur un batch, similaire à notre première rencontre avec `DataCollatorForLanguageModeling` :

```python
def insert_random_mask(batch):
    features = [dict(zip(batch, t)) for t in zip(*batch.values())]
    masked_inputs = data_collator(features)
    # Créer une nouvelle colonne "masquée" pour chaque colonne du jeu de données
    return {"masked_" + k: v.numpy() for k, v in masked_inputs.items()}
```

Ensuite, nous allons appliquer cette fonction à notre jeu de test et laisser tomber les colonnes non masquées afin de les remplacer par les colonnes masquées. Vous pouvez utiliser le masquage de mots entiers en remplaçant le `data_collator` ci-dessus par celui qui est approprié. Dans ce cas vous devez supprimer la première ligne ici :

```py
downsampled_dataset = downsampled_dataset.remove_columns(["word_ids"])
eval_dataset = downsampled_dataset["test"].map(
    insert_random_mask,
    batched=True,
    remove_columns=downsampled_dataset["test"].column_names,
)
eval_dataset = eval_dataset.rename_columns(
    {
        "masked_input_ids": "input_ids",
        "masked_attention_mask": "attention_mask",
        "masked_labels": "labels",
    }
)
```

Nous pouvons ensuite configurer les *dataloaders* comme d'habitude, mais nous utiliserons le `default_data_collator` de 🤗 *Transformers* pour le jeu d'évaluation :

```python
from torch.utils.data import DataLoader
from transformers import default_data_collator

batch_size = 64
train_dataloader = DataLoader(
    downsampled_dataset["train"],
    shuffle=True,
    batch_size=batch_size,
    collate_fn=data_collator,
)
eval_dataloader = DataLoader(
    eval_dataset, batch_size=batch_size, collate_fn=default_data_collator
)
```

Nous suivons les étapes standard avec 🤗 *Accelerate*. La première est de charger une version fraîche du modèle pré-entraîné :

```
model = AutoModelForMaskedLM.from_pretrained(model_checkpoint)
```

Ensuite, nous devons spécifier l'optimiseur. Nous utiliserons le standard `AdamW` :

```python
from torch.optim import AdamW

optimizer = AdamW(model.parameters(), lr=5e-5)
```

Avec ces objets, nous pouvons maintenant tout préparer pour l'entraînement avec l'objet `Accelerator` :

```python
from accelerate import Accelerator

accelerator = Accelerator()
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, eval_dataloader
)
```

Maintenant que notre modèle, notre optimiseur et nos chargeurs de données sont configurés, nous pouvons spécifier le planificateur du taux d'apprentissage comme suit :

```python
from transformers import get_scheduler

num_train_epochs = 3
num_update_steps_per_epoch = len(train_dataloader)
num_training_steps = num_train_epochs * num_update_steps_per_epoch

lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)
```

Il ne reste qu'une dernière chose à faire avant de s'entraîner : créer un dépôt de modèles sur le *Hub* d'Hugging Face ! Nous pouvons utiliser la bibliothèque 🤗 *Hub* pour générer d'abord le nom complet de notre dépôt :

```python
from huggingface_hub import get_full_repo_name

model_name = "distilbert-base-uncased-finetuned-imdb-accelerate"
repo_name = get_full_repo_name(model_name)
repo_name
```

```python out
'lewtun/distilbert-base-uncased-finetuned-imdb-accelerate'
```

puis créer et cloner le dépôt en utilisant la classe `Repository` du 🤗 *Hub* :

```python
from huggingface_hub import Repository

output_dir = model_name
repo = Repository(output_dir, clone_from=repo_name)
```

Une fois cela fait, il ne reste plus qu'à rédiger la boucle complète d'entraînement et d'évaluation :

```python
from tqdm.auto import tqdm
import torch
import math

progress_bar = tqdm(range(num_training_steps))

for epoch in range(num_train_epochs):
    # Entraînement
    model.train()
    for batch in train_dataloader:
        outputs = model(**batch)
        loss = outputs.loss
        accelerator.backward(loss)

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)

    # Evaluation
    model.eval()
    losses = []
    for step, batch in enumerate(eval_dataloader):
        with torch.no_grad():
            outputs = model(**batch)

        loss = outputs.loss
        losses.append(accelerator.gather(loss.repeat(batch_size)))

    losses = torch.cat(losses)
    losses = losses[: len(eval_dataset)]
    try:
        perplexity = math.exp(torch.mean(losses))
    except OverflowError:
        perplexity = float("inf")

    print(f">>> Epoch {epoch}: Perplexity: {perplexity}")

    # Sauvegarder et télécharger
    accelerator.wait_for_everyone()
    unwrapped_model = accelerator.unwrap_model(model)
    unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
    if accelerator.is_main_process:
        tokenizer.save_pretrained(output_dir)
        repo.push_to_hub(
            commit_message=f"Training in progress epoch {epoch}", blocking=False
        )
```

```python out
>>> Epoch 0: Perplexity: 11.397545307900472
>>> Epoch 1: Perplexity: 10.904909330983092
>>> Epoch 2: Perplexity: 10.729503505340409
```

Cool, nous avons été en mesure d'évaluer la perplexité à chaque époque et de garantir la reproductibilité des entraînements multiples !

{/if}

### Utilisation de notre modèle <i>finetuné</i>

Vous pouvez interagir avec votre modèle *finetuné* soit en utilisant son *widget* sur le *Hub*, soit localement avec le `pipeline` de 🤗 *Transformers*. Utilisons ce dernier pour télécharger notre modèle en utilisant le pipeline `fill-mask` :

```python
from transformers import pipeline

mask_filler = pipeline(
    "fill-mask", model="huggingface-course/distilbert-base-uncased-finetuned-imdb"
)
```

Nous pouvons ensuite donner au pipeline notre exemple de texte « this is a great [MASK] » et voir quelles sont les 5 premières prédictions :

```python
preds = mask_filler(text)

for pred in preds:
    print(f">>> {pred['sequence']}")
```

```python out
'>>> this is a great movie.'
'>>> this is a great film.'
'>>> this is a great story.'
'>>> this is a great movies.'
'>>> this is a great character.'
```

Notre modèle a clairement adapté ses pondérations pour prédire les mots qui sont plus fortement associés aux films !

<Youtube id="0Oxphw4Q9fo"/>

Ceci conclut notre première expérience d'entraînement d'un modèle de langage. Dans la [section 6](/course/fr/chapter7/section6), vous apprendrez comment entraîner à partir de zéro un modèle autorégressif comme GPT-2. Allez-y si vous voulez voir comment vous pouvez pré-entraîner votre propre *transformer* !

<Tip>

✏️ **Essayez !** Pour quantifier les avantages de l'adaptation au domaine, <i>finetunez</i> un classifieur sur le jeu de données IMDb pour à la fois, le <i>checkpoint</i> de DistilBERT pré-entraîné et e <i>checkpoint</i> de DistilBERT <i>finetuné</i>. Si vous avez besoin d'un rafraîchissement sur la classification de texte, consultez le [chapitre 3](/course/fr/chapter3). 

</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter7/3.mdx" />

### Entraîner un modèle de langage causal à partir de zéro
https://huggingface.co/learn/course/fr/chapter7/6.md

# Entraîner un modèle de langage causal à partir de zéro

{#if fw === 'pt'}
<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section6_pt.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter7/section6_pt.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section6_pt.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter7/section6_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section6_tf.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter7/section6_tf.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section6_tf.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter7/section6_tf.ipynb"},
]} />

{/if}

Jusqu'à présent, nous avons surtout réutilisé des modèles pré-entraînés et les avons *finetunés* sur de nouveaux cas d'usage. Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), ceci est communément appelé _apprentissage par transfert_, et il s'agit d'une stratégie très efficace pour appliquer les *transformers* à la plupart des applications du monde réel où les données étiquetées sont rares. Dans ce chapitre, nous allons adopter une approche différente consistant à entraîner un modèle complètement nouveau à partir de zéro. C'est une bonne démarche à adopter si vous avez beaucoup de données et qu'elles sont très différentes des données de pré-entraînement utilisées par les modèles disponibles. Cependant, le pré-entraînement d'un modèle de langue nécessite beaucoup plus de ressources informatiques que le simple *finetuning* d'un modèle existant. Parmi les exemples où il peut être utile d'entraîner un nouveau modèle, citons les jeux de données constitués de notes de musique, de séquences moléculaires telles que l'ADN, ou de langages de programmation. Ces derniers ont récemment gagné en popularité grâce à des outils tels que TabNine et Copilot de GitHub (alimentés par le modèle Codex d'OpenAI) qui peuvent générer de longues séquences de code. Cette tâche de génération de texte est mieux abordée avec des modèles de langage autorégressifs ou causaux tels que le GPT-2.

Dans cette section, nous allons construire une version réduite d'un modèle de génération de code Python. Nous nous concentrerons sur la complétion d'une ligne de code au lieu de fonctions ou de classes complètes. Lorsque vous travaillez sur des projets de science des données en Python, vous êtes souvent en contact avec les bibliothèques `matplotlib`, `seaborn`, `pandas` et `scikit-learn`. Lors de l'utilisation de ces *frameworks*, il est fréquent d'avoir besoin de rechercher des commandes spécifiques. Il serait donc bien d'utiliser un modèle pour compléter ces appels pour nous.


<Youtube id="Vpjb1lu0MDk"/>

Dans le [chapitre 6](/course/fr/chapter6), nous avons créé un *tokenizer* efficace pour traiter du code Python. Nous avons besoin d'un jeu de données à grande échelle pour pré-entraîner un modèle. Ici, nous allons appliquer notre *tokenizer* à un corpus de code Python provenant des dépôts GitHub. Nous utiliserons ensuite l'API `Trainer` et 🤗 *Accelerate* pour entraîner le modèle. C'est parti !

<iframe src="https://course-demos-codeparrot-ds.hf.space" frameBorder="0" height="300" title="Gradio app" class="block dark:hidden container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>
<iframe src="https://course-demos-codeparrot-ds-darkmode.hf.space" frameBorder="0" height="300" title="Gradio app" class="hidden dark:block container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Il s'agit d'une présentation du modèle qui a été entraîné à l'aide du code présenté dans cette section et qui a ensuité été téléchargé sur le *Hub*. Vous pouvez le trouver [ici](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28). Notez qu'étant donné qu'il y a un certains aléat dans la génération du texte, vous obtiendrez probablement un résultat légèrement différent.
 
## Collecte des données

On peut trouver du code Python en abondance dans les dépôts de code tels que GitHub, que nous pouvons utiliser pour créer un jeu de données en récupérant chaque dépôt Python. C'est l'approche adoptée dans le [livre *Natural Language Processing with Transformers*](https://learning.oreilly.com/library/view/natural-language-processing/9781098136789/) pour pré-entraîner un grand GPT-2. En utilisant un dépôt GitHub d'environ 180 Go contenant approximativement 20 millions de fichiers Python, les auteurs du livre ont construit un jeu de données appelé `codeparrot` qu'ils ont ensuite partagé sur le [*Hub*](https://huggingface.co/datasets/transformersbook/codeparrot).

Cependant, entraîner sur l'ensemble du corpus prend beaucoup de temps et demande beaucoup de ressources de calculs. Dans notre cas, nous n'avons besoin que du sous-ensemble du jeu de données qui est relatif aux codes portant sur la science des données. Commençons donc par filtrer le jeu de données `codeparrot` en ne gardant que les fichiers incluant l'une des bibliothèques de science des données énumérées précédemment. En raison de la taille du jeu de données, nous voulons éviter de le télécharger. Nous utiliserons donc la fonctionnalité de *streaming* de 🤗 *Datasets* afin de le filtrer à la volée. Pour nous aider à filtrer les échantillons de code utilisant les bibliothèques que nous avons mentionnées précédemment, nous utilisons la fonction suivante :

```py
def any_keyword_in_string(string, keywords):
    for keyword in keywords:
        if keyword in string:
            return True
    return False
```

Testons-le sur deux exemples :

```py
filters = ["pandas", "sklearn", "matplotlib", "seaborn"]
example_1 = "import numpy as np"
example_2 = "import pandas as pd"

print(
    any_keyword_in_string(example_1, filters), any_keyword_in_string(example_2, filters)
)
```

```python out
False True
```

Nous pouvons l'utiliser pour créer une fonction qui va *streamer* le jeu de donner et filtrer les éléments que nous voulons :

```py
def filter_streaming_dataset(dataset, filters):
    filtered_dict = defaultdict(list)
    total = 0
    for sample in tqdm(iter(dataset)):
        total += 1
        if any_keyword_in_string(sample["content"], filters):
            for k, v in sample.items():
                filtered_dict[k].append(v)
    print(f"{len(filtered_dict['content'])/total:.2%} of data after filtering.")
    return Dataset.from_dict(filtered_dict)
```

Ensuite, nous pouvons simplement appliquer cette fonction :

```py
# Cette cellule prendra beaucoup de temps à s'exécuter, donc vous devriez la sauter et aller à la suivante !
from datasets import load_dataset

split = "train"  # "valid"
filters = ["pandas", "sklearn", "matplotlib", "seaborn"]

data = load_dataset(f"transformersbook/codeparrot-{split}", split=split, streaming=True)
filtered_data = filter_streaming_dataset(data, filters)
```

```python out
3.26% of data after filtering.
```

Cela nous laisse avec environ 3 % du jeu de données original, ce qui est tout de même assez important puisqu'il fait 6 Go et se compose de 600 000 scripts Python !

Le filtrage peut prendre de 2 à 3 heures, selon votre machine et votre bande passante. Si vous ne voulez pas passer par ce long processus, nous fournissons sur le *Hub* le jeu de données filtré pour que vous puissiez le télécharger :

```py
from datasets import load_dataset, DatasetDict

ds_train = load_dataset("huggingface-course/codeparrot-ds-train", split="train")
ds_valid = load_dataset("huggingface-course/codeparrot-ds-valid", split="train")

raw_datasets = DatasetDict(
    {
        "train": ds_train,  # .shuffle().select(range(50000)),
        "valid": ds_valid,  # .shuffle().select(range(500))
    }
)

raw_datasets
```

```python out
DatasetDict({
    train: Dataset({
        features: ['repo_name', 'path', 'copies', 'size', 'content', 'license'],
        num_rows: 606720
    })
    valid: Dataset({
        features: ['repo_name', 'path', 'copies', 'size', 'content', 'license'],
        num_rows: 3322
    })
})
```

<Tip>

Le pré-entraînement du modèle de langue prendra un certain temps. Nous vous suggérons donc d'exécuter d'abord la boucle d'entraînement sur un petit échantillon des données en décommentant les deux lignes dans le code ci-dessus. Assurez-vous alors que l'entraînement se termine avec succès et que les modèles sont stockés. Rien n'est plus frustrant qu'un entraînement qui échoue à la dernière étape car vous avez oublié de créer un dossier ou parce qu'il y a une faute de frappe à la fin de la boucle d'entraînement !

</Tip>

Examinons un exemple tiré du jeu de données. Nous ne montrerons que les 200 premiers caractères de chaque champ :

```py
for key in raw_datasets["train"][0]:
    print(f"{key.upper()}: {raw_datasets['train'][0][key][:200]}")
```

```python out
'REPO_NAME: kmike/scikit-learn'
'PATH: sklearn/utils/__init__.py'
'COPIES: 3'
'SIZE: 10094'
'''CONTENT: """
The :mod:`sklearn.utils` module includes various utilites.
"""

from collections import Sequence

import numpy as np
from scipy.sparse import issparse
import warnings

from .murmurhash import murm
LICENSE: bsd-3-clause'''
```

Nous pouvons voir que le champ `content` contient le code sur lequel nous voulons que notre modèle s'entraîne. Maintenant que nous avons un jeu de données, nous devons préparer les textes afin qu'ils soient dans un format approprié pour le pré-entraînement.

## Préparation du jeu de données

<Youtube id="ma1TrR7gE7I"/>

La première étape est de tokeniser les données afin de pouvoir les utiliser pour l'entraînement. Puisque notre objectif est d'autocompléter de courts appels de fonctions, nous pouvons garder la taille du contexte relativement petite. L'avantage est que nous pouvons entraîner le modèle beaucoup plus rapidement et qu'il nécessite beaucoup moins de mémoire. Si c'est important pour votre application d'avoir davantage de contexte (par exemple, si vous voulez que le modèle écrive des tests unitaires basés sur un fichier avec la définition de la fonction), assurez-vous d'augmenter ce nombre. Gardez néanmoins à l'esprit que cela s'accompagne d'une plus grande empreinte mémoire du GPU. Pour l'instant, fixons la taille du contexte à 128 *tokens*, par opposition aux 1 024 ou 2 048 utilisés respectivement dans le GPT-2 et le GPT-3.

La plupart des documents contiennent beaucoup plus de 128 *tokens*, donc le fait de tronquer les entrées à la longueur maximale éliminerait une grande partie de notre jeu de données. A la place, nous allons utiliser l'option `return_overflowing_tokens` pour tokeniser l'entrée entière et la diviser en plusieurs morceaux, comme nous l'avons fait dans le [chapitre 6](/course/fr/chapter6/4). Nous utiliserons également l'option `return_length` pour retourner automatiquement la longueur de chaque morceau créé. Souvent, le dernier morceau est plus petit que la taille du contexte et nous nous en débarrasserons pour éviter les problèmes de *padding*. Nous n'en avons pas vraiment besoin puisque de toute façon nous avons beaucoup de données.

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/chunking_texts.svg" alt="Chunking a large texts in several pieces."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/chunking_texts-dark.svg" alt="Chunking a large texts in several pieces."/>
</div>

Voyons comment cela fonctionne en examinant les deux premiers exemples :

```py
from transformers import AutoTokenizer

context_length = 128
tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer")

outputs = tokenizer(
    raw_datasets["train"][:2]["content"],
    truncation=True,
    max_length=context_length,
    return_overflowing_tokens=True,
    return_length=True,
)

print(f"Input IDs length: {len(outputs['input_ids'])}")
print(f"Input chunk lengths: {(outputs['length'])}")
print(f"Chunk mapping: {outputs['overflow_to_sample_mapping']}")
```

```python out
Input IDs length: 34
Input chunk lengths: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 117, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 41]
Chunk mapping: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
```

Nous pouvons voir que nous obtenons 34 morceaux à partir de ces deux exemples. En regardant leurs longueurs, nous pouvons voir qu'ils se terminent avec moins de 128 *tokens* (117 et 41, respectivement). Ils ne représentent qu'une petite fraction du total des morceaux que nous avons (2/34), donc nous pouvons les jeter sans risque. Avec le champ `overflow_to_sample_mapping`, nous pouvons aussi reconstruire quels morceaux appartenaient à quels échantillons d'entrée.

Avec cette opération, nous utilisons une fonctionnalité pratique de la fonction `Dataset.map()` de 🤗 *Datasets*. En effet, celle-ci ne nécessite pas une correspondance un à un comme nous l'avons vu dans la [section 3](/course/fr/chapter7/3). Nous pouvons créer des batchs avec plus ou moins d'éléments que le batch d'entrée. C'est utile lorsque l'on effectue des opérations telles que l'augmentation ou le filtrage des données qui modifient le nombre d'éléments. Dans notre cas, lors de la tokenisation de chaque élément en morceaux de longeur de la taille de contexte spécifiée, nous créons de nombreux échantillons de chaque document. Nous devons juste nous assurer de supprimer les colonnes existantes, car elles ont une taille conflictuelle. Si nous voulions les garder, nous pourrions les répéter de manière appropriée et les retourner dans l'appel `Dataset.map()` :

```py
def tokenize(element):
    outputs = tokenizer(
        element["content"],
        truncation=True,
        max_length=context_length,
        return_overflowing_tokens=True,
        return_length=True,
    )
    input_batch = []
    for length, input_ids in zip(outputs["length"], outputs["input_ids"]):
        if length == context_length:
            input_batch.append(input_ids)
    return {"input_ids": input_batch}


tokenized_datasets = raw_datasets.map(
    tokenize, batched=True, remove_columns=raw_datasets["train"].column_names
)
tokenized_datasets
```

```python out
DatasetDict({
    train: Dataset({
        features: ['input_ids'],
        num_rows: 16702061
    })
    valid: Dataset({
        features: ['input_ids'],
        num_rows: 93164
    })
})
```

Nous avons maintenant 16,7 millions d'exemples avec 128 *tokens* chacun, ce qui correspond à environ 2,1 milliards de *tokens* au total. A titre de comparaison, les modèles GPT-3 et Codex d'OpenAI sont entraînés sur 300 et 100 milliards de *tokens*, respectivement. Les modèles Codex étant initialisés à partir des *checkpoints* GPT-3. Notre objectif dans cette section n'est pas de rivaliser avec ces modèles, qui peuvent générer des textes longs et cohérents, mais de créer une version réduite fournissant une fonction d'autocomplétion rapide.

Maintenant que le jeu de données est prêt, configurons le modèle !

<Tip>

✏️ **Essayez !** Se débarrasser de tous les morceaux qui sont plus petits que la taille du contexte n'était pas un gros problème ici parce que nous utilisons de petites fenêtres de contexte. Si vous augmentez la taille du contexte (ou si vous avez un corpus de documents courts), la fraction des morceaux qui sont jetés augmentera. Une façon plus efficace de préparer les données est de joindre tous les échantillons dans un batch avec un *token* `eos_token_id` entre les deux, puis d'effectuer le découpage sur les séquences concaténées. Comme exercice, modifiez la fonction `tokenize()` pour utiliser cette approche. Notez que vous devrez mettre `truncation=False` et enlever les autres arguments du *tokenizer* pour obtenir la séquence complète des identifiants des *tokens*.

</Tip>


## Initialisation d'un nouveau modèle

Notre première étape consiste à initialiser un GPT-2. Pour notre modèle, nous utiliserons la même configuration que pour le petit modèle GPT-2. Ainsi nous chargeons la configuration pré-entraînée, nous nous assurons que la taille du *tokenizer* correspond à la taille du vocabulaire du modèle et nous passons les identifiants des *tokens* `bos` et `eos` (début et fin de séquence) :

{#if fw === 'pt'}

```py
from transformers import AutoTokenizer, GPT2LMHeadModel, AutoConfig

config = AutoConfig.from_pretrained(
    "gpt2",
    vocab_size=len(tokenizer),
    n_ctx=context_length,
    bos_token_id=tokenizer.bos_token_id,
    eos_token_id=tokenizer.eos_token_id,
)
```

Avec cette configuration, nous pouvons charger un nouveau modèle. Notez que c'est la première fois que nous n'utilisons pas la fonction `from_pretrained()` puisque nous initialisons nous-mêmes un modèle :

```py
model = GPT2LMHeadModel(config)
model_size = sum(t.numel() for t in model.parameters())
print(f"GPT-2 size: {model_size/1000**2:.1f}M parameters")
```

```python out
GPT-2 size: 124.2M parameters
```

{:else}

```py
from transformers import AutoTokenizer, TFGPT2LMHeadModel, AutoConfig

config = AutoConfig.from_pretrained(
    "gpt2",
    vocab_size=len(tokenizer),
    n_ctx=context_length,
    bos_token_id=tokenizer.bos_token_id,
    eos_token_id=tokenizer.eos_token_id,
)
```

Avec cette configuration, nous pouvons charger un nouveau modèle. Notez que c'est la première fois que nous n'utilisons pas la fonction `from_pretrained()` puisque nous initialisons nous-mêmes un modèle :

```py
model = TFGPT2LMHeadModel(config)
model(model.dummy_inputs)  # Construit le modèle
model.summary()
```

```python out
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
transformer (TFGPT2MainLayer multiple                  124242432 
=================================================================
Total params: 124,242,432
Trainable params: 124,242,432
Non-trainable params: 0
_________________________________________________________________
```

{/if}

Notre modèle comporte 124 millions de paramètres que nous devrons régler. Avant de commencer l'entraînement, nous devons configurer un assembleur de données qui se chargera de créer les batchs. Nous pouvons utiliser le assembleur `DataCollatorForLanguageModeling`, qui est conçu spécifiquement pour la modélisation du langage (comme son nom le suggère subtilement). En plus de l'empilage et du rembourrage des batchs, il s'occupe aussi de la création des étiquettes du modèle de langage. Dans la modélisation causale du langage, les entrées servent aussi d'étiquettes (juste décalées d'un élément) et que le assembleur de données crée à la volée pendant l'entraînement pour ne pas avoir à dupliquer les `input_ids`.

Notez que `DataCollatorForLanguageModeling` supporte à la fois la modélisation du langage masqué (MLM pour *masked language modeling*) et la modélisation du langage causal (CLM pour *causal language modeling*). Par défaut, il prépare les données pour la MLM mais nous pouvons passer à la CLM en définissant l'argument `mlm=False` :

{#if fw === 'pt'}

```py
from transformers import DataCollatorForLanguageModeling

tokenizer.pad_token = tokenizer.eos_token
data_collator = DataCollatorForLanguageModeling(tokenizer, mlm=False)
```

{:else}

```py
from transformers import DataCollatorForLanguageModeling

tokenizer.pad_token = tokenizer.eos_token
data_collator = DataCollatorForLanguageModeling(tokenizer, mlm=False, return_tensors="tf")
```

{/if}

Prenons un exemple :

```py
out = data_collator([tokenized_dataset["train"][i] for i in range(5)])
for key in out:
    print(f"{key} shape: {out[key].shape}")
```

{#if fw === 'pt'}

```python out
input_ids shape: torch.Size([5, 128])
attention_mask shape: torch.Size([5, 128])
labels shape: torch.Size([5, 128])
```

{:else}

```python out
input_ids shape: (5, 128)
attention_mask shape: (5, 128)
labels shape: (5, 128)
```

{/if}

Nous pouvons voir que les exemples ont été empilés et que tous les tenseurs ont la même forme.

{#if fw === 'tf'}

Maintenant nous pouvons utiliser la méthode `prepare_tf_dataset()` pour convertir nos jeux de données en jeux de données TensorFlow avec l'assembleur de données que nous avons créé ci-dessus :

```python
tf_train_dataset = model.prepare_tf_dataset(
    tokenized_dataset["train"],
    collate_fn=data_collator,
    shuffle=True,
    batch_size=32,
)
tf_eval_dataset = model.prepare_tf_dataset(
    tokenized_dataset["valid"],
    collate_fn=data_collator,
    shuffle=False,
    batch_size=32,
)
```

{/if}

<Tip warning={true}>

⚠️ Le déplacement des entrées et des étiquettes pour les aligner se fait à l'intérieur du modèle, de sorte que l'assembleur de données ne fait que copier les entrées pour créer les étiquettes.

</Tip>


Nous avons maintenant tout ce qu'il faut pour entraîner notre modèle. Ce n'était pas si compliqué ! Avant de commencer l'entraînement, nous devons nous connecter à Hugging Face. Si vous travaillez dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante :

```python
from huggingface_hub import notebook_login

notebook_login()
```

Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face.

Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal :

```bash
huggingface-cli login
```

{#if fw === 'pt'}

Tout ce qu'il reste à faire est de configurer les arguments d'entraînement et de lancer la fonction `Trainer`. Nous utiliserons un programme de taux d'apprentissage de type cosinus avec un réchauffement et une taille de batch de 256 (`per_device_train_batch_size` x `gradient_accumulation_steps`). L'accumulation du gradient est utilisée lorsqu'un seul batch ne tient pas en mémoire, et construit le gradient de manière incrémentale à travers plusieurs passages en avant/en arrière. Nous verrons cela en action lorsque nous créerons la boucle d'entraînement avec 🤗 *Accelerate*.

```py
from transformers import Trainer, TrainingArguments

args = TrainingArguments(
    output_dir="codeparrot-ds",
    per_device_train_batch_size=32,
    per_device_eval_batch_size=32,
    evaluation_strategy="steps",
    eval_steps=5_000,
    logging_steps=5_000,
    gradient_accumulation_steps=8,
    num_train_epochs=1,
    weight_decay=0.1,
    warmup_steps=1_000,
    lr_scheduler_type="cosine",
    learning_rate=5e-4,
    save_steps=5_000,
    fp16=True,
    push_to_hub=True,
)

trainer = Trainer(
    model=model,
    tokenizer=tokenizer,
    args=args,
    data_collator=data_collator,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["valid"],
)
```

Maintenant, nous pouvons simplement lancer le `Trainer` et attendre que l'entraînement se termine. Selon que vous l'exécutez sur la totalité ou sur un sous-ensemble de l'échantillon d'entraînement, cela prendra respectivement 20 ou 2 heures. Alors prenez quelques cafés et un bon livre à lire !

```py
trainer.train()
```

Une fois l'entraînement terminé, nous pouvons pousser le modèle et le *tokenizer* vers le *Hub* :

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

{:else}

Tout ce qu'il reste à faire est de configurer les hyperparamètres d'entraînement et d'appeler `compile()` et `fit()`. Nous utiliserons un programme de taux d'apprentissage avec un réchauffement pour améliorer la stabilité de l'entraînement :

```py
from transformers import create_optimizer
import tensorflow as tf

num_train_steps = len(tf_train_dataset)
optimizer, schedule = create_optimizer(
    init_lr=5e-5,
    num_warmup_steps=1_000,
    num_train_steps=num_train_steps,
    weight_decay_rate=0.01,
)
model.compile(optimizer=optimizer)

# Entraîner en mixed-precision float16
tf.keras.mixed_precision.set_global_policy("mixed_float16")
```

Maintenant, nous pouvons simplement appeler `model.fit()` et attendre que l'entraînement se termine. Selon que vous l'exécutez sur la totalité ou sur un sous-ensemble de l'échantillon d'entraînement, cela prendra respectivement 20 ou 2 heures. Alors prenez quelques cafés et un bon livre à lire ! Une fois l'entraînement terminé, nous pouvons pousser le modèle et le *tokenizer* vers le *Hub* :

```py
from transformers.keras_callbacks import PushToHubCallback

callback = PushToHubCallback(output_dir="codeparrot-ds", tokenizer=tokenizer)

model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback])
```

{/if}

<Tip>

✏️ **Essayez !** Il ne nous a fallu qu'une trentaine de lignes de code en plus des `TrainingArguments` pour passer des textes bruts à l'entraînement du GPT-2. Essayez-le avec votre propre jeu de données et voyez si vous pouvez obtenir de bons résultats ! 

</Tip>

<Tip>

{#if fw === 'pt'}

💡 Si vous avez accès à une machine avec plusieurs GPUs, essayez d'y exécuter le code. `Trainer` gère automatiquement plusieurs machines ce qui peut accélérer considérablement l'entraînement.

{:else}

💡 Si vous avez accès à une machine avec plusieurs GPUs, vous pouvez essayer d'utiliser `MirroredStrategy` pour accélérer considérablement l'entraînement. Vous devrez créer un objet `tf.distribute.MirroredStrategy` et vous assurer que toutes les méthodes `to_tf_dataset()` ou `prepare_tf_dataset()` ainsi que la création du modèle et l'appel à `fit()` sont tous exécutés dans `scope()`. Vous pouvez consulter la documentation à ce sujet [ici](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit).

{/if}

</Tip>

## Génération de code avec le pipeline

C'est maintenant le moment de vérité : voyons comment le modèle entraîné fonctionne réellement ! Nous pouvons voir dans les logs que la perte a diminué régulièrement, mais pour mettre le modèle à l'épreuve, regardons comment il fonctionne sur certains messages. Pour ce faire, nous allons envelopper le modèle dans un `pipeline` de génération de texte et, s'il y en a un de disponible, utiliser un GPU pour avoir des générations rapidement :

{#if fw === 'pt'}

```py
import torch
from transformers import pipeline

device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
pipe = pipeline(
    "text-generation", model="huggingface-course/codeparrot-ds", device=device
)
```

{:else}

```py
from transformers import pipeline

course_model = TFGPT2LMHeadModel.from_pretrained("huggingface-course/codeparrot-ds")
course_tokenizer = AutoTokenizer.from_pretrained("huggingface-course/codeparrot-ds")
pipe = pipeline(
    "text-generation", model=course_model, tokenizer=course_tokenizer, device=0
)
```

{/if}

Let's start with the simple task of creating a scatter plot:

```py
txt = """\
# créer des données
x = np.random.randn(100)
y = np.random.randn(100)

# créer un nuage de points avec x, y
"""
print(pipe(txt, num_return_sequences=1)[0]["generated_text"])
```

```python out
# créer des données
x = np.random.randn(100)
y = np.random.randn(100)

# créer un nuage de points avec x, y
plt.scatter(x, y)
```

Le résultat semble correct. Est-ce que cela fonctionne aussi pour une opération `pandas` ? Voyons si nous pouvons créer un `DataFrame` à partir de deux tableaux :

```py
txt = """\
# créer des données
x = np.random.randn(100)
y = np.random.randn(100)

# créer un tableau de données à partir de x et y
"""
print(pipe(txt, num_return_sequences=1)[0]["generated_text"])
```

```python out
# créer des données
x = np.random.randn(100)
y = np.random.randn(100)

# créer un tableau de données à partir de x et y
df = pd.DataFrame({'x': x, 'y': y})
df.insert(0,'x', x)
for
```

Bien, c'est la bonne réponse. Bien qu'il insère ensuite la colonne `x` à nouveau. Comme le nombre de *tokens* générés est limité, la boucle `for` suivante est coupée. Voyons si nous pouvons faire quelque chose d'un peu plus complexe et faire en sorte que le modèle nous aide à utiliser l'opération `groupby` : 

```py
txt = """\
# tableau de données avec profession, revenu et nom
df = pd.DataFrame({'profession': x, 'income':y, 'name': z})

# calculer le revenu moyen par profession
"""
print(pipe(txt, num_return_sequences=1)[0]["generated_text"])
```

```python out
# tableau de données avec profession, revenu et nom
df = pd.DataFrame({'profession': x, 'income':y, 'name': z})

# calculer le revenu moyen par profession
profession = df.groupby(['profession']).mean()
```

Pas mal, c'est la bonne façon de faire. Enfin, voyons si nous pouvons aussi l'utiliser pour `scikit-learn` et utiliser un modèle *Random Forest* :

```py
txt = """
# import random forest regressor from scikit-learn
from sklearn.ensemble import RandomForestRegressor

# entraînement du modèle de forêt aléatoire avec 300 estimateurs sur X, y :
"""
print(pipe(txt, num_return_sequences=1)[0]["generated_text"])
```

```python out
# import random forest regressor from scikit-learn
from sklearn.ensemble import RandomForestRegressor

# entraînement du modèle de forêt aléatoire avec 300 estimateurs sur X, y :
rf = RandomForestRegressor(n_estimators=300, random_state=random_state, max_depth=3)
rf.fit(X, y)
rf
```

{#if fw === 'tf'}

Au vu de ces quelques exemples, il semble que le modèle ait appris une partie de la syntaxe des bibliothèques Python de science des données. Bien sûr, nous devrions évaluer le modèle de manière plus approfondie avant de le déployer dans le monde réel, mais il s'agit tout de même d'un prototype impressionnant.

{:else}

Au vu de ces quelques exemples, il semble que le modèle ait appris une partie de la syntaxe des bibliothèques Python de science des données. Bien sûr, nous devrions évaluer le modèle de manière plus approfondie avant de le déployer dans le monde réel, mais il s'agit tout de même d'un prototype impressionnant. Parfois, il est nécessaire de personnaliser davantage l'entraînement du modèle afin d'obtenir les performances nécessaires pour un cas d'utilisation donné. Par exemple, que se passe-t-il si l'on souhaite mettre à jour dynamiquement la taille du batch ou si l'on dispose d'une boucle d'entraînement conditionnelle qui ignore les mauvais exemples à la volée ? Une option serait de sous-classer le `Trainer` et d'ajouter les changements nécessaires, mais parfois il est plus simple d'écrire la boucle d'entraînement à partir de zéro. C'est là qu'intervient 🤗 *Accelerate*.

{/if}

{#if fw === 'pt'}

## Entraîner avec 🤗 <i>Accelerate</i>

Nous avons vu comment entraîner un modèle avec le `Trainer`, qui permet une certaine personnalisation. Cependant, parfois nous voulons un contrôle total sur la boucle d'entraînement ou nous souhaitons faire quelques changements exotiques. Dans ce cas, 🤗 *Accelerate* est un excellent choix, et dans cette section, nous allons suivre les étapes pour l'utiliser pour entraîner notre modèle. Pour rendre les choses plus intéressantes, nous allons également ajouter une touche à la boucle d'entraînement.

<Youtube id="Hm8_PgVTFuc"/>

Puisque nous sommes principalement intéressés par l'autocomplétion pour les bibliothèques de science des données, il est logique de donner plus de poids aux échantillons d'entraînement qui utilisent davantage ces bibliothèques. Nous pouvons facilement identifier ces exemples grâce à l'utilisation de mots-clés tels que `plt`, `pd`, `sk`, `fit`, et `predict`, qui sont les noms d'importation les plus fréquents pour `matplotlib.pyplot`, `pandas`, et `sklearn` ainsi que les fonctions `fit` et `predict` de cette dernière. Si chacun d'entre eux est représenté par un seul *token*, nous pouvons facilement vérifier s'ils apparaissent dans la séquence d'entrée. Les *tokens* peuvent avoir un préfixe d'espacement, donc nous vérifierons aussi ces versions dans le vocabulaire du *tokenizer*. Pour vérifier que cela fonctionne, nous ajouterons un *token* de test qui devrait être divisé en plusieurs *tokens* :

```py
keytoken_ids = []
for keyword in [
    "plt",
    "pd",
    "sk",
    "fit",
    "predict",
    " plt",
    " pd",
    " sk",
    " fit",
    " predict",
    "testtest",
]:
    ids = tokenizer([keyword]).input_ids[0]
    if len(ids) == 1:
        keytoken_ids.append(ids[0])
    else:
        print(f"Keyword has not single token: {keyword}")
```

```python out
'Keyword has not single token: testtest'
```

Super, ça a l'air de bien fonctionner ! Nous pouvons maintenant écrire une fonction de perte personnalisée qui prend la séquence d'entrée, les logits et les *tokens* clés que nous venons de sélectionner comme entrées. Tout d'abord, nous devons aligner les logits et les entrées : la séquence d'entrée décalée d'une unité vers la droite forme les étiquettes, puisque le *token* suivant est l'étiquette du *token* actuel. Nous pouvons y parvenir en commençant les étiquettes à partir du deuxième *token* de la séquence d'entrée, puisque le modèle ne fait pas de prédiction pour le premier *token* de toute façon. Ensuite, nous coupons le dernier logit, car nous n'avons pas d'étiquette pour le *token* qui suit la séquence d'entrée complète. Avec cela, nous pouvons calculer la perte par échantillon et compter les occurrences de tous les mots-clés dans chaque échantillon. Enfin, nous calculons la moyenne pondérée sur tous les échantillons en utilisant les occurrences comme poids. Comme nous ne voulons pas rejeter tous les échantillons qui ne contiennent pas de mots-clés, nous ajoutons 1 aux poids :

```py
from torch.nn import CrossEntropyLoss
import torch


def keytoken_weighted_loss(inputs, logits, keytoken_ids, alpha=1.0):
    # Décalage pour que tokens < n prédisent n
    shift_labels = inputs[..., 1:].contiguous()
    shift_logits = logits[..., :-1, :].contiguous()
    # Calcul de la perte par token
    loss_fct = CrossEntropyLoss(reduce=False)
    loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))
    # Redimensionnement et perte moyenne par échantillon
    loss_per_sample = loss.view(shift_logits.size(0), shift_logits.size(1)).mean(axis=1)
    # Calculer et échelonner la pondération
    weights = torch.stack([(inputs == kt).float() for kt in keytoken_ids]).sum(
        axis=[0, 2]
    )
    weights = alpha * (1.0 + weights)
    # Calculer la moyenne pondérée
    weighted_loss = (loss_per_sample * weights).mean()
    return weighted_loss
```

Avant de commencer à entraîner avec cette nouvelle fonction de perte géniale, nous devons préparer quelques éléments :

- Nous avons besoin de chargeurs de données pour charger les données par batch.
- Nous devons définir les paramètres de décroissance des poids.
- De temps en temps, nous voulons évaluer, il est donc logique d'envelopper le code d'évaluation dans une fonction.

Commençons par les chargeurs de données. Nous avons seulement besoin de définir le format du jeu de données à `"torch"` et ensuite nous pouvons le passer à un PyTorch `DataLoader` avec la taille de batch appropriée :

```py
from torch.utils.data.dataloader import DataLoader

tokenized_dataset.set_format("torch")
train_dataloader = DataLoader(tokenized_dataset["train"], batch_size=32, shuffle=True)
eval_dataloader = DataLoader(tokenized_dataset["valid"], batch_size=32)
```

Ensuite, nous regroupons les paramètres de façon à ce que l'optimiseur sache lesquels bénéficieront d'une décroissance de poids supplémentaire. Habituellement, tous les termes de biais et les poids de la *LayerNorm* en sont exemptés. Voici comment nous pouvons le faire :

```py
weight_decay = 0.1


def get_grouped_params(model, no_decay=["bias", "LayerNorm.weight"]):
    params_with_wd, params_without_wd = [], []
    for n, p in model.named_parameters():
        if any(nd in n for nd in no_decay):
            params_without_wd.append(p)
        else:
            params_with_wd.append(p)
    return [
        {"params": params_with_wd, "weight_decay": weight_decay},
        {"params": params_without_wd, "weight_decay": 0.0},
    ]
```

Puisque nous voulons évaluer le modèle régulièrement sur l'ensemble de validation pendant l'entraînement, écrivons une fonction pour cela aussi. Elle passe simplement par le *dataloader* d'évaluation et rassemble toutes les pertes à travers les processus :

```py
def evaluate():
    model.eval()
    losses = []
    for step, batch in enumerate(eval_dataloader):
        with torch.no_grad():
            outputs = model(batch["input_ids"], labels=batch["input_ids"])

        losses.append(accelerator.gather(outputs.loss))
    loss = torch.mean(torch.cat(losses))
    try:
        perplexity = torch.exp(loss)
    except OverflowError:
        perplexity = float("inf")
    return loss.item(), perplexity.item()
```

Avec la fonction `evaluate()` nous pouvons rapporter la perte et la [perplexité](/course/fr/chapter7/3) à intervalles réguliers. Ensuite, nous redéfinissons notre modèle pour nous assurer que nous entraînons à nouveau à partir de zéro :

```py
model = GPT2LMHeadModel(config)
```

Nous pouvons ensuite définir notre optimiseur, en utilisant la fonction précédente pour diviser les paramètres de décroissance des poids :

```py
from torch.optim import AdamW

optimizer = AdamW(get_grouped_params(model), lr=5e-4)
```

Préparons maintenant le modèle, l'optimiseur et les chargeurs de données pour pouvoir commencer l'entraînement :

```py
from accelerate import Accelerator

accelerator = Accelerator(fp16=True)

model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, eval_dataloader
)
```

<Tip>

🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code commençant à la cellule ci-dessus dans une fonction d'entraînement dédiée. Voir le [chapitre 3](/course/fr/chapter3) pour plus de détails.

</Tip>

Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devons toujours faire cela après avoir préparé le *dataloader* car cette méthode modifiera sa longueur. Nous utilisons un programme linéaire classique du taux d'apprentissage à 0 :

```py
num_train_epochs = 1
num_update_steps_per_epoch = len(train_dataloader)
num_training_steps = num_train_epochs * num_update_steps_per_epoch

lr_scheduler = get_scheduler(
    name="linear",
    optimizer=optimizer,
    num_warmup_steps=1_000,
    num_training_steps=num_training_steps,
)
```

Enfin, pour pousser notre modèle vers le *Hub*, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub*, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix. Il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `get_full_repo_name()`) :

```py
from huggingface_hub import Repository, get_full_repo_name

model_name = "codeparrot-ds-accelerate"
repo_name = get_full_repo_name(model_name)
repo_name
```

```python out
'sgugger/codeparrot-ds-accelerate'
```

Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone existant du dépôt avec lequel nous travaillons :

```py
output_dir = "codeparrot-ds-accelerate"
repo = Repository(output_dir, clone_from=repo_name)
```

Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque.

Avant de nous entraîner, exécutons un test rapide pour voir si la fonction d'évaluation fonctionne correctement :

```py
evaluate()
```

```python out
(10.934126853942871, 56057.14453125)
```

Ce sont des valeurs très élevées pour la perte et la perplexité, mais ce n'est pas surprenant puisque nous n'avons pas encore entraîné le modèle. Avec cela, nous avons tout préparé pour écrire la partie principale du script d'entraînement : la boucle d'entraînement. Dans celle-ci, nous itérons sur le chargeur de données et transmettons les batchs au modèle. Avec les logits, nous pouvons alors évaluer notre fonction de perte personnalisée. Nous mettons à l'échelle la perte par le nombre d'étapes d'accumulation du gradient afin de ne pas créer de plus grandes pertes en agrégeant plus d'étapes. Avant de procéder à l'optimisation, nous découpons également les gradients pour une meilleure convergence. Enfin, tous les quelques pas, nous évaluons le modèle sur l'ensemble d'évaluation avec notre nouvelle fonction `evaluate()` :

```py
from tqdm.notebook import tqdm

gradient_accumulation_steps = 8
eval_steps = 5_000

model.train()
completed_steps = 0
for epoch in range(num_train_epochs):
    for step, batch in tqdm(
        enumerate(train_dataloader, start=1), total=len(train_dataloader)
    ):
        logits = model(batch["input_ids"]).logits
        loss = keytoken_weighted_loss(batch["input_ids"], logits, keytoken_ids)
        if step % 100 == 0:
            accelerator.print(
                {
                    "lr": get_lr(),
                    "samples": step * samples_per_step,
                    "steps": completed_steps,
                    "loss/train": loss.item() * gradient_accumulation_steps,
                }
            )
        loss = loss / gradient_accumulation_steps
        accelerator.backward(loss)
        if step % gradient_accumulation_steps == 0:
            accelerator.clip_grad_norm_(model.parameters(), 1.0)
            optimizer.step()
            lr_scheduler.step()
            optimizer.zero_grad()
            completed_steps += 1
        if (step % (eval_steps * gradient_accumulation_steps)) == 0:
            eval_loss, perplexity = evaluate()
            accelerator.print({"loss/eval": eval_loss, "perplexity": perplexity})
            model.train()
            accelerator.wait_for_everyone()
            unwrapped_model = accelerator.unwrap_model(model)
            unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
            if accelerator.is_main_process:
                tokenizer.save_pretrained(output_dir)
                repo.push_to_hub(
                    commit_message=f"Training in progress step {step}", blocking=False
                )
```

Et voilà, vous disposez maintenant de votre propre boucle d'entraînement personnalisée pour les modèles de langage causal tels que le GPT-2. Vous pouvez encore l'adapter à vos besoins. 

<Tip>

✏️ **Essayez !** Vous pouvez créer votre propre fonction de perte personnalisée, adaptée à votre cas d'utilisation, ou ajouter une autre étape personnalisée dans la boucle d'entraînement.

</Tip>

<Tip>

✏️ **Essayez !** Lorsque vous effectuez de longues expériences d'entraînement, il est bon d'enregistrer les mesures importantes à l'aide d'outils tels que *TensorBoard* ou *Weights & Biases*. Ajoutez l'un d'eux à la boucle d'entraînement afin de pouvoir toujours vérifier comment se déroule l'entraînement.

</Tip>

{/if}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter7/6.mdx" />

### Maîtriser le <i>NLP</i>
https://huggingface.co/learn/course/fr/chapter7/8.md

# Maîtriser le <i>NLP</i>

<CourseFloatingBanner
    chapter={7}
    classNames="absolute z-10 right-0 top-0"
/>

Si vous êtes arrivé jusqu'ici dans le cours, félicitations ! Vous avez maintenant toutes les connaissances et les outils nécessaires pour aborder (presque) n'importe quelle tâche de *NLP* avec 🤗 *Transformers* et l'écosystème d'*Hugging Face* !

Nous avons vu beaucoup d'assembleurs de données différents, c'est pourquoi nous avons fait cette petite vidéo pour vous aider à trouver lequel utiliser pour chaque tâche :

<Youtube id="-RPeakdlHYo"/>

Après avoir terminé ce tour d'horizon des principales tâches de *NLP*, vous devriez :

* savoir quelles architectures (encodeur, décodeur ou encodeur-décodeur) sont les mieux adaptées à chaque tâche,
* comprendre la différence entre le pré-entraînement et le *finetuning* d'un modèle de langage,
* savoir comment entraîner des *transformers* en utilisant soit l'API `Trainer` et les fonctionnalités d'entraînement distribué d' 🤗 *Accelerate* ou TensorFlow et Keras selon la piste que vous avez suivie,
* comprendre la signification et les limites de métriques comme ROUGE et BLEU pour les tâches de génération de texte,
* savoir comment interagir avec vos modèles *finetunés*, à la fois sur le *Hub* et en utilisant la `pipeline` de 🤗 *Transformers*.

Malgré toutes ces connaissances, il arrivera un moment où vous rencontrerez un *bug* difficile dans votre code ou aurez une question sur la façon de résoudre un problème de *NLP* particulier. Heureusement, la communauté d'*Hugging Face* est là pour vous aider ! Dans le dernier chapitre de cette partie du cours, nous allons explorer comment vous pouvez déboguer vos modèles et demander de l'aide efficacement.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter7/8.mdx" />

### Traduction
https://huggingface.co/learn/course/fr/chapter7/4.md

# Traduction

{#if fw === 'pt'}
<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section4_pt.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter7/section4_pt.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section4_pt.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter7/section4_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section4_tf.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter7/section4_tf.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section4_tf.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter7/section4_tf.ipynb"},
]} />

{/if}

Plongeons maintenant dans la traduction. Il s'agit d'une autre [tâche de séquence à séquence](/course/fr/chapitre1/7), ce qui signifie que c'est un problème qui peut être formulé comme le passage d'une séquence à une autre. En ce sens, le problème est assez proche de la tâche de [résumé](/course/fr/chapitre7/6) et vous pouvez adapter ce que nous allons voir ici à d'autres problèmes de séquence à séquence tels que :

- Le **transfert de style** ? c'est-à-dire créer un modèle qui *traduit* des textes écrits dans un certain style vers un autre (par exemple, du formel au décontracté ou de l'anglais shakespearien à l'anglais moderne).
- La **génération de réponse à des questions** c'est-à-dire créer un modèle qui génère des réponses à des questions compte tenu d'un contexte.

<Youtube id="1JvfrvZgi6c"/>

Si vous disposez d'un corpus de textes suffisamment important en deux langues différentes (ou plus), vous pouvez entraîner un nouveau modèle de traduction à partir de zéro, comme nous le ferons dans la section sur la [modélisation causale du langage](/course/fr/chapitre7/6). Il est toutefois plus rapide de *finetuner* un modèle de traduction existant, qu'il s'agisse d'un modèle multilingue comme mT5 ou mBART que vous souhaitez adapter à une paire de langues spécifique, ou même d'un modèle spécialisé dans la traduction d'une langue vers une autre que vous souhaitez adapter à votre corpus spécifique.

Dans cette section, nous allons *finetuner* un modèle Marian pré-entraîné pour traduire de l'anglais au français (puisque de nombreux employés de Hugging Face parlent ces deux langues) sur le jeu de données [KDE4](https://huggingface.co/datasets/kde4) qui est un jeu de données de fichiers localisés pour les applications [KDE](https://apps.kde.org/). Le modèle que nous utiliserons a été pré-entraîné sur un large corpus de textes français et anglais provenant du jeu de données [Opus](https://opus.nlpl.eu/) qui contient en fait le jeu de données KDE4. A noter que même si le modèle pré-entraîné que nous utilisons a vu ces données pendant son pré-entraînement, nous verrons que nous pouvons obtenir une meilleure version de ce modèle après un *finetuning*.

Une fois que nous aurons terminé, nous aurons un modèle capable de faire des prédictions comme celle-ci :

<iframe src="https://course-demos-marian-finetuned-kde4-en-to-fr.hf.space" frameBorder="0" height="350" title="Gradio app" class="block dark:hidden container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>
<iframe src="https://course-demos-marian-finetuned-kde4-en-to-fr-darkmode.hf.space" frameBorder="0" height="350" title="Gradio app" class="hidden dark:block container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

<a class="flex justify-center" href="/huggingface-course/marian-finetuned-kde4-en-to-fr">
<img class="block dark:hidden lg:w-3/5" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/modeleval-marian-finetuned-kde4-en-to-fr.png" alt="One-hot encoded labels for question answering."/>
<img class="hidden dark:block lg:w-3/5" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/modeleval-marian-finetuned-kde4-en-to-fr-dark.png" alt="One-hot encoded labels for question answering."/>
</a>

Comme dans les sections précédentes, vous pouvez trouver, télécharger et vérifier les précisions de ce modèle sur le [*Hub*](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr?text=This+plugin+allows+you+to+automatically+translate+web+pages+between+several+languages.).

## Préparation des données

Pour *finetuner* ou entraîner un modèle de traduction à partir de zéro, nous avons besoin d'un jeu de données adapté à cette tâche. Comme mentionné précédemment, nous utiliserons le jeu de données [KDE4](https://huggingface.co/datasets/kde4) dans cette section. Notez que vous pouvez adapter assez facilement le code pour utiliser vos propres données du moment que vous disposez de paires de phrases dans les deux langues que vous voulez traduire. Reportez-vous au [chapitre 5](/course/fr/chapter5) si vous avez besoin d'un rappel sur la façon de charger vos données personnalisées dans un `Dataset`.

### Le jeu de données KDE4

Comme d'habitude, nous téléchargeons notre jeu de données en utilisant la fonction `load_dataset()` :

```py
from datasets import load_dataset

raw_datasets = load_dataset("kde4", lang1="en", lang2="fr")
```

Si vous souhaitez travailler avec une autre paire de langues, 92 langues sont disponibles au total pour ce jeu de données. Vous pouvez les voir dans la [carte du jeu de données](https://huggingface.co/datasets/kde4).

<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/language_tags.png" alt="Language available for the KDE4 dataset." width="100%">

Jetons un coup d'œil au jeu de données :

```py
raw_datasets
```

```python out
DatasetDict({
    train: Dataset({
        features: ['id', 'translation'],
        num_rows: 210173
    })
})
```

Nous avons 210 173 paires de phrases. Cependant regroupées dans un seul échantillon. Nous devrons donc créer notre propre jeu de validation. Comme nous l'avons vu dans le [chapitre 5](/course/fr/chapter5), un `Dataset` possède une méthode `train_test_split()` qui peut nous aider. Nous allons fournir une graine pour la reproductibilité :

```py
split_datasets = raw_datasets["train"].train_test_split(train_size=0.9, seed=20)
split_datasets
```

```python out
DatasetDict({
    train: Dataset({
        features: ['id', 'translation'],
        num_rows: 189155
    })
    test: Dataset({
        features: ['id', 'translation'],
        num_rows: 21018
    })
})
```

Nous pouvons renommer la clé `test` en `validation` comme ceci :

```py
split_datasets["validation"] = split_datasets.pop("test")
```

Examinons maintenant un élément de ce jeu de données :

```py
split_datasets["train"][1]["translation"]
```

```python out
{'en': 'Default to expanded threads',
 'fr': 'Par défaut, développer les fils de discussion'}
```

Nous obtenons un dictionnaire contenant deux phrases dans la paire de langues qui nous intéresse. 
Une particularité de ce jeu de données rempli de termes techniques informatiques est qu'ils sont tous entièrement traduits en français. Cependant, les ingénieurs français laissent la plupart des mots spécifiques à l'informatique en anglais lorsqu'ils parlent. Ici, par exemple, le mot « *threads* » pourrait très bien apparaître dans une phrase française, surtout dans une conversation technique. Mais dans ce jeu de données, il a été traduit en « fils de discussion ». Le modèle pré-entraîné que nous utilisons (qui a été pré-entraîné sur un plus grand corpus de phrases françaises et anglaises) prend l'option de laisser le mot tel quel :

```py
from transformers import pipeline

model_checkpoint = "Helsinki-NLP/opus-mt-en-fr"
translator = pipeline("translation", model=model_checkpoint)
translator("Default to expanded threads")
```

```python out
[{'translation_text': 'Par défaut pour les threads élargis'}]
```

Un autre exemple de ce comportement peut être observé avec le mot  « *plugin* » qui n'est pas officiellement un mot français mais que la plupart des francophones comprendront et ne prendront pas la peine de traduire.
Dans le jeu de données KDE4, ce mot a été traduit en français par le plus officiel « module d'extension » :

```py
split_datasets["train"][172]["translation"]
```

```python out
{'en': 'Unable to import %1 using the OFX importer plugin. This file is not the correct format.',
 'fr': "Impossible d'importer %1 en utilisant le module d'extension d'importation OFX. Ce fichier n'a pas un format correct."}
```

Notre modèle pré-entraîné, lui, s'en tient au mot anglais :

```py
translator(
    "Unable to import %1 using the OFX importer plugin. This file is not the correct format."
)
```

```python out
[{'translation_text': "Impossible d'importer %1 en utilisant le plugin d'importateur OFX. Ce fichier n'est pas le bon format."}]
```

Il sera intéressant de voir si notre modèle *finetuné* tient compte de ces particularités (alerte *spoiler* : il le fera).

<Youtube id="0Oxphw4Q9fo"/>

<Tip>

✏️ **A votre tour !** Un autre mot anglais souvent utilisé en français est « *email* ». Trouvez le premier échantillon dans l'échantillon d'entraînement qui utilise ce mot. Comment est-il traduit ? Comment le modèle pré-entraîné traduit-il cette même phrase ?

</Tip>

### Traitement des données

<Youtube id="XAR8jnZZuUs"/>

Vous devriez maintenant connaître le principe : les textes doivent tous être convertis en ensembles d'ID de *tokens* pour que le modèle puisse leur donner un sens. Pour cette tâche, nous aurons besoin de tokeniser les entrées et les cibles. Notre première tâche est de créer notre objet `tokenizer`. Comme indiqué précédemment, nous utiliserons un modèle pré-entraîné Marian English to French. Si vous essayez ce code avec une autre paire de langues, assurez-vous d'adapter le *checkpoint* du modèle. L'organisation [Helsinki-NLP](https://huggingface.co/Helsinki-NLP) fournit plus de mille modèles dans plusieurs langues.

```python
from transformers import AutoTokenizer

model_checkpoint = "Helsinki-NLP/opus-mt-en-fr"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="tf")
```

Vous pouvez remplacer le `model_checkpoint` par un tout autre modèle disponible sur le [*Hub*](https://huggingface.co/models) qui aurait votre préférence, ou par un dossier en local où vous avez sauvegardé un modèle pré-entraîné et un *tokenizer*.

<Tip>

💡 Si vous utilisez un *tokenizer* multilingue tel que mBART, mBART-50 ou M2M100, vous devrez définir les codes de langue de vos entrées et cibles dans le *tokenizer* en définissant `tokenizer.src_lang` et `tokenizer.tgt_lang` aux bonnes valeurs.

</Tip>

La préparation de nos données est assez simple. Il y a juste une chose à retenir : vous traitez les entrées comme d'habitude, mais pour les cibles, vous devez envelopper le *tokenizer* dans le gestionnaire de contexte `as_target_tokenizer()`.

Un gestionnaire de contexte en Python est introduit avec l'instruction `with` et est utile lorsque vous avez deux opérations liées à exécuter en paire. L'exemple le plus courant est lorsque vous écrivez ou lisez un fichier, ce qui est souvent fait dans une instruction comme :

```
with open(file_path) as f:
    content = f.read()
```

Ici, les deux opérations connexes qui sont exécutées en paire sont les actions d'ouverture et de fermeture du fichier. L'objet correspondant au fichier ouvert `f` n'existe qu'à l'intérieur du bloc indenté sous le `with`. L'ouverture se produit avant ce bloc et la fermeture à la fin du bloc.

Dans le cas présent, le gestionnaire de contexte `as_target_tokenizer()` va définir le *tokenizer* dans la langue de sortie (ici, le français) avant l'exécution du bloc indenté, puis le redéfinir dans la langue d'entrée (ici, l'anglais).

Ainsi, le prétraitement d'un échantillon ressemble à ceci :

```python
en_sentence = split_datasets["train"][1]["translation"]["en"]
fr_sentence = split_datasets["train"][1]["translation"]["fr"]

inputs = tokenizer(en_sentence)
with tokenizer.as_target_tokenizer():
    targets = tokenizer(fr_sentence)
```

Si nous oublions de tokeniser les cibles dans le gestionnaire de contexte, elles seront tokenisées par le *tokenizer* d'entrée, ce qui dans le cas d'un modèle Marian, ne va pas du tout bien se passer :

```python
wrong_targets = tokenizer(fr_sentence)
print(tokenizer.convert_ids_to_tokens(wrong_targets["input_ids"]))
print(tokenizer.convert_ids_to_tokens(targets["input_ids"]))
```

```python out
['▁Par', '▁dé', 'f', 'aut', ',', '▁dé', 've', 'lop', 'per', '▁les', '▁fil', 's', '▁de', '▁discussion', '</s>']
['▁Par', '▁défaut', ',', '▁développer', '▁les', '▁fils', '▁de', '▁discussion', '</s>']
```

Comme on peut le voir, utiliser le *tokenizer* anglais pour prétraiter une phrase française donne un batch de *tokens* plus important, puisque le *tokenizer* ne connaît aucun mot français (sauf ceux qui apparaissent aussi en anglais, comme « discussion »).

Les `inputs` et les `targets` sont des dictionnaires avec nos clés habituelles (identifiants d'entrée, masque d'attention, etc.). La dernière étape est de définir une clé `"labels"` dans les entrées. Nous faisons cela dans la fonction de prétraitement que nous allons appliquer sur les jeux de données :

```python
max_input_length = 128
max_target_length = 128


def preprocess_function(examples):
    inputs = [ex["en"] for ex in examples["translation"]]
    targets = [ex["fr"] for ex in examples["translation"]]
    model_inputs = tokenizer(inputs, max_length=max_input_length, truncation=True)

    # Configurer le tokenizer pour les cibles.
    with tokenizer.as_target_tokenizer():
        labels = tokenizer(targets, max_length=max_target_length, truncation=True)

    model_inputs["labels"] = labels["input_ids"]
    return model_inputs
```

Notez que nous avons fixé des longueurs maximales similaires pour nos entrées et nos sorties. Comme les textes que nous traitons semblent assez courts, nous utilisons 128.

<Tip>

💡 Si vous utilisez un modèle T5 (plus précisément, un des *checkpoints* `t5-xxx`), le modèle s'attendra à ce que les entrées aient un préfixe indiquant la tâche à accomplir, comme `translate: English to French:`.

</Tip>

<Tip warning={true}>

⚠️ Nous ne faisons pas attention au masque d'attention des cibles car le modèle ne s'y attend pas. Au lieu de cela, les étiquettes correspondant à un *token* de *padding* doivent être mises à `-100` afin qu'elles soient ignorées dans le calcul de la perte. Cela sera fait par notre assembleur de données plus tard puisque nous appliquons le *padding* dynamique, mais si vous utilisez le *padding* ici, vous devriez adapter la fonction de prétraitement pour mettre toutes les étiquettes qui correspondent au *token* de *padding* à `-100`.

</Tip>

Nous pouvons maintenant appliquer ce prétraitement en une seule fois sur toutes les échantillons de notre jeu de données :

```py
tokenized_datasets = split_datasets.map(
    preprocess_function,
    batched=True,
    remove_columns=split_datasets["train"].column_names,
)
```

Maintenant que les données ont été prétraitées, nous sommes prêts à *finetuner* notre modèle pré-entraîné !

{#if fw === 'pt'}

## <i>Finetuner</i> le modèle avec l'API `Trainer`

Le code actuel utilisant `Trainer` sera le même que précédemment, avec juste un petit changement : nous utilisons ici [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer) qui est une sous-classe de `Trainer` qui nous permet de traiter correctement l'évaluation, en utilisant la méthode `generate()` pour prédire les sorties à partir des entrées. Nous y reviendrons plus en détail lorsque nous parlerons du calcul de la métrique.

Tout d'abord, nous avons besoin d'un modèle à *finetuner*. Nous allons utiliser l'API habituelle `AutoModel` :

```py
from transformers import AutoModelForSeq2SeqLM

model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)
```

{:else}

## <i>Finetuner</i> du modèle avec Keras

Tout d'abord, nous avons besoin d'un modèle à *finetuner*. Nous allons utiliser l'API habituelle `AutoModel` :

```py
from transformers import TFAutoModelForSeq2SeqLM

model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True)
```

<Tip warning={false}>

💡 Le *checkpoint* `Helsinki-NLP/opus-mt-en-fr` ne dispose que de poids PyTorch, vous aurez donc une erreur si vous essayez de charger le modèle sans utiliser l'argument `from_pt=True` dans la méthode `from_pretrained()`. Lorsque vous spécifiez `from_pt=True`, la bibliothèque téléchargera et convertira automatiquement les poids PyTorch pour vous. Comme vous pouvez le constater, c'est très simple de passer d'un *framework* à l'autre dans 🤗 *Transformers* !

</Tip>

{/if}

Notez que cette fois-ci, nous utilisons un modèle qui a été entraîné sur une tâche de traduction et qui peut déjà être utilisé, donc il n'y a pas d'avertissement concernant les poids manquants ou ceux nouvellement initialisés.

### Assemblage des données

Nous aurons besoin d'un assembleur de données pour gérer le rembourrage pour la mise en batchs dynamique. Ici, nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans le [chapitre 3](/course/fr/chapter3) car cela ne rembourre que les entrées (identifiants d'entrée, masque d'attention, et *token* de type identifiants). Nos étiquettes doivent également être rembourrées à la longueur maximale rencontrée dans les étiquettes. Et, comme mentionné précédemment, la valeur de remplissage utilisée pour remplir les étiquettes doit être `-100` et non le *token* de *padding* du *tokenizer* afin de s'assurer que ces valeurs soient ignorées dans le calcul de la perte.

Tout ceci est réalisé par un [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq). Comme le `DataCollatorWithPadding`, il prend le `tokenizer` utilisé pour prétraiter les entrées, mais également le `model`. C'est parce que cet assembleur de données est également responsable de la préparation des identifiants d'entrée du décodeur, qui sont des versions décalées des étiquettes avec un *token* spécial au début. Comme ce décalage est effectué de manière légèrement différente selon les architectures, le `DataCollatorForSeq2Seq` a besoin de connaître l'objet `model` :

{#if fw === 'pt'}

```py
from transformers import DataCollatorForSeq2Seq

data_collator = DataCollatorForSeq2Seq(tokenizer, model=model)
```

{:else}

```py
from transformers import DataCollatorForSeq2Seq

data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="tf")
```

{/if}

Pour le tester sur quelques échantillons, nous l'appelons simplement sur une liste d'exemples de notre échantillon d'entrainement tokénisé :

```py
batch = data_collator([tokenized_datasets["train"][i] for i in range(1, 3)])
batch.keys()
```

```python out
dict_keys(['attention_mask', 'input_ids', 'labels', 'decoder_input_ids'])
```

Nous pouvons vérifier que nos étiquettes ont été rembourrées à la longueur maximale du batch, en utilisant `-100` :

```py
batch["labels"]
```

```python out
tensor([[  577,  5891,     2,  3184,    16,  2542,     5,  1710,     0,  -100,
          -100,  -100,  -100,  -100,  -100,  -100],
        [ 1211,     3,    49,  9409,  1211,     3, 29140,   817,  3124,   817,
           550,  7032,  5821,  7907, 12649,     0]])
```

Nous pouvons aussi jeter un coup d'œil aux identifiants d'entrée du décodeur, pour voir qu'il s'agit de versions décalées des étiquettes :

```py
batch["decoder_input_ids"]
```

```python out
tensor([[59513,   577,  5891,     2,  3184,    16,  2542,     5,  1710,     0,
         59513, 59513, 59513, 59513, 59513, 59513],
        [59513,  1211,     3,    49,  9409,  1211,     3, 29140,   817,  3124,
           817,   550,  7032,  5821,  7907, 12649]])
```

Voici les étiquettes des premier et deuxième éléments de notre jeu de données :

```py
for i in range(1, 3):
    print(tokenized_datasets["train"][i]["labels"])
```

```python out
[577, 5891, 2, 3184, 16, 2542, 5, 1710, 0]
[1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124, 817, 550, 7032, 5821, 7907, 12649, 0]
```

{#if fw === 'pt'}

Nous allons transmettre ce `data_collator` au `Seq2SeqTrainer`. Ensuite, jetons un coup d'oeil à la métrique.

{:else}

Nous pouvons maintenant utiliser ce `data_collator` pour convertir chacun de nos jeux de données en un `tf.data.Dataset`, prêt pour l'entraînement :

```python
model.prepare_tf_dataset(
    tokenized_datasets["train"],
    collate_fn=data_collator,
    shuffle=True,
    batch_size=32,
)
tf_eval_dataset = model.prepare_tf_dataset(
    tokenized_datasets["validation"],
    collate_fn=data_collator,
    shuffle=False,
    batch_size=16,
)
```

{/if}


### Métriques

<Youtube id="M05L1DhFqcw"/>

{#if fw === 'pt'}

La fonctionnalité que `Seq2SeqTrainer` ajoute à sa superclasse `Trainer` est la possibilité d'utiliser la méthode `generate()` pendant l'évaluation ou la prédiction. Pendant l'entraînement, le modèle utilisera les `decoder_input_ids` avec un masque d'attention assurant qu'il n'utilise pas les *tokens* après le *token* qu'il essaie de prédire, pour accélérer l'entraînement. Pendant l'inférence, nous ne pourrons pas les utiliser puisque nous n'aurons pas d'étiquettes. Ainsi c'est une bonne idée d'évaluer notre modèle avec la même configuration.

Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1/6), le décodeur effectue l'inférence en prédisant les *tokens* un par un. C'est quelque chose qui est implémenté en coulisses dans 🤗 *Transformers* par la méthode `generate()`. Le `Seq2SeqTrainer` nous laissera utiliser cette méthode pour l'évaluation si nous indiquons `predict_with_generate=True`.

{/if}

La métrique traditionnelle utilisée pour la traduction est le [score BLEU](https://en.wikipedia.org/wiki/BLEU), introduit dans [un article de 2002](https://aclanthology.org/P02-1040.pdf) par Kishore Papineni et al. Le score BLEU évalue dans quelle mesure les traductions sont proches de leurs étiquettes. Il ne mesure pas l'intelligibilité ou l'exactitude grammaticale des résultats générés par le modèle, mais utilise des règles statistiques pour garantir que tous les mots des résultats générés apparaissent également dans les cibles. En outre, il existe des règles qui pénalisent les répétitions des mêmes mots s'ils ne sont pas également répétés dans les cibles (pour éviter que le modèle ne produise des phrases telles que « the the the the the the the ») et les phrases produites qui sont plus courtes que celles des cibles (pour éviter que le modèle ne produise des phrases telles que « the »).

L'une des faiblesses de BLEU est qu'il s'attend à ce que le texte soit déjà tokenisé, ce qui rend difficile la comparaison des scores entre les modèles qui utilisent différents *tokenizers*. Par conséquent, la mesure la plus couramment utilisée aujourd'hui pour évaluer les modèles de traduction est [SacreBLEU](https://github.com/mjpost/sacrebleu) qui remédie à cette faiblesse (et à d'autres) en standardisant l'étape de tokenisation. Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *SacreBLEU* :

```py
!pip install sacrebleu
```

Nous pouvons ensuite charger ce score via `evaluate.load()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) :

```py
import evaluate

metric = evaluate.load("sacrebleu")
```

Cette métrique prend des textes comme entrées et cibles. Elle est conçue pour accepter plusieurs cibles acceptables car il y a souvent plusieurs traductions possibles d'une même phrase. Le jeu de données que nous utilisons n'en fournit qu'une seule, mais en NLP, il n'est pas rare de trouver des jeux de données ayant plusieurs phrases comme étiquettes. Ainsi, les prédictions doivent être une liste de phrases mais les références doivent être une liste de listes de phrases.

Essayons un exemple :

```py
predictions = [
    "This plugin lets you translate web pages between several languages automatically."
]
references = [
    [
        "This plugin allows you to automatically translate web pages between several languages."
    ]
]
metric.compute(predictions=predictions, references=references)
```

```python out
{'score': 46.750469682990165,
 'counts': [11, 6, 4, 3],
 'totals': [12, 11, 10, 9],
 'precisions': [91.67, 54.54, 40.0, 33.33],
 'bp': 0.9200444146293233,
 'sys_len': 12,
 'ref_len': 13}
```

Cela donne un score BLEU de 46.75, ce qui est plutôt bon. A titre de comparaison, le *Transformer* original dans l'article [*Attention Is All You Need*](https://arxiv.org/pdf/1706.03762.pdf) a obtenu un score BLEU de 41.8 sur une tâche de traduction similaire entre l'anglais et le français ! (Pour plus d'informations sur les métriques individuelles, comme `counts` et `bp`, voir le [dépôt SacreBLEU](https://github.com/mjpost/sacrebleu/blob/078c440168c6adc89ba75fe6d63f0d922d42bcfe/sacrebleu/metrics/bleu.py#L74). D'autre part, si nous essayons avec les deux mauvais types de prédictions (répétitions ou prédiction trop courte) qui sortent souvent des modèles de traduction, nous obtiendrons des scores BLEU plutôt mauvais :

```py
predictions = ["This This This This"]
references = [
    [
        "This plugin allows you to automatically translate web pages between several languages."
    ]
]
metric.compute(predictions=predictions, references=references)
```

```python out
{'score': 1.683602693167689,
 'counts': [1, 0, 0, 0],
 'totals': [4, 3, 2, 1],
 'precisions': [25.0, 16.67, 12.5, 12.5],
 'bp': 0.10539922456186433,
 'sys_len': 4,
 'ref_len': 13}
```

```py
predictions = ["This plugin"]
references = [
    [
        "This plugin allows you to automatically translate web pages between several languages."
    ]
]
metric.compute(predictions=predictions, references=references)
```

```python out
{'score': 0.0,
 'counts': [2, 1, 0, 0],
 'totals': [2, 1, 0, 0],
 'precisions': [100.0, 100.0, 0.0, 0.0],
 'bp': 0.004086771438464067,
 'sys_len': 2,
 'ref_len': 13}
```

Le score peut aller de 0 à 100. Plus il est élevé, mieux c'est.

{#if fw === 'tf'}

Pour passer des sorties du modèle aux textes utilisables par la métrique, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100` dans les étiquettes. Le *tokenizer* fera automatiquement la même chose pour le *token* de remplissage. Définissons une fonction qui prend notre modèle et un jeu de données et calcule des métriques sur ceux-ci. Nous allons également utiliser une astuce qui augmente considérablement les performances : compiler notre code de génération avec [XLA](https://www.tensorflow.org/xla), le compilateur d'algèbre linéaire accéléré de TensorFlow. XLA applique diverses optimisations au graphe de calcul du modèle, ce qui permet d'améliorer considérablement la vitesse et l'utilisation de la mémoire. Comme décrit dans un article du [blog d’Hugging Face](https://huggingface.co/blog/tf-xla-generate), XLA fonctionne mieux lorsque nos formes d'entrée ne varient pas trop. Pour gérer cela, nous allons rembourrer nos entrées à des multiples de 128, et créer un nouveau jeu de données avec l’assembleur de rembourrage. Puis nous appliquerons le décorateur `@tf.function(jit_compile=True)` à notre fonction de génération, qui marque la fonction entière pour la compilation avec XLA. 

```py
import numpy as np
import tensorflow as tf
from tqdm import tqdm

generation_data_collator = DataCollatorForSeq2Seq(
    tokenizer, model=model, return_tensors="tf", pad_to_multiple_of=128
)
tf_generate_dataset = model.prepare_tf_dataset(
    tokenized_datasets["validation"],
    collate_fn=generation_data_collator,
    shuffle=False,
    batch_size=8,
)


@tf.function(jit_compile=True)
def generate_with_xla(batch):
    return model.generate(
        input_ids=batch["input_ids"],
        attention_mask=batch["attention_mask"],
        max_new_tokens=128,
    )


def compute_metrics():
    all_preds = []
    all_labels = []

    for batch, labels in tqdm(tf_generate_dataset):
        predictions = generate_with_xla(batch)
        decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True)
        labels = labels.numpy()
        labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
        decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)
        decoded_preds = [pred.strip() for pred in decoded_preds]
        decoded_labels = [[label.strip()] for label in decoded_labels]
        all_preds.extend(decoded_preds)
        all_labels.extend(decoded_labels)

    result = metric.compute(predictions=all_preds, references=all_labels)
    return {"bleu": result["score"]}
```

{:else}

Pour passer des sorties du modèle aux textes utilisables par la métrique, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100` dans les étiquettes. Le *tokenizer* fera automatiquement la même chose pour le *token* de *padding* :

```py
import numpy as np


def compute_metrics(eval_preds):
    preds, labels = eval_preds
    # Dans le cas où le modèle retourne plus que les logits de prédiction
    if isinstance(preds, tuple):
        preds = preds[0]

    decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True)

    # Remplacer les -100 dans les étiquettes car nous ne pouvons pas les décoder
    labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
    decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)

    # Quelques post-traitements simples
    decoded_preds = [pred.strip() for pred in decoded_preds]
    decoded_labels = [[label.strip()] for label in decoded_labels]

    result = metric.compute(predictions=decoded_preds, references=decoded_labels)
    return {"bleu": result["score"]}
```

{/if}

Maintenant que c'est fait, nous sommes prêts à *finetuner* notre modèle !


### <i>Finetuner</i> le modèle

La première étape consiste à se connecter à Hugging Face, afin de pouvoir télécharger vos résultats sur le *Hub*. Il y a une fonction pratique pour vous aider à le faire dans un *notebook* :

```python
from huggingface_hub import notebook_login

notebook_login()
```

Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face.

Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal :

```bash
huggingface-cli login
```

{#if fw === 'tf'}

Avant de commencer, voyons quel type de résultats nous obtenons avec notre modèle sans entraînement :

```py
print(compute_metrics())
```

```
{'bleu': 33.26983701454733}
```

Une fois ceci fait, nous pouvons préparer tout ce dont nous avons besoin pour compiler et entraîner notre modèle. Notez l'utilisation de `tf.keras.mixed_precision.set_global_policy("mixed_float16")`. Ceci indiquera à Keras de s'entraîner en utilisant float16, ce qui peut donner un gain de vitesse significatif sur les GPUs qui le supportent (Nvidia 20xx/V100 ou plus récent).

```python
from transformers import create_optimizer
from transformers.keras_callbacks import PushToHubCallback
import tensorflow as tf

# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch,
# puis multiplié par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un tf.data.Dataset,
# et non le jeu de données original donc son len() est déjà num_samples // batch_size.
num_epochs = 3
num_train_steps = len(tf_train_dataset) * num_epochs

optimizer, schedule = create_optimizer(
    init_lr=5e-5,
    num_warmup_steps=0,
    num_train_steps=num_train_steps,
    weight_decay_rate=0.01,
)
model.compile(optimizer=optimizer)

# Entraîner en mixed-precision float16
tf.keras.mixed_precision.set_global_policy("mixed_float16")
```

Ensuite, nous définissons un `PushToHubCallback` pour télécharger notre modèle sur le *Hub* pendant l'entraînement, comme nous l'avons vu dans la [section 2](/course/fr/chapter7/2), puis nous entraînons simplement le modèle avec ce *callback* :

```python
from transformers.keras_callbacks import PushToHubCallback

callback = PushToHubCallback(
    output_dir="marian-finetuned-kde4-en-to-fr", tokenizer=tokenizer
)

model.fit(
    tf_train_dataset,
    validation_data=tf_eval_dataset,
    callbacks=[callback],
    epochs=num_epochs,
)
```

Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser le modèle avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` dans `Seq2SeqTrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace et nommé après le répertoire de sortie que vous avez défini. Ici ce sera `"sgugger/marian-finetuned-kde4-en-to-fr"` (qui est le modèle que nous avons lié au début de cette section).

<Tip>

💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de l'appel de `model.fit()` et devrez définir un nouveau nom.

</Tip>

Enfin, voyons à quoi ressemblent nos métriques maintenant que l'entraînement est terminé :

```py
print(compute_metrics())
```

```
{'bleu': 57.334066271545865}
```

À ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de traduction. Félicitations !

{:else}

Une fois ceci fait, nous pouvons définir notre `Seq2SeqTrainingArguments`. Comme pour le `Trainer`, nous utilisons une sous-classe de `TrainingArguments` qui contient quelques champs supplémentaires :

```python
from transformers import Seq2SeqTrainingArguments

args = Seq2SeqTrainingArguments(
    f"marian-finetuned-kde4-en-to-fr",
    evaluation_strategy="no",
    save_strategy="epoch",
    learning_rate=2e-5,
    per_device_train_batch_size=32,
    per_device_eval_batch_size=64,
    weight_decay=0.01,
    save_total_limit=3,
    num_train_epochs=3,
    predict_with_generate=True,
    fp16=True,
    push_to_hub=True,
)
```

En dehors des hyperparamètres habituels (comme le taux d'apprentissage, le nombre d'époques, la taille des batchs et une le taux de décroissance des poids), voici quelques changements par rapport à ce que nous avons vu dans les sections précédentes :

- Nous ne définissons pas d'évaluation car elle prend du temps. Nous allons juste évaluer une fois notre modèle avant l'entraînement et après.
- Nous avons mis `fp16=True`, ce qui accélère l'entraînement sur les GPUs modernes.
- Nous définissons `predict_with_generate=True`, comme discuté ci-dessus.
- Nous utilisons `push_to_hub=True` pour télécharger le modèle sur le *Hub* à la fin de chaque époque.

Notez que vous pouvez spécifier le nom complet du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` à `Seq2SeqTrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace et nommé d'après le répertoire de sortie que vous avez défini. Dans notre cas ce sera `"sgugger/marian-finetuned-kde4-en-to-fr"` (qui est le modèle que nous avons lié au début de cette section).

<Tip>

💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de la définition de votre `Seq2SeqTrainer` et devrez définir un nouveau nom.

</Tip>


Enfin, nous passons tout au `Seq2SeqTrainer` :

```python
from transformers import Seq2SeqTrainer

trainer = Seq2SeqTrainer(
    model,
    args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation"],
    data_collator=data_collator,
    tokenizer=tokenizer,
    compute_metrics=compute_metrics,
)
```

Avant d'entraîner, nous allons d'abord regarder le score obtenu par notre modèle, pour vérifier que nous n'aggravons pas les choses avec notre *finetuning*. Cette commande va prendre un peu de temps, vous pouvez donc prendre un café pendant qu'elle s'exécute :

```python
trainer.evaluate(max_length=max_target_length)
```

```python out
{'eval_loss': 1.6964408159255981,
 'eval_bleu': 39.26865061007616,
 'eval_runtime': 965.8884,
 'eval_samples_per_second': 21.76,
 'eval_steps_per_second': 0.341}
```

Un score BLEU de 39 n'est pas trop mauvais, ce qui reflète le fait que notre modèle est déjà bon pour traduire des phrases anglaises en phrases françaises.

Vient ensuite l'entraînement, qui prendra également un peu de temps :

```python
trainer.train()
```

Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous serez en mesure de reprendre votre entraînement sur une autre machine si nécessaire.

Une fois l'entraînement terminé, nous évaluons à nouveau notre modèle. Avec un peu de chance, nous verrons une amélioration du score BLEU !

```py
trainer.evaluate(max_length=max_target_length)
```

```python out
{'eval_loss': 0.8558505773544312,
 'eval_bleu': 52.94161337775576,
 'eval_runtime': 714.2576,
 'eval_samples_per_second': 29.426,
 'eval_steps_per_second': 0.461,
 'epoch': 3.0}
```

C'est une amélioration de près de 14 points, ce qui est formidable.

Enfin, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la dernière version du modèle. `Trainer` rédige également une carte de modèle avec tous les résultats de l'évaluation et la télécharge. Cette carte de modèle contient des métadonnées qui aident le *Hub* à choisir le *widget* pour l'inférence. Habituellement, il n'y a pas besoin de dire quoi que ce soit car il peut inférer le bon *widget* à partir de la classe du modèle, mais dans ce cas, la même classe de modèle peut être utilisée pour toutes sortes de problèmes de séquence à séquence. Ainsi nous spécifions que c'est un modèle de traduction :

```py
trainer.push_to_hub(tags="translation", commit_message="Training complete")
```

Cette commande renvoie l'URL du commit qu'elle vient de faire, si vous voulez l'inspecter :

```python out
'https://huggingface.co/sgugger/marian-finetuned-kde4-en-to-fr/commit/3601d621e3baae2bc63d3311452535f8f58f6ef3'
```

À ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de traduction. Félicitations !

Si vous souhaitez vous plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*.

{/if}

{#if fw === 'pt'}

## Une boucle d'entraînement personnalisée

Jetons maintenant un coup d'œil à la boucle d'entraînement complète afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à ce que nous avons fait dans la [section 2](/course/fr/chapter7/2) et dans le [chapitre 3](/course/fr/chapter3/4).

### Préparer le tout pour l'entraînement

Vous avez vu tout cela plusieurs fois maintenant, donc nous allons passer en revue le code assez rapidement. D'abord, nous allons construire le `DataLoader` à partir de nos jeux de données, après avoir configuré les jeux de données au format `"torch"` pour obtenir les tenseurs PyTorch :

```py
from torch.utils.data import DataLoader

tokenized_datasets.set_format("torch")
train_dataloader = DataLoader(
    tokenized_datasets["train"],
    shuffle=True,
    collate_fn=data_collator,
    batch_size=8,
)
eval_dataloader = DataLoader(
    tokenized_datasets["validation"], collate_fn=data_collator, batch_size=8
)
```

Ensuite, nous réinstantifions notre modèle pour nous assurer que nous ne poursuivons pas le *finetuning* précédent et que nous repartons du modèle pré-entraîné :

```py
model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)
```

Nous aurons alors besoin d'un optimiseur :

```py
from torch.optim import AdamW

optimizer = AdamW(model.parameters(), lr=2e-5)
```

Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()`. Rappelez-vous que si vous voulez entraîner sur des TPUs dans un *notebook* de Colab, vous devez déplacer tout ce code dans une fonction d'entraînement et ne devrait pas exécuter une cellule qui instancie un `Accelerator`.

```py
from accelerate import Accelerator

accelerator = Accelerator()
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, eval_dataloader
)
```

Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devrions toujours faire cela après avoir préparé le chargeur de données car cette méthode va changer la longueur du `DataLoader`. Nous utilisons un programme linéaire classique du taux d'apprentissage à 0 :

```py
from transformers import get_scheduler

num_train_epochs = 3
num_update_steps_per_epoch = len(train_dataloader)
num_training_steps = num_train_epochs * num_update_steps_per_epoch

lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)
```

Enfin, pour pousser notre modèle vers le *Hub*, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub* si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix, il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `get_full_repo_name()`) :

```py
from huggingface_hub import Repository, get_full_repo_name

model_name = "marian-finetuned-kde4-en-to-fr-accelerate"
repo_name = get_full_repo_name(model_name)
repo_name
```

```python out
'sgugger/marian-finetuned-kde4-en-to-fr-accelerate'
```

Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone du dépôt avec lequel nous travaillons :

```py
output_dir = "marian-finetuned-kde4-en-to-fr-accelerate"
repo = Repository(output_dir, clone_from=repo_name)
```

Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque.

### Boucle d'entraînement

Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Pour simplifier sa partie évaluation, nous définissons cette fonction `postprocess()` qui prend les prédictions et les étiquettes et les convertit en listes de chaînes de caractères que notre objet `metric` attend :

```py
def postprocess(predictions, labels):
    predictions = predictions.cpu().numpy()
    labels = labels.cpu().numpy()

    decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True)

    # Remplace -100 dans les étiquettes car nous ne pouvons pas les décoder
    labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
    decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)

    # Quelques post-traitements simples
    decoded_preds = [pred.strip() for pred in decoded_preds]
    decoded_labels = [[label.strip()] for label in decoded_labels]
    return decoded_preds, decoded_labels
```

La boucle d'entraînement ressemble beaucoup à celles de la [section 2](/course/fr/chapter7/2) et du [chapitre 3](/course/fr/chapter3), avec quelques différences dans la partie évaluation. Donc concentrons-nous sur cela !

La première chose à noter est que nous utilisons la méthode `generate()` pour calculer les prédictions. C'est une méthode sur notre modèle de base et non pas le modèle enveloppé créé dans la méthode `prepare()`. C'est pourquoi nous déballons d'abord le modèle, puis nous appelons cette méthode.

La deuxième chose est que, comme avec la classification de [*token*](/course/fr/chapter7/2), deux processus peuvent avoir rembourrés les entrées et les étiquettes à des formes différentes. Ainsi nous utilisons `accelerator.pad_across_processes()` pour rendre les prédictions et les étiquettes de la même forme avant d'appeler la méthode `gather()`. Si nous ne faisons pas cela, l'évaluation va soit se tromper, soit se bloquer pour toujours.

```py
from tqdm.auto import tqdm
import torch

progress_bar = tqdm(range(num_training_steps))

for epoch in range(num_train_epochs):
    # Entraînement
    model.train()
    for batch in train_dataloader:
        outputs = model(**batch)
        loss = outputs.loss
        accelerator.backward(loss)

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)

    # Evaluation
    model.eval()
    for batch in tqdm(eval_dataloader):
        with torch.no_grad():
            generated_tokens = accelerator.unwrap_model(model).generate(
                batch["input_ids"],
                attention_mask=batch["attention_mask"],
                max_length=128,
            )
        labels = batch["labels"]

        # Nécessaire pour rembourrer les prédictions et les étiquettes à rassembler
        generated_tokens = accelerator.pad_across_processes(
            generated_tokens, dim=1, pad_index=tokenizer.pad_token_id
        )
        labels = accelerator.pad_across_processes(labels, dim=1, pad_index=-100)

        predictions_gathered = accelerator.gather(generated_tokens)
        labels_gathered = accelerator.gather(labels)

        decoded_preds, decoded_labels = postprocess(predictions_gathered, labels_gathered)
        metric.add_batch(predictions=decoded_preds, references=decoded_labels)

    results = metric.compute()
    print(f"epoch {epoch}, BLEU score: {results['score']:.2f}")

    # Sauvegarder et télécharger
    accelerator.wait_for_everyone()
    unwrapped_model = accelerator.unwrap_model(model)
    unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
    if accelerator.is_main_process:
        tokenizer.save_pretrained(output_dir)
        repo.push_to_hub(
            commit_message=f"Training in progress epoch {epoch}", blocking=False
        )
```

```python out
epoch 0, BLEU score: 53.47
epoch 1, BLEU score: 54.24
epoch 2, BLEU score: 54.44
```

Une fois que c'est fait, vous devriez avoir un modèle qui a des résultats assez similaires à celui entraîné avec `Seq2SeqTrainer`. Vous pouvez vérifier celui que nous avons entraîné en utilisant ce code sur [*huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate*](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les mettre en œuvre directement en modifiant le code ci-dessus !

{/if}

### Utilisation du modèle <i>finetuné</i>

Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons *finetuné* sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, nous devons juste spécifier l'identifiant de modèle approprié :

```py
from transformers import pipeline

# Remplacez ceci par votre propre checkpoint
model_checkpoint = "huggingface-course/marian-finetuned-kde4-en-to-fr"
translator = pipeline("translation", model=model_checkpoint)
translator("Default to expanded threads")
```

```python out
[{'translation_text': 'Par défaut, développer les fils de discussion'}]
```

Comme prévu, notre modèle pré-entraîné a adapté ses connaissances au corpus sur lequel nous l'avons *finetuné*. Et au lieu de laisser le mot anglais « *threads* », le modèle le traduit maintenant par la version française officielle. Il en va de même pour « *plugin* » :

```py
translator(
    "Unable to import %1 using the OFX importer plugin. This file is not the correct format."
)
```

```python out
[{'translation_text': "Impossible d'importer %1 en utilisant le module externe d'importation OFX. Ce fichier n'est pas le bon format."}]
```

Un autre excellent exemple d'adaptation au domaine !

<Tip>

✏️ **A votre tour !** Que retourne le modèle sur l'échantillon avec le mot « *email* » que vous avez identifié plus tôt ?

</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter7/4.mdx" />

### Réponse aux questions
https://huggingface.co/learn/course/fr/chapter7/7.md

# Réponse aux questions

{#if fw === 'pt'}

<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter7/section7_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter7/section7_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter7/section7_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter7/section7_tf.ipynb"},
]} />

{/if}

Il est temps de s'intéresser à la réponse aux questions ! Cette tâche peut prendre plusieurs formes mais celle sur laquelle nous allons nous concentrer dans cette section est appelée réponse aux questions *extractives*. Il s'agit de poser des questions sur un document et d'identifier les réponses sous forme de « d'étendue de texte » dans le document lui-même.

<Youtube id="ajPx5LwJD-I"/>

Nous allons *finetuner* un modèle BERT sur le [jeu de données SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), qui consiste en des questions posées par des *crowdworkers* sur un ensemble d'articles de Wikipedia. Cela nous donnera un modèle capable de calculer des prédictions comme celui-ci :

<iframe src="https://course-demos-bert-finetuned-squad.hf.space" frameBorder="0" height="450" title="Gradio app" class="block dark:hidden container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>
<iframe src="https://course-demos-bert-finetuned-squad-darkmode.hf.space" frameBorder="0" height="450" title="Gradio app" class="hidden dark:block container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Il s'agit d'une présentation du modèle qui a été entraîné à l'aide du code présenté dans cette section et qui a ensuité été téléchargé sur le *Hub*. Vous pouvez le trouver [ici](https://huggingface.co/huggingface-course/bert-finetuned-squad?context=%F0%9F%A4%97+Transformers+is+backed+by+the+three+most+popular+deep+learning+libraries+%E2%80%94+Jax%2C+PyTorch+and+TensorFlow+%E2%80%94+with+a+seamless+integration+between+them.+It%27s+straightforward+to+train+your+models+with+one+before+loading+them+for+inference+with+the+other.&question=Which+deep+learning+libraries+back+%F0%9F%A4%97+Transformers%3F)

<Tip>

💡 Les modèles basé que sur l'encodeur comme BERT ont tendance à être excellents pour extraire les réponses à des questions factuelles comme « Qui a inventé l'architecture Transformer ? » mais ne sont pas très performants lorsqu'on leur pose des questions ouvertes comme « Pourquoi le ciel est-il bleu ? ». Dans ces cas plus difficiles, les modèles encodeurs-décodeurs comme le T5 et BART sont généralement utilisés pour synthétiser les informations d'une manière assez similaire au [résumé de texte](/course/fr/chapter7/5). Si vous êtes intéressé par ce type de réponse aux questions *génératives*, nous vous recommandons de consulter notre [démo](https://yjernite.github.io/lfqa.html) basée sur le [jeu de données ELI5](https://huggingface.co/datasets/eli5).

</Tip>

## Préparation des données

Le jeu de données le plus utilisé comme référence académique pour la réponse extractive aux questions est [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/). C'est donc celui que nous utiliserons ici. Il existe également une version plus difficile [SQuAD v2](https://huggingface.co/datasets/squad_v2), qui comprend des questions sans réponse. Tant que votre propre jeu de données contient une colonne pour les contextes, une colonne pour les questions et une colonne pour les réponses, vous devriez être en mesure d'adapter les étapes ci-dessous.

### Le jeu de données SQuAD

Comme d'habitude, nous pouvons télécharger et mettre en cache le jeu de données en une seule étape grâce à `load_dataset()` :

```py
from datasets import load_dataset

raw_datasets = load_dataset("squad")
```

Nous pouvons jeter un coup d'œil à cet objet pour en savoir plus sur le jeu de données SQuAD :

```py
raw_datasets
```

```python out
DatasetDict({
    train: Dataset({
        features: ['id', 'title', 'context', 'question', 'answers'],
        num_rows: 87599
    })
    validation: Dataset({
        features: ['id', 'title', 'context', 'question', 'answers'],
        num_rows: 10570
    })
})
```

On dirait que nous avons tout ce dont nous avons besoin avec les champs `context`, `question` et `answers`. Affichons-les pour le premier élément de notre ensemble d'entraînement :

```py
print("Context: ", raw_datasets["train"][0]["context"])
print("Question: ", raw_datasets["train"][0]["question"])
print("Answer: ", raw_datasets["train"][0]["answers"])
```

```python out
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.'
# Sur le plan architectural, l'école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras levés, avec la légende "Venite Ad Me Omnes". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l'allée principale (et dans une ligne directe qui passe par 3 statues et le Dôme d'or), se trouve une statue de pierre simple et moderne de Marie'.
Question: 'To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France?' 
# A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes, en France ?
Answer: {'text': ['Saint Bernadette Soubirous'], 'answer_start': [515]}
```

Les champs `context` et `question` sont très simples à utiliser. Le champ `answers` est un peu plus délicat car il compile un dictionnaire avec deux champs qui sont tous deux des listes. C'est le format qui sera attendu par la métrique `squad` lors de l'évaluation. Si vous utilisez vos propres données, vous n'avez pas nécessairement besoin de vous soucier de mettre les réponses dans le même format. Le champ `text` est assez évident et le champ `answer_start` contient l'indice du caractère de départ de chaque réponse dans le contexte.

Pendant l'entraînement, il n'y a qu'une seule réponse possible. Nous pouvons vérifier cela en utilisant la méthode `Dataset.filter()` :

```py
raw_datasets["train"].filter(lambda x: len(x["answers"]["text"]) != 1)
```

```python out
Dataset({
    features: ['id', 'title', 'context', 'question', 'answers'],
    num_rows: 0
})
```

Pour l'évaluation, cependant, il existe plusieurs réponses possibles pour chaque échantillon, qui peuvent être identiques ou différentes :

```py
print(raw_datasets["validation"][0]["answers"])
print(raw_datasets["validation"][2]["answers"])
```

```python out
{'text': ['Denver Broncos', 'Denver Broncos', 'Denver Broncos'], 'answer_start': [177, 177, 177]}
{'text': ['Santa Clara, California', "Levi's Stadium", "Levi's Stadium in the San Francisco Bay Area at Santa Clara, California."], 'answer_start': [403, 355, 355]}
```

Nous ne nous plongerons pas dans le script d'évaluation car tout sera enveloppé pour nous par une métrique de 🤗 *Datasets*. La version courte est que certaines des questions ont plusieurs réponses possibles, et ce script va comparer une réponse prédite à toutes les réponses acceptables et prendre le meilleur score. Par exemple, si nous regardons l'échantillon de l'indice 2 :

```py
print(raw_datasets["validation"][2]["context"])
print(raw_datasets["validation"][2]["question"])
```

```python out
'Super Bowl 50 was an American football game to determine the champion of the National Football League (NFL) for the 2015 season. The American Football Conference (AFC) champion Denver Broncos defeated the National Football Conference (NFC) champion Carolina Panthers 24–10 to earn their third Super Bowl title. The game was played on February 7, 2016, at Levi\'s Stadium in the San Francisco Bay Area at Santa Clara, California. As this was the 50th Super Bowl, the league emphasized the "golden anniversary" with various gold-themed initiatives, as well as temporarily suspending the tradition of naming each Super Bowl game with Roman numerals (under which the game would have been known as "Super Bowl L"), so that the logo could prominently feature the Arabic numerals 50.'
# Le Super Bowl 50 était un match de football américain visant à déterminer le champion de la National Football League (NFL) pour la saison 2015. Les Denver Broncos, champions de la Conférence de football américain (AFC), ont battu les Carolina Panthers, champions de la Conférence nationale de football (NFC), 24 à 10, pour remporter leur troisième titre de Super Bowl. Le match s'est déroulé le 7 février 2016 au Levi\'s Stadium, dans la baie de San Francisco, à Santa Clara, en Californie. Comme il s'agissait du 50e Super Bowl, la ligue a mis l'accent sur l'" anniversaire doré " avec diverses initiatives sur le thème de l'or, ainsi qu'en suspendant temporairement la tradition de nommer chaque match du Super Bowl avec des chiffres romains (en vertu de laquelle le match aurait été appelé " Super Bowl L "), afin que le logo puisse mettre en évidence les chiffres arabes 50.''
'Where did Super Bowl 50 take place?' 
# Où a eu lieu le Super Bowl 50 ?
```

nous pouvons voir que la réponse peut effectivement être l'une des trois possibilités que nous avons vues précédemment.

### Traitement des données d'entraînement

<Youtube id="qgaM0weJHpA"/>

Commençons par le prétraitement des données d'entraînement. La partie la plus difficile est de générer des étiquettes pour la réponse à la question, c'est-à-dire les positions de début et de fin des *tokens* correspondant à la réponse dans le contexte.

Mais ne nous emballons pas. Tout d'abord, à l'aide d'un *tokenizer*, nous devons convertir le texte d'entrée en identifiants que le modèle peut comprendre :

```py
from transformers import AutoTokenizer

model_checkpoint = "bert-base-cased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
```

Comme mentionné précédemment, nous allons *finetuner* un modèle BERT, mais vous pouvez utiliser n'importe quel autre type de modèle tant qu'il a un *tokenizer* rapide implémenté. Vous pouvez voir toutes les architectures qui sont livrées avec un *tokenizer* rapide dans [ce tableau](https://huggingface.co/transformers/#supported-frameworks), et pour vérifier que l'objet `tokenizer` que vous utilisez est bien soutenu par 🤗 *Tokenizers* vous pouvez regarder son attribut `is_fast` :

```py
tokenizer.is_fast
```

```python out
True
```

Nous pouvons transmettre à notre *tokenizer* la question et le contexte ensemble. Il insérera correctement les *tokens* spéciaux pour former une phrase comme celle-ci :

```
[CLS] question [SEP] context [SEP]
```

Vérifions à nouveau :

```py
context = raw_datasets["train"][0]["context"]
question = raw_datasets["train"][0]["question"]

inputs = tokenizer(question, context)
tokenizer.decode(inputs["input_ids"])
```

```python out
'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] 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. [SEP]'

'[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] Architecturalement, '
'l école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge '
'Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras '
'levés avec la légende " Venite Ad Me Omnes ". A côté du bâtiment principal se trouve la basilique du Sacré '
'Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une '
'réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette '
'Soubirous en 1858. Au bout de l'allée principale ( et en ligne directe qui passe par 3 statues '
'et le Dôme d'or), se trouve une statue de Marie en pierre, simple et moderne. [SEP]'
```

Les étiquettes sont l'index des *tokens* de début et de fin de la réponse. Le modèle sera chargé de prédire dans l'entrée un logit de début et de fin par *token*, les étiquettes théoriques étant les suivantes :

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/qa_labels.svg" alt="One-hot encoded labels for question answering."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/qa_labels-dark.svg" alt="One-hot encoded labels for question answering."/>
</div>

Dans ce cas, le contexte n'est pas trop long, mais certains des exemples du jeu de données ont des contextes très longs qui dépasseront la longueur maximale que nous avons fixée (qui est de 384 dans ce cas). Comme nous l'avons vu dans le [chapitre 6](/course/fr/chapter6/4) lorsque nous avons exploré le pipeline de `question-answering`, nous allons traiter les contextes longs en créant plusieurs caractéristiques d'entraînement à partir d'un échantillon de notre jeu de données et avec une fenêtre glissante entre eux.

Pour voir comment cela fonctionne sur notre exemple, nous pouvons limiter la longueur à 100 et utiliser une fenêtre glissante de 50 *tokens*. Pour rappel, nous utilisons :

- `max_length` pour définir la longueur maximale (ici 100)
- `truncation="only_second"` pour tronquer le contexte (qui est en deuxième position) quand la question avec son contexte est trop longue
- `stride` pour fixer le nombre de *tokens* se chevauchant entre deux morceaux successifs (ici 50)
- `return_overflowing_tokens=True` pour indiquer au *tokenizer* que l'on veut les *tokens* qui débordent

```py
inputs = tokenizer(
    question,
    context,
    max_length=100,
    truncation="only_second",
    stride=50,
    return_overflowing_tokens=True,
)

for ids in inputs["input_ids"]:
    print(tokenizer.decode(ids))
```

```python out
'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] 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 basi [SEP]'
'[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] Sur le plan architectural, l école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras levés, avec la légende " Venite Ad Me Omnes ". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basi [SEP]'

'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] 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 [SEP]'
'[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] le bâtiment principal et face à lui, une statue en cuivre du Christ aux bras levés avec la légende " Venite Ad Me Omnes ". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s agit d'une réplique de la grotte de Lourdes, en France, où la Vierge [SEP]'

'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] 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 [SEP]'
'[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] A côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s agit d une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l allée principale ( et dans une ligne directe qui relie par 3 [SEP]'

'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP]. 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. [SEP]'
'[CLS] A qui la Vierge Marie est-elle prétendument apparue en 1858 à Lourdes France ? [SEP]. Il s agit d une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l allée principale (et dans une ligne directe qui passe par 3 statues et le Dôme d or), se trouve une simple statue de pierre moderne de Marie. [SEP]'
```

Comme nous pouvons le voir, notre exemple a été divisé en quatre entrées, chacune d'entre elles contenant la question et une partie du contexte. Notez que la réponse à la question (« Bernadette Soubirous ») n'apparaît que dans la troisième et la dernière entrée. Donc en traitant les longs contextes de cette façon, nous allons créer quelques exemples d'entraînement où la réponse n'est pas incluse dans le contexte. Pour ces exemples, les étiquettes seront `start_position = end_position = 0` (donc nous prédisons le *token* `[CLS]`). Nous définirons également ces étiquettes dans le cas malheureux où la réponse a été tronquée de sorte que nous n'avons que le début (ou la fin) de celle-ci. Pour les exemples où la réponse est entièrement dans le contexte, les étiquettes seront l'index du *token* où la réponse commence et l'index du *token* où la réponse se termine.

Le jeu de données nous fournit le caractère de début de la réponse dans le contexte, et en ajoutant la longueur de la réponse, nous pouvons trouver le caractère de fin dans le contexte. Pour faire correspondre ces indices aux *tokens*, nous devrons utiliser les correspondances *offset* que nous avons étudiés au [chapitre 6](/course/fr/chapter6/4). Nous pouvons faire en sorte que notre *tokenizer* renvoie ces index en passant `return_offsets_mapping=True` :

```py
inputs = tokenizer(
    question,
    context,
    max_length=100,
    truncation="only_second",
    stride=50,
    return_overflowing_tokens=True,
    return_offsets_mapping=True,
)
inputs.keys()
```

```python out
dict_keys(['input_ids', 'token_type_ids', 'attention_mask', 'offset_mapping', 'overflow_to_sample_mapping'])
```

Comme nous pouvons le voir, nous récupérons les identifiants d'entrée, les *tokens* de type identifiant, le masque d'attention, ainsi que la correspondance *offset* dont nous avions besoin et une clé supplémentaire, `overflow_to_sample_mapping`. La valeur correspondante nous sera utile lorsque nous tokeniserons plusieurs textes en même temps (ce que nous devrions faire pour bénéficier du fait que notre *tokenizer* est en Rust). Puisqu'un échantillon peut donner plusieurs caractéristiques, il fait correspondre chaque caractéristique à l'exemple d'où elle provient. Parce qu'ici nous avons seulement tokenisé un exemple, nous obtenons une liste de `0` :

```py
inputs["overflow_to_sample_mapping"]
```

```python out
[0, 0, 0, 0]
```

Mais si nous tokenisons davantage d'exemples, cela deviendra plus utile :

```py
inputs = tokenizer(
    raw_datasets["train"][2:6]["question"],
    raw_datasets["train"][2:6]["context"],
    max_length=100,
    truncation="only_second",
    stride=50,
    return_overflowing_tokens=True,
    return_offsets_mapping=True,
)

print(f"The 4 examples gave {len(inputs['input_ids'])} features.")
print(f"Here is where each comes from: {inputs['overflow_to_sample_mapping']}.")
```

```python out
'The 4 examples gave 19 features.'
'Here is where each comes from: [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3].'
```

Comme nous pouvons le voir, les trois premiers exemples (aux indices 2, 3 et 4 de l'ensemble d'entraînement) ont chacun donné quatre caractéristiques et le dernier exemple (à l'indice 5 de l'ensemble d'entraînement) a donné 7 caractéristiques.

Ces informations seront utiles pour associer chaque caractéristique obtenue à son étiquette correspondante. Comme mentionné précédemment, ces étiquettes sont :

- `(0, 0)` si la réponse n'est pas dans l'espace correspondant du contexte.
- `(start_position, end_position)` si la réponse est dans l'espace correspondant du contexte, avec `start_position` étant l'index du *token* (dans les identifiants d'entrée) au début de la réponse et `end_position` étant l'index du *token* (dans les identifiants d'entrée) où la réponse se termine.

Pour déterminer ce qui est le cas et, le cas échéant, les positions des *tokens*, nous trouvons d'abord les indices qui commencent et finissent le contexte dans les identifiants d'entrée. Nous pourrions utiliser les *tokens* de type identifiants pour le faire, mais puisque ceux-ci n'existent pas nécessairement pour tous les modèles (DistilBERT ne les requiert pas par exemple), nous allons plutôt utiliser la méthode `sequence_ids()` du `BatchEncoding` que notre *tokenizer* retourne. 

Une fois que nous avons ces indices de *tokens*, nous regardons les *offsets* correspondants, qui sont des *tuples* de deux entiers représentant l'étendue des caractères dans le contexte original. Nous pouvons ainsi détecter si le morceau de contexte dans cette fonctionnalité commence après la réponse ou se termine avant que la réponse ne commence (dans ce cas, l'étiquette est `(0, 0)`). Si ce n'est pas le cas, nous bouclons pour trouver le premier et le dernier *token* de la réponse :

```py
answers = raw_datasets["train"][2:6]["answers"]
start_positions = []
end_positions = []

for i, offset in enumerate(inputs["offset_mapping"]):
    sample_idx = inputs["overflow_to_sample_mapping"][i]
    answer = answers[sample_idx]
    start_char = answer["answer_start"][0]
    end_char = answer["answer_start"][0] + len(answer["text"][0])
    sequence_ids = inputs.sequence_ids(i)

    # Trouver le début et la fin du contexte
    idx = 0
    while sequence_ids[idx] != 1:
        idx += 1
    context_start = idx
    while sequence_ids[idx] == 1:
        idx += 1
    context_end = idx - 1

    # Si la réponse n'est pas entièrement dans le contexte, l'étiquette est (0, 0)
    if offset[context_start][0] > start_char or offset[context_end][1] < end_char:
        start_positions.append(0)
        end_positions.append(0)
    else:
        # Sinon, ce sont les positions de début et de fin du token
        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)

start_positions, end_positions
```

```python out
([83, 51, 19, 0, 0, 64, 27, 0, 34, 0, 0, 0, 67, 34, 0, 0, 0, 0, 0],
 [85, 53, 21, 0, 0, 70, 33, 0, 40, 0, 0, 0, 68, 35, 0, 0, 0, 0, 0])
```

Jetons un coup d'œil  à quelques résultats pour vérifier que notre approche est correcte. Pour la première caractéristique, nous trouvons `(83, 85)` comme étiquettes. Comparons alors la réponse théorique avec l'étendue décodée des *tokens* de 83 à 85 (inclus) :

```py
idx = 0
sample_idx = inputs["overflow_to_sample_mapping"][idx]
answer = answers[sample_idx]["text"][0]

start = start_positions[idx]
end = end_positions[idx]
labeled_answer = tokenizer.decode(inputs["input_ids"][idx][start : end + 1])

print(f"Theoretical answer: {answer}, labels give: {labeled_answer}")
```

```python out
'Theoretical answer: the Main Building, labels give: the Main Building'
```

Cela correspond ! Maintenant vérifions l'index 4, où nous avons mis les étiquettes à `(0, 0)`, signifiant que la réponse n'est pas dans le morceau de contexte de cette caractéristique :

```py
idx = 4
sample_idx = inputs["overflow_to_sample_mapping"][idx]
answer = answers[sample_idx]["text"][0]

decoded_example = tokenizer.decode(inputs["input_ids"][idx])
print(f"Theoretical answer: {answer}, decoded example: {decoded_example}")
```

```python out
'Theoretical answer: a Marian place of prayer and reflection, decoded example: [CLS] What is the Grotto at Notre Dame? [SEP] 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 Grot [SEP]'
```

En effet, nous ne voyons pas la réponse dans le contexte.

<Tip>

✏️ **A votre tour !** En utilisant l'architecture XLNet, le *padding* est appliqué à gauche et la question et le contexte sont intervertis. Adaptez tout le code que nous venons de voir à l'architecture XLNet (et ajoutez `padding=True`). Soyez conscient que le token `[CLS]` peut ne pas être à la position 0 avec le *padding* appliqué.

</Tip>

Maintenant que nous avons vu étape par étape comment prétraiter nos données d'entraînement, nous pouvons les regrouper dans une fonction que nous appliquerons à l'ensemble des données d'entraînement. Nous allons rembourrer chaque caractéristique à la longueur maximale que nous avons définie, car la plupart des contextes seront longs (et les échantillons correspondants seront divisés en plusieurs caractéristiques). Il n'y a donc pas de réel avantage à appliquer un rembourrage dynamique ici :

```py
max_length = 384
stride = 128


def preprocess_training_examples(examples):
    questions = [q.strip() for q in examples["question"]]
    inputs = tokenizer(
        questions,
        examples["context"],
        max_length=max_length,
        truncation="only_second",
        stride=stride,
        return_overflowing_tokens=True,
        return_offsets_mapping=True,
        padding="max_length",
    )

    offset_mapping = inputs.pop("offset_mapping")
    sample_map = inputs.pop("overflow_to_sample_mapping")
    answers = examples["answers"]
    start_positions = []
    end_positions = []

    for i, offset in enumerate(offset_mapping):
        sample_idx = sample_map[i]
        answer = answers[sample_idx]
        start_char = answer["answer_start"][0]
        end_char = answer["answer_start"][0] + len(answer["text"][0])
        sequence_ids = inputs.sequence_ids(i)

        # Trouver le début et la fin du contexte
        idx = 0
        while sequence_ids[idx] != 1:
            idx += 1
        context_start = idx
        while sequence_ids[idx] == 1:
            idx += 1
        context_end = idx - 1

        # Si la réponse n'est pas entièrement dans le contexte, l'étiquette est (0, 0)
        if offset[context_start][0] > start_char or offset[context_end][1] < end_char:
            start_positions.append(0)
            end_positions.append(0)
        else:
            # Sinon, ce sont les positions de début et de fin du token
            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
```

Notez que nous avons défini deux constantes pour déterminer la longueur maximale utilisée ainsi que la longueur de la fenêtre glissante, et que nous avons ajouté un petit nettoyage avant la tokénisation : certaines des questions dans SQuAD ont des espaces supplémentaires au début et à la fin qui n'ajoutent rien (et prennent de la place lors de la tokénisation si vous utilisez un modèle comme RoBERTa), donc nous avons supprimé ces espaces supplémentaires.

Pour appliquer cette fonction à l'ensemble de l'entraînement, nous utilisons la méthode `Dataset.map()` avec le flag `batched=True`. C'est nécessaire ici car nous changeons la longueur du jeu de données (puisqu'un exemple peut donner plusieurs caractéristiques d'entraînement) :

```py
train_dataset = raw_datasets["train"].map(
    preprocess_training_examples,
    batched=True,
    remove_columns=raw_datasets["train"].column_names,
)
len(raw_datasets["train"]), len(train_dataset)
```

```python out
(87599, 88729)
```

Comme nous pouvons le voir, le prétraitement a ajouté environ 1 000 caractéristiques. Notre ensemble d'entraînement est maintenant prêt à être utilisé. Passons au prétraitement de l'ensemble de validation !

### Traitement des données de validation

Le prétraitement des données de validation sera légèrement plus facile car nous n'avons pas besoin de générer des étiquettes (sauf si nous voulons calculer une perte de validation, mais elle ne nous aidera pas vraiment à comprendre la qualité du modèle). Le réel plaisir sera d'interpréter les prédictions du modèle dans des étendues du contexte original. Pour cela, il nous suffit de stocker les correspondances d'*offset* et un moyen de faire correspondre chaque caractéristique créée à l'exemple original dont elle provient. Puisqu'il y a une colonne identifiant dans le jeu de données original, nous l'utiliserons.

La seule chose que nous allons ajouter ici est un petit nettoyage des correspondances d'*offset*. Elles contiendront les *offsets* pour la question et le contexte, mais une fois que nous serons à la phase de post-traitement, nous n'aurons aucun moyen de savoir quelle partie des identifiants d'entrée correspondait au contexte et quelle partie était la question (la méthode `sequence_ids()` que nous avons utilisée n'est disponible que pour la sortie du *tokenizer*). Donc, nous allons mettre les *offsets* correspondant à la question à `None` :

```py
def preprocess_validation_examples(examples):
    questions = [q.strip() for q in examples["question"]]
    inputs = tokenizer(
        questions,
        examples["context"],
        max_length=max_length,
        truncation="only_second",
        stride=stride,
        return_overflowing_tokens=True,
        return_offsets_mapping=True,
        padding="max_length",
    )

    sample_map = inputs.pop("overflow_to_sample_mapping")
    example_ids = []

    for i in range(len(inputs["input_ids"])):
        sample_idx = sample_map[i]
        example_ids.append(examples["id"][sample_idx])

        sequence_ids = inputs.sequence_ids(i)
        offset = inputs["offset_mapping"][i]
        inputs["offset_mapping"][i] = [
            o if sequence_ids[k] == 1 else None for k, o in enumerate(offset)
        ]

    inputs["example_id"] = example_ids
    return inputs
```

Nous pouvons appliquer cette fonction sur l'ensemble de validation comme précédemment :

```py
validation_dataset = raw_datasets["validation"].map(
    preprocess_validation_examples,
    batched=True,
    remove_columns=raw_datasets["validation"].column_names,
)
len(raw_datasets["validation"]), len(validation_dataset)
```

```python out
(10570, 10822)
```

Dans ce cas, nous n'avons ajouté que quelques centaines d'échantillons, il semble donc que les contextes dans l'ensemble de validation soient un peu plus courts.

Maintenant que nous avons prétraité toutes les données, nous pouvons passer à l'entraînement. 

{#if fw === 'pt'}

## <i>Finetuner</i> le modèle avec l'API `Trainer`

Le code d'entraînement pour cet exemple ressemblera beaucoup au code des sections précédentes mais le calcul de la métrique avec la fonction `compute_metrics()` sera un défi unique. Puisque nous avons rembourré tous les échantillons à la longueur maximale que nous avons définie, il n'y a pas d'assembleur de données à définir. Ainsi le calcul de la métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en étendues de texte dans les exemples originaux. Une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous.

{:else}

## <i>Finetuner</i> fin du modèle avec Keras

Le code d'entraînement de cet exemple ressemblera beaucoup au code des sections précédentes, mais le calcul de la métrique sera un défi unique. Puisque nous avons rembourré tous les échantillons à la longueur maximale que nous avons définie, il n'y a pas d'assembleur de données à définir. Ainsi le calcul de la métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en étendues de texte dans les exemples originaux. Une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous.

{/if}

### Post-traitement

{#if fw === 'pt'}

<Youtube id="BNy08iIWVJM"/>

{:else}

<Youtube id="VN67ZpN33Ss"/>

{/if}

Le modèle produira des logits pour les positions de début et de fin de la réponse dans les identifiants d'entrée, comme nous l'avons vu lors de notre exploration du pipeline de `question-answering` [au chapitre 6](/course/fr/chapter6/3b). L'étape de post-traitement sera similaire à ce que nous avons fait à ce chapitre là. Voici un rapide rappel des actions que nous avons prises :

- nous avons masqué les logits de début et de fin correspondant aux *tokens* en dehors du contexte,
- nous avons ensuite converti les logits de début et de fin en probabilités en utilisant une fonction SoftMax,
- nous avons attribué un score à chaque paire `(start_token, end_token)` en prenant le produit des deux probabilités correspondantes,
- nous avons cherché la paire avec le score maximum qui donnait une réponse valide (par exemple, un `start_token` inférieur au `end_token`).

Ici, nous allons modifier légèrement ce processus car nous n'avons pas besoin de calculer les scores réels (juste la réponse prédite). Cela signifie que nous pouvons sauter l'étape de la SoftMax. Pour aller plus vite, nous ne donnerons pas non plus un score à toutes les paires `(start_token, end_token)` possibles, mais seulement celles correspondant aux `n_best` logits les plus élevés (avec `n_best=20`). Puisque nous sautons la SoftMax, les scores seront des scores logi, et seront obtenus en prenant la somme des logits de début et de fin (au lieu du produit, à cause de la règle \\(\log(ab) = \log(a) + \log(b)\\)).

Pour démontrer tout cela, nous aurons besoin d'un certain type de prédictions. Puisque nous n'avons pas encore entraîné notre modèle, nous allons utiliser le modèle par défaut du pipeline de `question-answering` pour générer quelques prédictions sur une petite partie de l'ensemble de validation. Nous pouvons utiliser la même fonction de traitement que précédemment car elle repose sur la constante globale `tokenizer`, nous devons juste changer cet objet par le *tokenizer* du modèle que nous voulons utiliser temporairement :

```python
small_eval_set = raw_datasets["validation"].select(range(100))
trained_checkpoint = "distilbert-base-cased-distilled-squad"

tokenizer = AutoTokenizer.from_pretrained(trained_checkpoint)
eval_set = small_eval_set.map(
    preprocess_validation_examples,
    batched=True,
    remove_columns=raw_datasets["validation"].column_names,
)
```

Maintenant que le prétraitement est terminé, nous changeons le *tokenizer* pour celui que nous avons choisi à l'origine :

```python
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
```

Nous supprimons ensuite les colonnes de notre `eval_set` qui ne sont pas attendues par le modèle. Nous construisons un batch avec tout de ce petit ensemble de validation et le passons au modèle. Si un GPU est disponible, nous l'utilisons pour aller plus vite :

{#if fw === 'pt'}

```python
import torch
from transformers import AutoModelForQuestionAnswering

eval_set_for_model = eval_set.remove_columns(["example_id", "offset_mapping"])
eval_set_for_model.set_format("torch")

device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
batch = {k: eval_set_for_model[k].to(device) for k in eval_set_for_model.column_names}
trained_model = AutoModelForQuestionAnswering.from_pretrained(trained_checkpoint).to(
    device
)

with torch.no_grad():
    outputs = trained_model(**batch)
```

Puisque `Trainer` nous donne les prédictions sous forme de tableaux NumPy, nous récupérons les logits de début et de fin et les convertissons dans ce format :

```python
start_logits = outputs.start_logits.cpu().numpy()
end_logits = outputs.end_logits.cpu().numpy()
```

{:else}

```python
import tensorflow as tf
from transformers import TFAutoModelForQuestionAnswering

eval_set_for_model = eval_set.remove_columns(["example_id", "offset_mapping"])
eval_set_for_model.set_format("numpy")

batch = {k: eval_set_for_model[k] for k in eval_set_for_model.column_names}
trained_model = TFAutoModelForQuestionAnswering.from_pretrained(trained_checkpoint)

outputs = trained_model(**batch)
```

Pour faciliter l'expérimentation, nous allons convertir ces sorties en tableaux NumPy :

```python
start_logits = outputs.start_logits.numpy()
end_logits = outputs.end_logits.numpy()
```

{/if}

Maintenant, nous devons trouver la réponse prédite pour chaque exemple dans notre `small_eval_set`. Un exemple peut avoir été divisé en plusieurs caractéristiques dans `eval_set`, donc la première étape est de faire correspondre chaque exemple dans `small_eval_set` aux caractéristiques correspondantes dans `eval_set` :

```python
import collections

example_to_features = collections.defaultdict(list)
for idx, feature in enumerate(eval_set):
    example_to_features[feature["example_id"]].append(idx)
```

Avec cela, nous pouvons vraiment nous mettre au travail en bouclant tous les exemples et, pour chaque exemple, toutes les caractéristiques associées. Comme nous l'avons dit précédemment, nous allons regarder les scores logit pour les `n_best` logits de début et logits de fin, en excluant les positions qui donnent :

- une réponse qui ne serait pas dans le contexte
- une réponse avec une longueur négative
- une réponse qui est trop longue (nous limitons les possibilités à `max_answer_length=30`)

Une fois que nous avons toutes les réponses possibles notées pour un exemple, nous choisissons simplement celle qui a le meilleur score logit :

```python
import numpy as np

n_best = 20
max_answer_length = 30
predicted_answers = []

for example in small_eval_set:
    example_id = example["id"]
    context = example["context"]
    answers = []

    for feature_index in example_to_features[example_id]:
        start_logit = start_logits[feature_index]
        end_logit = end_logits[feature_index]
        offsets = eval_set["offset_mapping"][feature_index]

        start_indexes = np.argsort(start_logit)[-1 : -n_best - 1 : -1].tolist()
        end_indexes = np.argsort(end_logit)[-1 : -n_best - 1 : -1].tolist()
        for start_index in start_indexes:
            for end_index in end_indexes:
                # Ignore les réponses qui ne sont pas entièrement dans le contexte
                if offsets[start_index] is None or offsets[end_index] is None:
                    continue
                # Ignore les réponses dont la longueur est soit < 0 soit > max_answer_length
                if (
                    end_index < start_index
                    or end_index - start_index + 1 > max_answer_length
                ):
                    continue

                answers.append(
                    {
                        "text": context[offsets[start_index][0] : offsets[end_index][1]],
                        "logit_score": start_logit[start_index] + end_logit[end_index],
                    }
                )

    best_answer = max(answers, key=lambda x: x["logit_score"])
    predicted_answers.append({"id": example_id, "prediction_text": best_answer["text"]})
```

Le format final des réponses prédites est celui qui sera attendu par la métrique que nous allons utiliser. Comme d'habitude, nous pouvons la charger à l'aide de la bibliothèque 🤗 *Evaluate* :

```python
import evaluate

metric = evaluate.load("squad")
```

Cette métrique attend les réponses prédites dans le format que nous avons vu ci-dessus (une liste de dictionnaires avec une clé pour l'identifiant de l'exemple et une clé pour le texte prédit) et les réponses théoriques dans le format ci-dessous (une liste de dictionnaires avec une clé pour l'identifiant de l'exemple et une clé pour les réponses possibles) :

```python
theoretical_answers = [
    {"id": ex["id"], "answers": ex["answers"]} for ex in small_eval_set
]
```

Nous pouvons maintenant vérifier que nous obtenons des résultats raisonnables en examinant le premier élément des deux listes :

```python
print(predicted_answers[0])
print(theoretical_answers[0])
```

```python out
{'id': '56be4db0acb8001400a502ec', 'prediction_text': 'Denver Broncos'}
{'id': '56be4db0acb8001400a502ec', 'answers': {'text': ['Denver Broncos', 'Denver Broncos', 'Denver Broncos'], 'answer_start': [177, 177, 177]}}
```

Pas trop mal ! Voyons maintenant le score que la métrique nous donne :

```python
metric.compute(predictions=predicted_answers, references=theoretical_answers)
```

```python out
{'exact_match': 83.0, 'f1': 88.25}
```

Encore une fois, c'est plutôt bon si l'on considère que, d'après [le papier](https://arxiv.org/abs/1910.01108v2) de DistilBERT, *finetuné* sur SQuAD, ce modèle obtient 79,1 et 86,9 pour ces scores sur l'ensemble du jeu de données.

{#if fw === 'pt'}

Maintenant, mettons tout ce que nous venons de faire dans une fonction `compute_metrics()` que nous utiliserons dans le `Trainer`. Normalement, cette fonction `compute_metrics()` reçoit seulement un *tuple* `eval_preds` avec les logits et les étiquettes. Ici, nous aurons besoin d'un peu plus, car nous devons chercher dans le jeu de données des caractéristiques pour le décalage et dans le jeu de données des exemples pour les contextes originaux. Ainsi nous ne serons pas en mesure d'utiliser cette fonction pour obtenir des résultats d'évaluation standards pendant l'entraînement. Nous ne l'utiliserons qu'à la fin de l'entraînement pour vérifier les résultats.

La fonction `compute_metrics()` regroupe les mêmes étapes que précédemment. Nous ajoutons juste une petite vérification au cas où nous ne trouverions aucune réponse valide (dans ce cas nous prédisons une chaîne vide).

{:else}

Maintenant, mettons tout ce que nous venons de faire dans une fonction `compute_metrics()` que nous utiliserons après avoir entraîné notre modèle. Nous aurons besoin de passer un peu plus que juste les logits de sortie, car nous devons chercher dans le jeu de données des caractéristiques pour le décalage et dans le jeu de données des exemples pour les contextes originaux :

{/if}

```python
from tqdm.auto import tqdm


def compute_metrics(start_logits, end_logits, features, examples):
    example_to_features = collections.defaultdict(list)
    for idx, feature in enumerate(features):
        example_to_features[feature["example_id"]].append(idx)

    predicted_answers = []
    for example in tqdm(examples):
        example_id = example["id"]
        context = example["context"]
        answers = []

        # Parcourir en boucle toutes les fonctionnalités associées à cet exemple
        for feature_index in example_to_features[example_id]:
            start_logit = start_logits[feature_index]
            end_logit = end_logits[feature_index]
            offsets = features[feature_index]["offset_mapping"]

            start_indexes = np.argsort(start_logit)[-1 : -n_best - 1 : -1].tolist()
            end_indexes = np.argsort(end_logit)[-1 : -n_best - 1 : -1].tolist()
            for start_index in start_indexes:
                for end_index in end_indexes:
                    # Ignore les réponses qui ne sont pas entièrement dans le contexte
                    if offsets[start_index] is None or offsets[end_index] is None:
                        continue
                    # Ignore les réponses dont la longueur est soit < 0, soit > max_answer_length
                    if (
                        end_index < start_index
                        or end_index - start_index + 1 > max_answer_length
                    ):
                        continue

                    answer = {
                        "text": context[offsets[start_index][0] : offsets[end_index][1]],
                        "logit_score": start_logit[start_index] + end_logit[end_index],
                    }
                    answers.append(answer)

        # Sélectionne la réponse avec le meilleur score
        if len(answers) > 0:
            best_answer = max(answers, key=lambda x: x["logit_score"])
            predicted_answers.append(
                {"id": example_id, "prediction_text": best_answer["text"]}
            )
        else:
            predicted_answers.append({"id": example_id, "prediction_text": ""})

    theoretical_answers = [{"id": ex["id"], "answers": ex["answers"]} for ex in examples]
    return metric.compute(predictions=predicted_answers, references=theoretical_answers)
```

Nous pouvons vérifier que cela fonctionne sur nos prédictions :

```python
compute_metrics(start_logits, end_logits, eval_set, small_eval_set)
```

```python out
{'exact_match': 83.0, 'f1': 88.25}
```

C'est bien ! Maintenant, utilisons ceci pour *finetuner* notre modèle.

### <i>Finetuning</i> du modèle

{#if fw === 'pt'}

Nous sommes maintenant prêts à entraîner notre modèle. Créons-le en utilisant la classe `AutoModelForQuestionAnswering` comme précédemment :

```python
model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint)
```

{:else}

Nous sommes maintenant prêts à entraîner notre modèle. Créons-le en utilisant la classe `TFAutoModelForQuestionAnswering` comme précédemment :

```python
model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint)
```

{/if}

Comme d'habitude, nous recevons un avertissement indiquant que certains poids ne sont pas utilisés (ceux de la tête de pré-entraînement) et que d'autres sont initialisés de manière aléatoire (ceux de la tête de réponse aux questions). Vous devriez être habitué à cela maintenant, mais cela signifie que ce modèle n'est pas encore prêt à être utilisé et qu'il a besoin d'être *finetuné*. Une bonne chose que nous soyons sur le point de le faire !

Pour pouvoir pousser notre modèle vers le *Hub*, nous devons nous connecter à Hugging Face. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante, qui affiche un *widget* où vous pouvez entrer vos identifiants de connexion :

```python
from huggingface_hub import notebook_login

notebook_login()
```

Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal :

```bash
huggingface-cli login
```

{#if fw === 'pt'}

Une fois ceci fait, nous pouvons définir nos `TrainingArguments`. Comme nous l'avons dit lorsque nous avons défini notre fonction pour calculer la métrique, nous ne serons pas en mesure d'avoir une boucle d'évaluation standard à cause de la signature de la fonction `compute_metrics()`. Nous pourrions écrire notre propre sous-classe de `Trainer` pour faire cela (une approche que vous pouvez trouver dans le [script d'exemple de réponse aux questions](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question-answering/trainer_qa.py)), mais c'est un peu trop long pour cette section. A la place, nous n'évaluerons le modèle qu'à la fin de l'entraînement et nous vous montrerons comment faire une évaluation cela dans le paragraphe « Une boucle d'entraînement personnalisée » ci-dessous.

C'est là que l'API `Trainer` montre ses limites et que la bibliothèque 🤗 *Accelerate* brille : personnaliser la classe pour un cas d'utilisation spécifique peut être pénible, mais modifier une boucle d'entraînement est facile.

Jetons un coup d'œil à notre `TrainingArguments` :

```python
from transformers import TrainingArguments

args = TrainingArguments(
    "bert-finetuned-squad",
    evaluation_strategy="no",
    save_strategy="epoch",
    learning_rate=2e-5,
    num_train_epochs=3,
    weight_decay=0.01,
    fp16=True,
    push_to_hub=True,
)
```

Nous avons déjà vu la plupart d'entre eux. Nous définissons quelques hyperparamètres (comme le taux d'apprentissage, le nombre d'époques d'entraînement, un taux de décroissance des poids) et nous indiquons que nous voulons sauvegarder le modèle à la fin de chaque époque, sauter l'évaluation, et télécharger nos résultats vers le *Hub*. Nous activons également l'entraînement en précision mixte avec `fp16=True`, car cela peut accélérer l'entraînement sur un GPU récent.

{:else}

Maintenant que c'est fait, nous pouvons créer nos jeux de données TensorFlow. Nous pouvons utiliser le simple assembleur de données par défaut cette fois-ci :

```python
from transformers import DefaultDataCollator

data_collator = DefaultDataCollator(return_tensors="tf")
```

Et maintenant nous créons les jeux de données comme d'habitude.

```python
tf_train_dataset = model.prepare_tf_dataset(
    train_dataset,
    collate_fn=data_collator,
    shuffle=True,
    batch_size=16,
)
tf_eval_dataset = model.prepare_tf_dataset(
    validation_dataset,
    collate_fn=data_collator,
    shuffle=False,
    batch_size=16,
)
```

Ensuite, nous configurons nos hyperparamètres d'entraînement et compilons notre modèle :

```python
from transformers import create_optimizer
from transformers.keras_callbacks import PushToHubCallback
import tensorflow as tf

# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch,
# puis multiplié par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un tf.data.Dataset,
# et non le jeu de données original donc son len() est déjà num_samples // batch_size.
num_train_epochs = 3
num_train_steps = len(tf_train_dataset) * num_train_epochs
optimizer, schedule = create_optimizer(
    init_lr=2e-5,
    num_warmup_steps=0,
    num_train_steps=num_train_steps,
    weight_decay_rate=0.01,
)
model.compile(optimizer=optimizer)

# Entraîner en mixed-precision float16
tf.keras.mixed_precision.set_global_policy("mixed_float16")
```

Enfin, nous sommes prêts à entraîner avec `model.fit()`. Nous utilisons un `PushToHubCallback` pour télécharger le modèle sur le *Hub* après chaque époque.

{/if}

Par défaut, le dépôt utilisé sera dans votre espace et nommé après le répertoire de sortie que vous avez défini. Donc dans notre cas il sera dans `"sgugger/bert-finetuned-squad"`. Nous pouvons passer outre en passant un `hub_model_id`, par exemple, pour pousser le modèle dans l'organisation `huggingface_course` nous avons utilisé `hub_model_id= "huggingface_course/bert-finetuned-squad"` (qui est le modèle que nous avons lié au début de cette section).

{#if fw === 'pt'}

<Tip>

💡 Si le répertoire de sortie que vous utilisez existe, il doit être un clone local du dépôt vers lequel vous voulez pousser (donc définissez un nouveau nom si vous obtenez une erreur lors de la définition de votre `Trainer`).

</Tip>

Enfin, nous passons tout à la classe `Trainer` et lançons l'entraînement :

```python
from transformers import Trainer

trainer = Trainer(
    model=model,
    args=args,
    train_dataset=train_dataset,
    eval_dataset=validation_dataset,
    tokenizer=tokenizer,
)
trainer.train()
```

{:else}

```python
from transformers.keras_callbacks import PushToHubCallback

callback = PushToHubCallback(output_dir="bert-finetuned-squad", tokenizer=tokenizer)

# Nous allons faire la validation après, donc pas de validation au milieu de l'entraînement.
model.fit(tf_train_dataset, callbacks=[callback], epochs=num_train_epochs)
```

{/if}

Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. Ainsi, vous pourrez reprendre votre entraînement sur une autre machine si nécessaire. L'ensemble de l'entraînement prend un certain temps (un peu plus d'une heure sur une Titan RTX), vous pouvez donc prendre un café ou relire les parties du cours qui vous ont semblé plus difficiles pendant qu'il se déroule. Notez également que dès que la première époque est terminée, vous verrez des poids téléchargés sur le *Hub* et vous pourrez commencer à jouer avec votre modèle sur sa page.

{#if fw === 'pt'}

Une fois l'entraînement terminé, nous pouvons enfin évaluer notre modèle (et prier pour ne pas avoir dépensé tout ce temps de calcul pour rien). La méthode `predict()` du `Trainer` retournera un *tuple* où les premiers éléments seront les prédictions du modèle (ici une paire avec les logits de début et de fin). Nous envoyons ceci à notre fonction `compute_metrics()` :

```python
predictions, _ = trainer.predict(validation_dataset)
start_logits, end_logits = predictions
compute_metrics(start_logits, end_logits, validation_dataset, raw_datasets["validation"])
```

{:else}

Une fois l'entraînement terminé, nous pouvons enfin évaluer notre modèle (et prier pour ne pas avoir dépensé tout ce temps de calcul pour rien). La méthode `predict()` de notre `model` se chargera d'obtenir les prédictions, et puisque nous avons fait tout le travail difficile de définir une fonction `compute_metrics()` plus tôt, nous pouvons obtenir nos résultats en une seule ligne :

```python
predictions = model.predict(tf_eval_dataset)
compute_metrics(
    predictions["start_logits"],
    predictions["end_logits"],
    validation_dataset,
    raw_datasets["validation"],
)
```

{/if}

```python out
{'exact_match': 81.18259224219489, 'f1': 88.67381321905516}
```

Super ! À titre de comparaison, les scores indiqués dans l'article de BERT pour ce tâche sont de 80,8 et 88,5. Donc nous sommes exactement là où nous devrions être.

{#if fw === 'pt'}

Enfin, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la dernière version du modèle :

```py
trainer.push_to_hub(commit_message="Training complete")
```

Cela renvoie l'URL du commit qu'il vient de faire, si vous voulez l'inspecter :

```python out
'https://huggingface.co/sgugger/bert-finetuned-squad/commit/9dcee1fbc25946a6ed4bb32efb1bd71d5fa90b68'
```

Le `Trainer` rédige également une carte de modèle avec tous les résultats de l'évaluation et la télécharge.

{/if}

À ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* du modèle pour tester le modèle et le partager avec vos amis, votre famille et vos animaux préférés. Vous avez réussi à *finetuner* un modèle sur une tâche de réponse à une question. Félicitations !

<Tip>

✏️ **A votre tour** Essayez un autre modèle pour voir s'il est plus performant pour cette tâche !

</Tip>

{#if fw === 'pt'}

Si vous voulez plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*.

## Une boucle d'entraînement personnalisée

Jetons maintenant un coup d'œil à la boucle d'entraînement complète, afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à la boucle d'entraînement du [chapitre 3](/course/fr/chapter3/4), à l'exception de la boucle d'évaluation. Nous serons en mesure d'évaluer le modèle régulièrement puisque nous ne sommes plus contraints par la classe `Trainer`.

### Préparer tout pour l'entraînement

Tout d'abord, nous devons construire le `DataLoader`s à partir de nos jeux de données. Nous définissons le format de ces jeux de données à `"torch"` et supprimons les colonnes dans le jeu de validation qui ne sont pas utilisées par le modèle. Ensuite, nous pouvons utiliser le `default_data_collator` fourni par 🤗 *Transformers* comme `collate_fn` et mélanger l'ensemble d'entraînement mais pas celui de validation :

```py
from torch.utils.data import DataLoader
from transformers import default_data_collator

train_dataset.set_format("torch")
validation_set = validation_dataset.remove_columns(["example_id", "offset_mapping"])
validation_set.set_format("torch")

train_dataloader = DataLoader(
    train_dataset,
    shuffle=True,
    collate_fn=default_data_collator,
    batch_size=8,
)
eval_dataloader = DataLoader(
    validation_set, collate_fn=default_data_collator, batch_size=8
)
```

Ensuite, nous réinstantifions notre modèle afin de nous assurer que nous ne poursuivons pas le *finetuning* précédent et que nous repartons du modèle BERT pré-entraîné :

```py
model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint)
```

Ensuite, nous aurons besoin d'un optimiseur. Comme d'habitude, nous utilisons le classique `AdamW`, qui est comme Adam mais avec une correction dans la façon dont le taux de décroissance des poids est appliqué :

```py
from torch.optim import AdamW

optimizer = AdamW(model.parameters(), lr=2e-5)
```

Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()`. Rappelez-vous que si vous voulez entraîner sur des TPUs dans un *notebook* Colab, vous devrez déplacer tout ce code dans une fonction d'entraînement, et qui ne devrait pas exécuter une cellule qui instancie un `Accelerator`. Nous pouvons forcer l'entraînement en précision mixte en passant l'argument `fp16=True` à `Accelerator` (ou, si vous exécutez le code comme un script, assurez-vous de remplir la 🤗 *Accelerate* `config` de manière appropriée).

```py
from accelerate import Accelerator

accelerator = Accelerator(fp16=True)
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, eval_dataloader
)
```

Comme vous devez le savoir depuis les sections précédentes, nous ne pouvons utiliser la longueur de `train_dataloader` pour calculer le nombre d'étapes d'entraînement qu'après qu'il soit passé par la méthode `accelerator.prepare()`. Nous utilisons le même programme linéaire que dans les sections précédentes :

```py
from transformers import get_scheduler

num_train_epochs = 3
num_update_steps_per_epoch = len(train_dataloader)
num_training_steps = num_train_epochs * num_update_steps_per_epoch

lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)
```

Pour pousser notre modèle vers le *Hub*, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub*, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix. Il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `get_full_repo_name()`) :

```py
from huggingface_hub import Repository, get_full_repo_name

model_name = "bert-finetuned-squad-accelerate"
repo_name = get_full_repo_name(model_name)
repo_name
```

```python out
'sgugger/bert-finetuned-squad-accelerate'
```

Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone du dépôt avec lequel nous travaillons :

```py
output_dir = "bert-finetuned-squad-accelerate"
repo = Repository(output_dir, clone_from=repo_name)
```

Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque.

## Boucle d'entraînement

Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Après avoir défini une barre de progression pour suivre l'évolution de l'entraînement, la boucle comporte trois parties :

- l'entraînement à proprement dit, qui est l'itération classique sur le `train_dataloader`, passage en avant du modèle, puis passage en arrière et étape d'optimisation.
- l'évaluation, dans laquelle nous rassemblons toutes les valeurs pour `start_logits` et `end_logits` avant de les convertir en tableaux NumPy. Une fois la boucle d'évaluation terminée, nous concaténons tous les résultats. Notez que nous devons tronquer car `Accelerator` peut avoir ajouté quelques échantillons à la fin pour s'assurer que nous avons le même nombre d'exemples dans chaque processus.
- sauvegarde et téléchargement, où nous sauvegardons d'abord le modèle et le *tokenizer*, puis appelons `repo.push_to_hub()`. Comme nous l'avons fait auparavant, nous utilisons l'argument `blocking=False` pour dire à la bibliothèque 🤗 *Hub* de pousser dans un processus asynchrone. De cette façon, l'entraînement continue normalement et cette (longue) instruction est exécutée en arrière-plan.

Voici le code complet de la boucle d'entraînement :

```py
from tqdm.auto import tqdm
import torch

progress_bar = tqdm(range(num_training_steps))

for epoch in range(num_train_epochs):
    # Entraînement
    model.train()
    for step, batch in enumerate(train_dataloader):
        outputs = model(**batch)
        loss = outputs.loss
        accelerator.backward(loss)

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)

    # Evaluation
    model.eval()
    start_logits = []
    end_logits = []
    accelerator.print("Evaluation!")
    for batch in tqdm(eval_dataloader):
        with torch.no_grad():
            outputs = model(**batch)

        start_logits.append(accelerator.gather(outputs.start_logits).cpu().numpy())
        end_logits.append(accelerator.gather(outputs.end_logits).cpu().numpy())

    start_logits = np.concatenate(start_logits)
    end_logits = np.concatenate(end_logits)
    start_logits = start_logits[: len(validation_dataset)]
    end_logits = end_logits[: len(validation_dataset)]

    metrics = compute_metrics(
        start_logits, end_logits, validation_dataset, raw_datasets["validation"]
    )
    print(f"epoch {epoch}:", metrics)

    # Sauvegarder et télécharger
    accelerator.wait_for_everyone()
    unwrapped_model = accelerator.unwrap_model(model)
    unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
    if accelerator.is_main_process:
        tokenizer.save_pretrained(output_dir)
        repo.push_to_hub(
            commit_message=f"Training in progress epoch {epoch}", blocking=False
        )
```

Au cas où ce serait la première fois que vous verriez un modèle enregistré avec 🤗 *Accelerate*, prenons un moment pour inspecter les trois lignes de code qui l'accompagnent :

```py
accelerator.wait_for_everyone()
unwrapped_model = accelerator.unwrap_model(model)
unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
```

La première ligne est explicite : elle indique à tous les processus d'attendre que tout le monde soit à ce stade avant de continuer. C'est pour s'assurer que nous avons le même modèle dans chaque processus avant de sauvegarder. Ensuite, nous prenons le `unwrapped_model`, qui est le modèle de base que nous avons défini. La méthode `accelerator.prepare()` modifie le modèle pour qu'il fonctionne dans l'entraînement distribué. Donc il n'aura plus la méthode `save_pretrained()` car la méthode `accelerator.unwrap_model()` annule cette étape. Enfin, nous appelons `save_pretrained()` mais nous disons à cette méthode d'utiliser `accelerator.save()` au lieu de `torch.save()`. 

Une fois ceci fait, vous devriez avoir un modèle qui produit des résultats assez similaires à celui entraîné avec `Trainer`. Vous pouvez vérifier le modèle que nous avons entraîné en utilisant ce code à [*huggingface-course/bert-finetuned-squad-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-squad-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les implémenter directement en modifiant le code ci-dessus !

{/if}

### Utilisation du modèle <i>finetuné</i>

Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons *finetuné* sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, il suffit de spécifier l'identifiant du modèle :

```py
from transformers import pipeline

# Remplacez par votre propre checkpoint
model_checkpoint = "huggingface-course/bert-finetuned-squad"
question_answerer = pipeline("question-answering", model=model_checkpoint)

context = """
🤗 Transformers is backed by the three most popular deep learning libraries — Jax, PyTorch and TensorFlow — with a seamless integration
between them. It's straightforward to train your models with one before loading them for inference with the other.
"""
question = "Which deep learning libraries back 🤗 Transformers?"
question_answerer(question=question, context=context)
```

```python out
{'score': 0.9979003071784973,
 'start': 78,
 'end': 105,
 'answer': 'Jax, PyTorch and TensorFlow'}
```

Super ! Notre modèle fonctionne aussi bien que le modèle par défaut pour ce pipeline !


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter7/7.mdx" />

### Classification de <i>tokens</i>
https://huggingface.co/learn/course/fr/chapter7/2.md

# Classification de <i>tokens</i>

{#if fw === 'pt'}
<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section2_pt.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter7/section2_pt.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section2_pt.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter7/section2_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section2_tf.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter7/section2_tf.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section2_tf.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter7/section2_tf.ipynb"},
]} />

{/if}

La première application que nous allons explorer est la classification de *tokens*. Cette tâche générique englobe tous les problèmes qui peuvent être formulés comme l'attribution d'une étiquette à chaque *token* d'une phrase, tels que :

- la **reconnaissance d'entités nommées (NER de l'anglais *Named Entity Recognition*)**, c'est-à-dire trouver les entités (telles que des personnes, des lieux ou des organisations) dans une phrase. Ce tâche peut être formulée comme l'attribution d'une étiquette à chaque *token* faisant parti d'une entité en ayant une classe spécifique par entité, et une classe pour les *tokens* ne faisant pas parti d'entité.
- le ***part-of-speech tagging* (POS)**, c'est-à-dire marquer chaque mot dans une phrase comme correspondant à une partie particulière (comme un nom, un verbe, un adjectif, etc.).
- le ***chunking***, c'est-à-dire trouver les *tokens* qui appartiennent à la même entité. Cette tâche (qui peut être combinée avec le POS ou la NER) peut être formulée comme l'attribution d'une étiquette (habituellement `B-`) à tous les *tokens* qui sont au début d'un morceau, une autre étiquette (habituellement `I-`) aux *tokens* qui sont à l'intérieur d'un morceau, et une troisième étiquette (habituellement `O`) aux *tokens* qui n'appartiennent à aucun morceau.

<Youtube id="wVHdVlPScxA"/>

Bien sûr, il existe de nombreux autres types de problèmes de classification de *tokens*. Ce ne sont là que quelques exemples représentatifs. Dans cette section, nous allons *finetuner* un modèle (BERT) sur la tâche de NER. Il sera alors capable de calculer des prédictions comme celle-ci :

<iframe src="https://course-demos-bert-finetuned-ner.hf.space" frameBorder="0" height="350" title="Gradio app" class="block dark:hidden container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>
<iframe src="https://course-demos-bert-finetuned-ner-darkmode.hf.space" frameBorder="0" height="350" title="Gradio app" class="hidden dark:block container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

<a class="flex justify-center" href="/huggingface-course/bert-finetuned-ner">
<img class="block dark:hidden lg:w-3/5" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/model-eval-bert-finetuned-ner.png" alt="One-hot encoded labels for question answering."/>
<img class="hidden dark:block lg:w-3/5" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/model-eval-bert-finetuned-ner-dark.png" alt="One-hot encoded labels for question answering."/>
</a>

Vous pouvez trouver, télécharger et vérifier les précisions de ce modèle sur le [*Hub*](https://huggingface.co/huggingface-course/bert-finetuned-ner?text=My+nom+est+Sylvain+et+je+travaille+à+Hugging+Face+in+Brooklyn) les prédictions du modèle que nous allons entraîner.

## Préparation des données

Tout d'abord, nous avons besoin d'un jeu de données adapté à la classification des *tokens*. Dans cette section, nous utiliserons le jeu de données [CoNLL-2003](https://huggingface.co/datasets/conll2003), qui contient des articles de presse de Reuters. 

<Tip>

💡 Tant que votre jeu de données consiste en des textes divisés en mots avec leurs étiquettes correspondantes, vous pourrez adapter les procédures de traitement des données décrites ici à votre propre jeu de données. Reportez-vous au [chapitre 5](/course/fr/chapter5) si vous avez besoin d'un rafraîchissement sur la façon de charger vos propres données personnalisées dans un `Dataset`.

</Tip>

### Le jeu de données CoNLL-2003

Pour charger le jeu de données CoNLL-2003, nous utilisons la méthode `load_dataset()` de la bibliothèque 🤗 *Datasets* :

```py
from datasets import load_dataset

raw_datasets = load_dataset("conll2003")
```

Cela va télécharger et mettre en cache le jeu de données, comme nous l'avons vu dans [chapitre 3](/course/fr/chapter3) pour le jeu de données GLUE MRPC. L'inspection de cet objet nous montre les colonnes présentes dans ce jeu de données et la répartition entre les ensembles d'entraînement, de validation et de test :

```py
raw_datasets
```

```python out
DatasetDict({
    train: Dataset({
        features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'],
        num_rows: 14041
    })
    validation: Dataset({
        features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'],
        num_rows: 3250
    })
    test: Dataset({
        features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'],
        num_rows: 3453
    })
})
```

En particulier, nous pouvons voir que le jeu de données contient des étiquettes pour les trois tâches que nous avons mentionnées précédemment : NER, POS et *chunking*. Une grande différence avec les autres jeux de données est que les entrées textuelles ne sont pas présentés comme des phrases ou des documents, mais comme des listes de mots (la dernière colonne est appelée `tokens`, mais elle contient des mots dans le sens où ce sont des entrées prétokénisées qui doivent encore passer par le *tokenizer* pour la tokenisation en sous-mots).

Regardons le premier élément de l'ensemble d'entraînement :

```py
raw_datasets["train"][0]["tokens"]
```

```python out
['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.']
```

Puisque nous voulons effectuer reconnaître des entités nommées, nous allons examiner les balises NER :

```py
raw_datasets["train"][0]["ner_tags"]
```

```python out
[3, 0, 7, 0, 0, 0, 7, 0, 0]
```

Ce sont les étiquettes sous forme d'entiers disponibles pour l'entraînement mais ne sont pas nécessairement utiles lorsque nous voulons inspecter les données. Comme pour la classification de texte, nous pouvons accéder à la correspondance entre ces entiers et les noms des étiquettes en regardant l'attribut `features` de notre jeu de données :

```py
ner_feature = raw_datasets["train"].features["ner_tags"]
ner_feature
```

```python out
Sequence(feature=ClassLabel(num_classes=9, names=['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC'], names_file=None, id=None), length=-1, id=None)
```

Cette colonne contient donc des éléments qui sont des séquences de `ClassLabel`. Le type des éléments de la séquence se trouve dans l'attribut `feature` de cette `ner_feature`, et nous pouvons accéder à la liste des noms en regardant l'attribut `names` de cette `feature` :

```py
label_names = ner_feature.feature.names
label_names
```

```python out
['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC']
```

Nous avons déjà vu ces étiquettes au [chapitre 6](/course/fr/chapter6/3) lorsque nous nous sommes intéressés au pipeline `token-classification` mais nosu pouvons tout de même faire un rapide rappel : 

- `O` signifie que le mot ne correspond à aucune entité.
- `B-PER`/`I-PER` signifie que le mot correspond au début/est à l'intérieur d'une entité *personne*.
- `B-ORG`/`I-ORG` signifie que le mot correspond au début/est à l'intérieur d'une entité *organisation*.
- `B-LOC`/`I-LOC` signifie que le mot correspond au début/est à l'intérieur d'une entité *location*.
- `B-MISC`/`I-MISC` signifie que le mot correspond au début/est à l'intérieur d'une entité *divers*.

Maintenant, le décodage des étiquettes que nous avons vues précédemment nous donne ceci :

```python
words = raw_datasets["train"][0]["tokens"]
labels = raw_datasets["train"][0]["ner_tags"]
line1 = ""
line2 = ""
for word, label in zip(words, labels):
    full_label = label_names[label]
    max_length = max(len(word), len(full_label))
    line1 += word + " " * (max_length - len(word) + 1)
    line2 += full_label + " " * (max_length - len(full_label) + 1)

print(line1)
print(line2)
```

```python out
'EU    rejects German call to boycott British lamb .'
'B-ORG O       B-MISC O    O  O       B-MISC  O    O'
```

Et pour un exemple mélangeant les étiquettes `B-` et `I-`, voici ce que le même code nous donne sur le quatrième élément du jeu d'entraînement :

```python out
'Germany \'s representative to the European Union \'s veterinary committee Werner Zwingmann said on Wednesday consumers should buy sheepmeat from countries other than Britain until the scientific advice was clearer .'
'B-LOC   O  O              O  O   B-ORG    I-ORG O  O          O         B-PER  I-PER     O    O  O         O         O      O   O         O    O         O     O    B-LOC   O     O   O          O      O   O       O'
```

Comme on peut le voir, les entités couvrant deux mots, comme « European Union » et « Werner Zwingmann », se voient attribuer une étiquette `B-` pour le premier mot et une étiquette `I-` pour le second.

<Tip>

✏️ *A votre tour !* Affichez les deux mêmes phrases avec leurs étiquettes POS ou *chunking*.

</Tip>

### Traitement des données

<Youtube id="iY2AZYdZAr0"/>

Comme d'habitude, nos textes doivent être convertis en identifiants de *tokens* avant que le modèle puisse leur donner un sens. Comme nous l'avons vu au [chapitre 6](/course/fr/chapter6/), une grande différence dans le cas des tâches de classification de *tokens* est que nous avons des entrées prétokénisées. Heureusement, l'API `tokenizer` peut gérer cela assez facilement. Nous devons juste avertir le `tokenizer` avec un drapeau spécial.

Pour commencer, nous allons créer notre objet `tokenizer`. Comme nous l'avons dit précédemment, nous allons utiliser un modèle BERT pré-entraîné, donc nous allons commencer par télécharger et mettre en cache le *tokenizer* associé :

```python
from transformers import AutoTokenizer

model_checkpoint = "bert-base-cased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
```

Vous pouvez remplacer le `model_checkpoint` par tout autre modèle que vous préférez à partir du [*Hub*](https://huggingface.co/models), ou par un dossier local dans lequel vous avez sauvegardé un modèle pré-entraîné et un *tokenizer*. La seule contrainte est que le *tokenizer* doit être soutenu par la bibliothèque 🤗 *Tokenizers*. Il y a donc une version rapide disponible. Vous pouvez voir toutes les architectures qui ont une version rapide dans [ce tableau](https://huggingface.co/transformers/#supported-frameworks), et pour vérifier que l'objet `tokenizer` que vous utilisez est bien soutenu par 🤗 *Tokenizers* vous pouvez regarder son attribut `is_fast` :

```py
tokenizer.is_fast
```

```python out
True
```

Pour tokeniser une entrée prétokenisée, nous pouvons utiliser notre `tokenizer` comme d'habitude et juste ajouter `is_split_into_words=True` :

```py
inputs = tokenizer(raw_datasets["train"][0]["tokens"], is_split_into_words=True)
inputs.tokens()
```

```python out
['[CLS]', 'EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'la', '##mb', '.', '[SEP]']
```

Comme on peut le voir, le *tokenizer* a ajouté les *tokens* spéciaux utilisés par le modèle (`[CLS]` au début et `[SEP]` à la fin) et n'a pas touché à la plupart des mots. Le mot `lamb`, cependant, a été tokenisé en deux sous-mots, `la` et `##mb`. Cela introduit un décalage entre nos entrées et les étiquettes : la liste des étiquettes n'a que 9 éléments, alors que notre entrée a maintenant 12 *tokens*. Il est facile de tenir compte des *tokens* spéciaux (nous savons qu'ils sont au début et à la fin), mais nous devons également nous assurer que nous alignons toutes les étiquettes avec les mots appropriés.

Heureusement, comme nous utilisons un *tokenizer* rapide, nous avons accès aux superpouvoirs des 🤗 *Tokenizers*, ce qui signifie que nous pouvons facilement faire correspondre chaque *token* au mot correspondant (comme on le voit au [chapitre 6](/course/fr/chapter6/3)) :

```py
inputs.word_ids()
```

```python out
[None, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, None]
```

Avec un peu de travail, nous pouvons étendre notre liste d'étiquettes pour qu'elle corresponde aux *tokens*. La première règle que nous allons appliquer est que les *tokens* spéciaux reçoivent une étiquette de `-100`. En effet, par défaut, `-100` est un indice qui est ignoré dans la fonction de perte que nous allons utiliser (l'entropie croisée). Ensuite, chaque *token* reçoit la même étiquette que le *token* qui a commencé le mot dans lequel il se trouve puisqu'ils font partie de la même entité. Pour les *tokens* à l'intérieur d'un mot mais pas au début, nous remplaçons le `B-` par `I-` (puisque le *token* ne commence pas l'entité) :

```python
def align_labels_with_tokens(labels, word_ids):
    new_labels = []
    current_word = None
    for word_id in word_ids:
        if word_id != current_word:
            # Début d'un nouveau mot !
            current_word = word_id
            label = -100 if word_id is None else labels[word_id]
            new_labels.append(label)
        elif word_id is None:
            # Token spécial
            new_labels.append(-100)
        else:
            # Même mot que le token précédent
            label = labels[word_id]
            # Si l'étiquette est B-XXX, nous la changeons en I-XXX
            if label % 2 == 1:
                label += 1
            new_labels.append(label)

    return new_labels
```

Essayons-le sur notre première phrase :

```py
labels = raw_datasets["train"][0]["ner_tags"]
word_ids = inputs.word_ids()
print(labels)
print(align_labels_with_tokens(labels, word_ids))
```

```python out
[3, 0, 7, 0, 0, 0, 7, 0, 0]
[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100]
```

Comme nous pouvons le voir, notre fonction a ajouté `-100` pour les deux *tokens* spéciaux du début et de fin, et un nouveau `0` pour notre mot qui a été divisé en deux *tokens*.

<Tip>

✏️ *A votre tour !* Certains chercheurs préfèrent n'attribuer qu'une seule étiquette par mot et attribuer `-100` aux autres sous-*tokens* dans un mot donné. Ceci afin d'éviter que les longs mots qui se divisent en plusieurs batchs ne contribuent fortement à la perte. Changez la fonction précédente pour aligner les étiquettes avec les identifiants d'entrée en suivant cette règle.
</Tip>

Pour prétraiter notre jeu de données, nous devons tokeniser toutes les entrées et appliquer `align_labels_with_tokens()` sur toutes les étiquettes. Pour profiter de la vitesse de notre *tokenizer* rapide, il est préférable de tokeniser beaucoup de textes en même temps. Nous allons donc écrire une fonction qui traite une liste d'exemples et utiliser la méthode `Dataset.map()` avec l'option `batched=True`. La seule chose qui diffère de notre exemple précédent est que la fonction `word_ids()` a besoin de récupérer l'index de l'exemple dont nous voulons les identifiants de mots lorsque les entrées du *tokenizer* sont des listes de textes (ou dans notre cas, des listes de mots), donc nous l'ajoutons aussi :

```py
def tokenize_and_align_labels(examples):
    tokenized_inputs = tokenizer(
        examples["tokens"], truncation=True, is_split_into_words=True
    )
    all_labels = examples["ner_tags"]
    new_labels = []
    for i, labels in enumerate(all_labels):
        word_ids = tokenized_inputs.word_ids(i)
        new_labels.append(align_labels_with_tokens(labels, word_ids))

    tokenized_inputs["labels"] = new_labels
    return tokenized_inputs
```

Notez que nous n'avons pas encore rembourré nos entrées. Nous le ferons plus tard lors de la création des batchs avec un assembleur de données. 

Nous pouvons maintenant appliquer tout ce prétraitement en une seule fois sur les autres divisions de notre jeu de données :

```py
tokenized_datasets = raw_datasets.map(
    tokenize_and_align_labels,
    batched=True,
    remove_columns=raw_datasets["train"].column_names,
)
```

Nous avons fait la partie la plus difficile ! Maintenant que les données ont été prétraitées, l'entraînement ressemblera beaucoup à ce que nous avons fait dans le [chapitre 3](/course/fr/chapter3).

{#if fw === 'pt'}

## <i>Finetuning</i> du modèle avec l'API `Trainer`

Le code utilisant `Trainer` sera le même que précédemment. Les seuls changements sont la façon dont les données sont rassemblées dans un batch ainsi que la fonction de calcul de la métrique.

{:else}

## <i>Finetuning</i> du modèle avec Keras

Le code utilisant Keras sera très similaire au précédent. Les seuls changements sont la façon dont les données sont rassemblées dans un batch ainsi que la fonction de calcul de la métrique.

{/if}


### Assemblage des données

Nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans le [chapitre 3](/course/fr/chapter3) car cela ne fait que rembourrer les entrées (identifiants d'entrée, masque d'attention et *token* de type identifiants). Ici, nos étiquettes doivent être rembourréés exactement de la même manière que les entrées afin qu'elles gardent la même taille, en utilisant `-100` comme valeur afin que les prédictions correspondantes soient ignorées dans le calcul de la perte.

Tout ceci est fait par un [`DataCollatorForTokenClassification`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorfortokenclassification). Comme le `DataCollatorWithPadding`, il prend le `tokenizer` utilisé pour prétraiter les entrées :

{#if fw === 'pt'}

```py
from transformers import DataCollatorForTokenClassification

data_collator = DataCollatorForTokenClassification(tokenizer=tokenizer)
```

{:else}

```py
from transformers import DataCollatorForTokenClassification

data_collator = DataCollatorForTokenClassification(
    tokenizer=tokenizer, return_tensors="tf"
)
```

{/if}

Pour tester cette fonction sur quelques échantillons, nous pouvons simplement l'appeler sur une liste d'exemples provenant de notre jeu d'entraînement tokénisé :

```py
batch = data_collator([tokenized_datasets["train"][i] for i in range(2)])
batch["labels"]
```

```python out
tensor([[-100,    3,    0,    7,    0,    0,    0,    7,    0,    0,    0, -100],
        [-100,    1,    2, -100, -100, -100, -100, -100, -100, -100, -100, -100]])
```

Comparons cela aux étiquettes des premier et deuxième éléments de notre jeu de données :

```py
for i in range(2):
    print(tokenized_datasets["train"][i]["labels"])
```

```python out
[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100]
[-100, 1, 2, -100]
```

{#if fw === 'pt'}

Comme nous pouvons le voir, le deuxième jeu d'étiquettes a été complété à la longueur du premier en utilisant des `-100`.

{:else}

Notre assembleur de données est prêt à fonctionner ! Maintenant, utilisons-le pour créer un `tf.data.Dataset` avec la méthode `to_tf_dataset()`. Une alternative est d'utiliser `model.prepare_tf_dataset()` pour faire cela qui prend un peu moins de code passe-partout. Vous verrez cela dans certaines des autres sections de ce chapitre.

```py
tf_train_dataset = tokenized_datasets["train"].to_tf_dataset(
    columns=["attention_mask", "input_ids", "labels", "token_type_ids"],
    collate_fn=data_collator,
    shuffle=True,
    batch_size=16,
)

tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset(
    columns=["attention_mask", "input_ids", "labels", "token_type_ids"],
    collate_fn=data_collator,
    shuffle=False,
    batch_size=16,
)
```


Prochain arrêt : le modèle lui-même.

{/if}

{#if fw === 'tf'}

### Définir le modèle

Puisque nous travaillons sur un problème de classification de *tokens*, nous allons utiliser la classe `TFAutoModelForTokenClassification`. La principale chose à retenir lors de la définition de ce modèle est de transmettre des informations sur le nombre d'étiquettes que nous avons. La façon la plus simple de le faire est de passer ce nombre avec l'argument `num_labels`, mais si nous voulons un joli *widget* d'inférence fonctionnant comme celui que nous avons vu au début de cette section, il est préférable de définir les correspondances correctes des étiquettes à la place.

Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent la correspondance de l'identifiant à l'étiquette et vice versa :

```py
id2label = {i: label for i, label in enumerate(label_names)}
label2id = {v: k for k, v in id2label.items()}
```

Maintenant, nous pouvons simplement les passer à la méthode `TFAutoModelForTokenClassification.from_pretrained()`, et ils seront définis dans la configuration du modèle puis correctement enregistrés et téléchargés vers le *Hub* :

```py
from transformers import TFAutoModelForTokenClassification

model = TFAutoModelForTokenClassification.from_pretrained(
    model_checkpoint,
    id2label=id2label,
    label2id=label2id,
)
```

Comme lorsque nous avons défini notre `TFAutoModelForSequenceClassification` au [chapitre 3](/course/fr/chapter3), la création du modèle émet un avertissement indiquant que certains poids n'ont pas été utilisés (ceux de la tête de pré-entraînement) et que d'autres poids ont été initialisés de manière aléatoire (ceux de la tête de classification des nouveaux *tokens*), et que ce modèle doit être entraîné. Nous ferons cela dans une minute mais vérifions d'abord que notre modèle a le bon nombre d'étiquettes :

```python
model.config.num_labels
```

```python out
9
```

<Tip warning={true}>

⚠️ Si vous avez un modèle avec le mauvais nombre d'étiquettes, vous obtiendrez plus tard une erreur obscure lors de l'appel de `model.fit()`. Cela peut être ennuyeux à déboguer donc assurez-vous de faire cette vérification pour confirmer que vous avez le nombre d'étiquettes attendu.

</Tip>

### <i>Finetuning</i> du modèle

Nous sommes maintenant prêts à entraîner notre modèle ! Mais nous devons d'abord faire un peu de ménage : nous devons nous connecter à Hugging Face et définir nos hyperparamètres d'entraînement. Si vous travaillez dans un *notebook*, il y a une fonction pratique pour vous aider à le faire :

```python
from huggingface_hub import notebook_login

notebook_login()
```

Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face.

Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal :

```bash
huggingface-cli login
```

Après s'être connecté, nous pouvons préparer tout ce dont nous avons besoin pour compiler notre modèle. 🤗 *Transformers* fournit une fonction pratique `create_optimizer()` qui vous donnera un optimiseur `AdamW` avec des paramètres appropriés pour le taux de décroissance des poids et le taux de décroissance de l'apprentissage, les deux améliorant les performances de votre modèle par rapport à l'optimiseur `Adam` : 

```python
from transformers import create_optimizer
import tensorflow as tf

# Entraîner en mixed-precision float16
# Commentez cette ligne si vous utilisez un GPU qui ne bénéficiera pas de cette fonction
tf.keras.mixed_precision.set_global_policy("mixed_float16")

# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du batch puis multiplié par le nombre total d'époques
# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un batchtf.data.Dataset,
# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà num_samples // batch_size
num_epochs = 3
num_train_steps = len(tf_train_dataset) * num_epochs

optimizer, schedule = create_optimizer(
    init_lr=2e-5,
    num_warmup_steps=0,
    num_train_steps=num_train_steps,
    weight_decay_rate=0.01,
)
model.compile(optimizer=optimizer)
```

Notez également que nous ne fournissons pas d'argument `loss` à `compile()`. C'est parce que les modèles peuvent en fait calculer la perte en interne. Si vous compilez sans perte et fournissez vos étiquettes dans le dictionnaire d'entrée (comme nous le faisons dans nos jeux de données), alors le modèle s'entraînera en utilisant cette perte interne, qui sera appropriée pour la tâche et le type de modèle que vous avez choisi.

Ensuite, nous définissons un `PushToHubCallback` pour télécharger notre modèle vers le *Hub* pendant l'entraînement, et nous ajustons le modèle avec ce *callback* :

```python
from transformers.keras_callbacks import PushToHubCallback

callback = PushToHubCallback(output_dir="bert-finetuned-ner", tokenizer=tokenizer)

model.fit(
    tf_train_dataset,
    validation_data=tf_eval_dataset,
    callbacks=[callback],
    epochs=num_epochs,
)
```

Vous pouvez spécifier le nom complet du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/bert-finetuned-ner"`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, par exemple `"cool_huggingface_user/bert-finetuned-ner"`.

<Tip>

💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de l'appel de `model.fit()` et devrez définir un nouveau nom.

</Tip>

Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous pourrez reprendre votre entraînement sur une autre machine si nécessaire.

A ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de classification de *tokens*. Félicitations ! Mais quelle est la qualité réelle de notre modèle ? Nous devons évaluer certaines métriques pour le découvrir.

{/if}


### Métriques

{#if fw === 'pt'}

Pour que le `Trainer` calcule une métrique à chaque époque, nous devrons définir une fonction `compute_metrics()` qui prend les tableaux de prédictions et d'étiquettes, et retourne un dictionnaire avec les noms et les valeurs des métriques. 

Le *framework* traditionnel utilisé pour évaluer la prédiction de la classification des *tokens* est [*seqeval*](https://github.com/chakki-works/seqeval). Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *seqeval* :

```py
!pip install seqeval
```

Nous pouvons ensuite le charger via la fonction `evaluate.load()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) :

{:else}

Le *framework*  traditionnel utilisé pour évaluer la prédiction de la classification des *tokens* est [*seqeval*](https://github.com/chakki-works/seqeval). Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *seqeval* :

```py
!pip install seqeval
```

Nous pouvons ensuite le charger via la fonction `evaluate.load()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) :

{/if}

```py
import evaluate

metric = evaluate.load("seqeval")
```

Cette métrique ne se comporte pas comme la précision standard : elle prend les listes d'étiquettes comme des chaînes de caractères et non comme des entiers. Nous devrons donc décoder complètement les prédictions et les étiquettes avant de les transmettre à la métrique. Voyons comment cela fonctionne. Tout d'abord, nous allons obtenir les étiquettes pour notre premier exemple d'entraînement :

```py
labels = raw_datasets["train"][0]["ner_tags"]
labels = [label_names[i] for i in labels]
labels
```

```python out
['B-ORG', 'O', 'B-MISC', 'O', 'O', 'O', 'B-MISC', 'O', 'O']
```

Nous pouvons alors créer de fausses prédictions pour celles-ci en changeant simplement la valeur de l'indice 2 :

```py
predictions = labels.copy()
predictions[2] = "O"
metric.compute(predictions=[predictions], references=[labels])
```

Notez que la métrique prend une liste de prédictions (pas seulement une) et une liste d'étiquettes. Voici la sortie :

```python out
{'MISC': {'precision': 1.0, 'recall': 0.5, 'f1': 0.67, 'number': 2},
 'ORG': {'precision': 1.0, 'recall': 1.0, 'f1': 1.0, 'number': 1},
 'overall_precision': 1.0,
 'overall_recall': 0.67,
 'overall_f1': 0.8,
 'overall_accuracy': 0.89}
```

{#if fw === 'pt'}

Cela renvoie un batch d'informations ! Nous obtenons la précision, le rappel et le score F1 pour chaque entité séparée, ainsi que le score global. Pour notre calcul de métrique, nous ne garderons que le score global, mais n'hésitez pas à modifier la fonction `compute_metrics()` pour retourner toutes les métriques que vous souhaitez.

Cette fonction `compute_metrics()` prend d'abord l'argmax des logits pour les convertir en prédictions (comme d'habitude, les logits et les probabilités sont dans le même ordre, donc nous n'avons pas besoin d'appliquer la fonction softmax). Ensuite, nous devons convertir les étiquettes et les prédictions des entiers en chaînes de caractères. Nous supprimons toutes les valeurs dont l'étiquette est `-100`, puis nous passons les résultats à la méthode `metric.compute()` :

```py
import numpy as np


def compute_metrics(eval_preds):
    logits, labels = eval_preds
    predictions = np.argmax(logits, axis=-1)

    # Suppression de l'index ignoré (tokens spéciaux) et conversion en étiquettes
    true_labels = [[label_names[l] for l in label if l != -100] for label in labels]
    true_predictions = [
        [label_names[p] for (p, l) in zip(prediction, label) if l != -100]
        for prediction, label in zip(predictions, labels)
    ]
    all_metrics = metric.compute(predictions=true_predictions, references=true_labels)
    return {
        "precision": all_metrics["overall_precision"],
        "recall": all_metrics["overall_recall"],
        "f1": all_metrics["overall_f1"],
        "accuracy": all_metrics["overall_accuracy"],
    }
```

Maintenant que ceci est fait, nous sommes presque prêts à définir notre `Trainer`. Nous avons juste besoin d'un objet `model` pour *finetuner* !

{:else}

Cela renvoie un batch d'informations ! Nous obtenons la précision, le rappel et le score F1 pour chaque entité séparée, ainsi que pour l'ensemble. Voyons maintenant ce qui se passe si nous essayons d'utiliser les prédictions de notre modèle pour calculer des scores réels.

TensorFlow n'aime pas concaténer nos prédictions ensemble car elles ont des longueurs de séquence variables. Cela signifie que nous ne pouvons pas simplement utiliser `model.predict()`. Mais cela ne va pas nous arrêter. Nous obtiendrons des prédictions un batch à la fois et les concaténerons en une grande liste longue au fur et à mesure et en laissant de côté les *tokens* `-100` qui indiquent le masquage/le remplissage. Puis nous calculerons les métriques sur la liste à la fin :

```py
import numpy as np

all_predictions = []
all_labels = []
for batch in tf_eval_dataset:
    logits = model.predict_on_batch(batch)["logits"]
    labels = batch["labels"]
    predictions = np.argmax(logits, axis=-1)
    for prediction, label in zip(predictions, labels):
        for predicted_idx, label_idx in zip(prediction, label):
            if label_idx == -100:
                continue
            all_predictions.append(label_names[predicted_idx])
            all_labels.append(label_names[label_idx])
metric.compute(predictions=[all_predictions], references=[all_labels])
```


```python out
{'LOC': {'precision': 0.91, 'recall': 0.92, 'f1': 0.91, 'number': 1668},
 'MISC': {'precision': 0.70, 'recall': 0.79, 'f1': 0.74, 'number': 702},
 'ORG': {'precision': 0.85, 'recall': 0.90, 'f1': 0.88, 'number': 1661},
 'PER': {'precision': 0.95, 'recall': 0.95, 'f1': 0.95, 'number': 1617},
 'overall_precision': 0.87,
 'overall_recall': 0.91,
 'overall_f1': 0.89,
 'overall_accuracy': 0.97}
```

Comment s'est comporté votre modèle, comparé au nôtre ? Si vous avez obtenu des chiffres similaires, votre entraînement a été un succès !

{/if}

{#if fw === 'pt'}

### Définir le modèle

Puisque nous travaillons sur un problème de classification de *tokens*, nous allons utiliser la classe `AutoModelForTokenClassification`. La principale chose à retenir lors de la définition de ce modèle est de transmettre des informations sur le nombre d'étiquettes que nous avons. La façon la plus simple de le faire est de passer ce nombre avec l'argument `num_labels`, mais si nous voulons un joli *widget* d'inférence fonctionnant comme celui que nous avons vu au début de cette section, il est préférable de définir les correspondances des étiquettes à la place.

Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent les correspondances entre identifiants et étiquettes et vice versa :

```py
id2label = {i: label for i, label in enumerate(label_names)}
label2id = {v: k for k, v in id2label.items()}
```

Maintenant nous pouvons simplement les passer à la méthode `AutoModelForTokenClassification.from_pretrained()`, ils seront définis dans la configuration du modèle puis correctement sauvegardés et téléchargés vers le *Hub* :

```py
from transformers import AutoModelForTokenClassification

model = AutoModelForTokenClassification.from_pretrained(
    model_checkpoint,
    id2label=id2label,
    label2id=label2id,
)
```

Comme lorsque nous avons défini notre `AutoModelForSequenceClassification` au [chapitre 3](/course/fr/chapter3), la création du modèle émet un avertissement indiquant que certains poids n'ont pas été utilisés (ceux de la tête de pré-entraînement) et que d'autres poids ont été initialisés de manière aléatoire (ceux de la tête de classification des nouveaux *tokens*), et que ce modèle doit être entraîné. Nous ferons cela dans une minute, mais vérifions d'abord que notre modèle a le bon nombre d'étiquettes :

```python
model.config.num_labels
```

```python out
9
```

<Tip warning={true}>

⚠️ Si vous avez un modèle avec le mauvais nombre d'étiquettes, vous obtiendrez une erreur obscure lors de l'appel de la méthode `Trainer.train()` (quelque chose comme "CUDA error : device-side assert triggered"). C'est la première cause de bogues signalés par les utilisateurs pour de telles erreurs, donc assurez-vous de faire cette vérification pour confirmer que vous avez le nombre d'étiquettes attendu.

</Tip>

### <i>Finetuning</i> du modèle

Nous sommes maintenant prêts à entraîner notre modèle ! Nous devons juste faire deux dernières choses avant de définir notre `Trainer` : se connecter à Hugging Face et définir nos arguments d'entraînement. Si vous travaillez dans un *notebook*, il y a une fonction pratique pour vous aider à le faire :

```python
from huggingface_hub import notebook_login

notebook_login()
```

Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face.

Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal :

```bash
huggingface-cli login
```

Une fois ceci fait, nous pouvons définir nos `TrainingArguments` :

```python
from transformers import TrainingArguments

args = TrainingArguments(
    "bert-finetuned-ner",
    evaluation_strategy="epoch",
    save_strategy="epoch",
    learning_rate=2e-5,
    num_train_epochs=3,
    weight_decay=0.01,
    push_to_hub=True,
)
```

Vous avez déjà vu la plupart d'entre eux. Nous définissons quelques hyperparamètres (comme le taux d'apprentissage, le nombre d'époques à entraîner, et le taux de décroissance des poids), et nous spécifions `push_to_hub=True` pour indiquer que nous voulons sauvegarder le modèle, l'évaluer à la fin de chaque époque, et que nous voulons télécharger nos résultats vers le *Hub*. Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/bert-finetuned-ner"``TrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé d'après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"sgugger/bert-finetuned-ner"`.

<Tip>

💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de la définition de votre `Trainer` et devrez définir un nouveau nom.

</Tip>

Enfin, nous passons tout au `Trainer` et lançons l'entraînement :

```python
from transformers import Trainer

trainer = Trainer(
    model=model,
    args=args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation"],
    data_collator=data_collator,
    compute_metrics=compute_metrics,
    tokenizer=tokenizer,
)
trainer.train()
```

Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous serez en mesure de reprendre votre entraînement sur une autre machine si nécessaire.

Une fois l'entraînement terminé, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la version la plus récente du modèle :

```py
trainer.push_to_hub(commit_message="Training complete")
```

Cette commande renvoie l'URL du commit qu'elle vient de faire, si vous voulez l'inspecter :

```python out
'https://huggingface.co/sgugger/bert-finetuned-ner/commit/26ab21e5b1568f9afeccdaed2d8715f571d786ed'
```

Le `Trainer` rédige également une carte modèle avec tous les résultats de l'évaluation et la télécharge. A ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à affiner un modèle sur une tâche de classification de *tokens*. Félicitations !

Si vous voulez plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*.

## Une boucle d'entraînement personnalisée

Jetons maintenant un coup d'œil à la boucle d'entraînement complète afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à ce que nous avons fait dans le [chapitre 3](/course/fr/chapter3/4) avec quelques changements pour l'évaluation.

### Préparer tout pour l'entraînement

D'abord nous devons construire le `DataLoader`s à partir de nos jeux de données. Nous réutilisons notre `data_collator` comme un `collate_fn` et mélanger l'ensemble d'entraînement, mais pas l'ensemble de validation :

```py
from torch.utils.data import DataLoader

train_dataloader = DataLoader(
    tokenized_datasets["train"],
    shuffle=True,
    collate_fn=data_collator,
    batch_size=8,
)
eval_dataloader = DataLoader(
    tokenized_datasets["validation"], collate_fn=data_collator, batch_size=8
)
```

Ensuite, nous réinstantifions notre modèle pour nous assurer que nous ne continuons pas le *finetuning* d'avant et que nous repartons bien du modèle pré-entraîné de BERT :

```py
model = AutoModelForTokenClassification.from_pretrained(
    model_checkpoint,
    id2label=id2label,
    label2id=label2id,
)
```

Ensuite, nous avons besoin d'un optimiseur. Nous utilisons le classique `AdamW`, qui est comme `Adam`, mais avec un correctif dans la façon dont le taux de décroissance des poids est appliquée :

```py
from torch.optim import AdamW

optimizer = AdamW(model.parameters(), lr=2e-5)
```

Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()` :

```py
from accelerate import Accelerator

accelerator = Accelerator()
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, eval_dataloader
)
```

<Tip>

🚨 Si vous entraînez sur un TPU, vous devrez déplacer tout le code à partir de la cellule ci-dessus dans une fonction d'entraînement dédiée. Voir le [chapitre 3](/course/fr/chapter3) pour plus de détails.

</Tip>

Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devrions toujours faire cela après avoir préparé le *dataloader* car cette méthode modifiera sa longueur. Nous utilisons un programme linéaire classique du taux d'apprentissage à 0 :

```py
from transformers import get_scheduler

num_train_epochs = 3
num_update_steps_per_epoch = len(train_dataloader)
num_training_steps = num_train_epochs * num_update_steps_per_epoch

lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)
```

Enfin, pour pousser notre modèle vers le *Hub*, nous avons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous à Hugging Face si vous n'êtes pas déjà connecté. Nous déterminons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix, il doit juste contenir votre nom d'utilisateur et ce que fait la fonction `get_full_repo_name()`) :

```py
from huggingface_hub import Repository, get_full_repo_name

model_name = "bert-finetuned-ner-accelerate"
repo_name = get_full_repo_name(model_name)
repo_name
```

```python out
'sgugger/bert-finetuned-ner-accelerate'
```

Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone existant du dépôt avec lequel nous travaillons :

```py
output_dir = "bert-finetuned-ner-accelerate"
repo = Repository(output_dir, clone_from=repo_name)
```

Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque.

### Boucle d'entraînement

Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Pour simplifier sa partie évaluation, nous définissons cette fonction `postprocess()` qui prend les prédictions et les étiquettes, et les convertit en listes de chaînes de caractères comme notre objet `metric` l'attend :

```py
def postprocess(predictions, labels):
    predictions = predictions.detach().cpu().clone().numpy()
    labels = labels.detach().cpu().clone().numpy()

    # Suppression de l'index ignoré (tokens spéciaux) et conversion en étiquettes
    true_labels = [[label_names[l] for l in label if l != -100] for label in labels]
    true_predictions = [
        [label_names[p] for (p, l) in zip(prediction, label) if l != -100]
        for prediction, label in zip(predictions, labels)
    ]
    return true_labels, true_predictions
```

Ensuite, nous pouvons écrire la boucle d'entraînement. Après avoir défini une barre de progression pour suivre l'évolution de l'entraînement, la boucle comporte trois parties :

- L'entraînement proprement dit, qui est l'itération classique sur le `train_dataloader`, passage en avant, puis passage en arrière et étape d'optimisation.
- L'évaluation, dans laquelle il y a une nouveauté après avoir obtenu les sorties de notre modèle sur un batch : puisque deux processus peuvent avoir paddé les entrées et les étiquettes à des formes différentes, nous devons utiliser `accelerator.pad_across_processes()` pour rendre les prédictions et les étiquettes de la même forme avant d'appeler la méthode `gather()`. Si nous ne le faisons pas, l'évaluation va soit se tromper, soit se bloquer pour toujours. Ensuite, nous envoyons les résultats à `metric.add_batch()` et appelons `metric.compute()` une fois que la boucle d'évaluation est terminée.
- Sauvegarde et téléchargement, où nous sauvegardons d'abord le modèle et le *tokenizer*, puis appelons `repo.push_to_hub()`. Remarquez que nous utilisons l'argument `blocking=False` pour indiquer à la bibliothèque 🤗 *Hub* de pousser dans un processus asynchrone. De cette façon, l'entraînement continue normalement et cette (longue) instruction est exécutée en arrière-plan.

Voici le code complet de la boucle d'entraînement :

```py
from tqdm.auto import tqdm
import torch

progress_bar = tqdm(range(num_training_steps))

for epoch in range(num_train_epochs):
    # Entraînement
    model.train()
    for batch in train_dataloader:
        outputs = model(**batch)
        loss = outputs.loss
        accelerator.backward(loss)

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)

    # Evaluation
    model.eval()
    for batch in eval_dataloader:
        with torch.no_grad():
            outputs = model(**batch)

        predictions = outputs.logits.argmax(dim=-1)
        labels = batch["labels"]

        # Nécessaire pour rembourrer les prédictions et les étiquettes à rassembler
        predictions = accelerator.pad_across_processes(predictions, dim=1, pad_index=-100)
        labels = accelerator.pad_across_processes(labels, dim=1, pad_index=-100)

        predictions_gathered = accelerator.gather(predictions)
        labels_gathered = accelerator.gather(labels)

        true_predictions, true_labels = postprocess(predictions_gathered, labels_gathered)
        metric.add_batch(predictions=true_predictions, references=true_labels)

    results = metric.compute()
    print(
        f"epoch {epoch}:",
        {
            key: results[f"overall_{key}"]
            for key in ["precision", "recall", "f1", "accuracy"]
        },
    )

    # Sauvegarder et télécharger
    accelerator.wait_for_everyone()
    unwrapped_model = accelerator.unwrap_model(model)
    unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
    if accelerator.is_main_process:
        tokenizer.save_pretrained(output_dir)
        repo.push_to_hub(
            commit_message=f"Training in progress epoch {epoch}", blocking=False
        )
```

Au cas où ce serait la première fois que vous verriez un modèle enregistré avec 🤗 *Accelerate*, prenons un moment pour inspecter les trois lignes de code qui l'accompagnent :

```py
accelerator.wait_for_everyone()
unwrapped_model = accelerator.unwrap_model(model)
unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
```

La première ligne est explicite : elle indique à tous les processus d'attendre que tout le monde soit à ce stade avant de continuer. C'est pour s'assurer que nous avons le même modèle dans chaque processus avant de sauvegarder. Ensuite, nous prenons le `unwrapped_model` qui est le modèle de base que nous avons défini. La méthode `accelerator.prepare()` modifie le modèle pour qu'il fonctionne dans l'entraînement distribué, donc il n'aura plus la méthode `save_pretrained()` ; la méthode `accelerator.unwrap_model()` annule cette étape. Enfin, nous appelons `save_pretrained()` mais nous disons à cette méthode d'utiliser `accelerator.save()` au lieu de `torch.save()`. 

Une fois ceci fait, vous devriez avoir un modèle qui produit des résultats assez similaires à celui entraîné avec le `Trainer`. Vous pouvez vérifier le modèle que nous avons formé en utilisant ce code à [*huggingface-course/bert-finetuned-ner-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-ner-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les implémenter directement en modifiant le code ci-dessus !

{/if}

### Utilisation du modèle <i>finetuné</i>

Nous vous avons déjà montré comment vous pouvez utiliser le modèle *finetuné* sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, vous devez juste spécifier l'identifiant de modèle approprié :

```py
from transformers import pipeline

# Remplacez ceci par votre propre checkpoint
model_checkpoint = "huggingface-course/bert-finetuned-ner"
token_classifier = pipeline(
    "token-classification", model=model_checkpoint, aggregation_strategy="simple"
)
token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.")
```

```python out
[{'entity_group': 'PER', 'score': 0.9988506, 'word': 'Sylvain', 'start': 11, 'end': 18},
 {'entity_group': 'ORG', 'score': 0.9647625, 'word': 'Hugging Face', 'start': 33, 'end': 45},
 {'entity_group': 'LOC', 'score': 0.9986118, 'word': 'Brooklyn', 'start': 49, 'end': 57}]
```

Super ! Notre modèle fonctionne aussi bien que le modèle par défaut pour ce pipeline !



<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter7/2.mdx" />

### Fin de la première partie du cours !
https://huggingface.co/learn/course/fr/chapter4/5.md

# Fin de la première partie du cours !

<CourseFloatingBanner
    chapter={4}
    classNames="absolute z-10 right-0 top-0"
/>

C'est la fin de la première partie du cours ! La partie 2 sera publiée le 15 novembre 2021 avec un grand événement communautaire, pour plus d'informations voir [ici](https://huggingface.co/blog/course-launch-event).

Vous devriez maintenant être capable de *finetuner* un modèle pré-entraîné sur un problème de classification de texte (phrases simples ou paires de phrases) et de télécharger le résultat sur le *Hub*. Pour vous assurer que vous maîtrisez cette première section, vous devriez refaire ça sur un problème qui vous intéresse (et pas nécessairement en anglais si vous parlez une autre langue) ! Vous pouvez trouver de l'aide dans les [forums d'Hugging Face](https://discuss.huggingface.co/) et partager votre projet dans [ce sujet](https://discuss.huggingface.co/t/share-your-projects/6803) une fois que vous avez terminé.

Nous sommes impatients de voir ce que vous allez construire avec cet outil !

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter4/5.mdx" />

### Le <i>Hub</i> d'Hugging Face
https://huggingface.co/learn/course/fr/chapter4/1.md

# Le <i>Hub</i> d'Hugging Face

<CourseFloatingBanner
    chapter={4}
    classNames="absolute z-10 right-0 top-0"
/>

Le [*Hub* d'Hugging Face](https://huggingface.co/), notre site internet principal, est une plateforme centrale qui permet à quiconque de découvrir, d'utiliser et de contribuer à de nouveaux modèles et jeux de données de pointe. Il héberge une grande variété de modèles, dont plus de 10 000 sont accessibles au public. Nous nous concentrerons sur les modèles dans ce chapitre, et nous examinerons les jeux de données au chapitre 5.

Les modèles présents dans le *Hub* ne sont pas limités à 🤗 *Transformers* ou même au NLP. Il existe des modèles de [Flair](https://github.com/flairNLP/flair) et [AllenNLP](https://github.com/allenai/allennlp) pour le NLP, [Asteroid](https://github.com/asteroid-team/asteroid) et [pyannote](https://github.com/pyannote/pyannote-audio) pour l'audio, et [timm](https://github.com/rwightman/pytorch-image-models) pour la vision, pour n'en citer que quelques-uns. 

Chacun de ces modèles est hébergé sous forme de dépôt Git, ce qui permet le suivi des versions et la reproductibilité. Partager un modèle sur le *Hub*, c'est l'ouvrir à la communauté et le rendre accessible à tous ceux qui souhaitent l'utiliser facilement, ce qui leur évite d'avoir à entraîner eux-mêmes un modèle et simplifie le partage et l'utilisation. 

En outre, le partage d'un modèle sur le *Hub* déploie automatiquement une API d'inférence hébergée pour ce modèle. Toute personne de la communauté est libre de la tester directement sur la page du modèle, avec des entrées personnalisées et des *widgets* appropriés.

La meilleure partie est que le partage ainsi que l'utilisation de n'importe quel modèle public sur le *Hub* sont totalement gratuits ! [Des plans payants](https://huggingface.co/pricing) existent également si vous souhaitez partager des modèles en privé.

La vidéo ci-dessous montre comment naviguer sur le *Hub* :

<Youtube id="XvSGPZFEjDY"/>

Avoir un compte huggingface.co est nécessaire pour suivre cette partie car nous allons créer et gérer des répertoires sur le *Hub* : [créer un compte](https://huggingface.co/join)

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter4/1.mdx" />

### Partage de modèles pré-entraînés
https://huggingface.co/learn/course/fr/chapter4/3.md

# Partage de modèles pré-entraînés

{#if fw === 'pt'}

<CourseFloatingBanner chapter={4}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter4/section3_pt.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter4/section3_pt.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter4/section3_pt.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter4/section3_pt.ipynb"},
]} />
{:else}
<CourseFloatingBanner chapter={4}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter4/section3_tf.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter4/section3_tf.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter4/section3_tf.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter4/section3_tf.ipynb"},
]} />

{/if}

Dans les étapes ci-dessous, nous allons examiner les moyens les plus simples de partager des modèles pré-entraînés sur le 🤗 *Hub*. Il existe des outils et des services disponibles qui permettent de simplifier le partage et la mise à jour des modèles directement sur le *Hub*, que nous allons explorer ci-dessous.

<Youtube id="9yY3RB_GSPM"/>

Nous encourageons tous les utilisateurs qui entraînent des modèles à contribuer en les partageant avec la communauté. Le partage des modèles, même s'ils ont été entraînés sur des jeux de données très spécifiques, aidera les autres, en leur faisant gagner du temps, des ressources de calcul et en leur donnant accès à des artefacts entraînés utiles. À votre tour, vous pourrez bénéficier du travail effectué par les autres !

Il y a trois façons de créer de nouveaux dépôts de modèles :

- en utilisant l'API `push_to_hub`,
- en utilisant la bibliothèque Python `huggingface_hub`,
- en utilisant l'interface web.

Une fois que vous avez créé un dépôt, vous pouvez y charger des fichiers via git et git-lfs. Nous allons vous guider dans la création de dépôts de modèles et le téléchargement de fichiers dans les sections suivantes.


## Utilisation de l'API `push_to_hub`

{#if fw === 'pt'}

<Youtube id="Zh0FfmVrKX0"/>

{:else}

<Youtube id="pUh5cGmNV8Y"/>

{/if}

La façon la plus simple de télécharger des fichiers vers le *Hub* est d'utiliser l'API `push_to_hub`.

Avant d'aller plus loin, vous devrez générer un jeton d'authentification afin que l'API `huggingface_hub` sache qui vous êtes et à quels espaces de noms vous avez accès en écriture. Assurez-vous que vous êtes dans un environnement où vous avez installé `transformers` (voir la [Configuration](/course/fr/chapter0)). Si vous êtes dans un *notebook*, vous pouvez utiliser la fonction suivante pour vous connecter :

```python
from huggingface_hub import notebook_login

notebook_login()
```

Dans un terminal, vous pouvez exécuter :

```bash
huggingface-cli login
```

Dans les deux cas, vous serez invité à saisir votre nom d'utilisateur et votre mot de passe, qui sont les mêmes que ceux que vous utilisez pour vous connecter au *Hub*. Si vous n'avez pas encore de profil pour le Hub, vous devez en créer un [ici](https://huggingface.co/join).

Super ! Votre jeton d'authentification est maintenant stocké dans votre dossier de cache. Créons quelques dépôts !

{#if fw === 'pt'}

Si vous avez joué avec l'API `Trainer` pour entraîner un modèle, le moyen le plus simple de le télécharger sur le *Hub* est de définir `push_to_hub=True` lorsque vous définissez vos `TrainingArguments` :

```py
from transformers import TrainingArguments

training_args = TrainingArguments(
    "bert-finetuned-mrpc", save_strategy="epoch", push_to_hub=True
)
```

Lorsque vous appelez `trainer.train()`, le `Trainer` téléchargera alors votre modèle vers le *Hub* à chaque fois qu'il sera sauvegardé (ici à chaque époque) dans un dépôt dans votre espace personnel. Ce dépôt sera nommé comme le répertoire de sortie que vous avez choisi (ici `bert-finetuned-mrpc`) mais vous pouvez choisir un nom différent avec `hub_model_id = "a_different_name"`.

Pour télécharger votre modèle vers une organisation dont vous êtes membre, passez-le simplement avec `hub_model_id = "my_organization/my_repo_name"`.

Une fois que votre entraînement est terminé, vous devriez faire un dernier `trainer.push_to_hub()` pour télécharger la dernière version de votre modèle. Cela générera également une carte pour le modèle avec toutes les métadonnées pertinentes, rapportant les hyperparamètres utilisés et les résultats d'évaluation ! Voici un exemple du contenu que vous pourriez trouver dans une telle carte de modèle :
<div class="flex justify-center">
  <img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/model_card.png" alt="An example of an auto-generated model card." width="100%"/>
</div>

{:else}

Si vous utilisez Keras pour entraîner votre modèle, le moyen le plus simple de le télécharger sur le *Hub* est de passer un `PushToHubCallback` lorsque vous appelez `model.fit()` :

```py
from transformers import PushToHubCallback

callback = PushToHubCallback(
    "bert-finetuned-mrpc", save_strategy="epoch", tokenizer=tokenizer
)
```

Ensuite, vous devez ajouter `callbacks=[callback]` dans votre appel à `model.fit()`. Le *callback* téléchargera alors votre modèle vers le *Hub* à chaque fois qu'il sera sauvegardé (ici à chaque époque) dans un dépôt dans votre espace de noms. Ce dépôt sera nommé comme le répertoire de sortie que vous avez choisi (ici `bert-finetuned-mrpc`) mais vous pouvez choisir un nom différent avec `hub_model_id = "a_different_name"`.

Pour télécharger votre modèle dans une organisation dont vous êtes membre, passez-le simplement avec `hub_model_id = "my_organization/my_repo_name"`.

{/if}

A un niveau inférieur, l'accès au *Hub* peut être fait directement sur les modèles, les *tokenizers* et les objets de configuration via leur méthode `push_to_hub()`. Cette méthode s'occupe à la fois de la création du dépôt et de l'envoi les fichiers du modèle et du *tokenizer* directement dans le dépôt. Aucune manipulation manuelle n'est nécessaire, contrairement à l'API que nous verrons plus loin.

Pour avoir une idée de son fonctionnement, commençons par initialiser un modèle et un *tokenizer* :

{#if fw === 'pt'}
```py
from transformers import AutoModelForMaskedLM, AutoTokenizer

checkpoint = "camembert-base"

model = AutoModelForMaskedLM.from_pretrained(checkpoint)
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
```
{:else}
```py
from transformers import TFAutoModelForMaskedLM, AutoTokenizer

checkpoint = "camembert-base"

model = TFAutoModelForMaskedLM.from_pretrained(checkpoint)
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
```
{/if}

Vous êtes libre de faire ce que vous voulez avec ces objets : ajouter des *tokens* au *tokenizer*, entraîner le modèle, le *finetuner*. Une fois que vous êtes satisfait du modèle, des poids et du *tokenizer* obtenus, vous pouvez utiliser la méthode `push_to_hub()` directement disponible sur l'objet `model` :

```py
model.push_to_hub("dummy-model")
```

Cela va créer le nouveau dépôt `dummy-model` dans votre profil et le remplir avec les fichiers du modèle.
Faites la même chose avec le *tokenizer*, de sorte que tous les fichiers sont maintenant disponibles dans ce dépôt :

```py
tokenizer.push_to_hub("dummy-model")
```

Si vous appartenez à une organisation, il suffit de spécifier l'argument `organization` pour télécharger dans l'espace de cette organisation :

```py
tokenizer.push_to_hub("dummy-model", organization="huggingface")
```

Si vous souhaitez utiliser un jeton Hugging Face spécifique, vous pouvez également le spécifier à la méthode `push_to_hub()` :

```py
tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token="<TOKEN>")
```

Maintenant, dirigez-vous sur *Hub* pour trouver votre modèle nouvellement téléchargé : *https://huggingface.co/user-or-organization/dummy-model*.

Cliquez sur l'onglet « Fichiers et versions » et vous devriez voir les fichiers visibles dans la capture d'écran suivante :

{#if fw === 'pt'}
<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/push_to_hub_dummy_model.png" alt="Dummy model containing both the tokenizer and model files." width="80%"/>
</div>
{:else}
<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/push_to_hub_dummy_model_tf.png" alt="Dummy model containing both the tokenizer and model files." width="80%"/>
</div>
{/if}

<Tip>

✏️ **Essayez** Prenez le modèle et le *tokenizer* associés au *checkpoint* `bert-base-cased` et téléchargez-les vers un dépôt dans votre espace en utilisant la méthode `push_to_hub()`. Vérifiez que le dépôt apparaît correctement sur votre page avant de le supprimer.

</Tip>

Comme vous l'avez vu, la méthode `push_to_hub()` accepte plusieurs arguments, ce qui permet de télécharger vers un dépôt ou un espace d'organisation spécifique, ou d'utiliser un jeton d'API différent. Nous vous recommandons de jeter un coup d'œil à la spécification de la méthode disponible directement dans la documentation de [🤗 *Transformers*](https://huggingface.co/transformers/model_sharing.html) pour avoir une idée de ce qui est possible.

La méthode `push_to_hub()` est soutenue par le *package* Python [`huggingface_hub`](https://github.com/huggingface/huggingface_hub), qui offre une API directe au *Hub*. C'est intégré à 🤗 *Transformers* et à plusieurs autres bibliothèques d'apprentissage automatique, comme [`allenlp`](https://github.com/allenai/allennlp). Bien que nous nous concentrions sur l'intégration via 🤗 *Transformers* dans ce chapitre, son intégration dans votre propre code ou bibliothèque est simple.

Passez à la dernière section pour voir comment télécharger des fichiers dans votre dépôt nouvellement créé !

## Utilisation de la bibliothèque Python `huggingface_hub`

La bibliothèque Python `huggingface_hub` est un *package* qui offre un ensemble d'outils pour les hubs des modèles et des jeux de données. Elle fournit des méthodes et des classes simples pour des tâches courantes telles qu'obtenir et gérer des informations à propos des dépôts sur le *Hub*. Elle fournit des APIs simples qui fonctionnent au-dessus de git pour gérer le contenu de ces dépôts et pour intégrer le *Hub* dans vos projets et bibliothèques.

De la même manière que pour l'utilisation de l'API `push_to_hub`, vous devrez avoir votre jeton d'API enregistré dans votre cache. Pour ce faire, vous devrez utiliser la commande `login` de la CLI, comme mentionné dans la section précédente (encore une fois, assurez-vous de faire précéder ces commandes du caractère `!` si vous les exécutez dans Google Colab) :

```bash
huggingface-cli login
```

Le *package* `huggingface_hub` offre plusieurs méthodes et classes qui sont utiles pour notre objectif. Tout d'abord, il y a quelques méthodes pour gérer la création, la suppression des dépôts, et autres :

```python no-format
from huggingface_hub import (
    # Gestion des utilisateurs
    login,
    logout,
    whoami,

    # Création et gestion du dépôt
    create_repo,
    delete_repo,
    update_repo_visibility,

    # Et quelques méthodes pour récupérer/changer des informations sur le contenu
    list_models,
    list_datasets,
    list_metrics,
    list_repo_files,
    upload_file,
    delete_file,
)
```


De plus, elle offre la très puissante classe `Repository` pour gérer un dépôt local. Nous allons explorer ces méthodes et cette classe dans les prochaines sections pour comprendre comment les exploiter.

La méthode `create_repo` peut être utilisée pour créer un nouveau dépôt sur le *Hub* :

```py
from huggingface_hub import create_repo

create_repo("dummy-model")
```

Ceci créera le dépôt `dummy-model` dans votre espace. Si vous le souhaitez, vous pouvez spécifier à quelle organisation le dépôt doit appartenir en utilisant l'argument `organization` :

```py
from huggingface_hub import create_repo

create_repo("dummy-model", organization="huggingface")
```

Cela créera le dépôt `dummy-model` dans l'espace de nom `huggingface`, en supposant que vous appartenez à cette organisation.
D'autres arguments qui peuvent être utiles sont :

- `private`, afin de spécifier si le dépôt doit être visible des autres ou non,
- `token`, si vous voulez remplacer le jeton stocké dans votre cache par un jeton donné,
- `repo_type`, si vous souhaitez créer un `dataset` ou un `space` au lieu d'un modèle. Les valeurs acceptées sont `"dataset"` et `"space"`.

Une fois que le dépôt est créé, nous devons y ajouter des fichiers ! Passez à la section suivante pour voir les trois façons dont cela peut être géré.


## Utilisation de l'interface web

L'interface web offre des outils pour gérer les dépôts directement dans le *Hub*. En utilisant l'interface, vous pouvez facilement créer des dépôts, ajouter des fichiers (même de grande taille !), explorer des modèles, visualiser les différences, et bien plus encore.

Pour créer un nouveau dépôt, visitez [huggingface.co/new](https://huggingface.co/new) :

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/new_model.png" alt="Page showcasing the model used for the creation of a new model repository." width="80%"/>
</div>

Tout d'abord, indiquez le propriétaire du dépôt : il peut s'agir de vous ou de l'une des organisations auxquelles vous êtes affilié. Si vous choisissez une organisation, le modèle sera présenté sur la page de l'organisation et chaque membre de l'organisation aura la possibilité de contribuer au dépôt.

Ensuite, saisissez le nom de votre modèle. Ce sera également le nom du dépôt. Enfin, vous pouvez préciser si vous souhaitez que votre modèle soit public ou privé. Les modèles privés sont cachés de la vue du public.

Après avoir créé votre dépôt de modèles, vous devriez voir une page comme celle-ci :

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/empty_model.png" alt="An empty model page after creating a new repository." width="80%"/>
</div>

C'est là que votre modèle sera hébergé. Pour commencer à le remplir, vous pouvez ajouter un fichier README directement depuis l'interface web.

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/dummy_model.png" alt="The README file showing the Markdown capabilities." width="80%"/>
</div>

Le fichier README est en Markdown. N'hésitez pas à vous lâcher avec lui ! La troisième partie de ce chapitre est consacrée à la construction d'une carte de modèle. Celles-ci sont d'une importance capitale pour valoriser votre modèle, car c'est par elles que vous indiquez aux autres ce qu'il peut faire.

Si vous regardez l'onglet « *Files and versions* », vous verrez qu'il n'y a pas encore beaucoup de fichiers : juste le *README.md* que vous venez de créer et le fichier *.gitattributes* qui garde la trace des gros fichiers.

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/files.png" alt="The 'Files and versions' tab only shows the .gitattributes and README.md files." width="80%"/>
</div>

Nous allons maintenant voir comment ajouter de nouveaux fichiers.

## Téléchargement des fichiers du modèle

Le système de gestion des fichiers sur le *Hub* est basé sur git pour les fichiers ordinaires et git-lfs (qui signifie [Git Large File Storage](https://git-lfs.github.com/)) pour les fichiers plus importants. 

Dans la section suivante, nous passons en revue trois façons différentes de télécharger des fichiers sur le *Hub* : par `huggingface_hub` et par des commandes git.

### L'approche `upload_file'

L'utilisation de `upload_file` ne nécessite pas que git et git-lfs soient installés sur votre système. Il pousse les fichiers directement vers le 🤗 *Hub* en utilisant des requêtes HTTP POST. Une limitation de cette approche est qu'elle ne gère pas les fichiers dont la taille est supérieure à 5 Go.
Si vos fichiers ont une taille supérieure à 5 Go, veuillez suivre les deux autres méthodes détaillées ci-dessous.

L'API peut être utilisée comme suit :

```py
from huggingface_hub import upload_file

upload_file(
    "<path_to_file>/config.json",
    path_in_repo="config.json",
    repo_id="<namespace>/dummy-model",
)
```

Ceci téléchargera le fichier `config.json` disponible à `<path_to_file>` à la racine du dépôt en tant que `config.json`, vers le dépôt `dummy-model`.
D'autres arguments qui peuvent être utiles sont :

- `token`, si vous souhaitez remplacer le jeton stocké dans votre cache par un jeton donné,
- `repo_type`, si vous souhaitez télécharger vers un `dataset` ou un `space` au lieu d'un modèle. Les valeurs acceptées sont `"dataset"` et `"space"`.


### La classe `Repository`

La classe `Repository` gère un dépôt local d'une manière similaire à git. Elle abstrait la plupart des problèmes que l'on peut rencontrer avec git pour fournir toutes les fonctionnalités dont nous avons besoin. 

L'utilisation de cette classe nécessite l'installation de git et de git-lfs, donc assurez-vous que git-lfs est installé (voir [ici](https://git-lfs.github.com/) pour les instructions d'installation) et configuré avant de commencer. 

Afin de commencer à jouer avec le dépôt que nous venons de créer, nous pouvons commencer par l'initialiser dans un dossier local en clonant le dépôt distant :

```py
from huggingface_hub import Repository

repo = Repository("<path_to_dummy_folder>", clone_from="<namespace>/dummy-model")
```

Cela a créé le dossier `<path_to_dummy_folder>` dans notre répertoire de travail. Ce dossier ne contient que le fichier `.gitattributes` car c'est le seul fichier créé lors de l'instanciation du dépôt par `create_repo`.

A partir de maintenant, nous pouvons utiliser plusieurs des méthodes traditionnelles de git :

```py
repo.git_pull()
repo.git_add()
repo.git_commit()
repo.git_push()
repo.git_tag()
```

Et d'autres encore ! Nous vous recommandons de jeter un coup d’œil  à la documentation de `Repository` disponible [ici](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management) pour une vue d'ensemble de toutes les méthodes disponibles.

Actuellement, nous avons un modèle et un *tokenizer* que nous voulons pousser vers le *Hub*. Nous avons réussi à cloner le dépôt, nous pouvons donc enregistrer les fichiers dans ce dépôt.

Nous nous assurons d'abord que notre clone local est à jour en récupérant les dernières modifications :

```py
repo.git_pull()
```

Une fois que c'est fait, nous sauvegardons les fichiers du modèle et du *tokenizer* :

```py
model.save_pretrained("<path_to_dummy_folder>")
tokenizer.save_pretrained("<path_to_dummy_folder>")
```

Le `<path_to_dummy_folder>` contient maintenant tous les fichiers du modèle et du *tokenizer*. Nous suivons le flux de travail git habituel en ajoutant des fichiers à la zone de transit, en les validant et en les poussant vers le *Hub* :

```py
repo.git_add()
repo.git_commit("Add model and tokenizer files")
repo.git_push()
```

Félicitations ! Vous venez de pousser vos premiers fichiers sur le *Hub*.

### L'approche basée sur git

Il s'agit de l'approche la plus basique pour télécharger des fichiers : nous le ferons directement avec git et git-lfs. La plupart des difficultés sont abstraites par les approches précédentes, mais il y a quelques réserves avec la méthode suivante, nous allons donc suivre un cas d'utilisation plus complexe.

L'utilisation de cette classe nécessite l'installation de git et de git-lfs, donc assurez-vous d'avoir [git-lfs](https://git-lfs.github.com/) installé et configuré avant de commencer. 

Commencez par initialiser git-lfs :

```bash
git lfs install
```

```bash
Updated git hooks.
Git LFS initialized.
```

Une fois que c'est fait, la première étape consiste à cloner votre dépôt de modèles :

```bash
git clone https://huggingface.co/<namespace>/<your-model-id>
```

Mon nom d'utilisateur est `lysandre` et j'ai utilisé le nom de modèle `dummy`, donc pour moi la commande ressemble à ce qui suit :

```
git clone https://huggingface.co/lysandre/dummy
```

J'ai maintenant un dossier nommé *dummy* dans mon répertoire de travail. Je peux `cd` dans ce dossier et jeter un coup d'oeil à son contenu :

```bash
cd dummy && ls
```

```bash
README.md
```

Si vous venez de créer votre dépôt en utilisant la méthode `create_repo` du *Hub*, ce dossier devrait seulement contenir un fichier caché `.gitattributes`. Si vous avez suivi les instructions de la section précédente pour créer un dépôt en utilisant l'interface web, le dossier devrait contenir un seul fichier *README.md* à côté du fichier caché `.gitattributes`, comme indiqué ici.

L'ajout d'un fichier de taille normale, comme un fichier de configuration, un fichier de vocabulaire, ou tout autre fichier de moins de quelques mégaoctets, est fait exactement comme on le ferait dans n'importe quel système basé sur git. Cependant, les fichiers plus volumineux doivent être enregistrés via git-lfs afin de les pousser vers *huggingface.co*. 

Revenons un peu à Python pour générer un modèle et un *tokenizer* que nous souhaitons « commiter » dans notre dépôt fictif :

{#if fw === 'pt'}
```py
from transformers import AutoModelForMaskedLM, AutoTokenizer

checkpoint = "camembert-base"

model = AutoModelForMaskedLM.from_pretrained(checkpoint)
tokenizer = AutoTokenizer.from_pretrained(checkpoint)

# Faites ce que vous voulez avec le modèle, entraînez-le, finetunez-le...

model.save_pretrained("<path_to_dummy_folder>")
tokenizer.save_pretrained("<path_to_dummy_folder>")
```
{:else}
```py
from transformers import TFAutoModelForMaskedLM, AutoTokenizer

checkpoint = "camembert-base"

model = TFAutoModelForMaskedLM.from_pretrained(checkpoint)
tokenizer = AutoTokenizer.from_pretrained(checkpoint)

# Faites ce que vous voulez avec le modèle, entraînez-le, finetunez-le...

model.save_pretrained("<path_to_dummy_folder>")
tokenizer.save_pretrained("<path_to_dummy_folder>")
```
{/if}

Maintenant que nous avons sauvegardé quelques artefacts de modèle et de *tokenizer*, regardons à nouveau le dossier *dummy* :

```bash
ls
```

{#if fw === 'pt'}
```bash
config.json  pytorch_model.bin  README.md  sentencepiece.bpe.model  special_tokens_map.json tokenizer_config.json  tokenizer.json
```

Si vous regardez la taille des fichiers (par exemple, avec `ls -lh`), vous devriez voir que le fichier d'état du modèle (*pytorch_model.bin*) est la seule exception, avec plus de 400 Mo.

{:else}
```bash
config.json  README.md  sentencepiece.bpe.model  special_tokens_map.json  tf_model.h5  tokenizer_config.json  tokenizer.json
```

Si vous regardez la taille des fichiers (par exemple, avec `ls -lh`), vous devriez voir que le fichier dict de l'état du modèle (*t5_model.h5*) est la seule aberration, avec plus de 400 Mo.

{/if}

<Tip>
✏️ Lors de la création du dépôt à partir de l'interface web, le fichier <i>.gitattributes</i> est automatiquement configuré pour considérer les fichiers avec certaines extensions, comme <i>.bin</i> et <i>.h5</i>, comme des fichiers volumineux, et git-lfs les suivra sans aucune configuration nécessaire de votre part.
</Tip> 

Nous pouvons maintenant aller de l'avant et procéder comme nous le ferions habituellement avec des dépôts Git traditionnels. Nous pouvons ajouter tous les fichiers à l'environnement Git en utilisant la commande `git add` :

```bash
git add .
```

Nous pouvons alors jeter un coup d'œil aux fichiers :

```bash
git status
```

{#if fw === 'pt'}
```bash
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
  modified:   .gitattributes
	new file:   config.json
	new file:   pytorch_model.bin
	new file:   sentencepiece.bpe.model
	new file:   special_tokens_map.json
	new file:   tokenizer.json
	new file:   tokenizer_config.json
```
{:else}
```bash
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
  modified:   .gitattributes
  	new file:   config.json
	new file:   sentencepiece.bpe.model
	new file:   special_tokens_map.json
	new file:   tf_model.h5
	new file:   tokenizer.json
	new file:   tokenizer_config.json
```
{/if}

De même, nous pouvons nous assurer que git-lfs suit les bons fichiers en utilisant sa commande `status` :

```bash
git lfs status
```

{#if fw === 'pt'}
```bash
On branch main
Objects to be pushed to origin/main:


Objects to be committed:

	config.json (Git: bc20ff2)
	pytorch_model.bin (LFS: 35686c2)
	sentencepiece.bpe.model (LFS: 988bc5a)
	special_tokens_map.json (Git: cb23931)
	tokenizer.json (Git: 851ff3e)
	tokenizer_config.json (Git: f0f7783)

Objects not staged for commit:


```

Nous pouvons voir que tous les fichiers ont `Git` comme gestionnaire, sauf *pytorch_model.bin* et *sentencepiece.bpe.model*, qui ont `LFS`. Super !

{:else}
```bash
On branch main
Objects to be pushed to origin/main:


Objects to be committed:

	config.json (Git: bc20ff2)
	sentencepiece.bpe.model (LFS: 988bc5a)
	special_tokens_map.json (Git: cb23931)
	tf_model.h5 (LFS: 86fce29)
	tokenizer.json (Git: 851ff3e)
	tokenizer_config.json (Git: f0f7783)

Objects not staged for commit:


```

Nous pouvons voir que tous les fichiers ont `Git` comme gestionnaire, sauf *t5_model.h5* qui a `LFS`. Super !

{/if}

Passons aux étapes finales, *committing* et *pushing* vers le dépôt distant *huggingface.co* :

```bash
git commit -m "First model version"
```

{#if fw === 'pt'}
```bash
[main b08aab1] First model version
 7 files changed, 29027 insertions(+)
  6 files changed, 36 insertions(+)
 create mode 100644 config.json
 create mode 100644 pytorch_model.bin
 create mode 100644 sentencepiece.bpe.model
 create mode 100644 special_tokens_map.json
 create mode 100644 tokenizer.json
 create mode 100644 tokenizer_config.json
```
{:else}
```bash
[main b08aab1] First model version
 6 files changed, 36 insertions(+)
 create mode 100644 config.json
 create mode 100644 sentencepiece.bpe.model
 create mode 100644 special_tokens_map.json
 create mode 100644 tf_model.h5
 create mode 100644 tokenizer.json
 create mode 100644 tokenizer_config.json
```
{/if}

Le chargement peut prendre un peu de temps, en fonction de la vitesse de votre connexion Internet et de la taille de vos fichiers :

```bash
git push
```

```bash
Uploading LFS objects: 100% (1/1), 433 MB | 1.3 MB/s, done.
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 12 threads
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 288.27 KiB | 6.27 MiB/s, done.
Total 9 (delta 1), reused 0 (delta 0), pack-reused 0
To https://huggingface.co/lysandre/dummy
   891b41d..b08aab1  main -> main
```

{#if fw === 'pt'}
Si nous jetons un coup d'œil au dépôt du modèle, lorsque cette opération est terminée, nous pouvons voir tous les fichiers récemment ajoutés :

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/full_model.png" alt="The 'Files and versions' tab now contains all the recently uploaded files." width="80%"/>
</div>

L'interface utilisateur vous permet d'explorer les fichiers du modèle et les *commits* et de voir la différence introduite par chaque *commit* :

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/diffs.gif" alt="The diff introduced by the recent commit." width="80%"/>
</div>
{:else}
Si nous jetons un coup d'œil au dépôt du modèle, lorsque cette opération est terminée, nous pouvons voir tous les fichiers récemment ajoutés :

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/full_model_tf.png" alt="The 'Files and versions' tab now contains all the recently uploaded files." width="80%"/>
</div>

L'interface utilisateur vous permet d'explorer les fichiers du modèle et les *commits* et de voir la différence introduite par chaque *commit* :

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/diffstf.gif" alt="The diff introduced by the recent commit." width="80%"/>
</div>
{/if}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter4/3.mdx" />

### Quiz de fin de chapitre
https://huggingface.co/learn/course/fr/chapter4/6.md

# Quiz de fin de chapitre

<CourseFloatingBanner
    chapter={4}
    classNames="absolute z-10 right-0 top-0"
/>

Testons ce que vous avez appris dans ce chapitre !
  
### 1. A quoi sont limités les modèles du *Hub* ?

<Question
	choices={[
		{
			text: "Aux modèles de la bibliothèque 🤗 <i>Transformers</i>.",
			explain: "Si les modèles de la bibliothèque 🤗 <i>Transformers</i> sont pris en charge sur le <i>Hub</i>, ils ne sont pas les seuls !"
		},
		{
			text: "Tous les modèles avec une interface similaire à 🤗 <i>Transformers</i>.",
			explain: "Aucune exigence d'interface n'est fixée lors du téléchargement de modèles vers le <i>Hub</i>."
		},
		{
			text: "Il n'y a pas de limites.",
			explain: "Il n'y a pas de limites au téléchargement de modèles sur le <i>Hub</i>.",
            correct: true
		},
        {
			text: "Des modèles qui sont d'une certaine manière liés au NLP.",
			explain: "Aucune exigence n'est fixée concernant le domaine d'application !"
		}
	]}
/>

### 2. Comment pouvez-vous gérer les modèles sur le *Hub* ?

<Question
	choices={[
		{
			text: "Par le biais d'un compte GCP.",
			explain: " "
		},
		{
			text: "Grâce à la distribution pair à pair.",
			explain: " "
		},
		{
			text: "Par le biais de git et git-lfs.",
			explain: "Les modèles sur le <i>Hub</i> sont de simples dépôts Git exploitant <code>git-lfs</code> pour les fichiers volumineux.",
            correct: true
		}
	]}
/>

### 3. Que pouvez-vous faire en utilisant l'interface web du *Hub* ? 

<Question
	choices={[
		{
			text: "« <i>Forker</i> » un dépôt existant.",
			explain: "« <i>Forker</i> » un dépôt n'est pas possible sur le <i>Hub</i>."
		},
		{
			text: "Créer un nouveau dépôt de modèles.",
			explain: "Ce n'est pas tout ce que vous pouvez faire, cependant.",
            correct: true
		},
		{
			text: "Gérer et modifier des fichiers.",
			explain: "Ce n'est pas la seule bonne réponse, cependant.",
            correct: true
		},
        {
			text: "Télécharger des fichiers.",
			explain: "Mais ce n'est pas tout.",
            correct: true
		},
        {
			text: "Voir les différences entre les versions.",
			explain: "Ce n'est pas tout ce que vous pouvez faire.",
            correct: true
		}
	]}
/>

### 4. Qu'est-ce qu'une carte de modèle ?

<Question
	choices={[
		{
			text: "Une description approximative du modèle, donc moins importante que les fichiers du modèle et du <i>tokenizer</i>.",
			explain: "Il s'agit bien d'une description du modèle, mais c'est un élément important : s'il est incomplet ou absent, l'utilité du modèle est considérablement réduite."
		},
		{
			text: "Un moyen d'assurer la reproductibilité, la réutilisation et l'équité..",
			explain: "Le fait de partager les bonnes informations dans la fiche du modèle aidera les utilisateurs à tirer parti de votre modèle et à être conscients de ses limites et de ses biais.",
            correct: true
		},
		{
			text: "Un fichier Python qui peut être exécuté pour récupérer des informations sur le modèle.",
			explain: "Les cartes de modèle sont de simples fichiers Markdown."
		}
	]}
/>

### 5. Lesquels de ces objets de la bibliothèque 🤗 *Transformers* peuvent être directement partagés sur le Hub avec `push_to_hub()` ?

{#if fw === 'pt'}
<Question
	choices={[
		{
			text: "Un <i>tokenizer</i>",
			explain: "Tous les <i>tokenizers</i> ont la méthode <code>push_to_hub</code> et l'utiliser poussera tous les fichiers du <i>tokenizer</i> (vocabulaire, architecture du <i>tokenizer</i>, etc.) vers un dépôt donné. Ce n'est pas la seule bonne réponse, cependant !",
            correct: true
		},
		{
			text: "Une configuration de modèle",
			explain: "Toutes les configurations de modèles ont la méthode <code>push_to_hub</code> et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?",
            correct: true
		},
		{
			text: "Un <i>modèle</i>",
			explain: "Tous les modèles ont la méthode <code>push_to_hub</code> qui le <i>pushra</i> ainsi que leurs fichiers de configuration, vers un dépôt donné. Ce n'est pas tout ce que vous pouvez partager, cependant.",
            correct: true
		},
        {
			text: "<i>Trainer</i>",
			explain: "Le <code>Trainer</code> implémente aussi la méthode <code>push_to_hub</code>. L'utiliser téléchargera le modèle, sa configuration, le <i>tokenizer</i> et une ébauche de carte de modèle vers un dépôt donné. Essayez une autre réponse !",
            correct: true
		}
	]}
/>
{:else}
<Question
	choices={[
		{
			text: "Un <i>tokenizer</i>",
			explain: "Toutes les configurations de modèles ont la méthode <code>push_to_hub</code> et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?",
            correct: true
		},
		{
			text: "Une configuration de modèle",
			explain: "Toutes les configurations de modèles ont la méthode <code>push_to_hub</code> et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?",
            correct: true
		},
		{
			text: "Un <i>modèle</i>",
			explain: "Tous les modèles ont la méthode <code>push_to_hub</code> qui le <i>pushra</i> ainsi que leurs fichiers de configuration, vers un dépôt donné. Ce n'est pas tout ce que vous pouvez partager, cependant.",
            correct: true
		},
		{
			text: "Tout ce qui précède avec un <i>callback</i> dédié",
			explain: "Le <code>PushToHubCallback</code> enverra régulièrement tous ces objets à un dépôt pendant l'entraînement.",
            correct: true
		}
	]}
/>
{/if}

### 6. Quelle est la première étape lorsqu'on utilise la méthode `push_to_hub()` ou les outils CLI ?

<Question
	choices={[
		{
			text: "Se connecter sur le site web.",
			explain: "Cela ne vous aidera pas sur votre machine locale."
		},
		{
			text: "Exécuter 'huggingface-cli login' dans un terminal.",
			explain: "Cela va télécharger et mettre en cache votre jeton personnel.",
            correct: true
		},
		{
			text: "Exécuter 'notebook_login()' dans un <i>notebook</i>.",
			explain: "Cela affichera un <i>widget</i> pour vous permettre de vous authentifier.",
            correct: true
		},
	]}
/>

### 7. Vous utilisez un modèle et un *tokenizer*, comment pouvez-vous les télécharger sur le *Hub* ?

<Question
	choices={[
		{
			text: "En appelant la méthode push_to_hub directement sur le modèle et le <i>tokenizer</i>.",
			explain: " ",
            correct: true
		},
		{
			text: "Au sein du moteur d'exécution Python, en les enveloppant dans une balise <code>huggingface_hub</code>.",
			explain: "Les modèles et les <i>tokenizers</i> bénéficient déjà de <code>huggingface_hub</code> : pas besoin d'emballage supplémentaire !"
		},
		{
			text: "En les sauvegardant sur le disque et en appelant <code>transformers-cli upload-model</code>.",
			explain: "La commande <code>upload-model</code> n'existe pas."
		}
	]}
/>

### 8. Quelles opérations git pouvez-vous faire avec la classe `Repository` ?

<Question
	choices={[
		{
			text: "Un <i>commit</i>.",
			explain: "La méthode <code>git_commit()</code> est là pour ça.",
            correct: true
		},
		{
			text: "Un <i>pull</i>.",
			explain: "C'est le but de la méthode <code>git_pull()</code>.",
            correct: true
		},
		{
			text: "Un <i>push</i>.",
			explain: "La méthode <code>git_push()</code> fait ça.",
            correct: true
		},
		{
			text: "Un <i>merge</i>.",
			explain: "Cette opération ne sera jamais possible avec cette API."
		}
	]}
/>

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter4/6.mdx" />

### Construire une carte de modèle
https://huggingface.co/learn/course/fr/chapter4/4.md

# Construire une carte de modèle

<CourseFloatingBanner
    chapter={4}
    classNames="absolute z-10 right-0 top-0"
/>

La carte de modèle est un fichier qui est sans doute aussi important que les fichiers du modèle et du *tokenizer* dans un dépôt de modèles. Il s'agit de la définition centrale du modèle, qui garantit la réutilisation par les autres membres de la communauté, la reproductibilité des résultats, et une plateforme sur laquelle les autres membres peuvent construire leurs artefacts. 

Documenter le processus d'entraînement et d'évaluation aide les autres à comprendre ce qu'ils peuvent attendre d'un modèle. Fournir suffisamment d'informations concernant les données utilisées, les prétraitements et post-traitements effectués permet d'identifier et de comprendre les limites, les biais et les contextes dans lesquels le modèle est ou n'est pas utile.

Par conséquent, la création d'une carte de modèle définissant clairement votre modèle est une étape très importante. Nous vous donnons ici quelques conseils qui vous aideront à le faire. La création de la fiche de modèle se fait par le biais du fichier *README.md* que vous avez vu précédemment, qui est un fichier Markdown.

Le concept de carte de modèle provient d'une direction de recherche de Google, partagée pour la première fois dans l'article [« *Model Cards for Model Reporting* »](https://arxiv.org/abs/1810.03993) par Margaret Mitchell et al. De nombreuses informations contenues dans ce document sont basées sur cet article et nous vous recommandons d'y jeter un coup d'œil pour comprendre pourquoi les cartes de modèles sont si importantes dans un monde qui valorise la reproductibilité, la réutilisation et l'équité.

La carte de modèle commence généralement par une très brève présentation de haut niveau de l'objet du modèle, suivie de détails supplémentaires dans les sections suivantes :

- description du modèle
- utilisations et limites prévues
- comment utiliser le modèle
- limites et biais
- données d'entraînement 
- procédure d'entraînement
- résultats de l'évaluation 

Voyons ce que chacune de ces sections doit contenir.


### Description du modèle

La description du modèle fournit des détails de base sur le modèle. Cela inclut l'architecture, la version, s'il a été présenté dans un article, si une implémentation originale est disponible, l'auteur et des informations générales sur le modèle. Tout droit d'auteur doit être attribué ici. Des informations générales sur les procédures d'entraînement, les paramètres et les avertissements importants peuvent également être mentionnés dans cette section.

### Utilisations et limitations prévues

Vous décrivez ici les cas d'utilisation auxquels le modèle est destiné, y compris les langues, les domaines et les champs où il peut être appliqué. Cette section de la fiche de modèle peut également documenter les domaines qui sont connus pour être hors de portée du modèle, ou dans lesquels il est susceptible de fonctionner de manière sous-optimale.

### Comment utiliser

Cette section doit inclure des exemples d'utilisation du modèle. Cela peut montrer l'utilisation de la fonction `pipeline()`, l'utilisation des classes du modèle et du *tokenizer*, et tout autre code que vous pensez être utile.

### Données d'entraînement

Cette partie doit indiquer sur quel(s) jeu(x) de données le modèle a été entraîné. Une brève description du ou des jeux de données est également la bienvenue.

### Procédure d'entraînement

Dans cette section, vous devez décrire tous les aspects pertinents de l'entraînement qui sont utiles du point de vue de la reproductibilité. Cela inclut tout prétraitement et post-traitement effectué sur les données, ainsi que des détails tels que le nombre d'époques pour lesquelles le modèle a été entraîné, la taille du batch, le taux d'apprentissage, etc.

### Variable et métriques

Décrivez ici les métriques que vous utilisez pour l'évaluation et les différents facteurs que vous mesurez. En mentionnant la ou les métriques utilisées, sur quel jeu de données et quelle division du jeu de données, il est plus facile de comparer les performances de votre modèle à celles d'autres modèles. Les sections précédentes, telles que les utilisateurs prévus et les cas d'utilisation, doivent être prises en compte.

### Résultats de l'évaluation

Enfin, fournissez une indication de la performance du modèle sur l'ensemble de données d'évaluation. Si le modèle utilise un seuil de décision, indiquez le seuil de décision utilisé dans l'évaluation ou fournissez des détails sur l'évaluation à différents seuils pour les utilisations prévues.


## Exemple

Voici quelques exemples de cartes de modèles bien conçues :

- [`bert-base-case`](https://huggingface.co/bert-base-cased)
- [`gpt2`](https://huggingface.co/gpt2)
- [`distilbert`](https://huggingface.co/distilbert-base-uncased)

D'autres exemples provenant de différentes organisations et entreprises sont disponibles [ici](https://github.com/huggingface/model_card/blob/master/examples.md).

## Note

Les fiches de modèle ne sont pas une exigence lors de la publication de modèles, et vous n'avez pas besoin d'inclure toutes les sections décrites ci-dessus lorsque vous en faites une. Cependant, une documentation explicite du modèle ne peut qu'être bénéfique aux futurs utilisateurs. Nous vous recommandons donc de remplir autant de sections que possible, au mieux de vos connaissances et de vos capacités. 

## Métadonnées de la carte de modèle

Si vous avez exploré un peu le *Hub*, vous devriez avoir vu que certains modèles appartiennent à certaines catégories : vous pouvez les filtrer par tâches, langues, bibliothèques, et plus encore. Les catégories auxquelles appartient un modèle sont identifiées en fonction des métadonnées que vous ajoutez dans l'en-tête de la fiche du modèle.

Par exemple, si vous regardez la fiche de modèle de [`camembert-base`](https://huggingface.co/camembert-base/blob/main/README.md), vous devriez voir les lignes suivantes dans l'en-tête de la fiche de modèle :

```
---
language: fr
license: mit
datasets:
- oscar
---
```

Ces métadonnées sont analysées par le *Hub* qui identifie alors ce modèle comme étant un modèle français, avec une licence MIT, entraîné sur le jeu de données Oscar. 

La [spécification complète de la carte du modèle](https://github.com/huggingface/hub-docs/blame/main/modelcard.md) permet de spécifier les langues, les licences, les balises, les jeux de données, les mesures, ainsi que les résultats d'évaluation obtenus par le modèle lors de l'entraînement.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter4/4.mdx" />

### Utilisation de modèles pré-entraînés
https://huggingface.co/learn/course/fr/chapter4/2.md

# Utilisation de modèles pré-entraînés

{#if fw === 'pt'}

<CourseFloatingBanner chapter={4}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter4/section2_pt.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter4/section2_pt.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter4/section2_pt.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter4/section2_pt.ipynb"},
]} />

{:else}
<CourseFloatingBanner chapter={4}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter4/section2_tf.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter4/section2_tf.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter4/section2_tf.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter4/section2_tf.ipynb"},
]} />

{/if}

Le *Hub* rend simple la sélection d'un modèle et permet alors que celui-ci puisse être utilisé dans toute bibliothèque en aval en seulement quelques lignes de code. Voyons comment utiliser concrètement l'un de ces modèles et comment contribuer au développement de la communauté.

Supposons que nous recherchions un modèle basé sur le français, capable de remplir des masques.

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/camembert.gif" alt="Selecting the Camembert model." width="80%"/>
</div>

Nous choisissons le *checkpoint* `camembert-base` pour essayer. L'identifiant `camembert-base` est tout ce dont nous avons besoin pour commencer à utiliser le modèle ! Comme vous l'avez vu dans les chapitres précédents, nous pouvons l'instancier en utilisant la fonction `pipeline()` :

```py
from transformers import pipeline

camembert_fill_mask = pipeline("fill-mask", model="camembert-base")
results = camembert_fill_mask("Le camembert est <mask> :)")
```

```python out
[
  {'sequence': 'Le camembert est délicieux :)', 'score': 0.49091005325317383, 'token': 7200, 'token_str': 'délicieux'}, 
  {'sequence': 'Le camembert est excellent :)', 'score': 0.1055697426199913, 'token': 2183, 'token_str': 'excellent'}, 
  {'sequence': 'Le camembert est succulent :)', 'score': 0.03453313186764717, 'token': 26202, 'token_str': 'succulent'}, 
  {'sequence': 'Le camembert est meilleur :)', 'score': 0.0330314114689827, 'token': 528, 'token_str': 'meilleur'}, 
  {'sequence': 'Le camembert est parfait :)', 'score': 0.03007650189101696, 'token': 1654, 'token_str': 'parfait'}
]
```

Comme vous pouvez le constater, le chargement d'un modèle dans un pipeline est extrêmement simple. La seule chose à laquelle vous devez faire attention est que le *checkpoint* choisi soit adapté à la tâche pour laquelle il va être utilisé. Par exemple, ici nous chargeons le *checkpoint* `camembert-base` dans le pipeline `fill-mask`, ce qui est tout à fait correct. Mais si nous chargerions ce *checkpoint* dans le pipeline `text-classification`, les résultats n'auraient aucun sens car la tête de `camembert-base` n'est pas adaptée à cette tâche ! Nous recommandons d'utiliser le sélecteur de tâche dans l'interface du *Hub* afin de sélectionner les *checkpoints* appropriés :

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/tasks.png" alt="The task selector on the web interface." width="80%"/>
</div>

Vous pouvez également instancier le *checkpoint* en utilisant directement l'architecture du modèle :

{#if fw === 'pt'}
```py
from transformers import CamembertTokenizer, CamembertForMaskedLM

tokenizer = CamembertTokenizer.from_pretrained("camembert-base")
model = CamembertForMaskedLM.from_pretrained("camembert-base")
```

Cependant, nous recommandons d'utiliser les classes [`Auto*`](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) à la place, car elles sont par conception indépendantes de l'architecture. Alors que l'exemple de code précédent limite les utilisateurs aux *checkpoints* chargeables dans l'architecture CamemBERT, l'utilisation des classes `Auto*` facilite le changement de *checkpoint* :

```py
from transformers import AutoTokenizer, AutoModelForMaskedLM

tokenizer = AutoTokenizer.from_pretrained("camembert-base")
model = AutoModelForMaskedLM.from_pretrained("camembert-base")
```
{:else}
```py
from transformers import CamembertTokenizer, TFCamembertForMaskedLM

tokenizer = CamembertTokenizer.from_pretrained("camembert-base")
model = TFCamembertForMaskedLM.from_pretrained("camembert-base")
```

Cependant, nous recommandons d'utiliser les classes [`TFAuto*`](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) à la place, car elles sont par conception indépendantes de l'architecture. Alors que l'exemple de code précédent limite les utilisateurs aux *checkpoints* chargeables dans l'architecture CamemBERT, l'utilisation des classes `TFAuto*` facilite le changement de *checkpoint* :


```py
from transformers import AutoTokenizer, TFAutoModelForMaskedLM

tokenizer = AutoTokenizer.from_pretrained("camembert-base")
model = TFAutoModelForMaskedLM.from_pretrained("camembert-base")
```
{/if}

<Tip>
Lorsque vous utilisez un modèle pré-entraîné, assurez-vous de vérifier comment il a été entraîné, sur quels jeux de données, ses limites et ses biais. Toutes ces informations doivent être indiquées dans sa carte.
</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter4/2.mdx" />

### <i>Finetuning</i>, coché !
https://huggingface.co/learn/course/fr/chapter3/5.md

# <i>Finetuning</i>, coché !

<CourseFloatingBanner
    chapter={3}
    classNames="absolute z-10 right-0 top-0"
/>

C'était amusant ! Dans les deux premiers chapitres, vous avez appris à connaître les modèles et les *tokenizers*, et vous savez maintenant comment les *finetuner* pour vos propres données. Pour récapituler, dans ce chapitre vous :

{#if fw === 'pt'}
* avez appris à connaître les jeux de données dans le [*Hub*](https://huggingface.co/datasets),
* avez appris à charger et à prétraiter des jeux de données, notamment en utilisant le remplissage dynamique et les assembleurs,
* avez implémenté votre propre *finetuning* et évaluation d'un modèle,
* avez implémenté une boucle d'entraînement de niveau inférieur,
* avez utilisé 🤗 *Accelerate* pour adapter facilement votre boucle d'entraînement afin qu'elle fonctionne pour plusieurs GPUs ou TPUs.

{:else}
* avez appris à connaître les jeux de données dans le [*Hub*](https://huggingface.co/datasets),
* avez appris comment charger et prétraiter les jeux de données,
* avez appris comment *finetuner* et évaluer un modèle avec Keras,
* avez implémenté une métrique personnalisée.

{/if}

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter3/5.mdx" />

### Introduction
https://huggingface.co/learn/course/fr/chapter3/1.md

# Introduction

<CourseFloatingBanner
    chapter={3}
    classNames="absolute z-10 right-0 top-0"
/>

Dans le [chapitre 2](/course/fr/chapter2) nous avons étudié comment utiliser les *tokenizers* et les modèles pré-entraînés pour faire des prédictions.
Mais que faire si vous souhaitez *finetuner* un modèle pré-entraîné pour votre propre jeu de données ? C'est le sujet de ce chapitre ! Vous allez apprendre à :
	
{#if fw === 'pt'}
* savoir comment préparer un très grand jeu de données à partir du *Hub*, 
* savoir comment utiliser l'API de haut niveau `Trainer` pour *finetuner* un modèle,
* savoir comment utiliser une boucle d'entraînement personnalisée,
* savoir comment tirer parti de la bibliothèque 🤗 *Accelerate* pour exécuter facilement cette boucle d'entraînement personnalisée sur n'importe quelle configuration distribuée.

{:else}
* savoir comment préparer un très grand jeu de données à partir du *Hub*, 
* savoir comment utiliser Keras pour *finetuner* un modèle,
* savoir comment utiliser Keras pour obtenir des prédictions,
* savoir comment utiliser des métriques personnalisées.

{/if}

Afin de télécharger vos *checkpoints* entraînés sur le *Hub* Hugging Face, vous aurez besoin d'un compte huggingface.co : [créer un compte](https://huggingface.co/join)


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter3/1.mdx" />

### <i>Finetuner</i> un modèle avec l'API Trainer
https://huggingface.co/learn/course/fr/chapter3/3.md

# <i>Finetuner</i> un modèle avec l'API Trainer

<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter3/section3.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter3/section3.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter3/section3.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter3/section3.ipynb"},
]} />

<Youtube id="nvBXf7s7vTI"/>

La bibliothèque 🤗 *Transformers* fournit une classe `Trainer` pour vous aider à *finetuner* n'importe lequel des modèles pré-entraînés qu'elle met à disposition sur votre jeu de données. Une fois que vous avez fait tout le travail de prétraitement des données dans la dernière section, il ne vous reste que quelques étapes pour définir le `Trainer`. La partie la plus difficile sera probablement de préparer l'environnement pour exécuter `Trainer.train()`, car elle fonctionnera très lentement sur un CPU. Si vous n'avez pas de GPU, vous pouvez avoir accès à des GPUs ou TPUs gratuits sur [Google Colab](https://colab.research.google.com/).

Les exemples de code ci-dessous supposent que vous avez déjà exécuté les exemples de la section précédente. Voici un bref résumé de ce dont vous avez besoin :

```py
from datasets import load_dataset
from transformers import AutoTokenizer, DataCollatorWithPadding

raw_datasets = load_dataset("glue", "mrpc")
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)


def tokenize_function(example):
    return tokenizer(example["sentence1"], example["sentence2"], truncation=True)


tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
```

### Entraînement

La première étape avant de pouvoir définir notre `Trainer` est de définir une classe `TrainingArguments` qui contiendra tous les hyperparamètres que le `Trainer` utilisera pour l'entraînement et l'évaluation. Le seul argument que vous devez fournir est un répertoire où le modèle entraîné sera sauvegardé, ainsi que les *checkpoints*. Pour tout le reste, vous pouvez laisser les valeurs par défaut, qui devraient fonctionner assez bien pour un *finetuning* de base.

```py
from transformers import TrainingArguments

training_args = TrainingArguments("test-trainer")
```

<Tip>

💡 Si vous voulez télécharger automatiquement votre modèle sur le *Hub* pendant l'entraînement, passez `push_to_hub=True` dans le `TrainingArguments`. Nous en apprendrons plus à ce sujet au [chapitre 4](/course/fr/chapter4/3).

</Tip>

La deuxième étape consiste à définir notre modèle. Comme dans le [chapitre précédent](/course/fr/chapter2), nous utiliserons la classe `AutoModelForSequenceClassification`, avec deux labels :

```py
from transformers import AutoModelForSequenceClassification

model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
```

Vous remarquerez que contrairement au [chapitre 2](/course/fr/chapter2), vous obtenez un message d'avertissement après l'instanciation de ce modèle pré-entraîné. C'est parce que BERT n'a pas été pré-entraîné à la classification de paires de phrases, donc la tête du modèle pré-entraîné a été supprimée et une nouvelle tête adaptée à la classification de séquences a été ajoutée à la place. Les messages d'avertissement indiquent que certains poids n'ont pas été utilisés (ceux correspondant à la tête de pré-entraînement abandonnée) et que d'autres ont été initialisés de manière aléatoire (ceux pour la nouvelle tête). Il conclut en vous encourageant à entraîner le modèle, ce qui est exactement ce que nous allons faire maintenant.

Une fois que nous avons notre modèle, nous pouvons définir un `Trainer` en lui passant tous les objets construits jusqu'à présent : le `model`, le `training_args`, les jeux de données d'entraînement et de validation, notre `data_collator`, et notre `tokenizer` :

```py
from transformers import Trainer

trainer = Trainer(
    model,
    training_args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation"],
    data_collator=data_collator,
    tokenizer=tokenizer,
)
```

Notez que lorsque vous passez le `tokenizer` comme nous l'avons fait ici, le `data_collator` par défaut utilisé par le `Trainer` sera un `DataCollatorWithPadding` comme défini précédemment. Ainsi, vous pouvez sauter la ligne `data_collator=data_collator` dans cet appel. Il était quand même important de vous montrer cette partie du traitement dans la section 2 !

Pour *finetuner* le modèle sur notre jeu de données, il suffit d'appeler la méthode `train()` de notre `Trainer` :

```py
trainer.train()
```

Cela lancera le *finetuning* (qui devrait prendre quelques minutes sur un GPU) et indiquera la perte d'entraînement tous les 500 pas. Cependant, elle ne vous dira pas si votre modèle fonctionne bien (ou mal). Ceci est dû au fait que :

1. nous n'avons pas dit au `Trainer` d'évaluer pendant l'entraînement en réglant `evaluation_strategy` à soit `"steps"` (évaluer chaque `eval_steps`) ou `"epoch"` (évaluer à la fin de chaque *epoch*).
2. nous n'avons pas fourni au `Trainer` une fonction `compute_metrics()` pour calculer une métrique pendant ladite évaluation (sinon l'évaluation aurait juste affiché la perte, qui n'est pas un nombre très intuitif).


### Evaluation

Voyons comment nous pouvons construire une fonction `compute_metrics()` utile et l'utiliser la prochaine fois que nous entraînons. La fonction doit prendre un objet `EvalPrediction` (qui est un *tuple* nommé avec un champ `predictions` et un champ `label_ids`) et retournera un dictionnaire de chaînes de caractères vers des flottants (les chaînes de caractères étant les noms des métriques retournées, et les flottants leurs valeurs). Pour obtenir des prédictions de notre modèle, nous pouvons utiliser la commande `Trainer.predict()` :

```py
predictions = trainer.predict(tokenized_datasets["validation"])
print(predictions.predictions.shape, predictions.label_ids.shape)
```

```python out
(408, 2) (408,)
```

La sortie de la méthode `predict()` est un autre *tuple* nommé avec trois champs : `predictions`, `label_ids`, et `metrics`. Le champ `metrics` contiendra juste la perte sur le jeu de données passé, ainsi que quelques mesures de temps (combien de temps il a fallu pour prédire, au total et en moyenne). Une fois que nous aurons complété notre fonction `compute_metrics()` et que nous l'aurons passé au `Trainer`, ce champ contiendra également les métriques retournées par `compute_metrics()`.

Comme vous pouvez le voir, `predictions` est un tableau bidimensionnel de forme 408 x 2 (408 étant le nombre d'éléments dans le jeu de données que nous avons utilisé). Ce sont les logits pour chaque élément du jeu de données que nous avons passé à `predict()` (comme vous l'avez vu dans le [chapitre précédent](/course/fr/chapter2), tous les *transformers* retournent des logits). Pour les transformer en prédictions que nous pouvons comparer à nos étiquettes, nous devons prendre l'indice avec la valeur maximale sur le second axe :

```py
import numpy as np

preds = np.argmax(predictions.predictions, axis=-1)
```

Nous pouvons maintenant comparer ces `preds` aux étiquettes. Pour construire notre fonction `compute_metric()`, nous allons nous appuyer sur les métriques de la bibliothèque 🤗 [*Evaluate*](https://github.com/huggingface/evaluate/). Nous pouvons charger les métriques associées au jeu de données MRPC aussi facilement que nous avons chargé le jeu de données, cette fois avec la fonction `evaluate.load()`. L'objet retourné possède une méthode `compute()` que nous pouvons utiliser pour effectuer le calcul de la métrique :

```py
import evaluate

metric = evaluate.load("glue", "mrpc")
metric.compute(predictions=preds, references=predictions.label_ids)
```

```python out
{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542}
```

Les résultats exacts que vous obtiendrez peuvent varier, car l'initialisation aléatoire de la tête du modèle peut modifier les métriques obtenues. Ici, nous pouvons voir que notre modèle a une précision de 85,78% sur l'ensemble de validation et un score F1 de 89,97. Ce sont les deux métriques utilisées pour évaluer les résultats sur le jeu de données MRPC pour le benchmark GLUE. Le tableau du papier de [BERT](https://arxiv.org/pdf/1810.04805.pdf) indique un score F1 de 88,9 pour le modèle de base. Il s'agissait du modèle `uncased` alors que nous utilisons actuellement le modèle `cased`, ce qui explique le meilleur résultat.

En regroupant le tout, nous obtenons notre fonction `compute_metrics()` :

```py
def compute_metrics(eval_preds):
    metric = evaluate.load("glue", "mrpc")
    logits, labels = eval_preds
    predictions = np.argmax(logits, axis=-1)
    return metric.compute(predictions=predictions, references=labels)
```

Et pour le voir utilisé en action pour rapporter les métriques à la fin de chaque époque, voici comment nous définissons un nouveau `Trainer` avec cette fonction `compute_metrics()` :

```py
training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch")
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)

trainer = Trainer(
    model,
    training_args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation"],
    data_collator=data_collator,
    tokenizer=tokenizer,
    compute_metrics=compute_metrics,
)
```

Notez que nous créons un nouveau `TrainingArguments` avec sa `evaluation_strategy` définie sur `"epoch"` et un nouveau modèle. Sinon, nous ne ferions que continuer l'entraînement du modèle que nous avons déjà entraîné. Pour lancer un nouveau cycle d'entraînement, nous exécutons :

```
trainer.train()
```

Cette fois, il indiquera la perte et les mesures de validation à la fin de chaque époque, en plus de la perte d'entraînement. Encore une fois, le score exact de précision/F1 que vous atteignez peut être un peu différent de ce que nous avons trouvé, en raison de l'initialisation aléatoire de la tête du modèle, mais il devrait être dans la même fourchette.

Le `Trainer` fonctionnera sur plusieurs GPUs ou TPUs et fournit beaucoup d'options, comme l'entraînement en précision mixte (utilisez `fp16 = True` dans vos arguments d'entraînement). Nous passerons en revue tout ce qu'il supporte dans le chapitre 10.

Ceci conclut l'introduction au *fine-tuning* en utilisant l'API `Trainer`. Un exemple d'utilisation pour les tâches de NLP les plus communes es donné dans le [chapitre 7](/course/fr/chapter7), mais pour l'instant regardons comment faire la même chose en PyTorch pur.

<Tip>

✏️ **Essayez !** *Finetunez* un modèle sur le jeu de données GLUE SST-2, en utilisant le traitement des données que vous avez fait dans la section 2.

</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter3/3.mdx" />

### Quiz de fin de chapitre
https://huggingface.co/learn/course/fr/chapter3/6.md

# Quiz de fin de chapitre

<CourseFloatingBanner
    chapter={3}
    classNames="absolute z-10 right-0 top-0"
/>

Testez ce que vous avez appris dans ce chapitre !

### 1. Le jeu de données `emotion` contient des messages Twitter étiquetés avec des émotions. Cherchez-le dans le [*Hub*](https://huggingface.co/datasets) et lisez la carte du jeu de données. Laquelle de ces émotions n'est pas une de ses émotions de base ?

<Question
	choices={[
		{
			text: "Joie",
			explain: "Essayez à nouveau, cette émotion est présente dans ce jeu de données !"
		},
		{
			text: "Amour",
			explain: "Essayez à nouveau, cette émotion est présente dans ce jeu de données !"
		},
		{
			text: "Confusion",
			explain: "La confusion n'est pas l'une des six émotions de base.",
            correct: true
		},
        {
			text: "Surprise",
			explain: "Surprise ! Essayez-en une autre !"
		}
	]}
/>

### 2. Cherchez le jeu de données `ar_sarcasme` dans le [*Hub*](https://huggingface.co/datasets). Quelle tâche prend-il en charge ?

<Question
	choices={[
		{
			text: "Classification de sentiments",
			explain: "C'est vrai ! Vous pouvez le dire grâce aux <i>tags</i>.",
            correct: true
		},
		{
			text: "Traduction automatique",
			explain: "Ce n'est pas ça. Jetez un autre coup d'œil à la <a href='https://huggingface.co/datasets/ar_sarcasm'>carte du jeu de données</a> !"
		},
		{
			text: "Reconnaissance des entités nommées",
			explain: "Ce n'est pas ça. Jetez un autre coup d'œil à la <a href='https://huggingface.co/datasets/ar_sarcasm'>carte du jeu de données</a> !"
		},
        {
			text: "Réponse aux questions",
			explain: "Hélas, cette question n'a pas reçu de réponse correcte. Essayez à nouveau !"
		}
	]}
/>

### 3. Comment le modèle BERT attend-il qu'une paire de phrases soit traitée ?

<Question
	choices={[
		{
			text: "Tokens_de_la_phrase_1 [SEP] Tokens_de_la_phrase_2",
			explain: "Un jeton spécial <code>[SEP]</code> est nécessaire pour séparer les deux phrases, mais ce n'est pas tout !"
		},
		{
			text: "[CLS] Tokens_de_la_phrase_1 Tokens_de_la_phrase_2",
			explain: "Un jeton spécial <code>[CLS]</code> est requis au début, mais ce n'est pas la seule chose !"
		},
		{
			text: "[CLS] Tokens_de_la_phrase_1 [SEP] Tokens_de_la_phrase_2 [SEP]",
			explain: "C'est exact !",
            correct: true
		},
        {
			text: "[CLS] Tokens_de_la_phrase_1 [SEP] Tokens_de_la_phrase_2",
			explain: "Un jeton spécial <code>[CLS]</code> est nécessaire au début, ainsi qu'un jeton spécial <code>[SEP]</code> pour séparer les deux phrases, mais ce n'est pas tout !"
		}
	]}
/>

{#if fw === 'pt'}
### 4. Quels sont les avantages de la méthode `Dataset.map()` ?

<Question
	choices={[
		{
			text: "Les résultats de la fonction sont mis en cache, donc cela ne prendra pas de temps si nous réexécutons le code.",
			explain: "C'est en effet l'un des avantages de cette méthode ! Ce n'est pas le seul, cependant...",
            correct: true
		},
		{
			text: "Elle peut appliquer le multitraitement pour aller plus vite que l'application de la fonction sur chaque élément du jeu de données.",
			explain: "C'est une caractéristique intéressante de cette méthode, mais ce n'est pas la seule !",
            correct: true
		},
		{
			text: "Elle ne charge pas le jeu de données en mémoire, mais sauvegarde les résultats dès qu'un élément est traité.",
			explain: "C'est l'un des avantages de cette méthode. Mais il y en a d'autres !",
            correct: true
		},
	]}
/>

### 5. Que signifie le remplissage (*padding*) dynamique ?

<Question
	choices={[
		{
			text: "C'est lorsque vous remplissez les entrées de chaque batch à la longueur maximale du jeu de données entier.",
			explain: "Cela implique un remplissage lors de la création du batch, mais pas à la longueur maximale du jeu de données entier."
		},
		{
			text: "C'est lorsque vous remplissez vos entrées lorsque le batch est créé, à la longueur maximale des phrases à l'intérieur de ce batch.",
			explain: "La partie dynamique vient du fait que la taille de chaque batch est déterminée au moment de la création, et que tous vos batchs peuvent avoir des formes différentes.",
            correct: true
		},
		{
			text: "C'est lorsque vous remplissez vos entrées de sorte que chaque phrase ait le même nombre de <i>tokens</i> que la précédente dans le jeu de données.",
			explain: "Cela n'a pas de sens de regarder l'ordre dans le jeu de données puisque nous le mélangeons pendant l'entraînement."
		},
	]}
/>

### 6. Quel est le but d'une fonction d'assemblement ?

<Question
	choices={[
		{
			text: "Elle garantit que toutes les séquences du jeu de données ont la même longueur.",
			explain: "Une fonction d'assemblement est impliquée dans le traitement des batchs individuels, et non de tout le jeu de données. De plus, nous parlons de fonctions génériques et pas spécialement du <code>DataCollatorWithPadding</code>."
		},
		{
			text: "Elle rassemble tous les échantillons dans un batch.",
			explain: "Vous pouvez passer la fonction d'assemblement comme argument d'une fonction <code>DataLoader</code>. Nous avons utilisé la fonction <code>DataCollatorWithPadding</code> qui remplit tous les éléments d'un batch pour qu'ils aient la même longueur.",
            correct: true
		},
		{
			text: "Elle pré-traite tout le jeu de données.",
			explain: "Ce serait une fonction de prétraitement, pas une fonction d'assemblement."
		},
        {
			text: "Elle tronque les séquences dans le jeu de données.",
			explain: "Une fonction d'assemblement est impliquée dans le traitement des batchs individuels, et non de tout le jeu de données. Si vous êtes intéressé par la troncature, vous pouvez utiliser la fonction <code>truncate</code> en argument du <code>tokenizer</code>."
		}
	]}
/>

### 7. Que se passe-t-il lorsque vous instanciez une des classes `AutoModelForXxx` avec un modèle de langage pré-entraîné (tel que `bert-base-uncased`) qui correspond à une tâche différente de celle pour laquelle il a été entraîné ?

<Question
	choices={[
		{
			text: "Rien, mais vous recevez un message d'avertissement.",
			explain: "Effectivement, mais ce n'est pas tout !"
		},
		{
			text: "La tête du modèle pré-entraîné est supprimée et une nouvelle tête adaptée à la tâche est insérée à la place.",
			explain: "Par exemple, lorsque nous avons utilisé l'<code>AutoModelForSequenceClassification</code> avec <code>bert-base-uncased</code>, nous avons eu des messages d'avertissement lors de l'instanciation du modèle. La tête pré-entraînée n'est pas utilisée pour la tâche de classification de séquences, elle est donc supprimée et une nouvelle tête est instanciée avec des poids aléatoires..",
            correct: true
		},
		{
			text: "La tête du modèle pré-entraîné est supprimée.",
			explain: "Quelque chose d'autre doit se produire. Essayez encore !"
		},
        {
			text: "Rien, puisque le modèle peut encore être <i>finetuné</i> pour les différentes tâches.",
			explain: "La tête du modèle pré-entraîné n'a pas été entraînée à résoudre cette tâche, nous devons donc la supprimer !"
		}
	]}
/>

### 8. Quel est le but de `TrainingArguments` ?

<Question
	choices={[
		{
			text: "Contenir tous les hyperparamètres utilisés pour l'entraînement et l'évaluation avec le <code>Trainer</code>.",
			explain: "",
            correct: true
		},
		{
			text: "Préciser la taille du modèle.",
			explain: "La taille du modèle est définie par la configuration du modèle, et non par la classe <code>TrainingArguments</code>."
		},
		{
			text: "Juste contenir les hyperparamètres utilisés pour l'évaluation.",
			explain: "Dans l'exemple, nous avons spécifié où le modèle et ses <i>checkpoints</i> seront sauvegardés. Essayez à nouveau !"
		},
        {
			text: "Contenir seulement les hyperparamètres utilisés pour l'entraînement.",
			explain: "Dans l'exemple, nous avons utilisé une <code>evaluation_strategy</code> également, ce qui a un impact sur l'évaluation. Essayez à nouveau !"
		}
	]}
/>

### 9. Pourquoi devriez-vous utiliser la librairie 🤗 *Accelerate* ?

<Question
	choices={[
		{
			text: "Elle permet d'accéder à des modèles plus rapides.",
			explain: "Non, la librairie 🤗 <i>Accelerate</i> ne fournit aucun modèles."
		},
		{
			text: "Elle fournit une API de haut niveau qui évite d'avoir à mettre en place sa propre boucle d'entraînement.",
			explain: "C'est ce que nous avons fait avec le <code>Trainer</code> mais pas avec la librairie 🤗 <i>Accelerate</i>. Essayez à nouveau !"
		},
		{
			text: "Elle permet à nos boucles d'entraînement de fonctionner avec des stratégies distribuées.",
			explain: "Avec 🤗 <i>Accelerate</i>, vos boucles d'entraînement fonctionneront pour plusieurs GPUs et TPUs.",
            correct: true
		},
        {
			text: "Elle offre davantage de fonctions d'optimisation.",
			explain: "Non, la librairie 🤗 <i>Accelerate</i> ne fournit pas de fonctions d'optimisation."
		}
	]}
/>

{:else}
### 4. Que se passe-t-il lorsque vous instanciez une des classes `TFAutoModelForXxx` avec un modèle de langage pré-entraîné (tel que `bert-base-uncased`) qui correspond à une tâche différente de celle pour laquelle il a été entraîné ?

<Question
	choices={[
		{
			text: "Rien, mais vous recevez un message d'avertissement.",
			explain: "Effectivement, mais ce n'est pas tout !"
		},
		{
			text: "La tête du modèle pré-entraîné est supprimée et une nouvelle tête adaptée à la tâche est insérée à la place.",
			explain: "Par exemple, lorsque nous avons utilisé <code>TFAutoModelForSequenceClassification</code> avec <code>bert-base-uncased</code>, nous avons eu des messages d'avertissement lors de l'instanciation du modèle. La tête pré-entraînée n'est pas utilisée pour la tâche de classification de séquences, elle est donc supprimée et une nouvelle tête est instanciée avec des poids aléatoires..",
            correct: true
		},
		{
			text: "La tête du modèle pré-entraîné est supprimée.",
			explain: "Quelque chose d'autre doit se produire. Essayez encore !"
		},
        {
			text: "Rien, puisque le modèle peut encore être <i>finetuné</i> pour les différentes tâches.",
			explain: "La tête du modèle pré-entraîné n'a pas été entraînée à résoudre cette tâche, nous devons donc la supprimer !"
		}
	]}
/>

### 5. Les modèles TensorFlow de `transformers` sont déjà des modèles Keras. Quel avantage cela offre-t-il ?

<Question
	choices={[
		{
			text: "Les modèles fonctionnent sur un TPU prêt à l'emploi.",
			explain: "Presque ! Il y a quelques petites modifications supplémentaires à apporter. Par exemple, vous devez tout exécuter dans un fichier de type <code>TPUStrategy</code>, y compris l'initialisation du modèle."
		},
		{
			text: "Vous pouvez tirer parti des méthodes existantes telles que <code>compile()</code>, <code>fit()</code> et <code>predict()</code>.",
			explain: "Une fois que vous disposez des données, l'entraînement sur celles-ci ne demande que très peu de travail.",
            correct: true
		},
		{
			text: "Vous apprendrez à connaître Keras ainsi que transformers.",
			explain: "Mais nous cherchons quelque chose d'autre :)",
			correct: true
		},
        {
			text: "Vous pouvez facilement calculer les métriques liées au jeu de données.",
			explain: "Keras nous aide à entraîner et à évaluer le modèle, et non à calculer les paramètres liés aux jeux de données."
		}
	]}
/>

### 6. Comment pouvez-vous définir votre propre métrique personnalisée ?

<Question
	choices={[
		{
			text: "Par le biais de la sous-classe <code>tf.keras.metrics.Metric</code>.",
			explain: "Excellent !",
			correct: true
		},
		{
			text: "Utilisation de l'API fonctionnelle de Keras.",
			explain: "Essayez à nouveau !"
		},
		{
			text: "En utilisant un callable avec la signature <code>metric_fn(y_true, y_pred)</code>.",
			explain: " ",
			correct: true
		},
        {
			text: "En le googlant.",
			explain: "Ce n'est pas la réponse que nous cherchons, mais cela devrait vous aider à la trouver.",
			correct: true
		}
	]}
/>

{/if}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter3/6.mdx" />

### <i>Finetuner</i> un modèle avec Keras
https://huggingface.co/learn/course/fr/chapter3/3_tf.md

# <i>Finetuner</i> un modèle avec Keras

<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter3/section3_tf.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter3/section2_tf.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter3/section3_tf.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter3/section3_tf.ipynb"},
]} />

Une fois que vous avez fait tout le travail de prétraitement des données dans la dernière section, il ne vous reste que quelques étapes pour entraîner le modèle. Notez, cependant, que la commande `model.fit()` s'exécutera très lentement sur un CPU. Si vous n'avez pas de GPU, vous pouvez avoir accès à des GPUs ou TPUs gratuits sur [Google Colab](https://colab.research.google.com/).

Les exemples de code ci-dessous supposent que vous avez déjà exécuté les exemples de la section précédente. Voici un bref résumé de ce dont vous avez besoin :

```py
from datasets import load_dataset
from transformers import AutoTokenizer, DataCollatorWithPadding
import numpy as np

raw_datasets = load_dataset("glue", "mrpc")
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)


def tokenize_function(example):
    return tokenizer(example["sentence1"], example["sentence2"], truncation=True)


tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)

data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf")

tf_train_dataset = tokenized_datasets["train"].to_tf_dataset(
    columns=["attention_mask", "input_ids", "token_type_ids"],
    label_cols=["labels"],
    shuffle=True,
    collate_fn=data_collator,
    batch_size=8,
)

tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset(
    columns=["attention_mask", "input_ids", "token_type_ids"],
    label_cols=["labels"],
    shuffle=False,
    collate_fn=data_collator,
    batch_size=8,
)
```

### Entraînement

Les modèles TensorFlow importés depuis 🤗 *Transformers* sont déjà des modèles Keras. Voici une courte introduction à Keras.

<Youtube id="rnTGBy2ax1c"/>

Cela signifie qu'une fois que nous disposons de nos données, très peu de travail est nécessaire pour commencer à entraîner sur celles-ci.

<Youtube id="AUozVp78dhk"/>

Comme dans le [chapitre précédent](/course/fr/chapter2), nous allons utiliser la classe `TFAutoModelForSequenceClassification`, avec deux étiquettes : 

```py
from transformers import TFAutoModelForSequenceClassification

model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
```

Vous remarquerez que, contrairement au [chapitre 2](/course/fr/chapter2), vous obtenez un message d'avertissement après l'instanciation de ce modèle pré-entraîné. Ceci est dû au fait que BERT n'a pas été pré-entraîné à la classification de paires de phrases, donc la tête du modèle pré-entraîné a été supprimée et une nouvelle tête adaptée à la classification de séquences a été insérée à la place. Les messages d'avertissement indiquent que certains poids n'ont pas été utilisés (ceux correspondant à la tête de pré-entraînement abandonnée) et que d'autres ont été initialisés de manière aléatoire (ceux pour la nouvelle tête). Il conclut en vous encourageant à entraîner le modèle, ce qui est exactement ce que nous allons faire maintenant.

Pour *finetuner* le modèle sur notre jeu de données, nous devons simplement `compiler()` notre modèle et ensuite passer nos données à la méthode `fit()`. Cela va démarrer le processus de *finetuning* (qui devrait prendre quelques minutes sur un GPU) et rapporter la perte d'entraînement au fur et à mesure, plus la perte de validation à la fin de chaque époque.

<Tip>

Notez que les modèles 🤗 *Transformers* ont une capacité spéciale que la plupart des modèles Keras n'ont pas. Ils peuvent automatiquement utiliser une perte appropriée qu'ils calculent en interne. Ils utiliseront cette perte par défaut si vous ne définissez pas un argument de perte dans `compile()`. Notez que pour utiliser la perte interne, vous devrez passer vos labels comme faisant partie de l'entrée, et non pas comme un label séparé, ce qui est la façon normale d'utiliser les labels avec les modèles Keras. Vous verrez des exemples de cela dans la partie 2 du cours, où la définition de la fonction de perte correcte peut être délicate. Pour la classification des séquences, cependant, une fonction de perte standard de Keras fonctionne bien, et c'est donc ce que nous utiliserons ici.

</Tip>

```py
from tensorflow.keras.losses import SparseCategoricalCrossentropy

model.compile(
    optimizer="adam",
    loss=SparseCategoricalCrossentropy(from_logits=True),
    metrics=["accuracy"],
)
model.fit(
    tf_train_dataset,
    validation_data=tf_validation_dataset,
)
```

<Tip warning={true}>

Notez un piège très commun ici. Vous *pouvez* simplement passer le nom de la perte comme une chaîne à Keras, mais par défaut Keras supposera que vous avez déjà appliqué une fonction softmax à vos sorties. Cependant, de nombreux modèles produisent les valeurs juste avant l'application de la softmax, que l'on appelle aussi les *logits*. Nous devons indiquer à la fonction de perte que c'est ce que fait notre modèle, et la seule façon de le faire est de l'appeler directement, plutôt que par son nom avec une chaîne.

</Tip>


### Améliorer les performances d'entraînement

<Youtube id="cpzq6ESSM5c"/>

Si vous essayez le code ci-dessus, il fonctionne certainement, mais vous constaterez que la perte ne diminue que lentement ou sporadiquement. La cause principale est le *taux d'apprentissage*. Comme pour la perte, lorsque nous transmettons à Keras le nom d'un optimiseur sous forme de chaîne de caractères, Keras initialise cet optimiseur avec des valeurs par défaut pour tous les paramètres, y compris le taux d'apprentissage. Cependant, nous savons depuis longtemps que les *transformers* bénéficient d'un taux d'apprentissage beaucoup plus faible que celui par défaut d'Adam, qui est de 1e-3, également écrit comme 10 à la puissance -3, ou 0,001. 5e-5 (0,00005), qui est environ vingt fois inférieur, est un bien meilleur point de départ.

En plus de réduire le taux d'apprentissage, nous avons une deuxième astuce dans notre manche : nous pouvons réduire lentement le taux d'apprentissage au cours de l'entraînement. Dans la littérature, on parle parfois de *décroissance* ou d'*annulation* du taux d'apprentissage.le taux d'apprentissage. Dans Keras, la meilleure façon de le faire est d'utiliser un *planificateur du taux d'apprentissage*. Un bon planificateur à utiliser est `PolynomialDecay`. Malgré son nom, avec les paramètres par défaut, il diminue simplement de façon linéaire le taux d'apprentissage de la valeur initiale à la valeur finale au cours de l'entraînement, ce qui est exactement ce que nous voulons. Afin d'utiliser correctement un planificateur, nous devons lui dire combien de temps l'entraînement va durer. Nous calculons cela comme `num_train_steps` ci-dessous.

```py
from tensorflow.keras.optimizers.schedules import PolynomialDecay

batch_size = 8
num_epochs = 3
# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du batch puis multiplié
# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un lot tf.data.Dataset
# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà num_samples // batch_size.
num_train_steps = len(tf_train_dataset) * num_epochs
lr_scheduler = PolynomialDecay(
    initial_learning_rate=5e-5, end_learning_rate=0.0, decay_steps=num_train_steps
)
from tensorflow.keras.optimizers import Adam

opt = Adam(learning_rate=lr_scheduler)
```

<Tip>

La bibliothèque 🤗 *Transformers* possède également une fonction `create_optimizer()` qui créera un optimiseur `AdamW` avec un taux d'apprentissage décroissant. Il s'agit d'un raccourci pratique que vous verrez en détail dans les prochaines sections du cours.

</Tip>

Nous avons maintenant notre tout nouvel optimiseur et nous pouvons essayer de nous entraîner avec lui. Tout d'abord, rechargeons le modèle pour réinitialiser les modifications apportées aux poids lors de l'entraînement que nous venons d'effectuer, puis nous pouvons le compiler avec le nouvel optimiseur :

```py
import tensorflow as tf

model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
model.compile(optimizer=opt, loss=loss, metrics=["accuracy"])
```

Maintenant, on *fit* :

```py
model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3)
```

<Tip>

💡 Si vous voulez télécharger automatiquement votre modèle sur le *Hub* pendant l'entraînement, vous pouvez passer un `PushToHubCallback` dans la méthode `model.fit()`. Nous en apprendrons davantage à ce sujet au [chapitre 4](/course/fr/chapter4/3).

</Tip>

### Prédictions du modèle

<Youtube id="nx10eh4CoOs"/>


Entraîner et regarder la perte diminuer, c'est très bien, mais que faire si l'on veut réellement obtenir des résultats du modèle entraîné, soit pour calculer des métriques, soit pour utiliser le modèle en production ? Pour ce faire, nous pouvons simplement utiliser la méthode `predict()`. Ceci retournera les *logits* de la tête de sortie du modèle, un par classe.

```py
preds = model.predict(tf_validation_dataset)["logits"]
```

Nous pouvons convertir ces logits en prédictions de classe du modèle en utilisant `argmax` pour trouver le logit le plus élevé, qui correspond à la classe la plus probable :

```py
class_preds = np.argmax(preds, axis=1)
print(preds.shape, class_preds.shape)
```

```python out
(408, 2) (408,)
```

Maintenant, utilisons ces `preds` pour calculer des métriques ! Nous pouvons charger les métriques associées au jeu de données MRPC aussi facilement que nous avons chargé le jeu de données, cette fois avec la fonction `evaluate.load()`. L'objet retourné a une méthode `compute()` que nous pouvons utiliser pour faire le calcul de la métrique :

```py
import evaluate

metric = evaluate.load("glue", "mrpc")
metric.compute(predictions=class_preds, references=raw_datasets["validation"]["label"])
```

```python out
{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542}
```

Les résultats exacts que vous obtiendrez peuvent varier, car l'initialisation aléatoire de la tête du modèle peut modifier les métriques obtenues. Ici, nous pouvons voir que notre modèle a une précision de 85,78% sur l'ensemble de validation et un score F1 de 89,97. Ce sont les deux métriques utilisées pour évaluer les résultats sur le jeu de données MRPC pour le benchmark GLUE. Le tableau du papier de [BERT](https://arxiv.org/pdf/1810.04805.pdf) indique un score F1 de 88,9 pour le modèle de base. Il s'agissait du modèle `uncased` alors que nous utilisons actuellement le modèle `cased`, ce qui explique le meilleur résultat.

Ceci conclut l'introduction à le *finetuning* en utilisant l'API Keras. Un exemple d'application de cette méthode aux tâches les plus courantes du traitement automatique des langues sera présenté au [chapitre 7](/course/fr/chapter7). Si vous souhaitez affiner vos connaissances de l'API Keras, essayez *finetuner* un modèle sur le jeu de données GLUE SST-2, en utilisant le traitement des données que vous avez effectué dans la section 2.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter3/3_tf.mdx" />

### Un entraînement complet
https://huggingface.co/learn/course/fr/chapter3/4.md

# Un entraînement complet

<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter3/section4.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter3/section4.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter3/section4.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter3/section4.ipynb"},
]} />

<Youtube id="Dh9CL8fyG80"/>

Maintenant nous allons voir comment obtenir les mêmes résultats que dans la dernière section sans utiliser la classe `Trainer`. Encore une fois, nous supposons que vous avez fait le traitement des données dans la section 2. Voici un court résumé couvrant tout ce dont vous aurez besoin :

```py
from datasets import load_dataset
from transformers import AutoTokenizer, DataCollatorWithPadding

raw_datasets = load_dataset("glue", "mrpc")
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)


def tokenize_function(example):
    return tokenizer(example["sentence1"], example["sentence2"], truncation=True)


tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
```

### Préparer l'entraînement

Avant d'écrire réellement notre boucle d'entraînement, nous devons définir quelques objets. Les premiers sont les *dataloaders* que nous utiliserons pour itérer sur les batchs. Mais avant de pouvoir définir ces chargeurs de données, nous devons appliquer un peu de post-traitement à nos `tokenized_datasets`, pour prendre soin de certaines choses que le `Trainer` fait pour nous automatiquement. Spécifiquement, nous devons :

- supprimer les colonnes correspondant aux valeurs que le modèle n'attend pas (comme les colonnes `sentence1` et `sentence2`),
- renommer la colonne `label` en `labels` (parce que le modèle s'attend à ce que l'argument soit nommé `labels`),
- définir le format des jeux de données pour qu'ils retournent des tenseurs PyTorch au lieu de listes.

Notre `tokenized_datasets` a une méthode pour chacune de ces étapes :

```py
tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"])
tokenized_datasets = tokenized_datasets.rename_column("label", "labels")
tokenized_datasets.set_format("torch")
tokenized_datasets["train"].column_names
```

Nous pouvons alors vérifier que le résultat ne comporte que des colonnes que notre modèle acceptera :

```python
["attention_mask", "input_ids", "labels", "token_type_ids"]
```

Maintenant que cela est fait, nous pouvons facilement définir nos *dataloaders* :

```py
from torch.utils.data import DataLoader

train_dataloader = DataLoader(
    tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator
)
eval_dataloader = DataLoader(
    tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator
)
```

Pour vérifier rapidement qu'il n'y a pas d'erreur dans le traitement des données, nous pouvons inspecter un batch comme celui-ci :

```py
for batch in train_dataloader:
    break
{k: v.shape for k, v in batch.items()}
```

```python out
{'attention_mask': torch.Size([8, 65]),
 'input_ids': torch.Size([8, 65]),
 'labels': torch.Size([8]),
 'token_type_ids': torch.Size([8, 65])}
```

Notez que les formes réelles seront probablement légèrement différentes pour vous puisque nous avons défini `shuffle=True` pour le chargeur de données d'entraînement et que nous *paddons* à la longueur maximale dans le batch.

Maintenant que nous en avons terminé avec le prétraitement des données (un objectif satisfaisant mais difficile à atteindre pour tout praticien d'apprentissage automatique), passons au modèle. Nous l'instancions exactement comme nous l'avons fait dans la section précédente :

```py
from transformers import AutoModelForSequenceClassification

model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
```

Pour s'assurer que tout se passera bien pendant l'entraînement, nous transmettons notre batch à ce modèle :

```py
outputs = model(**batch)
print(outputs.loss, outputs.logits.shape)
```

```python out
tensor(0.5441, grad_fn=<NllLossBackward>) torch.Size([8, 2])
```

Tous les modèles 🤗 *Transformers* renvoient la perte lorsque les `labels` sont fournis. Nous obtenons également les logits (deux pour chaque entrée de notre batch, donc un tenseur de taille 8 x 2).

Nous sommes presque prêts à écrire notre boucle d'entraînement ! Il nous manque juste deux choses : un optimiseur et un planificateur de taux d'apprentissage. Puisque nous essayons de reproduire à la main ce que fait la fonction `Trainer`, utilisons les mêmes paramètres par défaut. L'optimiseur utilisé par `Trainer` est `AdamW`, qui est le même qu'Adam, mais avec une torsion pour la régularisation par décroissance de poids (voir [*Decoupled Weight Decay Regularization*](https://arxiv.org/abs/1711.05101) par Ilya Loshchilov et Frank Hutter) :

```py
from torch.optim import AdamW

optimizer = AdamW(model.parameters(), lr=5e-5)
```

Enfin, le planificateur du taux d'apprentissage utilisé par défaut est juste une décroissance linéaire de la valeur maximale (5e-5) à 0. Pour le définir correctement, nous devons connaître le nombre d'étapes d'entraînement que nous prendrons, qui est le nombre d'époques que nous voulons exécuter multiplié par le nombre de batch d'entraînement (qui est la longueur de notre *dataloader* d'entraînement). Le `Trainer` utilise trois époques par défaut, nous allons donc suivre ça :

```py
from transformers import get_scheduler

num_epochs = 3
num_training_steps = num_epochs * len(train_dataloader)
lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)
print(num_training_steps)
```

```python out
1377
```

### La boucle d'entraînement

Une dernière chose : nous voulons utiliser le GPU si nous en avons un (sur un CPU, l'entraînement peut prendre plusieurs heures au lieu de quelques minutes). Pour ce faire, nous définissons un `device` sur lequel nous allons placer notre modèle et nos batchs :

```py
import torch

device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
model.to(device)
device
```

```python out
device(type='cuda')
```

Nous sommes maintenant prêts à entraîner ! Pour avoir une idée du moment où l'entraînement sera terminé, nous ajoutons une barre de progression sur le nombre d'étapes d'entraînement, en utilisant la bibliothèque `tqdm` :

```py
from tqdm.auto import tqdm

progress_bar = tqdm(range(num_training_steps))

model.train()
for epoch in range(num_epochs):
    for batch in train_dataloader:
        batch = {k: v.to(device) for k, v in batch.items()}
        outputs = model(**batch)
        loss = outputs.loss
        loss.backward()

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)
```

Vous pouvez voir que le cœur de la boucle d'entraînement ressemble beaucoup à celui de l'introduction. Nous n'avons pas demandé de rapport, donc cette boucle d'entraînement ne nous dira rien sur les résultats du modèle. Pour cela, nous devons ajouter une boucle d'évaluation.


### La boucle d'évaluation

Comme nous l'avons fait précédemment, nous allons utiliser une métrique fournie par la bibliothèque 🤗 *Evaluate*. Nous avons déjà vu la méthode `metric.compute()`, mais les métriques peuvent en fait accumuler des batchs pour nous au fur et à mesure que nous parcourons la boucle de prédiction avec la méthode `add_batch()`. Une fois que nous avons accumulé tous les batchs, nous pouvons obtenir le résultat final avec `metric.compute()`. Voici comment implémenter tout cela dans une boucle d'évaluation :

```py
import evaluate

metric = evaluate.load("glue", "mrpc")
model.eval()
for batch in eval_dataloader:
    batch = {k: v.to(device) for k, v in batch.items()}
    with torch.no_grad():
        outputs = model(**batch)

    logits = outputs.logits
    predictions = torch.argmax(logits, dim=-1)
    metric.add_batch(predictions=predictions, references=batch["labels"])

metric.compute()
```

```python out
{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535}
```

Une fois encore, vos résultats seront légèrement différents en raison du caractère aléatoire de l'initialisation de la tête du modèle et du mélange des données, mais ils devraient se situer dans la même fourchette.

<Tip>

✏️ **Essayez** Modifiez la boucle d'entraînement précédente pour *finetuner* votre modèle sur le jeu de données SST-2.

</Tip>

### Optimisez votre boucle d'entraînement avec 🤗 <i>Accelerate</i>

<Youtube id="s7dy8QRgjJ0" />

La boucle d'entraînement que nous avons définie précédemment fonctionne bien sur un seul CPU ou GPU. Mais en utilisant la bibliothèque [🤗 *Accelerate*](https://github.com/huggingface/accelerate), il suffit de quelques ajustements pour permettre un entraînement distribué sur plusieurs GPUs ou TPUs. En partant de la création des *dataloaders* d'entraînement et de validation, voici à quoi ressemble notre boucle d'entraînement manuel :

```py
from torch.optim import AdamW
from transformers import AutoModelForSequenceClassification, get_scheduler

model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
optimizer = AdamW(model.parameters(), lr=3e-5)

device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
model.to(device)

num_epochs = 3
num_training_steps = num_epochs * len(train_dataloader)
lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)

progress_bar = tqdm(range(num_training_steps))

model.train()
for epoch in range(num_epochs):
    for batch in train_dataloader:
        batch = {k: v.to(device) for k, v in batch.items()}
        outputs = model(**batch)
        loss = outputs.loss
        loss.backward()

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)
```

Et voici les changements :

```diff
+ from accelerate import Accelerator
  from torch.optim import AdamW
  from transformers import AutoModelForSequenceClassification, get_scheduler

+ accelerator = Accelerator()

  model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
  optimizer = AdamW(model.parameters(), lr=3e-5)

- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
- model.to(device)

+ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare(
+     train_dataloader, eval_dataloader, model, optimizer
+ )

  num_epochs = 3
  num_training_steps = num_epochs * len(train_dataloader)
  lr_scheduler = get_scheduler(
      "linear",
      optimizer=optimizer,
      num_warmup_steps=0,
      num_training_steps=num_training_steps
  )

  progress_bar = tqdm(range(num_training_steps))

  model.train()
  for epoch in range(num_epochs):
      for batch in train_dataloader:
-         batch = {k: v.to(device) for k, v in batch.items()}
          outputs = model(**batch)
          loss = outputs.loss
-         loss.backward()
+         accelerator.backward(loss)

          optimizer.step()
          lr_scheduler.step()
          optimizer.zero_grad()
          progress_bar.update(1)
```

La première ligne à ajouter est la ligne d'importation. La deuxième ligne instancie un objet `Accelerator` qui va regarder l'environnement et initialiser la bonne configuration distribuée. 🤗 *Accelerate* gère le placement des périphériques pour vous, donc vous pouvez enlever les lignes qui placent le modèle sur le périphérique (ou, si vous préférez, les changer pour utiliser `accelerator.device` au lieu de `device`).

Ensuite, le gros du travail est fait dans la ligne qui envoie les *dataloaders*, le modèle, et l'optimiseur à `accelerator.prepare()`. Cela va envelopper ces objets dans le conteneur approprié pour s'assurer que votre entraînement distribué fonctionne comme prévu. Les changements restants à faire sont la suppression de la ligne qui met le batch sur le `device` (encore une fois, si vous voulez le garder, vous pouvez juste le changer pour utiliser `accelerator.device`) et le remplacement de `loss.backward()` par `accelerator.backward(loss)`.

<Tip>
⚠️ Afin de bénéficier de la rapidité offerte par les TPUs du Cloud, nous vous recommandons de rembourrer vos échantillons à une longueur fixe avec les arguments `padding="max_length"` et `max_length` du <i>tokenizer</i>.
</Tip>

Si vous souhaitez faire un copier-coller pour jouer, voici à quoi ressemble la boucle d'entraînement complète avec 🤗 <i>Accelerate</i> :

```py
from accelerate import Accelerator
from torch.optim import AdamW
from transformers import AutoModelForSequenceClassification, get_scheduler

accelerator = Accelerator()

model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
optimizer = AdamW(model.parameters(), lr=3e-5)

train_dl, eval_dl, model, optimizer = accelerator.prepare(
    train_dataloader, eval_dataloader, model, optimizer
)

num_epochs = 3
num_training_steps = num_epochs * len(train_dl)
lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)

progress_bar = tqdm(range(num_training_steps))

model.train()
for epoch in range(num_epochs):
    for batch in train_dl:
        outputs = model(**batch)
        loss = outputs.loss
        accelerator.backward(loss)

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)
```

En plaçant ceci dans un script `train.py`, cela sera exécutable sur n'importe quel type d'installation distribuée. Pour l'essayer dans votre installation distribuée, exécutez la commande :

```bash
accelerate config
```

qui vous demandera de répondre à quelques questions et enregistrera vos réponses dans un fichier de configuration utilisé par cette commande :

```
accelerate launch train.py
```

qui lancera l'entraînement distribué.

Si vous voulez essayer ceci dans un *notebook* (par exemple, pour le tester avec des TPUs sur Colab), collez simplement le code dans une `training_function()` et lancez une dernière cellule avec :

```python
from accelerate import notebook_launcher

notebook_launcher(training_function)
```

Vous trouverez d'autres exemples dans le dépôt d'[🤗 *Accelerate*](https://github.com/huggingface/accelerate/tree/main/examples).


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter3/4.mdx" />

### Préparer les données
https://huggingface.co/learn/course/fr/chapter3/2.md

# Préparer les données

{#if fw === 'pt'}


<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter3/section2_pt.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter3/section2_pt.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter3/section2_pt.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter3/section2_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter3/section2_tf.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter3/section2_tf.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter3/section2_tf.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter3/section2_tf.ipynb"},
]} />

{/if}

{#if fw === 'pt'}

En continuant avec l'exemple du [chapitre précédent](/course/fr/chapter2), voici comment entraîner un classifieur de séquences sur un batch avec PyTorch :

```python
import torch
from torch.optim import AdamW
from transformers import AutoTokenizer, AutoModelForSequenceClassification

# Même chose que précédemment
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
sequences = [
    "I've been waiting for a HuggingFace course my whole life.",
    # J'ai attendu un cours de HuggingFace toute ma vie.
    "This course is amazing!",  # Ce cours est incroyable !
]
batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt")

# Ceci est nouveau
batch["labels"] = torch.tensor([1, 1])

optimizer = AdamW(model.parameters())
loss = model(**batch).loss
loss.backward()
optimizer.step()
```
{:else}

En continuant avec l'exemple du [chapitre précédent](/course/fr/chapter2), voici comment entraîner un classifieur de séquences sur un batch avec TensorFlow :

```python
import tensorflow as tf
import numpy as np
from transformers import AutoTokenizer, TFAutoModelForSequenceClassification

# Même chose que précédemment
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)
sequences = [
    "I've been waiting for a HuggingFace course my whole life.",
    # J'ai attendu un cours de HuggingFace toute ma vie.
    "This course is amazing!",  # Ce cours est incroyable !
]
batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf"))

# Ceci est nouveau
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy")
labels = tf.convert_to_tensor([1, 1])
model.train_on_batch(batch, labels)
```
{/if}
Evidemment, entraîner un modèle avec seulement deux phrases ne va pas donner de bons résultats. Pour obtenir de meilleurs résultats, vous allez avoir à préparer un plus grand jeu de données. 

Dans cette section, nous allons utiliser comme exemple le jeu de données MRPC (*Microsoft Research Paraphrase Corpus*) présenté dans un [papier](https://www.aclweb.org/anthology/I05-5002.pdf) par William B. Dolan et Chris Brockett. Ce jeu de données contient 5801 paires de phrases avec un label indiquant si ces paires sont des paraphrases ou non (i.e. si elles ont la même signification). Nous l'avons choisi pour ce chapitre parce que c'est un petit jeu de données et cela rend donc simples les expériences d'entraînement sur ce jeu de données. 

### Charger un jeu de données depuis le <i>Hub</i>

{#if fw === 'pt'}
<Youtube id="_BZearw7f0w"/>
{:else}
<Youtube id="W_gMJF0xomE"/>
{/if}

Le *Hub* ne contient pas seulement des modèles mais aussi plusieurs jeux de données dans un tas de langues différentes. Vous pouvez explorer les jeux de données [ici](https://huggingface.co/datasets) et nous vous conseillons d'essayer de charger un nouveau jeu de données une fois que vous avez étudié cette section (voir la documentation générale [ici](https://huggingface.co/docs/datasets/loading)). Mais pour l'instant, concentrons-nous sur le jeu de données MRPC ! Il s'agit de l'un des 10 jeux de données qui constituent le [*benchmark* GLUE](https://gluebenchmark.com/) qui est un *benchmark* académique utilisé pour mesurer les performances des modèles d'apprentissage automatique sur 10 différentes tâches de classification de textes.  

La bibliothèque 🤗 *Datasets* propose une commande très simple pour télécharger et mettre en cache un jeu de données à partir du *Hub*. On peut télécharger le jeu de données MRPC comme ceci :   

<Tip>
⚠️ **Attention** Assurez-vous que `datasets` est installé en exécutant `pip install datasets`. Ensuite, chargez le jeu de données MRPC et imprimez-le pour voir ce qu'il contient.
</Tip> 

```py
from datasets import load_dataset

raw_datasets = load_dataset("glue", "mrpc")
raw_datasets
```

```python out
DatasetDict({
    train: Dataset({
        features: ['sentence1', 'sentence2', 'label', 'idx'],
        num_rows: 3668
    })
    validation: Dataset({
        features: ['sentence1', 'sentence2', 'label', 'idx'],
        num_rows: 408
    })
    test: Dataset({
        features: ['sentence1', 'sentence2', 'label', 'idx'],
        num_rows: 1725
    })
})
```

Comme vous le voyez, on obtient un objet de type `DatasetDict` qui contient le jeu de données d'entraînement, celui de validation et celui de test. Chacun d'eux contient plusieurs colonnes (`sentence1`, `sentence2`, `label` et `idx`) et une variable nombre de lignes qui contient le nombre d'éléments dans chaque jeu de données (il y a donc 3.668 paires de phrases dans le jeu d'entraînement, 408 dans celui de validation et 1.725 dans celui de test).

Cette commande télécharge et met en cache le jeu de données dans *~/.cache/huggingface/dataset*. Rappelez-vous que comme vu au chapitre 2, vous pouvez personnaliser votre dossier cache en modifiant la variable d'environnement `HF_HOME`.

Nous pouvons accéder à chaque paire de phrase de notre objet `raw_datasets` par les indices, comme avec un dictionnaire :

```py
raw_train_dataset = raw_datasets["train"]
raw_train_dataset[0]
```

```python out
{'idx': 0,
 'label': 1,
 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', 
 # Amrozi a accusé son frère, qu'il a appelé « le témoin », de déformer délibérément son témoignage.
 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} 
 # Se référant à lui uniquement comme « le témoin », Amrozi a accusé son frère de déformer délibérément son témoignage.
```

Nous pouvons voir que les étiquettes sont déjà des entiers, donc nous n'aurons pas à faire de prétraitement ici. Pour savoir quel entier correspond à quel label, nous pouvons inspecter les `features` de notre `raw_train_dataset`. Cela nous indiquera le type de chaque colonne :

```py
raw_train_dataset.features
```

```python out
{'sentence1': Value(dtype='string', id=None),
 'sentence2': Value(dtype='string', id=None),
 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None),
 'idx': Value(dtype='int32', id=None)}
```

En réalité, `label` est de type `ClassLabel` et la correspondance des entiers aux noms des labels est enregistrée le dossier *names*. `0` correspond à  `not_equivalent` et `1` correspond à `equivalent`.

<Tip>

✏️ **Essayez !** Regardez l'élément 15 de l'ensemble d'entraînement et l'élément 87 de l'ensemble de validation. Quelles sont leurs étiquettes ?
</Tip>

### Prétraitement d'un jeu de données

{#if fw === 'pt'}
<Youtube id="0u3ioSwev3s"/>
{:else}
<Youtube id="P-rZWqcB6CE"/>
{/if}

Pour prétraiter le jeu de données, nous devons convertir le texte en chiffres compréhensibles par le modèle. Comme vous l'avez vu dans le [chapitre précédent](/course/fr/chapter2), cette conversion est effectuée par un *tokenizer*. Nous pouvons fournir au *tokenizer* une phrase ou une liste de phrases, de sorte que nous pouvons directement tokeniser toutes les premières phrases et toutes les secondes phrases de chaque paire comme ceci :

```py
from transformers import AutoTokenizer

checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"])
tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"])
```

Cependant, nous ne pouvons pas simplement passer deux séquences au modèle et obtenir une prédiction pour savoir si les deux phrases sont des paraphrases ou non. Nous devons traiter les deux séquences comme une paire, et appliquer le prétraitement approprié. Heureusement, le *tokenizer* peut également prendre une paire de séquences et la préparer de la manière attendue par notre modèle BERT : 

```py
inputs = tokenizer(
    "This is the first sentence.", "This is the second one."
)  # "C'est la première phrase.", "C'est la deuxième."
inputs
```

```python out
{ 
  'input_ids': [101, 2023, 2003, 1996, 2034, 6251, 1012, 102, 2023, 2003, 1996, 2117, 2028, 1012, 102],
  'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
  'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
}
```

Nous avons discuté des clés `input_ids` et `attention_mask` dans le [chapitre 2](/course/fr/chapter2), mais nous avons laissé de côté les `token_type_ids`. Dans cet exemple, c'est ce qui indique au modèle quelle partie de l'entrée est la première phrase et quelle partie est la deuxième phrase.

<Tip>

✏️ **Essayez !** Prenez l'élément 15 de l'ensemble d'entraînement et tokenisez les deux phrases séparément et par paire. Quelle est la différence entre les deux résultats ?

</Tip>

Si on décode les IDs dans `input_ids` en mots :

```py
tokenizer.convert_ids_to_tokens(inputs["input_ids"])
```

nous aurons :

```python out
['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]']
```

Nous voyons donc que le modèle s'attend à ce que les entrées soient de la forme `[CLS] phrase1 [SEP] phrase2 [SEP]` lorsqu'il y a deux phrases. En alignant cela avec les `token_type_ids`, on obtient :

```python out
['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]']
[      0,      0,    0,     0,       0,          0,   0,       0,      1,    1,     1,        1,     1,   1,       1]
```

Comme vous pouvez le voir, les parties de l'entrée correspondant à `[CLS] sentence1 [SEP]` ont toutes un *token* de type ID de `0`, tandis que les autres parties, correspondant à `sentence2 [SEP]`, ont toutes un *token* de type ID de `1`.

Notez que si vous choisissez un autre *checkpoint*, vous n'aurez pas nécessairement les `token_type_ids` dans vos entrées tokenisées (par exemple, ils ne sont pas retournés si vous utilisez un modèle DistilBERT). Ils ne sont retournés que lorsque le modèle sait quoi faire avec eux, parce qu'il les a vus pendant son pré-entraînement. 

Ici, BERT est pré-entraîné avec les *tokens* de type ID et en plus de l'objectif de modélisation du langage masqué dont nous avons abordé dans [chapitre 1](/course/fr/chapter1), il a un objectif supplémentaire appelé _prédiction de la phrase suivante_. Le but de cette tâche est de modéliser la relation entre des paires de phrases.

Avec la prédiction de la phrase suivante, on fournit au modèle des paires de phrases (avec des *tokens* masqués de manière aléatoire) et on lui demande de prédire si la deuxième phrase suit la première. Pour rendre la tâche non triviale, la moitié du temps, les phrases se suivent dans le document d'origine dont elles ont été extraites, et l'autre moitié du temps, les deux phrases proviennent de deux documents différents. 

En général, vous n'avez pas besoin de vous inquiéter de savoir s'il y a ou non des `token_type_ids` dans vos entrées tokenisées : tant que vous utilisez le même *checkpoint* pour le *tokenizer* et le modèle, tout ira bien puisque le *tokenizer* sait quoi fournir à son modèle.

Maintenant que nous avons vu comment notre *tokenizer* peut traiter une paire de phrases, nous pouvons l'utiliser pour tokeniser l'ensemble de notre jeu de données : comme dans le [chapitre précédent](/course/fr/chapter2), nous pouvons fournir au *tokenizer* une liste de paires de phrases en lui donnant la liste des premières phrases, puis la liste des secondes phrases. Ceci est également compatible avec les options de remplissage et de troncature que nous avons vues dans le [chapitre 2](/course/fr/chapter2). Voici donc une façon de prétraiter le jeu de données d'entraînement :

```py
tokenized_dataset = tokenizer(
    raw_datasets["train"]["sentence1"],
    raw_datasets["train"]["sentence2"],
    padding=True,
    truncation=True,
)
```

Cela fonctionne bien, mais a l'inconvénient de retourner un dictionnaire (avec nos clés, `input_ids`, `attention_mask`, et `token_type_ids`, et des valeurs qui sont des listes de listes). Cela ne fonctionnera également que si vous avez assez de RAM pour stocker l'ensemble de votre jeu de données pendant la tokenisation (alors que les jeux de données de la bibliothèque 🤗 *Datasets* sont des fichiers [Apache Arrow](https://arrow.apache.org/) stockés sur le disque, vous ne gardez donc en mémoire que les échantillons que vous demandez).

Pour conserver les données sous forme de jeu de données, nous utiliserons la méthode [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map). Cela nous permet également une certaine flexibilité, si nous avons besoin d'un prétraitement plus poussé que la simple tokenisation. La méthode `map()` fonctionne en appliquant une fonction sur chaque élément de l'ensemble de données, donc définissons une fonction qui tokenise nos entrées :

```py
def tokenize_function(example):
    return tokenizer(example["sentence1"], example["sentence2"], truncation=True)
```

Cette fonction prend un dictionnaire (comme les éléments de notre jeu de données) et retourne un nouveau dictionnaire avec les clés `input_ids`, `attention_mask`, et `token_type_ids`. Notez que cela fonctionne également si le dictionnaire `example` contient plusieurs échantillons (chaque clé étant une liste de phrases) puisque le `tokenizer` travaille sur des listes de paires de phrases, comme vu précédemment. Cela nous permettra d'utiliser l'option `batched=True` dans notre appel à `map()`, ce qui accélérera grandement la tokénisation. Le `tokenizer` est soutenu par un *tokenizer* écrit en Rust à partir de la bibliothèque [🤗 *Tokenizers*](https://github.com/huggingface/tokenizers). Ce *tokenizer* peut être très rapide, mais seulement si on lui donne beaucoup d'entrées en même temps.

Notez que nous avons laissé l'argument `padding` hors de notre fonction de *tokenizer* pour le moment. C'est parce que le *padding* de tous les échantillons à la longueur maximale n'est pas efficace : il est préférable de remplir les échantillons lorsque nous construisons un batch, car alors nous avons seulement besoin de remplir à la longueur maximale dans ce batch, et non la longueur maximale dans l'ensemble des données. Cela peut permettre de gagner beaucoup de temps et de puissance de traitement lorsque les entrées ont des longueurs très variables ! 

Voici comment nous appliquons la fonction de tokenization sur tous nos jeux de données en même temps. Nous utilisons `batched=True` dans notre appel à `map` pour que la fonction soit appliquée à plusieurs éléments de notre jeu de données en une fois, et non à chaque élément séparément. Cela permet un prétraitement plus rapide.

```py
tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
tokenized_datasets
```

La façon dont la bibliothèque 🤗 *Datasets* applique ce traitement consiste à ajouter de nouveaux champs aux jeux de données, un pour chaque clé du dictionnaire renvoyé par la fonction de prétraitement :

```python out
DatasetDict({
    train: Dataset({
        features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
        num_rows: 3668
    })
    validation: Dataset({
        features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
        num_rows: 408
    })
    test: Dataset({
        features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
        num_rows: 1725
    })
})
```

Vous pouvez même utiliser le multitraitement lorsque vous appliquez votre fonction de prétraitement avec `map()` en passant un argument `num_proc`. Nous ne l'avons pas fait ici parce que la bibliothèque 🤗 *Tokenizers* utilise déjà plusieurs *threads* pour tokeniser nos échantillons plus rapidement, mais si vous n'utilisez pas un *tokenizer* rapide soutenu par cette bibliothèque, cela pourrait accélérer votre prétraitement.

Notre `tokenize_function` retourne un dictionnaire avec les clés `input_ids`, `attention_mask`, et `token_type_ids`, donc ces trois champs sont ajoutés à toutes les divisions de notre jeu de données. Notez que nous aurions également pu modifier des champs existants si notre fonction de prétraitement avait retourné une nouvelle valeur pour une clé existante dans l'ensemble de données auquel nous avons appliqué `map()`.

La dernière chose que nous devrons faire est de remplir tous les exemples à la longueur de l'élément le plus long lorsque nous regroupons les éléments, une technique que nous appelons le *padding dynamique*.

### <i>Padding</i> dynamique

<Youtube id="7q5NyFT8REg"/>

{#if fw === 'pt'}
La fonction qui est responsable de l'assemblage des échantillons dans un batch est appelée *fonction d'assemblement*. C'est un argument que vous pouvez passer quand vous construisez un `DataLoader`, la valeur par défaut étant une fonction qui va juste convertir vos échantillons en tenseurs PyTorch et les concaténer (récursivement si vos éléments sont des listes, des *tuples* ou des dictionnaires). Cela ne sera pas possible dans notre cas puisque les entrées que nous avons ne seront pas toutes de la même taille. Nous avons délibérément reporté le *padding*, pour ne l'appliquer que si nécessaire sur chaque batch et éviter d'avoir des entrées trop longues avec beaucoup de remplissage. Cela accélère considérablement l'entraînement, mais notez que si vous vous entraînez sur un TPU, cela peut poser des problèmes. En effet, les TPU préfèrent les formes fixes, même si cela nécessite un *padding* supplémentaire.

{:else}
La fonction qui est responsable de l'assemblage des échantillons dans un batch est appelée *fonction d'assemblement*. C'est un argument que vous pouvez passer quand vous construisez un `DataLoader`, la valeur par défaut étant une fonction qui va juste convertir vos échantillons en type tf.Tensor et les concaténer (récursivement si les éléments sont des listes, des *tuples* ou des dictionnaires). Cela ne sera pas possible dans notre cas puisque les entrées que nous avons ne seront pas toutes de la même taille. Nous avons délibérément reporté le *padding*, pour ne l'appliquer que si nécessaire sur chaque batch et éviter d'avoir des entrées trop longues avec beaucoup de remplissage. Cela accélère considérablement l'entraînement, mais notez que si vous vous entraînez sur un TPU, cela peut poser des problèmes. En effet, les TPU préfèrent les formes fixes, même si cela nécessite un *padding* supplémentaire.

{/if}
Pour faire cela en pratique, nous devons définir une fonction d'assemblement qui appliquera la bonne quantité de *padding* aux éléments du jeu de données que nous voulons regrouper. Heureusement, la bibliothèque 🤗 *Transformers* nous fournit une telle fonction via `DataCollatorWithPadding`. Elle prend un *tokenizer* lorsque vous l'instanciez (pour savoir quel *token* de *padding* utiliser et si le modèle s'attend à ce que le *padding* soit à gauche ou à droite des entrées) et fera tout ce dont vous avez besoin :

{#if fw === 'pt'}
```py
from transformers import DataCollatorWithPadding

data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
```
{:else}
```py
from transformers import DataCollatorWithPadding

data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf")
```
{/if}

Pour tester notre nouveau jouet, prenons quelques éléments de notre jeu d'entraînement avec lesquels nous allons former un batch. Ici, on supprime les colonnes `idx`, `sentence1` et `sentence2` puisque nous n'en aurons pas besoin et qu'elles contiennent des *strings* (et nous ne pouvons pas créer des tenseurs avec des *strings*) et on regarde la longueur de chaque entrée du batch : 

```py
samples = tokenized_datasets["train"][:8]
samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]}
[len(x) for x in samples["input_ids"]]
```

```python out
[50, 59, 47, 67, 59, 50, 62, 32]
```

Sans surprise, nous obtenons des échantillons de longueur variable, de 32 à 67. Le *padding* dynamique signifie que les échantillons de ce batch doivent tous être rembourrés à une longueur de 67, la longueur maximale dans le batch. Sans le *padding* dynamique, tous les échantillons devraient être rembourrés à la longueur maximale du jeu de données entier, ou à la longueur maximale que le modèle peut accepter. Vérifions à nouveau que notre `data_collator` rembourre dynamiquement le batch correctement :

```py
batch = data_collator(samples)
{k: v.shape for k, v in batch.items()}
```

{#if fw === 'tf'}

```python out
{'attention_mask': TensorShape([8, 67]),
 'input_ids': TensorShape([8, 67]),
 'token_type_ids': TensorShape([8, 67]),
 'labels': TensorShape([8])}
```

{:else}

```python out
{'attention_mask': torch.Size([8, 67]),
 'input_ids': torch.Size([8, 67]),
 'token_type_ids': torch.Size([8, 67]),
 'labels': torch.Size([8])}
```

C'est beau ! Maintenant que nous sommes passés du texte brut à des batchs que notre modèle peut traiter, nous sommes prêts à le *finetuner* !

{/if}

<Tip>

✏️ **Essayez !** Reproduisez le prétraitement sur le jeu de données GLUE SST-2. C'est un peu différent puisqu'il est composé de phrases simples au lieu de paires, mais le reste de ce que nous avons fait devrait être identique. Pour un défi plus difficile, essayez d'écrire une fonction de prétraitement qui fonctionne sur toutes les tâches GLUE.

</Tip>

{#if fw === 'tf'}

Maintenant que nous disposons de notre jeu de données et d'un assembleur de données, nous devons les assembler. Nous pourrions charger manuellement des batchs et les assembler mais c'est beaucoup de travail et probablement pas très performant non plus. A la place, il existe une méthode simple qui offre une solution performante à ce problème : `to_tf_dataset()`. Cela va envelopper un `tf.data.Dataset` autour de votre jeu de données, avec une fonction de collation optionnelle. `tf.data.Dataset` est un format natif de TensorFlow que Keras peut utiliser pour `model.fit()`, donc cette seule méthode convertit immédiatement un *dataset* en un format prêt pour l'entraînement. Voyons cela en action avec notre jeu de données !

```py
tf_train_dataset = tokenized_datasets["train"].to_tf_dataset(
    columns=["attention_mask", "input_ids", "token_type_ids"],
    label_cols=["labels"],
    shuffle=True,
    collate_fn=data_collator,
    batch_size=8,
)

tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset(
    columns=["attention_mask", "input_ids", "token_type_ids"],
    label_cols=["labels"],
    shuffle=False,
    collate_fn=data_collator,
    batch_size=8,
)
```

Et c'est tout ! Nous pouvons utiliser ces jeux de données dans le prochain cours, où l'entraînement sera agréablement simple après tout le dur travail de prétraitement des données.

{/if}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter3/2.mdx" />

### Comment rédiger une bonne <i>issue</i>
https://huggingface.co/learn/course/fr/chapter8/5.md

# Comment rédiger une bonne <i>issue</i>

<CourseFloatingBanner chapter={8}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter8/section5.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter8/section5.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter8/section5.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter8/section5.ipynb"},
]} />


Lorsque vous rencontrez un problème avec l'une des bibliothèques d'Hugging Face, faites le nous savoir afin que nous puissions le corriger (il en va de même pour toute bibliothèque open source).<br>
Si vous n'êtes pas complètement certain que le *bug* se trouve dans votre propre code ou dans l'une de nos bibliothèques, le premier endroit à vérifier est le [forum](https://discuss.huggingface.co/). La communauté vous aidera à résoudre votre problème et l'équipe d'Hugging Face y suit de près les discussions qui s'y déroulent.

<Youtube id="_PAli-V4wj0"/>

Lorsque vous êtes sûr d'avoir identifier un *bug*, la première étape consiste à construire un exemple minimal qui soit reproductible.

## Créer un exemple minimal reproductible

Il est très important d'isoler le morceau de code qui produit le *bug* car personne dans l'équipe d'Hugging Face n'est (encore) un magicien et on ne peut pas réparer ce qu'on ne peut pas voir. Un exemple minimal reproductible doit, comme son nom l'indique, être reproductible. Cela signifie qu'il ne doit pas dépendre de fichiers ou de données externes que vous pourriez avoir. Essayez de remplacer les données que vous utilisez par des valeurs fictives qui ressemblent à vos valeurs réelles et qui produisent toujours la même erreur.

<Tip>

🚨 De nombreux problèmes dans le dépôt 🤗 *Transformers* ne sont pas résolus car les données utilisées pour les reproduire ne sont pas accessibles.

</Tip>

Une fois que vous avez quelque chose d'autonome, essayez de le réduire au moins de lignes de code possible, en construisant ce que nous appelons un _exemple reproductible minimal_. Bien que cela nécessite un peu plus de travail de votre part, vous serez presque assuré d'obtenir de l'aide et une correction si vous fournissez un exemple reproductible court et agréable.

Si vous vous sentez suffisamment à l'aise, allez inspecter le code source où se trouve votre *bug*. Vous trouverez peut-être une solution à votre problème (dans ce cas, vous pouvez même suggérer une *pull request* pour le corriger), mais plus généralement, cela peut aider les mainteneurs à mieux comprendre le code source lorsqu'ils lisent votre message.

## Remplir le gabarit de problème

Lorsque vous ouvrerez votre *issue* vous remarquerez qu'il y a un gabarit à remplir. Nous suivrons ici celui pour la bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers/issues/new/choose) mais le même type d'information sera requis dans un autre dépôt. Ne laissez pas le gabarit vide : prendre le temps de le remplir maximisera vos chances d'obtenir une réponse et de résoudre votre problème.

En général, lorsque vous signalez un problème, restez toujours courtois. Il s'agit d'un projet open source, vous utilisez donc un logiciel libre, et personne n'est obligé de vous aider. Vous pouvez inclure dans votre *issue* des critiques qui vous semblent justifiées mais les mainteneurs pourraient très bien les prendre mal et ne pas être pressés de vous aider. Assurez-vous de lire le [code de conduite](https://github.com/huggingface/transformers/blob/master/CODE_OF_CONDUCT.md) du projet.

### Inclure les informations sur votre environnement

🤗 *Transformers* fournit un utilitaire pour obtenir toutes les informations nécessaire concernant votre environnement. Il suffit de taper ce qui suit dans votre terminal :

```
transformers-cli env
```

et vous devriez obtenir quelque chose comme :

```out
Copy-and-paste the text below in your GitHub issue and FILL OUT the two last points.

- `transformers` version: 4.12.0.dev0
- Platform: Linux-5.10.61-1-MANJARO-x86_64-with-arch-Manjaro-Linux
- Python version: 3.7.9
- PyTorch version (GPU?): 1.8.1+cu111 (True)
- Tensorflow version (GPU?): 2.5.0 (True)
- Flax version (CPU?/GPU?/TPU?): 0.3.4 (cpu)
- Jax version: 0.2.13
- JaxLib version: 0.1.65
- Using GPU in script?: <fill in>
- Using distributed or parallel set-up in script?: <fill in>
```

Vous pouvez également ajouter un `!` au début de la commande `transformers-cli env` pour l'exécuter depuis une cellule de *notebook* puis copier et coller le résultat au début de votre *issue*.

### Taguer des personnes 

Taguer des personnes en tapant un `@` suivi de leur identifiant GitHub leur enverra une notification afin qu'elles voient votre problème et puissent répondre plus rapidement. Néanmoins utilisez cette fonction avec modération car les personnes que vous taguez peuvent ne pas apprécier d'être notifiées si elles n'ont pas de lien direct avec le problème. Si vous avez regardé les fichiers sources liés à votre *bug*, vous devriez taguer la dernière personne qui a fait des changements à la ligne que vous pensez être responsable de votre problème (vous pouvez trouver cette information en regardant ladite ligne sur GitHub, en la sélectionnant, puis en cliquant sur « *View git blame* »).

Sinon, le gabarit propose des suggestions de personnes à taguer. En général, ne marquez jamais plus de trois personnes !

### Inclure un exemple reproductible

Si vous avez réussi à créer un exemple autonome qui produit le *bug*, il est temps de l'inclure ! Tapez une ligne avec trois *backticks* suivis de `python`, comme ceci :

```
```python
```

puis collez votre exemple minimal reproductible et tapez une nouvelle ligne avec trois *backticks*. Cela permettra de s'assurer que votre code est correctement formaté.

Si vous n'avez pas réussi à créer un exemple reproductible, expliquez en des étapes claires comment vous êtes arrivé à votre problème. Si vous le pouvez, incluez un lien vers un *notebook* d'un Google Colab où vous avez trouvé l'erreur. Plus vous partagerez d'informations, plus les mainteneurs seront en mesure de vous répondre.

Dans tous les cas, vous devez copier et coller l'intégralité du message d'erreur que vous obtenez. Si vous travaillez dans Colab, n'oubliez pas que certaines cellules peuvent être automatiquement réduites dans la trace de la pile et veillez donc à les afficher avant de les copier. Comme pour l'exemple de code, placez le message d'erreur entre deux lignes avec trois *backticks* afin qu'il soit correctement formaté.

### Décrire le comportement attendu

Expliquez en quelques lignes ce que vous vous attendiez à obtenir afin que les mainteneurs comprennent bien le problème. Cette partie est généralement assez évidente, elle devrait donc tenir en une seule phrase mais dans certains cas vous pouvez avoir beaucoup à dire.

## Et ensuite ?

Une fois que votre problème est classé, vérifiez rapidement que tout est en ordre. Vous pouvez modifier le problème si vous avez fait une erreur ou même changer son titre si vous vous rendez compte que le problème est différent de ce que vous pensiez initialement.

Il est inutile d'envoyer des messages aux personnes concernées si vous n'obtenez pas de réponse. Si personne ne vous aide au bout de quelques jours, il est probable que personne n'a pu donner un sens à votre problème. N'hésitez pas à revenir à l'exemple reproductible. Pouvez-vous le rendre plus court et plus concis ? Si vous n'obtenez pas de réponse au bout d'une semaine, vous pouvez laisser un message demandant gentiment de l'aide, surtout si vous avez modifié votre question pour inclure plus d'informations sur le problème.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter8/5.mdx" />

### Introduction
https://huggingface.co/learn/course/fr/chapter8/1.md

# Introduction

<CourseFloatingBanner
    chapter={8}
    classNames="absolute z-10 right-0 top-0"
/>

Maintenant que vous savez comment aborder les tâches de NLP les plus courantes avec 🤗 *Transformers*, vous devriez être en mesure de vous lancer dans vos propres projets ! Dans ce chapitre, nous allons explorer ce qu'il faut faire lorsque vous rencontrez un problème. Vous apprendrez comment déboguer avec succès votre code ou votre entraînement et comment demander de l'aide à la communauté si vous ne parvenez pas à résoudre le problème par vous-même. Et si vous pensez avoir trouvé un bug dans l'une des bibliothèques d'Hugging Face, nous vous montrerons la meilleure façon de le signaler afin que le problème soit résolu le plus rapidement possible.

Plus précisément, dans ce chapitre vous allez apprendre :

- la première chose à faire lorsque vous obtenez une erreur,
- comment demander de l'aide sur le [forum](https://discuss.huggingface.co/),
- comment déboguer votre pipeline d'entraînement,
- comment rédiger une bonne *issue*.

Rien de tout cela n'est spécifiquement lié à 🤗 *Transformers* ou à l'écosystème Hugging Face. Les leçons de ce chapitre sont applicables à la plupart des projets open source !


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter8/1.mdx" />

### Demander de l'aide sur les forums
https://huggingface.co/learn/course/fr/chapter8/3.md

# Demander de l'aide sur les forums


<CourseFloatingBanner chapter={8}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter8/section3.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter8/section3.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter8/section3.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter8/section3.ipynb"},
]} />


<Youtube id="S2EEG3JIt2A"/>

Le [forum d'Hugging Face](https://discuss.huggingface.co) est un endroit idéal pour obtenir de l'aide de l'équipe open source d'Hugging Face et de la communauté au sens large. Voici à quoi ressemble la page principale :

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/forums.png" alt="The Hugging Face forums." width="100%"/>
</div>

Dans la partie gauche, vous pouvez voir toutes les catégories dans lesquelles les différents sujets sont regroupés, tandis que la partie droite montre les sujets les plus récents. Un sujet est un message qui contient un titre, une catégorie et une description. C'est assez similaire au format des *issues* GitHub que nous avons vu lors de la création de notre propre jeu de données dans le [chapitre 5](/course/fr/chapter5). Comme son nom l'indique, la catégorie [*Beginners*](https://discuss.huggingface.co/c/beginners/5) est principalement destinée aux personnes qui débutent avec les bibliothèques et l'écosystème d'Hugging Face. Toute question sur l'une des bibliothèques est la bienvenue ici, que ce soit pour déboguer du code ou pour demander de l'aide sur la façon de faire quelque chose. (Cela dit, si votre question concerne une bibliothèque en particulier, vous devriez probablement vous diriger vers la catégorie de bibliothèque correspondante sur le forum).

De même, les catégories [*Intermediate*](https://discuss.huggingface.co/c/intermediate/6) et [*Research*](https://discuss.huggingface.co/c/research/7) sont destinées aux questions plus avancées. Par exemple sur les bibliothèques ou sur une avancée en recherche en NLP dont vous aimeriez discuter.

Et naturellement, nous devrions aussi mentionner la catégorie [*Course*](https://discuss.huggingface.co/c/course/20) où vous pouvez poser toutes les questions que vous avez en rapport avec le cours d'Hugging Face !

Une fois une catégorie choisie, vous êtes prêt à rédiger votre premier sujet. Vous pouvez trouver quelques [indications](https://discuss.huggingface.co/t/how-to-request-support/3128) dans le forum sur la façon de le faire. Dans cette section, nous allons jeter un coup d'oeil à certaines caractéristiques d'un bon sujet.

## Rédiger un bon message sur le forum

A titre d'exemple, supposons que nous essayons de générer des enchâssements à partir d'articles Wikipédia pour créer un moteur de recherche personnalisé. Comme d'habitude, nous chargeons le *tokenizer* et le modèle comme suit :

```python
from transformers import AutoTokenizer, AutoModel

model_checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
model = AutoModel.from_pretrained(model_checkpoint)
```

Supposons maintenant que nous essayons d'enchâsser une section entière de l'[article Wikipedia](https://en.wikipedia.org/wiki/Transformers) sur Transformers (la franchise de films, pas la bibliothèque !):

```python
text = """
Generation One is a retroactive term for the Transformers characters that
appeared between 1984 and 1993. The Transformers began with the 1980s Japanese
toy lines Micro Change and Diaclone. They presented robots able to transform
into everyday vehicles, electronic items or weapons. Hasbro bought the Micro
Change and Diaclone toys, and partnered with Takara. Marvel Comics was hired by
Hasbro to create the backstory; editor-in-chief Jim Shooter wrote an overall
story, and gave the task of creating the characthers to writer Dennis O'Neil.
Unhappy with O'Neil's work (although O'Neil created the name "Optimus Prime"),
Shooter chose Bob Budiansky to create the characters.

The Transformers mecha were largely designed by Shōji Kawamori, the creator of
the Japanese mecha anime franchise Macross (which was adapted into the Robotech
franchise in North America). Kawamori came up with the idea of transforming
mechs while working on the Diaclone and Macross franchises in the early 1980s
(such as the VF-1 Valkyrie in Macross and Robotech), with his Diaclone mechs
later providing the basis for Transformers.

The primary concept of Generation One is that the heroic Optimus Prime, the
villainous Megatron, and their finest soldiers crash land on pre-historic Earth
in the Ark and the Nemesis before awakening in 1985, Cybertron hurtling through
the Neutral zone as an effect of the war. The Marvel comic was originally part
of the main Marvel Universe, with appearances from Spider-Man and Nick Fury,
plus some cameos, as well as a visit to the Savage Land.

The Transformers TV series began around the same time. Produced by Sunbow
Productions and Marvel Productions, later Hasbro Productions, from the start it
contradicted Budiansky's backstories. The TV series shows the Autobots looking
for new energy sources, and crash landing as the Decepticons attack. Marvel
interpreted the Autobots as destroying a rogue asteroid approaching Cybertron.
Shockwave is loyal to Megatron in the TV series, keeping Cybertron in a
stalemate during his absence, but in the comic book he attempts to take command
of the Decepticons. The TV series would also differ wildly from the origins
Budiansky had created for the Dinobots, the Decepticon turned Autobot Jetfire
(known as Skyfire on TV), the Constructicons (who combine to form
Devastator),[19][20] and Omega Supreme. The Marvel comic establishes early on
that Prime wields the Creation Matrix, which gives life to machines. In the
second season, the two-part episode The Key to Vector Sigma introduced the
ancient Vector Sigma computer, which served the same original purpose as the
Creation Matrix (giving life to Transformers), and its guardian Alpha Trion.
"""

text_fr = """
Génération 1 est un terme rétroactif pour les personnages de Transformers qui sont apparus
entre 1984 et 1993. Les Transformers ont commencé avec les lignes de jouets japonaises 
des années 1980, Micro Change et Diaclone. Elles présentaient des robots capables
de se transformer en véhicules de tous les jours, en objets électroniques ou en armes. 
Hasbro a acheté les jouets Micro Change et Diaclone, et s'est associé à Takara. 
Marvel Comics est engagé par Hasbro pour créer l'histoire de fond ; le rédacteur en chef 
Jim Shooter a écrit une histoire générale et confie la tâche de créer les personnages au 
scénariste Dennis O'Neil. Mécontent du travail d'O'Neil (bien que ce dernier ait créé 
le nom "Optimus Prime"), Shooter choisit Bob Budiansky pour créer les personnages.

Les mecha de Transformers ont été en grande partie conçus par Shōji Kawamori, le créateur
de l'anime japonais Macross (qui a été adapté en Robotech en Amérique du Nord). Kawamori
a eu l'idée de transformer des mechas transformables alors qu'il travaillait sur les
franchises Diaclone et Macross au début des années 1980 (comme le VF-1 Valkyrie dans 
Macross et Robotech), et ses méchas Diaclone ont plus tard servi de base à Transformers.

Le concept principal de la Génération 1 est que l'héroïque Optimus Prime, le méchant
Megatron, et leurs meilleurs soldats s'écrasent sur une Terre préhistorique dans l'Arche
et le Némésis avant de se réveiller en 1985, Cybertron traversant à toute allure la zone
neutre en raison de la guerre. La bande dessinée Marvel faisait à l'origine partie 
de l'univers principal de Marvel, avec des apparitions de Spider-Man et Nick Fury, 
plus quelques caméos, ainsi qu'une visite à la Terre Sauvage.

La série télévisée Transformers a commencé à peu près à la même époque. 
Produite par Sunbow Productions et Marvel Productions, puis Hasbro Productions, 
dès le début elle a contredit les histoires de Budiansky. La série TV montre les Autobots
cherchant de nouvelles sources d'énergie et s'écrasent lors de l'attaque des Decepticons. 
Marvel a interprété les Autobots comme la destruction d'un astéroïde malveillant 
s'approchant de Cybertron. Shockwave est loyal envers Megatron dans la série TV, 
et maintient Cybertron dans une impasse en son absence.
Cybertron dans une impasse pendant son absence, mais dans la BD, 
il tente de prendre le commandement des Decepticons. 
La série télévisée s'écarte aussi radicalement des origines que Budiansky avait 
créé pour les Dinobots, le Decepticon devenu Autobot Jetfire
(connu sous le nom de Skyfire à la télévision), 
les Constructicons (qui s'associent pour former Devastator) et Oméga Suprême. 
La bande dessinée Marvel établit très tôt que Prime manie la matrice de création,
qui donne la vie aux machines. Dans la saison, l'épisode en deux parties 
The Key to Vector Sigma a introduit l'ancien ordinateur l'ancien ordinateur
Vector Sigma, qui servait le même objectif original que la matrice de création 
(donner la vie aux Transformers), et son gardien Alpha Trion.
"""

inputs = tokenizer(text, return_tensors="pt")
logits = model(**inputs).logits
```

```python output
IndexError: index out of range in self
```

Oh nous avons rencontré un problème. Le message d'erreur est bien plus énigmatique que ceux que nous avons vus dans la [section 2](/course/chapter8/fr/section2) ! Nous n'arrivons pas à comprendre le *traceback* complet, alors nous décidons de nous tourner vers le forum d'Hugging Face pour obtenir de l'aide. Comment pouvons-nous élaborer le sujet ?

Pour commencer, nous devons cliquer sur le bouton *New Topic* dans le coin supérieur droit (notez que pour créer un sujet, nous devons être connectés) :

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/forums-new-topic.png" alt="Creating a new forum topic." width="100%"/>
</div>

Cela fait apparaître une interface de rédaction où nous pouvons saisir le titre de notre sujet, sélectionner une catégorie et rédiger le contenu :

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/forum-topic01.png" alt="The interface for creating a forum topic." width="100%"/>
</div>

Puisque l'erreur semble concerner exclusivement 🤗 *Transformers*, nous allons la sélectionner pour la catégorie. Notre première tentative d'explication du problème pourrait ressembler à quelque chose comme ça :

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/forum-topic02.png" alt="Drafting the content for a new forum topic." width="100%"/>
</div>

Bien que ce sujet contienne le message d'erreur pour lequel nous avons besoin d'aide, il y a quelques problèmes avec la façon dont il est écrit :

1. le titre n'est pas très descriptif, ainsi toute personne parcourant le forum ne sera pas en mesure de dire de quoi il s'agit sans lire également le corps du sujet,
2. le corps du texte ne fournit pas suffisamment d'informations sur *l'origine* de l'erreur et sur *la manière* de la reproduire,
3. le sujet s'adresse directement à quelques personnes sur un ton quelque peu exigeant.

Les sujets comme celui-ci ne sont pas susceptibles d'obtenir une réponse rapide (si tant est qu'ils en obtiennent une) alors voyons comment nous pouvons l'améliorer. Commençons par la première question, celle du choix d'un bon titre.

### Choisir un titre descriptif

Si vous essayez d'obtenir de l'aide pour résoudre un *bug* dans votre code, une bonne règle de base consiste à inclure suffisamment d'informations dans le titre pour que les autres puissent rapidement déterminer s'ils pensent pouvoir répondre à votre question ou non. Dans notre exemple, nous connaissons le nom de l'exception et savons qu'elle est déclenchée dans la passe avant du modèle, où nous appelons `model(**inputs)`. Pour communiquer cela, un titre possible pourrait être :

> Source de l'IndexError dans la passe avant d'AutoModel ?

Ce titre indique au lecteur _où_ vous pensez que le *bug* provient, et s'il a déjà rencontré un `IndexError`, il y a de fortes chances qu'il sache comment le déboguer. Bien sûr, le titre peut être ce que vous voulez et d'autres variations comme :

> Pourquoi mon modèle produit-il un IndexError ?

pourrait également convenir. Maintenant que nous avons un titre descriptif, voyons comment améliorer le corps du texte.

### Formatage de vos extraits de code

La lecture du code source est déjà difficile dans un IDE, mais c'est encore plus difficile lorsque le code est copié et collé en texte brut ! Heureusement, le forum d'Hugging Face supporte l'utilisation de Markdown donc vous devriez toujours entourer vos blocs de code avec trois *backticks* (```) pour qu'ils soient plus facilement lisibles. Faisons cela pour embellir le message d'erreur et pendant que nous y sommes, rendons le corps un peu plus poli que notre version originale :

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/forum-topic03.png" alt="Our revised forum topic, with proper code formatting." width="100%"/>
</div>

Comme vous pouvez le voir dans la capture d'écran, le fait d'entourer les blocs de code de *backticks* convertit le texte brut en code formaté, avec un style de couleur ! Notez également que des *backticks* simples peuvent être utilisés pour formater des variables en ligne comme nous l'avons fait pour `distilbert-base-uncased`. Ce sujet a l'air bien meilleur, et avec un peu de chance, nous pourrions trouver quelqu'un dans la communauté qui pourrait deviner à quoi correspond l'erreur. Cependant, au lieu de compter sur la chance, rendons la vie plus facile en incluant le *traceback* dans ses moindres détails !

### Inclure le <i>traceback</i> complet

Puisque la dernière ligne de le *traceback* est souvent suffisante pour déboguer votre propre code, il peut être tentant de ne fournir que cela dans votre sujet pour "gagner de la place". Bien que bien intentionné, cela rend en fait le débogage du problème _plus difficile_ pour les autres, car les informations situées plus haut dans le *traceback* peuvent également être très utiles. Une bonne pratique consiste donc à copier et coller le *traceback* _entier_, en veillant à ce qu'elle soit bien formatée. Comme ces tracebacks peuvent être assez longs, certaines personnes préfèrent les montrer après avoir expliqué le code source. C'est ce que nous allons faire. Maintenant, notre sujet de forum ressemble à ce qui suit :

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/forum-topic04.png" alt="Our example forum topic, with the complete traceback." width="100%"/>
</div>

Ceci est beaucoup plus informatif et un lecteur attentif pourrait être en mesure d'indiquer que le problème semble être dû à la transmission d'une longue entrée en raison de cette ligne dans le *traceback* :

> Token indices sequence length is longer than the specified maximum sequence length for this model (583 > 512).

Cependant, nous pouvons leur faciliter les choses en leur fournissant le code qui a déclenché l'erreur. C'est ce que nous allons faire maintenant.

### Fournir un exemple reproductible

Si vous avez déjà essayé de déboguer le code de quelqu'un d'autre, vous avez probablement d'abord essayé de recréer le problème qu'il a signalé afin de pouvoir commencer à travailler sur le *traceback* pour identifier l'erreur. Il en va de même lorsqu'il s'agit d'obtenir (ou de donner) de l'aide sur les forums. Il est donc très utile de pouvoir fournir un petit exemple qui reproduit l'erreur. La moitié du temps, le simple fait de faire cet exercice vous aidera à comprendre ce qui ne va pas. Dans tous les cas, la pièce manquante de notre exemple est de montrer les _entrées_ que nous avons fournies au modèle. En faisant cela, nous obtenons quelque chose comme l'exemple complet suivant :

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/forum-topic05.png" alt="The final version of our forum topic." width="100%"/>
</div>

Ce sujet contient maintenant un bon lot d'informations et il est rédigé d'une manière qui a beaucoup plus de chances d'attirer l'attention de la communauté et d'obtenir une réponse utile. Avec ces directives de base, vous pouvez maintenant créer de superbes sujets pour trouver les réponses à vos questions sur 🤗 *Transformers* !


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter8/3.mdx" />

### Partie 2 terminée !
https://huggingface.co/learn/course/fr/chapter8/6.md

# Partie 2 terminée !

<CourseFloatingBanner
    chapter={8}
    classNames="absolute z-10 right-0 top-0"
/>

Félicitations, vous avez terminé la deuxième partie du cours ! Nous travaillons activement sur la troisième alors inscrivez-vous à notre [*newsletter*](https://huggingface.curated.co/) pour être sûr de ne pas manquer sa sortie.

Vous devriez maintenant être en mesure d'aborder une série de tâches de NLP et de *finetuner* ou de prétraîner un modèle sur celles-ci. N'oubliez pas de partager vos résultats avec la communauté sur le [*Hub*](https://huggingface.co/models).

Nous sommes impatients de voir ce que vous allez construire avec les connaissances que vous avez acquises !


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter8/6.mdx" />

### Débogage du pipeline d'entraînement
https://huggingface.co/learn/course/fr/chapter8/4.md

# Débogage du pipeline d'entraînement

<CourseFloatingBanner chapter={8}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter8/section4.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter8/section4.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter8/section4.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter8/section4.ipynb"},
]} />

Vous avez écrit un magnifique script pour entraîner ou *finetuner* un modèle sur une tâche donnée en suivant consciencieusement les conseils du [chapitre 7](/course/fr/chapter7). Mais lorsque vous lancez la commande `model.fit()`, quelque chose d'horrible se produit : vous obtenez une erreur 😱 ! Ou pire, tout semble aller bien et l'entraînement se déroule sans erreur mais le modèle résultant est mauvais. Dans cette section, nous allons vous montrer ce que vous pouvez faire pour déboguer ce genre de problèmes.

## Déboguer le pipeline d'entraînement

<Youtube id="L-WSwUWde1U"/>

Le problème lorsque vous rencontrez une erreur dans `trainer.train()` est qu'elle peut provenir de plusieurs sources, car la fonction `Trainer` assemble généralement des batchs de choses. Elle convertit les jeux de données en chargeurs de données donc le problème pourrait être quelque chose d'erroné dans votre jeu de données, ou un problème en essayant de regrouper les éléments des jeux de données ensemble. Ensuite, elle prend un batch de données et le transmet au modèle, le problème peut donc se situer dans le code du modèle. Après cela, elle calcule les gradients et effectue l'étape d'optimisation, le problème peut donc également se situer dans votre optimiseur. Et même si tout se passe bien pendant l'entraînement, quelque chose peut encore mal tourner pendant l'évaluation si votre métrique pose problème.

La meilleure façon de déboguer une erreur qui survient dans `trainer.train()` est de passer manuellement en revue tout le pipeline pour voir où les choses se sont mal passées. L'erreur est alors souvent très facile à résoudre.

Pour le démontrer, nous utiliserons le script suivant qui tente de *finetuner* un modèle DistilBERT sur le [jeu de données MNLI](https://huggingface.co/datasets/glue) :

```py
from datasets import load_dataset
import evaluate
from transformers import (
    AutoTokenizer,
    AutoModelForSequenceClassification,
    TrainingArguments,
    Trainer,
)

raw_datasets = load_dataset("glue", "mnli")

model_checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)


def preprocess_function(examples):
    return tokenizer(examples["premise"], examples["hypothesis"], truncation=True)


tokenized_datasets = raw_datasets.map(preprocess_function, batched=True)
model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint)

args = TrainingArguments(
    f"distilbert-finetuned-mnli",
    evaluation_strategy="epoch",
    save_strategy="epoch",
    learning_rate=2e-5,
    num_train_epochs=3,
    weight_decay=0.01,
)

metric = evaluate.load("glue", "mnli")


def compute_metrics(eval_pred):
    predictions, labels = eval_pred
    return metric.compute(predictions=predictions, references=labels)


trainer = Trainer(
    model,
    args,
    train_dataset=raw_datasets["train"],
    eval_dataset=raw_datasets["validation_matched"],
    compute_metrics=compute_metrics,
)
trainer.train()
```

Si vous essayez de l'exécuter, vous serez confronté à une erreur plutôt cryptique :

```python out
'ValueError: You have to specify either input_ids or inputs_embeds'
```

### Vérifiez vos données

Cela va sans dire, mais si vos données sont corrompues, le `Trainer` ne sera pas capable de former des batchs et encore moins d'entraîner votre modèle. Donc, tout d'abord, vous devez jeter un coup d'oeil à ce qui se trouve dans votre jeu d'entraînement.

Pour éviter d'innombrables heures passées à essayer de corriger quelque chose qui n'est pas la source du bug, nous vous recommandons d'utiliser `trainer.train_dataset` pour vos vérifications et rien d'autre. Faisons donc cela ici :

```py
trainer.train_dataset[0]
```

```python out
{'hypothesis': 'Product and geography are what make cream skimming work. ',
 'idx': 0,
 'label': 1,
 'premise': 'Conceptually cream skimming has two basic dimensions - product and geography.'}
```

Vous remarquez quelque chose d'anormal ? Ceci, en conjonction avec le message d'erreur sur les `input_ids` manquants, devrait vous faire réaliser que ce sont des textes et non des nombres que le modèle peut comprendre. Ici, l'erreur originale est très trompeuse parce que le `Trainer` enlève automatiquement les colonnes qui ne correspondent pas à la signature du modèle (c'est-à-dire, les arguments attendus par le modèle). Cela signifie qu'ici, tout, sauf les étiquettes, a été éliminé. Il n'y avait donc aucun problème à créer des batchs et à les envoyer ensuite au modèle, qui s'est plaint à son tour de ne pas avoir reçu les bons arguments.

Pourquoi les données n'ont-elles pas été traitées ? Nous avons utilisé la méthode `Dataset.map()` sur les jeux de données pour appliquer le *tokenizer* sur chaque échantillon. Mais si vous regardez attentivement le code, vous verrez que nous avons fait une erreur en passant les ensembles d'entraînement et d'évaluation au `Trainer`. Au lieu d'utiliser `tokenized_datasets` ici, nous avons utilisé `raw_datasets` 🤦. Alors corrigeons ça !

```py
from datasets import load_dataset
import evaluate
from transformers import (
    AutoTokenizer,
    AutoModelForSequenceClassification,
    TrainingArguments,
    Trainer,
)

raw_datasets = load_dataset("glue", "mnli")

model_checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)


def preprocess_function(examples):
    return tokenizer(examples["premise"], examples["hypothesis"], truncation=True)


tokenized_datasets = raw_datasets.map(preprocess_function, batched=True)
model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint)

args = TrainingArguments(
    f"distilbert-finetuned-mnli",
    evaluation_strategy="epoch",
    save_strategy="epoch",
    learning_rate=2e-5,
    num_train_epochs=3,
    weight_decay=0.01,
)

metric = evaluate.load("glue", "mnli")


def compute_metrics(eval_pred):
    predictions, labels = eval_pred
    return metric.compute(predictions=predictions, references=labels)


trainer = Trainer(
    model,
    args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation_matched"],
    compute_metrics=compute_metrics,
)
trainer.train()
```

Ce nouveau code donnera maintenant une erreur différente (c'est un progrès !) :

```python out
'ValueError: expected sequence of length 43 at dim 1 (got 37)'
```

En regardant le *traceback*, nous pouvons voir que l'erreur se produit dans l'étape de collationnement des données :

```python out
~/git/transformers/src/transformers/data/data_collator.py in torch_default_data_collator(features)
    105                 batch[k] = torch.stack([f[k] for f in features])
    106             else:
--> 107                 batch[k] = torch.tensor([f[k] for f in features])
    108 
    109     return batch
```

Donc, nous devrions passer à cela. Mais avant finissons d'inspecter nos données, pour être sûrs à 100% qu'elles sont correctes.

Une chose que vous devriez toujours faire lorsque vous déboguez une session d'entraînement est de jeter un coup d'oeil aux entrées décodées de votre modèle. Nous ne pouvons pas donner un sens aux chiffres que nous lui fournissons directement, nous devons donc examiner ce que ces chiffres représentent. Dans le domaine de la vision par ordinateur cela signifie regarder les images décodées des pixels que vous passez, dans le domaine de la parole cela signifie écouter les échantillons audio décodés, et pour notre exemple de NLP cela signifie utiliser notre *tokenizer* pour décoder les entrées :

```py
tokenizer.decode(trainer.train_dataset[0]["input_ids"])
```

```python out
'[CLS] conceptually cream skimming has two basic dimensions - product and geography. [SEP] product and geography are what make cream skimming work. [SEP]'
```

Cela semble correct. Vous devriez faire cela pour toutes les clés dans les entrées : 

```py
trainer.train_dataset[0].keys()
```

```python out
dict_keys(['attention_mask', 'hypothesis', 'idx', 'input_ids', 'label', 'premise'])
```

Notez que les clés qui ne correspondent pas à des entrées acceptées par le modèle seront automatiquement écartées, donc ici nous ne garderons que `input_ids`, `attention_mask`, et `label` (qui sera renommé `labels`). Pour revérifier la signature du modèle, vous pouvez imprimer la classe de votre modèle, puis aller consulter sa documentation :

```py
type(trainer.model)
```

```python out
transformers.models.distilbert.modeling_distilbert.DistilBertForSequenceClassification
```

Donc dans notre cas, nous pouvons vérifier les paramètres acceptés sur [cette page](https://huggingface.co/transformers/model_doc/distilbert.html#distilbertforsequenceclassification). Le `Trainer` va également enregistrer les colonnes qu'il rejette.

Nous avons vérifié que les identifiants d'entrée sont corrects en les décodant. Ensuite, il y a le `attention_mask` :

```py
tokenizer.decode(trainer.train_dataset[0]["attention_mask"])
```

```python out
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
```

Comme nous n'avons pas appliqué de *padding* dans notre prétraitement, cela semble parfaitement naturel. Pour être sûr qu'il n'y a pas de problème avec ce masque d'attention, vérifions qu'il est de la même longueur que nos identifiants d'entrée :

```py
len(trainer.train_dataset[0]["attention_mask"]) == len(
    trainer.train_dataset[0]["input_ids"]
)
```

```python out
True
```

C'est bien ! Enfin, vérifions notre étiquette :

```py
trainer.train_dataset[0]["label"]
```

```python out
1
```

Comme les identifiants d'entrée, c'est un nombre qui n'a pas vraiment de sens en soi. Comme nous l'avons vu précédemment, la correspondance entre les entiers et les noms d'étiquettes est stockée dans l'attribut `names` de la *caractéristique* correspondante du jeu de données :

```py
trainer.train_dataset.features["label"].names
```

```python out
['entailment', 'neutral', 'contradiction']
```

Donc `1` signifie `neutral`, ce qui signifie que les deux phrases que nous avons vues ci-dessus ne sont pas en contradiction : la première n'implique pas la seconde. Cela semble correct !

Nous n'avons pas de *token* de type identifiant ici puisque DistilBERT ne les attend pas. Si vous en avez dans votre modèle, vous devriez également vous assurer qu'ils correspondent correctement à l'endroit où se trouvent la première et la deuxième phrase dans l'entrée.

<Tip>

✏️ *A votre tour !* Vérifiez que tout semble correct avec le deuxième élément du jeu de données d'entraînement.

</Tip>

Ici nous ne vérifions que le jeu d'entraînement. Vous devez bien sûr vérifier de la même façon les jeux de validation et de test.

Maintenant que nous savons que nos jeux de données sont bons, il est temps de vérifier l'étape suivante du pipeline d'entraînement.

### Des jeux de données aux chargeurs de données

La prochaine chose qui peut mal tourner dans le pipeline d'entraînement est lorsque le `Trainer` essaie de former des batchs à partir du jeu d'entraînement ou de validation. Une fois que vous êtes sûr que les jeux de données du `Trainer` sont corrects, vous pouvez essayer de former manuellement un batch en exécutant ce qui suit (remplacez `train` par `eval` pour le *dataloader* de validation) :

```py
for batch in trainer.get_train_dataloader():
    break
```

Ce code crée le *dataloader* d'entraînement puis le parcourt en s'arrêtant à la première itération. Si le code s'exécute sans erreur, vous avez le premier batch d'entraînement que vous pouvez inspecter, et si le code se trompe, vous êtes sûr que le problème se situe dans le *dataloader*, comme c'est le cas ici :

```python out
~/git/transformers/src/transformers/data/data_collator.py in torch_default_data_collator(features)
    105                 batch[k] = torch.stack([f[k] for f in features])
    106             else:
--> 107                 batch[k] = torch.tensor([f[k] for f in features])
    108 
    109     return batch

ValueError: expected sequence of length 45 at dim 1 (got 76)
```

L'inspection de la dernière image du *traceback* devrait suffire à vous donner un indice mais creusons un peu plus. La plupart des problèmes lors de la création d'un batch sont dus à l'assemblage des exemples en un seul batch. La première chose à vérifier en cas de doute est le `collate_fn` utilisé par votre `DataLoader` :

```py
data_collator = trainer.get_train_dataloader().collate_fn
data_collator
```

```python out
<function transformers.data.data_collator.default_data_collator(features: List[InputDataClass], return_tensors='pt') -> Dict[str, Any]>
```

C'est donc `default_data_collator`, mais ce n'est pas ce que nous voulons dans ce cas. Nous voulons rembourrer nos exemples à la phrase la plus longue du batch, ce qui est fait par `DataCollatorWithPadding`. Et cette assembleur de données est censé être utilisé par défaut par le `Trainer`, alors pourquoi n'est-il pas utilisé ici ?

La réponse est que nous n'avons pas passé le `tokenizer` au `Trainer`, donc il ne pouvait pas créer le `DataCollatorWithPadding` que nous voulons. En pratique, il ne faut jamais hésiter à transmettre explicitement l'assembleur de données que l'on veut utiliser pour être sûr d'éviter ce genre d'erreurs. Adaptons notre code pour faire exactement cela :

```py
from datasets import load_dataset
import evaluate
from transformers import (
    AutoTokenizer,
    AutoModelForSequenceClassification,
    DataCollatorWithPadding,
    TrainingArguments,
    Trainer,
)

raw_datasets = load_dataset("glue", "mnli")

model_checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)


def preprocess_function(examples):
    return tokenizer(examples["premise"], examples["hypothesis"], truncation=True)


tokenized_datasets = raw_datasets.map(preprocess_function, batched=True)
model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint)

args = TrainingArguments(
    f"distilbert-finetuned-mnli",
    evaluation_strategy="epoch",
    save_strategy="epoch",
    learning_rate=2e-5,
    num_train_epochs=3,
    weight_decay=0.01,
)

metric = evaluate.load("glue", "mnli")


def compute_metrics(eval_pred):
    predictions, labels = eval_pred
    return metric.compute(predictions=predictions, references=labels)


data_collator = DataCollatorWithPadding(tokenizer=tokenizer)

trainer = Trainer(
    model,
    args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation_matched"],
    compute_metrics=compute_metrics,
    data_collator=data_collator,
    tokenizer=tokenizer,
)
trainer.train()
```

La bonne nouvelle ? Nous n'avons plus la même erreur qu'avant, ce qui est un progrès certain. La mauvaise nouvelle ? Nous obtenons une erreur CUDA infâme à la place :

```python out
RuntimeError: CUDA error: CUBLAS_STATUS_ALLOC_FAILED when calling `cublasCreate(handle)`
```

C'est une mauvaise chose car les erreurs CUDA sont extrêmement difficiles à déboguer en général. Nous verrons dans une minute comment résoudre ce problème mais terminons d'abord notre analyse de la création de batchs.

Si vous êtes sûr que votre collecteur de données est le bon, vous devriez essayer de l'appliquer sur quelques échantillons de votre jeu de données :

```py
data_collator = trainer.get_train_dataloader().collate_fn
batch = data_collator([trainer.train_dataset[i] for i in range(4)])
```

Ce code échouera parce que le `train_dataset` contient des colonnes de type *string* que le `Trainer` supprime habituellement. Vous pouvez les supprimer manuellement ou si vous voulez reproduire exactement ce que le `Trainer` fait en coulisse, vous pouvez appeler la méthode `Trainer._remove_unused_columns()` qui fait cela :

```py
data_collator = trainer.get_train_dataloader().collate_fn
actual_train_set = trainer._remove_unused_columns(trainer.train_dataset)
batch = data_collator([actual_train_set[i] for i in range(4)])
```

Vous devriez alors être en mesure de déboguer manuellement ce qui se passe dans le collecteur de données si l'erreur persiste.

Maintenant que nous avons débogué le processus de création de batch, il est temps d'en passer un dans le modèle !


### Passage par le modèle

Vous devriez être en mesure d'obtenir un batch en exécutant la commande suivante :

```py
for batch in trainer.get_train_dataloader():
    break
```

Si vous exécutez ce code dans un *notebook*, vous risquez d'obtenir une erreur CUDA similaire à celle que nous avons vue précédemment, auquel cas vous devrez redémarrer votre *notebook* et réexécuter le dernier extrait sans la ligne `trainer.train()`. C'est la deuxième chose la plus ennuyeuse à propos des erreurs CUDA : elles cassent irrémédiablement votre noyau. La première plus ennuyeuse est le fait qu'elles sont difficiles à déboguer.

Comment cela se fait-il ? Cela tient à la façon dont les GPUs fonctionnent. Ils sont extrêmement efficaces pour exécuter un batch d'opérations en parallèle, mais l'inconvénient est que lorsque l'une de ces instructions entraîne une erreur, vous ne le savez pas immédiatement. Ce n'est que lorsque le programme appelle une synchronisation des multiples processus sur le GPU qu'il réalise que quelque chose s'est mal passé, de sorte que l'erreur est en fait mentionnée à un endroit qui n'a rien à voir avec ce qui l'a créée. Par exemple, si nous regardons notre *traceback* précédent, l'erreur a été soulevée pendant la passe arrière, mais nous verrons dans une minute qu'elle provient en fait de quelque chose dans la passe avant.

Alors comment déboguer ces erreurs ? La réponse est simple : nous ne le faisons pas. À moins que votre erreur CUDA ne soit une erreur *out-of-memory* (ce qui signifie qu'il n'y a pas assez de mémoire dans votre GPU), vous devez toujours revenir au CPU pour la déboguer.

Pour faire cela dans notre cas, nous devons juste remettre le modèle sur le CPU et l'appeler sur notre batch. Le batch retourné par le `DataLoader` n'a pas encore été déplacé sur le GPU :

```python
outputs = trainer.model.cpu()(**batch)
```

```python out
~/.pyenv/versions/3.7.9/envs/base/lib/python3.7/site-packages/torch/nn/functional.py in nll_loss(input, target, weight, size_average, ignore_index, reduce, reduction)
   2386         )
   2387     if dim == 2:
-> 2388         ret = torch._C._nn.nll_loss(input, target, weight, _Reduction.get_enum(reduction), ignore_index)
   2389     elif dim == 4:
   2390         ret = torch._C._nn.nll_loss2d(input, target, weight, _Reduction.get_enum(reduction), ignore_index)

IndexError: Target 2 is out of bounds.
```

L'image devient plus claire. Au lieu d'avoir une erreur CUDA, nous avons maintenant une `IndexError` dans le calcul de la perte (donc rien à voir avec la passe arrière comme nous l'avons dit plus tôt). Plus précisément, nous pouvons voir que c'est la cible 2 qui crée l'erreur, donc c'est un bon moment pour vérifier le nombre de labels de notre modèle :

```python
trainer.model.config.num_labels
```

```python out
2
```

Avec deux étiquettes, seuls les 0 et les 1 sont autorisés comme cibles, mais d'après le message d'erreur, nous avons obtenu un 2. Obtenir un 2 est en fait normal : si nous nous souvenons des noms des étiquettes que nous avons extraits plus tôt, il y en avait trois, donc nous avons les indices 0, 1 et 2 dans notre jeu de données. Le problème est que nous n'avons pas indiqué cela à notre modèle, qui aurait dû être créé avec trois étiquettes. Alors, corrigeons cela !

```py
from datasets import load_dataset
import evaluate
from transformers import (
    AutoTokenizer,
    AutoModelForSequenceClassification,
    DataCollatorWithPadding,
    TrainingArguments,
    Trainer,
)

raw_datasets = load_dataset("glue", "mnli")

model_checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)


def preprocess_function(examples):
    return tokenizer(examples["premise"], examples["hypothesis"], truncation=True)


tokenized_datasets = raw_datasets.map(preprocess_function, batched=True)
model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=3)

args = TrainingArguments(
    f"distilbert-finetuned-mnli",
    evaluation_strategy="epoch",
    save_strategy="epoch",
    learning_rate=2e-5,
    num_train_epochs=3,
    weight_decay=0.01,
)

metric = evaluate.load("glue", "mnli")


def compute_metrics(eval_pred):
    predictions, labels = eval_pred
    return metric.compute(predictions=predictions, references=labels)


data_collator = DataCollatorWithPadding(tokenizer=tokenizer)

trainer = Trainer(
    model,
    args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation_matched"],
    compute_metrics=compute_metrics,
    data_collator=data_collator,
    tokenizer=tokenizer,
)
```

Nous n'incluons pas encore la ligne `trainer.train()` pour prendre le temps de vérifier que tout se passe bien. Si nous passons un batch à notre modèle, il fonctionne maintenant sans erreur !

```py
for batch in trainer.get_train_dataloader():
    break

outputs = trainer.model.cpu()(**batch)
```

L'étape suivante consiste alors à revenir au GPU et à vérifier que tout fonctionne encore :

```py
import torch

device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
batch = {k: v.to(device) for k, v in batch.items()}

outputs = trainer.model.to(device)(**batch)
```

Si vous obtenez toujours une erreur, assurez-vous de redémarrer votre *notebook* et d'exécuter uniquement la dernière version du script.

### Exécution d'une étape d'optimisation

Maintenant que nous savons que nous pouvons construire des batchs qui passent réellement par le modèle, nous sommes prêts pour l'étape suivante du pipeline d'entraînement : calculer les gradients et effectuer une étape d'optimisation.

La première partie est juste une question d'appel de la méthode `backward()` sur la perte :

```py
loss = outputs.loss
loss.backward()
```

Il est plutôt rare d'obtenir une erreur à ce stade, mais si vous en obtenez une, assurez-vous de retourner au CPU pour obtenir un message d'erreur utile.

Pour effectuer l'étape d'optimisation, il suffit de créer le `optimizer` et d'appeler sa méthode `step()` :

```py
trainer.create_optimizer()
trainer.optimizer.step()
```

Encore une fois, si vous utilisez l'optimiseur par défaut dans le `Trainer`, vous ne devriez pas avoir d'erreur à ce stade, mais si vous avez un optimiseur personnalisé, il pourrait y avoir quelques problèmes à déboguer ici. N'oubliez pas de revenir au CPU si vous obtenez une erreur CUDA bizarre à ce stade. En parlant d'erreurs CUDA, nous avons mentionné précédemment un cas particulier. Voyons cela maintenant.

### Gérer les erreurs <i>CUDA out of memory</i>

Chaque fois que vous obtenez un message d'erreur qui commence par `RuntimeError : CUDA out of memory`, cela indique que vous êtes à court de mémoire GPU. Cela n'est pas directement lié à votre code et peut arriver avec un script qui fonctionne parfaitement bien. Cette erreur signifie que vous avez essayé de mettre trop de choses dans la mémoire interne de votre GPU et que cela a entraîné une erreur. Comme pour d'autres erreurs CUDA, vous devrez redémarrer votre noyau pour être en mesure d'exécuter à nouveau votre entraînement.

Pour résoudre ce problème, il suffit d'utiliser moins d'espace GPU, ce qui est souvent plus facile à dire qu'à faire. Tout d'abord, assurez-vous que vous n'avez pas deux modèles sur le GPU en même temps (sauf si cela est nécessaire pour votre problème, bien sûr). Ensuite, vous devriez probablement réduire la taille de votre batch car elle affecte directement les tailles de toutes les sorties intermédiaires du modèle et leurs gradients. Si le problème persiste, envisagez d'utiliser une version plus petite de votre modèle.

<Tip>

Dans la prochaine partie du cours, nous examinerons des techniques plus avancées qui peuvent vous aider à réduire votre empreinte mémoire et vous permettre de <i>finetuner</i> les plus grands modèles.

</Tip>

### Évaluation du modèle

Maintenant que nous avons résolu tous les problèmes liés à notre code, tout est parfait et l'entraînement devrait se dérouler sans problème, n'est-ce pas ? Pas si vite ! Si vous exécutez la commande `trainer.train()`, tout aura l'air bien au début, mais après un moment vous obtiendrez ce qui suit :

```py
# Cela prendra beaucoup de temps et se soldera par une erreur, vous ne devriez donc pas utiliser cette cellule.
trainer.train()
```

```python out
TypeError: only size-1 arrays can be converted to Python scalars
```

Vous réaliserez que cette erreur apparaît pendant la phase d'évaluation, donc c'est la dernière chose que nous aurons besoin de déboguer.

Vous pouvez exécuter la boucle d'évaluation du `Trainer` indépendamment de l'entraînement comme ceci :

```py
trainer.evaluate()
```

```python out
TypeError: only size-1 arrays can be converted to Python scalars
```

<Tip>

💡 Vous devriez toujours vous assurer que vous pouvez exécuter `trainer.evaluate()` avant de lancer `trainer.train()`, pour éviter de gaspiller beaucoup de ressources de calcul avant de tomber sur une erreur.

</Tip>

Avant de tenter de déboguer un problème dans la boucle d'évaluation, vous devez d'abord vous assurer que vous avez examiné les données, que vous êtes en mesure de former un batch correctement et que vous pouvez exécuter votre modèle sur ces données. Nous avons effectué toutes ces étapes, et le code suivant peut donc être exécuté sans erreur :

```py
for batch in trainer.get_eval_dataloader():
    break

batch = {k: v.to(device) for k, v in batch.items()}

with torch.no_grad():
    outputs = trainer.model(**batch)
```

L'erreur survient plus tard, à la fin de la phase d'évaluation, et si nous regardons le *traceback*, nous voyons ceci :

```python trace
~/git/datasets/src/datasets/metric.py in add_batch(self, predictions, references)
    431         """
    432         batch = {"predictions": predictions, "references": references}
--> 433         batch = self.info.features.encode_batch(batch)
    434         if self.writer is None:
    435             self._init_writer()
```

Cela nous indique que l'erreur provient du module `datasets/metric.py` donc c'est un problème avec notre fonction `compute_metrics()`. Elle prend un *tuple* avec les logits et les labels sous forme de tableaux NumPy, alors essayons de lui fournir cela :

```py
predictions = outputs.logits.cpu().numpy()
labels = batch["labels"].cpu().numpy()

compute_metrics((predictions, labels))
```

```python out
TypeError: only size-1 arrays can be converted to Python scalars
```

Nous obtenons la même erreur, donc le problème vient bien de cette fonction. Si on regarde son code, on voit qu'elle transmet simplement les `predictions` et les `labels` à `metric.compute()`. Y a-t-il donc un problème avec cette méthode ? Pas vraiment. Jetons un coup d'oeil rapide aux formes :

```py
predictions.shape, labels.shape
```

```python out
((8, 3), (8,))
```

Nos prédictions sont toujours des logits et non les prédictions réelles, c'est pourquoi la métrique retourne cette erreur (quelque peu obscure). La correction est assez simple, il suffit d'ajouter un argmax dans la fonction `compute_metrics()` :

```py
import numpy as np


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


compute_metrics((predictions, labels))
```

```python out
{'accuracy': 0.625}
```

Maintenant notre erreur est corrigée ! C'était la dernière, donc notre script va maintenant entraîner un modèle correctement.

Pour référence, voici le script complètement corrigé :

```py
import numpy as np
from datasets import load_dataset
import evaluate
from transformers import (
    AutoTokenizer,
    AutoModelForSequenceClassification,
    DataCollatorWithPadding,
    TrainingArguments,
    Trainer,
)

raw_datasets = load_dataset("glue", "mnli")

model_checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)


def preprocess_function(examples):
    return tokenizer(examples["premise"], examples["hypothesis"], truncation=True)


tokenized_datasets = raw_datasets.map(preprocess_function, batched=True)
model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=3)

args = TrainingArguments(
    f"distilbert-finetuned-mnli",
    evaluation_strategy="epoch",
    save_strategy="epoch",
    learning_rate=2e-5,
    num_train_epochs=3,
    weight_decay=0.01,
)

metric = evaluate.load("glue", "mnli")


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


data_collator = DataCollatorWithPadding(tokenizer=tokenizer)

trainer = Trainer(
    model,
    args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation_matched"],
    compute_metrics=compute_metrics,
    data_collator=data_collator,
    tokenizer=tokenizer,
)
trainer.train()
```

Dans ce cas, il n'y a plus de problème, et notre script va *finetuner* un modèle qui devrait donner des résultats raisonnables. Mais que faire lorsque l'entraînement se déroule sans erreur et que le modèle entraîné n'est pas du tout performant ? C'est la partie la plus difficile de l'apprentissage automatique et nous allons vous montrer quelques techniques qui peuvent vous aider.

<Tip>

💡 Si vous utilisez une boucle d'entraînement manuelle, les mêmes étapes s'appliquent pour déboguer votre pipeline d'entraînement, mais il est plus facile de les séparer. Assurez-vous cependant de ne pas avoir oublié le `model.eval()` ou le `model.train()` aux bons endroits, ou le `zero_grad()` à chaque étape !

</Tip>

## Déboguer les erreurs silencieuses pendant l'entraînement

Que peut-on faire pour déboguer un entraînement qui se termine sans erreur mais qui ne donne pas de bons résultats ? Nous allons vous donner quelques pistes ici, mais sachez que ce type de débogage est la partie la plus difficile de l'apprentissage automatique et qu'il n'y a pas de réponse magique.

### Vérifiez vos données (encore !)

Votre modèle n'apprendra quelque chose que s'il est réellement possible d'apprendre quelque chose de vos données. Si un *bug* corrompt les données ou si les étiquettes sont attribuées de manière aléatoire, il est très probable que vous n'obtiendrez aucun entraînement de modèle sur votre jeu de données. Commencez donc toujours par revérifier vos entrées et étiquettes décodées, et posez-vous les questions suivantes :

- les données décodées sont-elles compréhensibles ?
- êtes-vous d'accord avec les étiquettes ?
- y a-t-il une étiquette qui est plus courante que les autres ?
- quelle devrait être la perte/métrique si le modèle prédisait une réponse aléatoire/toujours la même réponse ?

<Tip warning={true}>

⚠️ Si vous effectuez un entraînement distribué, imprimez des échantillons de votre ensemble de données dans chaque processus et vérifiez par trois fois que vous obtenez la même chose. Un bug courant consiste à avoir une source d'aléa dans la création des données qui fait que chaque processus a une version différente du jeu de données.

</Tip>

Après avoir examiné vos données, examinez quelques-unes des prédictions du modèle. Si votre modèle produit des *tokens*, essayez aussi de les décoder ! Si le modèle prédit toujours la même chose, cela peut être dû au fait que votre jeu de données est biaisé en faveur d'une catégorie (pour les problèmes de classification). Des techniques telles que le suréchantillonnage des classes rares peuvent aider. D'autre part, cela peut également être dû à des problèmes d'entraînement tels que de mauvais réglages des hyperparamètres.

Si la perte/la métrique que vous obtenez sur votre modèle initial avant entraînement est très différente de la perte/la métrique à laquelle vous vous attendez pour des prédictions aléatoires, vérifiez la façon dont votre perte ou votre métrique est calculée. Il y a probablement un bug. Si vous utilisez plusieurs pertes que vous ajoutez à la fin, assurez-vous qu'elles sont de la même échelle.

Lorsque vous êtes sûr que vos données sont parfaites, vous pouvez voir si le modèle est capable de s'entraîner sur elles grâce à un test simple.

### Surentraînement du modèle sur un seul batch

Le surentraînement est généralement une chose que nous essayons d'éviter lors de l'entraînement car cela signifie que le modèle n'apprend pas à reconnaître les caractéristiques générales que nous voulons qu'il reconnaisse et se contente de mémoriser les échantillons d'entraînement. Cependant, essayer d'entraîner votre modèle sur un batch encore et encore est un bon test pour vérifier si le problème tel que vous l'avez formulé peut être résolu par le modèle que vous essayez d'entraîner. Cela vous aidera également à voir si votre taux d'apprentissage initial est trop élevé.

Une fois que vous avez défini votre `modèle`, c'est très facile. Il suffit de prendre un batch de données d'entraînement, puis de le traiter comme votre jeu de données entier que vous *finetunez* sur un grand nombre d'époques :

```py
for batch in trainer.get_train_dataloader():
    break

batch = {k: v.to(device) for k, v in batch.items()}
trainer.create_optimizer()

for _ in range(20):
    outputs = trainer.model(**batch)
    loss = outputs.loss
    loss.backward()
    trainer.optimizer.step()
    trainer.optimizer.zero_grad()
```

<Tip>

💡 Si vos données d'entraînement ne sont pas équilibrées, veillez à créer un batch de données d'entraînement contenant toutes les étiquettes.

</Tip>

Le modèle résultant devrait avoir des résultats proches de la perfection sur le même `batch`. Calculons la métrique sur les prédictions résultantes :

```py
with torch.no_grad():
    outputs = trainer.model(**batch)
preds = outputs.logits
labels = batch["labels"]

compute_metrics((preds.cpu().numpy(), labels.cpu().numpy()))
```

```python out
{'accuracy': 1.0}
```

100% de précision, voilà un bel exemple de surentraînement (ce qui signifie que si vous essayez votre modèle sur n'importe quelle autre phrase, il vous donnera très probablement une mauvaise réponse) !

Si vous ne parvenez pas à ce que votre modèle obtienne des résultats parfaits comme celui-ci, cela signifie qu'il y a quelque chose qui ne va pas dans la façon dont vous avez formulé le problème ou dans vos données. Vous devez donc y remédier. Ce n'est que lorsque vous parviendrez à passer le test de surentraînement que vous pourrez être sûr que votre modèle peut réellement apprendre quelque chose.

<Tip warning={true}>

⚠️ Vous devrez recréer votre modèle et votre `Trainer` après ce test, car le modèle obtenu ne sera probablement pas capable de récupérer et d'apprendre quelque chose d'utile sur votre jeu de données complet.

</Tip>

### Ne réglez rien tant que vous n'avez pas une première ligne de base

Le réglage des hyperparamètres est toujours considéré comme la partie la plus difficile de l'apprentissage automatique mais c'est juste la dernière étape pour vous aider à gagner un peu sur la métrique. La plupart du temps, les hyperparamètres par défaut du `Trainer` fonctionneront très bien pour vous donner de bons résultats. Donc ne vous lancez pas dans une recherche d'hyperparamètres longue et coûteuse jusqu'à ce que vous ayez quelque chose qui batte la ligne de base que vous avez sur votre jeu de données.

Une fois que vous avez un modèle suffisamment bon, vous pouvez commencer à le *finetuner* un peu. N'essayez pas de lancer un millier d'exécutions avec différents hyperparamètres mais comparez quelques exécutions avec différentes valeurs pour un hyperparamètre afin de vous faire une idée de celui qui a le plus d'impact.

Si vous modifiez le modèle lui-même, restez simple et n'essayez rien que vous ne puissiez raisonnablement justifier. Veillez toujours à revenir au test de surentraînement pour vérifier que votre modification n'a pas eu de conséquences inattendues.

### Demander de l'aide

Nous espérons que vous avez trouvé dans cette section des conseils qui vous ont aidé à résoudre votre problème. Si ce n'est pas le cas, n'oubliez pas que vous pouvez toujours demander de l'aide à la communauté sur le [forum](https://discuss.huggingface.co/). 

Voici quelques ressources (en anglais) supplémentaires qui peuvent s'avérer utiles :

- [La reproductibilité comme vecteur des meilleures pratiques d'ingénierie](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) par Joel Grus
- [Liste de contrôle pour le débogage des réseaux de neurones](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) par Cecelia Shao
- [Comment tester unitairement le code d'apprentissage automatique](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) par Chase Roberts
- [Une recette pour entraîner les réseaux de neurones](http://karpathy.github.io/2019/04/25/recipe/) par Andrej Karpathy

Bien sûr, tous les problèmes rencontrés lors de l'entraînement ne sont pas forcément de votre faute ! Si vous rencontrez quelque chose dans la bibliothèque 🤗 *Transformers* ou 🤗 *Datasets* qui ne semble pas correct, vous avez peut-être trouver un *bug*. Vous devez absolument nous en parler pour qu'on puisse le corriger. Dans la section suivante, nous allons vous expliquer exactement comment faire.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter8/4.mdx" />

### Quiz de fin de chapitre
https://huggingface.co/learn/course/fr/chapter8/7.md

# Quiz de fin de chapitre

<CourseFloatingBanner
    chapter={8}
    classNames="absolute z-10 right-0 top-0"
/>

Testons ce que vous avez appris dans ce chapitre !

### 1. Dans quel ordre devez-vous lire un <i>traceback</i> Python ?

<Question
	choices={[
		{
			text: "De haut en bas",
			explain: "Essayez à nouveau. Bien que la plupart des autres langages de programmation impriment l'exception en haut de la page, Python est spécial à cet égard."
		},
		{
			text: "De bas en haut",
			explain: "L'avantage d'un <i>traceback</i> de Python montrant l'exception en bas est qu'il est plus facile de déboguer lorsque vous travaillez dans le terminal et que c'est la dernière ligne que vous voyez.",
			correct: true
		}
	]}
/>

### 2. Qu'est-ce qu'un exemple minimal reproductible ?

<Question
	choices={[
		{
			text: "Une implémentation simple d'un <i>transformer</i> à partir d'un article de recherche.",
			explain: "Bien qu'il soit très éducatif d'implémenter vos propres modèles de <i>transformers</i> à partir de zéro, ce n'est pas ce dont nous parlons ici."
		},
		{
			text: "Un bloc de code compact et autonome qui peut être exécuté sans aucune dépendance externe sur des fichiers ou des données privées.",
			explain: "Des exemples minimaux reproductibles aident les mainteneurs de la bibliothèque à reproduire le problème que vous rencontrez, afin qu'ils puissent trouver des solutions plus rapidement.",
			correct: true
		},
		{
			text: "Une capture d'écran de la traceback Python",
			explain: "Essayez à nouveau. Bien qu'il soit tentant d'inclure une capture d'écran de l'erreur à laquelle vous êtes confronté lorsque vous soumettez un problème, cela rend très difficile pour les autres de reproduire l'erreur."
		},
		{
			text: "Un <i>notebook</i> qui contient toute votre analyse, y compris les parties sans rapport avec l'erreur.",
			explain: "Pas tout à fait. Bien qu'il puisse être utile de partager un <i>notebook</i> Google Colab qui montre l'erreur, assurez-vous qu'il est court et ne contient que le code pertinent."
		}
	]}
/>

### 3. Supposons que vous essayez d'exécuter le code suivant, qui génère une erreur :

```py
from transformers import GPT3ForSequenceClassification

# ImportError: cannot import name 'GPT3ForSequenceClassification' from 'transformers' (/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/__init__.py)
# ---------------------------------------------------------------------------
# ImportError                               Traceback (most recent call last)
# /var/folders/28/k4cy5q7s2hs92xq7_h89_vgm0000gn/T/ipykernel_30848/333858878.py in <module>
# ----> 1 from transformers import GPT3ForSequenceClassification

# ImportError: cannot import name 'GPT3ForSequenceClassification' from 'transformers' (/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/__init__.py)
```

Lequel des éléments suivants pourrait être un bon choix pour le titre d'un sujet de forum pour demander de l'aide ?

<Question
	choices={[
		{
			text: "<code>ImportError: cannot import name 'GPT3ForSequenceClassification' from 'transformers' (/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/__init__.py)</code>",
			explain: "Inclure la dernière ligne de la <i>traceback</i> peut être descriptif, mais il est préférable de le réserver au corps principal du sujet. Essayez à nouveau !"
		},
		{
			text: "Problème avec <code>from transformers import GPT3ForSequenceClassification</code>",
			explain: "Essayez à nouveau. Bien que cette information soit utile, il est probablement préférable de la réserver au corps du texte.",
		},
		{
			text: "Pourquoi je ne peux pas importer <code>GPT3ForSequenceClassification</code>?",
			explain: "Ce titre est concis et donne au lecteur un indice sur ce qui pourrait être erroné (par exemple, que GPT-3 n'est pas pris en charge dans 🤗 <i>Transformers</i>).",
			correct: true
		},
		{
			text: "Le GPT-3 est-il pris en charge dans 🤗 <i>Transformers</i> ?",
			explain: "Utiliser des questions comme titres de sujets est un excellent moyen de communiquer le problème à la communauté.",
			correct: true
		}
	]}
/>

### 4. Supposons que vous ayez essayé d'exécuter `trainer.train()` et que vous soyez confronté à une erreur énigmatique qui ne vous dit pas exactement d'où vient l'erreur. Quel est le premier endroit où vous devez chercher les erreurs dans votre pipeline d'entraînement ?

<Question
	choices={[
		{
			text: "L'étape d'optimisation où nous calculons les gradients et effectuons la rétropropagation.",
			explain: "Bien qu'il puisse y avoir des <i>bugs</i> dans votre optimiseur, cela se produit généralement à plusieurs étapes du pipeline d'entraînement, il y a donc d'autres choses à vérifier d'abord. Essayez à nouveau !"
		},
		{
			text: "L'étape d'évaluation où nous calculons les métriques",
			explain: "L'évaluation est généralement ce que vous faites après l'entraînement pour une époque complète, donc vous devriez d'abord vérifier quelque part plus tôt dans le pipeline d'entraînement.",
		},
		{
			text: "Les jeux de données",
			explain: "C'est exact ! L'examen de vos données est presque toujours la première chose à faire, pour vous assurer que le texte est codé de manière appropriée, qu'il présente les caractéristiques attendues, etc.",
			correct: true
		},
		{
			text: "Les chargeurs de données",
			explain: "Essayez à nouveau. C'est très proche de la première chose que vous devriez vérifier. Vous souvenez-vous de l'objet que nous passons aux <i>dataloaders</i> ?"
		}
	]}
/>

### 5. Quelle est la meilleure façon de déboguer une erreur CUDA ?

<Question
	choices={[
		{
			text: "Postez le message d'erreur sur les forums ou sur GitHub.",
			explain: "Cela n'aidera personne car les messages d'erreur CUDA sont généralement très peu informatifs."
		},
		{
			text: "Exécutez le même code sur le CPU.",
			explain: "Exactement, cela devrait vous donner un meilleur message d'erreur !",
			correct: true
		},
		{
			text: "Lisez la <i>traceback</i> pour découvrir ce qui a causé l'erreur.",
			explain: "C'est ce que vous feriez pour toute autre erreur, mais les erreurs CUDA ne sont généralement pas signalées là où elles se sont produites, car la plupart des opérations CUDA sont asynchrones."
		},
		{
			text: "Réduisez la taille du <i>batch</i>.",
			explain: "La réduction de la taille du batch est généralement une bonne stratégie pour gérer les erreurs CUDA hors mémoire, mais pas pour ce problème particulier. Essayez à nouveau !"
		},
		{
			text: "Redémarrez le noyau Jupyter.",
			explain: "Essayez à nouveau. Le redémarrage du noyau ne fera pas disparaître l'erreur comme par magie !",
		}
	]}
/>

### 6. Quelle est la meilleure façon de faire corriger un problème sur GitHub ?

<Question
	choices={[
		{
			text: "Postez un exemple complet et reproductible du <i>bug</i>.",
			explain: "C'est la meilleure façon d'aider les mainteneurs à trouver votre bogue. Que devez-vous faire d'autre ?",
			correct: true
		},
		{
			text: "Demandez chaque jour une mise à jour.",
			explain: "Il est peu probable que cela vous apporte de l'aide. Les gens vous ignoreront probablement davantage.",
		},
		{
			text: "Inspectez le code source autour du bogue et essayez de trouver la raison pour laquelle il se produit. Postez les résultats dans le problème.",
			explain: "Cela aidera certainement les mainteneurs ! Et si vous trouvez la source du bogue et un correctif, vous pouvez même ouvrir une demande de modification. Que devez-vous faire d'autre ?",
			correct: true
		}
	]}
/>

### 7. Pourquoi le surapprentissage à un batch est-il généralement une bonne technique de débogage ?

<Question
	choices={[
		{
			text: "Ce n'est pas le cas. Le surapprentissage est toujours mauvais et doit être évité.",
			explain: "Lors de l'entraînement sur le jeu de données, le surapprentissage peut en effet être un signe que votre modèle ne généralisera pas bien aux nouveaux exemples. Pour le débogage, cependant, nous n'avons pas l'habitude de nous entraîner sur l'ensemble des données. Essayez à nouveau !"
		},
		{
			text: "Elle nous permet de vérifier que le modèle est capable de réduire la perte à zéro.",
			explain: "Avec un petit batch d'à peine deux exemples, nous pouvons rapidement vérifier si le modèle est capable d'apprendre.",
			correct: true
		},
		{
			text: "Il nous permet de vérifier que les formes tensorielles de nos entrées et de nos étiquettes sont correctes.",
			explain: "Essayez à nouveau. Si vos formes tensorielles sont mal alignées, vous ne pourrez certainement pas vous entraîner, même sur un seul batch.",
		}
	]}
/>

### 8. Pourquoi est-ce une bonne idée d'inclure des détails sur votre environnement de calcul avec `transformers-cli env` lorsque vous créez une nouvelle question dans le dépôt 🤗 *Transformers* ?

<Question
	choices={[
		{
			text: "Cela permet aux mainteneurs de comprendre quelle version de la bibliothèque vous utilisez.",
			explain: "Comme chaque version majeure de la bibliothèque peut comporter des modifications de l'API, le fait de connaître la version spécifique que vous utilisez peut vous aider à circonscrire le problème. Quels sont les autres avantages ?",
			correct: true
		},
		{
			text: "Il permet aux mainteneurs de savoir si vous exécutez le code sous Windows, macOS ou Linux.",
			explain: "Les erreurs peuvent parfois être causées par le système d'exploitation spécifique que vous utilisez, et le fait de le savoir aide les mainteneurs à les reproduire localement. Mais ce n'est pas la seule raison.",
			correct: true
		},
		{
			text: "Il permet aux mainteneurs de savoir si le code est exécuté sur un GPU ou un CPU.",
			explain: "Comme nous l'avons vu dans ce chapitre, les erreurs sur les GPU et les CPU peuvent avoir une saveur très différente, et savoir quel matériel vous utilisez peut aider à focaliser l'attention des mainteneurs. Mais ce n'est pas le seul avantage...",
			correct: true
		}
	]}
/>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter8/7.mdx" />

### Débogage du pipeline d'entraînement
https://huggingface.co/learn/course/fr/chapter8/4_tf.md

# Débogage du pipeline d'entraînement


<CourseFloatingBanner chapter={8}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter8/section4_tf.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter8/section4_tf.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter8/section4_tf.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter8/section4_tf.ipynb"},
]} />


Vous avez écrit un magnifique script pour entraîner ou *finetuner* un modèle sur une tâche donnée en suivant consciencieusement les conseils du [chapitre 7](/course/fr/chapter7). Mais lorsque vous lancez la commande `model.fit()`, quelque chose d'horrible se produit : vous obtenez une erreur 😱 ! Ou pire, tout semble aller bien et l'entraînement se déroule sans erreur mais le modèle résultant est mauvais. Dans cette section, nous allons vous montrer ce que vous pouvez faire pour déboguer ce genre de problèmes.

## Déboguer le pipeline d'entraînement

<Youtube id="N9kO52itd0Q"/>

Le problème lorsque vous rencontrez une erreur dans `trainer.train()` est qu'elle peut provenir de plusieurs sources, car la fonction `Trainer` assemble généralement des batchs de choses. Elle convertit les jeux de données en chargeurs de données donc le problème pourrait être quelque chose d'erroné dans votre jeu de données, ou un problème en essayant de regrouper les éléments des jeux de données ensemble. Ensuite, elle prend un batch de données et le transmet au modèle, le problème peut donc se situer dans le code du modèle. Après cela, elle calcule les gradients et effectue l'étape d'optimisation, le problème peut donc également se situer dans votre optimiseur. Et même si tout se passe bien pendant l'entraînement, quelque chose peut encore mal tourner pendant l'évaluation si votre métrique pose problème.

La meilleure façon de déboguer une erreur qui survient dans `trainer.train()` est de passer manuellement en revue tout le pipeline pour voir où les choses se sont mal passées. L'erreur est alors souvent très facile à résoudre.

Pour le démontrer, nous utiliserons le script suivant qui tente de *finetuner* un modèle DistilBERT sur le [jeu de données MNLI](https://huggingface.co/datasets/glue) :

```py
from datasets import load_dataset
import evaluate
from transformers import (
    AutoTokenizer,
    TFAutoModelForSequenceClassification,
)

raw_datasets = load_dataset("glue", "mnli")

model_checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)


def preprocess_function(examples):
    return tokenizer(examples["premise"], examples["hypothesis"], truncation=True)


tokenized_datasets = raw_datasets.map(preprocess_function, batched=True)

train_dataset = tokenized_datasets["train"].to_tf_dataset(
    columns=["input_ids", "labels"], batch_size=16, shuffle=True
)

validation_dataset = tokenized_datasets["validation_matched"].to_tf_dataset(
    columns=["input_ids", "labels"], batch_size=16, shuffle=True
)

model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint)

model.compile(loss="sparse_categorical_crossentropy", optimizer="adam")

model.fit(train_dataset)
```

Si vous essayez de l'exécuter, il se peut que vous obteniez des `VisibleDeprecationWarning`s lors de la conversion du jeu de données. Il s'agit d'un problème UX connu par l'équipe d'Hugging Face, donc veuillez l'ignorer. Si vous lisez le cours après novembre 2021 et que cela se produit encore, envoyez des tweets de rage à @carrigmat jusqu'à ce qu'il le corrige.

Le problème cependant est que nous avons une erreur flagrante. Et c'est vraiment, terriblement long :

```python out
ValueError: No gradients provided for any variable: ['tf_distil_bert_for_sequence_classification/distilbert/embeddings/word_embeddings/weight:0', '...']
```

Qu'est-ce que cela signifie ? Nous avons essayé d'entraîner sur nos données mais nous n'avons pas obtenu de gradient. C'est assez déconcertant. Comment commencer à déboguer quelque chose comme ça ? Lorsque l'erreur que vous obtenez ne suggère pas immédiatement l'origine du problème, la meilleure solution consiste souvent à procéder par étapes, en s'assurant à chaque fois que tout semble correct. Et bien sûr, il faut toujours commencer par...

### Vérifier vos données

Cela va sans dire, mais si vos données sont corrompues, Keras ne sera pas en mesure de les réparer pour vous. Avant toute chose, vous devez donc jeter un coup d'œil à ce que contient votre ensemble d'entraînement.

Bien qu'il soit tentant de regarder dans `raw_datasets` et `tokenized_datasets`, nous vous recommandons fortement d'aller voir les données au moment où elles vont entrer dans le modèle. Cela signifie lire une sortie du `tf.data.Dataset` que vous avez créé avec la fonction `to_tf_dataset()` ! Alors comment faire ? Les objets `tf.data.Dataset` nous donnent des batchs entiers à la fois et ne supportent pas l'indexation, donc nous ne pouvons pas simplement demander `train_dataset[0]`. Nous pouvons, cependant, lui demander poliment un batch :

```py
for batch in train_dataset:
    break
```

`break` termine la boucle après une itération, donc cela prend le premier batch qui sort de `train_dataset` et l'enregistre comme `batch`. Maintenant, jetons un coup d'oeil à ce qu'il y a à l'intérieur :

```python out
{'attention_mask': <tf.Tensor: shape=(16, 76), dtype=int64, numpy=
 array([[1, 1, 1, ..., 0, 0, 0],
        [1, 1, 1, ..., 0, 0, 0],
        [1, 1, 1, ..., 0, 0, 0],
        ...,
        [1, 1, 1, ..., 1, 1, 1],
        [1, 1, 1, ..., 0, 0, 0],
        [1, 1, 1, ..., 0, 0, 0]])>,
 'label': <tf.Tensor: shape=(16,), dtype=int64, numpy=array([0, 2, 1, 2, 1, 1, 2, 0, 0, 0, 1, 0, 1, 2, 2, 1])>,
 'input_ids': <tf.Tensor: shape=(16, 76), dtype=int64, numpy=
 array([[ 101, 2174, 1010, ...,    0,    0,    0],
        [ 101, 3174, 2420, ...,    0,    0,    0],
        [ 101, 2044, 2048, ...,    0,    0,    0],
        ...,
        [ 101, 3398, 3398, ..., 2051, 2894,  102],
        [ 101, 1996, 4124, ...,    0,    0,    0],
        [ 101, 1999, 2070, ...,    0,    0,    0]])>}
```

Cela semble correct. Nous passons les `labels`, `attention_mask`, et `input_ids` au modèle, ce qui devrait être tout ce dont il a besoin pour calculer les sorties et la perte. Alors pourquoi n'avons-nous pas de gradient ? Regardez de plus près : nous passons un seul dictionnaire en entrée mais un batch d'entraînement est généralement un tenseur ou un dictionnaire d'entrée, plus un tenseur d'étiquettes. Nos étiquettes sont juste une clé dans notre dictionnaire d'entrée.

Est-ce un problème ? Pas toujours, en fait ! Mais c'est l'un des problèmes les plus courants que vous rencontrerez lorsque vous entraînerez des *transformers* avec TensorFlow. Nos modèles peuvent tous calculer la perte en interne, mais pour ce faire, les étiquettes doivent être transmises dans le dictionnaire d'entrée. C'est la perte qui est utilisée lorsque nous ne spécifions pas de valeur de perte à `compile()`. Keras, d'autre part, s'attend généralement à ce que les étiquettes soient passées séparément du dictionnaire d'entrée, et les calculs de perte échoueront généralement si vous ne le faites pas.

Le problème est maintenant devenu plus clair : nous avons passé un argument `loss`, ce qui signifie que nous demandons à Keras de calculer les pertes pour nous, mais nous avons passé nos étiquettes comme entrées au modèle, et non comme étiquettes à l'endroit où Keras les attend ! Nous devons choisir l'un ou l'autre : soit nous utilisons la perte interne du modèle et gardons les étiquettes où elles sont, soit nous continuons à utiliser les pertes de Keras, mais nous déplaçons les étiquettes à l'endroit où Keras les attend. Pour simplifier, prenons la première approche. Changez l'appel à `compile()` pour lire :

```py
model.compile(optimizer="adam")
```

Maintenant, nous allons utiliser la perte interne du modèle et ce problème devrait être résolu !

<Tip>

✏️ *A votre tour !* Comme défi optionnel après avoir résolu les autres problèmes, vous pouvez essayer de revenir à cette étape et faire fonctionner le modèle avec la perte originale calculée par Keras au lieu de la perte interne. Vous devrez ajouter `"labels"` à l'argument `label_cols` de `to_tf_dataset()` pour vous assurer que les labels sont correctement sortis, ce qui vous donnera des gradients. Mais il y a un autre problème avec la perte que nous avons spécifiée. L'entraînement fonctionnera toujours avec ce problème mais l'apprentissage sera très lent et se stabilisera à une perte d'entraînement élevée. Pouvez-vous trouver ce que c'est ?

Un indice codé en ROT13, si vous êtes coincé : Vs lbh ybbx ng gur bhgchgf bs FrdhraprPynffvsvpngvba zbqryf va Genafsbezref, gurve svefg bhgchg vf `ybtvgf`. Jung ner ybtvgf ?

Et un deuxième indice : Jura lbh fcrpvsl bcgvzvmref, npgvingvbaf be ybffrf jvgu fgevatf, Xrenf frgf ny gur nethzrag inyhrf gb gurve qrsnhygf. Jung nethzragf qbrf FcnefrPngrtbevpnyPebffragebcl unir, naq jung ner gurve qrsnhygf ?

</Tip>

Maintenant, essayons d'entraîner. Nous devrions obtenir des gradients maintenant, donc avec un peu de chance nous pouvons juste appeler `model.fit()` et tout fonctionnera bien !

```python out
  246/24543 [..............................] - ETA: 15:52 - loss: nan
```

Oh non. 

`nan` n'est pas une valeur de perte très encourageante. Pourtant, nous avons vérifié nos données et elles semblent plutôt bonnes. Si ce n'est pas le problème, quelle est la prochaine étape ? La prochaine étape évidente est de...

### Vérifier votre modèle

`model.fit()` est une fonction très pratique dans Keras, mais elle fait beaucoup de choses pour vous. Cela peut rendre plus difficile de trouver exactement où un problème est survenu. Si vous déboguez votre modèle, une stratégie qui peut vraiment vous aider est de passer un seul batch au modèle et d'examiner les sorties de ce batch en détail. Une autre astuce vraiment utile est de `compiler()` le modèle avec `run_eagerly=True`. Cela le rendra beaucoup plus lent mais les messages d'erreur seront beaucoup plus compréhensibles car ils indiqueront exactement où le problème est survenu dans le code de votre modèle.

Pour l'instant, cependant, nous n'avons pas besoin de `run_eagerly`. Exécutons le `batch` que nous avons obtenu précédemment à travers le modèle et voyons à quoi ressemblent les résultats :

```py
model(batch)
```

```python out
TFSequenceClassifierOutput(loss=<tf.Tensor: shape=(16,), dtype=float32, numpy=
array([nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,
       nan, nan, nan], dtype=float32)>, logits=<tf.Tensor: shape=(16, 2), dtype=float32, numpy=
array([[nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan]], dtype=float32)>, hidden_states=None, attentions=None)
```

Eh bien, c'est délicat. Tout est "nan" ! Mais c'est étrange, n'est-ce pas ? Comment tous nos logits pourraient-ils devenir `nan` ? "NAN" signifie "*not a number*". Les valeurs `nan` apparaissent souvent quand on effectue une opération interdite comme la division par zéro. Mais une chose très importante à savoir sur `nan` en apprentissage automatique est que cette valeur a tendance à *se propager*. Si vous multipliez un nombre par `nan`, le résultat sera également `nan`. Et si vous obtenez une valeur `nan` n'importe où dans votre sortie, votre perte ou votre gradient, alors elle se propagera rapidement à travers tout votre modèle. 
Ceci parce que lorsque cette valeur `nan` est propagée à travers votre réseau, vous obtiendrez des gradients `nan`, et lorsque les mises à jour des poids sont calculées avec ces gradients, vous obtiendrez des poids `nan`, et ces poids calculeront encore plus de sorties `nan` ! Très vite, le réseau entier ne sera plus qu'un gros bloc de `nan`. Une fois que cela arrive, il est assez difficile de voir où le problème a commencé. Comment peut-on isoler l'endroit où les `nan` se sont introduits en premier ?

La réponse est d'essayer de *reinitialiser* notre modèle. Une fois que nous avons commencé l'entraînement, nous avons eu un `nan` quelque part et il s'est rapidement propagé à travers tout le modèle. Donc, chargeons le modèle à partir d'un checkpoint et ne faisons aucune mise à jour de poids, et voyons où nous obtenons une valeur `nan` :

```py
model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint)
model(batch)
```

Quand on fait ça, on obtient :

```py out
TFSequenceClassifierOutput(loss=<tf.Tensor: shape=(16,), dtype=float32, numpy=
array([0.6844486 ,        nan,        nan, 0.67127866, 0.7068601 ,
              nan, 0.69309855,        nan, 0.65531296,        nan,
              nan,        nan, 0.675402  ,        nan,        nan,
       0.69831556], dtype=float32)>, logits=<tf.Tensor: shape=(16, 2), dtype=float32, numpy=
array([[-0.04761693, -0.06509043],
       [-0.0481936 , -0.04556257],
       [-0.0040929 , -0.05848458],
       [-0.02417453, -0.0684005 ],
       [-0.02517801, -0.05241832],
       [-0.04514256, -0.0757378 ],
       [-0.02656011, -0.02646275],
       [ 0.00766164, -0.04350497],
       [ 0.02060014, -0.05655622],
       [-0.02615328, -0.0447021 ],
       [-0.05119278, -0.06928903],
       [-0.02859691, -0.04879177],
       [-0.02210129, -0.05791225],
       [-0.02363213, -0.05962167],
       [-0.05352269, -0.0481673 ],
       [-0.08141848, -0.07110836]], dtype=float32)>, hidden_states=None, attentions=None)
```

*Maintenant* on arrive à quelque chose ! Il n'y a pas de valeurs `nan` dans nos logits, ce qui est rassurant. Mais nous voyons quelques valeurs `nan` dans notre perte ! Y a-t-il quelque chose dans ces échantillons en particulier qui cause ce problème ? Voyons de quels échantillons il s'agit (notez que si vous exécutez ce code vous-même, vous pouvez obtenir des indices différents parce que le jeu de données a été mélangé) :

```python
import numpy as np

loss = model(batch).loss.numpy()
indices = np.flatnonzero(np.isnan(loss))
indices
```

```python out
array([ 1,  2,  5,  7,  9, 10, 11, 13, 14])
```

Examinons les échantillons d'où proviennent ces indices :

```python
input_ids = batch["input_ids"].numpy()
input_ids[indices]
```

```python out
array([[  101,  2007,  2032,  2001,  1037, 16480,  3917,  2594,  4135,
        23212,  3070,  2214, 10170,  1010,  2012,  4356,  1997,  3183,
         6838, 12953,  2039,  2000,  1996,  6147,  1997,  2010,  2606,
         1012,   102,  6838,  2001,  3294,  6625,  3773,  1996,  2214,
         2158,  1012,   102,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0],
       [  101,  1998,  6814,  2016,  2234,  2461,  2153,  1998, 13322,
         2009,  1012,   102,  2045,  1005,  1055,  2053,  3382,  2008,
         2016,  1005,  2222,  3046,  8103,  2075,  2009,  2153,  1012,
          102,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0],
       [  101,  1998,  2007,  1996,  3712,  4634,  1010,  2057,  8108,
         2025,  3404,  2028,  1012,  1996,  2616, 18449,  2125,  1999,
         1037,  9666,  1997,  4100,  8663, 11020,  6313,  2791,  1998,
         2431,  1011,  4301,  1012,   102,  2028,  1005,  1055,  5177,
         2110,  1998,  3977,  2000,  2832,  2106,  2025,  2689,  2104,
         2122,  6214,  1012,   102,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0],
       [  101,  1045,  2001,  1999,  1037, 13090,  5948,  2007,  2048,
         2308,  2006,  2026,  5001,  2043,  2026,  2171,  2001,  2170,
         1012,   102,  1045,  2001,  3564,  1999,  2277,  1012,   102,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0],
       [  101,  2195,  4279,  2191,  2039,  1996,  2181,  2124,  2004,
         1996,  2225,  7363,  1012,   102,  2045,  2003,  2069,  2028,
         2451,  1999,  1996,  2225,  7363,  1012,   102,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0],
       [  101,  2061,  2008,  1045,  2123,  1005,  1056,  2113,  2065,
         2009,  2428, 10654,  7347,  2030,  2009,  7126,  2256,  2495,
         2291,   102,  2009,  2003,  5094,  2256,  2495,  2291,  2035,
         2105,  1012,   102,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0],
       [  101,  2051,  1010,  2029,  3216,  2019,  2503,  3444,  1010,
         6732,  1996,  2265,  2038, 19840,  2098,  2125,  9906,  1998,
         2003,  2770,  2041,  1997,  4784,  1012,   102,  2051,  6732,
         1996,  2265,  2003,  9525,  1998,  4569,  1012,   102,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0],
       [  101,  1996, 10556,  2140, 11515,  2058,  1010,  2010,  2162,
         2252,  5689,  2013,  2010,  7223,  1012,   102,  2043,  1996,
        10556,  2140, 11515,  2058,  1010,  2010,  2252,  3062,  2000,
         1996,  2598,  1012,   102,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0],
       [  101, 13543,  1999,  2049,  6143,  2933,  2443,   102,  2025,
        13543,  1999,  6143,  2933,  2003,  2443,   102,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0]])
```

Il y a beaucoup de batchs ici mais rien d'inhabituel. Regardons les étiquettes :

```python out
labels = batch['labels'].numpy()
labels[indices]
```

```python out
array([2, 2, 2, 2, 2, 2, 2, 2, 2])
```

Ah ! Les échantillons `nan` ont tous le même label. C'est un gros indice. Le fait que nous n'obtenions une perte de `nan` que lorsque notre étiquette vaut 2 suggère que c'est un très bon moment pour vérifier le nombre d'étiquettes dans notre modèle :

```python
model.config.num_labels
```

```python out
2
```

Nous voyons maintenant le problème : le modèle pense qu'il n'y a que deux classes, mais les étiquettes vont jusqu'à 2, ce qui signifie qu'il y a en fait trois classes (car 0 est aussi une classe). C'est ainsi que nous avons obtenu un `nan`. En essayant de calculer la perte pour une classe inexistante ! Essayons de changer cela et de réajuster le modèle :

```
model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=3)
model.compile(optimizer='adam')
model.fit(train_dataset)
```

```python out
  869/24543 [>.............................] - ETA: 15:29 - loss: 1.1032
```

On entraîne ! Plus de `nan` et nos pertes diminuent... en quelque sorte. Si vous regardez pendant un certain temps, vous pouvez commencer à vous impatienter car la valeur des pertes reste obstinément élevée. Arrêtons l'entraînement ici et essayons de réfléchir à ce qui pourrait causer ce problème. À ce stade, nous sommes pratiquement sûrs que les données et le modèle sont corrects, mais notre modèle n'apprend pas bien. Que reste-t-il d'autre ? Il est temps de...

### Vérifier les hyperparamètres

Si vous regardez le code ci-dessus, vous ne verrez peut-être aucun hyperparamètre, sauf peut-être le `batch_size` qui ne semble pas être un coupable probable. Cependant, ne soyez pas dupe, il y a toujours des hyperparamètres. Si vous ne pouvez pas les voir, cela signifie simplement que vous ne connaissez pas leur réglage. En particulier, souvenez-vous d'une chose essentielle à propos de Keras : si vous définissez une fonction de perte, d'optimisation ou d'activation avec une chaîne, _tous ses arguments seront définis sur leurs valeurs par défaut_. Cela signifie que, même si l'utilisation de chaînes de caractères est très pratique, vous devez être très prudent car cela peut facilement vous cacher des éléments critiques. (Toute personne essayant le défi optionnel ci-dessus devrait prendre bonne note de ce fait).

Dans ce cas, où avons-nous défini un argument avec une chaîne de caractères ? Au départ, nous définissions la perte avec une chaîne de caractères, mais nous ne le faisons plus. Cependant, nous le faisons pour l'optimiseur. Cela pourrait-il nous cacher quelque chose ? Jetons un coup d'œil à [ses arguments](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adam).

Y a-t-il quelque chose qui ressort ? C'est exact : le taux d'apprentissage ! Lorsque nous indiquons simplement `'adam'` nous allons obtenir le taux d'apprentissage par défaut qui est de 0.001 (ou 1e-3). C'est beaucoup trop élevé pour un *transformer* ! En général, nous recommandons d'essayer des taux d'apprentissage entre 1e-5 et 1e-4 pour vos modèles soit entre 10X et 100X plus petit que la valeur que nous utilisons ici. Cela semble être un problème majeur, alors essayons de le réduire. Pour ce faire, nous devons importer l'objet `optimizer`. Pendant que nous y sommes, réinitialisons le modèle à partir du *checkpoint* au cas où l'entraînement avec un taux d'apprentissage élevé aurait endommagé ses poids :

```python
from tensorflow.keras.optimizers import Adam

model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint)
model.compile(optimizer=Adam(5e-5))
```

<Tip>

💡 Vous pouvez également importer la fonction `create_optimizer()` de 🤗 <i>Transformers</i> qui vous donnera un optimiseur AdamW avec une décroissance du taux des poids correcte ainsi qu'un réchauffement et une décroissance du taux d'apprentissage. Cet optimiseur produira souvent des résultats légèrement meilleurs que ceux que vous obtenez avec l'optimiseur Adam par défaut.

</Tip>

Maintenant, nous pouvons essayer de *finetuner* le modèle avec le nouveau taux d'apprentissage :

```python
model.fit(train_dataset)
```

```python out
319/24543 [..............................] - ETA: 16:07 - loss: 0.9718
```

Maintenant notre perte va vraiment aller quelque part ! L'entraînement semble enfin fonctionner. Il y a une leçon à tirer ici : lorsque votre modèle fonctionne mais que la perte ne diminue pas, et que vous êtes sûr que vos données sont correctes, c'est une bonne idée de vérifier les hyperparamètres comme le taux d'apprentissage et le taux de décroissance des poids. Un réglage trop élevé de l'un ou l'autre de ces paramètres risque fort de faire « caler »  l'entraînement à une valeur de perte élevée.

## Autres problèmes potentiels 

Nous avons couvert les problèmes dans le script ci-dessus, mais il existe plusieurs autres erreurs courantes auxquelles vous pouvez être confronté. Jetons un coup d'oeil à une liste (très incomplète).

### Gérer les erreurs de manque de mémoire

Le signe révélateur d'un manque de mémoire est une erreur du type "OOM when allocating tensor" (OOM étant l'abréviation de *out of memory*). Il s'agit d'un risque très courant lorsque l'on utilise de grands modèles de langage. Si vous rencontrez ce problème, une bonne stratégie consiste à diviser par deux la taille de votre batch et à réessayer. Gardez à l'esprit, cependant, que certains modèles sont *très* grands. Par exemple, le modèle GPT-2 complet possède 1,5 Go de paramètres, ce qui signifie que vous aurez besoin de 6 Go de mémoire rien que pour stocker le modèle, et 6 autres Go pour ses gradients ! Entraîner le modèle GPT-2 complet nécessite généralement plus de 20 Go de VRAM, quelle que soit la taille du batch utilisé, ce dont seuls quelques GPUs sont dotés. Des modèles plus légers comme `distilbert-base-cased` sont beaucoup plus faciles à exécuter et s'entraînent aussi beaucoup plus rapidement.

<Tip>

Dans la prochaine partie du cours, nous examinerons des techniques plus avancées qui peuvent vous aider à réduire votre empreinte mémoire et vous permettre de <i>finetuner</i> les plus grands modèles.

</Tip>

### TensorFlow affamé 🦛

Une bizarrerie particulière de TensorFlow dont vous devez être conscient est qu'il s'alloue *toute* la mémoire de votre GPU dès que vous chargez un modèle ou que vous effectuez un entraînement. Puis il divise cette mémoire selon les besoins. Ce comportement est différent de celui d'autres *frameworks*, comme PyTorch, qui alloue la mémoire selon les besoins avec CUDA plutôt que de le faire en interne. L'un des avantages de l'approche de TensorFlow est qu'elle peut souvent donner des erreurs utiles lorsque vous manquez de mémoire et qu'elle peut récupérer de cet état sans planter tout le noyau CUDA. Mais il y a aussi un inconvénient important : si vous exécutez deux processus TensorFlow en même temps alors **vous allez passer un mauvais moment**.

Si vous travaillez sur Colab, vous n'avez pas à vous soucier de cela. Si vous travaillez localement, vous devez absolument faire attention. En particulier, sachez que la fermeture d'un onglet de *notebook* n'entraîne pas nécessairement la fermeture de ce *notebook* ! Vous devrez peut-être sélectionner les *notebooks* en cours d'exécution (ceux qui ont une icône verte) et les fermer manuellement dans la liste des répertoires. Tout *notebook* en cours d'exécution qui utilisait TensorFlow peut encore utiliser une grande partie de la mémoire de votre GPU, ce qui signifie que tout nouveau *notebook* que vous démarrez peut rencontrer des problèmes très étranges.

Si vous commencez à obtenir des erreurs concernant CUDA, BLAS ou cuBLAS dans du code qui fonctionnait auparavant, c'est très souvent le coupable. Vous pouvez utiliser une commande comme `nvidia-smi` pour vérifier si la plupart de votre mémoire est libre ou toujours utilisée. Si elle est toujours utilisée, c'est que quelque chose d'autre s'y accroche !


### Vérifiez vos données (encore !)

Votre modèle n'apprendra quelque chose que s'il est réellement possible d'apprendre quelque chose de vos données. S'il y a un *bug* qui corrompt les données ou si les étiquettes sont attribuées de manière aléatoire, il est très probable que vous n'obtiendrez aucun entraînement de modèle sur votre jeu de données. Un outil utile ici est `tokenizer.decode()`. Il transformera les `input_ids` en chaînes de caractères, afin que vous puissiez visualiser les données et voir si vos données d'entraînement renseignent ce que vous voulez. Par exemple, après avoir obtenu un `batch` de votre `tf.data.Dataset` comme nous l'avons fait ci-dessus, vous pouvez décoder le premier élément comme suit :


```py
input_ids = batch["input_ids"].numpy()
tokenizer.decode(input_ids[0])
```

Vous pouvez ensuite la comparer avec la première étiquette, comme suit :

```py
labels = batch["labels"].numpy()
label = labels[0]
```

Une fois que vous pouvez visualiser vos données de cette manière, vous pouvez vous poser les questions suivantes :

- les données décodées sont-elles compréhensibles ?
- êtes-vous d'accord avec les étiquettes ?
- y a-t-il une étiquette qui est plus courante que les autres ?
- quelle devrait être la perte/métrique si le modèle prédisait une réponse aléatoire/toujours la même réponse ?

Après avoir examiné vos données, examinez quelques-unes des prédictions du modèle. Si votre modèle produit des *tokens*, essayez aussi de les décoder ! Si le modèle prédit toujours la même chose, cela peut être dû au fait que votre jeu de données est biaisé en faveur d'une catégorie (pour les problèmes de classification). Des techniques telles que le suréchantillonnage des classes rares peuvent aider. D'autre part, cela peut également être dû à des problèmes d'entraînement tels que de mauvais réglages des hyperparamètres.

Si la perte/la métrique que vous obtenez sur votre modèle initial avant entraînement est très différente de la perte/la métrique à laquelle vous vous attendez pour des prédictions aléatoires, vérifiez la façon dont votre perte ou votre métrique est calculée. Il y a probablement un bug. Si vous utilisez plusieurs pertes que vous ajoutez à la fin, assurez-vous qu'elles sont de la même échelle.

Lorsque vous êtes sûr que vos données sont parfaites, vous pouvez voir si le modèle est capable de s'entraîner sur elles grâce à un test simple.

### Surentraînement du modèle sur un seul batch

Le surentraînement est généralement une chose que nous essayons d'éviter lors de l'entraînement car cela signifie que le modèle n'apprend pas à reconnaître les caractéristiques générales que nous voulons qu'il reconnaisse et se contente de mémoriser les échantillons d'entraînement. Cependant, essayer d'entraîner votre modèle sur un batch encore et encore est un bon test pour vérifier si le problème tel que vous l'avez formulé peut être résolu par le modèle que vous essayez d'entraîner. Cela vous aidera également à voir si votre taux d'apprentissage initial est trop élevé.

Une fois que vous avez défini votre `modèle`, c'est très facile. Il suffit de prendre un batch de données d'entraînement, puis de le traiter comme votre jeu de données entier que vous *finetunez* sur un grand nombre d'époques :


```py
for batch in train_dataset:
    break

# Assurez-vous que vous avez exécuté model.compile() et défini votre optimiseur,
# et vos pertes/métriques si vous les utilisez.

model.fit(batch, epochs=20)
```

<Tip>

💡 Si vos données d'entraînement ne sont pas équilibrées, veillez à créer un batch de données d'entraînement contenant toutes les étiquettes.

</Tip>

Le modèle résultant devrait avoir des résultats proches de la perfection sur le `batch`, avec une perte diminuant rapidement vers 0 (ou la valeur minimale pour la perte que vous utilisez).

Si vous ne parvenez pas à ce que votre modèle obtienne des résultats parfaits comme celui-ci, cela signifie qu'il y a quelque chose qui ne va pas dans la façon dont vous avez formulé le problème ou dans vos données et vous devez donc y remédier. Ce n'est que lorsque vous parviendrez à passer le test de surentraînement que vous pourrez être sûr que votre modèle peut réellement apprendre quelque chose.

<Tip warning={true}>

⚠️ Vous devrez recréer votre modèle et votre `Trainer` après ce test, car le modèle obtenu ne sera probablement pas capable de récupérer et d'apprendre quelque chose d'utile sur votre jeu de données complet.

</Tip>

### Ne réglez rien tant que vous n'avez pas une première ligne de base

Le réglage des hyperparamètres est toujours considéré comme la partie la plus difficile de l'apprentissage automatique mais c'est juste la dernière étape pour vous aider à gagner un peu sur la métrique. La plupart du temps, les hyperparamètres par défaut du `Trainer` fonctionneront très bien pour vous donner de bons résultats. Donc ne vous lancez pas dans une recherche d'hyperparamètres longue et coûteuse jusqu'à ce que vous ayez quelque chose qui batte la ligne de base que vous avez sur votre jeu de données.

Une fois que vous avez un modèle suffisamment bon, vous pouvez commencer à le *finetuner* un peu. N'essayez pas de lancer un millier d'exécutions avec différents hyperparamètres mais comparez quelques exécutions avec différentes valeurs pour un hyperparamètre afin de vous faire une idée de celui qui a le plus d'impact.

Si vous modifiez le modèle lui-même, restez simple et n'essayez rien que vous ne puissiez raisonnablement justifier. Veillez toujours à revenir au test de surentraînement pour vérifier que votre modification n'a pas eu de conséquences inattendues.

### Demander de l'aide

Nous espérons que vous avez trouvé dans cette section des conseils qui vous ont aidé à résoudre votre problème. Si ce n'est pas le cas, n'oubliez pas que vous pouvez toujours demander de l'aide à la communauté sur le [forum](https://discuss.huggingface.co/). 

Voici quelques ressources (en anglais) supplémentaires qui peuvent s'avérer utiles :

- [La reproductibilité comme vecteur des meilleures pratiques d'ingénierie](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) par Joel Grus
- [Liste de contrôle pour le débogage des réseaux de neurones](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) par Cecelia Shao
- [Comment tester unitairement le code d'apprentissage automatique](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) par Chase Roberts
- [Une recette pour entraîner les réseaux de neurones](http://karpathy.github.io/2019/04/25/recipe/) par Andrej Karpathy

Bien sûr, tous les problèmes rencontrés lors de l'entraînement ne sont pas forcément de votre faute ! Si vous rencontrez quelque chose dans la bibliothèque 🤗 *Transformers* ou 🤗 *Datasets* qui ne semble pas correct, vous avez peut-être trouver un *bug*. Vous devez absolument nous en parler pour qu'on puisse le corriger. Dans la section suivante, nous allons vous expliquer exactement comment faire.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter8/4_tf.mdx" />

### Que faire lorsque vous obtenez une erreur
https://huggingface.co/learn/course/fr/chapter8/2.md

# Que faire lorsque vous obtenez une erreur

<CourseFloatingBanner chapter={8}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter8/section2.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter8/section2.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter8/section2.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter8/section2.ipynb"},
]} />

Dans cette section, nous allons examiner certaines erreurs courantes qui peuvent se produire lorsque vous essayez de générer des prédictions à partir de votre *transformer* fraîchement *finetuné*. Cela vous préparera pour la [section 4](/course/chapter8/fr/section4) de ce chapitre où nous explorerons comment déboguer la phase d'entraînement elle-même.

<Youtube id="DQ-CpJn6Rc4"/>

Nous avons préparé un gabarit de [dépôt de modèles](https://huggingface.co/lewtun/distilbert-base-uncased-finetuned-squad-d5716d28) pour cette section et si vous voulez exécuter le code de ce chapitre, vous devrez d'abord copier le modèle dans votre compte sur le [*Hub* d'Hugging Face](https://huggingface.co). Pour ce faire, connectez-vous d'abord en exécutant l'une ou l'autre des commandes suivantes dans un *notebook* Jupyter :

```python
from huggingface_hub import notebook_login

notebook_login()
```

ou ce qui suit dans votre terminal préféré :

```bash
huggingface-cli login
```

Cela vous demandera d'entrer votre nom d'utilisateur et votre mot de passe, et enregistrera un jeton sous *~/.cache/huggingface/*. Une fois que vous vous êtes connecté, vous pouvez copier le gabarit du dépôt avec la fonction suivante :

```python
from distutils.dir_util import copy_tree
from huggingface_hub import Repository, snapshot_download, create_repo, get_full_repo_name


def copy_repository_template():
    # Cloner le dépôt et extraire le chemin local
    template_repo_id = "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28"
    commit_hash = "be3eaffc28669d7932492681cd5f3e8905e358b4"
    template_repo_dir = snapshot_download(template_repo_id, revision=commit_hash)
    # Créer un dépôt vide sur le Hub
    model_name = template_repo_id.split("/")[1]
    create_repo(model_name, exist_ok=True)
    # Cloner le dépôt vide
    new_repo_id = get_full_repo_name(model_name)
    new_repo_dir = model_name
    repo = Repository(local_dir=new_repo_dir, clone_from=new_repo_id)
    # Copier les fichiers
    copy_tree(template_repo_dir, new_repo_dir)
    # Pousser sur le Hub
    repo.push_to_hub()
```

Maintenant, lorsque vous appelez `copy_repository_template()`, cela va créer une copie du gabarit du dépôt sous votre compte.

## Déboguer le pipeline à partir de 🤗 <i>Transformers</i>

Pour donner le coup d'envoi de notre voyage dans le monde merveilleux du débogage de *transformers*, considérez le scénario suivant : vous travaillez avec un collègue sur un projet de réponse à des questions pour aider les clients d'un site de commerce en ligne à trouver des réponses à des produits. Votre collègue vous envoie un message du genre :

> Bonjour ! Je viens de réaliser une expérience en utilisant les techniques du [chapitre 7](/course/fr/chapiter7/7) du cours d'Hugging Face et j'ai obtenu d'excellents résultats sur SQuAD ! Je pense que nous pouvons utiliser ce modèle comme point de départ pour notre projet. L'identifiant du modèle sur le *Hub* est "lewtun/distillbert-base-uncased-finetuned-squad-d5716d28". N'hésite pas à le tester :)

et la première chose à laquelle on pense est de charger le modèle en utilisant le `pipeline` de 🤗 *Transformers* :

```python
from transformers import pipeline

model_checkpoint = get_full_repo_name("distillbert-base-uncased-finetuned-squad-d5716d28")
reader = pipeline("question-answering", model=model_checkpoint)
```

```python out
"""
OSError: Can't load config for 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28'. Make sure that:

- 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is a correct model identifier listed on 'https://huggingface.co/models'

- or 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is the correct path to a directory containing a config.json file
"""
```

Oh non, quelque chose semble s'être mal passée ! Si vous êtes novice en programmation, ce genre d'erreurs peut sembler un peu cryptique au début (qu'est-ce qu'une `OSError` ?!). L'erreur affichée ici n'est que la dernière partie d'un rapport d'erreur beaucoup plus long appelé _Python traceback_ (alias *stack trace*). Par exemple, si vous exécutez ce code sur Google Colab, vous devriez voir quelque chose comme la capture d'écran suivante :

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/traceback.png" alt="A Python traceback." width="100%"/>
</div>

Il y a beaucoup d'informations dans ces rapports, nous allons donc en parcourir ensemble les éléments clés. La première chose à noter est que les *tracebacks* doivent être lus _de bas en haut_. Cela peut sembler bizarre si vous avez l'habitude de lire du texte français de haut en bas mais cela reflète le fait que le *traceback* montre la séquence d'appels de fonction que le `pipeline` fait lors du téléchargement du modèle et du *tokenizer*. Consultez le [chapitre 2](/course/fr/chapter2) pour plus de détails sur la façon dont le `pipeline` fonctionne sous le capot.

<Tip>

🚨 Vous voyez le cadre bleu autour de « <i>6 frames</i> » dans le <i>traceback</i> de Google Colab ? Il s'agit d'une fonctionnalité spéciale de Colab qui compresse le <i>traceback</i> en <i>frames</i>. Si vous ne parvenez pas à trouver la source d'une erreur, déroulez le <i>traceback</i> en cliquant sur ces deux petites flèches.

</Tip>

Cela signifie que la dernière ligne du <i>traceback</i> indique le dernier message d'erreur et donne le nom de l'exception qui a été levée. Dans ce cas, le type d'exception est `OSError`, ce qui indique une erreur liée au système. Si nous lisons le message d'erreur qui l'accompagne, nous pouvons voir qu'il semble y avoir un problème avec le fichier *config.json* du modèle et deux suggestions nous sont données pour le résoudre :

```python out
"""
Make sure that:

- 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is a correct model identifier listed on 'https://huggingface.co/models'

- or 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is the correct path to a directory containing a config.json file
"""
```

<Tip>

💡 Si vous rencontrez un message d'erreur difficile à comprendre, copiez et collez le message dans Google ou sur [Stack Overflow](https://stackoverflow.com/) (oui, vraiment !). Il y a de fortes chances que vous ne soyez pas la première personne à rencontrer cette erreur et c'est un bon moyen de trouver des solutions que d'autres membres de la communauté ont publiées. Par exemple, en recherchant `OSError : Can't load config for` sur Stack Overflow donne plusieurs [réponses](https://stackoverflow.com/search?q=OSError%3A+Can%27t+load+config+for+) qui peuvent être utilisées comme point de départ pour résoudre le problème.

</Tip>

La première suggestion nous demande de vérifier si l'identifiant du modèle est effectivement correct, la première chose à faire est donc de copier l'identifiant et de le coller dans la barre de recherche du *Hub* :

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/wrong-model-id.png" alt="The wrong model name." width="100%"/>
</div>

Hmm, il semble en effet que le modèle de notre collègue ne soit pas sur le *Hub*... Mais il y a une faute de frappe dans le nom du modèle ! DistilBERT n'a qu'un seul « l » dans son nom alors corrigeons cela et cherchons « lewtun/distilbert-base-uncased-finetuned-squad-d5716d28 » à la place :

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/true-model-id.png" alt="The right model name." width="100%"/>
</div>

Ok, ça a marché. Maintenant essayons de télécharger à nouveau le modèle avec le bon identifiant :

```python
model_checkpoint = get_full_repo_name("distilbert-base-uncased-finetuned-squad-d5716d28")
reader = pipeline("question-answering", model=model_checkpoint)
```

```python out
"""
OSError: Can't load config for 'lewtun/distilbert-base-uncased-finetuned-squad-d5716d28'. Make sure that:

- 'lewtun/distilbert-base-uncased-finetuned-squad-d5716d28' is a correct model identifier listed on 'https://huggingface.co/models'

- or 'lewtun/distilbert-base-uncased-finetuned-squad-d5716d28' is the correct path to a directory containing a config.json file
"""
```

Argh, encore un échec. Bienvenue dans la vie quotidienne d'un ingénieur en apprentissage machine ! Puisque nous avons corrigé l'identifiant du modèle, le problème doit se situer dans le dépôt lui-même. Une façon rapide d'accéder au contenu d'un dépôt sur le 🤗 *Hub* est via la fonction `list_repo_files()` de la bibliothèque `huggingface_hub` :

```python
from huggingface_hub import list_repo_files

list_repo_files(repo_id=model_checkpoint)
```

```python out
['.gitattributes', 'README.md', 'pytorch_model.bin', 'special_tokens_map.json', 'tokenizer_config.json', 'training_args.bin', 'vocab.txt']
```

Intéressant. Il ne semble pas y avoir de fichier *config.json* dans le dépôt ! Pas étonnant que notre `pipeline` n'ait pas pu charger le modèle. Notre collègue a dû oublier de pousser ce fichier vers le *Hub* après l'avoir *finetuné*. Dans ce cas, le problème semble assez simple à résoudre : nous pouvons lui demander d'ajouter le fichier, ou, puisque nous pouvons voir à partir de l'identifiant du modèle que le modèle pré-entraîné utilisé est [`distilbert-base-uncased`](https://huggingface.co/distilbert-base-uncased), nous pouvons télécharger la configuration de ce modèle et la pousser dans notre dépôt pour voir si cela résout le problème. Essayons cela. En utilisant les techniques apprises dans le [chapitre 2](/course/fr/chapter2), nous pouvons télécharger la configuration du modèle avec la classe `AutoConfig` :

```python
from transformers import AutoConfig

pretrained_checkpoint = "distilbert-base-uncased"
config = AutoConfig.from_pretrained(pretrained_checkpoint)
```

<Tip warning={true}>

🚨 L'approche que nous adoptons ici n'est pas infaillible puisque notre collègue peut avoir modifié la configuration de `distilbert-base-uncased` avant de <i>finetuner</i> le modèle. Dans la vie réelle, nous voudrions vérifier avec lui d'abord, mais pour les besoins de cette section nous supposerons qu'il a utilisé la configuration par défaut.

</Tip>

Nous pouvons ensuite le pousser vers notre dépôt de modèles avec la fonction `push_to_hub()` de la configuration :

```python
config.push_to_hub(model_checkpoint, commit_message="Add config.json")
```

Maintenant, nous pouvons tester si cela a fonctionné en chargeant le modèle depuis le dernier *commit* de la branche `main` :

```python
reader = pipeline("question-answering", model=model_checkpoint, revision="main")

context = r"""
Extractive Question Answering is the task of extracting an answer from a text
given a question. An example of a question answering dataset is the SQuAD
dataset, which is entirely based on that task. If you would like to fine-tune a
model on a SQuAD task, you may leverage the
examples/pytorch/question-answering/run_squad.py script.

🤗 Transformers is interoperable with the PyTorch, TensorFlow, and JAX
frameworks, so you can use your favourite tools for a wide variety of tasks!
"""

context_fr = r"""
La réponse à des questions consiste à extraire une réponse d'un texte
à partir d'une question. Un exemple de jeu de données de réponse aux questions est le 
jeu de données SQuAD qui est entièrement basé sur cette tâche. Si vous souhaitez finetuner
un modèle sur une tâche SQuAD, vous pouvez utiliser le fichier
exemples/pytorch/question-answering/run_squad.py.

🤗 Transformers est interopérable avec les frameworks PyTorch, TensorFlow et JAX.
de sorte que vous pouvez utiliser vos outils préférés pour une grande variété de tâches !
"""

question = "What is extractive question answering?"
# Qu'est-ce que la réponse extractive aux questions ?
reader(question=question, context=context)
```

```python out
{'score': 0.38669535517692566,
 'start': 34,
 'end': 95,
 'answer': 'the task of extracting an answer from a text given a question'} 
 # la tâche consistant à extraire une réponse d'un texte à partir d'une question.
```

Woohoo, ça a marché ! Récapitulons ce que vous venez d'apprendre :

- les messages d'erreur en Python sont appelés _tracebacks_ et sont lus de bas en haut. La dernière ligne du message d'erreur contient généralement les informations dont vous avez besoin pour localiser la source du problème,
- si la dernière ligne ne contient pas suffisamment d'informations, remontez dans le *traceback* et voyez si vous pouvez identifier où l'erreur se produit dans le code source,
- si aucun des messages d'erreur ne peut vous aider à déboguer le problème, essayez de rechercher en ligne une solution à un problème similaire,
- l'`huggingface_hub` fournit une suite d'outils que vous pouvez utiliser pour interagir avec et déboguer les dépôts sur le *Hub*.

Maintenant que vous savez comment déboguer un pipeline, examinons un exemple plus délicat dans la passe avant du modèle lui-même.

## Déboguer la passe avant de votre modèle

Bien que le `pipeline` soit parfait pour la plupart des applications où vous devez générer rapidement des prédictions, vous aurez parfois besoin d'accéder aux logits du modèle (par exemple si vous avez un post-traitement personnalisé que vous souhaitez appliquer). Pour voir ce qui peut mal tourner dans ce cas, commençons par récupérer le modèle et le *tokenizer* de notre `pipeline` :

```python
tokenizer = reader.tokenizer
model = reader.model
```

Ensuite, nous avons besoin d'une question, alors voyons si nos *frameworks* préférés sont supportés :

```python
question = "Which frameworks can I use?"  # Quel frameworks puis-je utiliser ?
```

Comme nous l'avons vu dans le [chapitre 7](/course/fr/chapter7), les étapes habituelles que nous devons suivre sont la tokénisation des entrées, l'extraction des logits des *tokens* de début et de fin, puis le décodage de l'étendue de la réponse :

```python
import torch

inputs = tokenizer(question, context, add_special_tokens=True)
input_ids = inputs["input_ids"][0]
outputs = model(**inputs)
answer_start_scores = outputs.start_logits
answer_end_scores = outputs.end_logits
# Pour obtenir le début de réponse le plus probable avec l'argmax du score
answer_start = torch.argmax(answer_start_scores)
# Pour obtenir la fin de réponse la plus probable avec l'argmax du score
answer_end = torch.argmax(answer_end_scores) + 1
answer = tokenizer.convert_tokens_to_string(
    tokenizer.convert_ids_to_tokens(input_ids[answer_start:answer_end])
)
print(f"Question: {question}")
print(f"Answer: {answer}")
```

```python out
"""
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/var/folders/28/k4cy5q7s2hs92xq7_h89_vgm0000gn/T/ipykernel_75743/2725838073.py in <module>
      1 inputs = tokenizer(question, text, add_special_tokens=True)
      2 input_ids = inputs["input_ids"]
----> 3 outputs = model(**inputs)
      4 answer_start_scores = outputs.start_logits
      5 answer_end_scores = outputs.end_logits

~/miniconda3/envs/huggingface/lib/python3.8/site-packages/torch/nn/modules/module.py in _call_impl(self, *input, **kwargs)
   1049         if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks
   1050                 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1051             return forward_call(*input, **kwargs)
   1052         # Do not call functions when jit is used
   1053         full_backward_hooks, non_full_backward_hooks = [], []

~/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/models/distilbert/modeling_distilbert.py in forward(self, input_ids, attention_mask, head_mask, inputs_embeds, start_positions, end_positions, output_attentions, output_hidden_states, return_dict)
    723         return_dict = return_dict if return_dict is not None else self.config.use_return_dict
    724
--> 725         distilbert_output = self.distilbert(
    726             input_ids=input_ids,
    727             attention_mask=attention_mask,

~/miniconda3/envs/huggingface/lib/python3.8/site-packages/torch/nn/modules/module.py in _call_impl(self, *input, **kwargs)
   1049         if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks
   1050                 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1051             return forward_call(*input, **kwargs)
   1052         # Do not call functions when jit is used
   1053         full_backward_hooks, non_full_backward_hooks = [], []

~/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/models/distilbert/modeling_distilbert.py in forward(self, input_ids, attention_mask, head_mask, inputs_embeds, output_attentions, output_hidden_states, return_dict)
    471             raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time")
    472         elif input_ids is not None:
--> 473             input_shape = input_ids.size()
    474         elif inputs_embeds is not None:
    475             input_shape = inputs_embeds.size()[:-1]

AttributeError: 'list' object has no attribute 'size'
"""
```

Il semble que nous ayons un *bug* dans notre code ! Mais il ne nous fait pas peur. Nous pouvons utiliser le débogueur Python dans un *notebook* :

<Youtube id="rSPyvPw0p9k"/>

ou dans un terminal :

<Youtube id="5PkZ4rbHL6c"/>

Ici, la lecture du message d'erreur nous indique que l'objet `'list' n'a pas d'attribut 'size'`, et nous pouvons voir une flèche `-->` pointant vers la ligne où le problème a été soulevé dans `model(**inputs)`. Vous pouvez déboguer ceci de manière interactive en utilisant le débogueur Python, mais pour l'instant nous allons simplement imprimer une tranche de `inputs` pour voir ce que nous avons :

```python
inputs["input_ids"][:5]
```

```python out
[101, 2029, 7705, 2015, 2064]
```

Cela ressemble certainement à une `list` ordinaire en Python mais vérifions le type :

```python
type(inputs["input_ids"])
```

```python out
list
```

Oui, c'est bien une `list` Python. Alors, qu'est-ce qui a mal tourné ? Rappelez-vous que dans le [chapitre 2](/course/fr/chapter2) nous avons vu que les classes `AutoModelForXxx` opèrent sur des _tenseurs_ (soit dans PyTorch ou TensorFlow) et qu'une opération commune est d'extraire les dimensions d'un tenseur en utilisant `Tensor.size()`. Jetons un autre coup d'oeil au *traceback* pour voir quelle ligne a déclenché l'exception :

```
~/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/models/distilbert/modeling_distilbert.py in forward(self, input_ids, attention_mask, head_mask, inputs_embeds, output_attentions, output_hidden_states, return_dict)
    471             raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time")
    472         elif input_ids is not None:
--> 473             input_shape = input_ids.size()
    474         elif inputs_embeds is not None:
    475             input_shape = inputs_embeds.size()[:-1]

AttributeError: 'list' object has no attribute 'size'
```

Il semble que notre code ait essayé d'appeler `input_ids.size()`, mais cela ne fonctionne clairement pas pour une `list` Python qui est juste un conteneur. Comment pouvons-nous résoudre ce problème ? La recherche du message d'erreur sur Stack Overflow donne quelques [réponses](https://stackoverflow.com/search?q=AttributeError%3A+%27list%27+object+has+no+attribute+%27size%27&s=c15ec54c-63cb-481d-a749-408920073e8f) pertinentes. En cliquant sur la première, une question similaire à la nôtre s'affiche, avec la réponse indiquée dans la capture d'écran ci-dessous :

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/stack-overflow.png" alt="An answer from Stack Overflow." width="100%"/>
</div>

La réponse recommande d'ajouter `return_tensors='pt'` au *tokenizer*, voyons donc si cela fonctionne pour nous :

```python out
inputs = tokenizer(question, context, add_special_tokens=True, return_tensors="pt")
input_ids = inputs["input_ids"][0]
outputs = model(**inputs)
answer_start_scores = outputs.start_logits
answer_end_scores = outputs.end_logits
# Pour obtenir le début de réponse le plus probable avec l'argmax du score
answer_start = torch.argmax(answer_start_scores)
# Pour obtenir la fin de réponse la plus probable avec l'argmax du score
answer_end = torch.argmax(answer_end_scores) + 1
answer = tokenizer.convert_tokens_to_string(
    tokenizer.convert_ids_to_tokens(input_ids[answer_start:answer_end])
)
print(f"Question: {question}")
print(f"Answer: {answer}")
```

```python out
"""
Question: Which frameworks can I use? # Quels frameworks puis-je utiliser ?
Answer: pytorch, tensorflow, and jax # pytorch, tensorflow et jax
"""
```

Super, ça a marché ! Voilà un excellent exemple de l'utilité de Stack Overflow : en identifiant un problème similaire, nous avons pu bénéficier de l'expérience d'autres membres de la communauté. Cependant, une recherche de ce type ne donne pas toujours une réponse pertinente. Que faire alors dans ce cas ? Heureusement, il existe une communauté accueillante de développeurs sur le [forum d'Hugging Face](https://discuss.huggingface.co/) qui peut vous aider ! Dans la prochaine section, nous verrons comment rédiger de bonnes questions sur les forums pour avoir de bonnes chances d'obtenir une réponse.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter8/2.mdx" />

### Quiz de fin de chapitre
https://huggingface.co/learn/course/fr/chapter9/9.md

# Quiz de fin de chapitre

<CourseFloatingBanner
    chapter={9}
    classNames="absolute z-10 right-0 top-0"
/>

Testons ce que vous avez appris dans ce chapitre !

### 1. Que pouvez-vous faire avec <i>Gradio</i> ?

<Question
	choices={[
        {
			text: "Créez une démo pour votre modèle d'apprentissage automatique.",
			explain: "Avec quelques lignes de code python, vous pouvez générer une démo pour votre modèle d'apprentissage automatique en utilisant notre bibliothèque de composants préconstruits.",
			correct: true
		},
		{
			text: "Partagez votre modèle d'apprentissage automatique avec d'autres personnes.",
			explain: "En utilisant le paramètre <code>share=True</code> dans la méthode de lancement, vous pouvez générer un lien de partage à envoyer à tout le monde.",
            correct: true
		},
		{
			text: "Déboguez votre modèle.",
			explain: "L'un des avantages d'une démo <i>Gradio</i> est de pouvoir tester votre modèle avec des données réelles que vous pouvez modifier et observer les prédictions du modèle changer en temps réel, ce qui vous aide à déboguer votre modèle.",
			correct: true
		},
		{
			text: "Entraîner votre modèle.",
			explain: "<i>Gradio</i> est conçu pour être utilisé pour l'inférence, APRÈS que votre modèle a été entraîné.",
		}
	]}
/>

### 2. <i>Gradio</i> fonctionne UNIQUEMENT avec les modèles en PyTorch

<Question
	choices={[
        {
			text: "Vrai",
			explain: "<i>Gradio</i> fonctionne avec les modèles Pytorch mais aussi pour tout type de modèle d'apprentissage automatique !"
        },
        {
			text: "Faux",
			explain: "<i>Gradio</i> est indifférent au modèle ce qui signifie que vous pouvez créer une démo pour tout type de modèle d'apprentissage automatique.",
			correct: true
        }
	]}
/>

### 3. D'où pouvez-vous lancer une démo <i>Gradio</i> ?

<Question
	choices={[
        {
			text: "D'un IDE Python standard",
			explain: "<i>Gradio</i> fonctionne parfaitement avec votre IDE préféré.",
            correct: true
        },
        {
			text: "De <i>notebooks</i> Google Colab",
			explain: "Vous pouvez créer et lancer une démo dans votre <i>notebook</i> Google Colab.",
			correct: true
        },
        {
			text: "De <i>notebooks</i> Jupyter",
			explain: "Vous pouvez créer et lancer une démo dans votre <i>notebook</i> Jupyter.",
			correct: true
        }
	]}
/>

### 4. <i>Gradio</i> est conçu principalement pour les modèles de NLP

<Question
	choices={[
        {
			text: "Vrai",
			explain: "<i>Gradio</i> fonctionne avec pratiquement tous les types de données, pas seulement avec le NLP."
        },
        {
			text: "Faux",
			explain: "<i>Gradio</i> fournit aux développeurs une bibliothèque de composants préconstruits pour pratiquement tous les types de données.",
            correct: true
        }
	]}
/>

### 5. Parmi les fonctionnalités suivantes, lesquelles sont prises en charge par <i>Gradio</i> ?

<Question
	choices={[
        {
			text: "Entrées et sorties multiples.",
			explain: "Les entrées et sorties multiples sont possibles avec <i>Gradio</i>. Tout ce que vous devez faire est de passer une liste d'entrées et de sorties à leurs paramètres correspondants.",
            correct: true
        },
        {
			text: "État pour la persistance des données.",
			explain: "<i>Gradio</i> est capable d'ajouter un état à votre interface.",
			correct: true
        },
        {
			text: "Authentification par nom d'utilisateur et mot de passe.",
			explain: "Passez une liste de <i>tuples</i> de nom d'utilisateur/mot de passe à la méthode de lancement pour ajouter l'authentification.",
			correct: true
        },
        {
			text: "Analyse automatique de l'utilisation de votre démo <i>Gradio</i>.",
			explain: "<i>Gradio</i> ne fournit pas aux développeurs des analyses sur les personnes qui utilisent leurs démos."
        },
        {
			text: "Chargement d'un modèle à partir du <i>Hub</i> ou de <i>Space</i>.",
			explain: "Charger n'importe quel modèle de <i>Hugging Face</i> en utilisant la méthode <code>gr.Interface.load()</code>.",
			correct: true
        }
	]}
/>

### 6. Lesquelles des méthodes suivantes sont valides pour charger un modèle à partir du <i>Hub</i> ou de <i>Space</i> ?

<Question
	choices={[
        {
			text: "<code>gr.Interface.load('huggingface/{user}/{model_name}')</code>",
			explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir du <i>Hub</i>.",
            correct: true
        },
        {
			text: "<code>gr.Interface.load('model/{user}/{model_name}')</code>",
			explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir du <i>Hub</i>.",
			correct: true
        },
        {
			text: "<code>gr.Interface.load('demos/{user}/{model_name}')</code>",
			explain: "Vous ne pouvez pas charger un modèle en utilisant le préfixe <code>demos</code>."
        },
        {
			text: "<code>gr.Interface.load('spaces/{user}/{model_name}')</code>",
			explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir de <i>Space</i>.",
			correct: true
        }
	]}
/>

### 7. Sélectionnez toutes les étapes nécessaires pour ajouter un état à votre interface <i>Gradio</i>

<Question
	choices={[
        {
			text: "Passez un paramètre supplémentaire dans votre fonction de prédiction, qui représente l'état de l'interface.",
			explain: "Un paramètre supplémentaire stockant l'historique ou l'état de votre interface est nécessaire.",
            correct: true
        },
        {
			text: "À la fin de la fonction de prédiction, renvoyer la valeur mise à jour de l'état comme une valeur de retour supplémentaire.",
			explain: "Cette valeur d'historique ou d'état doit être retournée par votre fonction.",
            correct: true
        },
        {
			text: "Ajoutez les composants d'entrée et de sortie d'état lors de la création de votre interface.",
			explain: "<i>Gradio</i> fournit un composant d'entrée et de sortie d'état pour persister les données.",
            correct: true
        }
	]}
/>

### 8. Lesquels des éléments suivants sont des composants inclus dans la bibliothèque <i>Gradio</i> ?

<Question
	choices={[
        {
			text: "<code>Textbox</code>.",
			explain: "Oui, vous pouvez créer des zones de texte avec le composant <code>Textbox</code>.",
            correct: true
        },
        {
			text: "<code>Graph</code>.",
			explain: "Il n'y a actuellement aucun composant <code>Graph</code>.",
        },
        {
			text: "<code>Image</code>.",
			explain: "Oui, vous pouvez créer un <i>widget</i> de téléchargement d'images avec le composant <code>Image</code>.",
            correct: true
        },
        {
			text: "<code>Audio</code>.",
			explain: "Oui, vous pouvez créer un <i>widget</i> de téléchargement audio avec le composant <code>Audio</code>.",
            correct: true
        },
	]}
/>

### 9. Qu'est-ce que les `Blocks` vous permet de faire ?

<Question
	choices={[
        {
			text: "Combiner plusieurs démos en une seule application web.",
			explain: "Vous pouvez utiliser la fonction <code>with gradio.Tabs():</code> pour ajouter des onglets pour plusieurs démos.",
			correct: true
        },
        {
			text: "Attribuer des déclencheurs d'événements tels que clicked/changed/etc aux composants <code>Blocks</code>.",
			explain: "Lorsque vous assignez un événement, vous passez trois paramètres : <code>fn</code> qui est la fonction qui doit être appelée, <code>inputs</code> qui est la (liste) des composants d'entrée, et <code>outputs</code> qui est la (liste) des composants de sortie qui doivent être appelés.",
			correct: true
        },
        {
			text: "Déterminer automatiquement quel composant <code>Blocks</code> doit être interactif ou statique.",
			explain: "En fonction des déclencheurs d'événements que vous définissez, <code>Blocks</code> détermine automatiquement si un composant doit accepter ou non les entrées de l'utilisateur..",
			correct: true
        },
		 {
			text: "Créer des démos en plusieurs étapes, c'est-à-dire vous permettre de réutiliser la sortie d'un composant comme entrée pour le suivant.",
			explain: "Vous pouvez utiliser un composant pour l'entrée d'un déclencheur d'événement mais la sortie d'un autre.",
            correct: true
        },
	]}
/>

### 10. Vous pouvez partager un lien public vers une démo <code>Blocks</code> et accueillir une démo <code>Blocks</code> sur <i>Space</i>

<Question
	choices={[
        {
			text: "Vrai",
			explain: "Tout comme <code>Interface</code>, toutes les capacités de partage et d'hébergement sont les mêmes pour les démos basées sur <code>Blocks</code> !",
			correct: true
        },
        {
			text: "Faux",
			explain: "Tout comme <code>Interface</code>, toutes les capacités de partage et d'hébergement sont les mêmes pour les démos basées sur <code>Blocks</code> !",
        }
	]}
/>

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter9/9.mdx" />

### Intégrations avec le <i>Hub d'Hugging Face</i>
https://huggingface.co/learn/course/fr/chapter9/5.md

# Intégrations avec le <i>Hub d'Hugging Face</i>

<CourseFloatingBanner chapter={9}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter9/section5.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter9/section5.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter9/section5.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter9/section5.ipynb"},
]} />

Pour vous rendre la vie encore plus facile, *Gradio* s'intègre directement avec *Hub* et *Spaces*.
Vous pouvez charger des démos depuis le *Hub* et les *Spaces* avec seulement *une ligne de code*.

### Chargement de modèles depuis le <i>Hub d'Hugging Face</i>

Pour commencer, choisissez un des milliers de modèles qu'*Hugging Face* offre à travers le *Hub*, comme décrit dans le [chapitre 4](/course/fr/chapter4/2).

En utilisant la méthode spéciale `Interface.load()`, vous passez `"model/"` (ou, de manière équivalente, `"huggingface/"`) suivi du nom du modèle. 
Par exemple, voici le code pour construire une démo pour le [GPT-J](https://huggingface.co/EleutherAI/gpt-j-6B), un grand modèle de langue, ajouter quelques exemples d'entrées :

```py
import gradio as gr

title = "GPT-J-6B"
description = "Gradio Demo for GPT-J 6B, a transformer model trained using Ben Wang's Mesh Transformer JAX. 'GPT-J' refers to the class of model, while '6B' represents the number of trainable parameters. To use it, simply add your text, or click one of the examples to load them. Read more at the links below."
# Démo Gradio pour GPT-J 6B, un modèle de transformer entraîné avec le Mesh Transformer JAX de Ben Wang. GPT-J fait référence à la classe du modèle, tandis que '6B' représente le nombre de paramètres entraînables. Pour l'utiliser, il suffit d'ajouter votre texte, ou de cliquer sur l'un des exemples pour le charger. Pour en savoir plus, consultez les liens ci-dessous.
article = "<p style='text-align: center'><a href='https://github.com/kingoflolz/mesh-transformer-jax' target='_blank'>GPT-J-6B: A 6 Billion Parameter Autoregressive Language Model</a></p>"
# GPT-J-6B : Un modèle linguistique autorégressif à 6 milliards de paramètres
examples = [
    ["The tower is 324 metres (1,063 ft) tall,"],
    # La tour mesure 324 mètres (1 063 pieds) de haut,
    ["The Moon's orbit around Earth has"],
    # L'orbite de la Lune autour de la Terre a
    ["The smooth Borealis basin in the Northern Hemisphere covers 40%"],
    # Le bassin de Borealis dans l'hémisphère nord couvre 40 %.
]
gr.Interface.load(
    "huggingface/EleutherAI/gpt-j-6B",
    inputs=gr.Textbox(lines=5, label="Input Text"),
    title=title,
    description=description,
    article=article,
    examples=examples,
    enable_queue=True,
).launch()
```
    
Le code ci-dessus produira l'interface ci-dessous :

<iframe src="https://hf.space/gradioiframe/akhaliq/gpt-j-6B/+" frameBorder="0" height="750" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Le chargement d'un modèle de cette manière utilise l'[API d'Inference](https://huggingface.co/inference-api) de *Hugging Face* au lieu de charger le modèle en mémoire. C'est idéal pour les modèles énormes comme GPT-J ou T0pp qui nécessitent beaucoup de RAM.

### Chargement depuis <i>Hugging Face Spaces</i>

Pour charger n'importe quel *Space* depuis le *Hub* et le recréer localement, vous pouvez passer `spaces/` à l'`Interface`, suivi du nom du *Space*.

Vous vous souvenez de la démo de la section 1 qui supprime le fond d'une image ? Chargeons-la à partir de *Hugging Face Spaces* :

```py
gr.Interface.load("spaces/abidlabs/remove-bg").launch()
```

<iframe src="https://hf.space/gradioiframe/abidlabs/remove-bg/+" frameBorder="0" height="650" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

L'un des avantages du chargement de démos à partir du *Hub* ou de *Spaces* est que vous pouvez les personnaliser en remplaçant n'importe lequel des paramètres. Ici, nous ajoutons un titre et faisons en sorte qu'elle fonctionne avec une webcam à la place :

```py
gr.Interface.load(
    "spaces/abidlabs/remove-bg", inputs="webcam", title="Remove your webcam background!"
).launch()
```

<iframe src="https://course-demos-Remove-bg.hf.space" frameBorder="0" height="550" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Maintenant que nous avons exploré quelques façons d'intégrer *Gradio* avec le *Hub*, jetons un coup d'oeil à certaines fonctionnalités avancées de la classe `Interface`. C'est le sujet de la prochaine section !


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter9/5.mdx" />

### Introduction à <i>Gradio</i>
https://huggingface.co/learn/course/fr/chapter9/1.md

# Introduction à <i>Gradio</i>

<CourseFloatingBanner
    chapter={9}
    classNames="absolute z-10 right-0 top-0"
/>

Dans ce chapitre, nous allons apprendre à construire des **démos interactives** pour vos modèles d'apprentissage automatique. 

Pourquoi construire une démo ou une interface graphique pour votre modèle d'apprentissage automatique ? Les démos permettent :

- aux **développeurs en apprentissage automatique** de présenter facilement leur travail à un large public, y compris des équipes non techniques ou des clients.
- aux **chercheurs** de reproduire plus facilement les modèles d'apprentissage automatique et leur comportement.
- aux **testeurs qualité** ou **utilisateurs finaux** d'identifier et de déboguer plus facilement les points de défaillance des modèles.
- aux **utilisateurs divers** de découvrir les biais algorithmiques des modèles.

Nous utiliserons la bibliothèque *Gradio* pour construire des démos pour nos modèles. *Gradio* vous permet de construire, de personnaliser et de partager des démos en ligne pour n'importe quel modèle d'apprentissage automatique. Et cela entièrement en Python.

Voici quelques exemples de démos d'apprentissage automatique construites avec Gradio :

* Un modèle de **reconnaissance de croquis** qui prend un croquis et produit des étiquettes de ce qu'il pense être dessiné : 

<iframe src="https://hf.space/gradioiframe/abidlabs/draw2/+" frameBorder="0" height="400" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

* Un modèle extractif de **réponse à une question** qui prend en entrée un paragraphe de contexte et une requête et produit une réponse et un score de probabilité (nous avons discuté de ce type de modèle [au chapitre 7](/course/fr/chapter7/7)) : 

<iframe src="https://hf.space/gradioiframe/abidlabs/question-answering-simple/+" frameBorder="0" height="640" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

* Un modèle de **suppression de l'arrière-plan** qui prend une image et la restitue avec l'arrière-plan supprimé :

<iframe src="https://hf.space/gradioiframe/abidlabs/remove-bg/+" frameBorder="0" height="640" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Ce chapitre est divisé en sections qui comprennent à la fois des _concepts_ et des _applications_. Après avoir appris le concept dans chaque section, vous l'appliquerez pour construire un type particulier de démo, allant de la classification d'images à la reconnaissance vocale. À la fin de ce chapitre, vous serez en mesure de créer ces démos (et bien d'autres !) en quelques lignes de code Python seulement. 

<Tip>
👀 Consultez <a href="https://huggingface.co/spaces" target="_blank">Hugging Face Spaces</a> pour voir de nombreux exemples récents de démos d'apprentissage automatique construites par la communauté !
</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter9/1.mdx" />

### Comprendre la classe <i>Interface</i>
https://huggingface.co/learn/course/fr/chapter9/3.md

# Comprendre la classe <i>Interface</i>

<CourseFloatingBanner chapter={9}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter9/section3.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter9/section3.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter9/section3.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter9/section3.ipynb"},
]} />

Dans cette section, nous allons examiner de plus près la classe `Interface`, et comprendre les principaux paramètres utilisés pour en créer une.

## Comment créer une interface

Vous remarquerez que la classe `Interface` a 3 paramètres obligatoires :  

`Interface(fn, inputs, outputs, ...)`

Ces paramètres sont :

  - `fn`: la fonction de prédiction qui est enveloppée par l'interface *Gradio*. Cette fonction peut prendre un ou plusieurs paramètres et retourner une ou plusieurs valeurs.
  - `inputs`: le(s) type(s) de composant(s) d'entrée. *Gradio* fournit de nombreux composants préconstruits tels que`"image"` ou `"mic"`. 
  - `outputs`: le(s) type(s) de composant(s) de sortie. Encore une fois, *Gradio* fournit de nombreux composants pré-construits, par ex. `"image"` ou `"label"`.

Pour une liste complète des composants, [jetez un coup d'œil à la documentation de *Gradio*](https://gradio.app/docs). Chaque composant préconstruit peut être personnalisé en instanciant la classe correspondant au composant. 

Par exemple, comme nous l'avons vu dans la [section précédente](/course/fr/chapter9/2), au lieu de passer le paramètre `inputs` par `"textbox"`, vous pouvez passer un composant `Textbox(lines=7, label="Prompt")` pour créer une zone de texte avec 7 lignes et un label.

Voyons un autre exemple, cette fois avec un composant `Audio`.

## Un exemple simple avec audio

Comme mentionné précédemment, *Gradio* fournit de nombreuses entrées et sorties différentes. 
Construisons donc une `Interface` qui fonctionne avec l'audio.

Dans cet exemple, nous allons construire une fonction audio-vers-audio qui prend un fichier audio et l'inverse simplement.

Nous utiliserons comme entrée le composant `Audio`. Lorsque vous utilisez le composant `Audio`, vous pouvez spécifier si vous voulez que la `source` de l'audio soit un fichier que l'utilisateur télécharge ou un microphone avec lequel l'utilisateur enregistre sa voix. Dans ce cas, nous allons choisir un microphone. Juste pour le plaisir, nous allons ajouter une étiquette à notre `Audio` qui dit « *Speak here...* » (Parler ici).

De plus, nous aimerions recevoir l'audio sous la forme d'un tableau numpy afin de pouvoir facilement l'inverser. Nous allons donc définir le `"type"` comme étant `"numpy"`, ce qui permet de passer les données d'entrée comme un *tuple* de (`sample_rate`, `data`) dans notre fonction.

Nous utiliserons également le composant de sortie `Audio` qui peut automatiquement rendre un *tuple* avec un taux d'échantillonnage et un tableau numpy de données comme un fichier audio lisible. 
Dans ce cas, nous n'avons pas besoin de faire de personnalisation, donc nous utiliserons le raccourci de la chaîne `"audio"`.


```py
import numpy as np
import gradio as gr


def reverse_audio(audio):
    sr, data = audio
    reversed_audio = (sr, np.flipud(data))
    return reversed_audio


mic = gr.Audio(source="microphone", type="numpy", label="Speak here...")
gr.Interface(reverse_audio, mic, "audio").launch()
```

Le code ci-dessus produira une interface comme celle qui suit (si votre navigateur ne vous demande pas l'autorisation pour accéder au microphone, <a href="https://huggingface.co/spaces/course-demos/audio-reverse" target="_blank">ouvrez la démo dans un onglet séparé</a>).

<iframe src="https://course-demos-audio-reverse.hf.space" frameBorder="0" height="250" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Vous devriez maintenant être capable d'enregistrer votre voix et de vous entendre parler à l'envers. Effrayant 👻 !

## Gérer les entrées et sorties multiples

Imaginons que nous ayons une fonction plus compliquée, avec plusieurs entrées et sorties. 
Dans l'exemple ci-dessous, nous avons une fonction qui prend un index de liste déroulante, une valeur de curseur et un nombre, et renvoie un échantillon audio d'une tonalité musicale. 

Regardez comment nous passons une liste de composants d'entrée et de sortie, et voyez si vous pouvez suivre ce qui se passe.

La clé ici est que lorsque vous passez :
* une liste de composants d'entrée, chaque composant correspond à un paramètre dans l'ordre.
* une liste de composants de sortie, chaque composant correspond à une valeur retournée.

L'extrait de code ci-dessous montre comment trois composants d'entrée correspondent aux trois arguments de la fonction `generate_tone()` :

```py
import numpy as np
import gradio as gr

notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]


def generate_tone(note, octave, duration):
    sr = 48000
    a4_freq, tones_from_a4 = 440, 12 * (octave - 4) + (note - 9)
    frequency = a4_freq * 2 ** (tones_from_a4 / 12)
    duration = int(duration)
    audio = np.linspace(0, duration, duration * sr)
    audio = (20000 * np.sin(audio * (2 * np.pi * frequency))).astype(np.int16)
    return (sr, audio)


gr.Interface(
    generate_tone,
    [
        gr.Dropdown(notes, type="index"),
        gr.Slider(minimum=4, maximum=6, step=1),
        gr.Textbox(type="number", value=1, label="Duration in seconds"),
    ],
    "audio",
).launch()
```

<iframe src="https://course-demos-generate-tone.hf.space" frameBorder="0" height="450" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>


### La méthode `launch()`

Jusqu'à présent, nous avons utilisé la méthode `launch()` pour lancer l'interface, mais nous n'avons pas vraiment discuté de ce qu'elle fait. 

Par défaut, la méthode `launch()` lancera la démo dans un serveur web qui tourne localement. Si vous exécutez votre code dans un *notebook* Jupyter ou Colab, *Gradio* va intégrer l'interface graphique de la démo dans le *notebook* afin que vous puissiez l'utiliser facilement.

Vous pouvez personnaliser le comportement de `launch()` à travers différents paramètres :

  - `inline` : si vous voulez afficher l'interface en ligne sur les *notebooks* Python.
  - `inbrowser` : pour lancer automatiquement l'interface dans un nouvel onglet du navigateur par défaut.
  - `share` : si vous voulez créer un lien public partageable depuis votre ordinateur pour l'interface. Un peu comme un lien Google Drive !

Nous couvrirons le paramètre `share` plus en détail dans la section suivante !

## ✏️ Appliquons ça !

Construisons une interface qui vous permette de faire la démonstration d'un modèle de **reconnaissance vocale**.
Pour rendre la chose intéressante, nous accepterons *soit* une entrée micro, soit un fichier téléchargé.

Comme d'habitude, nous allons charger notre modèle de reconnaissance vocale en utilisant la fonction `pipeline()` de 🤗 *Transformers*. 
Si vous avez besoin d'un rafraîchissement rapide, vous pouvez revenir à [cette section du chapitre 1](/course/fr/chapter1/3). Ensuite, nous allons implémenter une fonction `transcribe_audio()` qui traite l'audio et retourne la transcription (en anglais). Enfin, nous allons envelopper cette fonction dans une `Interface` avec les composants `Audio` pour les entrées et juste le texte pour la sortie. Au total, le code de cette application est le suivant :

```py
from transformers import pipeline
import gradio as gr

model = pipeline("automatic-speech-recognition")


def transcribe_audio(mic=None, file=None):
    if mic is not None:
        audio = mic
    elif file is not None:
        audio = file
    else:
        return "You must either provide a mic recording or a file"
    transcription = model(audio)["text"]
    return transcription


gr.Interface(
    fn=transcribe_audio,
    inputs=[
        gr.Audio(source="microphone", type="filepath", optional=True),
        gr.Audio(source="upload", type="filepath", optional=True),
    ],
    outputs="text",
).launch()
```

Si votre navigateur ne vous demande pas l'autorisation pour accéder au microphone, <a href="https://huggingface.co/spaces/course-demos/audio-reverse" target="_blank">ouvrez la démo dans un onglet séparé</a>.

<iframe src="https://course-demos-asr.hf.space" frameBorder="0" height="550" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Voilà, c'est fait ! Vous pouvez maintenant utiliser cette interface pour transcrire de l'audio. Remarquez ici qu'en passant le paramètre `optional` à `True`, nous permettons à l'utilisateur de soit fournir un microphone ou un fichier audio (ou aucun des deux, mais cela retournera un message d'erreur).

Continuez pour voir comment partager votre interface avec d'autres !


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter9/3.mdx" />

### Fonctionnalités avancées de l'interface
https://huggingface.co/learn/course/fr/chapter9/6.md

# Fonctionnalités avancées de l'interface

<CourseFloatingBanner chapter={9}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter9/section6.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter9/section6.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter9/section6.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter9/section6.ipynb"},
]} />

Maintenant que nous pouvons construire et partager une interface de base, explorons quelques fonctionnalités plus avancées comme l'état, l'interprétation et l'authentification.

### Utilisation de l'état pour faire persister les données

*Gradio* supporte *l'état de session* où les données persistent à travers plusieurs soumissions dans un chargement de page. L'état de session est utile pour construire des démos où vous souhaitez faire persister les données au fur et à mesure que l'utilisateur interagit avec le modèle (par exemple des chatbots). Notez que l'état de session ne partage pas les données entre les différents utilisateurs de votre modèle.

Pour stocker des données dans un état de session, vous devez faire trois choses : 

- Passez un *paramètre supplémentaire* dans votre fonction, qui représente l'état de l'interface. 
- A la fin de la fonction, renvoyer la valeur mise à jour de l'état comme une *valeur de retour supplémentaire*.
- Ajoutez les composants "state" input et "state" output lors de la création de votre `Interface`. 

Voir l'exemple de chatbot ci-dessous :

```py
import random

import gradio as gr


def chat(message, history):
    history = history or []
    if message.startswith("How many"):
        response = random.randint(1, 10)
    elif message.startswith("How"):
        response = random.choice(["Great", "Good", "Okay", "Bad"])
    elif message.startswith("Where"):
        response = random.choice(["Here", "There", "Somewhere"])
    else:
        response = "I don't know"
    history.append((message, response))
    return history, history


iface = gr.Interface(
    chat,
    ["text", "state"],
    ["chatbot", "state"],
    allow_screenshot=False,
    allow_flagging="never",
)
iface.launch()
```

<iframe src="https://course-demos-Chatbot-Demo.hf.space" frameBorder="0" height="350" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>
Remarquez comment l'état du composant de sortie persiste entre les soumissions. 
Remarque : vous pouvez transmettre une valeur par défaut au paramètre state, qui est utilisée comme valeur initiale de l'état. 

### Utilisation de l'interprétation pour comprendre les prédictions

La plupart des modèles d'apprentissage automatique sont des boîtes noires et la logique interne de la fonction est cachée à l'utilisateur final. Pour encourager la transparence, nous avons fait en sorte qu'il soit très facile d'ajouter l'interprétation à votre modèle en définissant simplement le mot-clé interprétation dans la classe Interface par défaut. Cela permet à vos utilisateurs de comprendre quelles parties de l'entrée sont responsables de la sortie. Jetez un coup d'œil à l'interface simple ci-dessous qui montre un classifieur d'images incluant l'interprétation :

```py
import requests
import tensorflow as tf

import gradio as gr

inception_net = tf.keras.applications.MobileNetV2()  # charger le modèle

# Télécharger des étiquettes lisibles par l'homme pour ImageNet
response = requests.get("https://git.io/JJkYN")
labels = response.text.split("\n")


def classify_image(inp):
    inp = inp.reshape((-1, 224, 224, 3))
    inp = tf.keras.applications.mobilenet_v2.preprocess_input(inp)
    prediction = inception_net.predict(inp).flatten()
    return {labels[i]: float(prediction[i]) for i in range(1000)}


image = gr.Image(shape=(224, 224))
label = gr.Label(num_top_classes=3)

title = "Gradio Image Classifiction + Interpretation Example"
gr.Interface(
    fn=classify_image, inputs=image, outputs=label, interpretation="default", title=title
).launch()
```

Testez la fonction d'interprétation en soumettant une entrée puis en cliquant sur « Interpréter » sous le composant de sortie.

<iframe src="https://hf.space/gradioiframe/akhaliq/gradio-image-interpretation/+" frameBorder="0" height="570" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

En plus de la méthode d'interprétation par défaut fournie par *Gradio*, vous pouvez également spécifier `shap` pour le paramètre `interpretation` et définir le paramètre `num_shap`. Ceci utilise l'interprétation basée sur Shapley, dont vous pouvez lire plus sur [ici](https://christophm.github.io/interpretable-ml-book/shap.html).
Enfin, vous pouvez aussi passer votre propre fonction d'interprétation dans le paramètre `interpretation`. Vous trouverez un exemple dans la page de démarrage de *Gradio* [ici](https://gradio.app/getting_started/).


### Ajouter l'authentification

Vous pouvez vouloir ajouter une authentification à votre interface *Gradio* afin de contrôler qui peut accéder et utiliser votre démo.

L'authentification peut être ajoutée en fournissant une liste de tuples de nom d'utilisateur/mot de passe au paramètre `auth` de la méthode `launch()`. Pour une gestion plus complexe de l'authentification, vous pouvez passer une fonction qui prend un nom d'utilisateur et un mot de passe comme arguments, et retourne `True` pour permettre l'authentification, `False` sinon.

Prenons la démo de classification d'images ci-dessus et ajoutons l'authentification :

```py
import requests
import tensorflow as tf

import gradio as gr

inception_net = tf.keras.applications.MobileNetV2()  # charger le modèle

# Télécharger des étiquettes lisibles par l'homme pour ImageNet
response = requests.get("https://git.io/JJkYN")
labels = response.text.split("\n")


def classify_image(inp):
    inp = inp.reshape((-1, 224, 224, 3))
    inp = tf.keras.applications.mobilenet_v2.preprocess_input(inp)
    prediction = inception_net.predict(inp).flatten()
    return {labels[i]: float(prediction[i]) for i in range(1000)}


image = gr.Image(shape=(224, 224))
label = gr.Label(num_top_classes=3)

title = "Gradio Image Classifiction + Interpretation Example"
gr.Interface(
    fn=classify_image, inputs=image, outputs=label, interpretation="default", title=title
).launch(auth=("admin", "pass1234"))
```

<iframe src="https://course-demos-gradio-image-interpretation.hf.space" frameBorder="0" height="570" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Ceci conclut notre plongée dans la classe `Interface` de *Gradio*. Comme nous l'avons vu, cette classe permet de créer facilement des démos d'apprentissage automatique en quelques lignes de code Python. Cependant, vous voudrez parfois personnaliser votre démo en changeant la mise en page ou en enchaînant plusieurs fonctions de prédiction. Ne serait-il pas agréable de pouvoir diviser l'interface en blocs personnalisables ? Heureusement, c'est possible ! C'est le sujet de la dernière section.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter9/6.mdx" />

### <i>Gradio</i>, coché !
https://huggingface.co/learn/course/fr/chapter9/8.md

# <i>Gradio</i>, coché !

<CourseFloatingBanner
    chapter={9}
    classNames="absolute z-10 right-0 top-0"
/>

Ceci conclut le chapitre sur la construction de démos d'apprentissage automatique avec *Gradio*. Nous espérons que vous l'avez apprécié ! Pour récapituler, dans ce chapitre nous avons appris :

- à créer des démos *Gradio* avec l'API `Interface` de haut niveau et comment configurer les différentes modalités d'entrée et de sortie,
- les différentes manières de partager les démos *Gradio*, à travers des liens temporaires et l'hébergement sur [*Hugging Face Spaces*](https://huggingface.co/spaces),
- comment intégrer les démos *Gradio* avec le *Hub* et *Spaces*,
- des fonctionnalités avancées comme le stockage de l'état dans une démo ou l'authentification,
- comment avoir un contrôle total sur le flux de données et la mise en page de votre démo avec les *Blocks*.

Si vous souhaitez tester votre compréhension des concepts abordés dans ce chapitre, consultez le quiz dans la section suivante !

## Où aller ensuite ?

Si vous voulez en savoir plus à propos de Gradio, vous pouvez :
- Jeter un coup d'œil à la page [Demos](https://github.com/gradio-app/gradio/tree/main/demo) dans le dépôt GitHub pour consulter beaucoup d'exemples.
- Voir la page [Guides](https://gradio.app/guides/) où vous trouverez des guides sur les fonctionnalités avancées.
- Consulter la page [Docs](https://gradio.app/docs/) pour connaître les détails.

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter9/8.mdx" />

### Partager ses démos avec les autres
https://huggingface.co/learn/course/fr/chapter9/4.md

# Partager ses démos avec les autres

<CourseFloatingBanner chapter={9}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter9/section4.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter9/section4.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter9/section4.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter9/section4.ipynb"},
]} />

Maintenant que vous avez construit une démo, vous voudrez probablement la partager à d'autres personnes. Les démos *Gradio* peuvent être partagées de deux façons : en utilisant un lien de partage temporaire (***temporary share link***) ou un hébergement permanent (***permanent hosting on Spaces***).

Nous aborderons ces deux approches sous peu. Mais avant de partager votre démo, vous voudrez peut-être la peaufiner 💅.

### Polir votre démo <i>Gradio</i>

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter9/gradio-demo-overview.png" alt="Overview of a gradio interface">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter9/gradio-demo-overview-dark.png" alt="Overview of a gradio interface">
</div>

Pour ajouter du contenu supplémentaire à votre démo, la classe `Interface` supporte quelques paramètres optionnels :
    - `title` : vous pouvez donner un titre à votre démo, qui apparaît _au-dessus_ des composants d'entrée et de sortie.
    - `description` : vous pouvez donner une description (en texte, Markdown, ou HTML) pour l'interface, qui apparaît au-dessus des composants d'entrée et de sortie et en dessous du titre.
    - `article` : vous pouvez également écrire un article étendu (en texte, Markdown ou HTML) expliquant l'interface. S'il est fourni, il apparaît _sous_ les composants d'entrée et de sortie.
    - `theme` : vous n'aimez pas les couleurs par défaut ? Définissez le thème pour utiliser une des couleurs suivantes : `default`, `huggingface`, `grass`, `peach`. Vous pouvez également ajouter le préfixe `dark-`, par exemple `dark-peach` pour un thème sombre (ou juste `dark` pour le thème sombre par défaut).
    - `examples` : pour rendre votre démo *beaucoup plus facile à utiliser*, vous pouvez fournir quelques exemples d'entrées pour la fonction. Ceux-ci apparaissent sous les composants de l'interface utilisateur et peuvent être utilisés pour remplir l'interface. Ils doivent être fournis sous forme de liste imbriquée, dans laquelle la liste extérieure est constituée d'exemples et chaque liste intérieure est constituée d'une entrée correspondant à chaque composant d'entrée.
    - `live` : si vous voulez que votre modèle soit relancé à chaque fois que l'entrée change, vous pouvez mettre `live=True`. Ceci est utile pour les modèles rapides (nous verrons un exemple à la fin de cette section).
En utilisant les options ci-dessus, nous obtenons une interface plus complète. Exécutez le code ci-dessous pour pouvoir discuter avec Rick et Morty :

```py
title = "Ask Rick a Question"  # "Posez une question à Rick"
description = """
The bot was trained to answer questions based on Rick and Morty dialogues. Ask Rick anything!
# Le robot a été entraîné à répondre à des questions basées sur les dialogues de Rick et Morty.
# Demandez à Rick ce que vous voulez !
<img src="https://huggingface.co/spaces/course-demos/Rick_and_Morty_QA/resolve/main/rick.png" width=200px>
"""

article = "Check out [the original Rick and Morty Bot](https://huggingface.co/spaces/kingabzpro/Rick_and_Morty_Bot) that this demo is based off of."
# Jetez un coup d'œil au [bot original Rick et Morty](https://huggingface.co/spaces/kingabzpro/Rick_and_Morty_Bot) sur lequel cette démo est basée.

gr.Interface(
    fn=predict,
    inputs="textbox",
    outputs="text",
    title=title,
    description=description,
    article=article,
    examples=[["What are you doing?"], ["Where should we time travel to?"]],
    # ["Que faites-vous ?"], ["Où devrions-nous voyager dans le temps ?"]
).launch()
```

En utilisant les options ci-dessus, nous obtenons une interface plus complète. Essayez l'interface ci-dessous :

<iframe src="https://course-demos-Rick-and-Morty-QA.hf.space" frameBorder="0" height="800" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

### Partager votre démo avec des liens temporaires
Maintenant que nous avons une démo fonctionnelle de notre modèle d'apprentissage automatique, apprenons à partager facilement un lien vers notre interface.
Les interfaces peuvent être facilement partagées publiquement en mettant `share=True` dans la méthode `launch()` :

```python
gr.Interface(classify_image, "image", "label").launch(share=True)
```
        
Cela génère un lien public et partageable que vous pouvez envoyer à n'importe qui ! Lorsque vous envoyez ce lien, l'utilisateur de l'autre côté peut essayer le modèle dans son navigateur pendant 72 heures au maximum. Le traitement s'effectuant sur votre appareil (tant qu'il reste allumé !), vous n'avez pas à vous soucier de la mise en place de dépendances. Si vous travaillez à partir d'un *notebook* Google Colab, un lien de partage est toujours créé automatiquement. Il ressemble généralement à quelque chose comme ceci : **XXXXX.gradio.app**. Bien que le lien soit servi par un lien *Gradio*, nous ne sommes qu'un proxy pour votre serveur local, et nous ne stockons pas les données envoyées par les interfaces.

Gardez cependant à l'esprit que ces liens sont accessibles au public, ce qui signifie que n'importe qui peut utiliser votre modèle pour la prédiction ! Par conséquent, assurez-vous de ne pas exposer d'informations sensibles à travers les fonctions que vous écrivez, ou de permettre que des changements critiques se produisent sur votre appareil. Si vous définissez `share=False` (la valeur par défaut), seul un lien local est créé.

### Hébergement de votre démo sur <i>Hugging Face Spaces</i>

Un lien de partage que vous pouvez passer à vos collègues est cool, mais comment pouvez-vous héberger de façon permanente votre démo et la faire exister dans son propre « espace » sur internet ?

*Hugging Face Spaces* fournit l'infrastructure pour héberger de façon permanente votre démo *Gradio* sur internet et **gratuitement** ! *Spaces* vous permet de créer et de pousser vers un dépôt (public ou privé) le code de votre interface *Gradio*. Il sera placé dans un fichier `app.py`. [Lisez ce tutoriel étape par étape](https://huggingface.co/blog/gradio-spaces) pour commencer ou regardez la vidéo ci-dessous.

<Youtube id="LS9Y2wDVI0k" />

## ✏️ Appliquons ça !

En utilisant ce que nous avons appris dans les sections précédentes, créons la démo de reconnaissance de croquis que nous avons décrit dans la [section un de ce chapitre](/course/fr/chapter9/1). Ajoutons quelques personnalisations à notre interface et définissons `share=True` pour créer un lien public que nous pouvons faire circuler.

Nous pouvons charger les étiquettes depuis [class_names.txt](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/class_names.txt) et charger le modèle Pytorch pré-entraîné depuis [pytorch_model.bin](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/pytorch_model.bin). Téléchargez ces fichiers en suivant le lien et en cliquant sur « *download* » dans le coin supérieur gauche de l'aperçu du fichier. Regardons le code ci-dessous pour voir comment nous utilisons ces fichiers pour charger notre modèle et créer une fonction `predict()` :

```py
from pathlib import Path
import torch
import gradio as gr
from torch import nn

LABELS = Path("class_names.txt").read_text().splitlines()

model = nn.Sequential(
    nn.Conv2d(1, 32, 3, padding="same"),
    nn.ReLU(),
    nn.MaxPool2d(2),
    nn.Conv2d(32, 64, 3, padding="same"),
    nn.ReLU(),
    nn.MaxPool2d(2),
    nn.Conv2d(64, 128, 3, padding="same"),
    nn.ReLU(),
    nn.MaxPool2d(2),
    nn.Flatten(),
    nn.Linear(1152, 256),
    nn.ReLU(),
    nn.Linear(256, len(LABELS)),
)
state_dict = torch.load("pytorch_model.bin", map_location="cpu")
model.load_state_dict(state_dict, strict=False)
model.eval()


def predict(im):
    x = torch.tensor(im, dtype=torch.float32).unsqueeze(0).unsqueeze(0) / 255.0
    with torch.no_grad():
        out = model(x)
    probabilities = torch.nn.functional.softmax(out[0], dim=0)
    values, indices = torch.topk(probabilities, 5)
    return {LABELS[i]: v.item() for i, v in zip(indices, values)}
```

Maintenant que nous avons une fonction `predict()`. La prochaine étape est de définir et de lancer notre interface *Gradio* :

```py
interface = gr.Interface(
    predict,
    inputs="sketchpad",
    outputs="label",
    theme="huggingface",
    title="Sketch Recognition",
    description="Who wants to play Pictionary? Draw a common object like a shovel or a laptop, and the algorithm will guess in real time!",
    # Qui veut jouer au Pictionary ? Dessinez un objet courant comme une pelle ou un ordinateur portable, et l'algorithme le devinera en temps réel !
    article="<p style='text-align: center'>Sketch Recognition | Demo Model</p>",
    live=True,
)
interface.launch(share=True)
```

<iframe src="https://course-demos-Sketch-Recognition.hf.space" frameBorder="0" height="650" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>


Remarquez le paramètre `live=True` dans `Interface`, qui signifie que la démo de sketchs fait une prédiction chaque fois que quelqu'un dessine sur le bloc (pas de bouton de soumission !). 

De plus, nous avons également défini l'argument `share=True` dans la méthode `launch()`. 
Cela créera un lien public que vous pourrez envoyer à n'importe qui ! Lorsque vous envoyez ce lien, l'utilisateur de l'autre côté peut essayer le modèle de reconnaissance de croquis. Pour réitérer, vous pouvez également héberger le modèle sur *Hugging Face Spaces*, ce qui nous permet d'intégrer la démo ci-dessus.

La prochaine fois, nous couvrirons d'autres façons dont *Gradio* peut être utilisé avec l'écosystème d'*Hugging Face* !


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter9/4.mdx" />

### Introduction à la classe <i>Blocks</i>
https://huggingface.co/learn/course/fr/chapter9/7.md

# Introduction à la classe <i>Blocks</i>

<CourseFloatingBanner chapter={9}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter9/section7.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter9/section7.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter9/section7.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter9/section7.ipynb"},
]} />

Dans les sections précédentes, nous avons exploré et créé des démos en utilisant la classe `Interface`. Dans cette section, nous allons présenter une **nouvelle** API de bas niveau appelée `gradio.Blocks`.

Quelle est la différence entre `Interface` et `Blocks` ?

- ⚡ `Interface` : une API de haut niveau qui vous permet de créer une démo complète d'apprentissage automatique simplement en fournissant une liste d'entrées et de sorties.

- 🧱 `Blocks` : une API de bas niveau qui vous permet d'avoir un contrôle total sur les flux de données et la disposition de votre application. Vous pouvez construire des applications très complexes, en plusieurs étapes, en utilisant `Blocks`.


### Pourquoi <i>Blocks</i> 🧱 ?

Comme nous l'avons vu dans les sections précédentes, la classe `Interface` vous permet de créer facilement des démos d'apprentissage automatique à part entière avec seulement quelques lignes de code. L'API `Interface` est extrêmement facile à utiliser, mais elle n'a pas la flexibilité qu'offre l'API `Blocks`. Par exemple, vous pourriez vouloir :

- regrouper des démos connexes sous forme d'onglets multiples dans une application web,
- modifier la mise en page de votre démo, par exemple pour spécifier l'emplacement des entrées et des sorties,
- disposer d'interfaces multi-étapes dans lesquelles la sortie d'un modèle devient l'entrée du modèle suivant ou avoir des flux de données plus flexibles en général,
- modifier les propriétés d'un composant (par exemple, les choix dans une liste déroulante) ou sa visibilité en fonction des entrées de l'utilisateur.

Nous allons explorer tous ces concepts ci-dessous.

### Création d'une démo simple en utilisant <i>Blocks</i>

Après avoir installé *Gradio*, exécutez le code ci-dessous sous forme de script Python, de *notebook* Jupyter ou de *notebook* Colab.

```py
import gradio as gr


def flip_text(x):
    return x[::-1]


demo = gr.Blocks()

with demo:
    gr.Markdown(
        """
    # Flip Text!
    Start typing below to see the output.
    """
    )
    input = gr.Textbox(placeholder="Flip this text")
    output = gr.Textbox()

    input.change(fn=flip_text, inputs=input, outputs=output)

demo.launch()
```

<iframe src="https://course-demos-flip-text.hf.space" frameBorder="0" height="400" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Ce simple exemple ci-dessus introduit 4 concepts qui sous-tendent les *Blocks* :

1. Les *Blocks* vous permettent de construire des applications web qui combinent Markdown, HTML, boutons et composants interactifs simplement en instanciant des objets en Python dans un contexte `with gradio.Blocks`. 

<Tip>
🙋Si vous n'êtes pas familier avec l'instruction `with` en Python, nous vous recommandons de consulter l'excellent <a href="https://realpython.com/python-with-statement/">tutoriel</a> de Real Python. Revenez ici après l'avoir lu 🤗
</Tip>

L'ordre dans lequel vous instanciez les composants est important car chaque élément est restitué dans l'application Web dans l'ordre où il a été créé. (Les mises en page plus complexes sont abordées ci-dessous)

2. Vous pouvez définir des fonctions Python ordinaires n'importe où dans votre code et les exécuter avec des entrées utilisateur en utilisant les `Blocks`. Dans notre exemple, nous avons une fonction simple qui inverse le texte entré mais vous pouvez écrire n'importe quelle fonction Python, du simple calcul au traitement des prédictions d'un modèle d'apprentissage automatique.

3. Vous pouvez assigner des événements à n'importe quel composant `Blocks`. Ainsi, votre fonction sera exécutée lorsque le composant sera cliqué, modifié, etc. Lorsque vous assignez un événement, vous passez trois paramètres : 
- `fn` : la fonction qui doit être appelée,  
- `inputs` : la (liste) des composants d'entrée  
- `outputs` : la (liste) des composants de sortie qui doivent être appelés.  
   Dans l'exemple ci-dessus, nous exécutons la fonction `flip_text()` lorsque la valeur de la `Textbox` nommée input `input` change. L'événement lit la valeur dans `input`, la passe comme paramètre de nom à `flip_text()`, qui renvoie alors une valeur qui est assignée à notre seconde `Textbox` nommée `output`.
   Pour voir la liste des événements que chaque composant supporte, consultez la [documentation](https://www.gradio.app/docs/) de *Gradio*.

4. *Blocks* détermine automatiquement si un composant doit être interactif (accepter les entrées de l'utilisateur) ou non, en fonction des déclencheurs d'événements que vous définissez. Dans notre exemple, la première zone de texte est interactive, puisque sa valeur est utilisée par la fonction `flip_text()`. La deuxième zone de texte n'est pas interactive, puisque sa valeur n'est jamais utilisée comme entrée. Dans certains cas, vous voudrez peut-être passer outre, ce que vous pouvez faire en passant un booléen au paramètre `interactive` du composant (par exemple, `gr.Textbox(placeholder="Flip this text", interactive=True)`).


### Personnaliser la mise en page de votre démo

Comment pouvons-nous utiliser `Blocks` pour personnaliser la mise en page de notre démo ? Par défaut, `Blocks` affiche verticalement dans une colonne les composants que vous créez. Vous pouvez changer cela en créant des colonnes supplémentaires `avec gradio.Column():` ou des lignes `avec gradio.Row():` et en créant des composants dans ces contextes.

Voici ce que vous devez garder à l'esprit : tout composant créé sous une `Column` (c'est aussi le défaut) sera disposé verticalement. Tout composant créé sous une `Row` sera disposé horizontalement, comme le [modèle flexbox dans le développement web](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox).

Enfin, vous pouvez également créer des onglets pour votre démo en utilisant le gestionnaire de contexte `with gradio.Tabs()`. Dans ce contexte, vous pouvez créer plusieurs onglets en spécifiant des enfants `with gradio.TabItem(name_of_tab):`. Tout composant créé dans un contexte `with gradio.TabItem(name_of_tab):` apparaît dans cet onglet.

Maintenant, ajoutons une fonction `flip_image()` à notre démo et ajoutons un nouvel onglet qui retourne les images. Vous trouverez ci-dessous un exemple avec 2 onglets et utilisant également une `Row` :

```py
import numpy as np
import gradio as gr

demo = gr.Blocks()


def flip_text(x):
    return x[::-1]


def flip_image(x):
    return np.fliplr(x)


with demo:
    gr.Markdown("Flip text or image files using this demo.")
    with gr.Tabs():
        with gr.TabItem("Flip Text"):
            with gr.Row():
                text_input = gr.Textbox()
                text_output = gr.Textbox()
            text_button = gr.Button("Flip")
        with gr.TabItem("Flip Image"):
            with gr.Row():
                image_input = gr.Image()
                image_output = gr.Image()
            image_button = gr.Button("Flip")

    text_button.click(flip_text, inputs=text_input, outputs=text_output)
    image_button.click(flip_image, inputs=image_input, outputs=image_output)

demo.launch()
```

<iframe src="https://course-demos-flip-text-image.hf.space" frameBorder="0" height="450" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>


Vous remarquerez que dans cet exemple, nous avons également créé un composant `Button` dans chaque onglet et avons assigné un événement de clic à chaque bouton qui est l'élément qui exécute réellement la fonction.

### Exploration des événements et de l'état

De la même manière que vous pouvez contrôler la mise en page, `Blocks` vous donne un contrôle fin sur les événements qui déclenchent les appels de fonction. Chaque composant et de nombreux layouts ont des événements spécifiques qu'ils supportent.

Par exemple, le composant `Textbox` a 2 événements : `change()` (lorsque la valeur contenue dans la zone de texte change), et `submit()` (lorsqu'un utilisateur appuie sur la touche Entrée alors qu'il est concentré sur la zone de texte). Les composants plus complexes peuvent avoir encore plus d'événements : par exemple, le composant `Audio` a aussi des événements séparés pour quand le fichier audio est joué, effacé, mis en pause, etc. Consultez la documentation pour connaître les événements pris en charge par chaque composant.

Vous pouvez attacher un déclencheur d'événement à aucun, un ou plusieurs de ces événements. Vous créez un déclencheur d'événement en appelant le nom de l'événement sur l'instance du composant comme une fonction. Par exemple, `textbox.change(...)` ou `btn.click(...)`. La fonction prend trois paramètres, comme indiqué ci-dessus :

- `fn` : la fonction à exécuter
- `inputs` : une (liste de) composante(s) dont les valeurs doivent être fournies comme paramètres d'entrée à la fonction. La valeur de chaque composant est mise en correspondance avec le paramètre de fonction correspondant, dans l'ordre. Ce paramètre peut être `None` si la fonction ne prend aucun paramètre.
- `outputs` : un (liste de) composant(s) dont les valeurs doivent être mises à jour en fonction des valeurs retournées par la fonction. Chaque valeur de retour met à jour la valeur du composant correspondant, dans l'ordre. Ce paramètre peut être None si la fonction ne retourne rien.

Vous pouvez même faire en sorte que le composant d'entrée et de sortie soit le même composant, comme nous le faisons dans cet exemple qui utilise un modèle GPT pour compléter du texte :

```py
import gradio as gr

api = gr.Interface.load("huggingface/EleutherAI/gpt-j-6B")


def complete_with_gpt(text):
    # Utilise les 50 derniers caractères du texte comme contexte.
    return text[:-50] + api(text[-50:])


with gr.Blocks() as demo:
    textbox = gr.Textbox(placeholder="Type here and press enter...", lines=4)
    btn = gr.Button("Generate")

    btn.click(complete_with_gpt, textbox, textbox)

demo.launch()
```

<iframe src="https://course-demos-blocks-gpt.hf.space" frameBorder="0" height="300" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

### Création de démos multi-étapes

Dans certains cas, vous pouvez vouloir une _démo multi-étapes_, dans laquelle vous réutilisez la sortie d'une fonction comme entrée de la suivante. C'est très facile à faire avec les `Blocks`, car vous pouvez utiliser un composant pour l'entrée d'un déclencheur d'événement mais la sortie d'un autre. Regardez le composant texte dans l'exemple ci-dessous, sa valeur est le résultat d'un modèle de conversion de la parole en texte, mais il est également transmis à un modèle d'analyse des sentiments :

```py
from transformers import pipeline

import gradio as gr

asr = pipeline("automatic-speech-recognition", "facebook/wav2vec2-base-960h")
classifier = pipeline("text-classification")


def speech_to_text(speech):
    text = asr(speech)["text"]
    return text


def text_to_sentiment(text):
    return classifier(text)[0]["label"]


demo = gr.Blocks()

with demo:
    audio_file = gr.Audio(type="filepath")
    text = gr.Textbox()
    label = gr.Label()

    b1 = gr.Button("Recognize Speech")
    b2 = gr.Button("Classify Sentiment")

    b1.click(speech_to_text, inputs=audio_file, outputs=text)
    b2.click(text_to_sentiment, inputs=text, outputs=label)

demo.launch()
```

<iframe src="https://course-demos-blocks-multi-step.hf.space" frameBorder="0" height="550" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

### Mise à jour des propriétés des composants

Jusqu'à présent, nous avons vu comment créer des événements pour mettre à jour la valeur d'un autre composant. Mais que se passe-t-il si vous voulez modifier d'autres propriétés d'un composant, comme la visibilité d'une zone de texte ou les choix dans un groupe de boutons radio ? Vous pouvez le faire en renvoyant la méthode `update()` d'une classe de composant au lieu d'une valeur de retour normale de votre fonction.

L'exemple le plus facile à illustrer est le suivant :

```py
import gradio as gr


def change_textbox(choice):
    if choice == "short":
        return gr.Textbox.update(lines=2, visible=True)
    elif choice == "long":
        return gr.Textbox.update(lines=8, visible=True)
    else:
        return gr.Textbox.update(visible=False)


with gr.Blocks() as block:
    radio = gr.Radio(
        ["short", "long", "none"], label="What kind of essay would you like to write?"
    )
    text = gr.Textbox(lines=2, interactive=True)

    radio.change(fn=change_textbox, inputs=radio, outputs=text)
    block.launch()
```

<iframe src="https://course-demos-blocks-update-component-properti-833c723.hf.space" frameBorder="0" height="300" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Nous venons d'explorer tous les concepts de base des `Blocks` ! Tout comme avec `Interface`, vous pouvez créer des démos sympas qui peuvent être partagées en utilisant `share=True` dans la méthode `launch()` ou déployées sur [*Spaces*](https://huggingface.co/spaces).


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter9/7.mdx" />

### Construire votre première démo
https://huggingface.co/learn/course/fr/chapter9/2.md

# Construire votre première démo

<CourseFloatingBanner chapter={9}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter9/section2.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter9/section2.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter9/section2.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter9/section2.ipynb"},
]} />

Commençons par installer *Gradio* ! Comme il s'agit d'un *package* Python, il suffit de l'exécuter :

`$ pip install gradio `

Vous pouvez exécuter *Gradio* n'importe où, que ce soit dans votre IDE Python préféré, dans des *notebooks* ou même dans Google Colab 🤯 !
Alors installez *Gradio* partout où vous exécutez Python !

Commençons par un exemple simple de type « *Hello World* » pour nous familiariser avec la syntaxe de *Gradio*  :

```py
import gradio as gr


def greet(name):
    return "Hello " + name


demo = gr.Interface(fn=greet, inputs="text", outputs="text")

demo.launch()
```

Parcourons le code ci-dessus :

- D'abord, nous définissons une fonction appelée `greet()`. Dans ce cas, c'est une simple fonction qui ajoute « *Hello* » devant votre nom, mais cela peut être *n'importe quelle* fonction Python en général. Par exemple, dans les applications d'apprentissage automatique, cette fonction pourrait *appeler un modèle pour faire une prédiction* sur une entrée et retourner la sortie.
- Ensuite, nous créons une `Interface` *Gradio* avec trois arguments, `fn`, `inputs`, et `outputs`. Ces arguments définissent la fonction de prédiction, ainsi que le _type_ de composants d'entrée et de sortie que nous souhaitons. Dans notre cas, les deux composants sont de simples boîtes de texte. 
- Nous appelons ensuite la méthode `launch()` sur l'`Interface` que nous avons créée. 

Si vous exécutez ce code, l'interface ci-dessous apparaîtra automatiquement dans un *notebook* Jupyter/Colab ou dans un navigateur sur **[http://localhost:7860](http://localhost:7860/)** si vous l'exécutez à partir d'un script.

<iframe src="https://course-demos-hello-world.hf.space" frameBorder="0" height="250" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Essayez d'utiliser cette interface maintenant avec votre propre nom ou une autre entrée !

Vous remarquerez que dedans, *Gradio* a automatiquement déduit le nom du paramètre d'entrée (`name`) et l'a appliqué comme étiquette au dessus de la zone de texte. Que faire si vous souhaitez changer cela ?
Ou si vous souhaitez personnaliser la zone de texte d'une autre manière ? Dans ce cas, vous pouvez instancier un objet de classe représentant le composant de saisie. 

Jetez un coup d'œil à l'exemple ci-dessous :

```py
import gradio as gr


def greet(name):
    return "Hello " + name


# Nous instancions la classe Textbox
textbox = gr.Textbox(label="Type your name here:", placeholder="John Doe", lines=2)

gr.Interface(fn=greet, inputs=textbox, outputs="text").launch()
```

<iframe src="https://course-demos-hello-world-custom.hf.space" frameBorder="0" height="300" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Ici, nous avons créé une zone de texte d'entrée avec une étiquette, un espace réservé et un nombre de lignes défini.
Vous pourriez faire la même chose pour la zone de texte de sortie, mais nous allons laisser cela pour le moment.

Nous avons vu qu'avec seulement quelques lignes de code, *Gradio* vous permet de créer une interface simple autour de n'importe quelle fonction 
avec n'importe quel type d'entrées ou de sorties. Dans cette section, nous avons commencé par une simple boîte de texte mais dans les sections suivantes, nous couvrirons d'autres types d'entrées et de sorties. Voyons maintenant comment inclure un peu de NLP dans une application *Gradio*.


## 🤖 Inclure les prédictions du modèle

Construisons maintenant une interface simple qui permet de faire une démo d'un modèle de **génération de texte** comme le GPT-2.

Nous allons charger notre modèle en utilisant la fonction `pipeline()` de 🤗 *Transformers*. 
Si vous avez besoin d'un rafraîchissement rapide, vous pouvez revenir à [cette section du chapitre 1](/course/fr/chapter1/3#text-generation).   

Tout d'abord, nous définissons une fonction de prédiction qui prend une invite de texte et renvoie la complétion du texte :

```py
from transformers import pipeline

model = pipeline("text-generation")


def predict(prompt):
    completion = model(prompt)[0]["generated_text"]
    return completion
```

Cette fonction complète le texte que vous fournissez, et vous pouvez l'exécuter avec les votres pour voir comment elle fonctionne. Voici un exemple (vous obtiendrez peut-être un résultat différent) :


```
predict("My favorite programming language is") # Mon langage de programmation préféré est
```

```
>> My favorite programming language is Haskell. I really enjoyed the Haskell language, but it doesn't have all the features that can be applied to any other language. For example, all it does is compile to a byte array.
#  Mon langage de programmation préféré est Haskell. J'ai vraiment apprécié le langage Haskell, mais il n'a pas toutes les caractéristiques que l'on peut appliquer à n'importe quel autre langage. Par exemple, il ne fait que compiler un tableau d'octets.
```

Maintenant que nous avons une fonction pour générer des prédictions, nous pouvons créer et lancer une `Interface` de la même manière que nous l'avons fait précédemment :

```py
import gradio as gr

gr.Interface(fn=predict, inputs="text", outputs="text").launch()
```


C'est fait ! Vous pouvez maintenant utiliser cette interface pour générer du texte en utilisant le modèle GPT-2 comme indiqué ci-dessous 🤯.

<iframe src="https://course-demos-gpt-2.hf.space" frameBorder="0" height="250" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Continuez votre lecture du cours pour voir comment construire d'autres types de démos avec *Gradio* !


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter9/2.mdx" />

### Sessions en direct et ateliers
https://huggingface.co/learn/course/fr/events/1.md

# Sessions en direct et ateliers

Pour la parution des parties 1 et 2 du cours, nous avons organisé plusieurs sessions et ateliers de codage en direct. Vous trouverez ci-dessous les liens vers les enregistrements de ces sessions et ateliers.

## Sessions de codage en direct

Lors de la première session, Sylvain parcourt avec vous le chapitre 1 du cours, en l'expliquant étape par étape :

<div class="flex justify-center">
<Youtube id="aV4wfnIakSQ"/>
</div>

Lors de la deuxième session, c'est au tour de Lewis de présenter le chapitre 2 :

<div class="flex justify-center">
<Youtube id="qEl7eORxpFA"/>
</div>

Parce que le chapitre 2 est tellement cool, Sylvain a également fourni une présentation de ce chapitre !

<div class="flex justify-center">
<Youtube id="u4e8OGWYpPk"/>
</div>

Pour le chapitre 3, Lewis revient pour vous guider dans le code :

<div class="flex justify-center">
<Youtube id="Be4s0dsbavM"/>
</div>

Enfin, Omar conclut les sessions en direct liées à la première partie du cours en abordant le chapitre 4 :

<div class="flex justify-center">
<Youtube id="1ATVsyBxu1I"/>
</div>

## Ateliers

Dans le premier atelier, Merve accueille Lewis pour discuter de la section 7 du chapitre 7 sur le [*question answering*]( https://huggingface.co/course/chapter7/7?fw=pt).

<div class="flex justify-center">
<Youtube id="Ihgk8kGLpIE"/>
</div>

Pour le deuxième atelier, Merve reçoit Leandro pour parler de la section 6 du chapitre 7 sur [entraîner un modèle de langage causal à partir de zéro]( https://huggingface.co/course/chapter7/6?fw=pt) avec une application avec [CodeParrot](https://huggingface.co/codeparrot).

<div class="flex justify-center">
<Youtube id="ExUR7w6xe94"/>
</div>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/events/1.mdx" />

### Fête des blocs Gradio
https://huggingface.co/learn/course/fr/events/3.md

# Fête des blocs Gradio

Parallèlement à la publication du chapitre sur *Gradio* du cours, Hugging Face a organisé un événement communautaire sur la création de démonstrations d'apprentissage automatique à l'aide de la nouvelle fonctionnalité *Gradio Blocks*.

Vous pouvez trouver toutes les démos que la communauté a créées sous l'organisation [`Gradio-Blocks`](https://huggingface.co/Gradio-Blocks) sur le Hub. Voici la démonstration des gagnants :

**Langage naturel vers SQL**

<iframe src="https://huggingface.co/spaces/Gradio-Blocks/Words_To_SQL/+" frameBorder="0" height="640" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/events/3.mdx" />

### Événement pour le lancement de la partie 2
https://huggingface.co/learn/course/fr/events/2.md

# Événement pour le lancement de la partie 2

Pour la sortie de la deuxième partie du cours, nous avons organisé un événement en direct consistant en deux jours de conférences suivies d’un *sprint* de *finetuning*. Si vous l'avez manqué, vous pouvez rattraper les présentations qui sont toutes listées ci-dessous !

## Jour 1 : Une vue d'ensemble des <i>transformers</i> et comment les entraîner


**Thomas Wolf :** *L'apprentissage par transfert et la naissance de la bibliothèque 🤗 Transformers*

<div class="flex justify-center">
<Youtube id="wCYVeahJES0"/>
</div>

<p align="center">
<img src="https://i.imgur.com/9eq8oUi.png" alt="A visual summary of Thom's talk" width="80%"/>
</p>

Thomas Wolf est cofondateur et directeur scientifique d’Hugging Face. Les outils créés par Thomas Wolf et l'équipe d’Hugging Face sont utilisés par plus de 5 000 organismes de recherche, dont Facebook Artificial Intelligence Research, Google Research, DeepMind, Amazon Research, Apple, l'Allen Institute for Artificial Intelligence ainsi que la plupart des départements universitaires. Thomas Wolf est l'initiateur et le président principal de la plus grande collaboration de recherche qui ait jamais existé dans le domaine de l'intelligence artificielle : [« BigScience »](https://bigscience.huggingface.co), ainsi que d'un ensemble de [bibliothèques et outils](https://github.com/huggingface/) largement utilisés. Thomas Wolf est également un éducateur prolifique, un *leader* d'opinion dans le domaine de l'intelligence artificielle et du traitement du langage naturel, et un orateur régulièrement invité à des conférences dans le monde entier [https://thomwolf.io](https://thomwolf.io).

**Jay Alammar :** *Une introduction visuelle douce aux transformers*

<div class="flex justify-center">
<Youtube id="VzvG23gmcYU"/>
</div>

<p align="center">
<img src="https://i.imgur.com/rOZAuE9.png" alt="A visual summary of Jay's talk" width="80%"/>
</p>

Grâce à son blog d’apprentissage automatique très populaire, Jay a aidé des millions de chercheurs et d'ingénieurs à comprendre visuellement les outils et les concepts de l'apprentissage automatique, des plus élémentaires (qui se retrouvent dans les docs NumPy et Pandas) aux plus pointus (Transformer, BERT, GPT-3).

**Margaret Mitchell :** *Les valeurs dans le développement de l’apprentissage automatique*

<div class="flex justify-center">
<Youtube id="8j9HRMjh_s8"/>
</div>

<p align="center">
<img src="https://i.imgur.com/NuIsnY3.png" alt="A visual summary of Margaret's talk" width="80%"/>
</p>

Margaret Mitchell est une chercheuse travaillant sur l'IA éthique. Elle se concentre actuellement sur les tenants et aboutissants du développement de l'IA éthique dans le domaine de la technologie. Elle a publié plus de cinquante articles sur la génération de langage naturel, les technologies d'assistance, la vision par ordinateur et l'IA éthique. Elle détient plusieurs brevets dans le domaine de la génération de conversations et celui de la classification des sentiments. Elle a précédemment travaillé chez Google AI en tant que chercheuse où elle a fondé et codirigé le groupe d'IA éthique de Google. Ce groupe est axé sur la recherche fondamentale en matière d'IA éthique et l'opérationnalisation de d'IA éthique en interne à Google. Avant de rejoindre Google, elle a été chercheuse chez Microsoft Research où elle s'est concentrée sur la génération de la vision par ordinateur vers le langage et a été post-doc à Johns Hopkins où elle s'est concentrée sur la modélisation bayésienne et l'extraction d'informations. Elle est titulaire d'un doctorat en informatique de l'université d'Aberdeen et d'une maîtrise en linguistique informatique de l'université de Washington. Tout en obtenant ses diplômes, elle a également travaillé de 2005 à 2012 sur l'apprentissage automatique, les troubles neurologiques et les technologies d'assistance à l'Oregon Health and Science University. Elle a dirigé un certain nombre d'ateliers et d'initiatives au croisement de la diversité, de l'inclusion, de l'informatique et de l'éthique. Ses travaux ont été récompensés par le secrétaire à la défense Ash Carter et la Fondation américaine pour les aveugles, et ont été implémenté par plusieurs entreprises technologiques.

**Matthew Watson et Chen Qian :** *Les flux de travail en NLP avec Keras*

<div class="flex justify-center">
<Youtube id="gZIP-_2XYMM"/>
</div>

<p align="center">
<img src="https://i.imgur.com/1vD2az8.png" alt="A visual summary of Matt and Chen's talk" width="80%"/>
</p>

Matthew Watson est ingénieur en apprentissage automatique au sein de l'équipe Keras et se concentre sur les API de modélisation de haut niveau. Il a étudié l'infographie pendant ses études et a obtenu un master à l'université de Stanford. Il s'est orienté vers l'informatique après avoir étudié l'anglais. Il est passionné par le travail interdisciplinaire et par la volonté de rendre le traitement automatique des langues accessible à un public plus large.

Chen Qian est un ingénieur logiciel de l'équipe Keras spécialisé dans les API de modélisation de haut niveau. Chen est titulaire d'un master en génie électrique de l'université de Stanford et s'intéresse particulièrement à la simplification de l'implémentation du code des tâches d’apprentissage automatique et le passage à grande échelle de ces codes.


**Mark Saroufim :** *Comment entraîner un modèle avec PyTorch*

<div class="flex justify-center">
<Youtube id="KmvPlW2cbIo"/>
</div>

<p align="center">
<img src="https://i.imgur.com/TPmlkm8.png" alt="A visual summary of Mark's talk" width="80%"/>
</p>

Mark Saroufim est ingénieur partenaire chez PyTorch et travaille sur les outils de production OSS, notamment TorchServe et PyTorch Enterprise. Dans ses vies antérieures, Mark a été un scientifique appliqué et un chef de produit chez Graphcore, [yuri.ai](http://yuri.ai/), Microsoft et au JPL de la NASA. Sa principale passion est de rendre la programmation plus amusante.

**Jakob Uszkoreit :** *Ce n'est pas cassé alors <del>ne réparez pas</del> cassez tout*

<div class="flex justify-center">
<Youtube id="C6jweXYFHSA"/>
</div>

<p align="center">
<img src="https://i.imgur.com/5dWQeNB.png" alt="A visual summary of Jakob's talk" width="80%"/>
</p>

Jakob Uszkoreit est le cofondateur d'Inceptive. Inceptive conçoit des molécules d'ARN pour les vaccins et les thérapies en utilisant l'apprentissage profond à grande échelle. Le tout en boucle étroite avec des expériences à haut débit, dans le but de rendre les médicaments à base d'ARN plus accessibles, plus efficaces et plus largement applicables. Auparavant, Jakob a travaillé chez Google pendant plus de dix ans, dirigeant des équipes de recherche et de développement au sein de Google Brain, Research et Search, travaillant sur les fondamentaux de l'apprentissage profond, la vision par ordinateur, la compréhension du langage et la traduction automatique.

## Jour 2 : Les outils à utiliser


**Lewis Tunstall :** *Un entraînement simple avec la fonction *Trainer*  de la bibliotèque 🤗 Transformers*

<div class="flex justify-center">
<Youtube id="u--UVvH-LIQ"/>
</div>

Lewis est un ingénieur en apprentissage machine chez Hugging Face qui se concentre sur le développement d'outils open-source et les rend accessibles à la communauté. Il est également co-auteur du livre [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) paru chez O'Reilly. Vous pouvez le suivre sur Twitter (@_lewtun) pour des conseils et astuces en traitement du langage naturel !

**Matthew Carrigan :** *Nouvelles fonctionnalités en TensorFlow pour 🤗 Transformers et 🤗 Datasets*

<div class="flex justify-center">
<Youtube id="gQUlXp1691w"/>
</div>

Matt est responsable de la maintenance des modèles en TensorFlow chez *Transformers*. Il finira par mener un coup d'État contre la faction PyTorch en place Celui sera probablement coordonné via son compte Twitter @carrigmat.

**Lysandre Debut :** *Le Hub d’Hugging Face, un moyen de collaborer et de partager des projets d'apprentissage automatique*

<div class="flex justify-center">
<Youtube id="RBw1TmdEZp0"/>
</div>

<p align="center">
<img src="https://i.imgur.com/TarIPCz.png" alt="A visual summary of Lysandre's talk" width="80%"/>
</p>

Lysandre est ingénieur en apprentissage machine chez Hugging Face où il participe à de nombreux projets open source. Son objectif est de rendre l'apprentissage automatique accessible à tous en développant des outils puissants avec une API très simple.

**Lucile Saulnier :** *Avoir son propre tokenizer avec 🤗 Transformers & 🤗 Tokenizers*

<div class="flex justify-center">
<Youtube id="UkNmyTFKriI"/>
</div>

Lucile est ingénieure en apprentissage automatique chez Hugging Face où elle développe et soutient l'utilisation d'outils open source. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine du traitement du langage naturel tels que l’entraînement collaboratif et BigScience.

**Sylvain Gugger :** *Optimisez votre boucle d'entraînement PyTorch avec 
🤗 Accelerate*

<div class="flex justify-center">
<Youtube id="t8Krzu-nSeY"/>
</div>

Sylvain est ingénieur de recherche chez Hugging Face. Il est l'un des principaux mainteneurs de 🤗 Transformers et le développeur derrière 🤗 Accelerate. Il aime rendre l'apprentissage des modèles plus accessible.

**Merve Noyan :** *Présentez vos démonstrations de modèles avec 
🤗 Spaces*

<div class="flex justify-center">
<Youtube id="vbaKOa4UXoM"/>
</div>

Merve est *developer advocate* chez Hugging Face travaillant au développement d'outils et à la création de contenu autour d'eux afin de démocratiser l'apprentissage automatique pour tous.

**Abubakar Abid :** *Créer rapidement des applications d'apprentissage automatique*

<div class="flex justify-center">
<Youtube id="c7mle2yYpwQ"/>
</div>

<p align="center">
<img src="https://i.imgur.com/qWIFeiF.png" alt="A visual summary of Abubakar's talk" width="80%"/>
</p>

Abubakar Abid est le PDG de [Gradio](www.gradio.app). Il a obtenu sa licence en génie électrique et en informatique au MIT en 2015, et son doctorat en apprentissage automatique appliqué à Stanford en 2021. En tant que PDG de Gradio, Abubakar s'efforce de faciliter la démonstration, le débogage et le déploiement des modèles d'apprentissage automatique.

**Mathieu Desvé :** *AWS ML Vision : Rendre l'apprentissage automatique accessible à tous les clients*

<div class="flex justify-center">
<Youtube id="O2e3pXO4aRE"/>
</div>

<p align="center">
<img src="https://i.imgur.com/oLdZTKy.png" alt="A visual summary of Mathieu's talk" width="80%"/>
</p>

Passionné de technologie, il est un créateur pendant son temps libre. Il aime les défis et résoudre les problèmes des clients et des utilisateurs ainsi que travailler avec des personnes talentueuses pour apprendre chaque jour. Depuis 2004, il a occupé plusieurs postes, passant du frontend au backend, de l'infrastructure aux opérations et à la gestion. Il essaie de résoudre les problèmes techniques et de gestion courants de manière agile.

**Philipp Schmid :** *Entraînement dirigé avec Amazon SageMaker et 🤗 Transformers*

<div class="flex justify-center">
<Youtube id="yG6J2Zfo8iw"/>
</div>

Philipp Schmid est ingénieur en apprentissage machine et *Tech Lead* chez Hugging Face où il dirige la collaboration avec l'équipe Amazon SageMaker. Il est passionné par la démocratisation et la mise en production de modèles de traitement du langage naturel de pointe et par l'amélioration de la facilité d'utilisation de l'apprentissage profond.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/events/2.mdx" />

### <i>Tokenizer</i>, coché !
https://huggingface.co/learn/course/fr/chapter6/9.md

# <i>Tokenizer</i>, coché !

<CourseFloatingBanner
    chapter={6}
    classNames="absolute z-10 right-0 top-0"
/>

Bon travail pour finir ce chapitre !

Après cette plongée en profondeur dans les *tokenizers*, vous devriez :

- être capable d'entraîner un nouveau tokenizer en utilisant un ancien tokenizer comme modèle,
- comprendre comment utiliser les *offsets* pour faire correspondre la position des *tokens* à l'étendue de texte d'origine,
- connaître les différences entre BPE, *WordPiece* et *Unigram*,
- être capable de combiner les blocs fournis par la bibliothèque 🤗 *Tokenizers* pour construire votre propre *tokenizer*,
- être capable d'utiliser ce *tokenizer* dans la bibliothèque 🤗 *Transformers*.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter6/9.mdx" />

### Tokénisation <i>Byte-Pair Encoding</i>
https://huggingface.co/learn/course/fr/chapter6/5.md

# Tokénisation <i>Byte-Pair Encoding</i>

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter6/section5.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter6/section5.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section5.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter6/section5.ipynb"},
]} />

Le *Byte-Pair Encoding* (BPE) a été initialement développé en tant qu'algorithme de compression de textes puis utilisé par OpenAI pour la tokenisation du pré-entraînement du modèle GPT. Il est utilisé par de nombreux *transformers* dont GPT, GPT-2, RoBERTa, BART et DeBERTa.

<Youtube id="HEikzVL-lZU"/>

<Tip>

💡 Cette section couvre le BPE en profondeur, allant jusqu'à montrer une implémentation complète. Vous pouvez passer directement à la fin si vous souhaitez simplement avoir un aperçu général de l'algorithme de tokenisation.

</Tip>

## Algorithme d'entraînement

L'entraînement du BPE commence par le calcul de l'unique ensemble de mots utilisés dans le corpus (après les étapes de normalisation et de prétokénisation), puis la construction du vocabulaire en prenant tous les symboles utilisés pour écrire ces mots. A titre d'exemple, disons que notre corpus utilise ces cinq mots :

```
"hug", "pug", "pun", "bun", "hugs" # "câlin", "carlin", "jeu de mots", "brioche", "câlins"
```

Le vocabulaire de base sera alors `["b", "g", "h", "n", "p", "s", "u"]`. Dans le monde réel, le vocabulaire de base contient au moins tous les caractères ASCII et probablement aussi quelques caractères Unicode. Si un exemple que vous tokenisez utilise un caractère qui n'est pas dans le corpus d'entraînement, ce caractère est converti en *token* inconnu. C'est l'une des raisons pour lesquelles de nombreux modèles de NLP sont par exemple très mauvais dans l'analyse de contenus contenant des emojis.

<Tip>

Les *tokenizers* du GPT-2 et de RoBERTa (qui sont assez similaires) ont une façon intelligente de gérer ce problème : ils ne considèrent pas les mots comme étant écrits avec des caractères Unicode mais avec des octets. De cette façon, le vocabulaire de base a une petite taille (256) et tous les caractères auxquels vous pouvez penser seront inclus dedans et ne finiront pas par être convertis en un *token* inconnu. Cette astuce est appelée *byte-level BPE*.

</Tip>

Après avoir obtenu ce vocabulaire de base, nous ajoutons de nouveaux *tokens* jusqu'à ce que la taille souhaitée du vocabulaire soit atteinte en apprenant les fusions qui sont des règles permettant de fusionner deux éléments du vocabulaire existant pour en créer un nouveau. Ainsi, au début, ces fusions créeront des *tokens* de deux caractères, puis au fur et à mesure de l'entraînement, des sous-mots plus longs.

À chaque étape de l'entraînement du *tokenizer*, l'algorithme BPE recherche la paire la plus fréquente de *tokens* existants (par « paire », nous entendons ici deux *tokens* consécutifs dans un mot). Cette paire la plus fréquente est celle qui sera fusionnée. Nous rinçons et répétons pour l'étape suivante.

Pour revenir à notre exemple précédent, supposons que les mots ont les fréquences suivantes :

```
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
```

ce qui veut dire que `"hug"` était présent 10 fois dans le corpus, `"pug"` 5 fois, `"pun"` 12 fois, `"bun"` 4 fois et `"hugs"`" 5 fois. Nous commençons l'entraînement en divisant chaque mot en caractères (ceux qui forment notre vocabulaire initial) afin de voir chaque mot comme une liste de *tokens* :

```
("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5)
```

Ensuite, nous regardons les paires. La paire `("h", "u")` est présente dans les mots `"hug"` et `"hugs"`, donc 15 fois au total dans le corpus. Ce n'est cependant pas la paire la plus fréquente. Cet honneur revient à `("u", "g")` qui est présent dans `"hug"`, `"pug"`, et `"hugs"`, pour un total de 20 fois dans le vocabulaire.

Ainsi, la première règle de fusion apprise par le *tokenizer* est `("u", "g") -> "ug"`, ce qui signifie que `"ug"` est ajouté au vocabulaire et que la paire doit être fusionnée dans tous les mots du corpus. A la fin de cette étape, le vocabulaire et le corpus ressemblent à ceci :

```
Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug"]
Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "ug" "s", 5)
```

Nous avons maintenant quelques paires qui aboutissent à un *token* de plus de deux caractères. Par exemple la paire `("h", "ug")` présente 15 fois dans le corpus. La paire la plus fréquente à ce stade est `("u", "n")`, présente 16 fois dans le corpus, donc la deuxième règle de fusion apprise est `("u", "n") -> "un"`. En ajoutant cela au vocabulaire et en fusionnant toutes les occurrences existantes, nous obtenons :

```
Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un"]
Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("h" "ug" "s", 5)
```

Maintenant la paire la plus fréquente est `("h", "ug")` donc nous apprenons la règle de fusion `("h", "ug") -> "hug"`. Cela nous donne donc notre premier *token* de trois lettres. Après la fusion, le corpus ressemble à ceci :

```
Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"]
Corpus: ("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5)
```

Et nous continuons ainsi jusqu'à ce que nous atteignions la taille de vocabulaire souhaitée.

<Tip>

✏️ **A votre tour !** A votre avis, quelle sera la prochaine règle de fusion ?

</Tip>

## Algorithme de tokenisation

La tokenisation suit de près le processus d'entraînement, dans le sens où les nouvelles entrées sont tokenisées en appliquant les étapes suivantes :

1. Normalisation
2. Prétokénisation
3. Découpage des mots en caractères individuels
4. Application des règles de fusion apprises dans l'ordre sur ces divisions.

Prenons l'exemple que nous avons utilisé pendant l'entraînement, avec les trois règles de fusion apprises :

```
("u", "g") -> "ug"
("u", "n") -> "un"
("h", "ug") -> "hug"
```

Le mot « bug »  sera traduit par « ["b", "ug"] ». Par contre, le mot « mug » (tasse en français) sera traduit par « ["[UNK]", "ug"] » puisque la lettre « m » ne fait pas partie du vocabulaire de base. De la même façon, le mot « thug » (voyou en français) sera tokenisé en « ["[UNK]", "hug"] » car la lettre « t » n'est pas dans le vocabulaire de base et l'application des règles de fusion résulte d'abord en la fusion de « u » et « g » et ensuite en la fusion de « hu » et « g ».

<Tip>

✏️ **A votre tour !** Comment pensez-vous que le mot « unhug » (détacher en français) sera tokenisé ?

</Tip>

## Implémentation du BPE

Voyons maintenant une implémentation de l'algorithme BPE. Il ne s'agira pas d'une version optimisée que vous pourrez utiliser sur un grand corpus. Nous voulons simplement vous montrer le code afin que vous puissiez comprendre un peu mieux l'algorithme.

Tout d'abord, nous avons besoin d'un corpus, alors créons un corpus simple avec quelques phrases :

```python
corpus = [
    "This is the Hugging Face Course.",
    # C'est le cours d'Hugging Face.
    "This chapter is about tokenization.",
    # Ce chapitre traite de la tokenisation.
    "This section shows several tokenizer algorithms.",
    # Cette section présente plusieurs algorithmes de tokenizer.
    "Hopefully, you will be able to understand how they are trained and generate tokens.",
    # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des tokens.
]
```

Ensuite, nous devons prétokeniser ce corpus en mots. Puisque nous répliquons un *tokenizer* BPE (comme celui du GPT-2), nous utiliserons le *tokenizer* `gpt2` pour la prétokénisation :

```python
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("gpt2")
```

Ensuite, nous calculons les fréquences de chaque mot dans le corpus comme nous le faisons pour la prétokénisation :

```python
from collections import defaultdict

word_freqs = defaultdict(int)

for text in corpus:
    words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text)
    new_words = [word for word, offset in words_with_offsets]
    for word in new_words:
        word_freqs[word] += 1

print(word_freqs)
```

```python out
defaultdict(int, {'This': 3, 'Ġis': 2, 'Ġthe': 1, 'ĠHugging': 1, 'ĠFace': 1, 'ĠCourse': 1, '.': 4, 'Ġchapter': 1,
    'Ġabout': 1, 'Ġtokenization': 1, 'Ġsection': 1, 'Ġshows': 1, 'Ġseveral': 1, 'Ġtokenizer': 1, 'Ġalgorithms': 1,
    'Hopefully': 1, ',': 1, 'Ġyou': 1, 'Ġwill': 1, 'Ġbe': 1, 'Ġable': 1, 'Ġto': 1, 'Ġunderstand': 1, 'Ġhow': 1,
    'Ġthey': 1, 'Ġare': 1, 'Ġtrained': 1, 'Ġand': 1, 'Ġgenerate': 1, 'Ġtokens': 1})
```

L'étape suivante consiste à calculer le vocabulaire de base, formé par tous les caractères utilisés dans le corpus :

```python
alphabet = []

for word in word_freqs.keys():
    for letter in word:
        if letter not in alphabet:
            alphabet.append(letter)
alphabet.sort()

print(alphabet)
```

```python out
[ ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's',
  't', 'u', 'v', 'w', 'y', 'z', 'Ġ']
```

Nous ajoutons également les *tokens* spéciaux utilisés par le modèle au début de ce vocabulaire. Dans le cas du GPT-2, le seul *token* spécial est `"<|endoftext|>"` :

```python
vocab = ["<|endoftext|>"] + alphabet.copy()
```

Nous devons maintenant diviser chaque mot en caractères individuels pour pouvoir commencer l'entraînement :

```python
splits = {word: [c for c in word] for word in word_freqs.keys()}
```

Maintenant que nous sommes prêts pour l'entraînement, écrivons une fonction qui calcule la fréquence de chaque paire. Nous devrons l'utiliser à chaque étape de l'entraînement :

```python
def compute_pair_freqs(splits):
    pair_freqs = defaultdict(int)
    for word, freq in word_freqs.items():
        split = splits[word]
        if len(split) == 1:
            continue
        for i in range(len(split) - 1):
            pair = (split[i], split[i + 1])
            pair_freqs[pair] += freq
    return pair_freqs
```

Jetons un coup d'œil à une partie de ce dictionnaire après les premières divisions :

```python
pair_freqs = compute_pair_freqs(splits)

for i, key in enumerate(pair_freqs.keys()):
    print(f"{key}: {pair_freqs[key]}")
    if i >= 5:
        break
```

```python out
('T', 'h'): 3
('h', 'i'): 3
('i', 's'): 5
('Ġ', 'i'): 2
('Ġ', 't'): 7
('t', 'h'): 3
```

Maintenant, trouver la paire la plus fréquente ne demande qu'une rapide boucle :

```python
best_pair = ""
max_freq = None

for pair, freq in pair_freqs.items():
    if max_freq is None or max_freq < freq:
        best_pair = pair
        max_freq = freq

print(best_pair, max_freq)
```

```python out
('Ġ', 't') 7
```

Donc la première fusion à apprendre est `('Ġ', 't') -> 'Ġt'`, et on ajoute `'Ġt'` au vocabulaire :

```python
merges = {("Ġ", "t"): "Ġt"}
vocab.append("Ġt")
```

Pour continuer, nous devons appliquer cette fusion dans notre dictionnaire `splits`. Écrivons une autre fonction pour cela :

```python
def merge_pair(a, b, splits):
    for word in word_freqs:
        split = splits[word]
        if len(split) == 1:
            continue

        i = 0
        while i < len(split) - 1:
            if split[i] == a and split[i + 1] == b:
                split = split[:i] + [a + b] + split[i + 2 :]
            else:
                i += 1
        splits[word] = split
    return splits
```

Et nous pouvons regarder le résultat de la première fusion :

```py
splits = merge_pair("Ġ", "t", splits)
print(splits["Ġtrained"])
```

```python out
['Ġt', 'r', 'a', 'i', 'n', 'e', 'd']
```

Maintenant, nous avons tout ce dont nous avons besoin pour boucler jusqu'à ce que nous ayons appris toutes les fusions que nous voulons. Visons une taille de vocabulaire de 50 :

```python
vocab_size = 50

while len(vocab) < vocab_size:
    pair_freqs = compute_pair_freqs(splits)
    best_pair = ""
    max_freq = None
    for pair, freq in pair_freqs.items():
        if max_freq is None or max_freq < freq:
            best_pair = pair
            max_freq = freq
    splits = merge_pair(*best_pair, splits)
    merges[best_pair] = best_pair[0] + best_pair[1]
    vocab.append(best_pair[0] + best_pair[1])
```

En conséquence, nous avons appris 19 règles de fusion (le vocabulaire initial avait une taille de 31 : 30 caractères dans l'alphabet plus le *token* spécial) :

```py
print(merges)
```

```python out
{('Ġ', 't'): 'Ġt', ('i', 's'): 'is', ('e', 'r'): 'er', ('Ġ', 'a'): 'Ġa', ('Ġt', 'o'): 'Ġto', ('e', 'n'): 'en',
 ('T', 'h'): 'Th', ('Th', 'is'): 'This', ('o', 'u'): 'ou', ('s', 'e'): 'se', ('Ġto', 'k'): 'Ġtok',
 ('Ġtok', 'en'): 'Ġtoken', ('n', 'd'): 'nd', ('Ġ', 'is'): 'Ġis', ('Ġt', 'h'): 'Ġth', ('Ġth', 'e'): 'Ġthe',
 ('i', 'n'): 'in', ('Ġa', 'b'): 'Ġab', ('Ġtoken', 'i'): 'Ġtokeni'}
```

Et le vocabulaire est composé du *token* spécial, de l'alphabet initial, et de tous les résultats des fusions :

```py
print(vocab)
```

```python out
['<|endoftext|>', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o',
 'p', 'r', 's', 't', 'u', 'v', 'w', 'y', 'z', 'Ġ', 'Ġt', 'is', 'er', 'Ġa', 'Ġto', 'en', 'Th', 'This', 'ou', 'se',
 'Ġtok', 'Ġtoken', 'nd', 'Ġis', 'Ġth', 'Ġthe', 'in', 'Ġab', 'Ġtokeni']
```

<Tip>

💡 Utiliser `train_new_from_iterator()` sur le même corpus ne donnera pas exactement le même vocabulaire. C'est parce que lorsqu'il y a un choix de la paire la plus fréquente, nous avons sélectionné la première rencontrée, alors que la bibliothèque 🤗 *Tokenizers* sélectionne la première en fonction de ses identifiants internes.

</Tip>

Pour tokeniser un nouveau texte, on le prétokenise, on le divise, puis on applique toutes les règles de fusion apprises :

```python
def tokenize(text):
    pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text)
    pre_tokenized_text = [word for word, offset in pre_tokenize_result]
    splits = [[l for l in word] for word in pre_tokenized_text]
    for pair, merge in merges.items():
        for idx, split in enumerate(splits):
            i = 0
            while i < len(split) - 1:
                if split[i] == pair[0] and split[i + 1] == pair[1]:
                    split = split[:i] + [merge] + split[i + 2 :]
                else:
                    i += 1
            splits[idx] = split

    return sum(splits, [])
```

Nous pouvons essayer cela sur n'importe quel texte composé de caractères de l'alphabet :

```py
tokenize("This is not a token.")
```

```python out
['This', 'Ġis', 'Ġ', 'n', 'o', 't', 'Ġa', 'Ġtoken', '.']
```

<Tip warning={true}>

⚠️ Notre implémentation lancera une erreur s'il y a un caractère inconnu puisque nous n'avons rien fait pour les gérer. GPT-2 n'a pas réellement de <i>token</i> inconnu (il est impossible d'obtenir un caractère inconnu en utilisant le BPE au niveau de l'octet) mais cela pourrait arriver ici car nous n'avons pas inclus tous les octets possibles dans le vocabulaire initial. Cet aspect du BPE dépasse le cadre de cette section, nous avons donc laissé ces détails de côté.

</Tip>

C'est tout pour l'algorithme BPE ! Nous allons nous intéresser à WordPiece dans la suite.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter6/5.mdx" />

### Introduction
https://huggingface.co/learn/course/fr/chapter6/1.md

# Introduction

<CourseFloatingBanner
    chapter={6}
    classNames="absolute z-10 right-0 top-0"
/>

Dans le [chapitre 3](/course/fr/chapter3), nous avons vu comment *finetuner* un modèle sur une tâche donnée. Pour ce faire, nous utilisons le même *tokenizer* que celui avec lequel le modèle a été pré-entraîné. Mais que faisons-nous lorsque nous voulons entraîner un modèle à partir de zéro ? Dans ces cas, l'utilisation d'un *tokenizer* qui a été pré-entraîné sur un corpus d'un autre domaine ou d'une autre langue est généralement sous-optimale. Par exemple, un *tokenizer* entraîné sur un corpus anglais sera peu performant sur un corpus de textes japonais car l'utilisation des espaces et de la ponctuation est très différente entre les deux langues.

Dans ce chapitre, vous apprendrez à entraîner un tout nouveau *tokenizer* sur un corpus de textes afin qu'il puisse ensuite être utilisé pour pré-entraîner un modèle de langue. Tout cela se fera à l'aide de la bibliothèque [🤗 *Tokenizers*](https://github.com/huggingface/tokenizers), qui fournit les *tokenizers* « rapides » de la bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers). Nous examinerons de près les fonctionnalités offertes par cette bibliothèque et nous étudierons comment les *tokenizers* rapides diffèrent des versions « lentes ».

Les sujets que nous couvrirons comprennent :
* comment entraîner sur un nouveau corpus de textes, un nouveau *tokenizer* similaire à celui utilisé par un *checkpoint* donné,
* les caractéristiques spéciales des *tokenizers* rapides,
* les différences entre les trois principaux algorithmes de tokénisation utilisés aujourd'hui en NLP,
* comment construire un *tokenizer* à partir de zéro avec la bibliothèque 🤗 *Tokenizers* et l'entraîner sur des données.

Les techniques présentées dans ce chapitre vous prépareront à la section du [chapitre 7](/course/fr/chapter7/6) où nous verrons comment créer un modèle de langue pour le langage Python. Commençons par examiner ce que signifie « entraîner » un *tokenizer*.

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter6/1.mdx" />

### Pouvoirs spéciaux des <i>tokenizers</i> rapides
https://huggingface.co/learn/course/fr/chapter6/3.md

# Pouvoirs spéciaux des <i>tokenizers</i> rapides

{#if fw === 'pt'}

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter6/section3_pt.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter6/section3_pt.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section3_pt.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter6/section3_pt.ipynb"},
]} />

{:else}
<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter6/section3_tf.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter6/section3_tf.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section3_tf.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter6/section3_tf.ipynb"},
]} />

{/if}

Dans cette section, nous allons examiner de plus près les capacités des *tokenizers* dans 🤗 *Transformers*.
Jusqu'à présent, nous ne les avons utilisés que pour tokeniser les entrées ou décoder les identifiants pour revenir à du texte. Mais les *tokenizers*, et surtout ceux soutenus par la bibliothèque 🤗 *Tokenizers*, peuvent faire beaucoup plus. Pour illustrer ces fonctionnalités supplémentaires, nous allons explorer comment reproduire les résultats des pipelines `token-classification` (que nous avons appelé `ner`) et `question-answering` que nous avons rencontrés pour la première fois dans le [chapitre 1](/course/fr/chapter1).

<Youtube id="g8quOxoqhHQ"/>

Dans la discussion qui suit, nous ferons souvent la distinction entre les *tokenizers* « lents » et les « rapides ». Les *tokenizers* lents sont ceux écrits en Python à l'intérieur de la bibliothèque 🤗 *Transformers*, tandis que les rapides sont ceux fournis par 🤗 *Tokenizers* et sont codés en Rust. Si vous vous souvenez du tableau du [chapitre 5](/course/fr/chapter5/3) qui indiquait combien de temps il fallait à un *tokenizer* rapide et à un *tokenizer* lent pour tokeniser le jeu de données *Drug Review*, vous devriez avoir une idée de la raison pour laquelle nous les appelons rapides et lents :

                | *Tokenizer* rapide | *Tokenizer* lent
:--------------:|:--------------:|:-------------:
`batched=True`  | 10.8s          | 4min41s
`batched=False` | 59.2s          | 5min3s

<Tip warning={true}>

⚠️ Lors de la tokenisation d'une seule phrase, vous ne verrez pas toujours une différence de vitesse entre les versions lente et rapide d'un même *tokenizer*. En fait, la version rapide peut même être plus lente ! Ce n'est que lorsque vous tokenisez beaucoup de textes en parallèle et en même temps que vous pourrez clairement voir la différence.

</Tip>

## L'objet <i>BatchEncoding</i>

<Youtube id="3umI3tm27Vw"/>

La sortie d'un *tokenizer* n'est pas un simple dictionnaire Python. Ce que nous obtenons est en fait un objet spécial `BatchEncoding`. C'est une sous-classe d'un dictionnaire (c'est pourquoi nous avons pu indexer ce résultat sans problème auparavant), mais avec des méthodes supplémentaires qui sont principalement utilisées par les *tokenizers* rapides.

En plus de leurs capacités de parallélisation, la fonctionnalité clé des *tokenizers* rapides est qu'ils gardent toujours la trace de l'étendue originale des textes d'où proviennent les *tokens* finaux, une fonctionnalité que nous appelons *mapping offset*. Cela permet de débloquer des fonctionnalités telles que le mappage de chaque mot aux *tokens* qu'il a générés ou le mappage de chaque caractère du texte original au *token* qu'il contient, et vice versa.

Prenons un exemple :

```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
example = "My name is Sylvain and I work at Hugging Face in Brooklyn."
# Je m'appelle Sylvain et je travaille chez Hugging Face à Brooklyn.
encoding = tokenizer(example)
print(type(encoding))
```

Comme mentionné précédemment, nous obtenons un objet `BatchEncoding` dans la sortie du *tokenizer* :

```python out
<class 'transformers.tokenization_utils_base.BatchEncoding'>
```

Puisque la classe `AutoTokenizer` choisit un *tokenizer* rapide par défaut, nous pouvons utiliser les méthodes supplémentaires que cet objet `BatchEncoding` fournit. Nous avons deux façons de vérifier si notre *tokenizer* est rapide ou lent. Nous pouvons soit vérifier l'attribut `is_fast` du *tokenizer* comme suit :

```python
tokenizer.is_fast
```

```python out
True
```

soit vérifier le même attribut mais avec notre `encoding` :

```python
encoding.is_fast
```

```python out
True
```

Voyons ce qu'un *tokenizer* rapide nous permet de faire. Tout d'abord, nous pouvons accéder aux *tokens* sans avoir à reconvertir les identifiants en *tokens* :

```py
encoding.tokens()
```

```python out
['[CLS]', 'My', 'name', 'is', 'S', '##yl', '##va', '##in', 'and', 'I', 'work', 'at', 'Hu', '##gging', 'Face', 'in',
 'Brooklyn', '.', '[SEP]']
```

Dans ce cas, le *token* à l'index 5 est `##yl` et fait partie du mot « Sylvain » dans la phrase originale. Nous pouvons également utiliser la méthode `word_ids()` pour obtenir l'index du mot dont provient chaque *token* :

```py
encoding.word_ids()
```

```python out
[None, 0, 1, 2, 3, 3, 3, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11, 12, None]
```

On peut voir que les *tokens* spéciaux du *tokenizer*, `[CLS]` et `[SEP]`, sont mis en correspondance avec `None` et que chaque *token* est mis en correspondance avec le mot dont il provient. Ceci est particulièrement utile pour déterminer si un *token* est au début d'un mot ou si deux *tokens* sont dans le même mot. Nous pourrions nous appuyer sur le préfixe `##` pour cela, mais il ne fonctionne que pour les *tokenizers* de type BERT. Cette méthode fonctionne pour n'importe quel type de *tokenizer*, du moment qu'il est rapide. Dans le chapitre suivant, nous verrons comment utiliser cette capacité pour appliquer correctement les étiquettes que nous avons pour chaque mot aux *tokens* dans des tâches comme la reconnaissance d'entités nommées et le POS (*Part-of-speech*). Nous pouvons également l'utiliser pour masquer tous les *tokens* provenant du même mot dans la modélisation du langage masqué (une technique appelée _whole word masking_).



La notion de ce qu'est un mot est compliquée. Par exemple, est-ce que « I'll » (contraction de « I will ») compte pour un ou deux mots ? Cela dépend en fait du *tokenizer* et de l'opération de prétokénisation qu'il applique. Certains *tokenizer* se contentent de séparer les espaces et considèrent donc qu'il s'agit d'un seul mot. D'autres utilisent la ponctuation en plus des espaces et considèrent donc qu'il s'agit de deux mots.

<Tip>

✏️ **Essayez !** Créez un *tokenizer* à partir des <i>checkpoints</i> `bert-base-cased` et `roberta-base` et tokenisez « 81s » avec. Qu'observez-vous ? Quels sont les identifiants des mots ?

</Tip>

De même, il existe une méthode `sentence_ids()` que nous pouvons utiliser pour associer un *token* à la phrase dont il provient (bien que dans ce cas, le `token_type_ids` retourné par le *tokenizer* peut nous donner la même information).

Enfin, nous pouvons faire correspondre n'importe quel mot ou *token* aux caractères du texte d'origine (et vice versa) grâce aux méthodes `word_to_chars()` ou `token_to_chars()` et `char_to_word()` ou `char_to_token()`. Par exemple, la méthode `word_ids()` nous a dit que `##yl` fait partie du mot à l'indice 3, mais de quel mot s'agit-il dans la phrase ? Nous pouvons le découvrir comme ceci :

```py
start, end = encoding.word_to_chars(3)
example[start:end]
```

```python out
Sylvain
```

Comme nous l'avons mentionné précédemment, tout ceci est rendu possible par le fait que le *tokenizer* rapide garde la trace de la partie du texte d'où provient chaque *token* dans une liste d'*offsets*. Pour illustrer leur utilisation, nous allons maintenant vous montrer comment reproduire manuellement les résultats du pipeline `token-classification`.

<Tip>

✏️ **Essayez !** Rédigez votre propre texte et voyez si vous pouvez comprendre quels *tokens* sont associés à l'identifiant du mot et comment extraire les étendues de caractères pour un seul mot. Pour obtenir des points bonus, essayez d'utiliser deux phrases en entrée et voyez si les identifiants ont un sens pour vous.

</Tip>

## A l'intérieur du pipeline `token-classification`

Dans le [chapitre 1](/course/fr/chapter1), nous avons eu un premier aperçu de la NER (où la tâche est d'identifier les parties du texte qui correspondent à des entités telles que des personnes, des lieux ou des organisations) avec la fonction `pipeline()` de 🤗 *Transformers*. Puis, dans le [chapitre 2](/course/fr/chapter2), nous avons vu comment un pipeline regroupe les trois étapes nécessaires pour obtenir les prédictions à partir d'un texte brut : la tokenisation, le passage des entrées dans le modèle et le post-traitement. Les deux premières étapes du pipeline de `token-classification` sont les mêmes que dans tout autre pipeline mais le post-traitement est un peu plus complexe. Voyons comment !

{#if fw === 'pt'}

<Youtube id="0E7ltQB7fM8"/>

{:else}

<Youtube id="PrX4CjrVnNc"/>

{/if}

### Obtenir les résultats de base avec le pipeline

Tout d'abord, prenons un pipeline de classification de *tokens* afin d'obtenir des résultats à comparer manuellement. Le modèle utilisé par défaut est [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english). Il effectue une NER sur les phrases :

```py
from transformers import pipeline

token_classifier = pipeline("token-classification")
token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.")
```

```python out
[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S', 'start': 11, 'end': 12},
 {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl', 'start': 12, 'end': 14},
 {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va', 'start': 14, 'end': 16},
 {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in', 'start': 16, 'end': 18},
 {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu', 'start': 33, 'end': 35},
 {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging', 'start': 35, 'end': 40},
 {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face', 'start': 41, 'end': 45},
 {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}]
```

Le modèle a correctement identifié chaque *token* généré par « Sylvain » comme une personne, chaque *token* généré par « Hugging Face » comme une organisation, et le *token* « Brooklyn » comme un lieu. Nous pouvons également demander au pipeline de regrouper les *tokens* qui correspondent à la même entité :

```py
from transformers import pipeline

token_classifier = pipeline("token-classification", aggregation_strategy="simple")
token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.")
```

```python out
[{'entity_group': 'PER', 'score': 0.9981694, 'word': 'Sylvain', 'start': 11, 'end': 18},
 {'entity_group': 'ORG', 'score': 0.97960204, 'word': 'Hugging Face', 'start': 33, 'end': 45},
 {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}]
```

La propriété `aggregation_strategy` choisie va changer les scores calculés pour chaque entité groupée. Avec `"simple"` le score est juste la moyenne des scores de chaque *token* dans l'entité donnée. Par exemple, le score de « Sylvain » est la moyenne des scores que nous avons vu dans l'exemple précédent pour les tokens `S`, `##yl`, `##va`, et `##in`. D'autres stratégies sont disponibles :

- `"first"`, où le score de chaque entité est le score du premier *token* de cette entité (donc pour « Sylvain » ce serait 0.993828, le score du token `S`)
- `"max"`, où le score de chaque entité est le score maximal des *tokens* de cette entité (ainsi, pour « Hugging Face », le score de « Face » serait de 0,98879766).
- `"average"`, où le score de chaque entité est la moyenne des scores des mots qui composent cette entité (ainsi, pour « Sylvain », il n'y aurait pas de différence avec la stratégie `"simple"`, mais "Hugging Face" aurait un score de 0,9819, la moyenne des scores de « Hugging », 0,975, et « Face », 0,98879).

Voyons maintenant comment obtenir ces résultats sans utiliser la fonction `pipeline()` !

### Des entrées aux prédictions

{#if fw === 'pt'}

D'abord, nous devons tokeniser notre entrée et la faire passer dans le modèle. Cela se fait exactement comme dans le [chapitre 2](/course/fr/chapter2). Nous instancions le *tokenizer* et le modèle en utilisant les classes `TFAutoXxx` et les utilisons ensuite dans notre exemple :

```py
from transformers import AutoTokenizer, AutoModelForTokenClassification

model_checkpoint = "dbmdz/bert-large-cased-finetuned-conll03-english"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
model = AutoModelForTokenClassification.from_pretrained(model_checkpoint)

example = "My name is Sylvain and I work at Hugging Face in Brooklyn."
inputs = tokenizer(example, return_tensors="pt")
outputs = model(**inputs)
```

Puisque nous utilisons `AutoModelForTokenClassification`, nous obtenons un ensemble de logits pour chaque *token* dans la séquence d'entrée :

```py
print(inputs["input_ids"].shape)
print(outputs.logits.shape)
```

```python out
torch.Size([1, 19])
torch.Size([1, 19, 9])
```

{:else}

D'abord, nous devons tokeniser notre entrée et la faire passer dans le modèle. Cela se fait exactement comme dans le [chapitre 2](/course/fr/chapter2). Nous instancions le *tokenizer* et le modèle en utilisant les classes `TFAutoXxx` et les utilisons ensuite dans notre exemple :

```py
from transformers import AutoTokenizer, TFAutoModelForTokenClassification

model_checkpoint = "dbmdz/bert-large-cased-finetuned-conll03-english"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
model = TFAutoModelForTokenClassification.from_pretrained(model_checkpoint)

example = "My name is Sylvain and I work at Hugging Face in Brooklyn."
inputs = tokenizer(example, return_tensors="tf")
outputs = model(**inputs)
```

Puisque nous utilisons `TFAutoModelForTokenClassification`, nous obtenons un ensemble de logits pour chaque *token* dans la séquence d'entrée :

```py
print(inputs["input_ids"].shape)
print(outputs.logits.shape)
```

```python out
(1, 19)
(1, 19, 9)
```

{/if}

Nous avons un batch avec 1 séquence de 19 *tokens* et le modèle a 9 étiquettes différentes. Ainsi, la sortie du modèle a une forme de 1 x 19 x 9. Comme pour le pipeline de classification de texte, nous utilisons une fonction softmax pour convertir ces logits en probabilités et nous prenons l'argmax pour obtenir des prédictions (notez que nous pouvons prendre l'argmax sur les logits car la fonction softmax ne change pas l'ordre) :

{#if fw === 'pt'}

```py
import torch

probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1)[0].tolist()
predictions = outputs.logits.argmax(dim=-1)[0].tolist()
print(predictions)
```

{:else}

```py
import tensorflow as tf

probabilities = tf.math.softmax(outputs.logits, axis=-1)[0]
probabilities = probabilities.numpy().tolist()
predictions = tf.math.argmax(outputs.logits, axis=-1)[0]
predictions = predictions.numpy().tolist()
print(predictions)
```

{/if}

```python out
[0, 0, 0, 0, 4, 4, 4, 4, 0, 0, 0, 0, 6, 6, 6, 0, 8, 0, 0]
```

L'attribut `model.config.id2label` contient la correspondance entre les index et les étiquettes que nous pouvons utiliser pour donner un sens aux prédictions :

```py
model.config.id2label
```

```python out
{0: 'O',
 1: 'B-MISC',
 2: 'I-MISC',
 3: 'B-PER',
 4: 'I-PER',
 5: 'B-ORG',
 6: 'I-ORG',
 7: 'B-LOC',
 8: 'I-LOC'}
```

Comme nous l'avons vu précédemment, il y a 9 étiquettes : `O` est le label pour les *tokens* qui ne sont dans aucune entité nommée (il signifie *outside* (en dehors)) et nous avons ensuite deux labels pour chaque type d'entité (divers, personne, organisation et lieu). L'étiquette `B-XXX` indique que le *token* est au début d'une entité `XXX` et l'étiquette `I-XXX` indique que le *token* est à l'intérieur de l'entité `XXX`. Par exemple, dans l'exemple actuel, nous nous attendons à ce que notre modèle classe le *token* `S` comme `B-PER` (début d'une entité personne) et les *tokens* `##yl`, `##va` et `##in` comme `I-PER` (à l'intérieur d'une entité personne). 

Vous pourriez penser que le modèle s'est trompé ici car il a attribué l'étiquette `I-PER` à ces quatre *tokens* mais ce n'est pas tout à fait vrai. Il existe en fait deux formats pour ces étiquettes `B-` et `I-` : *IOB1* et *IOB2*. Le format IOB2 (en rose ci-dessous) est celui que nous avons introduit alors que dans le format IOB1 (en bleu), les étiquettes commençant par `B-` ne sont jamais utilisées que pour séparer deux entités adjacentes du même type. Le modèle que nous utilisons a été *finetuné* sur un jeu de données utilisant ce format, c'est pourquoi il attribue le label `I-PER` au *token* `S`.

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/IOB_versions.svg" alt="IOB1 vs IOB2 format"/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/IOB_versions-dark.svg" alt="IOB1 vs IOB2 format"/>
</div>

Nous sommes à présent prêts à reproduire (presque entièrement) les résultats du premier pipeline. Nous pouvons simplement récupérer le score et le label de chaque *token* qui n'a pas été classé comme `O` :

```py
results = []
tokens = inputs.tokens()

for idx, pred in enumerate(predictions):
    label = model.config.id2label[pred]
    if label != "O":
        results.append(
            {"entity": label, "score": probabilities[idx][pred], "word": tokens[idx]}
        )

print(results)
```

```python out
[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S'},
 {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl'},
 {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va'},
 {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in'},
 {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu'},
 {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging'},
 {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face'},
 {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn'}]
```

C'est très similaire à ce que nous avions avant, à une exception près : le pipeline nous a aussi donné des informations sur le `début` et la `fin` de chaque entité dans la phrase originale. C'est là que notre *offset mapping* va entrer en jeu. Pour obtenir les *offsets*, il suffit de définir `return_offsets_mapping=True` lorsque nous appliquons le *tokenizer* à nos entrées :

```py
inputs_with_offsets = tokenizer(example, return_offsets_mapping=True)
inputs_with_offsets["offset_mapping"]
```

```python out
[(0, 0), (0, 2), (3, 7), (8, 10), (11, 12), (12, 14), (14, 16), (16, 18), (19, 22), (23, 24), (25, 29), (30, 32),
 (33, 35), (35, 40), (41, 45), (46, 48), (49, 57), (57, 58), (0, 0)]
```

Chaque *tuple* est l'étendue de texte correspondant à chaque *token* où `(0, 0)` est réservé aux *tokens* spéciaux. Nous avons vu précédemment que le *token* à l'index 5 est `##yl`, qui a `(12, 14)` comme *offsets* ici. Si on prend la tranche correspondante dans notre exemple :


```py
example[12:14]
```

nous obtenons le bon espace de texte sans le `##` :

```python out
yl
```

En utilisant cela, nous pouvons maintenant compléter les résultats précédents :

```py
results = []
inputs_with_offsets = tokenizer(example, return_offsets_mapping=True)
tokens = inputs_with_offsets.tokens()
offsets = inputs_with_offsets["offset_mapping"]

for idx, pred in enumerate(predictions):
    label = model.config.id2label[pred]
    if label != "O":
        start, end = offsets[idx]
        results.append(
            {
                "entity": label,
                "score": probabilities[idx][pred],
                "word": tokens[idx],
                "start": start,
                "end": end,
            }
        )

print(results)
```

```python out
[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S', 'start': 11, 'end': 12},
 {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl', 'start': 12, 'end': 14},
 {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va', 'start': 14, 'end': 16},
 {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in', 'start': 16, 'end': 18},
 {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu', 'start': 33, 'end': 35},
 {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging', 'start': 35, 'end': 40},
 {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face', 'start': 41, 'end': 45},
 {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}]
```

C'est la même chose que ce que nous avons obtenu avec le premier pipeline !

### Regroupement des entités

L'utilisation des *offsets* pour déterminer les clés de début et de fin pour chaque entité est pratique mais cette information n'est pas strictement nécessaire. Cependant, lorsque nous voulons regrouper les entités, les *offsets* nous épargnent un batch de code compliqué. Par exemple, si nous voulions regrouper les *tokens* `Hu`, `##gging`, et `Face`, nous pourrions établir des règles spéciales disant que les deux premiers devraient être attachés tout en enlevant le `##`, et le `Face` devrait être ajouté avec un espace puisqu'il ne commence pas par `##` mais cela ne fonctionnerait que pour ce type particulier de *tokenizer*. Il faudrait écrire un autre ensemble de règles pour un *tokenizer* de type SentencePiece ou *Byte-Pair-Encoding* (voir plus loin dans ce chapitre).

Avec les *offsets*, tout ce code personnalisé disparaît : il suffit de prendre l'intervalle du texte original qui commence par le premier *token* et se termine par le dernier *token*. Ainsi, dans le cas des *tokens* `Hu`, `##gging`, et `Face`, nous devrions commencer au caractère 33 (le début de `Hu`) et finir avant le caractère 45 (la fin de `Face`) :

```py
example[33:45]
```

```python out
Hugging Face
```

Pour écrire le code qui post-traite les prédictions tout en regroupant les entités, nous regrouperons les entités qui sont consécutives et étiquetées avec `I-XXX`, à l'exception de la première, qui peut être étiquetée comme `B-XXX` ou `I-XXX` (ainsi, nous arrêtons de regrouper une entité lorsque nous obtenons un `O`, un nouveau type d'entité, ou un `B-XXX` qui nous indique qu'une entité du même type commence) :

```py
import numpy as np

results = []
inputs_with_offsets = tokenizer(example, return_offsets_mapping=True)
tokens = inputs_with_offsets.tokens()
offsets = inputs_with_offsets["offset_mapping"]

idx = 0
while idx < len(predictions):
    pred = predictions[idx]
    label = model.config.id2label[pred]
    if label != "O":
        # Enlever le B- ou le I-
        label = label[2:]
        start, _ = offsets[idx]

        # Récupérer tous les tokens étiquetés avec I-label
        all_scores = []
        while (
            idx < len(predictions)
            and model.config.id2label[predictions[idx]] == f"I-{label}"
        ):
            all_scores.append(probabilities[idx][pred])
            _, end = offsets[idx]
            idx += 1

        # Le score est la moyenne de tous les scores des tokens dans cette entité groupée
        score = np.mean(all_scores).item()
        word = example[start:end]
        results.append(
            {
                "entity_group": label,
                "score": score,
                "word": word,
                "start": start,
                "end": end,
            }
        )
    idx += 1

print(results)
```

Et nous obtenons les mêmes résultats qu'avec notre deuxième pipeline !

```python out
[{'entity_group': 'PER', 'score': 0.9981694, 'word': 'Sylvain', 'start': 11, 'end': 18},
 {'entity_group': 'ORG', 'score': 0.97960204, 'word': 'Hugging Face', 'start': 33, 'end': 45},
 {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}]
```

Un autre exemple de tâche où ces *offsets* sont extrêmement utiles est la réponse aux questions. Plonger dans ce pipeline, ce que nous ferons dans la section suivante, nous permettra de jeter un coup d'œil à une dernière caractéristique des *tokenizers* de la bibliothèque 🤗 *Transformers* : la gestion des *tokens* qui débordent lorsque nous tronquons une entrée à une longueur donnée.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter6/3.mdx" />

### Tokénisation <i>WordPiece</i>
https://huggingface.co/learn/course/fr/chapter6/6.md

# Tokénisation <i>WordPiece</i>

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter6/section6.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter6/section6.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section6.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter6/section6.ipynb"},
]} />

*WordPiece* est l'algorithme de tokénisation développé par Google pour prétraîner BERT. Il a depuis été réutilisé dans un grand nombre de modèles de *transformers* basés sur BERT tels que DistilBERT, MobileBERT, Funnel Transformers et MPNET. Il est très similaire au BPE en termes d'entraînement mais la tokenisation réelle est effectuée différemment.

<Youtube id="qpv6ms_t_1A"/>

<Tip>

💡 Cette section couvre le <i>WordPiece</i> en profondeur, allant jusqu'à montrer une implémentation complète. Vous pouvez passer directement à la fin si vous souhaitez simplement avoir un aperçu général de l'algorithme de tokénisation.

</Tip>

## Algorithme d'entraînement

<Tip warning={true}>

⚠️ Google n'a jamais mis en ligne son implémentation de l'algorithme d'entraînement du <i>WordPiece</i>. Ce qui suit est donc notre meilleure estimation basée sur la littérature publiée. Il se peut qu'elle ne soit pas exacte à 100 %.

</Tip>

Comme le BPE, *WordPiece* part d'un petit vocabulaire comprenant les *tokens* spéciaux utilisés par le modèle et l'alphabet initial. Puisqu'il identifie les sous-mots en ajoutant un préfixe (comme `##` pour BERT), chaque mot est initialement découpé en ajoutant ce préfixe à tous les caractères du mot. Ainsi par exemple, `"word"` est divisé comme ceci :

```
w ##o ##r ##d
```

Ainsi, l'alphabet initial contient tous les caractères présents au début d'un mot et les caractères présents à l'intérieur d'un mot précédé du préfixe de *WordPiece*.

Ensuite, toujours comme le BPE, *WordPiece* apprend des règles de fusion. La principale différence réside dans la manière dont la paire à fusionner est sélectionnée. Au lieu de sélectionner la paire la plus fréquente, *WordPiece* calcule un score pour chaque paire en utilisant la formule suivante :

$$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times \mathrm{freq\_of\_second\_element})$$

En divisant la fréquence de la paire par le produit des fréquences de chacune de ses parties, l'algorithme donne la priorité à la fusion des paires dont les parties individuelles sont moins fréquentes dans le vocabulaire. Par exemple, il ne fusionnera pas nécessairement `("un", "##able")` même si cette paire apparaît très fréquemment dans le vocabulaire car les deux paires `"un"`" et `"##able"` apparaîtront probablement chacune dans un batch d'autres mots et auront une fréquence élevée. En revanche, une paire comme `("hu", "##gging")` sera probablement fusionnée plus rapidement (en supposant que le mot `"hugging"` apparaisse souvent dans le vocabulaire) puisque `"hu"` et `"##gging"` sont probablement moins fréquents individuellement.

Examinons le même vocabulaire que celui utilisé dans l'exemple d'entraînement du BPE :

```
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
```

Les divisions ici seront :

```
("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5)
```

Si on oublie les *tokens* spéciaux pour l'instant, le vocabulaire initial sera donc `["b", "h", "p", "##g", "##n", "##s", "##u"]`. La paire la plus fréquente est `("##u", "##g")` (présente 20 fois), mais la fréquence individuelle de `"##u"` est très élevée, donc son score n'est pas le plus élevé (il est de 1 / 36). Toutes les paires avec un `"##u"` ont en fait le même score (1 / 36). Ainsi le meilleur score va à la paire `("##g", "##s")` qui est la seule sans un `"##u"` avec un score 1 / 20. Et la première fusion apprise est `("##g", "##s") -> ("##gs")`.

Notez que lorsque nous fusionnons, nous enlevons le `##` entre les deux *tokens*, donc nous ajoutons `"##gs"` au vocabulaire et appliquons la fusion dans les mots du corpus :

```
Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs"]
Corpus: ("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##gs", 5)
```

À ce stade, `" ##u "` est dans toutes les paires possibles, donc elles finissent toutes par avoir le même score. Disons que dans ce cas, la première paire est fusionnée, donc `("h", "##u") -> "hu"`. Cela nous amène à :

```
Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu"]
Corpus: ("hu" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5)
```

Ensuite, le meilleur score suivant est partagé par `("hu", "##g")` et `("hu", "##gs")` (avec 1/15, comparé à 1/21 pour toutes les autres paires). Ainsi la première paire avec le plus grand score est fusionnée :

```
Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu", "hug"]
Corpus: ("hug", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5)
```

et nous continuons ainsi jusqu'à ce que nous atteignions la taille de vocabulaire souhaitée.

<Tip>

✏️ **A votre tour !** Quelle sera la prochaine règle de fusion ?

</Tip>

## Algorithme de tokenisation

La tokénisation diffère dans *WordPiece* et BPE en ce que *WordPiece* ne sauvegarde que le vocabulaire final et non pas les règles de fusion apprises. En partant du mot à tokeniser, *WordPiece* trouve le sous-mot le plus long qui se trouve dans le vocabulaire, puis se sépare sur celui-ci. Par exemple, si nous utilisons le vocabulaire appris dans l'exemple ci-dessus, pour le mot `"hugs"` le plus long sous-mot en partant du début qui est dans le vocabulaire est `"hug"`. Donc nous le divisons et obtenons `["hug", "##s"]`. On continue avec `"##s"`, qui est dans le vocabulaire, donc la tokenisation de `"hugs"` est `["hug", "##s"]`.

Avec BPE, nous aurions appliqué les fusions apprises dans l'ordre et la tokénisation aurait été `["hu", "##gs"]`, l'encodage est donc différent.

Comme autre exemple, voyons comment le mot `"bugs"` serait tokenisé. `"b"` est le plus long sous-mot commençant au début du mot qui est dans le vocabulaire donc on le divise et on obtient `["b", "##ugs"]`. Ensuite, `"##u"` est le plus long sous-mot commençant au début de `"##ugs"` qui est dans le vocabulaire, donc on le sépare et on obtient `["b", "##u, "##gs"]`. Enfin, `"##gs"` est dans le vocabulaire, donc cette dernière liste est la tokenization de `"bugs"`.

Lorsque la tokenisation arrive à un stade où il n'est pas possible de trouver un sous-mot dans le vocabulaire, le mot entier est tokenisé comme inconnu. Par exemple, `"mug"` serait tokenisé comme `["[UNK]"]`, tout comme `"bum"` (même si on peut commencer par " b " et " ##u ", " ##m " ne fait pas partie du vocabulaire, et le *tokenizer* résultant sera simplement `["[UNK]"]` " et non `["b", "##u", "[UNK]"]` "). C'est une autre différence avec le BPE qui classerait seulement les caractères individuels qui ne sont pas dans le vocabulaire comme inconnus.

<Tip>

✏️ **A votre tour !** Comment le mot `"pugs"` sera-t-il tokenisé ?

</Tip>

## Implémentation de <i>WordPiece</i>

Voyons maintenant une implémentation de l'algorithme *WordPiece*. Comme pour le BPE, il s'agit d'un exemple pédagogique et vous ne pourrez pas l'utiliser sur un grand corpus.

Nous utiliserons le même corpus que dans l'exemple BPE :

```python
corpus = [
    "This is the Hugging Face Course.",
    # C'est le cours d'Hugging Face.
    "This chapter is about tokenization.",
    # This chapter is about tokenization
    "This section shows several tokenizer algorithms.",
    # Cette section présente plusieurs algorithmes de tokenizer.
    "Hopefully, you will be able to understand how they are trained and generate tokens.",
    # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des tokens.
]
```

Tout d'abord, nous devons prétokéniser le corpus en mots. Puisque nous répliquons un *tokenizer WordPiece* (comme BERT), nous utiliserons le *tokenizer* `bert-base-cased` pour la prétokénisation :

```python
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
```

Ensuite, nous calculons les fréquences de chaque mot dans le corpus comme nous le faisons pour la prétokénisation :

```python
from collections import defaultdict

word_freqs = defaultdict(int)
for text in corpus:
    words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text)
    new_words = [word for word, offset in words_with_offsets]
    for word in new_words:
        word_freqs[word] += 1

word_freqs
```

```python out
defaultdict(
    int, {'This': 3, 'is': 2, 'the': 1, 'Hugging': 1, 'Face': 1, 'Course': 1, '.': 4, 'chapter': 1, 'about': 1,
    'tokenization': 1, 'section': 1, 'shows': 1, 'several': 1, 'tokenizer': 1, 'algorithms': 1, 'Hopefully': 1,
    ',': 1, 'you': 1, 'will': 1, 'be': 1, 'able': 1, 'to': 1, 'understand': 1, 'how': 1, 'they': 1, 'are': 1,
    'trained': 1, 'and': 1, 'generate': 1, 'tokens': 1})
```

Comme nous l'avons vu précédemment, l'alphabet est l'unique ensemble composé de toutes les premières lettres des mots, et de toutes les autres lettres qui apparaissent dans les mots préfixés par `##` :

```python
alphabet = []
for word in word_freqs.keys():
    if word[0] not in alphabet:
        alphabet.append(word[0])
    for letter in word[1:]:
        if f"##{letter}" not in alphabet:
            alphabet.append(f"##{letter}")

alphabet.sort()
alphabet

print(alphabet)
```

```python out
['##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k', '##l', '##m', '##n', '##o', '##p', '##r', '##s',
 '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u',
 'w', 'y']
```

Nous ajoutons également les *tokens* spéciaux utilisés par le modèle au début de ce vocabulaire. Dans le cas de BERT, il s'agit de la liste `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]` :

```python
vocab = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + alphabet.copy()
```

Ensuite, nous devons diviser chaque mot, avec toutes les lettres qui ne sont pas les premières préfixées par `##` :

```python
splits = {
    word: [c if i == 0 else f"##{c}" for i, c in enumerate(word)]
    for word in word_freqs.keys()
}
```

Maintenant que nous sommes prêts pour l'entraînement, écrivons une fonction qui calcule le score de chaque paire. Nous devrons l'utiliser à chaque étape de l'entraînement :

```python
def compute_pair_scores(splits):
    letter_freqs = defaultdict(int)
    pair_freqs = defaultdict(int)
    for word, freq in word_freqs.items():
        split = splits[word]
        if len(split) == 1:
            letter_freqs[split[0]] += freq
            continue
        for i in range(len(split) - 1):
            pair = (split[i], split[i + 1])
            letter_freqs[split[i]] += freq
            pair_freqs[pair] += freq
        letter_freqs[split[-1]] += freq

    scores = {
        pair: freq / (letter_freqs[pair[0]] * letter_freqs[pair[1]])
        for pair, freq in pair_freqs.items()
    }
    return scores
```

Jetons un coup d'œil à une partie de ce dictionnaire après les premières divisions :

```python
pair_scores = compute_pair_scores(splits)
for i, key in enumerate(pair_scores.keys()):
    print(f"{key}: {pair_scores[key]}")
    if i >= 5:
        break
```

```python out
('T', '##h'): 0.125
('##h', '##i'): 0.03409090909090909
('##i', '##s'): 0.02727272727272727
('i', '##s'): 0.1
('t', '##h'): 0.03571428571428571
('##h', '##e'): 0.011904761904761904
```

Maintenant, trouver la paire avec le meilleur score ne prend qu'une rapide boucle :

```python
best_pair = ""
max_score = None
for pair, score in pair_scores.items():
    if max_score is None or max_score < score:
        best_pair = pair
        max_score = score

print(best_pair, max_score)
```

```python out
('a', '##b') 0.2
```

Ainsi, la première fusion à apprendre est `('a', '##b') -> 'ab'` et nous ajoutons `'ab'` au vocabulaire :

```python
vocab.append("ab")
```

Pour continuer, nous devons appliquer cette fusion dans notre dictionnaire `splits`. Écrivons une autre fonction pour cela :

```python
def merge_pair(a, b, splits):
    for word in word_freqs:
        split = splits[word]
        if len(split) == 1:
            continue
        i = 0
        while i < len(split) - 1:
            if split[i] == a and split[i + 1] == b:
                merge = a + b[2:] if b.startswith("##") else a + b
                split = split[:i] + [merge] + split[i + 2 :]
            else:
                i += 1
        splits[word] = split
    return splits
```

Et nous pouvons regarder le résultat de la première fusion :

```py
splits = merge_pair("a", "##b", splits)
splits["about"]
```

```python out
['ab', '##o', '##u', '##t']
```

Nous avons maintenant tout ce dont nous avons besoin pour boucler jusqu'à ce que nous ayons appris toutes les fusions que nous voulons. Visons une taille de vocabulaire de 70 :

```python
vocab_size = 70
while len(vocab) < vocab_size:
    scores = compute_pair_scores(splits)
    best_pair, max_score = "", None
    for pair, score in scores.items():
        if max_score is None or max_score < score:
            best_pair = pair
            max_score = score
    splits = merge_pair(*best_pair, splits)
    new_token = (
        best_pair[0] + best_pair[1][2:]
        if best_pair[1].startswith("##")
        else best_pair[0] + best_pair[1]
    )
    vocab.append(new_token)
```

Nous pouvons ensuite examiner le vocabulaire généré :

```py
print(vocab)
```

```python out
['[PAD]', '[UNK]', '[CLS]', '[SEP]', '[MASK]', '##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k',
 '##l', '##m', '##n', '##o', '##p', '##r', '##s', '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'C', 'F', 'H',
 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u', 'w', 'y', '##fu', 'Fa', 'Fac', '##ct', '##ful', '##full', '##fully',
 'Th', 'ch', '##hm', 'cha', 'chap', 'chapt', '##thm', 'Hu', 'Hug', 'Hugg', 'sh', 'th', 'is', '##thms', '##za', '##zat',
 '##ut']
```

Comme nous pouvons le voir, comparé à BPE, ce *tokenizer* apprend les parties de mots comme des *tokens* un peu plus rapidement.

<Tip>

💡 Utiliser `train_new_from_iterator()` sur le même corpus ne donnera pas exactement le même vocabulaire. C'est parce que la bibliothèque 🤗 *Tokenizers* n'implémente pas *WordPiece* pour l'entraînement (puisque nous ne sommes pas complètement sûrs de son fonctionnement interne), mais utilise le BPE à la place.

</Tip>

Pour tokeniser un nouveau texte, on le prétokenise, on le divise, puis on applique l'algorithme de tokenisation sur chaque mot. En d'autres termes, nous recherchons le plus grand sous-mot commençant au début du premier mot et le divisons. Puis nous répétons le processus sur la deuxième partie et ainsi de suite pour le reste de ce mot et les mots suivants dans le texte :

```python
def encode_word(word):
    tokens = []
    while len(word) > 0:
        i = len(word)
        while i > 0 and word[:i] not in vocab:
            i -= 1
        if i == 0:
            return ["[UNK]"]
        tokens.append(word[:i])
        word = word[i:]
        if len(word) > 0:
            word = f"##{word}"
    return tokens
```

Testons-le sur un mot qui fait partie du vocabulaire, et un autre qui n'en fait pas partie :

```python
print(encode_word("Hugging"))
print(encode_word("HOgging"))
```

```python out
['Hugg', '##i', '##n', '##g']
['[UNK]']
```

Maintenant, écrivons une fonction qui permet de tokeniser un texte :

```python
def tokenize(text):
    pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text)
    pre_tokenized_text = [word for word, offset in pre_tokenize_result]
    encoded_words = [encode_word(word) for word in pre_tokenized_text]
    return sum(encoded_words, [])
```

On peut l'essayer sur n'importe quel texte :

```python
tokenize("This is the Hugging Face Course!")  # C'est le cours d'Hugging Face
```

```python out
['Th', '##i', '##s', 'is', 'th', '##e', 'Hugg', '##i', '##n', '##g', 'Fac', '##e', 'c', '##o', '##u', '##r', '##s',
 '##e', '[UNK]']
```

C'est tout pour l'algorithme *WordPiece* ! Maintenant, jetons un coup d'oeil à *Unigram*.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter6/6.mdx" />

### Quiz de fin de chapitre
https://huggingface.co/learn/course/fr/chapter6/10.md

# Quiz de fin de chapitre

<CourseFloatingBanner
    chapter={6}
    classNames="absolute z-10 right-0 top-0"
/>

Testons ce que vous avez appris dans ce chapitre !

### 1. Quand devez-vous entraîner un nouveau <i>tokenizer</i> ?

<Question
	choices={[
		{
			text: "Lorsque votre jeu de données est similaire à celui utilisé par un modèle pré-entraîné existant et que vous voulez pré-entraîner un nouveau modèle",
			explain: "Dans ce cas, pour économiser du temps et des ressources de calcul, il est préférable d'utiliser le même <i>tokenizer</i> que le modèle pré-entraîné et de <i>finetuner</i> ce modèle à la place."
		},
		{
			text: "Lorsque votre jeu de données est similaire à celui utilisé par un modèle pré-entraîné existant et que vous souhaitez <i>finetuner</i> un nouveau modèle en utilisant ce modèle pré-entraîné.",
			explain: "Pour <i>finetuner</i> un modèle à partir d'un modèle pré-entraîné, vous devez toujours utiliser le même <i>tokenizer</i>."
		},
		{
			text: "Lorsque votre jeu de données est différent de celui utilisé par un modèle pré-entraîné existant et que vous souhaitez pré-entraîner un nouveau modèle.",
			explain: "Dans ce cas, il n'y a aucun avantage à utiliser le même <i>tokenizer</i>.",
            correct: true
		},
        {
			text: "Lorsque votre jeu de données est différent de celui utilisé par un modèle pré-entraîné existant mais que vous souhaitez <i>finetuner</i> un nouveau modèle en utilisant ce modèle pré-entraîné.",
			explain: "Pour <i>finetuner</i> un modèle à partir d'un modèle pré-entraîné, vous devez toujours utiliser le même <i>tokenizer</i>."
		}
	]}
/>

### 2. Quel est l'avantage d'utiliser un générateur de listes par rapport à une liste de listes lors de l'utilisation de <code>train_new_from_iterator()</code> ?

<Question
	choices={[
		{
			text: "C'est le seul type que la méthode <code>train_new_from_iterator()</code> accepte.",
			explain: "Une liste de listes de textes est un type particulier de générateur de listes de textes, la méthode l'acceptera donc aussi. Essayez à nouveau !"
		},
		{
			text: "Vous éviterez de charger l'ensemble des données en mémoire en une seule fois.",
			explain: "Chaque batch de textes sera libéré de la mémoire lorsque vous itérerez et le gain sera particulièrement visible si vous utilisez des 🤗 <i>Datasets</i> pour stocker vos textes.",
			correct: true
		},
		{
			text: "Cela permettra à la bibliothèque 🤗 <i>Tokenizers</i> d'utiliser le multitraitement.",
			explain: "Il utilisera le multiprocesseur dans tous les cas."
		},
        {
			text: "Le <i>tokenizer</i> que vous entraînez générera de meilleurs textes.",
			explain: "Le <i>tokenizer</i> ne génère pas de texte. Vous le confondez avec un modèle de langage ?"
		}
	]}
/>

### 3. Quels sont les avantages d'utiliser un <i>tokenizer</i> « rapide » ?

<Question
	choices={[
		{
			text: "Il peut traiter les entrées plus rapidement qu'un <i>tokenizer</i> lent lorsque vous faites des batchs d'entrées.",
			explain: "Grâce au parallélisme implémenté dans Rust, il sera plus rapide sur les batchs d'entrées. Quel autre avantage pouvez-vous imaginer ?",
			correct: true
		},
		{
			text: "Les <i>tokenizers</i> rapides sont toujours plus rapides que leurs homologues lents.",
			explain: "Un <i>tokenizer</i> rapide peut en fait être plus lent si vous ne lui donnez qu'un seul ou très peu de textes, car il ne peut pas utiliser le parallélisme."
		},
		{
			text: "Il peut appliquer le <i>padding</i> et la troncature.",
			explain: "C'est vrai, mais les <i>tokenizers</i> lents le font aussi."
		},
        {
			text: "Il possède des fonctionnalités supplémentaires qui vous permettent d'associer les <i>tokens</i> à l'extrait de texte qui les a créés.",
			explain: "En effet, c'est ce qu'on appelle des correspondances d'<i>offset</i>. Ce n'est pas le seul avantage, cependant.",
			correct: true
		}
	]}
/>

### 4. Comment le pipeline `token-classification` gère-t-il les entités qui s'étendent sur plusieurs <i>tokens</i> ?

<Question
	choices={[
		{
			text: "Les entités ayant la même étiquette sont fusionnées en une seule entité.",
			explain: "C'est un peu trop simplifier les choses. Essayez encore !"
		},
		{
			text: "Il existe une étiquette pour le début d'une entité et une étiquette pour la suite d'une entité.",
			explain: " ",
			correct: true
		},
		{
			text: "Dans un mot donné, tant que le premier <i>token</i> porte l'étiquette de l'entité, le mot entier est considéré comme étiqueté avec cette entité.",
			explain: "C'est une stratégie pour gérer les entités. Quelles autres réponses s'appliquent ici ?",
			correct: true
		},
        {
			text: "Lorsqu'un <i>token</i> a l'étiquette d'une entité donnée, tout autre <i>token</i> suivant ayant la même étiquette est considéré comme faisant partie de la même entité, à moins qu'il ne soit étiqueté comme le début d'une nouvelle entité.",
			explain: "C'est la façon la plus courante de regrouper des entités, mais ce n'est pas la seule bonne réponse.",
			correct: true
		}
	]}
/>

### 5. Comment le pipeline `question-answering` gère-t-il les contextes longs ?

<Question
	choices={[
		{
			text: "Ce n'est pas vraiment le cas car il tronque le long contexte à la longueur maximale acceptée par le modèle.",
			explain: "Il existe une astuce que vous pouvez utiliser pour gérer les longs contextes. Vous en souvenez-vous ?"
		},
		{
			text: "Il divise le contexte en plusieurs parties et fait la moyenne des résultats obtenus.",
			explain: "Cela n'aurait pas de sens de faire la moyenne des résultats car certaines parties du contexte n'incluront pas la réponse."
		},
		{
			text: "Il divise le contexte en plusieurs parties (avec chevauchement) et trouve le score maximum pour une réponse dans chaque partie.",
			explain: "C'est la bonne réponse !",
			correct: true
		},
        {
			text: "Il divise le contexte en plusieurs parties (sans chevauchemen par souci d'efficacité) et trouve le score maximum pour une réponse dans chaque partie.",
			explain: "Il comprend un certain chevauchement entre les parties pour éviter une situation où la réponse serait divisée en deux parties."
		}
	]}
/>

### 6. Qu'est-ce que la normalisation ?

<Question
	choices={[
		{
			text: "C'est le nettoyage que le <i>tokenizer</i> effectue sur les textes lors des étapes initiales.",
			explain: "Par exemple, il peut s'agir de supprimer les accents ou les espaces, ou de mettre les entrées en minuscules.",
			correct: true
		},
		{
			text: "Il s'agit d'une technique d'augmentation de données qui consiste à rendre le texte plus normal en supprimant les mots rares.",
			explain: "Essayez encore."
		},
		{
			text: "C'est l'étape finale du post-traitement où le <i>tokenizer</i> ajoute les <i>tokens</i> spéciaux.",
			explain: "Cette étape est simplement appelée post-traitement."
		},
        {
			text: "C'est lorsque les enchâssements sont faits avec une moyenne nulle et un écart-type de 1, en soustrayant la moyenne et en divisant par l'écart-type.",
			explain: "Ce processus est communément appelé normalisation lorsqu'il est appliqué aux valeurs des pixels en vision par ordinateur, mais ce n'est pas ce que signifie la normalisation en NLP."
		}
	]}
/>

### 7. Qu'est-ce que la pré-tokénisation pour un <i>tokenizer</i> en sous-mots ?

<Question
	choices={[
		{
			text: "C'est l'étape qui précède la tokénisation, où l'augmentation des données (comme le masquage aléatoire) est appliquée.",
			explain: "Cette étape fait partie du prétraitement."
		},
		{
			text: "C'est l'étape avant la tokénisation, où les opérations de nettoyage souhaitées sont appliquées au texte.",
			explain: "C'est l'étape de normalisation."
		},
		{
			text: "C'est l'étape qui précède l'application du modèle <i>tokenizer</i>, pour diviser l'entrée en mots.",
			explain: "C'est la bonne réponse !",
			correct: true
		},
        {
			text: "Il s'agit de l'étape précédant l'application du modèle <i>tokenizer</i>, qui divise l'entrée en <i>tokens</i>.",
			explain: "La division en <i>tokens</i> est le travail du modèle <i>tokenizer</i>."
		}
	]}
/>

### 8. Sélectionnez les phrases qui s'appliquent au <i>tokenizer</i> BPE.

<Question
	choices={[
		{
			text: "BPE est un algorithme de tokénisation en sous-mots qui part d'un petit vocabulaire et apprend des règles de fusion.",
			explain: "C'est le cas en effet !",
			correct: true
		},
		{
			text: "BPE est un algorithme de tokénisation en sous-mots qui part d'un grand vocabulaire et en retire progressivement les <i>tokens</i>.",
			explain: "C'est l'approche adoptée par un algorithme de tokénisation différent."
		},
		{
			text: "Un <i>tokenizer</i> BPE apprend les règles de fusion en fusionnant la paire de <i>tokens</i> la plus fréquente.",
			explain: "C'est exact !",
			correct: true
		},
		{
			text: "Un <i>tokenizer</i> BPE apprend une règle de fusion en fusionnant la paire de <i>tokens</i> qui maximise un score qui privilégie les paires fréquentes avec des parties individuelles moins fréquentes.",
			explain: "C'est la stratégie appliquée par un autre algorithme de tokenization."
		},
		{
			text: "BPE tokenise les mots en sous-mots en les divisant en caractères, puis en appliquant les règles de fusion.",
			explain: " ",
			correct: true
		},
		{
			text: "BPE tokenise les mots en sous-mots en trouvant le plus long sous-mot du vocabulaire en commençant par le début, puis en répétant le processus pour le reste du texte.",
			explain: "C'est la façon de faire d'un autre algorithme de tokenization."
		},
	]}
/>

### 9. Sélectionnez les phrases qui s'appliquent au <i>tokenizer</i> WordPiece.

<Question
	choices={[
		{
			text: "WordPiece est un algorithme de tokénisation en sous-mots qui part d'un petit vocabulaire et apprend des règles de fusion.",
			explain: "C'est le cas en effet !",
			correct: true
		},
		{
			text: "WordPiece est un algorithme de tokénisation en sous-mots qui part d'un grand vocabulaire et en retire progressivement les <i>tokens</i>.",
			explain: "C'est la façon de faire d'un autre algorithme de tokenization."
		},
		{
			text: "Les <i>tokenizer</i> WordPiece apprennent les règles de fusion en fusionnant la paire de <i>tokens</i> la plus fréquente.",
			explain: "C'est la façon de faire d'un autre algorithme de tokenization."
		},
		{
			text: "Un <i>tokenizer</i> WordPiece apprend une règle de fusion en fusionnant la paire de <i>tokens</i> qui maximise un score qui privilégie les paires fréquentes avec des parties individuelles moins fréquentes.",
			explain: " ",
			correct: true
		},
		{
			text: "WordPiece tokenise les mots en sous-mots en trouvant la segmentation en <i>tokens</i> la plus probable, selon le modèle.",
			explain: "C'est la façon de faire d'un autre algorithme de tokenization."
		},
		{
			text: "WordPiece tokenise les mots en sous-mots en trouvant le plus long sous-mot du vocabulaire en commençant par le début, puis en répétant le processus pour le reste du texte.",
			explain: "C'est ainsi que WordPiece procède pour l'encodage.",
			correct: true
		},
	]}
/>

### 10. Sélectionnez les phrases qui s'appliquent au <i>tokenizer</i> Unigram.

<Question
	choices={[
		{
			text: "Unigram est un algorithme de tokénisation en sous-mots qui part d'un petit vocabulaire et apprend des règles de fusion.",
			explain: "C'est la façon de faire d'un autre algorithme de tokenization."
		},
		{
			text: "Unigram est un algorithme de tokénisation en sous-mots qui part d'un grand vocabulaire et en retire progressivement les <i>tokens</i>.",
			explain: " ",
			correct: true
		},
		{
			text: "Unigram adapte son vocabulaire en minimisant une perte calculée sur l'ensemble du corpus.",
			explain: " ",
			correct: true
		},
		{
			text: "Unigram adapte son vocabulaire en conservant les sous-mots les plus fréquents.",
			explain: " "
		},
		{
			text: "Unigram segmente les mots en sous-mots en trouvant la segmentation la plus probable en <i>tokens</i>, selon le modèle.",
			explain: " ",
			correct: true
		},
		{
			text: "Unigram décompose les mots en sous-mots en les divisant en caractères puis en appliquant les règles de fusion.",
			explain: "C'est la façon de faire d'un autre algorithme de tokenization."
		},
	]}
/>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter6/10.mdx" />

### Construction d'un <i>tokenizer</i>, bloc par bloc
https://huggingface.co/learn/course/fr/chapter6/8.md

# Construction d'un <i>tokenizer</i>, bloc par bloc


<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter6/section8.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter6/section8.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section8.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter6/section8.ipynb"},
]} />

Comme nous l'avons vu dans les sections précédentes, la tokenisation comprend plusieurs étapes :

- normalisation (tout nettoyage du texte jugé nécessaire, comme la suppression des espaces ou des accents, la normalisation Unicode, etc.),
- prétokénisation (division de l'entrée en mots),
- passage de l'entrée dans le modèle (utilisation des mots prétokénisés pour produire une séquence de *tokens*),
- post-traitement (ajout des *tokens* spéciaux du *tokenizer*, génération du masque d'attention et des identifiants du type de *token*).

Pour mémoire, voici un autre aperçu du processus global :

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/tokenization_pipeline.svg" alt="The tokenization pipeline.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/tokenization_pipeline-dark.svg" alt="The tokenization pipeline.">
</div>

La bibliothèque 🤗 *Tokenizers* a été construite pour fournir plusieurs options pour chacune de ces étapes. Vous pouvez les mélanger et assortir ensemble. Dans cette section, nous verrons comment nous pouvons construire un *tokenizer* à partir de zéro, par opposition à entraîner un nouveau *tokenizer* à partir d'un ancien, comme nous l'avons fait dans [section 2](/course/fr/chapter6/2). Vous serez alors en mesure de construire n'importe quel type de *tokenizer* auquel vous pouvez penser !

<Youtube id="MR8tZm5ViWU"/>

Plus précisément, la bibliothèque est construite autour d'une classe centrale `Tokenizer` avec les blocs de construction regroupés en sous-modules :

- `normalizers` contient tous les types de `Normalizer` que vous pouvez utiliser (liste complète [ici](https://huggingface.co/docs/tokenizers/api/normalizers)),
- `pre_tokenizers` contient tous les types de `PreTokenizer` que vous pouvez utiliser (liste complète [ici](https://huggingface.co/docs/tokenizers/api/pre-tokenizers)),
- `models` contient les différents types de `Model` que vous pouvez utiliser, comme `BPE`, `WordPiece`, et `Unigram` (liste complète [ici](https://huggingface.co/docs/tokenizers/api/models)),
- `trainers` contient tous les différents types de `Trainer` que vous pouvez utiliser pour entraîner votre modèle sur un corpus (un par type de modèle ; liste complète [ici](https://huggingface.co/docs/tokenizers/api/trainers)),
- `post_processors` contient les différents types de `PostProcessor` que vous pouvez utiliser (liste complète [ici](https://huggingface.co/docs/tokenizers/api/post-processors)),
- `decoders` contient les différents types de `Decoder` que vous pouvez utiliser pour décoder les sorties de tokenization (liste complète [ici](https://huggingface.co/docs/tokenizers/components#decoders)).

Vous pouvez trouver la liste complète des blocs de construction [ici](https://huggingface.co/docs/tokenizers/components).

## Acquisition d'un corpus

Pour entraîner notre nouveau *tokenizer*, nous utiliserons un petit corpus de texte (pour que les exemples soient rapides). Les étapes pour acquérir ce corpus sont similaires à celles que nous avons suivies au [début du chapitre](/course/fr/chapter6/2), mais cette fois nous utiliserons le jeu de données [WikiText-2](https://huggingface.co/datasets/wikitext) :


```python
from datasets import load_dataset

dataset = load_dataset("wikitext", name="wikitext-2-raw-v1", split="train")


def get_training_corpus():
    for i in range(0, len(dataset), 1000):
        yield dataset[i : i + 1000]["text"]
```

La fonction `get_training_corpus()` est un générateur qui donne des batchs de 1 000 textes, que nous utiliserons pour entraîner le *tokenizer*. 

🤗 *Tokenizers* peut aussi être entraîné directement sur des fichiers texte. Voici comment nous pouvons générer un fichier texte contenant tous les textes de WikiText-2 que nous pourrons ensuite utilisé en local :

```python
with open("wikitext-2.txt", "w", encoding="utf-8") as f:
    for i in range(len(dataset)):
        f.write(dataset[i]["text"] + "\n")
```

Ensuite, nous vous montrerons comment construire vos propres *tokenizers* pour BERT, GPT-2 et XLNet, bloc par bloc. Cela vous donnera un exemple de chacun des trois principaux algorithmes de tokenisation : *WordPiece*, BPE et *Unigram*. Commençons par BERT !

## Construire un <i>tokenizer WordPiece</i> à partir de zéro

Pour construire un *tokenizer* avec la bibliothèque 🤗 *Tokenizers*, nous commençons par instancier un objet `Tokenizer` avec un `model`. Puis nous définissons ses attributs `normalizer`, `pre_tokenizer`, `post_processor` et `decoder` aux valeurs que nous voulons.

Pour cet exemple, nous allons créer un `Tokenizer` avec un modèle *WordPiece* :

```python
from tokenizers import (
    decoders,
    models,
    normalizers,
    pre_tokenizers,
    processors,
    trainers,
    Tokenizer,
)

tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]"))
```

Nous devons spécifier le `unk_token` pour que le modèle sache quoi retourner lorsqu'il rencontre des caractères qu'il n'a pas vu auparavant. D'autres arguments que nous pouvons définir ici incluent le `vocab` de notre modèle (nous allons entraîner le modèle, donc nous n'avons pas besoin de le définir) et `max_input_chars_per_word`, qui spécifie une longueur maximale pour chaque mot (les mots plus longs que la valeur passée seront séparés).

La première étape de la tokénisation est la normalisation. Puisque BERT est largement utilisé, une fonction `BertNormalizer` a été créée avec les options classiques que nous pouvons définir pour BERT : `lowercase` pour mettre le texte en minuscule, `strip_accents` qui enlève les accents, `clean_text` pour enlever tous les caractères de contrôle et fusionner des espaces répétés par un seul, et `handle_chinese_chars` qui place des espaces autour des caractères chinois. Pour reproduire le *tokenizer* `bert-base-uncased`, nous pouvons simplement définir ce *normalizer* :

```python
tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True)
```

Cependant, généralement, lorsque vous construisez un nouveau *tokenizer*, vous n'avez pas accès à un normaliseur aussi pratique déjà implémenté dans la bibliothèque 🤗 *Tokenizers*. Donc voyons comment créer le normaliseur de BERT manuellement. La bibliothèque fournit un normaliseur `Lowercase` et un normaliseur `StripAccents`. Il est possible de composer plusieurs normaliseurs en utilisant une `Sequence` :

```python
tokenizer.normalizer = normalizers.Sequence(
    [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()]
)
```

Nous utilisons également un normaliseur Unicode `NFD`, car sinon `StripAccents` ne reconnaîtra pas correctement les caractères accentués et ne les supprimera donc pas.

Comme nous l'avons vu précédemment, nous pouvons utiliser la méthode `normalize_str()` du `normalizer` pour vérifier les effets qu'il a sur un texte donné :

```python
print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?"))
```

```python out
hello how are u?
```

<Tip>

**Pour aller plus loin** Si vous testez les deux versions des normaliseurs précédents sur une chaîne contenant le caractère unicode `u"\u0085"` vous remarquerez sûrement qu'ils ne sont pas exactement équivalents. 
Pour ne pas trop compliquer la version avec `normalizers.Sequence`, nous n'avons pas inclus les Regex que le `BertNormalizer` requiert quand l'argument `clean_text` est mis à `True` ce qui est le comportement par défaut. Mais ne vous inquiétez pas : il est possible d'obtenir exactement la même normalisation sans utiliser le très pratique `BertNormalizer` en ajoutant deux `normalizers.Replace` à la séquence de normalisation. 

</Tip>

L'étape suivante est la prétokenisation. Encore une fois, il y a un `BertPreTokenizer` préconstruit que nous pouvons utiliser :

```python
tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer()
```

Ou nous pouvons le construire à partir de zéro :

```python
tokenizer.pre_tokenizer = pre_tokenizers.Whitespace()
```

Notez que le `Whitespace` divise sur les espaces et tous les caractères qui ne sont pas des lettres, des chiffres ou le caractère de soulignement. Donc techniquement il divise sur les espaces et la ponctuation :

```python
tokenizer.pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.")
```

```python out
[('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)),
 ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))]
```

Si vous voulez seulement séparer sur les espaces, vous devez utiliser `WhitespaceSplit` à la place :

```python
pre_tokenizer = pre_tokenizers.WhitespaceSplit()
pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.")
```

```python out
[("Let's", (0, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre-tokenizer.', (14, 28))]
```

Comme pour les normaliseurs, vous pouvez utiliser une `Sequence` pour composer plusieurs prétokenizers :

```python
pre_tokenizer = pre_tokenizers.Sequence(
    [pre_tokenizers.WhitespaceSplit(), pre_tokenizers.Punctuation()]
)
pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.")
```

```python out
[('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)),
 ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))]
```

L'étape suivante dans le pipeline de tokénisation est de faire passer les entrées par le modèle. Nous avons déjà spécifié notre modèle dans l'initialisation, mais nous devons encore l'entraîner, ce qui nécessitera un `WordPieceTrainer`. La principale chose à retenir lors de l'instanciation d'un entraîneur dans 🤗 *Tokenizers* est que vous devez lui passer tous les *tokens* spéciaux que vous avez l'intention d'utiliser. Sinon il ne les ajoutera pas au vocabulaire puisqu'ils ne sont pas dans le corpus d'entraînement :

```python
special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"]
trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens)
```

En plus de spécifier la `vocab_size` et les `special_tokens`, nous pouvons définir la `min_frequency` (le nombre de fois qu'un *token* doit apparaître pour être inclus dans le vocabulaire) ou changer le `continuing_subword_prefix` (si nous voulons utiliser quelque chose de différent de `##`).

Pour entraîner notre modèle en utilisant l'itérateur que nous avons défini plus tôt, il suffit d'exécuter cette commande :

```python
tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)
```

Nous pouvons également utiliser des fichiers texte pour entraîner notre *tokenizer* qui ressemblerait alors à ceci (nous réinitialisons le modèle avec un `WordPiece` vide au préalable) :

```python
tokenizer.model = models.WordPiece(unk_token="[UNK]")
tokenizer.train(["wikitext-2.txt"], trainer=trainer)
```

Dans les deux cas, nous pouvons ensuite tester le *tokenizer* sur un texte en appelant la méthode `encode()` :

```python
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```

```python out
['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.']
```

L'encodage obtenu est un `Encoding` contenant toutes les sorties nécessaires du *tokenizer* dans ses différents attributs : `ids`, `type_ids`, `tokens`, `offsets`, `attention_mask`, `special_tokens_mask` et `overflowing`.

La dernière étape du pipeline de tokénisation est le post-traitement. Nous devons ajouter le *token* `[CLS]` au début et le *token* `[SEP]` à la fin (ou après chaque phrase si nous avons une paire de phrases). Nous utiliserons `TemplateProcessor` pour cela, mais d'abord nous devons connaître les identifiants des *tokens* `[CLS]` et `[SEP]` dans le vocabulaire :

```python
cls_token_id = tokenizer.token_to_id("[CLS]")
sep_token_id = tokenizer.token_to_id("[SEP]")
print(cls_token_id, sep_token_id)
```

```python out
(2, 3)
```

Pour écrire le gabarit pour `TemplateProcessor`, nous devons spécifier comment traiter une seule phrase et une paire de phrases. Pour les deux, nous écrivons les *tokens* spéciaux que nous voulons utiliser. La première (ou unique) phrase est représentée par `$A`, alors que la deuxième phrase (si on code une paire) est représentée par `$B`. Pour chacun de ces éléments (*tokens* spéciaux et phrases), nous spécifions également l'identifiant du *token* correspondant après un deux-points. 

Le gabarit classique de BERT est donc défini comme suit :

```python
tokenizer.post_processor = processors.TemplateProcessing(
    single=f"[CLS]:0 $A:0 [SEP]:0",
    pair=f"[CLS]:0 $A:0 [SEP]:0 $B:1 [SEP]:1",
    special_tokens=[("[CLS]", cls_token_id), ("[SEP]", sep_token_id)],
)
```

Notez que nous devons transmettre les identifiants des *tokens* spéciaux afin que le *tokenizer* puisse les convertir correctement.

Une fois cela ajouté, revenons à notre exemple précédent donnera :

```python
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```

```python out
['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]']
```

Et sur une paire de phrases, on obtient le bon résultat :

```python
encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences.")
print(encoding.tokens)
print(encoding.type_ids)
```

```python out
['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '...', '[SEP]', 'on', 'a', 'pair', 'of', 'sentences', '.', '[SEP]']
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1]
```

Nous avons presque fini de construire ce *tokenizer* à partir de zéro. La dernière étape consiste à inclure un décodeur : 

```python
tokenizer.decoder = decoders.WordPiece(prefix="##")
```

Testons-le sur notre précédent `encoding` :

```python
tokenizer.decode(encoding.ids)
```

```python out
"let's test this tokenizer... on a pair of sentences." # Testons ce tokenizer... sur une paire de phrases.
```

Génial ! Nous pouvons enregistrer notre *tokenizer* dans un seul fichier JSON comme ceci :

```python
tokenizer.save("tokenizer.json")
```

Nous pouvons alors recharger ce fichier dans un objet `Tokenizer` avec la méthode `from_file()` :

```python
new_tokenizer = Tokenizer.from_file("tokenizer.json")
```

Pour utiliser ce *tokenizer* dans 🤗 *Transformers*, nous devons l'envelopper dans un `PreTrainedTokenizerFast`. Nous pouvons soit utiliser la classe générique, soit, si notre *tokenizer* correspond à un modèle existant, utiliser cette classe (ici, `BertTokenizerFast`). Si vous appliquez cette logique pour construire un tout nouveau *tokenizer*, vous devrez utiliser la première option.

Pour envelopper le *tokenizer* dans un `PreTrainedTokenizerFast`, nous pouvons soit passer le *tokenizer* que nous avons construit comme un `tokenizer_object`, soit passer le fichier de *tokenizer* que nous avons sauvegardé comme `tokenizer_file`. Ce qu'il faut retenir, c'est que nous devons définir manuellement tous les *tokens* spéciaux car cette classe ne peut pas déduire de l'objet `tokenizer` quel *token* est le *token* de masque, quel est le *token*`[CLS]`, etc :

```python
from transformers import PreTrainedTokenizerFast

wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=tokenizer,
    # tokenizer_file="tokenizer.json", # Vous pouvez charger à partir du fichier du tokenizer, alternativement
    unk_token="[UNK]",
    pad_token="[PAD]",
    cls_token="[CLS]",
    sep_token="[SEP]",
    mask_token="[MASK]",
)
```

Si vous utilisez une classe de *tokenizer* spécifique (comme `BertTokenizerFast`), vous aurez seulement besoin de spécifier les *tokens* spéciaux qui sont différents de ceux par défaut (ici, aucun) :

```python
from transformers import BertTokenizerFast

wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer)
```

Vous pouvez ensuite utiliser ce *tokenizer* comme n'importe quel autre *tokenizer* de 🤗 *Transformers*. Vous pouvez le sauvegarder avec la méthode `save_pretrained()` ou le télécharger sur le *Hub* avec la méthode `push_to_hub()`.

Maintenant que nous avons vu comment construire un *tokenizer WordPiece*, faisons de même pour un *tokenizer* BPE. Nous irons un peu plus vite puisque vous connaissez toutes les étapes. Nous ne soulignerons que les différences.

## Construire un <i>tokenizer</i> BPE à partir de zéro

Construisons maintenant un *tokenizer* BPE. Comme pour le *tokenizer* BERT, nous commençons par initialiser un `Tokenizer` avec un modèle BPE :

```python
tokenizer = Tokenizer(models.BPE())
```

Comme pour BERT, nous pourrions initialiser ce modèle avec un vocabulaire si nous en avions un (nous aurions besoin de passer le `vocab` et le `merges` dans ce cas), mais puisque nous allons nous entraîner à partir de zéro, nous n'avons pas besoin de le faire. Nous n'avons pas non plus besoin de spécifier un `unk_token` parce que le GPT-2 utilise un BPE au niveau de l'octet.

GPT-2 n'utilise pas de normaliseur, donc nous sautons cette étape et allons directement à la prétokénisation :

```python
tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)
```

L'option que nous avons ajoutée à `ByteLevel` ici est de ne pas ajouter d'espace en début de phrase (ce qui est le cas par défaut). Nous pouvons jeter un coup d'oeil à la prétokénisation d'un texte d'exemple comme avant :

```python
tokenizer.pre_tokenizer.pre_tokenize_str("Let's test pre-tokenization!")
```

```python out
[('Let', (0, 3)), ("'s", (3, 5)), ('Ġtest', (5, 10)), ('Ġpre', (10, 14)), ('-', (14, 15)),
 ('tokenization', (15, 27)), ('!', (27, 28))]
```

Vient ensuite le modèle, qui doit être entraîné. Pour le GPT-2, le seul *token* spécial est le *token* de fin de texte :

```python
trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"])
tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)
```

Comme avec le `WordPieceTrainer`, ainsi que le `vocab_size` et le `special_tokens`, nous pouvons spécifier la `min_frequency` si nous le voulons, ou si nous avons un suffixe de fin de mot (comme `</w>`), nous pouvons le définir avec `end_of_word_suffix`. 

Ce *tokenizer* peut aussi être entraîné sur des fichiers texte :

```python
tokenizer.model = models.BPE()
tokenizer.train(["wikitext-2.txt"], trainer=trainer)
```

Regardons la tokenisation d'un exemple de texte :

```python
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```

```python out
['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.']
```

Nous appliquons le post-traitement au niveau de l'octet pour le *tokenizer* du GPT-2 comme suit :

```python
tokenizer.post_processor = processors.ByteLevel(trim_offsets=False)
```

L'option `trim_offsets = False` indique au post-processeur que nous devons laisser les *offsets* des *tokens* qui commencent par 'Ġ' tels quels : de cette façon, le début des *offsets* pointera sur l'espace avant le mot, et non sur le premier caractère du mot (puisque l'espace fait techniquement partie du *token*). Regardons le résultat avec le texte que nous venons de coder, où `'Ġtest'` est le *token* à l'index 4 :

```python
sentence = "Let's test this tokenizer."
encoding = tokenizer.encode(sentence)
start, end = encoding.offsets[4]
sentence[start:end]
```

```python out
' test'
```

Enfin, nous ajoutons un décodeur au niveau de l'octet :

```python
tokenizer.decoder = decoders.ByteLevel()
```

et nous pouvons vérifier qu'il fonctionne correctement :

```python
tokenizer.decode(encoding.ids)
```

```python out
"Let's test this tokenizer." # Testons ce tokenizer
```

Super ! Maintenant que nous avons terminé, nous pouvons sauvegarder le tokenizer comme avant, et l'envelopper dans un `PreTrainedTokenizerFast` ou un `GPT2TokenizerFast` si nous voulons l'utiliser dans 🤗 *Transformers* :

```python
from transformers import PreTrainedTokenizerFast

wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=tokenizer,
    bos_token="<|endoftext|>",
    eos_token="<|endoftext|>",
)
```

ou :

```python
from transformers import GPT2TokenizerFast

wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer)
```

Comme dernier exemple, nous allons vous montrer comment construire un *tokenizer* *Unigram* à partir de zéro.

## Construire un <i>tokenizer Unigram</i> à partir de zéro

Construisons maintenant un *tokenizer* XLNet. Comme pour les *tokenizers* précédents, nous commençons par initialiser un `Tokenizer` avec un modèle *Unigram* :

```python
tokenizer = Tokenizer(models.Unigram())
```

Encore une fois, nous pourrions initialiser ce modèle avec un vocabulaire si nous en avions un.

Pour la normalisation, XLNet utilise quelques remplacements (qui proviennent de *SentencePiece*) :

```python
from tokenizers import Regex

tokenizer.normalizer = normalizers.Sequence(
    [
        normalizers.Replace("``", '"'),
        normalizers.Replace("''", '"'),
        normalizers.NFKD(),
        normalizers.StripAccents(),
        normalizers.Replace(Regex(" {2,}"), " "),
    ]
)
```

Il remplace <code>``</code> et <code>''</code> par <code>"</code> et toute séquence de deux espaces ou plus par un seul espace, de plus il supprime les accents.

Le prétokenizer à utiliser pour tout *tokenizer SentencePiece* est `Metaspace` :

```python
tokenizer.pre_tokenizer = pre_tokenizers.Metaspace()
```

Nous pouvons jeter un coup d'oeil à la prétokénisation sur le même exemple de texte que précédemment :

```python
tokenizer.pre_tokenizer.pre_tokenize_str("Let's test the pre-tokenizer!")
```

```python out
[("▁Let's", (0, 5)), ('▁test', (5, 10)), ('▁the', (10, 14)), ('▁pre-tokenizer!', (14, 29))]
```

Vient ensuite le modèle, qui doit être entraîné. XLNet possède un certain nombre de *tokens* spéciaux :

```python
special_tokens = ["<cls>", "<sep>", "<unk>", "<pad>", "<mask>", "<s>", "</s>"]
trainer = trainers.UnigramTrainer(
    vocab_size=25000, special_tokens=special_tokens, unk_token="<unk>"
)
tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)
```

Un argument très important à ne pas oublier pour le `UnigramTrainer` est le `unk_token`. Nous pouvons aussi passer d'autres arguments spécifiques à l'algorithme *Unigram*, comme le `shrinking_factor` pour chaque étape où nous enlevons des *tokens* (par défaut 0.75) ou le `max_piece_length` pour spécifier la longueur maximale d'un *token* donné (par défaut 16).

Ce *tokenizer* peut aussi être entraîné sur des fichiers texte :

```python
tokenizer.model = models.Unigram()
tokenizer.train(["wikitext-2.txt"], trainer=trainer)
```

Regardons la tokenisation de notre exemple :

```python
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```

```python out
['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.']
```

Une particularité de XLNet est qu'il place le *token* `<cls>` à la fin de la phrase, avec un identifiant de 2 (pour le distinguer des autres *tokens*). Le résultat est un remplissage à gauche. Nous pouvons traiter tous les *tokens* spéciaux et les types d'identifiant de *token* avec un modèle, comme pour BERT. Mais d'abord nous devons obtenir les identifiants des *tokens* `<cls>` et `<sep>` :

```python
cls_token_id = tokenizer.token_to_id("<cls>")
sep_token_id = tokenizer.token_to_id("<sep>")
print(cls_token_id, sep_token_id)
```

```python out
0 1
```

Le modèle ressemble à ceci :

```python
tokenizer.post_processor = processors.TemplateProcessing(
    single="$A:0 <sep>:0 <cls>:2",
    pair="$A:0 <sep>:0 $B:1 <sep>:1 <cls>:2",
    special_tokens=[("<sep>", sep_token_id), ("<cls>", cls_token_id)],
)
```

Et nous pouvons tester son fonctionnement en codant une paire de phrases :

```python
encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences!")
print(encoding.tokens)
print(encoding.type_ids)
```

```python out
['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.', '.', '.', '<sep>', '▁', 'on', '▁', 'a', '▁pair', 
  '▁of', '▁sentence', 's', '!', '<sep>', '<cls>']
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2]
```

Enfin, nous ajoutons un décodeur `Metaspace` :

```python
tokenizer.decoder = decoders.Metaspace()
```

et on en a fini avec ce *tokenizer* ! On peut le sauvegarder et l'envelopper dans un `PreTrainedTokenizerFast` ou `XLNetTokenizerFast` si on veut l'utiliser dans 🤗 *Transformers*. Une chose à noter lors de l'utilisation de `PreTrainedTokenizerFast` est qu'en plus des *tokens* spéciaux, nous devons dire à la bibliothèque 🤗 *Transformers* de rembourrer à gauche :

```python
from transformers import PreTrainedTokenizerFast

wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=tokenizer,
    bos_token="<s>",
    eos_token="</s>",
    unk_token="<unk>",
    pad_token="<pad>",
    cls_token="<cls>",
    sep_token="<sep>",
    mask_token="<mask>",
    padding_side="left",
)
```

Ou alternativement :

```python
from transformers import XLNetTokenizerFast

wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer)
```

Maintenant que vous avez vu comment les différentes briques sont utilisées pour construire des *tokenizers* existants, vous devriez être capable d'écrire n'importe quel *tokenizer* que vous voulez avec la bibliothèque 🤗 *Tokenizers* et pouvoir l'utiliser dans 🤗 *Transformers*.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter6/8.mdx" />

### Normalisation et prétokenization
https://huggingface.co/learn/course/fr/chapter6/4.md

# Normalisation et prétokenization

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter6/section4.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter6/section4.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section4.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter6/section4.ipynb"},
]} />

Avant de nous plonger plus profondément dans les trois algorithmes de tokénisation en sous-mots les plus courants utilisés avec les *transformers* (*Byte-Pair Encoding* (BPE), *WordPiece* et *Unigram*), nous allons d'abord examiner le prétraitement que chaque *tokenizer* applique au texte. Voici un aperçu de haut niveau des étapes du pipeline de tokenisation :

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/tokenization_pipeline.svg" alt="The tokenization pipeline.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/tokenization_pipeline-dark.svg" alt="The tokenization pipeline.">
</div>

Avant de diviser un texte en sous-*tokens* (selon le modèle), le *tokenizer* effectue deux étapes : la _normalisation_ et la _prétokénisation_.

## Normalisation

<Youtube id="4IIC2jI9CaU"/>

L'étape de normalisation implique un nettoyage général, comme la suppression des espaces inutiles, la mise en minuscules et/ou la suppression des accents. Si vous êtes familier avec la [normalisation Unicode](http://www.unicode.org/reports/tr15/) (comme NFC ou NFKC), c'est aussi quelque chose que le *tokenizer* peut appliquer.

Le `tokenizer` de 🤗 *Transformers* possède un attribut appelé `backend_tokenizer` qui donne accès au *tokenizer* sous-jacent de la bibliothèque 🤗 *Tokenizers* :

```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
print(type(tokenizer.backend_tokenizer))
```

```python out
<class 'tokenizers.Tokenizer'>
```

L'attribut `normalizer` de l'objet `tokenizer` possède une méthode `normalize_str()` que nous pouvons utiliser pour voir comment la normalisation est effectuée :

```py
print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü?"))
```

```python out
'hello how are u?'
```

Dans cet exemple, puisque nous avons choisi le *checkpoint* `bert-base-uncased`, la normalisation a mis le texte en minuscule et supprimé les accents. 

<Tip>

✏️ **Essayez !** Chargez un *tokenizer* depuis le *checkpoint* `bert-base-cased` et passez-lui le même exemple. Quelles sont les principales différences que vous pouvez voir entre les versions casée et non casée du *tokenizer* ?

</Tip>

## Prétokenization

<Youtube id="grlLV8AIXug"/>

Comme nous le verrons dans les sections suivantes, un *tokenizer* ne peut pas être entraîné uniquement sur du texte brut. Au lieu de cela, nous devons d'abord diviser les textes en petites entités, comme des mots. C'est là qu'intervient l'étape de prétokénisation. Comme nous l'avons vu dans le [chapitre 2](/course/fr/chapter2), un *tokenizer* basé sur les mots peut simplement diviser un texte brut en mots sur les espaces et la ponctuation. Ces mots constitueront les limites des sous-*tokens* que le *tokenizer* peut apprendre pendant son entraînement.

Pour voir comment un *tokenizer* rapide effectue la prétokénisation, nous pouvons utiliser la méthode `pre_tokenize_str()`de l'attribut `pre_tokenizer` de l'objet `tokenizer` :

```py
tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are  you?")
```

```python out
[('Hello', (0, 5)), (',', (5, 6)), ('how', (7, 10)), ('are', (11, 14)), ('you', (16, 19)), ('?', (19, 20))]
```

Remarquez que le *tokenizer* garde déjà la trace des *offsets*, ce qui lui permet de nous donner la correspondance des décalages que nous avons utilisée dans la section précédente. Ici, le *tokenizer* ignore les deux espaces et les remplace par un seul, mais le décalage saute entre `are` et `you` pour en tenir compte.

Puisque nous utilisons le *tokenizer* de BERT, la prétokénisation implique la séparation des espaces et de la ponctuation. D'autres *tokenizers* peuvent avoir des règles différentes pour cette étape. Par exemple, si nous utilisons le *tokenizer* du GPT-2 :

```py
tokenizer = AutoTokenizer.from_pretrained("gpt2")
tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are  you?")
```

Il séparera aussi sur les espaces et la ponctuation mais gardera les espaces et les remplacera par un symbole `Ġ`, ce qui lui permettra de récupérer les espaces originaux si nous décodons les *tokens* :

```python out
[('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)),
 ('?', (19, 20))]
```

Notez également que, contrairement au *tokenizer* de BERT, ce *tokenizer* n'ignore pas les doubles espaces.

Pour un dernier exemple, regardons le *tokenizer* du 5, qui est basé sur l'algorithme SentencePiece :

```py
tokenizer = AutoTokenizer.from_pretrained("t5-small")
tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are  you?")
```

```python out
[('▁Hello,', (0, 6)), ('▁how', (7, 10)), ('▁are', (11, 14)), ('▁you?', (16, 20))]
```

Comme le *tokenizer* du GPT-2, celui-ci garde les espaces et les remplace par un *token* spécifique (`_`), mais le *tokenizer* du T5 ne sépare que sur les espaces, pas sur la ponctuation. Notez également qu'il a ajouté un espace par défaut au début de la phrase (avant `Hello`) et il ignore le double espace entre `are` et `you`.

Maintenant que nous avons vu un peu comment les différents *tokenizers* traitent le texte, nous pouvons commencer à explorer les algorithmes sous-jacents eux-mêmes. Nous commencerons par jeter un coup d'oeil rapide sur le très répandu SentencePiece, puis au cours des trois sections suivantes nous examinerons le fonctionnement des trois principaux algorithmes utilisés pour la tokenisation en sous-mots.

## SentencePiece

[SentencePiece](https://github.com/google/sentencepiece) est un algorithme de tokenisation pour le prétraitement du texte que vous pouvez utiliser avec n'importe lequel des modèles que nous verrons dans les trois prochaines sections. Il considère le texte comme une séquence de caractères Unicode et il remplace les espaces par un caractère spécial : `▁`. Utilisé en conjonction avec l'algorithme Unigram (voir la [section 7](/course/fr/chapter7/7)), il ne nécessite même pas d'étape de prétokénisation, ce qui est très utile pour les langues où le caractère espace n'est pas utilisé (comme le chinois ou le japonais).

L'autre caractéristique principale de SentencePiece est le *tokenisation réversible* : comme il n'y a pas de traitement spécial des espaces, le décodage des *tokens* se fait simplement en les concaténant et en remplaçant les `_` par des espaces, ce qui donne le texte normalisé. Comme nous l'avons vu précédemment, le *tokenizer* de BERT supprime les espaces répétitifs, donc sa tokenisation n'est pas réversible.

## Vue d'ensemble des algorithmes

Dans les sections suivantes, nous allons nous plonger dans les trois principaux algorithmes de tokenisation en sous-mots : BPE (utilisé par GPT-2 et autres), WordPiece (utilisé par exemple par BERT), et Unigram (utilisé par T5 et autres). Avant de commencer, voici un rapide aperçu du fonctionnement de chacun d'entre eux. N'hésitez pas à revenir à ce tableau après avoir lu chacune des sections suivantes si cela n'a pas encore de sens pour vous.


Modèle | BPE | WordPiece | Unigramme
:----:|:---:|:---------:|:------:
Entraînement | Part d'un petit vocabulaire et apprend des règles pour fusionner les *tokens* | Part d'un petit vocabulaire et apprend des règles pour fusionner les *tokens* | Part d'un grand vocabulaire et apprend des règles pour supprimer les *tokens*
Étape d'entraînement | Fusionne les *tokens* correspondant à la paire la plus commune | Fusionne les *tokens* correspondant à la paire ayant le meilleur score basé sur la fréquence de la paire, en privilégiant les paires où chaque *token* individuel est moins fréquent | Supprime tous les *tokens* du vocabulaire qui minimiseront la perte calculée sur le corpus entier
Apprend | A fusionner des règles et un vocabulaire | Juste un vocabulaire | Un vocabulaire avec un score pour chaque *token*
Encodage | Découpe un mot en caractères et applique les fusions apprises pendant l'entraînement | Trouve le plus long sous-mot depuis le début qui est dans le vocabulaire puis fait de même pour le reste du mot | Trouve la division la plus probable en tokens, en utilisant les scores appris pendant l'entraînement

Maintenant, plongeons dans le BPE !


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter6/4.mdx" />

### Tokenisation <i>Unigram</i>
https://huggingface.co/learn/course/fr/chapter6/7.md

# Tokenisation <i>Unigram</i>

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter6/section7.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter6/section7.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section7.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter6/section7.ipynb"},
]} />

L'algorithme *Unigram* est souvent utilisé dans *SentencePiece*, qui est l'algorithme de tokenization utilisé par des modèles comme ALBERT, T5, mBART, Big Bird et XLNet.

<Youtube id="TGZfZVuF9Yc"/>

<Tip>

💡 Cette section couvre *Unigram* en profondeur, allant jusqu'à montrer une implémentation complète. Vous pouvez passer directement à la fin si vous souhaitez simplement avoir un aperçu général de l'algorithme de tokénisation.

</Tip>

## Algorithme d'entraînement

Comparé au BPE et *WordPiece*, *Unigram* fonctionne dans l'autre sens : il part d'un grand vocabulaire et enlève des *tokens* jusqu'à atteindre la taille de vocabulaire désirée. Il existe plusieurs options pour construire ce vocabulaire de base. Nous pouvons par exemple prendre les sous-chaînes les plus courantes dans les mots prétokénisés ou appliquer le BPE sur le corpus initial avec une grande taille de vocabulaire.

À chaque étape de l'entraînement, l'algorithme *Unigram* calcule une perte sur le corpus compte tenu du vocabulaire actuel. Ensuite, pour chaque symbole du vocabulaire, l'algorithme calcule de combien la perte globale augmenterait si le symbole était supprimé et recherche les symboles qui l'augmenteraient le moins. Ces symboles ont un effet moindre sur la perte globale du corpus, ils sont donc en quelque sorte « moins nécessaires » et sont les meilleurs candidats à la suppression.

Comme il s'agit d'une opération très coûteuse, nous ne nous contentons pas de supprimer le symbole unique associé à la plus faible augmentation de la perte mais le \\(p\\) pourcent des symboles associés à la plus faible augmentation de la perte. \\(p\\) est un hyperparamètre que vous pouvez contrôler, valant généralement 10 ou 20. Ce processus est ensuite répété jusqu'à ce que le vocabulaire ait atteint la taille souhaitée.

Notez que nous ne supprimons jamais les caractères de base, afin de nous assurer que tout mot peut être tokenisé.

Tout ceci peut paraître encore un peu vague. En effet, la partie principale de l'algorithme est de calculer une perte sur le corpus et de voir comment elle change lorsque nous supprimons certains *tokens* du vocabulaire mais nous n'avons pas encore expliqué comment le faire. Cette étape repose sur l'algorithme de tokénisation *Unigram*, nous allons donc l'aborder à présent.

Nous allons réutiliser le corpus des exemples précédents :

```
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) # "câlin", "carlin", "jeu de mots", "brioche", "câlins"...
```

et pour cet exemple, nous prendrons toutes les sous-chaînes strictes pour le vocabulaire initial :

```
["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"]
```

## Algorithme de tokenisation

Un modèle *Unigram* est un type de modèle de langage qui considère que chaque *token* est indépendant des *tokens* qui le précèdent. Il s'agit du modèle de langage le plus simple, dans le sens où la probabilité du *token* X compte tenu du contexte précédent est simplement la probabilité du *token* X. Ainsi, si nous utilisions un modèle de langage *Unigram* pour générer du texte, nous prédirions toujours le *token* le plus courant.

La probabilité d'un *token* donné est sa fréquence (le nombre de fois que nous le trouvons) dans le corpus original, divisée par la somme de toutes les fréquences de tous les *tokens* dans le vocabulaire (pour s'assurer que la somme des probabilités est égale à 1). Par exemple, `"ug"` est présent dans `"hug"`, `"pug"`, et `"hugs"`. Il a donc une fréquence de 20 dans notre corpus.

Voici les fréquences de tous les sous-mots possibles dans le vocabulaire :

```
("h", 15) ("u", 36) ("g", 20) ("hu", 15) ("ug", 20) ("p", 17) ("pu", 17) ("n", 16)
("un", 16) ("b", 4) ("bu", 4) ("s", 5) ("hug", 15) ("gs", 5) ("ugs", 5)
```

Ainsi, la somme de toutes les fréquences est de 210 et la probabilité du sous-mot `"ug"` est donc de 20/210.

<Tip>

✏️ **A votre tour !** Ecrivez le code permettant de calculer les fréquences ci-dessus et vérifiez que les résultats affichés sont corrects, de même que la somme totale.

</Tip>

Maintenant, pour tokeniser un mot donné, nous examinons toutes les segmentations possibles en *tokens* et calculons la probabilité de chacune d'entre elles selon le modèle *Unigram*. Puisque tous les *tokens* sont considérés comme indépendants, cette probabilité est juste le produit de la probabilité de chaque *token*. Par exemple, la tokenisation `["p", "u", "g"]` de `"pug"` a la probabilité :

$$P([``p", ``u", ``g"]) = P(``p") \times P(``u") \times P(``g") = \frac{5}{210} \times \frac{36}{210} \times \frac{20}{210} = 0.000389$$

Comparativement, la tokenization `["pu", "g"]` a la probabilité :

$$P([``pu", ``g"]) = P(``pu") \times P(``g") = \frac{5}{210} \times \frac{20}{210} = 0.0022676$$

donc celle-là est beaucoup plus probable. En général, les tokénisations comportant le moins de *tokens* possible auront la probabilité la plus élevée (en raison de la division par 210 répétée pour chaque *token*), ce qui correspond à ce que nous voulons intuitivement : diviser un mot en un nombre de *tokens* le plus faible possible.

La tokenisation d'un mot avec le modèle *Unigram* est donc la tokenisation avec la plus haute probabilité. Dans l'exemple de `"pug"`, voici les probabilités que nous obtiendrions pour chaque segmentation possible :

```
["p", "u", "g"] : 0.000389
["p", "ug"] : 0.0022676
["pu", "g"] : 0.0022676
```

Ainsi, `"pug"` sera tokenisé comme `["p", "ug"]` ou `["pu", "g"]`, selon la segmentation rencontrée en premier (notez que dans un corpus plus large, les cas d'égalité comme celui-ci seront rares).

Dans ce cas-ci, cela a été facile de trouver toutes les segmentations possibles et de calculer leurs probabilités, mais en général ce sera un peu plus difficile. Il existe un algorithme classique utilisé pour cela, appelé *algorithme de Viterbi*. Essentiellement, on peut construire un graphe pour détecter les segmentations possibles d'un mot donné en disant qu'il existe une branche du caractère _a_ au caractère _b_ si le sous-mot de _a_ à _b_ est dans le vocabulaire, et attribuer à cette branche la probabilité du sous-mot.

Pour trouver le chemin qui va avoir le meilleur score dans ce graphe, l'algorithme de Viterbi détermine, pour chaque position dans le mot, la segmentation avec le meilleur score qui se termine à cette position. Puisque nous allons du début à la fin, ce meilleur score peut être trouvé en parcourant en boucle tous les sous-mots se terminant à la position actuelle, puis en utilisant le meilleur score de tokenization de la position à laquelle ce sous-mot commence. Ensuite, il suffit de dérouler le chemin emprunté pour arriver à la fin.

Prenons un exemple en utilisant notre vocabulaire et le mot `"unhug"`. Pour chaque position, les sous-mots avec les meilleurs scores se terminant là sont les suivants :

```
Character 0 (u): "u" (score 0.171429)
Character 1 (n): "un" (score 0.076191)
Character 2 (h): "un" "h" (score 0.005442)
Character 3 (u): "un" "hu" (score 0.005442)
Character 4 (g): "un" "hug" (score 0.005442)
```

Ainsi, `"unhug"` serait tokenisé comme `["un", "hug"]`.

<Tip>

✏️ **A votre tour !** Déterminer la tokenization du mot `"huggun"` et son score.

</Tip>

## Retour à l'entraînement

Maintenant que nous avons vu comment fonctionne la tokenisation, nous pouvons nous plonger un peu plus profondément dans la perte utilisée pendant l'entraînement. À n'importe quelle étape, cette perte est calculée en tokenisant chaque mot du corpus, en utilisant le vocabulaire courant et le modèle *Unigram* déterminé par les fréquences de chaque *token* dans le corpus (comme vu précédemment).

Chaque mot du corpus a un score, et la perte est le négatif du logarithme de ces scores, c'est-à-dire la somme pour tous les mots du corpus de tous les `-log(P(word))`.

Revenons à notre exemple avec le corpus suivant :

```
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
```

La tokenisation de chaque mot avec leurs scores respectifs est :

```
"hug": ["hug"] (score 0.071428)
"pug": ["pu", "g"] (score 0.007710)
"pun": ["pu", "n"] (score 0.006168)
"bun": ["bu", "n"] (score 0.001451)
"hugs": ["hug", "s"] (score 0.001701)
```

Donc la perte est :

```
10 * (-log(0.071428)) + 5 * (-log(0.007710)) + 12 * (-log(0.006168)) + 4 * (-log(0.001451)) + 5 * (-log(0.001701)) = 169.8
```

Maintenant, nous devons calculer comment la suppression de chaque token affecte la perte. C'est plutôt fastidieux, donc nous allons le faire pour deux *tokens* ici et garder tout le processus pour quand nous aurons du code pour nous aider. Dans ce cas (très) particulier, nous avions deux tokenizations équivalentes de tous les mots. Par exmeple, comme nous l'avons vu précédemment, `"pug"` pourrait être tokenisé en `["p", "ug"]` avec le même score. Ainsi, enlever le token `"pu"` du vocabulaire donnera exactement la même perte.

D'un autre côté, supprimer le mot `"hug"` aggravera la perte, car la tokenisation de `"hug"` et `"hugs"` deviendra :

```
"hug": ["hu", "g"] (score 0.006802)
"hugs": ["hu", "gs"] (score 0.001701)
```

Ces changements entraîneront une augmentation de la perte de :

```
- 10 * (-log(0.071428)) + 10 * (-log(0.006802)) = 23.5
```

Par conséquent, le token `"pu"` sera probablement retiré du vocabulaire, mais pas `"hug"`.

## Implémentation d'<i>Unigram</i>

Maintenant, implémentons tout ce que nous avons vu jusqu'à présent dans le code. Comme pour le BPE et *WordPiece*, ce n'est pas une implémentation efficace de l'algorithme *Unigram* (bien au contraire), mais elle devrait vous aider à le comprendre un peu mieux.

Nous allons utiliser le même corpus que précédemment comme exemple :

```python
corpus = [
    "This is the Hugging Face Course.",
    # C'est le cours d'Hugging Face.
    "This chapter is about tokenization.",
    # Ce chapitre traite de la tokenisation.
    "This section shows several tokenizer algorithms.",
    # Cette section présente plusieurs algorithmes de *tokenizer*.
    "Hopefully, you will be able to understand how they are trained and generate tokens.",
    # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des *tokens*.
]
```

Cette fois, nous allons utiliser `xlnet-base-cased` comme modèle :

```python
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased")
```

Comme pour le BPE et *WordPiece*, nous commençons par compter le nombre d'occurrences de chaque mot dans le corpus :

```python
from collections import defaultdict

word_freqs = defaultdict(int)
for text in corpus:
    words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text)
    new_words = [word for word, offset in words_with_offsets]
    for word in new_words:
        word_freqs[word] += 1

word_freqs
```

Ensuite, nous devons initialiser notre vocabulaire à une taille plus grande que celle du vocabulaire que nous voudrons à la fin. Nous devons inclure tous les caractères de base (sinon nous ne serons pas en mesure de tokeniser chaque mot), mais pour les sous-chaînes plus grandes, nous ne garderons que les plus communs. AInsi nous les trions par fréquence :

```python
char_freqs = defaultdict(int)
subwords_freqs = defaultdict(int)
for word, freq in word_freqs.items():
    for i in range(len(word)):
        char_freqs[word[i]] += freq
        # Boucle à travers les sous-mots de longueur au moins égale à 2
        for j in range(i + 2, len(word) + 1):
            subwords_freqs[word[i:j]] += freq

# Trier les sous-mots par fréquence
sorted_subwords = sorted(subwords_freqs.items(), key=lambda x: x[1], reverse=True)
sorted_subwords[:10]
```

```python out
[('▁t', 7), ('is', 5), ('er', 5), ('▁a', 5), ('▁to', 4), ('to', 4), ('en', 4), ('▁T', 3), ('▁Th', 3), ('▁Thi', 3)]
```

Nous regroupons les caractères avec les meilleurs sous-mots pour arriver à un vocabulaire initial de taille 300 :

```python
token_freqs = list(char_freqs.items()) + sorted_subwords[: 300 - len(char_freqs)]
token_freqs = {token: freq for token, freq in token_freqs}
```

<Tip>

💡 *SentencePiece* utilise un algorithme plus efficace appelé *Enhanced Suffix Array* (ESA) pour créer le vocabulaire initial.

</Tip>

Ensuite, nous calculons la somme de toutes les fréquences, pour convertir les fréquences en probabilités. Pour notre modèle, nous allons stocker les logarithmes des probabilités, car c'est plus stable numériquement d'additionner des logarithmes que de multiplier des petits nombres. Cela simplifiera aussi le calcul de la perte du modèle :

```python
from math import log

total_sum = sum([freq for token, freq in token_freqs.items()])
model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()}
```

Maintenant la fonction principale est celle qui tokenise les mots en utilisant l'algorithme de Viterbi. Comme nous l'avons vu précédemment, cet algorithme calcule la meilleure segmentation de chaque sous-chaîne du mot que nous allons stocker dans une variable nommée `best_segmentations`. Nous allons stocker un dictionnaire par position dans le mot (de 0 à sa longueur totale), avec deux clés : l'index du début du dernier *token* dans la meilleure segmentation et le score de la meilleure segmentation. Avec l'index du début du dernier *token*, nous serons en mesure de récupérer la segmentation complète une fois que la liste est complètement remplie.

Le remplissage de la liste se fait à l'aide de deux boucles seulement : la boucle principale passe en revue chaque position de départ et la seconde boucle essaie toutes les sous-chaînes commençant à cette position de départ. Si la sous-chaîne est dans le vocabulaire, nous avons une nouvelle segmentation du mot jusqu'à cette position finale que nous comparons à ce qui est dans `best_segmentations`.

Une fois que la boucle principale est terminée, nous commençons juste à la fin et sautons d'une position de départ à une autre, en enregistrant les *tokens* au fur et à mesure, jusqu'à ce que nous atteignions le début du mot :

```python
def encode_word(word, model):
    best_segmentations = [{"start": 0, "score": 1}] + [
        {"start": None, "score": None} for _ in range(len(word))
    ]
    for start_idx in range(len(word)):
        # Doit être correctement rempli par les étapes précédentes de la boucle
        best_score_at_start = best_segmentations[start_idx]["score"]
        for end_idx in range(start_idx + 1, len(word) + 1):
            token = word[start_idx:end_idx]
            if token in model and best_score_at_start is not None:
                score = model[token] + best_score_at_start
                # Si nous avons trouvé une meilleure segmentation se terminant à end_idx, nous mettons à jour
                if (
                    best_segmentations[end_idx]["score"] is None
                    or best_segmentations[end_idx]["score"] > score
                ):
                    best_segmentations[end_idx] = {"start": start_idx, "score": score}

    segmentation = best_segmentations[-1]
    if segmentation["score"] is None:
        # Nous n'avons pas trouvé de tokenization du mot -> inconnu (<unk>)
        return ["<unk>"], None

    score = segmentation["score"]
    start = segmentation["start"]
    end = len(word)
    tokens = []
    while start != 0:
        tokens.insert(0, word[start:end])
        next_start = best_segmentations[start]["start"]
        end = start
        start = next_start
    tokens.insert(0, word[start:end])
    return tokens, score
```

Nous pouvons déjà essayer notre modèle initial sur quelques mots :

```python
print(encode_word("Hopefully", model))
print(encode_word("This", model))
```

```python out
(['H', 'o', 'p', 'e', 'f', 'u', 'll', 'y'], 41.5157494601402)
(['This'], 6.288267030694535)
```

Il est maintenant facile de calculer la perte du modèle sur le corpus !

```python
def compute_loss(model):
    loss = 0
    for word, freq in word_freqs.items():
        _, word_loss = encode_word(word, model)
        loss += freq * word_loss
    return loss
```

Nous pouvons vérifier que cela fonctionne sur le modèle que nous avons :

```python
compute_loss(model)
```

```python out
413.10377642940875
```

Le calcul des scores pour chaque *token* n'est pas très difficile non plus. Il suffit de calculer la perte pour les modèles obtenus en supprimant chaque *token* :

```python
import copy


def compute_scores(model):
    scores = {}
    model_loss = compute_loss(model)
    for token, score in model.items():
        # Nous gardons toujours les tokens de longueur 1.
        if len(token) == 1:
            continue
        model_without_token = copy.deepcopy(model)
        _ = model_without_token.pop(token)
        scores[token] = compute_loss(model_without_token) - model_loss
    return scores
```

Nous pouvons l'essayer sur un *token* donné :

```python
scores = compute_scores(model)
print(scores["ll"])
print(scores["his"])
```

Puisque `"ll"` est utilisé dans la tokenisation de `"Hopefully"`, et que le supprimer nous fera probablement utiliser le token `"l"` deux fois à la place, nous nous attendons à ce qu'il ait une perte positive. `"his"` n'est utilisé qu'à l'intérieur du mot `"This"`, qui est tokenisé comme lui-même, donc nous nous attendons à ce qu'il ait une perte nulle. Voici les résultats :

```python out
6.376412403623874
0.0
```

<Tip>

💡 Cette approche est très inefficace, c'est pourquoi *SentencePiece* utilise une approximation de la perte du modèle sans le *token* X. Au lieu de partir de zéro, il remplace simplement le *token* X par sa segmentation dans le vocabulaire restant. De cette façon, tous les scores peuvent être calculés en une seule fois, en même temps que la perte du modèle.

</Tip>

Une fois tout cela en place, la dernière chose à faire est d'ajouter les *tokens* spéciaux utilisés par le modèle au vocabulaire, puis de boucler jusqu'à ce que nous ayons élagué suffisamment de *tokens* du vocabulaire pour atteindre la taille souhaitée :

```python
percent_to_remove = 0.1
while len(model) > 100:
    scores = compute_scores(model)
    sorted_scores = sorted(scores.items(), key=lambda x: x[1])
    # Supprime les tokens percent_to_remove ayant les scores les plus bas
    for i in range(int(len(model) * percent_to_remove)):
        _ = token_freqs.pop(sorted_scores[i][0])

    total_sum = sum([freq for token, freq in token_freqs.items()])
    model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()}
```

Ensuite, pour tokeniser un texte, il suffit d'appliquer la prétokénisation et d'utiliser la fonction `encode_word()` :

```python
def tokenize(text, model):
    words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text)
    pre_tokenized_text = [word for word, offset in words_with_offsets]
    encoded_words = [encode_word(word, model)[0] for word in pre_tokenized_text]
    return sum(encoded_words, [])


tokenize("This is the Hugging Face course.", model)
```

```python out
['▁This', '▁is', '▁the', '▁Hugging', '▁Face', '▁', 'c', 'ou', 'r', 's', 'e', '.']
```

C'est tout pour *Unigram* ! Avec un peu de chance, vous vous sentez à présent être un expert des *tokenizers*. Dans la prochaine section, nous allons nous plonger dans les blocs de construction de la bibliothèque 🤗 *Tokenizers* et allons vous montrer comment vous pouvez les utiliser pour construire votre propre *tokenizer*.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter6/7.mdx" />

### <i>Tokenizer</i> rapide dans le pipeline de QA
https://huggingface.co/learn/course/fr/chapter6/3b.md

# <i>Tokenizer</i> rapide dans le pipeline de QA

{#if fw === 'pt'}

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter6/section3b_pt.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter6/section3b_pt.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section3b_pt.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter6/section3b_pt.ipynb"},
]} />

{:else}
<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter6/section3b_tf.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter6/section3b_tf.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section3b_tf.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter6/section3b_tf.ipynb"},
]} />

{/if}

Nous allons maintenant nous plonger dans le pipeline de `question-answering` et voir comment exploiter les *offsets* pour extraire d'un contexte la réponse à la question posée. Nous verrons ensuite comment gérer les contextes très longs qui finissent par être tronqués. Vous pouvez sauter cette section si vous n'êtes pas intéressé par la tâche de réponse aux questions.

{#if fw === 'pt'}

<Youtube id="_wxyB3j3mk4"/>

{:else}

<Youtube id="b3u8RzBCX9Y"/>

{/if}

## Utilisation du pipeline de `question-answering`

Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), nous pouvons utiliser le pipeline de `question-answering` comme ceci pour obtenir une réponse à une question :

```py
from transformers import pipeline

question_answerer = pipeline("question-answering")
context = """
🤗 Transformers is backed by the three most popular deep learning libraries
 — Jax, PyTorch, and TensorFlow — with a seamless integration between them. 
It's straightforward to train your models with one before loading them for inference with the other.
"""
# 🤗 Transformers s'appuie sur les trois bibliothèques d'apprentissage profond les plus populaires
# (Jax, PyTorch et TensorFlow) avec une intégration transparente entre elles.
# C'est simple d'entraîner vos modèles avec l'une avant de les charger pour l'inférence avec l'autre.
question = "Which deep learning libraries back 🤗 Transformers?"
# Quelles bibliothèques d'apprentissage profond derrière 🤗 Transformers ?
question_answerer(question=question, context=context)
```

```python out
{'score': 0.97773,
 'start': 78,
 'end': 105,
 'answer': 'Jax, PyTorch and TensorFlow'}
```

Contrairement aux autres pipelines, qui ne peuvent pas tronquer et diviser les textes dont la longueur est supérieure à la longueur maximale acceptée par le modèle (et qui peuvent donc manquer des informations à la fin d'un document), ce pipeline peut traiter des contextes très longs et retournera la réponse à la question même si elle se trouve à la fin :

```py
long_context = """
🤗 Transformers: State of the Art NLP

🤗 Transformers provides thousands of pretrained models to perform tasks on texts such as classification, information extraction,
question answering, summarization, translation, text generation and more in over 100 languages.
Its aim is to make cutting-edge NLP easier to use for everyone.

🤗 Transformers provides APIs to quickly download and use those pretrained models on a given text, fine-tune them on your own datasets and
then share them with the community on our model hub. At the same time, each python module defining an architecture is fully standalone and
can be modified to enable quick research experiments.

Why should I use transformers?

1. Easy-to-use state-of-the-art models:
  - High performance on NLU and NLG tasks.
  - Low barrier to entry for educators and practitioners.
  - Few user-facing abstractions with just three classes to learn.
  - A unified API for using all our pretrained models.
  - Lower compute costs, smaller carbon footprint:

2. Researchers can share trained models instead of always retraining.
  - Practitioners can reduce compute time and production costs.
  - Dozens of architectures with over 10,000 pretrained models, some in more than 100 languages.

3. Choose the right framework for every part of a model's lifetime:
  - Train state-of-the-art models in 3 lines of code.
  - Move a single model between TF2.0/PyTorch frameworks at will.
  - Seamlessly pick the right framework for training, evaluation and production.

4. Easily customize a model or an example to your needs:
  - We provide examples for each architecture to reproduce the results published by its original authors.
  - Model internals are exposed as consistently as possible.
  - Model files can be used independently of the library for quick experiments.

🤗 Transformers is backed by the three most popular deep learning libraries — Jax, PyTorch and TensorFlow — with a seamless integration
between them. It's straightforward to train your models with one before loading them for inference with the other.
"""

long_context - fr = """
🤗 Transformers : l'état de l'art du NLP

🤗 Transformers fournit des milliers de modèles pré-entraînés pour effectuer des tâches sur des textes telles que la classification, 
l'extraction d'informations, la réponse à des questions, le résumé de textes, la traduction, la génération de texte et plus encore dans plus de 100 langues.
Son objectif est de rendre le traitement automatique des langues de pointe plus facile à utiliser pour tout le monde.

🤗 Transformers fournit des API permettant de télécharger et d'utiliser rapidement ces modèles pré-entraînés sur un texte donné, de les affiner sur vos propres ensembles de données et de les partager avec la communauté sur notre site Web.
puis de les partager avec la communauté sur notre hub de modèles. En même temps, chaque module python définissant une architecture est entièrement autonome et peut être modifié pour permettre des expériences de recherche rapides.
peut être modifié pour permettre des expériences de recherche rapides.

Pourquoi devrais-je utiliser des transformateurs ?

1. Des modèles de pointe faciles à utiliser :
  - Haute performance sur les tâches NLU et NLG.
  - Faible barrière à l'entrée pour les éducateurs et les praticiens.
  - Peu d'abstractions pour l'utilisateur avec seulement trois classes à apprendre.
  - Une API unifiée pour utiliser tous nos modèles pré-entraînés.
  - Des coûts de calcul plus faibles, une empreinte carbone réduite :

2. Les chercheurs peuvent partager les modèles formés au lieu de toujours les reformer.
  - Les praticiens peuvent réduire le temps de calcul et les coûts de production.
  - Des dizaines d'architectures avec plus de 10 000 modèles pré-formés, certains dans plus de 100 langues.

3. Choisissez le cadre approprié pour chaque étape de la vie d'un modèle :
  - Entraînez des modèles de pointe en 3 lignes de code.
  - Déplacez un seul modèle entre les frameworks TF2.0/PyTorch à volonté.
  - Choisissez de manière transparente le bon framework pour l'entraînement, l'évaluation et la production.

4. Adaptez facilement un modèle ou un exemple à vos besoins :
  - Nous fournissons des exemples pour chaque architecture afin de reproduire les résultats publiés par ses auteurs originaux.
  - Les éléments internes des modèles sont exposés de manière aussi cohérente que possible.
  - Les fichiers de modèles peuvent être utilisés indépendamment de la bibliothèque pour des expériences rapides.

🤗 Transformers s'appuie sur les trois bibliothèques d'apprentissage profond les plus populaires (Jax, PyTorch et TensorFlow) avec une intégration parfaite
entre elles. Il est simple d'entraîner vos modèles avec l'une avant de les charger pour l'inférence avec l'autre.
"""
question_answerer(question=question, context=long_context)
```

```python out
{'score': 0.97149,
 'start': 1892,
 'end': 1919,
 'answer': 'Jax, PyTorch and TensorFlow'}
```

Voyons comment il fait tout cela !

### Utilisation d'un modèle pour répondre à des questions

Comme avec n'importe quel autre pipeline, nous commençons par tokeniser notre entrée et l'envoyons ensuite à travers le modèle. Le *checkpoint* utilisé par défaut pour le pipeline de `question-answering` est [`distilbert-base-cased-distilled-squad`](https://huggingface.co/distilbert-base-cased-distilled-squad) (le « squad » dans le nom vient du jeu de données sur lequel le modèle a été *finetuné*, nous parlerons davantage de ce jeu de données dans le [chapitre 7](/course/fr/chapter7/7)) :

{#if fw === 'pt'}

```py
from transformers import AutoTokenizer, AutoModelForQuestionAnswering

model_checkpoint = "distilbert-base-cased-distilled-squad"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint)

inputs = tokenizer(question, context, return_tensors="pt")
outputs = model(**inputs)
```

{:else}

```py
from transformers import AutoTokenizer, TFAutoModelForQuestionAnswering

model_checkpoint = "distilbert-base-cased-distilled-squad"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint)

inputs = tokenizer(question, context, return_tensors="tf")
outputs = model(**inputs)
```

{/if}

Notez que nous tokenizons la question et le contexte comme une paire, la question en premier.

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/question_tokens.svg" alt="An example of tokenization of question and context"/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/question_tokens-dark.svg" alt="An example of tokenization of question and context"/>
</div>

Les modèles de réponse aux questions fonctionnent un peu différemment des modèles que nous avons vus jusqu'à présent. En utilisant l'image ci-dessus comme exemple, le modèle a été entraîné à prédire l'index du *token* de début de la réponse (ici 21) et l'index du *token* où la réponse se termine (ici 24). C'est pourquoi ces modèles ne retournent pas un tenseur de logits mais deux : un pour les logits correspondant au *token* de début de la réponse, et un pour les logits correspondant au *token* de fin de la réponse. Puisque dans ce cas nous n'avons qu'une seule entrée contenant 66 *tokens*, nous obtenons :

```py
start_logits = outputs.start_logits
end_logits = outputs.end_logits
print(start_logits.shape, end_logits.shape)
```

{#if fw === 'pt'}

```python out
torch.Size([1, 66]) torch.Size([1, 66])
```

{:else}

```python out
(1, 66) (1, 66)
```

{/if}

Pour convertir ces logits en probabilités, nous allons appliquer une fonction softmax. Mais avant cela, nous devons nous assurer de masquer les indices qui ne font pas partie du contexte. Notre entrée est `[CLS] question [SEP] contexte [SEP]` donc nous devons masquer les *tokens* de la question ainsi que le *token* `[SEP]`. Nous garderons cependant le *token* `[CLS]` car certains modèles l'utilisent pour indiquer que la réponse n'est pas dans le contexte.

Puisque nous appliquerons une fonction softmax par la suite, il nous suffit de remplacer les logits que nous voulons masquer par un grand nombre négatif. Ici, nous utilisons `-10000` :

{#if fw === 'pt'}

```py
import torch

sequence_ids = inputs.sequence_ids()
# Masque tout, sauf les tokens du contexte
mask = [i != 1 for i in sequence_ids]
# Démasquer le token [CLS]
mask[0] = False
mask = torch.tensor(mask)[None]

start_logits[mask] = -10000
end_logits[mask] = -10000
```

{:else}

```py
import tensorflow as tf

sequence_ids = inputs.sequence_ids()
# Masque tout, sauf les tokens du contexte
mask = [i != 1 for i in sequence_ids]
# Démasquer le token [CLS]
mask[0] = False
mask = tf.constant(mask)[None]

start_logits = tf.where(mask, -10000, start_logits)
end_logits = tf.where(mask, -10000, end_logits)
```

{/if}

Maintenant que nous avons correctement masqué les logits correspondant aux positions que nous ne voulons pas prédire, nous pouvons appliquer la softmax :

{#if fw === 'pt'}

```py
start_probabilities = torch.nn.functional.softmax(start_logits, dim=-1)[0]
end_probabilities = torch.nn.functional.softmax(end_logits, dim=-1)[0]
```

{:else}

```py
start_probabilities = tf.math.softmax(start_logits, axis=-1)[0].numpy()
end_probabilities = tf.math.softmax(end_logits, axis=-1)[0].numpy()
```

{/if}

A ce stade, nous pourrions prendre l'argmax des probabilités de début et de fin mais nous pourrions nous retrouver avec un indice de début supérieur à l'indice de fin. Nous devons donc prendre quelques précautions supplémentaires. Nous allons calculer les probabilités de chaque `start_index` et `end_index` possible où `start_index<=end_index`, puis nous prendrons le *tuple* `(start_index, end_index)` avec la plus grande probabilité.

En supposant que les événements « La réponse commence à `start_index` » et « La réponse se termine à `end_index` » sont indépendants, la probabilité que la réponse commence à `start_index` et se termine à `end_index` est :

$$\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]$$ 

Ainsi, pour calculer tous les scores, il suffit de calculer tous les produits $$\\(\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]\\)$$ où `start_index <= end_index`.

Calculons d'abord tous les produits possibles :

```py
scores = start_probabilities[:, None] * end_probabilities[None, :]
```

{#if fw === 'pt'}

Ensuite, nous masquerons les valeurs où `start_index > end_index` en les mettant à `0` (les autres probabilités sont toutes des nombres positifs). La fonction `torch.triu()` renvoie la partie triangulaire supérieure du tenseur 2D passé en argument, elle fera donc ce masquage pour nous :

```py
scores = torch.triu(scores)
```

{:else}

Ensuite, nous masquerons les valeurs où `start_index > end_index` en les mettant à `0` (les autres probabilités sont toutes des nombres positifs). La fonction `np.triu()` renvoie la partie triangulaire supérieure du tenseur 2D passé en argument, elle fera donc ce masquage pour nous :

```py
scores = np.triu(scores)
```

{/if}

Il ne nous reste plus qu'à obtenir l'indice du maximum. Puisque PyTorch retourne l'index dans le tenseur aplati, nous devons utiliser les opérations division `//` et modulo `%` pour obtenir le `start_index` et le `end_index` :

```py
max_index = scores.argmax().item()
start_index = max_index // scores.shape[1]
end_index = max_index % scores.shape[1]
print(scores[start_index, end_index])
```

Nous n'avons pas encore tout à fait terminé, mais au moins nous avons déjà le score correct pour la réponse (vous pouvez le vérifier en le comparant au premier résultat de la section précédente) :

```python out
0.97773
```

<Tip>

✏️ **Essayez !** Calculez les indices de début et de fin pour les cinq réponses les plus probables.

</Tip>

Nous avons les `start_index` et `end_index` de la réponse en termes de *tokens*. Maintenant nous devons juste convertir en indices de caractères dans le contexte. C'est là que les *offsets* seront super utiles. Nous pouvons les saisir et les utiliser comme nous l'avons fait dans la tâche de classification des *tokens* :

```py
inputs_with_offsets = tokenizer(question, context, return_offsets_mapping=True)
offsets = inputs_with_offsets["offset_mapping"]

start_char, _ = offsets[start_index]
_, end_char = offsets[end_index]
answer = context[start_char:end_char]
```

Il ne nous reste plus qu'à tout formater pour obtenir notre résultat :

```py
result = {
    "answer": answer,
    "start": start_char,
    "end": end_char,
    "score": scores[start_index, end_index],
}
print(result)
```

```python out
{'answer': 'Jax, PyTorch and TensorFlow',
 'start': 78,
 'end': 105,
 'score': 0.97773}
```

Super ! C'est la même chose que dans notre premier exemple !

<Tip>

✏️ **Essayez !** Utilisez les meilleurs scores que vous avez calculés précédemment pour afficher les cinq réponses les plus probables. Pour vérifier vos résultats, retournez au premier pipeline et passez dans `top_k=5` lorsque vous l'appelez.

</Tip>

## Gestion des contextes longs

Si nous essayons de tokeniser la question et le long contexte que nous avons utilisé dans l'exemple précédemment, nous obtenons un nombre de *tokens* supérieur à la longueur maximale utilisée dans le pipeline `question-answering` (qui est de 384) :

```py
inputs = tokenizer(question, long_context)
print(len(inputs["input_ids"]))
```

```python out
461
```

Nous devrons donc tronquer nos entrées à cette longueur maximale. Il y a plusieurs façons de le faire mais nous ne voulons pas tronquer la question, seulement le contexte. Puisque le contexte est la deuxième phrase, nous utilisons la stratégie de troncature `"only_second"`. Le problème qui se pose alors est que la réponse à la question peut ne pas se trouver dans le contexte tronqué. Ici, par exemple, nous avons choisi une question dont la réponse se trouve vers la fin du contexte, et lorsque nous la tronquons, cette réponse n'est pas présente :

```py
inputs = tokenizer(question, long_context, max_length=384, truncation="only_second")
print(tokenizer.decode(inputs["input_ids"]))
```

```python out
"""
[CLS] Which deep learning libraries back [UNK] Transformers? [SEP] [UNK] Transformers : State of the Art NLP

[UNK] Transformers provides thousands of pretrained models to perform tasks on texts such as classification, information extraction,
question answering, summarization, translation, text generation and more in over 100 languages.
Its aim is to make cutting-edge NLP easier to use for everyone.

[UNK] Transformers provides APIs to quickly download and use those pretrained models on a given text, fine-tune them on your own datasets and
then share them with the community on our model hub. At the same time, each python module defining an architecture is fully standalone and
can be modified to enable quick research experiments.

Why should I use transformers?

1. Easy-to-use state-of-the-art models:
  - High performance on NLU and NLG tasks.
  - Low barrier to entry for educators and practitioners.
  - Few user-facing abstractions with just three classes to learn.
  - A unified API for using all our pretrained models.
  - Lower compute costs, smaller carbon footprint:

2. Researchers can share trained models instead of always retraining.
  - Practitioners can reduce compute time and production costs.
  - Dozens of architectures with over 10,000 pretrained models, some in more than 100 languages.

3. Choose the right framework for every part of a model's lifetime:
  - Train state-of-the-art models in 3 lines of code.
  - Move a single model between TF2.0/PyTorch frameworks at will.
  - Seamlessly pick the right framework for training, evaluation and production.

4. Easily customize a model or an example to your needs:
  - We provide examples for each architecture to reproduce the results published by its original authors.
  - Model internal [SEP]
"""

"""
[CLS] Quelles sont les bibliothèques d'apprentissage profond qui soutiennent [UNK] Transformers ? [SEP] [UNK] Transformers : l'état de l'art du NLP

[UNK] Transformers fournit des milliers de modèles pré-entraînés pour effectuer des tâches sur des textes telles que la classification, l'extraction d'informations, la réponse à des questions, le résumé, la traduction, la génération de textes, etc,
la réponse à des questions, le résumé, la traduction, la génération de texte et plus encore dans plus de 100 langues.
Son objectif est de rendre le traitement automatique des langues de pointe plus facile à utiliser pour tous.

Transformers [UNK] fournit des API permettant de télécharger et d'utiliser rapidement ces modèles pré-entraînés sur un texte donné, de les affiner sur vos propres ensembles de données et de les partager avec la communauté sur notre site Web.
puis de les partager avec la communauté sur notre hub de modèles. En même temps, chaque module python définissant une architecture est entièrement autonome et peut être modifié pour permettre des expériences de recherche rapides.
peut être modifié pour permettre des expériences de recherche rapides.

Pourquoi devrais-je utiliser des transformateurs ?

1. Des modèles de pointe faciles à utiliser :
  - Haute performance sur les tâches NLU et NLG.
  - Faible barrière à l'entrée pour les éducateurs et les praticiens.
  - Peu d'abstractions pour l'utilisateur avec seulement trois classes à apprendre.
  - Une API unifiée pour utiliser tous nos modèles pré-entraînés.
  - Des coûts de calcul plus faibles, une empreinte carbone réduite :

2. Les chercheurs peuvent partager les modèles formés au lieu de toujours les reformer.
  - Les praticiens peuvent réduire le temps de calcul et les coûts de production.
  - Des dizaines d'architectures avec plus de 10 000 modèles pré-formés, certains dans plus de 100 langues.

3. Choisissez le cadre approprié pour chaque étape de la vie d'un modèle :
  - Entraînez des modèles de pointe en 3 lignes de code.
  - Déplacez un seul modèle entre les frameworks TF2.0/PyTorch à volonté.
  - Choisissez de manière transparente le bon framework pour l'entraînement, l'évaluation et la production.

4. Adaptez facilement un modèle ou un exemple à vos besoins :
  - Nous fournissons des exemples pour chaque architecture afin de reproduire les résultats publiés par ses auteurs originaux.
  - Modèle interne [SEP]
"""
```

Cela signifie que le modèle a du mal à trouver la bonne réponse. Pour résoudre ce problème, le pipeline de `question-answering` nous permet de diviser le contexte en morceaux plus petits, en spécifiant la longueur maximale. Pour s'assurer que nous ne divisons pas le contexte exactement au mauvais endroit pour permettre de trouver la réponse, il inclut également un certain chevauchement entre les morceaux.

Nous pouvons demander au *tokenizer* (rapide ou lent) de le faire pour nous en ajoutant `return_overflowing_tokens=True`, et nous pouvons spécifier le chevauchement que nous voulons avec l'argument `stride`. Voici un exemple, en utilisant une phrase plus petite :

```py
sentence = "This sentence is not too long but we are going to split it anyway."
# "Cette phrase n'est pas trop longue mais nous allons la diviser quand même."
inputs = tokenizer(
    sentence, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2
)

for ids in inputs["input_ids"]:
    print(tokenizer.decode(ids))
```

```python out
'[CLS] This sentence is not [SEP]'
'[CLS] is not too long [SEP]'
'[CLS] too long but we [SEP]'
'[CLS] but we are going [SEP]'
'[CLS] are going to split [SEP]'
'[CLS] to split it anyway [SEP]'
'[CLS] it anyway. [SEP]'
```

Comme on peut le voir, la phrase a été découpée en morceaux de telle sorte que chaque entrée dans `inputs["input_ids"]` a au maximum 6 *tokens* (il faudrait ajouter du *padding* pour que la dernière entrée ait la même taille que les autres) et il y a un chevauchement de 2 *tokens* entre chacune des entrées. 

Regardons de plus près le résultat de la tokénisation :

```py
print(inputs.keys())
```

```python out
dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping'])
```

Comme prévu, nous obtenons les identifiants d'entrée et un masque d'attention. La dernière clé, `overflow_to_sample_mapping`, est une carte qui nous indique à quelle phrase correspond chacun des résultats. Ici nous avons 7 résultats qui proviennent tous de la (seule) phrase que nous avons passée au *tokenizer* :

```py
print(inputs["overflow_to_sample_mapping"])
```

```python out
[0, 0, 0, 0, 0, 0, 0]
```

C'est plus utile lorsque nous tokenisons plusieurs phrases ensemble. Par exemple :

```py
sentences = [
    "This sentence is not too long but we are going to split it anyway.",
    # Cette phrase n'est pas trop longue mais nous allons la diviser quand même.
    "This sentence is shorter but will still get split.",
    # Cette phrase est plus courte mais sera quand même divisée.
]
inputs = tokenizer(
    sentences, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2
)

print(inputs["overflow_to_sample_mapping"])
```

nous donne :

```python out
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]
```

ce qui signifie que la première phrase est divisée en 7 morceaux comme précédemment et que les 4 morceaux suivants proviennent de la deuxième phrase.

Revenons maintenant à notre contexte long. Par défaut, le pipeline `question-answering` utilise une longueur maximale de 384 et un *stride* de 128, qui correspondent à la façon dont le modèle a été *finetuné* (vous pouvez ajuster ces paramètres en passant les arguments `max_seq_len` et `stride` lorsque vous appelez le pipeline). Nous utiliserons donc ces paramètres lors de la tokenisation. Nous ajouterons aussi du *padding* (pour avoir des échantillons de même longueur afin de pouvoir construire des tenseurs) ainsi que demander les *offsets* :

```py
inputs = tokenizer(
    question,
    long_context,
    stride=128,
    max_length=384,
    padding="longest",
    truncation="only_second",
    return_overflowing_tokens=True,
    return_offsets_mapping=True,
)
```

Ces `inputs` contiendront les identifiants d'entrée, les masques d'attention que le modèle attend, ainsi que les *offsets* et le `overflow_to_sample_mapping` dont on vient de parler. Puisque ces deux éléments ne sont pas des paramètres utilisés par le modèle, nous allons les sortir des `inputs` (et nous ne stockerons pas la correspondance puisqu'elle n'est pas utile ici) avant de le convertir en tenseur :

{#if fw === 'pt'}

```py
_ = inputs.pop("overflow_to_sample_mapping")
offsets = inputs.pop("offset_mapping")

inputs = inputs.convert_to_tensors("pt")
print(inputs["input_ids"].shape)
```

```python out
torch.Size([2, 384])
```

{:else}

```py
_ = inputs.pop("overflow_to_sample_mapping")
offsets = inputs.pop("offset_mapping")

inputs = inputs.convert_to_tensors("tf")
print(inputs["input_ids"].shape)
```

```python out
(2, 384)
```

{/if}

Notre contexte long a été divisé en deux, ce qui signifie qu'après avoir traversé notre modèle, nous aurons deux ensembles de logits de début et de fin :

```py
outputs = model(**inputs)

start_logits = outputs.start_logits
end_logits = outputs.end_logits
print(start_logits.shape, end_logits.shape)
```

{#if fw === 'pt'}

```python out
torch.Size([2, 384]) torch.Size([2, 384])
```

{:else}

```python out
(2, 384) (2, 384)
```

{/if}

Comme précédemment, nous masquons d'abord les *tokens* qui ne font pas partie du contexte avant de prendre le softmax. Nous masquons également tous les *tokens* de *padding* (tels que signalés par le masque d'attention) :

{#if fw === 'pt'}

```py
sequence_ids = inputs.sequence_ids()
# Masque tout, sauf les tokens du contexte
mask = [i != 1 for i in sequence_ids]
# Démasquer le jeton [CLS]
mask[0] = False
# Masquer tous les tokens [PAD]
mask = torch.logical_or(torch.tensor(mask)[None], (inputs["attention_mask"] == 0))

start_logits[mask] = -10000
end_logits[mask] = -10000
```

{:else}

```py
sequence_ids = inputs.sequence_ids()
# Masque tout, sauf les tokens du contexte
mask = [i != 1 for i in sequence_ids]
# Démasquer le jeton [CLS]
mask[0] = False
# Masquer tous les tokens [PAD]
mask = tf.math.logical_or(tf.constant(mask)[None], inputs["attention_mask"] == 0)

start_logits = tf.where(mask, -10000, start_logits)
end_logits = tf.where(mask, -10000, end_logits)
```

{/if}

Ensuite, nous pouvons utiliser la fonction softmax pour convertir nos logits en probabilités :

{#if fw === 'pt'}

```py
start_probabilities = torch.nn.functional.softmax(start_logits, dim=-1)
end_probabilities = torch.nn.functional.softmax(end_logits, dim=-1)
```

{:else}

```py
start_probabilities = tf.math.softmax(start_logits, axis=-1).numpy()
end_probabilities = tf.math.softmax(end_logits, axis=-1).numpy()
```

{/if}

L'étape suivante est similaire à ce que nous avons fait pour le petit contexte mais nous la répétons pour chacun de nos deux morceaux. Nous attribuons un score à tous les espaces de réponse possibles puis nous prenons l'espace ayant le meilleur score :

{#if fw === 'pt'}

```py
candidates = []
for start_probs, end_probs in zip(start_probabilities, end_probabilities):
    scores = start_probs[:, None] * end_probs[None, :]
    idx = torch.triu(scores).argmax().item()

    start_idx = idx // scores.shape[1]
    end_idx = idx % scores.shape[1]
    score = scores[start_idx, end_idx].item()
    candidates.append((start_idx, end_idx, score))

print(candidates)
```

{:else}

```py
candidates = []
for start_probs, end_probs in zip(start_probabilities, end_probabilities):
    scores = start_probs[:, None] * end_probs[None, :]
    idx = np.triu(scores).argmax().item()

    start_idx = idx // scores.shape[1]
    end_idx = idx % scores.shape[1]
    score = scores[start_idx, end_idx].item()
    candidates.append((start_idx, end_idx, score))

print(candidates)
```

{/if}

```python out
[(0, 18, 0.33867), (173, 184, 0.97149)]
```

Ces deux candidats correspondent aux meilleures réponses que le modèle a pu trouver dans chaque morceau. Le modèle est beaucoup plus confiant dans le fait que la bonne réponse se trouve dans la deuxième partie (ce qui est bon signe !). Il ne nous reste plus qu'à faire correspondre ces deux espaces de *tokens* à des espaces de caractères dans le contexte (nous n'avons besoin de faire correspondre que le second pour avoir notre réponse, mais il est intéressant de voir ce que le modèle a choisi dans le premier morceau).

<Tip>

✏️ **Essayez !** Adaptez le code ci-dessus pour renvoyer les scores et les étendues des cinq réponses les plus probables (au total, pas par morceau).

</Tip>

Le `offsets` que nous avons saisi plus tôt est en fait une liste d'*offsets* avec une liste par morceau de texte :

```py
for candidate, offset in zip(candidates, offsets):
    start_token, end_token, score = candidate
    start_char, _ = offset[start_token]
    _, end_char = offset[end_token]
    answer = long_context[start_char:end_char]
    result = {"answer": answer, "start": start_char, "end": end_char, "score": score}
    print(result)
```

```python out
{'answer': '\n🤗 Transformers: State of the Art NLP', 'start': 0, 'end': 37, 'score': 0.33867}
{'answer': 'Jax, PyTorch and TensorFlow', 'start': 1892, 'end': 1919, 'score': 0.97149}
```

Si nous ignorons le premier résultat, nous obtenons le même résultat que notre pipeline pour ce long contexte !

<Tip>

✏️ **Essayez !** Utilisez les meilleurs scores que vous avez calculés auparavant pour montrer les cinq réponses les plus probables (pour l'ensemble du contexte, pas pour chaque morceau). Pour vérifier vos résultats, retournez au premier pipeline et spécifiez `top_k=5` en argument en l'appelant.

</Tip>

Ceci conclut notre plongée en profondeur dans les capacités du *tokenizer*. Nous mettrons à nouveau tout cela en pratique dans le prochain chapitre, lorsque nous vous montrerons comment *finetuner* un modèle sur une série de tâches NLP courantes.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter6/3b.mdx" />

### Entraîner un nouveau <i>tokenizer</i> à partir d'un ancien
https://huggingface.co/learn/course/fr/chapter6/2.md

# Entraîner un nouveau <i>tokenizer</i> à partir d'un ancien

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter6/section2.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter6/section2.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section2.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter6/section2.ipynb"},
]} />

Si un modèle de langue n'est pas disponible dans la langue qui vous intéresse ou si votre corpus est très différent de celui sur lequel votre modèle de langue a été entraîné, vous voudrez très probablement réentraîner le modèle à partir de zéro en utilisant un *tokenizer* adapté à vos données. Pour ce faire, vous devrez entraîner un nouveau *tokenizer* sur votre jeu de données. Mais qu'est-ce que cela signifie exactement ? Lorsque nous avons examiné pour la première fois les *tokenizers* dans le [chapitre 2](/course/fr/chapter2), nous avons vu que la plupart des *transformers* utilisent un _algorithme de tokenisation en sous-mots_. Pour identifier les sous-mots qui sont intéressants et qui apparaissent le plus fréquemment dans un corpus donné, le *tokenizer* doit examiner attentivement tous les textes du corpus. C'est un processus que nous appelons *entraînement*. Les règles exactes qui régissent cet apprentissage dépendent du type de *tokenizer* utilisé. Nous passerons en revue les trois principaux algorithmes plus loin dans ce chapitre.

<Youtube id="DJimQynXZsQ"/>

<Tip warning={true}>

⚠️ Entraîner un *tokenizer* n'est pas la même chose qu'entraîner un modèle ! L'entraînement du modèle utilise la descente de gradient stochastique pour réduire un peu plus la perte à chaque batch. Il est par nature aléatoire (ce qui signifie que vous devez définir des graines pour obtenir les mêmes résultats lorsque vous effectuez deux fois le même entraînement). Entraîner un *tokenizer* est un processus statistique qui identifie les meilleurs sous-mots à choisir pour un corpus donné. Les règles exactes utilisées pour les choisir dépendent de l'algorithme de tokénisation. Le processus est déterministe, ce qui signifie que vous obtenez toujours les mêmes résultats lorsque vous vous entraînez avec le même algorithme sur le même corpus.


</Tip>

## Assemblage d'un corpus

Il y a une API très simple dans 🤗 *Transformers* que vous pouvez utiliser pour entraîner un nouveau *tokenizer* avec les mêmes caractéristiques qu'un déjà existant : `AutoTokenizer.train_new_from_iterator()`. Pour illustrer cela, disons que nous voulons entraîner GPT-2 à partir de zéro mais dans une langue autre que l'anglais. Notre première tâche est de rassembler des batchs de données dans cette langue dans un corpus d'entraînement. Pour avoir des exemples que tout le monde puisse comprendre, nous n'utiliserons pas ici une langue comme le russe ou le chinois mais plutôt une langue anglaise spécialisée : le langage Python.

La bibliothèque [🤗 *Datasets*](https://github.com/huggingface/datasets) peut nous aider à assembler un corpus de code source Python. Nous allons utiliser la fonction habituelle `load_dataset()` pour télécharger et mettre en cache le jeu de données [CodeSearchNet](https://huggingface.co/datasets/code_search_net). Ce jeu de données a été créé pour le [CodeSearchNet challenge](https://wandb.ai/github/CodeSearchNet/benchmark) et contient des millions de fonctions provenant de bibliothèques open source sur GitHub dans plusieurs langages de programmation. Ici, nous allons charger la partie Python de ce jeu de données :

```py
from datasets import load_dataset

# Cela peut prendre quelques minutes alors prenez un thé ou un café pendant que vous patientez !
raw_datasets = load_dataset("code_search_net", "python")
```

Nous pouvons jeter un coup d'œil au jeu d'entraînement pour voir quelles sont les colonnes auxquelles nous avons accès :

```py
raw_datasets["train"]
```

```python out
Dataset({
    features: ['repository_name', 'func_path_in_repository', 'func_name', 'whole_func_string', 'language', 
      'func_code_string', 'func_code_tokens', 'func_documentation_string', 'func_documentation_tokens', 'split_name', 
      'func_code_url'
    ],
    num_rows: 412178
})
```

Nous pouvons voir que le jeu de données sépare les chaînes de documents du code et suggère une tokenization des deux. Ici, nous utiliserons simplement la colonne `whole_func_string` pour entraîner notre *tokenizer*. Nous pouvons regarder un exemple de la façon suivante :

```py
print(raw_datasets["train"][123456]["whole_func_string"])
```

qui nous affiche ce qui suit :

```out
def handle_simple_responses(
      self, timeout_ms=None, info_cb=DEFAULT_MESSAGE_CALLBACK):
    """Accepts normal responses from the device.

    Args:
      timeout_ms: Timeout in milliseconds to wait for each response.
      info_cb: Optional callback for text sent from the bootloader.

    Returns:
      OKAY packet's message.
    """
    return self._accept_responses('OKAY', info_cb, timeout_ms=timeout_ms)
```

La première chose à faire est de transformer le jeu de données en un _itérateur_ de listes de textes. Par exemple, une liste de listes de textes. L'utilisation de listes de textes permet à notre *tokenizer* d'aller plus vite (l'entraînement a alors lieu sur des batchs de textes au lieu de traiter des textes un par un). Et le fait que ce soit un itérateur permet d'éviter d'avoir tout en mémoire en même temps. Si votre corpus est énorme, vous voudrez profiter du fait que 🤗 *Datasets* ne charge pas tout en RAM mais stocke les éléments du jeu de données sur le disque. 

Faire ce qui suit créerait une liste de listes de 1 000 textes chacune mais chargerait tout en mémoire :


```py
# Ne décommentez pas la ligne suivante à moins que votre jeu de données soit petit !
# training_corpus = [raw_datasets["train"][i: i + 1000]["whole_func_string"] for i in range(0, len(raw_datasets["train"]), 1000)]
```

En utilisant un générateur, nous pouvons éviter que Python ne charge quoi que ce soit en mémoire à moins que cela soit réellement nécessaire. Pour créer un tel générateur, il suffit de remplacer les crochets par des parenthèses :

```py
training_corpus = (
    raw_datasets["train"][i : i + 1000]["whole_func_string"]
    for i in range(0, len(raw_datasets["train"]), 1000)
)
```

Cette ligne de code ne récupère aucun élément du jeu de données. Elle crée simplement un objet que vous pouvez utiliser dans une boucle `for` Python. Les textes ne seront chargés que lorsque vous en aurez besoin (c'est-à-dire lorsque vous serez à l'étape de la boucle `for` qui les requiert) et seulement 1 000 textes à la fois. De cette façon, vous n'épuiserez pas toute votre mémoire, même si vous traitez un énorme jeu de données.

Le problème avec un objet générateur est qu'il ne peut être utilisé qu'une seule fois. Ainsi, au lieu que cet objet nous donne deux fois la liste des 10 premiers chiffres :


```py
gen = (i for i in range(10))
print(list(gen))
print(list(gen))
```

on les reçoit une fois et ensuite une liste vide :

```python out
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
```

C'est pourquoi nous définissons une fonction qui renvoie un générateur à la place :

```py
def get_training_corpus():
    return (
        raw_datasets["train"][i : i + 1000]["whole_func_string"]
        for i in range(0, len(raw_datasets["train"]), 1000)
    )


training_corpus = get_training_corpus()
```

Vous pouvez également définir votre générateur à l'intérieur d'une boucle `for` en utilisant l'instruction `yield` :

```py
def get_training_corpus():
    dataset = raw_datasets["train"]
    for start_idx in range(0, len(dataset), 1000):
        samples = dataset[start_idx : start_idx + 1000]
        yield samples["whole_func_string"]
```

qui produit exactement le même générateur que précédemment mais  permet d'utiliser une logique plus complexe que celle que vous pouvez utiliser dans une compréhension de liste.


## Entraînement d'un nouveau <i>tokenizer</i>

Maintenant que nous avons notre corpus sous la forme d'un itérateur de batchs de textes, nous sommes prêts à entraîner un nouveau *tokenizer*. Pour ce faire, nous devons d'abord charger le *tokenizer* que nous voulons coupler avec notre modèle (ici, le GPT-2) :


```py
from transformers import AutoTokenizer

old_tokenizer = AutoTokenizer.from_pretrained("gpt2")
```

Même si nous allons entraîner un nouveau *tokenizer*, c'est une bonne idée de faire ça pour éviter de partir entièrement de zéro. De cette façon, nous n'aurons pas à spécifier l'algorithme de tokénisation ou les jetons spéciaux que nous voulons utiliser. Notre nouveau *tokenizer* sera exactement le même que celui du GPT-2. La seule chose qui changera sera le vocabulaire qui sera déterminé lors de l'entraînement sur notre corpus.

Voyons d'abord comment ce *tokenizer* traiterait un exemple de fonction :


```py
example = '''def add_numbers(a, b):
    """Add the two numbers `a` and `b`."""
    return a + b'''

tokens = old_tokenizer.tokenize(example)
tokens
```

```python out
['def', 'Ġadd', '_', 'n', 'umbers', '(', 'a', ',', 'Ġb', '):', 'Ċ', 'Ġ', 'Ġ', 'Ġ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo',
 'Ġnumbers', 'Ġ`', 'a', '`', 'Ġand', 'Ġ`', 'b', '`', '."', '""', 'Ċ', 'Ġ', 'Ġ', 'Ġ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb']
```

Ce *tokenizer* possède quelques symboles spéciaux, comme `Ġ` et `Ċ`, qui désignent respectivement les espaces et les retours à la ligne. Comme on peut le voir, ce n'est pas très efficace. Le *tokenizer* renvoie des jetons individuels pour chaque espace alors qu'il pourrait regrouper ceux des indentations (puisqu’avoir des ensembles de quatre ou huit espaces est très courant dans du code). Il divise également le nom de la fonction de façon un peu bizarre car pas habitué à voir des mots avec le caractère `_`.

Entraînons un nouveau *tokenizer* et voyons s'il résout ces problèmes. Pour cela, nous allons utiliser la méthode `train_new_from_iterator()` :


```py
tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000)
```

Cette commande peut prendre un peu de temps si votre corpus est très grand. Pour ce jeu de données de 1,6 Go de textes, elle est très rapide (1 minute 16 secondes sur un CPU AMD Ryzen 9 3900X avec 12 cœurs).

Notez que `AutoTokenizer.train_new_from_iterator()` ne fonctionne que si le *tokenizer* que vous utilisez est un *tokenizer* « rapide ». Comme vous le verrez dans la section suivante, la bibliothèque 🤗 *Transformers* contient deux types de *tokenizers* : certains sont écrits en pur Python et d'autres (les rapides) sont soutenus par la bibliothèque 🤗 *Tokenizers* qui est écrite dans le langage [Rust](https://www.rust-lang.org). Python est le langage le plus souvent utilisé pour les applications de science des données et d'apprentissage profond, mais lorsque quelque chose doit être parallélisé pour être rapide, il faut que cela soit écrit dans un autre langage. Par exemple, les multiplications matricielles qui sont au cœur du calcul du modèle sont écrites en CUDA, une bibliothèque en C optimisée pour les GPUs.

Entraîner un tout nouveau *tokenizer* en Python pur est atrocement lent, c'est pourquoi nous avons développé la bibliothèque 🤗 *Tokenizers*. Notez que, tout comme vous n'avez pas eu à apprendre le langage CUDA pour pouvoir exécuter votre modèle sur un batch d'entrées sur un GPU, vous n'aurez pas besoin d'apprendre Rust pour utiliser un *tokenizer* rapide. La bibliothèque 🤗 *Tokenizers* fournit des liaisons Python pour de nombreuses méthodes qui appellent en interne un morceau de code en Rust. Par exemple, pour paralléliser l'entraînement de votre nouveau *tokenizer* ou, comme nous l'avons vu dans le [chapitre 3](/course/fr/chapter3), la tokenisation d'un lot d'entrées.

La plupart des *transformers* ont un *tokenizer* rapide de disponible. Il y a quelques exceptions que vous pouvez vérifier [ici](https://huggingface.co/transformers/#supported-frameworks). S'il est disponible, l'API `AutoTokenizer` sélectionne toujours pour vous le *tokenizer* rapide. Dans la prochaine section, nous allons jeter un coup d'oeil à certaines des autres caractéristiques spéciales des *tokenizers* rapides, qui seront très utiles pour des tâches comme la classification de *tokens* et la réponse aux questions. Mais avant cela, essayons notre tout nouveau *tokenizer* sur l'exemple précédent :

```py
tokens = tokenizer.tokenize(example)
tokens
```

```python out
['def', 'Ġadd', '_', 'numbers', '(', 'a', ',', 'Ġb', '):', 'ĊĠĠĠ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', 'Ġnumbers', 'Ġ`',
 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb']
```

Ici, nous voyons à nouveau les symboles spéciaux `Ġ` et `Ċ` qui indiquent les espaces et les retours à la ligne. Nous pouvons également voir que notre *tokenizer* a appris certains *tokens* qui sont très spécifiques à un corpus de fonctions Python. Par exemple, il y a un token `ĊĠĠĠ` qui représente une indentation et un *token* `Ġ"""` qui représente les trois guillemets qui commencent une *docstring*. Le *tokenizer* divise également correctement le nom de la fonction sur `_`. Il s'agit d'une représentation assez compacte. En comparaison, l'utilisation du *tokenizer* en anglais « simple » sur le même exemple nous donnera une phrase plus longue :

```py
print(len(tokens))
print(len(old_tokenizer.tokenize(example)))
```

```python out
27
36
```

Prenons un autre exemple :

```python
example = """class LinearLayer():
    def __init__(self, input_size, output_size):
        self.weight = torch.randn(input_size, output_size)
        self.bias = torch.zeros(output_size)

    def __call__(self, x):
        return x @ self.weights + self.bias
    """
tokenizer.tokenize(example)
```

```python out
['class', 'ĠLinear', 'Layer', '():', 'ĊĠĠĠ', 'Ġdef', 'Ġ__', 'init', '__(', 'self', ',', 'Ġinput', '_', 'size', ',',
 'Ġoutput', '_', 'size', '):', 'ĊĠĠĠĠĠĠĠ', 'Ġself', '.', 'weight', 'Ġ=', 'Ġtorch', '.', 'randn', '(', 'input', '_',
 'size', ',', 'Ġoutput', '_', 'size', ')', 'ĊĠĠĠĠĠĠĠ', 'Ġself', '.', 'bias', 'Ġ=', 'Ġtorch', '.', 'zeros', '(',
 'output', '_', 'size', ')', 'ĊĊĠĠĠ', 'Ġdef', 'Ġ__', 'call', '__(', 'self', ',', 'Ġx', '):', 'ĊĠĠĠĠĠĠĠ',
 'Ġreturn', 'Ġx', 'Ġ@', 'Ġself', '.', 'weights', 'Ġ+', 'Ġself', '.', 'bias', 'ĊĠĠĠĠ']
```

En plus du *token* correspondant à une indentation, on peut également voir ici un *token* pour une double indentation : `ĊĠĠĠĠĠĠĠĠĠ`. Les mots spéciaux de Python comme `class`, `init`, `call`, `self`, et `return` sont tous tokenizés comme un seul *token*. Nous pouvons voir qu'en plus de séparer sur `_` et `.` le tokenizer sépare correctement même les noms en minuscules. Par exemple `LinearLayer` est tokenisé comme `["ĠLinear", "Layer"]`.

## Sauvegarde du <i>tokenizer</i>

Pour être sûr de pouvoir l'utiliser plus tard, nous devons sauvegarder notre nouveau *tokenizer*. Comme pour les modèles, ceci est fait avec la méthode `save_pretrained()` :


```py
tokenizer.save_pretrained("code-search-net-tokenizer")
```

Cela créera un nouveau dossier nommé *code-search-net-tokenizer* contenant tous les fichiers dont le *tokenizer* a besoin pour être rechargé. Si vous souhaitez partager ce *tokenizer* avec vos collègues et amis, vous pouvez le télécharger sur le *Hub* en vous connectant à votre compte. Si vous travaillez dans un *notebook*, il existe une fonction pratique pour vous aider à le faire :

```python
from huggingface_hub import notebook_login

notebook_login()
```

Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. Si vous ne travaillez pas sur un ordinateur portable, tapez simplement la ligne suivante dans votre terminal :

```bash
huggingface-cli login
```

Une fois connecté, vous pouvez pousser votre *tokenizer* en exécutant la commande suivante :

```py
tokenizer.push_to_hub("code-search-net-tokenizer")
```

Cela créera un nouveau dépôt dans votre espace avec le nom `code-search-net-tokenizer` contenant le fichier *tokenizer*. Vous pouvez ensuite charger le *tokenizer* de n'importe où avec la méthode `from_pretrained()` :

```py
# Remplacez "huggingface-course" ci-dessous par votre espace réel pour utiliser votre propre tokenizer
tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer")
```

Vous êtes maintenant prêt à entraîner un modèle de langue à partir de zéro et à le *finetuner* sur votre tâche ! Nous verrons cela dans le [chapitre 7](/course/fr/chapter7), mais d'abord, dans le reste de ce chapitre, nous allons examiner de plus près les *tokenizers* rapides et explorer en détail ce qui se passe lorsque nous appelons la méthode `train_new_from_iterator()`.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter6/2.mdx" />

### Création de votre propre jeu de données
https://huggingface.co/learn/course/fr/chapter5/5.md

# Création de votre propre jeu de données

<CourseFloatingBanner chapter={5}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter5/section5.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter5/section5.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter5/section5.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter5/section5.ipynb"},
]} />


Parfois, le jeu de données dont vous avez besoin pour créer une application de NLP n'existe pas. Vous devrez donc le créer vous-même. Dans cette section, nous allons vous montrer comment créer un corpus de [problèmes GitHub](https://github.com/features/issues/), qui sont couramment utilisés pour suivre les bogues ou les fonctionnalités dans les dépôts GitHub. Ce corpus pourrait être utilisé à diverses fins, notamment :

* explorer combien de temps il faut pour fermer les problèmes ouverts ou les *pull requests*
* entraîner d'un _classifieur multilabel_ capable d'étiqueter les problèmes avec des métadonnées basées sur la description du problème (par exemple : « bug », « amélioration » ou  « question »)
* créer un moteur de recherche sémantique pour trouver les problèmes correspondant à la requête d'un utilisateur

Ici, nous nous concentrerons sur la création du corpus, et dans la section suivante, nous aborderons l'application de recherche sémantique. Pour garder les choses méta, nous utiliserons les problèmes GitHub associés à un projet open source populaire : 🤗 *Datasets* ! Voyons comment obtenir les données et explorons les informations contenues dans ces problèmes.

## Obtenir les données

Vous pouvez trouver tous les problèmes dans 🤗 *Datasets* en accédant à l'[onglet « Issues »](https://github.com/huggingface/datasets/issues) du dépôt. Comme le montre la capture d'écran suivante, au moment de la rédaction, il y avait 331 problèmes ouverts et 668 problèmes fermés.

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter5/datasets-issues.png" alt="The GitHub issues associated with 🤗 Datasets." width="80%"/>
</div>

Si vous cliquez sur l'un de ces problèmes, vous constaterez qu'il contient un titre, une description et un ensemble d'étiquettes qui caractérisent le problème. Un exemple est montré dans la capture d'écran ci-dessous.

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter5/datasets-issues-single.png" alt="A typical GitHub issue in the 🤗 Datasets repository." width="80%"/>
</div>

Pour télécharger tous les problèmes du dépôt, nous utilisons l'[API REST GitHub](https://docs.github.com/en/rest) pour interroger le point de terminaison [`Issues`](https://docs.github.com/en/rest/reference/issues#list-repository-issues). Ce point de terminaison renvoie une liste d'objets JSON. Chaque objet contenant un grand nombre de champs qui incluent le titre et la description ainsi que des métadonnées sur l'état du problème, etc.

Un moyen pratique de télécharger les problèmes consiste à utiliser la bibliothèque `requests`, qui est la méthode standard pour effectuer des requêtes HTTP en Python. Vous pouvez installer la bibliothèque en exécutant :

```python
!pip install requests
```

Une fois la bibliothèque installée, vous pouvez envoyer des requêtes GET au point de terminaison `Issues` en appelant la fonction `requests.get()`. Par exemple, vous pouvez exécuter la commande suivante pour récupérer le premier numéro sur la première page :

```py
import requests

url = "https://api.github.com/repos/huggingface/datasets/issues?page=1&per_page=1"
response = requests.get(url)
```

L'objet `response` contient de nombreuses informations utiles sur la requête, y compris le code d'état HTTP :

```py
response.status_code
```

```python out
200
```

où un statut `200` signifie que la requête a réussi (vous pouvez trouver une liste des codes de statut HTTP possibles [ici](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)). Ce qui nous intéresse vraiment, cependant, c'est le _payload_, qui peut être consulté dans différents formats comme les octets, les chaînes ou JSON. Comme nous savons que nos problèmes sont au format JSON, examinons la charge utile comme suit :

```py
response.json()
```

```python out
[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/2792',
  'repository_url': 'https://api.github.com/repos/huggingface/datasets',
  'labels_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/labels{/name}',
  'comments_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/comments',
  'events_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/events',
  'html_url': 'https://github.com/huggingface/datasets/pull/2792',
  'id': 968650274,
  'node_id': 'MDExOlB1bGxSZXF1ZXN0NzEwNzUyMjc0',
  'number': 2792,
  'title': 'Update GooAQ',
  'user': {'login': 'bhavitvyamalik',
   'id': 19718818,
   'node_id': 'MDQ6VXNlcjE5NzE4ODE4',
   'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4',
   'gravatar_id': '',
   'url': 'https://api.github.com/users/bhavitvyamalik',
   'html_url': 'https://github.com/bhavitvyamalik',
   'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers',
   'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}',
   'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}',
   'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}',
   'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions',
   'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs',
   'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos',
   'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}',
   'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events',
   'type': 'User',
   'site_admin': False},
  'labels': [],
  'state': 'open',
  'locked': False,
  'assignee': None,
  'assignees': [],
  'milestone': None,
  'comments': 1,
  'created_at': '2021-08-12T11:40:18Z',
  'updated_at': '2021-08-12T12:31:17Z',
  'closed_at': None,
  'author_association': 'CONTRIBUTOR',
  'active_lock_reason': None,
  'pull_request': {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/2792',
   'html_url': 'https://github.com/huggingface/datasets/pull/2792',
   'diff_url': 'https://github.com/huggingface/datasets/pull/2792.diff',
   'patch_url': 'https://github.com/huggingface/datasets/pull/2792.patch'},
  'body': '[GooAQ](https://github.com/allenai/gooaq) dataset was recently updated after splits were added for the same. This PR contains new updated GooAQ with train/val/test splits and updated README as well.',
  'performed_via_github_app': None}]
```

Waouh, ça fait beaucoup d'informations ! Nous pouvons voir des champs utiles comme `title`, `body` et `number` qui décrivent le problème, ainsi que des informations sur l'utilisateur GitHub qui a ouvert le problème.

<Tip>

✏️ **Essayez !** Cliquez sur quelques-unes des URL pour avoir une idée du type d'informations auxquelles chaque problème GitHub est lié.

</Tip>

Comme décrit dans la [documentation GitHub](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting), les requêtes non authentifiées sont limitées à 60 requêtes par heure. Bien que vous puissiez augmenter le paramètre de requête `per_page` pour réduire le nombre de requêtes que vous effectuez, vous atteindrez toujours la limite de débit sur tout dépôt contenant des milliers de problèmes. Donc, à la place, vous devez suivre les [instructions de GitHub](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) sur la création d'un _jeton d'accès personnel_ afin que vous peut augmenter la limite de débit à 5 000 requêtes par heure. Une fois que vous avez votre *token*, vous pouvez l'inclure dans l'en-tête de la requête :

```py
GITHUB_TOKEN = xxx  # Copiez votre jeton GitHub ici
headers = {"Authorization": f"token {GITHUB_TOKEN}"}
```

<Tip warning={true}>

⚠️ Ne partagez pas un *notebook* avec votre `GITHUB_TOKEN` collé dedans. Nous vous recommandons de supprimer la dernière cellule une fois que vous l'avez exécutée pour éviter de divulguer accidentellement ces informations. Mieux encore, stockez le jeton dans un fichier *.env* et utilisez la [bibliothèque `python-dotenv`](https://github.com/theskumar/python-dotenv) pour le charger automatiquement pour vous en tant que variable d'environnement.

</Tip>

Maintenant que nous avons notre jeton d'accès, créons une fonction qui peut télécharger tous les problèmes depuis un référentiel GitHub :

```py
import time
import math
from pathlib import Path
import pandas as pd
from tqdm.notebook import tqdm


def fetch_issues(
    owner="huggingface",
    repo="datasets",
    num_issues=10_000,
    rate_limit=5_000,
    issues_path=Path("."),
):
    if not issues_path.is_dir():
        issues_path.mkdir(exist_ok=True)

    batch = []
    all_issues = []
    per_page = 100  # Nombre d'issues à retourner par page
    num_pages = math.ceil(num_issues / per_page)
    base_url = "https://api.github.com/repos"

    for page in tqdm(range(num_pages)):
        # Requête avec state=all pour obtenir les issues ouvertes et fermées
        query = f"issues?page={page}&per_page={per_page}&state=all"
        issues = requests.get(f"{base_url}/{owner}/{repo}/{query}", headers=headers)
        batch.extend(issues.json())

        if len(batch) > rate_limit and len(all_issues) < num_issues:
            all_issues.extend(batch)
            batch = []  # Vider le batch pour la période de temps suivante
            print(f"Reached GitHub rate limit. Sleeping for one hour ...")
            time.sleep(60 * 60 + 1)

    all_issues.extend(batch)
    df = pd.DataFrame.from_records(all_issues)
    df.to_json(f"{issues_path}/{repo}-issues.jsonl", orient="records", lines=True)
    print(
        f"Downloaded all the issues for {repo}! Dataset stored at {issues_path}/{repo}-issues.jsonl"
    )
```

Désormais, lorsque nous appellerons `fetch_issues()`, tous les problèmes seront téléchargés par batchs pour éviter de dépasser la limite de GitHub sur le nombre de requêtes par heure. Le résultat sera stocké dans un fichier _repository_name-issues.jsonl_, où chaque ligne est un objet JSON qui représente un problème. Utilisons cette fonction pour saisir tous les problèmes de 🤗 *Datasets* :

```py
# En fonction de votre connexion Internet, l'exécution peut prendre plusieurs minutes...
fetch_issues()
```

Une fois les problèmes téléchargés, nous pouvons les charger localement en utilisant nos nouvelles compétences de [section 2](/course/fr/chapter5/2) :

```py
issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train")
issues_dataset
```

```python out
Dataset({
    features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'timeline_url', 'performed_via_github_app'],
    num_rows: 3019
})
```

Génial, nous avons créé notre premier jeu de données à partir de rien ! Mais pourquoi y a-t-il plusieurs milliers de problèmes alors que l'[onglet « Issues »](https://github.com/huggingface/datasets/issues) de la librairie 🤗 *Datasets* n'affiche qu'environ 1 000 problèmes au total 🤔 ? Comme décrit dans la [documentation GitHub](https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user), c'est parce que nous avons téléchargé toutes les *pull requests* également :

> L'API REST v3 de GitHub considère chaque *pull request* comme un problème, mais chaque problème n'est pas une *pull request*. Pour cette raison, les points de terminaison « Issues » peuvent renvoyer à la fois des problèmes et des *pull requests* dans la réponse. Vous pouvez identifier les *pull requests* par la clé `pull_request`. Sachez que l'identifiant d'une *pull request* renvoyée par les points de terminaison « Issues » sera un identifiant de problème.

Étant donné que le contenu des « Issues » et des *pull request* est assez différent, procédons à un prétraitement mineur pour nous permettre de les distinguer.

## Nettoyer les données

L'extrait ci-dessus de la documentation de GitHub nous indique que la colonne `pull_request` peut être utilisée pour différencier les *issues* et les *pull requests*. Regardons un échantillon aléatoire pour voir quelle est la différence. Comme nous l'avons fait dans [section 3](/course/fr/chapter5/3), nous allons enchaîner `Dataset.shuffle()` et `Dataset.select()` pour créer un échantillon aléatoire, puis compresser `html_url` et ` pull_request` afin que nous puissions comparer les différentes URL :

```py
sample = issues_dataset.shuffle(seed=666).select(range(3))

# Afficher l'URL et les entrées de la PR
for url, pr in zip(sample["html_url"], sample["pull_request"]):
    print(f">> URL: {url}")
    print(f">> Pull request: {pr}\n")
```

```python out
>> URL: https://github.com/huggingface/datasets/pull/850
>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/850', 'html_url': 'https://github.com/huggingface/datasets/pull/850', 'diff_url': 'https://github.com/huggingface/datasets/pull/850.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/850.patch'}

>> URL: https://github.com/huggingface/datasets/issues/2773
>> Pull request: None

>> URL: https://github.com/huggingface/datasets/pull/783
>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/783', 'html_url': 'https://github.com/huggingface/datasets/pull/783', 'diff_url': 'https://github.com/huggingface/datasets/pull/783.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/783.patch'}
```

Ici, nous pouvons voir que chaque *pull request* est associée à diverses URL, tandis que les problèmes ordinaires ont une entrée `None`. Nous pouvons utiliser cette distinction pour créer une nouvelle colonne `is_pull_request` qui vérifie si le champ `pull_request` est `None` ou non :

```py
issues_dataset = issues_dataset.map(
    lambda x: {"is_pull_request": False if x["pull_request"] is None else True}
)
```

<Tip>

✏️ **Essayez !** Calculez le temps moyen nécessaire pour résoudre les problèmes dans 🤗 *Datasets*. Vous pouvez trouver la fonction `Dataset.filter()` utile pour filtrer les demandes d'extraction et les problèmes ouverts. Vous pouvez utiliser la fonction `Dataset.set_format()` pour convertir le jeu de données en un `DataFrame` afin que vous puissiez facilement manipuler les horodatages `created_at` et `closed_at`. Pour les points bonus, calculez le temps moyen nécessaire pour fermer les *pull_requests*.

</Tip>

Bien que nous puissions continuer à nettoyer davantage le jeu de données en supprimant ou en renommant certaines colonnes, il est généralement recommandé de le conserver aussi brut que possible à ce stade afin qu'il puisse être facilement utilisé dans plusieurs applications.

Avant de pousser notre jeu de données vers le *Hub* d’Hugging Face, traitons une chose manquante : les commentaires associés à chaque problème et *pull requests*. Nous les ajouterons ensuite avec l'API GitHub REST !

## Enrichir le jeu de données

Comme le montre la capture d'écran suivante, les commentaires associés à un problème ou à une *pull request* fournissent une riche source d'informations, en particulier si nous souhaitons créer un moteur de recherche pour répondre aux requêtes des utilisateurs sur la bibliothèque.

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter5/datasets-issues-comment.png" alt="Comments associated with an issue about 🤗 Datasets." width="80%"/>
</div>

L'API REST GitHub fournit un point de terminaison [`Comments`](https://docs.github.com/en/rest/reference/issues#list-issue-comments) qui renvoie tous les commentaires associés à un numéro de problème. Testons le point de terminaison pour voir ce qu'il renvoie :

```py
issue_number = 2792
url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments"
response = requests.get(url, headers=headers)
response.json()
```

```python out
[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/comments/897594128',
  'html_url': 'https://github.com/huggingface/datasets/pull/2792#issuecomment-897594128',
  'issue_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792',
  'id': 897594128,
  'node_id': 'IC_kwDODunzps41gDMQ',
  'user': {'login': 'bhavitvyamalik',
   'id': 19718818,
   'node_id': 'MDQ6VXNlcjE5NzE4ODE4',
   'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4',
   'gravatar_id': '',
   'url': 'https://api.github.com/users/bhavitvyamalik',
   'html_url': 'https://github.com/bhavitvyamalik',
   'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers',
   'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}',
   'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}',
   'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}',
   'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions',
   'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs',
   'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos',
   'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}',
   'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events',
   'type': 'User',
   'site_admin': False},
  'created_at': '2021-08-12T12:21:52Z',
  'updated_at': '2021-08-12T12:31:17Z',
  'author_association': 'CONTRIBUTOR',
  'body': "@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n    def test_load_dataset(self, dataset_name):\r\n        configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n>       self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n    self.parent.assertTrue(len(dataset[split]) > 0)\r\nE   AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?",
  'performed_via_github_app': None}]
```

Nous pouvons voir que le commentaire est stocké dans le champ `body`. Ecrivons donc une fonction simple qui renvoie tous les commentaires associés à un problème en sélectionnant le contenu `body` pour chaque élément dans `response.json()` :

```py
def get_comments(issue_number):
    url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments"
    response = requests.get(url, headers=headers)
    return [r["body"] for r in response.json()]


# Tester notre fonction fonctionne comme prévu
get_comments(2792)
```

```python out
["@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n    def test_load_dataset(self, dataset_name):\r\n        configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n>       self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n    self.parent.assertTrue(len(dataset[split]) > 0)\r\nE   AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?"]
```

Cela a l'air bien. Utilisons `Dataset.map()` pour ajouter une nouvelle colonne `comments` à chaque problème de notre jeu de données :

```py
# Selon votre connexion internet, cela peut prendre quelques minutes...
issues_with_comments_dataset = issues_dataset.map(
    lambda x: {"comments": get_comments(x["number"])}
)
```

La dernière étape consiste à enregistrer le jeu de données augmentées avec nos données brutes afin que nous puissions les pousser tous les deux vers le *Hub* :

```py
issues_with_comments_dataset.to_json("issues-datasets-with-comments.jsonl")
```

## Téléchargement du jeu de données sur le <i>Hub</i>

<Youtube id="HaN6qCr_Afc"/>

Maintenant que nous avons notre jeu de données augmenté, il est temps de le pousser vers le *Hub* afin que nous puissions le partager avec la communauté ! Pour télécharger le jeu de données, nous utilisons la [bibliothèque 🤗 *Hub*](https://github.com/huggingface/huggingface_hub), qui nous permet d'interagir avec le *Hub* d’Hugging Face via une API Python. 🤗 *Hub* est préinstallé avec 🤗 *Transformers*, nous pouvons donc l'utiliser directement. Par exemple, nous pouvons utiliser la fonction `list_datasets()` pour obtenir des informations sur tous les ensembles de données publics actuellement hébergés sur le *Hub*:

```py
from huggingface_hub import list_datasets

all_datasets = list_datasets()
print(f"Number of datasets on Hub: {len(all_datasets)}")
print(all_datasets[0])
```

```python out
Number of datasets on Hub: 1487
Dataset Name: acronym_identification, Tags: ['annotations_creators:expert-generated', 'language_creators:found', 'languages:en', 'licenses:mit', 'multilinguality:monolingual', 'size_categories:10K<n<100K', 'source_datasets:original', 'task_categories:structure-prediction', 'task_ids:structure-prediction-other-acronym-identification']
```

Nous pouvons voir qu'il y a actuellement près de 1 500 jeux de données sur le *Hub* et la fonction `list_datasets()` fournit également des métadonnées de base sur chaque dépôts de jeux de données.

Pour nos besoins, la première chose que nous devons faire est de créer un nouveau dépôt de jeux de données sur le *Hub*. Pour ce faire, nous avons besoin d'un jeton d'authentification, qui peut être obtenu en se connectant d'abord au *Hub* d’Hugging Face avec la fonction `notebook_login()` :

```py
from huggingface_hub import notebook_login

notebook_login()
```

Cela créé un *widget* dans lequel vous pouvez entrer votre nom d'utilisateur et votre mot de passe. Un jeton API sera enregistré dans *~/.huggingface/token*. Si vous exécutez le code dans un terminal, vous pouvez vous connecter via la CLI à la place :

```bash
huggingface-cli login
```

Une fois que nous avons fait cela, nous pouvons créer un nouveau dépôt de jeux de données avec la fonction `create_repo()` :

```py
from huggingface_hub import create_repo

repo_url = create_repo(name="github-issues", repo_type="dataset")
repo_url
```

```python out
'https://huggingface.co/datasets/lewtun/github-issues'
```

Dans cet exemple, nous avons créé un dépôt vide appelé `github-issues` sous le nom d'utilisateur `lewtun` (le nom d'utilisateur doit être votre nom d'utilisateur Hub lorsque vous exécutez ce code !).

<Tip>

✏️ **Essayez !** Utilisez votre nom d'utilisateur et votre mot de passe Hugging Face pour obtenir un jeton et créer un dépôt vide appelé `github-issues`. N'oubliez pas de **n'enregistrez jamais vos informations d'identification** dans Colab ou tout autre référentiel car ces informations peuvent être exploitées par de mauvais individus.

</Tip>

Ensuite, clonons le dépôt du Hub sur notre machine locale et copions-y notre fichier jeu de données. 🤗 *Hub* fournit une classe `Repository` pratique qui encapsule de nombreuses commandes Git courantes. Donc pour cloner le dépôt distant, nous devons simplement fournir l'URL et le chemin local vers lesquels nous souhaitons cloner :

```py
from huggingface_hub import Repository

repo = Repository(local_dir="github-issues", clone_from=repo_url)
!cp datasets-issues-with-comments.jsonl github-issues/
```

Par défaut, diverses extensions de fichiers (telles que *.bin*, *.gz* et *.zip*) sont suivies avec Git LFS afin que les fichiers volumineux puissent être versionnés dans le même workflow Git. Vous pouvez trouver une liste des extensions de fichiers suivis dans le fichier *.gitattributes* du référentiel. Pour inclure le format JSON Lines dans la liste, nous pouvons exécuter la commande suivante :

```py
repo.lfs_track("*.jsonl")
```

Ensuite, nous pouvons utiliser `Repository.push_to_hub()` pour pousser le jeu de données vers le *Hub* :

```py
repo.push_to_hub()
```

Si nous naviguons vers l'URL contenue dans `repo_url`, nous devrions maintenant voir que notre fichier de jeu de données a été téléchargé.

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter5/hub-repo.png" alt="Our dataset repository on the Hugging Face Hub." width="80%"/>
</div>

À partir de là, n'importe qui peut télécharger le jeu de données en fournissant simplement `load_dataset()` avec l'ID du référentiel comme argument `path` :

```py
remote_dataset = load_dataset("lewtun/github-issues", split="train")
remote_dataset
```

```python out
Dataset({
    features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'],
    num_rows: 2855
})
```

Cool, nous avons poussé notre jeu de données vers le *Hub* et il est disponible pour que d'autres puissent l'utiliser ! Il ne reste plus qu'une chose importante à faire : ajouter une _carte de jeu de données_ qui explique comment le corpus a été créé et fournit d'autres informations utiles à la communauté.

<Tip>

💡 Vous pouvez également télécharger un jeu de données sur le *Hub* directement depuis le terminal en utilisant `huggingface-cli` et un peu de magie Git. Consultez le [guide de 🤗 *Datasets*](https://huggingface.co/docs/datasets/share#share-a-dataset-using-the-cli) pour savoir comment procéder.

</Tip>

## Création d'une carte pour un jeu de données

Des jeux de données bien documentés sont plus susceptibles d'être utiles aux autres (y compris à vous-même) car ils fournissent le contexte permettant aux utilisateurs de décider si le jeu de données est pertinent pour leur tâche et d'évaluer les biais potentiels ou les risques associés à l'utilisation du jeu de données.

Sur le *Hub*, ces informations sont stockées dans le fichier *README.md* de chaque dépôt de jeux de données. Il y a deux étapes principales que vous devez suivre avant de créer ce fichier :

1. Utilisez l'[application `datasets-tagging`](https://huggingface.co/datasets/tagging/) pour créer des balises de métadonnées au format YAML. Ces balises sont utilisées pour une variété de fonctionnalités de recherche sur le *Hub* d’Hugging Face et garantissent que votre jeu de données peut être facilement trouvé par les membres de la communauté. Puisque nous avons créé un jeu de données personnalisé ici, vous devrez cloner le référentiel `datasets-tagging` et exécuter l'application localement. Voici à quoi ressemble l'interface :

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter5/datasets-tagger.png" alt="The 'datasets-tagging' interface." width="80%"/>
</div>

2. Lisez le [guide de 🤗 *Datasets*](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) sur la création des cartes informatives des jeux de données et utilisez-le comme modèle.

Vous pouvez créer le fichier *README.md* directement sur le *Hub* et vous pouvez trouver un modèle de carte dans le dépot `lewtun/github-issues`. Une capture d'écran de la carte remplie est illustrée ci-dessous.


<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter5/dataset-card.png" alt="A dataset card." width="80%"/>
</div>

<Tip>

✏️ **Essayez !** Utilisez l'application `dataset-tagging` et [le guide de 🤗 *Datasets*](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) pour compléter le fichier *README.md* de votre jeu de données de problèmes GitHub.
</Tip>

C’est tout ! Nous avons vu dans cette section que la création d'un bon jeu de données peut être assez complexe, mais heureusement, le télécharger et le partager avec la communauté ne l'est pas. Dans la section suivante, nous utiliserons notre nouveau jeu de données pour créer un moteur de recherche sémantique avec 🤗 *Datasets* qui peut faire correspondre les questions aux problèmes et commentaires les plus pertinents.

<Tip>

✏️ **Essayez !** Suivez les étapes que nous avons suivies dans cette section pour créer un jeu de données de problèmes GitHub pour votre bibliothèque open source préférée (choisissez autre chose que 🤗 *Datasets*, bien sûr !). Pour obtenir des points bonus, *finetunez* un classifieur multilabel pour prédire les balises présentes dans le champ `labels`.

</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter5/5.mdx" />

### Introduction
https://huggingface.co/learn/course/fr/chapter5/1.md

# Introduction

<CourseFloatingBanner
    chapter={5}
    classNames="absolute z-10 right-0 top-0"
/>

Dans le [chapitre 3](/course/fr/chapter3) vous avez eu un premier aperçu de la bibliothèque 🤗 *Datasets* et des trois étapes principales pour *finetuner* un modèle :

1. chargement d'un jeu de données à partir du *Hub* d’Hugging Face,
2. prétraitement des données avec `Dataset.map()`,
3. chargement et calcul des métriques.

Mais ce n'est qu'effleurer la surface de ce que 🤗 *Datasets* peut faire ! Dans ce chapitre, nous allons plonger profondément dans cette bibliothèque. En cours de route, nous trouverons des réponses aux questions suivantes :

* que faire lorsque votre jeu de données n'est pas sur le *Hub* ?
* comment découper et trancher un jeu de données ? (Et si on a _vraiment_ besoin d'utiliser Pandas ?)
* que faire lorsque votre jeu de données est énorme et va monopoliser la RAM de votre ordinateur portable ?
* qu'est-ce que c'est que le « *memory mapping* » et Apache Arrow ?
* comment créer votre propre jeu de données et le pousser sur le *Hub* ?

Les techniques apprises dans ce chapitre vous prépareront aux tâches avancées de tokenisation du [chapitre 6](/course/fr/chapter6) et de *finetuning* du [chapitre 7](/course/fr/chapter7). Alors prenez un café et commençons !

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter5/1.mdx" />

### Il est temps de trancher et de découper
https://huggingface.co/learn/course/fr/chapter5/3.md

# Il est temps de trancher et de découper


<CourseFloatingBanner chapter={5}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter5/section3.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter5/section3.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter5/section3.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter5/section3.ipynb"},
]} />

La plupart du temps, les données avec lesquelles vous travaillez ne sont pas parfaitement préparées pour l’entraînements de modèles. Dans cette section, nous allons explorer les différentes fonctionnalités fournies par 🤗 *Datasets* pour nettoyer vos jeux de données.

<Youtube id="tqfSFcPMgOI"/>

## Trancher et découper nos données

Semblable à Pandas, 🤗 *Datasets* fournit plusieurs fonctions pour manipuler le contenu des objets `Dataset` et `DatasetDict`. Nous avons déjà rencontré la méthode `Dataset.map()` dans le [chapitre 3](/course/fr/chapter3) et dans cette section nous allons explorer certaines des autres fonctions à notre disposition.

Pour cet exemple, nous utiliserons le [*Drug Review Dataset*](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29) qui est hébergé sur [*UC Irvine Machine Learning Repository*](https://archive.ics.uci.edu/ml/index.php) et contenant des avis de patients sur divers médicaments ainsi que la condition traitée et une note de 10 étoiles sur la satisfaction du patient.

Nous devons d'abord télécharger et extraire les données, ce qui peut être fait avec les commandes `wget` et `unzip` :

```py
!wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip"
!unzip drugsCom_raw.zip
```

Étant donné que TSV n'est qu'une variante de CSV qui utilise des tabulations au lieu de virgules comme séparateurs, nous pouvons charger ces fichiers en utilisant le script de chargement `csv` et en spécifiant l'argument `delimiter` dans la fonction `load_dataset()` comme suit :

```py
from datasets import load_dataset

data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"}
# \t est le caractère de tabulation en Python
drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t")
```

Une bonne pratique lors de toute sorte d'analyse de données consiste à prélever un petit échantillon aléatoire pour avoir une idée rapide du type de données avec lesquelles vous travaillez. Dans 🤗 *Datasets*, nous pouvons créer un échantillon aléatoire en enchaînant les fonctions `Dataset.shuffle()` et `Dataset.select()` :

```py
drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000))
# Un coup d'œil sur les premiers exemples
drug_sample[:3]
```

```python out
{'Unnamed: 0': [87571, 178045, 80482],
 'drugName': ['Naproxen', 'Duloxetine', 'Mobic'],
 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'], 
 #['Goutte aiguë', 'ibromyalgie', 'Affections inflammatoires']
 'review': ['"like the previous person mention, I&#039;m a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"', 
 # comme la personne précédente l'a mentionné, je suis un fervent partisan de l'aleve, il fonctionne plus rapidement pour ma goutte que les médicaments sur ordonnance que je prends. Je n'ai plus besoin d'aller chez le médecin pour des renouvellements.....Aleve fonctionne !"
  '"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."', 
  # J'ai pris du Cymbalta pendant environ un an et demi pour des douleurs de la fibromyalgie. C'est un excellent analgésique et un antidépresseur, mais les effets secondaires l'ont emporté sur tous les avantages que j'en ai tirés. J'ai eu des problèmes d'agitation, de fatigue constante, de vertiges, de bouche sèche, d'engourdissement, de picotements dans les pieds, et de transpiration horrible. Je suis en train de m'en sevrer maintenant. Je suis passée de 60 mg à 30 mg et maintenant à 15 mg. Je l'arrêterai complètement dans environ une semaine. La douleur de la fibrose revient, mais je préfère la supporter plutôt que les effets secondaires.
  '"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure.  I had severe knee and ankle pain which completely went away after taking Mobic.  I attempted to stop the medication however pain returned after a few days."'], 
  # J'ai pris Mobic pendant plus d'un an sans effets secondaires autres qu'une pression sanguine élevée.  J'avais de fortes douleurs au genou et à la cheville qui ont complètement disparu après avoir pris Mobic. J'ai essayé d'arrêter le médicament mais la douleur est revenue après quelques jours."
 'rating': [9.0, 3.0, 10.0],
 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'], 
        #['2 septembre 2015', '7 novembre 2011', '5 juin 2013']
 'usefulCount': [36, 13, 128]}
```

Notez que nous avons corrigé la graine dans `Dataset.shuffle()` à des fins de reproductibilité. `Dataset.select()` attend un itérable d'indices, nous avons donc passé `range(1000)` pour récupérer les 1 000 premiers exemples du jeu de données mélangé. À partir de cet échantillon, nous pouvons déjà voir quelques bizarreries dans notre jeu de données :

* la colonne `Unnamed: 0` ressemble étrangement à un identifiant anonyme pour chaque patient,
* la colonne `condition` comprend un mélange d'étiquettes en majuscules et en minuscules,
* les avis sont de longueur variable et contiennent un mélange de séparateurs de lignes Python (`\r\n`) ainsi que des codes de caractères HTML comme `&\#039;`.

Voyons comment nous pouvons utiliser 🤗 *Datasets* pour traiter chacun de ces problèmes. Pour tester l'hypothèse de l'ID patient pour la colonne `Unnamed : 0`, nous pouvons utiliser la fonction `Dataset.unique()` pour vérifier que le nombre d'ID correspond au nombre de lignes dans chaque division :

```py
for split in drug_dataset.keys():
    assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0"))
```

Cela semble confirmer notre hypothèse, alors nettoyons un peu en renommant la colonne `Unnamed: 0` en quelque chose d'un peu plus interprétable. Nous pouvons utiliser la fonction `DatasetDict.rename_column()` pour renommer la colonne sur les deux divisions en une seule fois :

```py
drug_dataset = drug_dataset.rename_column(
    original_column_name="Unnamed: 0", new_column_name="patient_id"
)
drug_dataset
```

```python out
DatasetDict({
    train: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'],
        num_rows: 161297
    })
    test: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'],
        num_rows: 53766
    })
})
```

<Tip>

✏️ **Essayez !** Utilisez la fonction ` Dataset.unique()` pour trouver le nombre de médicaments et de conditions uniques dans les échantillons d'entraînement et de test.

</Tip>

Ensuite, normalisons toutes les étiquettes `condition` en utilisant `Dataset.map()`. Comme nous l'avons fait avec la tokenisation dans le [chapitre 3](/course/fr/chapter3), nous pouvons définir une fonction simple qui peut être appliquée sur toutes les lignes de chaque division dans `drug_dataset` :

```py
def lowercase_condition(example):
    return {"condition": example["condition"].lower()}


drug_dataset.map(lowercase_condition)
```

```python out
AttributeError: 'NoneType' object has no attribute 'lower'
```

Oh non, nous rencontrons un problème avec notre fonction ! À partir de l'erreur, nous pouvons déduire que certaines des entrées de la colonne `condition` sont `None` ne pouvant donc pas être mises en minuscules car ce ne sont pas des chaînes. Supprimons ces lignes en utilisant `Dataset.filter()`, qui fonctionne de manière similaire à `Dataset.map()` et attend une fonction qui reçoit un seul exemple issu du jeu de données. Au lieu d'écrire une fonction explicite comme :

```py
def filter_nones(x):
    return x["condition"] is not None
```

puis exécuter `drug_dataset.filter(filter_nones)`, nous pouvons le faire en une seule ligne en utilisant une _fonction lambda_. En Python, les fonctions lambda sont de petites fonctions que vous pouvez définir sans les nommer explicitement. Ils prennent la forme générale :

```
lambda <arguments> : <expression>
```

où `lambda` est l'un des [mots clés](https://docs.python.org/3/reference/lexical_analysis.html#keywords) spéciaux de Python, `<arguments>` est une liste/ensemble de valeurs séparées par des virgules qui définissent les entrées de la fonction et `<expression>` représente les opérations que vous souhaitez exécuter. Par exemple, nous pouvons définir une simple fonction lambda qui met au carré un nombre comme suit :

```
lambda x : x * x
```

Pour appliquer cette fonction à une entrée, nous devons l'envelopper ainsi que l'entrée entre parenthèses :

```py
(lambda x: x * x)(3)
```

```python out
9
```

De même, nous pouvons définir des fonctions lambda avec plusieurs arguments en les séparant par des virgules. Par exemple, nous pouvons calculer l'aire d'un triangle comme suit :

```py
(lambda base, height: 0.5 * base * height)(4, 8)
```

```python out
16.0
```

Les fonctions lambda sont pratiques lorsque vous souhaitez définir de petites fonctions à usage unique (pour plus d'informations à leur sujet, nous vous recommandons de lire l'excellent [tutoriel Real Python](https://realpython.com/python-lambda/) d'André Burgaud) . Dans le contexte de la bibliothèque 🤗 *Datasets*, nous pouvons utiliser des fonctions lambda pour définir des opérations simples de « mappage » et de filtrage. Utilisons cette astuce pour éliminer les entrées `None` dans notre jeu de données :

```py
drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None)
```

Avec les entrées `None` supprimées, nous pouvons normaliser notre colonne `condition` :

```py
drug_dataset = drug_dataset.map(lowercase_condition)
# Vérification que la mise en minuscule a fonctionné
drug_dataset["train"]["condition"][:3]
```

```python out
['left ventricular dysfunction', 'adhd', 'birth control']
```

Ça marche ! Maintenant que nous avons nettoyé les étiquettes, examinons le nettoyage des avis eux-mêmes.

## Création de nouvelles colonnes

Chaque fois que vous avez affaire à des avis de clients, une bonne pratique consiste à vérifier le nombre de mots dans chaque avis. Une critique peut être un simple mot comme « Génial ! » ou un essai complet avec des milliers de mots. Selon le cas d'usage, vous devrez gérer ces extrêmes différemment. Pour calculer le nombre de mots dans chaque révision, nous utiliserons une heuristique approximative basée sur la division de chaque texte par des espaces.

Définissons une fonction simple qui compte le nombre de mots dans chaque avis :

```py
def compute_review_length(example):
    return {"review_length": len(example["review"].split())}
```

Contrairement à notre fonction `lowercase_condition()`, `compute_review_length()` renvoie un dictionnaire dont la clé ne correspond pas à l'un des noms de colonne du jeu de données. Dans ce cas, lorsque `compute_review_length()` est passé à `Dataset.map()`, il est appliqué à toutes les lignes du jeu de données pour créer une nouvelle colonne `review_length` :

```py
drug_dataset = drug_dataset.map(compute_review_length)
# Inspecter le premier exemple d'entraînement
drug_dataset["train"][0]
```

```python out
{'patient_id': 206461,
 'drugName': 'Valsartan',
 'condition': 'left ventricular dysfunction', # dysfonctionnement du ventricule gauche
 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"', 
           # Il n'a aucun effet secondaire, je le prends en combinaison avec Bystolic 5 mg et de l'huile de poisson.
 'rating': 9.0,
 'date': 'May 20, 2012', # 20 mai 2012
 'usefulCount': 27,
 'review_length': 17}
```

Comme prévu, nous pouvons voir qu'une colonne `review_length` a été ajoutée à notre jeu d'entraînement. Nous pouvons trier cette nouvelle colonne avec `Dataset.sort()` pour voir à quoi ressemblent les valeurs extrêmes :

```py
drug_dataset["train"].sort("review_length")[:3]
```

```python out
{'patient_id': [103488, 23627, 20558],
 'drugName': ['Loestrin 21 1 / 20', 'Chlorzoxazone', 'Nucynta'],
 'condition': ['birth control', 'muscle spasm', 'pain'], 
              # contraception, spasme musculaire, douleur.
 'review': ['"Excellent."', '"useless"', '"ok"'], # Excellent, inutile, ok 
 'rating': [10.0, 1.0, 6.0],
 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'], 
         # 4 novembre 2008, 24 mars 2017, 20 août 2016
 'usefulCount': [5, 2, 10],
 'review_length': [1, 1, 1]}
```

Comme nous le soupçonnions, certaines critiques ne contiennent qu'un seul mot, ce qui, bien que cela puisse convenir à l'analyse des sentiments, n’est pas informatif si nous voulons prédire la condition.

<Tip>

🙋 Une autre façon d'ajouter de nouvelles colonnes à un jeu de données consiste à utiliser la fonction `Dataset.add_column()`. Cela vous permet de donner la colonne sous forme de liste Python ou de tableau NumPy et peut être utile dans les situations où `Dataset.map()` n'est pas bien adapté à votre analyse.

</Tip>

Utilisons la fonction `Dataset.filter()` pour supprimer les avis contenant moins de 30 mots. De la même manière que nous l'avons fait avec la colonne `condition`, nous pouvons filtrer les avis très courts en exigeant que les avis aient une longueur supérieure à ce seuil :

```py
drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30)
print(drug_dataset.num_rows)
```

```python out
{'train': 138514, 'test': 46108}
```

Comme vous pouvez le constater, cela a supprimé environ 15 % des avis de nos jeux d'entraînement et de test d'origine.

<Tip>

✏️ **Essayez !** Utilisez la fonction `Dataset.sort()` pour inspecter les avis avec le plus grand nombre de mots. Consultez la [documentation](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.sort) pour voir quel argument vous devez utiliser pour trier les avis par longueur dans l'ordre décroissant.

</Tip>

La dernière chose à laquelle nous devons faire face est la présence de caractères HTML dans nos avis. Nous pouvons utiliser le module `html` de Python pour supprimer ces caractères, comme ceci :

```py
import html

text = "I&#039;m a transformer called BERT"
html.unescape(text)
```

```python out
"I'm a transformer called BERT"
```

Nous utilisons `Dataset.map()` pour démasquer tous les caractères HTML de notre corpus :

```python
drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])})
```

Comme vous pouvez le voir, la méthode `Dataset.map()` est très utile pour le traitement des données. Et nous n'avons même pas effleuré la surface de tout ce qu'elle peut faire !

## Les superpouvoirs de la méthode `map()`

La méthode `Dataset.map()` prend un argument `batched` qui, s'il est défini sur `True`, l'amène à envoyer un batch d'exemples à la fonction *map* en une seule fois (la taille du batch est configurable mais est fixé par défaut à 1 000). Par exemple, la fonction `map()` précédente qui supprime tout le code HTML prend un peu de temps à s'exécuter (vous pouvez lire le temps pris dans les barres de progression). On peut accélérer cela en traitant plusieurs éléments en même temps à l'aide d'une compréhension de liste.

Lorsque vous spécifiez `batched=True`, la fonction reçoit un dictionnaire avec les champs du jeu de données mais chaque valeur est maintenant une _liste de valeurs_ et non plus une seule valeur. La valeur retournée par `Dataset.map()` devrait être la même : un dictionnaire avec les champs que nous voulons mettre à jour ou ajouter à notre jeu de données, et une liste de valeurs. Par exemple, voici une autre façon de supprimer tous les caractères HTML, mais en utilisant `batched=True` :

```python
new_drug_dataset = drug_dataset.map(
    lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True
)
```

Si vous exécutez ce code dans un *notebook*, vous verrez que cette commande s'exécute beaucoup plus rapidement que la précédente. Et ce n'est pas parce que nos critiques ont déjà été scannées au format HTML. Si vous ré-exécutez l'instruction de la section précédente (sans `batched=True`), cela prendra le même temps qu'avant. En effet, les compréhensions de liste sont généralement plus rapides que l'exécution du même code dans une boucle `for` et nous gagnons également en performances en accédant à de nombreux éléments en même temps au lieu d'un par un.

L'utilisation de `Dataset.map()` avec `batched=True` est essentielle pour les *tokenizers rapides* que nous rencontrerons dans le [chapitre 6](/course/fr/chapter6) et qui peuvent rapidement tokeniser de grandes listes de textes. Par exemple, pour tokeniser toutes les critiques de médicaments avec un *tokenizer* rapide nous pouvons utiliser une fonction comme celle-ci :

```python
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")


def tokenize_function(examples):
    return tokenizer(examples["review"], truncation=True)
```

Comme vous l'avez vu dans le [chapitre 3](/course/fr/chapter3), nous pouvons passer un ou plusieurs exemples au *tokenizer*. Nous pouvons donc utiliser cette fonction avec ou sans `batched=True`. Profitons-en pour comparer les performances des différentes options. Dans un *notebook*, vous pouvez chronométrer une instruction d'une ligne en ajoutant `%time` avant la ligne de code que vous souhaitez mesurer :

```python no-format
%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True)
```

Vous pouvez également chronométrer une cellule entière en mettant `%%time` au début de la cellule. Sur le matériel sur lequel nous avons exécuté cela, cela affichait 10,8 s pour cette instruction (c'est le nombre écrit après "Wall time").

<Tip>

✏️ **Essayez !** Exécutez la même instruction avec et sans `batched=True`, puis essayez-le avec un *tokenizer* lent (ajoutez `use_fast=False` dans la méthode `AutoTokenizer.from_pretrained()`) afin que vous puissiez voir quels temps vous obtenez sur votre matériel.

</Tip>

Voici les résultats que nous avons obtenus avec et sans *batching*, avec un *tokenizer* rapide et un lent :

Options         | *Tokenizer* rapide | *Tokenizer* lent
:--------------:|:----------------:|:-----------------:
`batched=True`  | 10.8s            | 4min41s
`batched=False` | 59.2s            | 5min3s

Cela signifie que l'utilisation d'un *tokenizer* rapide avec l'option `batched=True` est 30 fois plus rapide que son homologue lent sans batch. C'est vraiment incroyable ! C'est la raison principale pour laquelle les *tokenizers* rapides sont la valeur par défaut lors de l'utilisation de `AutoTokenizer` (et pourquoi ils sont appelés « rapides »). Ils sont capables d'atteindre une telle vitesse car en coulisses le code de tokenisation est exécuté en Rust qui est un langage facilitant la parallélisation de l'exécution du code.

La parallélisation est également la raison du gain de vitesse de près de 6 fois obtenue par le *tokenizer* rapide avec batch. Vous ne pouvez pas paralléliser une seule opération de tokenisation, mais lorsque vous souhaitez tokeniser de nombreux textes en même temps, vous pouvez simplement répartir l'exécution sur plusieurs processus. Chacun responsable de ses propres textes.

`Dataset.map()` possède aussi ses propres capacités de parallélisation. Comme elles ne sont pas soutenus par Rust, un *tokenizer* lent ne peut pas rattraper un rapide mais cela peut toujours être utile (surtout si vous utilisez un *tokenizer* qui n'a pas de version rapide). Pour activer le multitraitement, utilisez l'argument `num_proc` et spécifiez le nombre de processus à utiliser dans votre appel à `Dataset.map()` :

```py
slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False)


def slow_tokenize_function(examples):
    return slow_tokenizer(examples["review"], truncation=True)


tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_proc=8)
```

Vous pouvez faire des tests pour déterminer le nombre optimal de processus à utiliser. Dans notre cas 8 semble produire le meilleur gain de vitesse. Voici les chiffres que nous avons obtenus avec et sans multitraitement :

Options                       | *Tokenizer* rapide | *Tokenizer* lent
:----------------------------:|:----------------:|:---------------:
`batched=True`                | 10.8s            | 4min41s
`batched=False`               | 59.2s            | 5min3s
`batched=True`, `num_proc=8`  | 6.52s            | 41.3s
`batched=False`, `num_proc=8` | 9.49s            | 45.2s

Ce sont des résultats beaucoup plus raisonnables pour le *tokenizer* lent mais les performances du *tokenizer* rapide ont également été considérablement améliorées. Notez, cependant, que ce ne sera pas toujours le cas : pour des valeurs de `num_proc` autres que 8, nos tests ont montré qu'il était plus rapide d'utiliser `batched=True` sans cette option. En général, nous ne recommandons pas d'utiliser le multitraitement pour les *tokenizers* rapides avec `batched=True`.

<Tip>

Utiliser `num_proc` pour accélérer votre traitement est généralement une bonne idée tant que la fonction que vous utilisez n'effectue pas déjà une sorte de multitraitement.

</Tip>

Toutes ces fonctionnalités condensées en une seule méthode sont déjà assez étonnantes, mais il y a plus ! Avec `Dataset.map()` et `batched=True` vous pouvez modifier le nombre d'éléments dans votre jeu de données. Ceci est très utile dans de nombreuses situations où vous souhaitez créer plusieurs fonctionnalités d'entraînement à partir d'un exemple. Nous devrons le faire dans le cadre du prétraitement de plusieurs des tâches de traitement du langage naturel que nous entreprendrons dans le [chapitre 7](/course/fr/chapter7).

<Tip>

💡 En apprentissage automatique, un _exemple_ est généralement défini comme l'ensemble de _features_ que nous donnons au modèle. Dans certains contextes, ces caractéristiques seront l'ensemble des colonnes d'un `Dataset`, mais dans d'autres (comme ici et pour la réponse aux questions), plusieurs caractéristiques peuvent être extraites d'un seul exemple et appartenir à une seule colonne.

</Tip>

Voyons comment cela fonctionne ! Ici, nous allons tokeniser nos exemples et les tronquer à une longueur maximale de 128 mais nous demanderons au *tokenizer* de renvoyer *tous* les morceaux des textes au lieu du premier. Cela peut être fait avec `return_overflowing_tokens=True` :

```py
def tokenize_and_split(examples):
    return tokenizer(
        examples["review"],
        truncation=True,
        max_length=128,
        return_overflowing_tokens=True,
    )
```

Testons cela sur un exemple avant d'utiliser `Dataset.map()` sur le jeu de données :

```py
result = tokenize_and_split(drug_dataset["train"][0])
[len(inp) for inp in result["input_ids"]]
```

```python out
[128, 49]
```

Notre premier exemple du jeu d’entraînement est devenu deux caractéristiques car il a été segmenté à plus que le nombre maximum de *tokens* que nous avons spécifié : le premier de longueur 128 et le second de longueur 49. Faisons maintenant cela pour tous les éléments du jeu de données !

```py
tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True)
```

```python out
ArrowInvalid: Column 1 named condition expected length 1463 but got length 1000
```

Oh non ! Cela n'a pas fonctionné ! Pourquoi ? L'examen du message d'erreur nous donne un indice : il y a une incompatibilité dans les longueurs de l'une des colonnes. L'une étant de longueur 1 463 et l'autre de longueur 1 000. Si vous avez consulté la [documentation](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map) de `Dataset.map()`, vous vous souvenez peut-être qu'il s'agit du nombre d'échantillons passés à la fonction que nous mappons. Ici, ces 1 000 exemples ont donné 1 463 nouvelles caractéristiques, entraînant une erreur de forme.

Le problème est que nous essayons de mélanger deux jeux de données différents de tailles différentes : les colonnes `drug_dataset` auront un certain nombre d'exemples (les 1 000 dans notre erreur), mais le `tokenized_dataset` que nous construisons en aura plus (le 1 463 dans le message d'erreur). Cela ne fonctionne pas pour un `Dataset`, nous devons donc soit supprimer les colonnes de l'ancien jeu de données, soit leur donner la même taille que dans le nouveau jeu de données. Nous pouvons faire la première option avec l'argument `remove_columns` :

```py
tokenized_dataset = drug_dataset.map(
    tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names
)
```

Maintenant, cela fonctionne sans erreur. Nous pouvons vérifier que notre nouveau jeu de données contient beaucoup plus d'éléments que le jeu de données d'origine en comparant les longueurs :

```py
len(tokenized_dataset["train"]), len(drug_dataset["train"])
```

```python out
(206772, 138514)
```

Nous avons mentionné que nous pouvions également résoudre le problème de longueur non concordante en donnant aux anciennes colonnes la même taille que les nouvelles. Pour ce faire, nous avons besoin du champ `overflow_to_sample_mapping` que le *tokenizer* renvoie lorsque nous définissons `return_overflowing_tokens=True`. Il nous donne une correspondance entre un nouvel index de caractéristique et l'index de l'échantillon dont il est issu. Grâce à cela, nous pouvons associer chaque clé présente dans notre jeu de données d'origine à une liste de valeurs de la bonne taille en répétant les valeurs de chaque exemple autant de fois qu'il génère de nouvelles caractéristiques :

```py
def tokenize_and_split(examples):
    result = tokenizer(
        examples["review"],
        truncation=True,
        max_length=128,
        return_overflowing_tokens=True,
    )
    # Extraire la correspondance entre les nouveaux et les anciens indices
    sample_map = result.pop("overflow_to_sample_mapping")
    for key, values in examples.items():
        result[key] = [values[i] for i in sample_map]
    return result
```

Nous pouvons voir que cela fonctionne avec `Dataset.map()` sans que nous ayons besoin de supprimer les anciennes colonnes :

```py
tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True)
tokenized_dataset
```

```python out
DatasetDict({
    train: Dataset({
        features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'],
        num_rows: 206772
    })
    test: Dataset({
        features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'],
        num_rows: 68876
    })
})
```

Nous obtenons le même nombre de caractéristiques d'entraînement qu'auparavant, mais ici nous avons conservé tous les anciens champs. Si vous en avez besoin pour un post-traitement après l'application de votre modèle, vous pouvez utiliser cette approche.

Vous avez maintenant vu comment 🤗 *Datasets* peut être utilisé pour prétraiter un jeu de données de différentes manières. Bien que les fonctions de traitement de 🤗 *Datasets* couvrent la plupart de vos besoins, il peut arriver que vous deviez passer à Pandas pour accéder à des fonctionnalités plus puissantes, telles que `DataFrame.groupby()` ou des API de haut niveau pour la visualisation. Heureusement, 🤗 *Datasets* est conçu pour être interopérable avec des bibliothèques telles que Pandas, NumPy, PyTorch, TensorFlow et JAX. Voyons comment cela fonctionne.

## De `Dataset` à `DataFrame` et vice versa

<Youtube id="tfcY1067A5Q"/>

Pour permettre la conversion entre diverses bibliothèques tierces, 🤗 *Datasets* fournit une fonction `Dataset.set_format()`. Cette fonction ne modifie que le _format de sortie_ du jeu de données. Vous pouvez donc facilement passer à un autre format sans affecter le _format de données_ sous-jacent, qui est Apache Arrow. Le formatage se fait sur place. Pour démontrer, convertissons notre jeu de données vers Pandas :

```py
drug_dataset.set_format("pandas")
```

Maintenant, lorsque nous accédons aux éléments du jeu de données, nous obtenons un `pandas.DataFrame` au lieu d'un dictionnaire :

```py
drug_dataset["train"][:3]
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>patient_id</th>
      <th>drugName</th>
      <th>condition</th>
      <th>review</th>
      <th>rating</th>
      <th>date</th>
      <th>usefulCount</th>
      <th>review_length</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>95260</td>
      <td>Guanfacine</td>
      <td>adhd</td>
      <td>"My son is halfway through his fourth week of Intuniv..."</td>
      <td>8.0</td>
      <td>April 27, 2010</td>
      <td>192</td>
      <td>141</td>
    </tr>
    <tr>
      <th>1</th>
      <td>92703</td>
      <td>Lybrel</td>
      <td>birth control</td>
      <td>"I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects..."</td>
      <td>5.0</td>
      <td>December 14, 2009</td>
      <td>17</td>
      <td>134</td>
    </tr>
    <tr>
      <th>2</th>
      <td>138000</td>
      <td>Ortho Evra</td>
      <td>birth control</td>
      <td>"This is my first time using any form of birth control..."</td>
      <td>8.0</td>
      <td>November 3, 2015</td>
      <td>10</td>
      <td>89</td>
    </tr>
  </tbody>
</table>

Créons un `pandas.DataFrame` pour l'ensemble d'entraînement en sélectionnant tous les éléments de `drug_dataset["train"]` :

```py
train_df = drug_dataset["train"][:]
```

<Tip>

🚨 Sous le capot, `Dataset.set_format()` change le format de retour pour la méthode `__getitem__()`. Cela signifie que lorsque nous voulons créer un nouvel objet comme `train_df` à partir d'un `Dataset` au format `"pandas"`, nous devons découper tout le jeu de données pour obtenir un `pandas.DataFrame`. Vous pouvez vérifier par vous-même que le type de `drug_dataset["train"]` est `Dataset`, quel que soit le format de sortie.

</Tip>


De là, nous pouvons utiliser toutes les fonctionnalités Pandas que nous voulons. Par exemple, nous pouvons faire un chaînage sophistiqué pour calculer la distribution de classe parmi les entrées `condition` :

```py
frequencies = (
    train_df["condition"]
    .value_counts()
    .to_frame()
    .reset_index()
    .rename(columns={"index": "condition", "count": "frequency"})
)
frequencies.head()
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>condition</th>
      <th>frequency</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>birth control</td>
      <td>27655</td>
    </tr>
    <tr>
      <th>1</th>
      <td>depression</td>
      <td>8023</td>
    </tr>
    <tr>
      <th>2</th>
      <td>acne</td>
      <td>5209</td>
    </tr>
    <tr>
      <th>3</th>
      <td>anxiety</td>
      <td>4991</td>
    </tr>
    <tr>
      <th>4</th>
      <td>pain</td>
      <td>4744</td>
    </tr>
  </tbody>
</table>


Et une fois que nous avons terminé notre analyse Pandas, nous pouvons toujours créer un nouvel objet `Dataset` en utilisant la fonction `Dataset.from_pandas()` comme suit :


```py
from datasets import Dataset

freq_dataset = Dataset.from_pandas(frequencies)
freq_dataset
```

```python out
Dataset({
    features: ['condition', 'frequency'],
    num_rows: 819
})
```

<Tip>

✏️ **Essayez !** Calculez la note moyenne par médicament et stockez le résultat dans un nouveau jeu de données.

</Tip>

Ceci conclut notre visite des différentes techniques de prétraitement disponibles dans 🤗 *Datasets*. Pour compléter la section, créons un ensemble de validation pour préparer le jeu de données à l’entraînement d'un classifieur. Avant cela, nous allons réinitialiser le format de sortie de `drug_dataset` de `"pandas"` à `"arrow"` :

```python
drug_dataset.reset_format()
```

## Création d'un ensemble de validation

Bien que nous ayons un jeu de test que nous pourrions utiliser pour l'évaluation, il est recommandé de ne pas toucher au jeu de test et de créer un jeu de validation séparé pendant le développement. Une fois que vous êtes satisfait des performances de vos modèles sur l'ensemble de validation, vous pouvez effectuer une dernière vérification d'intégrité sur l'ensemble test. Ce processus permet d'atténuer le risque de surentraînement sur le jeu de test et de déployer un modèle qui échoue sur des données du monde réel.

🤗 *Datasets* fournit une fonction `Dataset.train_test_split()` basée sur la célèbre fonctionnalité de `scikit-learn`. Utilisons-la pour diviser notre ensemble d'entraînement `train` et `validation` (nous définissons l'argument `seed` pour la reproductibilité) :

```py
drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42)
# Renommer la division par défaut "test" en "validation"
drug_dataset_clean["validation"] = drug_dataset_clean.pop("test")
# Ajoutez le jeu "test" à notre `DatasetDict`
drug_dataset_clean["test"] = drug_dataset["test"]
drug_dataset_clean
```

```python out
DatasetDict({
    train: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'],
        num_rows: 110811
    })
    validation: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'],
        num_rows: 27703
    })
    test: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'],
        num_rows: 46108
    })
})
```

Génial, nous avons maintenant préparé un jeu de données prêt pour l'entraînement de certains modèles ! Dans la [section 5](/course/fr/chapter5/5), nous vous montrerons comment télécharger des jeux de données sur le *Hub*. Mais pour l'instant, terminons notre analyse en examinant quelques façons d'enregistrer des jeux de données sur votre ordinateur local. 

## Enregistrer un jeu de données

<Youtube id="blF9uxYcKHo"/>

Bien que 🤗 *Datasets* mette en cache chaque jeu de données téléchargé et les opérations qui y sont effectuées, il y a des moments où vous voudrez enregistrer un jeu de données sur le disque (par exemple, au cas où le cache serait supprimé). Comme indiqué dans le tableau ci-dessous, 🤗 *Datasets* fournit trois fonctions principales pour enregistrer votre jeu de données dans différents formats :

| Format de données |         Fonction         |
| :---------------: | :----------------------: |
|      Arrow        | `Dataset.save_to_disk()` |
|       CSV         |    `Dataset.to_csv()`    |
|      JSON         |   `Dataset.to_json()`    |

Par exemple, enregistrons notre jeu de données nettoyé au format Arrow :

```py
drug_dataset_clean.save_to_disk("drug-reviews")
```

Cela créera un répertoire avec la structure suivante :

```
drug-reviews/
├── dataset_dict.json
├── test
│   ├── dataset.arrow
│   ├── dataset_info.json
│   └── state.json
├── train
│   ├── dataset.arrow
│   ├── dataset_info.json
│   ├── indices.arrow
│   └── state.json
└── validation
    ├── dataset.arrow
    ├── dataset_info.json
    ├── indices.arrow
    └── state.json
```

où nous pouvons voir que chaque division est associée à sa propre table *dataset.arrow* et à certaines métadonnées dans *dataset_info.json* et *state.json*. Vous pouvez considérer le format Arrow comme un tableau sophistiqué de colonnes et de lignes optimisé pour la création d'applications hautes performances qui traitent et transportent de grands ensembles de données.

Une fois le jeu de données enregistré, nous pouvons le charger en utilisant la fonction `load_from_disk()` comme suit :

```py
from datasets import load_from_disk

drug_dataset_reloaded = load_from_disk("drug-reviews")
drug_dataset_reloaded
```

```python out
DatasetDict({
    train: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'],
        num_rows: 110811
    })
    validation: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'],
        num_rows: 27703
    })
    test: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'],
        num_rows: 46108
    })
})
```

Pour les formats CSV et JSON, nous devons stocker chaque fractionnement dans un fichier séparé. Pour ce faire, vous pouvez parcourir les clés et les valeurs de l'objet `DatasetDict` :

```py
for split, dataset in drug_dataset_clean.items():
    dataset.to_json(f"drug-reviews-{split}.jsonl")
```

Cela enregistre chaque fractionnement au [format JSON Lines](https://jsonlines.org), où chaque ligne du jeu de données est stockée sous la forme d'une seule ligne de JSON. Voici à quoi ressemble le premier exemple :

```py
!head -n 1 drug-reviews-train.jsonl
```

```python out
{"patient_id":141780,"drugName":"Escitalopram","condition":"depression","review":"\"I seemed to experience the regular side effects of LEXAPRO, insomnia, low sex drive, sleepiness during the day. I am taking it at night because my doctor said if it made me tired to take it at night. I assumed it would and started out taking it at night. Strange dreams, some pleasant. I was diagnosed with fibromyalgia. Seems to be helping with the pain. Have had anxiety and depression in my family, and have tried quite a few other medications that haven't worked. Only have been on it for two weeks but feel more positive in my mind, want to accomplish more in my life. Hopefully the side effects will dwindle away, worth it to stick with it from hearing others responses. Great medication.\"","rating":9.0,"date":"May 29, 2011","usefulCount":10,"review_length":125}
                                                                                  # Il semble que je ressente les effets secondaires habituels de LEXAPRO : insomnie, baisse de la libido, somnolence pendant la journée. Je le prends le soir parce que mon médecin m'a dit de le prendre le soir s'il me fatiguait. J'ai supposé que ce serait le cas et j'ai commencé à le prendre la nuit. Rêves étranges, certains agréables. On m'a diagnostiqué une fibromyalgie. Il semble que ce médicament aide à soulager la douleur. J'ai eu de l'anxiété et de la dépression dans ma famille, et j'ai essayé plusieurs autres médicaments qui n'ont pas fonctionné. Cela ne fait que deux semaines que je prends ce médicament, mais je me sens plus positif dans mon esprit et je veux accomplir davantage dans ma vie. J'espère que les effets secondaires vont s'estomper, cela vaut la peine de s'y tenir d'après les réponses des autres. C'est un excellent médicament.
```

Nous pouvons ensuite utiliser les techniques de [section 2](/course/fr/chapter5/2) pour charger les fichiers JSON comme suit :

```py
data_files = {
    "train": "drug-reviews-train.jsonl",
    "validation": "drug-reviews-validation.jsonl",
    "test": "drug-reviews-test.jsonl",
}
drug_dataset_reloaded = load_dataset("json", data_files=data_files)
```

Et c'est tout pour notre excursion dans la manipulation des données avec 🤗 *Datasets* ! Maintenant que nous disposons d'un ensemble de données nettoyé pour entraîner un modèle, voici quelques idées que vous pouvez essayer :

1. Utilisez les techniques du [chapitre 3](/course/fr/chapter3) pour entraîner un classifieur capable de prédire l'état du patient en fonction de l'examen du médicament.
2. Utilisez le pipeline `summarization` du [chapitre 1](/course/fr/chapter1) pour générer des résumés des révisions.

Ensuite, nous verrons comment 🤗 *Datasets* peut vous permettre de travailler avec d'énormes jeux de données sans faire exploser votre ordinateur portable !


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter5/3.mdx" />

### Recherche sémantique avec FAISS
https://huggingface.co/learn/course/fr/chapter5/6.md

# Recherche sémantique avec FAISS

{#if fw === 'pt'}
<CourseFloatingBanner chapter={5}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter5/section6_pt.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter5/section6_pt.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter5/section6_pt.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter5/section6_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={5}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter5/section6_tf.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter5/section6_tf.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter5/section6_tf.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter5/section6_tf.ipynb"},
]} />

{/if}

Dans [section 5](/course/fr/chapter5/5), nous avons créé un jeu de données de problèmes et de commentaires GitHub à partir du dépôt 🤗 *Datasets*. Dans cette section, nous utilisons ces informations pour créer un moteur de recherche qui peut nous aider à trouver des réponses à nos questions les plus urgentes sur la bibliothèque !

<Youtube id="OATCgQtNX2o"/>

## Utilisation des enchâssements pour la recherche sémantique

Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), les modèles de langage basés sur les *transformers* représentent chaque *token* dans une étendue de texte sous la forme d'un _enchâssement_. Il s'avère que l'on peut regrouper les enchâssements individuels pour créer une représentation vectorielle pour des phrases entières, des paragraphes ou (dans certains cas) des documents. Ces enchâssements peuvent ensuite être utilisés pour trouver des documents similaires dans le corpus en calculant la similarité du produit scalaire (ou une autre métrique de similarité) entre chaque enchâssement et en renvoyant les documents avec le plus grand chevauchement.

Dans cette section, nous utilisons les enchâssements pour développer un moteur de recherche sémantique. Ces moteurs de recherche offrent plusieurs avantages par rapport aux approches conventionnelles basées sur la correspondance des mots-clés dans une requête avec les documents.

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter5/semantic-search.svg" alt="Recherche sémantique."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter5/semantic-search-dark.svg" alt="Recherche sémantique."/>
</div>

## Chargement et préparation du jeu de données

La première chose que nous devons faire est de télécharger notre jeu de données de problèmes GitHub. Utilisons la bibliothèque 🤗 *Hub* pour résoudre l'URL où notre fichier est stocké sur le *Hub* d’Hugging Face :

```py
from huggingface_hub import hf_hub_url

data_files = hf_hub_url(
    repo_id="lewtun/github-issues",
    filename="datasets-issues-with-comments.jsonl",
    repo_type="dataset",
)
```

Avec l'URL stocké dans `data_files`, nous pouvons ensuite charger le jeu de données distant en utilisant la méthode introduite dans [section 2](/course/fr/chapter5/2) :

```py
from datasets import load_dataset

issues_dataset = load_dataset("json", data_files=data_files, split="train")
issues_dataset
```

```python out
Dataset({
    features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'],
    num_rows: 2855
})
```

Ici, nous avons spécifié l’échantillon `train` par défaut dans `load_dataset()`, de sorte que cela renvoie un `Dataset` au lieu d'un `DatasetDict`. La première chose à faire est de filtrer les *pull requests* car celles-ci ont tendance à être rarement utilisées pour répondre aux requêtes des utilisateurs et introduiront du bruit dans notre moteur de recherche. Comme cela devrait être familier maintenant, nous pouvons utiliser la fonction `Dataset.filter()` pour exclure ces lignes de notre jeu de données. Pendant que nous y sommes, filtrons également les lignes sans commentaires, car celles-ci ne fournissent aucune réponse aux requêtes des utilisateurs :

```py
issues_dataset = issues_dataset.filter(
    lambda x: (x["is_pull_request"] == False and len(x["comments"]) > 0)
)
issues_dataset
```

```python out
Dataset({
    features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'],
    num_rows: 771
})
```

Nous pouvons voir qu'il y a beaucoup de colonnes dans notre jeu de données, dont la plupart n'ont pas besoin de construire notre moteur de recherche. Du point de vue de la recherche, les colonnes les plus informatives sont `title`, `body` et `comments`, tandis que `html_url` nous fournit un lien vers le problème source. Utilisons la fonction `Dataset.remove_columns()` pour supprimer le reste :

```py
columns = issues_dataset.column_names
columns_to_keep = ["title", "body", "html_url", "comments"]
columns_to_remove = set(columns_to_keep).symmetric_difference(columns)
issues_dataset = issues_dataset.remove_columns(columns_to_remove)
issues_dataset
```

```python out
Dataset({
    features: ['html_url', 'title', 'comments', 'body'],
    num_rows: 771
})
```

Pour créer nos enchâssements, nous ajoutons à chaque commentaire le titre et le corps du problème, car ces champs contiennent des informations contextuelles utiles. Étant donné que notre colonne `comments` est actuellement une liste de commentaires pour chaque problème, nous devons « éclater » la colonne afin que chaque ligne se compose d'un *tuple* `(html_url, title, body, comment)`. Dans Pandas, nous pouvons le faire avec la fonction [`DataFrame.explode()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), qui crée une nouvelle ligne pour chaque élément dans une colonne de type liste, tout en répliquant toutes les autres valeurs de colonne. Pour voir cela en action, passons d'abord au format `DataFrame` de Pandas :

```py
issues_dataset.set_format("pandas")
df = issues_dataset[:]
```

Si nous inspectons la première ligne de ce `DataFrame`, nous pouvons voir qu'il y a quatre commentaires associés à ce problème :

```py
df["comments"][0].tolist()
```

```python out
['the bug code locate in ：\r\n    if data_args.task_name is not None:\r\n        # Downloading and loading a dataset from the hub.\r\n        datasets = load_dataset("glue", data_args.task_name, cache_dir=model_args.cache_dir)',
 'Hi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com\r\n\r\nNormally, it should work if you wait a little and then retry.\r\n\r\nCould you please confirm if the problem persists?',
 'cannot connect，even by Web browser，please check that  there is some  problems。',
 'I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...']
```

Lorsque nous décomposons `df`, nous nous attendons à obtenir une ligne pour chacun de ces commentaires. Vérifions si c'est le cas :

```py
comments_df = df.explode("comments", ignore_index=True)
comments_df.head(4)
```

<table border="1" class="dataframe" style="table-layout: fixed; word-wrap:break-word; width: 100%;">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>html_url</th>
      <th>title</th>
      <th>comments</th>
      <th>body</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>https://github.com/huggingface/datasets/issues/2787</td>
      <td>ConnectionError: Couldn't reach https://raw.githubusercontent.com</td>
      <td>the bug code locate in ：\r\n    if data_args.task_name is not None...</td>
      <td>Hello,\r\nI am trying to run run_glue.py and it gives me this error...</td>
    </tr>
    <tr>
      <th>1</th>
      <td>https://github.com/huggingface/datasets/issues/2787</td>
      <td>ConnectionError: Couldn't reach https://raw.githubusercontent.com</td>
      <td>Hi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com...</td>
      <td>Hello,\r\nI am trying to run run_glue.py and it gives me this error...</td>
    </tr>
    <tr>
      <th>2</th>
      <td>https://github.com/huggingface/datasets/issues/2787</td>
      <td>ConnectionError: Couldn't reach https://raw.githubusercontent.com</td>
      <td>cannot connect，even by Web browser，please check that  there is some  problems。</td>
      <td>Hello,\r\nI am trying to run run_glue.py and it gives me this error...</td>
    </tr>
    <tr>
      <th>3</th>
      <td>https://github.com/huggingface/datasets/issues/2787</td>
      <td>ConnectionError: Couldn't reach https://raw.githubusercontent.com</td>
      <td>I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...</td>
      <td>Hello,\r\nI am trying to run run_glue.py and it gives me this error...</td>
    </tr>
  </tbody>
</table>

Génial, nous pouvons voir que les lignes ont été répliquées, avec la colonne `comments` contenant les commentaires individuels ! Maintenant que nous en avons fini avec Pandas, nous pouvons rapidement revenir à un `Dataset` en chargeant le `DataFrame` en mémoire :

```py
from datasets import Dataset

comments_dataset = Dataset.from_pandas(comments_df)
comments_dataset
```

```python out
Dataset({
    features: ['html_url', 'title', 'comments', 'body'],
    num_rows: 2842
})
```

D'accord, cela nous a donné quelques milliers de commentaires avec lesquels travailler !


<Tip>

✏️ **Essayez !** Voyez si vous pouvez utiliser `Dataset.map()` pour exploser la colonne `comments` de `issues_dataset` _sans_ recourir à l'utilisation de Pandas. C'est un peu délicat. La section [« Batch mapping »](https://huggingface.co/docs/datasets/about_map_batch#batch-mapping) de la documentation 🤗 *Datasets* peut être utile pour cette tâche.

</Tip>

Maintenant que nous avons un commentaire par ligne, créons une nouvelle colonne `comments_length` contenant le nombre de mots par commentaire :

```py
comments_dataset = comments_dataset.map(
    lambda x: {"comment_length": len(x["comments"].split())}
)
```

Nous pouvons utiliser cette nouvelle colonne pour filtrer les commentaires courts incluant généralement des éléments tels que « cc @lewtun » ou « Merci ! » qui ne sont pas pertinents pour notre moteur de recherche. Il n'y a pas de nombre précis à sélectionner pour le filtre mais 15 mots semblent être un bon début :

```py
comments_dataset = comments_dataset.filter(lambda x: x["comment_length"] > 15)
comments_dataset
```

```python out
Dataset({
    features: ['html_url', 'title', 'comments', 'body', 'comment_length'],
    num_rows: 2098
})
```

Après avoir un peu nettoyé notre jeu de données, concaténons le titre, la description et les commentaires du problème dans une nouvelle colonne `text`. Comme d'habitude, nous allons écrire une fonction simple que nous pouvons passer à `Dataset.map()` :

```py
def concatenate_text(examples):
    return {
        "text": examples["title"]
        + " \n "
        + examples["body"]
        + " \n "
        + examples["comments"]
    }


comments_dataset = comments_dataset.map(concatenate_text)
```

Nous sommes enfin prêts à créer des enchâssements ! Jetons un coup d'œil.

## Création d’enchâssements pour les textes

Nous avons vu dans [chapitre 2](/course/fr/chapter2) que nous pouvons obtenir des enchâssements de *tokens* en utilisant la classe `AutoModel`. Tout ce que nous avons à faire est de choisir un *checkpoint* approprié à partir duquel charger le modèle. Heureusement, il existe une bibliothèque appelée `sentence-transformers` dédiée à la création d’enchâssements. Comme décrit dans la [documentation de la bibliothèque](https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search), notre cas d'utilisation est un exemple de _recherche sémantique asymétrique_. En effet, nous avons une requête courte dont nous aimerions trouver la réponse dans un document plus long, par exemple un commentaire à un problème. Le [tableau de présentation des modèles](https://www.sbert.net/docs/pretrained_models.html#model-overview) de la documentation indique que le *checkpoint* `multi-qa-mpnet-base-dot-v1` a les meilleures performances pour la recherche sémantique. Utilisons donc le pour notre application. Nous allons également charger le *tokenizer* en utilisant le même *checkpoint* :

{#if fw === 'pt'}

```py
from transformers import AutoTokenizer, AutoModel

model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1"
tokenizer = AutoTokenizer.from_pretrained(model_ckpt)
model = AutoModel.from_pretrained(model_ckpt)
```

Pour accélérer le processus, il est utile de placer le modèle et les entrées sur un périphérique GPU, alors faisons-le maintenant :

```py
import torch

device = torch.device("cuda")
model.to(device)
```

{:else}

```py
from transformers import AutoTokenizer, TFAutoModel

model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1"
tokenizer = AutoTokenizer.from_pretrained(model_ckpt)
model = TFAutoModel.from_pretrained(model_ckpt, from_pt=True)
```

Notez que nous avons défini `from_pt=True` comme argument de la méthode `from_pretrained()`. C'est parce que le point de contrôle `multi-qa-mpnet-base-dot-v1` n'a que des poids PyTorch. Donc définir `from_pt=True` converti automatiquement au format TensorFlow pour nous. Comme vous pouvez le voir, il est très simple de passer d'un *framework* à l'autre dans 🤗 *Transformers* !

{/if}

Comme nous l'avons mentionné précédemment, nous aimerions représenter chaque entrée dans notre corpus de problèmes GitHub comme un vecteur unique. Nous devons donc regrouper ou faire la moyenne de nos enchâssements de *tokens* d'une manière ou d'une autre. Une approche populaire consiste à effectuer un *regroupement CLS* sur les sorties de notre modèle, où nous collectons simplement le dernier état caché pour le *token* spécial `[CLS]`. La fonction suivante fait ça pour nous :

```py
def cls_pooling(model_output):
    return model_output.last_hidden_state[:, 0]
```

Ensuite, nous allons créer une fonction utile qui va tokeniser une liste de documents, placer les tenseurs dans le GPU, les donner au modèle et enfin appliquer le regroupement CLS aux sorties :

{#if fw === 'pt'}

```py
def get_embeddings(text_list):
    encoded_input = tokenizer(
        text_list, padding=True, truncation=True, return_tensors="pt"
    )
    encoded_input = {k: v.to(device) for k, v in encoded_input.items()}
    model_output = model(**encoded_input)
    return cls_pooling(model_output)
```

Nous pouvons tester le fonctionnement de la fonction en lui donnant la première entrée textuelle de notre corpus et en inspectant la forme de sortie :

```py
embedding = get_embeddings(comments_dataset["text"][0])
embedding.shape
```

```python out
torch.Size([1, 768])
```

Super ! Nous avons converti la première entrée de notre corpus en un vecteur à 768 dimensions. Nous pouvons utiliser `Dataset.map()` pour appliquer notre fonction `get_embeddings()` à chaque ligne de notre corpus. Créons donc une nouvelle colonne `embeddings` comme suit :

```py
embeddings_dataset = comments_dataset.map(
    lambda x: {"embeddings": get_embeddings(x["text"]).detach().cpu().numpy()[0]}
)
```

{:else}

```py
def get_embeddings(text_list):
    encoded_input = tokenizer(
        text_list, padding=True, truncation=True, return_tensors="tf"
    )
    encoded_input = {k: v for k, v in encoded_input.items()}
    model_output = model(**encoded_input)
    return cls_pooling(model_output)
```

Nous pouvons tester le fonctionnement de la fonction en lui donnant la première entrée textuelle de notre corpus et en inspectant la forme de sortie :

```py
embedding = get_embeddings(comments_dataset["text"][0])
embedding.shape
```

```python out
TensorShape([1, 768])
```

Super ! Nous avons converti la première entrée de notre corpus en un vecteur à 768 dimensions. Nous pouvons utiliser `Dataset.map()` pour appliquer notre fonction `get_embeddings()` à chaque ligne de notre corpus. Créons donc une nouvelle colonne `embeddings` comme suit :

```py
embeddings_dataset = comments_dataset.map(
    lambda x: {"embeddings": get_embeddings(x["text"]).numpy()[0]}
)
```

{/if}


Notez que nous avons converti les enchâssements en tableaux NumPy. C'est parce que 🤗 *Datasets* nécessite ce format lorsque nous essayons de les indexer avec FAISS, ce que nous ferons ensuite.

## Utilisation de FAISS pour une recherche de similarité efficace

Maintenant que nous avons un jeu de données d'incorporations, nous avons besoin d'un moyen de les rechercher. Pour ce faire, nous utiliserons une structure de données spéciale dans 🤗 *Datasets* appelée _FAISS index_. [FAISS](https://faiss.ai/) (abréviation de *Facebook AI Similarity Search*) est une bibliothèque qui fournit des algorithmes efficaces pour rechercher et regrouper rapidement des vecteurs d'intégration.

L'idée de base derrière FAISS est de créer une structure de données spéciale appelée un _index_ qui permet de trouver quels plongements sont similaires à un plongement d'entrée. Créer un index FAISS dans 🤗 *Datasets* est simple -- nous utilisons la fonction `Dataset.add_faiss_index()` et spécifions quelle colonne de notre jeu de données nous aimerions indexer :

```py
embeddings_dataset.add_faiss_index(column="embeddings")
```

Nous pouvons maintenant effectuer des requêtes sur cet index en effectuant une recherche des voisins les plus proches avec la fonction `Dataset.get_nearest_examples()`. Testons cela en enchâssant d'abord une question comme suit :

{#if fw === 'pt'}

```py
question = "How can I load a dataset offline?"
question_embedding = get_embeddings([question]).cpu().detach().numpy()
question_embedding.shape
```

```python out
torch.Size([1, 768])
```

{:else}

```py
question = "How can I load a dataset offline?"
question_embedding = get_embeddings([question]).numpy()
question_embedding.shape
```

```python out
(1, 768)
```

{/if}

Tout comme avec les documents, nous avons maintenant un vecteur de 768 dimensions représentant la requête. Nous pouvons le comparer à l’ensemble du corpus pour trouver les enchâssements les plus similaires :

```py
scores, samples = embeddings_dataset.get_nearest_examples(
    "embeddings", question_embedding, k=5
)
```

La fonction `Dataset.get_nearest_examples()` renvoie un *tuple* de scores qui classent le chevauchement entre la requête et le document, et un jeu correspondant d'échantillons (ici, les 5 meilleures correspondances). Collectons-les dans un `pandas.DataFrame` afin de pouvoir les trier facilement :

```py
import pandas as pd

samples_df = pd.DataFrame.from_dict(samples)
samples_df["scores"] = scores
samples_df.sort_values("scores", ascending=False, inplace=True)
```

Nous pouvons maintenant parcourir les premières lignes pour voir dans quelle mesure notre requête correspond aux commentaires disponibles :

```py
for _, row in samples_df.iterrows():
    print(f"COMMENT: {row.comments}")
    print(f"SCORE: {row.scores}")
    print(f"TITLE: {row.title}")
    print(f"URL: {row.html_url}")
    print("=" * 50)
    print()
```

```python out
"""
COMMENT: Requiring online connection is a deal breaker in some cases unfortunately so it'd be great if offline mode is added similar to how `transformers` loads models offline fine.

@mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like?
SCORE: 25.505046844482422
TITLE: Discussion using datasets in offline mode
URL: https://github.com/huggingface/datasets/issues/824
==================================================

COMMENT: The local dataset builders (csv, text , json and pandas) are now part of the `datasets` package since #1726 :)
You can now use them offline
\`\`\`python
datasets = load_dataset("text", data_files=data_files)
\`\`\`

We'll do a new release soon
SCORE: 24.555509567260742
TITLE: Discussion using datasets in offline mode
URL: https://github.com/huggingface/datasets/issues/824
==================================================

COMMENT: I opened a PR that allows to reload modules that have already been loaded once even if there's no internet.

Let me know if you know other ways that can make the offline mode experience better. I'd be happy to add them :)

I already note the "freeze" modules option, to prevent local modules updates. It would be a cool feature.

----------

> @mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like?

Indeed `load_dataset` allows to load remote dataset script (squad, glue, etc.) but also you own local ones.
For example if you have a dataset script at `./my_dataset/my_dataset.py` then you can do
\`\`\`python
load_dataset("./my_dataset")
\`\`\`
and the dataset script will generate your dataset once and for all.

----------

About I'm looking into having `csv`, `json`, `text`, `pandas` dataset builders already included in the `datasets` package, so that they are available offline by default, as opposed to the other datasets that require the script to be downloaded.
cf #1724
SCORE: 24.14896583557129
TITLE: Discussion using datasets in offline mode
URL: https://github.com/huggingface/datasets/issues/824
==================================================

COMMENT: > here is my way to load a dataset offline, but it **requires** an online machine
>
> 1. (online machine)
>
> ```
>
> import datasets
>
> data = datasets.load_dataset(...)
>
> data.save_to_disk(/YOUR/DATASET/DIR)
>
> ```
>
> 2. copy the dir from online to the offline machine
>
> 3. (offline machine)
>
> ```
>
> import datasets
>
> data = datasets.load_from_disk(/SAVED/DATA/DIR)
>
> ```
>
>
>
> HTH.


SCORE: 22.893993377685547
TITLE: Discussion using datasets in offline mode
URL: https://github.com/huggingface/datasets/issues/824
==================================================

COMMENT: here is my way to load a dataset offline, but it **requires** an online machine
1. (online machine)
\`\`\`
import datasets
data = datasets.load_dataset(...)
data.save_to_disk(/YOUR/DATASET/DIR)
\`\`\`
2. copy the dir from online to the offline machine
3. (offline machine)
\`\`\`
import datasets
data = datasets.load_from_disk(/SAVED/DATA/DIR)
\`\`\`

HTH.
SCORE: 22.406635284423828
TITLE: Discussion using datasets in offline mode
URL: https://github.com/huggingface/datasets/issues/824
==================================================
"""
```

Pas mal ! Notre deuxième résultat semble correspondre à la requête.

<Tip>

✏️ **Essayez !** Créez votre propre requête et voyez si vous pouvez trouver une réponse dans les documents récupérés. Vous devrez peut-être augmenter le paramètre `k` dans `Dataset.get_nearest_examples()` pour élargir la recherche.

</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter5/6.mdx" />

### Quiz de fin de chapitre
https://huggingface.co/learn/course/fr/chapter5/8.md

# Quiz de fin de chapitre

<CourseFloatingBanner
    chapter={5}
    classNames="absolute z-10 right-0 top-0"
/>

Ce chapitre a couvert beaucoup de terrain ! Ne vous inquiétez pas si vous n'avez pas saisi tous les détails, les chapitres suivants vous aideront à comprendre comment les choses fonctionnent sous le capot.

Avant de poursuivre, testons ce que vous avez appris dans ce chapitre.

### 1. La fonction `load_dataset()` dans 🤗 *Datasets* vous permet de charger un jeu de données depuis lequel des emplacements suivants ?

<Question
	choices={[
		{
			text: "Localement, par exemple depuis son ordinateur portable.",
			explain: "Vous pouvez passer les chemins des fichiers locaux à l'argument <code>data_files</code> de <code>load_dataset()</code> pour charger les jeux de données locaux.",
			correct: true
		},
		{
			text: "Le <i>Hub</i> d’Hugging Face.",
			explain: "Vous pouvez charger des jeux de données sur le <i>Hub</i>  en fournissant l'ID du jeu de données. Par exemple : <code>load_dataset('emotion')</code>.",
			correct: true
		},
		{
			text: "Un serveur distant.",
			explain: "Vous pouvez passer des URLs à l'argument <code>data_files</code> de <code>load_dataset()</code> pour charger des fichiers distants.",
			correct: true
		},
	]}
/>

### 2. Supposons que vous chargiez l'une des tâches du jeu de données GLUE comme suit :

```py
from datasets import load_dataset

dataset = load_dataset("glue", "mrpc", split="train")
```

Laquelle des commandes suivantes produira un échantillon aléatoire de 50 éléments à partir de `dataset` ?

<Question
	choices={[
		{
			text: "<code>dataset.sample(50)</code>",
			explain: "Il n'y a pas de méthode <code>Dataset.sample()</code>."
		},
		{
			text: "<code>dataset.shuffle().select(range(50))</code>",
			explain: "Comme vous l'avez vu dans ce chapitre, vous mélangez d'abord le jeu de données puis sélectionnez les échantillons à partir de celui-ci.",
			correct: true
		},
		{
			text: "<code>dataset.select(range(50)).shuffle()</code>",
			explain: "Bien que le code s'exécute, il ne mélange que les 50 premiers éléments du jeu de données."
		}
	]}
/>

### 3. Supposons que vous disposiez d'un jeu de données sur les animaux domestiques appelé `pets_dataset` qui comporte une colonne `name` indiquant le nom de chaque animal. Parmi les approches suivantes, laquelle vous permettrait de filtrer le jeu de données pour tous les animaux dont le nom commence par la lettre « L » ?

<Question
	choices={[
		{
			text: "<code>pets_dataset.filter(lambda x : x['name'].startswith('L'))</code>",
			explain: "L'utilisation d'une fonction Python lambda pour ces filtres rapides est une excellente idée. Pouvez-vous penser à une autre solution ?",
			correct: true
		},
		{
			text: "<code>pets_dataset.filter(lambda x['name'].startswith('L'))</code>",
			explain: "Une fonction lambda prend la forme générale <code>lambda *arguments* : *expression*</code>, vous devez donc fournir des arguments dans ce cas."
		},
		{
			text: "Créer une fonction comme <code>def filter_names(x): return x['name'].startswith('L')</code> et exécuter <code>pets_dataset.filter(filter_names)</code>.",
			explain: "Tout comme avec <code>Dataset.map()</code>, vous pouvez passer des fonctions explicites à <code>Dataset.filter()</code>. Ceci est utile lorsque vous avez une logique complexe qui ne convient pas à une fonction lambda courte. Parmi les autres solutions, laquelle fonctionnerait ?",
			correct: true
		}
	]}
/>

### 4. Qu'est-ce que le *memory mapping* ?

<Question
	choices={[
		{
			text: "Un <i>mapping</i> entre la RAM CPU et GPU.",
			explain: "Ce n'est pas ça, réessayez !",
		},
		{
			text: "Un <i>mapping</i> entre la RAM et le stockage du système de fichiers.",
			explain: "🤗 <i>Datasets</i> traite chaque jeu de données comme un fichier mappé en mémoire. Cela permet à la bibliothèque d'accéder et d'opérer sur des éléments du jeu de données sans avoir à le charger complètement en mémoire.",
			correct: true
		},
		{
			text: "Un <i>mapping</i> entre deux fichiers dans le cache 🤗 <i>Datasets</i>.",
			explain: "Ce n'est pas ça, réessayez !"
		}
	]}
/>

### 5. Parmi les éléments suivants, lesquels sont les principaux avantages du *memory mapping* ?

<Question
	choices={[
		{
			text: "L'accès aux fichiers mappés en mémoire est plus rapide que la lecture ou l'écriture sur le disque.",
			explain: "Cela permet à 🤗 <i>Datasets</i> d'être extrêmement rapide. Ce n'est cependant pas le seul avantage.",
			correct: true
		},
		{
			text: "Les applications peuvent accéder à des segments de données dans un fichier extrêmement volumineux sans avoir à lire tout le fichier dans la RAM au préalable.",
			explain: "Cela permet à 🤗 <i>Datasets</i> de charger des jeux de données de plusieurs Go sur votre ordinateur portable sans faire exploser votre CPU. Quel autre avantage cette technique offre-t-elle ?",
			correct: true
		},
		{
			text: "Cela consomme moins d'énergie, donc votre batterie dure plus longtemps.",
			explain: "Ce n'est pas ça, réessayez !"
		}
	]}
/>

### 6. Pourquoi le code suivant échoue-t-il ?

```py
from datasets import load_dataset

dataset = load_dataset("allocine", streaming=True, split="train")
dataset[0]
```

<Question
	choices={[
		{
			text: "Il essaie de streamer un jeu de données trop volumineux pour tenir dans la RAM.",
			explain: "Les jeux de données streamés sont décompressés à la volée et vous pouvez en traiter de données de la taille d'un To avec très peu de RAM !",
		},
		{
			text: "Il essaie d'accéder à un <code>IterableDataset</code>.",
			explain: "Un <code>IterableDataset</code> est un générateur, pas un conteneur. Vous devez donc accéder à ses éléments en utilisant <code>next(iter(dataset))</code>.",
			correct: true
		},
		{
			text: "Le jeu de données <code>allocine</code> n'a pas d’échantillon <code>train</code>.",
			explain: "Consultez le jeu de données <code>allocine</code> sur le <i>Hub</i> (https://huggingface.co/datasets/allocine) pour voir quels échantillons il contient."
		}
	]}
/>

### 7. Parmi les avantages suivants, lesquels sont les principaux pour la création d'une fiche pour les jeux de données ?

<Question
	choices={[
		{
			text: "Elle fournit des informations sur l'utilisation prévue ainsi que les tâches prises en charge de jeu de données. Le but est que d'autres membres de la communauté puissent prendre une décision éclairée quant à son utilisation.",
			explain: "Les jeux de données non documentés peuvent être utilisés pour entraîner des modèles pouvant ne pas refléter les intentions des créateurs du jeu de données ou peuvent produire des modèles dont le statut juridique est trouble s'ils sont entraînés sur des données qui violent la confidentialité ou les restrictions de licence. Cependant ce n'est pas le seul avantage !",
			correct : true
		},
		{
			text: "Elle permet d'attirer l'attention sur les biais présents dans un corpus.",
			explain: "Presque tous les jeux de données présentent une forme de biais, ce qui peut avoir des conséquences négatives en aval. En être conscient aide les constructeurs de modèles à comprendre comment traiter les biais inhérents. À quoi d'autre les fiches sont-elles utiles ?",
			correct : true
		},
		{
			text: "Cela augmente les chances que d'autres membres de la communauté utilisent mon jeu de données.",
			explain: "Une carte de jeu de données bien rédigée aura tendance à conduire à une utilisation accrue de votre jeu de données. Quels autres avantages offre-t-elle ?",
			correct: true
		},
	]}
/>


### 8. Qu'est-ce que la recherche sémantique ?

<Question
	choices={[
		{
			text: "Un moyen de rechercher des correspondances exactes entre les mots d'une requête et les documents d'un corpus.",
			explain: "Ce type de recherche est appelé <i>recherche lexicale</i> et c'est ce que vous voyez généralement avec les moteurs de recherche traditionnels."
		},
		{
			text: "Un moyen de rechercher des documents correspondants en comprenant la signification contextuelle d'une requête.",
			explain: "La recherche sémantique utilise des vecteurs d’enchâssement pour représenter les requêtes et les documents. Elle utilise ensuite une métrique de similarité pour mesurer la quantité de chevauchement entre eux. Comment la décrire autrement ?",
			correct: true
		},
		{
			text: "Un moyen d'améliorer la précision de la recherche.",
			explain: "Les moteurs de recherche sémantique peuvent capturer l'intention d'une requête bien mieux que la correspondance des mots clés et récupèrent généralement les documents avec une plus grande précision. Mais ce n'est pas la seule bonne réponse. Qu'est-ce que la recherche sémantique apporte d'autre ?",
			correct: true
		}
	]}
/>

### 9. Pour la recherche sémantique asymétrique, vous avez généralement :

<Question
	choices={[
		{
			text: "Une requête courte et un paragraphe plus long qui répond à la requête.",
			explain: "C'est ça !",
			correct : true
		},
		{
			text: "Requêtes et paragraphes qui sont à peu près de la même longueur.",
			explain: "Il s'agit en fait d'un exemple de recherche sémantique symétrique. Réessayez !"
		},
		{
			text: "Une longue requête et un paragraphe plus court qui répond à la requête.",
			explain: "Réessayez !"
		}
	]}
/>

### 10. Puis-je utiliser 🤗 *Datasets* pour charger des données à utiliser dans d'autres domaines, comme le traitement de la parole ?

<Question
	choices={[
		{
			text: "Non.",
			explain: "🤗 <i>Datasets</i> prend actuellement en charge les données tabulaires, l'audio et la vision par ordinateur. Consultez le jeu de données <a href='https://huggingface.co/datasets/mnist'>MNIST</a> sur le <i>Hub</i> pour un exemple de vision par ordinateur."
		},
		{
			text: "Oui.",
			explain: "Découvrez les développements passionnants concernant la parole et la vision dans la bibliothèque 🤗 <i>Transformers</i> pour voir comment 🤗 <i>Datasets</i> est utilisé dans ces domaines.",
			correct : true
		},
	]}
/>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter5/8.mdx" />

### Données massives ? 🤗 <i>Datasets</i> à la rescousse !
https://huggingface.co/learn/course/fr/chapter5/4.md

# Données massives ? 🤗 <i>Datasets</i> à la rescousse !


<CourseFloatingBanner chapter={5}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter5/section4.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter5/section4.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter5/section4.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter5/section4.ipynb"},
]} />

De nos jours, il n'est pas rare de travailler avec des jeux de données de plusieurs gigaoctets surtout si vous envisagez de pré-entraîner un *transformer* comme BERT ou GPT-2 à partir de zéro. Dans ces cas, même _charger_ les données peut être un défi. Par exemple, le corpus WebText utilisé pour pré-entraîner GPT-2 se compose de plus de 8 millions de documents et de 40 Go de texte. Le charger dans la RAM de votre ordinateur portable est susceptible de lui donner une crise cardiaque !

Heureusement, 🤗 *Datasets* a été conçu pour surmonter ces limitations. Il vous libère des problèmes de gestion de la mémoire en traitant les jeux de données comme des fichiers _mappés en mémoire_, ainsi que des limites du disque dur en faisant du _streaming_ sur les entrées dans un corpus.

<Youtube id="JwISwTCPPWo"/>

Dans cette section, nous allons explorer ces fonctionnalités de 🤗 *Datasets* avec un énorme corpus de 825 Go connu sous le nom de [*The Pile*](https://pile.eleuther.ai). Commençons !

## Qu'est-ce que <i>The Pile</i> ?

*The Pile* est un corpus de texte en anglais créé par [EleutherAI](https://www.eleuther.ai) pour entraîner des modèles de langage à grande échelle. Il comprend une gamme variée de jeux de données, couvrant des articles scientifiques, des référentiels de code GitHub et du texte Web filtré. Le corpus d’entraînement est disponible en [morceaux de 14 Go](https://the-eye.eu/public/AI/pile/) et vous pouvez aussi télécharger plusieurs des [composants individuels]( https://the-eye.eu/public/AI/pile_preliminary_components/). Commençons par jeter un coup d'œil au jeu de données *PubMed Abstracts*, qui est un corpus de résumés de 15 millions de publications biomédicales sur [PubMed](https://pubmed.ncbi.nlm.nih.gov/). Le jeu de données est au [format JSON Lines](https://jsonlines.org) et est compressé à l'aide de la bibliothèque `zstandard`. Nous devons donc d'abord installer cette bibliothèque :

```py
!pip install zstandard
```

Ensuite, nous pouvons charger le jeu de données en utilisant la méthode pour les fichiers distants que nous avons apprise dans [section 2](/course/fr/chapter5/2) :

```py
from datasets import load_dataset

# Cela prend quelques minutes à exécuter, alors allez prendre un thé ou un café en attendant :)
data_files = "https://the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst"
pubmed_dataset = load_dataset("json", data_files=data_files, split="train")
pubmed_dataset
```

```python out
Dataset({
    features: ['meta', 'text'],
    num_rows: 15518009
})
```

Nous pouvons voir qu'il y a 15 518 009 lignes et 2 colonnes dans notre jeu de données. C'est beaucoup !

<Tip>

✎ Par défaut, 🤗 *Datasets* décompresse les fichiers nécessaires pour charger un jeu de données. Si vous souhaitez conserver de l'espace sur le disque dur, vous pouvez passer `DownloadConfig(delete_extracted=True)` à l'argument `download_config` de `load_dataset()`. Voir la [documentation](https://huggingface.co/docs/datasets/package_reference/builder_classes#datasets.DownloadConfig) pour plus de détails.

</Tip>

Inspectons le contenu du premier exemple :

```py
pubmed_dataset[0]
```

```python out
{'meta': {'pmid': 11409574, 'language': 'eng'},
 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection.\nTo determine the prevalence of hypoxaemia in children aged under 5 years suffering acute lower respiratory infections (ALRI), the risk factors for hypoxaemia in children under 5 years of age with ALRI, and the association of hypoxaemia with an increased risk of dying in children of the same age ...'
# Épidémiologie de l'hypoxémie chez les enfants souffrant d'une infection aiguë des voies respiratoires inférieures. Déterminer la prévalence de l'hypoxémie chez les enfants de moins de 5 ans souffrant d'une infection aiguë des voies respiratoires inférieures (IAVI), les facteurs de risque de l'hypoxémie chez les enfants de moins de 5 ans souffrant d'une IAVI, et l'association de l'hypoxémie à un risque accru de décès chez les enfants du même âge ...
}
```

Cela ressemble au résumé d'un article médical. Voyons maintenant combien de RAM nous avons utilisé pour charger le jeu de données !

## La magie du <i>memory mapping</i>

Un moyen simple de mesurer l'utilisation de la mémoire dans Python consiste à utiliser la bibliothèque [`psutil`](https://psutil.readthedocs.io/en/latest/) qui peut être installée avec `pip` comme suit :

```python
!pip install psutil
```

Elle fournit une classe `Process` qui permet de vérifier l'utilisation de la mémoire du processus en cours :

```py
import psutil

# Process.memory_info est exprimé en octets, donc convertir en mégaoctets
print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB")
```

```python out
RAM used: 5678.33 MB
```

Ici, l'attribut `rss` fait référence à la _taille de l'ensemble résident_, qui est la fraction de mémoire qu'un processus occupe dans la RAM. Cette mesure inclut également la mémoire utilisée par l'interpréteur Python et les bibliothèques que nous avons chargées, de sorte que la quantité réelle de mémoire utilisée pour charger le jeu de données est un peu plus petite. À titre de comparaison, voyons la taille du jeu de données sur le disque en utilisant l'attribut `dataset_size`. Comme le résultat est exprimé en octets comme précédemment, nous devons le convertir manuellement en gigaoctets :

```py
print(f"Number of files in dataset : {pubmed_dataset.dataset_size}")
size_gb = pubmed_dataset.dataset_size / (1024**3)
print(f"Dataset size (cache file) : {size_gb:.2f} GB")
```

```python out
Number of files in dataset : 20979437051
Dataset size (cache file) : 19.54 GB
```

Malgré sa taille de près de 20 Go, nous pouvons charger et accéder au jeu de données avec beaucoup moins de RAM !

<Tip>

✏️ **Essayez !** Choisissez l'un des [sous-ensembles](https://the-eye.eu/public/AI/pile_preliminary_components/) de *The Pile* qui est plus grand que la RAM de votre ordinateur portable ou de bureau. Chargez-le avec 🤗 *Datasets* et mesurez la quantité de RAM utilisée. Notez que pour obtenir une mesure précise, vous devrez le faire dans un nouveau processus. Vous pouvez trouver les tailles décompressées de chaque sous-ensemble dans le tableau 1 du papier de [*The Pile*](https://arxiv.org/abs/2101.00027).

</Tip>

Si vous êtes familier avec Pandas, ce résultat pourrait surprendre en raison de la célèbre [règle d'or](https://wesmckinney.com/blog/apache-arrow-pandas-internals/) de Wes Kinney selon laquelle vous avez généralement besoin de 5 à 10 fois plus de RAM que la taille de votre jeu de données. Alors, comment 🤗 *Datasets* résout-il ce problème de gestion de la mémoire ? 🤗 *Datasets* traite chaque jeu de données comme un [fichier mappé en mémoire](https://en.wikipedia.org/wiki/Memory-mapped_file). Cela fournit un mappage entre la RAM et le stockage du système de fichiers permettant à la bibliothèque d'accéder et d'opérer sur des éléments du jeu de données sans avoir besoin de le charger entièrement en mémoire.

Les fichiers mappés en mémoire peuvent également être partagés entre plusieurs processus ce qui permet de paralléliser des méthodes telles que `Dataset.map()` sans avoir à déplacer ou copier le jeu de données. Sous le capot, ces capacités sont toutes réalisées par le format de mémoire [Apache Arrow](https://arrow.apache.org) et [`pyarrow`](https://arrow.apache.org/docs/python/index.html), qui accélèrent le chargement et le traitement des données. (Pour plus de détails sur Apache Arrow et les comparaisons avec Pandas, consultez [l'article de blog de Dejan Simic](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a)). Pour voir ceci en action, effectuons un petit test de vitesse en itérant sur tous les éléments du jeu de données *PubMed Abstracts* :

```py
import timeit

code_snippet = """batch_size = 1000

for idx in range(0, len(pubmed_dataset), batch_size):
    _ = pubmed_dataset[idx:idx + batch_size]
"""

time = timeit.timeit(stmt=code_snippet, number=1, globals=globals())
print(
    f"Iterated over {len(pubmed_dataset)} examples (about {size_gb:.1f} GB) in "
    f"{time:.1f}s, i.e. {size_gb/time:.3f} GB/s"
)
```

```python out
'Iterated over 15518009 examples (about 19.5 GB) in 64.2s, i.e. 0.304 GB/s'
```

Ici, nous avons utilisé le module `timeit` de Python pour mesurer le temps d'exécution pris par `code_snippet`. Vous pourrez généralement itérer sur un jeu de données à une vitesse de quelques dixièmes de Go/s à plusieurs Go/s. Cela fonctionne très bien pour la grande majorité des applications, mais vous devrez parfois travailler avec un jeu de données trop volumineux pour être même stocké sur le disque dur de votre ordinateur portable. Par exemple, si nous essayions de télécharger *The Pile* dans son intégralité, nous aurions besoin de 825 Go d'espace disque libre ! Pour gérer ces cas, 🤗 *Datasets* fournit une fonctionnalité de streaming qui nous permet de télécharger et d'accéder aux éléments à la volée, sans avoir besoin de télécharger l'intégralité du jeu de données. Voyons comment cela fonctionne.

<Tip>

💡 Dans les *notebooks* Jupyter, vous pouvez également chronométrer les cellules à l'aide de la fonction magique [`%%timeit`](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit).

</Tip>

## Jeux de données en continu

Pour activer le streaming du jeu de données, il vous suffit de passer l'argument `streaming=True` à la fonction `load_dataset()`. Par exemple, chargeons à nouveau le jeu de données *PubMed Abstracts* mais en mode streaming :

```py
pubmed_dataset_streamed = load_dataset(
    "json", data_files=data_files, split="train", streaming=True
)
```

Au lieu du familier `Dataset` que nous avons rencontré ailleurs dans ce chapitre, l'objet retourné avec `streaming=True` est un `IterableDataset`. Comme son nom l'indique, pour accéder aux éléments d'un `IterableDataset`, nous devons parcourir celui-ci. Nous pouvons accéder au premier élément de notre jeu de données diffusé comme suit :


```py
next(iter(pubmed_dataset_streamed))
```

```python out
{'meta': {'pmid': 11409574, 'language': 'eng'},
 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection.\nTo determine the prevalence of hypoxaemia in children aged under 5 years suffering acute lower respiratory infections (ALRI), the risk factors for hypoxaemia in children under 5 years of age with ALRI, and the association of hypoxaemia with an increased risk of dying in children of the same age ...'}
```

Les éléments d'un jeu de données diffusé en continu peuvent être traités à la volée à l'aide de `IterableDataset.map()`, ce qui est utile pendant l’entraînement si vous avez besoin de tokeniser les entrées. Le processus est exactement le même que celui que nous avons utilisé pour tokeniser notre jeu de données dans le [chapitre 3](/course/fr/chapter3), à la seule différence que les sorties sont renvoyées une par une :

```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")
tokenized_dataset = pubmed_dataset_streamed.map(lambda x: tokenizer(x["text"]))
next(iter(tokenized_dataset))
```

```python out
{'input_ids': [101, 4958, 5178, 4328, 6779, ...], 'attention_mask': [1, 1, 1, 1, 1, ...]}
```

<Tip>

💡 Pour accélérer la tokenisation avec le streaming, vous pouvez passer `batched=True`, comme nous l'avons vu dans la dernière section. Il traitera les exemples batch par batch. La taille de batch par défaut est de 1 000 et peut être spécifiée avec l'argument `batch_size`.

</Tip>

Vous pouvez également mélanger un jeu de données diffusé en continu à l'aide de `IterableDataset.shuffle()`, mais contrairement à `Dataset.shuffle()`, cela ne mélange que les éléments dans un `buffer_size` prédéfini :

```py
shuffled_dataset = pubmed_dataset_streamed.shuffle(buffer_size=10_000, seed=42)
next(iter(shuffled_dataset))
```

```python out
{'meta': {'pmid': 11410799, 'language': 'eng'},
 'text': 'Randomized study of dose or schedule modification of granulocyte colony-stimulating factor in platinum-based chemotherapy for elderly patients with lung cancer ...'
# Étude randomisée sur la modification de la dose ou du calendrier d'administration du facteur de stimulation des colonies de granulocytes dans le cadre d'une chimiothérapie à base de platine chez les patients âgés atteints de cancer du poumon ...
}
```

Dans cet exemple, nous avons sélectionné un exemple aléatoire parmi les 10 000 premiers exemples du tampon. Une fois qu'un exemple est accédé, sa place dans le tampon est remplie avec l'exemple suivant dans le corpus (c'est-à-dire le 10 001e exemple dans le cas ci-dessus). Vous pouvez également sélectionner des éléments d'un jeu de données diffusé en continu à l'aide des fonctions `IterableDataset.take()` et `IterableDataset.skip()`, qui agissent de la même manière que `Dataset.select()`. Par exemple, pour sélectionner les 5 premiers exemples dans le jeu de données *PubMed Abstracts*, nous pouvons procéder comme suit :

```py
dataset_head = pubmed_dataset_streamed.take(5)
list(dataset_head)
```

```python out
[{'meta': {'pmid': 11409574, 'language': 'eng'},
  'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'
# Épidémiologie de l'hypoxémie chez les enfants atteints d'une infection aiguë des voies respiratoires inférieures ...},
 {'meta': {'pmid': 11409575, 'language': 'eng'},
  'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...'
# Signes cliniques d'hypoxémie chez les enfants atteints d'une infection aiguë des voies respiratoires inférieures : indicateurs de l'oxygénothérapie ...},
 {'meta': {'pmid': 11409576, 'language': 'eng'},
  'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..."
# Hypoxémie chez les enfants atteints de pneumonie grave en Papouasie-Nouvelle-Guinée ...},
 {'meta': {'pmid': 11409577, 'language': 'eng'},
  'text': 'Oxygen concentrators and cylinders ...'
# Concentrateurs et bouteilles d'oxygène...},
 {'meta': {'pmid': 11409578, 'language': 'eng'},
  'text': 'Oxygen supply in rural africa: a personal experience ...'
# L'approvisionnement en oxygène dans les zones rurales africaines : une expérience personnelle ...}]
```

De même, vous pouvez utiliser la fonction `IterableDataset.skip()` pour créer des fractionnements d'entraînement et de validation à partir d'un jeu de données mélangé comme suit :

```py
# Ignorer les 1 000 premiers exemples et inclure le reste dans l'ensemble d'apprentissage.
train_dataset = shuffled_dataset.skip(1000)
# Prendre les 1 000 premiers exemples pour l'ensemble de validation.
validation_dataset = shuffled_dataset.take(1000)
```

Terminons notre exploration du streaming des jeux de données avec une application commune : combiner plusieurs jeux de données pour créer un seul corpus. 🤗 *Datasets* fournit une fonction `interleave_datasets()` qui convertit une liste d'objets `IterableDataset` en un seul `IterableDataset`, où les éléments du nouveau jeu de données sont obtenus en alternant entre les exemples source. Cette fonction est particulièrement utile lorsque vous essayez de combiner de grands jeux de données. Par exemple, streamons FreeLaw, un sous-ensemble de *The Pile* et qui est un jeu de données de 51 Go d'avis juridiques de tribunaux américains :

```py
law_dataset_streamed = load_dataset(
    "json",
    data_files="https://the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst",
    split="train",
    streaming=True,
)
next(iter(law_dataset_streamed))
```

```python out
{'meta': {'case_ID': '110921.json',
  'case_jurisdiction': 'scotus.tar.gz',
  'date_created': '2010-04-28T17:12:49Z'},
 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}
```

Ce jeu de données est suffisamment volumineux pour solliciter la RAM de la plupart des ordinateurs portables, mais nous avons pu le charger et y accéder sans transpirer ! Combinons maintenant les jeux de données FreeLaw et *PubMed Abstracts* avec la fonction `interleave_datasets()` :

```py
from itertools import islice
from datasets import interleave_datasets

combined_dataset = interleave_datasets([pubmed_dataset_streamed, law_dataset_streamed])
list(islice(combined_dataset, 2))
```

```python out
[{'meta': {'pmid': 11409574, 'language': 'eng'},
  'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'},
 {'meta': {'case_ID': '110921.json',
   'case_jurisdiction': 'scotus.tar.gz',
   'date_created': '2010-04-28T17:12:49Z'},
  'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}]
```

Ici, nous avons utilisé la fonction `islice()` du module `itertools` de Python pour sélectionner les deux premiers exemples du jeu de données combiné. Nous pouvons voir qu'ils correspondent aux premiers exemples de chacun des deux jeux de données source.

Enfin, si vous souhaitez streamer *The Pile* dans son intégralité de 825 Go, vous pouvez récupérer tous les fichiers préparés comme suit :

```py
base_url = "https://the-eye.eu/public/AI/pile/"
data_files = {
    "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)],
    "validation": base_url + "val.jsonl.zst",
    "test": base_url + "test.jsonl.zst",
}
pile_dataset = load_dataset("json", data_files=data_files, streaming=True)
next(iter(pile_dataset["train"]))
```

```python out
{'meta': {'pile_set_name': 'Pile-CC'},
 'text': 'It is done, and submitted. You can play “Survival of the Tastiest” on Android, and on the web...'}
```

<Tip>

✏️ **Essayez !** Utilisez l'un des grands corpus Common Crawl comme [`mc4`](https://huggingface.co/datasets/mc4) ou [`oscar`](https://huggingface.co/datasets/oscar) pour créer en streaming un jeu de données multilingue représentant les proportions de langues parlées dans un pays de votre choix. Par exemple, les quatre langues nationales en Suisse sont l'allemand, le français, l'italien et le romanche. Vous pouvez donc essayer de créer un corpus suisse en échantillonnant les sous-ensembles Oscar en fonction de leur proportion parlée.

</Tip>

Vous disposez maintenant de tous les outils dont vous avez besoin pour charger et traiter des jeux de données de toutes formes et tailles. Cependant à moins que vous ne soyez exceptionnellement chanceux, il arrivera un moment dans votre cheminement en traitement du langage naturel où vous devrez réellement créer un jeu de données pour résoudre un problème donné. C'est le sujet de la section suivante !


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter5/4.mdx" />

### 🤗 <i>Datasets</i>, coché !
https://huggingface.co/learn/course/fr/chapter5/7.md

# 🤗 <i>Datasets</i>, coché !

<CourseFloatingBanner
    chapter={5}
    classNames="absolute z-10 right-0 top-0"
/>

Eh bien, ce fut une sacrée visite de la bibliothèque 🤗 *Datasets*. Félicitations d’être arrivé jusqu'ici ! Avec les connaissances que vous avez acquises dans ce chapitre, vous devriez être en mesure de :
- charger des jeux de données depuis n'importe où, que ce soit le *Hub* d’Hugging Face, votre ordinateur portable ou un serveur distant de votre entreprise,
- manipuler vos données en utilisant un mélange des fonctions Dataset.map() et Dataset.filter(),
- passer rapidement d'un format de données à un autre, comme Pandas et NumPy, en utilisant Dataset.set_format(),
- créer votre propre jeu de données et l’envoyer vers le *Hub*,
- enchâsser vos documents en utilisant un *transformer* et construire un moteur de recherche sémantique en utilisant FAISS.

Dans le [chapitre 7](/course/fr/chapter7), nous mettrons tout cela à profit en plongeant dans les tâches de traitement du langage naturel de base pour lesquelles les *transformers* sont parfaits. Avant cela mettez vos connaissances sur la librairie 🤗 *Datasets* à l'épreuve avec un petit quiz !


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter5/7.mdx" />

### Que faire si mon jeu de données n'est pas sur le <i>Hub</i> ?
https://huggingface.co/learn/course/fr/chapter5/2.md

# Que faire si mon jeu de données n'est pas sur le <i>Hub</i> ?

<CourseFloatingBanner chapter={5}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter5/section2.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter5/section2.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter5/section2.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter5/section2.ipynb"},
]} />

Vous savez comment utiliser le [*Hub*](https://huggingface.co/datasets) pour télécharger des jeux de données mais en pratique vous vous retrouverez souvent à travailler avec des données stockées sur votre ordinateur portable ou sur un serveur distant. Dans cette section, nous allons vous montrer comment 🤗 *Datasets* peut être utilisé pour charger des jeux de données qui ne sont pas disponibles sur le *Hub* d’Hugging Face.

<Youtube id="HyQgpJTkRdE"/>

## Travailler avec des jeux de données locaux et distants

🤗 *Datasets* fournit des scripts de chargement de jeux de données locaux et distants. La bibliothèque prend en charge plusieurs formats de données courants, tels que :

| Format des données  | Script de chargement |                         Exemple                         |
| :----------------: | :------------------: | :-----------------------------------------------------: |
|     CSV & TSV      |        `csv`         |     `load_dataset("csv", data_files="my_file.csv")`     |
|   Fichiers texte   |        `text`        |    `load_dataset("text", data_files="my_file.txt")`     |
| JSON & JSON Lines |        `json`        |   `load_dataset("json", data_files="my_file.jsonl")`    |
| DataFrames en Pickle |       `pandas`       | `load_dataset("pandas", data_files="my_dataframe.pkl")` |

Comme indiqué dans le tableau, pour chaque format de données, nous avons juste besoin de spécifier le type de script de chargement dans la fonction `load_dataset()`, ainsi qu'un argument `data_files` qui spécifie le chemin vers un ou plusieurs fichiers. Commençons par charger un jeu de données à partir de fichiers locaux puis plus tard comment faire la même chose avec des fichiers distants.

## Charger un jeu de données local

Pour cet exemple, nous utilisons le jeu de données [SQuAD-it](https://github.com/crux82/squad-it/) qui est un grand jeu de données pour la tâche de *Question Awnswering* en italien.

Les échantillons d’entraînement et de test sont hébergés sur GitHub, nous pouvons donc les télécharger avec une simple commande `wget` :

```python
!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-train.json.gz
!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-test.json.gz
```

Cela télécharge deux fichiers compressés appelés *SQuAD_it-train.json.gz* et *SQuAD_it-test.json.gz* que nous pouvons décompresser avec la commande Linux `gzip` :

```python
!gzip -dkv SQuAD_it-*.json.gz
```

```bash
SQuAD_it-test.json.gz:	   87.4% -- replaced with SQuAD_it-test.json
SQuAD_it-train.json.gz:	   82.2% -- replaced with SQuAD_it-train.json
```

Nous pouvons voir que les fichiers compressés ont été remplacés par _SQuAD_it-train.json_ et _SQuAD_it-text.json_, et que les données sont stockées au format JSON.

<Tip>

✎ Si vous vous demandez pourquoi il y a un caractère `!` dans les commandes *shell* ci-dessus, c'est parce que nous les exécutons dans un *notebook* Jupyter. Supprimez simplement le préfixe si vous souhaitez télécharger et décompresser le jeu de données dans un terminal.

</Tip>

Pour charger un fichier JSON avec la fonction `load_dataset()`, nous avons juste besoin de savoir si nous avons affaire à du JSON ordinaire (similaire à un dictionnaire imbriqué) ou à des lignes JSON (JSON séparé par des lignes). Comme de nombreux jeux de données de questions-réponses, SQuAD-it utilise le format imbriqué où tout le texte est stocké dans un champ `data`. Cela signifie que nous pouvons charger le jeu de données en spécifiant l'argument `field` comme suit :

```py
from datasets import load_dataset

squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data")
```

Par défaut, le chargement de fichiers locaux crée un objet `DatasetDict` avec un échantillon `train`. Nous pouvons le voir en inspectant l'objet `squad_it_dataset` :

```py
squad_it_dataset
```

```python out
DatasetDict({
    train: Dataset({
        features: ['title', 'paragraphs'],
        num_rows: 442
    })
})
```

Cela nous montre le nombre de lignes et les noms de colonnes associés à l’échantillon d’entraînement. Nous pouvons afficher l'un des exemples en indexant l’échantillon `train` comme suit :

```py
squad_it_dataset["train"][0]
```

```python out
{
    "title": "Terremoto del Sichuan del 2008", # Séisme du Sichuan 2008
    "paragraphs": [
        {
            "context": "Il terremoto del Sichuan del 2008 o il terremoto...",
			# Le tremblement de terre du Sichuan de 2008 ou le...
            "qas": [
                {
                    "answers": [{"answer_start": 29, "text": "2008"}],
                    "id": "56cdca7862d2951400fa6826",
                    "question": "In quale anno si è verificato il terremoto nel Sichuan?", 
					# En quelle année le tremblement de terre du Sichuan a-t-il eu lieu ?
                },
                ...
            ],
        },
        ...
    ],
}
```

Super, nous avons chargé notre premier jeu de données local ! Mais ce que nous voulons vraiment, c'est inclure à la fois les échantillons `train` et `test` dans un seul objet `DatasetDict` afin que nous puissions appliquer les fonctions `Dataset.map()` sur les deux à la fois . Pour ce faire, nous pouvons fournir un dictionnaire à l'argument `data_files` qui associe chaque nom des échantillons à un fichier associé à cet échantillon :

```py
data_files = {"train": "SQuAD_it-train.json", "test": "SQuAD_it-test.json"}
squad_it_dataset = load_dataset("json", data_files=data_files, field="data")
squad_it_dataset
```

```python out
DatasetDict({
    train: Dataset({
        features: ['title', 'paragraphs'],
        num_rows: 442
    })
    test: Dataset({
        features: ['title', 'paragraphs'],
        num_rows: 48
    })
})
```

C'est exactement ce que nous voulions. Désormais, nous pouvons appliquer diverses techniques de prétraitement pour nettoyer les données, tokeniser les avis, etc.

<Tip>

L'argument `data_files` de la fonction `load_dataset()` est assez flexible et peut être soit un chemin de fichier unique, une liste de chemins de fichiers, ou un dictionnaire qui fait correspondre les noms des échantillons aux chemins de fichiers. Vous pouvez également regrouper les fichiers correspondant à un motif spécifié selon les règles utilisées par le shell Unix. Par exemple, vous pouvez regrouper tous les fichiers JSON d'un répertoire en une seule division en définissant `data_files="*.json"`. Voir la [documentation](https://huggingface.co/docs/datasets/loading#local-and-remote-files) de 🤗 *Datasets* pour plus de détails.

</Tip>

Les scripts de chargement de 🤗 *Datasets* prennent en charge la décompression automatique des fichiers d'entrée. Nous aurions donc pu ignorer l'utilisation de `gzip` en pointant l'argument `data_files` directement sur les fichiers compressés :

```py
data_files = {"train": "SQuAD_it-train.json.gz", "test": "SQuAD_it-test.json.gz"}
squad_it_dataset = load_dataset("json", data_files=data_files, field="data")
```

Cela peut être utile si vous ne souhaitez pas décompresser manuellement de nombreux fichiers GZIP. La décompression automatique s'applique également à d'autres formats courants tels que ZIP et TAR. Il vous suffit donc de pointer `data_files` vers les fichiers compressés et vous êtes prêt à partir !

Maintenant que vous savez comment charger des fichiers locaux sur votre ordinateur portable ou de bureau, examinons le chargement de fichiers distants.

## Charger un jeu de données distant

Si vous travaillez en tant que *data scientist* ou codeur dans une entreprise, il y a de fortes chances que les jeux de données que vous souhaitez analyser soient stockés sur un serveur distant. Heureusement, charger des fichiers distants est aussi simple que de charger des fichiers locaux ! Au lieu de fournir un chemin vers les fichiers locaux, nous pointons l'argument `data_files` de `load_dataset()` vers une ou plusieurs URL où les fichiers distants sont stockés. Par exemple, pour le jeu de données SQuAD-it hébergé sur GitHub, nous pouvons simplement faire pointer `data_files` vers les URL _SQuAD_it-*.json.gz_ comme suit :

```py
url = "https://github.com/crux82/squad-it/raw/master/"
data_files = {
    "train": url + "SQuAD_it-train.json.gz",
    "test": url + "SQuAD_it-test.json.gz",
}
squad_it_dataset = load_dataset("json", data_files=data_files, field="data")
```

Cela renvoie le même objet `DatasetDict` obtenu ci-dessus mais nous évite de télécharger et de décompresser manuellement les fichiers _SQuAD_it-*.json.gz_. Ceci conclut notre incursion dans les différentes façons de charger des jeux de données qui ne sont pas hébergés sur le *Hub*. Maintenant que nous avons un jeu de données avec lequel jouer, mettons la main à la pâte avec diverses techniques de gestion des données !

<Tip>

✏️ **Essayez !** Choisissez un autre jeu de données hébergé sur GitHub ou dans le [*UCI Machine Learning Repository*](https://archive.ics.uci.edu/ml/index.php) et essayez de le charger localement et à distance en utilisant les techniques présentées ci-dessus. Pour obtenir des points bonus, essayez de charger un jeu de données stocké au format CSV ou texte (voir la [documentation](https://huggingface.co/docs/datasets/loading#local-and-remote-files) pour plus d'informations sur ces formats).

</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter5/2.mdx" />

### Glossaire
https://huggingface.co/learn/course/fr/glossary/1.md

# Glossaire

| Original                    | Français                                  |
|-----------------------------|---------------------------------          |
| Accuracy                    | Précision                                 |
| Backward Pass               | Passe arrière                             |
| Batch                       | *Batch*                                   |
| Benchmark                   | *Benchmark*                               |
| Cache                       | Cache                                     |
| Chapter                     | Chapitre                                  |
| Checkpoint                  | *Checkpoint* (plus rarement « point de sauvegarde »)                           |
| Colab Notebook              | *Notebook* Google Colab                   |
| Colator function            | Fonction d'assemblement                   |
| Command                     | Commande                                  |
| Configuration               | Configuration                             |
| Course                      | Cours                                     |
| Dataloader                  | Chargeur de données                       |
| Dependency                  | Dépendances                               |
| Deployment                  | Déploiement                               |
| Development                 | Développement                             |
| Dictionary                  | Dictionnaire                              |
| Download                    | Télécharger                               |
| Feature                     | Variable                                  |
| Field                       | Champ                                     |
| Fine-tuning                 | Finetuning                                |
| Folder                      | Dossier                                   |
| Forward Pass                | Passe avant                               |
| Google                      | *Google*                                  |
| Hugging Face                | *Hugging Face*                            |
| Inference                   | Inférence                                 |
| Learning rate               | Taux d’apprentissage                      |
| Library                     | Bibliothèque                              |
| Linux                       | Linux                                     |
| Loss function               | Fonction de perte/coût                    |
| Loop                        | Boucle                                    |
| macOS                       | macOS                                     |
| Model                       | Modèle                                    |
| Hugging Face Hub            | *Hub* d’*Hugging Face*                    |
| Module                      | Module                                    |
| Natural Language Processing | Traitement du langage naturel             |
| Package                     | Paquet                                    |
| Padding                     | Rembourrage                               |
| Parameter                   | Paramètre                                 |
| Python                      | Python                                    |
| PyTorch                     | PyTorch                                   |
| Samples                     | Echantillons                              |
| Scheduler                   | Planificateur                             |
| Script                      | Script                                    |
| Setup                       | Installation                              |
| TensorFlow                  | TensorFlow                                |
| Terminal                    | Terminal                                  |
| Tokenizer                   | Tokeniseur                                |
| Train                       | Entraîner                                 |
| Transformer                 | *Transformer*                             |
| Virtual Environment         | Environnement virtuel                     |
| Weight decay                | Taux de décroissance des poids            |
| Weights                     | Poids                                     |
| Windows                     | *Windows*                                 |
| Working Environment         | Environnement de travail                  |


A noter que les mots anglais non traduits sont indiqués en italique dans le cours.  
De plus, les abréviations techniques comme API, GPU, TPU, etc. ne sont pas traduites.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/glossary/1.mdx" />

### Manipulation de plusieurs séquences
https://huggingface.co/learn/course/fr/chapter2/5.md

# Manipulation de plusieurs séquences

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter2/section5_pt.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter2/section5_pt.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter2/section5_pt.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter2/section5_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter2/section5_tf.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter2/section5_tf.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter2/section5_tf.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter2/section5_tf.ipynb"},
]} />

{/if}

{#if fw === 'pt'}
<Youtube id="M6adb1j2jPI"/>
{:else}
<Youtube id="ROxrFOEbsQE"/>
{/if}

Dans la section précédente, nous avons exploré le cas d'utilisation le plus simple : faire une inférence sur une seule séquence de petite longueur. Cependant, certaines questions émergent déjà :

- comment gérer de plusieurs séquences ?
- comment gérer de plusieurs séquences *de longueurs différentes* ?
- les indices du vocabulaire sont-ils les seules entrées qui permettent à un modèle de bien fonctionner ?
- existe-t-il une séquence trop longue ?

Voyons quels types de problèmes ces questions posent et comment nous pouvons les résoudre en utilisant l'API 🤗 *Transformers*.

## Les modèles attendent un batch d'entrées

Dans l'exercice précédent, vous avez vu comment les séquences sont traduites en listes de nombres. 
Convertissons cette liste de nombres en un tenseur et envoyons-le au modèle :

{#if fw === 'pt'}
```py
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence = "I've been waiting for a HuggingFace course my whole life."
# J'ai attendu un cours d’HuggingFace toute ma vie.

tokens = tokenizer.tokenize(sequence)
ids = tokenizer.convert_tokens_to_ids(tokens)
input_ids = torch.tensor(ids)
# Cette ligne va échouer.
model(input_ids)
```

```python out
IndexError: Dimension out of range (expected to be in range of [-1, 0], but got 1)
```
{:else}
```py
import tensorflow as tf
from transformers import AutoTokenizer, TFAutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence = "I've been waiting for a HuggingFace course my whole life."
# J'ai attendu un cours d’HuggingFace toute ma vie.

tokens = tokenizer.tokenize(sequence)
ids = tokenizer.convert_tokens_to_ids(tokens)
input_ids = tf.constant(ids)
# This line will fail.
model(input_ids)
```

```py out
InvalidArgumentError: Input to reshape is a tensor with 14 values, but the requested shape has 196 [Op:Reshape]
```
{/if}

Pourquoi cela a échoué ? Nous avons suivi les étapes du pipeline de la section 2.

Le problème est que nous avons envoyé une seule séquence au modèle, alors que les modèles de l’API 🤗 *Transformers* attendent plusieurs phrases par défaut. Ici, nous avons essayé de faire ce que le *tokenizer* fait en coulisses lorsque nous l'avons appliqué à une `séquence`. Cependant si vous regardez de près, vous verrez qu'il n'a pas seulement converti la liste des identifiants d'entrée en un tenseur mais aussi ajouté une dimension par-dessus :


{#if fw === 'pt'}
```py
tokenized_inputs = tokenizer(sequence, return_tensors="pt")
print(tokenized_inputs["input_ids"])
```

```python out
tensor([[  101,  1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662, 12172,
          2607,  2026,  2878,  2166,  1012,   102]])
```
{:else}
```py
tokenized_inputs = tokenizer(sequence, return_tensors="tf")
print(tokenized_inputs["input_ids"])
```

```py out
<tf.Tensor: shape=(1, 16), dtype=int32, numpy=
array([[  101,  1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662,
        12172,  2607,  2026,  2878,  2166,  1012,   102]], dtype=int32)>
```
{/if}

Essayons à nouveau en ajoutant une nouvelle dimension :

{#if fw === 'pt'}
```py
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence = "I've been waiting for a HuggingFace course my whole life."
# J'ai attendu un cours d’HuggingFace toute ma vie.


tokens = tokenizer.tokenize(sequence)
ids = tokenizer.convert_tokens_to_ids(tokens)

input_ids = torch.tensor([ids])
print("Input IDs:", input_ids)

output = model(input_ids)
print("Logits:", output.logits)
```
{:else}
```py
import tensorflow as tf
from transformers import AutoTokenizer, TFAutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence = "I've been waiting for a HuggingFace course my whole life."
# J'ai attendu un cours d’HuggingFace toute ma vie.


tokens = tokenizer.tokenize(sequence)
ids = tokenizer.convert_tokens_to_ids(tokens)

input_ids = tf.constant([ids])
print("Input IDs:", input_ids)

output = model(input_ids)
print("Logits:", output.logits)
```
{/if}

Nous affichons les identifiants d'entrée ainsi que les logits résultants. Voici la sortie :

{#if fw === 'pt'}
```python out
Input IDs: [[ 1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662, 12172,  2607, 2026,  2878,  2166,  1012]]
Logits: [[-2.7276,  2.8789]]
```
{:else}
```py out
Input IDs: tf.Tensor(
[[ 1045  1005  2310  2042  3403  2005  1037 17662 12172  2607  2026  2878
   2166  1012]], shape=(1, 14), dtype=int32)
Logits: tf.Tensor([[-2.7276208  2.8789377]], shape=(1, 2), dtype=float32)
```
{/if}

Le « *batching* » est l'acte d'envoyer plusieurs phrases à travers le modèle, toutes en même temps. Si vous n'avez qu'une seule phrase, vous pouvez simplement construire un batch avec une seule séquence : 

```
batched_ids = [ids, ids]
```

Il s'agit d'un batch de deux séquences identiques !

<Tip>

✏️ **Essayez !** Convertissez cette liste `batched_ids` en un tenseur et passez-la dans votre modèle. Vérifiez que vous obtenez les mêmes logits que précédemment (mais deux fois) !

</Tip>

Utiliser des *batchs* permet au modèle de fonctionner lorsque vous lui donnez plusieurs séquences. Utiliser plusieurs séquences est aussi simple que de construire un batch avec une seule séquence. Il y a cependant un deuxième problème. Lorsque vous essayez de regrouper deux phrases (ou plus), elles peuvent être de longueurs différentes. Si vous avez déjà travaillé avec des tenseurs, vous savez qu'ils doivent être de forme rectangulaire. Vous ne pourrez donc pas convertir directement la liste des identifiants d'entrée en un tenseur. Pour contourner ce problème, nous avons l'habitude de *rembourrer*/*remplir* (le *padding* en anglais) les entrées.

## <i>Padding</i> des entrées

La liste de listes suivante ne peut pas être convertie en un tenseur :


```py no-format
batched_ids = [
    [200, 200, 200],
    [200, 200]
]
```

Afin de contourner ce problème, nous utilisons le *padding* pour que nos tenseurs aient une forme rectangulaire. Le *padding* permet de s'assurer que toutes nos phrases ont la même longueur en ajoutant un mot spécial appelé *padding token* aux phrases ayant moins de valeurs. Par exemple, si vous avez 10 phrases de 10 mots et 1 phrase de 20 mots, le *padding* fait en sorte que toutes les phrases aient 20 mots. Dans notre exemple, le tenseur résultant ressemble à ceci :

```py no-format
padding_id = 100

batched_ids = [
    [200, 200, 200],
    [200, 200, padding_id],
]
```

L'identifiant du jeton de *padding* peut être trouvé dans `tokenizer.pad_token_id`. Utilisons-le et envoyons nos deux phrases à travers le modèle premièrement individuellement puis en étant mises dans un même batch :

{#if fw === 'pt'}
```py no-format
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence1_ids = [[200, 200, 200]]
sequence2_ids = [[200, 200]]
batched_ids = [
    [200, 200, 200],
    [200, 200, tokenizer.pad_token_id],
]

print(model(torch.tensor(sequence1_ids)).logits)
print(model(torch.tensor(sequence2_ids)).logits)
print(model(torch.tensor(batched_ids)).logits)
```

```python out
tensor([[ 1.5694, -1.3895]], grad_fn=<AddmmBackward>)
tensor([[ 0.5803, -0.4125]], grad_fn=<AddmmBackward>)
tensor([[ 1.5694, -1.3895],
        [ 1.3373, -1.2163]], grad_fn=<AddmmBackward>)
```
{:else}
```py no-format
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence1_ids = [[200, 200, 200]]
sequence2_ids = [[200, 200]]
batched_ids = [
    [200, 200, 200],
    [200, 200, tokenizer.pad_token_id],
]

print(model(tf.constant(sequence1_ids)).logits)
print(model(tf.constant(sequence2_ids)).logits)
print(model(tf.constant(batched_ids)).logits)
```

```py out
tf.Tensor([[ 1.5693678 -1.3894581]], shape=(1, 2), dtype=float32)
tf.Tensor([[ 0.5803005  -0.41252428]], shape=(1, 2), dtype=float32)
tf.Tensor(
[[ 1.5693681 -1.3894582]
 [ 1.3373486 -1.2163193]], shape=(2, 2), dtype=float32)
```
{/if}

Il y a quelque chose qui ne va pas avec les logits de notre prédiction avec les séquences mises dans un même batch. La deuxième ligne devrait être la même que les logits pour la deuxième phrase, mais nous avons des valeurs complètement différentes !

C'est parce que dans un *transformer* les couches d’attention *contextualisent* chaque *token*. Celles-ci prennent en compte les *tokens* de *padding* puisqu'elles analysent tous les *tokens* d'une séquence. Pour obtenir le même résultat lorsque l'on passe dans notre modèle des phrases individuelles de différentes longueurs ou un batch composé de mêmes phrases avec *padding*, nous devons dire à ces couches d'attention d'ignorer les jetons de *padding*. Ceci est fait en utilisant un masque d'attention.


## Masques d'attention

Les masques d'attention sont des tenseurs ayant exactement la même forme que le tenseur d'identifiants d'entrée, remplis de 0 et de 1 :
-	1 indique que les *tokens* correspondants doivent être analysés
-	0 indique que les *tokens* correspondants ne doivent pas être analysés (c'est-à-dire qu'ils doivent être ignorés par les couches d'attention du modèle).

Complétons l'exemple précédent avec un masque d'attention :


{#if fw === 'pt'}
```py no-format
batched_ids = [
    [200, 200, 200],
    [200, 200, tokenizer.pad_token_id],
]

attention_mask = [
    [1, 1, 1],
    [1, 1, 0],
]

outputs = model(torch.tensor(batched_ids), attention_mask=torch.tensor(attention_mask))
print(outputs.logits)
```

```python out
tensor([[ 1.5694, -1.3895],
        [ 0.5803, -0.4125]], grad_fn=<AddmmBackward>)
```
{:else}
```py no-format
batched_ids = [
    [200, 200, 200],
    [200, 200, tokenizer.pad_token_id],
]

attention_mask = [
    [1, 1, 1],
    [1, 1, 0],
]

outputs = model(tf.constant(batched_ids), attention_mask=tf.constant(attention_mask))
print(outputs.logits)
```

```py out
tf.Tensor(
[[ 1.5693681  -1.3894582 ]
 [ 0.5803021  -0.41252586]], shape=(2, 2), dtype=float32)
```
{/if}

Nous obtenons maintenant les mêmes logits pour la deuxième phrase du batch.

Remarquez comment la dernière valeur de la deuxième séquence est un identifiant de *padding* valant 0 dans le masque d'attention.


<Tip>

✏️ **Essayez !** Appliquez la tokenisation manuellement sur les deux phrases utilisées dans la section 2 (« <i>I've been waiting for a HuggingFace course my whole life.</i> » et « <i>I hate this so much!</i> »). Passez-les dans le modèle et vérifiez que vous obtenez les mêmes logits que dans la section 2. Ensuite regroupez-les en utilisant le jeton de *padding* et créez le masque d'attention approprié. Vérifiez que vous obtenez les mêmes résultats qu’en passant par le modèle !

</Tip>


## Séquences plus longues

Les *transformers* acceptent en entrée que des séquences d’une longueur limitée. La plupart des modèles traitent des séquences allant jusqu'à 512 ou 1024 *tokens* et plantent lorsqu'on leur demande de traiter des séquences plus longues. Il existe deux solutions à ce problème :

- utiliser un modèle avec une longueur de séquence supportée plus longue,
- tronquer les séquences.

Certains modèles sont spécialisés dans le traitement de très longues séquences comme par exemple le [Longformer](https://huggingface.co/transformers/model_doc/longformer.html) ou le [LED](https://huggingface.co/transformers/model_doc/led.html). Si vous travaillez sur une tâche qui nécessite de très longues séquences, nous vous recommandons de jeter un coup d'œil à ces modèles.

Sinon, nous vous recommandons de tronquer vos séquences en spécifiant le paramètre `max_sequence_length` :


```py
sequence = sequence[:max_sequence_length]
```


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter2/5.mdx" />

### Introduction
https://huggingface.co/learn/course/fr/chapter2/1.md

# Introduction

<CourseFloatingBanner
    chapter={2}
    classNames="absolute z-10 right-0 top-0"
/>

Comme vous l'avez vu dans le [chapitre 1](/course/fr/chapter1), les *transformers* sont généralement très grands. Pouvant aller de plusieurs millions à des dizaines de milliards de paramètres, l'entraînement et le déploiement de ces modèles est une entreprise compliquée. De plus, avec de nouveaux modèles publiés presque quotidiennement et ayant chacun sa propre implémentation, les essayer tous n'est pas une tâche facile.

La bibliothèque 🤗 *Transformers* a été créée pour résoudre ce problème. Son objectif est de fournir une API unique à travers laquelle tout modèle de *transformers* peut être chargé, entraîné et sauvegardé. Les principales caractéristiques de la bibliothèque sont :

- **La facilité d'utilisation** : en seulement deux lignes de code il est possible de télécharger, charger et utiliser un modèle de NLP à l'état de l'art pour faire de l'inférence,
- **La flexibilité** : au fond, tous les modèles sont de simples classes PyTorch `nn.Module` ou TensorFlow `tf.keras.Model` et peuvent être manipulés comme n'importe quel autre modèle dans leurs *frameworks* d'apprentissage automatique respectifs,
- **La simplicité** : pratiquement aucune abstraction n'est faite dans la bibliothèque. Avoir tout dans un fichier est un concept central : la passe avant d'un modèle est entièrement définie dans un seul fichier afin que le code lui-même soit compréhensible et modifiable.

Cette dernière caractéristique rend 🤗 *Transformers* très différent des autres bibliothèques d'apprentissage automatique. 
Les modèles ne sont pas construits sur des modules partagés entre plusieurs fichiers. Au lieu de cela, chaque modèle possède ses propres couches. 
En plus de rendre les modèles plus accessibles et compréhensibles, cela vous permet d'expérimenter des choses facilement sur un modèle sans affecter les autres.

Ce chapitre commence par un exemple de bout en bout où nous utilisons un modèle et un *tokenizer* ensemble pour reproduire la fonction `pipeline()` introduite dans le [chapitre 1](/course/fr/chapter1). 
Ensuite, nous aborderons l'API *model* : nous nous plongerons dans les classes de modèle et de configuration, nous verrons comment charger un modèle et enfin comment il traite les entrées numériques pour produire des prédictions. 

Nous examinerons ensuite l'API *tokenizer* qui est l'autre composant principal de la fonction `pipeline()`. 
Les *tokenizers* s'occupent de la première et de la dernière étape du traitement en gérant la conversion du texte en entrées numériques pour le réseau neuronal et la reconversion en texte lorsqu'elle est nécessaire. 
Enfin, nous montrerons comment gérer l'envoi de plusieurs phrases à travers un modèle dans un batch préparé et nous conclurons le tout en examinant de plus près la fonction `tokenizer()`.

<Tip>
  ⚠️ Afin de bénéficier de toutes les fonctionnalités disponibles avec le <i>Hub</i> et la bibliothèque 🤗 <i>Transformers</i>, nous vous recommandons <a href="https://huggingface.co/join">de créer un compte</a>.
</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter2/1.mdx" />

### Les modèles
https://huggingface.co/learn/course/fr/chapter2/3.md

# Les modèles

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter2/section3_pt.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter2/section3_pt.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter2/section3_pt.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter2/section3_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter2/section3_tf.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter2/section3_tf.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter2/section3_tf.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter2/section3_tf.ipynb"},
]} />

{/if}

{#if fw === 'pt'}
<Youtube id="AhChOFRegn4"/>
{:else}
<Youtube id="d3JVgghSOew"/>
{/if}

{#if fw === 'pt'}
Dans cette section, nous allons examiner de plus près la création et l'utilisation d'un modèle. Nous utiliserons la classe `AutoModel` qui est pratique lorsque vous voulez instancier n'importe quel modèle à partir d'un checkpoint.

La classe `AutoModel` et toutes les classes apparentées sont en fait de simples *wrappers* sur la grande variété de modèles disponibles dans la bibliothèque. C'est une enveloppe intelligente car elle peut automatiquement deviner l'architecture appropriée pour votre *checkpoint* et ensuite instancier un modèle avec cette architecture.

{:else}
Dans cette section, nous allons examiner de plus près la création et l'utilisation d'un modèle. Nous utiliserons la classe `TFAutoModel` qui est pratique lorsque vous voulez instancier n'importe quel modèle à partir d'un checkpoint.

La classe `TFAutoModel` et toutes les classes apparentées sont en fait de simples *wrappers* sur la grande variété de modèles disponibles dans la bibliothèque. C'est une enveloppe intelligente car elle peut automatiquement deviner l'architecture appropriée pour votre *checkpoint* et ensuite instancier un modèle avec cette architecture.

{/if}

Cependant, si vous connaissez le type de modèle que vous voulez utiliser, vous pouvez utiliser directement la classe qui définit son architecture. Voyons comment cela fonctionne avec un modèle BERT.

## Création d’un <i>transformer</i>

La première chose que nous devons faire pour initialiser un modèle BERT est de charger un objet configuration :

{#if fw === 'pt'}
```py
from transformers import BertConfig, BertModel

# Construire la configuration
config = BertConfig()

# Construire le modèle à partir de la configuration
model = BertModel(config)
```
{:else}
```py
from transformers import BertConfig, TFBertModel

# Construire la configuration
config = BertConfig()

# Construire le modèle à partir de la configuration
model = TFBertModel(config)
```
{/if}

La configuration contient de nombreux attributs qui sont utilisés pour construire le modèle :
```py
print(config)
```

```python out
BertConfig {
  [...]
  "hidden_size": 768,
  "intermediate_size": 3072,
  "max_position_embeddings": 512,
  "num_attention_heads": 12,
  "num_hidden_layers": 12,
  [...]
}
```

Bien que vous n'ayez pas encore vu ce que font tous ces attributs, vous devriez en reconnaître certains : l'attribut `hidden_size` définit la taille du vecteur `hidden_states`, et `num_hidden_layers` définit le nombre de couches que le *transformer* possède.

### Différentes méthodes de chargement

Un modèle créé à partir de la configuration par défaut est initialisé avec des valeurs aléatoires :


{#if fw === 'pt'}
```py
from transformers import BertConfig, BertModel

config = BertConfig()
model = BertModel(config)

# Le modèle est initialisé de façon aléatoire !
```
{:else}
```py
from transformers import BertConfig, TFBertModel

config = BertConfig()
model = TFBertModel(config)

# Le modèle est initialisé de façon aléatoire !
```
{/if}

Le modèle peut être utilisé tel quel mais il produira du charabia. En effet, il doit d'abord être entraîné. Nous pourrions entraîner le modèle à partir de zéro sur la tâche qui nous intéresse mais comme vous l'avez vu dans le [chapitre 1](/course/fr/chapter1) cela nécessiterait beaucoup de temps et de données. De plus cela aurait un impact environnemental non négligeable. Pour éviter les efforts inutiles et redondants, il est impératif de pouvoir partager et réutiliser les modèles qui ont déjà été entraînés.

Charger un *transformer* qui a déjà été entraîné est simple : nous pouvons le faire en utilisant la méthode `from_pretrained()` :


{#if fw === 'pt'}
```py
from transformers import BertModel

model = BertModel.from_pretrained("bert-base-cased")
```

Comme vu précédemment, nous pouvons remplacer `BertModel` par la classe équivalente `AutoModel`. A partir de maintenant nous ferons cela car cela produit un code agnostique *checkpoint*, c’est-à-dire que si votre code fonctionne pour un *checkpoint* donné, il devrait fonctionner sans problème avec un autre. Cela s'applique même si l'architecture est différente du moment que le *checkpoint* a été entraîné pour une tâche similaire (par exemple, une tâche d'analyse de sentiments).

{:else}
```py
from transformers import TFBertModel

model = TFBertModel.from_pretrained("bert-base-cased")
```

Comme vu précédemment, nous pouvons remplacer `TFBertModel` par la classe équivalente `TFAutoModel`. A partir de maintenant nous ferons cela car cela produit un code agnostique *checkpoint*, c’est-à-dire que si votre code fonctionne pour un *checkpoint* donné, il devrait fonctionner sans problème avec un autre. Cela s'applique même si l'architecture est différente du moment que le *checkpoint* a été entraîné pour une tâche similaire (par exemple, une tâche d'analyse de sentiments).

{/if}

Dans l'exemple de code ci-dessus, nous n'avons pas utilisé `BertConfig` et avons à la place chargé un modèle pré-entraîné via l'identifiant `bert-base-cased`. Il s'agit d'un *checkpoint* qui a été entraîné par les auteurs de BERT eux-mêmes. Vous pouvez trouver davantage de détails à son sujet dans la [fiche du modèle](https://huggingface.co/bert-base-cased).

Ce modèle est maintenant initialisé avec tous les poids du *checkpoint*. Il peut être utilisé directement pour l'inférence sur les tâches sur lesquelles il a été entraîné. Il peut également être *finetuné* sur une nouvelle tâche. En entraînant avec des poids pré-entraînés plutôt qu'à partir de zéro, nous pouvons rapidement obtenir de bons résultats.

Les poids ont été téléchargés et mis en cache (afin que les futurs appels à la méthode `from_pretrained()` ne les retéléchargent pas) dans le dossier cache, qui est par défaut *~/.cache/huggingface/transformers*. Vous pouvez personnaliser votre dossier de cache en définissant la variable d'environnement `HF_HOME`.

L'identifiant utilisé pour charger le modèle peut être l'identifiant de n'importe quel modèle sur le *Model Hub* du moment qu'il est compatible avec l'architecture BERT. La liste complète des *checkpoints* de BERT disponibles peut être trouvée [ici](https://huggingface.co/models?filter=bert).

### Méthodes de sauvegarde

Sauvegarder un modèle est aussi facile que d'en charger un. Nous utilisons la méthode `save_pretrained()`, qui est analogue à la méthode `from_pretrained()` :


```py
model.save_pretrained("directory_on_my_computer")
```

Cela enregistre deux fichiers sur votre disque :

{#if fw === 'pt'}
```
ls directory_on_my_computer

config.json model.safetensors
```
{:else}
```
ls directory_on_my_computer

config.json tf_model.h5
```
{/if}

Si vous jetez un coup d'œil au fichier *config.json*, vous reconnaîtrez les attributs nécessaires pour construire l'architecture du modèle. Ce fichier contient également certaines métadonnées, comme l'origine du *checkpoint* et la version de la bibliothèque 🤗 *Transformers* que vous utilisiez lors du dernier enregistrement du point *checkpoint*.

{#if fw === 'pt'}
Le fichier *model.safetensors* est connu comme le *dictionnaire d'état*. Il contient tous les poids de votre modèle. Les deux fichiers vont de pair : la configuration est nécessaire pour connaître l'architecture de votre modèle, tandis que les poids du modèle sont les paramètres de votre modèle.

{:else}
Le fichier *tf_model.h5* est connu comme le *dictionnaire d'état*. Il contient tous les poids de votre modèle. Les deux fichiers vont de pair : la configuration est nécessaire pour connaître l'architecture de votre modèle, tandis que les poids du modèle sont les paramètres de votre modèle.

{/if}

### Utilisation d'un <i>transformer</i> pour l'inférence

Maintenant que vous savez comment charger et sauvegarder un modèle, essayons de l'utiliser pour faire quelques prédictions. Les *transformers* ne peuvent traiter que des nombres. Des nombres que le *tokenizer* génère. Mais avant de parler des *tokenizers*, explorons les entrées que le modèle accepte.

Les *tokenizers* se chargent de passer les entrées vers les tenseurs du *framework* approprié. Pour vous aider à comprendre ce qui se passe, jetons un coup d'œil rapide à ce qui doit être fait avant d'envoyer les entrées au modèle.

Disons que nous avons les séquences suivantes :


```py
sequences = ["Hello!", "Cool.", "Nice!"]
```

Le *tokenizer* les convertit en indices de vocabulaire qui sont généralement appelés *input IDs*. Chaque séquence est maintenant une liste de nombres ! La sortie résultante est :

```py no-format
encoded_sequences = [
    [101, 7592, 999, 102],
    [101, 4658, 1012, 102],
    [101, 3835, 999, 102],
]
```

Il s'agit d'une liste de séquences encodées : une liste de listes. Les tenseurs n'acceptent que des formes rectangulaires (pensez aux matrices). Ce « tableau » est déjà de forme rectangulaire, donc le convertir en tenseur est facile :

{#if fw === 'pt'}
```py
import torch

model_inputs = torch.tensor(encoded_sequences)
```
{:else}
```py
import tensorflow as tf

model_inputs = tf.constant(encoded_sequences)
```
{/if}

### Utilisation des tenseurs comme entrées du modèle

L'utilisation des tenseurs avec le modèle est extrêmement simple, il suffit d'appeler le modèle avec les entrées :


```py
output = model(model_inputs)
```

Bien que le modèle accepte un grand nombre d'arguments différents, seuls les identifiants d'entrée sont nécessaires. Nous expliquerons plus tard ce que font les autres arguments et quand ils sont nécessaires. Avant cela, regardons de plus près les *tokenizers*, cet outil qui construit les entrées qu'un *transformer* peut comprendre.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter2/3.mdx" />

### Tout assembler
https://huggingface.co/learn/course/fr/chapter2/6.md

# Tout assembler

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter2/section6_pt.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter2/section6_pt.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter2/section6_pt.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter2/section6_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter2/section6_tf.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter2/section6_tf.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter2/section6_tf.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter2/section6_tf.ipynb"},
]} />

{/if}

Dans les dernières sections, nous avons fait de notre mieux pour effectuer la plupart du travail manuellement. Nous avons exploré le fonctionnement des *tokenizers* et examiné la tokenisation, la conversion en identifiants d'entrée, le *padding*, la troncature et les masques d'attention.

Cependant, comme nous l'avons vu dans la section 2, l'API 🤗 *Transformers* peut gérer tout cela pour nous via une fonction dans laquelle nous allons nous plonger ici. Lorsque vous appelez votre `tokenizer` directement sur la phrase, vous récupérez des entrées qui sont prêtes à être passées dans votre modèle :


```py
from transformers import AutoTokenizer

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)

sequence = "I've been waiting for a HuggingFace course my whole life."
# J'ai attendu un cours d’HuggingFace toute ma vie.

model_inputs = tokenizer(sequence)
```

Ici, la variable `model_inputs` contient tout ce qui est nécessaire au bon fonctionnement d'un modèle. Pour DistilBERT, cela inclut les identifiants d'entrée ainsi que le masque d'attention. D'autres modèles qui acceptent des entrées supplémentaires sont également fournis par l'objet `tokenizer`.

Comme nous allons le voir dans les quelques exemples ci-dessous, cette méthode est très puissante. Premièrement, elle peut tokeniser une seule séquence :


```py
sequence = "I've been waiting for a HuggingFace course my whole life."
# J'ai attendu un cours d’HuggingFace toute ma vie.


model_inputs = tokenizer(sequence)
```

Elle gère également plusieurs séquences à la fois, sans modification de l'API :


```py
sequences = [
    "I've been waiting for a HuggingFace course my whole life.",
    "So have I!",
]  # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! »

model_inputs = tokenizer(sequences)
```

Il est possible de faire du *padding* selon plusieurs objectifs :

```py
# Remplit les séquences jusqu'à la longueur maximale de la séquence
model_inputs = tokenizer(sequences, padding="longest")

# Remplit les séquences jusqu'à la longueur maximale du modèle (512 pour BERT ou DistilBERT)
model_inputs = tokenizer(sequences, padding="max_length")

# Remplit les séquences jusqu'à la longueur maximale spécifiée
model_inputs = tokenizer(sequences, padding="max_length", max_length=8)
```

La fonction peut également tronquer les séquences :

```py
sequences = [
    "I've been waiting for a HuggingFace course my whole life.",
    "So have I!",
]  # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! »

# Tronque les séquences qui sont plus longues que la longueur maximale du modèle
# (512 pour BERT ou DistilBERT)
model_inputs = tokenizer(sequences, truncation=True)

# Tronque les séquences qui sont plus longues que la longueur maximale spécifiée
model_inputs = tokenizer(sequences, max_length=8, truncation=True)
```

L'objet `tokenizer` peut gérer la conversion en des tenseurs de *frameworks* spécifiques. Ils peuvent ensuite être directement envoyés au modèle. Par exemple, dans le code suivant, nous demandons au *tokenizer* de retourner des tenseurs PyTorch lorsque l’on spécifie `"pt"`, de retourner des tenseurs TensorFlow lorsque l’on spécifie `"tf"` et des tableaux NumPy lorsque l’on indique `"np"` :

```py
sequences = [
    "I've been waiting for a HuggingFace course my whole life.",
    "So have I!",
]  # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! »

# Retourne des tenseurs PyTorch
model_inputs = tokenizer(sequences, padding=True, return_tensors="pt")

# Retourne des tenseurs TensorFlow
model_inputs = tokenizer(sequences, padding=True, return_tensors="tf")

# Retourne des tableaux NumPy
model_inputs = tokenizer(sequences, padding=True, return_tensors="np")
```

## Jetons spéciaux

Si nous jetons un coup d'œil aux identifiants d'entrée renvoyés par le *tokenizer*, nous verrons qu'ils sont un peu différents de ceux que nous avions précédemment :


```py
sequence = "I've been waiting for a HuggingFace course my whole life."
# « J'ai attendu un cours de HuggingFace toute ma vie. »

model_inputs = tokenizer(sequence)
print(model_inputs["input_ids"])

tokens = tokenizer.tokenize(sequence)
ids = tokenizer.convert_tokens_to_ids(tokens)
print(ids)
```

```python out
[101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102]
[1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012]
```

Un identifiant symbolique a été ajouté au début ainsi qu’un autre à la fin. Décodons les deux séquences d'identifiants ci-dessus pour voir de quoi il s'agit :

```py
print(tokenizer.decode(model_inputs["input_ids"]))
print(tokenizer.decode(ids))
```

```python out
"[CLS] i've been waiting for a huggingface course my whole life. [SEP]"
"i've been waiting for a huggingface course my whole life."
```

Le *tokenizer* a ajouté le mot spécial `[CLS]` au début et le mot spécial `[SEP]` à la fin. C'est parce que le modèle a été pré-entraîné avec ces mots, donc pour obtenir les mêmes résultats pour l'inférence, nous devons également les ajouter. Notez que certains modèles n'ajoutent pas de mots spéciaux, ou en ajoutent des différents. Les modèles peuvent aussi ajouter ces mots spéciaux seulement au début, ou seulement à la fin. Dans tous les cas, le *tokenizer* sait lesquels sont attendus et s'en occupe pour vous.

## Conclusion : du <i>tokenizer</i> au modèle

Maintenant que nous avons vu toutes les étapes individuelles que l'objet `tokenizer` utilise lorsqu'il est appliqué sur des textes, voyons une dernière fois comment il peut gérer plusieurs séquences (*padding*), de très longues séquences (*troncation*) et plusieurs types de tenseurs avec son API principale :


{#if fw === 'pt'}
```py
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
sequences = [
    "I've been waiting for a HuggingFace course my whole life.",
    "So have I!",
]  # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! »


tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt")
output = model(**tokens)
```
{:else}
```py
import tensorflow as tf
from transformers import AutoTokenizer, TFAutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)
sequences = [
    "I've been waiting for a HuggingFace course my whole life.",
    "So have I!",
]  # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! »

tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")
output = model(**tokens)
```
{/if}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter2/6.mdx" />

### Quiz de fin de chapitre
https://huggingface.co/learn/course/fr/chapter2/8.md

# Quiz de fin de chapitre

<CourseFloatingBanner
    chapter={2}
    classNames="absolute z-10 right-0 top-0"
/>

### 1. Quel est l'ordre du pipeline de modélisation du langage ?


<Question
	choices={[
		{
			text: " Tout d'abord, le modèle, qui traite le texte et renvoie des prédictions brutes. Puis le <i>tokenizer</i> donne un sens à ces prédictions et les reconvertit en texte si nécessaire.",
			explain: " Le modèle ne peut pas comprendre le texte ! Le <i>tokenizer</i> doit d'abord tokeniser le texte et le convertir en identifiants afin qu'il soit compréhensible par le modèle."},
		{
			text: " Tout d'abord, le <i>tokenizer</i>, qui traite le texte et renvoie des identifiants. Puis le modèle traite ces identifiants et produit une prédiction, qui peut être du texte.",
			explain: " La prédiction du modèle ne peut pas être du texte immédiatement. Le <i>tokenizer</i> doit être utilisé afin de reconvertir la prédiction en texte !"},
		{
			text: " Le <i>tokenizer</i> traite le texte et renvoie des identifiants. Le modèle traite ces identifiants et produit une prédiction. Le <i>tokenizer</i> peut alors être utilisé à nouveau pour reconvertir ces prédictions en texte.",
			explain: " Le <i>tokenizer</i> peut être utilisé à la fois pour la tokenisation et la dé-tokénisation.",
            correct: true
		}
	]}
/>

### 2. Combien de dimensions le tenseur produit par le <i>transformer</i> de base possède-t-il et quelles sont-elles ?


<Question
	choices={[
		{
			text: "2: la longueur de la séquence et la taille du batch",
			explain: "Le tenseur produit par le modèle possède une troisième dimension : la taille cachée."
		},
		{
			text: "2: la longueur de la séquence et la taille cachée",
			explain: "Tous les <i>transformers</i>  gèrent les batchs, même avec une seule séquence ce serait une taille de batch de 1 !"
		},
		{
			text: "3: la longueur de la séquence, la taille du batch et la taille cachée.",
			explain: "",
            correct: true
		}
	]}
/>

### 3. Lequel des éléments suivants est un exemple de tokenisation en sous-mots ?

<Question
	choices={[
		{
			text: "WordPiece",
			explain: "Oui, c'est un exemple de tokenisation en sous-mots !",
            correct: true
		},
		{
			text: "La tokenization basée sur les caractères",
			explain: "La tokenization basée sur les caractères n’est pas un type de tokenisation en sous-mots."
		},
		{
			text: "Découpage sur les espaces et la ponctuation",
			explain: "C’est une tokenisation basée sur les mots !"
		},
		{
			text: "BPE",
			explain: "Oui, c'est un exemple de tokenisation en sous-mots !",
            correct: true
        },
		{
			text: "Unigram",
			explain: "Oui, c'est un exemple de tokenisation en sous-mots !",
            correct: true
        },
		{
			text: "Aucune des propositions ci-dessus",
			explain: ""
        }
	]}
/>

### 4. Qu'est-ce qu'une tête de modèle ?

<Question
	choices={[
		{
			text: " Un composant du <i>transformer</i>  de base qui redirige les tenseurs vers leurs couches correctes.",
			explain: "Il n'y a pas de tel composant."
		},
		{
			text:  "Également connu sous le nom de mécanisme d'auto-attention, il adapte la représentation d'un <i>token</i>  en fonction des autres <i>tokens</i>  de la séquence.",
			explain: "La couche d'auto-attention contient des têtes d'attention mais ce ne sont pas des têtes d'adaptation."
		},
		{
			text: "Un composant supplémentaire, généralement constitué d'une ou plusieurs couches, pour convertir les prédictions du <i>transformer</i>  en une sortie spécifique à la tâche.",
			explain: "Les têtes d'adaptation, aussi appelées simplement têtes, se présentent sous différentes formes : têtes de modélisation du langage, têtes de réponse aux questions, têtes de classification des séquences, etc.",
			correct: true
		} 
	]}
/>

{#if fw === 'pt'}
### 5. Qu'est-ce qu'un AutoModel?

<Question
	choices={[
		{
			text: "Un modèle qui s'entraîne automatiquement sur vos données",
			explain: "Vous confondez cela avec notre produit <a href='https://huggingface.co/autotrain>AutoTrain</a>"
		},
		{
			text: "Un objet qui renvoie la bonne architecture basée sur le <i>checkpoint</i> .",
			explain: "Exactement : <code>AutoModel</code> a seulement besoin de connaître le <i>checkpoint</i> à partir duquel il doit s'initialiser pour retourner à la bonne architecture.",
			correct: true
		},
		{
			text: "Un modèle qui détecte automatiquement la langue utilisée pour ses entrées afin de charger les bonnes pondérations.",
			explain: "Bien que certains <i>checkpoints</i> et modèles soient capables de gérer plusieurs langues, il n'existe pas d'outils intégrés pour la sélection automatique des <i>checkpoints</i> en fonction de la langue. Vous devez vous rendre sur le <a href='https://huggingface.co/models'>Hub des modèles</a> pour trouver le meilleur <i>checkpoint</i> pour votre tâche !"
		} 
	]}
/>

{:else}
### 5. What is an AutoModel?

<Question
	choices={[
		{
			text: "Un modèle qui s'entraîne automatiquement sur vos données",
			explain: "Vous confondez cela avec notre produit <a href='https://huggingface.co/autotrain>AutoTrain</a>"
		},
		{
			text: "Un objet qui renvoie la bonne architecture basée sur le <i>checkpoint</i> .",
			explain: "Exactement : <code>TFAutoModel</code> a seulement besoin de connaître le <i>checkpoint</i> à partir duquel il doit s'initialiser pour retourner à la bonne architecture.",
			correct: true
		},
		{
			text: "Un modèle qui détecte automatiquement la langue utilisée pour ses entrées afin de charger les bonnes pondérations.",
			explain: "Bien que certains <i>checkpoints</i> et modèles soient capables de gérer plusieurs langues, il n'existe pas d'outils intégrés pour la sélection automatique des <i>checkpoints</i> en fonction de la langue. Vous devez vous rendre sur le <a href='https://huggingface.co/models'>Hub des modèles</a> pour trouver le meilleur <i>checkpoint</i> pour votre tâche !"
		} 
	]}
/>

{/if}

### 6. Quelles sont les techniques à connaître lors de la mise en batch de séquences de longueurs différentes ?


<Question
	choices={[
		{
			text: "La troncature",
			explain: " La troncature est une façon correcte d'égaliser les séquences pour qu'elles s'inscrivent dans une forme rectangulaire. Mais est-ce la seule ?",
			correct: true
		},
		{
			text: "Retourner les tenseurs",
			explain: "Alors que les autres techniques vous permettent de renvoyer des tenseurs rectangulaires, retourner les tenseurs n'est pas utile lorsque vous mettez en batch des séquences."
		},
		{
			text: "Le <i>padding</i>",
			explain: "Le <i>padding</i> est une façon correcte d'égaliser les séquences pour qu'elles tiennent dans une forme rectangulaire. Mais est-ce le seul moyen ?",
			correct: true
		}, 
		{
			text: "Les masques d'attention ",
			explain: "Les masques d'attention sont d'une importance capitale lorsqu'on manipule des séquences de longueurs différentes. Ce n'est cependant pas la seule technique à laquelle il faut faire attention.",
			correct: true
		} 
	]}
/>

### 7. Quel est l'intérêt d'appliquer une fonction SoftMax aux logits produits par un modèle de classification de séquences ?

<Question
	choices={[
		{
			text: "Elle adoucit les logits pour qu'ils soient plus fiables.",
			explain: "La fonction SoftMax n'affecte pas la fiabilité des résultats."
		},
		{
			text: "Elle applique une limite inférieure et supérieure pour qu'ils soient compréhensibles.",
			explain: "Les valeurs résultantes sont comprises entre 0 et 1. Ce n'est cependant pas la seule raison pour laquelle nous utilisons une fonction SoftMax.",
            correct: true
		},
		{
			text: "La somme totale des sorties est alors égale à 1, ce qui permet une interprétation probabiliste.",
			explain: "Mais ce n'est pas la seule raison pour laquelle nous utilisons une fonction SoftMax.",
            correct: true
		}
	]}
/>

### 8. Autour de quelle méthode s'articule la majeure partie de l'API <i>tokenizer</i> ?

<Question
	choices={[
		{
			text: "<code>encode</code>, car elle peut encoder du texte en identifiants et des identifiants en prédictions.",
			explain: "Bien que la méthode <code>encode</code> existe sur les <i>tokenizer</i>, elle n'existe pas sur les modèles."
		},
		{
			text: "Appeler directement l'objet tokenizer",
			explain: "La méthode <code>__call__</code> du <i>tokenizer</i> est une méthode très puissante qui peut traiter à peu près tout. C'est également la méthode utilisée pour récupérer les prédictions d'un modèle.",
			correct: true
		},
		{
			text: "<code>pad</code>",
			explain: "Le <i>padding</i> est très utile mais ce n'est qu'une partie de l'API <i>tokenizer</i>."
		},
		{
			text: "<code>tokenize</code>",
			explain: "La méthode <code>tokenize</code> est est sans doute l'une des méthodes les plus utiles, mais elle ne constitue pas le cœur de l'API <i>tokenizer</i>."
		}
	]}
/>

### 9. Que contient la variable `result` dans cet exemple de code ?

```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
result = tokenizer.tokenize("Hello!")
```

<Question
	choices={[
		{
			text: "Une liste de chaînes de caractères, chaque chaîne étant un <i>token</i>.",
			explain: "Convertissez cela en identifiants, et donnez-les à un modèle !",
            correct: true
		},
		{
			text: "Une liste d'identifiants",
			explain: "C'est à cela que la méthode <code>__call__</code> ou la méthode <code>convert_tokens_to_ids</code> sert !"
		},
		{
			text: "Une chaîne contenant tous les <i>tokens</i>",
			explain: "Ce serait sous-optimal car le but est de diviser la chaîne de caractères en plusieurs éléments."
		}
	]}
/>

{#if fw === 'pt'}
### 10. Y a-t-il un problème avec le code suivant ?


```py
from transformers import AutoTokenizer, AutoModel

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
model = AutoModel.from_pretrained("gpt2")

encoded = tokenizer("Hey!", return_tensors="pt")
result = model(**encoded)
```

<Question
	choices={[
		{
			text: "Non, ça semble correct.",
			explain: "Malheureusement, coupler un modèle avec un <i>tokenizer</i> qui a été entraîné avec un <i>checkpoint</i> différent est rarement une bonne idée. Le modèle n'a pas été entraîné pour donner du sens à la sortie de ce <i>tokenizer</i> donc la sortie du modèle (s'il peut même fonctionner !) n'aura aucun sens."
		},
		{
			text: " Le <i>tokenizer</i> et le modèle doivent toujours provenir du même <i>checkpoint</i>.",
			explain: "",
            correct: true
		},
		{
			text: " C'est une bonne pratique de faire du <i>padding</i> et de troncage avec le <i>tokenizer</i> car chaque entrée est un batch.",
			explain: "Il est vrai que chaque entrée de modèle doit être un batch. Cependant, tronquer ou compléter cette séquence n'aurait pas nécessairement de sens puisqu'il n'y en a qu'une seule. Il s'agit là de techniques permettant de mettre en batch une liste de phrases."
		}
	]}
/>

{:else}
### 10. Y a-t-il un problème avec le code suivant ?

```py
from transformers import AutoTokenizer, TFAutoModel

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
model = TFAutoModel.from_pretrained("gpt2")

encoded = tokenizer("Hey!", return_tensors="pt")
result = model(**encoded)
```

<Question
	choices={[
		{
			text: "Non, ça semble correct.",
			explain: "Malheureusement, coupler un modèle avec un <i>tokenizer</i> qui a été entraîné avec un <i>checkpoint</i> différent est rarement une bonne idée. Le modèle n'a pas été entraîné pour donner du sens à la sortie de ce <i>tokenizer</i> donc la sortie du modèle (s'il peut même fonctionner !) n'aura aucun sens."
		},
		{
			text: " Le <i>tokenizer</i> et le modèle doivent toujours provenir du même <i>checkpoint</i>.",
			explain: "",
            correct: true
		},
		{
			text: " C'est une bonne pratique de faire du <i>padding</i> et de troncage avec le <i>tokenizer</i> car chaque entrée est un batch.",
			explain: "Il est vrai que chaque entrée de modèle doit être un batch. Cependant, tronquer ou compléter cette séquence n'aurait pas nécessairement de sens puisqu'il n'y en a qu'une seule. Il s'agit là de techniques permettant de mettre en batch une liste de phrases."
		}
	]}
/>

{/if}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter2/8.mdx" />

### Les <i>tokenizers</i>
https://huggingface.co/learn/course/fr/chapter2/4.md

# Les <i>tokenizers</i>

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter2/section4_pt.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter2/section4_pt.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter2/section4_pt.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter2/section4_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter2/section4_tf.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter2/section4_tf.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter2/section4_tf.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter2/section4_tf.ipynb"},
]} />

{/if}

<Youtube id="VFp38yj8h3A"/>

Les *tokenizers* sont l'un des principaux composants du pipeline de NLP. Ils ont un seul objectif : traduire le texte en données pouvant être traitées par le modèle. Les modèles ne pouvant traiter que des nombres, les *tokenizers* doivent convertir nos entrées textuelles en données numériques. Dans cette section, nous allons explorer ce qui se passe exactement dans le pipeline de tokénisation. 

Dans les tâches de NLP, les données traitées sont généralement du texte brut. Voici un exemple de ce type de texte :


```
Jim Henson was a puppeteer # Jim Henson était un marionnettiste
```

Les modèles ne pouvant traiter que des nombres, nous devons trouver un moyen de convertir le texte brut en nombres. C'est ce que font les *tokenizers* et il existe de nombreuses façons de procéder. L'objectif est de trouver la représentation la plus significative, c'est-à-dire celle qui a le plus de sens pour le modèle, et si possible qui soit la plus petite.

Voyons quelques exemples d'algorithmes de tokénisation et essayons de répondre à certaines des questions que vous pouvez vous poser à ce sujet.

## <i>Tokenizer</i> basé sur les mots


<Youtube id="nhJxYji1aho"/>

Le premier type de *tokenizer* qui vient à l'esprit est celui basé sur les mots. Il est généralement très facile à utiliser et configurable avec seulement quelques règles. Il donne souvent des résultats décents. Par exemple, dans l'image ci-dessous, l'objectif est de diviser le texte brut en mots et de trouver une représentation numérique pour chacun d'eux :

<div class="flex justify-center">
  <img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/word_based_tokenization.svg" alt="An example of word-based tokenization."/>
  <img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/word_based_tokenization-dark.svg" alt="An example of word-based tokenization."/>
</div>

Il existe différentes façons de diviser le texte. Par exemple, nous pouvons utiliser les espaces pour segmenter le texte en mots en appliquant la fonction `split()` de Python :

```py
tokenized_text = "Jim Henson was a puppeteer".split()
print(tokenized_text)
```

```python out
['Jim', 'Henson', 'was', 'a', 'puppeteer'] # ['Jim', 'Henson', était, 'un', 'marionnettiste']
```

Il existe également des variantes des *tokenizers* basés sur les mots qui ont des règles supplémentaires pour la ponctuation. Avec ce type de *tokenizers* nous pouvons nous retrouver avec des « vocabulaires » assez larges, où un vocabulaire est défini par le nombre total de *tokens* indépendants que nous avons dans notre corpus.

Un identifiant est attribué à chaque mot, en commençant par 0 et en allant jusqu'à la taille du vocabulaire. Le modèle utilise ces identifiants pour identifier chaque mot.

Si nous voulons couvrir complètement une langue avec un *tokenizer* basé sur les mots, nous devons avoir un identifiant pour chaque mot de la langue que nous traitons, ce qui génère une énorme quantité de *tokens*. Par exemple, il y a plus de 500 000 mots dans la langue anglaise. Ainsi pour associer chaque mot à un identifiant, nous devrions garder la trace d'autant d'identifiants. De plus, des mots comme « chien » sont représentés différemment de mots comme « chiens ». Le modèle n'a initialement aucun moyen de savoir que « chien » et « chiens » sont similaires : il identifie les deux mots comme non apparentés. Il en va de même pour d'autres mots similaires, comme « maison » et « maisonnette » que le modèle ne considérera pas comme similaires au départ.

Enfin, nous avons besoin d'un *token* personnalisé pour représenter les mots qui ne font pas partie de notre vocabulaire. C'est ce qu'on appelle le *token* « inconnu » souvent représenté par « [UNK] » (de l’anglais « unknown ») ou « &lt;unk&gt ; ». C'est généralement un mauvais signe si vous constatez que le *tokenizer* produit un nombre important de ce jeton spécial. Cela signifie qu’il n'a pas été en mesure de récupérer une représentation sensée d'un mot et que vous perdez des informations en cours de route. L'objectif de l'élaboration du vocabulaire est de faire en sorte que le *tokenizer* transforme le moins de mots possible en *token* inconnu.

Une façon de réduire la quantité de *tokens* inconnus est d'aller un niveau plus profond, en utilisant un *tokenizer* basé sur les caractères.


## <i>Tokenizer</i>  basé sur les caractères

<Youtube id="ssLq_EK2jLE"/>

Les *tokenizers* basés sur les caractères divisent le texte en caractères, plutôt qu'en mots. Cela présente deux avantages principaux :

- le vocabulaire est beaucoup plus petit
- il y a beaucoup moins de *tokens* hors vocabulaire (inconnus) puisque chaque mot peut être construit à partir de caractères.

Mais là aussi, des questions se posent concernant les espaces et la ponctuation :


<div class="flex justify-center">
  <img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/character_based_tokenization.svg" alt="An example of character-based tokenization."/>
  <img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/character_based_tokenization-dark.svg" alt="An example of character-based tokenization."/>
</div>

Cette approche n'est pas non plus parfaite. Puisque la représentation est maintenant basée sur des caractères plutôt que sur des mots, on pourrait dire intuitivement qu’elle est moins significative : chaque caractère ne signifie pas grand-chose en soi, alors que c'est le cas pour les mots. Toutefois, là encore, cela diffère selon la langue. En chinois, par exemple, chaque caractère est porteur de plus d'informations qu'un caractère dans une langue latine.

Un autre élément à prendre en compte est que nous nous retrouverons avec une très grande quantité de *tokens* à traiter par notre modèle. Alors qu’avec un *tokenizer* basé sur les mots, pour un mot donné on aurait qu'un seul *token*, avec un *tokenizer* basé sur les caractères, cela peut facilement se transformer en 10 *tokens* voire plus.

Pour obtenir le meilleur des deux mondes, nous pouvons utiliser une troisième technique qui combine les deux approches : la *tokénisation en sous-mots*.


## Tokénisation en sous-mots

<Youtube id="zHvTiHr506c"/>

Les algorithmes de tokenisation en sous-mots reposent sur le principe selon lequel les mots fréquemment utilisés ne doivent pas être divisés en sous-mots plus petits, mais les mots rares doivent être décomposés en sous-mots significatifs.

Par exemple, le mot « maisonnette » peut être considéré comme un mot rare et peut être décomposé en « maison » et « ette ». Ces deux mots sont susceptibles d'apparaître plus fréquemment en tant que sous-mots autonomes, alors qu'en même temps le sens de « maison » est conservé par le sens composite de « maison » et « ette ».

Voici un exemple montrant comment un algorithme de tokenisation en sous-mots tokeniserait la séquence « *Let's do tokenization* ! » :


<div class="flex justify-center">
  <img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/bpe_subword.svg" alt="A subword tokenization algorithm."/>
  <img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/bpe_subword-dark.svg" alt="A subword tokenization algorithm."/>
</div>

Ces sous-mots finissent par fournir beaucoup de sens sémantique. Par exemple, ci-dessus, « *tokenization* » a été divisé en « *token* » et « *ization* » : deux *tokens* qui ont un sens sémantique tout en étant peu encombrants (seuls deux *tokens* sont nécessaires pour représenter un long mot). Cela nous permet d'avoir une couverture relativement bonne avec de petits vocabulaires et presque aucun *token* inconnu.

Cette approche est particulièrement utile dans les langues agglutinantes comme le turc, où l'on peut former des mots complexes (presque) arbitrairement longs en enchaînant des sous-mots.


### Et plus encore !

Il existe de nombreuses autres techniques. Pour n'en citer que quelques-unes :

- le *Byte-level BPE* utilisé par exemple dans le GPT-2
- le *WordPiece* utilisé par exemple dans BERT
- *SentencePiece* ou *Unigram*, utilisés dans plusieurs modèles multilingues.

Vous devriez maintenant avoir une connaissance suffisante du fonctionnement des tokenizers pour commencer à utiliser l'API.


## Chargement et sauvegarde

Le chargement et la sauvegarde des *tokenizers* est aussi simple que pour les modèles. En fait, c'est basé sur les deux mêmes méthodes : `from_pretrained()` et `save_pretrained()`. Ces méthodes vont charger ou sauvegarder l'algorithme utilisé par le *tokenizer* (un peu comme l'*architecture* du modèle) ainsi que son vocabulaire (un peu comme les *poids* du modèle).

Le chargement du *tokenizer* de BERT entraîné avec le même *checkpoint* que BERT se fait de la même manière que le chargement du modèle, sauf que nous utilisons la classe `BertTokenizer` :


```py
from transformers import BertTokenizer

tokenizer = BertTokenizer.from_pretrained("bert-base-cased")
```

{#if fw === 'pt'}
Similaire à `AutoModel`, la classe `AutoTokenizer` récupère la classe de *tokenizer* appropriée dans la bibliothèque basée sur le nom du *checkpoint*. Elle peut être utilisée directement avec n'importe quel *checkpoint* :

{:else}
Similaire à `TFAutoModel`, la classe `AutoTokenizer` récupère la classe de *tokenizer* appropriée dans la bibliothèque basée sur le nom du *checkpoint*. Elle peut être utilisée directement avec n'importe quel *checkpoint* :

{/if}

```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
```

Nous pouvons à présent utiliser le *tokenizer* comme indiqué dans la section précédente :

```python
tokenizer("Using a Transformer network is simple")
```

```python out
{'input_ids': [101, 7993, 170, 11303, 1200, 2443, 1110, 3014, 102],
 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0],
 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]}
```

La sauvegarde d'un tokenizer est identique à celle d'un modèle :

```py
tokenizer.save_pretrained("directory_on_my_computer")
```

Nous parlerons plus en détail des `token_type_ids` au [chapitre 3](/course/fr/chapter3) et nous expliquerons la clé `attention_mask` un peu plus tard. Tout d'abord, voyons comment les `input_ids` sont générés. Pour ce faire, nous devons examiner les méthodes intermédiaires du *tokenizer*.

## Encodage


<Youtube id="Yffk5aydLzg"/>

La traduction d'un texte en chiffres est connue sous le nom d’*encodage*. L'encodage se fait en deux étapes : la tokenisation, suivie de la conversion en identifiants d'entrée.

Comme nous l'avons vu, la première étape consiste à diviser le texte en mots (ou parties de mots, symboles de ponctuation, etc.), généralement appelés *tokens*. De nombreuses règles peuvent régir ce processus. C'est pourquoi nous devons instancier le *tokenizer* en utilisant le nom du modèle afin de nous assurer que nous utilisons les mêmes règles que celles utilisées lors du pré-entraînement du modèle.

La deuxième étape consiste à convertir ces *tokens* en nombres afin de construire un tenseur à partir de ceux-ci ainsi que de les transmettre au modèle. Pour ce faire, le *tokenizer* possède un *vocabulaire*, qui est la partie que nous téléchargeons lorsque nous l'instancions avec la méthode `from_pretrained()`. Encore une fois, nous devons utiliser le même vocabulaire que celui utilisé lors du pré-entraînement du modèle.

Pour mieux comprendre les deux étapes, nous allons les explorer séparément. A noter que nous utilisons des méthodes effectuant séparément des parties du pipeline de tokenisation afin de montrer les résultats intermédiaires de ces étapes. Néanmoins, en pratique, il faut appeler le *tokenizer* directement sur vos entrées (comme indiqué dans la section 2).

### Tokenisation

Le processus de tokenisation est effectué par la méthode `tokenize()` du *tokenizer* :


```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")

sequence = "Using a Transformer network is simple"
tokens = tokenizer.tokenize(sequence)

print(tokens)
```

La sortie de cette méthode est une liste de chaînes de caractères ou de *tokens* :

```python out
['Using', 'a', 'transform', '##er', 'network', 'is', 'simple']
```

Ce *tokenizer* est un *tokenizer* de sous-mots : il découpe les mots jusqu'à obtenir des *tokens* qui peuvent être représentés par son vocabulaire. C'est le cas ici avec `transformer` qui est divisé en deux *tokens* : `transform` et `##er`.

### De <i>tokens</i> aux identifiants d'entrée

La conversion en identifiants d'entrée est gérée par la méthode `convert_tokens_to_ids()` du *tokenizer* :


```py
ids = tokenizer.convert_tokens_to_ids(tokens)

print(ids)
```

```python out
[7993, 170, 11303, 1200, 2443, 1110, 3014]
```

Une fois converties en tenseur dans le *framework* approprié, ces sorties peuvent ensuite être utilisées comme entrées d'un modèle, comme nous l'avons vu précédemment dans ce chapitre.

<Tip>

✏️ **Essayez !** Reproduisez les deux dernières étapes (tokénisation et conversion en identifiants d'entrée) sur les phrases des entrées que nous avons utilisées dans la section 2 (« <i>I've been waiting for a HuggingFace course my whole life.</i> » et « <i>I hate this so much!</i> »). Vérifiez que vous obtenez les mêmes identifiants d'entrée que nous avons obtenus précédemment !

</Tip>

## Décodage

Le *décodage* va dans l'autre sens : à partir d'indices du vocabulaire nous voulons obtenir une chaîne de caractères. Cela peut être fait avec la méthode `decode()` comme suit :


```py
decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014])
print(decoded_string)
```

```python out
'Using a Transformer network is simple'
```

Notez que la méthode `decode` non seulement reconvertit les indices en *tokens* mais regroupe également les *tokens* faisant partie des mêmes mots. Le but étant de produire une phrase lisible. Ce comportement sera extrêmement utile lorsque dans la suite du cours nous utiliserons des modèles pouvant produire du nouveau texte (soit du texte généré à partir d'un *prompt*, soit pour des problèmes de séquence à séquence comme la traduction ou le résumé de texte).

Vous devriez maintenant comprendre les opérations atomiques qu'un *tokenizer* peut gérer : tokenisation, conversion en identifiants, et reconversion des identifiants en chaîne de caractères. Cependant, nous n'avons fait qu'effleurer la partie émergée de l'iceberg. Dans la section suivante, nous allons pousser notre approche jusqu'à ses limites et voir comment les surmonter.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter2/4.mdx" />

### Utilisation de base terminée !
https://huggingface.co/learn/course/fr/chapter2/7.md

# Utilisation de base terminée !

<CourseFloatingBanner
    chapter={2}
    classNames="absolute z-10 right-0 top-0"
/>

Bravo à vous pour avoir suivi le cours jusqu'ici ! Pour récapituler, dans ce chapitre vous avez :
- appris les blocs de construction de base d'un *transformer*,
- appris ce qui constitue un pipeline de tokenisation,
- vu comment utiliser un *transformer* en pratique,
- appris comment tirer parti d'un *tokenizer* pour convertir du texte en tenseurs compréhensibles par le modèle,
- configurer ensemble un *tokenizer* et un modèle afin de passer du texte aux prédictions,
- appris les limites des identifiants d'entrée et ce que sont que les masques d'attention,
- joué avec des méthodes de *tokenizer* polyvalentes et configurables.

À partir de maintenant, vous devriez être en mesure de naviguer librement dans la documentation 🤗 *Transformers*. Le vocabulaire vous semblera familier et vous avez vu les méthodes que vous utiliserez la plupart du temps.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter2/7.mdx" />

### Derrière le pipeline
https://huggingface.co/learn/course/fr/chapter2/2.md

# Derrière le pipeline

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter2/section2_pt.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter2/section2_pt.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter2/section2_pt.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter2/section2_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter2/section2_tf.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter2/section2_tf.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter2/section2_tf.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter2/section2_tf.ipynb"},
]} />

{/if}

<Tip>
Il s'agit de la première section dont le contenu est légèrement différent selon que vous utilisez PyTorch ou TensorFlow. Cliquez sur le bouton situé au-dessus du titre pour sélectionner la plateforme que vous préférez !
</Tip>

{#if fw === 'pt'}
<Youtube id="1pedAIvTWXk"/>
{:else}
<Youtube id="wVN12smEvqg"/>
{/if}

Commençons par un exemple complet en regardant ce qui s'est passé en coulisses lorsque nous avons exécuté le code suivant dans le [chapitre 1](/course/chapter1) :

```python
from transformers import pipeline

classifier = pipeline("sentiment-analysis")
classifier(
    [
        "I've been waiting for a HuggingFace course my whole life.",
        # J'ai attendu un cours de HuggingFace toute ma vie.
        "I hate this so much!",  # Je déteste tellement ça !
    ]
)
```

la sortie :

```python out
[{'label': 'POSITIVE', 'score': 0.9598047137260437},
 {'label': 'NEGATIVE', 'score': 0.9994558095932007}]
```

Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), ce pipeline regroupe trois étapes : le prétraitement, le passage des entrées dans le modèle et le post-traitement.

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/full_nlp_pipeline.svg" alt="The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/full_nlp_pipeline-dark.svg" alt="The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head."/>
</div>

Passons rapidement en revue chacun de ces éléments.

## Prétraitement avec un <i>tokenizer</i>

Comme d'autres réseaux de neurones, les *transformers* ne peuvent pas traiter directement le texte brut, donc la première étape de notre pipeline est de convertir les entrées textuelles en nombres afin que le modèle puisse les comprendre. Pour ce faire, nous utilisons un *tokenizer*, qui sera responsable de :
- diviser l'entrée en mots, sous-mots, ou symboles (comme la ponctuation) qui sont appelés *tokens*,
- associer chaque *token* à un nombre entier,
- ajouter des entrées supplémentaires qui peuvent être utiles au modèle.

Tout ce prétraitement doit être effectué exactement de la même manière que celui appliqué lors du pré-entraînement du modèle. Nous devons donc d'abord télécharger ces informations depuis le [*Hub*](https://huggingface.co/models). Pour ce faire, nous utilisons la classe `AutoTokenizer` et sa méthode `from_pretrained()`. En utilisant le nom du *checkpoint* de notre modèle, elle va automatiquement récupérer les données associées au *tokenizer* du modèle et les mettre en cache (afin qu'elles ne soient téléchargées que la première fois que vous exécutez le code ci-dessous).

Puisque le *checkpoint* par défaut du pipeline `sentiment-analysis` (analyse de sentiment) est `distilbert-base-uncased-finetuned-sst-2-english` (vous pouvez voir la carte de ce modèle [ici](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)), nous exécutons ce qui suit :

```python
from transformers import AutoTokenizer

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
```

Une fois que nous avons le *tokenizer* nous pouvons lui passer directement nos phrases et obtenir un dictionnaire prêt à être donné à notre modèle ! La seule chose qui reste à faire est de convertir en tenseurs la liste des identifiants d'entrée.

Vous pouvez utiliser 🤗 *Transformers* sans avoir à vous soucier du *framework* utilisé comme *backend*. Il peut s'agir de PyTorch, de TensorFlow ou de Flax pour certains modèles. Cependant, les *transformers* n'acceptent que les *tenseurs* en entrée. Si c'est la première fois que vous entendez parler de tenseurs, vous pouvez les considérer comme des tableaux NumPy. Un tableau NumPy peut être un scalaire (0D), un vecteur (1D), une matrice (2D), ou avoir davantage de dimensions. Les tenseurs des autres *frameworks* d'apprentissage machine se comportent de manière similaire et sont généralement aussi simples à instancier que les tableaux NumPy.

Pour spécifier le type de tenseurs que nous voulons récupérer (PyTorch, TensorFlow, ou simplement NumPy), nous utilisons l'argument `return_tensors` :

{#if fw === 'pt'}
```python
raw_inputs = [
    "I've been waiting for a HuggingFace course my whole life.",
    # J'ai attendu un cours de HuggingFace toute ma vie.
    "I hate this so much!",  # Je déteste tellement ça !
]
inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt")
print(inputs)
```
{:else}
```python
raw_inputs = [
    "I've been waiting for a HuggingFace course my whole life.",
    # J'ai attendu un cours de HuggingFace toute ma vie.
    "I hate this so much!",  # Je déteste tellement ça !
]
inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf")
print(inputs)
```
{/if}

Ne vous préoccupez pas encore du remplissage (*padding*) et de la troncature, nous les expliquerons plus tard. Les principales choses à retenir ici sont que vous pouvez passer une phrase ou une liste de phrases, ainsi que spécifier le type de tenseurs que vous voulez récupérer (si aucun type n'est passé, par défaut vous obtiendrez une liste de listes comme résultat).

{#if fw === 'pt'}

Voici à quoi ressemblent les résultats sous forme de tenseurs PyTorch :

```python out
{
    'input_ids': tensor([
        [  101,  1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662, 12172, 2607,  2026,  2878,  2166,  1012,   102],
        [  101,  1045,  5223,  2023,  2061,  2172,   999,   102,     0,     0,     0,     0,     0,     0,     0,     0]
    ]), 
    'attention_mask': tensor([
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    ])
}
```
{:else}

Voici à quoi ressemblent les résultats sous forme de tenseurs TensorFlow :

```python out
{
    'input_ids': <tf.Tensor: shape=(2, 16), dtype=int32, numpy=
        array([
            [  101,  1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662, 12172,  2607,  2026,  2878,  2166,  1012,   102],
            [  101,  1045,  5223,  2023,  2061,  2172,   999,   102,     0,     0,     0,     0,     0,     0,     0,     0]
        ], dtype=int32)>, 
    'attention_mask': <tf.Tensor: shape=(2, 16), dtype=int32, numpy=
        array([
            [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
            [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        ], dtype=int32)>
}
```
{/if}

La sortie elle-même est un dictionnaire contenant deux clés : `input_ids` et `attention_mask`. `input_ids` contient deux lignes d'entiers (une pour chaque phrase) qui sont les identifiants uniques des *tokens* dans chaque phrase. Nous expliquerons ce qu'est l'`attention_mask` plus tard dans ce chapitre. 

## Passage au modèle

{#if fw === 'pt'}
Nous pouvons télécharger notre modèle pré-entraîné de la même manière que nous l'avons fait avec notre *tokenizer*. 🤗 *Transformers* fournit une classe `AutoModel` qui possède également une méthode `from_pretrained()` :

```python
from transformers import AutoModel

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModel.from_pretrained(checkpoint)
```
{:else}
Nous pouvons télécharger notre modèle prétraîné de la même manière que nous l'avons fait avec notre *tokenizer*. 🤗 *Transformers* fournit une classe `TFAutoModel` qui possède également une méthode `from_pretrained()` :

```python
from transformers import TFAutoModel

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = TFAutoModel.from_pretrained(checkpoint)
```
{/if}

Dans cet extrait de code, nous avons téléchargé le même *checkpoint* que nous avons utilisé dans notre pipeline auparavant (il devrait en fait avoir déjà été mis en cache) et instancié un modèle avec lui.

Cette architecture ne contient que le module de *transformer* de base : étant donné certaines entrées, il produit ce que nous appellerons des *états cachés*, également connus sous le nom de *caractéristiques*. 
Pour chaque entrée du modèle, nous récupérons un vecteur en grande dimension représentant la **compréhension contextuelle de cette entrée par le *transformer***.

Si cela n'a pas de sens, ne vous inquiétez pas. Nous expliquons tout plus tard.

Bien que ces états cachés puissent être utiles en eux-mêmes, ils sont généralement les entrées d'une autre partie du modèle, connue sous le nom de *tête*. Dans le [chapitre 1](/course/fr/chapter1), les différentes tâches auraient pu être réalisées avec la même architecture mais en ayant chacune d'elles une tête différente.

### Un vecteur de grande dimension ?

Le vecteur produit en sortie par le *transformer* est généralement de grande dimension. Il a généralement trois dimensions :

- **la taille du lot** : le nombre de séquences traitées à la fois (2 dans notre exemple),
- **la longueur de la séquence** : la longueur de la représentation numérique de la séquence (16 dans notre exemple),
- **la taille cachée** : la dimension du vecteur de chaque entrée du modèle.

On dit qu'il est de « grande dimension » en raison de la dernière valeur. La taille cachée peut être très grande (généralement 768 pour les petits modèles et pour les grands modèles cela peut atteindre 3072 voire plus).

Nous pouvons le constater si nous alimentons notre modèle avec les entrées que nous avons prétraitées :


{#if fw === 'pt'}
```python
outputs = model(**inputs)
print(outputs.last_hidden_state.shape)
```

```python out
torch.Size([2, 16, 768])
```
{:else}
```py
outputs = model(inputs)
print(outputs.last_hidden_state.shape)
```

```python out
(2, 16, 768)
```
{/if}

Notez que les sorties des modèles de la bibliothèque 🤗 *Transformers* se comportent comme des `namedtuples` ou des dictionnaires. Vous pouvez accéder aux éléments par attributs (comme nous l'avons fait), par clé (`outputs["last_hidden_state"]`), ou même par l’index si vous savez exactement où se trouve la chose que vous cherchez (`outputs[0]`).

### Les têtes des modèles : donner du sens aux chiffres
Les têtes des modèles prennent en entrée le vecteur de grande dimension des états cachés et le projettent sur une autre dimension. Elles sont généralement composées d'une ou de quelques couches linéaires :
<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/transformer_and_head.svg" alt="A Transformer network alongside its head."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/transformer_and_head-dark.svg" alt="A Transformer network alongside its head."/>
</div>

La sortie du *transformer* est envoyée directement à la tête du modèle pour être traitée.
Dans ce diagramme, le modèle est représenté par sa couche d’enchâssement et les couches suivantes. La couche d’enchâssement convertit chaque identifiant d'entrée dans l'entrée tokenisée en un vecteur qui représente le *token* associé. Les couches suivantes manipulent ces vecteurs en utilisant le mécanisme d'attention pour produire la représentation finale des phrases.
Il existe de nombreuses architectures différentes disponibles dans la bibliothèque 🤗 *Transformers*, chacune étant conçue autour de la prise en charge d'une tâche spécifique. En voici une liste non exhaustive :
- `*Model` (récupérer les états cachés)
- `*ForCausalLM`
- `*ForMaskedLM`
- `*ForMultipleChoice`
- `*ForQuestionAnswering`
- `*ForSequenceClassification`
- `*ForTokenClassification`
- et autres 🤗

{#if fw === 'pt'}
Pour notre exemple, nous avons besoin d'un modèle avec une tête de classification de séquence (pour pouvoir classer les phrases comme positives ou négatives). Donc, nous n'utilisons pas réellement la classe `AutoModel` mais plutôt `AutoModelForSequenceClassification` :
```python
from transformers import AutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
outputs = model(**inputs)
```
{:else}
Pour notre exemple, nous avons besoin d'un modèle avec une tête de classification de séquence (pour pouvoir classer les phrases comme positives ou négatives). Donc, nous n'utilisons pas réellement la classe ` TFAutoModel` mais plutôt ` TFAutoModelForSequenceClassification` :
```python
from transformers import TFAutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)
outputs = model(inputs)
```
{/if}

Maintenant, si nous examinons la forme de nos entrées, la dimensionnalité est beaucoup plus faible. La tête du modèle prend en entrée les vecteurs de grande dimension que nous avons vus précédemment et elle produit des vecteurs contenant deux valeurs (une par étiquette) :
```python
print(outputs.logits.shape)
```

{#if fw === 'pt'}
```python out
torch.Size([2, 2])
```
{:else}
```python out
(2, 2)
```
{/if}

Comme nous n'avons que deux phrases et deux étiquettes, le résultat que nous obtenons est de forme 2 x 2

## Post-traitement de la sortie

Les valeurs que nous obtenons en sortie de notre modèle n'ont pas nécessairement de sens en elles-mêmes. Jetons-y un coup d’œil  :

```python
print(outputs.logits)
```

{#if fw === 'pt'}
```python out
tensor([[-1.5607,  1.6123],
        [ 4.1692, -3.3464]], grad_fn=<AddmmBackward>)
```
{:else}
```python out
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
    array([[-1.5606991,  1.6122842],
           [ 4.169231 , -3.3464472]], dtype=float32)>
```
{/if}

Notre modèle a prédit `[-1.5607, 1.6123]` pour la première phrase et `[ 4.1692, -3.3464]` pour la seconde. Ce ne sont pas des probabilités mais des *logits*, les scores bruts, non normalisés, produits par la dernière couche du modèle. Pour être convertis en probabilités, ils doivent passer par une couche [SoftMax](https://fr.wikipedia.org/wiki/Fonction_softmax) (tous les modèles de la bibliothèque 🤗 *Transformers* sortent les logits car la fonction de perte de l'entraînement fusionne généralement la dernière fonction d'activation, comme la SoftMax, avec la fonction de perte réelle, comme l'entropie croisée) :

{#if fw === 'pt'}
```py
import torch

predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
print(predictions)
```
{:else}
```py
import tensorflow as tf

predictions = tf.math.softmax(outputs.logits, axis=-1)
print(predictions)
```
{/if}

{#if fw === 'pt'}
```python out
tensor([[4.0195e-02, 9.5980e-01],
        [9.9946e-01, 5.4418e-04]], grad_fn=<SoftmaxBackward>)
```
{:else}
```python out
tf.Tensor(
[[4.01951671e-02 9.59804833e-01]
 [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32)
```
{/if}

Maintenant nous pouvons voir que le modèle a prédit `[0.0402, 0.9598]` pour la première phrase et `[0.9995, 0.0005]` pour la seconde. Ce sont des scores de probabilité reconnaissables.

Pour obtenir les étiquettes correspondant à chaque position, nous pouvons inspecter l'attribut `id2label` de la configuration du modèle (plus de détails dans la section suivante) :

```python
model.config.id2label
```

```python out
{0: 'NEGATIVE', 1: 'POSITIVE'}
```

Nous pouvons maintenant conclure que le modèle a prédit ce qui suit :
 
- première phrase : NEGATIVE: 0.0402, POSITIVE: 0.9598
- deuxième phrase : NEGATIVE: 0.9995, POSITIVE: 0.0005

Nous avons reproduit avec succès les trois étapes du pipeline : prétraitement avec les *tokenizers*, passage des entrées dans le modèle et post-traitement ! Prenons maintenant le temps de nous plonger plus profondément dans chacune de ces étapes.

<Tip>

✏️ **Essayez !** Choisissez deux (ou plus) textes de votre choix (en anglais) et faites-les passer par le pipeline `sentiment-analysis`. Reproduisez ensuite vous-même les étapes vues ici et vérifiez que vous obtenez les mêmes résultats !
  
</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter2/2.mdx" />

### Introduction
https://huggingface.co/learn/course/fr/chapter0/1.md

# Introduction

Bienvenue au cours d'Hugging Face ! Cette introduction est là pour vous guider dans la mise en place d'un environnement de travail. Si vous venez de commencer le cours, nous vous recommandons de consulter d'abord le [chapitre 1](/course/fr/chapter1) puis de revenir ici et de configurer votre environnement afin de pouvoir essayer le code vous-même.

Toutes les bibliothèques que nous utiliserons dans ce cours sont disponibles sous forme de *packages* Python. Nous allons donc vous montrer comment configurer un environnement Python et installer les bibliothèques spécifiques dont vous aurez besoin.

Nous aborderons deux façons de configurer votre environnement de travail : soit en utilisant un *notebook* Google Colab, soit en utilisant un environnement virtuel Python. N'hésitez pas à choisir celle qui vous convient le mieux. Pour les débutants, nous vous recommandons vivement de commencer en utilisant un *notebook* Google Colab.

Notez que nous ne couvrirons pas le système Windows. Si vous travaillez sous Windows, nous vous recommandons de suivre le cours en utilisant un *notebook* Google Colab. Si vous utilisez une distribution Linux ou macOS, vous pouvez utiliser l'une des deux approches décrites ci-dessous.

La plupart du cours repose sur le fait que vous ayez un compte Hugging Face. Si vous n'en disposez pas d'un, nous vous recommandons d'en créer un dès maintenant : [créer un compte](https://huggingface.co/join).

## Utilisation d'un <i>notebook</i> Google Colab

L'utilisation d'un *notebook* Google Colab est la configuration la plus simple possible. Démarrez un *notebook* dans votre navigateur et passez directement au codage !

Si vous n'êtes pas familier avec Colab, nous vous recommandons de commencer par suivre l'[introduction](https://colab.research.google.com/notebooks/intro.ipynb). Colab vous permet d'utiliser du matériel comme les GPUs ou les TPUs et est gratuit pour les petites charges de travail.

Une fois que vous vous sentez suffisamment à l'aise avec Colab, créez un nouveau *notebook* et commencez à le configurer :

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter0/new_colab.png" alt="An empty colab notebook" width="80%"/>
</div>

L'étape suivante consiste à installer les bibliothèques que nous allons utiliser dans ce cours. Nous utiliserons `pip` pour l'installation qui est le gestionnaire de *packages* pour Python. Dans les *notebooks*, vous pouvez exécuter des commandes système en les faisant précéder du caractère `!`. Vous pouvez donc installer la bibliothèque 🤗 *Transformers* comme suit :

```
!pip install transformers
```

Vous pouvez vous assurer que le paquet a été correctement installé en l'important dans votre runtime Python :

```
import transformers
```

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter0/install.gif" alt="A gif showing the result of the two commands above: installation and import" width="80%"/>
</div>

Cela installe une version très légère de 🤗 *Transformers*. En particulier, aucun *framework* d'apprentissage automatique spécifique (comme PyTorch ou TensorFlow) n'est installé. Comme nous utiliserons de nombreuses fonctionnalités différentes de la bibliothèque, nous recommandons d'installer la version de développement qui est livrée avec toutes les dépendances requises pour à peu près tous les cas d'utilisation imaginables :

```
!pip install transformers[sentencepiece]
```

Cela prendra un peu de temps, mais vous serez alors prêt pour le reste du cours !


## Utilisation d'un environnement virtuel Python

Si vous préférez utiliser un environnement virtuel Python, la première étape consiste à installer Python sur votre système. Nous vous recommandons de suivre [ce guide](https://realpython.com/installing-python/) pour commencer.

Une fois Python installé, vous devriez être en mesure d'exécuter des commandes Python dans votre terminal. Vous pouvez commencer par exécuter la commande suivante pour vous assurer qu'il est correctement installé avant de passer aux étapes suivantes : `python --version`. Cette commande devrait vous indiquer la version de Python disponible sur votre système.

Lorsque vous exécutez une commande Python dans votre terminal, comme `python --version`, vous devez considérer le programme qui exécute votre commande comme la fonction « main » Python sur votre système. Nous vous recommandons de garder cette installation principale libre de tout *package* et de l'utiliser pour créer des environnements séparés pour chaque application sur laquelle vous travaillez. De cette façon, chaque application peut avoir ses propres dépendances et *packages*, et vous n'aurez pas à vous soucier de problèmes potentiels de compatibilité avec d'autres applications.

En Python, cela se fait avec les [*environnements virtuels*](https://docs.python.org/3/tutorial/venv.html), qui sont des arbres de répertoires autonomes contenant chacun une installation Python avec une version particulière de Python ainsi que tous les *packages* dont l'application a besoin. La création d'un tel environnement virtuel peut se faire à l'aide d'un certain nombre d'outils différents, mais nous utiliserons le *package* officiel de Python : [`venv`](https://docs.python.org/3/library/venv.html#module-venv).

Tout d'abord, créez le répertoire dans lequel vous souhaitez que votre application se trouve. Par exemple, vous pouvez créer un nouveau répertoire appelé *transformers-course* à la racine de votre répertoire personnel :
```
mkdir ~/transformers-course
cd ~/transformers-course
```

A l'intérieur de ce répertoire, créez un environnement virtuel en utilisant le module Python `venv` :

```
python -m venv .env
```

Vous devriez maintenant avoir un répertoire appelé *.env* dans votre dossier autrement vide :

```
ls -a
```

```out
.      ..    .env
```

Vous pouvez entrer et sortir de votre environnement virtuel avec les scripts `activate` et `deactivate` :

```
# Activate the virtual environment
source .env/bin/activate

# Deactivate the virtual environment
deactivate
```

Vous pouvez vous assurer que l'environnement est activé en exécutant la commande `which python` : si elle pointe vers l'environnement virtuel, alors vous l'avez activé avec succès !

```
which python
```

```out
/home/<user>/transformers-course/.env/bin/python
```

### Installation des dépendances

Comme dans la section précédente sur l'utilisation des instances Google Colab, vous devez maintenant installer les *packages* requis pour continuer. Encore une fois, vous pouvez installer la version de développement de 🤗 *Transformers* à l'aide du gestionnaire de packages `pip` :

```
pip install "transformers[sentencepiece]"
```

Vous êtes maintenant prêt !


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter0/1.mdx" />

### Résumé du chapitre
https://huggingface.co/learn/course/fr/chapter1/9.md

# Résumé du chapitre

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

Au cours de ce chapitre, vous avez vu comment approcher différents problèmes de NLP en utilisant la fonction `pipeline()` de la bibliothèque 🤗 *Transformers*. Vous avez également vu comment rechercher et utiliser des modèles dans le *Hub* ainsi que comment utiliser l'API d'inférence pour tester les modèles directement dans votre navigateur.

Nous avons pu aborder le fonctionnement des *transformers* de façon générale et parler de l'importance de l'apprentissage par transfert et du *finetuning*. Un point important est que vous pouvez utiliser l'architecture complète ou seulement l'encodeur ou le décodeur, selon le type de tâche que vous souhaitez résoudre. Le tableau suivant résume ceci :

| Modèle            | Exemples                                   | Tâches                                                                                       |
|-------------------|--------------------------------------------|----------------------------------------------------------------------------------------------|
| Encodeur          | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | Classification de phrase, reconnaissance d'entités nommées, extraction de question-réponse |
| Décodeur          | CTRL, GPT, GPT-2, Transformer XL           | Génération de texte                                                                          |
| Encodeur-décodeur | BART, T5, Marian, mBART                    | Résumé, traduction, génération de question-réponse                                           |

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter1/9.mdx" />

### Les modèles basés sur l'encodeur
https://huggingface.co/learn/course/fr/chapter1/5.md

# Les modèles basés sur l'encodeur

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

<Youtube id="MUqNwgPjJvQ" />

Les modèles basés sur l'encodeur utilisent uniquement l'encodeur d'un *transformer*. À chaque étape, les couches d'attention peuvent accéder à tous les mots de la phrase initiale. Ces modèles sont souvent caractérisés comme ayant une attention bidirectionnelle et sont souvent appelés *modèles d'auto-encodage*.

Le pré-entraînement de ces modèles se concentre généralement sur la modification d'une phrase donnée (par exemple, en masquant des mots aléatoires dans celle-ci) et en demandant au modèle de trouver ou de reconstruire la phrase initiale.

Ces modèles sont les plus adaptés pour des tâches qui requièrent une compréhension complète de la phrase, telles que la classification de phrases, la reconnaissance d'entités nommées (et plus généralement la classification de mots) et les questions-réponses extractives.

Les modèles les plus représentatifs de cette famille sont :

- [ALBERT](https://huggingface.co/transformers/model_doc/albert.html)
- [BERT](https://huggingface.co/transformers/model_doc/bert.html)
- [DistilBERT](https://huggingface.co/transformers/model_doc/distilbert.html)
- [ELECTRA](https://huggingface.co/transformers/model_doc/electra.html)
- [RoBERTa](https://huggingface.co/transformers/model_doc/roberta.html)


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter1/5.mdx" />

### Introduction
https://huggingface.co/learn/course/fr/chapter1/1.md

# Introduction

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

## Bienvenue au cours 🤗 !

<Youtube id="00GKzGyWFEs" />

Ce cours vous apprendra à utiliser les bibliothèques de NLP de l'écosystème [Hugging Face](https://huggingface.co/) : [🤗 *Transformers*](https://github.com/huggingface/transformers), [🤗 *Datasets*](https://github.com/huggingface/datasets), [🤗 *Tokenizers*](https://github.com/huggingface/tokenizers) et [🤗 *Accelerate*](https://github.com/huggingface/accelerate), ainsi que le [*Hub*](https://huggingface.co/models). C'est totalement gratuit et sans publicité.

## À quoi s'attendre ?

Voici un bref aperçu du cours :

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/summary.svg" alt="Bref aperçu du contenu du cours.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/summary-dark.svg" alt="Bref aperçu des différents chapitres du cours.">
</div>

- Les chapitres 1 à 4 présentent les principaux concepts de la bibliothèque 🤗 *Transformers*. À la fin de ces chapitres, vous serez familier avec le fonctionnement des *transformers* et vous saurez comment utiliser un modèle présent sur le [*Hub*](https://huggingface.co/models), le *finetuner* sur un jeu de données, et partager vos résultats sur le *Hub* !
- Les chapitres 5 à 8 présentent les bases des librairies 🤗 *Datasets* et 🤗 *Tokenizers* ainsi qu'une découverte des problèmes classiques de NLP. À la fin de ce chapitre, vous serez capable de résoudre les problèmes de NLP les plus communs par vous-même.
- Les chapitres 9 à 12 proposent d'aller plus loin et d'explorer comment les *transformers* peuvent être utilisés pour résoudre des problèmes de traitement de la parole et de vision par ordinateur. En suivant ces chapitres, vous apprendrez à construire et à partager vos modèles via des démonstrateurs, et vous serez capable d'optimiser ces modèles pour des environnements de production. Enfin, vous serez prêt à appliquer 🤗 *Transformers* à (presque) n'importe quel problème d'apprentissage automatique !

Ce cours :

* requiert un bon niveau en Python,
* se comprend mieux si vous avez déjà suivi un cours d'introduction à l'apprentissage profond comme [fast.ai's](https://www.fast.ai/), [*Practical Deep Learning for Coders*](https://course.fast.ai/) ou un des cours développés par [*DeepLearning.AI*](https://www.deeplearning.ai/),
* n'attend pas une connaissance appronfondie de [PyTorch](https://pytorch.org/) ou de [TensorFlow](https://www.tensorflow.org/), bien qu'être familiarisé avec l'un d'entre eux peut aider.

Après avoir terminé ce cours, nous vous recommandons de suivre la [Spécialisation en NLP](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh) dispensée par DeepLearning.AI, qui couvre une grande partie des modèles traditionnels de NLP comme le Bayésien naïf et les LSTMs qui sont importants à connaître !

## Qui sommes-nous ?

À propos des auteurs de ce cours :

[**Abubakar Abid**](https://huggingface.co/abidlabs) a obtenu son doctorat en apprentissage automatique appliqué à Stanford. Pendant son doctorat, il a fondé [Gradio](https://github.com/gradio-app/gradio), une bibliothèque Python *open source* qui a été utilisée pour construire plus de 600 000 démos d'apprentissage automatique. Gradio a été rachetée par Hugging Face, où Abubakar occupe désormais le poste de responsable de l'équipe d'apprentissage automatique.

[**Matthew Carrigan**](https://huggingface.co/Rocketknight1) est ingénieur en apprentissage machine chez Hugging Face. Il vit à Dublin en Irlande. Il a travaillé auparavant comme ingénieur en apprentissage machine chez Parse.ly et avant cela comme chercheur postdoctoral au Trinity College Dublin. Il ne croit pas que nous arrivions à l'*AGI* en mettant à l'échelle les architectures existantes mais a tout de même beaucoup d'espoir dans l'immortalité des robots.

[**Lysandre Debut**](https://huggingface.co/lysandre) est ingénieur en apprentissage machine chez Hugging Face et a travaillé sur la bibliothèque 🤗 *Transformers* depuis les premières phases de développement. Son but est de rendre le NLP accessible à tous en développant des outils disposant d'une API très simple.

[**Sylvain Gugger**](https://huggingface.co/sgugger) est ingénieur de recherche chez Hugging Face et un des principaux responsables de la bibliothèque 🤗 *Transformers*. Avant cela, il était chercheur en apprentissage machine chez fast.ai et a écrit le livre [*Deep Learning for Coders with fastai and PyTorch*](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/) avec Jeremy Howard. Son but est de rendre l'apprentissage profond plus accessible en développant et en améliorant des techniques permettant aux modèles d'apprendre rapidement sur des ressources limitées.

[**Dawood Khan**](https://huggingface.co/dawoodkhan82) est un ingénieur en apprentissage automatique chez Hugging Face. Il vient de New York et est diplômé en informatique de l’Université de New York. Après avoir travaillé comme ingénieur iOS pendant quelques années, Dawood a quitté son poste pour créer Gradio avec ses cofondateurs. Gradio a finalement été acquis par Hugging Face.

[**Merve Noyan**](https://huggingface.co/merve) est développeuse *advocate* chez Hugging Face et travaille à la création d'outils et de contenus visant à démocratiser l'apprentissage machine pour tous.

[**Lucile Saulnier**](https://huggingface.co/SaulLu) est ingénieure en apprentissage machine chez Hugging Face et travaille au développement et à l'implémentation de nombreux outils *open source*. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine du NLP comme l'entraînement collaboratif de modèles et le projet [BigScience](https://bigscience.huggingface.co/).

[**Lewis Tunstall**](https://huggingface.co/lewtun) est ingénieur en apprentissage machine chez Hugging Face et dévoué au développement d'outils *open source* avec la volonté de les rendre accessibles à une communauté plus large. Il est également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/).

[**Leandro von Werra**](https://huggingface.co/lvwerra) est ingénieur en apprentissage machine dans l'équipe *open source* d'Hugging Face et également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). Il a plusieurs années d'expérience dans l'industrie où il a pu déployer des projets de NLP en production et travailler sur toutes les étapes clefs du déploiement.


## FAQ

Voici quelques réponses aux questions fréquemment posées :

- **Suivre ce cours mène-t-il à une certification ?**  
Actuellement, nous n'avons pas de certification pour ce cours. Cependant, nous travaillons sur un programme de certification pour l'écosystème *Hugging Face*. Restez à l'écoute !

- **Combien de temps dois-je consacrer à ce cours ?**  
Chaque chapitre de ce cours est conçu pour être complété en une semaine, avec environ 6 à 8 heures de travail par semaine. Cependant, vous pouvez prendre tout le temps nécessaire pour le suivre.

- **Où puis-je poser une question si j'en ai une ?**  
Si vous avez une question sur l'une des sections du cours, il vous suffit de cliquer sur la bannière « *Ask a question* » en haut de la page pour être automatiquement redirigé vers la bonne section du [forum d’*Hugging Face*](https://discuss.huggingface.co/) :

<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/forum-button.png" alt="Link to the Hugging Face forums" width="75%">

Notez qu'une liste d'[idées de projets](https://discuss.huggingface.co/c/course/course-event/25) est également disponible sur le forum si vous souhaitez pratiquer davantage une fois le cours terminé.

- **Où puis-je obtenir le code du cours ?**  
Pour chaque section, vous pouvez cliquer sur la bannière en haut de la page pour exécuter son code dans *Google Colab* ou *Amazon SageMaker Studio Lab* :

<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/notebook-buttons.png" alt="Link to the Hugging Face course notebooks" width="75%">

A noter que pour la version en français du cours, deux choix s’offrent à vous lorsque vous cliquez sur la bannière. Le premier est de sélectionner le *notebook* utilisant des modèles en anglais. L’intérêt est qu’il s’agit de celui sur lequel sont basées les explications du cours (interprétation des résultats, etc.). Le second est de sélectionner le *notebook* utilisant des modèles en français. Il s’agit alors d’une proposition d’adaptation (un modèle parmi tous ceux existant en français est utilisé).

Si vous souhaitez accéder à l’ensemble des *notebooks* Jupyter du cours, il existe deux possibilités. La première est de cloner le dépôt [`huggingface/notebooks`](https://github.com/huggingface/notebooks) et de consulter les *notebooks* contenus dans le dossier *course*. La seconde est de générer les *notebooks* localement en suivant les instructions dans le *README* du dépôt [`course`](https://github.com/huggingface/course#-jupyter-notebooks) sur GitHub.

- **Comment puis-je contribuer au cours ?**  
Il existe de nombreuses façons de contribuer au cours ! Si vous trouvez une coquille ou un bug, veuillez ouvrir une « *Issue* » sur le dépôt [`course`](https://github.com/huggingface/course). Si vous souhaitez aider à traduire le cours dans votre langue maternelle, consultez les instructions [ici](https://github.com/huggingface/course#translating-the-course-into-your-language).

- **Quels ont été les choix retenus pour la traduction ?**  
Vous pouvez consulter le [glossaire](https://huggingface.co/course/fr/glossary/1) détaillant les choix retenus pour la traduction vers le français.

- **Peut-on réutiliser ce cours?**  
Bien sûr ! Le cours est publié sous la licence [Apache 2 license](https://www.apache.org/licenses/LICENSE-2.0.html). Cela signifie que vous devez créditer de manière appropriée, fournir un lien vers la licence et indiquer si des modifications ont été apportées. Vous pouvez le faire de toute manière raisonnable, mais pas d'une façon qui suggère que le distributeur de la licence vous approuve ou approuve votre utilisation. Si vous souhaitez citer le cours, veuillez utiliser le BibTeX suivant :

```
@misc{huggingfacecourse,
  author = {Hugging Face},
  title = {The Hugging Face Course, 2022},
  howpublished = "\url{https://huggingface.co/course}",
  year = {2022},
  note = "[Online; accessed <today>]"
}
```


## C'est parti !

Êtes-vous prêt à commencer ? Dans ce chapitre, vous apprendrez :
* à utiliser la fonction `pipeline()` pour résoudre des problèmes de NLP comme la génération de texte et la classification,
* l'architecture d'un *transformer*,
* comment faire la distinction entre les différentes architectures d'encodeur, de décodeur et d'encodeur-décodeur ainsi que leurs différents cas d'usage.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter1/1.mdx" />

### Que peuvent faire les <i>transformers</i> ?
https://huggingface.co/learn/course/fr/chapter1/3.md

# Que peuvent faire les <i>transformers</i> ?

<CourseFloatingBanner chapter={1}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter1/section3.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter1/section3.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter1/section3.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter1/section3.ipynb"},
]} />

Dans cette section, nous allons voir ce que peuvent faire les *transformers* et utiliser notre premier outil de la bibliothèque 🤗 *Transformers* : la fonction `pipeline()`.

<Tip>
👀 Vous voyez ce bouton <em>Open in Colab</em> en haut à droite ? Cliquez dessus pour ouvrir un <i>notebook</i> Colab avec tous les exemples de code de cette section. Ce bouton sera présent dans n'importe quelle section contenant des exemples de code.

Si vous souhaitez exécuter les codes en local, nous vous recommandons de jeter un œil au chapitre <a href="/course/fr/chapter0">configuration</a>.
</Tip>

## Les <i>transformers</i> sont partout !

Les *transformers* sont utilisés pour résoudre toute sorte de tâches de NLP comme celles mentionnées dans la section précédente. Voici quelques-unes des entreprises et organisations qui utilisent Hugging Face, les *transformers* et qui contribuent aussi à la communauté en partageant leurs modèles :

<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/companies.PNG" alt="Companies using Hugging Face" width="100%">

La bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers) fournit toutes les fonctionnalités nécessaires pour créer et utiliser les modèles partagés. Le [*Hub*](https://huggingface.co/models) contient des milliers de modèles pré-entraînés que n'importe qui peut télécharger et utiliser. Vous pouvez également transférer vos propres modèles vers le *Hub* !

<Tip>
	⚠️ Le <i>Hub</i> n'est pas limité aux <i>transformers</i>. Tout le monde peut partager n'importe quel modèle ou jeu de données s'il le souhaite ! <a href="https://huggingface.co/join">Créez un compte sur huggingface.co</a> pour bénéficier de toutes les fonctionnalités disponibles !
</Tip>

Avant de découvrir en détail comment les *transformers* fonctionnent, nous allons voir quelques exemples de comment ils peuvent être utilisés pour résoudre des problèmes intéressants de NLP.

## Travailler avec les pipelines

<Youtube id="tiZFewofSLM" />

L'outil le plus basique de la bibliothèque 🤗 *Transformers* est la fonction `pipeline()`. Elle relie un modèle avec ses étapes de pré-traitement et de post-traitement, permettant d'entrer n'importe quel texte et d'obtenir une réponse compréhensible :

```python
from transformers import pipeline

classifier = pipeline("sentiment-analysis")
classifier(
    "I've been waiting for a HuggingFace course my whole life."
)  # J'ai attendu un cours d'HuggingFace toute ma vie.
```

```python out
[{'label': 'POSITIVE', 'score': 0.9598047137260437}]
```

On peut même passer plusieurs phrases !

```python
classifier(
    [
        "I've been waiting for a HuggingFace course my whole life.",
        "I hate this so much!",
    ]  # « J'ai attendu un cours d'HuggingFace toute ma vie. »,  « Je déteste tellement ça ! »
)
```

```python out
[{'label': 'POSITIVE', 'score': 0.9598047137260437},
 {'label': 'NEGATIVE', 'score': 0.9994558095932007}]
```

Par défaut, ce pipeline sélectionne un modèle pré-entraîné qui a été spécifiquement entraîné pour l'analyse de sentiment en anglais. Le modèle est téléchargé et mis en cache lorsque vous créez l'objet `classifier`. Si vous réexécutez la commande, c'est le modèle mis en cache qui sera utilisé et il n'y a pas besoin de télécharger le modèle à nouveau.

Il y a trois étapes principales lorsque vous passez du texte à un pipeline :

1. le texte est prétraité pour qu'il ait un format compréhensible par le modèle,
2. les données prétraitées sont passées au modèle,
3. les prédictions du modèle sont post-traitées de sorte que vous puissiez les comprendre.


Voici une liste non-exhaustive des [pipelines disponibles](https://huggingface.co/transformers/main_classes/pipelines.html) :

- `feature-extraction` (pour obtenir la représentation vectorielle d'un texte)
- `fill-mask`
- `ner` (*named entity recognition* ou reconnaissance d'entités nommées en français)
- `question-answering`
- `sentiment-analysis`
- `summarization`
- `text-generation`
- `translation`
- `zero-shot-classification`

Regardons de plus près certains d'entre eux !

## <i>Zero-shot classification</i>

Nous allons commencer par nous attaquer à une tâche plus difficile où nous devons classer des textes qui n'ont pas été annotés. C'est un scénario très répandu dans les projets réels car l'annotation de textes est généralement longue et nécessite parfois une expertise dans un domaine. Pour ce cas d'usage, le pipeline `zero-shot-classification` est très puissant : il vous permet de spécifier les labels à utiliser pour la classification, de sorte que vous n'ayez pas à vous soucier des labels du modèle pré-entraîné. Nous avons déjà vu comment le modèle peut classer un texte comme positif ou négatif en utilisant ces deux labels mais il peut également classer le texte en utilisant n'importe quel autre ensemble de labels que vous souhaitez.

```python
from transformers import pipeline

classifier = pipeline("zero-shot-classification")
classifier(
    "This is a course about the Transformers library",
    # C'est un cours sur la bibliothèque Transformers
    candidate_labels=["education", "politics", "business"],
)
```

```python out
{'sequence': 'This is a course about the Transformers library', 
# C'est un cours sur la bibliothèque Transformers
 'labels': ['education', 'business', 'politics'],
 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]}
```

Ce pipeline est appelé _zero-shot_ car vous n'avez pas besoin d'entraîner spécifiquement le modèle sur vos données pour l'utiliser. Il peut directement retourner des scores de probabilité pour n'importe quel ensemble de labels que vous choisissez !

<Tip>

✏️ **Essayez !** Jouez avec vos propres séquences et labels et voyez comment le modèle fonctionne.

</Tip>


## Génération de texte

Maintenant, nous allons voir comment utiliser un pipeline pour générer du texte. L'idée principale ici est que vous fournissez seulement un extrait de texte qui va être complété par du texte généré automatiquement par le modèle. Cette fonction est similaire à la fonction de texte prédictif que l'on trouve sur de nombreux téléphones portables. La génération de texte implique de l'aléatoire, donc il est normal que vous n'obteniez pas les mêmes résultats que ceux présentés ci-dessous.

```python
from transformers import pipeline

generator = pipeline("text-generation")
generator(
    "In this course, we will teach you how to"
)  # Dans ce cours, nous vous enseignerons comment
```

```python out
[{'generated_text': 'In this course, we will teach you how to understand and use ' 
		    # Dans ce cours, nous vous enseignerons comment comprendre et utiliser
                    'data flow and data interchange when handling user data. We '
		    # flux de données et l'échange de données lors de la manipulation des données utilisateur. Nous 
                    'will be working with one or more of the most commonly used ' 
		    # travaillerons avec un ou plusieurs des plus couramment utilisés
                    'data flows — data flows of various types, as seen by the ' 
		    # flux de données - flux de données de différents types, tels qu'ils sont vus par
                    'HTTP'}] 
		    # HTTP
```

Il est possible de contrôler le nombre de séquences générées avec l'argument `num_return_sequences` et la longueur totale du texte généré avec l'argument `max_length`.

<Tip>

✏️ **Essayez !** Utilisez les arguments `num_return_sequences` et `max_length` pour générer deux phrases de 15 mots chacune.

</Tip>


## Utiliser n'importe quel modèle du <i>Hub</i> dans un pipeline

Les exemples précédents utilisaient le modèle par défaut pour la tâche en question mais vous pouvez aussi choisir un modèle particulier du *Hub* et l'utiliser dans un pipeline pour une tâche spécifique comme par exemple la génération de texte. Rendez-vous sur le [*Hub*](https://huggingface.co/models) et cliquez sur le *filtre* correspondant sur la gauche pour afficher seulement les modèles supportés pour cette tâche. Vous devriez arriver sur une page comme [celle-ci](https://huggingface.co/models?pipeline_tag=text-generation).

Essayons le modèle [`distilgpt2`](https://huggingface.co/distilgpt2) ! Voici comment charger le modèle dans le même pipeline que précédemment :

```python
from transformers import pipeline

generator = pipeline("text-generation", model="distilgpt2")
generator(
    "In this course, we will teach you how to",
    # Dans ce cours, nous vous enseignerons comment
    max_length=30,
    num_return_sequences=2,
)
```

```python out
[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' 
		    # Dans ce cours, nous vous enseignerons comment manipuler le monde et
                    'move your mental and physical capabilities to your advantage.'}, 
		    # utiliser vos capacités mentales et physiques à votre avantage.
 {'generated_text': 'In this course, we will teach you how to become an expert and ' 
		    # Dans ce cours, nous vous apprendrons comment devenir un expert et
                    'practice realtime, and with a hands on experience on both real ' 
		    # pratique en temps réel, et avec une expérience pratique à la fois sur de vrais
                    'time and real'}] 
		    # temps et réel
```

Vous pouvez améliorer votre recherche de modèle en cliquant sur les *filtres* de langue et choisir un modèle qui génère du texte dans une autre langue. Le *Hub* contient également des *checkpoints* pour des modèles multilingues qui supportent plusieurs langues.

Une fois que vous avez choisi un modèle, vous verrez que vous pouvez tester son fonctionnement en ligne directement. Cela vous permet de tester rapidement les capacités du modèle avant de le télécharger.

<Tip>

✏️ **Essayez !** Utilisez les filtres pour trouver un modèle de génération de texte pour une autre langue. N'hésitez pas à jouer avec le *widget* et l'utiliser dans un pipeline !

</Tip>

### L'API d'inférence

Tous les modèles peuvent être testé directement depuis votre navigateur en utilisant l'API d'inférence qui est disponible sur le site [Hugging Face](https://huggingface.co/). Vous pouvez jouer avec le modèle directement sur sa page en entrant du texte personnalisé et en regardant le modèle traiter les données d'entrée.

L'API d'inférence qui est utilisée par le *widget* est également disponible en tant que produit payant si vous avez besoin de l'API pour votre travail. Consultez la [page des prix](https://huggingface.co/pricing) pour plus de détails.

## Remplacement des mots manqués

Le prochain pipeline que vous allez essayer est celui de `fill-mask`. L'idée de cette tâche est de remplir les mots manqués d'un texte donné :

```python
from transformers import pipeline

unmasker = pipeline("fill-mask")
unmasker("This course will teach you all about <mask> models.", top_k=2)
```

```python out
[{'sequence': 'This course will teach you all about mathematical models.', 
# Ce cours vous apprendra tout sur les modèles mathématiques.
  'score': 0.19619831442832947,
  'token': 30412,
  'token_str': ' mathematical'},
 {'sequence': 'This course will teach you all about computational models.', 
 # Ce cours vous apprendra tout sur les modèles  de calcul.
  'score': 0.04052725434303284,
  'token': 38163,
  'token_str': ' computational'}]
```

L'argument `top_k` permet de contrôler le nombre de possibilités que vous souhaitez afficher. Notez que dans ce cas, le modèle remplace le mot spécial `<mask>`, qui est souvent appelé un *mot masqué*. D'autres modèles permettant de remplacer les mots manquants peuvent avoir des mots masqués différents, donc il est toujours bon de vérifier le mot masqué approprié lorsque vous comparez d'autres modèles. Une façon de le vérifier est de regarder le mot masqué utilisé dans l'outil de test de la page du modèle.

<Tip>

✏️ **Essayez !** Recherchez le modèle `bert-base-cased` sur le *Hub* et identifiez le mot masqué dans l'outil d'inférence. Que prédit le modèle pour la phrase dans notre exemple de pipeline au-dessus ?

</Tip>

## Reconnaissance d'entités nommées

La reconnaissance d'entités nommées ou NER (pour *Named Entity Recognition*) est une tâche où le modèle doit trouver les parties du texte d'entrée qui correspondent à des entités telles que des personnes, des lieux ou des organisations. Voyons un exemple :

```python
from transformers import pipeline

ner = pipeline("ner", grouped_entities=True)
ner(
    "My name is Sylvain and I work at Hugging Face in Brooklyn."
)  # Je m'appelle Sylvain et je travaille à Hugging Face à Brooklyn.
```

```python out
[{'entity_group': 'PER', 'score': 0.99816, 'word': 'Sylvain', 'start': 11, 'end': 18}, 
 {'entity_group': 'ORG', 'score': 0.97960, 'word': 'Hugging Face', 'start': 33, 'end': 45}, 
 {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57}
]
```

Nous pouvons voir que le modèle a correctement identifié Sylvain comme une personne (PER), Hugging Face comme une organisation (ORG) et Brooklyn comme un lieu (LOC).

Il est possible d'utiliser l'option `grouped_entities=True` lors de la création du pipeline pour regrouper les parties du texte qui correspondent à la même entité : ici le modèle à correctement regroupé `Hugging` et `Face` comme une seule organisation, même si le nom comporte plusieurs mots. En effet, comme nous allons voir dans le prochain chapitre, la prétraitement du texte sépare parfois certains mots en plus petites parties. Par exemple, `Sylvain` est séparé en quatre morceaux : `S`, `##yl`, `##va`, et `##in`. Dans l'étape de post-traitement, le pipeline a réussi à regrouper ces morceaux.

<Tip>

✏️ **Essayez !** Recherchez sur le *Hub* un modèle capable de reconnaître les différentes parties du langage (généralement abrégé en POS pour *Part-of-speech*) en anglais. Que prédit le modèle pour la phrase dans notre exemple du pipeline au-dessus ?

</Tip>

## Réponse à des questions

Le pipeline `question-answering` répond à des questions en utilisant des informations données en contexte :

```python
from transformers import pipeline

question_answerer = pipeline("question-answering")
question_answerer(
    question="Where do I work?",  # Où est-ce que je travaille ?
    context="My name is Sylvain and I work at Hugging Face in Brooklyn",
    # Je m'appelle Sylvain et je travaille à Hugging Face à Brooklyn.
)
```

```python out
{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'}
```

Notez que ce pipeline fonctionne par extraction d'information depuis le contexte fourni, il ne génère pas la réponse.

## Résumé

Le résumé est une tâche de réduction d'un texte en un texte plus court, tout en gardant tous (ou presque tous) les aspects importants référencés dans le texte. Voici un exemple :

```python
from transformers import pipeline

summarizer = pipeline("summarization")
summarizer(
    """
    America has changed dramatically during recent years. Not only has the number of 
    graduates in traditional engineering disciplines such as mechanical, civil, 
    electrical, chemical, and aeronautical engineering declined, but in most of 
    the premier American universities engineering curricula now concentrate on 
    and encourage largely the study of engineering science. As a result, there 
    are declining offerings in engineering subjects dealing with infrastructure, 
    the environment, and related issues, and greater concentration on high 
    technology subjects, largely supporting increasingly complex scientific 
    developments. While the latter is important, it should not be at the expense 
    of more traditional engineering.

    Rapidly developing economies such as China and India, as well as other 
    industrial countries in Europe and Asia, continue to encourage and advance 
    the teaching of engineering. Both China and India, respectively, graduate 
    six and eight times as many traditional engineers as does the United States. 
    Other industrial countries at minimum maintain their output, while America 
    suffers an increasingly serious decline in the number of engineering graduates 
    and a lack of well-educated engineers.
"""
)

"""
    L'Amérique a changé de façon spectaculaire au cours des dernières années. Non seulement le nombre de 
    diplômés dans les disciplines traditionnelles de l'ingénierie telles que le génie mécanique, civil, 
    l'électricité, la chimie et l'aéronautique a diminué, mais dans la plupart 
    des grandes universités américaines, les programmes d'études d'ingénierie se concentrent désormais sur 
    et encouragent largement l'étude des sciences de l'ingénieur. Par conséquent, il y a 
    de moins en moins d'offres dans les sujets d'ingénierie traitant de l'infrastructure, 
    l'environnement et les questions connexes, et une plus grande concentration sur les sujets de haute 
    technologie, qui soutiennent en grande partie des développements scientifiques de plus en plus 
    complexes. Si cette dernière est importante, elle ne doit pas se faire au détriment
    de l'ingénierie plus traditionnelle.

    Les économies en développement rapide telles que la Chine et l'Inde, ainsi que d'autres 
    pays industrialisés d'Europe et d'Asie, continuent d'encourager et de promouvoir
    l'enseignement de l'ingénierie. La Chine et l'Inde, respectivement, diplôment 
    six et huit fois plus d'ingénieurs traditionnels que les États-Unis. 
    Les autres pays industriels maintiennent au minimum leur production, tandis que l'Amérique 
    souffre d'une baisse de plus en plus importante du nombre de diplômés en ingénierie
    et un manque d'ingénieurs bien formés.
"""
```

```python out
[{'summary_text': ' America has changed dramatically during recent years . The ' 
		  # L'Amérique a changé de façon spectaculaire au cours des dernières années. Le
                  'number of engineering graduates in the U.S. has declined in ' 
		  # nombre de diplômés en ingénierie aux États-Unis a diminué dans
                  'traditional engineering disciplines such as mechanical, civil ' 
		  # les disciplines traditionnelles de l'ingénierie, comme le génie mécanique, civil
                  ', electrical, chemical, and aeronautical engineering . Rapidly ' 
		  # l'électricité, la chimie et l'aéronautique. Les économies
                  'developing economies such as China and India, as well as other ' 
		  # en développement rapide comme la Chine et l'Inde, ainsi que d'autres
                  'industrial countries in Europe and Asia, continue to encourage ' 
		  # pays industriels d'Europe et d'Asie, continuent d'encourager
                  'and advance engineering.'}] 
		  # et à faire progresser l'ingénierie.
```

Comme pour la génération de texte, vous pouvez spécifier une `max_length` (longueur maximale) ou une `min_length` (longueur minimale) pour le résultat.


## Traduction

Pour la traduction, vous pouvez utiliser un modèle par défaut si vous fournissez un couple de langues dans le nom de la tâche (comme `"translation_en_to_fr"`), mais le plus simple reste d'utiliser un modèle adéquat disponible sur le [*Hub*](https://huggingface.co/models). Ici, nous allons essayer de traduire du français en anglais :

```python
from transformers import pipeline

translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en")
translator("Ce cours est produit par Hugging Face.")
```

```python out
[{'translation_text': 'This course is produced by Hugging Face.'}]
```

Comme pour la génération de texte et le résumé de texte, il est possible de spécifier une `max_length` (longueur maximale) ou une `min_length` (longueur minimale) pour le résultat.

<Tip>

✏️ **Essayez !** Recherchez d'autres modèles de traduction sur le *Hub* et essayez de traduire la phrase précédente en plusieurs langues différentes.

</Tip>

Les pipelines présentés jusqu'ici sont principalement destinés à des fins de démonstration. Ils ont été programmés pour des tâches spécifiques et ne peuvent pas effectuer de variations de celles-ci. Dans le chapitre suivant, vous apprendrez ce qu'il y a dans un `pipeline()` et comment modifier son comportement.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter1/3.mdx" />

### Les modèles basés sur le décodeur
https://huggingface.co/learn/course/fr/chapter1/6.md

# Les modèles basés sur le décodeur

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

<Youtube id="d_ixlCubqQw" />

Les modèles basés sur le décodeur utilisent seulement le décodeur d'un *transformer*. À chaque étape, pour un mot donné, les couches d'attention ne peuvent strictement accéder qu'aux mots situés avant dans la phrase. Ces modèles sont souvent appelés *modèles autorégressifs*.

Le pré-entraînement des modèles basés sur le décodeur se concentre généralement sur la prédiction du prochain mot dans la phrase.

Ces modèles sont vraiment adaptés aux tâches qui impliquent la génération de texte.

Les modèles qui représentent le mieux la famille des modèles décodeurs sont :

- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html)
- [GPT](https://huggingface.co/docs/transformers/model_doc/openai-gpt)
- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html)
- [Transformer XL](https://huggingface.co/transformers/model_doc/transformerxl.html)


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter1/6.mdx" />

### Quiz de fin de chapitre
https://huggingface.co/learn/course/fr/chapter1/10.md

# Quiz de fin de chapitre

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

Ce chapitre a couvert un grand nombre de notions ! Ne vous inquiétez pas si vous n'avez pas compris tous les détails, les chapitres suivants vous aideront à comprendre comment les choses fonctionnent concrètement.

Mais avant d'aller plus loin, prenons un instant pour voir ce que vous avez appris dans ce chapitre !


### 1. Explorez le *Hub* et cherchez le modèle `roberta-large-mnli`. Quelle tâche accomplit-il ?


<Question
	choices={[
		{
			text: "Résumé de texte",
			explain: "Regardez à nouveau sur la <a href=\"https://huggingface.co/roberta-large-mnli\">page roberta-large-mnli</a>."
		},
		{
			text: "Classification de texte",
			explain: "Pour être plus précis, il classifie si deux phrases sont logiquement liées entre elles parmis trois possibilités (contradiction, neutre, lien). Il s'agit d'une tâche aussi appelée <em>inference de langage naturel</em>.",
			correct: true
		},
		{
			text: "Génération de texte",
			explain: "Regardez à nouveau sur la <a href=\"https://huggingface.co/roberta-large-mnli\">page roberta-large-mnli</a>."
		}
	]}
/>

### 2. Que renvoie le code suivant ?

```py
from transformers import pipeline

ner = pipeline("ner", grouped_entities=True)
ner(
    "My name is Sylvain and I work at Hugging Face in Brooklyn."
)  # Je m'appelle Sylvain et je travaille à Hugging Face à Brooklyn.
```

<Question
	choices={[
		{
			text: "Il renvoie les scores de classification pour cette phrase, avec les labels \"positive\" ou \"negative\".",
			explain: "Cela correspondrait au pipeline <code>d'analyse de sentiment</code> (<i>sentiment-analysis</i> dans la documentation d'Hugging-Face)."
		},
		{
			text: "Il renvoie un texte généré qui complète cette phrase.",
			explain: "Cela correspondrait au pipeline de <code>génération de texte</code> (<i>text-generation</i> dans la documentation d'Hugging-Face)."
		},
		{
			text: "Il renvoie les entités nommées dans cette phrase, telles que les personnes, les organisations ou lieux.",
			explain: "De plus, avec <code>grouped_entities=True</code>, cela regroupe les mots appartenant à la même entité, comme par exemple \"Hugging Face\".",
			correct: true
		}
	]}
/>

### 3. Que remplace « ... » dans ce code ?

```py
from transformers import pipeline

filler = pipeline("fill-mask", model="bert-base-cased")
result = filler("...")
```

<Question
	choices={[
		{
			text: "This &#60;mask> has been waiting for you. # Ce &#60;mask> vous attend.",
			explain: "Regardez la description du modèle <code>bert-base-cased</code> et essayez de trouver votre erreur."
		},
		{
			text: "This [MASK] has been waiting for you. # Ce [MASK] vous attend.",
			explain: "Le modèle utilise [MASK] comme mot-masque.",
			correct: true
		},
		{
			text: "This man has been waiting for you. # Cet homme vous attend.",
			explain: "Ce pipeline permet de remplacer les mot manquants donc il a besoin d'un mot-masque."
		}
	]}
/>

### 4. Pourquoi ce code ne fonctionne-t-il pas ?

```py
from transformers import pipeline

classifier = pipeline("zero-shot-classification")
result = classifier(
    "This is a course about the Transformers library"
)  # C'est un cours sur la bibliothèque Transformers
```

<Question
	choices={[
		{
			text: "Ce pipeline nécessite que des étiquettes soient données pour classifier ce texte.",
			explain: "Le code doit inclure <code>candidate_labels=[...]</code>.",
			correct: true
		},
		{
			text: "Ce pipeline nécessite que des phrases soient données, pas juste une phrase.",
			explain: "Bien que ce pipeline puisse prendre une liste de phrases à traiter (comme tous les autres pipelines)."
		},
		{
			text: "La bibliothèque 🤗 <i>Transformers</i> est cassée, comme d'habitude.",
			explain: "Nous n'avons aucun commentaire pour cette réponse !",
		},
		{
			text: "Ce pipeline nécessite des phrases plus longues, celle-ci est trop courte.",
			explain: "Notez que si un texte est très long, il est tronqué par le pipeline."
		}
	]}
/>

### 5. Que signifie « apprentissage par transfert » ?

<Question
	choices={[
		{
			text: "Transférer les connaissances d'un modèle pré-entraîné vers un nouveau modèle en entraînant ce second modèle sur le même jeu de données.",
			explain: "Non, cela donnerait deux versions du même modèle."
		},
		{
			text: "Transférer les connaissances d'un modèle pré-entraîné vers un nouveau modèle en initialisant ce second modèle avec les poids du premier.",
			explain: "Quand le second modèle est entraîné sur une nouvelle tâche, il transfère les connaissances du premier modèle.",
			correct: true
		},
		{
			text: "Transférer les connaissances d'un modèle pré-entraîné vers un nouveau modèle en construisant le second modèle avec la même architecture que le premier.",
			explain: "L'architecture correspond uniquement à la structure du modèle, pas à ses connaissances. Il n'y a donc pas de connaissances à transférer dans ce cas.",
		}
	]}
/>

### 6. Vrai ou faux ? Un modèle de langage n'a généralement pas besoin d'étiquettes pour son pré-entraînement.


<Question
	choices={[
		{
			text: "Vrai",
			explain: "Le pré-entraînement est <em>autosupervisé</em>, ce qui signifie que les étiquettes sont créées automatiquement à partir des données d'entrée (comme prédire le mot suivant ou remplacer des mots masqués).",
			correct: true
		},
		{
			text: "Faux",
			explain: "Ce n'est pas la bonne réponse."
		}
	]}
/>

### 7. Sélectionnez la phrase qui décrit le mieux les termes « modèle », « architecture » et « poids ».

<Question
	choices={[
		{
			text: "Si un modèle est un bâtiment, son architecture est le plan de construction et les poids représentent les personnes qui vivent dedans.",
			explain: "Si on suit cette métaphore, les poids seraient plutôt les briques et les matériaux utilisés pour construire le bâtiment."
		},
		{
			text: "Une architecture est une carte pour construire un modèle et les poids sont les villes représentées sur la carte.",
			explain: "Le problème avec cette métaphore est que la carte représente une réalité existante (il n'y a qu'une seule ville nommée Paris en France). Pour une architecture donnée, plusieurs poids sont possibles."
		},
		{
			text: "Une architecture est une succession de fonctions mathématiques permettant de construire un modèle et les poids sont les paramètres de ces fonctions.",
			explain: "Le même ensemble de fonctions mathématiques peut être utilisé pour construire plusieurs modèles avec différents paramètres (poids).",
			correct: true
		}
	]}
/>


### 8. Parmi ces types de modèles, quel est le plus approprié pour générer du texte à partir d'une instruction (*prompt*) ?

<Question
	choices={[
		{
			text: "Un modèle basé sur l'encodeur",
			explain: "Un modèle basé sur l'encodeur génère une représentation de la phrase entière qui est plus adaptée à des tâches de classification."
		},
		{
			text: "Un modèle basé sur le décodeur",
			explain: "Les modèles basés sur le décodeur sont bien pour générer du texte à partir d'une instruction.",
			correct: true
		},
		{
			text: "Un modèle de séquence-à-séquence",
			explain: "Les modèles de séquence-à-séquence sont davantage adaptés aux tâches qui nécessitent de générer des phrases à partir d'un texte donné en entrée, pas un texte généré à partir d'une instruction."
		}
	]}
/>

### 9. Parmi ces types de modèles, quel est le plus approprié pour le résumé de texte ?

<Question
	choices={[
		{
			text: "Un modèle basé sur l'encodeur",
			explain: "Un modèle basé sur l'encodeur génère une représentation de la phrase entière qui est plus adaptée à des tâches de classification.",
		},
		{
			text: "Un modèle basé sur le décodeur",
			explain: "Les modèles basés sur le décodeur sont bien pour générer du texte (comme les résumés) mais ils n'ont pas la capacité d'exploiter un contexte comme un texte entier pour en faire un résumé.",
		},
		{
			text: "Un modèle de séquence-à-séquence",
			explain: "Les modèles de séquence-à-séquence sont parfaitement adaptés à une tâche de résumé.",
			correct: true
		}
	]}
/>

### 10. Quel type de modèle utiliseriez-vous pour classifier des entrées de texte en fonction de certains labels ?

<Question
	choices={[
		{
			text: "Un modèle basé sur l'encodeur",
			explain: "Un modèle basé sur un encodeur génère une représentation de la phrase entière et est donc parfaitement adapté à des tâches de classification.",
			correct: true
		},
		{
			text: "Un modèle basé sur le décodeur",
			explain: "Les modèles basés sur le décodeur sont bons pour générer des textes et non pour extraire une étiquette d'une phrase.",
		},
		{
			text: "Un modèle de séquence-à-séquence",
			explain: "Les modèles de séquence-à-séquence sont davantage adaptés pour des tâches qui nécessitent de générer des phrases à partir d'un texte donné en entrée, non pour extraire une étiquette à partir d'une phrase.",
		}
	]}
/>

### 11. De quelle source possible peut être le biais observé dans un modèle ?

<Question
	choices={[
		{
			text: "Le modèle est une version <i>finetunée</i> d'un modèle pré-entraîné et il a conservé ses biais.",
			explain: "Avec l'apprentissage par transfert, les biais du modèle pré-entraîné perdurent dans le modèle <i>finetuné</i>.",
			correct: true
		},
		{
			text: "Le modèle a été entraîné sur des données qui sont biaisées.",
			explain: "Ceci représente la source de biais la plus évidente mais n'est pas la seule possible.",
			correct: true
		},
		{
			text: "La métrique optimisée lors de l'entraînement du modèle est biaisée.",
			explain: "Une source moins évidente est la façon dont le modèle est entraîné. Votre modèle va de façon aveugle optimiser la métrique que vous avez sélectionnée, sans prendre aucun recul.",
			correct: true
		}
	]}
/>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter1/10.mdx" />

### Biais et limitations
https://huggingface.co/learn/course/fr/chapter1/8.md

# Biais et limitations

<CourseFloatingBanner chapter={1}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter1/section8.ipynb"},
    {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter1/section8.ipynb"},
    {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter1/section8.ipynb"},
    {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter1/section8.ipynb"},
]} />

Si vous souhaitez utiliser un modèle pré-entraîné ou une version *finetunée* de celui-ci en production, il est important d'avoir conscience que, bien que ces modèles soient puissants, ils ont des limites. La plus importante de ces limitations est que, pour permettre le pré-entraînement des modèles sur de grandes quantités de données, les chercheurs récupèrent souvent tout le contenu qu'ils peuvent trouver et donc en prenant le meilleur et le pire de ce qui est disponible sur internet.

Pour illustrer cela rapidement, revenons au pipeline *fill-mask* avec le modèle BERT :

```python
from transformers import pipeline

unmasker = pipeline("fill-mask", model="bert-base-uncased")
result = unmasker("This man works as a [MASK].")  # Cet homme travaille comme [MASQUE]
print([r["token_str"] for r in result])

result = unmasker("This woman works as a [MASK].")  # Cette femme travaille comme [MASQUE]
print([r["token_str"] for r in result])
```

```python out
['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] 
# [avocat, charpentier, médecin, serveur, mécanicien]
['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] 
# ["infirmière", "serveuse", "professeur", "femme de chambre", "prostituée"]
```

Lorsque l'on demande au modèle de remplacer le mot manquant dans ces deux phrases, il ne propose qu'un seul métier ne portant pas la marque du genre (*waiter*/*waitress* → serveur/serveuse). Les autres sont des métiers habituellement associés à un genre spécifique : et oui malheureusement, prostituée a été retenu dans les 5 premiers choix du modèle, mot associé à « femme » et à « travail » par le modèle. Cela se produit même si BERT est l'un des rare *transformers* qui n'a pas été construit avec des données récupérées par *scrapping* sur internet, mais à l'aide de données en apparence neutres. En effet, il est entraîné sur les jeux de donnés [Wikipédia Anglais](https://huggingface.co/datasets/wikipedia) et [BookCorpus](https://huggingface.co/datasets/bookcorpus)).

Donc lorsque vous utilisez ce genre d'outils, il est important de garder en tête que le modèle que vous utilisez peut rapidement générer du contenu sexiste, raciste ou homophobe. Le *finetuning* du modèle sur vos données ne fera en aucun cas disparaître ce biais intrinsèque.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter1/8.mdx" />

### Comment fonctionnent les <i>transformers</i> ?
https://huggingface.co/learn/course/fr/chapter1/4.md

# Comment fonctionnent les <i>transformers</i> ?

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

Dans cette partie, nous allons jeter un coup d'œil à l'architecture des *transformers*.

## Court historique des <i>transformers</i>

Voici quelques dates clefs dans la courte histoire des *transformers* :

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers_chrono.svg" alt="A brief chronology of Transformers models.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers_chrono-dark.svg" alt="A brief chronology of Transformers models.">
</div>

[L'architecture *Transformer*](https://arxiv.org/abs/1706.03762) a été présentée en juin 2017. Initialement, la recherche portait sur la tâche de traduction. Elle a été suivie par l'introduction de plusieurs modèles influents, notamment :

- **Juin 2018** : [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), le premier *transformer* pré-entraîné et *finetuné* sur différentes tâches de NLP et ayant obtenu des résultats à l'état de l'art,

- **Octobre 2018** : [BERT](https://arxiv.org/abs/1810.04805), autre grand modèle pré-entraîné ayant été construit pour produire de meilleurs résumés de texte (plus de détails dans le chapitre suivant !),

- **Février 2019** : [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), une version améliorée (et plus grande) de GPT qui n'a pas été directement rendu publique pour cause de raisons éthiques,

- **Octobre 2019** : [DistilBERT](https://arxiv.org/abs/1910.01108), une version distillée de BERT étant 60% plus rapide, 40% plus légère en mémoire et conservant tout de même 97% des performances initiales de BERT,

- **Octobre 2019** : [BART](https://arxiv.org/abs/1910.13461) et [T5](https://arxiv.org/abs/1910.10683), deux modèles pré-entraînés utilisant la même architecture que le *transformer* original (les premiers à faire cela),

- **Mai 2020** : [GPT-3](https://arxiv.org/abs/2005.14165), une version encore plus grande que GPT-2 ayant des performances très bonnes sur une variété de tâches ne nécessitant pas de *finetuning* (appelé _zero-shot learning_).

Cette liste est loin d'être exhaustive et met en lumière certains *transformers*. Plus largement, ces modèles peuvent être regroupés en trois catégories :

- ceux de type GPT (aussi appelés *transformers* _autorégressifs_)  
- ceux de type BERT (aussi appelés *transformers* _auto-encodeurs_)
- ceux de type BART/T5 (aussi appelés *transformers* _séquence-à-séquence_)

Nous verrons plus en profondeur ces familles de modèles plus tard.

## Les <i>transformers</i> sont des modèles de langage

Tous les *transformers* mentionnés ci-dessus (GPT, BERT, BART, T5, etc.) ont été entraînés comme des *modèles de langage*. Cela signifie qu'ils ont été entraînés sur une large quantité de textes bruts de manière autosupervisée. L'apprentissage autosupervisé est un type d'entraînement dans lequel l'objectif est automatiquement calculé à partir des entrées du modèle. Cela signifie que les humains ne sont pas nécessaires pour étiqueter les données !

Ce type de modèle développe une compréhension statistique de la langue sur laquelle il a été entraîné, mais il n'est pas très utile pour des tâches pratiques spécifiques. Pour cette raison, le modèle pré-entraîné passe ensuite par un processus appelé apprentissage par transfert. Au cours de ce processus, le modèle est *finetuné* de manière supervisée (c'est-à-dire en utilisant des étiquettes annotées par des humains) pour une tâche donnée.

Un exemple de tâche consiste à prédire le mot suivant dans une phrase après avoir lu les *n* mots précédents. Cette tâche est appelée *modélisation causale du langage* car la sortie dépend des entrées passées et présentes, mais pas des entrées futures.
<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/causal_modeling.svg" alt="Example of causal language modeling in which the next word from a sentence is predicted.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/causal_modeling-dark.svg" alt="Example of causal language modeling in which the next word from a sentence is predicted.">
</div>

Un autre exemple est la *modélisation du langage masqué*, dans laquelle le modèle prédit un mot masqué dans la phrase.

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/masked_modeling.svg" alt="Example of masked language modeling in which a masked word from a sentence is predicted.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/masked_modeling-dark.svg" alt="Example of masked language modeling in which a masked word from a sentence is predicted.">
</div>

## Les <i>transformers</i> sont énormes

En dehors de quelques exceptions (comme DistilBERT), la stratégie générale pour obtenir de meilleure performance consiste à augmenter la taille des modèles ainsi que la quantité de données utilisées pour l'entraînement de ces derniers.

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/model_parameters.png" alt="Number of parameters of recent Transformers models" width="90%">
</div>

Malheureusement, entraîner un modèle et particulièrement un très grand modèle, nécessite une importante quantité de données. Cela devient très coûteux en termes de temps et de ressources de calcul. Cela se traduit même par un impact environnemental comme le montre le graphique suivant.

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/carbon_footprint.svg" alt="The carbon footprint of a large language model.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/carbon_footprint-dark.svg" alt="The carbon footprint of a large language model.">
</div>

<Youtube id="ftWlj4FBHTg"/>

L'image montre l'empreinte carbone pour un projet d'entraînement d'un (très grand) modèle mené par une équipe qui pourtant essaie consciemment de réduire l'impact environnemental du pré-entraînement. L'empreinte de l'exécution de nombreux essais pour obtenir les meilleurs hyperparamètres serait encore plus élevée.

Imaginez qu'à chaque fois qu'une équipe de recherche, une association d'étudiants ou une entreprise souhaite entraîner un modèle, elle le fasse en partant de zéro. Cela entraînerait des coûts globaux énormes et inutiles !

C'est pourquoi le partage des modèles du langage est primordial : partager les poids d'entraînement et construire à partir de ces poids permet de réduire les coûts de calcul globaux ainsi que l'empreinte carbone de toute la communauté.

A noter que vous pouvez d’ailleurs évaluer l’empreinte carbone de l’entraînement de vos modèles à travers plusieurs outils. Par exemple [ML CO2 Impact](https://mlco2.github.io/impact/) ou bien [Code Carbon]( https://codecarbon.io/) qui est intégré dans 🤗 Transformers. Pour en savoir plus à ce sujet, vous pouvez lire cet [article de blog](https://huggingface.co/blog/carbon-emissions-on-the-hub) qui vous montrera comment faire pour générer un fichier `emissions.csv` comportant une estimation de l’empreinte de votre entraînement, ainsi que la [documentation](https://huggingface.co/docs/hub/model-cards-co2) de 🤗 Transformers abordant ce thème.


## L'apprentissage par transfert

<Youtube id="BqqfQnyjmgg" />

Le pré-entraînement consiste à entraîner un modèle à partir de zéro : les poids sont initialisés de manière aléatoire et l'entraînement commence sans aucune connaissance préalable.

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/pretraining.svg" alt="The pretraining of a language model is costly in both time and money.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/pretraining-dark.svg" alt="The pretraining of a language model is costly in both time and money.">
</div>

Ce pré-entraînement est généralement effectué sur de très grandes quantités de données. Il nécessite donc un très grand corpus de données et l'entraînement peut prendre jusqu'à plusieurs semaines.

Le *finetuning*, quant à lui, est l'entrainement effectué après qu'un modèle ait été pré-entraîné. Pour effectuer un *finetuning*, vous devez d'abord acquérir un modèle de langue pré-entraîné, puis effectuer un entraînement supplémentaire avec un jeu de données spécifiques. Mais pourquoi ne pas entraîner directement pour la tâche finale ? Il y a plusieurs raisons à cela :

*  Le modèle pré-entraîné a déjà été entraîné sur un jeu de données qui présente certaines similitudes avec le jeu de données de *finetuning*. Le processus de *finetuning* est donc en mesure de tirer parti des connaissances acquises par le modèle initial lors du pré-entraînement (par exemple, pour les problèmes de langage naturel, le modèle pré-entraîné aura une certaine compréhension statistique de la langue que vous utilisez pour votre tâche)
*  Comme le modèle pré-entraîné a déjà été entraîné sur de nombreuses données, le *finetuning* nécessite beaucoup moins de données pour obtenir des résultats décents.
*  Pour la même raison, le temps et les ressources nécessaires pour obtenir de bons résultats sont beaucoup moins importants.

Par exemple, il est possible d'exploiter un modèle pré-entraîné entraîné sur la langue anglaise, puis de le *finetuner* sur un corpus arXiv, pour obtenir un modèle basé sur la science et la recherche. Le *finetuning* ne nécessitera qu'une quantité limitée de données : les connaissances acquises par le modèle pré-entraîné sont « transférées », d'où le terme d'apprentissage par transfert.

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/finetuning.svg" alt="The fine-tuning of a language model is cheaper than pretraining in both time and money.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/finetuning-dark.svg" alt="The fine-tuning of a language model is cheaper than pretraining in both time and money.">
</div>

Le *finetuning* d'un modèle a donc un coût moindre en termes de temps, de données, de finances et d'environnement. Il est aussi plus rapide et plus facile d'itérer sur différents schémas de *finetuning* car l'entraînement est moins contraignant qu'un pré-entraînement complet.

Ce processus permet également d'obtenir de meilleurs résultats que l'entraînement à partir de zéro (à moins que vous ne disposiez d'un grand nombre de données). C'est pourquoi vous devez toujours essayer de tirer parti d'un modèle pré-entraîné, c'est-à-dire un modèle aussi proche que possible de la tâche que vous avez à accomplir, et de le *finetuner*.

## Architecture générale

Dans cette section, nous allons voir l'architecture générale des *transformers*. Pas d'inquiétudes si vous ne comprenez pas tous les concepts, des sections détaillées qui couvrent chaque composant seront abordées plus tard.

<Youtube id="H39Z_720T5s" />

## Introduction

Le modèle est principalement composé de deux blocs :

* **Encodeur (à gauche)** : l'encodeur reçoit une entrée et construit une représentation de celle-ci (ses caractéristiques). Cela signifie que le modèle est optimisé pour acquérir une compréhension venant de ces entrées.
* **Décodeur (à droite)** : le décodeur utilise la représentation de l'encodeur (les caractéristiques) en plus des autres entrées pour générer une séquence cible. Cela signifie que le modèle est optimisé pour générer des sorties.

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers_blocks.svg" alt="Architecture of a Transformers models">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers_blocks-dark.svg" alt="Architecture of a Transformers models">
</div>

Chacun de ces blocs peuvent être utilisés indépendamment en fonction de la tâche que l'on souhaite traiter :

* **Modèles uniquement encodeurs** : adaptés pour des tâches qui nécessitent une compréhension de l'entrée, comme la classification de phrases et la reconnaissance d'entités nommées.
* **Modèles uniquement décodeurs** : adaptés pour les tâches génératives telles que la génération de texte.
* **Modèles encodeurs-décodeurs** (ou **modèles de séquence-à-séquence**) : adaptés aux tâches génératives qui nécessitent une entrée, telles que la traduction ou le résumé de texte.

Nous verrons plus en détails chacune de ces architectures plus tard.

## Les couches d'attention

Une caractéristique clé des *transformers* est qu'ils sont construits avec des couches spéciales appelées couches d'attention. En fait, le titre du papier introduisant l'architecture *transformer* se nomme [*Attention Is All You Need*](https://arxiv.org/abs/1706.03762) ! Nous explorerons les détails des couches d'attention plus tard dans le cours. Pour l'instant, tout ce que vous devez savoir est que cette couche indique au modèle de prêter une attention spécifique à certains mots de la phrase que vous lui avez passée (et d'ignorer plus ou moins les autres) lors du traitement de la représentation de chaque mot.

Pour mettre cela en contexte, considérons la tâche de traduire un texte de l'anglais au français. Étant donné l'entrée « *You like this course* », un modèle de traduction devra également s'intéresser au mot adjacent « *You* » pour obtenir la traduction correcte du mot « *like* », car en français le verbe « *like* » se conjugue différemment selon le sujet. Le reste de la phrase n'est en revanche pas utile pour la traduction de ce mot. Dans le même ordre d'idées, pour traduire « *this* », le modèle devra également faire attention au mot « *course* » car « *this* » se traduit différemment selon que le nom associé est masculin ou féminin. Là encore, les autres mots de la phrase n'auront aucune importance pour la traduction de « *this* ». Avec des phrases plus complexes (et des règles de grammaire plus complexes), le modèle devra prêter une attention particulière aux mots qui pourraient apparaître plus loin dans la phrase pour traduire correctement chaque mot.

Le même concept s'applique à toute tâche associée au langage naturel : un mot en lui-même a un sens, mais ce sens est profondément affecté par le contexte, qui peut être n'importe quel autre mot (ou mots) avant ou après le mot étudié.

Maintenant que vous avez une idée plus précise des couches d'attentions, nous allons regarder de plus près l'architecture des *transformers*.

## L'architecture originale

L'architecture du *transformer* a initialement été construite pour la tâche de traduction. Pendant l'entraînement, l'encodeur reçoit des entrées (des phrases) dans une certaine langue, tandis que le décodeur reçoit la même phrase traduite dans la langue cible. Pour l'encodeur, les couches d'attention peuvent utiliser tous les mots d'une phrase (puisque comme nous venons de le voir, la traduction d'un mot donné peut dépendre de ce qui le suit ou le précède dans la phrase). Le décodeur, quant à lui, fonctionne de façon séquentielle et ne peut porter son attention qu'aux mots déjà traduits dans la phrase (donc uniquement les mots générés avant le mot en cours). Par exemple, lorsqu'on a prédit les trois premiers mots de la phrase cible, on les donne au décodeur qui utilise alors toutes les entrées de l'encodeur pour essayer de prédire le quatrième mot.

Pour accélérer les choses pendant l'apprentissage (lorsque le modèle a accès aux phrases cibles), le décodeur est alimenté avec la cible entière, mais il n'est pas autorisé à utiliser les mots futurs (s'il avait accès au mot en position 2 lorsqu'il essayait de prédire le mot en position 2, le problème ne serait pas très difficile !). Par exemple, en essayant de prédire le quatrième mot, la couche d'attention n'aura accès qu'aux mots des positions 1 à 3.

L'architecture originale du *transformer* ressemble à ceci, avec l'encodeur à gauche et le décodeur à droite :

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers.svg" alt="Architecture of a Transformers models">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers-dark.svg" alt="Architecture of a Transformers models">
</div>

Notez que la première couche d'attention dans un bloc décodeur prête attention à toutes les entrées (passées) du décodeur, mais que la deuxième couche d'attention utilise la sortie de l'encodeur. Elle peut donc accéder à l'ensemble de la phrase d'entrée pour prédire au mieux le mot actuel. C'est très utile, car différentes langues peuvent avoir des règles grammaticales qui placent les mots dans un ordre différent, ou un contexte fourni plus tard dans la phrase peut être utile pour déterminer la meilleure traduction d'un mot donné.

Le *masque d'attention* peut également être utilisé dans l'encodeur/décodeur pour empêcher le modèle de prêter attention à certains mots spéciaux. Par exemple, le mot de remplissage spécial (le *padding*) utilisé pour que toutes les entrées aient la même longueur lors du regroupement de phrases.

## Architectures contre <i>checkpoints</i>
 
En approfondissant l'étude des <i>transformers</i> dans ce cours, vous verrez des mentions d'<i>architectures</i> et de <i>checkpoints</i> ainsi que de <i>modèles</i>. Ces termes ont tous des significations légèrement différentes :

* **Architecture** : c'est le squelette du modèle, la définition de chaque couche et chaque opération qui se produit au sein du modèle.
* **Checkpoints** : ce sont les poids qui seront chargés dans une architecture donnée.
* **Modèle** : c'est un mot valise n'étant pas aussi précis que les mots « architecture » ou « *checkpoint* ». Il peut désigner l'un comme l'autre. Dans ce cours, il sera spécifié *architecture* ou *checkpoint* lorsqu'il sera essentiel de réduire toute ambiguïté.

Par exemple, BERT est une architecture alors que `bert-base-cased` (un ensemble de poids entraîné par l'équipe de Google lors de la première sortie de BERT) est un *checkpoint*. Cependant, il est possible de dire « le modèle BERT » et « le modèle `bert-base-cased` ».


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter1/4.mdx" />

### Les modèles de séquence-à-séquence
https://huggingface.co/learn/course/fr/chapter1/7.md

# Les modèles de séquence-à-séquence

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

<Youtube id="0_4KEb08xrE" />

Les modèles encodeur-décodeur (également appelés modèles de séquence-à-séquence) utilisent les deux parties du *transformer*. À chaque étape, les couches d'attention de l'encodeur peuvent accéder à tous les mots de la phrase initiale, tandis que les couches d'attention du décodeur n'ont accès qu'aux mots positionnés avant un mot donné en entrée de ces couches d'attention.

Le pré-entraînement de ces modèles peut être réalisé en utilisant les objectifs des modèles basés sur l'encodeur ou des modèles basés sur le décodeur. En général cela implique quelque chose de plus complexe. Par exemple, le modèle [T5](https://huggingface.co/t5-base) est pré-entraîné en remplaçant des zones aléatoires de texte (qui peuvent contenir plusieurs mots) par un masque spécial et l'objectif est alors de prédire le texte que ce masque cache.

Les modèles de séquence-à-séquence sont les plus adaptés pour les tâches liées à la génération de nouvelles phrases en fonction d'une entrée donnée, comme le résumé de texte, la traduction ou la génération de questions-réponses.

Les modèles qui représentent le mieux cette famille sont :

- [BART](https://huggingface.co/transformers/model_doc/bart.html)
- [mBART](https://huggingface.co/transformers/model_doc/mbart.html)
- [Marian](https://huggingface.co/transformers/model_doc/marian.html)
- [T5](https://huggingface.co/transformers/model_doc/t5.html)


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter1/7.mdx" />

### Traitement du langage naturel (NLP pour <i>Natural Language Processing</i>)
https://huggingface.co/learn/course/fr/chapter1/2.md

# Traitement du langage naturel (NLP pour <i>Natural Language Processing</i>)

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

Avant de commencer avec les *transformers*, voyons succinctement ce qu'est le traitement du langage naturel et pourquoi il est important.

## Le NLP, qu'est-ce que c'est ?

Le traitement du langage naturel est un domaine de linguistique et d'apprentissage automatique se concentrant sur la compréhension de tout ce qui est lié à la langue humaine. L'objectif des tâches de NLP est non seulement de comprendre individuellement chaque mot, mais aussi de comprendre le contexte associé à l'utilisation de ces mots.

La liste suivante regroupe les tâches de NLP les plus courantes, avec pour chacune quelques exemples :

- **Classification de phrases entières** : analyser le sentiment d'un avis, détecter si un email est un spam, déterminer si une phrase est grammaticalement correcte, déterminer si deux phrases sont logiquement reliées ou non, etc.
- **Classification de chaque mot d'une phrase** : identifier les composants grammaticaux d'une phrase (nom, verbe, adjectif), identifier les entités nommées (personne, lieu, organisation), etc.
- **Génération de texte** : compléter le début d'un texte avec un texte généré automatiquement, remplacer les mots manquants ou masqués dans un texte, etc.
- **Extraction d'une réponse à partir d'un texte** : étant donné une question et un contexte extraire la réponse à la question en fonction des informations fournies par le contexte, etc.
- **Génération de nouvelles phrases à partir d'un texte** : traduire un texte dans une autre langue, faire le résumé d'un texte, etc.

Le traitement du langage naturel ne se limite pas qu'à la compréhension du texte. Il s'intéresse aussi aux problèmes complexes de reconnaissance de la parole et de vision par ordinateur tels que la génération d'une transcription à partir d'un échantillon audio ou la description d'une image.

## Pourquoi est-ce difficile ?

Les ordinateurs ne traitent pas les informations de la même manière que les humains. Par exemple, lorsque nous lisons la phrase « j'ai faim », nous comprenons très facilement son sens. De même, lorsque nous lisons deux phrases telles que « j'ai faim » et « je suis triste », nous pouvons facilement déterminer s'il existe des similitudes entre elles. Pour les modèles d'apprentissage automatique, ces tâches sont plus difficiles. Le texte doit être traité de manière à permettre au modèle d'apprendre. Et parce que le langage est complexe, nous devons prendre soin de réfléchir à la meilleure façon de faire ce traitement. Il y a eu beaucoup de recherches sur la façon de représenter le texte et nous allons voir quelques-unes de ces méthodes dans le chapitre suivant.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter1/2.mdx" />
