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

馬爾可夫決策過程

在二十世紀初,數學家 Andrey Markov 研究了沒有記憶的隨機過程,稱爲馬爾可夫鏈。這樣的過程具有固定數量的狀態,並且在每個步驟中隨機地從一個狀態演化到另一個狀態。它從狀態S演變爲狀態S'的概率是固定的,它只依賴於(S, S')對,而不是依賴於過去的狀態(系統沒有記憶)。

圖 16-7 展示了一個具有四個狀態的馬爾可夫鏈的例子。假設該過程從狀態S0開始,並且在下一步驟中有 70% 的概率保持在該狀態不變中。最終,它必然離開那個狀態,並且永遠不會回來,因爲沒有其他狀態回到S0。如果它進入狀態S1,那麼它很可能會進入狀態S2(90% 的概率),然後立即回到狀態S1(以 100% 的概率)。它可以在這兩個狀態之間交替多次,但最終它會落入狀態S3並永遠留在那裏(這是一個終端狀態)。馬爾可夫鏈可以有非常不同的應用,它們在熱力學、化學、統計學等方面有着廣泛的應用。

圖16-7 馬爾科夫鏈案例

馬爾可夫決策過程最初是在 20 世紀 50 年代由 Richard Bellman 描述的。它們類似於馬爾可夫鏈,但有一個連結:在狀態轉移的每一步中,一個智能體可以選擇幾種可能的動作中的一個,並且轉移概率取決於所選擇的動作。此外,一些狀態轉移返回一些獎勵(正或負),智能體的目標是找到一個策略,隨着時間的推移將最大限度地提高獎勵。

例如,圖 16-8 中所示的 MDP 在每個步驟中具有三個狀態和三個可能的離散動作。如果從狀態S0開始,隨着時間的推移可以在動作A0A1A2之間進行選擇。如果它選擇動作A1,它就保持在狀態S0中,並且沒有任何獎勵。因此,如果願意的話,它可以決定永遠呆在那裏。但是,如果它選擇動作A0,它有 70% 的概率獲得 10 獎勵,並保持在狀態S0。然後,它可以一次又一次地嘗試獲得儘可能多的獎勵。但它將在狀態S1中結束這樣的行爲。在狀態S1中,它只有兩種可能的動作:A0A1。它可以通過反覆選擇動作A1來選擇停留,或者它可以選擇動作A2移動到狀態S2並得到 -50 獎勵。在狀態S3中,除了採取行動A1之外,別無選擇,這將最有可能引導它回到狀態S0,在途中獲得 40 的獎勵。通過觀察這個 MDP,你能猜出哪一個策略會隨着時間的推移而獲得最大的回報嗎?在狀態S0中,清楚地知道A0是最好的選擇,在狀態S3中,智能體別無選擇,只能採取行動A1,但是在狀態S1中,智能體否應該保持不動(A0)或通過火(A2),這是不明確的。

圖16-8 馬爾科夫決策過程案例

Bellman 找到了一種估計任何狀態S的最佳狀態值的方法,他提出了V(s),它是智能體在其採取最佳行爲達到狀態s後所有衰減未來獎勵的總和的平均期望。他表明,如果智能體的行爲最佳,那麼貝爾曼最優性公式適用(見公式 16-1)。這個遞歸公式表示,如果智能體最優地運行,那麼當前狀態的最優值等於在採取一個最優動作之後平均得到的獎勵,加上該動作可能導致的所有可能的下一個狀態的期望最優值。

公式16-1 貝爾曼最優性公式

其中:

  • T爲智能體選擇動作a時從狀態s到狀態s'的概率
  • R爲智能體選擇以動作a從狀態s到狀態s'的過程中得到的獎勵
  • γ爲衰減率

這個等式直接引出了一種算法,該算法可以精確估計每個可能狀態的最優狀態值:首先將所有狀態值估計初始化爲零,然後用數值迭代算法迭代更新它們(見公式 16-2)。一個顯著的結果是,給定足夠的時間,這些估計保證收斂到最優狀態值,對應於最優策略。

公式16-2 數值迭代算法

其中:

  • Vk(s)是在k次算法迭代對狀態s的估計

