๋ฌธ์ ์ ์
๋ต๋ณ์ ์์ฑํด๋ด๋ ๊ฒ์ด ์๋๋ผ ์ง๋ฌธ์์ ์ฐพ์ ์ ์๊ฒ๋๋ค.
์ด๋ฌํ ๋ฐ์ดํฐ์
์ ์ง์ ํด๋น ์ฌ์ดํธ์์ ๋ค์ด๋ฐ์ ์๋ ์์ง๋ง huggingface์ datasets์์ ๋ค์ด์ด ๊ฐ๋ฅํ๋ค.
ํ๊ฐ ๋ฐฉ๋ฒ
์ผ์ชฝ์ SQuAD ๋ฐ์ดํฐ์
์ด๋ฉฐ ์ค๋ฅธ์ชฝ์ LG์์ SQuAD๋ฅผ ํ ๋๋ก ๋ง๋ KorQuAD์ด๋ค.
F1 ์ ์๊ฐ EM ์ ์๋ณด๋ค ๋์ ๊ฒ์ ์ ์ ์๋ค.
EM์ ์์ธก๊ฐ๊ณผ ์ ๋ต์ด ์บ๋ฆญํฐ ๋จ์๋ก ์์ ํ ๋๊ฐ์ ๊ฒฝ์ฐ์๋ง 1์ ์ ๋ถ์ฌํ๋ฉฐ, ํ๋๋ผ๋ ๋ค๋ฅด๋ฉด 0์ ์ ๋ถ์ฌํ๋ค.
๋ฐ๋ฉด F1์ ์์ธก๊ฐ๊ณผ ์ ๋ต์ overlap์ ๋น์จ๋ก ๊ณ์ฐํ๋ฉฐ 0์ ๊ณผ 1์ ์ฌ์ด์ ๋ถ๋ถ์ ์๋ฅผ ๋ฐ์ ์ ์๋ค.
์ข ๋ ์์ธํ๋ ๋ค์๊ณผ ๊ฐ๋ค.
์ด ๋ G.T๊ฐ ์ฌ๋ฌ๊ฐ์ด๋ฏ๋ก ๊ฐ๊ฐ์ G.T์ ์์ธก์ ๋น๊ตํ๊ฒ ๋๊ณ ๊ฐ์ฅ ์ต๊ณ ์ ์ ์๋ฅผ F1 Score๋ก ์ง์ ํ๋ค.
Overview
์ง๋ฌธ๊ณผ ์ง๋ฌธ์ด ๊ฐ๊ฐ ์๋ฒ ๋ฉ๋์ด ๋ชจ๋ธ์ ๋ค์ด๊ฐ๊ฒ ๋๊ณ ๋ชจ๋ธ์ ํน์ ์ํ์ค๋ฅผ ๋ฐํํ๋ ๊ฒ์ด ์๋๋ผ ๋ต์ด๋ผ๊ณ ์์ธก๋๋ ํ ํฐ์ ํฌ์ง์
์ ๋ฐํํ๋ค.
2.Pre-processing
Tokenization
์ฌ๊ธฐ์๋ OOV ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด์ฃผ๊ณ ์ ๋ณดํ์ ์ผ๋ก ์ด์ ์ ๊ฐ์ง BPE๋ฅผ ์ฌ์ฉํ ๊ฒ์ด๋ฉฐ ์ด ์ค WordPiece Tokenizer๋ฅผ ์ฌ์ฉํ๋ค.
Special Tokens
[CLS] ์ง๋ฌธ [SEP] ์ง๋ฌธ
์ ๊ผด๋ก ๋ชจ๋ธ์ ์
๋ ฅ๋๋ค.
Attention Mask
์
๋ ฅ ์ํ์ค ์ค์์ attention ์ฐ์ฐ์ ํ ๋ ๋ฌด์ํ ํ ํฐ์ ํ์ํ๋ค. 0์ ๋ฌด์, 1์ ์ฐ์ฐ์ ํฌํจํ๋ค. [PAD]์ ๊ฐ์ด ์๋ฏธ๊ฐ ์๋ ํน์ํ ํฐ์ ๋ฌด์ํ๊ธฐ ์ํด ์ฌ์ฉํ๋ค.
Token Type IDs
์ง๋ฌธ์๋ 0, ์ง๋ฌธ์๋ 1์ ์ฃผ๋ฉฐ PAD์๋ ํธ์์ 0์ ์ค๋ค.
๋ชจ๋ธ ์ถ๋ ฅ๊ฐ
๋ง์ฝ, ์ ๋ต์ด '๋ฏธ๊ตญ ์ก๊ตฐ ๋ถ ์ฐธ๋ชจ ์ด์ฅ' ์ด๋ผ๋ฉด 84์ 88์ ๋ฐํํ๊ฒ ๋๋ค. ๊ทผ๋ฐ ๋ง์ฝ ํ ํฐํ๊ฐ '์ ๋ฏธ๊ตญ', '์ก๊ตฐ', '๋ถ', '์ฐธ๋ชจ์ด', '์ฅ์ด' ๋ก ๋์ด์์ผ๋ฉด ์ด๋ป๊ฒ ํ ๊น? ์ด ๋๋ ์ ์๊ฐ ์ข ๋ฎ์์ง ์๋ ์๊ฒ ์ง๋ง ๊ทธ๋๋ ์ต์ SPAN์ ์ก์๋ค๋ ๊ฒ์ผ๋ก ์ธ์งํ๊ณ ๊ทธ๋๋ก ์ด ํ ํฐ์ ์ฌ์ฉํ๊ฒ ๋๋ค.
3.Fine-tuning
start, end token์ด๋ผ๊ณ ์์๋๋ ๋ฒกํฐ๋ ํด๋น ํฌ์ง์
์ด ์ง์ง ์์, ๋ token์ผ ํ๋ฅ ์ด๋ฉฐ ์ด๋ฅผ ์ค์ ๋ต์ start/end ์์น์ cross-entropy loss๋ฅผ ํตํด ํ์ตํ๋ค.
4.Post-processing
๋ถ๊ฐ๋ฅํ ๋ต ์ ๊ฑฐํ
์ง๋ฌธ์์ ๋ต์ด ์์ ๊ฒฝ์ฐ ์์ธกํ context๋ฅผ ๋ฒ์ด๋ ์ ์๋ค.
์ต์ ์ ๋ต์ ์ฐพ๊ธฐ
์ค์ต
Requirements
Copy !pip install datasets==1.4.1
!pip install transformers==4.4.1
# To use utility functions defined in examples.
!git clone https://github.com/huggingface/transformers.git
import sys
sys.path.append('transformers/examples/question-answering')
from datasets import load_dataset
datasets = load_dataset("squad_kor_v1")
7 : ํน์ ํด๋์ ์๋ ํน์ ํ์ด์ฌ ์ฝ๋๋ค์ ํ์ฌ ํ๊ฒฝ์ import ํ ์ ์๋ค.
๋ฐ์ดํฐ ๋ฐ ํ๊ฐ ์งํ ๋ถ๋ฌ์ค๊ธฐ
Copy from datasets import load_dataset
datasets = load_dataset("squad_kor_v1")
from datasets import load_metric
metric = load_metric('squad')
dataset์ train๊ณผ valid๋ก ์ด๋ฃจ์ด์ ธ ์์ผ๋ฉฐ dataset์ ๊ฐ๊ฐ์ ์์ id, title, context, question, answers๋ก ์ด๋ฃจ์ด์ ธ ์๋ค.
dataset['train']
์ผ๋ก train dataset์ ์ ๊ทผํ ์ ์์ผ๋ฉฐ dataset['train'][0]
์ผ๋ก ํน์ example์ ์ ๊ทผํ ์ ์๋ค.
Pre-trained ๋ชจ๋ธ ๋ถ๋ฌ์ค๊ธฐ
Copy from transformers import (
AutoConfig,
AutoModelForQuestionAnswering,
AutoTokenizer
)
model_name = "bert-base-multilingual-cased"
config = AutoConfig.from_pretrained(
model_name
)
tokenizer = AutoTokenizer.from_pretrained(
model_name,
use_fast=True
)
model = AutoModelForQuestionAnswering.from_pretrained(
model_name,
config=config
)
config์ tokenizer ๊ทธ๋ฆฌ๊ณ model์ Auto series library๋ฅผ ํตํด ๋ถ๋ฌ์ฌ ์ ์๋ค.
์ค์ ํ๊ธฐ
Copy max_seq_length = 384 # ์ง๋ฌธ๊ณผ ์ปจํ
์คํธ, special token์ ํฉํ ๋ฌธ์์ด์ ์ต๋ ๊ธธ์ด
pad_to_max_length = True
doc_stride = 128 # ์ปจํ
์คํธ๊ฐ ๋๋ฌด ๊ธธ์ด์ ๋๋ด์ ๋ ์ค๋ฒ๋ฉ๋๋ ์ํ์ค ๊ธธ์ด
max_train_samples = 16
max_val_samples = 16
preprocessing_num_workers = 4
batch_size = 4
num_train_epochs = 2
n_best_size = 20
max_answer_length = 30
max_seq_length๋ฅผ ์ค์ ํด์ผ ๋ชจ๋ธ์ size๋, pad๋ ์ ํ ์ ์๋ค.
pad_to_max_length=True๋ ๋จ์ ์ํ์ค๋ฅผ pad๋ก ์ฑ์ฐ๊ฒ ๋ค๋ ๊ฒ
max_train_samples, max_val_samples : ํ์ต ๋ฐ ๊ฒ์ฆํ ๋ฐ์ดํฐ ์๋ฅผ ์ ํด๋๋๋ค. ์ฌ๊ธฐ์๋ ๊ฐ๋จํ ํ
์คํธ ์ฉ์ด๊ธฐ ๋๋ฌธ์ ์๊ฒ ์ ํ์ง๋ง ์ค์ ๋ก๋ ๋งค์ฐ ํฐ ์๊ฐ ์
๋ ฅ๋๋ค.
preprocessing_num_workes : 4์ด์์ผ๋ก๋ ๋ณดํต ํ์๊ฐ ์๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ผ๋ฉฐ ํ๋์จ์ด์ dependent ํ๋ค.
n_best_size : ๋ต๋ณ ๊ธธ์ด์ ์ต์ ์ ๊ธธ์ด๋ฅผ ์ค์ ํ๋ค.
max_answer_length : ๋๋ฌด ๊ธด ๋ต๋ณ์ด ๋์ค์ง ์๋๋ก ์กฐ์ ํ๋ค.
์ ์ฒ๋ฆฌํ๊ธฐ
Copy def prepare_train_features(examples):
# ์ฃผ์ด์ง ํ
์คํธ๋ฅผ ํ ํฌ๋์ด์ง ํ๋ค. ์ด ๋ ํ
์คํธ์ ๊ธธ์ด๊ฐ max_seq_length๋ฅผ ๋์ผ๋ฉด stride๋งํผ ์ฌ๋ผ์ด๋ฉํ๋ฉฐ ์ฌ๋ฌ ๊ฐ๋ก ์ชผ๊ฐฌ.
# ์ฆ, ํ๋์ example์์ ์ผ๋ถ๋ถ์ด ๊ฒน์น๋ ์ฌ๋ฌ sequence(feature)๊ฐ ์๊ธธ ์ ์์.
tokenized_examples = tokenizer(
examples["question"],
examples["context"],
truncation="only_second", # max_seq_length๊น์ง truncateํ๋ค. pair์ ๋๋ฒ์งธ ํํธ(context)๋ง ์๋ผ๋.
max_length=max_seq_length,
stride=doc_stride,
return_overflowing_tokens=True, # ๊ธธ์ด๋ฅผ ๋์ด๊ฐ๋ ํ ํฐ๋ค์ ๋ฐํํ ๊ฒ์ธ์ง
return_offsets_mapping=True, # ๊ฐ ํ ํฐ์ ๋ํด (char_start, char_end) ์ ๋ณด๋ฅผ ๋ฐํํ ๊ฒ์ธ์ง
padding="max_length",
)
# example ํ๋๊ฐ ์ฌ๋ฌ sequence์ ๋์ํ๋ ๊ฒฝ์ฐ๋ฅผ ์ํด ๋งคํ์ด ํ์ํจ.
overflow_to_sample_mapping = tokenized_examples.pop("overflow_to_sample_mapping")
# offset_mappings์ผ๋ก ํ ํฐ์ด ์๋ณธ context ๋ด ๋ช๋ฒ์งธ ๊ธ์๋ถํฐ ๋ช๋ฒ์งธ ๊ธ์๊น์ง ํด๋นํ๋์ง ์ ์ ์์.
offset_mapping = tokenized_examples.pop("offset_mapping")
# ์ ๋ต์ง๋ฅผ ๋ง๋ค๊ธฐ ์ํ ๋ฆฌ์คํธ
tokenized_examples["start_positions"] = []
tokenized_examples["end_positions"] = []
for i, offsets in enumerate(offset_mapping):
input_ids = tokenized_examples["input_ids"][i]
cls_index = input_ids.index(tokenizer.cls_token_id)
# ํด๋น example์ ํด๋นํ๋ sequence๋ฅผ ์ฐพ์.
sequence_ids = tokenized_examples.sequence_ids(i)
# sequence๊ฐ ์ํ๋ example์ ์ฐพ๋๋ค.
example_index = overflow_to_sample_mapping[i]
answers = examples["answers"][example_index]
# ํ
์คํธ์์ answer์ ์์์ , ๋์
answer_start_offset = answers["answer_start"][0]
answer_end_offset = answer_start_offset + len(answers["text"][0])
# ํ
์คํธ์์ ํ์ฌ span์ ์์ ํ ํฐ ์ธ๋ฑ์ค
token_start_index = 0
while sequence_ids[token_start_index] != 1:
token_start_index += 1
# ํ
์คํธ์์ ํ์ฌ span ๋ ํ ํฐ ์ธ๋ฑ์ค
token_end_index = len(input_ids) - 1
while sequence_ids[token_end_index] != 1:
token_end_index -= 1
# answer๊ฐ ํ์ฌ span์ ๋ฒ์ด๋ฌ๋์ง ์ฒดํฌ
if not (offsets[token_start_index][0] <= answer_start_offset and offsets[token_end_index][1] >= answer_end_offset):
tokenized_examples["start_positions"].append(cls_index)
tokenized_examples["end_positions"].append(cls_index)
else:
# token_start_index์ token_end_index๋ฅผ answer์ ์์์ ๊ณผ ๋์ ์ผ๋ก ์ฎ๊น
while token_start_index < len(offsets) and offsets[token_start_index][0] <= answer_start_offset:
token_start_index += 1
tokenized_examples["start_positions"].append(token_start_index - 1)
while offsets[token_end_index][1] >= answer_end_offset:
token_end_index -= 1
tokenized_examples["end_positions"].append(token_end_index + 1)
return tokenized_examples
๋ฐ์ดํฐ๋ฅผ ์ฌ์ ์ ์ค์ ํ๋ ๋๋ก ์ ์ฒ๋ฆฌํด์ dictionary ํํ๋ก ๋ฐํํ๊ฒ ๋๋ค.
Copy train_dataset = datasets["train"]
len(train_dataset)
>>> 60000
train_dataset = train_dataset.select(range(max_train_samples))
len(train_dataset)
>>> 16
select๋ฅผ ์ด์ฉํ๋ฉด ๋ฐ์ดํฐ๋ฅผ ์ผ์ ์ ๋งํผ ๋ฝ์ ์ ๋ค.
Copy column_names = datasets["train"].column_names
train_dataset = train_dataset.map(
prepare_train_features,
batched=True,
num_proc=preprocessing_num_workers,
remove_columns=column_names,
load_from_cache_file=True,
)
Copy def prepare_validation_features(examples):
tokenized_examples = tokenizer(
examples['question'],
examples['context'],
truncation="only_second",
max_length=max_seq_length,
stride=doc_stride,
return_overflowing_tokens=True,
return_offsets_mapping=True,
padding="max_length",
)
sample_mapping = tokenized_examples.pop("overflow_to_sample_mapping")
tokenized_examples["example_id"] = []
for i in range(len(tokenized_examples["input_ids"])):
sequence_ids = tokenized_examples.sequence_ids(i)
context_index = 1
sample_index = sample_mapping[i]
tokenized_examples["example_id"].append(examples["id"][sample_index])
tokenized_examples["offset_mapping"][i] = [
(o if sequence_ids[k] == context_index else None)
for k, o in enumerate(tokenized_examples["offset_mapping"][i])
]
return tokenized_examples
valid ๋ฐ์ดํฐ๋ ๋๊ฐ์ด ์ฒ๋ฆฌ ์ค๋ค.
Copy eval_examples = datasets["validation"]
eval_examples = eval_examples.select(range(max_val_samples))
eval_dataset = eval_examples.map(
prepare_validation_features,
batched=True,
num_proc=preprocessing_num_workers,
remove_columns=column_names,
load_from_cache_file=True,
)
Fine-tuning ํ๊ธฐ
Copy from transformers import default_data_collator, TrainingArguments, EvalPrediction
from trainer_qa import QuestionAnsweringTrainer
from utils_qa import postprocess_qa_predictions
default_data_collator : ์ฌ๋ฌ ๊ฐ์ example์ collate ํด์ฃผ๋ ์ญ
TrainingArguments : ํ์ต์ ํ ๋ ์ค ์ ์๋ config๋ฅผ ํ๋ฒ์ ์ค ์ ์๋ ํธ๋ฆฌํ ๊ธฐ๋ฅ
Eval Prediction : ์ข ๋ ํธํ๊ฒ ์์ธกํ ์ ์๋๋ก ํจ
QuestionAnsweringTrainer : ํ์ต์ ๋ ํธํ๊ฒ ์ ์๋ค.
postprocess_qa_predictions : ๊ฒฐ๊ณผ๋ฅผ ์ป๊ณ ๋์ ํ๋ฒ ๋ post process๋ฅผ ํด์ผํ๋๋ฐ ์ด๋ฅผ ๊ฐ๋ฅํ๊ฒ ํด์ค๋ค.
Copy def compute_metrics(p: EvalPrediction):
return metric.compute(predictions=p.predictions, references=p.label_ids)
Copy training_args = TrainingArguments(
output_dir="outputs",
do_train=True,
do_eval=True,
learning_rate=3e-5,
per_device_train_batch_size=batch_size,
per_device_eval_batch_size=batch_size,
num_train_epochs=num_train_epochs,
weight_decay=0.01,
)
Copy trainer = QuestionAnsweringTrainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=eval_dataset,
eval_examples=datasets["validation"],
tokenizer=tokenizer,
data_collator=default_data_collator,
post_process_function=post_processing_function,
compute_metrics=compute_metrics,
)
Copy train_result = trainer.train()
train_result
>>> TrainOutput(global_step=12, training_loss=4.897604942321777, metrics={'train_runtime': 224.1287, 'train_samples_per_second': 0.054, 'total_flos': 19604022976512.0, 'epoch': 2.0, 'init_mem_cpu_alloc_delta': 4986, 'init_mem_cpu_peaked_delta': 16725, 'train_mem_cpu_alloc_delta': 160674, 'train_mem_cpu_peaked_delta': 153619})
ํ๊ฐํ
Copy metrics = trainer.evaluate()
metrics
>>> {'epoch': 2.0, 'exact_match': 0.0, 'f1': 0.0}