策略梯度(Policy Gradient)

Photo by Jannes Jacobs on Unsplash
Photo by Jannes Jacobs on Unsplash
在 RL 的 control 問題中,多數方法皆以 value function 作為學習核心,透過估計長期 return 來間接改善 policy。然而,當 state 或 action 空間變得連續、或 policy 本身必須保持隨機性時,這種方式便顯得不再直接。策略梯度(Policy Gradient)方法改採另一種觀點,將 policy 本身視為可最佳化的對象,直接對 expected return 進行梯度上升(gradient ascent)。

在 RL 的 control 問題中,多數方法皆以 value function 作為學習核心,透過估計長期 return 來間接改善 policy。然而,當 state 或 action 空間變得連續、或 policy 本身必須保持隨機性時,這種方式便顯得不再直接。策略梯度(Policy Gradient)方法改採另一種觀點,將 policy 本身視為可最佳化的對象,直接對 expected return 進行梯度上升(gradient ascent)。

完整程式碼可以在 下載。

策略梯度(Policy Gradient)

無論是動態規劃(Dynamic Programming, DP)、蒙地卡羅(Monte Carlo, MC),或時序差分(Temporal Difference, TD),在 control 問題中,我們所學習的對象始終是 state-value function v_\pi(s) 或 action-value function q_\pi(s, a),並透過這些 value function 來推導與改善 policy。

策略梯度(Policy Gradient)方法則採取一種不同的觀點。當 policy 本身被表示為一個可微分的機率分佈 \pi_\theta(a \mid s) 時,control 問題可以直接被視為一個策略參數最佳化問題。此時,與其先估計 value function,再透過 greedy 或近似 greedy 的方式間接地改善 policy,不如直接對 policy 的期望回報(expected return)進行梯度上升(gradient ascent)。

透過參數化策略(parameterized policy),agent 得以將 policy 本身作為主要的學習對象,而不再必須依賴 value function 作為 policy improvement 的中介。這種做法使學習目標與最終行為之間的關係更加直接,也為處理連續動作空間與隨機策略提供了自然的建模方式。

Policy gradient 方法並非否定 value-based 方法的價值,而是提供了一條不同的學習路徑。Value-based 方法透過估計長期回報(long-term return)來間接決定行為,而 policy gradient 則嘗試直接調整行為分佈本身,使高 return 的動作在 policy 中出現得更頻繁。這樣的差異,將在後續章節中體現在目標函數的定義方式、梯度估計的來源,以及學習穩定性與變異性之間的取捨。

參數化策略(Parameterized Policy)

我們以 \theta \in \mathbb{R}^d 表示 policy 的參數向量。當 agent 在 time step t 處於 state s,且目前的策略參數為 \theta 時,\pi(a \mid s, \theta) 表示在該狀態下採取 action a 的機率。形式化地,可寫為:

\pi(a \mid s, \theta) = \text{Pr} \{ A_t = a \mid S_t = s, \theta_t = \theta \}

在引入 parameterized policy \theta \in \mathbb{R}^d 之後,control 問題可被轉化為一個純粹的參數最佳化問題。我們不再直接尋找最優 policy,而是定義一個以策略參數為自變數的純量性能指標 J(\theta),並透過調整 \theta 來最大化該指標。

在此觀點下,policy 的學習可視為對 J(\theta) 進行 gradient ascent,其參數更新形式可寫為:

\theta_{t+1} = \theta_t + \widehat{\alpha \nabla J(\theta_t)}

其中,\widehat{\nabla J(\theta_t)} 表示對真實 gradient 的估計,而 \alpha 則為 step size,用以控制每次參數更新的幅度。

目標函數(The Objective)

在 policy gradient 方法中,control 問題被重新表述為一個對策略參數進行最佳化的問題。因此,在推導任何參數更新規則之前,首先必須釐清一個問題,我們究竟希望最大化的是什麼量?

在 RL 中,無論採用的是 value-based 或 policy-based 方法,最終目標始終一致,即最大化 agent 在環境中所能獲得的長期 return。不同方法之間的差異,並不在於學習目標本身,而在於 return 被形式化與最佳化的方式。

由於 policy gradient 方法直接對策略參數進行調整,學習目標必須被表達為一個可對 \theta 微分的純量函數。這個函數通常記為 J(\theta),用以衡量在策略 \pi_\theta 下,agent 的整體表現。根據任務是否為 episodic 或 continuing,以及是否引入折扣因子,J(\theta) 的具體定義可能有所不同,但其意涵始終是,在當前policy 下,期望能累積多少 return。

在明確定義 J(\theta) 之後,我們才能進一步討論其 gradient 形式,並推導出實際可用的策略更新規則。

分節任務(Episodic Tasks)

在分節任務(episodic tasks)中,agent 與 environment 的每一次互動都會在有限的 time steps 內結束。對於此類設定,我們可以將 return 定義為自 time step t 起,直到 episode 終止為止所累積的折扣回報(discounted return):

\displaystyle G_t \doteq \sum_{k=0}^{T-t-1} \gamma^k R_{t+k+1}

其中,T 表示該 episode 的終止時間。

對於參數化策略 \pi_\theta,policy gradient 方法的目標函數(objective)可定義為起始狀態的 state-value:

J(\theta) \doteq v_{\pi_\theta}(s_0) = \mathbb{E}_{\pi_\theta}[G_0]

亦即,在 policy \pi_\theta 下,agent 自起始 state s_0 出發,所能獲得的期望累積回報(expected accumulated return)。

此一定義在概念上與 value-based 方法中的最大化起始狀態價值 v_\pi(s_0)是等價的。差別僅在於,policy gradient 方法將最佳化的對象從 value function 本身,轉換為 policy 的參數 \theta

連續任務(Continuing Tasks)與平均獎勵(Average Reward)

在連續任務(continuing tasks)中,agent 與 environment 的互動並不會自然終止,因此不存在明確的 episode 邊界。在此設定下,除了使用 discounted return 作為性能指標之外,另一種常見且更貼近長期行為表現的衡量方式,是平均獎勵(average reward)。

對於給定的 policy \pi,其 average reward可定義為在無限時間範圍內,每個 time step 所能獲得的 expected return:

\displaystyle r(\pi) \doteq \lim_{T \to \infty} \frac{1}{T} \mathbb{E} \Big[ \sum_{t=1}^{T} R_t \mid S_0, A_{0:t-1} \sim \pi \Big]

此定義刻畫的是 agent 在長時間運作下的穩定行為表現,而非單一 episode 的累積結果。

若進一步假設在 policy \pi 下,系統存在一個穩態分佈(stationary distribution)\mu_\pi(s),則 average reward 可等價地寫為 state 與 action 的期望形式:

\displaystyle r(\pi) = \sum_s \mu_\pi(s) \sum_a \pi(a \mid s, \theta) \sum_{s^\prime, r} p(s^\prime, r \mid s, a) r

在 continuing tasks 中,policy gradient 方法的目標函數即被定義為此長期 average reward,亦即:

J(\theta) \doteq r(\pi_\theta)

策略學習的目的,便是透過調整參數 \theta,使得在穩態行為下的 average reward 達到最大。

相較於 discounted return,average reward 不依賴折扣因子 \gamma,因此更直接地反映policy 在長期運作下的穩定表現。這使其特別適合用於持續運行、且不存在自然終止條件的任務。然而,average reward 的分析通常仰賴穩態分佈的存在,這也使得其理論推導與實作相對複雜。後續在推導 policy gradient 時,這兩種目標函數形式將分別導出不同的梯度表達,但其主要思想仍然一致,調整 policy ,使高 return 的行為在長期中出現得更頻繁。

策略梯度定理(Policy Gradient Theorem)

Policy gradient 方法在 episodic 與 continuing tasks 兩種設定下,具有高度一致的數學結構,其差異僅體現在是否存在一個與時間尺度相關的比例常數,而不影響 gradient 的方向。

對於 episodic tasks,policy gradient 可寫為下列形式:

\displaystyle \nabla J(\theta) \propto \sum_s \mu(s) \sum_a \nabla \pi(a \mid s) q_\pi(s, a)

其中,\mu(s) 表示在目前 policy \pi 下,state s 在一個 episode 中被造訪的期望次數。換言之,\mu(s) 反映了該 state 對整體 return 的相對重要性。比例符號 \propto 則表示,在 episodic 設定中,policy gradient 的方向由上式所決定,而常數尺度並不影響 gradient ascent 的更新方向。

