Model Finetuning - 나만의 데이터로 학습 시키면서 겪은 문제#2

지난 글에서 모델 파인튜닝을 진행하기전에 고려해야할 사항 (컴퓨터 환경, 모델선택, 데이터셋 선택 등)과 파인튜닝 기법에 대해 알아보았다.

 

오늘은 모델을 로컬 환경에서 파인튜닝하는 과정에 대해서 소개하고자한다.

 

모델을 파인튜닝 하는 과정은 크게 다음과 같다.

  1. 데이터 수집
  2. 데이터 전처리
  3. 모델 학습
  4. 모델 평가

 

데이터 수집

지난 글에서 데이터셋으로 korean_rlhf_dataset 에서 2000개, 자체 제작 데이터 50개를 사용하기로 하였다.

1. korean_rlhf_dataset 데이터셋 다운로드 및 2000개 데이터 추출

git clone https://huggingface.co/datasets/jojo0217/korean_rlhf_dataset
import jsonlines

# Read the first 2000 entries from train.jsonl and write them to train_2000.jsonl
with jsonlines.open('train.jsonl', 'r') as reader, jsonlines.open('train_2000.jsonl', 'w') as writer:
    for idx, entry in enumerate(reader):
        if idx < 2000:
            writer.write(entry)
        else:
            break

2. 자체제작 데이터 50개 작성 및 가공

  • 자체제작 데이터를 text파일로 저장(makedata.txt)

  • text 파일을 jsonl파일로 변경(makedata.py)
def txt_to_jsonl(txt_filepath, jsonl_filepath):
    with open(txt_filepath, 'r', encoding='utf-8') as txt_file, open(jsonl_filepath, 'w', encoding='utf-8') as jsonl_file:
        for line in txt_file:
            # Just write the line as it's already a valid JSON string.
            jsonl_file.write(line)  # the line already includes a newline character

# Convert makedata.txt to makedata.jsonl
txt_to_jsonl("makedata.txt", "makedata.jsonl")

3. 원본 데이터 + 자체제작 데이터 (merge_data.py)

def merge_jsonl_files(filepaths, output_filepath):
    with open(output_filepath, 'w', encoding='utf-8') as output_file:
        for filepath in filepaths:
            with open(filepath, 'r', encoding='utf-8') as input_file:
                for line in input_file:
                    output_file.write(line)  # Each line is a valid JSON string

# Merge makedata.jsonl and train_2000.jsonl into final_data.jsonl
merge_jsonl_files(["makedata.jsonl", "train_2000.jsonl"], "final_data.jsonl")

 

데이터 전처리

1. 학습할 수 있는 형식에 맞게 데이터 전처리

import json

# Open the original file for reading and the new file for writing
with open('final_2050.jsonl', 'r', encoding='utf-8') as infile, open('final_2050_text.jsonl', 'w', encoding='utf-8') as outfile:
    for line in infile:
        # Load the JSON data from the current line
        data = json.loads(line)

        # Reformat the data
        reformatted_data = {
                "text": "Below is an instruction that describes a sentiment analysis task ### Instruction: {} ### Input: {} ### Output: {}".format(
                    data['instruction'], data['input'], data['output']
            )
        }

        # Write the reformatted data to the new file
        outfile.write(json.dumps(reformatted_data, ensure_ascii=False) + '\n')

2. huggingface에 업로드

huggingface에 python 코드를 작성해서 업로드하면, 데이터셋이 다 깨져버렸다.

그래서 huggingface에 직접 데이터 셋을 업로드했다. 

 

모델 학습

1. 모델을 학습시키기 위해서 모델을 다운로드 받는다.

지난 글에서 모델을 학습시키기 위해 선택한 모델은 hugging face에 올라온 open-ko-llm-leaderboard의 23.10.10일 기준 1위 모델인 kyujinpy/KoT-platypus2-13B  였다.

git lfs clone https://huggingface.co/[허깅페이스 모델 레포 주소]
# ex) git lfs clone` https://huggingface.co/kyujinpy/KoT-platypus2-13B

2. 가상환경 세팅(anaconda)

  • 여기서 꼭 필요한 기능
#가상환경 만들기 
conda create –n 가상환경이름 python=3.10.13 

#가상환경 활성화
conda activate 가상환경이름
  • 그 외 알고 있으면 좋은 기능
# 가상환경 비활성화 
conda deactivate 가상환경이름

# 가상환경에 패키지 설치 
conda install 패키지이름

# 가상환경 리스트 확인 
conda env list

# 가상환경 삭제 
conda env remove -n 가상환경이름

3. 필요한 모듈 다운로드

pip install -q huggingface_hub
pip install -q -U trl transformers accelerate peft
pip install -q -U datasets bitsandbytes einops wandb

# Uncomment to install new features that support latest models like Llama 2
# pip install git+https://github.com/huggingface/peft.git
# pip install git+https://github.com/huggingface/transformers.git

3. huggingface 로그인

huggingface-cli login --token $HUGGINGFACE_TOKEN

 

4. 파인튜닝 코드(vi finetune.py) 작성

