마스크 언어 모델링
Masked language modeling은 시퀀스에서 마스킹된 토큰을 예측하며, 모델은 토큰을 양방향으로 attend할 수 있습니다. 이는 모델이 왼쪽과 오른쪽의 토큰에 모두 접근할 수 있음을 의미합니다. Masked language modeling은 전체 시퀀스에 대한 좋은 문맥 이해가 필요한 작업에 적합합니다. BERT는 masked language model의 예입니다.
이 가이드에서는 다음 내용을 보여 드립니다:
- ELI5 데이터셋의 r/askscience 하위 집합에서 DistilRoBERTa를 fine-tune합니다.
- 파인튜닝한 모델을 추론에 사용하세요.
이 작업과 호환되는 모든 아키텍처와 체크포인트를 확인하려면 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 스플릿을 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 필드만 관심 있으면 됩니다. 언어 모델링 작업의 멋진 점은 다음 단어가 레이블이 되므로 레이블이 필요하지 않다는 것입니다(무감독 작업이라고도 함).
전처리
마스크 언어 모델링의 경우, 다음 단계는 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 메서드를 사용합니다. 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를 사용하여 예시 배치(batch)를 생성합니다. 데이터셋 전체를 최대 길이에 맞게 패딩하는 대신, collation 단계에서 배치 내 가장 긴 문장에 맞춰 동적으로 패딩하는 것이 더 효율적입니다.
시퀀스 종료 토큰을 패딩 토큰으로 사용하고, 데이터를 반복할 때마다 토큰을 무작위로 마스킹하도록 mlm_probability를 지정합니다:
>>> from transformers import DataCollatorForLanguageModeling
>>> tokenizer.pad_token = tokenizer.eos_token
>>> data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15)학습
Trainer를 사용하여 모델을 fine-tuning하는 방법에 대해 잘 모르신다면, 여기의 기본 튜토리얼을 참고하세요!
이제 모델 학습을 시작할 준비가 되었습니다! 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 노트북을 참고하세요.
추론
좋습니다. 이제 모델을 fine-tuning 했으므로 추론에 사용할 수 있습니다!
모델이 빈칸을 채우기를 원하는 텍스트를 작성하고, 빈칸을 나타내기 위해 특수 토큰인 <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.