在強化學習系列的前七篇里,我們主要討論的都是規模比較小的強化學習問題求解算法。今天開始我們步入深度強化學習。這一篇關注於價值函數的近似表示和Deep Q-Learning算法。
Deep Q-Learning這一篇對應Sutton書的第11章部分和UCL強化學習課程的第六講。
1. 為何需要價值函數的近似表示
在之前講到了強化學習求解方法,無論是動態規划DP,蒙特卡羅方法MC,還是時序差分TD,使用的狀態都是離散的有限個狀態集合$\mathbb{S}$。此時問題的規模比較小,比較容易求解。但是假如我們遇到復雜的狀態集合呢?甚至很多時候,狀態是連續的,那么就算離散化后,集合也很大,此時我們的傳統方法,比如Q-Learning,根本無法在內存中維護這么大的一張Q表。
比如經典的冰球世界(PuckWorld) 強化學習問題,具體的動態demo見這里。環境由一個正方形區域構成代表着冰球場地,場地內大的圓代表着運動員個體,小圓代表着目標冰球。在這個正方形環境中,小圓會每隔一定的時間隨機改變在場地的位置,而代表個體的大圓的任務就是盡可能快的接近冰球目標。大圓可以操作的行為是在水平和豎直共四個方向上施加一個時間步時長的一個大小固定的力,借此來改變大圓的速度。環境會在每一個時間步內告訴個體當前的水平與垂直坐標、當前的速度在水平和垂直方向上的分量以及目標的水平和垂直坐標共6項數據,獎勵值為個體與目標兩者中心距離的負數,也就是距離越大獎勵值越低且最高獎勵值為0。
在這個問題中,狀態是一個6維的向量,並且是連續值。沒法直接用之前離散集合的方法來描述狀態。當然,你可以說,我們可以把連續特征離散化。比如把這個冰球場100x100的框按1x1的格子划分成10000個格子,那么對於運動員的坐標和冰球的坐標就有$10^4*10^4=10^{8}$次種,如果再加上個體速度的分量就更是天文數字了,此時之前講過的強化學習方法都會因為問題的規模太大而無法使用。怎么辦呢?必須要對問題的建模做修改了,而價值函數的近似表示就是一個可行的方法。
2. 價值函數的近似表示方法
由於問題的狀態集合規模大,一個可行的建模方法是價值函數的近似表示。方法是我們引入一個狀態價值函數$\hat{v}$, 這個函數由參數$w$描述,並接受狀態$s$作為輸入,計算后得到狀態$s$的價值,即我們期望:$$\hat{v}(s, w) \approx v_{\pi}(s)$$
類似的,引入一個動作價值函數$\hat{q}$,這個函數由參數$w$描述,並接受狀態$s$與動作$a$作為輸入,計算后得到動作價值,即我們期望:$$\hat{q}(s,a,w) \approx q_{\pi}(s,a)$$
價值函數近似的方法很多,比如最簡單的線性表示法,用$\phi(s)$表示狀態s的特征向量,則此時我們的狀態價值函數可以近似表示為:$$\hat{v}(s, w) = \phi(s)^Tw$$
當然,除了線性表示法,我們還可以用決策樹,最近鄰,傅里葉變換,神經網絡來表達我們的狀態價值函數。而最常見,應用最廣泛的表示方法是神經網絡。因此后面我們的近似表達方法如果沒有特別提到,都是指的神經網絡的近似表示。
對於神經網絡,可以使用DNN,CNN或者RNN。沒有特別的限制。如果把我們計算價值函數的神經網絡看做一個黑盒子,那么整個近似過程可以看做下面這三種情況:
對於狀態價值函數,神經網絡的輸入是狀態s的特征向量,輸出是狀態價值$\hat{v}(s, w)$。對於動作價值函數,有兩種方法,一種是輸入狀態s的特征向量和動作a,輸出對應的動作價值$\hat{q}(s,a,w)$,另一種是只輸入狀態s的特征向量,動作集合有多少個動作就有多少個輸出$\hat{q}(s,a_i,w)$。這里隱含了我們的動作是有限個的離散動作。
對於我們前一篇講到的Q-Learning算法,我們現在就價值函數的近似表示來將其改造,采用上面右邊的第三幅圖的動作價值函數建模思路來做,現在我們叫它Deep Q-Learning。
3. Deep Q-Learning算法思路
Deep Q-Learning算法的基本思路來源於Q-Learning。但是和Q-Learning不同的地方在於,它的Q值的計算不是直接通過狀態值s和動作來計算,而是通過上面講到的Q網絡來計算的。這個Q網絡是一個神經網絡,我們一般簡稱Deep Q-Learning為DQN。
DQN的輸入是我們的狀態s對應的狀態向量$\phi(s)$, 輸出是所有動作在該狀態下的動作價值函數Q。Q網絡可以是DNN,CNN或者RNN,沒有具體的網絡結構要求。
DQN主要使用的技巧是經驗回放(experience replay),即將每次和環境交互得到的獎勵與狀態更新情況都保存起來,用於后面目標Q值的更新。為什么需要經驗回放呢?我們回憶一下Q-Learning,它是有一張Q表來保存所有的Q值的當前結果的,但是DQN是沒有的,那么在做動作價值函數更新的時候,就需要其他的方法,這個方法就是經驗回放。
通過經驗回放得到的目標Q值和通過Q網絡計算的Q值肯定是有誤差的,那么我們可以通過梯度的反向傳播來更新神經網絡的參數$w$,當$w$收斂后,我們的就得到的近似的Q值計算方法,進而貪婪策略也就求出來了。
下面我們總結下DQN的算法流程,基於NIPS 2013 DQN。
算法輸入:迭代輪數$T$,狀態特征維度$n$, 動作集$A$, 步長$\alpha$,衰減因子$\gamma$, 探索率$\epsilon$, Q網絡結構, 批量梯度下降的樣本數$m$。
輸出:Q網絡參數
1. 隨機初始化Q網絡的所有參數$w$,基於$w$初始化所有的狀態和動作對應的價值$Q$。清空經驗回放的集合$D$。
2. for i from 1 to T,進行迭代。
a) 初始化S為當前狀態序列的第一個狀態, 拿到其特征向量$\phi(S)$
b) 在Q網絡中使用$\phi(S)$作為輸入,得到Q網絡的所有動作對應的Q值輸出。用$\epsilon-$貪婪法在當前Q值輸出中選擇對應的動作$A$
c) 在狀態$S$執行當前動作$A$,得到新狀態$S'$對應的特征向量$\phi(S')和獎勵$R$,是否終止狀態is_end
d) 將$\{\phi(S),A,R,\phi(S'),is\_end\}$這個五元組存入經驗回放集合$D$
e) $S=S'$
f) 從經驗回放集合$D$中采樣$m$個樣本$\{\phi(S_j),A_j,R_j,\phi(S'_j),is\_end_j\}, j=1,2.,,,m$,計算當前目標Q值$y_j$:$$y_j= \begin{cases} R_j& {is\_end_j\; is \;true}\\ R_j + \gamma\max_{a'}Q(\phi(S'_j),A'_j,w) & {is\_end_j \;is\; false} \end{cases}$$
g) 使用均方差損失函數$\frac{1}{m}\sum\limits_{j=1}^m(y_j-Q(\phi(S_j),A_j,w))^2$,通過神經網絡的梯度反向傳播來更新Q網絡的所有參數$w$
h) 如果$S'$是終止狀態,當前輪迭代完畢,否則轉到步驟b)
注意,上述第二步的f步和g步的Q值計算也都需要通過Q網絡計算得到。另外,實際應用中,為了算法較好的收斂,探索率$\epsilon$需要隨着迭代的進行而變小。
4. Deep Q-Learning實例
下面我們用一個具體的例子來演示DQN的應用。這里使用了OpenAI Gym中的CartPole-v0游戲來作為我們算法應用。CartPole-v0游戲的介紹參見這里。它比較簡單,基本要求就是控制下面的cart移動使連接在上面的pole保持垂直不倒。這個任務只有兩個離散動作,要么向左用力,要么向右用力。而state狀態就是這個cart的位置和速度, pole的角度和角速度,4維的特征。堅持到200分的獎勵則為過關。
完整的代碼參見我的github: https://github.com/ljpzzz/machinelearning/blob/master/reinforcement-learning/dqn.py
代碼參考了知乎上的一個DQN實例,修改了代碼中的一些錯誤,並用最新的Python3.6+Tensorflow1.8.0運行。要跑代碼需要安裝OpenAI的Gym庫,使用"pip install gym"即可。
代碼使用了一個三層的神經網絡,輸入層,一個隱藏層和一個輸出層。下面我們看看關鍵部分的代碼。
算法第2步的步驟b通過$\epsilon-$貪婪法選擇動作的代碼如下,注意每次我們$\epsilon-$貪婪法后都會減小$\epsilon$值。
def egreedy_action(self,state): Q_value = self.Q_value.eval(feed_dict = { self.state_input:[state] })[0] if random.random() <= self.epsilon: self.epsilon -= (INITIAL_EPSILON - FINAL_EPSILON) / 10000 return random.randint(0,self.action_dim - 1) else: self.epsilon -= (INITIAL_EPSILON - FINAL_EPSILON) / 10000 return np.argmax(Q_value)
算法第2步的步驟c在狀態$S$執行當前動作$A$的代碼如下,這個交互是由Gym完成的。
next_state,reward,done,_ = env.step(action) # Define reward for agent reward = -1 if done else 0.1
算法第2步的步驟d保存經驗回放數據的代碼如下:
def perceive(self,state,action,reward,next_state,done): one_hot_action = np.zeros(self.action_dim) one_hot_action[action] = 1 self.replay_buffer.append((state,one_hot_action,reward,next_state,done)) if len(self.replay_buffer) > REPLAY_SIZE: self.replay_buffer.popleft() if len(self.replay_buffer) > BATCH_SIZE: self.train_Q_network()
算法第2步的步驟f,g計算目標Q值,並更新Q網絡的代碼如下:
def train_Q_network(self): self.time_step += 1 # Step 1: obtain random minibatch from replay memory minibatch = random.sample(self.replay_buffer,BATCH_SIZE) state_batch = [data[0] for data in minibatch] action_batch = [data[1] for data in minibatch] reward_batch = [data[2] for data in minibatch] next_state_batch = [data[3] for data in minibatch] # Step 2: calculate y y_batch = [] Q_value_batch = self.Q_value.eval(feed_dict={self.state_input:next_state_batch}) for i in range(0,BATCH_SIZE): done = minibatch[i][4] if done: y_batch.append(reward_batch[i]) else : y_batch.append(reward_batch[i] + GAMMA * np.max(Q_value_batch[i])) self.optimizer.run(feed_dict={ self.y_input:y_batch, self.action_input:action_batch, self.state_input:state_batch })
我們在每100輪迭代完后會去玩10次交互測試,計算10次的平均獎勵。運行了代碼后,我的3000輪迭代的輸出如下:
episode: 0 Evaluation Average Reward: 12.2
episode: 100 Evaluation Average Reward: 9.4
episode: 200 Evaluation Average Reward: 10.4
episode: 300 Evaluation Average Reward: 10.5
episode: 400 Evaluation Average Reward: 11.6
episode: 500 Evaluation Average Reward: 12.4
episode: 600 Evaluation Average Reward: 29.6
episode: 700 Evaluation Average Reward: 48.1
episode: 800 Evaluation Average Reward: 85.0
episode: 900 Evaluation Average Reward: 169.4
episode: 1000 Evaluation Average Reward: 200.0
episode: 1100 Evaluation Average Reward: 200.0
episode: 1200 Evaluation Average Reward: 200.0
episode: 1300 Evaluation Average Reward: 200.0
episode: 1400 Evaluation Average Reward: 200.0
episode: 1500 Evaluation Average Reward: 200.0
episode: 1600 Evaluation Average Reward: 200.0
episode: 1700 Evaluation Average Reward: 200.0
episode: 1800 Evaluation Average Reward: 200.0
episode: 1900 Evaluation Average Reward: 200.0
episode: 2000 Evaluation Average Reward: 200.0
episode: 2100 Evaluation Average Reward: 200.0
episode: 2200 Evaluation Average Reward: 200.0
episode: 2300 Evaluation Average Reward: 200.0
episode: 2400 Evaluation Average Reward: 200.0
episode: 2500 Evaluation Average Reward: 200.0
episode: 2600 Evaluation Average Reward: 200.0
episode: 2700 Evaluation Average Reward: 200.0
episode: 2800 Evaluation Average Reward: 200.0
episode: 2900 Evaluation Average Reward: 200.0
大概到第1000次迭代后,算法已經收斂,達到最高的200分。當然由於是$\epsilon-$探索,每次前面的輸出可能不同,但最后應該都可以收斂到200的分數。當然由於DQN不保證絕對的收斂,所以可能到了200分后還會有抖動。
5. Deep Q-Learning小結
DQN由於對價值函數做了近似表示,因此有了解決大規模強化學習問題的能力。但是DQN有個問題,就是它並不一定能保證Q網絡的收斂,也就是說,我們不一定可以得到收斂后的Q網絡參數。這會導致我們訓練出的模型效果很差。
針對這個問題,衍生出了DQN的很多變種,比如Nature DQN(NIPS 2015), Double DQN,Dueling DQN等。這些我們在下一篇討論。
(歡迎轉載,轉載請注明出處。歡迎溝通交流: liujianping-ok@163.com)