Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

DPO — 直接選好最適化

動機

PPOは、4つのモデル(方策、参照モデル、報酬モデル、価値ヘッド)をメモリ上に必要とし、複雑なRLインフラを要するうえ、悪名高いほど不安定です。DPO (Rafailov et al. 2023) は、次の問いを投げかけます。RLを省略し、選好から直接学習できないか?

重要な洞察 :RLHFの目的関数(報酬最大化+KLペナルティ)のもとでの最適方策には、 閉形式の解 があります。この同じ目的関数を暗黙に最適化する教師あり損失を導出できます。

数学的導出

ステップ1 :RLHFの目的関数:\(\max_\pi \mathbb{E}_{x,y\sim\pi}[r(x,y)] - \beta D_\text{KL}[\pi\\vert \pi_\text{ref}]\)

ステップ2 :最適解は次のとおりです:\(\pi^*(y\vert x) = \frac{1}{Z(x)} \pi_\text{ref}(y\vert x) \exp\left(\frac{r(x,y)}{\beta}\right)\)

ステップ3 :報酬を方策で表すように整理します:\(r(x,y) = \beta \log \frac{\pi^*(y\vert x)}{\pi_\text{ref}(y\vert x)} + \beta \log Z(x)\)

ステップ4 :Bradley-Terry選好モデル \(P(y_w \succ y_l) = \sigma(r(y_w) - r(y_l))\) に代入します。\(Z(x)\) は相殺されます。

\[ \boxed{\mathcal{L}_\text{DPO}(\theta) = -\mathbb{E}_{(x, y_w, y_l)}\left[\log\sigma\left(\beta\log\frac{\pi_\theta(y_w|x)}{\pi_\text{ref}(y_w|x)} - \beta\log\frac{\pi_\theta(y_l|x)}{\pi_\text{ref}(y_l|x)}\right)\right]} \]

Tip

DPOが実際に行っていること

暗黙の報酬 を \(\hat{r}(x,y) = \beta\log\frac{\pi_\theta(y\vert x)}{\pi_\text{ref}(y\vert x)}\) と定義します。

DPOは、「選ばれた応答の暗黙の報酬は、棄却された応答より高い」という「ラベル」を使った交差エントロピー損失を最小化します。マージンは \(\beta\) によって制御されます。

  • 大きな \(\beta\):大きなマージンが必要 \(\rightarrow\) 方策が積極的に移動 \(\rightarrow\) 忘却のリスク

  • 小さな \(\beta\):小さなマージンで十分 \(\rightarrow\) 方策が参照モデルの近くにとどまる \(\rightarrow\) 保守的

参照モデルは正則化器として機能します。方策は、選好への整合を示すことで、参照モデルからのあらゆる逸脱を「正当化」しなければなりません。

勾配の分析

DPOの勾配は次のように分解できます。

\[ \nabla_\theta \mathcal{L} = -\beta \cdot \underbrace{\sigma(-\hat{r}_w + \hat{r}_l)}_{\text{weight: higher when model is wrong}} \cdot \left[\nabla_\theta \log\pi_\theta(y_w|x) - \nabla_\theta \log\pi_\theta(y_l|x)\right] \]

解釈 :勾配は、選ばれた応答の確率を上げ、棄却された応答の確率を下げます。モデルが現在、誤った答えを好んでいるときに重みが最大になり、「混乱を招く」ペアに学習を集中させます。

Note

DPOの具体例

プロンプト :「10歳の子どもに量子もつれを説明してください。」

選択された応答 (\(y_w\)):「2枚の魔法のコインを持っていると想像してください。片方を投げて表が出ると、どれだけ離れていても、もう片方は瞬時に裏になります!」
\(\log\pi_\theta(y_w\vert x) = -15.3\)、\(\log\pi_\text{ref}(y_w\vert x) = -16.1\)

棄却された応答 (\(y_l\)):「量子もつれとは、2つの粒子が相関した状態になり、一方の粒子の量子状態を独立には記述できなくなる現象です。」
\(\log\pi_\theta(y_l\vert x) = -12.8\)、\(\log\pi_\text{ref}(y_l\vert x) = -12.5\)

暗黙の報酬 :\(\hat{r}_w = 0.1 \times ((-15.3) - (-16.1)) = 0.08\)、\(\hat{r}_l = 0.1 \times ((-12.8) - (-12.5)) = -0.03\)

損失への入力 :\(\sigma(0.08 - (-0.03)) = \sigma(0.11) = 0.527\)

損失 :\(-\log(0.527) = 0.64\) — モデルは選択された応答をわずかに好んでいるだけです。勾配は強く押し上げます。

学習後:マージンが \(1/(2\beta)\) 付近で安定するまで、選択された応答の確率が増加し、棄却された応答の確率が減少します。

TRLによる実装

以下は、HuggingFace TRLを使った最小限の動作例です。

