<FrameworkSwitchCourse {fw} />

# <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>