筆記: 該算法是動態規劃的一個例子,它將了一個複雜的問題(在這種情況下,估計潛在的未來衰減獎勵的總和)變爲可處理的子問題,可以迭代地處理(在這種情況下,找到最大化平均報酬與下一個衰減狀態值的和的動作)

瞭解最佳狀態值可能是有用的,特別是評估策略,但它沒有明確地告訴智能體要做什麼。幸運的是,Bellman 發現了一種非常類似的算法來估計最優狀態-動作值(state-action values),通常稱爲 Q 值。狀態行動(S, A)對的最優 Q 值,記爲Q(s, a),是智能體在到達狀態S,然後選擇動作A之後平均衰減未來獎勵的期望的總和。但是在它看到這個動作的結果之前,假設它在該動作之後的動作是最優的。

下面是它的工作原理:再次,通過初始化所有的 Q 值估計爲零,然後使用 Q 值迭代算法更新它們(參見公式 16-3)。

公式16-3 Q值迭代算法

一旦你有了最佳的 Q 值,定義最優的策略π*(s),它是平凡的:當智能體處於狀態S時,它應該選擇具有最高 Q 值的動作,用於該狀態:

讓我們把這個算法應用到圖 16-8 所示的 MDP 中。首先,我們需要定義 MDP:

nan=np.nan  # 代表不可能的動作 
T = np.array([  # shape=[s, a, s']        
    [[0.7, 0.3, 0.0], [1.0, 0.0, 0.0], [0.8, 0.2, 0.0]],
    [[0.0, 1.0, 0.0], [nan, nan, nan], [0.0, 0.0, 1.0]],       
    [[nan, nan, nan], [0.8, 0.1, 0.1], [nan, nan, nan]],    ]) 
R = np.array([  # shape=[s, a, s']        
    [[10., 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]],        
    [[10., 0.0, 0.0], [nan, nan, nan], [0.0, 0.0, -50.]],        
    [[nan, nan, nan], [40., 0.0, 0.0], [nan, nan, nan]],    ]) 
possible_actions = [[0, 1, 2], [0, 2], [1]] 

讓我們運行 Q 值迭代算法

Q = np.full((3, 3), -np.inf)  # -inf 對應着不可能的動作 
for state, actions in enumerate(possible_actions):    
    Q[state, actions] = 0.0  # 對所有可能的動作初始化爲0.0
learning_rate = 0.01 
discount_rate = 0.95 
n_iterations = 100
for iteration in range(n_iterations):   
    Q_prev = Q.copy()    
    for s in range(3):        
        for a in possible_actions[s]:            
            Q[s, a] = np.sum([T[s, a, sp] * (R[s, a, sp] + discount_rate * np.max(Q_prev[sp]))  
            for sp in range(3)]) 

結果的 Q 值類似於如下:

>>> Q 
array([[ 21.89498982,  20.80024033,  16.86353093],       
        [  1.11669335,         -inf,   1.17573546],       
        [        -inf,  53.86946068,         -inf]]) 
>>> np.argmax(Q, axis=1)  # 每一狀態的最優動作
array([0, 2, 1]) 

這給我們這個 MDP 的最佳策略,當使用 0.95 的衰減率時:在狀態S0選擇動作A0,在狀態S1選擇動作A2(通過火焰!)在狀態S2中選擇動作A1(唯一可能的動作)。有趣的是,如果你把衰減率降低到 0.9,最優的策略改變:在狀態S1中,最好的動作變成A0(保持不變;不通過火)。這是有道理的,因爲如果你認爲現在比未來更重要,那麼未來獎勵的前景是不值得立刻經歷痛苦的。

時間差分學習與 Q 學習

具有離散動作的強化學習問題通常可以被建模爲馬爾可夫決策過程,但是智能體最初不知道轉移概率是什麼(它不知道T),並且它不知道獎勵會是什麼(它不知道R)。它必須經歷每一個狀態和每一次轉變並且至少知道一次獎勵,並且如果要對轉移概率進行合理的估計,就必須經歷多次。

時間差分學習(TD 學習)算法與數值迭代算法非常類似,但考慮到智能體僅具有 MDP 的部分知識。一般來說,我們假設智能體最初只知道可能的狀態和動作,沒有更多了。智能體使用探索策略,例如,純粹的隨機策略來探索 MDP,並且隨着它的發展,TD 學習算法基於實際觀察到的轉換和獎勵來更新狀態值的估計(見公式 16-4)。

公式16-4 TD學習算法

其中:

a是學習率(例如 0.01)

TD 學習與隨機梯度下降有許多相似之處,特別是它一次處理一個樣本的行爲。就像 SGD 一樣,只有當你逐漸降低學習速率時,它才能真正收斂(否則它將在極值點震盪)。

對於每個狀態S,該算法只跟蹤智能體離開該狀態時立即獲得的獎勵的平均值,再加上它期望稍後得到的獎勵(假設它的行爲最佳)。

類似地,此時的Q 學習算法是 Q 值迭代算法的改編版本,其適應轉移概率和回報在初始未知的情況(見公式16-5)。

公式16-5 Q學習算法

對於每一個狀態動作對(s,a),該算法跟蹤智能體在以動作A離開狀態S時獲得的即時獎勵平均值R,加上它期望稍後得到的獎勵。由於目標策略將最優地運行,所以我們取下一狀態的 Q 值估計的最大值。

以下是如何實現 Q 學習:

import numpy.random as rnd
learning_rate0 = 0.05 
learning_rate_decay = 0.1 
n_iterations = 20000
s = 0 # 在狀態 0開始
Q = np.full((3, 3), -np.inf)  # -inf 對應着不可能的動作 
for state, actions in enumerate(possible_actions):    
    Q[state, actions] = 0.0  # 對於所有可能的動作初始化爲 0.0
for iteration in range(n_iterations):    
    a = rnd.choice(possible_actions[s])  # 隨機選擇動作   
    sp = rnd.choice(range(3), p=T[s, a]) # 使用 T[s, a] 挑選下一狀態   
    reward = R[s, a, sp]    
    learning_rate = learning_rate0 / (1 + iteration * learning_rate_decay)    
    Q[s, a] = learning_rate * Q[s, a] + (1 - learning_rate) * (reward + discount_rate * np.max(Q[sp]))    
    s = sp # 移動至下一狀態

給定足夠的迭代,該算法將收斂到最優 Q 值。這被稱爲離線策略算法,因爲正在訓練的策略不是正在執行的策略。令人驚訝的是,該算法能夠通過觀察智能體行爲隨機學習(例如學習當你的老師是一個醉猴子時打高爾夫球)最佳策略。我們能做得更好嗎?

探索策略

當然,只有在探索策略充分探索 MDP 的情況下,Q 學習才能起作用。儘管一個純粹的隨機策略保證最終訪問每一個狀態和每個轉換多次,但可能需要很長的時間這樣做。因此,一個更好的選擇是使用 ε 貪婪策略:在每個步驟中,它以概率ε隨機地或以概率爲1-ε貪婪地(選擇具有最高 Q 值的動作)。ε 貪婪策略的優點(與完全隨機策略相比)是,它將花費越來越多的時間來探索環境中有趣的部分,因爲 Q 值估計越來越好,同時仍花費一些時間訪問 MDP 的未知區域。以ε爲很高的值(例如,1)開始,然後逐漸減小它(例如,下降到 0.05)是很常見的。

可選擇的,相比於依賴於探索的可能性,另一種方法是鼓勵探索策略來嘗試它以前沒有嘗試過的行動。這可以被實現爲附加於 Q 值估計的獎金,如公式 16-6 所示。

公式16-6 使用探索函數的Q學習

其中:

  • N計算了在狀態s時選擇動作a的次數
  • f是一個探索函數,例如f=q+K/(1+n),其中K是一個好奇超參數,它測量智能體被吸引到未知狀態的程度。

近似 Q 學習

Q 學習的主要問題是,它不能很好地擴展到具有許多狀態和動作的大(甚至中等)的 MDP。試着用 Q 學習來訓練一個智能體去玩 Ms. Pac-Man。Ms. Pac-Man 可以吃超過 250 粒粒子,每一粒都可以存在或不存在(即已經吃過)。因此,可能狀態的數目大於 2 的 250 次冪,約等於 10 的 75 次冪(並且這是考慮顆粒的可能狀態)。這比在可觀測的宇宙中的原子要多得多,所以你絕對無法追蹤每一個 Q 值的估計值。

