Transformers documentation
Маскированное моделирование языка
Маскированное моделирование языка
Маскированное моделирование языка предсказывает замаскированный токен в последовательности, при этом модель может обращаться к токенам в обоих направлениях. Это означает, что модель имеет полный доступ к токенам слева и справа. Маскированное моделирование языка отлично подходит для задач, требующих хорошего контекстуального понимания всей последовательности. Примером маскированной языковой модели является BERT.
В этом руководстве показано, как:
- Дообучите DistilRoBERTa на подмножестве r/askscience набора данных ELI5.
- Используйте вашу дообученную модель для инференса.
Чтобы увидеть все архитектуры и контрольные точки, совместимые с этой задачей, рекомендуем обратиться к странице задачи
Прежде чем начать, убедитесь, что у вас установлены все необходимые библиотеки:
pip install transformers datasets evaluate
Мы рекомендуем вам войти в свой аккаунт Hugging Face, чтобы вы могли загрузить свою модель и поделиться ею с сообществом. При появлении запроса введите токен для входа:
>>> from huggingface_hub import notebook_login
>>> notebook_login()Загрузить набор данных ELI5
Начните с загрузки первых 5000 примеров из набора данных ELI5-Category с помощью библиотеки 🤗 Datasets. Это позволит вам провести эксперименты и убедиться, что всё работает, прежде чем тратить время на обучение на полном наборе данных.
>>> from datasets import load_dataset
>>> eli5 = load_dataset("dany0407/eli5_category", split="train[:5000]")Разбейте train-подмножество датасета на обучающую и тестовую выборки с помощью метода train_test_split:
>>> eli5 = eli5.train_test_split(test_size=0.2)Теперь рассмотрим пример:
>>> 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']}Хотя это может показаться большим объёмом, на самом деле вас интересует только поле text. В задачах языкового моделирования не нужны метки (это также называется задачей без учителя), потому что следующее слово и есть метка.
Предобработка
Для маскированного моделирования языка следующим шагом является загрузка токенизатора DistilRoBERTa для обработки подполя text:
>>> from transformers import AutoTokenizer
>>> tokenizer = AutoTokenizer.from_pretrained("distilbert/distilroberta-base")Как видно из примера выше, поле text вложенно внутри answers. Это означает, что вам нужно извлечь подполе text из его вложенной структуры с помощью метода 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']}Каждое подполе теперь представляет собой отдельный столбец, что обозначено префиксом answers, а поле text теперь является списком. Вместо токенизации каждого предложения по отдельности преобразуйте список в строку, чтобы токенизировать их совместно.
Вот первая функция предобработки, которая объединяет список строк для каждого примера и токенизирует результат:
>>> def preprocess_function(examples):
... return tokenizer([" ".join(x) for x in examples["answers.text"]])Чтобы применить эту функцию предобработки ко всему набору данных, используйте метод map библиотеки 🤗 Datasets. Вы можете ускорить работу функции map, установив batched=True для обработки нескольких элементов набора данных за один раз, а также увеличив количество процессов с помощью num_proc. Удалите все столбцы, которые вам не нужны:
>>> tokenized_eli5 = eli5.map(
... preprocess_function,
... batched=True,
... num_proc=4,
... remove_columns=eli5["train"].column_names,
... )Этот набор данных содержит последовательности токенов, однако некоторые из них длиннее максимальной длины входных данных для модели.
Теперь вы можете использовать вторую функцию предобработки, чтобы
- объединить все последовательности
- разделить объединённые последовательности на более короткие фрагменты, определяемые параметром
block_size, которые должны быть короче максимальной длины входных данных и достаточно короткими для вашей GPU RAM.
>>> 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Примените функцию group_texts ко всему набору данных:
>>> lm_dataset = tokenized_eli5.map(group_texts, batched=True, num_proc=4)Теперь создайте батч примеров с помощью DataCollatorForLanguageModeling. При сборке батча эффективнее динамически дополнять предложения до максимальной длины в рамках этого батча, чем дополнять весь датасет до максимальной длины.
Используйте токен конца последовательности в качестве токена отступов и укажите mlm_probability, чтобы случайно маскировать токены при каждой итерации по данным:
>>> from transformers import DataCollatorForLanguageModeling
>>> tokenizer.pad_token = tokenizer.eos_token
>>> data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15)Обучение
Если вы не знакомы с дообучением модели с помощью Trainer, загляните в базовое руководство здесь!
Теперь вы готовы приступить к обучению модели! Загрузите DistilRoBERTa с помощью AutoModelForMaskedLM:
>>> from transformers import AutoModelForMaskedLM
>>> model = AutoModelForMaskedLM.from_pretrained("distilbert/distilroberta-base")На этом этапе осталось выполнить только три шага:
- Определите гиперпараметры обучения в TrainingArguments. Единственный обязательный параметр —
output_dir, который указывает, куда сохранять модель. Вы загрузите эту модель на Hub, установивpush_to_hub=True(для загрузки модели необходимо войти в аккаунт Hugging Face). - Передайте аргументы обучения в Trainer вместе с моделью, наборами данных и коллаборатором данных.
- Вызовите train(), чтобы дообучить модель.
>>> 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()После завершения обучения используйте метод evaluate(), чтобы оценить модель и получить её perplexity:
>>> import math
>>> eval_results = trainer.evaluate()
>>> print(f"Perplexity: {math.exp(eval_results['eval_loss']):.2f}")
Perplexity: 8.76Затем опубликуйте свою модель на Hub с помощью метода push_to_hub(), чтобы все могли ею пользоваться:
>>> trainer.push_to_hub()Более подробный пример дообучения модели для задачи masked language modeling можно найти в соответствующем ноутбуке PyTorch.
Вывод
Отлично, теперь, когда вы дообучили модель, вы можете использовать её для инференса!
Придумайте текст, в котором модель должна заполнить пропуск, и используйте специальный токен <mask> для обозначения пропуска:
>>> text = "The Milky Way is a <mask> galaxy."Самый простой способ проверить вашу дообученную модель для инференса — использовать её в pipeline(). Создайте экземпляр pipeline для задачи fill-mask с вашей моделью и передайте в него текст. При желании вы можете использовать параметр top_k, чтобы указать, сколько предсказаний возвращать:
>>> 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.'}]Токенизируйте текст и верните input_ids в виде тензоров PyTorch. Также необходимо указать позицию токена <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]Передайте входные данные в модель и верните logits для замаскированного токена:
>>> 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, :]Затем верните три замаскированных токена с наивысшей вероятностью и выведите их:
>>> 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.