はじめに
カスタムDPO実装のバグを修正した後も成功率30%にとどまっていた私たちのプロジェクトが、HuggingFace TRL DPOTrainerを使用することで成功率90%を達成しました。 この記事では、DPOが各フェーズでモデルをどう変化させるかを、実際の日本語生成例を通じて直感的に解説します。3つのフェーズで何が起きるのか
質問例: 「東京の観光名所を教えてください」 ---Stage 1: Base Model(能力ゼロの状態)
Base Model: cyberagent/open-calm-3b
状態: 何も学習していない、ランダムな出力
#### 生成例
出力: 東京の観光名所をの名所を東京の名所を名所を東京の観光名所を東京の観光名所を
何が起きているか:
- モデルは質問に答える能力がない
- 単語の繰り返しやランダムな出力
- Chosen/Rejectedの区別もつかない
- Log probability: 非常に低い(-50.0くらい)
Stage 2: SFT後(能力獲得、選好なし)
訓練: Chosenデータのみで教師あり学習
目的: 「良い応答」を生成する能力を獲得
#### Chosen応答を生成しようとする
出力: 東京の観光名所には浅草寺、東京タワー、スカイツリー、皇居、上野公園などがあります。
#### でも、Rejected応答も同じように生成してしまう
出力: 浅草寺です。
何が起きているか:
- ✅ 質問に答える能力を獲得
- ❌ 詳しい応答と短い応答を区別できない
- 両方とも「正しい応答」として学習してしまった
- Chosen log probability: やや低い(-30.0くらい)
- Rejected log probability: やや低い(-25.0くらい)
- 差がないため、選好学習は失敗(成功率0%)
Chosen変化: -20.67 ❌(減少してしまった)
Rejected変化: -14.17 ✅(減少は良い)
成功率: 0/10 (0.0%)
なぜ失敗したのか:
- SFTは「Chosenデータのみ」で訓練
- モデルは「この応答を生成しろ」と学習
- でも、「Chosenを優先、Rejectedを回避」という比較は学習しない
- 結果:両方の確率が変化してしまう
Stage 3: DPO後(選好学習完了)
訓練: Chosen + Rejected ペアでDPO
目的: 「Chosenを優先、Rejectedを回避」を学習
#### Chosen応答を優先
出力: 東京の観光名所には浅草寺、東京タワー、スカイツリー、皇居、上野公園などがあります。
それぞれの特徴を説明すると、浅草寺は東京最古の寺院で、東京タワーは高さ333mの展望台があり...
#### Rejected応答を回避
(短い応答は生成されにくくなる)
何が起きているか:
- ✅ 質問に答える能力を維持
- ✅ 詳しい応答を優先、短い応答を回避
- Chosen log probability: 高い(-10.0くらい、+20.78の改善)
- Rejected log probability: 低い(-35.0くらい、-9.21の悪化)
- マージン+30.0 で明確な選好を獲得(成功率90%)
Chosen変化: +20.78 ✅(大きく増加!)
Rejected変化: -9.21 ✅(減少!)
成功率: 9/10 (90.0%)
なぜ成功したのか:
- DPOは「ChosenペアとRejectedペア」を同時に比較して学習
- DPOの損失関数の働き:
Loss = -log(sigmoid(β * (Chosen確率 - Rejected確率)))
`
- これが意味するのは:
- Chosen確率が高く、Rejected確率が低いほど、損失が小さい
- モデルは損失を小さくするために、Chosenを上げ、Rejectedを下げる
---
全体効果: Base → DPO
Base(能力ゼロ) → SFT(能力獲得) → DPO(選好学習)
検証結果(Base → DPO):
成功率: 7/10 (70.0%)
これは何を意味するのか:
- 70%の成功率: 10ペア中7ペアで、BaseからDPOまでの全体的な改善が見られた
- SFT → DPOの90%: 能力を獲得した後の選好学習は非常に効果的
- Base → DPOの70%: 全体的な改善も十分に達成
---
数値で見る変化
| フェーズ | Chosen確率 | Rejected確率 | マージン | 生成品質 |
|---------|-----------|-------------|---------|---------|
| Base | -50.0 | -50.0 | 0 | ランダム |
| SFT | -30.0 | -25.0 | -5.0 | 両方生成 |
| DPO | -10.0 | -35.0 | +25.0 | Chosen優先 |
---
カスタム実装 vs TRL実装
試行10(カスタム実装、バグ修正後)
成功率: 30%
問題点:
- log probability平均化バグを修正
- でも、他にも複数のバグが残存
- 実装の品質が低い
試行11(HuggingFace TRL実装)
成功率: 90%
利点:
- 公式実装で信頼性が高い
- 複雑な実装バグがない
- バージョン互換性も考慮
性能比較表
| 項目 | Trial 10 (カスタム) | Trial 11 (TRL) | 改善率 |
|------|-------------------|----------------|--------|
| 成功率 | 30% | 90% | 3倍 |
| Chosen変化 | +2.31 | +20.78 | 9倍 |
| Rejected変化 | -2.48 | -9.21 | 3.7倍 |
| マージン | +4.79 | +30.0 | 6.3倍 |
---
技術的詳細
DPOの損失関数
python
policy_log_ratios = policy_chosen_log_probs - policy_rejected_log_probs
reference_log_ratios = reference_chosen_log_probs - reference_rejected_log_probs
logits = beta * (policy_log_ratios - reference_log_ratios)
loss = -F.logsigmoid(logits).mean()
Dual Adapter方式
python
SFT済みアダプターを"default"としてロード(訓練用)
model = PeftModel.from_pretrained( base_model, "./trial10_sft_lora_adapter", is_trainable=True, adapter_name="default", )同じSFT済みアダプターを"reference"としてロード(参照モデル用)
model.load_adapter("./trial10_sft_lora_adapter", adapter_name="reference")"default"アダプターをアクティブに設定
model.set_adapter("default")
バージョン互換性の問題と解決
問題: trl==0.9.6ではprocessing_classパラメータが存在しない
修正:python
❌ 最新版TRL用(動かない)
trainer = DPOTrainer( model=model, args=training_args, processing_class=tokenizer, train_dataset=train_dataset, )✅ trl 0.9.6用(動く)
trainer = DPOTrainer( model=model, args=training_args, tokenizer=tokenizer, # パラメータ名が違う train_dataset=train_dataset, )`
---
結論
証明されたこと
1. ✅ DPO理論は正しい: Chosen↑, Rejected↓が明確に達成 2. ✅ HuggingFace TRL実装は信頼できる: 90%の高い成功率 3. ✅ 実装の品質が重要: カスタム実装より3倍の性能 4. ✅ 2段階アプローチの重要性: SFT(能力獲得)→ DPO(選好学習)が必須
教訓
#### 1. SFTは選好学習ではない
- SFTは「能力獲得」フェーズ
- 選好(Chosen vs Rejected)は学習できない
- 成功率0% がそれを証明
#### 2. DPOは選好学習
- ChosenとRejectedを同時に比較
- 成功率90% で明確な選好を獲得
- Bradley-Terry Modelに基づく理論的裏付け
#### 3. 公式実装を使うべき
- カスタム実装: 30%成功
- TRL実装: 90%成功
- 3倍の差が実装品質の重要性を示す
次のステップ
この成功を基に、以下を検討中:1. より大きなモデル(7B、13B)での検証 2. 異なる言語・ドメインでの適用 3. DPOパラメータ(beta)の最適化 4. 他の選好学習手法との比較(RLHF, PPO)
---参考資料
論文
ドキュメント
コード
- GitHub: masuidrive/dpo-rlhf-demo
- 試行11スクリプト:
train_trial11_dpo_trl.py - 検証スクリプト:
verify_trial11_trl.py
まとめ
MacBook上でDPOの動作を検証するプロジェクトを通じて、以下を学びました:1. DPO理論の正しさ: 90%の成功率で証明 2. 実装の重要性: カスタム実装の3倍の性能 3. 2段階アプローチの必要性: SFT → DPO が必須 4. 公式実装の信頼性: HuggingFace TRL DPOTrainerを使うべき
この知見が、LLMの選好学習に取り組む皆さんの助けになれば幸いです。 --- 2025年10月27日