banner
Nagi-ovo

Nagi-ovo

Breezing
github
x

Actor Critic メソッドの初探

方差問題#

戦略勾配(Policy Gradient)法は、その直感的で効果的な特性から注目されています。以前に私たちはReinforceアルゴリズムについて探討しましたが、これは多くのタスクで良好なパフォーマンスを示しました。しかし、Reinforce 法はモンテカルロ(Monte Carlo)サンプリングに依存して報酬を推定するため、全体のエピソードのデータを使用して報酬を計算する必要があります。この方法には重要な問題があります ——戦略勾配推定における高い方差

Pasted image 20241010090847

PG 推定の核心は、報酬が最も増加する方向を見つけることです。言い換えれば、高い報酬をもたらす行動が将来的に選ばれる確率を高めるように、戦略の重みを更新する必要があります。理想的には、このような更新により戦略が徐々に最適化され、より高い総報酬を得ることができます。

しかし、モンテカルロ法を使用して報酬を推定する場合、全体のエピソードのデータを使用して実際の報酬を計算するため(報酬を推定しない)、戦略勾配の推定には顕著な方差が生じます(無偏だが高方差)。高方差は、私たちの勾配推定が不安定であり、訓練プロセスが遅くなるか、収束しない可能性があることを意味します。信頼できる勾配推定を得るためには、多くのサンプルが必要になる場合があり、これは実際のアプリケーションではしばしば高コストです。

Pasted image 20241010091609

環境と戦略のランダム性により、同じ初期状態から異なる報酬が得られる可能性があり、高方差を引き起こします。したがって、同じ状態からの報酬は異なるシナリオで大きく変動する可能性があります。多くの軌跡を使用することで方差を減少させ、より正確な報酬推定を提供できます。しかし、大きなバッチサイズはサンプル効率を低下させるため、方差を減少させる他のメカニズムを探す必要があります。

Advantage Actor-Critic (A2C)#

Actor-Critic 法による方差の削減#

前述の知識と章の導入から得られる直感的な感覚は、「Value-Based と Policy-Based を組み合わせることで、方差と訓練の問題が最適化される」ということです。アクター - クリティック(Actor-Critic)法はまさにそのような混合アーキテクチャであり、具体的には:

  • アクター(Actor):行動を選択し、現在の戦略に基づいて行動確率分布を生成します。
  • クリティック(Critic):現在の戦略における価値関数を推定し、行動選択に対するフィードバックを提供します。

あなたとあなたの友人が初心者プレイヤーだと想像してください。あなたは操作(Actor)を担当し、友人は観察と評価(Critic)を担当します。最初は、あなたたちはゲームをあまり理解していません。あなたは無計画に操作し、友人もあなたのパフォーマンスを評価する方法を模索しています。時間が経つにつれて、あなたは実践を通じて操作スキル(Policy)を改善し続け、同時に友人も各行動の良し悪しをより正確に評価する方法を学んでいます(Value)。

Pasted image 20241010092831

あなたたちは互いに助け合い、共に進歩します:あなたの操作は友人に評価の基盤を提供し、友人のフィードバックはあなたが戦略を調整するのを助けます。

つまり、私たちは二つの関数を近似することを学びます(神経ネットワーク):

  • エージェント(Actor)の操作を制御する戦略関数:πθ(s)\pi_\theta(s)
  • 行動の良し悪しを測定し、戦略最適化を補助する価値関数(Critic):q^w(s,a)\hat{q}_w(s,a)

アルゴリズムの流れ#

  • 各時間ステップ tt で、環境から現在の状態 StS_t​ を取得し、これをアクターとクリティックに入力として渡します。
  • アクターは状態に基づいて行動 AtA_t を出力します。

Screenshot 2024-10-10 at 10.06.53

  • クリティックもその行動を入力として受け取り、StS_t​ と AtA_t​ を使用して、その状態でその行動を取ったときの価値を計算します:すなわち Q 値です。
  • 環境で行動 AtA_t​ を実行し、新しい状態 St+1S_{t+1}​ と報酬 Rt+1R_{t+1}​ を出力します。
  • アクターは QQ 値を利用してその戦略パラメータを更新します。

