为什么需要DQN

我们知道,最原始的Q-learning算法在执行过程中始终需要一个Q表进行记录,当维数不高时Q表尚可满足需求,但当遇到指数级别的维数时,Q表的效率就显得十分有限。因此,我们考虑一种值函数近似的方法,实现每次只需事先知晓S或者A,就可以实时得到其对应的Q值。DQN中采用了深度神经网络作为值函数近似的工具,这种方法被证明十分有效。

DQN简介

Q-learning算法很早就有了,但是其与深度学习的结合是在2013年的DeepMind发布的《Playing Atari with Deep Reinforcement Learning》论文中才实现的。这篇论文创造性的将RL与DL实现了融合,提出了存储记忆(Experience Replay)机制和Fixed-Q-Target,实现了一部分Atari游戏操控,甚至超过了人类水平。

讲到这篇论文的创新点,就是刚刚提到的Experience Replay 和 Fixed-Q-Target.

Experience Replay 经验回放

设计一个经验回放机制,就好比设置一个记忆存储池,这个池子有一定的空间上限,池子里存放着过去一段时间内的经验,包括s,a,r,s_next,分别对应当前s,当前action,当前reward,下一个s.一组(s,a,r,s_next)称为一组transition。

在执行learn()时,智能体会随机从经验池中抽取定量的transition,以进行学习,既可以学习现在也可以学习过去的经验,同时随机的抽取也降低了样本之间的相关性导致的过拟合。

Fixed-Q-Target目标网络

该论文的另一个创新点在于没有只采用一个神经网络进行训练,而是设计了两个网络,评估网络和目标网络,这两个网络在一开始拥有相同的结构和参数配置。在训练过程中,评估网络的参数不断地更新优化,只在一定间隔时间点将评估网络的参数赋给目标网络。评估网络根据输入的s状态给出预测的q_value,目标网络根据s_next的输入给出q_next,再将q_next代入Q-learning的核心递推式中可以得到q_target,最后loss(q_value,q_target)反向传播并优化参数即可不断地得到更好的神经网络,这样经过反复的训练优化后,智能体基本可以掌握各种状态下的action决策。

DQN代码实现

OpenAI gym

openAI开发的python第三方库gym,是一个现阶段主流的强化学习入门的环境。可以为强化学习算法提供许多游戏或者控制场景的接口,让开发者更多关注在算法的研究上而不是环境的设计。

#gym库的基本使用操作如下
import gym
env = gym.make("CartPole-v1")      #调用该环境
observation = env.reset()         #重置环境
for _ in range(1000):env.render()                     #打开环境窗口action = env.action_space.sample() # 从行为空间随机挑选动作observation, reward, done, info = env.step(action)   #执行一步action之后的返回值if done:observation = env.reset()
env.close()                         #关闭环境

接下来以CartPole游戏为例,具体实现DQN算法:
本项目采用pytorch深度学习框架,先设置好超参数。配置好环境。

import torch                                    # 导入torch
import torch.nn as nn                           # 导入torch.nn
import torch.nn.functional as F                 # 导入torch.nn.functional
import numpy as np                              # 导入numpy
import gym                                      # 导入gym# 超参数
BATCH_SIZE = 32                                 # 样本数量
LR = 0.01                                       # 学习率
EPSILON = 0.9                                   # greedy policy
GAMMA = 0.9                                     # reward discount
TARGET_REPLACE_ITER = 100                       # 目标网络更新频率
MEMORY_CAPACITY = 2000                          # 记忆库容量
env = gym.make('CartPole-v0').unwrapped         # 使用gym库中的环境:CartPole,且打开封装
N_ACTIONS = env.action_space.n                  # 杆子动作个数 (2个)
N_STATES = env.observation_space.shape[0]       # 杆子状态个数 (4个)

定义Net类,本质是两个全连接层神经网络构成输入和输出,中间加一个relu激活函数层。该神经网络模型的输入维度是N_STATES,输入是游戏中的状态向量(需转换为tensor类型)。输出维度是N_ACTIONS,输出是各个action的价值,即actions_value。两个全连接层的初始权重设置为方差为0.1的正态分布。

