banner
Nagi-ovo

Nagi-ovo

Breezing
github

從 DQN 到 Policy Gradient

對於像《Space Invaders》這樣的 Atari 遊戲,我們需要把遊戲幀作為狀態和輸入,單幀圖像由 210x160 像素組成。由於圖像為彩色(RGB),因此包含 3 個通道。所以觀測空間形狀為 (210, 160, 3)。每個像素的值範圍從 0 到 255,所以共有 256210×160×3=256100800256^{210×160×3}=256^{100800} 種可能的觀測結果。

Pasted image 20241004000133

在這種情況下生成和更新 Q 表效率會很低。因此我們會使用 Deep Q-Learning 而不是 Q-Learning 這樣的 Tabular Method,選擇用神經網絡作為 Q 函數的近似器。該神經網絡將根據給定狀態,近似估計該狀態下每個可能動作的 Q 值。

DQN#

輸入預處理與時序局限性#

我們肯定希望降低狀態複雜度以減少訓練所需的計算時間。

Pasted image 20241004000752

灰度化#

顏色並不提供重要信息,因此可以將三個顏色通道(RGB)減少到一個。

剪裁螢幕#

不包含重要信息的區域可以剪裁掉。

捕獲時序信息#

無法通過單幀知道一個像素的運動信息(方向、速度),為了得到時序信息,我們將四幀堆疊在一起。

CNN#

堆疊的幀通過三個卷積層進行處理,目的是 捕捉並利用圖像中的空間關係。此外,由於幀是堆疊在一起的,我們還可以得到跨幀的時間信息

MLP#

最後是全連接層作為輸出,為該狀態下每個可能的動作輸出一個 Q 值。

Pasted image 20241004000340

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 的權重。

Pasted image 20241004002003

訓練流程#

深度 Q 學習訓練算法有兩個階段:

  • 採樣:執行操作並將觀察到的經驗元組存儲在重放緩存中
  • 訓練:隨機選擇一小批量元組,並利用梯度下降更新步驟從該批次中學習。

Pasted image 20241004002352

由於深度 Q 學習(off-policy)中結合了非線性的 Q-Value 函數(神經網絡)和自舉法(即使用現有估計而非實際完整回報來更新目標,有偏),其訓練過程可能會出現不穩定性。Sutton 和 Barto 提出的 “deadly triad” 指的正是這種情況。

穩定訓練#

為了幫助我們穩定訓練,我們實施了三種不同的解決方案:

  1. 經驗回放以更高效利用經驗
  2. 固定 Q-Target 以穩定訓練。
  3. 雙重 DQN,用於解決 Q 值過高估計的問題

經驗回放#

深度 Q 學習中的經驗回放具有兩個功能:

  1. 更高效地利用訓練過程中的經驗。通常的在線強化學習中,智能體與環境交互獲取經驗(狀態、動作、獎勵和下一狀態),從中學習(更新神經網絡),然後丟棄這些經驗的方式是非常低效的。經驗回放通過更高效地利用訓練經驗來提供幫助。我們使用一個回放緩衝區來保存經驗樣本,以便在訓練期間重複使用

Pasted image 20241004003215

Agent 能夠從相同經驗中多次學習

  1. 避免遺忘先前的經驗(即災難性干擾,或災難性遺忘)並減少經驗之間的關聯性。Replay Buffer 的設置可以在與環境交互時存儲經驗元組,然後從中抽取一小批元組。這防止網絡僅學習最近執行的操作。通過隨機抽樣經驗,可以使接觸到的經驗多樣化,防止過度擬合短期狀態,並避免了動作值的劇烈波動或災難性發散。

Pasted image 20241004003640

採樣經驗並計算損失:

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 目標)。儘管牛仔逐漸接近奶牛(誤差減少),但目標仍然在移動,導致訓練中的顯著振蕩。

Pasted image 20241004012634

Pasted image 20241004012646

Pasted image 20241004012412

好喜歡這個表示🥹

為了解決這個問題,我們引入了一個固定的 Q-Target。它的核心思想是引入一個獨立的網絡,它不會在每個時間步都更新,而是每隔 C 步將主網絡的參數複製到這個目標網絡中。這意味著我們在多個時間步內的目標(Q-Target)保持固定,並且僅根據舊的估計來更新網絡。這樣就能顯著減少目標和估計之間的振蕩問題。

Pasted image 20241004012440

如上面的伪代码所示,關鍵在於使用兩個不同的網絡:一個是主網絡(用來選擇動作並進行更新),另一個是目標網絡(用來計算 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 值目標的生成:

  1. 主網絡(DQN 網絡) 用於選擇下一狀態的最佳動作(即 Q 值最高的動作)。
  2. 目標網絡(Target 網絡) 用於計算執行該動作後產生的目標 Q 值。

Pasted image 20241004013644

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 方法,它們通過估計價值函數來間接地尋找最優策略。策略(π\pi的存在完全依賴於動作價值的估計,因為策略是一個從價值函數生成的,比如貪婪策略,在給定狀態下選擇具有最高價值的動作。

而通過基於 Policy-based 的方法,我們希望直接優化策略,從而繞過學習價值函數的中間步驟。接下來,我們將深入學習其中的一個子集,即策略梯度(Policy Gradient)。

在基於策略的方法中,優化大多數情況下是 on-policy 的,因為在每次更新時,我們僅使用由 最新版本的 ​πθ\pi_{\theta} 收集的數據(行動軌跡)。

參數化隨機策略#

例如讓神經網絡 πθ\pi_{\theta}​ 輸出一個動作的概率分佈(隨機策略)πθ(as)\pi_{\theta}(a|s)

Pasted image 20241006164702

目標函數 J(θ)J(\theta),優化參數 θ\theta,通過梯度上升最大化參數化策略的性能。

優點#

方便集成#

  • 可以直接估計策略,而無需存儲額外數據(action value),可以理解為是端到端的。

可以學習隨機策略#

由於輸出是動作的概率分佈,agent 能夠探索狀態空間而不總是遵循相同的軌跡,無需手動實現探索 / 利用權衡。DNQ 學習的是確定性策略(deterministic policy),我們是通過一些技巧(如 ε- 貪婪策略)引入了隨機性,但這並不是價值函數方法的內在特性。同時能夠自然地處理狀態的不確定性,解決了感知混淆問題。

例如在下面的情景中,agent 吸塵器要吸走灰塵並避免傷害倉鼠,吸塵器只能感知牆壁的位置。在圖中這兩個紅色狀態被稱為 “混淆狀態”(aliased states),因為在這些狀態中,agent 感知到的是牆壁的位置 —— 即在上方和下方都有牆壁。這導致了狀態的模糊性,無法區分出它是在哪個具體的紅色狀態。

Pasted image 20241006170520

在使用確定性策略時,吸塵器在紅色狀態下總是向右或向左移動,若選擇錯誤方向,則會陷入循環。即使使用 ε- 貪心策略,吸塵器主要遵循最佳策略,但在錯誤狀態下仍可能反復探索錯誤方向,導致效率低下。

Pasted image 20241006170145

高維、連續動作空間中有效#

在高維或連續動作空間中策略梯度方法尤為有效。

自動駕駛汽車在每個狀態下可能有無窮多的動作選擇 —— 比如方向盤可以轉動 15°,17.2°, 19.4°,或者進行其他動作如鳴笛。Deep Q-Learning 必須為每個可能動作計算 Q 值,而連續動作空間中選取最大 Q 值本身也是個優化問題。
image
相反,策略梯度方法直接輸出動作的概率分佈,無需計算和存儲每個動作的 Q 值,在複雜的連續動作場景下更加高效。

收斂性更好#

在值方法中,我們通過 argmaxargmax 來取最大 Q 值來更新策略。在這種情況下即使 Q 值發生細微變化,動作選擇可能會劇烈改變。例如,訓練時左轉的 Q 值為 0.22,接著右轉的 Q 值變為 0.23,策略將發生了顯著變化,會更多地選擇向右而不是向左。

而在策略梯度方法中,動作的概率隨時間平滑變化,策略更穩定。

缺點#

局部最優#

常收斂於局部最優而非全局最優。

訓練效率低#

訓練過程緩慢,效率低。

high variance#

方差較大,這一點會在後面的 actor-critic 中探討原因及解決方法。

具體分析#

策略梯度是通過每次 agent 與環境交互來調整參數(策略),使動作的概率分佈能夠更多地採樣到那些能最大化回報的良好動作

Pasted image 20241006172416

目標函數#

Pasted image 20241006174139

我們的目標是找到能夠最大化期望回報的參數 θ\theta

maxθJ(θ)=Eτπθ[R(τ)]\max_{\theta} J(\theta) = \mathbb{E}_{\tau \sim \pi_{\theta}} [R(\tau)]

由於這是一個凹函數(我們希望值最大),所以用梯度上升方法: θθ+αθJ(θ)θ←θ+α∗∇_θJ(θ).

然而目標函數的真實梯度是無法計算的,因為它需要計算每條可能軌跡的概率,這在計算上極其昂貴。因此,我們希望通過基於樣本的估計(收集一些軌跡)來計算梯度估計

除此之外,環境的狀態轉移概率(或狀態分佈)往往是未知的,或者即使已知,它也是複雜的、非線性的,無法直接計算其導數,也就是無法直接對這種狀態轉移動力學(受馬爾可夫決策過程控制)進行微分,來優化策略。

Policy Gradient Theorem#

完整的推導可以在 Andrej Karpathy 的博客裡看到,我這裡之前也做了學習總結:
Policy Gradient 入門 6. PG 推導

Pasted image 20241006180211

強化算法(蒙特卡洛強化)#

利用整個 episode 的估計回報來更新策略參數 θ\theta.
使用策略 πθπ_θ​ 收集一個片段 ττ,利用該 episode 來估計梯度 g^=θJ(θ)\hat{g}=∇_θJ(θ)
優化: θθ+αg^θ←θ+α\hat{g}

Pasted image 20241006181910

  • θlogπθ(atst)\nabla_{\theta} \log \pi_{\theta}(a_t | s_t)
    這個部分表示對於某個狀態 sts_t 和動作 ata_t,我們計算的不是具體的動作值(Q 值),而是動作概率的對數的梯度。

  • R(τ)R(\tau)
    這裡 R(τ)R(\tau) 是整個軌跡 τ\tau 上的累積回報,用來衡量執行策略 πθ\pi_\theta 後的總回報。回報率高則提高(狀態,動作)組合的概率,反之則降低。

也可以 收集多個片段(軌跡) 來估計梯度:
Pasted image 20241006182438

參考資料#

cleanrl 代碼庫

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