1. 程式人生 > 其它 >詳解策略梯度演算法

詳解策略梯度演算法

詳解策略梯度演算法

引言

根據智慧體學習的不同,可將其分為Value-based方法、Policy-based方法以及Actor-Critic方法。之前我們介紹的Q-learning、Saras和DQN都是基於價值去學習,雖然這種強化學習方法在很多領域都獲得較多的應用,但是它的侷限性也是比較明顯。首先這類演算法基本上都是處理離散動作,建立簡單的Q表,很難對連續動作進行處理,更無法建立龐大的Q表;其次基於價值的方法使用值函式去近似Q值,雖然可以較高效率地解決連續狀態空間的問題,但動作依然只是離散的,動作選擇的策略也是不會變化的,通常是一個確定性的策略,但有些實際問題需要的最優策略並不是確定性的,而是隨機策略,比如石頭剪刀布,如果按照一種確定性策略出拳,那麼當別人抓住規律時,你就會輸。所以我們需要引入一個新的方法去解決以上問題,比如策略梯度的方法。

1. 蒙特卡羅

再講解策略梯度演算法(Policy Gradient,簡稱PG)前,大家可以先了解一下蒙特卡羅演算法,首先我們來看一個小故事:

在火影時代,還是下忍的鳴人為了提升自己的能力,從木葉忍者任務中心接了一個C級任務,在做任務的時候,突然被一個戴面具的人困在幻境中。類似迷宮的幻境(出口是光之門,可以理解為帶光的門),鳴人怎麼走都出不去,這個幻境雖然有很多出口,但只有最近的出口通過光之門才能走出幻境,其他的出口雖然有光之門,但是過不去。有可能鳴人對數學方面也頗有造詣,他用自己學習的禁術多重影分身之術,分出很多個分身(假設足夠多,不要問作為下忍的鳴人查克拉夠不夠,在危急時刻,他可以向體內的九尾借啊),然後開始對每一個分身交代任務:

注意: 分身都從起點出發,每走一步,都會得到相應的查克拉補充或降低能量(獎勵,有正有負)。且每一個分身面對分叉路口的選擇都是平均選擇。忽略獎勵的折扣因子。

  1. 你們每個人都需要找到一個出口,不論遠近,且途中每走一步都需要將自己所經過的路徑和得到查克拉的多少記錄到卷軸上;
  2. 記錄走過的這條路徑獲得的總查克拉,原路返回到出發點;
  3. 將你們每個人得到的總獎勵進行平均,最終結果彙報給我,作為當前出發點的值。
  4. 然後將出發點換成下一步可以選擇的出發地,重複1~3。

鳴人拿到所有路口的值後,每遇到一個分叉路口就選擇值最大的那個路口,最終鳴人成功的走出了幻境。

上面的故事其實是類似蒙特卡羅演算法,具體如下;

蒙特卡羅演算法是基於取樣的方法,給定策略\(\pi\)

,讓智慧體與環境進行互動,就會得到很多條軌跡。 每條軌跡都有對應的回報,我們把每條軌跡的回報進行平均,就可以知道某一個策略下面對應狀態的價值。這句話拆分開來可以對應上述故事:

  1. 蒙特卡羅是基於取樣的方法。(對應故事中鳴人的分身足夠多)
  2. 需要給定一個策略\(\pi\)(對應故事中每個分身遇到分叉路口都是平均選擇)
  3. 智慧體與環境進行互動,得到很多軌跡。(對應故事中每一個分身在幻境中找出口的過程,每個分身對應一條軌跡)
  4. 每條軌跡都有對應的回報。(對應故事中每個分身得到的總獎勵)
  5. 將每條軌跡的回報進行平均,就得到對應狀態的價值了。(對應鳴人將每個分身的總獎勵進行平均)

2. 策略梯度演算法

2.1 介紹

在強化學習中,有三個組成部分:演員(actor)、環境和獎勵函式。其中環境和獎勵函式不是我們可以控制的,在開始學習之前就已經事先給定。演員裡會有一個策略,它用來決定演員的動作。策略就是給定一個外界輸入,它會輸出演員現在應該要執行的動作。我們唯一可以做的就是調整演員裡面的策略,使得演員可以得到最大的獎勵。

當我們將深度學習與強化學習相結合時,策略π就是一個網路,用\(\theta\)表示\(\pi\)的引數。舉上面幻境的例子,輸入就是當前分身所在的分叉路口,假設可以向上,向下,向左走,經過策略網路後,輸出就是三個動作可以選擇的概率。然後演員就根據這個概率的分佈來決定它要採取的動作,概率分佈不同,演員採取的動作也不同。簡單來說,策略的網路輸出是一個概率分佈,演員根據這個分佈去做取樣,決定實際上要採取的動作是哪一個。

其實PG就是蒙特卡羅與神經網路結合的演算法,PG不再像Q-learning、DQN一樣輸出Q值,而是在一個連續區間內直接輸出當前狀態可以採用所有動作的概率。

我們之前在講解基於價值的方法中,使用價值函式近似將Q表更新問題變成一個函式擬合問題,相近的狀態得到相近的輸出動作,如下式,通過更新引數w使得函式f逼近最優Q值。

\[Q(s, a) \approx f(s, a, w) \]

在PG演算法中,因為策略是一個概率,不能直接用來迭代,所以我們採取類似的思路,將其轉化為函式形式,如下式所示,這時我們的目的則是使用帶有\(\theta\)引數的函式對策略進行近似,通過更新引數\(\theta\),逼近最優策略。

\[π(a|s)≈πθ(s,a)=P(a|s,θ) \]

現在有了策略函式,我們的目標當然是優化它,那麼該如何知道優化後的策略函式的優劣呢。大家肯定會想到需要一個可以優化的目標函式,我們讓這個目標函式朝著最大值的方向去優化,它的主要作用就是用來衡量策略的好壞程度。

2.2 演算法內容

首先,我們可以把環境看成一個函式,這個函式一開始就先吐出一個狀態,假如這個狀態是遊戲的畫面,接下來演員看到這個遊戲畫面 \(s_{1}\)以後,選擇了\(a_{1}\)這個動作。環境把\(a_{1}\)當作它的輸入,再吐出\(s_{2}\),也就是吐出新的遊戲畫面。演員看到新的遊戲畫面,再採取新的動作\(a_{2}\)。環境再看\(a_{2}\),再吐出\(s_{3}\)。這個過程會一直持續到環境覺得應該要停止為止。

在一場遊戲中,演員通過與環境的不斷互動最終結束,根據上述的說明,可以得到一條軌跡,表示為\(\tau\),其中\(s\)表示狀態,\(a\)表示行動,\(s_{1}\)\(a_{1}\)表示演員在狀態1的時候選擇了動作1,後面的以此類推。如下式所示。

\[\tau=\left\{s_{1}, a_{1}, s_{2}, a_{2}, \cdots, s_{t}, a_{t}\right\} \]

那麼假設當前演員的策略網路引數是$$\theta$$,我們就可以計算這一條軌跡發生的概率。它取決於兩部分,環境的動作和智慧體的動作,如下式所示,它就表示一條軌跡產生後所發生的概率。

\[\begin{aligned} p_{\theta}(\tau) &=p\left(s_{1}\right) p_{\theta}\left(a_{1} \mid s_{1}\right) p\left(s_{2} \mid s_{1}, a_{1}\right) p_{\theta}\left(a_{2} \mid s_{2}\right) p\left(s_{3} \mid s_{2}, a_{2}\right) \cdots \\ &=p\left(s_{1}\right) \prod_{t=1}^{T} p_{\theta}\left(a_{t} \mid s_{t}\right) p\left(s_{t+1} \mid s_{t}, a_{t}\right) \end{aligned} \]

環境的動作是指環境的函式內部的引數或內部的規則長什麼樣子,它是無法控制的,提前已經寫好,$$p\left(s_{t+1} \mid s_{t}, a_{t}\right)$$ 代表環境。智慧體的動作是指能夠自己控制,$$p_{\theta}\left(a_{t} \mid s_{t}\right)$$代表智慧體,給定一個狀態\(s_{t}\),演員要採取什麼樣的動作\(a_{t}\)會取決於演員的引數\(\theta\),所以這部分是演員可以自己控制的。隨著演員的動作不同,每個同樣的軌跡,它就會有不同的出現的概率。

除了環境跟演員以外,還有獎勵函式。給它輸入\(s_{1}\)\(a_{1}\),它告訴你得到\(r_{1}\)。給它 \(s_{2}\)\(a_{2}\),它告訴你得到\(r_{2}\)。把所有的\(r\)都加起來,我們就得到了\(R(\tau)\),代表某一個軌跡\(\tau\)的獎勵。

在某一場遊戲裡面,我們會得到\(R\)。通過調整演員內部的引數\(\theta\), 使得\(R\)的值越大越好,這就是PG演算法的優化目標。但實際上獎勵並不只是一個標量,獎勵\(R\)是一個隨機變數,因為演員在給定同樣的狀態會做什麼樣的動作,是具有隨機性的。環境在給定同樣的觀測要採取什麼樣的動作,要產生什麼樣的觀測,本身也是有隨機性的,所以\(R\)是一個隨機變數。那麼我們就可以計算,在給定某一組引數\(\theta\)的情況下,得到的\(R_{\theta}\)的期望值是多少。期望值如下公式所示。

\[\bar{R}_{\theta}=\sum_{\tau} R(\tau) p_{\theta}(\tau)=E_{\tau \sim p_{\theta}(\tau)}[R(\tau)] \]

我們需要窮舉所有可能的軌跡\(\tau\),每一個軌跡\(\tau\)都有一個概率和一個總獎勵\(R\)。也可以從分佈\(p_{\theta}(\tau)\)取樣一個軌跡\(\tau\),計算\(R(\tau)\)的期望值,就是期望獎勵。我們要做的事情就是最大化期望獎勵。

如何最大化期望獎勵呢,既然是最大化,那麼我們可以採用梯度上升的方式更新引數,使得期望獎勵最大化。對\(\bar{R}\)取梯度,這裡面只有\(p_{\theta}(\tau)\)是跟\(\theta\)有關。整個策略梯度公式如下圖所示。

圖1. 策略梯度公式

其中,對\(\nabla p_{\theta}(\tau)\)使用\(\nabla f(x)=f(x) \nabla \log f(x)\),得到$$\nabla p_{\theta}(\tau)=p_{\theta}(\tau) \nabla \log p_{\theta}(\tau)$$這個\(\nabla f(x)=f(x) \nabla \log f(x)\)大家可以把這個理解成一個固定的公式轉換,記住即可。

如下式所示,對\(\tau\)進行求和,把\(R(\tau)\)\(\log p_{\theta}(\tau)\)這兩項使用\(p_{\theta}(\tau)\)進行加權,既然使用\(p_{\theta}(\tau)\)進行加權,它們就可以被寫成期望的形式。也就是你從\(p_{\theta}(\tau)\)這個分佈裡面取樣\(\tau\)出來,去計算\(R(\tau)\)乘上\(\log p_{\theta}(\tau)\),把它對所有可能的\(\tau\)進行求和,就是這個期望的值。

\[\begin{aligned} \nabla \bar{R}_{\theta} &=\sum_{\tau} R(\tau) \nabla p_{\theta}(\tau) \\ &=\sum_{\tau} R(\tau) p_{\theta}(\tau) \frac{\nabla p_{\theta}(\tau)}{p_{\theta}(\tau)} \\ &=\sum_{\tau} R(\tau) p_{\theta}(\tau) \nabla \log p_{\theta}(\tau) \\ &=E_{\tau \sim p_{\theta}(\tau)}\left[R(\tau) \nabla \log p_{\theta}(\tau)\right] \end{aligned} \]

實際上這個期望值沒有辦法算,所以我們是用取樣的方式來取樣\(N\)\(\tau\),去計算每一筆的這些值,把它全部加起來,就可以得到梯度。我們就可以去更新引數,就可以去更新智慧體,如下式所示。

\[\begin{aligned} E_{\tau \sim p_{\theta}(\tau)}\left[R(\tau) \nabla \log p_{\theta}(\tau)\right] & \approx \frac{1}{N} \sum_{n=1}^{N} R\left(\tau^{n}\right) \nabla \log p_{\theta}\left(\tau^{n}\right) \\ &=\frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_{n}} R\left(\tau^{n}\right) \nabla \log p_{\theta}\left(a_{t}^{n} \mid s_{t}^{n}\right) \end{aligned} \]

\(\nabla \log p_{\theta}(\tau)\) 的具體計算過程,如下式所示

\[\begin{aligned} \nabla \log p_{\theta}(\tau) &=\nabla\left(\log p\left(s_{1}\right)+\sum_{t=1}^{T} \log p_{\theta}\left(a_{t} \mid s_{t}\right)+\sum_{t=1}^{T} \log p\left(s_{t+1} \mid s_{t}, a_{t}\right)\right) \\ &=\nabla \log p\left(s_{1}\right)+\nabla \sum_{t=1}^{T} \log p_{\theta}\left(a_{t} \mid s_{t}\right)+\nabla \sum_{t=1}^{T} \log p\left(s_{t+1} \mid s_{t}, a_{t}\right) \\ &=\nabla \sum_{t=1}^{T} \log p_{\theta}\left(a_{t} \mid s_{t}\right) \\ &=\sum_{t=1}^{T} \nabla \log p_{\theta}\left(a_{t} \mid s_{t}\right) \end{aligned} \]

注意,\(p\left(s_{1}\right)\)\(p\left(s_{t+1} \mid s_{t}, a_{t}\right)\)來自於環境,

\(p_{\theta}\left(a_{t} \mid s_{t}\right)\)是來自於智慧體。\(p\left(s_{1}\right)\)\(p\left(s_{t+1} \mid s_{t}, a_{t}\right)\)由環境決定,所以與\(\theta\)無關,因此

\[\nabla \log p\left(s_{1}\right)=0 \]\[\nabla \sum_{t=1}^{T} \log p\left(s_{t+1} \mid s_{t}, a_{t}\right)=0 \]

我們可以直觀地來理解圖1最終推匯出來的公式,也就是在我們取樣到的資料裡面,取樣到在某一個狀態\(s_{t}\)要執行某一 個動作\(a_{t}\)\(s_{t}\)\(a_{t}\)它是在整個軌跡\(\tau\)的裡面的某一個狀態和動作的對。 假設我們在\(s_{t}\)執行\(a_{t}\),最後發現\(\tau\)的獎勵是正的,我們就要增加這一項的概率,就要增加在\(s_{t}\)執行\(a_{t}\)的概率。反之,在\(s_{t}\)執行\(a_{t}\)會導致\(\tau\) 的獎勵變成負的,我們就要減少這一項的概率。

要計算上式,首先我們要先收集一大堆的\(s\)\(a\)的對(pair),還要知道這些\(s\)\(a\)在跟環境互動的時候,會得到多少的獎勵。具體我們要拿智慧體,它的引數是\(\theta\),去跟環境做互動,互動完以後,就會得到一大堆遊戲的紀錄。

我們就可以把取樣到的資料代到梯度公式裡面,把梯度算出來。也就是把取樣到的資料中的每一個\(s\)\(a\)的對拿進來,算一下它的對數概率,也就是計算在某一個狀態採取某一個動作的對數概率,對它取梯度,這個梯度前面會乘一個權重,權重就是這場遊戲的獎勵。有了這些以後,就會去更新模型。

圖2. 策略梯度演算法

更新完模型以後,我們要重新去收集資料再更新模型。注意,一般策略梯度取樣的資料就只會用一次。把這些資料取樣起來,然後拿去更新引數,這些資料就丟掉了。接著再重新取樣資料,才能夠去更新引數。不過這也是有解決方法的,接下來我們會介紹如何解決。

2.3 技巧

(1)增加基線

  • 在很多遊戲中,得到的獎勵總是正的,或者說最低也就是0。由於採取行動的概率和為1,當所有的reward都為正的時候,可能存在進行歸一化後,\(R\)(權重)大的上升的多,\(R\)小的,歸一化後它就是下降的。比如下面這種情況,假設某一狀態下有三個動作,分別是\(a\),\(b\),\(c\),獎勵都是正的。根據公式\(\nabla \bar{R}_{\theta}\),我們希望將這三個動作的概率以及對數概率都拉高,但是它們前面的權重\(R\)不一樣,有大有小,所以權重大的,上升的多一點;權重小的,上升的少一些,又因為對數概率是一個概率,三個動作的和要為0,那麼在做完歸一化後,上升多的才會上升,上升的少的就是下降的。
圖3. 新增基線
  • 取樣應該是一個期望,對所有可能的\(s\)\(a\)的對進行求和。但真正在學習時,只是取樣了少量的\(s\)\(a\)的對而已。有一些動作可能從來都沒有采樣到。同樣假設在某一個狀態可以執行的動作有\(a\),\(b\),\(c\),但你可能只採樣到動作\(b\)或者只採樣到動作\(c\),沒有采樣到動作\(a\)。 現在所有動作的獎勵都是正的,根據公式\(\nabla \bar{R}_{\theta}\),它的每一項概率都應上升。因為\(a\)沒有被取樣到,其它動作的概率如果都要上升,\(a\)的概率就下降。但\(a\)不一定是一個不好的動作,它只是沒被取樣到。但概率卻下降了,這顯然是有問題的,所以我們希望獎勵不要總是正的。

為了解決獎勵總是正的這一問題,可以把獎勵減掉一項\(b\),如下式所示。

\[\nabla \bar{R}_{\theta} \approx \frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_{n}}\left(R\left(\tau^{n}\right)-b\right) \nabla \log p_{\theta}\left(a_{t}^{n} \mid s_{t}^{n}\right) \]

其中,\(b\)叫做基線,減掉\(b\)以後,就可以讓\(R\left(\tau^{n}\right)-b\)這一項有正有負。所以如果得到的總獎勵\(R\left(\tau^{n}\right)\)大於\(b\)的話,就讓它的概率上升。如果這個總獎勵小於\(b\),就算它是正的,正的很小也是不好的,就要讓這一項的概率下降。\(b\)通常是把\(\tau^{n}\)的值取期望,算一下\(\tau^{n}\)的平均值,即\(b \approx E[R(\tau)]\)。在實際訓練的時候,不斷地把\(R\left(\tau^{n}\right)\)的分數記錄下來,不斷地計算\(R\left(\tau^{n}\right)\)的平均值當作\(b\)

(2)分配合適的分數

  • 在同一場遊戲或者同一個回合中,所有的狀態跟動作的對都會使用同樣的獎勵項進行加權,這不公平,因為在同一場遊戲中也許有些動作是好的,有些動作是不好的。假設整場遊戲的結果是好的,但不代表每一個動作都是對的,反之,也是。舉個例子,假設遊戲很短,在\(s_{1}\)執行\(a_{1}\)的獎勵\(r_{1}\)是5,在\(s_{2}\)執行\(a_{2}\)的獎勵\(r_{2}\)是0,在\(s_{3}\)執行\(a_{3}\)的獎勵\(r_{3}\)是-2。整場遊戲結束,總獎勵為3。但不代表在\(s_{2}\)執行動作\(a_{2}\)是好的,因為這個正的分數,主要來自於在\(s_{1}\)執行了\(a_{1}\), 跟在\(s_{2}\)執行\(a_{2}\)是沒有關係的,也許在\(s_{2}\)執行\(a_{2}\)反而是不好的,因為它導致你接下來會進入\(s_{3}\),執行\(s_{3}\)被扣分,所以整場遊戲得到的結果是好的,並不代表每一個動作都是對的。因此在訓練的時候,每一個狀態跟動作的對,都會被乘上3。

在理想的狀況下,這個問題,如果取樣夠多是可以被解決的。但現在的問題是我們取樣的次數不夠多,所以我們計算這個狀態-動作對的獎勵的時候,不把整場遊戲得到的獎勵全部加起來,只計算從這個動作執行以後所得到的獎勵。因為這場遊戲在執行這個動作之前發生的事情是跟執行這個動作是沒有關係的,所以在執行這個動作之前得到多少獎勵都不能算是這個動作的功勞。跟這個動作有關的東西,只有在執行這個動作以後發生的所有的獎勵把它加起來,才是這個動作真正的貢獻。如下式。

\[\nabla \bar{R}_{\theta} \approx \frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_{n}}\left(\sum_{t^{\prime}=t}^{T_{n}} \gamma^{t^{\prime}-t} r_{t^{\prime}}^{n}-b\right) \nabla \log p_{\theta}\left(a_{t}^{n} \mid s_{t}^{n}\right) \]

我們對未來的獎勵做了一個折扣,因為時間越久,獎勵的重要性就越小,折扣因子\(\gamma\)\(\gamma \in[0,1]\),一般設定為0.9或0.99,如果\(\gamma\)=0,這表示只關心即時獎勵;如果 \(\gamma\)=1, 這表示未來獎勵等同於即時獎勵。

舉個例子大家就明白了,比如現在給你100塊錢,和過個10年再把這個100塊錢給你,你願意選擇哪一個,當然是前者啦,10年後的100塊錢,有可能就相當於現在的10塊錢的價值了。換句話說,60年代的1塊錢和現在的1塊錢的價值是一樣的嗎?

3.程式碼實現

案例:模擬登月小艇降落在月球表面時的情形。任務的目標是讓登月小艇安全地降落在兩個黃色旗幟間的平地上。測試環境:LunarLander-v2

Obs:這個遊戲環境有八個觀測值,分別是水平座標x,垂直座標y,水平速度,垂直速度,角度,角速度,腿1觸地,腿2觸地;

Action:agent可以採取四種離散行動,分別是什麼都不做,發動左方向引擎噴射,發動主引擎向下噴射,發動右方向引擎噴射。

Reward:小艇墜毀得-100分;小艇成功著陸在兩個黃色旗幟之間得100~140分;噴射主引擎向下噴火每次得-0.3分;小艇最終完全靜止則再得100分;每條腿著地各得10分。

這裡我們雖然採用的是離散的動作空間,但是整體程式碼是相差不大的,感興趣的同學可以嘗試下連續的動作空間。

定義網路結構:

class PolicyNet(nn.Module):
    def __init__(self, n_states_num, n_actions_num, hidden_size):
        super(PolicyNet, self).__init__()
        self.data = []  # 儲存軌跡
        # 輸入為長度為8的向量 輸出為4個動作
        self.net = nn.Sequential(
            # 兩個線性層,中間使用Relu啟用函式連線,最後連線softmax輸出每個動作的概率
            nn.Linear(in_features=n_states_num, out_features=hidden_size, bias=False),
            nn.ReLU(),
            nn.Linear(in_features=hidden_size, out_features=n_actions_num, bias=False),
            nn.Softmax(dim=1)
        )

    def forward(self, inputs):
        # 狀態輸入s的shape為向量:[8]
        x = self.net(inputs)
        return x

定義PG類:

