この記事は生成AIを用いて作成しています。
1. はじめに
前回の記事ではDQNで強化学習を実行しましたが、結果的には行動が偏り、うまく行動することができませんでした。この問題を解決するために、方策勾配法であるPPO(Proximal Policy Optimization)をポケモンバトルに組み込み強化学習を行いました。
PPOは方策を確率的に学習できるため、多様な行動を獲得しやすくなります。しかし、PPOの学習にあたって方策更新がうまく行われず、安定した学習させるためには工夫が必要でした。本記事では、PPOにおける学習手順に則りながら、クリッピングにフォーカスして学習の安定化に向けた理論的背景と対策について解説します。
2. 対象読者
- PPOの基本的な理論を理解している方
- 学習の不安定性に悩んでいる方
- 実装面での具体的な改善方法を探している方
3. PPOにおける学習ステップ
PPOの学習ステップは以下の通りです。Stepごとに簡単に解説を加えつつ、学習の安定化に向けたポイントを説明します。
Step 1: 行動の選択 (行動の確率的サンプリング)
Step 2: 環境との相互作用
Step 3: ロールアウト(サンプル)の収集
Step 4: アドバンテージ推定 (GAE など)
Step 5: 方策の更新 (クリッピング付き目的関数) <---今回のメイン
Step 6: 価値関数の更新
Step 1: 行動の選択 (行動の確率的サンプリング)
学習の初めはactor(方策ネットワーク $π(θ)$) が出力する確率分布から行動をサンプリングします。
DQNの場合は行動がQネットワークの最大値に基づいて決定されるため探索が不足しがちですが、PPOでは確率的に行動を選択するため、多様な行動を獲得しやすくなります。
方策ネットワークの評価指標:エントロピー
エントロピーは方策の不確実性を測る指標であり、探索の程度を示します。エントロピーが高いほど、方策は多様な行動を選択しやすくなります。式は以下の通りです。
H(\pi) = -\sum_a \pi(a) \log \pi(a)
学習の初期段階ではエントロピーが高く、学習が進むにつれて徐々に低下していくことが理想的です。
Step 2: 環境との相互作用
環境からの状態 $s(t)$ を観測し、Step 1で選択した行動を環境に与え、次の状態 $s(t+1)$ と報酬 $r(t)$ を得ます。
Step 3: ロールアウト(サンプル)の収集
このステップでは、一定のエピソード数またはステップ数にわたって環境との相互作用を繰り返し、状態 $s(t)$、行動 $a(t)$、報酬 $r(t)$、次の状態 $s(t+1)$ のデータを収集します。これらのデータは後ほど方策と価値関数の更新に使用されます。
Step 4: アドバンテージ推定 (GAE)
アドバンテージ関数 $A(s, a)$ は、特定の状態 $s$ で行動 $a$ を取った場合の価値を示します。アドバンテージは以下の式で定義されます。
A(s, a) = Q(s, a) - V(s)
しかし、$Q$関数の直接的な推定は計算コストが高いため、PPOではTD(Temporal Difference)誤差を用いて報酬と$V(s)$から算出します。これを一般化アドバンテージ推定 (GAE: Generalized Advantage Estimation) と呼びます。GAEは以下の式で表されます。
GAE_t = \sum_{l=0}^{\infty} (\gamma \lambda)^l \delta_{t+l}
ここで、$\delta_t = r_t + \gamma V(s_{t+1}) - V(s_t)$ はTD誤差を表し、$\gamma$ は割引率、$\lambda$ はバイアスと分散のトレードオフを調整するパラメータです。
そして$V(s_t)$は状態$s_t$の価値関数の推定値であり、PPOではCriticとして別途ニューラルネットワークで学習します(Step 6)。
Step 5:方策の更新 (クリッピング付き目的関数)
方策の更新では、Step 4で推定したアドバンテージを用いて方策(行動の確率分布)を更新します。PPOでは以下のクリッピング付き損失関数を用います。
L^{CLIP}(\theta) = \hat{E}_t[\min(
r_t(\theta)\hat{A}_t,
\text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon)\hat{A}_t
)]
この式で得られた損失関数を最小化することで、方策ネットワークのパラメータ θ を更新します。
ここで重要な要素は以下です:
- $r_t(\theta) = \frac{\pi_{\theta}(a_t|s_t)}{\pi_{\theta_{old}}(a_t|s_t)}$: 新旧方策の比率。比率が大きいほどその行動が新しい方策でより選ばれやすくなっていることを示す。
- $\hat{A}_t$: 推定アドバンテージ。$\hat{A}_t$ > 0の時良い行動が取れており、$\hat{A}_t$ < 0の時悪い行動を取ったと判断される。
- $\epsilon$: クリッピング範囲(典型的には0.2)
この中でも特に以下のクリッピング関数によって、方策の急激な変化を抑制し、学習の安定性を向上させています。
\text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon)\
ここで、クリッピング関数の挙動について図を用いて説明しようと思います。
以下の図は $\epsilon$=0.2の場合の目的関数の挙動を示しています。
$\hat{A}_t$ > 0(左図について):方策比率$r_t(\theta)$が $1+\epsilon$ =1.2を超えた場合、$\hat{A}_t$>0(良い行動)が方策比率$r_t(\theta)$ > 1で古い方策に比べて多くできており良い傾向と判断できます。しかし、大きく方策を変えないように$r_t(\theta)$ =1.2でクリッピングされ、$L^{\text{CLIP}}(\theta)=1.2\hat{A}_t$ で定数となり、勾配計算時に0となり($\theta$で微分するため)、方策の大幅更新(良すぎる行動を良く評価しすぎ)を防ぎます。
$\hat{A}_t$ < 0(右図について):方策比率$r_t(\theta)$が $1-\epsilon$=0.8を下回った場合、$\hat{A}_t$ < 0(悪い行動)が方策比率$r_t(\theta)$ < 1で古い方策に比べて減っており良い傾向と判断できます。しかし、大きく方策を変えないように$r_t(\theta)$ =0.8でクリッピングされ、$L^{\text{CLIP}}(\theta)=0.8\hat{A}_t$ でこちらも勾配計算時に0となり、方策の大幅更新(悪すぎる行動を悪く評価しすぎ)を防ぎます。
このメカニズムにより、一度の更新で方策が極端に変化することを防ぎ、学習の安定性を確保しています。
逆に、クリッピング範囲が狭すぎると方策の更新が制限されすぎて学習が進まなくなるため、適切なクリッピング範囲$\epsilon$の設定が重要です。
また、このクリッピングは報酬計算においても重要な役割を果たします。報酬が極端に高い場合、方策の更新が過度に影響を受ける可能性があるため、報酬のクリッピングも行うことが一般的です。例えば、報酬を[-5, 5]の範囲にクリップすることで、極端な報酬が学習に与える影響を抑制します。
rewards = np.clip(rewards, -self.reward_clip, self.reward_clip) # reward_clip=5.0
方策更新時の評価指標:Clip Fraction
Clip Fraction
は、クリッピングが適用されたサンプルの割合を示します。理想的な範囲は0.0から0.3であり、これを超えると方策の更新が過度に制限されている可能性があります。私の場合 $\epsilon=0.01$などにしていたので、Clip Fraction
がほとんど1.0となってしまっており、常にクリッピングが適用されていました。こうなると、方策の更新がほとんど行われず(勾配0)、学習が進まなくなります。
Clip Fraction
が高すぎる場合は、クリッピング範囲$\epsilon$を広げるか、学習率の調整、後述する方策の比率を調整するためのMasked-Softmax
を検討します。
方策更新時の評価指標:KL Divergence
Step 5の新旧方策の比率を評価するのがKL Divergence
になります。
KL Divergence
は新旧の方策分布間の差異を可視化できます。この値が大きすぎる場合は方策の変化が大きいため、学習が不安定になります。
D_{KL}(\pi_{old} || \pi_{new}) = \sum_a \pi_{old}(a) \log\frac{\pi_{old}(a)}{\pi_{new}(a)}
- 目標値: 0.05以下
KL Divergenceを小さくするための対策
- 学習率を下げる
-
kl target
を追加しペナルティを与える
KL Divergenceを一定の範囲内に抑えるため、以下のようなペナルティ項を目的関数に追加します。
def compute_kl_penalty(kl_div, kl_target=0.05, kl_weight=1.0):
"""KLダイバージェンスのペナルティを計算"""
return kl_weight * (kl_div - kl_target)**2
これにより、KL Divergenceが目標値を超えた場合にペナルティが発生し、方策の急激な変化を抑制します(サンプルコードのtarget_kl
は0.05に設定)。
3. Masked-Softmax
の導入
方策ネットワークは全ての行動に対して確率を出力しますが、ポケモンバトルでは状況によって選択できない行動が存在しており、それは例えば以下のようなものがあります。
- PPが0の技が選択できない
- 状態異常で行動できない
- すでに倒れているポケモンに交代できない
これらの無効な行動を確率分布から除外するため、Masked-Softmaxを導入します。
サンプルコードは以下になります。
def masked_softmax(logits, valid_actions_mask, temperature=1.0):
"""
Args:
logits: ニューラルネットワークの出力 [batch_size, action_dim]
valid_actions_mask: 有効な行動のマスク [batch_size, action_dim]
temperature: 確率分布の温度パラメータ
"""
# 無効な行動に大きな負の値を加えることでsoftmax後に0に近づける
masked_logits = logits - (1 - valid_actions_mask) * 1e8
# 数値安定性のため、最大値を引く
max_logits = tf.reduce_max(masked_logits, axis=-1, keepdims=True)
exp_logits = tf.exp((masked_logits - max_logits) / temperature)
# マスクを適用して正規化
exp_logits = exp_logits * valid_actions_mask
probs = exp_logits / (tf.reduce_sum(exp_logits, axis=-1, keepdims=True) + 1e-10)
return probs
メリットは以下になります。
- 方策比率の安定化
r_t(\theta) = \frac{\pi_{\theta}(a_t|s_t)}{\pi_{\theta_{old}}(a_t|s_t)}
- 新旧両方の方策で同じマスクを使用することで、分母と分子の有効な行動集合が一致
- 結果として方策比率がより安定に計算可能
- 学習の効率化
- 無効な行動の確率が0になることで、有効な行動のみに方策が集中
- 探索空間の削減により、学習が効率化
Step 6:価値関数の更新
最後に価値関数(Critic)を更新します。価値関数は状態$s$に対する期待収益を予測します(MSE でターゲットとの差を最小化)。
ここで$V^{target}_t$ は一般化アドバンテージ推定(GAE)で使用した収益(returns)を使用します:
V^{target}_t = \hat{A}_t + V(s_t)
このように、価値関数のターゲットネットワークの算出にアドバンテージ推定を用いており、
Step4のようにアドバンテージ推定の算出にも価値関数が使われているため、価値関数の精度が学習全体の安定性に大きく影響します。
価値関数の評価指標 Explained Variance
価値関数の予測精度は以下で評価します。
\text{Explained Variance} = 1 - \frac{\text{Var}(V^{target} - V(s_t))}{\text{Var}(V^{target})}
- 1.0に近いほど良い
- 0.5以下の場合は価値関数の学習に問題
上記手法でPPOを学習させた結果
PPOを用いたポケモンバトルエージェントの学習に上記のような学習を安定させるための対策を行った結果、以下のような成果が得られました。
-
多様的な行動
学習したエージェントは、ポケモンバトルにおいて多様な行動を選択できるようになりました。特に、状況に応じた戦略的な行動選択が可能となり、従来の手法では難しかった複雑な戦術(状態アップ技から攻撃技、状態異常技から攻撃技など)を実行できるようになりました。 -
安定した学習
クリッピング手法により、方策の急激な変化が抑制され、学習が安定しました。特に、Clip Fractionが適切な範囲に収まるように調整することで、方策の更新がスムーズに行われました。
以下が参考程度にPPOを用いてポケモンバトルAIの学習を実行した結果です。KL Divergence
が0.05以下、Clip Fraction
が0.3以下に収まっていることが確認できます。
勝率についても74%と高い値を達成しました(相手にランダム、Max damage、DQN、PPOを含む)
[AI_trainer1] PPO最終学習 - 損失: 1.2080 (Actor: 0.2189, Critic: 0.3201), FinalReward: -1.0000, KL: 0.0513, ClipFrac: 0.0469, Entropy: 1.6956, ステップ: 49 Battle 100/100 | Win Rate: 0.74 | Total Wins: 74/100
-
実戦での性能向上
- 学習したエージェントは、ポケモンバトルにおいて以前の手法に比べて明らかに優れたパフォーマンスを発揮しました。特に、戦略的な行動選択が可能となり、勝率が向上しました。ランダム行動、Max damage行動に対してともに80%程度の勝率を確認しました。
まとめ
今回はPPOの学習ステップに沿って、特にクリッピングに焦点を当てて学習の安定化に向けた理論的背景と具体的な対策を解説しました。PPOは多様な選択が必要なポケモンバトルにおいて有効な手法であった半面、DQN
とは異なるパラメータ調整、評価指標が必要であり、実装に苦労しました。本記事によって、PPOの学習安定化に向けた理解が深まり、実装面での改善に役立てば幸いです。