深度強化學習-Actor-Critic算法原理和實現

在之前的幾篇文章中,我們介紹了基于價值Value的強化學習算法Deep Q Network。有關DQN算法以及各種改進算法的原理和實現,可以參考之前的文章:

實戰深度強化學習DQN-理論和實踐:http://www.lxweimin.com/p/10930c371cac
DQN三大改進(一)-Double DQN:http://www.lxweimin.com/p/fae51b5fe000
DQN三大改進(二)-Prioritised replay:http://www.lxweimin.com/p/db14fdc67d2c
DQN三大改進(三)-Dueling Network:http://www.lxweimin.com/p/b421c85796a2

基于值的強化學習算法的基本思想是根據當前的狀態,計算采取每個動作的價值,然后根據價值貪心的選擇動作。如果我們省略中間的步驟,即直接根據當前的狀態來選擇動作。

在強化學習中,還有另一種很重要的算法,即策略梯度(Policy Gradient)。之前我們已經介紹過策略梯度的基本思想和實現了,大家可以有選擇的進行預習和復習:
深度強化學習-Policy Gradient基本實現:http://www.lxweimin.com/p/2ccbab48414b

本文介紹的Actor-Critic算法呢,就是結合了上面兩種算法的基本思想而產生的,什么是Actor?什么是Critic?二者是如何結合的,通過這篇文章,我們來一探究竟。

本篇文章的大部分內容均學習自莫煩老師的強化學習課程,大家可以在b站上找到相關的視頻:https://www.bilibili.com/video/av16921335/#page=22

1、Actor-Critic算法原理

我們為什么要有Actor-Critic呢,下面的話摘自莫煩老師的文章:

我們有了像 Q-learning這么偉大的算法, 為什么還要瞎折騰出一個 Actor-Critic? 原來 Actor-Critic 的 Actor 的前生是 Policy Gradients, 這能讓它毫不費力地在連續動作中選取合適的動作, 而 Q-learning 做這件事會癱瘓. 那為什么不直接用 Policy Gradients 呢? 原來 Actor Critic 中的 Critic 的前生是 Q-learning 或者其他的 以值為基礎的學習法 , 能進行單步更新, 而傳統的 Policy Gradients 則是回合更新, 這降低了學習效率.

上面的一段話不僅解釋了為什么會有Actor-Critic這么一個算法,同時也告訴了我們,這個算法具體是怎么做的。如果大家已經心中有數并且想馬上看代碼的話,這一段是可以直接跳過的。既然Actor其實是一個Policy Network ,那么他就需要獎懲信息來進行調節不同狀態下采取各種動作的概率,在傳統的Policy Gradient算法中,這種獎懲信息是通過走完一個完整的episode來計算得到的。這不免導致了學習速率很慢,需要很長時間才可以學到東西。既然Critic是一個以值為基礎的學習法,那么他可以進行單步更新,計算每一步的獎懲值。那么二者相結合,Actor來選擇動作,Critic來告訴Actor它選擇的動作是否合適。在這一過程中,Actor不斷迭代,得到每一個狀態下選擇每一動作的合理概率,Critic也不斷迭代,不斷完善每個狀態下選擇每一個動作的獎懲值。

下圖就簡單的介紹了Actor-Critic算法的流程:

但Actor-Critic并不是一個完善的算法, 后面還會提到進一步的改進:

Actor-Critic 涉及到了兩個神經網絡, 而且每次都是在連續狀態中更新參數, 每次參數更新前后都存在相關性, 導致神經網絡只能片面的看待問題, 甚至導致神經網絡學不到東西。

2、代碼解析

本文的github地址為:https://github.com/princewen/tensorflow_practice/tree/master/Basic-Actor-Critic

2.1 Actor

定義Actor輸入
在這里,由于我們的Actor可以進行單次訓練,所以我們的輸入只需要是一個狀態,一個動作和一個獎勵:

self.s = tf.placeholder(tf.float32,[1,n_features],name='state')
self.a = tf.placeholder(tf.int32,None,name='act')
self.td_error = tf.placeholder(tf.float32,None,"td_error")

Actor的網絡定義
Actor的神經網絡結構和我們的Policy Gradient定義的是一樣的,是一個雙層的全鏈接神經網絡:

with tf.variable_scope('Actor'):
    l1 = tf.layers.dense(
        inputs = self.s,
        units = 20,
        activation = tf.nn.relu,
        kernel_initializer = tf.random_normal_initializer(mean=0,stddev=0.1),
        bias_initializer = tf.constant_initializer(0.1),
        name = 'l1'
    )

    self.acts_prob = tf.layers.dense(
        inputs = l1,
        units = n_actions,
        activation = tf.nn.softmax,
        kernel_initializer = tf.random_normal_initializer(mean=0,stddev=0.1),
        bias_initializer = tf.constant_initializer(0.1),
        name = 'acts_prob'
    )

損失函數
損失函數還是使用的Policy Gradient中提到過的loss= -log(prob)*vt,只不過這里的vt換成了由Critic計算出的時間差分誤差td_error

with tf.variable_scope('exp_v'):
    log_prob = tf.log(self.acts_prob[0,self.a])
    self.exp_v = tf.reduce_mean(log_prob * self.td_error)


with tf.variable_scope('train'):
    self.train_op =  tf.train.AdamOptimizer(lr).minimize(-self.exp_v)

Actor訓練
Actor的訓練只需要將狀態,動作以及時間差分值喂給網絡就可以。

def learn(self,s,a,td):
    s = s[np.newaxis,:]
    feed_dict = {self.s:s,self.a:a,self.td_error:td}
    _,exp_v = self.sess.run([self.train_op,self.exp_v],feed_dict=feed_dict)
    return exp_v

選擇動作

選擇動作和Policy Gradient一樣,根據計算出的softmax值來選擇動作

def choose_action(self,s):
    s = s[np.newaxis,:]
    probs = self.sess.run(self.acts_prob,feed_dict={self.s:s})
    return np.random.choice(np.arange(probs.shape[1]),p=probs.ravel())

2.2 Critic

定義Critic輸入

Critic要反饋給Actor一個時間差分值,來決定Actor選擇動作的好壞,如果時間差分值大的話,說明當前Actor選擇的這個動作的驚喜度較高,需要更多的出現來使得時間差分值減小。
考慮時間差分的計算:
TD = r + gamma * f(s') - f(s),這里f(s)代表將s狀態輸入到Critic神經網絡中得到的Q值。
所以Critic的輸入也分三個,首先是當前狀態,當前的獎勵,以及下一個時刻的獎勵折現值。為什么沒有動作A呢?動作A是確定的呀,是Actor選的呀,對不對!還有為什么不是下一時刻的Q值而不是下一個時刻的狀態,因為我們已經在計算TD時已經把狀態帶入到神經網絡中得到Q值了。相信你看代碼就明白了。

self.s = tf.placeholder(tf.float32,[1,n_features],name='state')
self.v_ = tf.placeholder(tf.float32,[1,1],name='v_next')
self.r = tf.placeholder(tf.float32,None,name='r')

定義網絡結構

同Actor一樣,我們的Critic也是一個雙層的神經網絡結構。

with tf.variable_scope('Critic'):
    l1 = tf.layers.dense(
        inputs = self.s,
        units = 20,
        activation = tf.nn.relu,
        kernel_initializer = tf.random_normal_initializer(0,0.1),
        bias_initializer = tf.constant_initializer(0.1),
        name = 'l1'
    )

    self.v = tf.layers.dense(
        inputs = l1,
        units = 1,
        activation = None,
        kernel_initializer=tf.random_normal_initializer(0,0.1),
        bias_initializer = tf.constant_initializer(0.1),
        name = 'V'
    )

定義損失
Critic的損失定義為時間差分值的平方值

with tf.variable_scope('squared_TD_error'):
    self.td_error  = self.r + gamma * self.v_ - self.v
    self.loss = tf.square(self.td_error)


with tf.variable_scope('train'):
    self.train_op = tf.train.AdamOptimizer(lr).minimize(self.loss)

訓練Critic
Critic的任務就是告訴Actor當前選擇的動作好不好,所以我們只要訓練得到TD并返回給Actor就好:

def learn(self,s,r,s_):
    s,s_ = s[np.newaxis,:],s_[np.newaxis,:]

    v_ = self.sess.run(self.v,feed_dict = {self.s:s_})

    td_error,_ = self.sess.run([self.td_error,self.train_op],
                               feed_dict={self.s:s,self.v_:v_,self.r:r})

    return td_error

2.3 整體模型訓練

有了Critic之后,Actor就可以進行單步訓練和更新了,所以訓練中的關鍵的代碼如下:

while True:
      a = actor.choose_action(s)
      s_,r,done,info = env.step(a)
      td_error = critic.learn(s,r,s_)
      actor.learn(s,a,td_error)
      s = s_

3.參考資料

  1. https://morvanzhou.github.io/tutorials/machine-learning/reinforcement-learning/6-1-actor-critic/
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,034評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,413評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,449評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,165評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,559評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,781評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,327評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,084評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,278評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,495評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,927評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,172評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,010評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,241評論 2 375

推薦閱讀更多精彩內容