【强化学习】DeepQNetwork (DQN )算法详解
> DQN (Deep Q-Learning )是将深度学习deeplearning 与强化学习reinforcementlearning 相结合,实现了从感知到动作的端到端的⾰命性算法。使⽤DQN 玩游戏的话简直6的飞起,其中fladdy bird 这个游戏就已经被DQN 玩坏了。当我们的Q-table 他过于庞⼤⽆法建⽴的话,使⽤DQN 是⼀种很好的选择
1、算法思想
DQN与Qleanring类似都是基于值迭代的算法,但是在普通的Q-learning中,当状态和动作空间是离散且维数不⾼时可使⽤Q-Table储存每个状态动作对的Q值,⽽当状态和动作空间是⾼维连续时,使⽤Q-Table不动作空间和状态太⼤⼗分困难。
所以在此处可以把Q-table更新转化为⼀函数拟合问题,通过拟合⼀个函数function来代替Q-table产⽣Q值,使得相近的状态得到相近的输出动作。因此我们可以想到深度神经⽹络对复杂特征的提取有很好效果,所以可以将DeepLearning与Reinforcement Learning结合。这
就成为了DQN DL与RL结合存在以下问题 :
DL是监督学习需要学习训练集,强化学习不需要训练集只通过环境进⾏返回奖励值reward,同时也存在着噪声和延迟的问题,所以存在很多状态state的reward值都是0也就是样本稀疏DL每个样本之间互相独⽴,⽽RL当前状态的状态值是依赖后⾯的状态返回值的。
当我们使⽤⾮线性⽹络来表⽰值函数的时候可能出现不稳定的问题DQN中的两⼤利器解决了以上问题通过Q-Learning使⽤reward来构造标签通过experience replay(经验池)的⽅法来解决相关性及⾮静态分布问题
使⽤⼀个MainNet产⽣当前Q值,使⽤另外⼀个Target产⽣Target Q
2、experience replay 经验池
经验池DQN中的记忆库⽤来学习之前的经历,⼜因为Q learning 是⼀种 off-policy 离线学习法, 它能学习当前经历着的, 也能学习过去经历过的, 甚⾄是学习别⼈的经历,所以在学习过程中随机的加⼊之前的经验会让神经⽹络更有效率。
所以经验池解决了相关性及⾮静态分布问题。他通过在每个timestep下agent与环境交互得到的转移样本 () 储存到回放记忆⽹络,要训练时就随机拿出⼀些(minibatch)来训练因此打乱其中的相关性。
3、Q-target ⽬标⽹络
Q-targets的作⽤其实也是⼀种打乱相关性的机制,使⽤Q-targets会使得DQN中出现两个结构完全相同但是参数却不同的⽹络,预测Q估计的的⽹络MainNet使⽤的是最新的参数,⽽预测Q现实的神经⽹络TargetNet参数使⽤的却是很久之前的, 表⽰当前⽹络MainNet的输出,⽤来评估当前状态动作对的值函数;表⽰TargetNet的输出,可以解出targetQ并根据LossFunction更新MainNet的参数,每经过⼀定次数的迭代,将MainNet的参数复制给TargetNet。
引⼊TargetNet后,再⼀段时间⾥⽬标Q值使保持不变的,⼀定程度降低了当前Q值和⽬标Q值的相关性,提⾼了算法稳定性。
4、算法流程
4.1、前置公式
s ,a ,r ,s t t t t +1Q (s ,a ;θ)i Q (s ,a ;θ)i
DQN的更新⽅式和Qlearning⼀样,详细的值函数与动作值函数此处不再推导,在Qlearning中有详细讲解不了解的请移步上⼀篇博客DQN的损失函数如下 θ表⽰⽹络参数为均⽅误差损失
4.2、算法流程
DQN中存在两个结构完全相同但是参数却不同的⽹络,预测Q估计的⽹络MainNet使⽤的是最新的参数,⽽预测Q现实的神经⽹络TargetNet参数使⽤的却是很久之前的,表⽰当前⽹络MainNet的输出,⽤来评估当前状态动作对的值函数; 表⽰TargetNet的输出,可以解出targetQ,因此当agent对环境采取动作a时就可以根据上述公式计算出Q并根据LossFunction更新MainNet
的参数,每经过⼀定次数的迭代,将MainNet的参数复制给TargetNet。这样就完成了⼀次学习过程Q (s ,a )←Q (s ,a )+α[r +γmax Q (s ′,a ′)−Q (s ,a )]
a ′L (θ)=E [(TargetQ −Q (s ,a ;θ))]
2TargetQ =r +γmax Q (s ′,a ′;θ)
a ′Q (s ,a ;θ)i Q (s ,a ;θ)i −
5、代码实现
根据morvan⽼师的例⼦所得
class DeepQNetwork:
def __init__(
self,
n_actions,
n_features,
learning_rate=0.01,
reward_decay=0.9,
e_greedy=0.9,
replace_target_iter=300,
memory_size=500,
batch_size=32,
e_greedy_increment=None,
output_graph=True,
):
self.n_actions = n_actions
self.n_features = n_featuresvariable怎么记
self.lr = learning_rate
self.gamma = reward_decay
self.epsilon_max = e_greedy
<_size = memory_size
self.batch_size = batch_size
self.epsilon_increment = e_greedy_increment
self.epsilon = 0 if e_greedy_increment is not None else self.epsilon_max
# 统计训练次数
self.learn_step_counter = 0
# 初始化记忆 memory [s, a, r, s_]
< = np.zeros((_size, n_features * 2 + 2))
# 有两个⽹络组成 [target_net, evaluate_net]
self._build_net()
t_params = tf.get_collection('target_net_params')
e_params = tf.get_collection('eval_net_params')
if output_graph:
# 开启tensorboard
# $ tensorboard --logdir=logs
# tf.train.SummaryWriter soon be deprecated, use following
tf.summary.FileWriter(r'D:\logs', aph)
self.sess.run(tf.global_variables_initializer())
def _build_net(self):
# -------------- 创建 eval 神经⽹络, 及时提升参数 --------------
self.s = tf.placeholder(tf.float32, [None, self.n_features], name='s') # ⽤来接收 observation
self.q_target = tf.placeholder(tf.float32, [None, self.n_actions],
name='Q_target') # ⽤来接收 q_target 的值, 这个之后会通过计算得到
with tf.variable_scope('eval_net'):
# c_names(collections_names) 是在更新 target_net 参数时会⽤到
c_names, n_l1, w_initializer, b_initializer = \
['eval_net_params', tf.GraphKeys.GLOBAL_VARIABLES], 10, \
tf.random_normal_initializer(0., 0.3), tf.constant_initializer(0.1) # config of layers
# eval_net 的第⼀层. collections 是在更新 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 = lu(tf.matmul(self.s, w1) + b1)
# eval_net 的第⼆层. collections 是在更新 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_eval = tf.matmul(l1, w2) + b2
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 神经⽹络, 提供 target Q ---------------------
self.s_ = tf.placeholder(tf.float32, [None, self.n_features], name='s_') # 接收下个 observation
with tf.variable_scope('target_net'):
# c_names(collections_names) 是在更新 target_net 参数时会⽤到
c_names = ['target_net_params', tf.GraphKeys.GLOBAL_VARIABLES]
# target_net 的第⼀层. collections 是在更新 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 = lu(tf.matmul(self.s_, w1) + b1)
# target_net 的第⼆层. collections 是在更新 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
def store_transition(self, s, a, r, s_):
# 判断是否包含对应属性没有就赋予初值
if not hasattr(self, 'memory_counter'):
<_counter = 0
# 纵向延伸
transition = np.hstack((s, [a, r], s_))
# 使⽤新的记忆替换掉旧⽹络的记忆
index = _counter % _size
index = _counter % _size
<[index, :] = transition
<_counter += 1
def choose_action(self, observation):
# 给观测值加上batch_size维度
observation = waxis, :]
if np.random.uniform() < self.epsilon:
# forward feed the observation and get q value for every actions
actions_value = self.sess.run(self.q_eval, feed_dict={self.s: observation})
action = np.argmax(actions_value)
else:
action = np.random.randint(0, self.n_actions)
return action
def learn(self):
# 判断是否应该更新target-net⽹络了
if self.learn_step_counter % place_target_iter == 0:
self.sess.place_target_op)
print('\ntarget_params_replaced\n')
# 从以前的记忆中随机抽取⼀些记忆
_counter > _size:
sample_index = np.random._size, size=self.batch_size)
else:
sample_index = np.random._counter, size=self.batch_size)
batch_memory = [sample_index, :]
q_next, q_eval = self.sess.run(
[self.q_next, self.q_eval],
feed_dict={
self.s_: batch_memory[:, -self.n_features:], # fixed params
self.s: batch_memory[:, :self.n_features], # newest params
})
# change q_ q_eval's action
q_target = py()
# 下⾯这⼏步⼗分重要. q_next, q_eval 包含所有 action 的值,
# ⽽我们需要的只是已经选择好的 action 的值, 其他的并不需要.
# 所以我们将其他的 action 值全变成 0, 将⽤到的 action 误差值反向传递回去, 作为更新凭据.
# 这是我们最终要达到的样⼦, ⽐如 q_target - q_eval = [1, 0, 0] - [-1, 0, 0] = [2, 0, 0]
# q_eval = [-1, 0, 0] 表⽰这⼀个记忆中有我选⽤过 action 0, ⽽ action 0 带来的 Q(s, a0) = -1, 所以其他
的 Q(s, a1) = Q(s, a2) = 0. # q_target = [1, 0, 0] 表⽰这个记忆中的 r+gamma*maxQ(s_) = 1, ⽽且不管在 s_ 上我们取了哪个 action,
# 我们都需要对应上 q_eval 中的 action 位置, 所以就将 1 放在了 action 0 的位置.
# 下⾯也是为了达到上⾯说的⽬的, 不过为了更⽅⾯让程序运算, 达到⽬的的过程有点不同.
# 是将 q_eval 全部赋值给 q_target, 这时 q_target-q_eval 全为 0,
# 不过我们再根据 batch_memory 当中的 action 这个 column 来给 q_target 中的对应的 memory-action 位置来修改赋值.
# 使新的赋值为 reward + gamma * maxQ(s_), 这样 q_target-q_eval 就可以变成我们所需的样⼦.
# 具体在下⾯还有⼀个举例说明.
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)
"""
假如在这个 batch 中, 我们有2个提取的记忆, 根据每个记忆可以⽣产3个 action 的值:
q_eval =
[[1, 2, 3],
[4, 5, 6]]
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论