天天看點

實戰深度強化學習DQN-理論和實踐

1、Q-learning回顧

Q-learning 的 算法過程如下圖所示:

實戰深度強化學習DQN-理論和實踐

在Q-learning中,我們維護一張Q值表,表的維數為:狀态數S * 動作數A,表中每個數代表在目前狀态S下可以采用動作A可以獲得的未來收益的折現和。我們不斷的疊代我們的Q值表使其最終收斂,然後根據Q值表我們就可以在每個狀态下選取一個最優政策。

Q值表的更新公式為:

實戰深度強化學習DQN-理論和實踐
公式中,Q(S,A) 我們可以稱做Q估計值,即我們目前估計的Q值,而:
實戰深度強化學習DQN-理論和實踐

稱為Q-target,即我們使用貝爾曼方程加貪心政策認為實際應該得到的獎勵,我們的目标就是使我們的Q值不斷的接近Q-target值。

2、深度Q網絡(Deep - Q - Network)

2.1 DQN簡介

為什麼會出現DQN呢

在普通的Q-learning中,當狀态和動作空間是離散且維數不高時可使用Q-Table儲存每個狀态動作對的Q值,而當狀态和動作空間是高維連續時,使用Q-Table不現實。

兩篇DQN奠基之作

[1]Playing Atari with Deep Reinforcement Learning

[2]Human-level control through deep reinforcement learning

如何将原始的Q-learning轉換成深度學習問題

将Q-Table的更新問題變成一個函數拟合問題,相近的狀态得到相近的輸出動作。如下式,通過更新參數 θ 使Q函數逼近最優Q值 。是以,DQN就是要設計一個神經網絡結構,通過函數來拟合Q值,即:

實戰深度強化學習DQN-理論和實踐

2.2 DL和RL結合帶來的問題

1、DL需要大量帶标簽的樣本進行監督學習;RL隻有reward傳回值,而且伴随着噪聲,延遲(過了幾十毫秒才傳回),稀疏(很多State的reward是0)等問題;

2、DL的樣本獨立;RL前後state狀态相關;

3、DL目标分布固定;RL的分布一直變化,比如你玩一個遊戲,一個關卡和下一個關卡的狀态分布是不同的,是以訓練好了前一個關卡,下一個關卡又要重新訓練;

4、過往的研究表明,使用非線性網絡表示值函數時出現不穩定等問題。

2.3 DQN解決問題方法

那麼DQN是如何解決上述問題的呢?

1、通過Q-Learning使用reward來構造标簽(對應問題1)

2、通過experience replay(經驗池)的方法來解決相關性及非靜态分布問題(對應問題2、3)

3、使用一個神經網絡産生目前Q值,使用另外一個神經網絡産生Target Q值(對應問題4)

構造标簽

對于函數優化問題,監督學習的一般方法是先确定Loss Function,然後求梯度,使用随機梯度下降等方法更新參數。DQN則基于Q-Learning來确定Loss Function。我們想要使q-target值和q-eval值相差越小越好。DQN中的損失函數是:

實戰深度強化學習DQN-理論和實踐
這裡yi是根據上一個疊代周期或者說target-net網絡的參數計算出的q-target值,跟目前網絡結構中的參數無關,yi的計算如下:
實戰深度強化學習DQN-理論和實踐
這樣,整個目标函數就可以通過随機梯度下降方法來進行優化:
實戰深度強化學習DQN-理論和實踐

經驗回放

經驗池的功能主要是解決相關性及非靜态分布問題。具體做法是把每個時間步agent與環境互動得到的轉移樣本 (st,at,rt,st+1) 儲存到回放記憶單元,要訓練時就随機拿出一些(minibatch)來訓練。(其實就是将遊戲的過程打成碎片存儲,訓練時随機抽取就避免了相關性問題)

雙網絡結構

