方差問題#
策略梯度(Policy Gradient)方法因其直觀和有效性而備受關注。我們之前探討過Reinforce算法,它在許多任務中表現良好。然而,Reinforce 方法依賴於蒙特卡洛(Monte Carlo)採樣來估計回報,這意味著我們需要使用整個回合的數據來計算回報。這種方法帶來了一個關鍵問題 ——策略梯度估計中的高方差。
PG 估計的核心在於找到回報增加最快的方向。換句話說,我們需要更新策略的權重,使得那些帶來高回報的動作在未來被選中的概率更高。理想情況下,這樣的更新會使得策略逐步優化,獲得更高的總回報。
然而,使用蒙特卡洛方法估計回報時,由於依賴整個回合的數據計算實際回報(不估計回報),策略梯度的估計會有顯著的方差(無偏但高方差)。高方差意味著我們的梯度估計不穩定,可能導致訓練過程緩慢甚至不收斂。為了獲得可靠的梯度估計,我們可能需要大量的樣本,這在實際應用中往往代價高昂。
環境和策略的隨機性導致相同初始狀態可能產生截然不同的回報,造成高方差。因此,從同一狀態開始的回報在不同情節中可能顯著變化。使用大量軌跡可減少方差,提供更準確的回報估計。但大 batch size 會降低樣本效率,因此需要尋找其他減少方差的機制。
Advantage Actor-Critic (A2C)#
通過 Actor-Critic 方法減少方差#
從前面所學知識和章節導語帶來的一個直觀感受是 “如果結合 Value-Based 和 Policy-Based,方差和訓練的問題都會得到優化”。演員 - 評論家(Actor-Critic)方法 正是這樣的混合架構,具體來說:
- 演員(Actor):負責選擇動作,基於當前策略生成動作概率分佈。
- 評論家(Critic):估計當前策略下的價值函數,提供對動作選擇的反饋。
想像你和你的朋友都是菜鳥玩家。你負責操作(Actor),而你的朋友負責觀察和評價(Critic)。一開始,你們都不太懂遊戲。你瞎貓撞死耗子般地操作,而你的朋友也在摸索如何評價你的表現。隨著時間推移,你通過實踐不斷改進操作技巧(Policy),同時你的朋友也在學習如何更準確地評估每個動作的好壞(Value)。
你們互相幫助,共同進步:你的操作為朋友提供了評估的基礎,而朋友的反饋幫助你調整策略。
也就是說,我們會學習兩個函數逼近(神經網絡):
- 控制 agent(Actor)操作的的策略函數:
- 衡量行動好壞輔助策略優化(Critic)的價值函數:
算法流程#
- 在每個時間步 ,我們從環境中獲取當前狀態 ,作為輸入傳遞給我們的 Actor 和 Critic。
- Actor 根據狀態輸出一個動作 。
- Critic 也將該動作作為輸入,並使用 和 ,計算在該狀態下採取該動作的價值:即 Q 值。
- 在環境中執行動作 ,輸出新狀態 和獎勵 。
- Actor 利用 值更新其策略參數。
- Actor 使用更新後的參數,在給定新狀態 時生成下一步要採取的動作 。
- Critic 隨後更新其價值參數。
Critic 起到了提供用於調整回報估計的 baseline 作用,從而使得梯度估計更加穩定。訓練過程更為平滑,收斂速度更快,所需樣本數量也大大減少。
添加 Advantage (A2C)#
可以通過使用Advantage Function 作為 Critic 而不是動作值函數來進一步穩定學習。
優勢:讓好動作脫穎而出#
核心想法是,將你的動作通過兩個部分來評估:
- 你獲得的即時獎勵和下一個狀態的價值。
- 你在當前狀態中對價值的預期。
數學上,我們稱之為優勢:
這表達的是:在狀態 中,你做出的動作 ,比你在狀態 中原本的期望(由 表示的 baseline 期望)好多少?
如果實際的獎勵和未來狀態的價值 高於你對當前狀態 的預期,那麼這個動作是好的;如果低了,那…… 你可以做得更好。
這個優勢不僅告訴你動作好不好,還告訴你有多好或者有多差(相對於 baseline 來說)。
策略更新#
當我們執行某個動作時,獎勵本身並不足夠指導策略的改進。獎勵告訴我們某個動作好或不好,但它並沒有告訴我們這個動作究竟有多好,或者比預期好多少。
所以改進策略時,與其盲目追逐獎勵,不如關注調整動作,基於它們超越(或低於)期望的程度。現在就可以微調策略,向那些持續表現優於 baseline 的動作靠攏。
這就是我們得到的更新公式:
- :表示在每個時間步 ,我們計算策略 選擇動作 的概率的對數梯度。這一步幫助我們找到在當前策略下,如何通過改變參數 來提高選擇 的可能性。
- :動作的優勢函數,告訴我們這個動作 在狀態 下相對於 baseline 有多好或多差。
簡單來說:你的策略 的梯度由優勢 調整。你在更新你的策略,不僅基於這個動作是否帶來了一些獎勵,而是基於這個動作相比期望超越了多少。
更妙的是:你只需要一個神經網絡來預測價值函數 。
現在來聊聊 TD 誤差#
當然,計算這個優勢函數很棒,但在線學習有一個妙處:你不需要等到最後再更新策略。於是,時序差分誤差(TD Error) 就登場了:
這裡的關鍵是:TD 誤差實際上是優勢函數的在線估計。它告訴你,就在此刻,你的動作是否讓未來狀態比你預期的要好。這個誤差 $\delta$ 直接反映了優勢的概念:
- 如果 :“嘿,這個動作比我想象中好!”(優勢為正)。
- 如果 :“嗯,我本來以為會更好……”(優勢為負)。
這讓你可以逐步調整你的策略,不用等到一整 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 實現更快。