對於 continuing tasks,policy gradient theorem 則可以寫成一個精確的等式:

\displaystyle \nabla J(\theta) = \sum_s \mu(s) \sum_a \nabla \pi(a \mid s) q_\pi(s, a)

此時,\mu(s) 表示在 policy \pi 下的 state 造訪權重。在 discounted 設定中,它對應於折扣後的 state visitation measure。在 average-reward 設定 中,則對應於系統的穩態分佈。與 episodic 情況相同,策略更新的機制仍是在每個 state 中,依照其重要性,調整 action 的選擇機率。

從操作層面來理解,上述公式表示,policy gradient 是由所有 state 的貢獻所加總而成,而每個 state 的權重正是其在目前 policy 下出現的頻率 \mu(s)。在特定 state s 中,對 action a 的策略機率變動 \nabla \pi(a \mid s),會依照該 action 的價值 q_\pi(s, a) 進行加權。價值越高的動作,其對梯度的貢獻也越大。

不論是在 episodic 或 continuing tasks 中,policy gradient theorem 皆揭示了以下幾個事實:

  1. Policy improvement 是逐狀態進行的。
    • Policy 的整體改變,可分解為在各個 state 中,對 action 機率所做的局部調整。
  2. 改善方向只依賴動作價值 q_\pi(s, a)
    • 高價值 action 的機率被提升,低價值 action 的機率被降低。
  3. State 的重要性由 \mu(s) 自然加權。
    • 越常被造訪、或對長期表現影響越大的 state,對整體 gradient 的貢獻也越大。
  4. 不需要對 environment dynamics 求導。
    • Policy gradient 的計算不涉及 state transition probabilities 或環境模型的 gradient。

Policy gradient theorem 為 \nabla J(\theta) 提供了一個解析形式。將此結果代回參數化策略的更新規則後,即可得到策略參數的學習方式:

\theta_{t+1} = \theta_t + \alpha \nabla J(\theta_t)

Policy gradient theorem 的意義在於,它將對整體 expected return 的 gredient 轉換為一個僅依賴policy 本身與動作價值的形式。雖然式中仍包含未知的 q_\pi(s, a)\mu(s),但這個結構已經明確指出,只要能從取樣資料中近似這些量,就能在不建模 environment dynamics 的情況下進行策略學習。後續章節將進一步說明,如何透過對數導數技巧,將上述表達式轉換為可由 trajectory 直接估計的更新規則。

估計策略梯度(Estimating the Policy Gradient)

雖然 policy gradient theorem 為我們提供了 policy gradient 的解析形式,但在實際問題中,該公式仍然無法直接計算,原因在於其中包含多個未知或不可得的量:

  • \mu(s):在目前 policy 下,各個 state 的造訪機率(或造訪權重)通常未知。
  • q_\pi(s, a):每個 state-action pair 的真實 expected return 亦無法事先取得。
  • 式子本身需要對所有 state-action pair 進行加總,在大型或連續狀態空間中更是不切實際。

為了克服上述困難,policy gradient 方法利用實際互動所產生的 trajectory samples,同時近似 \mu(s)q_\pi(s, a),並據此構造出對 \nabla J(\theta) 的無偏差(unbiased)或低偏差(low bias)估計量。

我們首先從 policy gradient theorem 出發,將其中的 gradient 項重新整理。對於任一 state s 與 action a,有:

\begin{aligned} \nabla J(\theta) &= \sum_s \mu(s) \sum_a \nabla \pi(a \mid s, \theta) q_\pi(s, a) \\ &= \sum_s \mu(s) \sum_a \pi(a \mid s, \theta) \frac{1}{\pi(a \mid s, \theta)} \nabla \pi(a \mid s, \theta) q_\pi(s, a) \\ &= \sum_s \mu(s) \sum_a \frac{\nabla \pi(a \mid s, \theta)}{\pi(a \mid s, \theta)} q_\pi(s, a) \\ &= \sum_s \mu(s) \sum_a \nabla \ln \pi(a \mid s, \theta) q_\pi(s, a) \end{aligned}

這個步驟利用了對數導數恆等式(log-derivative identity):

