LLMLLM 파인튜닝 · 2중급

파인튜닝 데이터 준비 — 수집, 정제, 포맷 변환

LLM파인튜닝데이터셋HuggingFace데이터정제

데이터 품질이 결정적

"Garbage in, garbage out" — 파인튜닝 결과는 데이터 품질에 달려 있습니다.

flowchart LR
    RAW["원시 데이터\n수집"]
    CLEAN["정제\n(중복·오류 제거)"]
    FORMAT["포맷 변환\n(지시-응답 형식)"]
    SPLIT["분할\n(train/val/test)"]
    UPLOAD["업로드\n(HuggingFace Hub)"]

    RAW --> CLEAN --> FORMAT --> SPLIT --> UPLOAD

데이터 수집 방법

# 1. 기존 데이터에서 추출 (고객 Q&A, 지원 티켓 등)
import pandas as pd

support_tickets = pd.read_csv("support_tickets.csv")

# 해결된 티켓만 사용 (질문-답변 쌍)
resolved = support_tickets[support_tickets["status"] == "resolved"]
qa_pairs = resolved[["question", "answer"]].dropna()

# 2. Hugging Face 공개 데이터셋 활용
from datasets import load_dataset

dataset = load_dataset("databricks/databricks-dolly-15k")
print(dataset["train"][0])
# {'instruction': '...', 'context': '...', 'response': '...', 'category': '...'}

# 3. LLM으로 합성 데이터 생성 (Self-Instruct)
from openai import OpenAI
client = OpenAI()

def generate_qa_pair(topic: str) -> dict:
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{
            "role": "user",
            "content": f"{topic}에 관한 전문적인 Q&A 쌍을 JSON으로 생성해줘."
        }],
        response_format={"type": "json_object"}
    )
    return json.loads(response.choices[0].message.content)

데이터 정제

import re
from datasets import Dataset

def clean_text(text: str) -> str:
    # 이상한 공백 제거
    text = re.sub(r'\s+', ' ', text).strip()
    # HTML 태그 제거
    text = re.sub(r'<[^>]+>', '', text)
    # URL 제거 (선택적)
    text = re.sub(r'https?://\S+', '[URL]', text)
    return text

def is_quality_sample(sample: dict) -> bool:
    instruction = sample.get("instruction", "")
    output = sample.get("output", "")

    # 최소 길이
    if len(instruction) < 10 or len(output) < 20:
        return False

    # 최대 길이 (토큰 초과 방지)
    if len(instruction) > 2000 or len(output) > 4000:
        return False

    # 유해 콘텐츠 필터 (간단한 예시)
    forbidden = ["욕설", "개인정보"]
    if any(word in output for word in forbidden):
        return False

    return True

# 필터 적용
raw_data = [...]  # 원시 데이터
cleaned = [
    {
        "instruction": clean_text(d["instruction"]),
        "output": clean_text(d["output"]),
    }
    for d in raw_data
    if is_quality_sample(d)
]

print(f"원본: {len(raw_data)}, 정제 후: {len(cleaned)}")

ChatML 포맷 변환

def to_chat_format(sample: dict) -> dict:
    """지시-응답 데이터를 ChatML 대화 형식으로 변환"""
    messages = []

    if system_prompt := sample.get("system"):
        messages.append({"role": "system", "content": system_prompt})

    user_content = sample["instruction"]
    if context := sample.get("input"):
        user_content = f"컨텍스트:\n{context}\n\n질문: {user_content}"

    messages.append({"role": "user", "content": user_content})
    messages.append({"role": "assistant", "content": sample["output"]})

    return {"messages": messages}

# 전체 데이터셋 변환
chat_data = [to_chat_format(d) for d in cleaned]

# HuggingFace Dataset 객체로 변환
dataset = Dataset.from_list(chat_data)

학습/검증/테스트 분할

from datasets import DatasetDict

# 8:1:1 분할
splits = dataset.train_test_split(test_size=0.2, seed=42)
train_val = splits["train"]
test = splits["test"]

train_val_splits = train_val.train_test_split(test_size=0.1, seed=42)

final_dataset = DatasetDict({
    "train": train_val_splits["train"],   # 72%
    "validation": train_val_splits["test"], # 8%
    "test": test,                           # 20%
})

print(final_dataset)
# DatasetDict({
#     train: Dataset({features: [...], num_rows: 7200})
#     validation: Dataset({features: [...], num_rows: 800})
#     test: Dataset({features: [...], num_rows: 2000})
# })

데이터 통계 확인

import matplotlib.pyplot as plt

# 길이 분포 확인
instruction_lengths = [len(d["messages"][1]["content"]) for d in chat_data]
output_lengths = [len(d["messages"][-1]["content"]) for d in chat_data]

print(f"지시 평균 길이: {sum(instruction_lengths)/len(instruction_lengths):.0f}자")
print(f"응답 평균 길이: {sum(output_lengths)/len(output_lengths):.0f}자")
print(f"지시 최대 길이: {max(instruction_lengths)}자")
print(f"응답 최대 길이: {max(output_lengths)}자")

정리

단계핵심 작업
수집기존 데이터 추출, 공개 데이터셋, LLM 생성
정제중복 제거, 길이 필터, 품질 기준 적용
포맷지시-응답 or 대화(ChatML) 형식
분할train/validation/test (8:1:1)
검증길이 분포, 샘플 확인

다음 편에서는 OpenAI 파인튜닝 — API를 통해 GPT 모델을 직접 파인튜닝하는 방법을 배웁니다.

궁금한 점이 있으신가요?

협업·의뢰는 아래로, 가벼운 소통은 인스타그램 @bluefox._.hi도 환영이에요.