banner
Nagi-ovo

Nagi-ovo

Breezing
github

Actor Critic 方法初探

方差問題#

策略梯度(Policy Gradient)方法因其直觀和有效性而備受關注。我們之前探討過Reinforce算法,它在許多任務中表現良好。然而,Reinforce 方法依賴於蒙特卡洛(Monte Carlo)採樣來估計回報,這意味著我們需要使用整個回合的數據來計算回報。這種方法帶來了一個關鍵問題 ——策略梯度估計中的高方差

Pasted image 20241010090847

PG 估計的核心在於找到回報增加最快的方向。換句話說,我們需要更新策略的權重,使得那些帶來高回報的動作在未來被選中的概率更高。理想情況下,這樣的更新會使得策略逐步優化,獲得更高的總回報。

然而,使用蒙特卡洛方法估計回報時,由於依賴整個回合的數據計算實際回報(不估計回報),策略梯度的估計會有顯著的方差(無偏但高方差)。高方差意味著我們的梯度估計不穩定,可能導致訓練過程緩慢甚至不收斂。為了獲得可靠的梯度估計,我們可能需要大量的樣本,這在實際應用中往往代價高昂。

Pasted image 20241010091609

環境和策略的隨機性導致相同初始狀態可能產生截然不同的回報,造成高方差。因此,從同一狀態開始的回報在不同情節中可能顯著變化。使用大量軌跡可減少方差,提供更準確的回報估計。但大 batch size 會降低樣本效率,因此需要尋找其他減少方差的機制。

Advantage Actor-Critic (A2C)#

通過 Actor-Critic 方法減少方差#

從前面所學知識和章節導語帶來的一個直觀感受是 “如果結合 Value-Based 和 Policy-Based,方差和訓練的問題都會得到優化”。演員 - 評論家(Actor-Critic)方法 正是這樣的混合架構,具體來說:

  • 演員(Actor):負責選擇動作,基於當前策略生成動作概率分佈。
  • 評論家(Critic):估計當前策略下的價值函數,提供對動作選擇的反饋。

想像你和你的朋友都是菜鳥玩家。你負責操作(Actor),而你的朋友負責觀察和評價(Critic)。一開始,你們都不太懂遊戲。你瞎貓撞死耗子般地操作,而你的朋友也在摸索如何評價你的表現。隨著時間推移,你通過實踐不斷改進操作技巧(Policy),同時你的朋友也在學習如何更準確地評估每個動作的好壞(Value)。

Pasted image 20241010092831

你們互相幫助,共同進步:你的操作為朋友提供了評估的基礎,而朋友的反饋幫助你調整策略。

也就是說,我們會學習兩個函數逼近(神經網絡):

  • 控制 agent(Actor)操作的的策略函數:πθ(s)\pi_\theta(s)
  • 衡量行動好壞輔助策略優化(Critic)的價值函數:q^w(s,a)\hat{q}_w(s,a)

算法流程#

  • 在每個時間步 tt,我們從環境中獲取當前狀態 StS_t​ ,作為輸入傳遞給我們的 Actor 和 Critic。
  • Actor 根據狀態輸出一個動作 AtA_t 。

Screenshot 2024-10-10 at 10.06.53

  • Critic 也將該動作作為輸入,並使用 StS_t​ 和 AtA_t​ ,計算在該狀態下採取該動作的價值:即 Q 值。
  • 在環境中執行動作 AtA_t​ ,輸出新狀態 St+1S_{t+1}​ 和獎勵 Rt+1R_{t+1}​ 。
  • Actor 利用 QQ 值更新其策略參數。

Screenshot 2024-10-10 at 10.08.30

Screenshot 2024-10-10 at 10.19.12

  • Actor 使用更新後的參數,在給定新狀態 St+1S_{t+1}​ 時生成下一步要採取的動作 At+1A_{t+1}​ 。
  • Critic 隨後更新其價值參數。

Screenshot 2024-10-10 at 10.37.56

Critic 起到了提供用於調整回報估計的 baseline 作用,從而使得梯度估計更加穩定。訓練過程更為平滑,收斂速度更快,所需樣本數量也大大減少。

添加 Advantage (A2C)#

可以通過使用Advantage Function 作為 Critic 而不是動作值函數來進一步穩定學習。

優勢:讓好動作脫穎而出#

核心想法是,將你的動作通過兩個部分來評估:

  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) 表示的 baseline 期望)好多少

如果實際的獎勵和未來狀態的價值 rt+1+γV(st+1)r_{t+1} + \gamma V(s_{t+1}) 高於你對當前狀態 V(st)V(s_t) 的預期,那麼這個動作是好的;如果低了,那…… 你可以做得更好。

這個優勢不僅告訴你動作好不好,還告訴你有多好或者有多差(相對於 baseline 來說)。

策略更新#

當我們執行某個動作時,獎勵本身並不足夠指導策略的改進。獎勵告訴我們某個動作好或不好,但它並沒有告訴我們這個動作究竟有多好,或者比預期好多少。
所以改進策略時,與其盲目追逐獎勵,不如關注調整動作,基於它們超越(或低於)期望的程度。現在就可以微調策略,向那些持續表現優於 baseline 的動作靠攏。

