DQN实现DEMO
找了很多DQN的例子,有原版的实现Atari的,也有Flappy Bird的,但是最简单的还是莫烦大神的Demo,github地址是:https://github.com/MorvanZhou/Reinforcement-learning-with-tensorflow。
?
其中,红色的方块代表寻宝人,黑色的方块代表陷阱,黄色的方块代表宝藏,我们的目标就是让寻宝人找到最终的宝藏。
这里,我们的状态可以用横纵坐标表示,而动作有上下左右四个动作。使用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值,即
?
代码如下:
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值:
?
第一部分的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:]
}
)...
展开详请