DQN三大改进(三)-Dueling Network
1、Dueling Network
什么是Dueling Deep Q Network呢?看下面的图片
上面是我们传统的DQN,下面是我们的Dueling DQN。在原始的DQN中,神经网络直接输出的是每种动作的 Q值, 而 Dueling DQN 每个动作的 Q值 是有下面的公式确定的:
它分成了这个 state 的值, 加上每个动作在这个 state 上的 advantage。我们通过下面这张图来解释一下:
在这款赛车游戏中。左边是 state value, 发红的部分证明了 state value 和前面的路线有关, 右边是 advantage, 发红的部分说明了 advantage 很在乎旁边要靠近的车子, 这时的动作会受更多 advantage 的影响. 发红的地方左右了自己车子的移动原则。
但是,利用上面的式子计算Q值会出现一个unidentifiable问题:给定一个Q,是无法得到唯一的V和A的。比如,V和A分别加上和减去一个值能够得到同样的Q,但反过来显然无法由Q得到唯一的V和A。
解决方法
强制令所选择贪婪动作的优势函数为0:
则我们能得到唯一的值函数:
解决方法的改进
使用优势函数的平均值代替上述的最优值
采用这种方法,虽然使得值函数V和优势函数A不再完美的表示值函数和优势函数(在语义上的表示),但是这种操作提高了稳定性。而且,并没有改变值函数V和优势函数A的本质表示。
2、代码实现
这里我们想要实现的效果类似于寻宝。
其中,红色的方块代表寻宝人,黑色的方块代表陷阱,黄色的方块代表宝藏,我们的目标就是让寻宝人找到最终的宝藏。
这里,我们的状态可以用横纵坐标表示,而动作有上下左右四个动作。使用tkinter来做这样一个动画效果。宝藏的奖励是1,陷阱的奖励是-1,而其他时候的奖励都为0。
接下来,我们重点看一下我们Dueling-DQN相关的代码。
定义输入
1 | # ------------------------input--------------------------- |
定义网络结构
根据Dueling DQN的网络结构,我们首先定义一个隐藏层,针对隐藏层的输出,我们将此输出分别作为两个隐藏层的输入,分别输出state的Value,和每个action的Advantage,最后, 根据Q = V+A得到每个action的Q值:
1 | def build_layers(s, c_names, n_l1, w_initializer, b_initializer): |
接下来,我们定义我们的eval-net和target-net
1 | # -----------------------------eval net --------------------- |
定义损失和优化器
接下来,我们定义我们的损失,和DQN一样,我们使用的是平方损失:
1 | with tf.variable_scope('loss'): |
定义经验池
我们使用一个函数定义我们的经验池,经验池每一行的长度为 状态feature * 2 + 2。
1 | def store_transition(self,s,a,r,s_): |
选择action
我们仍然使用的是e-greedy的选择动作策略,即以e的概率选择随机动作,以1-e的概率通过贪心算法选择能得到最多奖励的动作a。
1 | def choose_action(self,observation): |
选择数据batch
我们从经验池中选择我们训练要使用的数据。
1 | if self.memory_counter > self.memory_size: |
更新target-net
这里,每个一定的步数,我们就更新target-net中的参数:
1 | t_params = tf.get_collection('target_net_params') |
更新网络参数
我们使用DQN的做法来更新网络参数:
1 | q_next = self.sess.run(self.q_next, feed_dict={self.s_: batch_memory[:, -self.n_features:]}) # next observation |