解決方案是找到一個函數,使用可管理數量的參數來近似 Q 值。這被稱爲近似 Q 學習。多年來,人們都是手工在狀態中提取併線性組合特徵(例如,最近的鬼的距離,它們的方向等)來估計 Q 值,但是 DeepMind 表明使用深度神經網絡可以工作得更好,特別是對於複雜的問題。它不需要任何特徵工程。用於估計 Q 值的 DNN 被稱爲深度 Q 網絡(DQN),並且使用近似 Q 學習的 DQN 被稱爲深度 Q 學習。

在本章的剩餘部分,我們將使用深度 Q 學習來訓練一個智能體去玩 Ms. Pac-Man,就像 DeepMind 在 2013 所做的那樣。代碼可以很容易地調整,調整後學習去玩大多數 Atari 遊戲的效果都相當好。在大多數動作遊戲中,它可以達到超人的技能,但它在長時運行的遊戲中卻不太好。

學習去使用深度 Q 學習來玩 Ms.Pac-Man

由於我們將使用 Atari 環境,我們必須首先安裝 OpenAI gym 的 Atari 環境依賴項。當需要玩其他的時候,我們也會爲你想玩的其他 OpenAI gym 環境安裝依賴項。在 macOS 上,假設你已經安裝了 Homebrew 程序,你需要運行:

$ brew install cmake boost boost-python sdl2 swig wget

在 Ubuntu 上,輸入以下命令(如果使用 Python 2,用 Python 替換 Python 3):

$ apt-get install -y python3-numpy python3-dev cmake zlib1g-dev libjpeg-dev\    xvfb libav-tools xorg-dev python3-opengl libboost-all-dev libsdl2-dev swig 

隨後安裝額外的 python 包:

$ pip3 install --upgrade 'gym[all]' 

如果一切順利,你應該能夠創造一個 Ms.Pac-Man 環境:

>>> env = gym.make("MsPacman-v0") 
>>> obs = env.reset() 
>>> obs.shape  # [長,寬,通道] 
(210, 160, 3) 
>>> env.action_space 
Discrete(9) 

正如你所看到的,有九個離散動作可用,它對應於操縱桿的九個可能位置(左、右、上、下、中、左上等),觀察結果是 Atari 屏幕的截圖(見圖 16-9,左),表示爲 3D Numpy 矩陣。這些圖像有點大,所以我們將創建一個小的預處理函數,將圖像裁剪並縮小到88×80像素,將其轉換成灰度,並提高 Ms.Pac-Man 的對比度。這將減少 DQN 所需的計算量,並加快培訓練。

mspacman_color = np.array([210, 164, 74]).mean()

def preprocess_observation(obs):    
    img = obs[1:176:2, ::2] # 裁剪    
    img = img.mean(axis=2) # 灰度化    
    img[img==mspacman_color] = 0 # 提升對比度    
    img = (img - 128) / 128 - 1 # 正則化爲-1到1.   
    return img.reshape(88, 80, 1) 

過程的結果如圖 16-9 所示(右)。

圖16-9 原始的(左)和經過預處理(右)的Ms. Pac-Man

接下來,讓我們創建 DQN。它可以只取一個狀態動作對(S,A)作爲輸入,並輸出相應的 Q 值Q(s,a)的估計值,但是由於動作是離散的,所以使用只使用狀態S作爲輸入並輸出每個動作的一個 Q 值估計的神經網絡是更方便的。DQN 將由三個卷積層組成,接着是兩個全連接層,其中包括輸出層(如圖 16-10)。

圖16-10 用深度Q網絡玩Ms. Pac-Man

正如我們將看到的,我們將使用的訓練算法需要兩個具有相同架構(但不同參數)的 DQN:一個將在訓練期間用於驅動 Ms.Pac-Man(the actor,行動者),另一個將觀看行動者並從其試驗和錯誤中學習(the critic,評判者)。每隔一定時間,我們把評判者網絡複製給行動者網絡。因爲我們需要兩個相同的 DQN,所以我們將創建一個q_network()函數來構建它們:

from tensorflow.contrib.layers import convolution2d, fully_connected
input_height = 88 
input_width = 80 
input_channels = 1 
conv_n_maps = [32, 64, 64] 
conv_kernel_sizes = [(8,8), (4,4), (3,3)] 
conv_strides = [4, 2, 1] 
conv_paddings = ["SAME"]*3 
conv_activation = [tf.nn.relu]*3 
n_hidden_in = 64 * 11 * 10  # conv3 有 64 個 11x10 映射
each n_hidden = 512 
hidden_activation = tf.nn.relu 
n_outputs = env.action_space.n  # 9個離散動作
initializer = tf.contrib.layers.variance_scaling_initializer()

def q_network(X_state, scope):    
    prev_layer = X_state    
    conv_layers = []    
    with tf.variable_scope(scope) as scope:        
        for n_maps, kernel_size, stride, padding, activation in zip(conv_n_maps,                                                                  conv_kernel_sizes, 
                                                                    conv_strides,
                                                                    conv_paddings,                                                                  conv_activation):           
            prev_layer = convolution2d(prev_layer, 
                                       num_outputs=n_maps, 
                                       kernel_size=kernel_size,
                                       stride=stride, padding=padding, 
                                       activation_fn=activation,
                                       weights_initializer=initializer)           
            conv_layers.append(prev_layer)       
        last_conv_layer_flat = tf.reshape(prev_layer, shape=[-1, n_hidden_in])     
        hidden = fully_connected(last_conv_layer_flat, n_hidden,
                                 activation_fn=hidden_activation,                                                  weights_initializer=initializer) 
        outputs = fully_connected(hidden, n_outputs, 
                                  activation_fn=None,
                                  weights_initializer=initializer)  
        
    trainable_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES,
                                       scope=scope.name)                
    trainable_vars_by_name = {var.name[len(scope.name):]: var 
                              for var in trainable_vars}    
    return outputs, trainable_vars_by_name

該代碼的第一部分定義了DQN體系結構的超參數。然後q_network()函數創建 DQN,將環境的狀態X_state作爲輸入,以及變量範圍的名稱。請注意,我們將只使用一個觀察來表示環境的狀態,因爲幾乎沒有隱藏的狀態(除了閃爍的物體和鬼魂的方向)。

trainable_vars_by_name字典收集了所有 DQN 的可訓練變量。當我們創建操作以將評論家 DQN 複製到行動者 DQN 時,這將是有用的。字典的鍵是變量的名稱,去掉與範圍名稱相對應的前綴的一部分。看起來像這樣:

>>> trainable_vars_by_name 
{'/Conv/biases:0': <tensorflow.python.ops.variables.Variable at 0x121cf7b50>, '/Conv/weights:0': <tensorflow.python.ops.variables.Variable...>, 
'/Conv_1/biases:0': <tensorflow.python.ops.variables.Variable...>, '/Conv_1/weights:0': <tensorflow.python.ops.variables.Variable...>, '/Conv_2/biases:0': <tensorflow.python.ops.variables.Variable...>, '/Conv_2/weights:0': <tensorflow.python.ops.variables.Variable...>, '/fully_connected/biases:0': <tensorflow.python.ops.variables.Variable...>, '/fully_connected/weights:0': <tensorflow.python.ops.variables.Variable...>, '/fully_connected_1/biases:0': <tensorflow.python.ops.variables.Variable...>, '/fully_connected_1/weights:0': <tensorflow.python.ops.variables.Variable...>}

現在讓我們爲兩個 DQN 創建輸入佔位符,以及複製評論家 DQN 給行動者 DQN 的操作:

X_state = tf.placeholder(tf.float32, 
                         shape=[None, input_height, input_width,input_channels])          
actor_q_values, actor_vars = q_network(X_state, scope="q_networks/actor") 
critic_q_values, critic_vars = q_network(X_state, scope="q_networks/critic")
copy_ops = [actor_var.assign(critic_vars[var_name])  
            for var_name, actor_var in actor_vars.items()] 
copy_critic_to_actor = tf.group(*copy_ops) 

讓我們後退一步:我們現在有兩個 DQN,它們都能夠將環境狀態(即預處理觀察)作爲輸入,並輸出在該狀態下的每一個可能的動作的估計 Q 值。另外,我們有一個名爲copy_critic_to_actor的操作,將評論家 DQN 的所有可訓練變量複製到行動者 DQN。我們使用 TensorFlow 的tf.group()函數將所有賦值操作分組到一個方便的操作中。