\displaystyle \nabla \ln \pi(a \mid s, \theta) = \frac{\nabla \pi(a \mid s, \theta)}{\pi(a \mid s, \theta)}

其意義在於,policy 的 gradient 可以被轉換為對策略機率對數的 gradient,而這個量只依賴於 policy 本身。

將上述雙重加總視為在分佈 S \sim \mu, A \sim \pi 下的期望,可得:

\nabla J(\theta) = \mathbb{E}_{S \sim \mu, A \sim \pi} \Big[ \nabla \ln \pi(A \mid S) q_\pi(S, A) \Big]

此表達式揭示,我們並不需要顯式地計算 \mu(s) 或枚舉所有 state。只要依照目前的 policy 與 environment 互動,state 便會自然地依 \mu 的分佈出現,action 也會依 \pi 的機率被選擇。換言之,取樣過程本身已隱含地完成了對 \sum_s \mu(s) \sum_a \pi(a \mid s) 的計算。

基於上述期望形式,策略參數 \theta 的更新即可改寫為以下取樣近似的形式:

\theta_{t+1} \leftarrow \theta_t + \alpha \widehat{\nabla J(\theta_t)} = \theta_t + \alpha \nabla \ln \pi(A_t \mid S_t,\theta) \hat{q}(S_t,A_t)

其中,\hat{q}(S_t, A_t) 為對真實 action-value q_\pi(S_t, A_t) 的估計,實務上可由 MC return、TD 估計,或其他近似方式取得。

上述推導顯示,policy gradient 的估計誤差主要來自於兩個來源,一是有限取樣所造成的隨機變異,二是對 q_\pi(s,a) 的近似誤差。後續方法(例如 REINFORCE 與 Actor-Critic)正是透過不同方式來估計或近似這個 return 項,並在偏差與變異之間取得權衡。

REINFORCE:Monte Carlo Policy Gradient

REINFORCE 指的是在 policy gradient 架構中,使用 MC return 來直接估計 action-value function q_\pi(s, a) 的方法。其想法是,以完整軌跡(trajectory)中實際觀察到的 return,作為 policy gradient 中 return 項的近似。

考慮一個在 time step t 發生的 state–action pair (S_t, A_t)。沿著 policy \pi 繼續執行直到 episode 結束,MC 方法透過累積後續所有 rewards 來定義 return:

\displaystyle G_t = \sum_{k=0}^{T-t-1} \gamma^k R_{t+k+1}

在 episodic 設定下,對於給定的 (s, a),action-value function 可寫為:

q_\pi(s, a) = \mathbb{E}_\pi [ G_t \mid S_t=s, A_t=a ]

因此,由實際互動所得到的 return G_t,可視為對 q_\pi(s, a) 的一個無偏差(unbiased)樣本。

在實作層面上,MC 方法透過實際產生的 trajectory,同時完成兩件事情。其一,利用跑出來的 (S_t, A_t) 近似在 policy \pi 下的 state–action 分佈。其二,以實際觀察到的 G_t 作為對 q_\pi(s, a) 的估計。這使得 policy gradient 中的期望形式,能夠直接以樣本平均來近似。

綜合前一節對 policy gradient 估計的推導,REINFORCE 的 policy gradient ascent 更新式可寫為:

\theta_{t+1} \leftarrow \theta_t + \alpha G_t \nabla_\theta \ln \pi_\theta(A_t \mid S_t)

REINFORCE 因此成為一種直接以 MC return 作為 q_\pi(s, a) 無偏差估計量的 policy gradient 方法。由於它完全避免 bootstrapping,理論結構相當乾淨且直觀。然而,正因為使用整段 trajectory 的 return 作為學習訊號,其 policy gradient 估計通常具有極高的變異,這在實務上會造成學習不穩定或收斂緩慢。

以下將給出 REINFORCE 的完整演算法流程。

REINFORCE: Monte-Carlo Policy-Gradient Control (episodic) (source: Reinforcement Learning: An Introduction, 2nd).
REINFORCE: Monte-Carlo Policy-Gradient Control (episodic) (source: Reinforcement Learning: An Introduction, 2nd).

Actor-Critic

REINFORCE 的主要限制在於其 gradient 估計的變異過大,且必須等待整個 episode 結束後才能進行更新,因此無法進行真正的 online 學習。Actor–Critic 方法則引入一個可即時學習的 TD 方法作為 Critic,用以估計狀態價值,並透過每一個 time step 所產生的 TD 訊號來更新策略(Actor),而不必等待 episode 終止。

這樣的設計使 Actor–Critic 以 bootstrapping 換取較低的變異與 online 更新能力,但也因此引入了相對於 REINFORCE 的偏差。這正是 Actor–Critic 方法在偏差與變異之間所做出的取捨。

Actor–Critic 方法由兩個同時學習的元件所組成:

  • Actor 表示並更新策略 \pi(a \mid s, \theta),其學習目標是最大化 policy 的性能指標 J(\theta)
  • Critic 則負責學習在當前 policy \pi 下的 state-value function v_\pi(s, \mathbf{w}),並提供一個對當前行為品質的即時評價訊號,作為 Actor 更新策略的依據。

Critic 通常透過 TD learning 來進行學習。在最簡單的一步(one-step)設定中,Critic 使用 TD(0) 來更新其 value function 估計:

\delta_t = R_{t+1} + \gamma v_\mathbf{w}(S_{t+1}) - v_{\mathbf{w}}(S_t) \\\\ \mathbf{w} \leftarrow \mathbf{w} + \beta \delta_t \nabla_{\mathbf{w}} v_{\mathbf{w}}(S_t)

其中,\delta_t 為 TD error,反映了當前價值估計與一步 bootstrapped target 之間的差異。

Actor 則利用 Critic 所提供的 TD error,來近似 policy gradient 中的 return 項,並即時更新策略參數:

\theta \leftarrow \theta + \alpha \delta_t \nabla_\theta \ln \pi_\theta(A_t \mid S_t)

從 policy gradient 的角度來看,TD error 在此扮演的是一個即時 return 訊號的角色。當實際 return 與價值預期相比好於預期時(\delta_t > 0),Actor 會提高當前 action 的選擇機率;反之,則降低其機率。

在 policy gradient theorem 的統一框架下,Actor–Critic 與 REINFORCE 的差異可被清楚地理解為對 q_\pi(s, a) 的估計方式不同:

  • REINFORCE 使用完整的 MC return 作為 q_\pi(s, a) 的無偏差估計,但具有高變異,且無法 online 更新。
  • Actor–Critic 使用以 TD learning 訓練的 value function,並透過 TD error 進行 bootstrapping,以引入偏差換取較低變異與即時更新能力。

正因如此,Actor–Critic 方法使 policy gradient 能夠實際應用於長期或連續任務中,而不再受限於 episodic 設定。

以下將給出 One-Step Actor–Critic 的完整演算法流程。

One-step Actor–Critic (episodic) (source: Reinforcement Learning: An Introduction, 2nd).
One-step Actor–Critic (episodic) (source: Reinforcement Learning: An Introduction, 2nd).

範例

Actor-Critic 實作

在本實作中,Actor 與 Critic 皆以多層感知器(Multi-Layer Perceptron, MLP)作為 function approximator。Actor 網路輸出各個動作的 logits,並透過 Categorical 分佈進行取樣,以形成一個隨機策略 \pi_\theta(a \mid s)。Critic 則負責估計狀態價值 v(s),作為 TD 學習的基準。

在每一個 time step 的互動中,訓練流程依序如下進行。首先,由 Critic 根據當前狀態與下一狀態計算 TD target,並進而得到 TD error。Critic 以平方 TD error 作為 loss,更新其 value function 參數,使 v(s) 能逐步逼近在目前 policy 下的真實 state-value。

Actor 則使用同一個 TD error 作為策略更新的學習訊號,並將其與對應動作的對數機率 \log \pi_\theta(a \mid s) 相乘,形成 policy gradient 的估計。實作上,TD error 在用於 Actor 更新時會進行 detach(),以避免梯度經由 Actor 的 loss 回傳至 Critic,確保 Critic 的更新僅依賴其自身的 TD learning。這樣的處理方式,實質上對應於 policy gradient theorem 中的 semi-gradient 設定。