這就是我們得到的更新公式:

θ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 下相對於 baseline 有多好或多差。

簡單來說:你的策略 πθ\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:“嗯,我本來以為會更好……”(優勢為負)。

這讓你可以逐步調整你的策略,不用等到一整 episode 結束才做改變。這對提高效率來說,簡直是絕佳策略。

代碼實現#

Actor-Critic 網絡架構#

首先,我們需要構建一個神經網絡。這是一個雙頭網絡:一個用於 Actor(學習策略來選擇動作),另一個用於 Critic(估計狀態的價值)。

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

        # Critic 網絡(價值函數近似)
        # 這個網絡用於預測 V(s),也就是狀態 s 的價值
        self.critic_linear1 = nn.Linear(num_inputs, hidden_size)
        self.critic_linear2 = nn.Linear(hidden_size, 1)  # 價值函數是標量輸出

        # Actor 網絡(策略函數近似)
        # 這個網絡用於預測 π(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 tensor,並增加一個維度以支持 batch 處理
        state = Variable(torch.from_numpy(state).float().unsqueeze(0))
        
        # Critic 網絡的前向傳播
        value = F.relu(self.critic_linear1(state))
        value = self.critic_linear2(value)  # 輸出狀態的價值 V(s)
        
        # Actor 網絡的前向傳播
        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 的核心:主循環和更新機制。每一 episode 中,agent 在環境中運行一定步數,收集狀態、動作和獎勵的軌跡。在每集結束時,更新 Actor(策略)和 Critic(價值函數)。

def a2c(env):
    # 從環境中獲取輸入和輸出的維度
    num_inputs = env.observation_space.shape[0]
    num_outputs = env.action_space.n
    
    # 初始化 Actor-Critic 網絡
    actor_critic = ActorCritic(num_inputs, num_outputs, hidden_size)
    ac_optimizer = optim.Adam(actor_critic.parameters(), lr=learning_rate)

    # 用於追蹤性能的數據容器
    all_lengths = []  # 追蹤每 episode 的長度
    average_lengths = []  # 追蹤最近 10 episode 的平均長度
    all_rewards = []  # 追蹤每 episode 的累計獎勵
    entropy_term = 0  # 激勵探索

    # 進入每一 episode 的循環
    for episode in range(max_episodes):
        log_probs = []  # 存儲動作的對數概率
        values = []  # 存儲 Critic 的價值估計(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]  # Critic 估計當前狀態的價值
            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  # 更新為新的狀態
            
            # 如果本 episode 結束,記錄並退出本輪循環
            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))  # 記錄本 episode 的總獎勵
                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 值(Critic 的目標值)
        Qvals = np.zeros_like(values)  # 初始化 Q 值數組
        for t in reversed(range(len(rewards))):
            Qval = rewards[t] + GAMMA * Qval  # Bellman 公式計算 Q 值
            Qvals[t] = Qval

        Qvals = torch.FloatTensor(Qvals)
        log_probs = torch.stack(log_probs)
        
        # 計算優勢函數
        advantage = Qvals - values  # 動作的表現超出了 Critic 的期望多少?

        # 損失函數
        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()
  • Actor 更新基於策略梯度:我們將動作的對數概率與動作的優勢相乘,若動作比預期表現好,優勢為正,則鼓勵該動作。
  • Critic 更新基於均方誤差:Critic 比較其預測的 $V (s_t)$ 與實際的回報 $Q_t$,並最小化這個差異。
  • entropy_term:通過引入熵來鼓勵探索行為,防止智能體過早收斂到某些動作而缺乏足夠的探索。

總結#

A2C 的核心就是通過優勢函數引導策略更新,同時借助TD 誤差進行在線學習,讓 agent 的每一步行動更加聰明和高效。

  • 優勢函數:不僅告訴你動作好壞,還告訴你相對 baseline 有多好。
  • TD 誤差:一個實時計算優勢的利器,幫助你快速高效地更新策略。

Asynchronous A2C(A3C)#

A3C 是在 Deepmind 的論文《Asynchronous Methods for Deep Reinforcement Learning》中提出的。本質上,A3C 相當於 並行化 的 A2C,多個並行的 agent (工作線程)在並行環境中獨立更新全局的價值函數,因此被稱為 “異步”,在現代的多核 CPU 上效率較高。

可以從前面看出,A2C 是 A3C 的同步版本,在執行更新之前等待每個參與者完成其經驗段,平均所有參與者的結果,優點是可以更高效地利用 GPU。並且在OpenAI Baselines: ACKTR & A2C中提到:
我們的同步 A2C 實現的性能優於我們的異步實現 —— 我們沒有看到任何證據表明異步引入的噪聲提供了任何性能優勢。在使用單 GPU 機器時,這種 A2C 實現比 A3C 更具成本效益,並且在使用更大策略時比僅使用 CPU 的 A3C 實現更快。

參考資料#

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。