Q_Learning和Sarsa中都是利用了Q表来记录Q值,小规模场景状态往往比较少,便可以方便的用表存储再查询更新,但很多现实问题状态和动作都很复杂,而且如果出现连续值的状态则需要等距离分割离散,存储量往往太大,比如像下围棋如果还用Q表来存状态是不可能的事情。那么如果不用Q表存取,怎么得到价值函数呢?
近似表示
那么就来拟合吧!即近似表示学习价值函数。
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一次更新。
算法流程如下:
- 初始化两套网络的参数w
- 先从状态s,得到该状态的特征向量输入实时网络Q,输出该状态的所有动作a的Q值。然后对输出的Q结果按一定概率 ϵ−贪婪法的选择动作a,并执行a得到新状态的特征向量和奖励r。
- 存入到记忆库D中。
- 然后从记忆库中采样计算 旧网络Q’ 的(相当于真实的Q值):
- 然后与刚刚选择动作a得到的Q值(由实时网络计算的预测Q值)进行均方误差。反向传播更新实时Q网络。
- 最后每隔C步就再更新一次旧网络的参数,直到循环结束。
1. 全部通过max Q来计算有没有问题?
有问题,不断学习max会造成的Over Estimation,因为实时神经网络预测Q本身就有误差,用来差分的Q’值也有误差,反向传播后的Q就会过度预测,如预测的值超过了最大值,这显然是不合理的。
Double DQN
那么分开Q值动作选择和目标Q值的计算。即不在目标旧网络里面找最大值来计算 ,而是在当前的估值网络Q中找最大Q所对应的动作,然后用这个动作去在旧网络中计算Q值,即:
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变成:
为了增加这两部分的辨识度,即为了防止状态输出直接为0,而动作输出直接学到了Q,会将动作输出中心化,即减去他们的平均值,以便突出这两部分的不同。
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)