強化學習 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/
本人對這份程式碼並沒有完全搞明白,正在不斷研究中。。。。。