LabHub
Get started
배우기 러닝패스 코스

MiniMind — Train a Small Language Model Yourself, End to End

Shift the style with DPO and check the content was preserved

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

MiniMind train_dpo.py 의 손실(답 토큰 로그확률의 합으로 계산한 −logσ(β·Δ))을 직접 구현하고, SFT 기준 모델을 정책·기준 두 벌로 불러 100걸음 DPO 한다. 선호 쌍은 사실은 같고 말투만 다르다(chosen …입니다., rejected …이다.). 말투가 바뀌는지와 말투를 무시한 정답률이 지켜지는지를 함께 잰다.

왜 중요한가

DPO 는 보상 모델과 강화학습 없이 선호 쌍만으로 모델을 민다. 구현은 열 줄이지만, 손실이 '차이' 만 보기 때문에 잘못 밀면 손실은 잘 내려가는데 모델은 망가진다 — chosen 과 rejected 를 함께 끌어내려 차이만 벌리는 식으로. 이 코스의 모델에서 학습률을 열 배 올리면 실제로 그렇게 된다. 그래서 선호 학습은 손실 하나로 판단하지 않는다. 선호 지표(떼어 둔 쌍에서 chosen 이 더 그럴듯한 몫, 원하는 말투로 답하는 몫)와 원래 능력 지표(사실 정답률)를 전후로 함께 재는 것이 이 실습의 요점이다.

단계

  1. /root/mm/dpo/dpolib.py 에 선호 쌍을 배치로 바꾸는 함수와 답 토큰 로그확률의 합(seq_logp)을 만들고, 기준 모델로 첫 쌍의 두 로그확률을 /root/mm/dpo/logp.json 에 적으세요.
  2. dpolib.pydpo_loss(정책 chosen, 정책 rejected, 기준 chosen, 기준 rejected, beta) 를 구현하세요. 1단계의 logp.json 에 정책=기준일 때의 손실(loss_policy_equals_ref)도 적으세요.
  3. DPO 전 기준 모델의 지표(선호 정확도·공손 비율·두 평균 로그확률)를 /root/mm/dpo/baseline.json 에 적으세요.
  4. β 0.1·lr 1e-5·쌍 8개씩 100걸음 DPO 하고 정책을 /root/mm/dpo/dpo.pth 로 저장하세요.
  5. 같은 지표를 DPO 뒤 정책으로 재어 /root/mm/dpo/after.json 에 적으세요.
  6. SFT 자료 앞 40개 질문에서 말투를 무시한 정답률을 전후로 재어 /root/mm/dpo/drift.json 에 적으세요.
  7. /root/mm/dpo/report.md## 손실 ## 무엇이 바뀌었나 ## 무엇을 못 하나 세 절을 쓰고, DPO 뒤 공손 비율과 chosen 의 평균 로그확률을 넣으세요.

참고

문장의 로그확률은 답 토큰의 합

/root/mm/dpo/dpolib.pybatch(쌍들)(앞 절반 chosen·뒤 절반 rejected 의 입력·라벨)과 seq_logp(모델, 입력, 라벨)(문장마다 답 토큰 log p 의 합)을 만들고, 기준 모델(/opt/mm/ref/sft.pth)로 dpo.jsonl 첫 쌍의 두 값을 /root/mm/dpo/logp.jsonchosen·rejected 로 적으세요.

log_softmax 한 뒤 torch.gather 로 정답 토큰의 값만 꺼내고, 라벨이 -100 인 자리는 마스크로 0 을 곱해 뺍니다(gather 전에 -100 을 0 으로 바꿔야 인덱스 오류가 나지 않습니다). 두 답은 '입니다' 와 '이다' 만 다르니 그 부분의 확률 차이가 곧 두 값의 차이입니다.

DPO 손실을 식대로

dpolib.pydpo_loss(policy_chosen, policy_rejected, ref_chosen, ref_rejected, beta) 를 만드세요 — 네 인자는 문장별 로그확률 합(1차원 텐서)이고, −logσ(β·[(π_c − ref_c) − (π_r − ref_r)]) 의 평균을 돌려줍니다. 기준 모델의 첫 8쌍으로 정책=기준일 때의 값을 logp.jsonloss_policy_equals_ref 에 적으세요.

정책이 기준과 같으면 괄호 안이 0 이라 −log σ(0) = ln 2 입니다. 채점기는 여러분의 dpo_loss 를 불러 무작위 여섯 쌍·β 둘에서 논문 식과 같은지, 그리고 정책이 chosen 을 더 좋아할 때 손실이 ln 2 보다 작은지(부호) 봅니다. F.logsigmoid 를 쓰면 수치가 안정합니다.

DPO 전의 지표

스크립트 /root/mm/dpo/metrics.py(인자: 가중치 경로, 결과 경로)를 써서 기준 모델로 dpo_val.jsonl 100쌍의 선호 정확도(pref_acc), 앞 40쌍 질문의 공손 비율(polite_rate), chosen·rejected 의 평균 로그확률(mean_logp_chosen·mean_logp_rejected)을 재어 /root/mm/dpo/baseline.json 에 적으세요.

SFT 자료는 말투가 반반이었으므로 기준 모델은 두 말투를 비슷하게 섞어 씁니다. 지표를 재는 스크립트를 가중치 경로와 결과 경로를 인자로 받게 만들어 두면 5단계에서 그대로 씁니다.

100걸음 DPO

스크립트 /root/mm/dpo/train_dpo.py 를 써서 SFT 모델을 정책과 기준 두 벌로 불러 기준을 얼리고, dpo.jsonl 에서 쌍 8개씩 뽑아 β 0.1·lr 1e-5(AdamW)·100걸음(시드 42) DPO 한 뒤 정책의 state_dict 를 /root/mm/dpo/dpo.pth 로 저장하세요.

기준 모델의 로그확률은 torch.no_grad() 안에서 구합니다. MiniMind 기본 학습률은 4e-8 인데, 이 작은 모델과 짧은 학습에서는 1e-5 가 말투를 옮기면서 내용을 지킵니다. 손실은 ln 2 에서 시작해 내려갑니다.

DPO 뒤의 지표

3단계와 같은 지표를 dpo.pth 로 재어 /root/mm/dpo/after.json 에 적으세요. 채점기는 선호 정확도가 0.8 이상이고 공손 비율이 DPO 전보다 20%p 이상 올랐는지 봅니다.

평균 로그확률 둘도 전후로 견줘 보세요. chosen 과 rejected 가 모두 내려가는 것은 DPO 에서 흔한 일입니다 — 문제는 chosen 이 너무 많이 내려가 모델이 어느 쪽도 아닌 답을 낼 때입니다.

내용은 지켜졌나

sft.jsonl 에서 한 차례짜리 대화 앞 40개의 질문에 욕심쟁이로 답하게 해, 끝의 입니다.·이다. 를 떼고 자료의 답과 같은 몫을 기준 모델(fact_acc_ref)과 DPO 정책(fact_acc_dpo)에서 재어 /root/mm/dpo/drift.json 에 적으세요.

말투를 떼고 견주면 '무엇을 말했나' 만 남습니다. 선호 쌍은 사실이 같고 말투만 다르므로, 잘 된 DPO 라면 이 값이 거의 그대로여야 합니다. 10%p 넘게 떨어지면 기준에서 너무 멀리 간 것입니다.

선호 학습의 효과와 한계

/root/mm/dpo/report.md## 손실 ## 무엇이 바뀌었나 ## 무엇을 못 하나 세 절을 쓰고, 5단계의 polite_ratemean_logp_chosen 을 숫자로 넣으세요.

마지막 절에는 선호 쌍으로 가르칠 수 없는 것(모델이 모르는 사실)과, 학습률을 올렸을 때 본 것을 적으면 됩니다.