相关资源来自:伯禹学习平台-动手学强化学习

动手强化学习(七):DQN 改进算法——Dueling DQN

  • 1. 简介
  • 2. Dueling DQN
  • 3. Dueling DQN 代码实践
  • 4. 对 Q 值过高估计的定量分析
  • 总结

文章转于 伯禹学习平台-动手学强化学习 (强推)

本文所有代码均可在jupyter notebook运行

与君共勉,一起学习。

更多Ai资讯:公主号AiCharm

动手强化学习(八):DQN 改进算法——Dueling DQN_深度学习

1. 简介

  DQN 算法敲开了深度强化学习的大门,但是作为先驱性的工作,其本身存在着一些问题以及一些可以改进的地方。于是,在 DQN 之后,学术界涌现出了非常多的改进算法。本章将介绍其中两个非常著名的算法:Double DQNDueling DQN,这两个算法的实现非常简单,只需要在 DQN 的基础上稍加修改,它们能在一定程度上改善 DQN 的效果。

2. Dueling DQN

Dueling DQN 是 DQN 另一种的改进算法,它在传统 DQN 的基础上只进行了微小的改动,但却能大幅提升 DQN 的表现。在强化学习中,我们将状态动作价值函数 动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_02 减去状态价值函数 动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_03 的结果定 义为优势函数 动手强化学习(八):DQN 改进算法——Dueling DQN_深度学习_04 ,即 动手强化学习(八):DQN 改进算法——Dueling DQN_深度学习_05 。在同一个状态下,所有动作的优势值之和为 0 ,因为所有动作的动作价值的期望就是这个状态的状态价值。据此,在 Dueling DQN 中,Q网络被 建模为:

动手强化学习(八):DQN 改进算法——Dueling DQN_算法_06

其中, 动手强化学习(八):DQN 改进算法——Dueling DQN_深度学习_07 为状态价值函数,而 动手强化学习(八):DQN 改进算法——Dueling DQN_建模_08 则为该状态下采取不同动作的优势函数,表示采取不同动作的差异性; 动手强化学习(八):DQN 改进算法——Dueling DQN_算法_09 是状态价值函数和优势函数共享的网络参数,一般用在神经网络中,用来提 取特征的前几层; 而 动手强化学习(八):DQN 改进算法——Dueling DQN_建模_10动手强化学习(八):DQN 改进算法——Dueling DQN_建模_11 分别为状态价值函数和优势函数的参数。在这样的模型下,我们不再让神经网络直接输出 动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_02 值,而是训练神经网络的最后几层的两个分支,分别输出状态价值函数和优势 函数,再求和得到 动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_02 值。Dueling DQN 的网络结构如图所示。

动手强化学习(八):DQN 改进算法——Dueling DQN_强化学习_14


将状态价值函数和优势函数分别建模的好处在于:某些情境下智能体只会关注状态的价值,而并不关心不同动作导致的差异,此时将二者分开建模能够使智能体更好地处理与动作关联较小的状态。在下图所示的驾驶车辆游戏中,智能体注意力集中的部位被显示为橙色,当智能体前面没有车时,车辆自身动作并没有太大差异,此时智能体更关注状态价值,而当智能体前面有车时(智能体需要超车),智能体开始关注不同动作优势值的差异。

动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_15


对于 Dueling DQN 中的公式 动手强化学习(八):DQN 改进算法——Dueling DQN_强化学习_16 ,它存在对于 动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_03 值和 动手强化学习(八):DQN 改进算法——Dueling DQN_深度学习_04 值建模不唯一性的问题。例如,对于同样的 动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_02 值,如果将 动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_03 值加上任意大小的常数 动手强化学习(八):DQN 改进算法——Dueling DQN_强化学习_21 ,再将所有 动手强化学习(八):DQN 改进算法——Dueling DQN_深度学习_04 值减去 动手强化学习(八):DQN 改进算法——Dueling DQN_强化学习_21 ,则得到的 动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_02 值依然不变,这就导致了训练的不稳定性。为了解决这一问题,Dueling DQN 强制最优动作的优势函数的实际输出为 0 ,即:

动手强化学习(八):DQN 改进算法——Dueling DQN_深度学习_25

此时 动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_26 ,可以确保 动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_03 值建模的唯一性。在实现过程中,我们还可以用平均代替最大化操作,即:

动手强化学习(八):DQN 改进算法——Dueling DQN_算法_28

此时 动手强化学习(八):DQN 改进算法——Dueling DQN_建模_29