#When prompted, paste the HF access token you created earlier.
from huggingface_hub import notebook_login
notebook_login()

from datasets import load_dataset
import torch
from transformers import AutoModelForCausalLM, BitsAndBytesConfig, AutoTokenizer, TrainingArguments
from peft import LoraConfig
from trl import SFTTrainer

#private version으로 Hugging face에 올린 Dataset
dataset_name = "{your hugging face dataset address}"
dataset = load_dataset(dataset_name, split="train")

base_model_name = "models/KoT-platypus2-13B"

# BitsAndBytes(BNB) 양자화를 위한 사용자 정의 양자화 구성 정의
bnb_config = BitsAndBytesConfig(
	load_in_4bit=True, # 4비트 양자화로 모델 로드
	bnb_4bit_quant_type="nf4", # nf4 양자화 방법 사용
	bnb_4bit_compute_dtype=torch.float16, # bfloat16 데이터 유형에서 4비트 양자화된 가중치로 계산
)

device_map = {"": 0}

# 지정된 양자화 구성으로 사전 학습된 모델을 로드
base_model = AutoModelForCausalLM.from_pretrained(
	base_model_name,
	quantization_config=bnb_config,
	# A4600 두 개를 같이 사용할 수 있도록 조절
	device_map="balanced", #device_map,
	max_memory={0:"48GB", 1:"48GB"}
	trust_remote_code=True,
	use_auth_token=True
)

base_model.config.use_cache = False

# More info: https://github.com/huggingface/transformers/pull/24906
base_model.config.pretraining_tp = 1

# LoRA(학습 가능한 리퀀타이제이션 활성화) 메서드에 대한 구성을 정의
peft_config = LoraConfig(
	lora_alpha=16, # LoRA를 위한 하이퍼파라미터
	lora_dropout=0.1, # 드롭아웃 확률
	r=64, # 양자화 레벨 수
	bias="none", # 바이어스 유형
	task_type="CAUSAL_LM", # 작업 유형(이 경우, 인과 언어 모델링)
)

# 사전 학습된 동일한 모델에 대한 토큰라이저를 로드
tokenizer = AutoTokenizer.from_pretrained(base_model_name, trust_remote_code=True)
# 토큰마이저의 pad_token을 eos_token과 동일하게 설정합니다.
tokenizer.pad_token = tokenizer.eos_token

output_dir = "./results"

training_args = TrainingArguments(
	output_dir=output_dir, # 출력 파일을 저장할 디렉터리
	per_device_train_batch_size=4, # 트레이닝 중 디바이스당 배치 크기
	gradient_accumulation_steps=4, # 그라데이션 누적 단계 수
	learning_rate=2e-4,# 학습 속도
	logging_steps=10, # 훈련 중 로깅 빈도
	max_steps=500 # 최대 훈련 단계 수
)

max_seq_length = 512

# 모델 미세 조정을 위한 트레이너 만들기
trainer = SFTTrainer(
	model=base_model,
	train_dataset=dataset, # 훈련 데이터 세트
	peft_config=peft_config,
	dataset_text_field="text",
	max_seq_length=max_seq_length,
	tokenizer=tokenizer,
	args=training_args,
)

trainer.train()

import os
output_dir = os.path.join(output_dir, "final_checkpoint")
trainer.model.save_pretrained(output_dir)

 

모델 평가

text 부분에 질의할 물음을 넣으면 된다.

ex) "나는 당근을 "

from peft import AutoPeftModelForCausalLM
from transformers import AutoTokenizer
import torch

base_model_name = "models/KoT-platypus2-13B"

tokenizer = AutoTokenizer.from_pretrained(base_model_name, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token

output_dir = "{finetuning된 결과가 있는 디렉토리 주소}"
device_map = {"": 0}

# Define the device
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

model = AutoPeftModelForCausalLM.from_pretrained(
        output_dir,
        device_map="balanced", #device_map,
        max_memory={0:"48GB", 1:"48GB"},
        torch_dtype=torch.bfloat16
)
text = "{your prompt text}"
inputs = tokenizer(text, return_tensors="pt").to(device)
outputs = model.generate(input_ids=inputs["input_ids"].to("cuda"), attention_mask=inputs["attention_mask"], max_new_tokens=50, pad_token_id=tokenizer.eos_token_id)

print(tokenizer.decode(outputs[0], skip_special_tokens=True))

 

결론

  • loss 값이 0.6이하로 떨어지지 않아 파인튜닝 모델의 성능이 너무 별로였다. (할루시네이션이 너무 심함)
  • 모델의 크기가 상대적으로 작고, 한국어 모델의 한계일 수도 있다.
  • 따라서, 다음에는 영어 모델과 영어 데이터셋으로 파인튜닝을 진행할 때 epoch수를 좀 늘려서 진행해봐야겠다.

 

참고

https://ukey.co/blog/finetune-llama-2-peft-qlora-huggingface/

https://towardsdatascience.com/fine-tune-your-own-llama-2-model-in-a-colab-notebook-df9823a04a32