强化学习(八)价值函数的近似表示与Deep Q-Learning

    在强化学习系列的前七篇里,咱们主要讨论的都是规模比较小的强化学习问题求解算法。今天开始咱们步入深度强化学习。这一篇关注于价值函数的近似表示和Deep Q-Learning算法。html

    Deep Q-Learning这一篇对应Sutton书的第11章部分和UCL强化学习课程的第六讲。git

1. 为什么须要价值函数的近似表示

    在以前讲到了强化学习求解方法,不管是动态规划DP,蒙特卡罗方法MC,仍是时序差分TD,使用的状态都是离散的有限个状态集合$\mathbb{S}$。此时问题的规模比较小,比较容易求解。可是假如咱们遇到复杂的状态集合呢?甚至不少时候,状态是连续的,那么就算离散化后,集合也很大,此时咱们的传统方法,好比Q-Learning,根本没法在内存中维护这么大的一张Q表。    github

    好比经典的冰球世界(PuckWorld) 强化学习问题,具体的动态demo见这里。环境由一个正方形区域构成表明着冰球场地,场地内大的圆表明着运动员个体,小圆表明着目标冰球。在这个正方形环境中,小圆会每隔必定的时间随机改变在场地的位置,而表明个体的大圆的任务就是尽量快的接近冰球目标。大圆能够操做的行为是在水平和竖直共四个方向上施加一个时间步时长的一个大小固定的力,借此来改变大圆的速度。环境会在每个时间步内告诉个体当前的水平与垂直坐标、当前的速度在水平和垂直方向上的份量以及目标的水平和垂直坐标共6项数据,奖励值为个体与目标二者中心距离的负数,也就是距离越大奖励值越低且最高奖励值为0。算法

    在这个问题中,状态是一个6维的向量,而且是连续值。无法直接用以前离散集合的方法来描述状态。固然,你能够说,咱们能够把连续特征离散化。好比把这个冰球场100x100的框按1x1的格子划分红10000个格子,那么对于运动员的坐标和冰球的坐标就有$10^4*10^4=10^{8}$次种,若是再加上个体速度的份量就更是天文数字了,此时以前讲过的强化学习方法都会由于问题的规模太大而没法使用。怎么办呢?必需要对问题的建模作修改了,而价值函数的近似表示就是一个可行的方法。网络

2. 价值函数的近似表示方法

    因为问题的状态集合规模大,一个可行的建模方法是价值函数的近似表示。方法是咱们引入一个状态价值函数$\hat{v}$, 这个函数由参数$w$描述,并接受状态$s$做为输入,计算后获得状态$s$的价值,即咱们指望:$$\hat{v}(s, w) \approx v_{\pi}(s)$$app

    相似的,引入一个动做价值函数$\hat{q}$,这个函数由参数$w$描述,并接受状态$s$与动做$a$做为输入,计算后获得动做价值,即咱们指望:$$\hat{q}(s,a,w) \approx q_{\pi}(s,a)$$dom

    价值函数近似的方法不少,好比最简单的线性表示法,用$\phi(s)$表示状态s的特征向量,则此时咱们的状态价值函数能够近似表示为:$$\hat{v}(s, w) = \phi(s)^Tw$$函数

    固然,除了线性表示法,咱们还能够用决策树,最近邻,傅里叶变换,神经网络来表达咱们的状态价值函数。而最多见,应用最普遍的表示方法是神经网络。所以后面咱们的近似表达方法若是没有特别提到,都是指的神经网络的近似表示。学习

    对于神经网络,可使用DNN,CNN或者RNN。没有特别的限制。若是把咱们计算价值函数的神经网络看作一个黑盒子,那么整个近似过程能够看作下面这三种状况:测试

    对于状态价值函数,神经网络的输入是状态s的特征向量,输出是状态价值$\hat{v}(s, w)$。对于动做价值函数,有两种方法,一种是输入状态s的特征向量和动做a,输出对应的动做价值$\hat{q}(s,a,w)$,另外一种是只输入状态s的特征向量,动做集合有多少个动做就有多少个输出$\hat{q}(s,a_i,w)$。这里隐含了咱们的动做是有限个的离散动做。

    对于咱们前一篇讲到的Q-Learning算法,咱们如今就价值函数的近似表示来将其改造,采用上面右边的第三幅图的动做价值函数建模思路来作,如今咱们叫它Deep Q-Learning。

