4

DQN改进-Double DQN

 2 years ago
source link: https://leesen998.github.io/2018/03/05/DQN%E4%B8%89%E5%A4%A7%E6%94%B9%E8%BF%9B(%E4%B8%80)-Double%20DQN/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

ChMlWlyAyIiIQH8pABv6qMmlUWEAAIp0AAv7LcAG_rA821.jpg

DQN三大改进(一)-Double DQN

我们简单回顾一下DQN的过程(这里是2015版的DQN):

img

DQN中有两个关键的技术,叫做经验回放和双网络结构。

DQN中的损失函数定义为:

img

其中,yi也被我们称为q-target值,而后面的Q(s,a)我们称为q-eval值,我们希望q-target和q-eval值越接近越好。

q-target如何计算呢?根据下面的公式:

img

上面的两个公式分别截取自两篇不同的文章,所以可能有些出入。我们之前说到过,我们有经验池存储的历史经验,经验池中每一条的结构是(s,a,r,s’),我们的q-target值根据该轮的奖励r以及将s’输入到target-net网络中得到的Q(s’,a’)的最大值决定。

我们进一步展开我们的q-target计算公式:

img

也就是说,我们根据状态s’选择动作a’的过程,以及估计Q(s’,a’)使用的是同一张Q值表,或者说使用的同一个网络参数,这可能导致选择过高的估计值,从而导致过于乐观的值估计。为了避免这种情况的出现,我们可以对选择和衡量进行解耦,从而就有了双Q学习,在Double DQN中,q-target的计算基于如下的公式:

img

我们根据一张Q表或者网络参数来选择我们的动作a’,再用另一张Q值表活着网络参数来衡量Q(s’,a’)的值。

2、代码实现

本文的代码还是根据莫烦大神的代码,它的github地址为:https://github.com/MorvanZhou/Reinforcement-learning-with-tensorflow

这里我们想要实现的效果类似于寻宝。

img

其中,红色的方块代表寻宝人,黑色的方块代表陷阱,黄色的方块代表宝藏,我们的目标就是让寻宝人找到最终的宝藏。

这里,我们的状态可以用横纵坐标表示,而动作有上下左右四个动作。使用tkinter来做这样一个动画效果。宝藏的奖励是1,陷阱的奖励是-1,而其他时候的奖励都为0。

接下来,我们重点看一下我们Double-DQN相关的代码。

定义输入

# ------------------------input---------------------------
self.s = tf.placeholder(tf.float32, [None, self.n_features], name='s')
self.q_target = tf.placeholder(tf.float32, [None, self.n_actions], name='Q-target')
self.s_ = tf.placeholder(tf.float32,[None,self.n_features],name='s_')

定义双网络结构

这里我们的双网络结构都简单的采用简单的全链接神经网络,包含一个隐藏层。这里我们得到的输出是一个向量,表示该状态才取每个动作可以获得的Q值:

def build_layers(s,c_name,n_l1,w_initializer,b_initializer):
with tf.variable_scope('l1'):
w1 = tf.get_variable(name='w1',shape=[self.n_features,n_l1],initializer=w_initializer,collections=c_name)
b1 = tf.get_variable(name='b1',shape=[1,n_l1],initializer=b_initializer,collections=c_name)
l1 = tf.nn.relu(tf.matmul(s,w1)+b1)
with tf.variable_scope('l2'):
w2 = tf.get_variable(name='w2',shape=[n_l1,self.n_actions],initializer=w_initializer,collections=c_name)
b2 = tf.get_variable(name='b2',shape=[1,self.n_actions],initializer=b_initializer,collections=c_name)
out = tf.matmul(l1,w2) + b2
return out

接下来,我们定义两个网络:

# ------------------ build evaluate_net ------------------
with tf.variable_scope('eval_net'):
c_names = ['eval_net_params',tf.GraphKeys.GLOBAL_VARIABLES]
n_l1 = 20
w_initializer = tf.random_normal_initializer(0,0.3)
b_initializer =tf.constant_initializer(0.1)
self.q_eval = build_layers(self.s,c_names,n_l1,w_initializer,b_initializer)

# ------------------ build target_net ------------------
with tf.variable_scope('target_net'):
c_names = ['target_net_params', tf.GraphKeys.GLOBAL_VARIABLES]

self.q_next = build_layers(self.s_, c_names, n_l1, w_initializer, b_initializer)

定义损失和优化器
接下来,我们定义我们的损失,和DQN一样,我们使用的是平方损失:

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)

定义我们的经验池
我们使用一个函数定义我们的经验池,经验池每一行的长度为 状态feature * 2 + 2。

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

选择action
我们仍然使用的是e-greedy的选择动作策略,即以e的概率选择随机动作,以1-e的概率通过贪心算法选择能得到最多奖励的动作a。

def choose_action(self,observation):
observation = observation[np.newaxis,:]
actions_value = self.sess.run(self.q_eval,feed_dict={self.s:observation})
action = np.argmax(actions_value)

if np.random.random() > self.epsilon:
action = np.random.randint(0,self.n_actions)
return action

选择数据batch
我们从经验池中选择我们训练要使用的数据。

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,:]

更新target-net
这里,每个一定的步数,我们就更新target-net中的参数:

t_params = tf.get_collection('target_net_params')
e_params = tf.get_collection('eval_net_params')

self.replace_target_op = [tf.assign(t, e) for t, e in zip(t_params, e_params)]

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

更新网络参数
根据Double DQN的做法,我们需要用两个网络的来计算我们的q-target值,同时通过最小化损失来更新网络参数。这里的做法是,根据eval-net的值来选择动作,然后根据target-net的值来计算Q值。

q_next,q_eval4next = self.sess.run([self.q_next, self.q_eval],
feed_dict={self.s_: batch_memory[:, -self.n_features:],
self.s: batch_memory[:, -self.n_features:]})

q_next是根据经验池中下一时刻状态输入到target-net计算得到的q值,而q_eval4next是根据经验池中下一时刻状态s’输入到eval-net计算得到的q值,这个q值主要用来选择动作。

下面的动作用来得到我们batch中的实际动作和奖励

batch_index = np.arange(self.batch_size, dtype=np.int32)
eval_act_index = batch_memory[:, self.n_features].astype(int)
reward = batch_memory[:, self.n_features + 1]

接下来,我们就要来选择动作并计算该动作的q值了,如果是double dqn的话,我们是根据刚刚计算的q_eval4next来选择动作,然后根据q_next来得到q值的。而原始的dqn直接通过最大的q_next来得到q值:

if self.double_q:
max_act4next = np.argmax(q_eval4next, axis=1) # the action that brings the highest value is evaluated by q_eval
selected_q_next = q_next[batch_index, max_act4next] # Double DQN, select q_next depending on above actions
else:
selected_q_next = np.max(q_next, axis=1) # the natural DQN

那么我们的q-target值就可以计算得到了:

q_target = q_eval.copy()
q_target[batch_index, eval_act_index] = reward + self.gamma * selected_q_next

有了q-target值,我们就可以结合eval-net计算的q-eval值来更新网络参数了:

_, self.cost = self.sess.run([self.train_op, self.loss],
feed_dict={self.s: batch_memory[:, :self.n_features],
self.q_target: q_target})
self.cost_his.append(self.cost)

self.epsilon = self.epsilon + self.epsilon_increment if self.epsilon < self.epsilon_max else self.epsilon_max
self.learn_step_counter += 1

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK