Transformer Fine-tuning
Last updated
Last updated
Self-supervised Learning
자기 지도 학습에서는 모델이 훈련 데이터의 내재적 구조를 기반으로 학습됩니다. 언어 모델의 경우, 주어진 순서에서 다음 단어나 토큰을 예측하는 것이 일반적입니다.(next token prediction)
초기 모델 개발 이후, 자기 지도 학습은 세세한 조정에 적용될 수 있으며, 예제 텍스트를 기반으로 특정 쓰기 스타일을 에뮬레이트하는 모델을 만드는 등의 용도로 활용될 수 있습니다.
Supervised Learning
지도 학습은 모델 세세한 조정을 위한 인기 있는 방법 중 하나로 두드러집니다. 이는 특정 작업에 대한 입력-출력 쌍을 기반으로 모델을 훈련시키는 것을 포함합니다.
예를 들어, 지시 조정은 모델이 질문에 답하거나 사용자 프롬프트에 응답하는 능력을 향상시키는 것을 목표로 합니다.
Reinforcement Learning
강화 학습(RL)을 사용하여 모델을 세세하게 조정하는 것입니다. RL은 보상 모델을 활용하여 기본 모델의 훈련을 안내하며, 언어 모델의 완성을 인간 라벨러의 선호도에 맞추려고 합니다. 보상 모델을 Proximal Policy Optimization (PPO)과 같은 강화 학습 알고리즘과 결합하여, 사전 훈련된 모델이 효과적으로 세세하게 조정됩니다
LLM Fine-tuning에서 적용되는 기본 요소는 여러가지가 있습니다. Huggingface에서는 이러한 기본 요소를 Transformer 기본 라이브러리에서 Class로 제공하고 이외에 peft, trl, bitsandbytes, accelerate를 함께 사용하여 fine-tuning을 지원합니다.
1. 일반적인 모델 Fine-tuning: Transformer Trainer
클래스 사용
2. LLM에서 PEFT(Parameter Efficient Fine-tuning)은 아래 라이브러리를 함께 사용합니다.
transformer :기본 라이브러리
peft: PEFT를 위한 LoRa, QLoRa 사용
trl: SFTTraine
클래스 사용
bitsandbytes: 양자화를 위한 라이브러리
accelerate: GPU 분산 학습을 위한 라이브러리
3. Reinforcement Learning은 TRL 라이브러리를 사용합니다.
Supervised fine-tuning: SFTTrainer
Direct Preference Optimization: DPOTrainer
Reward: RewardTrainer
Proximal Policy Optimization: PPOTrainer
Contrastive Preference Optimization: CPOTrainer
Optimization without Reference Model: ORPOTrainer
가장 기본적인 Transformer 모델을 Dataset을 통해서 Fine-tuning 하겠습니다. 절차는 아래와 같습니다:
Load Dataset
Preprocessing: Tokenization & Embedding
Dynamic Padding
Train with Trainer
Evaluate
Train with evaluate function
%pip install -q transformers
%pip install -q datasets
%pip install -q -U accelerate
Huggingface의 dataset에서 MRPC(Microsoft Research Paraphrase Corpus) Dataset을 로드하겠습니다.
from datasets import load_dataset
raw_datasets = load_dataset("glue", "mrpc")
raw_datasets
DatasetDict({
train: Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx'],
num_rows: 3668
})
validation: Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx'],
num_rows: 408
})
test: Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx'],
num_rows: 1725
})
})
dataset의 한 문장을 확인해보면
raw_train_dataset = raw_datasets["train"]
raw_train_dataset[0]
{'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .',
'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .',
'label': 1,
'idx': 0}
이미 label이 숫자표기로 되어있습니다. 어떤 label 값을 가지고 있는지 확인해 보자
raw_train_dataset.features
{'sentence1': Value(dtype='string', id=None),
'sentence2': Value(dtype='string', id=None),
'label': ClassLabel(names=['not_equivalent', 'equivalent'], id=None),
'idx': Value(dtype='int32', id=None)}
AutoTokenizer로 Token 화 변환한다.
from transformers import AutoTokenizer
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"])
tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"])
model에 넣을 때 sentence1과 sentence2, 두 문장을 한번에 token화하게 된다.
inputs = tokenizer("This is the first sentence.", "This is the second one.")
inputs
{'input_ids': [101, 2023, 2003, 1996, 2034, 6251, 1012, 102, 2023, 2003, 1996, 2117, 2028, 1012, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
token_type_ids
: 첫 문장과 두번째 문장의 구분을 나타내는 값
input_ids
값을 decode 하게 되면 아래와 같이 얻을 수 있다
tokenizer.convert_ids_to_tokens(inputs["input_ids"])
['[CLS]',
'this',
'is',
'the',
'first',
'sentence',
'.',
'[SEP]',
'this',
'is',
'the',
'second',
'one',
'.',
'[SEP]']
Dataset 객체를 생성하는기본적인 방법은 아래와 같이 정의할 수 있다.
tokenized_dataset = tokenizer(
raw_datasets["train"]["sentence1"],
raw_datasets["train"]["sentence2"],
padding=True,
truncation=True,
)
Dataset.map method를 사용하여 dataset의 각 element들에게 적용된다.
tokenize 함수를 아래와 같이 설정하고 dataset.map
을 통해 모든 element를 tokenize할 수 있도록 한다.
def tokenize_function(example):
return tokenizer(
example["sentence1"],
example["sentence2"],
truncation=True)
tokenized_datasets = raw_datasets.map(
tokenize_function,
batched=True
)
tokenized_datasets
Map: 0%| | 0/3668 [00:00<?, ? examples/s]
Map: 0%| | 0/408 [00:00<?, ? examples/s]
Map: 0%| | 0/1725 [00:00<?, ? examples/s]
DatasetDict({
train: Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx', 'input_ids', 'token_type_ids', 'attention_mask'],
num_rows: 3668
})
validation: Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx', 'input_ids', 'token_type_ids', 'attention_mask'],
num_rows: 408
})
test: Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx', 'input_ids', 'token_type_ids', 'attention_mask'],
num_rows: 1725
})
})
input_ids
,attention_mask
, token_type_ids
가 추가된 것을 확인할 수 있다.
Dataset을 DataLoader에 담아 데이터를 꺼내어 사용하게 되는데 우리는 불필요한 패딩을 줄이기위해 각 batch별로 가장 큰 길이를 지정하여 padding을 생성하게 된다.
여기서는 DataCollatorWithPadding
함수를 사용해 padding을 만들어 본다.
from transformers import DataCollatorWithPadding
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
samples = tokenized_datasets["train"][:8]
samples = {
k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]
}
[len(x) for x in samples["input_ids"]]
[50, 59, 47, 67, 59, 50, 62, 32]
샘플 데이터를 추출하여 data_collator
에 넣어보면
batch = data_collator(samples)
{k: v.shape for k, v in batch.items()}
{'input_ids': torch.Size([8, 67]),
'token_type_ids': torch.Size([8, 67]),
'attention_mask': torch.Size([8, 67]),
'labels': torch.Size([8])}
Transformer에서 제공가는 Trainer
클래스를 사용하여 fine-tune을 합니다.
앞의 Data Load, Preprocessing을 하나로 구성하여 실행하겠습니다.
from datasets import load_dataset
from transformers import AutoTokenizer, DataCollatorWithPadding
raw_datasets = load_dataset("glue", "mrpc")
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
def tokenize_function(example):
return tokenizer(
example["sentence1"],
example["sentence2"],
truncation=True
)
tokenized_datasets = raw_datasets.map(
tokenize_function,
batched=True
)
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
Map: 0%| | 0/1725 [00:00<?, ? examples/s]
TrainingArguments
클래스를 TrainArgument를 구성합니다.
from transformers import TrainingArguments
training_args = TrainingArguments("test-trainer")
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
Some weights of BertForSequenceClassification were not initialized from the model checkpoint at bert-base-uncased and are newly initialized: ['classifier.bias', 'classifier.weight']
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.
Trainer로 Argument Congit를 구성합니다.
from transformers import Trainer
trainer = Trainer(
model,
training_args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["validation"],
data_collator=data_collator,
tokenizer=tokenizer,
)
이제 학습을 시작하겠습니다.
trainer.train()
Step Training Loss
TrainOutput(global_step=345, training_loss=0.3608564736186594, metrics={'train_runtime': 89.8265, 'train_samples_per_second': 122.503, 'train_steps_per_second': 3.841, 'total_flos': 450668126729280.0, 'train_loss': 0.3608564736186594, 'epoch': 3.0})
validation set으로 predict
메서드로 예측값을 뽑습니다.
predictions = trainer.predict(tokenized_datasets["validation"])
print(predictions.predictions.shape, predictions.label_ids.shape)
(408, 2) (408,)
Softmax classification이기 때문에 argmax로 class를 결정합니다.
import numpy as np
preds = np.argmax(predictions.predictions, axis=-1)
datasets 라이브러리에서 제공하는 load_metric 함수로 간단하게 metrics를 계산할 수 있습니다.
from datasets import load_metric
metric = load_metric("glue", "mrpc")
metric.compute(predictions=preds, references=predictions.label_ids)
{'accuracy': 0.8700980392156863, 'f1': 0.9112227805695142}
Metric을 사용자 함수 처리해 보겠습니다.
def compute_metrics(eval_preds):
metric = load_metric("glue", "mrpc")
logits, labels = eval_preds
predictions = np.argmax(logits, axis=-1)
return metric.compute(predictions=predictions, references=labels)
compute_metrics 사용자 함수를 Trainer에서 compute_metrics 매개변수에 넣어주면 학습 과정에서 자동으로 metric을 반영합니다.
training_args = TrainingArguments(
"test-trainer",
evaluation_strategy="epoch"
)
model = AutoModelForSequenceClassification.from_pretrained(
checkpoint,
num_labels=2
)
trainer = Trainer(
model,
training_args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["validation"],
data_collator=data_collator,
tokenizer=tokenizer,
compute_metrics=compute_metrics
)
trainer.train()
Epoch Training Loss Validation Loss Accuracy F1 1 No log 0.429820 0.833333 0.881119 2 No log 0.364128 0.838235 0.887755 3 No log 0.432673 0.848039 0.893471
TrainOutput(global_step=345, training_loss=0.36642157513162366, metrics={'train_runtime': 93.1513, 'train_samples_per_second': 118.13, 'train_steps_per_second': 3.704, 'total_flos': 450668126729280.0, 'train_loss': 0.36642157513162366, 'epoch': 3.0})