3. Deep Q-Learning算法思路

    Deep Q-Learning算法的基本思路来源于Q-Learning。可是和Q-Learning不一样的地方在于,它的Q值的计算不是直接经过状态值s和动做来计算,而是经过上面讲到的Q网络来计算的。这个Q网络是一个神经网络,咱们通常简称Deep Q-Learning为DQN。

    DQN的输入是咱们的状态s对应的状态向量$\phi(s)$, 输出是全部动做在该状态下的动做价值函数Q。Q网络能够是DNN,CNN或者RNN,没有具体的网络结构要求。

    DQN主要使用的技巧是经验回放(experience replay),即将每次和环境交互获得的奖励与状态更新状况都保存起来,用于后面目标Q值的更新。为何须要经验回放呢?咱们回忆一下Q-Learning,它是有一张Q表来保存全部的Q值的当前结果的,可是DQN是没有的,那么在作动做价值函数更新的时候,就须要其余的方法,这个方法就是经验回放。

    经过经验回放获得的目标Q值和经过Q网络计算的Q值确定是有偏差的,那么咱们能够经过梯度的反向传播来更新神经网络的参数$w$,当$w$收敛后,咱们的就获得的近似的Q值计算方法,进而贪婪策略也就求出来了。

    下面咱们总结下DQN的算法流程,基于NIPS 2013 DQN。    

    算法输入:迭代轮数$T$,状态特征维度$n$, 动做集$A$, 步长$\alpha$,衰减因子$\gamma$, 探索率$\epsilon$, Q网络结构, 批量梯度降低的样本数$m$。

    输出:Q网络参数

    1. 随机初始化Q网络的全部参数$w$,基于$w$初始化全部的状态和动做对应的价值$Q$。清空经验回放的集合$D$。

    2. for i from 1 to T,进行迭代。

      a) 初始化S为当前状态序列的第一个状态, 拿到其特征向量$\phi(S)$

      b) 在Q网络中使用$\phi(S)$做为输入,获得Q网络的全部动做对应的Q值输出。用$\epsilon-$贪婪法在当前Q值输出中选择对应的动做$A$

      c) 在状态$S$执行当前动做$A$,获得新状态$S'$对应的特征向量$\phi(S')和奖励$R$,是否终止状态is_end

      d) 将$\{\phi(S),A,R,\phi(S'),is\_end\}$这个五元组存入经验回放集合$D$

      e) $S=S'$

      f)  从经验回放集合$D$中采样$m$个样本$\{\phi(S_j),A_j,R_j,\phi(S'_j),is\_end_j\}, j=1,2.,,,m$,计算当前目标Q值$y_j$:$$y_j= \begin{cases} R_j& {is\_end_j\; is \;true}\\ R_j + \gamma\max_{a'}Q(\phi(S'_j),A'_j,w) & {is\_end_j \;is\; false} \end{cases}$$

      g)  使用均方差损失函数$\frac{1}{m}\sum\limits_{j=1}^m(y_j-Q(\phi(S_j),A_j,w))^2$,经过神经网络的梯度反向传播来更新Q网络的全部参数$w$

      h) 若是$S'$是终止状态,当前轮迭代完毕,不然转到步骤b)

      注意,上述第二步的f步和g步的Q值计算也都须要经过Q网络计算获得。另外,实际应用中,为了算法较好的收敛,探索率$\epsilon$须要随着迭代的进行而变小。

