在DQN代码中关于tf.stop_gradient的认识

记录一下今天遇到的这个问题,方便以后反过头来继续查阅。

在学习莫烦python强化学习中DQN这一节时,莫烦大佬给出了两种DQN代码,大致框架都是一致的,但是仔细一读就会发现在DQN_modified.py文件中对于target_net训练出的结果直接加入到loss值的计算,而RL_brain.py中单独添加了placeholder,将target_net的结果通过placeholder传入eval_net中的loss值进行计算。这些只是说了大概的思路,接下来通过代码详细介绍一下这两的具体区别。

首先来介绍RL_brain.py中比较简单而且容易理解的no stop_gradient写法。代码如下:

##################### eval_net #############################
...
self.q_target = tf.placeholder(tf.float32, [None, self.n_actions], name='Q_target') 
...
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_net #############################
self.s_ = tf.placeholder(tf.float32, [None, self.n_features], name='s_')    # input
with tf.variable_scope('target_net'):

    c_names = ['target_net_params', tf.GraphKeys.GLOBAL_VARIABLES]

    # first layer. collections is used later when assign to 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)

    # second layer. collections is used later when assign to 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

...
...
q_target[batch_index, eval_act_index] = reward + self.gamma * np.max(q_next, axis=1)

从上述代码中可以看出在DQN中会维持两个网络,一个eval net,一个target net。我们对eval net的参数更新是通过MSE + GD来更新的,而target_net的参数是之前eval_net的参数。loss函数中有一个参数q_target,而这个参数是target_net对下一状态的估值。no stop_gradient的做法是对eval net设置一个placeholder,也即引入一个输入,用这个placeholder计算loss。

接下来说一下stop_gradient的写法。

...
# ------------------ 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')
with tf.variable_scope('q_target'):
    q_target = self.r + self.gamma * tf.reduce_max(self.q_next, axis=1, name='Qmax_s_')    # shape=(None, )
    self.q_target = tf.stop_gradient(q_target)
with tf.variable_scope('q_eval'):
    a_indices = tf.stack([tf.range(tf.shape(self.a)[0], dtype=tf.int32), self.a], axis=1)
    self.q_eval_wrt_a = tf.gather_nd(params=self.q_eval, indices=a_indices)    # shape=(None, )
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)

在这段代码中可以看到self.q_target = tf.stop_gradient(q_target)这句,这就是对q_target的反向传递进行截断,在得到target_net的参数后,进行有关计算就得到q_target这个op(运行时就是Tensor了),然后利用通过截断反传得到的self.q_target来计算loss,并没有使用feed_dict。这样可以节约代码的结构,简单清晰明了。

在tensorlfow计算图中,要给那些op注入数据后成为常量tensor才能进行计算。第一种方法中placeholder输入的本身就是计算好了的q_target,也就是说我们通过feed_dict,将对target net进行计算得到的一个q_target,Tensor传入placeholder中,当做常量来对待,我们可以把一次计算(eval/run)看作是一次截图,得到当时各个op的值。这样的话,我们对于eval net中loss的反传就不会影响到target net了。 第二种方法中直接拿target net中的q_target这个op来计算eval net中的loss显然是不妥的,因为我们对loss进行反传时将会影响到target net,这不是我们想看到的结果。因为我们要保持target_net的参数不能实时更新,所以,这里引入stop_gradient来对从loss到target net的反传进行截断,换句话说,通过self.q_target = tf.stop_gradient(q_target),将原本为TensorFlow计算图中的一个op(节点)转为一个常量self.q_target,这时候对于loss的求导反传就不会传到target net去了。 这两种方法要实现的效果是一样的。

参考链接https://blog.csdn.net/u013745804/article/details/79589514,感谢这位博主解释了这个我困惑了很久的问题。

你可能感兴趣的:(tensorflow,深度强化学习)