from trl import DPOConfig, DPOTrainer
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import LoraConfig
from datasets import load_dataset

model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.1-8B-Instruct",
    torch_dtype=torch.bfloat16, attn_implementation="flash_attention_2")
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.1-8B-Instruct")

# Dataset format: {"prompt": str, "chosen": str, "rejected": str}
dataset = load_dataset("argilla/ultrafeedback-binarized-preferences")

lora_config = LoraConfig(r=64, lora_alpha=16, lora_dropout=0.05,
    target_modules=["q_proj","k_proj","v_proj","o_proj","gate_proj","up_proj","down_proj"])

dpo_config = DPOConfig(
    output_dir="./dpo_output",
    beta=0.1,                    # KL regularization strength
    learning_rate=5e-7,          # Very low LR for stability
    loss_type="sigmoid",         # Standard DPO loss
    max_length=2048,             # Max sequence length
    max_prompt_length=1024,      # Truncation for prompts
    per_device_train_batch_size=2,
    gradient_accumulation_steps=8,  # Effective batch = 16
    gradient_checkpointing=True,
    bf16=True,
    num_train_epochs=1,          # DPO overfits fast - 1 epoch!
    warmup_ratio=0.1,
    logging_steps=10,
    eval_strategy="steps",
    eval_steps=200,
    save_strategy="steps",
    save_steps=500,
)

trainer = DPOTrainer(
    model=model,
    ref_model=None,             # With LoRA, ref = base model (no copy needed!)
    args=dpo_config,
    train_dataset=dataset["train"],
    eval_dataset=dataset["test"],
    tokenizer=tokenizer,
    peft_config=lora_config,
)
trainer.train()
# Key metrics to monitor: train/rewards/chosen, train/rewards/rejected, train/rewards/margins

DPOの仕組み:完全なメカニクス

この節では、DPOの計算の詳細、つまり学習中にトークンレベルで何が起きるのかを完全に説明します。

系列レベルの対数確率

DPOにおける重要な量は、 完全な系列 \(y = (y_1, y_2, \ldots, y_T)\) の、プロンプト \(x\) が与えられたときの対数確率です。これは、 トークンごとの対数確率の合計 として計算します。

\[ \boxed{\log \pi_\theta(y|x) = \sum_{t=1}^{T} \log \pi_\theta(y_t \mid x, y_{<t})} \]

各項 \(\log \pi_\theta(y_t \vert x, y_{<t})\) は、位置 \(t\) における、系列内の実際のトークン \(y_t\) のlog-softmax出力です。これは標準的な言語モデリングで使う交差エントロピー損失と同じですが、ここでは平均ではなく 合計 します。

重要な詳細 :\(y_w\) と \(y_l\) の両方で、勾配は すべてのトークン位置 を通って流れます。中間トークンのマスクはありません。すべてのトークンが系列レベルの対数確率に寄与します。

DPO損失の分解

損失から始めます。

\[ \mathcal{L}_{\text{DPO}}(\theta) = -\mathbb{E}_{(x, y_w, y_l) \sim \mathcal{D}}!\left[\log \sigma!\left(\beta \cdot h_\theta(x, y_w, y_l)\right)\right] \]

ここで「暗黙の報酬マージン」\(h_\theta\) は次のとおりです。

\[ h_\theta(x, y_w, y_l) = \underbrace{\log \frac{\pi_\theta(y_w|x)}{\pi_{\text{ref}}(y_w|x)}}_{\text{chosen reward proxy}} - \underbrace{\log \frac{\pi_\theta(y_l|x)}{\pi_{\text{ref}}(y_l|x)}}_{\text{rejected reward proxy}} \]

トークンレベルの項に展開すると、次のようになります。

\[ \boxed{h_\theta = \sum_{t=1}^{|y_w|}!\left[\log\pi_\theta(y_w^t | x, y_w^{<t}) - \log\pi_{\text{ref}}(y_w^t | x, y_w^{<t})\right] - \sum_{t=1}^{|y_l|}!\left[\log\pi_\theta(y_l^t | x, y_l^{<t}) - \log\pi_{\text{ref}}(y_l^t | x, y_l^{<t})\right]} \]

フォワードパス:ステップごとの処理

1つの学習例 \((x, y_w, y_l)\) に対して、次を行います。

  1. 連結 :2つの系列 \([x; y_w]\) と \([x; y_l]\) を作ります。バッチ内で長さがそろうようにパディングします。

  2. フォワードパス(方策 \(\pi_\theta\)) :両方の系列をモデルに通します。各応答位置のロジットを収集します。

  3. 対数確率の抽出 :応答の各位置 \(t\) で、\(\log\text{softmax}(\text{logits}_t)[y_t]\)、つまり実際のトークンの対数確率を取得します。

  4. トークン上で合計

