[부스트캠프 NLP] NLP 4주차 학습정리

AirPlaneMode·2021년 10월 1일
0

부스트캠프 NLP

목록 보기
4/7
post-thumbnail

0. 서론

 NLP 4주차는 본격적인 KLUE Dataset을 활용한 Competition이 시작된 주다. 또한, 이에 맞춰 강의 역시 이론보다는 코드실습 위주로 진행되었다.

1. 학습정리

 이번 주 강의는 총 7개가 올라왔다. 7개의 강의 중 이론에 강한 내용은 이미 정리가 완료되어 따로 다루진 않으며, 6강부터의 내용은 이번 Compeition과 관련이 없기에 차후 따로 다룰 수 있도록 하겠다. 따라서, Compeition과 관련된 5주차 강의실습을 중심적으로 학습정리를 진행하도록 한다.

1.1. BERT 언어모델 기반의 단일 문장 분류

 강의에 따르면 RE(Relation Extraction) task는 단일문장분류 문제로 풀수 있다고 한다. NLP 문제를 풀 때의 pipeline은 다음과 같다.

1. Dataset download
2. Dataset PreProcessing and tokenization
3. DataLoader 설계
4. Train, Test Dataset 준비
5. Training Argument 설정
6. Pretrained Model import
7. Trainer 설정
8. Model 학습
9. Predict함수 구현 및 평가

저작권 문제로 전체 코드는 공개하지 않습니다.

1.1.1 Tokenizer 불러와 사용하기

from transformers import AutoTokenizer

MODEL_NAME = "bert-base-multilingual-cased"
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)

AutoTokenizer는 Generic한 Tokenizer를 생성하는 Class로, from_pretrained()변수로 pre-trained된 tokenizer를 불러 사용할 수 있다.

본 예시에서는 bert-base-multilingual-cased를 불러와 사용하였다.

tokenized_train_sentence = tokenizer(
    text = "동해 물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라 만세 무궁화 삼천리 화려 강산 대한 사람, 대한으로 길이 보전하세.",
    return_tensors="pt",
    padding=True,
    truncation=True,
    add_special_tokens=True
    )
  • text: Tokenization의 대상이 되는 문장들의 집합
  • return_tensors : Token을 어떤 type으로 반환할지 설정; tf, pt, np 가 있으며, 본 예시에서는 Tensor로 반환하기 위해 pt로 설정하였다.
  • padding : 해당 batch가 가질 수 있는 가장 긴 길이로 padding한다.
  • truncation : 문장이 사전에 설정된 길이보다 길 경우 자른다.
  • add_special_tokens : <CLS>, <SEP> 등의 special token을 추가한다.

Tokenizer의 결과는 다음과 같다.

print(tokenized_train_sentences[0])
>> Encoding(num_tokens=49, attributes=[ids, type_ids, tokens, offsets, attention_mask, special_tokens_mask, overflowing])
  • 총 몇 개의 Token이 반환되었는지, attribute는 뭐가 있는지 반환한다.
print(tokenized_train_sentences[0].ids)
>> [101, 9095, 14523, 9299, 11882, 9331, 118802, 21386, 10739, 9246, 81220, 100, 9952, 118760, 108578, 10739, 9356, 27355, 35506, 12945, 9604, 12692, 82490, 9248, 24982, 9294, 118658, 18227, 9410, 38631, 12692, 9993, 26737, 8853, 21386, 18154, 9405, 61250, 117, 18154, 11467, 8934, 10739, 9356, 16617, 35506, 24982, 119, 102]
  • Embedding vector의 index를 반환한다. Special Token은 포함되지만, Padding Token은 포함되지 않는다.
