在之前的幾篇文章中,我們介紹了基于價值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_