近日,学习了百度飞桨深度学习学院推出的强化学习课程,通过课程学习并结合网上一些知识,对DQN知识做了一个总结笔记。本篇文章内容涉及DQN算法介绍以及利用DQN解决MountainCar。
强化学习的目标是学习到策略,使得累计回报的期望值最大,即:
为了便于求解最优策略,引入值函数和动作状态值函数来评价某个状态和动作的优劣。值函数的定义如下:
动作状态值函数定义为:
求解值函数和动作状态值函数方法有基于表的方法和基于值函数逼近的方法。对于基于表的方法而言,传统的动态规划、蒙卡特罗和时间差分(Temporal Difference,TD)算法都属于表方法,本质是建立一张 Q(s,a) 的表,行为状态,列为动作,通过循环迭代计算来不断更新表中值。如下图中的上面三个环境维度所示,当状态较少时,可用Q表去装下这些维度,实际决策时去遍历较小的表耗时不是那么明显。但是当环境状态很大时,比如一盘围棋的状态,机器人的运动状态这些的状态量都是不可数的,也就无法用基于表的方法。因此,基于值函数逼近的方法更加适合实际环境。
2015年, DeepMind团队提出了深度Q网络(deep Q-network, DQN), 网络框架如图1所示. DQN只使用游戏的原始图像作为输入, 不依赖于人工提取特征,是一种端到端的学习方式. DQN创新性地将深度卷积神经网络和Q-learning结合到一起, 在Atari视频游戏上达到了人类玩家的控制效果. 通过经验回放技术和固定目标Q网络, DQN有效解决了使用神经网络非线性动作值函数逼近器带来的不稳定和发散性问题, 极大提升了强化学习的适用性. 经验回放增加了历史数据的利用率, 同时随机采样打破了数据间的相关性, 与目标Q网络的结合进一步稳定了动作值函数的训练过程. 此外, 通过截断奖赏和正则化网络参数, 梯度被限制到合适的范围内, 从而可以得到更加鲁棒的训练过程。DQN网络结构图网络结构图如下所示:
输入:从 Atari 游戏的一帧RGB图像提取出代表亮度(luminance)的 Y 通道, 并resize成 84×84, 将这样图像的连续m帧作为输入, 论文里取m=4,即连续四帖图像作为游戏的输入信息
输出:K个离散动作值
近似和算法设置理论:
整个过程的核心变为如何确定来近似值函数,最经典的做法就是采用梯度下降最小化损失函数来不断的调试网络权重θ,Loss function定义为:
其中,θ_i^-是第i次迭代的target网络参数,θ_i是Q-network网络参数,接下来就是对θ求梯度,如公式:
另外,在学习过程中,将训练的四元组存进一个replay memory中,在学习过程中以min-batch读取训练网络结构。
DQN伪代码如下
其中,两个非常重要的思想:经验回放和目标网络
(1) Experience Replay,其将系统探索环境得到的数据储存起来,然后随机采样样本更新深度神经网络的参数。
Experience Replay的原因:
1、深度神经网络作为有监督学习模型,要求数据满足独立同分布
2、Q Learning 算法得到的样本前后是有关系的。为了打破数据之间的关联性,Experience Replay 方法通过存储-采样的方法将这个关联性打破了。
在这个问题中,之所以加入experience replay是因为样本是从游戏中的连续帧获得的,这与简单RL比如maze)相比,样本的关联性大了很多,如果没有experience replay,算法在连续一段时间内基本朝着同一个方向做梯度下降,那么同样的步长下这样直接计算gradient就有可能不收敛。因此experience replay是从一个memory pool中随机选取了一些 experience,然后再求梯度,从而避免了这个问题
Experience Replay优点:
1、数据利用率高,因为一个样本被多次使用。
2、连续样本的相关性会使参数更新的方差(variance)比较大,该机制可减少这种相关性。注意这里用的是均匀随机采样
(2)TargetNet: 引入TargetNet后,在一段时间里目标Q值使保持不变的,一定程度降低了当前Q值和目标Q值的相关性,提高了算法稳定性。用另一个TargetNet产生Target Q值。具体地, 表示当前网络MainNet的输出,用来评估当前状态动作对的值函数, 表示TargetNet的输出,代入上面求 TargetQ 值的公式中得到目标Q值。根据上面的Loss Function更新MainNet的参数,每经过N轮迭代,将MainNet的参数复制给TargetNet。
本次利用IARL的框架进行DQN的训练,GitHub地址:https://github.com/PaddlePaddle/PARL
DQN在IARL的架构如下:
由图可知,IARL的架构还是很清晰合理的,Agent把产生的数据传给algorithm,algorithm根据model的模型结构计算出Loss,使用SGD或者其他优化器不断的优化,PARL这种架构可以很方便的应用在各类深度强化学习问题中。
class Model(parl.Model):
def __init__(self, act_dim):
hid1_size = 512
hid2_size = 256
hid3_size = 128
# 3层全连接网络
self.fc1 = layers.fc(size=hid1_size, act='relu')
self.fc2 = layers.fc(size=hid2_size, act='relu')
self.fc3 = layers.fc(size=hid3_size, act='relu')
self.fc4 = layers.fc(size=act_dim, act=None)
def value(self, obs):
# 定义网络
# 输入state,输出所有action对应的Q,[Q(s,a1), Q(s,a2), Q(s,a3)...]
h1 = self.fc1(obs)
h2 = self.fc2(h1)
h3 = self.fc3(h2)
Q = self.fc4(h3)
return Q
from parl.algorithms import DQN # 直接从parl库中导入DQN算法,无需自己重写算法
class Agent(parl.Agent):
def __init__(self,
algorithm,
obs_dim,
act_dim,
e_greed=0.1,
e_greed_decrement=0):
assert isinstance(obs_dim, int)
assert isinstance(act_dim, int)
self.obs_dim = obs_dim
self.act_dim = act_dim
super(Agent, self).__init__(algorithm)
self.global_step = 0
self.update_target_steps = 200 # 每隔200个training steps再把model的参数复制到target_model中
self.e_greed = e_greed # 有一定概率随机选取动作,探索
self.e_greed_decrement = e_greed_decrement # 随着训练逐步收敛,探索的程度慢慢降低
def build_program(self):
self.pred_program = fluid.Program()
self.learn_program = fluid.Program()
with fluid.program_guard(self.pred_program): # 搭建计算图用于 预测动作,定义输入输出变量
obs = layers.data(
name='obs', shape=[self.obs_dim], dtype='float32')
self.value = self.alg.predict(obs)
with fluid.program_guard(self.learn_program): # 搭建计算图用于 更新Q网络,定义输入输出变量
obs = layers.data(
name='obs', shape=[self.obs_dim], dtype='float32')
action = layers.data(name='act', shape=[1], dtype='int32')
reward = layers.data(name='reward', shape=[], dtype='float32')
next_obs = layers.data(
name='next_obs', shape=[self.obs_dim], dtype='float32')
terminal = layers.data(name='terminal', shape=[], dtype='bool')
self.cost = self.alg.learn(obs, action, reward, next_obs, terminal)
def sample(self, obs):
sample = np.random.rand() # 产生0~1之间的小数
if sample < self.e_greed:
act = np.random.randint(self.act_dim) # 探索:每个动作都有概率被选择
else:
act = self.predict(obs) # 选择最优动作
self.e_greed = max(
0.01, self.e_greed - self.e_greed_decrement) # 随着训练逐步收敛,探索的程度慢慢降低
return act
def predict(self, obs): # 选择最优动作
obs = np.expand_dims(obs, axis=0)
pred_Q = self.fluid_executor.run(
self.pred_program,
feed={'obs': obs.astype('float32')},
fetch_list=[self.value])[0]
pred_Q = np.squeeze(pred_Q, axis=0)
act = np.argmax(pred_Q) # 选择Q最大的下标,即对应的动作
return act
def learn(self, obs, act, reward, next_obs, terminal):
# 每隔200个training steps同步一次model和target_model的参数
if self.global_step % self.update_target_steps == 0:
self.alg.sync_target()
self.global_step += 1
act = np.expand_dims(act, -1)
feed = {
'obs': obs.astype('float32'),
'act': act.astype('int32'),
'reward': reward,
'next_obs': next_obs.astype('float32'),
'terminal': terminal
}
cost = self.fluid_executor.run(
self.learn_program, feed=feed, fetch_list=[self.cost])[0] # 训练一次网络
return cost
具体代码在这:https://aistudio.baidu.com/aistudio/projectdetail/594386
解决的环境细节:https://github.com/openai/gym/wiki/MountainCar-v0
解决环境要求平均回报至少大于-110.0分:
tips:训练过程中可以先将learning rate调大(3~5倍)训练,接着缩小learning rate在上一阶段训练的网络基础上接着训练,可以更容易收敛!
以上便是DQN的一些入门知识,已经在IARL上的实现。如有错误,欢迎指正!
参考
[1] Mnih, V., Kavukcuoglu, K., Silver, D., Graves, A., Antonoglou, I., Wierstra, D., and Riedmiller, M.(Dec 2013). Playing Atari with deep reinforcement learning. Technical Report arXiv:1312.5602[cs.LG], Deepmind Technologies.
[2] Mnih, V., Kavukcuoglu, K., Silver, D. et al. Human-level control through deep reinforcement learning. Nature 518, 529–533 (2015).
[3] 唐振韬, 邵坤, 赵冬斌,等. 深度强化学习进展:从AlphaGo到AlphaGo Zero%Recent progress of deep reinforcement learning:from AlphaGo to AlphaGo Zero[J]. 控制理论与应用, 2017, 034(012):1529-1546.
[4] 刘全, 翟建伟, 章宗长,等. 深度强化学习综述[J]. 计算机学报, 2018, 041(001):1-27.
知乎
1.DQN 从入门到放弃1 DQN与增强学习(https://zhuanlan.zhihu.com/p/21262246)
2.DQN(Deep Q-learning)算法原理与实现(https://zhuanlan.zhihu.com/p/97856004)