對於像《Space Invaders》這樣的 Atari 遊戲,我們需要把遊戲幀作為狀態和輸入,單幀圖像由 210x160 像素組成。由於圖像為彩色(RGB),因此包含 3 個通道。所以觀測空間形狀為 (210, 160, 3)。每個像素的值範圍從 0 到 255,所以共有 種可能的觀測結果。
在這種情況下生成和更新 Q 表效率會很低。因此我們會使用 Deep Q-Learning 而不是 Q-Learning 這樣的 Tabular Method,選擇用神經網絡作為 Q 函數的近似器。該神經網絡將根據給定狀態,近似估計該狀態下每個可能動作的 Q 值。
DQN#
輸入預處理與時序局限性#
我們肯定希望降低狀態複雜度以減少訓練所需的計算時間。
灰度化#
顏色並不提供重要信息,因此可以將三個顏色通道(RGB)減少到一個。
剪裁螢幕#
不包含重要信息的區域可以剪裁掉。
捕獲時序信息#
無法通過單幀知道一個像素的運動信息(方向、速度),為了得到時序信息,我們將四幀堆疊在一起。
CNN#
堆疊的幀通過三個卷積層進行處理,目的是 捕捉並利用圖像中的空間關係。此外,由於幀是堆疊在一起的,我們還可以得到跨幀的時間信息。
MLP#
最後是全連接層作為輸出,為該狀態下每個可能的動作輸出一個 Q 值。
class QNetwork(nn.Module):
def __init__(self, env):
super().__init__()
self.network = nn.Sequential(
nn.Conv2d(4, 32, 8, stride=4),
nn.ReLU(),
nn.Conv2d(32, 64, 4, stride=2),
nn.ReLU(),
nn.Conv2d(64, 64, 3, stride=1),
nn.ReLU(),
nn.Flatten(), # 展平多維輸入為一維
nn.Linear(3136, 512), # 全連接層,將3136維輸入映射到512維
nn.ReLU(),
nn.Linear(512, env.single_action_space.n), # 輸出層,對應動作空間的維度
)
def forward(self, x):
return self.network(x / 255.0) # 將輸入歸一化到[0,1]範圍
網絡的輸入:通過網絡傳遞的4 幀堆疊作為狀態
輸出:該狀態下每個可能動作的Q 值向量。
然後與 Q-Learning 類似,我們只需使用 epsilon-greedy 策略來選擇採取哪個動作。
在訓練階段,我們不再像 Q - 學習那樣直接更新狀態 - 動作對的 Q 值:通過設計損失函數和梯度下降來優化 DQN 的權重。
訓練流程#
深度 Q 學習訓練算法有兩個階段:
- 採樣:執行操作並將觀察到的經驗元組存儲在重放緩存中。
- 訓練:隨機選擇一小批量元組,並利用梯度下降更新步驟從該批次中學習。
由於深度 Q 學習(off-policy)中結合了非線性的 Q-Value 函數(神經網絡)和自舉法(即使用現有估計而非實際完整回報來更新目標,有偏),其訓練過程可能會出現不穩定性。Sutton 和 Barto 提出的 “deadly triad” 指的正是這種情況。
穩定訓練#
為了幫助我們穩定訓練,我們實施了三種不同的解決方案:
- 經驗回放以更高效利用經驗。
- 固定 Q-Target 以穩定訓練。
- 雙重 DQN,用於解決 Q 值過高估計的問題。
經驗回放#
深度 Q 學習中的經驗回放具有兩個功能:
- 更高效地利用訓練過程中的經驗。通常的在線強化學習中,智能體與環境交互獲取經驗(狀態、動作、獎勵和下一狀態),從中學習(更新神經網絡),然後丟棄這些經驗的方式是非常低效的。經驗回放通過更高效地利用訓練經驗來提供幫助。我們使用一個回放緩衝區來保存經驗樣本,以便在訓練期間重複使用。
Agent 能夠從相同經驗中多次學習。
- 避免遺忘先前的經驗(即災難性干擾,或災難性遺忘)並減少經驗之間的關聯性。Replay Buffer 的設置可以在與環境交互時存儲經驗元組,然後從中抽取一小批元組。這防止網絡僅學習最近執行的操作。通過隨機抽樣經驗,可以使接觸到的經驗多樣化,防止過度擬合短期狀態,並避免了動作值的劇烈波動或災難性發散。
採樣經驗並計算損失:
rb = ReplayBuffer(
args.buffer_size, # 回放緩衝區大小,決定存儲多少經驗。
envs.single_observation_space,
envs.single_action_space,
device,
optimize_memory_usage=True,
handle_timeout_termination=False,
)
if global_step > args.learning_starts:
if global_step % args.train_frequency == 0:
data = rb.sample(args.batch_size) # 隨機採樣一個 batch
with torch.no_grad():
target_max, _ = target_network(data.next_observations).max(dim=1)
td_target = data.rewards.flatten() + args.gamma * target_max * (1 - data.dones.flatten())
old_val = q_network(data.observations).gather(1, data.actions).squeeze()
loss = F.mse_loss(td_target, old_val)
固定 Q-Target#
在 Q-Learning 有一個關鍵的問題是,TD 目標(即 Q-Target)和當前 Q Value(即 Q 估計)的參數是共享的。這導致了 Q 目標和 Q 估計同時變化,就像你在追逐一個不斷移動的目標。一個美妙的比喻方式是一個牛仔(Q 估計)試圖追趕一頭移動的奶牛(Q 目標)。儘管牛仔逐漸接近奶牛(誤差減少),但目標仍然在移動,導致訓練中的顯著振蕩。
好喜歡這個表示🥹
為了解決這個問題,我們引入了一個固定的 Q-Target。它的核心思想是引入一個獨立的網絡,它不會在每個時間步都更新,而是每隔 C 步將主網絡的參數複製到這個目標網絡中。這意味著我們在多個時間步內的目標(Q-Target)保持固定,並且僅根據舊的估計來更新網絡。這樣就能顯著減少目標和估計之間的振蕩問題。
如上面的伪代码所示,關鍵在於使用兩個不同的網絡:一個是主網絡(用來選擇動作並進行更新),另一個是目標網絡(用來計算 Q-Target),並且每隔 C 步會將主網絡的權重拷貝到目標網絡中。這樣,我們可以穩定訓練過程,使 “牛仔能夠更有效地追逐奶牛”,減少振蕩並加快收斂速度。
q_network = QNetwork(envs).to(device) # 當前策略網絡,負責選擇動作和預測Q值
optimizer = optim.Adam(q_network.parameters(), lr=args.learning_rate)
target_network = QNetwork(envs).to(device) # 目標網絡,計算TD目標,提供穩定的學習目標。
target_network.load_state_dict(q_network.state_dict()) # 初始化:目標網絡的參數與當前策略網絡相同
每隔 args.target_network_frequency
步將主網絡的參數全量複製到目標網絡。這意味著在多個時間步內,Q-Target 保持固定,僅根據舊的估計來更新網絡,從而顯著減少目標和估計之間的振蕩問題。
tau = 1.0
if global_step % args.target_network_frequency == 0:
for target_param, param in zip(target_network.parameters(), q_network.parameters()):
target_param.data.copy_(args.tau * param.data + (1.0 - args.tau) * target_param.data)
Double DQN#
Double DQN 是由 Hado van Hasselt 提出的,專門用於解決 Q 值過估計的問題。
在 Q-Learning 的 TD-Target 計算中,一個常見問題是 “如何確定下一狀態的最佳動作是 Q 值最高的動作?” 我們知道,Q 值的準確性取決於我們嘗試的動作和探索過的鄰近狀態。因此,在訓練初期,關於最佳動作的信息並不充分。如果僅根據最高 Q 值來選擇動作,可能會導致錯誤判斷。
舉例來說,如果非最優的動作被賦予了一個高於最佳動作的 Q 值,學習過程將變得複雜,難以收斂。為了解決這個問題,Double DQN 引入了兩個網絡來解耦動作選擇和 Q 值目標的生成:
- 主網絡(DQN 網絡) 用於選擇下一狀態的最佳動作(即 Q 值最高的動作)。
- 目標網絡(Target 網絡) 用於計算執行該動作後產生的目標 Q 值。
with torch.no_grad():
# 使用主網絡選擇下一狀態的最佳動作
next_q_values = q_network(data.next_observations)
next_actions = torch.argmax(next_q_values, dim=1, keepdim=True)
# 使用目標網絡評估這些動作的 Q Value
target_q_values = target_network(data.next_observations)
target_max = target_q_values.gather(1, next_actions).squeeze()
# 計算 TD-Target
td_target = data.rewards.flatten() + args.gamma * target_max * (1 - data.dones.flatten())
# 計算當前 Q Value
old_val = q_network(data.observations).gather(1, data.actions).squeeze()
loss = F.mse_loss(td_target, old_val)
現代的深度強化學習中還有進一步改進的技術,如優先級經驗回放和決鬥網絡(Dueling Networks),這裡暫不涉及。
Optuna#
深度強化學習中最關鍵的任務之一是找到一組良好的訓練超參數。Optuna 是一個幫助你自動化搜索最佳的超參數組合的庫。
策略梯度#
前面的 Q-Learning 和 DQN 都屬於 Value-based 方法,它們通過估計價值函數來間接地尋找最優策略。策略()的存在完全依賴於動作價值的估計,因為策略是一個從價值函數生成的,比如貪婪策略,在給定狀態下選擇具有最高價值的動作。
而通過基於 Policy-based 的方法,我們希望直接優化策略,從而繞過學習價值函數的中間步驟。接下來,我們將深入學習其中的一個子集,即策略梯度(Policy Gradient)。
在基於策略的方法中,優化大多數情況下是 on-policy 的,因為在每次更新時,我們僅使用由 最新版本的 收集的數據(行動軌跡)。
參數化隨機策略#
例如讓神經網絡 輸出一個動作的概率分佈(隨機策略):
目標函數 ,優化參數 ,通過梯度上升最大化參數化策略的性能。
優點#
方便集成#
- 可以直接估計策略,而無需存儲額外數據(action value),可以理解為是端到端的。
可以學習隨機策略#
由於輸出是動作的概率分佈,agent 能夠探索狀態空間而不總是遵循相同的軌跡,無需手動實現探索 / 利用權衡。DNQ 學習的是確定性策略(deterministic policy),我們是通過一些技巧(如 ε- 貪婪策略)引入了隨機性,但這並不是價值函數方法的內在特性。同時能夠自然地處理狀態的不確定性,解決了感知混淆問題。
例如在下面的情景中,agent 吸塵器要吸走灰塵並避免傷害倉鼠,吸塵器只能感知牆壁的位置。在圖中這兩個紅色狀態被稱為 “混淆狀態”(aliased states),因為在這些狀態中,agent 感知到的是牆壁的位置 —— 即在上方和下方都有牆壁。這導致了狀態的模糊性,無法區分出它是在哪個具體的紅色狀態。
在使用確定性策略時,吸塵器在紅色狀態下總是向右或向左移動,若選擇錯誤方向,則會陷入循環。即使使用 ε- 貪心策略,吸塵器主要遵循最佳策略,但在錯誤狀態下仍可能反復探索錯誤方向,導致效率低下。
高維、連續動作空間中有效#
在高維或連續動作空間中策略梯度方法尤為有效。
自動駕駛汽車在每個狀態下可能有無窮多的動作選擇 —— 比如方向盤可以轉動 15°,17.2°, 19.4°,或者進行其他動作如鳴笛。Deep Q-Learning 必須為每個可能動作計算 Q 值,而連續動作空間中選取最大 Q 值本身也是個優化問題。
相反,策略梯度方法直接輸出動作的概率分佈,無需計算和存儲每個動作的 Q 值,在複雜的連續動作場景下更加高效。
收斂性更好#
在值方法中,我們通過 來取最大 Q 值來更新策略。在這種情況下即使 Q 值發生細微變化,動作選擇可能會劇烈改變。例如,訓練時左轉的 Q 值為 0.22,接著右轉的 Q 值變為 0.23,策略將發生了顯著變化,會更多地選擇向右而不是向左。
而在策略梯度方法中,動作的概率隨時間平滑變化,策略更穩定。
缺點#
局部最優#
常收斂於局部最優而非全局最優。
訓練效率低#
訓練過程緩慢,效率低。
high variance#
方差較大,這一點會在後面的 actor-critic 中探討原因及解決方法。
具體分析#
策略梯度是通過每次 agent 與環境交互來調整參數(策略),使動作的概率分佈能夠更多地採樣到那些能最大化回報的良好動作。
目標函數#
我們的目標是找到能夠最大化期望回報的參數 :
由於這是一個凹函數(我們希望值最大),所以用梯度上升方法: .
然而目標函數的真實梯度是無法計算的,因為它需要計算每條可能軌跡的概率,這在計算上極其昂貴。因此,我們希望通過基於樣本的估計(收集一些軌跡)來計算梯度估計。
除此之外,環境的狀態轉移概率(或狀態分佈)往往是未知的,或者即使已知,它也是複雜的、非線性的,無法直接計算其導數,也就是無法直接對這種狀態轉移動力學(受馬爾可夫決策過程控制)進行微分,來優化策略。
Policy Gradient Theorem#
完整的推導可以在 Andrej Karpathy 的博客裡看到,我這裡之前也做了學習總結:
Policy Gradient 入門 6. PG 推導
強化算法(蒙特卡洛強化)#
利用整個 episode 的估計回報來更新策略參數 .
使用策略 收集一個片段 ,利用該 episode 來估計梯度
優化:
-
:
這個部分表示對於某個狀態 和動作 ,我們計算的不是具體的動作值(Q 值),而是動作概率的對數的梯度。 -
:
這裡 是整個軌跡 上的累積回報,用來衡量執行策略 後的總回報。回報率高則提高(狀態,動作)組合的概率,反之則降低。
也可以 收集多個片段(軌跡) 來估計梯度: