Q_Learning和Sarsa中都是利用了Q表来记录Q值,小规模场景状态往往比较少,便可以方便的用表存储再查询更新,但很多现实问题状态和动作都很复杂,而且如果出现连续值的状态则需要等距离分割离散,存储量往往太大,比如像下围棋如果还用Q表来存状态是不可能的事情。那么如果不用Q表存取,怎么得到价值函数呢?
近似表示
那么就来拟合吧!即近似表示学习价值函数。
v
(
S
)
≈
v
^
(
S
,
w
)
v(S) \approx \hat{v}(S, w)
v(S)≈v^(S,w)
w表示引入的参数,通常是一个矩阵或至少是一个向量。如下图有三种表示方式,一是输入状态s,输出状态的近似价值。二是输入状态s和采取的行为a,输出它们的近似价值。三是输入状态s,输出该状态下每一种可能采取的行为价值。
线性逼近?那么为什么不用神经网络。
于是采用了将NN+RL的方式,使用第三种方式来拟合价值函数,代替Q表,便是Deep Q Network,即DQN。
@@其实神经网络的类型并没有限制,根据不同应用场景,使用CNN,RNN都是可以的。
DQN
DQN的更新点主要有三点:
- experience replay(记忆库) 。Q learning之所以是离线控制,就在于它不但可以学习当前状态,还可以学习以前状态,甚至其他经验(如每次选最大)。但由于DQN是没有表的,如何还能够学习到以前的经验,即使用experience replay,经验回放。使用一个固定大小的空间进行记忆,满了就重写最早的记录。通过记忆库就又可以知道了预测值与现实值的差分,就可以梯度反向传播了。
- 神经网络计算 Q 值。完成输入s,输出所有a的value的映射。
- 使用两个网络,参数相同异步更新。由于用神经网络计算了值,然后值又被用来更新了网络,两者会循环更新,依赖性太强了。所以为了网络更好的收敛。使用两个神经网络,结构一样但参数不同,其中一个网络延迟更新,使其还是旧的参数,再每隔如1000步等跟最新网络copy一次更新。
记忆库和target网络都能够减少数据关联性,以满足训练神经网络所需的独立同分布的前提,使RL的收敛能更加的稳定。
算法流程如下:
- 初始化两套网络的参数w
- 先从状态s,得到该状态的特征向量 ϕ ( S t ) \phi(S_t) ϕ(St)输入实时网络Q,输出该状态的所有动作a的Q值。然后对输出的Q结果按一定概率 ϵ−贪婪法的选择动作a,并执行a得到新状态的特征向量 ϕ ( S t + 1 ) \phi(S_{t+1}) ϕ(St+1)和奖励r。
- 存入到记忆库D中。
- 然后从记忆库中采样计算 旧网络Q’ 的 y i y_i yi(相当于真实的Q值): y j = { r j i s _ e n d j i s t r u e r j + γ max a ′ Q ′ ( ϕ j , a j ′ , w ′ ) i s _ e n d j i s f a l s e y_j= \begin{cases} r_j& {is\_end_j\; is \;true}\\ r_j + \gamma\max_{a'}Q'(\phi_j,a'_j,w') & {is\_end_j \;is\; false} \end{cases} yj={rjrj+γmaxa′Q′(ϕj,aj′,w′)is_endjistrueis_endjisfalse
- 然后与刚刚选择动作a得到的Q值(由实时网络计算的预测Q值)进行均方误差 1 m ∑ j = 1 m ( y j − Q ( ϕ j , a j , w ) ) 2 \frac{1}{m}\sum\limits_{j=1}^m(y_j-Q(\phi_j,a_j,w))^2 m1j=1∑m(yj−Q(ϕj,aj,w))2。反向传播更新实时Q网络。
- 最后每隔C步就再更新一次旧网络的参数,直到循环结束。
1.
y
i
y_i
yi全部通过max Q来计算有没有问题?
有问题,不断学习max会造成的Over Estimation,因为实时神经网络预测Q本身就有误差,用来差分的Q’值也有误差,反向传播后的Q就会过度预测,如预测的值超过了最大值,这显然是不合理的。
Double DQN
那么分开Q值动作选择和目标Q值的计算。即不在目标旧网络里面找最大值来计算
y
j
=
r
j
+
γ
max
a
′
Q
′
(
ϕ
j
,
a
j
′
,
w
′
)
y_j= r_j + \gamma\max_{a'}Q'(\phi_j,a'_j,w')
yj=rj+γmaxa′Q′(ϕj,aj′,w′),而是在当前的估值网络Q中找最大Q所对应的动作,然后用这个动作去在旧网络中计算Q值,即:
y
j
=
r
j
+
γ
Q
′
(
ϕ
j
,
arg
max
a
′
Q
(
ϕ
j
,
a
,
w
)
,
w
′
)
y_j = r_j + \gamma Q'(\phi_j,\arg\max_{a'}Q(\phi_j,a,w),w')
yj=rj+γQ′(ϕj,arga′maxQ(ϕj,a,w),w′)
2. 在记忆库后中随机采样好吗?
按道理不同样本的重要性是不一样的,特别是更新时的TD误差样本之间的差距还是挺大的。
Prioritised Replay DQN
那么优化记忆库抽取。按误差的大小进行重要程度排序,误差越大说明越需要被学习。但是为了效率,不能每次都排一遍太麻烦,所以使用sumtree树排序相对来说就简单了(线段树)。
如上SumTree 是一种树形结构(Jaromír Janisch), 每片树叶存储每个样本的优先级 p,二叉树,根节点是所有的和。抽样时会用总数除以batch size,然后再按等区间进行抽样,优先级高的区段显然会被更高频率的抽中。
3. Q值是代表Q(S,a)的价值,那么对于状态S,单独动作价值a的评估会不会更准确?
因为有些state可能无论做什么动作,对下一个state都没有多大的影响。
Dueling DQN
那么改进Q为S,a的两个输出。即分为了在这个state时的价值和在这个state上采取各种行动 “多加” 的值。即Q变成:
Q
(
S
,
A
,
w
,
α
,
β
)
=
V
(
S
,
w
,
α
)
+
A
(
S
,
A
,
w
,
β
)
Q(S,A, w, \alpha, \beta) = V(S,w,\alpha) + A(S,A,w,\beta)
Q(S,A,w,α,β)=V(S,w,α)+A(S,A,w,β)
为了增加这两部分的辨识度,即为了防止状态输出直接为0,而动作输出直接学到了Q,会将动作输出中心化,即减去他们的平均值,以便突出这两部分的不同。
Q
(
S
,
A
,
w
,
α
,
β
)
=
V
(
S
,
w
,
α
)
+
(
A
(
S
,
A
,
w
,
β
)
−
1
N
π
∑
a
′
∈
π
A
(
S
,
a
′
,
w
,
β
)
)
Q(S,A, w, \alpha, \beta) = V(S,w,\alpha) + (A(S,A,w,\beta) - \frac{1}{N_\pi}\sum\limits_{a' \in \pi}A(S,a', w,\beta))
Q(S,A,w,α,β)=V(S,w,α)+(A(S,A,w,β)−Nπ1a′∈π∑A(S,a′,w,β))
DQN代码
使用gym的游戏环境:
pip install gym
pip install gym[atari]
atari里面会有很多的游戏:
但同样的为了应对这样的环境,就不能使用NN了,而是需要通过CNN来构建神经网络部分。
from collections import deque
import gym
import numpy as np
import os
import tensorflow as tf
import sys
import matplotlib
def sample_memories(batch_size):
indices = np.random.permutation(len(replay_memory))[:batch_size]
cols = [[], [], [], [], []] #存储 state, action, reward, next_state, continue
for idx in indices: #利用索引
memory = replay_memory[idx]
for col, value in zip(cols, memory):
col.append(value)
cols = [np.array(col) for col in cols]
return cols
def epsilon_greedy(q_values, step): #epsilon贪婪
epsilon = max(eps_min, eps_max - (eps_max-eps_min) * step / args.explore_steps)
if np.random.rand() < epsilon:
return np.random.randint(env.action_space.n) # random action
else:
return np.argmax(q_values) # optimal action
def q_network(net, name, reuse=False): #使用CNN搭建网络
with tf.variable_scope(name, reuse=reuse) as scope:
initializer = tf.contrib.layers.variance_scaling_initializer()
for n_maps, kernel_size, strides, padding, activation in zip(
[32, 64, 64], [(8,8), (4,4), (3,3)], [4, 2, 1],
["SAME"] * 3 , [tf.nn.relu] * 3):
net = tf.layers.conv2d(net, filters=n_maps, kernel_size=kernel_size, strides=strides,
padding=padding, activation=activation, kernel_initializer=initializer)
net = tf.layers.dense(tf.contrib.layers.flatten(net), 256, activation=tf.nn.relu, kernel_initializer=initializer)
net = tf.layers.dense(net, env.action_space.n, kernel_initializer=initializer)
trainable_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope=scope.name)
return net, trainable_vars
最后再放一个github超级棒的项目:
(https://github.com/devsisters/DQN-tensorflow)
深度强化学习真的好用吗?
- 它的样本利用率非常低。换言之为了让模型的表现达到一定高度需要极为大量的训练样本。
- 参数难调。
- 对环境的过拟合。
- 最终表现很多时候不够好。不仅利用率低,参数很难调,表现也不一定会很好。
- 奖励函数往往很难设计。1加入了合适的先验,不然容易被找到作弊手段;2奖励函数的值太过稀疏,即非0值太少了。3奖励函数有时候会引入新的偏见。
- EE问题(exploration & exploitation)导致的不稳定性。
现有表现比较好的模型,都是在实验环境比较理想(如游戏),数据很多reward定义简单而且反馈快,可以从简单关卡开始学习,如何使其能很好的用在实践中还有待进一步的努力。