\[ \begin{aligned} \text{logp_chosen} &= \sum_{t \in \text{response positions}} \log\pi_\theta(y_w^t | x, y_w^{<t}) \\ \text{logp_rejected} &= \sum_{t \in \text{response positions}} \log\pi_\theta(y_l^t | x, y_l^{<t}) \end{aligned} \]

  1. 参照モデルを引く (事前計算済み、または2回目のフォワードパスから取得):

\[ \begin{aligned} \text{ratio_w} &= \text{logp_chosen} - \text{ref_logp_chosen} \\ \text{ratio_l} &= \text{logp_rejected} - \text{ref_logp_rejected} \end{aligned} \]

  1. 損失を計算 :\(\mathcal{L} = -\log\sigma(\beta \cdot (\text{ratio_w} - \text{ratio_l}))\)

  2. バックワードパス :勾配がステップ5 \(\rightarrow\) 4 \(\rightarrow\) 3 \(\rightarrow\) 2 を逆向きに流れ、\(\theta\) を更新します。

トークンレベルの勾配分析

すべてのトークンに勾配が付くのか? はい。選択された系列の位置 \(t\) におけるロジットに関する勾配は次のとおりです。

\[ \frac{\partial \mathcal{L}}{\partial \text{logits}_t^{(w)}} = -\underbrace{\sigma(-\beta \cdot h_\theta)}_{\text{scaling factor}} \cdot \beta \cdot \frac{\partial \log\pi_\theta(y_w^t | \cdot)}{\partial \text{logits}_t^{(w)}} \]

重要な洞察 :\(\sigma(-\beta \cdot h_\theta)\) のスケーリング係数は、両方の系列のすべてのトークンで 共有 されます。これは適応的な学習率として機能します。

  • \(h_\theta\) が小さい場合(モデルが選択された応答と棄却された応答を区別できない):スケーリングは \(\approx 0.5\)。勾配が強く、積極的に学習します。

  • \(h_\theta\) が大きい場合(モデルがすでに選択された応答を好んでいる):スケーリングは \(\approx 0\)。勾配は無視できるほど小さくなり、過適合を防ぎます。

選択されたトークンへの効果 :確率が増加します(対数確率が押し上げられます)。
棄却されたトークンへの効果 :確率が減少します(対数確率が押し下げられます)。
参照モデルとの相対関係 :\(\pi_{\text{ref}}\) との差だけが重要です。モデルがすでに選択された応答に高い確率を割り当てている場合(参照モデルと一致している場合)、勾配はほとんどありません。

トークン単位と系列単位:長さの正規化

微妙な問題があります。長い系列は、各項を合計するため自然に対数確率が低くなります(各項は \(\leq 0\))。\(\vert y_w\vert \gg \vert y_l\vert\) の場合、損失が短い応答を好む方向に偏る可能性があります。

解決策

  • 長さを正規化したDPO :\(\log\pi_\theta(y\vert x)\) を \(\frac{1}{\vert y\vert }\sum_t \log\pi_\theta(y_t\vert \cdot)\) に置き換えます。一部の実装で使われています(SimPOが採用)。

  • 標準DPO :生の合計(正規化なし)を使います。これは暗黙に冗長さを罰します。モデルは、選択された応答のすべてのトークンに高い確率を割り当てなければなりません。

  • 実際の影響 :ベンチマークでは、長さを正規化したDPOは長さの悪用を減らしますが、指示追従品質を損なうことがあります。標準(非正規化)DPOの方がプロダクションでは一般的です。

ラベルマスキング:どのトークンに勾配が付くか

Important

DPOで勾配を受け取るトークン

  • プロンプトトークン (\(x\)): 勾配なし 。損失は応答位置だけで計算されます。プロンプトトークンはコンテキストを提供しますが、そのロジットは \(\log\pi(y\vert x)\) に寄与しません。

  • 選択された応答トークン (\(y_w\)): すべてのトークンに勾配が付きます 。各 \(y_w^t\) が合計に寄与し、その確率を上げる方向に勾配が働きます。

  • 棄却された応答トークン (\(y_l\)): すべてのトークンに勾配が付きます 。各 \(y_l^t\) が合計に寄与し、その確率を下げる方向に勾配が働きます。

  • パディングトークン勾配なし 。アテンションマスクで除外されます。

疑似コード:DPO学習ステップ

Note

DPOのフォワード+バックワード(PyTorch形式)