行動者 DQN 可以用來扮演 Ms.Pac-Man(最初非常糟糕)。正如前面所討論的,你希望它足夠深入地探究遊戲,所以通常情況下你想將它用 ε 貪婪策略或另一種探索策略相結合。

但是評論家 DQN 呢?它如何去學習玩遊戲?簡而言之,它將試圖使其預測的 Q 值去匹配行動者通過其經驗的遊戲估計的 Q 值。具體來說,我們將讓行動者玩一段時間,把所有的經驗保存在回放記憶存儲器中。每個記憶將是一個 5 元組(狀態、動作、下一狀態、獎勵、繼續),其中“繼續”項在遊戲結束時等於 0,否則爲 1。接下來,我們定期地從回放存儲器中採樣一批記憶,並且我們將估計這些存儲器中的 Q 值。最後,我們將使用監督學習技術訓練評論家 DQN 去預測這些 Q 值。每隔幾個訓練週期,我們會把評論家 DQN 複製到行動者 DQN。就這樣!公式 16-7 示出了用於訓練評論家 DQN 的損失函數:

公式16-7 深度Q學習損失函數

其中:

  • s(i), a(i), r(i) and s′(i)分別爲狀態,行爲,回報,和下一狀態,均從存儲器中第i次採樣得到
  • m是記憶批處理的長度
  • θcritic和θactor爲評論者和行動者的參數
  • Q(s'(i),a',θactor)是評論家 DQN 對第i記憶狀態行爲 Q 值的預測
  • Q(s(i),a(i),θcritic)是演員 DQN 在選擇動作A'時的下一狀態S'的期望 Q 值的預測
  • y(i)是第ith記憶的目標 Q 值,注意,它等同於行動者實際觀察到的獎勵,再加上行動者對如果它能發揮最佳效果(據它所知),未來的回報應該是什麼的預測。
  • J(θcritic)爲訓練評論家 DQN 的損失函數。正如你所看到的,這只是由行動者 DQN 估計的目標 Q 值y和評論家 DQN 對這些 Q 值的預測之間的均方誤差。

回放記憶是可選的,但強烈推薦使它存在。沒有它,你會訓練評論家 DQN 使用連續的經驗,這可能是相關的。這將引入大量的偏差並且減慢訓練算法的收斂性。通過使用回放記憶,我們確保饋送到訓練算法的存儲器可以是不相關的。

讓我們添加評論家 DQN 的訓練操作。首先,我們需要能夠計算其在存儲器批處理中的每個狀態動作的預測 Q 值。由於 DQN 爲每一個可能的動作輸出一個 Q 值,所以我們只需要保持與在該存儲器中實際選擇的動作相對應的 Q 值。爲此,我們將把動作轉換成一個熱向量(記住這是一個滿是 0 的向量,除了第i個索引中的1),並乘以 Q 值:這將刪除所有與記憶動作對應的 Q 值外的 Q 值。然後只對第一軸求和,以獲得每個存儲器所需的 Q 值預測。

X_action = tf.placeholder(tf.int32, shape=[None]) 
q_value = tf.reduce_sum(critic_q_values * tf.one_hot(X_action, n_outputs), axis=1, keep_dims=True) 

接下來,讓我們添加訓練操作,假設目標Q值將通過佔位符饋入。我們還創建了一個不可訓練的變量global_step。優化器的minimize()操作將負責增加它。另外,我們創建了init操作和Saver

y = tf.placeholder(tf.float32, shape=[None, 1]) 
cost = tf.reduce_mean(tf.square(y - q_value)) 
global_step = tf.Variable(0, trainable=False, name='global_step') 
optimizer = tf.train.AdamOptimizer(learning_rate) 
training_op = optimizer.minimize(cost, global_step=global_step)
init = tf.global_variables_initializer() 
saver = tf.train.Saver() 

這就是訓練階段的情況。在我們查看執行階段之前,我們需要一些工具。首先,讓我們從回放記憶開始。我們將使用一個deque列表,因爲在將數據推送到隊列中並在達到最大內存大小時從列表的末尾彈出它們使是非常有效的。我們還將編寫一個小函數來隨機地從回放記憶中採樣一批處理:

from collections import deque

replay_memory_size = 10000 
replay_memory = deque([], maxlen=replay_memory_size)

def sample_memories(batch_size):    
    indices = rnd.permutation(len(replay_memory))[:batch_size]    
    cols = [[], [], [], [], []] # state, action, reward, next_state, continue    
    for idx in indices:        
        memory = replay_memory[idx]        
        for col, value in zip(cols, memory):            
            col.append(value)    
    cols = [np.array(col) for col in cols]    
    return (cols[0], cols[1], cols[2].reshape(-1, 1), cols[3],cols[4].reshape(-1, 1)) 

接下來,我們需要行動者來探索遊戲。我們使用 ε 貪婪策略,並在 50000 個訓練步驟中逐步將ε從 1 降低到 0.05。

eps_min = 0.05 
eps_max = 1.0 
eps_decay_steps = 50000
def epsilon_greedy(q_values, step):    
    epsilon = max(eps_min, eps_max - (eps_max-eps_min) * step/eps_decay_steps)   
     if rnd.rand() < epsilon:        
        return rnd.randint(n_outputs) # 隨機動作   
    else:        
        return np.argmax(q_values) # 最優動作

就是這樣!我們準備好開始訓練了。執行階段不包含太複雜的東西,但它有點長,所以深呼吸。準備好了嗎?來次夠!首先,讓我們初始化幾個變量:

n_steps = 100000  # 總的訓練步長 
training_start = 1000  # 在遊戲1000次迭代後開始訓練
training_interval = 3  # 每3次迭代訓練一次
save_steps = 50  # 每50訓練步長保存模型
copy_steps = 25  # 每25訓練步長後複製評論家Q值到行動者
discount_rate = 0.95 
skip_start = 90  # 跳過遊戲開始(只是等待時間) 
batch_size = 50 
iteration = 0  # 遊戲迭代
checkpoint_path = "./my_dqn.ckpt" 
done = True # env 需要被重置 

接下來,讓我們打開會話並開始訓練:

with tf.Session() as sess:    
    if os.path.isfile(checkpoint_path):        
        saver.restore(sess, checkpoint_path)    
    else:        
        init.run()    
    while True:        
        step = global_step.eval()        
        if step >= n_steps:            
            break        
        iteration += 1        
        if done: # 遊戲結束,重來            
            obs = env.reset()            
            for skip in range(skip_start): # 跳過遊戲開頭              
                obs, reward, done, info = env.step(0)            
                state = preprocess_observation(obs)

        # 行動者評估要幹什麼        
        q_values = actor_q_values.eval(feed_dict={X_state: [state]})        
        action = epsilon_greedy(q_values, step)

        # 行動者開始玩遊戲       
        obs, reward, done, info = env.step(action)        
        next_state = preprocess_observation(obs)

        # 讓我們記下來剛纔發生了啥        
        replay_memory.append((state, action, reward, next_state, 1.0 - done))        state = next_state

        if iteration < training_start or iteration % training_interval != 0:                continue

        # 評論家學習        
        X_state_val, X_action_val, rewards, X_next_state_val, continues = (            sample_memories(batch_size))        
        next_q_values = actor_q_values.eval( feed_dict={X_state: X_next_state_val})    
        max_next_q_values = np.max(next_q_values, axis=1, keepdims=True)        
        y_val = rewards + continues * discount_rate * max_next_q_values        
        training_op.run(feed_dict={X_state: X_state_val,X_action: X_action_val, y: y_val})

        # 複製評論家Q值到行動者        
        if step % copy_steps == 0:            
            copy_critic_to_actor.run()

        # 保存模型        
        if step % save_steps == 0:            
            saver.save(sess, checkpoint_path)

