強化學習(十五) A3C

劉建平Pinard發表於2019-01-29

    在強化學習(十四) Actor-Critic中,我們討論了Actor-Critic的演算法流程,但是由於普通的Actor-Critic演算法難以收斂,需要一些其他的優化。而Asynchronous Advantage Actor-critic(以下簡稱A3C)就是其中比較好的優化演算法。本文我們討論A3C的演算法原理和演算法流程。

    本文主要參考了A3C的論文,以及ICML 2016的deep RL tutorial

1. A3C的引入

    上一篇Actor-Critic演算法的程式碼,其實很難收斂,無論怎麼調參,最後的CartPole都很難穩定在200分,這是Actor-Critic演算法的問題。但是我們還是有辦法去有優化這個難以收斂的問題的。

    回憶下之前的DQN演算法,為了方便收斂使用了經驗回放的技巧。那麼我們的Actor-Critic是不是也可以使用經驗回放的技巧呢?當然可以!不過A3C更進一步,還克服了一些經驗回放的問題。經驗回放有什麼問題呢? 回放池經驗資料相關性太強,用於訓練的時候效果很可能不佳。舉個例子,我們學習下棋,總是和同一個人下,期望能提高棋藝。這當然沒有問題,但是到一定程度就再難提高了,此時最好的方法是另尋高手切磋。

    A3C的思路也是如此,它利用多執行緒的方法,同時在多個執行緒裡面分別和環境進行互動學習,每個執行緒都把學習的成果彙總起來,整理儲存在一個公共的地方。並且,定期從公共的地方把大家的齊心學習的成果拿回來,指導自己和環境後面的學習互動。

    通過這種方法,A3C避免了經驗回放相關性過強的問題,同時做到了非同步併發的學習模型。

2. A3C的演算法優化

    現在我們來看看相比Actor-Critic,A3C到底做了哪些具體的優化。

    相比Actor-Critic,A3C的優化主要有3點,分別是非同步訓練框架,網路結構優化,Critic評估點的優化。其中非同步訓練框架是最大的優化。

    我們首先來看這個非同步訓練框架,如下圖所示:

    圖中上面的Global Network就是上一節說的共享的公共部分,主要是一個公共的神經網路模型,這個神經網路包括Actor網路和Critic網路兩部分的功能。下面有n個worker執行緒,每個執行緒裡有和公共的神經網路一樣的網路結構,每個執行緒會獨立的和環境進行互動得到經驗資料,這些執行緒之間互不干擾,獨立執行。

    每個執行緒和環境互動到一定量的資料後,就計算在自己執行緒裡的神經網路損失函式的梯度,但是這些梯度卻並不更新自己執行緒裡的神經網路,而是去更新公共的神經網路。也就是n個執行緒會獨立的使用累積的梯度分別更新公共部分的神經網路模型引數。每隔一段時間,執行緒會將自己的神經網路的引數更新為公共神經網路的引數,進而指導後面的環境互動。

    可見,公共部分的網路模型就是我們要學習的模型,而執行緒裡的網路模型主要是用於和環境互動使用的,這些執行緒裡的模型可以幫助執行緒更好的和環境互動,拿到高質量的資料幫助模型更快收斂。

 

    現在我們來看看第二個優化,網路結構的優化。之前在強化學習(十四) Actor-Critic中,我們使用了兩個不同的網路Actor和Critic。在A3C這裡,我們把兩個網路放到了一起,即輸入狀態$S$,可以輸入狀態價值$V$,和對應的策略$\pi$, 當然,我們仍然可以把Actor和Critic看做獨立的兩塊,分別處理,如下圖所示:

    第三個優化點是Critic評估點的優化,在強化學習(十四) Actor-Critic第2節中,我們討論了不同的Critic評估點的選擇,其中d部分講到了使用優勢函式$A$來做Critic評估點,優勢函式$A$在時刻t不考慮引數的預設表示式為:$$A(S,A,t) = Q(S,A) - V(S)$$

    $Q(S,A)$的值一般可以通過單步取樣近似估計,即:$$Q(S,A) = R + \gamma V(S')$$

    這樣優勢函式去掉動作可以表達為:$$A(S,t) = R + \gamma V(S') - V(S)$$

    其中$V(S)$的值需要通過Critic網路來學習得到。

    在A3C中,取樣更進一步,使用了N步取樣,以加速收斂。這樣A3C中使用的優勢函式表達為:$$A(S,t) = R_t + +  \gamma R_{t+1} +...\gamma^{n-1} R_{t+n-1} + \gamma^n V(S') - V(S)$$

    對於Actor和Critic的損失函式部分,和Actor-Critic基本相同。有一個小的優化點就是在Actor-Critic策略函式的損失函式中,加入了策略$\pi$的熵項,係數為c, 即策略引數的梯度更新和Actor-Critic相比變成了這樣:$$\theta = \theta + \alpha \nabla_{\theta}log \pi_{\theta}(s_t,a_t)A(S,t) + c\nabla_{\theta}H(\pi(S_t, \theta))$$

    以上就是A3C和Actor-Critic相比有優化的部分。下面我們來總價下A3C的演算法流程。

3. A3C演算法流程

    這裡我們對A3C演算法流程做一個總結,由於A3C是非同步多執行緒的,我們這裡給出任意一個執行緒的演算法流程。

    輸入:公共部分的A3C神經網路結構,對應引數位$\theta, w$,本執行緒的A3C神經網路結構,對應引數$\theta ', w'$, 全域性共享的迭代輪數$T$,全域性最大迭代次數$T_{max}$, 執行緒內單次迭代時間序列最大長度$T_{local}$,狀態特徵維度$n$, 動作集$A$, 步長$\alpha,\beta$,熵係數c, 衰減因子$\gamma$, 探索率$\epsilon$

    輸入:公共部分的A3C神經網路引數$\theta, w$

    1. 更新時間序列$t=1$

    2. 重置Actor和Critic的梯度更新量:$d\theta  \gets 0, dw\gets 0$

    3. 從公共部分的A3C神經網路同步引數到本執行緒的神經網路:$\theta ' =\theta,\;\; w'=w$

    4. $t_{start} = t$,初始化狀態$s_t$

    5. 基於策略$\pi(a_t|s_t;\theta)$選擇出動作$a_t$

    6. 執行動作$a_t$得到獎勵$r_t$和新狀態$s_{t+1}$

    7. $t \gets t+1, T \gets T+1$

    8. 如果$s_t$是終止狀態,或$t-t_{start} == t_{local} $,則進入步驟9,否則回到步驟5

    9. 計算最後一個時間序列位置$s_t$的$Q(s,t)$:$$Q(s,t)= \begin{cases} 0& {terminal\;state}\\ V(s_t,w')& {none\;terminal\;state,bootstrapping} \end{cases}$$

    10. for $i \in (t-1,t-2,...t_{start})$:

      1) 計算每個時刻的$Q(s,i)$:$Q(s,i) = r_i + \gamma Q(s,i+1)$

      2) 累計Actor的本地梯度更新:$$d\theta \gets d\theta + \nabla_{\theta '}log \pi_{\theta'}(s_i,a_i)(Q(s,i)-V(S_i, w')) + c\nabla_{\theta '}H(\pi(s_i, \theta '))$$

      3) 累計Critic的本地梯度更新:$$dw  \gets dw + \frac{\partial (Q(s,i)-V(S_i, w'))^2}{\partial w'}$$

    11. 更新全域性神經網路的模型引數:$$\theta = \theta -\alpha d\theta,\;w = w -\beta dw$$

    12. 如果$T > T_{max}$,則演算法結束,輸出公共部分的A3C神經網路引數$\theta, w$,否則進入步驟3

    以上就是A3C演算法單個執行緒的演算法流程。

4. A3C演算法例項

    下面我們基於上述演算法流程給出A3C演算法例項。仍然使用了OpenAI Gym中的CartPole-v0遊戲來作為我們演算法應用。CartPole-v0遊戲的介紹參見這裡。它比較簡單,基本要求就是控制下面的cart移動使連線在上面的pole保持垂直不倒。這個任務只有兩個離散動作,要麼向左用力,要麼向右用力。而state狀態就是這個cart的位置和速度, pole的角度和角速度,4維的特徵。堅持到200分的獎勵則為過關。

    演算法程式碼大部分參考了莫煩的A3C程式碼,增加了模型測試部分的程式碼並調整了部分模型引數。完整的程式碼參見我的Github:https://github.com/ljpzzz/machinelearning/blob/master/reinforcement-learning/a3c.py

    整個演算法的Actor和Critic的網路結構都定義在這裡, 所有的執行緒中的網路結構,公共部分的網路結構都在這裡定義。

    def _build_net(self, scope):
        w_init = tf.random_normal_initializer(0., .1)
        with tf.variable_scope('actor'):
            l_a = tf.layers.dense(self.s, 200, tf.nn.relu6, kernel_initializer=w_init, name='la')
            a_prob = tf.layers.dense(l_a, N_A, tf.nn.softmax, kernel_initializer=w_init, name='ap')
        with tf.variable_scope('critic'):
            l_c = tf.layers.dense(self.s, 100, tf.nn.relu6, kernel_initializer=w_init, name='lc')
            v = tf.layers.dense(l_c, 1, kernel_initializer=w_init, name='v')  # state value
        a_params = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope=scope + '/actor')
        c_params = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope=scope + '/critic')
        return a_prob, v, a_params, c_params

    所有執行緒初始化部分,以及本執行緒和公共的網路結構初始化部分如下:

    with tf.device("/cpu:0"):
        OPT_A = tf.train.RMSPropOptimizer(LR_A, name='RMSPropA')
        OPT_C = tf.train.RMSPropOptimizer(LR_C, name='RMSPropC')
        GLOBAL_AC = ACNet(GLOBAL_NET_SCOPE)  # we only need its params
        workers = []
        # Create worker
        for i in range(N_WORKERS):
            i_name = 'W_%i' % i   # worker name
            workers.append(Worker(i_name, GLOBAL_AC))

    本執行緒神經網路將本地的梯度更新量用於更新公共網路引數的邏輯在update_global函式中,而從公共網路把引數拉回到本執行緒神經網路的邏輯在pull_global中。

    def update_global(self, feed_dict):  # run by a local
        SESS.run([self.update_a_op, self.update_c_op], feed_dict)  # local grads applies to global net

    def pull_global(self):  # run by a local
        SESS.run([self.pull_a_params_op, self.pull_c_params_op])

    詳細的內容大家可以對照程式碼和演算法流程一起看。在主函式裡我新加了一個測試模型效果的過程,大家可以試試看看最後的模型效果如何。

5. A3C小結

    A3C解決了Actor-Critic難以收斂的問題,同時更重要的是,提供了一種通用的非同步的併發的強化學習框架,也就是說,這個併發框架不光可以用於A3C,還可以用於其他的強化學習演算法。這是A3C最大的貢獻。目前,已經有基於GPU的A3C框架,這樣A3C的框架訓練速度就更快了。

    除了A3C, DDPG演算法也可以改善Actor-Critic難收斂的問題。它使用了Nature DQN,DDQN類似的思想,用兩個Actor網路,兩個Critic網路,一共4個神經網路來迭代更新模型引數。在下一篇我們討論DDPG演算法。

 

(歡迎轉載,轉載請註明出處。歡迎溝通交流: liujianping-ok@163.com)      

相關文章