基于值的深度强化学习算法

目录

  • DQN2013 —— Playing Atari with Deep Reinforcement Learning
  • DQN2015 —— Human-level control through deep reinforcement learning
  • Double DQN —— Deep Reinforcement Learning with Double Q-learning
  • 引用文献

DQN2013 —— Playing Atari with Deep Reinforcement Learning

  • 论文下载地址

  • 论文介绍

    该论文提出了一个基于卷积神经网络的深度强化学习模型,该模型是Q-learning算法的变体,该模型的输入数据是Atari游戏的原始原素,输出是预测未来的动作价值奖励(Q-value),其神经网络结构可以简单表示如图1所示:
    基于值的深度强化学习算法_第1张图片

    图1. DQN2013 模型结果

    输入数据为 [batch_size, width, height, channel](不同深度学习框架中的channel的位置可能不一样,在tf中输入的数据是将channel设置 在最后一个维度,我们暂以此描述)形式的多维数据,输入数据经过卷积神经网络得到特征向量 [batch_size, hidden_dim],将特征向量作为全连接神经网络的输入,得到输出的动作价值函数 Q ( s , a ) Q(s,a) Q(s,a) [batch_size, action_dim],使用完全贪婪算法或者epsilon贪婪算法基于 Q ( s , a ) Q(s,a) Q(s,a)的值选择对应的动作。

  • 前人相关工作

    • TD-gammon:TD-gammon是一种双陆棋游戏程序,通过强化学习和自我对弈来学习并达到了超人类的游戏水平。TD-gammon使用了类似的无模型强化学习算法Q-learning,并使用具有一个隐藏层的多层感知器来近似价值函数1 (事实上,TD-gammon近似的是状态价值函数 V ( s ) V(s) V(s)而不是动作价值函数 Q ( s , a ) Q(s,a) Q(s,a))。然而,通过改进TD-gammon或者直接使用相同的方法应用于国际象棋、围棋和跳棋都不太成功,这导致人们普遍认为TD-gammon是一种特殊情况,仅适用于双陆棋,也许是因为掷骰子的随机性有助于探索状态空间,并使价值函数特别平滑2
    • 研究表明,将无模型强化学习算法(如Q-learning)与非线性函数逼近器(近似器)3或者与离线策略学习4相结合可能会导致Q-network发散。随后,强化学习的大部分工作都集中在具有更好收敛保证的线性函数逼近器上3
    • 最近,人们对将深度学习与强化学习相结合重新产生了兴趣。深度神经网络已经被用来评估环境 ϵ \epsilon ϵ,受限玻尔兹曼机已经被用来预测价值函数5或者策略6。此外,Q-learning的发散问题已经通过梯度时间差分得到部分解决。这些方法被证明在使用非线性函数逼近器7预测固定策略或使用Q-learning的受限变体8通过线性函数逼近器学习控制策略时是收敛的。然而,这些方法尚未扩展到非线性控制。
    • 也许与该论文的方法最相似的前人工作是神经拟合Q-learning(neural fitted Q-learning,NFQ)9,NFQ优化了公式(1) L i ( θ i ) = E s , a ∼ ρ ( ⋅ ) [ ( y i − Q ( s , a ; θ i ) ) 2 ] (1) L_{i}(\theta_{i})=\mathbb{E}_{s,a\sim \rho(·)}[(y_i-Q(s,a;\theta_i))^{2}] \tag{1} Li(θi)=Es,aρ()[(yiQ(s,a;θi))2](1)的损失函数序列,使用RPROP算法更新Q-learning的参数。然后,它使用批量更新参数的方式,每次迭代的计算成本和数据集的大小成正比,而该论文DQN使用随机梯度更新,每次迭代的成本比较低,并且可以扩展到大型数据集,NFQ还成功应用于使用纯视觉输入的简单现实世界控制任务,首先使用深度自动编码器学习任务的低维表示,然后将低维表示作为NFQ的输入10。相比之下,DQN直接使用端到端的强化学习方法将视觉数据作为神经网络的输入,因此,DQN可以学习到与区分动作价值直接相关的特征。Q-learning之前也曾将经验回放和简单的神经网络11相结合,但同样是以低维状态而不是原始视觉作为神经网络的输入。
  • DQN2013伪代码
    基于值的深度强化学习算法_第2张图片

    图2. DQN2013 伪代码

    • 初始化经验回放池 D D D,回放池可存放数据数量为 N N N
    • 随机初始化动作值函数网络 Q Q Q的模型参数;
    • 在每个回合(episode)获取环境初始状态 s 1 s_1 s1,同时对状态进行预处理 ϕ 1 = ϕ ( s 1 ) \phi_1=\phi(s_1) ϕ1=ϕ(s1);
      • 根据网络 Q ( ϕ s t , a ; θ ) Q(\phi_{s_t},a;\theta) Q(ϕst,a;θ),以 ϵ \epsilon ϵ贪婪策略选择动作 a t a_t at
      • 执行动作 a t a_t at与模拟器进行交互,获取奖励 r t r_t rt和下一个环境状态 s t + 1 s_{t+1} st+1,对 s t + 1 s_{t+1} st+1进行预处理 ϕ ( s t + 1 ) \phi(s_{t+1}) ϕ(st+1)
      • ( ϕ t , a t , r t , ϕ t + 1 ) (\phi_{t},a_t,r_t,\phi_{t+1}) (ϕt,at,rt,ϕt+1)存储到回放池 D D D中,如果回放池数据量达到最大值 N N N,抛弃最先存储的数据;
      • 当回放池中的数据量足够,从回放池中随机采样 m i n i b a t c h minibatch minibatch个数据;
      • 对每个数据,根据是否为终止状态,通过公式 y j = { r j r j + γ   m a x a ′ Q ( ϕ j + 1 , a ′ ; θ ) y_j=\begin{cases} r_j \\ r_j + \gamma \space max_{a'}Q(\phi_{j+1,a';\theta}) \end{cases} yj={rjrj+γ maxaQ(ϕj+1,a;θ)计算 y j y_j yj
      • 最小化目标损失 ( y j − Q ( ϕ j , a j ; θ ) ) 2 (y_j-Q(\phi_{j,a_j;\theta}))^2 (yjQ(ϕj,aj;θ))2,以此更新网络 Q Q Q
      • 伪代码中的公式3是指下面的公式(2); ∇ θ i L i ( θ i ) = E s , a ∼ ρ ( ⋅ ) ; s ′ ∼ E [ ( r + γ max ⁡ a ′ Q ( s ′ , a ′ ; θ i − 1 ) − Q ( s , a ; θ i ) ) ∇ θ i Q ( s , a ; θ i ) ] (2) \nabla_{\theta_i} L_i\left(\theta_i\right)=\mathbb{E}_{s, a \sim \rho(\cdot) ; s^{\prime} \sim \mathcal{E}}\left[\left(r+\gamma \max _{a^{\prime}} Q\left(s^{\prime}, a^{\prime} ; \theta_{i-1}\right)-Q\left(s, a ; \theta_i\right)\right) \nabla_{\theta_i} Q\left(s, a ; \theta_i\right)\right] \tag{2} θiLi(θi)=Es,aρ();sE[(r+γamaxQ(s,a;θi1)Q(s,a;θi))θiQ(s,a;θi)](2) L i L_i Li如公式(3)所示: L i ( θ i ) = E s , a ∼ ρ ( ⋅ ) [ ( y i − Q ( s , a ; θ i ) ) 2 ] (3) L_i\left(\theta_i\right)=\mathbb{E}_{s, a \sim \rho(\cdot)}\left[\left(y_i-Q\left(s, a ; \theta_i\right)\right)^2\right] \tag{3} Li(θi)=Es,aρ()[(yiQ(s,a;θi))2](3)
  • 训练流程

    • 收集训练数据:我们可以用任何策略函数 π \pi π控制智能体与环境交互,这个 π \pi π可以叫做行为策略,dqn中使用的是 ϵ − g r e e d y \epsilon-greedy ϵgreedy策略,如公式(4)所示: a t = { a r g m a x a   Q ( s t , a ; ω ) ,                            以概率 ( 1 − ϵ ) 均匀抽取动作空间 A 中的一个动作     以概率 ϵ (4) a_t=\begin{cases} argmax_a \space Q(s_t,a;\omega),\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space以概率(1-\epsilon) \\ 均匀抽取动作空间\mathcal{A}中的一个动作 \space\space\space\space\space以概率\epsilon \end{cases} \tag{4} at={argmaxa Q(st,a;ω),                           以概率(1ϵ)均匀抽取动作空间A中的一个动作     以概率ϵ(4)把一个回合获取到的轨迹划分成 n n n个四元组 ( s t , a t , r t , s t + 1 ) (s_t,a_t,r_t,s_{t+1}) (st,at,rt,st+1),存入经验回放池。
    • 更新DQN参数 w \boldsymbol{w} w:随机从经验回放池中取出一个四元组,记作 ( s j , a j , r j , s j + 1 ) (s_j,a_j,r_j,s_{j+1}) (sj,aj,rj,sj+1),设DQN的当前参数为 w n o w \boldsymbol{w}_{now} wnow,执行下面的步骤对参数做一次更新,得到新的参数 w n e w \boldsymbol{w}_{new} wnew
      (1)对DQN做正向传播,得到 s t s_t st s t + 1 s_{t+1} st+1的Q-value值,如公式(5)和公式(6)所示: q ^ j = Q ( s j , a j , w n o w ) (5) \hat{q}_j=Q(s_j,a_j,\boldsymbol{w}_{now}) \tag{5} q^j=Q(sj,aj,wnow)(5) q ^ j + 1 = max ⁡ a ∈ A Q ( s j + 1 , a ; w now  ) (6) \widehat{q}_{j+1}=\max _{a \in \mathcal{A}} Q\left(s_{j+1}, a ; \boldsymbol{w}_{\text {now }}\right) \tag{6} q j+1=aAmaxQ(sj+1,a;wnow )(6)
      (2)计算目标Q-value值和误差,如公式(7)和公式(8)所示: y ^ j = r j + γ ⋅ q ^ j + 1 (7) \widehat{y}_j=r_j+\gamma \cdot \widehat{q}_{j+1} \tag{7} y j=rj+γq j+1(7) δ j = q ^ j − y ^ j (8) \delta_j=\widehat{q}_j-\widehat{y}_j \tag{8} δj=q jy j(8)
      (3)对DQN做反向传播,得到梯度,如公式(9)所示: g j = ∇ w Q ( s j , a j ; w now  ) (9) \boldsymbol{g}_j=\nabla_{\boldsymbol{w}} Q\left(s_j, a_j ; \boldsymbol{w}_{\text {now }}\right) \tag{9} gj=wQ(sj,aj;wnow )(9)
      (4)做梯度下降更新DQN的参数,如公式(10)所示: w new  ← w now  − α ⋅ δ j ⋅ g j (10) \boldsymbol{w}_{\text {new }} \leftarrow \boldsymbol{w}_{\text {now }}-\alpha \cdot \delta_j \cdot \boldsymbol{g}_j \tag{10} wnew wnow αδjgj(10)
  • 基于gym的DQN2013代码实现:

    • requirement:

      • python: 3.8
      • gym: 0.17.3
      • tensorflow: 2.9
    • code(代码主要参考书籍【动手学强化学习】(张伟楠,沈键,俞勇),京东购买链接 | 当当购买链接)

      import gym
      import random
      import collections
      import numpy as np
      import tensorflow as tf
      import matplotlib.pyplot as plt
      from tqdm import tqdm
      
      
      class ReplayBuffer:
          """经验回放池"""
          def __init__(self, capacity):
              self.buffer = collections.deque(maxlen=capacity)  # 队列,先进先出
      
          def add(self, state, action, reward, next_state, done):  # 将数据加入buffer
              self.buffer.append((state, action, reward, next_state, done))
      
          def sample(self, batch_size):  # 从buffer中采样数据,数量为batch_size
              transitions = random.sample(self.buffer, batch_size)
              state, action, reward, next_state, done = zip(*transitions)
              return np.array(state), action, reward, np.array(next_state), done
      
          def size(self):  # 目前buffer中数据的数量
              return len(self.buffer)
      
      
      class Q_net(tf.keras.Model):
          """只有一层隐藏层的Q网络"""
          def __init__(self, hidden_dim, action_dim):
              super(Q_net, self).__init__()
              self.fc1 = tf.keras.layers.Dense(hidden_dim, activation=tf.keras.activations.relu)
              self.fc2 = tf.keras.layers.Dense(action_dim)
      
          def call(self, x):
              return self.fc2(self.fc1(x))
      
      
      class DQN:
          def __init__(self, hidden_dim, action_dim, learning_rate, gamma, epsilon):
              self.action_dim = action_dim
              self.q_net = Q_net(hidden_dim, self.action_dim)  # Q网络
              # 使用Adam优化器
              self.optimizer = tf.optimizers.Adam(learning_rate=learning_rate)
              # 折扣因子
              self.gamma = gamma
              # epsilon-贪婪策略,这里没有对epsilon进行退火处理
              self.epsilon = epsilon
      
          def take_action(self, state):
              # epsilon-贪婪策略采取动作
              if np.random.random() < self.epsilon:
                  action = np.random.randint(self.action_dim)
              else:
                  state = tf.cast(tf.expand_dims(state, axis=0), dtype=tf.float32)
                  action = tf.argmax(self.q_net.call(state), axis=1).numpy()[0]
              return action
      
          def update(self, transition_dict):
              states = tf.cast(transition_dict['states'], dtype=tf.float32)
              actions = tf.reshape(transition_dict['actions'], shape=(-1))
              rewards = tf.reshape(tf.cast(transition_dict['rewards'], dtype=tf.float32), shape=(-1, 1))
              next_states = tf.cast(transition_dict['next_states'], dtype=tf.float32)
              dones = tf.reshape(tf.cast(transition_dict['dones'], dtype=tf.float32), shape=(-1, 1))
              # 梯度优化
              with tf.GradientTape() as tape:
                  # 获取当前状态动作对应的Q值
                  q_values = tf.gather(self.q_net.call(states), actions, batch_dims=1)
                  # 下个状态的最大Q值,下个状态不进行梯度优化,使用tf.stop_gradient
                  max_next_q_values = tf.stop_gradient(tf.reduce_max(self.q_net.call(next_states), axis=1, keepdims=True))
                  # TD误差目标
                  q_targets = rewards + self.gamma * max_next_q_values * (1 - dones)
                  # 均方差损失函数
                  loss_func = tf.keras.losses.MeanSquaredError()
                  # 计算均方误差损失函数
                  dqn_loss = tf.reduce_mean(loss_func(q_values, q_targets))  # 计算均方误差损失函数
              grads = tape.gradient(dqn_loss, self.q_net.trainable_variables)
              self.optimizer.apply_gradients(zip(grads, self.q_net.trainable_variables))
      
      
      lr = 2e-3
      num_episodes = 500
      hidden_dim = 128
      gamma = 0.98
      epsilon = 0.01
      buffer_size = 10000
      minimal_size = 500
      batch_size = 64
      
      
      env_name = 'CartPole-v0'
      env = gym.make(env_name)
      random.seed(0)
      np.random.seed(0)
      env.seed(0)
      tf.random.set_seed(0)
      replay_buffer = ReplayBuffer(buffer_size)
      state_dim = env.observation_space.shape[0]  # 输入状态维数
      action_dim = env.action_space.n  # 动作维数
      agent = DQN(hidden_dim, action_dim, lr, gamma, epsilon)
      
      
      return_list = []
      for i in range(10):
          with tqdm(total=int(num_episodes / 10), desc='Iteration %d' % i) as pbar:
              for i_episode in range(int(num_episodes / 10)):
                  episode_return = 0
                  state = env.reset()
                  done = False
                  while not done:
                      action = agent.take_action(state)
                      next_state, reward, done, _ = env.step(action)
                      replay_buffer.add(state, action, reward, next_state, done)
                      state = next_state
                      episode_return += reward
                      # 当buffer数据的数量超过一定值后,才进行Q网络训练
                      if replay_buffer.size() > minimal_size:
                          b_s, b_a, b_r, b_ns, b_d = replay_buffer.sample(batch_size)
                          transition_dict = {
                              'states': b_s,
                              'actions': b_a,
                              'next_states': b_ns,
                              'rewards': b_r,
                              'dones': b_d
                          }
                          agent.update(transition_dict)
                  return_list.append(episode_return)
                  if (i_episode + 1) % 10 == 0:
                      pbar.set_postfix({
                          'episode':
                          '%d' % (num_episodes / 10 * i + i_episode + 1),
                          'return':
                          '%.3f' % np.mean(return_list[-10:])
                      })
                  pbar.update(1)
      
      episodes_list = list(range(len(return_list)))
      plt.plot(episodes_list, return_list)
      plt.xlabel('Episodes')
      plt.ylabel('Returns')
      plt.title('DQN on {}'.format(env_name))
      plt.show()
      
    • result
      基于值的深度强化学习算法_第3张图片

      图3. DQN2013 运行结果

  • DQN2013的高估问题

    Q-learning算法有一个缺陷,用Q-learning训练的DQN会高估真实的价值,而且高估通常是非均匀的,这个缺陷导致DQN的表现很差。高估问题不是DQN模型的问题,是Q-learning的问题。

    Q-learning产生高估的原因有两个:

    (1)自举导致偏差的传播;
    (2)动作价值函数最大化导致目标动作价值高估真实价值;

    下面简单分析一下导致高估的两个原因(具体内容大部分使用了王树森 老师的 【深度强化学习】书籍(京东购买链接|当当购买链接),无意冒犯,侵权请联系我删除,抱歉):

    • 自举导致偏差的传播

      在强化学习中,自举意思是“用一个估算去更新同类的估算”,类似于“自己把自己给举起来” 。我们回顾一下训练DQN使用的Q-learning算法,研究其中存在的自举。

      算法每次从经验回放池中抽取四元组 ( s j , a j , r j , s j + 1 ) (s_j, a_j, r_j, s_{j+1}) (sj,aj,rj,sj+1),然后执行下面步骤更新DQN的参数:

      (1)计算目标动作值函数,如公式(11)所示: y ^ j = r j + γ ⋅ max ⁡ a j + 1 ∈ A Q ( s j + 1 , a j + 1 ; w now  ) ⏟ D Q N  自己做出的估计  (11) \widehat{y}_j=r_j+\gamma \cdot \underbrace{\max _{a_{j+1} \in \mathcal{A}} Q\left(s_{j+1}, a_{j+1} ; \boldsymbol{w}_{\text {now }}\right)}_{\mathrm{DQN} \text { 自己做出的估计 }} \tag{11} y j=rj+γDQN 自己做出的估计  aj+1AmaxQ(sj+1,aj+1;wnow )(11)(2)计算损失函数,如公式(12)所示: L ( w ) = 1 2 [ Q ( s j , a j ; w ) − y ^ j ⏟ 让 DQN 拟合  y ^ j ] 2 (12) L(\boldsymbol{w})=\frac{1}{2}[\underbrace{Q\left(s_j, a_j ; \boldsymbol{w}\right)-\widehat{y}_j}_{\text {让 DQN 拟合 } \widehat{y}_j}]^2 \tag{12} L(w)=21[ DQN 拟合 y j Q(sj,aj;w)y j]2(12)(3)进行梯度下降更新DQN参数,如公式(13)所示: w new  ← w now  − α ⋅ ∇ w L ( w now  ) (13) \boldsymbol{w}_{\text {new }} \leftarrow \boldsymbol{w}_{\text {now }}-\alpha \cdot \nabla_{\boldsymbol{w}} L\left(\boldsymbol{w}_{\text {now }}\right) \tag{13} wnew wnow αwL(wnow )(13)第一步计算目标动作值 y ^ j \hat{y}_j y^j是基于DQN自己做出的估计,第二步让DQN去拟合 y ^ j \hat{y}_j y^j,这意味着用DQN自己做出的估计去更新DQN自身,这属于自举。

      DQN中 Q ( s , a , w ) Q(s,a,\boldsymbol{w}) Q(s,a,w)是对于最优动作价值函数 Q ⋆ ( s , a ) Q_{\star}(s, a) Q(s,a)的近似,最理想的情况下 Q ( s , a ; w ) = Q ⋆ ( s , a ) , ∀ s , a Q(s, a ; \boldsymbol{w})=Q_{\star}(s, a), \forall s, a Q(s,a;w)=Q(s,a),s,a,假如碰巧 Q ( s j + 1 , a j + 1 ; w ) Q\left(s_{j+1}, a_{j+1} ; \boldsymbol{w}\right) Q(sj+1,aj+1;w)低估(或高估)真实价值 Q ⋆ ( s j + 1 , a j + 1 ) Q_{\star}\left(s_{j+1}, a_{j+1}\right) Q(sj+1,aj+1),则会发生下面的情况:           Q ( s j + 1 , a j + 1 ; w )  低估(或高估)      Q ⋆ ( s j + 1 , a j + 1 ) ⟹           y ^ j            低估(或高估)      Q ⋆ ( s j , a j ) ⟹      Q ( s j , a j ; w )  低估(或高估)  Q ⋆ ( s j , a j ) .  \begin{aligned} & \space \space \space \space \space \space \space \space \space Q\left(s_{j+1}, a_{j+1} ; \boldsymbol{w}\right) \text { 低估(或高估) } \space \space \space \space Q_{\star}\left(s_{j+1}, a_{j+1}\right) \\ & \Longrightarrow \quad \space \space \space \space \space \space \space \space \space \widehat{y}_j \space \space \space \space \space \space \space \space \space \quad \text { 低估(或高估) } \space \space \space \space Q_{\star}\left(s_j, a_j\right) \\ & \Longrightarrow \space \space \space \space Q\left(s_j, a_j ; \boldsymbol{w}\right) \quad \text { 低估(或高估) } \quad Q_{\star}\left(s_j, a_j\right) \text {. } \\ & \end{aligned}          Q(sj+1,aj+1;w) 低估(或高估)     Q(sj+1,aj+1)         y j          低估(或高估)     Q(sj,aj)    Q(sj,aj;w) 低估(或高估) Q(sj,aj)

      因为 y ^ j = r j + γ ⋅ max ⁡ a j + 1 ∈ A Q ( s j + 1 , a j + 1 ; w now  ) \widehat{y}_j=r_j+\gamma \cdot \max _{a_{j+1} \in \mathcal{A}} Q\left(s_{j+1}, a_{j+1} ; \boldsymbol{w}_{\text {now }}\right) y j=rj+γmaxaj+1AQ(sj+1,aj+1;wnow ),所以如果 Q ( s j + 1 , a j + 1 ; w ) Q\left(s_{j+1}, a_{j+1} ; \boldsymbol{w}\right) Q(sj+1,aj+1;w)是对真实价值的高估或者低估会传递给 y ^ j \hat{y}_j y^j;因为 Q ( s j , a j ; w ) Q\left(s_j, a_j ; \boldsymbol{w}\right) Q(sj,aj;w)会通过均方差损失函数进梯度求解拟合 y ^ j \hat{y}_j y^j,所以 y ^ j \hat{y}_j y^j对真实价值的高估或者低估会传递给 Q ( s j , a j ; w ) Q\left(s_j, a_j ; \boldsymbol{w}\right) Q(sj,aj;w)

      如果 Q ( s j + 1 , a j + 1 ; w ) Q\left(s_{j+1}, a_{j+1} ; \boldsymbol{w}\right) Q(sj+1,aj+1;w)是对真实价值 Q ⋆ ( s j + 1 , a j + 1 ) Q_{\star}\left(s_{j+1}, a_{j+1}\right) Q(sj+1,aj+1)的低估(或高估),就会导致 Q ( s j , a j ; w ) Q\left(s_{j}, a_{j} ; \boldsymbol{w}\right) Q(sj,aj;w)低估或高估价值 Q ⋆ ( s j , a j ) Q_{\star}\left(s_{j}, a_{j}\right) Q(sj,aj)。也就是说低估(或高估)从 ( s j + 1 , a j + 1 ) (s_{j+1},a_{j+1}) (sj+1,aj+1)传播到 ( s j , a j ) (s_j,a_j) (sj,aj),让更多的价值被低估(或高估)。

    • 最大化导致高估

      首先用数学解释为什么最大化会导致高估。设 x 1 , ⋯   , x d x_1, \cdots, x_d x1,,xd为任意 d d d个实数,往 x 1 , ⋯   , x d x_1, \cdots, x_d x1,,xd中加入任意均值为零的随机噪声后得到随机变量 Z 1 , ⋯   , Z d Z_1, \cdots, Z_d Z1,,Zd,随机性来源于随机噪声。

      我们可以证明,均值为零的随机噪声不会影响均值,如公式(14)所示: E [ mean ⁡ ( Z 1 , ⋯   , Z d ) ] = mean ⁡ ( x 1 , ⋯   , x d ) (14) \mathbb{E}\left[\operatorname{mean}\left(Z_1, \cdots, Z_d\right)\right]=\operatorname{mean}\left(x_1, \cdots, x_d\right) \tag{14} E[mean(Z1,,Zd)]=mean(x1,,xd)(14)证明如下:

      • Z 1 , ⋯   , Z d Z_1, \cdots, Z_d Z1,,Zd是带有噪声的随机变量,这意味着,每个 Z i Z_i Zi都可以表示为原始数据 x i x_i xi加上一个随机噪声 ϵ i \epsilon_i ϵi,即 Z i = x i + ϵ i Z_i=x_i + \epsilon_i Zi=xi+ϵi
      • mean ⁡ ( Z 1 , ⋯   , Z d ) \operatorname{mean}\left(Z_1, \cdots, Z_d\right) mean(Z1,,Zd)表示带有噪声的随机变量的均值,计算过程如公式(15)所示: mean ⁡ ( Z 1 , ⋯   , Z d ) = Z 1 + ⋯ + Z d d (15) \operatorname{mean}\left(Z_1, \cdots, Z_d\right)=\frac{Z_1+ \cdots+ Z_d}{d} \tag{15} mean(Z1,,Zd)=dZ1++Zd(15)
      • E [ mean ⁡ ( Z 1 , ⋯   , Z d ) ] \mathbb{E}\left[\operatorname{mean}\left(Z_1, \cdots, Z_d\right)\right] E[mean(Z1,,Zd)]表示带有噪声的随机变量均值的期望,由于期望是线性的,可以将其分解为如公式(16)所示: E [ mean ⁡ ( Z 1 , ⋯   , Z d ) ] = E [ Z 1 + ⋯ + Z d d ] = E [ Z 1 ] + ⋯ + E [ Z d ] d = E [ x 1 + ϵ 1 ] + ⋯ + E [ x d + ϵ d ] d = ( E [ x 1 ] + E [ ϵ 1 ] ) + ⋯ + E [ x d ] + E [ ϵ d ] ) d = ( x 1 + 0 ) + ⋯ + ( x d + 0 ) d = x 1 + ⋯ + x d d = mean ⁡ ( x 1 , ⋯   , x d ) \begin{align} \mathbb{E}\left[\operatorname{mean}\left(Z_1, \cdots, Z_d\right)\right]\notag&=\mathbb{E}\left[\frac{Z_1+ \cdots+ Z_d}{d}\right] \\\notag &=\frac{\mathbb{E}\left[Z_1\right] + \cdots + \mathbb{E}\left[Z_d\right]}{d} \\\notag &=\frac{\mathbb{E}\left[x_1+\epsilon_1\right] + \cdots + \mathbb{E}\left[x_d+\epsilon_d\right]}{d} \\\tag{16} &=\frac{(\mathbb{E}\left[x_1\right] +\mathbb{E}\left[\epsilon_1\right]) + \cdots + \mathbb{E}\left[x_d\right] +\mathbb{E}\left[\epsilon_d\right])}{d} \\\notag &=\frac{(x_1 +0) + \cdots + (x_d+0)}{d} \\\notag &=\frac{x_1 + \cdots + x_d}{d} \\\notag & =\operatorname{mean}\left(x_1, \cdots, x_d\right) \end{align} E[mean(Z1,,Zd)]=E[dZ1++Zd]=dE[Z1]++E[Zd]=dE[x1+ϵ1]++E[xd+ϵd]=d(E[x1]+E[ϵ1])++E[xd]+E[ϵd])=d(x1+0)++(xd+0)=dx1++xd=mean(x1,,xd)(16)

      下面证明,均值为零的随机噪声会影响最大值,如公式(17)所示: E [ max ⁡ ( Z 1 , ⋯   , Z d ) ] ≥ max ⁡ ( x 1 , ⋯   , x d ) (17) \mathbb{E}\left[\max \left(Z_1, \cdots, Z_d\right)\right] \geq \max \left(x_1, \cdots, x_d\right) \tag{17} E[max(Z1,,Zd)]max(x1,,xd)(17)证明如下:

      • 我们使用jensen不等式来证明,jensen不等式是一个关于凸函数的定理,它表明对于一个凸函数 f f f和随机变量 X X X,如公式(18)所示: E [ f ( X ) ] ≥ f ( E [ X ] ) (18) \mathbb{E}\left[ f(X) \right] \ge f(\mathbb{E}\left[ X \right]) \tag{18} E[f(X)]f(E[X])(18)

      • 对于 E [ max ⁡ ( Z 1 , ⋯   , Z d ) ] ≥ max ⁡ ( x 1 , ⋯   , x d ) \mathbb{E}\left[\max \left(Z_1, \cdots, Z_d\right)\right] \geq \max \left(x_1, \cdots, x_d\right) E[max(Z1,,Zd)]max(x1,,xd),我们只要证明 max ⁡ ( ) \max() max()是凸函数,就能通过公式(19)证明: E [ max ⁡ ( Z 1 , ⋯   , Z d ) ]   ≥ max ⁡ ( E [ Z 1 , ⋯   , Z d ] ) = max ⁡ ( E [ x 1 + ϵ 1 ] , ⋯   , E [ x d + ϵ d ] ) = max ⁡ ( x 1 , ⋯   , x d ) \begin{align} \mathbb{E}\left[\max \left(Z_1, \cdots, Z_d\right)\right]\ \geq \max (\mathbb{E} \left[Z_1, \cdots, Z_d\right]) \tag{19} &=\max (\mathbb{E} \left[x_1 + \epsilon_1\right], \cdots, \mathbb{E} \left[x_d+\epsilon_d\right]) \\\notag &= \max \left(x_1, \cdots, x_d\right) \end{align} E[max(Z1,,Zd)] max(E[Z1,,Zd])=max(E[x1+ϵ1],,E[xd+ϵd])=max(x1,,xd)(19)

      • 为了证明 max ⁡ ( ) \max() max()是凸函数,需要证明对于任意两个向量 X X X Y Y Y以及任意标量 λ ∈ [ 0 , 1 ] \lambda\in[0,1] λ[0,1],都满足以下不等式(20): max ⁡ ( λ X + ( 1 − λ ) Y ) ≤ λ max ⁡ ( X ) + ( 1 − λ ) max ⁡ ( Y ) (20) \max(\lambda X+(1- \lambda)Y) \leq \lambda\max(X)+(1-\lambda)\max(Y) \tag{20} max(λX+(1λ)Y)λmax(X)+(1λ)max(Y)(20)
        上面不等式中左侧是向量 λ X + ( 1 − λ ) X \lambda X+(1-\lambda)X λX+(1λ)X中的最大值,右边是 λ \lambda λ倍的向量 X X X中的最大值加上 ( 1 − λ ) (1-\lambda) (1λ)倍的向量 Y Y Y中的最大值,我们通过以下步骤来证明这个不等式:

        对于向量 λ X + ( 1 − λ ) Y \lambda X + (1-\lambda )Y λX+(1λ)Y中的任意元素,有公式(21): λ x i + ( 1 − λ ) y i ≤ λ max ⁡ ( X ) + ( 1 − λ ) max ⁡ ( Y ) (21) \lambda x_i + (1-\lambda)y_i\leq\lambda \max(X)+(1-\lambda)\max(Y) \tag{21} λxi+(1λ)yiλmax(X)+(1λ)max(Y)(21)这是因为 x i ≤ max ⁡ ( X ) x_i\leq \max(X) ximax(X) y i ≤ max ⁡ ( Y ) y_i\leq \max(Y) yimax(Y);

        因此,向量 λ X + ( 1 − λ ) Y \lambda X + (1-\lambda )Y λX+(1λ)Y中的最大值也满足公式(22): max ⁡ ( λ X + ( 1 − λ ) Y ) ≤ λ max ⁡ ( X ) + ( 1 − λ ) max ⁡ ( Y ) (22) \max(\lambda X + (1-\lambda )Y) \leq \lambda \max(X)+(1-\lambda)\max(Y) \tag{22} max(λX+(1λ)Y)λmax(X)+(1λ)max(Y)(22)由此我们证明了 max ⁡ ( ) \max() max()是凸函数,从而可以知道均值为零的随机噪声会影响最大值(公式(17))。

      公式(17)意味着先加入均值为零的噪声,然后求最大值,会产生高估。在DQN中,假设对于所有动作 a ∈ A a \in \mathcal{A} aA和状态 s ∈ S s \in \mathcal{S} sS,DQN的输出是真实价值 Q ⋆ ( s , a ) Q_{\star}(s,a) Q(s,a)加上均值为零的随机噪声 ϵ \epsilon ϵ,如公式(23)所示: Q ( s , a ; w ) = Q ⋆ ( s , a ) + ϵ (23) Q(s, a ; \boldsymbol{w})=Q_{\star}(s, a)+\epsilon \tag{23} Q(s,a;w)=Q(s,a)+ϵ(23)显然 Q ( s , a ; w ) Q(s, a ; \boldsymbol{w}) Q(s,a;w)是对真实价值 Q ⋆ ( s , a ) Q_{\star}(s, a) Q(s,a)的无偏估计,然而根据公式(17),我们可以知道以下不等式(24): E ϵ [ max ⁡ a ∈ A Q ( s , a ; w ) ] ≥ max ⁡ a ∈ A Q ⋆ ( s , a ) (24) \mathbb{E}_\epsilon\left[\max _{a \in \mathcal{A}} Q(s, a ; \boldsymbol{w})\right] \geq \max _{a \in \mathcal{A}} Q_{\star}(s, a) \tag{24} Eϵ[aAmaxQ(s,a;w)]aAmaxQ(s,a)(24)公式说明哪怕DQN是对真实价值的无偏估计,但如果求最大化,DQN就会高估真实价值。复习一下,DQN中希望 Q ( s , a ; w ) Q(s, a ; \boldsymbol{w}) Q(s,a;w)拟合的值 y ^ j \hat{y}_j y^j是通过下面公式(25)计算出来的: y ^ j = r j + γ ⋅ max ⁡ a ∈ A Q ( s j + 1 , a ; w ) ⏟ 高估  max ⁡ a ∈ A Q ⋆ ( s j + 1 , a ) (25) \widehat{y}_j=r_j+\gamma \cdot \underbrace{\max _{a \in \mathcal{A}} Q\left(s_{j+1}, a ; \boldsymbol{w}\right)}_{\text {高估 } \max _{a \in \mathcal{A}} Q_{\star}\left(s_{j+1}, a\right)} \tag{25} y j=rj+γ高估 maxaAQ(sj+1,a) aAmaxQ(sj+1,a;w)(25)这说明DQN在训练过程中通常是对真实价值的高估。

  • 高估的危害
    高估是有害的吗?如果高估是均匀的,则高估没有危害;如果高估是非均匀的就会有危害。举个例子,假如agent的动作空间为 A = { 左 , 右 , 上 } \mathcal{A}=\{左,右,上\} A={,,},给定当前状态 s s s,每个动作有一个真实价值: Q ⋆ ( s , 左  ) = 200 Q_{\star}(s \text {, 左 })=200 Q(s )=200 Q ⋆ ( s , 右  ) = 100 Q_{\star}(s \text {, 右 })=100 Q(s )=100 Q ⋆ ( s , 上  ) = 230 Q_{\star}(s \text {, 上 })=230 Q(s )=230根据贪婪策略,智能体应当选择动作"上",因为"上"的价值最大。如果高估是均匀的,所有动作价值都被高估了100,则会有: Q ⋆ ( s , 左  ) = 300 Q_{\star}(s \text {, 左 })=300 Q(s )=300 Q ⋆ ( s , 右  ) = 200 Q_{\star}(s \text {, 右 })=200 Q(s )=200 Q ⋆ ( s , 上  ) = 330 Q_{\star}(s \text {, 上 })=330 Q(s )=330根据贪婪策略,智能体还是会选择"上"。这个例子说明高估本身不是问题,只要所有动作价值被同等高估。

    但是在实际训练中,所有动作价值都会被同等高估吗?在DQN训练过程中,每当从经验回放池取出一个四元组 ( s , a , r , s ′ ) (s,a,r,s') (s,a,r,s) 用来更新DQN参数,就很有可能加重DQN对 Q ⋆ ( s , a ) Q_{\star}(s,a) Q(s,a)的高估。对于同一个状态 s s s,三种组合 ( s , 左 ) , ( s , 右 ) , ( s , 上 ) (s,左),(s,右),(s,上) (s,),(s,),(s,)出现在经验回放池中的频率是不同的,所以三种动作被高估的程度是不同的,假如动作价值被高估的程度不同,比如: Q ⋆ ( s , 左  ) = 280 Q_{\star}(s \text {, 左 })=280 Q(s )=280 Q ⋆ ( s , 右  ) = 300 Q_{\star}(s \text {, 右 })=300 Q(s )=300 Q ⋆ ( s , 上  ) = 260 Q_{\star}(s \text {, 上 })=260 Q(s )=260根据贪婪策略,智能体选择"右"动作,因为其动作价值最高,但实际上"右"是最差的动作,它的实际价值低于其余两个动作。

    综上所属,用Q算法训练DQN总会导致DQN高估真实价值,对于大多数的 s ∈ S s\in \mathcal{S} sS a ∈ A a\in \mathcal{A} aA,有不等式(26): Q ( s , a ; w ) > Q ⋆ ( s , a ) (26) Q(s, a ; \boldsymbol{w})>Q_{\star}(s, a) \tag{26} Q(s,a;w)>Q(s,a)(26)高估本身不是问题,真正的麻烦在于DQN的高估往往是非均匀的,如果DQN有非均匀的高估,那么用DQN做出的决策往往是不可靠的。

    我们找到了导致DQN高估的两个问题,想要避免DQN的高估,要么切断自举,哟啊么避免最大化造成的高估。注意高估不是DQN本身的属性,高估是Q-learning算法造成的。想要避免高估,就要用更好的算法替代原来的Q-learning。

    基于DQN2013存在的高估问题,DQN2015通过目标网络缓解DQN2013中因为自举导致的高估问题,但是DQN2015无法缓解最大化造成的高估,Double DQN在目标网络的基础上做改进,缓解最大化造成的高估问题,我们下面进行分析。

DQN2015 —— Human-level control through deep reinforcement learning

  • 论文下载地址

  • 论文介绍
    基于值的深度强化学习算法_第4张图片

    图4. DQN2015 卷积神经网络示意图

    众所周知,当强化学习使用神经网络等非线性函数来表示动作值函数时是不稳定的,甚至是发散的。这种不稳定性有几个原因:(1)观测序列数据存在相关性,当前状态和下一状态具有高度关联性,动作价值函数的微小更新都有可能显著改变策略参数并因此改变数据分布;(2)动作值函数Q和目标动作值函数 r + γ   m a x a ′ Q ( s ′ , a ′ ) r+\gamma \space max_{a'}Q(s',a') r+γ maxaQ(s,a)的相关性,具体可以看DQN,计算当前动作值函数和target动作值函数都是使用一个Q神经网络。

    该论文提出一种基于Q-learning的变体模型来解决这些不稳定性,它使用了两种关键方法,第一,使用经验回放机制,对训练数据进行随机化,消除观测序列中数据的相关性并平滑数据分布的变化;第二,使用目标网络预测下一状态的动作值函数用于更新当前网络的参数,并周期性地更新目标网络的参数(目标网络复制当前网络的参数),以此减少动作值函数Q和目标动作值函数 r + γ   m a x a ′ Q ( s ′ , a ′ ) r+\gamma \space max_{a'}Q(s',a') r+γ maxaQ(s,a)的相关性,通过目标网络缓解了DQN2013因为自举导致的高估危害问题。(可以简单理解为比DQN2013多了一个目标网络)

  • 伪代码
    基于值的深度强化学习算法_第5张图片

    图5. DQN2015 伪代码

    • 初始化经验回放池 D D D,容量大小为 N N N;
    • 初始化动作值函数 Q Q Q的参数 θ \theta θ
    • 初始化目标动作值函数 θ ^ \hat{\theta} θ^的参数 θ − \theta^{-} θ,令 θ − = θ \theta^{-}=\theta θ=θ
    • 在每个回合(episode)获取环境初始状态 s 1 s_1 s1,同时对状态进行预处理 ϕ 1 = ϕ ( s 1 ) \phi_1=\phi(s_1) ϕ1=ϕ(s1);
      • 根据网络 Q ( ϕ s t , a ; θ ) Q(\phi_{s_t},a;\theta) Q(ϕst,a;θ),以 ϵ \epsilon ϵ贪婪策略选择动作 a t a_t at
      • 执行动作 a t a_t at与模拟器进行交互,获取奖励 r t r_t rt和下一个环境状态 s t + 1 s_{t+1} st+1,对 s t + 1 s_{t+1} st+1进行预处理 ϕ ( s t + 1 ) \phi(s_{t+1}) ϕ(st+1)
      • ( ϕ t , a t , r t , ϕ t + 1 ) (\phi_{t},a_t,r_t,\phi_{t+1}) (ϕt,at,rt,ϕt+1)存储到回放池 D D D中,如果回放池数据量达到最大值 N N N,清理最先存储的数据;
      • 当回放池中的数据量足够,从回放池中随机采样 m i n i b a t c h minibatch minibatch个数据;
      • 对每个数据,根据是否为终止状态,通过公式 y j = { r j r j + γ   m a x a ′ Q ^ ( ϕ j + 1 , a ′ ; θ ) y_j=\begin{cases} r_j \\ r_j + \gamma \space max_{a'}\hat{Q}(\phi_{j+1,a';\theta}) \end{cases} yj={rjrj+γ maxaQ^(ϕj+1,a;θ)计算 y j y_j yj,注意这里计算 y j y_j yj的时候使用的是目标网络 Q ^ \hat{Q} Q^,DQN2013使用的是 Q Q Q
      • 最小化目标损失 ( y j − Q ( ϕ j , a j ; θ ) ) 2 (y_j-Q(\phi_{j,a_j;\theta}))^2 (yjQ(ϕj,aj;θ))2,以此更新网络 Q Q Q的模型参数;
      • 每间 C C C步更新目标网络 Q ^ \hat{Q} Q^的模型参数,即将 Q Q Q的模型参数复制给目标网络 Q ^ \hat{Q} Q^
  • 训练流程

    • 收集训练数据:我们可以用任何策略函数 π \pi π控制智能体与环境交互,这个 π \pi π可以叫做行为策略,dqn中使用的是 ϵ − g r e e d y \epsilon-greedy ϵgreedy策略,如公式(27)所示: a t = { a r g m a x a   Q ( s t , a ; ω ) ,                            以概率 ( 1 − ϵ ) 均匀抽取动作空间 A 中的一个动作     以概率 ϵ (27) a_t=\begin{cases} argmax_a \space Q(s_t,a;\omega),\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space以概率(1-\epsilon) \\ 均匀抽取动作空间\mathcal{A}中的一个动作 \space\space\space\space\space以概率\epsilon \end{cases} \tag{27} at={argmaxa Q(st,a;ω),                           以概率(1ϵ)均匀抽取动作空间A中的一个动作     以概率ϵ(27)把一个回合获取到的轨迹划分成 n n n个四元组 ( s t , a t , r t , s t + 1 ) (s_t,a_t,r_t,s_{t+1}) (st,at,rt,st+1),存入经验回放池。
    • 更新DQN参数 w \boldsymbol{w} w:随机从经验回放池中取出一个四元组,记作 ( s j , a j , r j , s j + 1 ) (s_j,a_j,r_j,s_{j+1}) (sj,aj,rj,sj+1),设DQN的当前参数为 w n o w \boldsymbol{w}_{now} wnow,执行下面的步骤对参数做一次更新,得到新的参数 w n e w \boldsymbol{w}_{new} wnew
      (1)对DQN做正向传播,得到 s t s_t st s t + 1 s_{t+1} st+1的Q-value值,如公式(28)和公式(29)所示: q ^ j = Q ( s j , a j , w n o w ) (28) \hat{q}_j=Q(s_j,a_j,\boldsymbol{w}_{now}) \tag{28} q^j=Q(sj,aj,wnow)(28) q ^ j + 1 = max ⁡ a ∈ A Q ^ ( s j + 1 , a ; w now  − ) (29) \widehat{q}_{j+1}=\max _{a \in \mathcal{A}} \hat{Q}\left(s_{j+1}, a ; \boldsymbol{w}^{-}_{\text {now }}\right) \tag{29} q j+1=aAmaxQ^(sj+1,a;wnow )(29)
      (2)计算目标Q-value值和误差,如公式(30)和公式(31)所示: y ^ j = r j + γ ⋅ q ^ j + 1 (30) \widehat{y}_j=r_j+\gamma \cdot \widehat{q}_{j+1} \tag{30} y j=rj+γq j+1(30) δ j = q ^ j − y ^ j (31) \delta_j=\widehat{q}_j-\widehat{y}_j \tag{31} δj=q jy j(31)
      (3)对DQN做反向传播,得到梯度,如公式(32)所示: g j = ∇ w Q ( s j , a j ; w now  ) (32) \boldsymbol{g}_j=\nabla_{\boldsymbol{w}} Q\left(s_j, a_j ; \boldsymbol{w}_{\text {now }}\right) \tag{32} gj=wQ(sj,aj;wnow )(32)
      (4)做梯度下降更新DQN的参数,如公式(33)所示: w new  ← w now  − α ⋅ δ j ⋅ g j (33) \boldsymbol{w}_{\text {new }} \leftarrow \boldsymbol{w}_{\text {now }}-\alpha \cdot \delta_j \cdot \boldsymbol{g}_j \tag{33} wnew wnow αδjgj(33)
      (5)间隔固定次数更新目标网络的参数,如公式(34)所示: w n e w − = w n e w (34) \boldsymbol{w}^{-}_{new}=\boldsymbol{w}_{new} \tag{34} wnew=wnew(34)
    • 分析:DQN2013算法使用DQN计算 y ^ \hat{y} y^,然后拿 y ^ \hat{y} y^更新DQN自己,造成自举导致高估危害。DQN2015中使用目标网络计算 y ^ \hat{y} y^,这样就避免了使用DQN的估计值更新DQN自己,缓解了自举导致的高估危害,然而DQN2015不能完全避免自举,原因是目标网络的参数仍然与DQN相关。
  • 基于gym的DQN2015代码实现

    • requirement:

      • python: 3.8
      • gym: 0.17.3
      • tensorflow: 2.9
    • code(代码主要参考书籍【动手学强化学习】(张伟楠,沈键,俞勇),京东购买链接 | 当当购买链接)

      import gym
      import random
      import collections
      import numpy as np
      from tqdm import tqdm
      import tensorflow as tf
      import matplotlib.pyplot as plt
      
      
      class ReplayBuffer:
          """
          经验回放池
          """
          def __init__(self, capacity):
              self.buffer = collections.deque(maxlen=capacity)  # 队列,先进先出
      
          def add(self, state, action, reward, next_state, done):  # 将数据加入buffer
              self.buffer.append((state, action, reward, next_state, done))
      
          def sample(self, batch_size):  # 从buffer中采样数据,数量为batch_size
              transitions = random.sample(self.buffer, batch_size)
              state, action, reward, next_state, done = zip(*transitions)
              return np.array(state), action, reward, np.array(next_state), done
      
          def size(self):  # 目前buffer中数据的数量
              return len(self.buffer)
      
      
      class Q_net(tf.keras.Model):
          """只有一层隐藏层的Q网络"""
          def __init__(self, state_dim, hidden_dim, action_dim):
              super(Q_net, self).__init__()
              self.fc1 = tf.keras.layers.Dense(hidden_dim, activation=tf.keras.activations.relu)
              self.fc2 = tf.keras.layers.Dense(action_dim)
      
          def call(self, x):
              return self.fc2(self.fc1(x))
      
      
      class DQN:
          def __init__(self, state_dim, hidden_dim, action_dim, learning_rate, gamma,
                       epsilon, target_update):
              self.action_dim = action_dim
              self.q_net = Q_net(state_dim, hidden_dim, self.action_dim)  # Q网络
              # 目标网络
              self.target_q_net = Q_net(state_dim, hidden_dim, self.action_dim)
              # 使用Adam优化器
              self.optimizer = tf.optimizers.Adam(learning_rate=learning_rate)
              self.gamma = gamma  # 折扣因子
              self.epsilon = epsilon  # epsilon-贪婪策略
              self.target_update = target_update  # 目标网络更新频率
              self.count = 0  # 计数器,记录更新次数
      
          def take_action(self, state):  # epsilon-贪婪策略采取动作
              if np.random.random() < self.epsilon:
                  action = np.random.randint(self.action_dim)
              else:
                  state = tf.cast(tf.expand_dims(state, axis=0), dtype=tf.float32)
                  action = tf.argmax(self.q_net.call(state), axis=1).numpy()[0]
              return action
      
          def update(self, transition_dict):
              states = tf.cast(transition_dict['states'], dtype=tf.float32)
              actions = tf.reshape(transition_dict['actions'], shape=(-1))
              rewards = tf.reshape(tf.cast(transition_dict['rewards'], dtype=tf.float32), shape=(-1, 1))
              next_states = tf.cast(transition_dict['next_states'], dtype=tf.float32)
              dones = tf.reshape(tf.cast(transition_dict['dones'], dtype=tf.float32), shape=(-1, 1))
              with tf.GradientTape() as tape:
                  q_values = tf.gather(self.q_net.call(states), actions, batch_dims=1)  # Q值
                  # 下个状态的最大Q值
                  max_next_q_values = tf.reduce_max(self.target_q_net.call(next_states), axis=1, keepdims=True)
                  q_targets = rewards + self.gamma * max_next_q_values * (1 - dones)  # TD误差目标
                  loss_func = tf.keras.losses.MeanSquaredError()
                  dqn_loss = tf.reduce_mean(loss_func(q_values, q_targets))  # 均方误差损失函数
              grads = tape.gradient(dqn_loss, self.q_net.trainable_variables)
              self.optimizer.apply_gradients(zip(grads, self.q_net.trainable_variables))
              if self.count % self.target_update == 0:
                  self.target_q_net.set_weights(self.q_net.get_weights())  # 更新目标网络
              self.count += 1
      
      lr = 2e-3
      num_episodes = 500
      hidden_dim = 128
      gamma = 0.98
      epsilon = 0.01
      target_update = 10
      buffer_size = 10000
      minimal_size = 500
      batch_size = 64
      
      
      env_name = 'CartPole-v0'
      env = gym.make(env_name)
      random.seed(0)
      np.random.seed(0)
      env.seed(0)
      tf.random.set_seed(0)
      replay_buffer = ReplayBuffer(buffer_size)
      state_dim = env.observation_space.shape[0]
      action_dim = env.action_space.n
      agent = DQN(state_dim, hidden_dim, action_dim, lr, gamma, epsilon, target_update)
      
      
      return_list = []
      for i in range(10):
          with tqdm(total=int(num_episodes / 10), desc='Iteration %d' % i) as pbar:
              for i_episode in range(int(num_episodes / 10)):
                  episode_return = 0
                  state = env.reset()
                  done = False
                  while not done:
                      action = agent.take_action(state)
                      next_state, reward, done, _ = env.step(action)
                      replay_buffer.add(state, action, reward, next_state, done)
                      state = next_state
                      episode_return += reward
                      # 当buffer数据的数量超过一定值后,才进行Q网络训练
                      if replay_buffer.size() > minimal_size:
                          b_s, b_a, b_r, b_ns, b_d = replay_buffer.sample(batch_size)
                          transition_dict = {
                              'states': b_s,
                              'actions': b_a,
                              'next_states': b_ns,
                              'rewards': b_r,
                              'dones': b_d
                          }
                          agent.update(transition_dict)
                  return_list.append(episode_return)
                  if (i_episode + 1) % 10 == 0:
                      pbar.set_postfix({
                          'episode':
                          '%d' % (num_episodes / 10 * i + i_episode + 1),
                          'return':
                          '%.3f' % np.mean(return_list[-10:])
                      })
                  pbar.update(1)
      
      episodes_list = list(range(len(return_list)))
      plt.plot(episodes_list, return_list)
      plt.xlabel('Episodes')
      plt.ylabel('Returns')
      plt.title('DQN on {}'.format(env_name))
      plt.show()
      
    • result
      基于值的深度强化学习算法_第6张图片

      图6. DQN2015 代码运行结果

Double DQN —— Deep Reinforcement Learning with Double Q-learning

  • 论文下载地址

  • 论文介绍

    造成DQN高估的原因不是DQN模型本身的问题,而是DQN使用的Q-learning算法有问题:第一,自举造成高估/低估的传播;第二,最大化造成目标动作价值的高估。在DQN2015中使用目标网络缓解自举造成的高估,但是这种方法不能缓解最大化导致的高估。本论文使用双Q学习算法(Double Q-learning),它在目标网络的基础上做改进,缓解最大化导致的高估问题。

    回顾一下DQN2013中计算目标动作价值的公式,如公式(35)所示: y ^ j = r j + γ ⋅ max ⁡ a ∈ A Q ( s j + 1 , a ; w ) (35) \widehat{y}_j=r_j+\gamma \cdot \max _{a \in \mathcal{A}} Q\left(s_{j+1}, a ; \boldsymbol{w}\right) \tag{35} y j=rj+γaAmaxQ(sj+1,a;w)(35)我们把最大化部分拆分成两步:

    (1)选择基于状态 s j + 1 s_{j+1} sj+1的最优动作 a ⋆ a^{\star} a,选择方式如下公式(36)所示: a ⋆ = argmax ⁡ a ∈ A Q ( s j + 1 , a ; w ) (36) a^{\star}=\underset{a \in \mathcal{A}}{\operatorname{argmax}} Q\left(s_{j+1}, a ; \boldsymbol{w}\right) \tag{36} a=aAargmaxQ(sj+1,a;w)(36)(2)基于状态 s j + 1 s_{j+1} sj+1和动作 a ⋆ a^{\star} a计算目标动作价值,如公式(37)所示: y ^ j = r j + Q ( s j + 1 , a ⋆ ; w ) (37) \widehat{y}_j=r_j+Q\left(s_{j+1}, a^{\star} ; \boldsymbol{w}\right) \tag{37} y j=rj+Q(sj+1,a;w)(37)上面是DQN2013使用的算法原理,选择基于状态 s j + 1 s_{j+1} sj+1的动作 a ⋆ a^{\star} a和计算基于 s j + 1 s_{j+1} sj+1 a ⋆ a^{\star} a的动作价值函数 Q ( s j + 1 , a ⋆ ) Q(s_{j+1},a^{\star}) Q(sj+1,a)都是使用同一个神经网络DQN。

    在DQN2015中改进了Q-learning学习,选择基于状态 s j + 1 s_{j+1} sj+1的动作 a ⋆ a^{\star} a和计算基于 s j + 1 s_{j+1} sj+1 a ⋆ a^{\star} a的动作价值函数 Q ( s j + 1 , a ⋆ ) Q(s_{j+1},a^{\star}) Q(sj+1,a)都是使用目标DQN,如下所示:

    (1)选择基于状态 s j + 1 s_{j+1} sj+1的最优动作 a ⋆ a^{\star} a,选择方式如下公式(38)所示: a ⋆ = argmax ⁡ a ∈ A Q ( s j + 1 , a ; w − ) (38) a^{\star}=\underset{a \in \mathcal{A}}{\operatorname{argmax}} Q\left(s_{j+1}, a ; \boldsymbol{w^{-}}\right) \tag{38} a=aAargmaxQ(sj+1,a;w)(38)(2)基于状态 s j + 1 s_{j+1} sj+1和动作 a ⋆ a^{\star} a计算目标动作价值,如公式(39)所示: y ^ j = r j + Q ( s j + 1 , a ⋆ ; w − ) (39) \widehat{y}_j=r_j+Q\left(s_{j+1}, a^{\star} ; \boldsymbol{w^{-}}\right) \tag{39} y j=rj+Q(sj+1,a;w)(39)

    在该论文中,第一步选择最优动作时使用DQN,第二步计算目标动作价值时使用目标网络:

    (1)选择基于状态 s j + 1 s_{j+1} sj+1的最优动作 a ⋆ a^{\star} a,选择方式如下公式(40)所示: a ⋆ = argmax ⁡ a ∈ A Q ( s j + 1 , a ; w ) (40) a^{\star}=\underset{a \in \mathcal{A}}{\operatorname{argmax}} Q\left(s_{j+1}, a ; \boldsymbol{w}\right) \tag{40} a=aAargmaxQ(sj+1,a;w)(40)(2)基于状态 s j + 1 s_{j+1} sj+1和动作 a ⋆ a^{\star} a计算目标动作价值,如公式(41)所示: y ^ j = r j + Q ( s j + 1 , a ⋆ ; w − ) (41) \widehat{y}_j=r_j+Q\left(s_{j+1}, a^{\star} ; \boldsymbol{w^{-}}\right) \tag{41} y j=rj+Q(sj+1,a;w)(41)
    双Q网络可以缓解最大化导致的高估,是因为在使用目标网络计算动作价值时所选择的动作 a ⋆ a^{\star} a不一定是目标网络中动作价值最大的动作,通过公式(42)进行理解: Q ( s j + 1 , a ⋆ ; w − ) ⏟ 双  Q  学习  ≤ max ⁡ a ∈ A Q ( s j + 1 , a ; w − ) ⏟ 用目标网络的  Q  学习  (42) \underbrace{Q\left(s_{j+1}, a^{\star} ; \boldsymbol{w}^{-}\right)}_{\text {双 } \mathrm{Q} \text { 学习 }} \leq \underbrace{\max _{a \in \mathcal{A}} Q\left(s_{j+1}, a ; \boldsymbol{w}^{-}\right)}_{\text {用目标网络的 } \mathrm{Q} \text { 学习 }} \tag{42}  Q 学习  Q(sj+1,a;w)用目标网络的 Q 学习  aAmaxQ(sj+1,a;w)(42)假定动作空间为 A = [ a 1 , a 2 , a 3 ] \mathcal{A}=[a_1,a_2,a_3] A=[a1,a2,a3],在第一步计算时基于DQN得到的最优动作为 a 1 a_1 a1,第二步使用目标网络计算 a 1 a_1 a1对应的动作价值函数,由于目标网络中 a 3 a_3 a3对应的动作价值函数最大,这时候可以知道有公式(43): Q ( s j + 1 , a 1 ; w − ) ⏟ 双  Q  学习  ≤ Q ( s j + 1 , a 3 ; w − ) ⏟ 用目标网络的  Q  学习  (43) \underbrace{Q\left(s_{j+1}, a_1 ; \boldsymbol{w}^{-}\right)}_{\text {双 } \mathrm{Q} \text { 学习 }} \leq \underbrace{ Q\left(s_{j+1}, a_3 ; \boldsymbol{w}^{-}\right)}_{\text {用目标网络的 } \mathrm{Q} \text { 学习 }} \tag{43}  Q 学习  Q(sj+1,a1;w)用目标网络的 Q 学习  Q(sj+1,a3;w)(43)因此,可以知道通过双Q学习的到的目标动作价值函数比使用目标网络的Q-leanring的到的动作价值函数更小,所以说双Q学习缓解了最大化导致的高估,如公式(44)所示: y ~ t ⏟ 双  Q  学习  ≤ y ^ t − ⏟ 用目标网络的  Q  学习  (44) \underbrace{\widetilde{y}_t}_{\text {双 } \mathrm{Q} \text { 学习 }} \leq \underbrace{\widehat{y}_t^{-}}_{\text {用目标网络的 } \mathrm{Q} \text { 学习 }} \tag{44}  Q 学习  y t用目标网络的 Q 学习  y t(44)

  • 伪代码
    基于值的深度强化学习算法_第7张图片

    图7. Double DQN 伪代码

    • 初始化网络参数 θ \theta θ,初始化目标网络参数 θ − \theta^{-} θ,目标网络参数 θ − \theta^{-} θ复制于网络参数 θ \theta θ,初始化经验回放池 D \mathcal{D} D
    • 设置经验回放池的容量为 N r N_r Nr,设置用于训练的批量大小为 N b N_b Nb,设置目标网络的参数更新频率为 N − N^{-} N
    • 循环每一条轨迹(每一个回合):
      • 初始化帧序列 x x x
      • 循环轨迹(回合)中每一个时间步:
        • 对帧序列信息 x x x进行处理得到状态 s s s,根据策略 π B \pi_{\mathcal{B}} πB 动作 a a a
        • 将动作 a a a与模拟器进行交互得到奖励 r r r和下一帧信息 x t x^t xt,将帧信息 x t x^t xt保存到帧序列 x x x中;
        • 如果帧序列 x x x的容量超过 N f N_f Nf,删除帧序列中最旧的数据 x t m i n x_{t_{min}} xtmin
        • 对帧序列 x x x处理得到状态 s ′ s' s,将 ( s , a , r , s ′ ) (s,a,r,s') (s,a,r,s)保存到经验回放池 D \mathcal{D} D ,如果经验回放池中的数据量超过最大容量 N r N_r Nr ,移除回放池中最旧的数据(最先保存的数据);
        • 从经验回放池中随机抽取批量大小为 N b N_b Nb的四元组 ( s , a , r , s ′ ) (s,a,r,s') (s,a,r,s)
        • 计算目标动作价值函数:
          • 基于网络 θ \theta θ 获取基于状态 s ′ s' s的最优动作 a m a x a^{max} amax a max ⁡ ( s ′ ; θ ) = arg ⁡ max ⁡ a ′ Q ( s ′ , a ′ ; θ ) a^{\max }\left(s^{\prime} ; \theta\right)=\arg \max _{a^{\prime}} Q\left(s^{\prime}, a^{\prime} ; \theta\right) amax(s;θ)=argmaxaQ(s,a;θ)
          • 基于目标网络 θ − \theta^{-} θ计算目标动作价值函数 y j y_j yj y j = { r  if  s ′  is terminal  r + γ Q ( s ′ , a max ⁡ ( s ′ ; θ ) ; θ − ) , otherwise.  y_j=\left\{\begin{array}{cc} r & \text { if } s^{\prime} \text { is terminal } \\ r+\gamma Q\left(s^{\prime}, a^{\max }\left(s^{\prime} ; \theta\right) ; \theta^{-}\right), & \text {otherwise. } \end{array}\right. yj={rr+γQ(s,amax(s;θ);θ), if s is terminal otherwise. 
        • 计算均方差损失函数并使用梯度下降优化网络参数 θ \theta θ
        • 间隔 N − N^{-} N更新一次目标网络参数 θ − \theta^{-} θ,更新方式为将网络参数 θ \theta θ值复制给目标网络参数 θ − \theta^{-} θ
      • 结束时间步循环;
    • 结束回合循环;
  • 训练流程

    • 收集训练数据:我们可以用任何策略函数 π \pi π控制智能体与环境交互,这个 π \pi π可以叫做行为策略,dqn中使用的是 ϵ − g r e e d y \epsilon-greedy ϵgreedy策略,如公式(45)所示: a t = { a r g m a x a   Q ( s t , a ; ω ) ,                            以概率 ( 1 − ϵ ) 均匀抽取动作空间 A 中的一个动作     以概率 ϵ (45) a_t=\begin{cases} argmax_a \space Q(s_t,a;\omega),\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space以概率(1-\epsilon) \\ 均匀抽取动作空间\mathcal{A}中的一个动作 \space\space\space\space\space以概率\epsilon \end{cases} \tag{45} at={argmaxa Q(st,a;ω),                           以概率(1ϵ)均匀抽取动作空间A中的一个动作     以概率ϵ(45)把一个回合获取到的轨迹划分成 n n n个四元组 ( s t , a t , r t , s t + 1 ) (s_t,a_t,r_t,s_{t+1}) (st,at,rt,st+1),存入经验回放池。
    • 更新Double DQN参数 w \boldsymbol{w} w:随机从经验回放池中取出一个四元组,记作 ( s j , a j , r j , s j + 1 ) (s_j,a_j,r_j,s_{j+1}) (sj,aj,rj,sj+1),设DQN的当前参数为 w n o w \boldsymbol{w}_{now} wnow,执行下面的步骤对参数做一次更新,得到新的参数 w n e w \boldsymbol{w}_{new} wnew
      (1)对DQN做正向传播,得到 s t s_t st s t + 1 s_{t+1} st+1的Q-value值,如公式(46)、公式(47)和公式(48)所示: q ^ j = Q ( s j , a j , w n o w ) (46) \hat{q}_j=Q(s_j,a_j,\boldsymbol{w}_{now}) \tag{46} q^j=Q(sj,aj,wnow)(46) a ⋆ = argmax ⁡ a ∈ A Q ( s j + 1 , a ; w now  ) (47) a^{\star}=\underset{a \in \mathcal{A}}{\operatorname{argmax}} Q\left(s_{j+1}, a ; \boldsymbol{w}_{\text {now }}\right) \tag{47} a=aAargmaxQ(sj+1,a;wnow )(47) q ^ j + 1 = max ⁡ a ∈ A Q ^ ( s j + 1 , a ⋆ ; w now  − ) (48) \widehat{q}_{j+1}=\max _{a \in \mathcal{A}} \hat{Q}\left(s_{j+1}, a^{\star} ; \boldsymbol{w}^{-}_{\text {now }}\right) \tag{48} q j+1=aAmaxQ^(sj+1,a;wnow )(48)
      (2)计算目标Q-value值和误差,如公式(49)和公式(50)所示: y ^ j = r j + γ ⋅ q ^ j + 1 (49) \widehat{y}_j=r_j+\gamma \cdot \widehat{q}_{j+1} \tag{49} y j=rj+γq j+1(49) δ j = q ^ j − y ^ j (50) \delta_j=\widehat{q}_j-\widehat{y}_j \tag{50} δj=q jy j(50)
      (3)对DQN做反向传播,得到梯度,如公式(51)所示: g j = ∇ w Q ( s j , a j ; w now  ) (51) \boldsymbol{g}_j=\nabla_{\boldsymbol{w}} Q\left(s_j, a_j ; \boldsymbol{w}_{\text {now }}\right) \tag{51} gj=wQ(sj,aj;wnow )(51)
      (4)做梯度下降更新DQN的参数,如公式(52)所示: w new  ← w now  − α ⋅ δ j ⋅ g j (52) \boldsymbol{w}_{\text {new }} \leftarrow \boldsymbol{w}_{\text {now }}-\alpha \cdot \delta_j \cdot \boldsymbol{g}_j \tag{52} wnew wnow αδjgj(52)
      (5)间隔固定次数更新目标网络的参数,如公式(53)所示: w n e w − = w n e w (53) \boldsymbol{w}^{-}_{new}=\boldsymbol{w}_{new} \tag{53} wnew=wnew(53)
  • DQN2013 | DQN2015 | Double DQN 对比

算法 选择下一状态 s ′ s' s的网络 获取目标价值函数 Q ( s ′ , a ) Q(s',a) Q(s,a) 的网络 自举造成高估/低估 最大化造成高估/低估
DQN2013 DQN DQN 严重 严重
DQN2015 目标网络 目标网络 不严重 严重
Double DQN DQN 目标网络 不严重 不严重
  • 基于gym的Double DQN代码实现
    • requirement:

      • python: 3.8
      • gym: 0.17.3
      • tensorflow: 2.9
    • code(代码主要参考书籍【动手学强化学习】(张伟楠,沈键,俞勇),京东购买链接 | 当当购买链接)

      DQN只能处理离散动作问题,因此对于动作为连续值的倒立摆,我们无法直接使用DQN。由于倒立摆可以比较方便验证DQN对Q值的高估问题(倒立摆环境下的Q值最大估计应该为0),预测得到的Q值大于0说明模型出现了高估问题。为了能够使用DQN,我们采用离散化动作技巧,将倒立摆的连续动作修改为11个离散动作。

      import gym
      import random
      import collections
      import numpy as np
      from tqdm import tqdm
      import tensorflow as tf
      import matplotlib.pyplot as plt
      
      
      class ReplayBuffer:
          """
          经验回放池
          """
          def __init__(self, capacity):
              self.buffer = collections.deque(maxlen=capacity)  # 队列,先进先出
      
          def add(self, state, action, reward, next_state, done):  # 将数据加入buffer
              self.buffer.append((state, action, reward, next_state, done))
      
          def sample(self, batch_size):  # 从buffer中采样数据,数量为batch_size
              transitions = random.sample(self.buffer, batch_size)
              state, action, reward, next_state, done = zip(*transitions)
              return np.array(state), action, reward, np.array(next_state), done
      
          def size(self):  # 目前buffer中数据的数量
              return len(self.buffer)
      
      
      class Q_net(tf.keras.Model):
          """只有一层隐藏层的Q网络"""
          def __init__(self, hidden_dim, action_dim):
              super(Q_net, self).__init__()
              self.fc1 = tf.keras.layers.Dense(hidden_dim, activation=tf.keras.activations.relu)
              self.fc2 = tf.keras.layers.Dense(action_dim)
      
          def call(self, x):
              return self.fc2(self.fc1(x))
      
      
      class DQN:
          def __init__(self, hidden_dim, action_dim, learning_rate, gamma, epsilon, target_update, dqn_type='VanillaDQN'):
              self.action_dim = action_dim
              self.q_net = Q_net(hidden_dim, self.action_dim)  # Q网络
              # 目标网络
              self.target_q_net = Q_net(hidden_dim, self.action_dim)
              # 使用Adam优化器
              self.optimizer = tf.optimizers.Adam(learning_rate=learning_rate)
              self.gamma = gamma  # 折扣因子
              self.epsilon = epsilon  # epsilon-贪婪策略
              self.target_update = target_update  # 目标网络更新频率
              self.count = 0  # 计数器,记录更新次数
              self.dqn_type = dqn_type
      
          def take_action(self, state):  # epsilon-贪婪策略采取动作
              if np.random.random() < self.epsilon:
                  action = np.random.randint(self.action_dim)
              else:
                  state = tf.cast(tf.expand_dims(state, axis=0), dtype=tf.float32)
                  action = tf.argmax(self.q_net.call(state), axis=1).numpy()[0]
              return action
      
          def max_q_value(self, state):
              state = tf.cast(tf.expand_dims(state, axis=0), dtype=tf.float32)
              return tf.reduce_max(self.q_net.call(state)).numpy()
      
          def update(self, transition_dict):
              states = tf.cast(transition_dict['states'], dtype=tf.float32)
              actions = tf.reshape(transition_dict['actions'], shape=(-1))
              rewards = tf.reshape(tf.cast(transition_dict['rewards'], dtype=tf.float32), shape=(-1, 1))
              next_states = tf.cast(transition_dict['next_states'], dtype=tf.float32)
              dones = tf.reshape(tf.cast(transition_dict['dones'], dtype=tf.float32), shape=(-1, 1))
              with tf.GradientTape() as tape:
                  q_values = tf.gather(self.q_net.call(states), actions, batch_dims=1)  # Q值
                  # 下个状态的最大Q值
                  if self.dqn_type == 'DoubleDQN':  # DQN与Double DQN的区别
                      max_action = tf.argmax(self.q_net.call(next_states), axis=1, output_type=tf.int32)
                      max_next_q_values = tf.reshape(tf.gather(self.target_q_net.call(next_states), max_action, batch_dims=1), shape=(-1,1))  # Q值
                  else:
                      max_next_q_values = tf.reduce_max(self.target_q_net.call(next_states), axis=1, keepdims=True)
                  q_targets = rewards + self.gamma * max_next_q_values * (1 - dones)  # TD误差目标
                  loss_func = tf.keras.losses.MeanSquaredError()
                  dqn_loss = tf.reduce_mean(loss_func(q_values, q_targets))  # 均方误差损失函数
              grads = tape.gradient(dqn_loss, self.q_net.trainable_variables)
              self.optimizer.apply_gradients(zip(grads, self.q_net.trainable_variables))
              if self.count % self.target_update == 0:
                  self.target_q_net.set_weights(self.q_net.get_weights())  # 更新目标网络
              self.count += 1
      
      lr = 1e-2
      num_episodes = 200
      hidden_dim = 128
      gamma = 0.98
      epsilon = 0.01
      target_update = 50
      buffer_size = 5000
      minimal_size = 1000
      batch_size = 64
      
      
      env_name = 'Pendulum-v0'
      env = gym.make(env_name)
      state_dim = env.observation_space.shape[0]
      action_dim = 11  # 将连续动作分成11个离散动作
      
      
      def dis_to_con(discrete_action, env, action_dim):  # 离散动作转回连续的函数
          action_lowbound = env.action_space.low[0]  # 连续动作的最小值
          action_upbound = env.action_space.high[0]  # 连续动作的最大值
          return action_lowbound + (discrete_action / (action_dim - 1)) * (action_upbound - action_lowbound)
      
      
      random.seed(0)
      np.random.seed(0)
      env.seed(0)
      tf.random.set_seed(0)
      replay_buffer = ReplayBuffer(buffer_size)
      agent = DQN(hidden_dim, action_dim, lr, gamma, epsilon, target_update, "DoubleDQN")
      
      return_list = []
      max_q_value_list = []
      max_q_value = 0
      for i in range(10):
          with tqdm(total=int(num_episodes / 10),
                    desc='Iteration %d' % i) as pbar:
              for i_episode in range(int(num_episodes / 10)):
                  episode_return = 0
                  state = env.reset()
                  done = False
                  while not done:
                      action = agent.take_action(state)
                      max_q_value = agent.max_q_value(
                          state) * 0.005 + max_q_value * 0.995  # 平滑处理
                      max_q_value_list.append(max_q_value)  # 保存每个状态的最大Q值
                      action_continuous = dis_to_con(action, env,
                                                     agent.action_dim)
                      next_state, reward, done, _ = env.step([action_continuous])
                      replay_buffer.add(state, action, reward, next_state, done)
                      state = next_state
                      episode_return += reward
                      if replay_buffer.size() > minimal_size:
                          b_s, b_a, b_r, b_ns, b_d = replay_buffer.sample(
                              batch_size)
                          transition_dict = {
                              'states': b_s,
                              'actions': b_a,
                              'next_states': b_ns,
                              'rewards': b_r,
                              'dones': b_d
                          }
                          agent.update(transition_dict)
                  return_list.append(episode_return)
                  if (i_episode + 1) % 10 == 0:
                      pbar.set_postfix({
                          'episode':
                              '%d' % (num_episodes / 10 * i + i_episode + 1),
                          'return':
                              '%.3f' % np.mean(return_list[-10:])
                      })
                  pbar.update(1)
      
      episodes_list = list(range(len(return_list)))
      plt.plot(episodes_list, return_list)
      plt.xlabel('Episodes')
      plt.ylabel('Returns')
      plt.title('Double DQN on {}'.format(env_name))
      plt.show()
      
      frames_list = list(range(len(max_q_value_list)))
      plt.plot(frames_list, max_q_value_list)
      plt.axhline(0, c='orange', ls='--')
      plt.axhline(10, c='red', ls='--')
      plt.xlabel('Frames')
      plt.ylabel('Q value')
      plt.title('Double DQN on {}'.format(env_name))
      plt.show()
      
    • result
      基于值的深度强化学习算法_第8张图片 基于值的深度强化学习算法_第9张图片
      基于值的深度强化学习算法_第10张图片 基于值的深度强化学习算法_第11张图片

      图8. Double DQN & DQN 代码运行结果对比

      从结果可以看出,DQN的不少Q值超过了0,这就是DQN的高估问题;Double DQN相较于DQN,出现Q值大于0的情况少了很多,说明Double DQN能够有效缓解高估问题。

引用文献


  1. Gerald Tesauro. Temporal difference learning and td-gammon. Communications of the ACM,38(3):58–68, 1995. ↩︎

  2. Jordan B. Pollack and Alan D. Blair. Why did td-gammon work. In Advances in Neural Information Processing Systems 9, pages 10–16, 1996. ↩︎

  3. John N Tsitsiklis and Benjamin Van Roy. An analysis of temporal-difference learning with function approximation. Automatic Control, IEEE Transactions on, 42(5):674–690, 1997. ↩︎ ↩︎

  4. Leemon Baird. Residual algorithms: Reinforcement learning with function approximation. In Proceedings of the 12th International Conference on Machine Learning (ICML 1995), pages 30–37. Morgan Kaufmann, 1995. ↩︎

  5. Brian Sallans and Geoffrey E. Hinton. Reinforcement learning with factored states and actions. Journal of Machine Learning Research, 5:1063–1088, 2004. ↩︎

  6. Nicolas Heess, David Silver, and Yee Whye Teh. Actor-critic reinforcement learning with energy-based policies. In European Workshop on Reinforcement Learning, page 43, 2012. ↩︎

  7. Hamid Maei, Csaba Szepesvari, Shalabh Bhatnagar, Doina Precup, David Silver, and Rich Sutton. Convergent Temporal-Difference Learning with Arbitrary Smooth Function Approximation. In Advances in Neural Information Processing Systems 22, pages 1204–1212, 2009. ↩︎

  8. Hamid Maei, Csaba Szepesv´ari, Shalabh Bhatnagar, and Richard S. Sutton. Toward off-policy learning control with function approximation. In Proceedings of the 27th International Conference on Machine Learning (ICML 2010), pages 719–726, 2010. ↩︎

  9. Martin Riedmiller. Neural fitted q iteration–first experiences with a data efficient neural reinforcement learning method. In Machine Learning: ECML 2005, pages 317–328. Springer, 2005. ↩︎

  10. Sascha Lange and Martin Riedmiller. Deep auto-encoder neural networks in reinforcement learning. In Neural Networks (IJCNN), The 2010 International Joint Conference on, pages 1–8. IEEE, 2010. ↩︎

  11. Long-Ji Lin. Reinforcement learning for robots using neural networks. Technical report, DTIC Document, 1993. ↩︎

你可能感兴趣的:(深度学习)