4

DQN三大改进(三)-Dueling Network

 2 years ago
source link: https://leesen998.github.io/2018/03/12/DQN%E4%B8%89%E5%A4%A7%E6%94%B9%E8%BF%9B(%E4%B8%89)-Dueling%20Network/
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

photo-1538831539254-abe4ffd1a812.jpg

DQN三大改进(三)-Dueling Network

1、Dueling Network

什么是Dueling Deep Q Network呢?看下面的图片

img

上面是我们传统的DQN,下面是我们的Dueling DQN。在原始的DQN中,神经网络直接输出的是每种动作的 Q值, 而 Dueling DQN 每个动作的 Q值 是有下面的公式确定的:

img

它分成了这个 state 的值, 加上每个动作在这个 state 上的 advantage。我们通过下面这张图来解释一下:

img

在这款赛车游戏中。左边是 state value, 发红的部分证明了 state value 和前面的路线有关, 右边是 advantage, 发红的部分说明了 advantage 很在乎旁边要靠近的车子, 这时的动作会受更多 advantage 的影响. 发红的地方左右了自己车子的移动原则。

但是,利用上面的式子计算Q值会出现一个unidentifiable问题:给定一个Q,是无法得到唯一的V和A的。比如,V和A分别加上和减去一个值能够得到同样的Q,但反过来显然无法由Q得到唯一的V和A。

解决方法
强制令所选择贪婪动作的优势函数为0:

img

则我们能得到唯一的值函数:

img

解决方法的改进
使用优势函数的平均值代替上述的最优值

img

采用这种方法,虽然使得值函数V和优势函数A不再完美的表示值函数和优势函数(在语义上的表示),但是这种操作提高了稳定性。而且,并没有改变值函数V和优势函数A的本质表示。

2、代码实现

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

img

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

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

接下来,我们重点看一下我们Dueling-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_')

定义网络结构
根据Dueling DQN的网络结构,我们首先定义一个隐藏层,针对隐藏层的输出,我们将此输出分别作为两个隐藏层的输入,分别输出state的Value,和每个action的Advantage,最后, 根据Q = V+A得到每个action的Q值:

def build_layers(s, c_names, n_l1, w_initializer, b_initializer):
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(s, w1) + b1)

if self.dueling:
with tf.variable_scope('Value'):
w2 = tf.get_variable('w2',[n_l1,1],initializer=w_initializer,collections=c_names)
b2 = tf.get_variable('b2',[1,1],initializer=b_initializer,collections=c_names)
self.V = tf.matmul(l1,w2) + b2

with tf.variable_scope('Advantage'):
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.A = tf.matmul(l1,w2) + b2

with tf.variable_scope('Q'):
out = self.V + self.A - tf.reduce_mean(self.A,axis=1,keep_dims=True)

else:
with tf.variable_scope('Q'):
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)
out = tf.matmul(l1, w2) + b2
return out

接下来,我们定义我们的eval-net和target-net

# -----------------------------eval net ---------------------
with tf.variable_scope('eval_net'):
c_names, n_l1, w_initializer, b_initializer = \
['eval_net_params', tf.GraphKeys.GLOBAL_VARIABLES], 20, \
tf.random_normal_initializer(0., 0.3), tf.constant_initializer(0.1) # config of layers

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')

更新网络参数
我们使用DQN的做法来更新网络参数:

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

q_target = q_eval.copy()

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_target[batch_index, eval_act_index] = reward + self.gamma * np.max(q_next, axis=1)

_, 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