4. Deep Q-Learning实例

    下面咱们用一个具体的例子来演示DQN的应用。这里使用了OpenAI Gym中的CartPole-v0游戏来做为咱们算法应用。CartPole-v0游戏的介绍参见这里。它比较简单,基本要求就是控制下面的cart移动使链接在上面的pole保持垂直不倒。这个任务只有两个离散动做,要么向左用力,要么向右用力。而state状态就是这个cart的位置和速度, pole的角度和角速度,4维的特征。坚持到200分的奖励则为过关。

    完整的代码参见个人github: https://github.com/ljpzzz/machinelearning/blob/master/reinforcement-learning/dqn.py

    代码参考了知乎上的一个DQN实例,修改了代码中的一些错误,并用最新的Python3.6+Tensorflow1.8.0运行。要跑代码须要安装OpenAI的Gym库,使用"pip install gym"便可。

    代码使用了一个三层的神经网络,输入层,一个隐藏层和一个输出层。下面咱们看看关键部分的代码。

    算法第2步的步骤b经过$\epsilon-$贪婪法选择动做的代码以下,注意每次咱们$\epsilon-$贪婪法后都会减少$\epsilon$值。

  def egreedy_action(self,state):
    Q_value = self.Q_value.eval(feed_dict = {
      self.state_input:[state]
      })[0]
    if random.random() <= self.epsilon:
        self.epsilon -= (INITIAL_EPSILON - FINAL_EPSILON) / 10000
        return random.randint(0,self.action_dim - 1)
    else:
        self.epsilon -= (INITIAL_EPSILON - FINAL_EPSILON) / 10000
        return np.argmax(Q_value)

    算法第2步的步骤c在状态$S$执行当前动做$A$的代码以下,这个交互是由Gym完成的。

      next_state,reward,done,_ = env.step(action)
      # Define reward for agent
      reward = -1 if done else 0.1

    算法第2步的步骤d保存经验回放数据的代码以下:

  def perceive(self,state,action,reward,next_state,done):
    one_hot_action = np.zeros(self.action_dim)
    one_hot_action[action] = 1
    self.replay_buffer.append((state,one_hot_action,reward,next_state,done))
    if len(self.replay_buffer) > REPLAY_SIZE:
      self.replay_buffer.popleft()

    if len(self.replay_buffer) > BATCH_SIZE:
      self.train_Q_network()

    算法第2步的步骤f,g计算目标Q值,并更新Q网络的代码以下:

  def train_Q_network(self):
    self.time_step += 1
    # Step 1: obtain random minibatch from replay memory
    minibatch = random.sample(self.replay_buffer,BATCH_SIZE)
    state_batch = [data[0] for data in minibatch]
    action_batch = [data[1] for data in minibatch]
    reward_batch = [data[2] for data in minibatch]
    next_state_batch = [data[3] for data in minibatch]

    # Step 2: calculate y
    y_batch = []
    Q_value_batch = self.Q_value.eval(feed_dict={self.state_input:next_state_batch})
    for i in range(0,BATCH_SIZE):
      done = minibatch[i][4]
      if done:
        y_batch.append(reward_batch[i])
      else :
        y_batch.append(reward_batch[i] + GAMMA * np.max(Q_value_batch[i]))

    self.optimizer.run(feed_dict={
      self.y_input:y_batch,
      self.action_input:action_batch,
      self.state_input:state_batch
      })

    咱们在每100轮迭代完后会去玩10次交互测试,计算10次的平均奖励。运行了代码后,个人3000轮迭代的输出以下:

episode: 0 Evaluation Average Reward: 12.2
episode: 100 Evaluation Average Reward: 9.4
episode: 200 Evaluation Average Reward: 10.4
episode: 300 Evaluation Average Reward: 10.5
episode: 400 Evaluation Average Reward: 11.6
episode: 500 Evaluation Average Reward: 12.4
episode: 600 Evaluation Average Reward: 29.6
episode: 700 Evaluation Average Reward: 48.1
episode: 800 Evaluation Average Reward: 85.0
episode: 900 Evaluation Average Reward: 169.4
episode: 1000 Evaluation Average Reward: 200.0
episode: 1100 Evaluation Average Reward: 200.0
episode: 1200 Evaluation Average Reward: 200.0
episode: 1300 Evaluation Average Reward: 200.0
episode: 1400 Evaluation Average Reward: 200.0
episode: 1500 Evaluation Average Reward: 200.0
episode: 1600 Evaluation Average Reward: 200.0
episode: 1700 Evaluation Average Reward: 200.0
episode: 1800 Evaluation Average Reward: 200.0
episode: 1900 Evaluation Average Reward: 200.0
episode: 2000 Evaluation Average Reward: 200.0
episode: 2100 Evaluation Average Reward: 200.0
episode: 2200 Evaluation Average Reward: 200.0
episode: 2300 Evaluation Average Reward: 200.0
episode: 2400 Evaluation Average Reward: 200.0
episode: 2500 Evaluation Average Reward: 200.0
episode: 2600 Evaluation Average Reward: 200.0
episode: 2700 Evaluation Average Reward: 200.0
episode: 2800 Evaluation Average Reward: 200.0
episode: 2900 Evaluation Average Reward: 200.0

    大概到第1000次迭代后,算法已经收敛,达到最高的200分。固然因为是$\epsilon-$探索,每次前面的输出可能不一样,但最后应该均可以收敛到200的分数。固然因为DQN不保证绝对的收敛,因此可能到了200分后还会有抖动。

5. Deep Q-Learning小结    

    DQN因为对价值函数作了近似表示,所以有了解决大规模强化学习问题的能力。可是DQN有个问题,就是它并不必定能保证Q网络的收敛,也就是说,咱们不必定能够获得收敛后的Q网络参数。这会致使咱们训练出的模型效果不好。

     针对这个问题,衍生出了DQN的不少变种,好比Nature DQN(NIPS 2015), Double DQN,Dueling DQN等。这些咱们在下一篇讨论。

 

(欢迎转载,转载请注明出处。欢迎沟通交流: liujianping-ok@163.com) 

相关文章
相关标签/搜索