# Modelagem de linguagem mascarada

O modelamento de linguagem mascarada prevê um token mascarado em uma sequência, e o modelo pode prestar atenção aos tokens bidirecionalmente. Isso significa que o modelo tem acesso completo aos tokens à esquerda e à direita. O modelamento de linguagem mascarada é excelente para tarefas que exigem uma boa compreensão contextual de uma sequência inteira. O BERT é um exemplo de um modelo de linguagem mascarada.

Este guia mostrará como:

1. Ajuste fino do [DistilRoBERTa](https://huggingface.co/distilbert/distilroberta-base) no subconjunto [r/askscience](https://www.reddit.com/r/askscience/) do conjunto de dados [ELI5](https://huggingface.co/datasets/dany0407/eli5_category).
2. Use seu modelo ajustado para inferência.

Para ver todas as arquiteturas e checkpoints compatíveis com esta tarefa, recomendamos consultar a [página da tarefa](https://huggingface.co/tasks/fill-mask)

Antes de começar, verifique se você tem todas as bibliotecas necessárias instaladas:

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

Recomendamos que você faça login na sua conta do Hugging Face para que possa enviar e compartilhar seu modelo com a comunidade. Quando solicitado, insira seu token para fazer login:

```py
>>> from huggingface_hub import notebook_login

>>> notebook_login()
```

## Carregar o conjunto de dados ELI5

Comece carregando os primeiros 5000 exemplos do conjunto de dados [ELI5-Category](https://huggingface.co/datasets/dany0407/eli5_category) com a biblioteca 🤗 Datasets. Isso lhe dará a oportunidade de experimentar e garantir que tudo funcione antes de gastar mais tempo treinando no conjunto de dados completo.

```py
>>> from datasets import load_dataset

>>> eli5 = load_dataset("dany0407/eli5_category", split="train[:5000]")
```

Divida o split `train` do conjunto de dados em um conjunto de treinamento e um conjunto de teste com o método [train_test_split](https://huggingface.co/docs/datasets/main/en/package_reference/main_classes#datasets.Dataset.train_test_split):

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

Em seguida, veja um exemplo:

```py
>>> eli5["train"][0]
{'q_id': '7h191n',
 'title': 'What does the tax bill that was passed today mean? How will it affect Americans in each tax bracket?',
 'selftext': '',
 'category': 'Economics',
 'subreddit': 'explainlikeimfive',
 'answers': {'a_id': ['dqnds8l', 'dqnd1jl', 'dqng3i1', 'dqnku5x'],
  'text': ["The tax bill is 500 pages long and there were a lot of changes still going on right to the end. It's not just an adjustment to the income tax brackets, it's a whole bunch of changes. As such there is no good answer to your question. The big take aways are: - Big reduction in corporate income tax rate will make large companies very happy. - Pass through rate change will make certain styles of business (law firms, hedge funds) extremely happy - Income tax changes are moderate, and are set to expire (though it's the kind of thing that might just always get re-applied without being made permanent) - People in high tax states (California, New York) lose out, and many of them will end up with their taxes raised.",
   'None yet. It has to be reconciled with a vastly different house bill and then passed again.',
   'Also: does this apply to 2017 taxes? Or does it start with 2018 taxes?',
   'This article explains both the House and senate bills, including the proposed changes to your income taxes based on your income level. URL_0'],
  'score': [21, 19, 5, 3],
  'text_urls': [[],
   [],
   [],
   ['https://www.investopedia.com/news/trumps-tax-reform-what-can-be-done/']]},
 'title_urls': ['url'],
 'selftext_urls': ['url']}
```

Embora isso possa parecer muito, você realmente se interessa apenas pelo campo `text`. O que é legal nas tarefas de modelagem de linguagem é que você não precisa de rótulos (também conhecida como tarefa não supervisionada), porque a próxima palavra *é* o rótulo.

## Pré-processar

Para o modelamento de linguagem mascarada, a próxima etapa é carregar um tokenizador DistilRoBERTa para processar o subcampo `text`:

```py
>>> from transformers import AutoTokenizer

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

Você notará, pelo exemplo acima, que o campo `text` está, na verdade, aninhado dentro de `answers`. Isso significa que você precisará extrair o subcampo `text` de sua estrutura aninhada com o método [`flatten`](https://huggingface.co/docs/datasets/process#flatten):

```py
>>> eli5 = eli5.flatten()
>>> eli5["train"][0]
{'q_id': '7h191n',
 'title': 'What does the tax bill that was passed today mean? How will it affect Americans in each tax bracket?',
 'selftext': '',
 'category': 'Economics',
 'subreddit': 'explainlikeimfive',
 'answers.a_id': ['dqnds8l', 'dqnd1jl', 'dqng3i1', 'dqnku5x'],
 'answers.text': ["The tax bill is 500 pages long and there were a lot of changes still going on right to the end. It's not just an adjustment to the income tax brackets, it's a whole bunch of changes. As such there is no good answer to your question. The big take aways are: - Big reduction in corporate income tax rate will make large companies very happy. - Pass through rate change will make certain styles of business (law firms, hedge funds) extremely happy - Income tax changes are moderate, and are set to expire (though it's the kind of thing that might just always get re-applied without being made permanent) - People in high tax states (California, New York) lose out, and many of them will end up with their taxes raised.",
  'None yet. It has to be reconciled with a vastly different house bill and then passed again.',
  'Also: does this apply to 2017 taxes? Or does it start with 2018 taxes?',
  'This article explains both the House and senate bills, including the proposed changes to your income taxes based on your income level. URL_0'],
 'answers.score': [21, 19, 5, 3],
 'answers.text_urls': [[],
  [],
  [],
  ['https://www.investopedia.com/news/trumps-tax-reform-what-can-be-done/']],
 'title_urls': ['url'],
 'selftext_urls': ['url']}
```

Cada subcampo agora é uma coluna separada, conforme indicado pelo prefixo `answers`, e o campo `text` agora é uma lista. Em vez de tokenizar cada frase separadamente, converta a lista em uma string para que você possa tokenizá-las em conjunto.

Aqui está uma primeira função de pré-processamento para juntar a lista de strings de cada exemplo e tokenizar o resultado:

```py
>>> def preprocess_function(examples):
...     return tokenizer([" ".join(x) for x in examples["answers.text"]])
```

Para aplicar esta função de pré-processamento em todo o conjunto de dados, use o método [map](https://huggingface.co/docs/datasets/main/en/package_reference/main_classes#datasets.Dataset.map) do 🤗 Datasets. Você pode acelerar a função `map` definindo `batched=True` para processar vários elementos do conjunto de dados de uma vez e aumentando o número de processos com `num_proc`. Remova quaisquer colunas que você não precise:

```py
>>> tokenized_eli5 = eli5.map(
...     preprocess_function,
...     batched=True,
...     num_proc=4,
...     remove_columns=eli5["train"].column_names,
... )
```

Este conjunto de dados contém as sequências de tokens, mas algumas delas são mais longas do que o comprimento máximo de entrada para o modelo.

Agora você pode usar uma segunda função de pré-processamento para

- concatenar todas as sequências
- divida as sequências concatenadas em blocos menores definidos por `block_size`, que devem ser mais curtos que o comprimento máximo de entrada e curtos o suficiente para a RAM da sua GPU.

```py
>>> block_size = 128

>>> def group_texts(examples):
...     # Concatenate all texts.
...     concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()}
...     total_length = len(concatenated_examples[list(examples.keys())[0]])
...     # We drop the small remainder, we could add padding if the model supported it instead of this drop, you can
...     # customize this part to your needs.
...     if total_length >= block_size:
...         total_length = (total_length // block_size) * block_size
...     # Split by chunks of block_size.
...     result = {
...         k: [t[i : i + block_size] for i in range(0, total_length, block_size)]
...         for k, t in concatenated_examples.items()
...     }
...     return result
```

Aplique a função `group_texts` em todo o conjunto de dados:

```py
>>> lm_dataset = tokenized_eli5.map(group_texts, batched=True, num_proc=4)
```

Agora crie um lote de exemplos usando [DataCollatorForLanguageModeling](/docs/transformers/main/pt/main_classes/data_collator#transformers.DataCollatorForLanguageModeling). É mais eficiente *preencher dinamicamente* as frases até o comprimento mais longo em um lote durante a colação, em vez de preencher todo o conjunto de dados até o comprimento máximo.

Use o token de fim de sequência como token de preenchimento e especifique `mlm_probability` para mascarar aleatoriamente os tokens a cada iteração sobre os dados:

```py
>>> from transformers import DataCollatorForLanguageModeling

>>> tokenizer.pad_token = tokenizer.eos_token
>>> data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15)
```

## Treinar

Se você não está familiarizado com o ajuste fino de um modelo usando o [Trainer](/docs/transformers/main/pt/main_classes/trainer#transformers.Trainer), dê uma olhada no tutorial básico [aqui](../training)!

Agora você está pronto para começar a treinar seu modelo! Carregue o DistilRoBERTa com [AutoModelForMaskedLM](/docs/transformers/main/pt/model_doc/auto#transformers.AutoModelForMaskedLM):

```py
>>> from transformers import AutoModelForMaskedLM

>>> model = AutoModelForMaskedLM.from_pretrained("distilbert/distilroberta-base")
```

Neste ponto, restam apenas três etapas:

1. Defina os hiperparâmetros de treinamento em [TrainingArguments](/docs/transformers/main/pt/main_classes/trainer#transformers.TrainingArguments). O único parâmetro obrigatório é `output_dir`, que especifica onde salvar seu modelo. Você enviará este modelo para o Hub definindo `push_to_hub=True` (você precisa estar conectado ao Hugging Face para enviar seu modelo).
2. Passe os argumentos de treinamento para o [Trainer](/docs/transformers/main/pt/main_classes/trainer#transformers.Trainer) junto com o modelo, os conjuntos de dados e o data collator.
3. Chame [train()](/docs/transformers/main/pt/main_classes/trainer#transformers.Trainer.train) para fazer o ajuste fino do seu modelo.

```py
>>> training_args = TrainingArguments(
...     output_dir="my_awesome_eli5_mlm_model",
...     eval_strategy="epoch",
...     learning_rate=2e-5,
...     num_train_epochs=3,
...     weight_decay=0.01,
...     push_to_hub=True,
... )

>>> trainer = Trainer(
...     model=model,
...     args=training_args,
...     train_dataset=lm_dataset["train"],
...     eval_dataset=lm_dataset["test"],
...     data_collator=data_collator,
...     processing_class=tokenizer,
... )

>>> trainer.train()
```

Após a conclusão do treinamento, use o método [evaluate()](/docs/transformers/main/pt/main_classes/trainer#transformers.Trainer.evaluate) para avaliar seu modelo e obter sua perplexidade:

```py
>>> import math

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

Em seguida, compartilhe seu modelo no Hub com o método [push_to_hub()](/docs/transformers/main/pt/main_classes/trainer#transformers.Trainer.push_to_hub) para que todos possam usar seu modelo:

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

Para um exemplo mais detalhado de como fazer o ajuste fino de um modelo para modelagem de linguagem mascarada, consulte o [notebook do PyTorch](https://colab.research.google.com/github/huggingface/notebooks/blob/main/examples/language_modeling.ipynb) correspondente.

## Inferência

Ótimo, agora que você ajustou um modelo, pode usá-lo para inferência!

Pense em algum texto que você gostaria que o modelo preenchesse em branco e use o token especial `<mask>` para indicar o espaço em branco:

```py
>>> text = "The Milky Way is a <mask> galaxy."
```

A maneira mais simples de testar seu modelo ajustado para inferência é usá-lo em um [pipeline()](/docs/transformers/main/pt/main_classes/pipelines#transformers.pipeline). Instancie um `pipeline` para fill-mask com seu modelo e passe seu texto para ele. Se desejar, você pode usar o parâmetro `top_k` para especificar quantas previsões retornar:

```py
>>> from transformers import pipeline

>>> mask_filler = pipeline("fill-mask", "username/my_awesome_eli5_mlm_model")
>>> mask_filler(text, top_k=3)
[{'score': 0.5150994658470154,
  'token': 21300,
  'token_str': ' spiral',
  'sequence': 'The Milky Way is a spiral galaxy.'},
 {'score': 0.07087188959121704,
  'token': 2232,
  'token_str': ' massive',
  'sequence': 'The Milky Way is a massive galaxy.'},
 {'score': 0.06434620916843414,
  'token': 650,
  'token_str': ' small',
  'sequence': 'The Milky Way is a small galaxy.'}]
```

Tokenize o texto e retorne os `input_ids` como tensores do PyTorch. Você também precisará especificar a posição do token `<mask>`:

```py
>>> from transformers import AutoTokenizer

>>> tokenizer = AutoTokenizer.from_pretrained("username/my_awesome_eli5_mlm_model")
>>> inputs = tokenizer(text, return_tensors="pt")
>>> mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1]
```

Passe suas entradas para o modelo e retorne os `logits` do token mascarado:

```py
>>> from transformers import AutoModelForMaskedLM

>>> model = AutoModelForMaskedLM.from_pretrained("username/my_awesome_eli5_mlm_model")
>>> logits = model(**inputs).logits
>>> mask_token_logits = logits[0, mask_token_index, :]
```

Em seguida, retorne os três tokens mascarados com a maior probabilidade e exiba-os:

```py
>>> top_3_tokens = torch.topk(mask_token_logits, 3, dim=1).indices[0].tolist()

>>> for token in top_3_tokens:
...     print(text.replace(tokenizer.mask_token, tokenizer.decode([token])))
The Milky Way is a spiral galaxy.
The Milky Way is a massive galaxy.
The Milky Way is a small galaxy.
```

