마스킹된 언어 모델링
마스킹된 언어 모델링 (Masked language modeling)
마스킹된 언어 모델링은 시퀀스에서 마스킹된 토큰을 예측하며, 모델은 토큰을 양방향으로 참조(attend)할 수 있습니다. 즉, 모델은 왼쪽과 오른쪽의 토큰에 완전히 접근할 수 있습니다. 전체 시퀀스에 대한 좋은 문맥 이해가 필요한 작업에 훌륭합니다. BERT가 마스킹된 언어 모델의 예입니다.
출처: 문서
본문
이 가이드에서 다룰 내용은 다음과 같습니다.
- ELI5 데이터셋의 r/askscience 하위 집합에서 DistilRoBERTa를 파인튜닝합니다.
- 파인튜닝된 모델을 추론(inference)에 사용합니다.
이 작업과 호환되는 모든 아키텍처와 체크포인트를 보려면 task-page를 확인하는 것을 권장합니다.
시작하기 전에 필요한 라이브러리를 모두 설치했는지 확인해 주세요.
pip install transformers datasets evaluate
Hugging Face 계정에 로그인해 모델을 커뮤니티에 업로드하고 공유하는 것을 권장합니다. 프롬프트가 나타나면 토큰을 입력해 로그인하세요.
>>> from huggingface_hub import notebook_login
>>> notebook_login()
ELI5 데이터셋 로드하기
🤗 Datasets 라이브러리로 ELI5-Category 데이터셋의 처음 5000개 예시를 로드하는 것부터 시작하겠습니다. 이렇게 하면 전체 데이터셋으로 훈련하는 데 더 많은 시간을 쓰기 전에 실험해 보고 모든 것이 잘 작동하는지 확인할 수 있습니다.
>>> from datasets import load_dataset
>>> eli5 = load_dataset("dany0407/eli5_category", split="train[:5000]")
train_test_split 메서드로 데이터셋의 train 스플릿을 train과 test 세트로 나눕니다.
>>> 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 필드뿐입니다. 언어 모델링 작업의 멋진 점은 라벨이 필요 없다는 것입니다(비지도 작업이라고도 함). 다음 단어가 곧 라벨이기 때문입니다.
전처리 (Preprocess)
마스킹된 언어 모델링의 경우, 다음 단계는 text 하위 필드를 처리할 DistilRoBERTa 토크나이저를 로드하는 것입니다.
>>> from transformers import AutoTokenizer
>>> tokenizer = AutoTokenizer.from_pretrained("distilbert/distilroberta-base")
위의 예시에서 text 필드가 실제로는 answers 안에 중첩되어 있다는 것을 알 수 있습니다. 즉, flatten 메서드로 중첩 구조에서 text 하위 필드를 추출해야 합니다.
>>> 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"]])
이 전처리 함수를 전체 데이터셋에 적용하려면 🤗 Datasets map 메서드를 사용하세요. batched=True로 설정하면 데이터셋의 여러 요소를 한 번에 처리하고 num_proc으로 프로세스 수를 늘려 map 함수를 더 빠르게 할 수 있습니다. 필요하지 않은 컬럼은 모두 제거하세요.
>>> tokenized_eli5 = eli5.map(
... preprocess_function,
... batched=True,
... num_proc=4,
... remove_columns=eli5["train"].column_names,
... )
이 데이터셋에는 토큰 시퀀스가 포함되어 있지만, 일부는 모델의 최대 입력 길이보다 깁니다.
이제 두 번째 전처리 함수를 사용해
- 모든 시퀀스를 연결하고
- 연결된 시퀀스를
block_size로 정의된 더 짧은 청크로 나눌 수 있습니다.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)
훈련 (Train)
Trainer로 모델을 파인튜닝하는 방법이 익숙하지 않다면 여기의 기본 튜토리얼을 확인해 보세요!
이제 모델 훈련을 시작할 준비가 되었습니다! AutoModelForMaskedLM으로 DistilRoBERTa를 로드합니다.
>>> from transformers import AutoModelForMaskedLM
>>> model = AutoModelForMaskedLM.from_pretrained("distilbert/distilroberta-base")
이 시점에서 남은 단계는 세 가지뿐입니다.
- TrainingArguments에서 훈련 하이퍼파라미터를 정의합니다. 유일한 필수 매개변수는 모델을 저장할 위치를 지정하는
output_dir입니다.push_to_hub=True로 설정하면 이 모델을 Hub에 푸시합니다(모델을 업로드하려면 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
그런 다음 push_to_hub() 메서드로 모델을 Hub에 공유해 모두가 사용할 수 있게 하세요.
>>> trainer.push_to_hub()
마스킹된 언어 모델링을 위해 모델을 파인튜닝하는 더 심층적인 예시는 해당 PyTorch notebook을 참고하세요.
추론 (Inference)
좋습니다. 이제 모델을 파인튜닝했으니 추론에 사용할 수 있습니다!
모델이 빈칸을 채우길 원하는 텍스트를 생각해 보고, 빈칸을 나타내는 특수 <mask> 토큰을 사용하세요.
>>> text = "The Milky Way is a <mask> galaxy."
파인튜닝된 모델을 추론에 사용해 보는 가장 간단한 방법은 pipeline()에서 사용하는 것입니다. 모델로 fill-mask용 pipeline을 만들고 텍스트를 전달합니다. 원한다면 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.