深度强化学习
第五篇:深度Q学习的四种新策略
提纲
- 前言
- fixed Q-targets
- double DQNs
- duelingDQN(DDQN)
- prioritized experience replay(PDE)
内容
🙋 前言
深度Q学习是2014年提出的,后来进行了许多改进,下面就是四种新策略可以显著改善训练和DQN智能体的结果。
🙋 fixed Q-targets
🍄 理论
在之前我们学到TD(时序差分)误差(损失)的公式为:
但真实的目标值我们其实是并不知道的,我们需要估计它。使用Bellman方程,我们TD目标只是在该状态下采取该行动的奖励加上下一个状态的折扣率下的最高Q值。
然而,问题是我们使用相同的参数来估计目标和Q值,结果是,这个目标与我们正在改变的参数(w)之间存在着很大的相关性。
这意味着在训练的每一步,Q值都会发生变化,目标值都会发生变化,我们在越来越接近目标,目标也在不断变化,这就导致了训练的振荡。
这就是“Q_estimation追逐Q_target"的问题。
💪 我们使用DeepMind引入的固定Q-targets的方法。
- 使用具有固定参数的单独往来来估计TD目标
- 在每个(定义的一个参数),从DQN网络复制参数来更新target网络。
此时,损失公式为
每过T步,有
这样,我们就有了更稳定的学习过程。
🍄 实现
- 创建2个网络(DQN网络和Target网络)
- 创建一个函数,它将获取DQN网络参数并将其复制到Target网络
- 训练期间,使用目标网络计算TD目标,使用DQN网络每一步更新目标网络。
🙋 Double DQNs
🍄 理论
💪 该方案是为了解决Q值估计过高的问题。
💪Q值估计过高问题:在训练开始时,agent没有足够信息来了解最佳动作,因此,将有噪声的最大Q值作为最佳动作会导致“false positive”,如果未优化的动作经常给出比所求得的最优化动作更高的Q值,学习将变得困难。
🍄 解决方案
- 使用DQN网络,选择对下一个状态采取的最佳动作(具有最高Q值)
- 使用目标网络计算在下一个状态执行该操作的目标Q值。
🍄 实现
Set target if the episode ends at +1,
otherwise set
🙋 基于竞争构架Q网络 DuelingDQN(DDQN)
🍄 理论
Q值表示状态s下采取行动a有多好。
可以将Q(s,a)分解为
- V(s):处于该状态s的值
- A(s,a):在该状态下采取该行动a的优势(相比于其他动作)
即
在DDQN中,我们希望通过两个streams得到V(s),A(s,a)这两个参数的估计量。
💪 为什么分别计算这两个参数呢?
通过解耦估计,DDQN可以直观的了解那些状态是有价值的,而不必了解每个状态下每个动作的效果。因此需要计算V(s)
原来的DQN,需要计算某个状态下每个动作的值,但如果该状态本身不好(导致死亡等),这样做就没有意义。
通过解耦计算V(s),可以找出那些任何行为都不会被影响的状态,有风险的状态的动作才被关注。
💪公式
其中,为common network 参数,为优势流参数,为价值流参数
🍄 实现
xxxxxxxxxx
11131class DDDQNNet:
2def __init__(self, state_size, action_size, learning_rate, name):
3self.state_size = state_size
4self.action_size = action_size
5self.learning_rate = learning_rate
6self.name = name
7
8
9# 使用 tf.variable_scope 了解具体用了什么网络模型 (DQN or target_net)
10# it will be useful when we will update our w- parameters (by copy the DQN parameters)
11with tf.variable_scope(self.name):
12
13# 创建 the placeholders(占位)
14# *state_size means that we take each elements of state_size in tuple hence is like if we wrote
15# [None, 100, 120, 4]
16self.inputs_ = tf.placeholder(tf.float32, [None, *state_size], name="inputs")
17
18self.actions_ = tf.placeholder(tf.float32, [None, action_size], name="actions_")
19
20# Remember that target_Q is the R(s,a) + ymax Qhat(s', a')
21self.target_Q = tf.placeholder(tf.float32, [None], name="target")
22
23"""
24First convnet: 第一个卷积层
25CNN
26ELU 激活函数
27"""
28# Input is 100x120x4
29self.conv1 = tf.layers.conv2d(inputs = self.inputs_,
30 nbsp; filters = 32,
31kernel_size = [8,8],
32strides = [4,4],
33padding = "VALID",
34kernel_initializer=tf.contrib.layers.xavier_initializer_conv2d(),
35name = "conv1")
36
37self.conv1_out = tf.nn.elu(self.conv1, name="conv1_out")
38
39
40"""
41Second convnet:第二个卷积层
42CNN
43ELU
44"""
45self.conv2 = tf.layers.conv2d(inputs = self.conv1_out,
46filters = 64,
47kernel_size = [4,4],
48strides = [2,2],
49padding = "VALID",
50kernel_initializer=tf.contrib.layers.xavier_initializer_conv2d(),
51name = "conv2")
52
53self.conv2_out = tf.nn.elu(self.conv2, name="conv2_out")
54
55
56"""
57Third convnet:第三个卷积层
58CNN
59ELU
60"""
61self.conv3 = tf.layers.conv2d(inputs = self.conv2_out,
62filters = 128,
63kernel_size = [4,4],
64strides = [2,2],
65padding = "VALID",
66kernel_initializer=tf.contrib.layers.xavier_initializer_conv2d(),
67name = "conv3")
68
69self.conv3_out = tf.nn.elu(self.conv3, name="conv3_out")
70
71
72self.flatten = tf.layers.flatten(self.conv3_out)
73
74
75## 这里分出两个流 Here we separate into two streams
76# 其中一个用来计算 V(s) -状态值函数
77self.value_fc = tf.layers.dense(inputs = self.flatten,
78units = 512,
79activation = tf.nn.elu,
80kernel_initializer=tf.contrib.layers.xavier_initializer(),
81name="value_fc")
82
83self.value = tf.layers.dense(inputs = self.value_fc,
84units = 1,
85activation = None,
86kernel_initializer=tf.contrib.layers.xavier_initializer(),
87name="value")
88
89# 另一个流计算 A(s,a) -动作优势函数
90self.advantage_fc = tf.layers.dense(inputs = self.flatten,
91units = 512,
92activation = tf.nn.elu,
93kernel_initializer=tf.contrib.layers.xavier_initializer(),
94name="advantage_fc")
95
96self.advantage = tf.layers.dense(inputs = self.advantage_fc,
97units = self.action_size,
98activation = None,
99kernel_initializer=tf.contrib.layers.xavier_initializer(),
100name="advantages")
101
102# Agregating layer 整合层
103# Q(s,a) = V(s) + (A(s,a) - 1/|A| * sum A(s,a'))
104self.output = self.value + tf.subtract(self.advantage, tf.reduce_mean(self.advantage, axis=1, keepdims=True))
105
106# 这个 Q 是模型对 Q值 的预测值
107self.Q = tf.reduce_sum(tf.multiply(self.output, self.actions_), axis=1)
108
109# 这个损失函数loss是预测Q值(Q_values)和Q目标(Q_target)的差
110# Sum(Qtarget - Q)^2 求平方和
111self.loss = tf.reduce_mean(tf.square(self.target_Q - self.Q))
112# RMSprop优化器,最小loss函数
113self.optimizer = tf.train.RMSPropOptimizer(self.learning_rate).minimize(self.loss)
🙋 优先经验回放 prioritized experience replay
🍄 l理论
对于训练,某些经验可能比其他经验更重要,但不那么频繁出现。对于随机选择抽样来说,某些重要的经验可能没有机会被选中。使用PER,通过使用一个标准来定义每个经验元组的优先级来改变采样分布。当预测值和TD目标差异较大时,设置优先获得该经验,这也意味着模型需要更多了解该经验信息。
使用TD误差幅度的绝对值:
其中表示TD误差幅度值,表示常数,保证没有经验被选中的概率为0
将优先级放在每个回放缓冲区的经验中。
💭 但也不能只做贪婪的优先次序,这样会过度拟合。因此引入随机优先级的概念,产生被选择中用于回放的概率。
当然,这样会像高优先级样本引入偏差,为了纠正这种偏差,可使用重要度采样(IS)通过减少常见样本的权重来调整更新模型。
🍄 实现
这里的实现比较复杂,需要采用二叉树SumTree,每个节点最多两个子节点,每个叶子存储每个样本的优先级p,每个树枝节点只有两个分叉,节点的值是两个分叉的和。
这种结果在更新优先级参数和采样时将非常有效率。
创建sumtree和data的内存对象后,采样得到大小为k的小批量样本,将[0,total_priority]划分为k个范围,从每个范围均匀采样。最终从sumtree中检索对应于这些采样值中的每一个的经验experience。