1. 程式人生 > >強化學習 DQN演算法

強化學習 DQN演算法

(以下內容取自莫凡大神的教程:https://morvanzhou.github.io/tutorials/machine-learning/reinforcement-learning/4-1-A-DQN/

1,什麼是DQN:

一種融合了神經網路和 Q learning 的方法。

2,為什麼提出DQN:

傳統的表格形式的強化學習有這樣一個瓶頸,用表格來儲存每一個狀態 state, 和在這個 state 每個行為 action 所擁有的 Q 值. 而當今問題是在太複雜, 狀態可以多到比天上的星星還多(比如下圍棋). 如果全用表格來儲存它們, 恐怕我們的計算機有再大的記憶體都不夠, 而且每次在這麼大的表格中搜索對應的狀態也是一件很耗時的事. 

3,DQN的輸入形式:

         可以將狀態和動作當成神經網路的輸入, 然後經過神經網路分析後得到動作的 Q 值, 這樣我們就沒必要在表格中記錄 Q 值, 而是直接使用神經網路生成 Q 值.

        也能只輸入狀態值, 輸出所有的動作值, 然後按照 Q learning 的原則, 直接選擇擁有最大值的動作當做下一步要做的動作.

4,神經網路的更新方式:

基於第二種神經網路來分析, 我們知道, 神經網路是要被訓練才能預測出準確的值. 那在強化學習中, 神經網路是如何被訓練的呢? 首先, 我們需要 a1, a2 正確的Q值, 這個 Q 值我們就用之前在 Q learning 中的 Q 現實來代替. 同樣我們還需要一個 Q 估計 來實現神經網路的更新. 所以神經網路的的引數就是老的 NN 引數 加學習率 alpha 乘以 Q 現實 和 Q 估計 的差距.也就是下圖這樣:

我們通過 NN 預測出Q(s2, a1) 和 Q(s2,a2) 的值, 這就是 Q 估計. 然後我們選取 Q 估計中最大值的動作來換取環境中的獎勵 reward. 而 Q 現實中也包含從神經網路分析出來的兩個 Q 估計值, 不過這個 Q 估計是針對於下一步在 s’ 的估計. 最後再通過剛剛所說的演算法更新神經網路中的引數。

5,DQN兩大利器

Experience replay:DQN 有一個記憶庫用於學習之前的經歷,Q learning 是一種 off-policy 離線學習法, 它能學習當前經歷著的, 也能學習過去經歷過的, 甚至是學習別人的經歷. 所以每次 DQN 更新的時候, 我們都可以隨機抽取一些之前的經歷進行學習. 隨機抽取這種做法打亂了經歷之間的相關性, 也使得神經網路更新更有效率.。

Fixed Q-targets:是一種打亂相關性的機理, 如果使用 fixed Q-targets, 我們就會在 DQN 中使用到兩個結構相同但引數不同的神經網路, 預測 Q 估計 的神經網路具備最新的引數, 而預測 Q 現實 的神經網路使用的引數則是很久以前的。

6,DQN演算法:

7,DQN實現走迷宮的小例子

下面的程式碼,是DQN用於環境互動重要的的部分:

def run_maze():
    step = 0    # 用來控制什麼時候學習
    for episode in range(300):
        # 初始化環境
        observation = env.reset()

        while True:
            # 重新整理環境
            env.render()

            # DQN 根據觀測值選擇行為
            action = RL.choose_action(observation)

            # 環境根據行為給出下一個 state, reward, 是否終止
            observation_, reward, done = env.step(action)

            # DQN 儲存記憶
            RL.store_transition(observation, action, reward, observation_)

            # 控制學習起始時間和頻率 (先累積一些記憶再開始學習)
            if (step > 200) and (step % 5 == 0):
                RL.learn()

            # 將下一個 state_ 變為 下次迴圈的 state
            observation = observation_

            # 如果終止, 就跳出迴圈
            if done:
                break
            step += 1   # 總步數

    # end of game
    print('game over')
    env.destroy()


if __name__ == "__main__":
    env = Maze()
    RL = DeepQNetwork(env.n_actions, env.n_features,
                      learning_rate=0.01,
                      reward_decay=0.9,
                      e_greedy=0.9,
                      replace_target_iter=200,  # 每 200 步替換一次 target_net 的引數
                      memory_size=2000, # 記憶上限
                      # output_graph=True   # 是否輸出 tensorboard 檔案
                      )
    env.after(100, run_maze)
    env.mainloop()
    RL.plot_cost()  # 觀看神經網路的誤差曲線

為了使用 Tensorflow 來實現 DQN, 比較推薦的方式是搭建兩個神經網路, target_net 用於預測 q_target 值, 他不會及時更新引數. eval_net 用於預測 q_eval, 這個神經網路擁有最新的神經網路引數. 不過這兩個神經網路結構是完全一樣的, 只是裡面的引數不一樣

兩個神經網路是為了固定住一個神經網路 (target_net) 的引數, target_net 是 eval_net 的一個歷史版本, 擁有 eval_net 很久之前的一組引數, 而且這組引數被固定一段時間, 然後再被 eval_net 的新引數所替換. 而 eval_net 是不斷在被提升的, 所以是一個可以被訓練的網路 trainable=True. 而 target_net 的 trainable=False.

class DeepQNetwork:
    def _build_net(self):
        # -------------- 建立 eval 神經網路, 及時提升引數 --------------
        self.s = tf.placeholder(tf.float32, [None, self.n_features], name='s')  # 用來接收 observation
        self.q_target = tf.placeholder(tf.float32, [None, self.n_actions], name='Q_target') # 用來接收 q_target 的值, 這個之後會通過計算得到
        with tf.variable_scope('eval_net'):
            # c_names(collections_names) 是在更新 target_net 引數時會用到
            c_names, n_l1, w_initializer, b_initializer = \
                ['eval_net_params', tf.GraphKeys.GLOBAL_VARIABLES], 10, \
                tf.random_normal_initializer(0., 0.3), tf.constant_initializer(0.1)  # config of layers

            # eval_net 的第一層. collections 是在更新 target_net 引數時會用到
            with tf.variable_scope('l1'):
                w1 = tf.get_variable('w1', [self.n_features, n_l1], initializer=w_initializer, collections=c_names)
                b1 = tf.get_variable('b1', [1, n_l1], initializer=b_initializer, collections=c_names)
                l1 = tf.nn.relu(tf.matmul(self.s, w1) + b1)

            # eval_net 的第二層. collections 是在更新 target_net 引數時會用到
            with tf.variable_scope('l2'):
                w2 = tf.get_variable('w2', [n_l1, self.n_actions], initializer=w_initializer, collections=c_names)
                b2 = tf.get_variable('b2', [1, self.n_actions], initializer=b_initializer, collections=c_names)
                self.q_eval = tf.matmul(l1, w2) + b2

        with tf.variable_scope('loss'): # 求誤差
            self.loss = tf.reduce_mean(tf.squared_difference(self.q_target, self.q_eval))
        with tf.variable_scope('train'):    # 梯度下降
            self._train_op = tf.train.RMSPropOptimizer(self.lr).minimize(self.loss)

        # ---------------- 建立 target 神經網路, 提供 target Q ---------------------
        self.s_ = tf.placeholder(tf.float32, [None, self.n_features], name='s_')    # 接收下個 observation
        with tf.variable_scope('target_net'):
            # c_names(collections_names) 是在更新 target_net 引數時會用到
            c_names = ['target_net_params', tf.GraphKeys.GLOBAL_VARIABLES]

            # target_net 的第一層. collections 是在更新 target_net 引數時會用到
            with tf.variable_scope('l1'):
                w1 = tf.get_variable('w1', [self.n_features, n_l1], initializer=w_initializer, collections=c_names)
                b1 = tf.get_variable('b1', [1, n_l1], initializer=b_initializer, collections=c_names)
                l1 = tf.nn.relu(tf.matmul(self.s_, w1) + b1)

            # target_net 的第二層. collections 是在更新 target_net 引數時會用到
            with tf.variable_scope('l2'):
                w2 = tf.get_variable('w2', [n_l1, self.n_actions], initializer=w_initializer, collections=c_names)
                b2 = tf.get_variable('b2', [1, self.n_actions], initializer=b_initializer, collections=c_names)
                self.q_next = tf.matmul(l1, w2) + b2

之後程式碼講解和完整程式請參考:https://morvanzhou.github.io/tutorials/machine-learning/reinforcement-learning/4-3-DQN3/

本人對這份程式碼並沒有完全搞明白,正在不斷研究中。。。。。