Transformers documentation
Modelagem de linguagem mascarada
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:
- Ajuste fino do DistilRoBERTa no subconjunto r/askscience do conjunto de dados ELI5.
- 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
Antes de começar, verifique se você tem todas as bibliotecas necessárias instaladas:
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:
>>> 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 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.
>>> 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:
>>> eli5 = eli5.train_test_split(test_size=0.2)Em seguida, veja um exemplo:
>>> 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, o que realmente te interessa é o campo text. O que é legal nas tarefas de modelagem de linguagem é que você não precisa de rótulos (também conhecidas como tarefas não supervisionadas), 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:
>>> 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:
>>> 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:
>>> def preprocess_function(examples):
... return tokenizer([" ".join(x) for x in examples["answers.text"]])Para aplicar essa função de pré-processamento em todo o conjunto de dados, use o método 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:
>>> 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.
>>> 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 resultAplique a função group_texts em todo o conjunto de dados:
>>> lm_dataset = tokenized_eli5.map(group_texts, batched=True, num_proc=4)Agora, crie um lote de exemplos usando DataCollatorForLanguageModeling. É mais eficiente preencher dinamicamente as frases até o comprimento mais longo dentro de 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:
>>> 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, confira o tutorial básico aqui!
Agora você está pronto para começar a treinar seu modelo! Carregue o DistilRoBERTa com AutoModelForMaskedLM:
>>> from transformers import AutoModelForMaskedLM
>>> model = AutoModelForMaskedLM.from_pretrained("distilbert/distilroberta-base")Neste ponto, restam apenas três etapas:
- Defina os hiperparâmetros de treinamento em TrainingArguments. O único parâmetro obrigatório é
output_dir, que especifica onde salvar seu modelo. Você enviará este modelo para o Hub definindopush_to_hub=True(é necessário estar conectado ao Hugging Face para enviar seu modelo). - Passe os argumentos de treinamento para o Trainer junto com o modelo, os conjuntos de dados e o data collator.
- Chame train() para fazer o ajuste fino do seu modelo.
>>> 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() para avaliar seu modelo e obter sua perplexidade:
>>> import math
>>> eval_results = trainer.evaluate()
>>> print(f"Perplexity: {math.exp(eval_results['eval_loss']):.2f}")
Perplexity: 8.76Em seguida, compartilhe seu modelo no Hub com o método push_to_hub() para que todos possam usar seu modelo:
>>> 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 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 a lacuna e use o token especial <mask> para indicar a lacuna:
>>> 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(). 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:
>>> 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>:
>>> 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:
>>> 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:
>>> 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.