# Transformer Fine-tuning

### Approach to Fine-tuning

1. **Self-supervised Learning**

자기 지도 학습에서는 모델이 훈련 데이터의 내재적 구조를 기반으로 학습됩니다. 언어 모델의 경우, 주어진 순서에서 다음 단어나 토큰을 예측하는 것이 일반적입니다.(next token prediction)

초기 모델 개발 이후, 자기 지도 학습은 세세한 조정에 적용될 수 있으며, 예제 텍스트를 기반으로 특정 쓰기 스타일을 에뮬레이트하는 모델을 만드는 등의 용도로 활용될 수 있습니다.&#x20;

2. **Supervised Learning**

지도 학습은 모델 세세한 조정을 위한 인기 있는 방법 중 하나로 두드러집니다. 이는 특정 작업에 대한 입력-출력 쌍을 기반으로 모델을 훈련시키는 것을 포함합니다.

예를 들어, 지시 조정은 모델이 질문에 답하거나 사용자 프롬프트에 응답하는 능력을 향상시키는 것을 목표로 합니다.&#x20;

3. **Reinforcement Learning**

강화 학습(RL)을 사용하여 모델을 세세하게 조정하는 것입니다. RL은 보상 모델을 활용하여 기본 모델의 훈련을 안내하며, 언어 모델의 완성을 인간 라벨러의 선호도에 맞추려고 합니다. 보상 모델을 Proximal Policy Optimization (PPO)과 같은 강화 학습 알고리즘과 결합하여, 사전 훈련된 모델이 효과적으로 세세하게 조정됩니다

###

### Huggingface Fine-tuning

LLM Fine-tuning에서 적용되는 기본 요소는 여러가지가 있습니다. Huggingface에서는 이러한 기본 요소를 Transformer 기본 라이브러리에서 Class로 제공하고 이외에 peft, trl, bitsandbytes, accelerate를 함께 사용하여 fine-tuning을 지원합니다.

![](https://miro.medium.com/v2/resize:fit:1400/1*aVpH4HOg3ERXcWdmNqtQBA.png)

### Huggingface 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`

***



## Huggingface: Basic Fine-tuning



가장 기본적인 Transformer 모델을 Dataset을 통해서 Fine-tuning 하겠습니다. 절차는 아래와 같습니다:

1. Load Dataset
2. Preprocessing: Tokenization & Embedding
3. Dynamic Padding
4. Train with Trainer
5. Evaluate
6. Train with evaluate function

### Setup Environments

```python
%pip install -q transformers
%pip install -q datasets
%pip install -q -U accelerate
```



### 1. Load Dataset

Huggingface의 dataset에서 MRPC(Microsoft Research Paraphrase Corpus) Dataset을 로드하겠습니다.

```python
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의 한 문장을 확인해보면

```python
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 값을 가지고 있는지 확인해 보자

```python
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)}
```

### 2. Preprocessing

AutoTokenizer로 Token 화 변환한다.

```python
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화하게 된다.

```python
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 하게 되면 아래와 같이 얻을 수 있다

```python
tokenizer.convert_ids_to_tokens(inputs["input_ids"])
```

```
['[CLS]',
 'this',
 'is',
 'the',
 'first',
 'sentence',
 '.',
 '[SEP]',
 'this',
 'is',
 'the',
 'second',
 'one',
 '.',
 '[SEP]']
```

Dataset 객체를 생성하는기본적인 방법은 아래와 같이 정의할 수 있다.

```python
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할 수 있도록 한다.

```python
def tokenize_function(example):
    return tokenizer(
        example["sentence1"], 
        example["sentence2"], 
        truncation=True)
```

```python
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` 가 추가된 것을 확인할 수 있다.

### 3. Dynamic padding

Dataset을 DataLoader에 담아 데이터를 꺼내어 사용하게 되는데 우리는 불필요한 패딩을 줄이기위해 각 batch별로 가장 큰 길이를 지정하여 padding을 생성하게 된다.

여기서는 `DataCollatorWithPadding` 함수를 사용해 padding을 만들어 본다.

```python
from transformers import DataCollatorWithPadding

data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
```

```python
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` 에 넣어보면

```python
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])}
```

### 4. Fine-tuning with Trainer API

Transformer에서 제공가는 `Trainer` 클래스를 사용하여 fine-tune을 합니다.

앞의 Data Load, Preprocessing을 하나로 구성하여 실행하겠습니다.

```python
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를 구성합니다.

```python
from transformers import TrainingArguments

training_args = TrainingArguments("test-trainer")
```

```python
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를 구성합니다.

```python
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,
)
```

이제 학습을 시작하겠습니다.

```python
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})
```

### 5. Evaluation

validation set으로 `predict`메서드로 예측값을 뽑습니다.

```python
predictions = trainer.predict(tokenized_datasets["validation"])

print(predictions.predictions.shape, predictions.label_ids.shape)
```

```
(408, 2) (408,)
```

Softmax classification이기 때문에 argmax로 class를 결정합니다.

```python
import numpy as np

preds = np.argmax(predictions.predictions, axis=-1)
```

datasets 라이브러리에서 제공하는 load\_metric 함수로 간단하게 metrics를 계산할 수 있습니다.

```python
from datasets import load_metric

metric = load_metric("glue", "mrpc")
metric.compute(predictions=preds, references=predictions.label_ids)
```

```
{'accuracy': 0.8700980392156863, 'f1': 0.9112227805695142}
```

### 6. Trainer with Evaluation

Metric을 사용자 함수 처리해 보겠습니다.

```python
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을 반영합니다.

```python
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})
```

```python
```
