NLP 4주차는 본격적인 KLUE Dataset을 활용한 Competition이 시작된 주다. 또한, 이에 맞춰 강의 역시 이론보다는 코드실습 위주로 진행되었다.
이번 주 강의는 총 7개가 올라왔다. 7개의 강의 중 이론에 강한 내용은 이미 정리가 완료되어 따로 다루진 않으며, 6강부터의 내용은 이번 Compeition과 관련이 없기에 차후 따로 다룰 수 있도록 하겠다. 따라서, Compeition과 관련된 5주차 강의실습을 중심적으로 학습정리를 진행하도록 한다.
강의에 따르면 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함수 구현 및 평가
저작권 문제로 전체 코드는 공개하지 않습니다.
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])
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]
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]
print(tokenized_train_sentences[0].tokens)
>> ['[CLS]', '동', '##해', '물', '##과', '백', '##두', '##산', '##이', '마', '##르고', '[UNK]', '하', '##느', '##님', '##이', '보', '##우', '##하', '##사', '우', '##리', '##나라', '만', '##세', '무', '##궁', '##화', '삼', '##천', '##리', '화', '##려', '강', '##산', '대한', '사', '##람', ',', '대한', '##으로', '길', '##이', '보', '##전', '##하', '##세', '.', '[SEP]']
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)]
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]
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]
<CLS>
토큰과 <SEP>
토큰이 위치한 맨 앞과 뒤는 1인 것을 확인할 수 있다.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를 추가하는 것으로 작업이 간단히 완성된다.
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 # 최종적으로 저장될 모델의 최대 개수
)
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을 할 수도 있다.
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를 할 때에는 앞선 작업을 다시 반복해주면 된다.
torch.no_grad()
를 통해 Gradient 계산을 비활성화 해준 후, trained model에 token값 전달이번주 피어세션엔 주로 Competition에 관한 이야기를 나누었다.
《》
로 통일.,!?
)를 제외한 모든 특수문자 삭제 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.
Competition 참여와 강의 수강을 동시에 하려다보니 시간이 빠듯한 감이 있었다. 강의는 실습 위주로 진행됐음에도 불구하고 주로 이론 위주로 공부했는데, 주말을 활용해 실습코드를 한 번 확인해보는 시간을 가지도록 하겠다.
또한, 이번 데이터셋은 편향이 심하기 때문에 모델 역시 편향되게 학습할 가능성이 높다. 이 문제를 해결하기 위해 마찬가지로 주말에 전처리와 데이터 증강에 집중해야겠다.
thumbnail : https://velog.io/@oneook/썸네일-메이커Thumbnail-Maker-Toy-Project
예제코드 : BERT 기반 단일 문장 분류 모델 학습 - 0_단일문장분류 (김성현, 김바다, 박상희, 이정우, 이녕우, 박채훈)