5장. LoRA 파인튜닝과 DPO 정렬
실습 2 — 채팅 템플릿으로 SFT 데이터 만들기
4장의 sft_shop.jsonl 을 Qwen 의 채팅 템플릿으로 바꿔 SFT 데이터를 만듭니다. 질문 부분(system, user)의 정답 자리는 -100 으로 두고, 답 부분(assistant)만 채점합니다. LoRA 를 붙이기 전의 모델이 시험 질문에 어떻게 답하는지도 봅니다.
1단계. 데이터와 템플릿
import json
from transformers import AutoTokenizer
name = "Qwen/Qwen2.5-0.5B-Instruct"
tok = AutoTokenizer.from_pretrained(name)
SYSTEM = "당신은 온라인 쇼핑몰 고객센터 상담원입니다."
with open("sft_shop.jsonl", encoding="utf-8") as f:
rows = [json.loads(line) for line in f]
train_rows = [r for r in rows if r["나눔"] == "train"]
test_rows = [r for r in rows if r["나눔"] == "test"]
def messages(q, a=None):
"""질문(과 답)을 메시지 목록으로. 답이 없으면 물어볼 때의 꼴."""
m = [{"role": "system", "content": SYSTEM}, {"role": "user", "content": q}]
if a is not None:
m.append({"role": "assistant", "content": a})
return m
# 학습용 한 줄이 실제로 어떤 글이 되는지 본다
print(tok.apply_chat_template(messages(train_rows[0]["질문"], train_rows[0]["답변"]), tokenize=False))<|im_start|>system
당신은 온라인 쇼핑몰 고객센터 상담원입니다.<|im_end|>
<|im_start|>user
배송은 얼마나 걸리나요?<|im_end|>
<|im_start|>assistant
안녕하세요, 고객님. 결제 후 1~3일 안에 도착합니다. 제주와 섬 지역은 2일 더 걸립니다. 감사합니다.<|im_end|>2단계. 질문 부분은 -100
def encode(q, a):
# 질문 부분: 생성 표시(assistant 차례)까지 붙인 글
p_text = tok.apply_chat_template(messages(q), add_generation_prompt=True, tokenize=False)
# 전체: 답까지 들어간 글
f_text = tok.apply_chat_template(messages(q, a), tokenize=False)
p_ids = tok(p_text, add_special_tokens=False)["input_ids"]
f_ids = tok(f_text, add_special_tokens=False)["input_ids"]
assert f_ids[:len(p_ids)] == p_ids # 전체의 앞부분이 질문 부분과 같아야 한다
return {"input_ids": f_ids, "labels": [-100] * len(p_ids) + f_ids[len(p_ids):]}
ex = encode(train_rows[0]["질문"], train_rows[0]["답변"])
scored = [t for t in ex["labels"] if t != -100]
print("토큰 수:", len(ex["input_ids"]), "/ 채점하는 자리:", len(scored))
print("채점하는 부분:", repr(tok.decode(scored)))토큰 수: 83 / 채점하는 자리: 42
채점하는 부분: '안녕하세요, 고객님. 결제 후 1~3일 안에 도착합니다. 제주와 섬 지역은 2일 더 걸립니다. 감사합니다.<|im_end|>\n'3단계. LoRA 전의 답 보기
import torch
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained(name, dtype=torch.float32).eval() # CPU 에서 확인만
def ask(m, q):
enc = tok.apply_chat_template(messages(q), add_generation_prompt=True, return_tensors="pt", return_dict=True).to(m.device)
with torch.no_grad():
out = m.generate(**enc, max_new_tokens=60, do_sample=False)
return tok.decode(out[0][enc["input_ids"].shape[1]:], skip_special_tokens=True).strip()
for r in test_rows[:3]:
print("Q:", r["질문"])
print("A:", ask(model, r["질문"]).replace("\n", " ")[:120])Q: 배송이 며칠 걸리는지 알려 주세요.
A: 죄송합니다, 저는 인공지능으로서 배송 시간에 대한 정보를 제공할 수 없습니다. 하지만 대부분의 온라인 쇼핑몰에서는 고객센터에서 배송 시간을 확인할 수 있습니다. 이는 일반적으로 3-7일 정도로 예
Q: 언제 도착하는지 궁금합니다.
A: 죄송합니다, 저는 인공지능으로서 시간을 관리하지 못하고 있습니다. 하지만 고객센터에 직접 방문하거나 전화를 통해 도움이 필요하신 경우 언제든지 문의해 주세요.
Q: 배송비 알려 주세요.
A: 죄송합니다, 저는 인공지능으로서 배송비를 제공할 수 없습니다. 하지만 일반적으로 온라인 쇼핑몰에서 배송비는 상품의 가격과 함께 상세한 정보를 제공하는 것이 일반적입니다. 이 정보는 상이 실습이 뜻하는 것
이 실습은 연극 연습 전에 대본을 정해진 양식으로 옮겨 적는 것과 같습니다. 4장의 질문과 답을 배역(system, user, assistant)이 적힌 대본 양식으로 옮기고(1단계), 배우가 외울 자기 대사인 답에만 형광펜을 칠하고(2단계), 연습 전에 배우에게 대사를 시켜 봅니다(3단계). 여기서 대본 양식이 채팅 템플릿, 형광펜을 칠한 곳이 채점하는 자리(나머지는 -100), 배우가 Qwen2.5-0.5B-Instruct 입니다.
| 단계 | 출력 | 뜻 |
|---|---|---|
| 1. 템플릿 | system, user, assistant 세 칸 | 질문과 답 한 쌍이 역할 이름이 붙은 세 칸짜리 글이 되었습니다. 칸마다 im_end 표시로 끝납니다 |
| 2. 토큰 수 | 83, 채점하는 자리 42 | 83토큰 가운데 답 부분 42토큰만 채점합니다. 시스템 메시지와 질문은 따라 쓰게 하지 않습니다 |
| 2. 채점하는 부분 | 답 글과 끝 표시 | 답과 함께 끝 표시까지 배워야 모델이 답을 마치고 멈출 수 있습니다 |
| 3. LoRA 전의 답 | "죄송합니다, 저는 인공지능으로서 …" | 세 질문 모두 피하는 답을 했습니다. 이 쇼핑몰의 배송 기간과 배송비를 모르기 때문입니다 |
| 3. 지어낸 숫자 | "일반적으로 3-7일 정도로" | 모르는 것을 일반론으로 메우며 이 쇼핑몰의 정책(1~3일)과 다른 숫자를 냈습니다 |
한 줄로 줄이면, 4장의 질문과 답 72쌍을 Qwen 이 알아듣는 대화 꼴로 바꾸고 답 부분만 채점하게 준비한 실습입니다. LoRA 를 붙이기 전의 모델은 시험 질문에 피하는 답만 했고, 이 데이터로 실제 학습하는 것은 실습 3입니다.
코드 설명
- 학습용 한 줄은
<|im_start|>system,<|im_start|>user,<|im_start|>assistant로 역할이 나뉘고, 역할마다<|im_end|>로 끝나는 글이 됩니다. 3장 실습 6에서 본 꼴에 답까지 붙은 것입니다. - 83토큰 가운데 답 부분 42토큰만 채점합니다. 채점하는 부분을 글로 되돌리면 답과 끝 표시
<|im_end|>가 나옵니다. 끝 표시까지 배워야 모델이 답을 마치고 멈춥니다. assert f_ids[:len(p_ids)] == p_ids는 "답이 들어간 글의 앞부분이 질문 부분과 똑같다"를 확인합니다. 토크나이저가 경계에서 토큰을 다르게 자르면 -100 자리가 어긋나는데, 그럴 때 바로 알아챌 수 있습니다.- LoRA 전의 모델은 세 질문 모두에 "죄송합니다, 저는 인공지능으로서 … 제공할 수 없습니다"로 피하고, 일반적인 쇼핑몰 이야기를 늘어놓습니다. 3장 프로젝트에서 본 그 모습입니다.