print(tokenized_train_sentences[0].type_ids)
>> [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  • 첫 번째 문장에 대한 값은 0, 두 번째 문장에 대한 값은 1로 반환된다. 본 예시에서는 한 문장을 넣었으므로 모두 0으로 치환되었다.
print(tokenized_train_sentences[0].tokens)
>> ['[CLS]', '동', '##해', '물', '##과', '백', '##두', '##산', '##이', '마', '##르고', '[UNK]', '하', '##느', '##님', '##이', '보', '##우', '##하', '##사', '우', '##리', '##나라', '만', '##세', '무', '##궁', '##화', '삼', '##천', '##리', '화', '##려', '강', '##산', '대한', '사', '##람', ',', '대한', '##으로', '길', '##이', '보', '##전', '##하', '##세', '.', '[SEP]']
  • Tokenization의 결과를 반환한다.
print(tokenized_train_sentences[0].offsets)
>> [(0, 0), (0, 1), (1, 2), (3, 4), (4, 5), (6, 7), (7, 8), (8, 9), (9, 10), (11, 12), (12, 14), (15, 18), (19, 20), (20, 21), (21, 22), (22, 23), (24, 25), (25, 26), (26, 27), (27, 28), (29, 30), (30, 31), (31, 33), (34, 35), (35, 36), (37, 38), (38, 39), (39, 40), (41, 42), (42, 43), (43, 44), (45, 46), (46, 47), (48, 49), (49, 50), (51, 53), (54, 55), (55, 56), (56, 57), (58, 60), (60, 62), (63, 64), (64, 65), (66, 67), (67, 68), (68, 69), (69, 70), (70, 71), (0, 0)]
  • Token이 원본 문장에서 어느 위치에 있었는지 반환한다.
print(tokenized_train_sentences[0].attention_mask)
>> [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
  • padding token은 0으로, 나머지는 1로 반환한다.
print(tokenized_train_sentences[0].special_tokens_mask)
>> [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
  • Speical Token은 1로, 나머지는 0로 반환한다. <CLS> 토큰과 <SEP> 토큰이 위치한 맨 앞과 뒤는 1인 것을 확인할 수 있다.

1.1.2 Dataset 만들기

class SingleSentDataset(torch.utils.data.Dataset):
    def __init__(self, encodings, labels):
        self.encodings = encodings
        self.labels = labels

    def __getitem__(self, idx):
        item = {key: torch.tensor(val[idx]) for key, val in self.encodings.items()}
        item['labels'] = torch.tensor(self.labels[idx])
        return item

    def __len__(self):
        return len(self.labels)

train_dataset = SingleSentDataset(tokenized_train_sentences, train_label)
test_dataset = SingleSentDataset(tokenized_test_sentences, test_label)

 인코딩한 값을 Tensor로 바꿔준 후, 'label' key를 추가하는 것으로 작업이 간단히 완성된다.

1.1.3 Traning Argument 설정

from transformers import BertForSequenceClassification, Trainer, TrainingArguments

training_args = TrainingArguments(
    output_dir='./results',          # 결과를 저장할 폴더
    num_train_epochs=1,              # 학습 에폭
    per_device_train_batch_size=32,  # 디바이스 별 배치사이즈
    per_device_eval_batch_size=64,   # 평가 시의 배치사이즈
    warmup_steps=500,                # 학습률이 증가하는 step의 수
    weight_decay=0.01,               # warmup 후의 학습률 감소 정도
    logging_dir='./logs',            # 로그값을 저장할 폴더
    logging_steps=500,		     # 로그값을 저장할 주기
    save_steps=500,		     # 모델을 저장할 주기
    save_total_limit=2		     # 최종적으로 저장될 모델의 최대 개수
)

1.1.4 Pretraind Model 불러온 후 학습

model = BertForSequenceClassification.from_pretrained(MODEL_NAME)
model.to(device)

trainer = Trainer(
    model=model,                         # the instantiated 🤗 Transformers model to be trained
    args=training_args,                  # training arguments, defined above
    train_dataset=train_dataset,         # training dataset
)

trainer.train()

 Trainer는 학습의 편의를 위해 transformer에서 제공하는 함수로, 마치 tensorflow처럼 변수를 입력한 후 train()을 실행하는 것으로 train을 진행할 수 있도록 해준다.

굳이 Trainer를 사용하지 않더라도, 일반 pytorch 환경에서 train을 하던 것처럼 train을 할 수도 있다.

1.1.7 Evaluation

from sklearn.metrics import precision_recall_fscore_support, accuracy_score

def compute_metrics(pred):
    labels = pred.label_ids
    preds = pred.predictions.argmax(-1)
    precision, recall, f1, _ = precision_recall_fscore_support(labels, preds, average='binary')
    acc = accuracy_score(labels, preds)
    return {
        'accuracy': acc,
        'f1': f1,
        'precision': precision,
        'recall': recall
    }
    
trainer = Trainer(
    model=model,
    args=training_args,
    compute_metrics=compute_metrics
)

trainer.evaluate(eval_dataset=test_dataset)

Test를 할 때에는 앞선 작업을 다시 반복해주면 된다.

  • test를 위한 tokenizer 정의
  • torch.no_grad()를 통해 Gradient 계산을 비활성화 해준 후, trained model에 token값 전달

2. 피어세션

 이번주 피어세션엔 주로 Competition에 관한 이야기를 나누었다.

  • Dataset내 Label 간의 분표가 불균형한데, 데이터 증강을 어떻게 할 것인가?
    • (1) 번역, (2) 비슷한 단어 대체, (3) 모델을 통한 증강을 실험해보자.
  • Dataset 전처리는 어떻게 할 것인가?
    • 서로 다른 따옴표를 전부 하나로(작은 따옴표)로 통일
    • 서로 다른 괄호를 전부 소괄호로 통일
    • 서로 다른 꺾쇠 기호를 전부 《》로 통일
    • 가운데점을 모두 쉼표로 통일
    • 앞서 언급된 특수문자와 문장부호(.,!?)를 제외한 모든 특수문자 삭제
        str_ = re.sub("[“‘’”\"]","\'", str_) # unify different quotation marks
        str_ = re.sub("\'{2,}","\'", str_) # remove duplicated quotation marks

        str_ = re.sub("[\[{(([{〔]","(", str_) # unify different brackets
        str_ = re.sub("[\]}))]}〕]",")", str_)
        str_ = re.sub("\({2,}","(", str_)
        str_ = re.sub("\){2,}",")", str_)

        str_ = re.sub("[〈《「『【]","《", str_) # unify different brackets
        str_ = re.sub("[〉》」』】]","》", str_)
        str_ = re.sub("《{2,}","《", str_)
        str_ = re.sub("》{2,}","》", str_)

        str_ = re.sub("·",", ",str_)

        str_ = re.sub("[^a-zA-Z0-9가-힣ㄱ-ㅎㅏ-ㅣぁ-ゔァ-ヴー々〆〤一-龥\'\-~.,?!()《》 ]", "", str_) # remove special chars
        str_ = re.sub("\.{2,}",".", str_) # multiple punkts to one. 
        str_ = re.sub(" {2,}"," ", str_) # multiple blanks to one.
  • 사용 모델은?
    • KLUE-RE 문제에서 가장 효과적이었던 RoBERTa 사용

3. 회고

 Competition 참여와 강의 수강을 동시에 하려다보니 시간이 빠듯한 감이 있었다. 강의는 실습 위주로 진행됐음에도 불구하고 주로 이론 위주로 공부했는데, 주말을 활용해 실습코드를 한 번 확인해보는 시간을 가지도록 하겠다.

또한, 이번 데이터셋은 편향이 심하기 때문에 모델 역시 편향되게 학습할 가능성이 높다. 이 문제를 해결하기 위해 마찬가지로 주말에 전처리와 데이터 증강에 집중해야겠다.


참조

thumbnail : https://velog.io/@oneook/썸네일-메이커Thumbnail-Maker-Toy-Project

예제코드 : BERT 기반 단일 문장 분류 모델 학습 - 0_단일문장분류 (김성현, 김바다, 박상희, 이정우, 이녕우, 박채훈)

0개의 댓글