1. 程式人生 > >強化學習(八)價值函式的近似表示與Deep Q-Learning

強化學習(八)價值函式的近似表示與Deep Q-Learning

    在強化學習系列的前七篇裡,我們主要討論的都是規模比較小的強化學習問題求解演算法。今天開始我們步入深度強化學習。這一篇關注於價值函式的近似表示和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個格子,那麼對於運動員的座標和冰球的座標就有$2^5*2^5=2^{10}$次種,如果再加上個體速度的分量就更是天文數字了,此時之前講過的強化學習方法都會因為問題的規模太大而無法使用。怎麼辦呢?必須要對問題的建模做修改了,而價值函式的近似表示就是一個可行的方法。

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的演算法流程。    

    演算法輸入:迭代輪數$T$,狀態特徵維度$n$, 動作集$A$, 步長$\alpha$,衰減因子$\gamma$, 探索率$\epsilon$, Q網路結構, 批量梯度下降的樣本數$m$。

    輸出:所有的狀態和動作對應的價值$Q$

    1. 隨機初始化所有的狀態和動作對應的價值$Q$.  隨機初始化Q網路的所有引數$w$。清空經驗回放的集合$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.2episode: 100 Evaluation Average Reward: 9.4episode: 200 Evaluation Average Reward: 10.4episode: 300 Evaluation Average Reward: 10.5episode: 400 Evaluation Average Reward: 11.6episode: 500 Evaluation Average Reward: 12.4episode: 600 Evaluation Average Reward: 29.6episode: 700 Evaluation Average Reward: 48.1episode: 800 Evaluation Average Reward: 85.0episode: 900 Evaluation Average Reward: 169.4episode: 1000 Evaluation Average Reward: 200.0episode: 1100 Evaluation Average Reward: 200.0episode: 1200 Evaluation Average Reward: 200.0episode: 1300 Evaluation Average Reward: 200.0episode: 1400 Evaluation Average Reward: 200.0episode: 1500 Evaluation Average Reward: 200.0episode: 1600 Evaluation Average Reward: 200.0episode: 1700 Evaluation Average Reward: 200.0episode: 1800 Evaluation Average Reward: 200.0episode: 1900 Evaluation Average Reward: 200.0episode: 2000 Evaluation Average Reward: 200.0episode: 2100 Evaluation Average Reward: 200.0episode: 2200 Evaluation Average Reward: 200.0episode: 2300 Evaluation Average Reward: 200.0episode: 2400 Evaluation Average Reward: 200.0episode: 2500 Evaluation Average Reward: 200.0episode: 2600 Evaluation Average Reward: 200.0episode: 2700 Evaluation Average Reward: 200.0episode: 2800 Evaluation Average Reward: 200.0episode: 2900 Evaluation Average Reward: 200.0

    大概到第1000次迭代後,演算法已經收斂,達到最高的200分。當然由於是$\epsilon-$探索,每次前面的輸出可能不同,但最後應該都可以收斂到200的分數。

5. Deep Q-Learning小結    

    DQN由於對價值函式做了近似表示,因此有了解決大規模強化學習問題的能力。但是DQN有個問題,就是它並不一定能保證Q網路的收斂,也就是說,我們不一定可以得到收斂後的Q網路引數。這會導致我們訓練出的模型效果很差。

     針對這個問題,衍生出了DQN的很多變種,比如Double DQN,Dueling DQN等。這些我們在一篇討論。

(歡迎轉載,轉載請註明出處。歡迎溝通交流: [email protected])