class PolicyGradient():

    def __init__(self, n_states_num, n_actions_num, learning_rate=0.01, reward_decay=0.95 ):
        # 狀態數   state是一個8維向量,分別是水平座標x,垂直座標y,水平速度,垂直速度,角度,角速度,腿1觸地,腿2觸地
        self.n_states_num = n_states_num
        # action是4維、離散,即什麼都不做,發動左方向引擎,發動主機,發動右方向引擎。
        self.n_actions_num = n_actions_num
        # 學習率
        self.lr = learning_rate
        # gamma
        self.gamma = reward_decay
        # 網路
        self.pi = PolicyNet(n_states_num, n_actions_num, 128)
        # 優化器
        self.optimizer = torch.optim.Adam(self.pi.parameters(), lr=learning_rate)
        # 儲存軌跡  儲存方式為  (每一次的reward,動作的概率)
        self.data = []
        self.cost_his = []

    # 儲存軌跡資料
    def put_data(self, item):
        # 記錄r,log_P(a|s)z
        self.data.append(item)

    def train_net(self):
        # 計算梯度並更新策略網路引數。tape為梯度記錄器
        R = 0  # 終結狀態的初始回報為0
        policy_loss = []
        for r, log_prob in self.data[::-1]:  # 逆序取
            R = r + gamma * R  # 計算每個時間戳上的回報
            # 每個時間戳都計算一次梯度
            loss = -log_prob * R
            policy_loss.append(loss)
        self.optimizer.zero_grad()
        policy_loss = torch.cat(policy_loss).sum()  # 求和
        # print('policy_loss:', policy_loss.item())
        # 反向傳播
        policy_loss.backward()
        self.optimizer.step()
        self.cost_his.append(policy_loss.item())
        # print('cost_his:', self.cost_his)
        self.data = []  # 清空軌跡

    # 將狀態傳入神經網路 根據概率選擇動作
    def choose_action(self, state):
        # 將state轉化成tensor 並且維度轉化為[8]->[1,8]  
        s = torch.Tensor(state).unsqueeze(0)
        prob = self.pi(s)  # 動作分佈:[0,1,2,3]
        # 從類別分佈中取樣1個動作, shape: [1] torch.log(prob), 1
        
        # 作用是建立以引數prob為標準的類別分佈,樣本是來自“0 … K-1”的整數,其中K是prob引數的長度。也就是說,按照傳入的prob中給定的概率,
        # 在相應的位置處進行取樣,取樣返回的是該位置的整數索引。不是最大的,是按照概率取樣的那個,取樣到那個就是哪個的索引
        m = torch.distributions.Categorical(prob)  # 生成分佈
        action = m.sample()
        return action.item(), m.log_prob(action)
    def plot_cost(self, avage_reward):
        import matplotlib.pyplot as plt
        plt.plot(np.arange(len(avage_reward)), avage_reward)
        plt.ylabel('Reward')
        plt.xlabel('training steps')
        plt.show()

訓練模型:

   for n_epi in range(1000):

        state = env.reset()  # 回到遊戲初始狀態,返回s0
        ep_reward = 0
        for t in range(1001):  # CartPole-v1 forced to terminates at 1000 step.
            # 根據狀態 傳入神經網路 選擇動作
            action, log_prob = policyGradient.choose_action2(state)
            # print(action, log_prob)
            # 與環境互動
            s_prime, reward, done, info = env.step(action)
            # s_prime, reward, done, info = env.step(action)
            if n_epi > 1000:
                env.render()
            # 記錄動作a和動作產生的獎勵r
            policyGradient.put_data((reward, log_prob))
            state = s_prime  # 重新整理狀態
            ep_reward += reward
            if done:  # 當前episode終止
                break
            # episode終止後,訓練一次網路
        running_reward = 0.05 * ep_reward + (1 - 0.05) * running_reward
        avage_reward.append(running_reward)
        # 互動完成後 進行學習
        policyGradient.train_net()
        if n_epi % print_interval == 0:
            print('Episode {}\tLast reward: {:.2f}\tAverage reward: {:.2f}'.format(
                n_epi, ep_reward, running_reward))
        if running_reward > env.spec.reward_threshold:  # 大於遊戲的最大閾值時,退出遊戲
            print("Solved! Running reward is now {} and "
                  "the last episode runs to {} time steps!".format(running_reward, t))
            torch.save(policyGradient.pi.state_dict(), 'pg.pkl')
            break
    policyGradient.plot_cost(avage_reward)

最後得到Reward的曲線如下圖,趨於收斂:

圖4. Reward曲線圖

最後的動畫效果如下圖:

圖5. 結果演示圖

4.總結

策略梯度可以很好的解決具有連續動作空間的場景,可以學習到一些隨機策略,有時是最優策略。可能還會有較好的收斂性,但也有可能收斂到區域性最優,而不是全域性最優,評價策略的過程有時也會比較低效,方差很大。不過總體還是不錯的,之後我們再介紹相對更好的演算法來解決這些缺點。

5.參考文獻

[1]《Reinforcement+Learning: An+Introduction》

[2] https://blog.csdn.net/baidu_41871794/article/details/111057371

我們是行者AI,我們在“AI+遊戲”中不斷前行。

快來【公眾號 | 行者AI】,和我們討論更多技術問題吧!