# 定义Net类 (定义网络)
class Net(nn.Module):def __init__(self):                                                         # 定义Net的一系列属性# nn.Module的子类函数必须在构造函数中执行父类的构造函数super(Net, self).__init__()                                             # 等价与nn.Module.__init__()self.fc1 = nn.Linear(N_STATES, 50)                                      # 设置第一个全连接层(输入层到隐藏层): 状态数个神经元到50个神经元self.fc1.weight.data.normal_(0, 0.1)                                    # 权重初始化 (均值为0,方差为0.1的正态分布)self.out = nn.Linear(50, N_ACTIONS)                                     # 设置第二个全连接层(隐藏层到输出层): 50个神经元到动作数个神经元self.out.weight.data.normal_(0, 0.1)                                    # 权重初始化 (均值为0,方差为0.1的正态分布)def forward(self, x):                                                       # 定义forward函数 (x为状态)x = F.relu(self.fc1(x))                                                 # 连接输入层到隐藏层,且使用激励函数ReLU来处理经过隐藏层后的值actions_value = self.out(x)                                             # 连接隐藏层到输出层,获得最终的输出值 (即动作值)return actions_value                                                    # 返回动作值

定义DQN类,主线思路是围绕eval_net和target_net两个网络展开,定义choose_action函数实现部分贪婪策略选择action,store_transition函数实现经验的存储管理,最核心的函数是learn函数,当经验池中存放满了经验之后,智能体开始在池中随机抽取经验进行学习,每次抽取BATCH_SIZE的行数,经过整理后得到b_s,b_a,b_r,b_s_四个可以被送入神经网络的张量。

接下来,将b_s送入eval_net得到输出q_eval;将b_s_送入target_net得到输出q_next(注意,这次送入神经网络只是为了得到q_next,我们并不关心此刻target_net网络权重参数是否优化,因此我们采用了.detach()方法来阻断反向传播),最后就是我们炼丹的常用套路,将q_value和q_target放入loss_function中,然后反向传播,采用Adam优化算法来更新评估网络参数。

# 定义DQN类 (定义两个网络)
class DQN(object):def __init__(self):                                                         # 定义DQN的一系列属性self.eval_net, self.target_net = Net(), Net()                           # 利用Net创建两个神经网络: 评估网络和目标网络self.learn_step_counter = 0                                             # for target updatingself.memory_counter = 0                                                 # for storing memoryself.memory = np.zeros((MEMORY_CAPACITY, N_STATES * 2 + 2))             # 初始化记忆库,一行代表一个transitionself.optimizer = torch.optim.Adam(self.eval_net.parameters(), lr=LR)    # 使用Adam优化器 (输入为评估网络的参数和学习率)self.loss_func = nn.MSELoss()                                           # 使用均方损失函数 (loss(xi, yi)=(xi-yi)^2)def choose_action(self, x):                                                 # 定义动作选择函数 (x为状态)x = torch.unsqueeze(torch.FloatTensor(x), 0)                            # 将x转换成32-bit floating point形式,并在dim=0增加维数为1的维度if np.random.uniform() < EPSILON:                                       # 生成一个在[0, 1)内的随机数,如果小于EPSILON,选择最优动作actions_value = self.eval_net.forward(x)                            # 通过对评估网络输入状态x,前向传播获得动作值action = torch.max(actions_value, 1)[1].data.numpy()                # 输出每一行最大值的索引,并转化为numpy ndarray形式action = action[0]                                                  # 输出action的第一个数else:                                                                   # 随机选择动作action = np.random.randint(0, N_ACTIONS)                            # 这里action随机等于0或1 (N_ACTIONS = 2)return action                                                           # 返回选择的动作 (0或1)def store_transition(self, s, a, r, s_):                                    # 定义记忆存储函数 (这里输入为一个transition)transition = np.hstack((s, [a, r], s_))                                 # 在水平方向上拼接数组# 如果记忆库满了,便覆盖旧的数据index = self.memory_counter % MEMORY_CAPACITY                           # 获取transition要置入的行数self.memory[index, :] = transition                                      # 置入transitionself.memory_counter += 1                                                # memory_counter自加1def learn(self):                                                            # 定义学习函数(记忆库已满后便开始学习)# 目标网络参数更新if self.learn_step_counter % TARGET_REPLACE_ITER == 0:                  # 一开始触发,然后每100步触发self.target_net.load_state_dict(self.eval_net.state_dict())         # 将评估网络的参数赋给目标网络self.learn_step_counter += 1                                            # 学习步数自加1# 抽取记忆库中的批数据sample_index = np.random.choice(MEMORY_CAPACITY, BATCH_SIZE)            # 在[0, 2000)内随机抽取32个数,可能会重复b_memory = self.memory[sample_index, :]                                 # 抽取32个索引对应的32个transition,存入b_memoryb_s = torch.FloatTensor(b_memory[:, :N_STATES])# 将32个s抽出,转为32-bit floating point形式,并存储到b_s中,b_s为32行4列b_a = torch.LongTensor(b_memory[:, N_STATES:N_STATES+1].astype(int))# 将32个a抽出,转为64-bit integer (signed)形式,并存储到b_a中 (之所以为LongTensor类型,是为了方便后面torch.gather的使用),b_a为32行1列b_r = torch.FloatTensor(b_memory[:, N_STATES+1:N_STATES+2])# 将32个r抽出,转为32-bit floating point形式,并存储到b_s中,b_r为32行1列b_s_ = torch.FloatTensor(b_memory[:, -N_STATES:])# 将32个s_抽出,转为32-bit floating point形式,并存储到b_s中,b_s_为32行4列# 获取32个transition的评估值和目标值,并利用损失函数和优化器进行评估网络参数更新q_eval = self.eval_net(b_s).gather(1, b_a)# eval_net(b_s)通过评估网络输出32行每个b_s对应的一系列动作值,然后.gather(1, b_a)代表对每行对应索引b_a的Q值提取进行聚合q_next = self.target_net(b_s_).detach()# q_next不进行反向传递误差,所以detach;q_next表示通过目标网络输出32行每个b_s_对应的一系列动作值q_target = b_r + GAMMA * q_next.max(1)[0].view(BATCH_SIZE, 1)# q_next.max(1)[0]表示只返回每一行的最大值,不返回索引(长度为32的一维张量);.view()表示把前面所得到的一维张量变成(BATCH_SIZE, 1)的形状;最终通过公式得到目标值loss = self.loss_func(q_eval, q_target)# 输入32个评估值和32个目标值,使用均方损失函数self.optimizer.zero_grad()                                      # 清空上一步的残余更新参数值loss.backward()                                                 # 误差反向传播, 计算参数更新值self.optimizer.step()                                           # 更新评估网络的所有参数