在Nature 2015版本的DQN中提出了這個改進,使用另一個網絡(這裡稱為target_net)産生Target Q值。具體地,Q(s,a;θi) 表示目前網絡eval_net的輸出,用來評估目前狀态動作對的值函數;Q(s,a;θ−i) 表示target_net的輸出,代入上面求 TargetQ 值的公式中得到目标Q值。根據上面的Loss Function更新eval_net的參數,每經過N輪疊代,将MainNet的參數複制給target_net。

引入target_net後,再一段時間裡目标Q值使保持不變的,一定程度降低了目前Q值和目标Q值的相關性,提高了算法穩定性。

2.4 DQN算法流程

NIPS 2013版

實戰深度強化學習DQN-理論和實踐
Nature 2015版
實戰深度強化學習DQN-理論和實踐

可以看到,兩版的DQN都使用了經驗池,而2015版的DQN增加了target-net,提高了算法穩定性。

3、DQN實作DEMO

找了很多DQN的例子,有原版的實作Atari的,也有Flappy Bird的,但是最簡單的還是莫煩大神的Demo,github位址是:

https://github.com/MorvanZhou/Reinforcement-learning-with-tensorflow

在介紹整個Demo前,我們介紹兩種DQN的實作方式,一種是将s和a輸入到網絡,得到q值,另一種是隻将s輸入到網絡,輸出為s和每個a結合的q值。這裡莫煩大神的代碼采取了後一種方式。

如果你對DQN的原理有比較深刻的認識,那麼讀莫煩大神的代碼也并不是十分困難。這裡我們想要實作的效果類似于尋寶。

實戰深度強化學習DQN-理論和實踐

其中,紅色的方塊代表尋寶人,黑色的方塊代表陷阱,黃色的方塊代表寶藏,我們的目标就是讓尋寶人找到最終的寶藏。

這裡,我們的狀态可以用橫縱坐标表示,而動作有上下左右四個動作。使用tkinter來做這樣一個動畫效果。寶藏的獎勵是1,陷阱的獎勵是-1,而其他時候的獎勵都為0。

接下來,我們重點看一下我們DQN相關的代碼。

定義相關輸入

這了,我們用s代表目前狀态,用a代表目前狀态下采取的動作,r代表獲得的獎勵,s_代表轉移後的狀态。

self.s = tf.placeholder(tf.float32,[None,self.n_features],name='s')
self.s_ = tf.placeholder(tf.float32,[None,self.n_features],name='s_')
self.r = tf.placeholder(tf.float32,[None,],name='r')
self.a = tf.placeholder(tf.int32,[None,],name='a')
           

經驗池

def store_transition(self,s,a,r,s_):
     if not hasattr(self, 'memory_counter'):
         self.memory_counter = 0
     # hstack:Stack arrays in sequence horizontally
     transition = np.hstack((s,[a,r],s_))
     index = self.memory_counter % self.memory_size
     self.memory[index,:] = transition
     self.memory_counter += 1
           

target_net和eval_net的網絡結構必須保持一緻,這裡我們使用的是兩層全連結的神經網絡,值得注意的一點是對于eval_net來說,網絡的輸入是目前的狀态s,而對target_net網絡來說,網絡的輸入是下一個狀态s_,因為target_net的輸出要根據貝爾曼公式計算q-target值,即

實戰深度強化學習DQN-理論和實踐

代碼如下:

w_initializer, b_initializer = tf.random_normal_initializer(0., 0.3), tf.constant_initializer(0.1)

# ------------------ build evaluate_net ------------------
with tf.variable_scope('eval_net'):
    e1 = tf.layers.dense(self.s,20,tf.nn.relu,kernel_initializer=w_initializer,
                         bias_initializer=b_initializer,name='e1'
                         )

    self.q_eval = tf.layers.dense(e1,self.n_actions,kernel_initializer=w_initializer,
                                  bias_initializer=b_initializer,name='q')

# ------------------ build target_net ------------------