如果檢查點文件存在,我們就開始恢復模型,否則我們只需初始化變量。然後,主循環開始,其中iteration計算從程序開始以來遊戲步驟的總數,同時step計算從訓練開始的訓練步驟的總數(如果恢復了檢查點,也恢復全局步驟)。然後代碼重置遊戲(跳過第一個無聊的等待遊戲的步驟,這步驟啥都沒有)。接下來,行動者評估該做什麼,並且玩遊戲,並且它的經驗被存儲在回放記憶中。然後,每隔一段時間(熱身期後),評論家開始一個訓練步驟。它採樣一批迴放記憶,並要求行動者估計下一狀態的所有動作的Q值,並應用公式 16-7 來計算目標 Q 值y_val.這裏唯一棘手的部分是,我們必須將下一個狀態的 Q 值乘以continues向量,以將對應於遊戲結束的記憶 Q 值清零。接下來,我們進行訓練操作,以提高評論家預測 Q 值的能力。最後,我們定期將評論家的 Q 值複製給行動者,然後保存模型。

不幸的是,訓練過程是非常緩慢的:如果你使用你的破筆記本電腦進行訓練的話,想讓 Ms. Pac-Man 變好一點點你得花好幾天,如果你看看學習曲線,計算一下每次的平均獎勵,你會發現到它是非常嘈雜的。在某些情況下,很長一段時間內可能沒有明顯的進展,直到智能體學會在合理的時間內生存。如前所述,一種解決方案是將儘可能多的先驗知識注入到模型中(例如,通過預處理、獎勵等),也可以嘗試通過首先訓練它來模仿基本策略來引導模型。在任何情況下,RL仍然需要相當多的耐心和調整,但最終結果是非常令人興奮的。

練習

  1. 你怎樣去定義強化學習?它與傳統的監督以及非監督學習有什麼不同?
  2. 你能想到什麼本章沒有提到過的強化學習應用?智能體是什麼?什麼是可能的動作,什麼是獎勵?
  3. 什麼是衰減率?如果你修改了衰減率那最優策略會變化嗎?
  4. 你怎麼去定義強化學習智能體的表現?
  5. 什麼是信用評估問題?它怎麼出現的?你怎麼解決?
  6. 使用回放記憶的目的是什麼?
  7. 什麼是閉策略 RL 算法?
  8. 使用深度 Q 學習來處理 OpenAI gym 的“BypedalWalker-v2” 。QNET 不需要對這個任務使用非常深的網絡。
  9. 使用策略梯度訓練智能體扮演 Pong,一個著名的 Atari 遊戲(PANV0 在 OpenAI gym 的 Pong-v0)。注意:個人的觀察不足以說明球的方向和速度。一種解決方案是一次將兩次觀測傳遞給神經網絡策略。爲了減少維度和加速訓練,你必須預先處理這些圖像(裁剪,調整大小,並將它們轉換成黑白),並可能將它們合併成單個圖像(例如去疊加它們)。
  10. 如果你有大約 100 美元備用,你可以購買 Raspberry Pi 3 再加上一些便宜的機器人組件,在 PI 上安裝 TensorFlow,然後讓我們嗨起來~!舉個例子,看看 Lukas Biewald 的這個有趣的帖子,或者看看 GoPiGo 或 BrickPi。爲什麼不嘗試通過使用策略梯度訓練機器人來構建真實的 cartpole ?或者造一個機器人蜘蛛,讓它學會走路;當它接近某個目標時,給予獎勵(你需要傳感器來測量目標的距離)。唯一的限制就是你的想象力。

練習答案均在附錄 A。

感謝

在我們結束這本書的最後一章之前,我想感謝你們讀到最後一段。我真心希望你能像我寫這本書一樣愉快地閱讀這本書,這對你的項目,或多或少都是有用的。

如果發現錯誤,請發送反饋。更一般地說,我很想知道你的想法,所以請不要猶豫,通過 O'Reilly 來與我聯繫,或者通過 ageron/handson-ml GITHUB 項目來練習。

對你來說,我最好的建議是練習和練習:如果你還沒有做過這些練習,試着使用 Juyter notebook 參加所有的練習,加入 kaggle 網站或其他 ML 社區,看 ML 課程,閱讀論文,參加會議,會見專家。您可能還想研究我們在本書中沒有涉及的一些主題,包括推薦系統、聚類算法、異常檢測算法和遺傳算法。

我最大的希望是,這本書將激勵你建立一個美妙的 ML 應用程序,這將有利於我們所有人!那會是什麼呢?

2016 年 11 月 26 日,Aurélien Géron

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