最后写主程序,跑400个episode,基本可以实现较好的游戏控制效果。

dqn = DQN()                                                             # 令dqn=DQN类for i in range(400):                                                    # 400个episode循环print('<<<<<<<<<Episode: %s' % i)s = env.reset()                                                     # 重置环境episode_reward_sum = 0                                              # 初始化该循环对应的episode的总奖励while True:                                                         # 开始一个episode (每一个循环代表一步)env.render()                                                    # 显示实验动画a = dqn.choose_action(s)                                        # 输入该步对应的状态s,选择动作s_, r, done, info = env.step(a)                                 # 执行动作,获得反馈# 修改奖励 (不修改也可以,修改奖励只是为了更快地得到训练好的摆杆)x, x_dot, theta, theta_dot = s_r1 = (env.x_threshold - abs(x)) / env.x_threshold - 0.8r2 = (env.theta_threshold_radians - abs(theta)) / env.theta_threshold_radians - 0.5new_r = r1 + r2dqn.store_transition(s, a, new_r, s_)                 # 存储样本episode_reward_sum += new_r                           # 逐步加上一个episode内每个step的rewards = s_                                                # 更新状态if dqn.memory_counter > MEMORY_CAPACITY:              # 如果累计的transition数量超过了记忆库的固定容量2000# 开始学习 (抽取记忆,即32个transition,并对评估网络参数进行更新,并在开始学习后每隔100次将评估网络的参数赋给目标网络)dqn.learn()if done:       # 如果done为True# round()方法返回episode_reward_sum的小数点四舍五入到2个数字print('episode%s---reward_sum: %s' % (i, round(episode_reward_sum, 2)))break                                             # 该episode结束env.close()

总结

DQN是深度学习和强化学习结合的一个例子,在游戏操控领域大放异彩,其本质思想仍然是Q-learning的时序差分算法和贪婪策略思想。在借助了神经网络的作用下,实现了价值函数近似的功能,并且利用经验回放机制和双神经网络架构,保证了算法的稳定性,降低了过拟合的概率。本文只是对于DQN入门级别的介绍。后续在学习过程中还会不断补充更加进一步地学习强化学习。

参考链接

强化学习DQN及其变体原理与PARL实现
DQN 的算法思想和代码实现
DQN PyTorch代码详解​