with tf.variable_scope('target_net'):
    t1 = tf.layers.dense(self.s_, 20, tf.nn.relu, kernel_initializer=w_initializer,
                         bias_initializer=b_initializer, name='t1')
    self.q_next = tf.layers.dense(t1, self.n_actions, kernel_initializer=w_initializer,
                                  bias_initializer=b_initializer, name='t2')
           

每隔一定的步數,我們就要将target_net中的參數複制到eval_net中:

t_params = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES,scope='target_net')
e_params = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES,scope='eval_net')

with tf.variable_scope('soft_replacement'):
      self.target_replace_op = [tf.assign(t,e) for t,e in zip(t_params,e_params)]
           

計算損失并優化

首先,對于eval_net來說,我們隻要得到目前的網絡輸出即可,但是我們定義的網絡輸出是四個動作對應的q-eval值,我們要根據實際的a來選擇對應的q-eval值,這一部分的代碼如下:

with tf.variable_scope('q_eval'):
    # tf.stack
    #a = tf.constant([1,2,3])
    # b = tf.constant([4,5,6])
    # c = tf.stack([a,b],axis=1)
    # [[1 4]
    #  [2 5]
    # [3 6]]
    a_indices = tf.stack([tf.range(tf.shape(self.a)[0], dtype=tf.int32), self.a], axis=1)
    # 用indices從張量params得到新張量
    # indices = [[0, 0], [1, 1]]
    # params = [['a', 'b'], ['c', 'd']]
    # output = ['a', 'd']
    # 這裡self.q_eval是batch * action_number,a_indices是batch * 1,也就是說選擇目前估計每個動作的Q值
    self.q_eval_wrt_a = tf.gather_nd(params=self.q_eval, indices=a_indices)
           

中間有幾個函數不太了解的,上面都有詳細的注釋,如果還不是很了解的話,大家可以百度或者閱讀相應函數的源碼。

對于target_net網絡來說,我們要根據下面的式子來計算q-target值:

實戰深度強化學習DQN-理論和實踐

第一部分的R我們是已經得到了的,剩下的就是根據貪心政策選擇四個輸出中最大的一個即可:

with tf.variable_scope('q_target'):
    q_target = self.r + self.gamma * tf.reduce_max(self.q_next,axis=1,name='Qmax_s_')
    # 一個節點被 stop之後,這個節點上的梯度,就無法再向前BP了
    self.q_target = tf.stop_gradient(q_target)
           

接下來,我們就可以定義我們的損失函數并選擇優化器進行優化:

with tf.variable_scope('loss'):
    self.loss = tf.reduce_mean(tf.squared_difference(self.q_target,self.q_eval_wrt_a,name='TD_error'))

with tf.variable_scope('train'):
    self._train_op = tf.train.RMSPropOptimizer(self.lr).minimize(self.loss)
           

網絡的訓練

每隔一定的步數,我們就要将eval_net中的參數複制到target_net中,同時我們要從經驗池中選擇batch大小的資料輸入到網絡中進行訓練。

def learn(self):
    if self.learn_step_counter % self.replace_target_iter == 0:
        self.sess.run(self.target_replace_op)
        print('\ntarget_params_replaced\n')

    if self.memory_counter > self.memory_size:
        sample_index = np.random.choice(self.memory_size,size=self.batch_size)
    else:
        sample_index = np.random.choice(self.memory_counter,size = self.batch_size)

    batch_memory = self.memory[sample_index,:]

    _,cost = self.sess.run(
        [self._train_op,self.loss],
        feed_dict={
            self.s:batch_memory[:,:self.n_features],
            self.a:batch_memory[:,self.n_features],
            self.r:batch_memory[:,self.n_features+1],
            self.s_:batch_memory[:,-self.n_features:]
        }
    )
           

剩下的代碼就不介紹啦,大家不妨去github上fork大神的代碼,跟着進行練習,相信會對DQN的原理有一個更進一步的認識。

原文釋出時間為:2018-07-29

本文作者:石曉文

本文來自雲栖社群合作夥伴“

Python愛好者社群

”,了解相關資訊可以關注“