强化学习(Double/Prioritised Replay/Dueling DQN)
Q_Learning和Sarsa中都是利用了Q表来记录Q值,小规模场景状态往往比较少,便可以方便的用表存储再查询更新,但很多现实问题状态和动作都很复杂,而且如果出现连续值的状态则需要等距离分割离散,存储量往往太大,比如像下围棋如果还用Q表来存状态是不可能的事情。那么如果不用Q表存取,怎么得到价值函数呢?

近似表示
那么就来拟合吧!即近似表示学习价值函数。v(S)v^(S,w)v(S) \approx \hat{v}(S, w)
w表示引入的参数,通常是一个矩阵或至少是一个向量。如下图有三种表示方式,一是输入状态s,输出状态的近似价值。二是输入状态s和采取的行为a,输出它们的近似价值。三是输入状态s,输出该状态下每一种可能采取的行为价值。
强化学习(Double/Prioritised Replay/Dueling DQN)
线性逼近?那么为什么不用神经网络。
于是采用了将NN+RL的方式,使用第三种方式来拟合价值函数,代替Q表,便是Deep Q Network,即DQN。
@@其实神经网络的类型并没有限制,根据不同应用场景,使用CNN,RNN都是可以的。

DQN
DQN的更新点主要有三点:

  • experience replay(记忆库) 。Q learning之所以是离线控制,就在于它不但可以学习当前状态,还可以学习以前状态,甚至其他经验(如每次选最大)。但由于DQN是没有表的,如何还能够学习到以前的经验,即使用experience replay,经验回放。使用一个固定大小的空间进行记忆,满了就重写最早的记录。通过记忆库就又可以知道了预测值与现实值的差分,就可以梯度反向传播了。
  • 神经网络计算 Q 值。完成输入s,输出所有a的value的映射。
  • 使用两个网络,参数相同异步更新。由于用神经网络计算了值,然后值又被用来更新了网络,两者会循环更新,依赖性太强了。所以为了网络更好的收敛。使用两个神经网络,结构一样但参数不同,其中一个网络延迟更新,使其还是旧的参数,再每隔如1000步等跟最新网络copy一次更新。

算法流程如下:
强化学习(Double/Prioritised Replay/Dueling DQN)

  • 初始化两套网络的参数w
  • 先从状态s,得到该状态的特征向量ϕ(St)\phi(S_t)输入实时网络Q,输出该状态的所有动作a的Q值。然后对输出的Q结果按一定概率 ϵ−贪婪法的选择动作a,并执行a得到新状态的特征向量ϕ(St+1)\phi(S_{t+1})和奖励r。
  • 存入到记忆库D中。
  • 然后从记忆库中采样计算 旧网络Q’yiy_i(相当于真实的Q值):yj={rjis_endj  is  truerj+γmaxaQ(ϕj,aj,w)is_endj  is  falsey_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}
  • 然后与刚刚选择动作a得到的Q值(由实时网络计算的预测Q值)进行均方误差1mj=1m(yjQ(ϕj,aj,w))2\frac{1}{m}\sum\limits_{j=1}^m(y_j-Q(\phi_j,a_j,w))^2。反向传播更新实时Q网络。
  • 最后每隔C步就再更新一次旧网络的参数,直到循环结束。

1. yiy_i全部通过max Q来计算有没有问题?
有问题,不断学习max会造成的Over Estimation,因为实时神经网络预测Q本身就有误差,用来差分的Q’值也有误差,反向传播后的Q就会过度预测,如预测的值超过了最大值,这显然是不合理的。

Double DQN
那么分开Q值动作选择和目标Q值的计算。即不在目标旧网络里面找最大值来计算 yj=rj+γmaxaQ(ϕj,aj,w)y_j= r_j + \gamma\max_{a'}Q'(\phi_j,a'_j,w'),而是在当前的估值网络Q中找最大Q所对应的动作,然后用这个动作去在旧网络中计算Q值,即:yj=rj+γQ(ϕj,argmaxaQ(ϕj,a,w),w)y_j = r_j + \gamma Q'(\phi_j,\arg\max_{a'}Q(\phi_j,a,w),w')

2. 在记忆库后中随机采样好吗?
按道理不同样本的重要性是不一样的,特别是更新时的TD误差样本之间的差距还是挺大的。
Prioritised Replay DQN
那么优化记忆库抽取。按误差的大小进行重要程度排序,误差越大说明越需要被学习。但是为了效率,不能每次都排一遍太麻烦,所以使用sumtree树排序相对来说就简单了(线段树)。
强化学习(Double/Prioritised Replay/Dueling DQN)
如上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)
为了增加这两部分的辨识度,即为了防止状态输出直接为0,而动作输出直接学到了Q,会将动作输出中心化,即减去他们的平均值,以便突出这两部分的不同。Q(S,A,w,α,β)=V(S,w,α)+(A(S,A,w,β)1Nπ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))

DQN代码
使用gym的游戏环境:
pip install gym
pip install gym[atari]

atari里面会有很多的游戏:
强化学习(Double/Prioritised Replay/Dueling DQN)
但同样的为了应对这样的环境,就不能使用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超级棒的项目:
强化学习(Double/Prioritised Replay/Dueling DQN)
https://github.com/devsisters/DQN-tensorflow)

相关文章: