《Scikit-Learn與TensorFlow機器學習實用指南》 第16章 強化學習(上)

強化學習(RL)如今是機器學習的一大令人激動的領域,當然之前也是。自從 1950 年被髮明出來後,它在這些年產生了一些有趣的應用,尤其是在遊戲(例如 TD-Gammon,一個西洋雙陸棋程序)和機器控制領域,但是從未弄出什麼大新聞。直到 2013 年一個革命性的發展:來自英國的研究者發起了Deepmind 項目,這個項目可以學習去玩任何從頭開始的 Atari 遊戲,在多數遊戲中,比人類玩的還好,它僅使用像素作爲輸入而沒有使用遊戲規則的任何先驗知識。這是一系列令人驚歎的壯舉中的第一個,並在 2016 年 3 月以他們的系統阿爾法狗戰勝了世界圍棋冠軍李世石而告終。從未有程序能勉強打敗這個遊戲的大師,更不用說世界冠軍了。今天,RL 的整個領域正在沸騰着新的想法,其都具有廣泛的應用範圍。DeepMind 在 2014 被谷歌以超過 5 億美元收購。

那麼他們是怎麼做到的呢?事後看來,原理似乎相當簡單:他們將深度學習運用到強化學習領域,結果卻超越了他們最瘋狂的設想。在本章中,我們將首先解釋強化學習是什麼,以及它擅長於什麼,然後我們將介紹兩個在深度強化學習領域最重要的技術:策略梯度和深度 Q 網絡(DQN),包括討論馬爾可夫決策過程(MDP)。我們將使用這些技術來訓練一個模型來平衡移動車上的杆子,另一個玩 Atari 遊戲。同樣的技術可以用於各種各樣的任務,從步行機器人到自動駕駛汽車。

學習優化獎勵

在強化學習中,智能體在環境(environment)中觀察(observation)並且做出決策(action),隨後它會得到獎勵(reward)。它的目標是去學習如何行動能最大化期望獎勵。如果你不在意去擬人化的話,你可以認爲正獎勵是愉快,負獎勵是痛苦(這樣的話獎勵一詞就有點誤導了)。簡單來說,智能體在環境中行動,並且在實驗和錯誤中去學習最大化它的愉快,最小化它的痛苦。

這是一個相當廣泛的設置,可以適用於各種各樣的任務。以下是幾個例子(詳見圖 16-1):

  1. 智能體可以是控制一個機械狗的程序。在此例中,環境就是真實的世界,智能體通過許多的傳感器例如攝像機或者傳感器來觀察,它可以通過給電機發送信號來行動。它可以被編程設置爲如果到達了目的地就得到正獎勵,如果浪費時間,或者走錯方向,或摔倒了就得到負獎勵。
  2. 智能體可以是控制 MS.Pac-Man 的程序。在此例中,環境是 Atari 遊戲的仿真,行爲是 9 個操縱桿位(上下左右中間等等),觀察是屏幕,回報就是遊戲點數。
  3. 相似地,智能體也可以是棋盤遊戲的程序例如:圍棋。
  4. 智能體也可以不用去控制一個實體(或虛擬的)去移動。例如它可以是一個智能程序,當它調整到目標溫度以節能時會得到正獎勵,當人們需要自己去調節溫度時它會得到負獎勵,所以智能體必須學會預見人們的需要。
  5. 智能體也可以去觀測股票市場價格以實時決定買賣。獎勵的依據顯然爲掙錢或者賠錢。

圖16-1 強化學習案例:(a)行走機器人,(b)MS.Pac-Man遊戲,(c)圍棋玩家,(d)恆溫器,(e)自動交易員

其實沒有正獎勵也是可以的,例如智能體在迷宮內移動,它每分每秒都得到一個負獎勵,所以它要儘可能快的找到出口!還有很多適合強化學習的領域,例如自動駕駛汽車,在網頁上放廣告,或者控制一個圖像分類系統讓它明白它應該關注於什麼。

策略搜索

被智能體使用去改變它行爲的算法叫做策略。例如,策略可以是一個把觀測當輸入,行爲當做輸出的神經網絡(見圖16-2)。

圖16-2 用神經網絡策略做加強學習

這個策略可以是你能想到的任何算法,它甚至可以不被確定。舉個例子,例如,考慮一個真空吸塵器,它的獎勵是在 30 分鐘內撿起的灰塵數量。它的策略可以是每秒以概率P向前移動,或者以概率1-P隨機地向左或向右旋轉。旋轉角度將是-R+R之間的隨機角度,因爲該策略涉及一些隨機性,所以稱爲隨機策略。機器人將有一個不確定的軌跡,它保證它最終會到達任何可以到達的地方,並撿起所有的灰塵。問題是:30分鐘後它會撿起多少灰塵?

你怎麼訓練這樣的機器人?你可以調整兩個策略參數:概率P和角度範圍R。一個想法是這些參數嘗試許多不同的值,並選擇執行最佳的組合(見圖 16-3)。這是一個策略搜索的例子,在這種情況下使用野蠻的方法。然而,當策略空間太大(通常情況下),以這樣的方式找到一組好的參數就像是大海撈針。

圖16-3 策略空間中的四個點以及機器人的對應行爲

另一種搜尋策略空間的方法是遺傳算法。例如你可以隨機創造一個包含 100 個策略的第一代基因,隨後殺死 80 個糟糕的策略,隨後讓 20 個倖存策略繁衍 4 代。一個後代只是它父輩基因的複製品加上一些隨機變異。倖存的策略加上他們的後代共同構成了第二代。你可以繼續以這種方式迭代代,直到找到一個好的策略。

另一種方法是使用優化技術,通過評估獎勵關於策略參數的梯度,然後通過跟隨梯度向更高的獎勵(梯度上升)調整這些參數。這種方法被稱爲策略梯度(policy gradient, PG),我們將在本章後面詳細討論。例如,回到真空吸塵器機器人,你可以稍微增加概率P並評估這是否增加了機器人在 30 分鐘內拾起的灰塵的量;如果確實增加了,就相對應增加P,否則減少P。我們將使用 Tensorflow 來實現 PG 算法,但是在這之前我們需要爲智能體創造一個生存的環境,所以現在是介紹 OpenAI 的時候了。

OpenAI 介紹

強化學習的一個挑戰是,爲了訓練對象,首先需要有一個工作環境。如果你想設計一個可以學習 Atari 遊戲的程序,你需要一個 Atari 遊戲模擬器。如果你想設計一個步行機器人,那麼環境就是真實的世界,你可以直接在這個環境中訓練你的機器人,但是這有其侷限性:如果機器人從懸崖上掉下來,你不能僅僅點擊“撤消”。你也不能加快時間;增加更多的計算能力不會讓機器人移動得更快。一般來說,同時訓練 1000 個機器人是非常昂貴的。簡而言之,訓練在現實世界中是困難和緩慢的,所以你通常需要一個模擬環境,至少需要引導訓練。

OpenAI gym 是一個工具包,它提供各種各樣的模擬環境(Atari 遊戲,棋盤遊戲,2D 和 3D 物理模擬等等),所以你可以訓練,比較,或開發新的 RL 算法。

讓我們安裝 OpenAI gym。可通過pip安裝:

$ pip install --upgrade gym

接下來打開 Python shell 或 Jupyter 筆記本創建您的第一個環境:

>>> import gym 
>>> env = gym.make("CartPole-v0") 
[2016-10-14 16:03:23,199] Making new env: MsPacman-v0 
>>> obs = env.reset() 
>>> obs 
array([-0.03799846,-0.03288115,0.02337094,0.00720711]) 
>>> env.render() 

使用make()函數創建一個環境,在此例中是 CartPole 環境。這是一個 2D 模擬,其中推車可以被左右加速,以平衡放置在它上面的平衡杆(見圖 16-4)。在創建環境之後,我們需要使用reset()初始化。這會返回第一個觀察結果。觀察取決於環境的類型。對於 CartPole 環境,每個觀測是包含四個浮點的 1D Numpy 向量:這些浮點數代表推車的水平位置(0 爲中心)、其速度、杆的角度(0 維垂直)及其角速度。最後,render()方法顯示如圖 16-4 所示的環境。

圖16-4 CartPole環境

如果你想讓render()讓圖像以一個 Numpy 數組格式返回,可以將mode參數設置爲rgb_array(注意其他環境可能支持不同的模式):

>>> img = env.render(mode="rgb_array") 
>>> img.shape  # height, width, channels (3=RGB) 
(400, 600, 3)

不幸的是,即使將mode參數設置爲rgb_array,CartPole(和其他一些環境)還是會將將圖像呈現到屏幕上。避免這種情況的唯一方式是使用一個 fake X 服務器,如 XVFB 或 XDimMy。例如,可以使用以下命令安裝 XVFB 和啓動 Python:xvfb-run -s "screen 0 1400x900x24" python。或者使用xvfbwrapper包。

讓我們來詢問環境什麼動作是可能的:

>>> env.action_space 
Discrete(2)

Discrete(2)表示可能的動作是整數 0 和 1,表示向左(0)或右(1)的加速。其他環境可能有更多的動作,或者其他類型的動作(例如,連續的)。因爲杆子向右傾斜,讓我們向右加速推車:

>>> action = 1  # accelerate right 
>>> obs, reward, done, info = env.step(action) 
>>> obs 
array([-0.03865608,  0.16189797,  0.02351508, -0.27801135]) 
>>> reward 
1.0 
>>> done 
False 
>>> info 
{} 

step()表示執行給定的動作並返回四個值:

obs:

這是新的觀測,小車現在正在向右走(obs[1]>0,注:當前速度爲正,向右爲正)。平衡杆仍然向右傾斜(obs[2]>0),但是他的角速度現在爲負(obs[3]<0),所以它在下一步後可能會向左傾斜。

reward

在這個環境中,無論你做什麼,每一步都會得到 1.0 獎勵,所以遊戲的目標就是儘可能長的運行。

done

當遊戲結束時這個值會爲True。當平衡杆傾斜太多時會發生這種情況。之後,必須重新設置環境才能重新使用。

info

該字典可以在其他環境中提供額外的調試信息。這些數據不應該用於訓練(這是作弊)。

讓我們硬編碼一個簡單的策略,當杆向左傾斜時加速左邊,當杆向右傾斜時加速。我們使用這個策略來獲得超過 500 步的平均回報:

def basic_policy(obs):    
    angle = obs[2]    
    return 0 if angle < 0 else 1

totals = [] 
for episode in range(500):    
    episode_rewards = 0    
    obs = env.reset()    
    for step in range(1000): # 最多1000 步,我們不想讓它永遠運行下去        
        action = basic_policy(obs)        
        obs, reward, done, info = env.step(action)        
        episode_rewards += reward        
        if done:            
            break    
    totals.append(episode_rewards)

這個代碼希望能自我解釋。讓我們看看結果:

>>> import numpy as np 
>>> np.mean(totals), np.std(totals), np.min(totals), np.max(totals) 
(42.125999999999998, 9.1237121830974033, 24.0, 68.0)

即使有 500 次嘗試,這一策略從未使平衡杆在超過 68 個連續的步驟裏保持直立。這不太好。如果你看一下 Juyter Notebook 中的模擬,你會發現,推車越來越強烈地左右擺動,直到平衡杆傾斜太多。讓我們看看神經網絡是否能提出更好的策略。

神經網絡策略

讓我們創建一個神經網絡策略。就像之前我們編碼的策略一樣,這個神經網絡將把觀察作爲輸入,輸出要執行的動作。更確切地說,它將估計每個動作的概率,然後我們將根據估計的概率隨機地選擇一個動作(見圖 16-5)。在 CartPole 環境中,只有兩種可能的動作(左或右),所以我們只需要一個輸出神經元。它將輸出動作 0(左)的概率p,動作 1(右)的概率顯然將是1 - p

例如,如果它輸出 0.7,那麼我們將以 70% 的概率選擇動作 0,以 30% 的概率選擇動作 1。

圖16-5 神經網絡策略

你可能奇怪爲什麼我們根據神經網絡給出的概率來選擇隨機的動作,而不是選擇最高分數的動作。這種方法使智能體在探索新的行爲利用那些已知可行的行動之間找到正確的平衡。舉個例子:假設你第一次去餐館,所有的菜看起來同樣吸引人,所以你隨機挑選一個。如果菜好吃,你可以增加下一次點它的概率,但是你不應該把這個概率提高到 100%,否則你將永遠不會嘗試其他菜餚,其中一些甚至比你嘗試的更好。

還要注意,在這個特定的環境中,過去的動作和觀察可以被安全地忽略,因爲每個觀察都包含環境的完整狀態。如果有一些隱藏狀態,那麼你也需要考慮過去的行爲和觀察。例如,如果環境僅僅揭示了推車的位置,而不是它的速度,那麼你不僅要考慮當前的觀測,還要考慮先前的觀測,以便估計當前的速度。另一個例子是當觀測是有噪聲的的,在這種情況下,通常你想用過去的觀察來估計最可能的當前狀態。因此,CartPole 問題是簡單的;觀測是無噪聲的,而且它們包含環境的全狀態。

import tensorflow as tf 
from tensorflow.contrib.layers import fully_connected
# 1. 聲明神經網絡結構 
n_inputs = 4  # == env.observation_space.shape[0] 
n_hidden = 4  # 這只是個簡單的測試,不需要過多的隱藏層
n_outputs = 1 # 只輸出向左加速的概率
initializer = tf.contrib.layers.variance_scaling_initializer()
# 2. 建立神經網絡 
X = tf.placeholder(tf.float32, shape=[None, n_inputs]) hidden = fully_connected(X, n_hidden, activation_fn=tf.nn.elu,weights_initializer=initializer) # 隱層激活函數使用指數線性函數                
logits = fully_connected(hidden, n_outputs, activation_fn=None,weights_initializer=initializer)                   
outputs = tf.nn.sigmoid(logits)
# 3. 在概率基礎上隨機選擇動作
p_left_and_right = tf.concat(axis=1, values=[outputs, 1 - outputs]) 
action = tf.multinomial(tf.log(p_left_and_right), num_samples=1)
init = tf.global_variables_initializer() 

讓我們通讀代碼:

  1. 在導入之後,我們定義了神經網絡體系結構。輸入的數量是觀測空間的大小(在 CartPole 的情況下是 4 個),我們只有 4 個隱藏單元,並且不需要更多,並且我們只有 1 個輸出概率(向左的概率)。
  2. 接下來我們構建了神經網絡。在這個例子中,它是一個 vanilla 多層感知器,只有一個輸出。注意,輸出層使用 Logistic(Sigmoid)激活函數,以便輸出從 0 到 1 的概率。如果有兩個以上的可能動作,每個動作都會有一個輸出神經元,相應的你將使用 Softmax 激活函數。
  3. 最後,我們調用multinomial()函數來選擇一個隨機動作。該函數獨立地採樣一個(或多個)整數,給定每個整數的對數概率。例如,如果通過設置num_samples=5,令數組爲[np.log(0.5), np.log(0.2), np.log(0.3)]來調用它,那麼它將輸出五個整數,每個整數都有 50% 的概率是 0,20% 爲 1,30% 爲 2。在我們的情況下,我們只需要一個整數來表示要採取的行動。由於輸出張量(output)僅包含向左的概率,所以我們必須首先將 1 - output 連接它,以得到包含左和右動作的概率的張量。請注意,如果有兩個以上的可能動作,神經網絡將不得不輸出每個動作的概率,這時你就不需要連接步驟了。

好了,現在我們有一個可以觀察和輸出動作的神經網絡了,那我們怎麼訓練它呢?

評價行爲:信用分配問題

如果我們知道每一步的最佳動作,我們可以像通常一樣訓練神經網絡,通過最小化估計概率和目標概率之間的交叉熵。這只是通常的監督學習。然而,在強化學習中,智能體獲得的指導的唯一途徑是通過獎勵,獎勵通常是稀疏的和延遲的。例如,如果智能體在 100 個步驟內設法平衡杆,它怎麼知道它採取的 100 個行動中的哪一個是好的,哪些是壞的?它所知道的是,在最後一次行動之後,杆子墜落了,但最後一次行動肯定不是完全負責的。這被稱爲信用分配問題:當智能體得到獎勵時,很難知道哪些行爲應該被信任(或責備)。想想一隻狗在行爲良好後幾小時就會得到獎勵,它會明白它得到了什麼回報嗎?

