DPO(Rafailov et al., NeurIPS 2023)は、PPOの「きれいな」代替であるべきだ。訓練ループの中に報酬モデルはなく、価値関数もなく、ロールアウト収集もない。選好ペアに対する二値クロスエントロピー損失だけだ。そして数式もエレガントで、Bradley-Terryモデルに対して対数比のリパラメータ化を代入すると、分配関数Z(x)が打ち消される。
私は、マルチステージRLHFプロジェクトの一環としてこれをゼロから実装した(同じモデル、同じトークナイザ、私のPPOおよびGRPO実装と同じ評価スイート)。では、実際に何が起きたのか。
get_logps関数
ここにサイレントな失敗が潜んでいる。シフトは正確でなければならない:
python
shift_logits = logits[:, :-1, :] # predict positions 1..T shift_labels = input_ids[:, 1:] # actual tokens 1..T shift_mask = response_mask[:, 1:] # only response positions ラベルに合わせるためにマスクを1つずらす。これを間違えると、損失は正常に見える一方で、モデルが応答トークンではなくプロンプトトークンを教師として学習してしまう。目立ったエラーシグナルはない。
損失曲線で見える報酬ハッキングの様子
ステップ30で、loss = 0.0、accuracy = 1.0になった。これは高速な収束に見える。でも違う。
報酬マージンが真実を語る:
| Step | Margin |
|---|---|
| 30 | 56.9 |
| 70 | 240.7 |
| 150 | 599.2 |
健全なマージンは1〜10。599では、ポリシーが参照(reference)からあまりに遠くにまでずれており、すべてのペアについて、却下された応答に対してほぼゼロの確率を割り当てるようになっていた。モデルは一般化可能な選好を学ぶのではなく、選好シグナルを記憶してしまった。
根本原因:平均化なしのバッチサイズ1。次のペアへ進む前に、各更新が(chosen, rejected)1ペアに完全に過適合してしまう。
ステップ20の挙動が教えてくれること
ステップ20では:loss = 0.693、accuracy = 0.0、margin = 0.0。
0.693 = log(2) = -log(σ(0))。これは、理論が予測する退化ケースで、ポリシーが参照を完全に模倣しているために、すべての対数比が0になり、DPOのマージンも0になり、損失がlog 2になる。モデルがchosenとrejectedに同じ確率を割り当てている。このような結果を実際の学習実行で見ることができたのは、実装が正しいことの良い裏取りになった。
結論(verdict)
アーキテクチャは健全だ。損失、固定された参照モデル、get_logpsのマスキング、RMなしの訓練ループはすべて正しい。壊れていたのはアルゴリズムではなく、訓練設定だ。これらのフェーズ1の結果(平均報酬:2.40)は、その後βを0.1から0.3へ調整し、適切なバッチ化を行い、同じ16個のプロンプトでPPOおよびGRPOとヘッドツーヘッドで比較することでチューニングされた。
完全な比較は別の書き込みにある。チューニング後、ランキングは完全に逆転した。DPOは3位から1位になった。
完全なDPO実装の投稿: brayanbrayan.github.io/machine-learning/rlhf/2026/03/24/dpo-implementation-blog.html
完全な比較調査: brayanbrayan.github.io/2026/04/02/rlhf-post-blog.html
実装の詳細について、どんな質問でもお答えできる。
[link] [comments]