有人可能会问:“为什么 Dueling DQN 会比 DQN 好? "部分原因在于 Dueling DQN 能更高效学习状态价值函数每一次更新时,函数 动手强化学习(八):DQN 改进算法——Dueling DQN_建模_30 都会被更新,这也会影响到其他动作的 动手强化学习(八):DQN 改进算法——Dueling DQN_深度学习_31 值。而传统的 DQN 只会更新某个动作的 动手强化学习(八):DQN 改进算法——Dueling DQN_深度学习_31 值,其他动作的 动手强化学习(八):DQN 改进算法——Dueling DQN_深度学习_31。因此,Dueling DQN 能够更加频繁、准确地学习状态价值函数。

3. Dueling DQN 代码实践

Dueling DQN 与 DQN 相比的差异只是在网络结构上,大部分代码依然可以继续沿用。我们定义状态价值函数和优势函数的复合神经网络VAnet。

class VAnet(torch.nn.Module):
    ''' 只有一层隐藏层的A网络和V网络 '''
    def __init__(self, state_dim, hidden_dim, action_dim):
        super(VAnet, self).__init__()
        self.fc1 = torch.nn.Linear(state_dim, hidden_dim)  # 共享网络部分
        self.fc_A = torch.nn.Linear(hidden_dim, action_dim)
        self.fc_V = torch.nn.Linear(hidden_dim, 1)

    def forward(self, x):
        A = self.fc_A(F.relu(self.fc1(x)))
        V = self.fc_V(F.relu(self.fc1(x)))
        Q = V + A - A.mean(1).view(-1, 1)  # Q值由V值和A值计算得到
        return Q


class DQN:
    ''' DQN算法,包括Double DQN和Dueling DQN '''
    def __init__(self,
                 state_dim,
                 hidden_dim,
                 action_dim,
                 learning_rate,
                 gamma,
                 epsilon,
                 target_update,
                 device,
                 dqn_type='VanillaDQN'):
        self.action_dim = action_dim
        if dqn_type == 'DuelingDQN':  # Dueling DQN采取不一样的网络框架
            self.q_net = VAnet(state_dim, hidden_dim,
                               self.action_dim).to(device)
            self.target_q_net = VAnet(state_dim, hidden_dim,
                                      self.action_dim).to(device)
        else:
            self.q_net = Qnet(state_dim, hidden_dim,
                              self.action_dim).to(device)
            self.target_q_net = Qnet(state_dim, hidden_dim,
                                     self.action_dim).to(device)
        self.optimizer = torch.optim.Adam(self.q_net.parameters(),
                                          lr=learning_rate)
        self.gamma = gamma
        self.epsilon = epsilon
        self.target_update = target_update
        self.count = 0
        self.dqn_type = dqn_type
        self.device = device

    def take_action(self, state):
        if np.random.random() < self.epsilon:
            action = np.random.randint(self.action_dim)
        else:
            state = torch.tensor([state], dtype=torch.float).to(self.device)
            action = self.q_net(state).argmax().item()
        return action

    def max_q_value(self, state):
        state = torch.tensor([state], dtype=torch.float).to(self.device)
        return self.q_net(state).max().item()

    def update(self, transition_dict):
        states = torch.tensor(transition_dict['states'],
                              dtype=torch.float).to(self.device)
        actions = torch.tensor(transition_dict['actions']).view(-1, 1).to(
            self.device)
        rewards = torch.tensor(transition_dict['rewards'],
                               dtype=torch.float).view(-1, 1).to(self.device)
        next_states = torch.tensor(transition_dict['next_states'],
                                   dtype=torch.float).to(self.device)
        dones = torch.tensor(transition_dict['dones'],
                             dtype=torch.float).view(-1, 1).to(self.device)

        q_values = self.q_net(states).gather(1, actions)
        if self.dqn_type == 'DoubleDQN':
            max_action = self.q_net(next_states).max(1)[1].view(-1, 1)
            max_next_q_values = self.target_q_net(next_states).gather(
                1, max_action)
        else:
            max_next_q_values = self.target_q_net(next_states).max(1)[0].view(
                -1, 1)
        q_targets = rewards + self.gamma * max_next_q_values * (1 - dones)
        dqn_loss = torch.mean(F.mse_loss(q_values, q_targets))
        self.optimizer.zero_grad()
        dqn_loss.backward()
        self.optimizer.step()

        if self.count % self.target_update == 0:
            self.target_q_net.load_state_dict(self.q_net.state_dict())
        self.count += 1


random.seed(0)
np.random.seed(0)
env.seed(0)
torch.manual_seed(0)
replay_buffer = rl_utils.ReplayBuffer(buffer_size)
agent = DQN(state_dim, hidden_dim, action_dim, lr, gamma, epsilon,
            target_update, device, 'DuelingDQN')
return_list, max_q_value_list = train_DQN(agent, env, num_episodes,
                                          replay_buffer, minimal_size,
                                          batch_size)

episodes_list = list(range(len(return_list)))
mv_return = rl_utils.moving_average(return_list, 5)
plt.plot(episodes_list, mv_return)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('Dueling 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('Dueling DQN on {}'.format(env_name))
plt.show()
-------------------------------------------------------------------------------------------
Iteration 0: 100%|████████████████████████████████████████| 20/20 [00:10<00:00,  1.87it/s, episode=20, return=-708.652]
Iteration 1: 100%|████████████████████████████████████████| 20/20 [00:15<00:00,  1.28it/s, episode=40, return=-229.557]
Iteration 2: 100%|████████████████████████████████████████| 20/20 [00:15<00:00,  1.32it/s, episode=60, return=-184.607]
Iteration 3: 100%|████████████████████████████████████████| 20/20 [00:13<00:00,  1.50it/s, episode=80, return=-200.323]
Iteration 4: 100%|███████████████████████████████████████| 20/20 [00:13<00:00,  1.51it/s, episode=100, return=-213.811]
Iteration 5: 100%|███████████████████████████████████████| 20/20 [00:13<00:00,  1.53it/s, episode=120, return=-181.165]
Iteration 6: 100%|███████████████████████████████████████| 20/20 [00:14<00:00,  1.35it/s, episode=140, return=-222.040]
Iteration 7: 100%|███████████████████████████████████████| 20/20 [00:14<00:00,  1.35it/s, episode=160, return=-173.313]
Iteration 8: 100%|███████████████████████████████████████| 20/20 [00:12<00:00,  1.62it/s, episode=180, return=-236.372]
Iteration 9: 100%|███████████████████████████████████████| 20/20 [00:12<00:00,  1.57it/s, episode=200, return=-230.058]

动手强化学习(八):DQN 改进算法——Dueling DQN_建模_34

动手强化学习(八):DQN 改进算法——Dueling DQN_强化学习_35

根据代码运行结果我们可以发现,相比于传统的 DQN,Dueling DQN 在多个动作选择下的学习更加稳定,得到的回报最大值也更大。由 Dueling DQN 的原理可知,随着动作空间的增大,Dueling DQN 相比于 DQN 的优势更为明显。之前我们在环境中设置的离散动作数为 11,我们可以增加离散动作数(例如 15、25 等),继续进行对比实验。

4. 对 Q 值过高估计的定量分析

动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_36 值过高估计的定量分析 动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_37 服从 动手强化学习(八):DQN 改进算法——Dueling DQN_深度学习_38 之间的均匀独立同分布;假设动作空间大小为 动手强化学习(八):DQN 改进算法——Dueling DQN_强化学习_39 。那么,对于任意状态 动手强化学习(八):DQN 改进算法——Dueling DQN_算法_40 ,有:
动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_41
即状态空间 动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_42 越大时, 动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_02 值过高,估计越严重。
证明: 将估算误差记为 动手强化学习(八):DQN 改进算法——Dueling DQN_建模_44 ,由于估算误差对于不同的动作是独立的,因此有:
动手强化学习(八):DQN 改进算法——Dueling DQN_强化学习_45
动手强化学习(八):DQN 改进算法——Dueling DQN_深度学习_46动手强化学习(八):DQN 改进算法——Dueling DQN_强化学习_47 的累积分布函数 (cumulative distribution function,即 CDF),它可以具体被写为:
动手强化学习(八):DQN 改进算法——Dueling DQN_深度学习_48
因此,我们得到关于 动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_49 的累积分布函数:
动手强化学习(八):DQN 改进算法——Dueling DQN_算法_50
最后我们可以得到:
动手强化学习(八):DQN 改进算法——Dueling DQN_强化学习_51
虽然这一分析简化了实际环境,但它仍然正确刻画了动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_36 值过高估计的一些性质,比如动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_36 值的过高估计随动作空间大小动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_42 的增加而增加,换言之,在动作选择数更多的环境中,动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_36

总结

在传统的 DQN 基础上,有两种非常容易实现的变式——Double DQN 和 Dueling DQN,Double DQN 解决了 DQN 中对动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_02值的过高估计,而 Dueling DQN 能够很好地学习到不同动作的差异性,在动作空间较大的环境下非常有效。从 Double DQN 和 Dueling DQN 的方法原理中,我们也能感受到深度强化学习的研究是在关注深度学习和强化学习有效结合:一是在深度学习的模块的基础上,强化学习方法如何更加有效地工作,并避免深度模型学习行为带来的一些问题,例如使用 Double DQN 解决动手强化学习(八):DQN 改进算法——Dueling DQN_神经网络_02值过高估计的问题;二是在强化学习的场景下,深度学习模型如何有效学习到有用的模式,例如设计 Dueling DQN 网络架构来高效地学习状态价值函数以及动作优势函数。

更多Ai资讯:公主号AiCharm

动手强化学习(八):DQN 改进算法——Dueling DQN_深度学习

相关资源来自:伯禹学习平台-动手学强化学习

动手强化学习(八):DQN 改进算法——Dueling DQN_算法_59