Screenshot 2024-10-10 at 10.08.30

Screenshot 2024-10-10 at 10.19.12

  • アクターは更新されたパラメータを使用して、新しい状態 St+1S_{t+1}​ に基づいて次に取るべき行動 At+1A_{t+1}​ を生成します。
  • クリティックはその後、価値パラメータを更新します。

Screenshot 2024-10-10 at 10.37.56

クリティックは報酬推定を調整するためのベースラインを提供し、勾配推定をより安定させます。訓練プロセスはよりスムーズになり、収束速度が速くなり、必要なサンプル数も大幅に減少します。

Advantage(A2C)の追加#

アドバンテージ関数をクリティックとして使用することで、学習をさらに安定させることができます。

アドバンテージ:良い行動を際立たせる#

核心的なアイデアは、あなたの行動を二つの部分で評価することです:

  1. あなたが得た即時報酬と次の状態の価値
  2. 現在の状態における価値の期待

数学的には、これをアドバンテージと呼びます:

A(st,at)=rt+1+γV(st+1)V(st)A(s_t, a_t) = r_{t+1} + \gamma V(s_{t+1}) - V(s_t)

これは、状態 sts_t においてあなたが行った行動 ata_t が、状態 sts_t における本来の期待V(st)V(s_t) で表されるベースライン期待)よりもどれだけ良いかを表しています。

もし実際の報酬と将来の状態の価値 rt+1+γV(st+1)r_{t+1} + \gamma V(s_{t+1}) があなたの現在の状態 V(st)V(s_t) の期待を上回るなら、その行動は良い;もし下回るなら…… もっと良くできるかもしれません。

このアドバンテージは、行動が良いかどうかを教えるだけでなく、どれだけ良いかまたはどれだけ悪いか(ベースラインに対して)も教えてくれます。

戦略の更新#

ある行動を実行する際、報酬そのものは戦略の改善を導くには不十分です。報酬は特定の行動が良いか悪いかを教えてくれますが、その行動がどれほど良いのか、または期待よりもどれだけ良いのかを教えてくれません。
したがって、戦略を改善する際には、報酬を盲目的に追い求めるのではなく、行動を調整し、それらが期待を超えて(または下回って)いる程度に基づいて注目する方が良いのです。これにより、戦略を微調整し、ベースラインを持続的に上回る行動に近づけることができます。

これが私たちが得る更新公式です:

θJ(θ)t=0T1θlogπθ(atst)A(st,at)\nabla_\theta J(\theta) \sim \sum_{t=0}^{T-1} \nabla_\theta \log \pi_\theta(a_t | s_t) A(s_t, a_t)
  • θlogπθ(atst)\nabla_\theta \log \pi_\theta(a_t | s_t):各時間ステップ tt において、私たちは戦略 πθ\pi_\theta が行動 ata_t を選択する確率の対数勾配を計算します。このステップは、現在の戦略の下で、パラメータ θ\theta を変更することで ata_t を選択する可能性を高める方法を見つけるのに役立ちます。
  • A(st,at)A(s_t, a_t):行動のアドバンテージ関数であり、その行動 ata_t が状態 sts_t においてベースラインに対してどれだけ良いかまたは悪いかを教えてくれます。

簡単に言えば:あなたの戦略 πθ\pi_\theta の勾配はアドバンテージ A(st,at)A(s_t, a_t) によって調整されます。あなたは戦略を更新する際に、この行動がいくつかの報酬をもたらしたかどうかだけでなく、この行動が期待をどれだけ超えたかに基づいて行います。

さらに素晴らしいことに:価値関数 V(s)V(s) を予測するために神経ネットワークを一つだけ必要とします。

さて、TD 誤差について話しましょう#

もちろん、このアドバンテージ関数を計算するのは素晴らしいですが、オンライン学習には一つの利点があります:戦略を更新するために最後まで待つ必要はありません。そこで、** 時系列差分誤差(TD Error)** が登場します:

δ=r+γV(s)V(s)\delta = r + \gamma V(s') - V(s)

ここでの重要な点は:TD 誤差は実際にはアドバンテージ関数のオンライン推定です。これは、まさに今、あなたの行動が将来の状態をあなたの期待よりも良くしたかどうかを教えてくれます。この誤差 $\delta$ はアドバンテージの概念を直接反映しています:

  • もし δ>0\delta > 0:“おお、この行動は私が想像していたよりも良い!”(アドバンテージは正)。
  • もし δ<0\delta < 0:“うーん、私はもっと良いと思っていたのに……”(アドバンテージは負)。

これにより、あなたは戦略を段階的に調整でき、エピソード全体が終了するのを待つ必要がありません。これは効率を向上させるための素晴らしい戦略です。

コード実装#

Actor-Critic ネットワークアーキテクチャ#

まず、神経ネットワークを構築する必要があります。これは二頭のネットワークです:一つは アクター(行動を選択するための戦略を学習)、もう一つは クリティック(状態の価値を推定)。

class ActorCritic(nn.Module):
    def __init__(self, num_inputs, num_actions, hidden_size, learning_rate=3e-4):
        super(ActorCritic, self).__init__()

        # クリティックネットワーク(価値関数の近似)
        # このネットワークは V(s) を予測します、すなわち状態 s の価値
        self.critic_linear1 = nn.Linear(num_inputs, hidden_size)
        self.critic_linear2 = nn.Linear(hidden_size, 1)  # 価値関数はスカラー出力

        # アクターネットワーク(戦略関数の近似)
        # このネットワークは π(a|s) を予測します、すなわち状態 s で行動 a を選択する確率
        self.actor_linear1 = nn.Linear(num_inputs, hidden_size)
        self.actor_linear2 = nn.Linear(hidden_size, num_actions)  # すべての行動の確率分布を出力
    
    def forward(self, state):
        # 状態を torch テンソルに変換し、バッチ処理をサポートするために次元を追加
        state = Variable(torch.from_numpy(state).float().unsqueeze(0))
        
        # クリティックネットワークの前向き伝播
        value = F.relu(self.critic_linear1(state))
        value = self.critic_linear2(value)  # 状態の価値 V(s) を出力
        
        # アクターネットワークの前向き伝播
        policy_dist = F.relu(self.actor_linear1(state))
        policy_dist = F.softmax(self.actor_linear2(policy_dist), dim=1)  # softmaxを使用して元の数値を行動の確率分布(戦略)に変換
        
        return value, policy_dist
A2C アルゴリズムの核心実装#

次に、A2C の核心に入ります:主ループと更新メカニズム。各エピソードで、エージェントは環境内で一定のステップを実行し、状態、行動、報酬の軌跡を収集します。各エピソードの終了時に、アクター(戦略)とクリティック(価値関数)を更新します。

def a2c(env):
    # 環境から入力と出力の次元を取得
    num_inputs = env.observation_space.shape[0]
    num_outputs = env.action_space.n
    
    # アクター-クリティックネットワークを初期化
    actor_critic = ActorCritic(num_inputs, num_outputs, hidden_size)
    ac_optimizer = optim.Adam(actor_critic.parameters(), lr=learning_rate)

    # パフォーマンスを追跡するためのデータコンテナ
    all_lengths = []  # 各エピソードの長さを追跡
    average_lengths = []  # 最近10エピソードの平均長さを追跡
    all_rewards = []  # 各エピソードの累積報酬を追跡
    entropy_term = 0  # 探索を促すための項

    # 各エピソードのループに入る
    for episode in range(max_episodes):
        log_probs = []  # 行動の対数確率を保存
        values = []  # クリティックの価値推定(V(s))を保存
        rewards = []  # 得られた報酬を保存

        state = env.reset()  # 環境をリセットし、新しいエピソードを開始
        for steps in range(num_steps):
            # ネットワークを通じて前向き伝播
            value, policy_dist = actor_critic.forward(state)
            value = value.detach().numpy()[0,0]  # クリティックが現在の状態の価値を推定
            dist = policy_dist.detach().numpy()
            
            # 行動確率分布から行動をサンプリング
            action = np.random.choice(num_outputs, p=np.squeeze(dist))
            log_prob = torch.log(policy_dist.squeeze(0)[action])  # 選択した行動の対数確率を記録
            entropy = -np.sum(np.mean(dist) * np.log(dist))  # 探索の多様性を測るためにエントロピーを使用
            new_state, reward, done, _ = env.step(action)  # 行動を実行し、報酬と新しい状態を取得

            # このセクションのデータを記録
            rewards.append(reward)
            values.append(value)
            log_probs.append(log_prob)
            entropy_term += entropy
            state = new_state  # 新しい状態に更新
            
            # エピソードが終了した場合、記録してループを抜ける
            if done or steps == num_steps-1:
                Qval, _ = actor_critic.forward(new_state)  # 最後の状態の価値推定
                Qval = Qval.detach().numpy()[0,0]
                all_rewards.append(np.sum(rewards))  # このエピソードの総報酬を記録
                all_lengths.append(steps)
                average_lengths.append(np.mean(all_lengths[-10:]))
                if episode % 10 == 0:
                    sys.stdout.write("episode: {}, reward: {}, total length: {}, average length: {} \n".format(
                        episode, np.sum(rewards), steps, average_lengths[-1]))
                break

        # Q値(クリティックの目標値)を計算
        Qvals = np.zeros_like(values)  # Q値配列を初期化
        for t in reversed(range(len(rewards))):
            Qval = rewards[t] + GAMMA * Qval  # ベルマン方程式を使用してQ値を計算
            Qvals[t] = Qval

        Qvals = torch.FloatTensor(Qvals)
        log_probs = torch.stack(log_probs)
        
        # アドバンテージ関数を計算
        advantage = Qvals - values  # 行動のパフォーマンスがクリティックの期待をどれだけ超えたか?

        # 損失関数
        actor_loss = (-log_probs * advantage).mean()  # 戦略損失(より良い行動を奨励)
        critic_loss = 0.5 * advantage.pow(2).mean()  # 価値関数損失(予測誤差を最小化)
        ac_loss = actor_loss + critic_loss + 0.001 * entropy_term  # エントロピー項を含む総損失

        # 逆伝播と最適化
        ac_optimizer.zero_grad()
        ac_loss.backward()
        ac_optimizer.step()
  • アクターは戦略勾配に基づいて更新されます:行動の対数確率と行動のアドバンテージを掛け合わせ、行動が期待よりも良い場合、アドバンテージが正であればその行動を奨励します。
  • クリティックは平均二乗誤差に基づいて更新されます:クリティックはその予測 V(st)V(s_t) と実際の報酬 QtQ_t を比較し、この差を最小化します。
  • エントロピー項:探索行動を奨励するためにエントロピーを導入し、エージェントが特定の行動に早期に収束するのを防ぎます。

まとめ#

A2C の核心は、アドバンテージ関数を通じて戦略更新を導くことです。また、TD 誤差を利用してオンライン学習を行い、エージェントの各行動をより賢く効率的にします。

  • アドバンテージ関数:行動の良し悪しを教えるだけでなく、ベースラインに対してどれだけ良いかを教えてくれます。
  • TD 誤差:アドバンテージをリアルタイムで計算するためのツールであり、戦略を迅速かつ効率的に更新するのに役立ちます。

非同期 A2C(A3C)#

A3Cは、Deepmind の論文「Asynchronous Methods for Deep Reinforcement Learning」で提案されました。本質的に、A3C は並列化された A2C であり、複数の並列エージェント(ワーカースレッド)が並行環境で独立してグローバルな価値関数を更新します。したがって、「非同期」と呼ばれ、現代のマルチコア CPU 上で効率が高いです。

前述のように、A2C は A3C の同期バージョンであり、更新を実行する前に各参加者がその経験セクションを完了するのを待ち、すべての参加者の結果を平均化します。利点は、GPU をより効率的に利用できることです。また、OpenAI Baselines: ACKTR & A2Cでは次のように述べています:
 私たちの同期 A2C 実装のパフォーマンスは、非同期実装を上回っています —— 非同期によって導入されたノイズがパフォーマンスの利点を提供する証拠は見られませんでした。単一の GPU マシンを使用する場合、この A2C 実装は A3C よりもコスト効率が高く、より大きな戦略を使用する場合は、CPU のみの A3C 実装よりも速くなります。

参考資料#

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。