【强化学习】Deep Q Network(DQN)算法详解

【强化学习】Deep Q Network(DQN)算法详解DQN DeepQ Learning 是将深度学习 deeplearning 与强化学习 reinforcemen 相结合 实现了从感知到动作的端到端的革命性算法 使用 DQN 玩游戏的话简直 6 的飞起 其中 fladdybird 这个游戏就已经被 DQN 玩坏了 当我们的 Q table 他过于庞大无法建立的话 使用 DQN 是一种很好的选择 1 算法思想 DQN 与 Qleanring 类似

> DQN(Deep Q-Learning)是将深度学习deeplearning与强化学习reinforcementlearning相结合,实现了从感知到动作的端到端的革命性算法。使用DQN玩游戏的话简直6的飞起,其中fladdy bird这个游戏就已经被DQN玩坏了。当我们的Q-table他过于庞大无法建立的话,使用DQN是一种很好的选择 

引用 morvan大神的教程图片 https://morvanzhou.github.io/tutorials/machine-learning/reinforcement-learning/4-1-A-DQN/

1、算法思想

  • DL是监督学习需要学习训练集,强化学习不需要训练集只通过环境进行返回奖励值reward,同时也存在着噪声和延迟的问题,所以存在很多状态state的reward值都是0也就是样本稀疏
  • DL每个样本之间互相独立,而RL当前状态的状态值是依赖后面的状态返回值的。
  • 当我们使用非线性网络来表示值函数的时候可能出现不稳定的问题

DQN中的两大利器解决了以上问题

  • 通过Q-Learning使用reward来构造标签
  • 通过experience replay(经验池)的方法来解决相关性及非静态分布问题
  • 使用一个MainNet产生当前Q值,使用另外一个Target产生Target Q

2、experience replay 经验池

3、Q-target 目标网络

Q-targets的作用其实也是一种打乱相关性的机制,使用Q-targets会使得DQN中出现两个结构完全相同但是参数却不同的网络,预测Q估计的的网络MainNet使用的是最新的参数,而预测Q现实的神经网络TargetNet参数使用的却是很久之前的, Q ( s , a ; θ i ) Q(s,a;θ_i) Q(s,a;θi) 表示当前网络MainNet的输出,用来评估当前状态动作对的值函数; Q ( s , a ; θ i ) Q(s,a;θ_i) Q(s,a;θi)表示TargetNet的输出,可以解出targetQ并根据LossFunction更新MainNet的参数,每经过一定次数的迭代,将MainNet的参数复制给TargetNet。
引入TargetNet后,再一段时间里目标Q值使保持不变的,一定程度降低了当前Q值和目标Q值的相关性,提高了算法稳定性。

4、算法流程

4.1、前置公式

DQN的更新方式和Qlearning一样,详细的值函数与动作值函数此处不再推导,在Qlearning中有详细讲解不了解的请移步上一篇博客

Q ( s , a ) ← Q ( s , a ) + α [ r + γ m a x a ′ Q ( s ′ , a ′ ) − Q ( s , a ) ] Q(s,a)←Q(s,a)+α[r+γmax_{a′}Q(s′,a′)−Q(s,a)] Q(s,a)Q(s,a)+α[r+γmaxaQ(s,a)Q(s,a)]

DQN的损失函数如下 θ表示网络参数为均方误差损失

L ( θ ) = E [ ( T a r g e t Q − Q ( s , a ; θ ) ) 2 ] L(θ)=E[(TargetQ−Q(s,a;θ))^2] L(θ)=E[(TargetQQ(s,a;θ))2]
T a r g e t Q = r + γ m a x a ′ Q ( s ′ , a ′ ; θ ) TargetQ=r+γmax_{a′}Q(s′,a′;θ) TargetQ=r+γmaxaQ(s,a;θ)
这里写图片描述
4.2、算法流程






DQN中存在两个结构完全相同但是参数却不同的网络,预测Q估计的网络MainNet使用的是最新的参数,而预测Q现实的神经网络TargetNet参数使用的却是很久之前的, Q ( s , a ; θ i ) Q(s,a;θ_i) Q(s,a;θi)表示当前网络MainNet的输出,用来评估当前状态动作对的值函数; Q ( s , a ; θ i − ) Q(s,a;θ^−_i) Q(s,a;θi) 表示TargetNet的输出,可以解出targetQ,因此当agent对环境采取动作a时就可以根据上述公式计算出Q并根据LossFunction更新MainNet的参数,每经过一定次数的迭代,将MainNet的参数复制给TargetNet。这样就完成了一次学习过程
算法伪代码
在这里插入图片描述




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_features self.lr = learning_rate self.gamma = reward_decay self.epsilon_max = e_greedy self.replace_target_iter = replace_target_iter self.memory_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_] self.memory = np.zeros((self.memory_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') self.replace_target_op = [tf.assign(t, e) for t, e in zip(t_params, e_params)] self.sess = tf.Session() if output_graph: # 开启tensorboard # $ tensorboard --logdir=logs # tf.train.SummaryWriter soon be deprecated, use following tf.summary.FileWriter(r'D:\logs', self.sess.graph) self.sess.run(tf.global_variables_initializer()) self.cost_his = [] 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 = tf.nn.relu(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 = tf.nn.relu(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'): 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 def choose_action(self, observation): # 给观测值加上batch_size维度 observation = observation[np.newaxis, :] 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 % self.replace_target_iter == 0: self.sess.run(self.replace_target_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, :] 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_target w.r.t q_eval's action q_target = q_eval.copy() # 下面这几步十分重要. 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]] q_target = q_eval = [[1, 2, 3], [4, 5, 6]] 然后根据 memory 当中的具体 action 位置来修改 q_target 对应 action 上的值: 比如在: 记忆 0 的 q_target 计算值是 -1, 而且我用了 action 0; 记忆 1 的 q_target 计算值是 -2, 而且我用了 action 2: q_target = [[-1, 2, 3], [4, 5, -2]] 所以 (q_target - q_eval) 就变成了: [[(-1)-(1), 0, 0], [0, 0, (-2)-(6)]] 最后我们将这个 (q_target - q_eval) 当成误差, 反向传递会神经网络. 所有为 0 的 action 值是当时没有选择的 action, 之前有选择的 action 才有不为0的值. 我们只反向传递之前选择的 action 的值, """ # 训练eval网络 _, 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) # 因为在训练过程中会逐渐收敛所以此处动态设置增长epsilon self.epsilon = self.epsilon + self.epsilon_increment if self.epsilon < self.epsilon_max else self.epsilon_max self.learn_step_counter += 1 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/218025.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 上午8:13
下一篇 2026年3月18日 上午8:13


相关推荐

  • ESLint简介

    ESLint简介一ESLint简介ESLint是一个语法规则和代码风格的检查工具,可以用来保证写出语法正确、风格统一的代码。不管是多人合作还是个人项目,代码规范是很重要的。这样做不仅可以很大程度地避免基本语法错误,也保证了代码的可读性。这所谓工欲善其事,必先利其器,推荐ESLint+vscode来写vue,有种飞一般的感觉。每次保存,vscode就能标红不符合ESLint规则的地方,同时还会做一些简单的自我修正。二启用ESLint1ESLint插件安装vscode的ESLint插件,

    2022年6月18日
    34
  • 青鸟s1java云题库答案_北大青鸟S1java内部测试试卷(试题)

    青鸟s1java云题库答案_北大青鸟S1java内部测试试卷(试题)本测试题为北大青鸟内部 java 测试题 仅供参考理论知识试卷注意 将答案写在答题纸上 不得在试卷上作答 选择题 针对以下题目 请选择最符合题目要求的答案 针对每一道题目 所有答案都选对 该题目得分 所选答案错误或不能选出所有答案 则该题不得分 50 题 每小题 2 分 1 下面 A C 是合法的

    2026年3月19日
    1
  • 自用vim配置文件.vimrc「建议收藏」

    自用vim配置文件.vimrc「建议收藏」.vimrcsetshowmatch”generalsetmouse=vsetnumbersetautochdirsetautoreadsetlaststatus=2″alwayshavestatus-line”setcursorline”hiCursorLinecterm=NONEctermbg=lightbluec

    2022年5月9日
    46
  • msiexec安装参数详解

    msiexec安装参数详解1 安装 i 表示安装 x 表示卸载 f 表示修复 l v 表示输出详细日志安装参数有 qn 无用户界面 qb 基本界面 qr 精简界面 qf 完整界面 默认值 q 设置 UI 级别 q qn 没有 UI qb 基本 UI qr 简化的 UI 在安装结束时显示一个模式对话框 qf 完整 UI 在安装结束时显示一个模式对话框 qn 没有 UI

    2025年8月12日
    9
  • Kong网关初探_API网关

    Kong网关初探_API网关安装Kong安装文档Kong开源版不提供dashboard支持,只有Kong企业版才有该功能。但有第三方控制台Konga同样可以友好地管理KongAdminAPI对象,快速安装如下:dockerrun-d-p1337:1337\–namekonga\–network=kong-net\-eDB_ADAPTER=postgres\-eDB_HOST=kong-database\-eDB_PORT=5432\-eDB_USER=kong\

    2025年10月21日
    4
  • 海思Hi3798处理器参数,Hi3798芯片详细信息介绍[通俗易懂]

    海思Hi3798处理器参数,Hi3798芯片详细信息介绍[通俗易懂]Hi3798CV200集成4核64位高性能CortexA53处理器、内置NEON加速引擎,强大的CPU处理能力可以满足各种差异化的业务需求。在码流兼容性、在线视频播放的流畅性、图像质量以及整机性能方面保持业界最好的用户体验。支持4K2KP60@10bit超高清视频解码和显示,支持H.265/HEVC、H.264/AVC、AVS+、MVC、MPEG2、MPEG4、VC-1、VP6、VP…

    2022年6月30日
    107

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号