DQN(Deep Q Network)及其代码实现相关推荐

  1. DQN(Deep Q Network)论文笔记

    1.简介 Deep Q Network 属于强化学习.本文是从<Playing Atari with Deep Reinforcement Learning>翻译总结的. 本模型包括卷积神 ...

  2. dqn/deep q network

    文章目录 核心思想 细节 experience replay fixed q-values 核心思想 基于q-learning,用深度神经网络,评估当前state和action组合的q值 细节 q-l ...

  3. 【强化学习】Deep Q Network深度Q网络(DQN)

    1 DQN简介 1.1 强化学习与神经网络 该强化学习方法是这么一种融合了神经网络和Q-Learning的方法,名字叫做Deep Q Network. Q-Learning使用表格来存储每一个状态st ...

  4. 【深度学习入门到精通系列】Deep Q Network

    文章目录 1 什么是 DQN 2 DQN 算法更新 (Tensorflow) 3 DQN 神经网络 (Tensorflow) 4 DQN 思维决策 (Tensorflow) 1 什么是 DQN 我们使 ...

  5. Deep Q Network 算法

     Deep Q Network 算法前置基础知识: Reinforcement Learning 基本概念 Q Leaning算法原理 深度学习神经网络知识 Tensorflow.Pytorch.Py ...

  6. 《ARPG游戏深度强化学习 》之实现一个Deep Q-learning Network(二叉树状态QDN(逐条分析python代码))

    实现一个Deep Q-learning Network(二叉树状态DQN) 代码主要解决的问题: 有0-6,7个状态,从上图是他们可以状态跳转的方向和关系. 我们设定状态6是最终GDAL,目标,训练a ...

  7. 深度学习(四十)——深度强化学习(3)Deep Q-learning Network(2), DQN进化史

    Deep Q-learning Network(续) Nature DQN DQN最早发表于NIPS 2013,该版本的DQN,也被称为NIPS DQN.NIPS DQN除了提出DQN的基本概念之外, ...

  8. Deep Q learning: DQN及其改进

    Deep Q Learning Generalization Deep Reinforcement Learning 使用深度神经网络来表示 价值函数 策略 模型 使用随机梯度下降(SGD)优化los ...

  9. 【CTR模型】TensorFlow2.0 的 DCN(Deep Cross Network) 实现与实战(附代码+数据)

    CTR 系列文章: 广告点击率(CTR)预测经典模型 GBDT + LR 理解与实践(附数据 + 代码) CTR经典模型串讲:FM / FFM / 双线性 FFM 相关推导与理解 CTR深度学习模型之 ...

最新文章

  1. 写给自己的web开发资源
  2. uoj#268. 【清华集训2016】数据交互(动态dp+堆)
  3. TPL中Task执行的内联性线程重入
  4. Asp.net中服务端控件事件是如何触发的(笔记)
  5. 【Android】刷Rom:adb reboot bootloader
  6. boot.oat FC问题分析报告
  7. centos7 改虚拟网卡名称
  8. 面对 Google、Facebook、微软等科技巨头的围剿,夹缝中的初创企业该何去何从?...
  9. 蚂蚁金服自主研发的三地五中心异地多活解决方案获金融科技创新大奖
  10. 扬州大学广陵学院c语言试卷,扬州大学广陵学院交直流调速复习题答案(试卷)
  11. android内存卡测试,安卓sd卡真假检测工具_内存卡检测扩容卡软件_sd insight
  12. Struts1框架轻易入门,经典示例
  13. 德育知识元素挖掘系统设计 软件工程 spring boot + Vue.js + python机器学习
  14. Sh.k6p index.php,第二篇:shell基础命令(部分)
  15. 没事别想不开去创业公司
  16. 百度发展史,百度发展历程
  17. 玩转华为ENSP模拟器系列 | 配置IS-IS基本功能示例
  18. 小数点化分数的过程_小数怎么化成分数
  19. SQL Server - sql 空间数据
  20. 读《CSS禅意花园》 有感

热门文章

  1. 使用Visual Studio Code搭建TypeScript开发环境
  2. 洛谷P2455 [SDOI2006]线性方程组
  3. WindowsServer2012 R2配置远程服务器并使用域名访问(非 IIS)
  4. XX大学学生选课系统需求规格说明书
  5. 天天搜题 大学生在线免费的搜题网站
  6. android文字闪动效果
  7. 2022年双十一百亿补贴,2022年聚划算双11玩法解读
  8. Softmax激活函数
  9. nestjs入门(controller,service,module)
  10. Python 浮点数计算 小数取舍指南