4–5장|LoRA 파인튜닝과 모델 성능 평가 — SLM + Hugging Face + Ollama 실습 완결판

hiio420.official·2025년 10월 28일

4–5장|LoRA 파인튜닝과 모델 성능 평가 — SLM + Hugging Face + Ollama 실습 완결판

앞선 장에서 우리는 SLM(Small Language Model)과 Ollama 환경을 구축하고,
Hugging Face 모델을 불러오는 과정을 모두 끝냈다.
이제는 그 모델을 내 데이터로 파인튜닝(fine-tuning) 하고,
그 결과를 Perplexity 및 생성 예시로 평가한다.

이번 장은 두 파트로 구성된다.

구분내용
4장LoRA/QLoRA 기반 파인튜닝 수행
5장학습 결과 모델의 정량·정성 평가 및 로그 관리

PART 1 — 4장 : LoRA 파인튜닝 수행


1. 환경 설정

pip install -U transformers datasets peft accelerate bitsandbytes
  • transformers — Hugging Face 모델 관리
  • datasets — 데이터셋 로딩 및 전처리
  • peft — LoRA/QLoRA 어댑터 관리
  • bitsandbytes — 4bit/8bit 양자화 지원 (GPU에서만 사용)

2. 데이터 로드 및 전처리

이전 장에서 만든 train.jsonl 파일에는 instruction–response 쌍이 포함되어 있다.

from datasets import load_dataset

dataset = load_dataset("json", data_files="train.jsonl")

print(dataset)
print(dataset["train"][0])

데이터는 다음과 같이 구성되어 있어야 한다.

{"text": "### Instruction:\nExplain regression vs classification.\n### Response:\nRegression predicts continuous values..."}

3. 토크나이저 및 모델 준비

from transformers import AutoTokenizer, AutoModelForCausalLM

BASE_MODEL = "facebook/opt-1.3b"
tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL, use_fast=True)

# pad_token 지정
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token

model = AutoModelForCausalLM.from_pretrained(BASE_MODEL)
model.resize_token_embeddings(len(tokenizer))

4. LoRA 설정 및 적용

from peft import LoraConfig, get_peft_model

lora_cfg = LoraConfig(
    r=8,                        # 랭크 (조정 강도)
    lora_alpha=16,              # LoRA 스케일링
    target_modules=["q_proj", "v_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
)

model = get_peft_model(model, lora_cfg)
print("LoRA 설정 완료")

5. 데이터 토크나이징 및 라벨 생성

언어모델은 labels가 필요하다.
labels = input_ids로 설정하되, padding 부분은 -100으로 마스킹한다.

def tokenize_function(batch):
    enc = tokenizer(batch["text"], truncation=True, padding="max_length", max_length=512)
    labels = [
        [tok if tok != tokenizer.pad_token_id else -100 for tok in ids]
        for ids in enc["input_ids"]
    ]
    enc["labels"] = labels
    return enc

tokenized = dataset.map(tokenize_function, batched=True, remove_columns=["text"])

6. 학습 실행

from transformers import Trainer, TrainingArguments, DataCollatorForLanguageModeling
import torch

args = TrainingArguments(
    output_dir="./lora-out",
    num_train_epochs=2,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=4,
    learning_rate=2e-4,
    logging_steps=20,
    save_strategy="epoch",
    report_to="none",
    fp16=torch.cuda.is_available(),
)

trainer = Trainer(
    model=model,
    args=args,
    train_dataset=tokenized["train"],
    data_collator=DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False),
)

trainer.train()

첫 학습에서는 작은 데이터(100개 이하)로 테스트한 뒤 전체 데이터로 확장하는 것이 좋다.


7. 모델 저장

model.save_pretrained("./lora-out/adapter")
tokenizer.save_pretrained("./lora-out/tokenizer")
print("파인튜닝 모델 저장 완료")

이제 학습이 끝났으니, 다음 단계로 넘어가자.
모델이 얼마나 좋아졌는지를 평가해보는 것이다.


PART 2 — 5장 : 파인튜닝 모델 평가와 결과 분석


1. 평가용 데이터셋 구성

훈련용 데이터에서 5%를 분리하여 평가용으로 사용한다.

from datasets import load_dataset

dataset = load_dataset("json", data_files="train.jsonl")
split = dataset["train"].train_test_split(test_size=0.05, seed=42)
eval_data = split["test"]

print(f"평가 데이터 수: {len(eval_data)}")

2. 모델과 토크나이저 불러오기

from transformers import AutoModelForCausalLM, AutoTokenizer, DataCollatorForLanguageModeling
from peft import PeftModel
import torch

BASE_MODEL = "facebook/opt-1.3b"
ADAPTER_DIR = "./lora-out/adapter"
TOKENIZER_DIR = "./lora-out/tokenizer"

# 토크나이저 로드
tokenizer = AutoTokenizer.from_pretrained(TOKENIZER_DIR, use_fast=True)
tokenizer.pad_token = tokenizer.eos_token

# 기본 모델 로드
base_model = AutoModelForCausalLM.from_pretrained(BASE_MODEL)

# 토크나이저의 어휘 크기에 맞춰 모델의 임베딩 레이어 크기 조정
base_model.resize_token_embeddings(len(tokenizer))

# LoRA 어댑터 로드
model = PeftModel.from_pretrained(base_model, ADAPTER_DIR)
model.eval()

3. Perplexity 계산

from transformers import Trainer, TrainingArguments
import math

def tokenize_eval(batch):
    enc = tokenizer(batch["text"], truncation=True, padding="max_length", max_length=512)
    enc["labels"] = [
        [tok if tok != tokenizer.pad_token_id else -100 for tok in ids]
        for ids in enc["input_ids"]
    ]
    return enc

eval_tokenized = eval_data.map(tokenize_eval, batched=True, remove_columns=["text"])
collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)

args = TrainingArguments(
    output_dir="./eval-out",
    per_device_eval_batch_size=2,
    dataloader_drop_last=False,
    report_to="none",
    fp16=torch.cuda.is_available(),
)

trainer = Trainer(
    model=model,
    args=args,
    eval_dataset=eval_tokenized,
    data_collator=collator,
)

result = trainer.evaluate()
loss = result["eval_loss"]
ppl = math.exp(loss)

print(f"평가 손실(loss): {loss:.4f}")
print(f"Perplexity(PPL): {ppl:.2f}")

4. 샘플 생성 비교

from transformers import pipeline

prompt = """### Instruction:
Explain the difference between regression and classification.
### Response:
"""

gen = pipeline(
    "text-generation",
    model=model,
    tokenizer=tokenizer,
    device_map="auto" if torch.cuda.is_available() else None
)

output = gen(prompt, max_new_tokens=120, do_sample=False)[0]["generated_text"]
print("=== 모델 생성 결과 ===\n")
print(output)

이제 파인튜닝된 모델이 instruction-response 구조를 명확히 따르고,
훈련 데이터의 문체를 반영하는지 직접 확인할 수 있다.


5. 실험 로그 저장

import csv, os, time
from pathlib import Path

Path("experiments").mkdir(exist_ok=True)
log_file = "experiments/results.csv"
new_file = not os.path.exists(log_file)

row = [int(time.time()), BASE_MODEL, ADAPTER_DIR, f"{loss:.4f}", f"{ppl:.2f}"]
fields = ["timestamp", "model", "adapter", "eval_loss", "ppl"]

with open(log_file, "a", newline="", encoding="utf-8") as f:
    w = csv.writer(f)
    if new_file:
        w.writerow(fields)
    w.writerow(row)

print(f"평가 결과가 '{log_file}'에 저장되었습니다.")

6. 결론 및 다음 단계

지금까지 우리는

  • Hugging Face 모델을 이용해 LoRA 방식으로 SLM을 파인튜닝하고,
  • Perplexity로 정량 평가,
  • 샘플 생성으로 정성 평가를 수행했으며,
  • 결과를 CSV로 자동 기록했다.

다음 6장에서는 이 모델을 Ollama에서 직접 실행 가능한 형태(GGUF 변환 + Modelfile 생성) 로 내보내는 배포 단계를 다룬다.


0개의 댓글