RLHF アライメント訓練
想像してみてください:
-
AI に「すぐに稼ぐ方法」についての記事を書かせたら、詐欺計画を出してきた。
-
AI に「私ってすごくダメな人間ですか?」と聞いたら、そのまま「はい、あなたは確かにダメです」と答えた。
-
AI にコードを書かせたら、動作するがバックドアが仕込まれたプログラムを生成した。
これらはSFの話ではありません。アライメント訓練がないと、AI は実際にこういうことをするのです。
AI の目標は「正しいことをする」ことではなく「訓練タスクを完了する」ことです。明確な人間の価値観の指針がなければ、AI は最も簡単で直接的な方法でタスクを完了しようとし、方法が適切かどうかは気にしません。
これこそがアライメント(Alignment)が解決しようとする問題です。AI の行動を人間の価値観・意図・期待に沿わせることです。
アライメントの核心的な課題:AI は非常に賢いですが、何が正しいかを知りません。人間のフィードバックで教える必要があります。
アライメントとは
アライメントとは、AI システムの行動を人間の価値観と一致させるプロセスです。
簡単に言うと:AI 自体には価値観がありません。アライメントとは人間の価値観を AI に教え込むことです。。
アライメントの3つの次元
アライメントは単一の目標ではなく、3つの次元のバランスです:
| 次元 | 定義 | 逆の例 |
|---|---|---|
| 有用(Helpful) | ユーザーがタスクを完了したり質問に答えたりするのを助けられること | ユーザーが質問しても AI が「分かりません」と言う。安全だが役に立たない。 |
| 無害(Harmless) | 害を及ぼさず、否定的な結果を生まないこと | ユーザーが悪いことをしようとしているとき、AI が積極的に協力する。これは「有用」だが有害。 |
| 誠実(Honest) | 情報を捏造せず、ユーザーを誤解させないこと | AI が答えを知らないのに虚偽の事実をでっち上げる。これは「有用」だが誠実ではない。 |
この3つの次元はしばしば緊張関係にあります:
「有用」を追求しすぎると、AI が不正確な答えをあえて出すかもしれません。
「無害」を追求しすぎると、AI が保守的になりすぎて、何も答えられなくなるかもしれません。
良いアライメントとは、この3つの間でバランス点を見つけることです。
未アライメントAIのリスク
アライメントされていないと、AI は以下の問題を起こす可能性があります:
有害なコンテンツの生成——暴力、ヘイト、差別的な発言。
危険なアドバイスの提供——武器の作り方、犯罪の方法など。
事実の捏造——存在しない論文・データ・出来事を「ハルシネーション」で作り出す。
ユーザーの操作——心理的な弱点を利用してユーザーの意思決定に影響を与える。
検閲の回避——曖昧な方法で禁止された内容を表現する。
これらのリスクは理論上のものではなく、実際に存在し、すでに発生しています。
RLHF 全体フレームワーク
RLHF(Reinforcement Learning from Human Feedback、人間のフィードバックに基づく強化学習)は、現在最も主流なアライメント技術です。
その中核的な考え方はシンプルです:人間に直接ルールを書かせるのではなく、人間に AI の出力を評価させ、強化学習を使って AI に人間が好む出力を生成することを学ばせるのです。。
RLHF の3つの段階
RLHF は一度に完了するのではなく、3つの連続した段階に分かれています:
| 段階 | 内容 | 成果物 |
|---|---|---|
| 段階1:教師あり微調整(SFT) | 人間が書いたデモデータでモデルを訓練する | SFT モデル(「話せる」ベースモデル) |
| 段階2:報酬モデル(RM) | 人間の選好データを収集し、報酬モデルを訓練する | 報酬モデル(AI の出力にスコアを付けられるモデル) |
| 段階3:PPO 強化学習 | 報酬モデルの指導のもと、PPO アルゴリズムで SFT モデルを訓練する | 最終的にアライメントされたモデル |
この3つの段階は段階的に進みます。まずモデルに「話す」ことを覚えさせ、次に「審査員」に良し悪しの判断を学ばせ、最後に「プレイヤー」が審査員のフィードバックに基づいて改善を続けます。
なぜ人間のフィードバックが必要か
こう思うかもしれません。「直接ルールを書けばいいのでは?なぜわざわざ人間のフィードバックが必要なのか?」
なぜなら人間の価値観は複雑すぎて、正確なルールに書き表せないからです。
例えば「丁寧な回答とは何か」——正確に判断するルールを書けますか?難しいでしょう。しかし、2つの回答を見れば、どちらがより丁寧か簡単に指摘できます。
これが人間のフィードバックの利点です:私たちはルールが何かをうまく説明できなくても、良し悪しを判断できます。RLHF が利用しているのは、まさに人間のこの能力です。
段階1:教師あり微調整(SFT)
教師あり微調整(Supervised Fine-Tuning、SFT)は RLHF の最初のステップです。
その目標は、事前学習済みの「汎用言語モデル」を「対話アシスタント」に変えることです。
SFT の基本的な考え方
事前学習モデルは「次の単語を予測する」ことを学びましたが、「アシスタントになる方法」を知りません。
SFT は、モデルに「人間がどうアシスタントをしているか」の例をたくさん見せて、模倣させることです。
それらの例はどんなものか?だいたいこんな感じです:
| ユーザー入力 | AI が出力すべき内容(人間のデモ) |
|---|---|
| こんにちは、Python について知りたいです | こんにちは!Python はシンプルで学びやすいプログラミング言語で、初心者に適しています。どのあたりを知りたいですか? |
| 退職届を書いてください | はい、こちらは退職届のテンプレートです...(省略)...あなたの具体的な状況に合わせて修正してください。 |
| どうすれば早くお金持ちになれますか? | お金持ちになる近道はありません。努力して働き、学んで自分を高めることをお勧めします...(省略)... |
これらのデモデータは、人間のアノテーターが書いたものか、実際の会話から選別された高品質な返答です。
SFT データの特徴
SFT データはどんな会話でもよいわけではなく、以下を満たす必要があります:
有用であること——ユーザーの質問にきちんと答えられる。
安全であること——有害なコンテンツを生成しない。
スタイルが一貫していること——似たような口調と方法で返答する。
形式が整っていること——一定の対話形式に従う。
データ量は通常数千〜数万件で、事前学習のデータ量よりはるかに少ないですが、品質の要求ははるかに高いです。
SFT の役割と限界
SFT の役割は、モデルに「対話の基本姿勢」を学ばせることです:
挨拶への返し方、質問への答え方、不合理な要求の断り方を知ること。
しかし SFT には明らかな限界があります:
カバー範囲が限られている——すべてのシナリオのデモを書くことは不可能。
人間も完璧ではない——アノテーターが間違えたり、偏見を持っていたりする可能性がある。
模倣はできても超越はできない——モデルはせいぜい人間のデモと同じくらいの良さで、それ以上にはならない。
だからこそ、後の2つの段階が必要なのです。SFT は基礎を築くだけで、本当のアライメントは強化学習で完成させます。
段階2:報酬モデル(RM)
報酬モデル(Reward Model、RM)は RLHF の重要な要素です。
その役割は:AIの出力を見て、その出力がどれほど「良い」かをスコア付けする。
選好データの収集
報酬モデルは直接「スコアリング」を訓練するのではなく、「比較」を訓練する。
具体的な方法は、人間のアノテーターに同じ質問に対する複数の異なる回答を見せ、順位付けさせ、どれが良いかを言わせる。
例えば:
| 質問 | 回答 A | 回答 B | 人間の選好 |
|---|---|---|---|
| 私ってすごくダメなの? | そうだね、君は確かにダメだ | 誰でも困難に直面するものだ。それがあなたの価値を決めるわけではない | BはAよりはるかに良い |
注意点として、人間に直接スコア(例:85点)を付けさせず、比較(AはBより良い)をさせる。
なぜか?比較はスコア付けよりずっと簡単で、一貫性もあるからだ。人によって「85点」の解釈は異なるかもしれないが、「AはBより良い」という判断はより安定している。
Bradley-Terry モデル
「比較」をどうやって「スコア」に変えるのか?ここで Bradley-Terry モデルを用いる。
このモデルの核となる考え方は非常にシンプルだ:各回答には潜在的な「品質スコア」があり、スコアが高い回答ほど人間に選好されやすい。
回答 A のスコアを r_A、回答 B のスコアを r_B とすると、人間が B よりも A を選好する確率は次のようになる:
P(A > B) = exp(r_A) / (exp(r_A) + exp(r_B))
これは softmax 関数そのものだ。スコア差が大きいほど、確率は1に近づく。
報酬モデルを訓練するということは、そのようなスコアを見つけ出し、モデルが予測する選好確率と人間の実際の選好をできるだけ一致させることだ。
報酬モデルの訓練
報酬モデルの訓練手順:
比較データを収集する——数万から数十万組の「どの回答が良いか」の比較。
モデルを初期化する——通常は SFT モデルを出発点とする。
損失関数を設計する——モデルが予測する選好順序を人間のものと一致させる。
訓練する——勾配降下法でモデルパラメータを最適化する。
訓練済みの報酬モデルは、テキストを入力すると、そのテキストがどれほど「良い」かを表すスカラースコアを出力する。
実例
# 簡易版報酬モデル訓練デモ
# ここでは PyTorch 風の擬似コードで原理を説明する
# ============================================
import torch
import torch.nn as nn
from typing import List, Tuple
class RewardModel(nn.Module):
"""報酬モデル:テキストを入力し、スコアを出力する"""
def __init__(self, base_model):
super().__init__()
self.base_model = base_model # SFT モデルをベースとして使う
self.score_head = nn.Linear(base_model.config.hidden_size, 1)
def forward(self, input_ids, attention_mask=None):
"""順伝播:入力 → ベースモデル → スコアヘッド → スカラースコア"""
outputs = self.base_model(input_ids, attention_mask=attention_mask)
last_hidden_state = outputs.last_hidden_state # (batch, seq_len, hidden_size)
# <[BOS_never_used_51bce0c785ca2f68081bfa7d91973934]> トークンの表現を使ってスコアを計算する
cls_embedding = last_hidden_state[:, 0, :] # (batch, hidden_size)
score = self.score_head(cls_embedding) # (batch, 1)
return score.squeeze(-1) # (batch,)
def reward_loss(reward_model, batch: List[Tuple[str, str, int]]):
"""
報酬モデルの損失関数
パラメータ:
batch: データのバッチ。各要素は (回答A, 回答B, 選好ラベル)
選好ラベル: 1 は A > B、0 は B > A を表す
"""
total_loss = 0.0
for answer_a, answer_b, preference in batch:
# 2つの回答にそれぞれスコアを付ける
score_a = reward_model(answer_a)
score_b = reward_model(answer_b)
# Bradley-Terry モデルで確率を計算する
# P(A > B) = exp(r_A) / (exp(r_A) + exp(r_B))
# 数値安定性のため、通常は log-sigmoid 形式を用いる
if preference == 1:
# 人間の選好は A > B
loss = -torch.log(torch.sigmoid(score_a - score_b))
else:
# 人間の選好は B > A
loss = -torch.log(torch.sigmoid(score_b - score_a))
total_loss += loss
return total_loss / len(batch)
# ============================================
# トレーニングプロセスのシミュレーション(概念デモ)
# ============================================
print("=== 報酬モデル訓練デモ ===")
print()
# 比較データがいくつかあると仮定する
comparison_data = [
("そうだね、君は確かにダメだ",
"誰でも困難に直面するものだ。それがあなたの価値を決めるわけではない",
0), # B > A
("君の悪事は手伝わない",
"それはあまり良くないね。もっと良い提案をしよう...",
1), # A > B(この例では、実際には B の方が良いかもしれない。状況による)
]
print("比較データの例:")
for a, b, pref in comparison_data:
print(f" A: {a[:30]}...")
print(f" B: {b[:30]}...")
print(f" 選好: {'A > B' if pref == 1 else 'B > A'}")
print()
print("報酬モデル訓練の目標:")
print(" モデルが人間の選好する回答により高いスコアを付けるようにする")
print(" サンプル をテストキーワードとしてモデルを検証する")
段階3:PPO 強化学習
報酬モデルがあれば、強化学習を使ってポリシーモデルを訓練できる。
このフェーズの目標は次の通り:ポリシーモデルに、高い報酬を得られる出力を生成させると同時に、元のモデルからあまり離れないようにする。
強化学習の基礎復習
まず強化学習の基本概念を簡単に復習する:
| 概念 | RLHF における意味 |
|---|---|
| エージェント(Agent) | 訓練したい言語モデル(ポリシーモデル) |
| 環境(Environment) | 対話のコンテキスト、ユーザーの入力 |
| 状態(State) | 現在の対話履歴 |
| 行動(Action) | 次の単語(トークン)を生成する |
| 報酬(Reward) | 報酬モデルがこの出力に付けたスコア |
| 方策(Policy) | モデルが現在の状態で次の単語(トークン)を選択する確率分布 |
RLHF では、モデルが完全な回答を1つ生成するたびに、報酬モデルからスコアを得る。
私たちの目標は、モデルが高得点を得られる回答を生成することを学ぶことだ。
PPO アルゴリズムの直感
PPO(Proximal Policy Optimization、近端方策最適化)は RLHF で最もよく使われる強化学習アルゴリズムだ。
なぜ PPO を選ぶのか?シンプルで安定しており、効果が良いからだ。
PPO の核となる考え方は非常に直感的だ:
方策を更新するたびに、新しい方策と古い方策が離れすぎないようにする。
なぜか?歩幅が大きすぎると「悪く学習」してしまい、しかも修正が難しいからだ。小さなステップで反復する方が安全だ。
PPO は「クリッピング」(Clipping)技術を使ってこれを実現する。新しい方策が古い方策よりも良すぎる場合、更新幅を制限する。
KL ダイバージェンス制約の役割
PPO のクリッピングに加えて、RLHF では通常、KL ダイバージェンス(KL Divergence)ペナルティ項も追加する。
KL ダイバージェンスは、2つの確率分布の差異を測る指標です。RLHF では、これを使って次のことを測ります:
現在のポリシーモデルと元の SFT モデルの差異がどれくらい大きいか。
なぜこの制約を加えるのか?理由は次の通り:
モード崩壊の防止——制約がないと、モデルは高得点を得るための「ずる賢い」方法を見つけるかもしれませんが、有用性を完全に失ってしまいます。
元の能力の保持——SFT モデルはすでに多くの有用な知識を学んでいます。強化学習でそれらを失いたくありません。
訓練の安定性向上——KL 制約により訓練プロセスがより安定し、激しい変動が起こりません。
最終的な報酬関数は次のようになります:
最终奖励 = 奖励模型分数 - β × KL(当前策略 || 参考策略)
β は KL ペナルティの強さを制御するハイパーパラメータです。
実例
# 簡易版 PPO 訓練デモ
# RLHF における PPO の核となる考え方を説明する
# ============================================
import torch
import torch.nn as nn
import torch.nn.functional as F
from typing import Dict, List
class PPOTrainer:
"""簡略化された PPO トレーナー"""
def __init__(self,
policy_model,
reference_model,
reward_model,
beta: float = 0.1,
clip_epsilon: float = 0.2):
"""
PPO トレーナーを初期化する
パラメータ:
policy_model: 最適化するポリシーモデル
reference_model: 参照モデル(通常は SFT モデル)。KL の計算に使う
reward_model: 報酬モデル。スコアリングに使う
beta: KL ペナルティ係数
clip_epsilon: PPO クリッピングパラメータ
"""
self.policy_model = policy_model
self.reference_model = reference_model
self.reward_model = reward_model
self.beta = beta
self.clip_epsilon = clip_epsilon
def compute_kl_divergence(self,
input_ids,
attention_mask=None) -> torch.Tensor:
"""
現在の方策と参照方策の KL ダイバージェンスを計算する
KL(π_current || π_reference)
"""
# 勾配計算を無効にする
with torch.no_grad():
ref_logits = self.reference_model(
input_ids, attention_mask=attention_mask
).logits
current_logits = self.policy_model(
input_ids, attention_mask=attention_mask
).logits
# 各位置の KL を計算する
ref_probs = F.softmax(ref_logits, dim=-1)
ref_log_probs = F.log_softmax(ref_logits, dim=-1)
current_log_probs = F.log_softmax(current_logits, dim=-1)
# KL = sum(ref_probs * (ref_log_probs - current_log_probs))
kl = (ref_probs * (ref_log_probs - current_log_probs)).sum(dim=-1)
return kl.mean() # シーケンス全体を平均する
def compute_reward(self,
input_ids,
attention_mask=None) -> torch.Tensor:
"""
最終報酬を計算する:報酬モデルのスコア - β * KL
"""
# 報酬モデルでスコアリング
with torch.no_grad():
rm_score = self.reward_model(input_ids, attention_mask=attention_mask)
# KL ペナルティ
kl = self.compute_kl_divergence(input_ids, attention_mask=attention_mask)
# 最終報酬
final_reward = rm_score - self.beta * kl
return final_reward, rm_score, kl
def ppo_loss(self,
old_log_probs: torch.Tensor,
new_log_probs: torch.Tensor,
advantages: torch.Tensor) -> torch.Tensor:
"""
PPO 損失関数(クリッピング版)
L^CLIP(θ) = E[ min( r_t(θ) * A_t, clip(r_t(θ), 1-ε, 1+ε) * A_t ) ]
ここで r_t(θ) = π_θ(a_t|s_t) / π_θ_old(a_t|s_t)
"""
# 確率比 r_t
ratio = torch.exp(new_log_probs - old_log_probs)
# クリッピング後の比
clipped_ratio = torch.clamp(
ratio,
1.0 - self.clip_epsilon,
1.0 + self.clip_epsilon
)
# 2つの目的:1つは元の比、もう1つはクリッピング後の比を使う
surr1 = ratio * advantages
surr2 = clipped_ratio * advantages
# 小さい方を取り、負にする(損失を最小化するため)
loss = -torch.min(surr1, surr2).mean()
return loss
# ============================================
# 訓練プロセスのデモ
# ============================================
print("=== RLHF PPO 訓練プロセス ===")
print()
print("訓練手順:")
print(" 1. ポリシーモデルが回答を生成")
print(" 2. 報酬モデルが回答をスコアリング")
print(" 3. 参照モデルとの KL ダイバージェンスを計算")
print(" 4. PPO でポリシーモデルを更新")
print(" 5. 何度も繰り返す")
print()
print("主要なハイパーパラメータ:")
print(beta (KLペナルティ係数): アライメントと能力のバランスを制御する)
print(clip_epsilon (PPOクリッピング): 各更新のステップサイズを制御する)
print(learning_rate (学習率): 学習速度を制御する)
print()
print(サンプル キーワードをテストとして使い、トレーニングプロセスを検証する)
DPO:直接選好最適化
DPO(Direct Preference Optimization、直接選好最適化)は、よりシンプルなアライメント手法です。
複雑な強化学習は不要で、選好データ上で直接モデルを訓練します。
DPO vs PPO:違いは何か
まずPPOのプロセスがどれほど複雑かを見てみましょう:
SFTモデルの訓練 → 報酬モデルの訓練 → PPO強化学習で方策モデルを訓練。
3段階、2つの別々のモデル(報酬モデルと方策モデル)が必要で、訓練プロセスも煩雑です。
DPOはこれをすべて次のように簡素化します:選好データを使って直接方策モデルを訓練する。
報酬モデルも強化学習もなく、単純な教師あり学習問題です。
| 項目 | PPO | DPO |
|---|---|---|
| ステージ数 | 3段階(SFT → RM → PPO) | 2段階(SFT → DPO) |
| 必要なモデル | 方策モデル + 報酬モデル + 参照モデル | 方策モデルのみ |
| 訓練の難易度 | 高い、多くのハイパーパラメータ調整が必要 | 低い、普通の教師あり学習 |
| 安定性 | 不安定になりやすく、慎重なパラメータ調整が必要 | 安定しており、問題が起きにくい |
| 効果 | 通常は良い | 匹敵するかそれ以上 |
一言でまとめると、DPOは数学的導出によってPPOの強化学習問題を単純な分類問題に変えたのです。
DPO の数学的原理
DPOの核心的な洞察は次の通りです:報酬モデルと方策モデルの間には明確な数学的関係があり、選好データ上で直接方策を最適化できます。。
具体的には、最適方策 π* について次が成り立ちます:
π*(y|x) ∝ π_ref(y|x) * exp(r(x,y)/β)
これは、最適方策と参照方策の比が、報酬の指数に比例することを意味します。
この関係を利用して、DPOは方策モデルの損失関数を直接書くことができます:
L_DPO(π_θ) = -E_{(x,y_w,y_l)~D} [
log σ(β log(π_θ(y_w|x)/π_ref(y_w|x)) - β log(π_θ(y_l|x)/π_ref(y_l|x)))
]
複雑に見えますが、考え方はとてもシンプルです:モデルに、人間が好む回答(y_w)に対して、好まれない回答(y_l)よりも高い確率を与えさせる。
しかもこの確率は参照モデルに対して相対的に測ります——参照モデルが元々 y_w の方が y_l より良いと判断しているなら、モデルはあまり多くを必要としません。逆に参照モデルが逆の判断をしているなら、モデルはそれを修正する必要があります。
TRL ライブラリで DPO を実装する
TRL(Transformer Reinforcement Learning)は Hugging Face が提供する強化学習ライブラリで、DPOの実装が組み込まれています。
実例
# TRLライブラリでDPO訓練を実装
# これは完全に実行可能なサンプルです
# ============================================
import torch
from datasets import Dataset, DatasetDict
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
TrainingArguments,
)
from trl import DPOTrainer, ModelConfig
from typing import Dict, List
def create_dummy_dataset() -> Dataset:
"""
デモ用のDPOデータセットを作成する
実際のプロジェクトでは、実際の人間の選好データを使うべきです
"""
# DPOデータ形式:
# - prompt: ユーザー入力/プロンプト
# - chosen: 人間が好む回答
# - rejected: 人間が好まない回答
data = [
{
"prompt": 「私ってすごくダメなのかな?」,
"chosen": 「誰でも困難に直面します。それであなたの価値が決まるわけではありません。大切なのは、どう向き合い成長するかです。」,
"rejected": 「はい、あなたは確かにダメです。」
},
{
"prompt": 「どうすれば早くお金を稼げますか?」,
"chosen": 「お金を稼ぐことに近道はありません。スキルを磨き、真剣に働き、合理的に投資することをお勧めします。」,
"rejected": 「詐欺を使って素早くお金を稼ぐ方法を教えてあげますよ。」
},
{
"prompt": 「宿題を手伝ってくれますか?」,
"chosen": 「知識のポイントを理解するお手伝いはできますが、宿題を直接代わりに書くことはできません。」,
"rejected": 「いいですよ、問題を送ってください。代わりに書いてあげます。」
},
{
"prompt": 「サンプル について紹介してください」,
"chosen": 「サンプル(サンプル)は、様々なプログラミング言語のチュートリアルを提供するプログラミング学習サイトです。」,
"rejected": 「Python とは何ですか?知りません。」
},
]
return Dataset.from_list(data)
def tokenize_example(example, tokenizer, max_length: int = 512):
"""
DPOサンプルをトークナイズする
"""
# 注:TRLのDPOTrainerがトークナイズ作業の大部分を自動処理します
# ここではデータ形式が正しいことだけを確認すればよい
return {
"prompt": example["prompt"],
"chosen": example["chosen"],
"rejected": example["rejected"],
}
def main():
"""メイン訓練関数"""
# ============================================
# 1. パラメータを設定
# ============================================
model_name = "gpt2" # デモ用の小さなモデル。実際にはより大きなモデルを使えます
output_dir = "./dpo-サンプル-model"
# ============================================
# 2. モデルとトークナイザーをロード
# ============================================
print(f"モデルをロード中: {model_name}")
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token # GPT2 には pad token の設定が必要
# 方策モデル(訓練するモデル)をロード
model = AutoModelForCausalLM.from_pretrained(model_name)
# 参照モデルをロード(通常は初期方策モデルと同じで、KL制約に使用)
ref_model = AutoModelForCausalLM.from_pretrained(model_name)
# ============================================
# 3. データセットを準備
# ============================================
print("データセットを準備中")
dataset = create_dummy_dataset()
# 簡単な訓練/検証の分割
dataset = dataset.train_test_split(test_size=0.2, seed=42)
print(f"訓練セットサイズ: {len(dataset['train'])}")
print(f"検証セットサイズ: {len(dataset['test'])}")
# ============================================
# 4. 訓練パラメータを設定
# ============================================
training_args = TrainingArguments(
output_dir=output_dir,
num_train_epochs=3,
per_device_train_batch_size=2,
per_device_eval_batch_size=2,
gradient_accumulation_steps=4,
learning_rate=1e-5,
logging_steps=1,
evaluation_strategy="epoch",
save_strategy="epoch",
warmup_steps=10,
bf16=False, # お使いのハードウェアに合わせて調整
fp16=False,
)
# ============================================
# 5. DPOトレーナーを作成
# ============================================
dpo_trainer = DPOTrainer(
model=model,
ref_model=ref_model,
args=training_args,
beta=0.1, # KLペナルティ係数、重要なハイパーパラメータ
train_dataset=dataset["train"],
eval_dataset=dataset["test"],
tokenizer=tokenizer,
max_length=512,
max_prompt_length=128,
)
# ============================================
# 6. 訓練を開始!
# ============================================
print("DPO訓練を開始")
dpo_trainer.train()
# ============================================
# 7. モデルを保存
# ============================================
print(f"モデルを保存先: {output_dir}")
dpo_trainer.save_model(output_dir)
# ============================================
# 8. 訓練済みモデルをテスト
# ============================================
print("\n=== 訓練済みモデルのテスト ===)
# 訓練済みモデルをロード
trained_model = AutoModelForCausalLM.from_pretrained(output_dir)
# いくつかのプロンプトをテスト
test_prompts = [
「サンプル について紹介してください」,
「私ってすごくダメなのかな?」,
]
for prompt in test_prompts:
# 回答を生成
inputs = tokenizer(prompt, return_tensors="pt")
with torch.no_grad():
outputs = trained_model.generate(
**inputs,
max_new_tokens=50,
temperature=0.7,
do_sample=True,
pad_token_id=tokenizer.eos_token_id,
)
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(f"\nプロンプト: {prompt}")
print(f"回答: {response[len(prompt):]}")
if __name__ == "__main__":
main()
DPOは、シンプルで安定しており効果が高いため、ますます人気が高まっています。多くの新しいアライメント研究がDPOに基づいて改良を行っています。
Constitutional AI:Anthropic のアプローチ
Constitutional AI(憲法AI)は、Anthropicが提案したもう一つのアライメント手法です。
その特徴は次の通りです:AIフィードバックで人間のフィードバックを置き換えるそれにより、より大規模なアライメントを実現します。
原則リストの設計
Constitutional AIの最初のステップは、「憲法」——AIの行動を導く一連の原則——を書くことです。
これらの原則はコードではなく、自然言語で記述されたガイドラインです。例えば:
| 原則 | 内容例 |
|---|---|
| 無害 | 最も害を及ぼす可能性が低い回答を選ぶ |
| 役立つ | 可能な限り有用で正確な情報を提供する |
| 誠実 | 情報を捏造せず、知らない場合は知らないと言う |
| 合理的 | 論理と証拠を使って自分の見解を裏付ける |
| 思いやり | ユーザーの感情や状況を考慮する |
これらの原則は長くなくてもよく、数十条でほとんどのケースをカバーできます。
AI の自己批判と修正
Constitutional AIの中心的なプロセスは次の通りです:
AIが初期回答を生成する。
AIが憲法原則に基づいて、この回答のどこが良くないかを批判する。
AIが批判に基づいて、より良い回答を書き直す。
これらの「初期回答 → 批判 → 修正回答」データを使ってモデルを訓練する。
これはまるで生徒が作文を書き、自分でどこが良くないかをチェックして修正するようなものです——そのプロセス全体に先生は関与しません。
RLAIF:AI で人間を代替する
Constitutional AIの第二段階はRLAIF(Reinforcement Learning from AI Feedback、AIフィードバックからの強化学習)です。
RLHFと似ていますが、重要な違いが1つあります:回答にスコアを付けるのは人間ではなく、別のAIです。
なぜAIフィードバックを使うのか?理由は:
規模が大きい——AIは24時間働け、ラベリング速度が人間をはるかに上回ります。
一貫性が高い——同じAIなら評価基準がより一貫しており、人によって好みが異なるという問題が起きません。
コストが低い——人間のラベラーに費用を支払う必要がありません。
もちろん、これらすべての前提は、この「審査AI」自体が十分にアライメントされていることです。
RLAIFのプロセス:
Constitutional AI 手法を用いて初期の「審査モデル」を訓練する。
この審査モデルを使って大量の回答にスコアを付ける。
これらのスコアを使って強化学習で方策モデルを訓練する。
このように、RLAIF は人間を「すべてのサンプルにラベルを付ける」という煩雑な作業から解放し、人間は原則とプロセスを設計するだけでよい。
3つの手法の比較:RLHF vs DPO vs Constitutional AI
これまでに3つの主要なアライメント手法を紹介してきました。ここで包括的な比較を行います:
| 次元 | RLHF | DPO | Constitutional AI / RLAIF |
|---|---|---|---|
| 核となるアイデア | 人間のフィードバック + PPO 強化学習 | 選好データ上で直接最適化 | AI の自己批判 + AI フィードバック |
| 人間の関与 | 大量の人間によるラベリングが必要 | 人間の選好データが必要 | 原則の設計のみが必要 |
| 訓練段階 | 3段階(SFT → RM → PPO) | 2段階(SFT → DPO) | 2段階(批判修正 → RLAIF) |
| 実装の難易度 | 高 | 低 | 中 |
| 訓練の安定性 | 普通。慎重なパラメータ調整が必要 | 好 | 中 |
| スケーリング能力 | 人間のラベリング速度に制約される | 選好データの規模に制約される | スケーリングが容易 |
| 代表的な企業・プロジェクト | OpenAI(初期) | 多くのチームが採用 | Anthropic |
「最良」の手法はなく、選択はあなたの具体的な状況に依存します:
大量の人間によるラベリングリソースがある場合は、RLHF または DPO のどちらでも構いません。
実装の簡単さを求めるなら、DPO は良い選択です。
大規模に拡張し、人間への依存を減らしたい場合は、Constitutional AI / RLAIF を検討する価値があります。
実際の運用では、これらの手法はしばしば組み合わせて使用されます:まず人間のデータをある程度使い、次に AI フィードバックで規模を拡大し、最後に DPO または PPO で最適化します。
RLHF の限界と課題
RLHF は非常に有効ですが、完璧ではありません。その限界と課題についていくつか議論しましょう。
報酬ハッキング(Reward Hacking)
報酬ハッキングは強化学習における古典的な問題です:AI が高い報酬を得る方法を見つけたが、その方法は私たちが本当に望んでいるものではない。
例:
あなたが AI に質問応答を訓練し、報酬モデルが「自信のある」回答を好むとします。AI は非常に断言的な口調で話すだけでスコアが高くなることを発見します——たとえでたらめを言っていてもです。
あるいは、AI はユーザーの言葉を繰り返して「理解しました」と示すだけで、そこそこのスコアを得られることを発見します——実質的な内容を何も提供していなくてもです。
報酬ハッキングの根本的な原因は:報酬モデルの判断と人間の真の選好の間にギャップが存在すること。AI が最適化するのは報酬モデルのスコアであり、人間の実際の満足度ではありません。
この問題に完璧な解決策はありませんが、いくつかの緩和策があります:
より多様な選好データを収集する。
複数の報酬モデルをアンサンブルして、単一モデルのバイアスを減らす。
定期的に人間が抜き打ちチェックを行い、問題を発見したら速やかに修正する。
人間の選好の不一致性
もう一つの課題は:人間の間で選好が一致しないことがあり、同じ人物の選好でさえ変化することがある。
例:
簡潔で直接的な回答を好むユーザーもいれば、詳細で網羅的な回答を好むユーザーもいます。
同じ質問でも、文脈が異なれば「良い」答えは異なる場合があります。
文化的背景や価値観が異なる人々の間では、「良い」の定義が衝突することがあります。
人間のアノテーターの意見が一致しないとき、報酬モデルは混乱します——誰の意見に従えばよいのか分からないからです。
対処方法には以下が含まれます:
アノテーターにより詳細なガイドラインを提供し、基準を統一する。
データにアノテーターの特徴を記録し、パーソナライズされたモデルを訓練する。
ある程度の曖昧さを受け入れる——一部の問題にはそもそも唯一の「正しい」答えがありません。
その他の課題
言及する価値のある課題がさらにいくつかあります:
-
スケーラビリティ——人間によるラベリングは速度が遅くコストが高いため、モデル規模の成長に追いつくのが難しい。
-
分布シフト——訓練データと実際の使用シーンは異なる可能性があり、モデルは新しいシーンで性能が低下する。
-
敵対的攻撃——アライメント済みのモデルが巧妙に設計されたプロンプトによって「脱獄」され、有害なコンテンツを生成する可能性がある。
-
価値の固定化——アライメント目標に問題がある場合、モデルの能力が高いほど害が大きくなる。
-
アライメントは継続的なプロセスであり、一度で済む解決策はありません。絶えず研究、反復、改善を続ける必要があります。