Introduction
Chapter 7. 자연어 이해(NLU) Task 강의의 한국어 관계 추출 Task 실습 강의입니다.
이번 실습에서는 (1) KLUE-BERT base 모델을 KLUE 벤치마크 데이터셋의 KLUE-RE 데이터셋으로 파인튜닝하고, (2) 파인튜닝한 모델의 관계 추출 작업에 대한 성능과 결과를 분석해보겠습니다.
!pip install transformers
1. KLUE-BERT 모델 불러오기
오늘 실습에는 대표적인 한국어 BERT 모델인 KLUE-BERT 모델을 사용합니다!
KLUE-BERT 모델은 벤치마크 데이터인 KLUE에서 베이스라인으로 사용되었던 모델로,모두의 말뭉치, CC-100-Kor, 나무위키, 뉴스, 청원 등 문서에서 추출한 63GB의 데이터로 학습된 모델입니다.
Morpheme-based Subword Tokenizer를 사용하였으며, vocab size는 32,000이고 모델의 크기는 111M입니다.
KLUE Benchmark : https://klue-benchmark.com/
KLUE-BERT : https://huggingface.co/klue/bert-base
from transformers import AutoTokenizer, AutoConfig, AutoModelForSequenceClassification
MODEL_NAME = "klue/bert-base" # 이름을 통해 모델을 불러온다.
오늘 사용할 데이터셋의 관계 레이블 개수는 30개이므로, 미리 모델 config에 반영합니다
model_config = AutoConfig.from_pretrained(MODEL_NAME)
model_config.num_labels = 30 # 레이블 수
model_config
model = AutoModelForSequenceClassification.from_pretrained(MODEL_NAME, config=model_config) # 30개의 레이블을 사용한다
model.parameters # 레이어를 다 볼 수 있다.
print(model.config)
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
2. 데이터셋 로드하기
본 실습에서는 KLUE 벤치마크 데이터셋 중 하나인 KLUE-RE 데이터셋을 사용하여, 자연어 추론 실습을 진행합니다.
해당 데이터셋에 대한 라이센스는 여기서 확인할 수 있습니다.
2.1. 관계 추출(RE) 작업 설명
관계 추출(RE) 작업은 텍스트에서 엔티티 쌍 간의 의미론적 관계를 식별하는 작업입니다.
구체적으로, 관계는 subject entity와 object entity 쌍 간에 정의되는 값으로,
예를 들어서, 'Kierkegaard was born to an affluent family in Copenhagen
’이라는 문장에서
subject_entity
가 Kierkegaard, object_entity
가 Copenhagen일 때,
관계 추출 작업의 목표는 두 엔티티 간의 관계가 place_of_birth임을 예측하는 것입니다.
2.2. 데이터셋 특징
본 데이터셋은 관계 추출(RE) 작업을 단일 문장 분류 작업으로 공식화하며,
따라서, 관계 추출 모델은 주어진 문장 내 두 엔티티 간의 관계를 설명하는 사전 정의된 관계 유형 중 하나를 예측해야 합니다.
데이터셋은 총 30개의 사전 정의된 관계 레이블을 사용하며, 구체적으로 18개의 person_related
관계, 11개의 organization_related
관계, 그리고 no_relation이
있습니다.
데이터셋은 WIKIPEDIA, WIKITREE 및 정책 말뭉치에서 후보 문장을 샘플링한 뒤, 다양한 엔터티 및 관계형 사실들을 추출하여 구축되었습니다.
데이터셋 내 문장의 최대 길이는 최대 510단어로 제한되었고,
우리는 사회적으로 편향될 수 있는 일부 문장을 걸러내거나 맥락 없이 제시될 때 증오심 표현을 포함할 수 있습니다.
KLUE RE 벤치마크 데이터셋은 (1) micro-F1 score, (2) precision-recall curve (AUPRC)로 평가 됩니다.
2.3. 데이터셋 로드
KLUE 벤치마크 데이터셋과 KLUE-RE 데이터셋은 다음의 위치에서 다운로드할 수 있습니다.
!wget -O ./raw_train_dataset.json https://raw.githubusercontent.com/KLUE-benchmark/KLUE/main/klue_benchmark/klue-re-v1.1/klue-re-v1.1_train.json
!wget -O ./raw_test_dataset.json https://raw.githubusercontent.com/KLUE-benchmark/KLUE/main/klue_benchmark/klue-re-v1.1/klue-re-v1.1_dev.json
!wget -O ./relation_list.json https://raw.githubusercontent.com/KLUE-benchmark/KLUE/main/klue_benchmark/klue-re-v1.1/relation_list.json
import pandas as pd
import torch
import json
label_list = []
with open("relation_list.json", "r") as f:
relation_list = json.load(f) # 제이슨을 리스트 형식으로 변화해준다.
label_list = relation_list["relations"]
print(label_list[:10])
print(len(label_list))
['no_relation', 'org:dissolved', 'org:founded', 'org:place_of_headquarters', 'org:alternate_names', 'org:member_of', 'org:members', 'org:political/religious_affiliation', 'org:product', 'org:founded_by']
30
def tranform_to_dataset(file_path):
dataset = []
with open(file_path, "r") as f:
raw_data = json.load(f)
for data in raw_data:
dataset.append([
data["sentence"],
data["subject_entity"],
data["object_entity"],
data["label"]
])
return dataset
raw_train_dataset = tranform_to_dataset("raw_train_dataset.json")
raw_test_dataset = tranform_to_dataset("raw_test_dataset.json")
print('Length of Train : ',len(raw_train_dataset)) # 32470
print('Length of Test : ',len(raw_test_dataset)) #7745
raw_test_dataset[0]
["20대 남성 A(26)씨가 아버지 치료비를 위해 B(30)씨가 모아둔 돈을 훔쳐 인터넷 방송 BJ에게 '별풍선'으로 쏜 사실이 알려졌다.",
{'word': 'A', 'start_idx': 7, 'end_idx': 7, 'type': 'PER'},
{'word': '30', 'start_idx': 29, 'end_idx': 30, 'type': 'NOH'},
'no_relation']
이번에는 데이터셋 입력이 복잡하므로, tokenized_dataset 형식을 먼저 만든 뒤, 데이터셋 클래스에서 사용합니다.
[CLS] + subject_entity + [SEP] + object_entity + [SEP] + sentence + [SEP]
def tokenized_dataset(dataset, tokenizer):
""" tokenizer에 따라 sentence를 tokenizing 합니다."""
concat_entity = []
sentences = []
labels = []
for sentence, subject_entity, object_entity, label in dataset:
temp = ''
temp = subject_entity['word'] + '[SEP]' + object_entity['word']
concat_entity.append(temp)
sentences.append(sentence)
labels.append(label)
tokenized_sentences = tokenizer(
concat_entity,
sentences,
return_tensors="pt",
padding=True,
truncation=True,
max_length=256,
add_special_tokens=True,
)
return tokenized_sentences, labels
#이번 실습에서는 3000개의 학습 데이터셋과 1000개의 테스트 데이터셋만 사용해볼게요!
tokenized_train_dataset, train_labels = tokenized_dataset(raw_train_dataset[:3000], tokenizer)
tokenized_test_dataset, test_labels = tokenized_dataset(raw_test_dataset[:1000], tokenizer)
tokenized_train_dataset[0].ids
2.4. 데이터셋 클래스 선언
import torch
from torch import nn
import torch.nn.functional as F
import numpy as np
from torch.utils.data import Dataset, DataLoader
class RE_Dataset(Dataset):
""" Dataset 구성을 위한 class."""
def __init__(self, pair_dataset, labels, dict_label_to_num):
self.pair_dataset = pair_dataset
self.labels = labels
def __getitem__(self, idx):
item = {key: val[idx].clone().detach() for key, val in self.pair_dataset.items()}
item['labels'] = torch.tensor(dict_label_to_num[self.labels[idx]])
return item
def __len__(self):
return len(self.labels)
#label_dict 선언
idx_list = [i for i in range(len(label_list))]
dict_label_to_num = dict(zip(label_list, idx_list))
print(dict_label_to_num)
{'no_relation': 0, 'org:dissolved': 1, 'org:founded': 2, 'org:place_of_headquarters': 3, 'org:alternate_names': 4, 'org:member_of': 5, 'org:members': 6, 'org:political/religious_affiliation': 7, 'org:product': 8, 'org:founded_by': 9, 'org:top_members/employees': 10, 'org:number_of_employees/members': 11, 'per:date_of_birth': 12, 'per:date_of_death': 13, 'per:place_of_birth': 14, 'per:place_of_death': 15, 'per:place_of_residence': 16, 'per:origin': 17, 'per:employee_of': 18, 'per:schools_attended': 19, 'per:alternate_names': 20, 'per:parents': 21, 'per:children': 22, 'per:siblings': 23, 'per:spouse': 24, 'per:other_family': 25, 'per:colleagues': 26, 'per:product': 27, 'per:religion': 28, 'per:title': 29}
train_dataset = RE_Dataset(tokenized_train_dataset, train_labels, dict_label_to_num)
test_dataset = RE_Dataset(tokenized_test_dataset, test_labels, dict_label_to_num)
3. 학습 파라미터 설정
import torch
from transformers import Trainer, TrainingArguments
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
model.to(device)
이번 실습에서는 학습 코드를 직접 짜는 대신
Transformers의 Trainer API를 사용해볼게요!
🤗Transformers는 Trainer 클래스를 제공함으로써 준비된 데이터셋으로 사전 학습된 모델(pretrained models)을 미세 조정(fine-tuning)할 수 있도록 도와줍니다.
https://huggingface.co/docs/transformers/main_classes/trainer
num_epochs = 1
batch_size = 16
learning_rate = 5e-5
weight_decay = 0.01
warmup_steps = 100
logging_steps = 100
training_args = TrainingArguments(
output_dir='./results', # output directory
save_total_limit=5, # number of total save model.
num_train_epochs=num_epochs, # total number of training epochs
learning_rate=learning_rate, # learning_rate
per_device_train_batch_size=batch_size, # batch size per device during training
per_device_eval_batch_size=batch_size, # batch size for evaluation
warmup_steps=warmup_steps, # number of warmup steps for learning rate scheduler
weight_decay=weight_decay, # strength of weight decay
logging_dir='./logs', # directory for storing logs
logging_steps=logging_steps, # log saving step.
evaluation_strategy='epoch', # evaluation strategy to adopt during training
save_strategy='epoch',
load_best_model_at_end = True
)
4. 모델 학습 및 분석
import os
import pandas as pd
import numpy as np
import sklearn
import matplotlib.pyplot as plt
import itertools
from sklearn.metrics import accuracy_score, recall_score, precision_score, f1_score, confusion_matrix
4.1. 평가 metric 선언
이번 실습에서는 KLUE-RE 벤치마크 데이터셋의 성능 평가 지표와 동일한 지표를 사용하여 성능을 평가해볼게요.
def klue_re_micro_f1(preds, labels, label_list):
"""KLUE-RE micro f1 (except no_relation)"""
no_relation_label_idx = label_list.index("no_relation")
label_indices = list(range(len(label_list)))
label_indices.remove(no_relation_label_idx)
return sklearn.metrics.f1_score(labels, preds, average="micro", labels=label_indices) * 100.0
def klue_re_auprc(probs, labels):
"""KLUE-RE AUPRC (with no_relation)"""
labels = np.eye(30)[labels]
score = np.zeros((30,))
for c in range(30):
targets_c = labels.take([c], axis=1).ravel()
preds_c = probs.take([c], axis=1).ravel()
precision, recall, _ = sklearn.metrics.precision_recall_curve(targets_c, preds_c)
score[c] = sklearn.metrics.auc(recall, precision)
return np.average(score) * 100.0
def plot_confusion_matrix(c_matrix, labels, title='Confusion Matrix', cmap=plt.cm.get_cmap('Blues')):
plt.figure(figsize=(20,20))
plt.imshow(c_matrix, interpolation='nearest', cmap=cmap)
plt.title(title)
plt.colorbar()
marks = np.arange(len(labels))
nlabels = []
for k in range(len(c_matrix)):
n = sum(c_matrix[k])
nlabel = '{0}(n={1})'.format(labels[k],n)
nlabels.append(nlabel)
# plt.xticks(marks, labels)
plt.yticks(marks, nlabels)
thresh = c_matrix.max() / 2.
for i, j in itertools.product(range(c_matrix.shape[0]), range(c_matrix.shape[1])):
plt.text(j, i, c_matrix[i, j], horizontalalignment="center", color="white" if c_matrix[i, j] > thresh else "black") # 몇개씩 맞췄는지시
plt.tight_layout()
plt.ylabel('True label')
plt.xlabel('Predicted label')
plt.show()
#Trainer 모듈에서 사용할 compute_metrics 함수를 정의합니다.
def compute_metrics(pred):
""" validation을 위한 metrics function """
labels = pred.label_ids
preds = pred.predictions.argmax(-1)
probs = pred.predictions
test_confusion_matrix = confusion_matrix(labels, preds)
plot_confusion_matrix(test_confusion_matrix, labels=label_list)
# calculate accuracy using sklearn's function
f1 = klue_re_micro_f1(preds, labels, label_list)
auprc = klue_re_auprc(probs, labels)
acc = accuracy_score(labels, preds) # 리더보드 평가에는 포함되지 않습니다.
return {
'micro f1 score': f1,
'auprc' : auprc,
'accuracy': acc,
}
4.2 모델 학습
trainer = Trainer(
model=model, # the instantiated 🤗 Transformers model to be trained
args=training_args, # training arguments, defined above
train_dataset=train_dataset, # training dataset
eval_dataset=test_dataset, # evaluation dataset
compute_metrics=compute_metrics # define metrics function
)
trainer.train()
model.save_pretrained('./best_model')
학습이 덜 되어서 패턴을 잘 못찾았다.
'인공지능 > 자연어 처리' 카테고리의 다른 글
자연어 처리 정리 - 3강 Word embedding 1 (1) | 2024.04.12 |
---|---|
자연어 처리 정리 - 2강 Text mining (0) | 2024.04.12 |
자연어 처리 python 실습 - 한국어 자연어 추론 Task 실습 (1) | 2024.04.11 |
자연어 이해 NLU - Relation Extraction Task 관계 추출 (0) | 2024.04.11 |
자연어 처리 온라인 강의 - Machine Translation with RNN (0) | 2024.04.10 |