為了提升整體訓練的穩定性,Actor 與 Critic 採用不同的 learning rate,以反映兩者在學習動態上的差異;同時,對兩個網路的 gradient 皆進行裁剪(gradient clipping),以避免因梯度爆炸而導致訓練不穩定。

import numpy as np
import torch
import torch.nn as nn
from torch import optim


class Actor(nn.Module):
    def __init__(self, observation_dim: int, n_actions: int, hidden_dim: int = 256):
        super().__init__()
        self.network = nn.Sequential(
            nn.Linear(observation_dim, hidden_dim),
            nn.Tanh(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.Tanh(),
            nn.Linear(hidden_dim, n_actions),
        )

    def forward(self, obs: torch.Tensor):
        logits = self.network(obs)
        return torch.distributions.Categorical(logits=logits)


class Critic(nn.Module):
    def __init__(self, observation_dim: int, hidden_dim: int = 256):
        super().__init__()
        self.critic = nn.Sequential(
            nn.Linear(observation_dim, hidden_dim),
            nn.Tanh(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.Tanh(),
            nn.Linear(hidden_dim, 1),
        )

    def forward(self, obs: torch.Tensor):
        return self.critic(obs).squeeze(-1)


class ActorCritic(nn.Module):
    def __init__(
        self,
        observation_dim: int,
        n_actions: int,
        *,
        hidden_dim: int,
        actor_lr: float = 1e-4,
        critic_lr: float = 1e-3,
        grad_clip=2.0,
        gamma: float = 0.99,
    ):
        super().__init__()
        self.actor = Actor(observation_dim, n_actions, hidden_dim)
        self.critic = Critic(observation_dim, hidden_dim)
        self.grad_clip = grad_clip
        self.gamma = gamma
        self.actor_optimizer = optim.Adam(self.actor.parameters(), lr=actor_lr)
        self.critic_optimizer = optim.Adam(self.critic.parameters(), lr=critic_lr)

    def act(self, s: np.ndarray):
        s_t = torch.tensor(s, dtype=torch.float32).unsqueeze(0)
        pi_s = self.actor(s_t)
        a = pi_s.sample()
        return int(a.item()), pi_s

    def update(
        self, s: np.ndarray, a: int, r: float, s_prime: np.ndarray, pi_s: torch.distributions.Categorical, terminated: bool
    ) -> tuple[float, float]:
        s_t = torch.tensor(s, dtype=torch.float32).unsqueeze(0)
        a_t = torch.tensor([a], dtype=torch.long).unsqueeze(0)
        s_tp1 = torch.tensor(s_prime, dtype=torch.float32).unsqueeze(0)
        v_t = self.critic(s_t)

        with torch.no_grad():
            if terminated:
                v_prime = torch.tensor(0.0)
            else:
                v_prime = self.critic(s_tp1)
            td_target = r + self.gamma * v_prime

        td_error = td_target - v_t

        critic_loss = 0.5 * td_error.pow(2)
        self.critic_optimizer.zero_grad()
        critic_loss.backward()
        nn.utils.clip_grad_norm_(self.critic.parameters(), self.grad_clip)
        self.critic_optimizer.step()

        actor_loss = -(td_error.detach() * pi_s.log_prob(a_t))
        self.actor_optimizer.zero_grad()
        actor_loss.backward()
        nn.utils.clip_grad_norm_(self.actor.parameters(), self.grad_clip)
        self.actor_optimizer.step()

        return float(actor_loss.item()), float(critic_loss.item())

Lunar Lander

Lunar Lander 是一個經典的火箭軌跡最佳化問題。根據龐特里亞金最大值原理(Pontryagin’s maximum principle),在 optimal policy 下,引擎不是以全推力運作,就是完全關閉。因此,這個環境採用了離散動作空間,對應於引擎的開啟或關閉。

Action space 中共有四個 actions:

  • 0:不執行任何動作。
  • 1:啟動左側引擎。
  • 2:啟動主引擎。
  • 3:啟動右側引擎。

每一個 state 是一個 8 維向量,其內容依序包含:

  • Lander 的 x 和 y 座標位置。
  • Lander 在 x 和 y 的線性速度。
  • Lander 的角度。
  • Lander 的角速度。
  • 兩個 boolean 值,分別表示 Lander 的左右腳是否與地面接觸。

對於每一個 time step,reward 為:

  • Lander 越接近地面,reward 越高;反之,reward 越低。
  • Lander 的移動速度越慢,reward 越高;反之,reward 越低。
  • Lander 的傾斜角度越大,reward 越低。
  • 每當有一隻腳與地面接觸時,reward 增加 10 分。
  • 每一個 frame 中,只要側向的引擎啟動一次,reward 減少 0.03 分。
  • 每一個 frame 中,只要主引擎啟動一次,reward 減少 0.3 分。

若 episode 以墜毀或安全著陸結束,則會額外給予一次性回饋:

  • 墜毀:-100 分。
  • 安全著陸:+100 分。

當一個 episode 的 total rewards 達到 200 分以上時,即被視為成功解(solution)。

以下的程式碼中,我們訓練 Actor-Critic 1000 episodes。

import time

import gymnasium as gym

from actor_critic import ActorCritic

GYM_ID = "LunarLander-v3"
N_EPISODES = 1000
MAX_STEPS = 1000


def play_game(agent: ActorCritic, episodes=1):
    visual_env = gym.make(GYM_ID, render_mode="human")  # UI window

    for episode in range(episodes):
        state, _ = visual_env.reset()
        terminated = False
        truncated = False
        total_reward = 0.0
        step_count = 0

        input("Press Enter to play game")

        print(f"Episode {episode + 1} starts")
        while not terminated and not truncated:
            action, _ = agent.act(state)
            state, reward, terminated, truncated, _ = visual_env.step(action)
            total_reward += reward
            step_count += 1
            visual_env.render()

        print(f"Episode {episode + 1} is finished: Total reward is {total_reward}, steps = {step_count}")
        time.sleep(1)

    visual_env.close()


def train() -> ActorCritic:
    env = gym.make(GYM_ID)
    agent = ActorCritic(
        observation_dim=env.observation_space.shape[0],
        n_actions=env.action_space.n,
        hidden_dim=256,
        actor_lr=3e-4,
    )

    returns = 0.0

    for i_episode in range(1, N_EPISODES + 1):
        print(f"\rEpisode: {i_episode}/{N_EPISODES}", end="", flush=True)

        s, _ = env.reset()
        done = False
        G = 0.0
        steps = 0

        while not done and steps < MAX_STEPS:
            a, pi_s = agent.act(s)
            s_prime, r, terminated, truncated, _ = env.step(a)
            G += r
            done = terminated or truncated
            agent.update(s, a, r, s_prime, pi_s, done)
            s = s_prime
            steps += 1

        returns += G
        if i_episode % 50 == 0:
            print(f"\nEpisode: {i_episode}/{N_EPISODES}, average return: {returns / 50:.2f}")
            returns = 0.0

    return agent


if __name__ == "__main__":
    agent = train()
    play_game(agent)

在訓練約 1000 個 episodes 後,Actor–Critic agent 已能穩定完成著陸任務。實際執行時可觀察到,飛行器多半維持在著陸平台正上方,橫向位移明顯受控,並以平緩且連續的速度逐步下降,最終成功著陸。

此一行為表現顯示,透過 TD error 作為即時學習訊號,Actor–Critic 能夠學得平順且穩定的控制策略。相較於依賴整段 return、容易產生大幅修正的學習方式,Actor–Critic 的策略更新更具漸進性,傾向於在維持姿態與位置穩定的前提下進行調整。這也反映出 on-policy Actor–Critic 方法在連續控制問題中,對學習穩定性與安全行為的自然偏好。

Lunar Lander by Actor-Critic with 1000 episodes.
Lunar Lander by Actor-Critic with 1000 episodes.

結語

透過 REINFORCE 與 Actor–Critic 的對照,可以清楚看出 policy gradient 方法在偏差與變異之間的取捨,以及 bootstrapping 與 online 更新能力對控制穩定性的影響。實驗結果顯示,Actor–Critic 透過 TD error 作為即時學習訊號,能夠學得連續且平順的控制行為,使 policy gradient 方法得以實際應用於長期與連續控制任務。整體而言,Policy Gradient 不僅提供了一條不同於 value-based 方法的學習路徑,也為後續更進階的 control 與 deep RL 方法奠定了清楚且一致的理論基礎。

參考

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

You May Also Like