爲了解決這個問題,一個通常的策略是基於這個動作後得分的總和來評估這個個動作,通常在每個步驟中應用衰減率r。例如(見圖 16-6),如果一個智能體決定連續三次向右,在第一步之後得到 +10 獎勵,第二步後得到 0,最後在第三步之後得到 -50,然後假設我們使用衰減率r=0.8,那麼第一個動作將得到10 +r×0 + r2×(-50)=-22的分述。如果衰減率接近 0,那麼與即時獎勵相比,未來的獎勵不會有多大意義。相反,如果衰減率接近 1,那麼對未來的獎勵幾乎等於即時回報。典型的衰減率通常爲是 0.95 或 0.99。如果衰減率爲 0.95,那麼未來 13 步的獎勵大約是即時獎勵的一半(0.9513×0.5),而當衰減率爲 0.99,未來 69 步的獎勵是即時獎勵的一半。在 CartPole 環境下,行爲具有相當短期的影響,因此選擇 0.95 的折扣率是合理的。

圖16-6 獎勵打折

當然,一個好的動作可能會伴隨着一些壞的動作,這些動作會導致平衡杆迅速下降,從而導致一個好的動作得到一個低分數(類似的,一個好行動者有時會在一部爛片中扮演主角)。然而,如果我們花足夠多的時間來訓練遊戲,平均下來好的行爲會得到比壞的更好的分數。因此,爲了獲得相當可靠的動作分數,我們必須運行很多次並將所有動作分數歸一化(通過減去平均值併除以標準偏差)。之後,我們可以合理地假設消極得分的行爲是壞的,而積極得分的行爲是好的。現在我們有一個方法來評估每一個動作,我們已經準備好使用策略梯度來訓練我們的第一個智能體。讓我們看看如何。

策略梯度

正如前面所討論的,PG 算法通過遵循更高回報的梯度來優化策略參數。一種流行的 PG 算法,稱爲增強算法,在 1929 由 Ronald Williams 提出。這是一個常見的變體:

  1. 首先,讓神經網絡策略玩幾次遊戲,並在每一步計算梯度,這使得智能體更可能選擇行爲,但不應用這些梯度。
  2. 運行幾次後,計算每個動作的得分(使用前面段落中描述的方法)。
  3. 如果一個動作的分數是正的,這意味着動作是好的,可應用較早計算的梯度,以便將來有更大的的概率選擇這個動作。但是,如果分數是負的,這意味着動作是壞的,要應用負梯度來使得這個動作在將來採取的可能性更低。我們的方法就是簡單地將每個梯度向量乘以相應的動作得分。
  4. 最後,計算所有得到的梯度向量的平均值,並使用它來執行梯度下降步驟。

讓我們使用 TensorFlow 實現這個算法。我們將訓練我們早先建立的神經網絡策略,讓它學會平衡車上的平衡杆。讓我們從完成之前編碼的構造階段開始,添加目標概率、代價函數和訓練操作。因爲我們的意願是選擇的動作是最好的動作,如果選擇的動作是動作 0(左),則目標概率必須爲 1,如果選擇動作 1(右)則目標概率爲 0:

y = 1. - tf.to_float(action) 

現在我們有一個目標概率,我們可以定義損失函數(交叉熵)並計算梯度:

learning_rate = 0.01
cross_entropy = tf.nn.sigmoid_cross_entropy_with_logits(                    labels=y, logits=logits) 
optimizer = tf.train.AdamOptimizer(learning_rate) 
grads_and_vars = optimizer.compute_gradients(cross_entropy)

注意,我們正在調用優化器的compute_gradients()方法,而不是minimize()方法。這是因爲我們想要在使用它們之前調整梯度。compute_gradients()方法返回梯度向量/變量對的列表(每個可訓練變量一對)。讓我們把所有的梯度放在一個列表中,以便方便地獲得它們的值:

gradients = [grad for grad, variable in grads_and_vars] 

好,現在是棘手的部分。在執行階段,算法將運行策略,並在每個步驟中評估這些梯度張量並存儲它們的值。在多次運行之後,它如先前所解釋的調整這些梯度(即,通過動作分數乘以它們並使它們歸一化),並計算調整後的梯度的平均值。接下來,需要將結果梯度反饋到優化器,以便它可以執行優化步驟。這意味着對於每一個梯度向量我們需要一個佔位符。此外,我們必須創建操作去應用更新的梯度。爲此,我們將調用優化器的apply_gradients()函數,該函數接受梯度向量/變量對的列表。我們不給它原始的梯度向量,而是給它一個包含更新梯度的列表(即,通過佔位符遞送的梯度):

gradient_placeholders = [] 
grads_and_vars_feed = [] 
for grad, variable in grads_and_vars:       
    gradient_placeholder = tf.placeholder(tf.float32, shape=grad.get_shape())    
    gradient_placeholders.append(gradient_placeholder)  
    grads_and_vars_feed.append((gradient_placeholder, variable))
training_op = optimizer.apply_gradients(grads_and_vars_feed) 

讓我們後退一步,看看整個運行過程:

n_inputs = 4 
n_hidden = 4 
n_outputs = 1 
initializer = tf.contrib.layers.variance_scaling_initializer()

learning_rate = 0.01
X = tf.placeholder(tf.float32, shape=[None, n_inputs]) 
hidden = fully_connected(X, n_hidden, activation_fn=tf.nn.elu,weights_initializer=initializer)                          
logits = fully_connected(hidden, n_outputs, activation_fn=None,                    weights_initializer=initializer) 
outputs = tf.nn.sigmoid(logits) 
p_left_and_right = tf.concat(axis=1, values=[outputs, 1 - outputs]) 
action = tf.multinomial(tf.log(p_left_and_right), num_samples=1)

y = 1. - tf.to_float(action) 
cross_entropy = tf.nn.sigmoid_cross_entropy_with_logits(labels=y, logits=logits) 
optimizer = tf.train.AdamOptimizer(learning_rate) 
grads_and_vars = optimizer.compute_gradients(cross_entropy) 
gradients = [grad for grad, variable in grads_and_vars] 
gradient_placeholders = [] 
grads_and_vars_feed = [] 
for grad, variable in grads_and_vars:    
gradient_placeholder = tf.placeholder(tf.float32, shape=grad.get_shape())    gradient_placeholders.append(gradient_placeholder)    
grads_and_vars_feed.append((gradient_placeholder, variable)) 
training_op = optimizer.apply_gradients(grads_and_vars_feed)

init = tf.global_variables_initializer() 
saver = tf.train.Saver()

到執行階段了!我們將需要兩個函數來計算總折扣獎勵,給予原始獎勵,以及歸一化多次循環的結果:

def discount_rewards(rewards, discount_rate):    
    discounted_rewards = np.empty(len(rewards))    
    cumulative_rewards = 0    
    for step in reversed(range(len(rewards))):        
        cumulative_rewards = rewards[step] + cumulative_rewards * discount_rate       discounted_rewards[step] = cumulative_rewards    
    return discounted_rewards

def discount_and_normalize_rewards(all_rewards, discount_rate):       
    all_discounted_rewards = [discount_rewards(rewards) for rewards in all_rewards]       
    flat_rewards = np.concatenate(all_discounted_rewards)    
    reward_mean = flat_rewards.mean()    
    reward_std = flat_rewards.std()    
    return [(discounted_rewards - reward_mean)/reward_std  for discounted_rewards in all_discounted_rewards] 

讓我們檢查一下運行的如何:

>>> discount_rewards([10, 0, -50], discount_rate=0.8) 
array([-22., -40., -50.]) 
>>> discount_and_normalize_rewards([[10, 0, -50], [10, 20]], discount_rate=0.8) 
[array([-0.28435071, -0.86597718, -1.18910299]), array([ 1.26665318,  1.0727777 ])] 

discount_rewards()的調用正好返回我們所期望的(見圖 16-6)。你也可以驗證函數iscount_and_normalize_rewards()確實返回了兩個步驟中每個動作的標準化分數。注意第一步比第二步差很多,所以它的歸一化分數都是負的;從第一步開始的所有動作都會被認爲是壞的,反之,第二步的所有動作都會被認爲是好的。

我們現在有了訓練策略所需的一切:

n_iterations = 250      # 訓練迭代次數 
n_max_steps = 1000      # 每一次的最大步長 
n_games_per_update = 10 # 每迭代十次訓練一次策略網絡 
save_iterations = 10    # 每十次迭代保存模型
discount_rate = 0.95
with tf.Session() as sess:    
    init.run()    
    for iteration in range(n_iterations):        
        all_rewards = []    #每一次的所有獎勵        
        all_gradients = []  #每一次的所有梯度        
        for game in range(n_games_per_update):            
            current_rewards = []   #當前步的所有獎勵        
            current_gradients = [] #當前步的所有梯度 
            obs = env.reset()            
            for step in range(n_max_steps):                
                action_val, gradients_val = sess.run([action, gradients],
                feed_dict={X: obs.reshape(1, n_inputs)}) # 一個obs                
                obs, reward, done, info = env.step(action_val[0][0])                current_rewards.append(reward)                
                current_gradients.append(gradients_val)                
                if done:                    
                    break            
                all_rewards.append(current_rewards)            
                all_gradients.append(current_gradients)
        # 此時我們每10次運行一次策略,我們已經準備好使用之前描述的算法去更新策略,注:即使用迭代10次的結果來優化當前的策略。      
        all_rewards = discount_and_normalize_rewards(all_rewards)        
        feed_dict = {}        
        for var_index, grad_placeholder in enumerate(gradient_placeholders):
            # 將梯度與行爲分數相乘,並計算平均值
            mean_gradients = np.mean([reward * all_gradients[game_index][step][var_index] for game_index, rewards in enumerate(all_rewards)  for step, reward in enumerate(rewards)],axis=0)            
            feed_dict[grad_placeholder] = mean_gradients        
        sess.run(training_op, feed_dict=feed_dict)  
        if iteration % save_iterations == 0:           
            saver.save(sess, "./my_policy_net_pg.ckpt")

每一次訓練迭代都是通過運行10次的策略開始的(每次最多 1000 步,以避免永遠運行)。在每一步,我們也計算梯度,假設選擇的行動是最好的。在運行了這 10 次之後,我們使用discount_and_normalize_rewards()函數計算動作得分;我們遍歷每個可訓練變量,在所有次數和所有步驟中,通過其相應的動作分數來乘以每個梯度向量;並且我們計算結果的平均值。最後,我們運行訓練操作,給它提供平均梯度(對每個可訓練變量提供一個)。我們繼續每 10 個訓練次數保存一次模型。

我們做完了!這段代碼將訓練神經網絡策略,它將成功地學會平衡車上的平衡杆(你可以在 Juyter notebook 上試用)。注意,實際上有兩種方法可以讓玩家遊戲結束:要麼平衡可以傾斜太大,要麼車完全脫離屏幕。在 250 次訓練迭代中,策略學會平衡極點,但在避免脫離屏幕方面還不夠好。額外數百次的訓練迭代可以解決這一問題。

研究人員試圖找到一種即使當智能體最初對環境一無所知時也能很好地工作的算法。然而,除非你正在寫論文,否則你應該儘可能多地將先前的知識注入到智能體中,因爲它會極大地加速訓練。例如,你可以添加與屏幕中心距離和極點角度成正比的負獎勵。此外,如果你已經有一個相當好的策略,你可以訓練神經網絡模仿它,然後使用策略梯度來改進它。

儘管它相對簡單,但是該算法是非常強大的。你可以用它來解決更難的問題,而不僅僅是平衡一輛手推車上的平衡杆。事實上,AlgPaGo 是基於類似的 PG 算法(加上蒙特卡羅樹搜索,這超出了本書的範圍)。

現在我們來看看另一個流行的算法。與 PG 算法直接嘗試優化策略以增加獎勵相反,我們現在看的算法是間接的:智能體學習去估計每個狀態的未來衰減獎勵的期望總和,或者在每個狀態中的每個行爲未來衰減獎勵的期望和。然後,使用這些知識來決定如何行動。爲了理解這些算法,我們必須首先介紹馬爾可夫決策過程(MDP)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章