def dpo_loss(model, ref_model, batch, beta=0.1):
    """One DPO training step."""
    # batch contains: input_ids_chosen, input_ids_rejected,
    #                 labels_chosen, labels_rejected (prompt masked to -100)

    # 1. Forward pass: get per-token log-probs
    logps_chosen = get_sequence_logprob(model, batch["chosen"])
    logps_rejected = get_sequence_logprob(model, batch["rejected"])

    # 2. Reference log-probs (pre-computed or computed here)
    with torch.no_grad():
        ref_logps_chosen = get_sequence_logprob(ref_model, batch["chosen"])
        ref_logps_rejected = get_sequence_logprob(ref_model, batch["rejected"])

    # 3. Compute implicit reward margins
    chosen_rewards = beta * (logps_chosen - ref_logps_chosen)
    rejected_rewards = beta * (logps_rejected - ref_logps_rejected)

    # 4. DPO loss = -log(sigmoid(chosen_reward - rejected_reward))
    loss = -F.logsigmoid(chosen_rewards - rejected_rewards).mean()
    return loss

def get_sequence_logprob(model, sequences):
    """Sum of log-probs over response tokens only."""
    outputs = model(sequences["input_ids"], attention_mask=sequences["mask"])
    logits = outputs.logits[:, :-1, :]  # Shift for next-token prediction

    # Gather log-prob of actual tokens
    labels = sequences["labels"][:, 1:]  # Shifted labels
    log_probs = F.log_softmax(logits, dim=-1)
    token_logps = log_probs.gather(-1, labels.unsqueeze(-1)).squeeze(-1)

    # Mask: only sum over response tokens (labels != -100)
    mask = (labels != -100).float()
    return (token_logps * mask).sum(dim=-1)  # Shape: [batch_size]

よくある落とし穴

Warning

DPO実装の落とし穴

  • プロンプトのマスクを忘れる :プロンプトトークンを対数確率の合計に含めると、モデルはプロンプトの尤度(役に立たないもの)を最適化し、実効的な \(\beta\) が誤ります。

  • 合計ではなく平均を使う :\(\frac{1}{T}\sum_t \log\pi\) と \(\sum_t \log\pi\) では、暗黙の長さペナルティが異なります。\(\pi_\theta\) と \(\pi_{\text{ref}}\) の間で一貫させなければなりません。

  • 参照モデルが古い :\(\pi_{\text{ref}}\) が \(\pi_\theta\) から遠すぎる場合(例:ベースモデル対ファインチューニング済みモデル)、KL項が支配的になり勾配が消えます。解決策は、ベースモデルではなくSFTチェックポイントを参照モデルとして使うことです。

  • \(\beta\) が大きすぎる :対数確率の差を増幅します \(\rightarrow\) sigmoidが飽和します \(\rightarrow\) 勾配がゼロになります。\(\beta = 0.1\) から始め、\([0.05, 0.5]\) の範囲で調整します。

  • \(\beta\) が小さすぎる :理論上は参照モデルからの自由度が増えます(KL制約が弱くなる)が、勾配 \(\propto \beta \cdot \sigma(-\beta h)\) が非常に小さくなります \(\rightarrow\) 損失地形が平坦になります \(\rightarrow\) 収束が極端に遅くなります。モデルには遠くへ移動する「許可」がある一方、どこへ移動すべきかを示すシグナルはほとんどありません。

DPOのバリアントとそれぞれが失敗する場合

Warning

DPOが失敗する場合

1. 分布シフト :古いモデルから得た選好データを使います。現在の方策は異なるテキストを生成します \(\rightarrow\) 損失が無関係な例を最適化することになります。

2. 探索がない :データセットにない振る舞いを発見できません。局所最適解にとどまります。

3. 参照モデルの崩壊 :参照モデルが強すぎると、方策が動けません。弱すぎると、正則化がありません。

4. データ品質 :ノイズのあるラベルが学習を汚染します。多くのサンプルで平均を取るPPOとは異なり、DPOは個々のペアを記憶します。

5. 選好データの多様性 :選択/棄却ペアが、品質差の全範囲をカバーするようにします(単に良い対ひどいだけにしない)。品質だけでなくアプローチが異なるペアは、より豊かな方策の区別を教えます。

\(\beta\) の選択ガイド

\(\beta\)領域使用する場合
0.01非常に積極的データが極めてクリーンで、大きな分布シフトが必要な場合のみ
0.05積極的良いデータがあり、SFTからの目立った改善を望む場合
0.1標準デフォルトの開始点。品質と安定性のバランスが良い
0.2保守的データにノイズがある場合、またはモデルが望ましい振る舞いにすでに近い場合
0.5非常に保守的能力を壊してはいけない安全性ファインチューニング

DPOのバッチサイズ設定とスケーリング

単一系列のトークン予測を行う標準的なSFTとは異なり、DPOは、好ましい系列と好ましくない系列を比較する ペア単位の損失 を利用します。これにより、メモリ使用量と最適化の安定性が根本的に変わります。

グローバルバッチサイズの目標

DPO実装全体の経験的な証拠から、最適なグローバルバッチサイズの範囲が確立されています。

\[ \boxed{B_{\text{global}} \in [32, 128]} \]

  • \(B_{\text{global}} < 32\):暗黙の報酬推定における勾配ノイズが深刻になります \(\rightarrow\) 方策が、(有用性と安全性という)アラインメント目標の間で破壊的に振動します。

  • \(B_{\text{global}} > 128\):収束速度に対する限界効果が小さくなり、分散計算間の通信オーバーヘッドが大きくなります。

数学的分解

DPOは2つのモデルコピー(アクティブな方策 \(\pi_\theta\)+凍結した参照モデル \(\pi_{\text{ref}}\))を同時に読み込むため、系列あたりのメモリは2倍になります。グローバルバッチサイズは次のように分解されます。

\[ \boxed{B_{\text{global}} = B_{\text{micro}} \times N_{\text{GPUs}} \times K_{\text{accum}}} \]

  • \(B_{\text{micro}}\):デバイスごとのマイクロバッチサイズ(1回のフォワードパスにおける選好ペア数)。

  • \(N_{\text{GPUs}}\):並列データ処理デバイスの数。

  • \(K_{\text{accum}}\):重みを更新する前の勾配蓄積ステップ数。

ペアリングの倍率 :1つのDPOデータインスタンスには、プロンプト(\(x\))、選択された応答(\(y_w\))、棄却された応答(\(y_l\))が含まれます。マイクロバッチあたりの実際のテンソル負荷は次のとおりです。

\[ T_{\text{sequences}} = 2 \times B_{\text{micro}} \]

パラメータが\(>\)7Bのモデルを80GB GPU上で、コンテキスト長4096〜8192トークンで動かす場合、物理的な上限によって \(B_{\text{micro}} \in [1, 2]\) に厳しく制限されます。

分散スケーリングの設定

設定単一GPU8-GPUノード
\(B_{\text{global}}\)6464
\(B_{\text{micro}}\)2(4系列)2(4系列)
\(N_{\text{GPUs}}\)18
\(K_{\text{accum}}\)32ステップ4ステップ
スループット逐次/低速高い並列スループット

DPO学習の分散スケーリングプロファイル(\(B_{\text{global}} = 64\) を目標)。

VRAM最適化:参照モデルの対数確率を事前計算する

DPO損失は次のとおりです。

\[ \mathcal{L}_{\text{DPO}}(\theta) = -\mathbb{E}_{(x, y_w, y_l)}!\left[\log \sigma!\left(\beta \log \frac{\pi_\theta(y_w|x)}{\pi_{\text{ref}}(y_w|x)} - \beta \log \frac{\pi_\theta(y_l|x)}{\pi_{\text{ref}}(y_l|x)}\right)\right] \]

\(\pi_{\text{ref}}\) は学習中 完全に静的 なので、その出力を事前計算できます。

Important

参照モデルを退避する戦略

  1. 学習開始前に、データセット \(\mathcal{D}\) 上で \(\pi_{\text{ref}}\) だけを使ってフォワードパスを実行します。

  2. スカラー \(\log \pi_{\text{ref}}(y_w\vert x)\) と \(\log \pi_{\text{ref}}(y_l\vert x)\) をディスクにキャッシュします。

  3. \(\pi_{\text{ref}}\) をGPUメモリから完全に退避させます。

結果 :利用可能なGPUメモリが2倍になります \(\rightarrow\) \(B_{\text{micro}}\) を1〜2から4〜8に増やせるため、ハードウェア利用率と学習スループットを最大化できます。

実装:TRLでは、DPOConfig に precompute_ref_log_probs=True を設定します。70Bモデルでは、クラスタ全体で\(\sim\)140GBのGPUメモリを節約できます。

DPOの拡張とバリアント

直接選好最適化(DPO)は、報酬関数と最適方策の間の閉形式の対応関係を導出することで、RLHFを教師あり学習問題として再定式化します。標準DPOの損失は次のとおりです。

\[ \mathcal{L}_{\text{DPO}}(\theta) = -\mathbb{E}_{(q,y_w,y_l)}!\left[ \log \sigma!\left( \beta \log \frac{\pi_\theta(y_w|q)}{\pi_{\text{ref}}(y_w|q)} - \beta \log \frac{\pi_\theta(y_l|q)}{\pi_{\text{ref}}(y_l|q)} \right) \right], \]

ここで \(y_w\) は好ましい(勝った)応答、\(y_l\) は好ましくない(負けた)応答、\(\beta\) はKLペナルティの強さを制御します。以下の小節では、最も重要な拡張とバリアントを扱います。

f-DPO — 一般化されたfダイバージェンスDPO

Tip

逆方向KLを超えて

標準DPOは、方策と参照モデルの間の正則化器として逆方向KLダイバージェンスを使います。逆方向KLはモードを探す性質があり、確率質量を少数の高報酬応答に集中させることを好みます。順方向KLはモードをカバーする性質があり、もっともらしい応答をすべてカバーするよう確率質量を広げます。f-DPO (J. Wang et al. 2024) は任意のfダイバージェンスへ一般化し、実務者がこれらの振る舞いをトレードオフできるようにします。

f-DPOの損失は、対数比をfダイバージェンス生成関数の導関数に置き換えます。

\[ \mathcal{L}_{f\text{-DPO}} = -\mathbb{E}!\left[ f'!\left(\frac{\pi_\theta(y_w|q)}{\pi_{\text{ref}}(y_w|q)}\right) - f'!\left(\frac{\pi_\theta(y_l|q)}{\pi_{\text{ref}}(y_l|q)}\right) \right], \]

ここで \(f'\) はfダイバージェンス生成関数の導関数です。

Important

TRLにおけるfダイバージェンスの選択肢

  • reverse_kl :\(f'(u) = \log u\)。標準DPO。モードを探します。

  • forward_kl :\(f'(u) = -1/u\)。モードをカバーします。多様性が向上します。

  • js_divergence :\(f'(u) = \log(2u/(u+1))\)。モード探索とモードカバーのバランスを取ります。

  • alpha_divergence :\(f'(u) = u^{\alpha-1}\)。順方向KLと逆方向KLの間を補間します。

Note

TRLでのf-DPO

from trl import DPOConfig, DPOTrainer

# Jensen-Shannon divergence (balanced)
config = DPOConfig(
    f_divergence_type="js_divergence",
    beta=0.1,
)

# Alpha divergence (alpha=0: forward KL, alpha=1: reverse KL)
config_alpha = DPOConfig(
    f_divergence_type="alpha_divergence",
    f_alpha_divergence_coef=0.5,   # alpha parameter
    beta=0.1,
)

trainer = DPOTrainer(
    model=model,
    ref_model=ref_model,
    args=config,
    train_dataset=dataset,
)

Important

f-DPOを使う場合

  • 多様性と品質のバランスが必要な場合は、 JSダイバージェンス を使います。

  • 多様性が最優先の創造的なタスクには、 順方向KL を使います。

  • 正解が1つのタスクには、 逆方向KL (標準DPO)を使います。

  • alphaダイバージェンス を使うと、連続的に補間してトレードオフを調整できます。

Robust DPO — ロバストDPO

Tip

選好データのノイズのあるラベル

人間による選好のアノテーションにはノイズがあります。アノテーターは意見が一致しなかったり、間違えたり、選好/非選好のラベルを逆にしたりすることがあります。標準DPOはすべてのラベルを正解として扱うため、モデルがノイズに過適合する可能性があります。Robust DPO (Chowdhury et al. 2024) は、既知のノイズモデルのもとで損失を解析的にバイアス補正します。

各ラベルが確率 \(\epsilon\)(ノイズ率)で反転すると仮定します。バイアス補正後の損失は次のとおりです。

\[ \boxed{ \mathcal{L}_{\text{robust}} = \frac{(1-\epsilon),\mathcal{L}_{\text{DPO}}(y_w, y_l) - \epsilon,\mathcal{L}_{\text{DPO}}(y_l, y_w)}{1 - 2\epsilon}, } \]

ここで \(\mathcal{L}_{\text{DPO}}(y_w, y_l)\) は \(y_w\) を選好された応答として扱う標準DPO損失、\(\mathcal{L}_{\text{DPO}}(y_l, y_w)\) はラベルを反転した場合の損失です。この補正によって、ラベルノイズが導入するバイアスを取り除きます。

Important

ロバストDPOの直感

この式は、反転したラベルの寄与を「差し引く」線形結合です。\(\epsilon=0\) では標準DPOに戻ります。\(\epsilon=0.5\) では分母が0に近づきます。ラベルが完全なノイズになり、学習は不可能です。実際には、\(\epsilon \in [0.05, 0.2]\) で現実のアノテーションノイズの大部分をカバーできます。

Note

TRLでのロバストDPO

from trl import DPOConfig, DPOTrainer

config = DPOConfig(
    loss_type="robust",
    label_smoothing=0.1,   # corresponds to epsilon = 0.1
    beta=0.1,
)

trainer = DPOTrainer(
    model=model,
    ref_model=ref_model,
    args=config,
    train_dataset=dataset,
)

TR-DPO — 信頼領域DPO

Tip

古くなった参照モデルの問題

標準DPOは学習中ずっと固定された参照モデル \(\pi_{\text{ref}}\) を使います。方策 \(\pi_\theta\) が改善すると、KLペナルティ \(\beta \log(\pi_\theta/\pi_{\text{ref}})\) が大きくなり、やがて損失を支配して、それ以上の改善を妨げます。TR-DPO (Gorbatenko 2024) は、現在の方策を追跡するよう参照モデルを定期的に更新します。

TR-DPOは指数移動平均(EMA)を使って参照モデルを更新します。

\[ \pi_{\text{ref}}^{(t+1)} \leftarrow \alpha \cdot \pi_\theta^{(t)} + (1-\alpha) \cdot \pi_{\text{ref}}^{(t)}, \]

ここで \(\alpha \in (0,1)\) は混合係数です。これは \(T_{\text{sync}}\) 回の勾配ステップごとに適用します。

Note

TRLでのTR-DPO

from trl import DPOConfig, DPOTrainer

config = DPOConfig(
    loss_type="sigmoid",        # standard DPO loss
    sync_ref_model=True,        # enable TR-DPO
    ref_model_mixup_alpha=0.6,  # alpha: how much of current policy to mix in
    ref_model_sync_steps=512,   # T_sync: sync every 512 steps
    beta=0.1,
)

trainer = DPOTrainer(
    model=model,
    ref_model=ref_model,
    args=config,
    train_dataset=dataset,
)

Important

TR-DPOを使う場合

  • 方策が初期参照モデルから大きく離れる長時間の学習。

  • KLペナルティの支配によってDPO損失が早期に頭打ちになる場合。

  • 現在の方策から新しい選好データを収集する反復的なDPOパイプライン。

  • \(\alpha\) を1に近づけると参照モデルを速く更新し、0に近づけるとゆっくり更新します。

EXO — 厳密な最適化

Tip

DPOにおけるKLの方向の問題

DPOは逆方向KL制約のもとで最適方策を解くことから導出されます。しかし、結果として得られる損失は実際には報酬空間で順方向KL目的関数を最適化しており、方向が間違っています。EXO (Ji et al. 2024) は、アラインメントに理論的に正しい目的関数である、逆方向KLの確率マッチングを使ってこれを修正します。

EXOは、モデル分布と目標(報酬最適)分布の間の逆方向KLを最小化します。

\[ \mathcal{L}_{\text{EXO}} = \mathbb{E}_{y \sim \pi_\theta}!\left[ \log \frac{\pi_\theta(y|q)}{p^*(y|q)} \right], \]

ここで \(p^*(y\vert q) \propto \pi_{\text{ref}}(y\vert q) \exp(r(y,q)/\beta)\) は最適方策です。実際には、EXOは利用可能な選好ペアを使って次のように近似します。

\[ \mathcal{L}_{\text{EXO}} \approx -\mathbb{E}!\left[ \log \sigma!\left( \beta \log \frac{\pi_{\text{ref}}(y_w|q)}{\pi_\theta(y_w|q)} - \beta \log \frac{\pi_{\text{ref}}(y_l|q)}{\pi_\theta(y_l|q)} \right) \right]. \]

DPOと比べて、\(\pi_\theta\) と \(\pi_{\text{ref}}\) の役割が入れ替わっていることに注目してください。

Note

TRLでのEXO

from trl import DPOConfig, DPOTrainer

config = DPOConfig(
    loss_type="exo_pair",
    beta=0.1,
)

trainer = DPOTrainer(
    model=model,
    ref_model=ref_model,
    args=config,
    train_dataset=dataset,
)

NCA — ノイズ対照アラインメント

Tip

DPOにおける尤度崩壊

DPOの既知の失敗モードは尤度崩壊です。モデルは負けた応答の確率を下げることを学びますが、損失が気にするのはだけなので、勝った応答の確率も下げてしまいます。NCA (H. Chen et al. 2024) は、これを防ぐために絶対尤度項を追加します。

NCAはアラインメントをノイズ対照推定として捉え直します。損失は3つの項を持ちます。

\[ \boxed{ \mathcal{L}_{\text{NCA}} = -\log \sigma(r_w) - \tfrac{1}{2}\log \sigma(-r_w) - \tfrac{1}{2}\log \sigma(-r_l), } \]

ここで \(r_y = \beta \log(\pi_\theta(y\vert q)/\pi_{\text{ref}}(y\vert q))\) は暗黙の報酬です。第1項は \(y_w\) に高い報酬を与えるよう促し、第2項と第3項は \(y_w\) と \(y_l\) の両方に高い報酬を与えることを罰します(崩壊を防ぎます)。

Note

TRLでのNCA

from trl import DPOConfig, DPOTrainer

config = DPOConfig(
    loss_type="nca_pair",
    beta=0.01,   # small beta: absolute likelihood term dominates
)

trainer = DPOTrainer(
    model=model,
    ref_model=ref_model,
    args=config,
    train_dataset=dataset,
)

Important

NCAを使う場合

  • DPO学習中に勝った応答の確率が下がっている場合。

  • 相対的な順位だけでなく、応答の絶対的な品質が重要なタスク。

  • 絶対尤度項により大きな重みを与えるため、小さな \(\beta\)(例:0.01)を使います。

SLiC-HF — 系列尤度キャリブレーション

Tip

より単純な代替手段としてのヒンジ損失

DPOのlog-sigmoid損失は滑らかですが、マージンが大きい場合には収束が遅くなることがあります。SLiC-HF (Yao Zhao et al. 2023) はヒンジ損失を使います。これはマージンが閾値を超えると0になり、それ以外では線形になります。単純で高速であり、驚くほど競争力があります。

SLiC-HFの損失は次のとおりです。

\[ \mathcal{L}_{\text{SLiC}} = \max!\left(0,; \delta - \beta\log\frac{\pi_\theta(y_w|q)}{\pi_{\text{ref}}(y_w|q)} + \beta\log\frac{\pi_\theta(y_l|q)}{\pi_{\text{ref}}(y_l|q)} \right), \]

ここで \(\delta\) はマージンの閾値です。モデルが勝った応答と負けた応答の間にすでに \(\delta\) のマージンを割り当てている場合、損失は0になります。

Note

TRLでのSLiC-HF

from trl import DPOConfig, DPOTrainer

config = DPOConfig(
    loss_type="hinge",
    beta=0.1,
)

trainer = DPOTrainer(
    model=model,
    ref_model=ref_model,
    args=config,
    train_dataset=dataset,
)

Iterative RPO — 推論選好最適化

Tip

DPOは生成方法を忘れる

標準DPOは、勝った応答と負けた応答を識別するようモデルを学習します。しかし、推論タスクでは、モデルは正しい推論トレースを生成する必要もあります。識別できても生成できないモデルは、推論時には役に立ちません。RPOは勝った応答にNLL(負の対数尤度)項を追加し、モデルがそれを生成することを学ぶようにします。

RPOの損失は、DPOとSFTを組み合わせたものです。

\[ \mathcal{L}_{\text{RPO}} = \lambda_1 \mathcal{L}_{\text{DPO}}(y_w, y_l) + \lambda_2 \mathcal{L}_{\text{NLL}}(y_w), \]

ここで \(\mathcal{L}_{\text{NLL}}(y_w) = -\log \pi_\theta(y_w\vert q)\) は、勝った応答に対する標準的な言語モデリング損失です。

Note

TRLでのIterative RPO

from trl import DPOConfig, DPOTrainer

config = DPOConfig(
    loss_type="sigmoid",            # Standard DPO loss
    rpo_alpha=1.0,                  # NLL regularisation weight (RPO)
    beta=0.1,
)

trainer = DPOTrainer(
    model=model,
    ref_model=ref_model,
    args=config,
    train_dataset=dataset,
)

Important

RPOを使う場合

  • モデルがステップごとの解を生成しなければならない推論タスク(数学、コード、論理)。

  • DPO学習によって、モデルの流暢さや生成品質が失われる場合。

  • 反復パイプライン:ロールアウトを生成し、ラベルを付け、RPOで学習し、繰り返します。

  • NLL項が正則化器として機能し、方策の崩壊を防ぎます。

SimPO — 単純な選好最適化

Tip

参照モデルなしの選好学習

DPOは暗黙の報酬を計算するために参照モデルを必要とします。これによりメモリ使用量が2倍になり、複雑さも増します。SimPO (Meng et al. 2024) は、応答の平均対数確率を暗黙の報酬として使い、モデルが短い応答を好むことを防ぐ長さの正規化項を加えることで、参照モデルを取り除きます。

SimPOは暗黙の報酬を次のように定義します。

\[ r_{\text{SimPO}}(y|q) = \frac{\beta}{|y|} \log \pi_\theta(y|q), \]

損失は次のとおりです。

\[ \boxed{ \mathcal{L}_{\text{SimPO}} = -\mathbb{E}!\left[ \log \sigma!\left( \frac{\beta}{|y_w|}\log\pi_\theta(y_w|q) - \frac{\beta}{|y_l|}\log\pi_\theta(y_l|q) - \gamma \right) \right], } \]

ここで \(\gamma > 0\) は目標報酬マージンです。これにより、勝った応答の報酬が負けた応答より少なくとも \(\gamma\) だけ厳密に高くなることを保証します。

Important

SimPOとDPOとORPOの比較

  • DPO :参照モデルを使う。比に基づく暗黙の報酬。

  • ORPO :参照モデルなし。SFT損失にオッズ比項を追加。

  • SimPO :参照モデルなし。長さを正規化した対数確率の報酬+マージン。

  • SimPOはDPOより単純(参照モデルが不要)で、ORPOより原理に忠実です。

  • SimPOの長さの正規化は重要です。これがないと、モデルは長い応答を好みます。

Note

TRLでのSimPO

from trl import DPOConfig, DPOTrainer

config = DPOConfig(
    loss_type="simpo",
    simpo_gamma=0.5,   # target reward margin gamma
    beta=2.5,          # length normalisation coefficient
    # No ref_model needed!
)

trainer = DPOTrainer(
    model=model,
    ref_model=None,    # SimPO is reference-free
    args=config,
    train_dataset=dataset,
)