【强化学习】11 —— Double DQN算法与Dueling DQN算法

news/2024/5/18 21:44:04 标签: 算法, 机器学习, 人工智能, 强化学习

文章目录

  • Q-learning中的过高估计
  • Double DQN
    • Double DQN代码实践
      • Pendulum环境
      • 代码
      • 结果
  • Dueling DQN
    • Dueling DQN网络结构
    • Dueling DQN优点
    • Dueling DQN 代码实践
      • 结果
  • 参考

Q-learning中的过高估计

普通的 DQN 算法通常会导致对值的过高估计(overestimation)。传统 DQN 优化的 TD 误差目标为 r + γ max ⁡ a ′ Q ω − ( s ′ , a ′ ) r+\gamma\max_{a^{\prime}}Q_{\omega^{-}}\left(s^{\prime},a^{\prime}\right) r+γamaxQω(s,a)
其中 max ⁡ a ′ Q ω − ( s ′ , a ′ ) \max_{a^{\prime}}Q_{\omega^{-}}\left(s^{\prime},a^{\prime}\right) maxaQω(s,a)可以变为这种形式 Q ω − ( s ′ , arg ⁡ max ⁡ a ′ Q ω − ( s ′ , a ′ ) ) Q_{\omega^-}\left(s',\arg\max_{a'}Q_{\omega^-}\left(s',a'\right)\right) Qω(s,argamaxQω(s,a)) m a x max max操作实际可以被拆解为两部分:首先选取状态 s ′ s' s下的最优动作 a ∗ = arg ⁡ max ⁡ a ′ Q ω − ( s ′ , a ′ ) a^*=\arg\max_{a^{\prime}}Q_{\omega^-}\left(s^{\prime},a^{\prime}\right) a=argmaxaQω(s,a),接着计算该动作对应的价值 Q ω − ( s ′ , a ∗ ) Q_{\omega^{-}}\left(s^{\prime},a^{*}\right) Qω(s,a)。但 m a x max max 操作使得 𝑄 函数的值越来越大,甚至高于真实值。原因如下:假设有随机变量 X 1 X_1 X1, X 2 X_2 X2,取 m a x max max之后的期望大于等于先取期望再取 m a x max max操作, E [ max ⁡ ( X 1 , X 2 ) ] ≥ max ⁡ ( E [ X 1 ] , E [ X 2 ] ) \mathbb{E}[\max(X_1,X_2)]\geq\max(\mathbb{E}[X_1],\mathbb{E}[X_2]) E[max(X1,X2)]max(E[X1],E[X2])𝑄 函数的值被视作在状态 𝑠′,动作 𝑎′ 下的回报期望值。于是有 max ⁡ a ′ ∈ A Q θ ′ ( s t + 1 , a ′ ) = Q θ ′ ( s t + 1 , arg ⁡ max ⁡ a ′ Q θ ′ ( s t + 1 , a ′ ) ) = E [ R ∣ s t + 1 , arg ⁡ max ⁡ a ′ Q θ ′ ( s t + 1 , a ′ ) , θ ′ ] ≥ max ⁡ ( E [ R ∣ s t + 1 , a 1 , θ ′ ] , E [ R ∣ s t + 1 , a 2 , θ ′ ] , ⋯   ) , a i ∈ A \begin{aligned} \max_{a'\in A}Q_{\theta'}(s_{t+1},a')& =Q_{\theta^\prime}(s_{t+1},\arg\max_{a^\prime}Q_{\theta^\prime}(s_{t+1},a^\prime)) \\ &=\mathbb{E}[R|s_{t+1},\arg\max_{a^{\prime}}Q_{\theta^{\prime}}(s_{t+1},a^{\prime}),\theta^{\prime}] \\ &\geq\max(\mathbb{E}[R|s_{t+1},a_1,\theta^{\prime}],\mathbb{E}[R|s_{t+1},a_2,\theta^{\prime}],\cdots),a_i\in A \end{aligned} aAmaxQθ(st+1,a)=Qθ(st+1,argamaxQθ(st+1,a))=E[Rst+1,argamaxQθ(st+1,a),θ]max(E[Rst+1,a1,θ],E[Rst+1,a2,θ],),aiA
举个例子:在这里插入图片描述
如图所示,x轴为状态,有10个候选行动;左列紫线是真实价值函数,绿点是训练数据点,绿线是拟合的价值函数。中间黑色虚线是取最大值后的估计。可以看到由于数据点的分布,使得拟合出的价值函数无法与真实的价值函数吻合,甚至在部分区域产生了明显的偏差。’

此外,Q函数的过高估计程度随着候选行动数量增大变得更严重.下图中𝑄′函数是另一组独立训练的价值函数。可以看到红色部分的误差明显大于蓝色。
在这里插入图片描述

Double DQN

为了解决这一问题,Double DQN 算法提出利用两个独立训练的神经网络估算 max ⁡ a ′ Q ∗ ( s ′ , a ′ ) \max_{a^{\prime}}Q_{*}\left(s^{\prime},a^{\prime}\right) maxaQ(s,a)。具体做法是将原有的 Q ω − ( s ′ , arg ⁡ max ⁡ a ′ Q ω − ( s ′ , a ′ ) ) Q_{\omega^-}\left(s',\arg\max_{a'}Q_{\omega^-}\left(s',a'\right)\right) Qω(s,argmaxaQω(s,a))更改为 Q ω − ( s ′ , arg ⁡ max ⁡ a ′ Q ω ( s ′ , a ′ ) ) Q_{\omega^-}\left(s',\arg\max_{a'}Q_{\omega}\left(s',a'\right)\right) Qω(s,argmaxaQω(s,a)),即利用一套神经网络 Q ω Q_{\omega} Qω的输出选取价值最大的动作,但在使用该动作的价值时,用另一套神经网络 Q ω − Q_{\omega^-} Qω计算该动作的价值。这样,即使其中一套神经网络的某个动作存在比较严重的过高估计问题,由于另一套神经网络的存在,这个动作最终使用的值不会存在很大的过高估计问题。

在传统的 DQN 算法中,本来就存在两套 Q Q Q函数的神经网络——目标网络和训练网络,只不过 Q ω − ( s ′ , arg ⁡ max ⁡ a ′ Q ω − ( s ′ , a ′ ) ) Q_{\omega^-}\left(s',\arg\max_{a'}Q_{\omega^-}\left(s',a'\right)\right) Qω(s,argmaxaQω(s,a))的计算只用到了其中的目标网络,那么我们恰好可以直接将训练网络作为 Double DQN 算法中的第一套神经网络来选取动作,将目标网络作为第二套神经网络计算值,这便是 Double DQN 的主要思想。由于在 DQN 算法中将训练网络的参数记为 ω \omega ω,将目标网络的参数记为 ω − \omega^- ω,这与本节中 Double DQN 的两套神经网络的参数是统一的,因此,我们可以直接写出如下 Double DQN 的优化目标: r + γ Q ω − ( s ′ , arg ⁡ max ⁡ a ′ Q ω ( s ′ , a ′ ) ) r+\gamma Q_{\omega^-}\left(s^{\prime},\arg\max_{a^{\prime}}Q_{\omega}\left(s^{\prime},a^{\prime}\right)\right) r+γQω(s,argamaxQω(s,a))

Double DQN代码实践

Pendulum环境

Pendulum环境文档
本节采用的环境是倒立摆(Inverted Pendulum),该环境下有一个处于随机位置的倒立摆。环境的状态包括倒立摆角度的正弦值 s i n θ sin\theta sinθ,余弦值 c o s θ cos\theta cosθ,角速度 θ ˙ \dot\theta θ˙;动作为对倒立摆施加的力矩 τ \tau τ.每一步都会根据当前倒立摆的状态的好坏给予智能体不同的奖励,该环境的奖励函数为 r = − ( t h e t a 2 + 0.1 ∗ t h e t a d t 2 + 0.001 ∗ t o r q u e 2 ) r = -(theta^2 + 0.1 * theta_dt^2 + 0.001 * torque^2) r=(theta2+0.1thetadt2+0.001torque2).倒立摆向上保持直立不动时奖励为 0,倒立摆在其他位置时奖励为负数(最大回报为零,倒立摆是垂直的,速度为零,没有施加扭矩)。环境本身没有终止状态,运行 200 步后游戏自动结束。
在这里插入图片描述
Pendulum环境的状态空间

标号名称最小值最大值
0x = cos(theta)-1.01.0
1y = sin(theta)-1.01.0
2Angular Velocity-8.08.0

Pendulum环境的动作空间

标号名称最小值最大值
0力矩-2.02.0

力矩大小是在 [ − 2 , 2 ] [-2,2] [2,2]范围内的连续值。为了能够应用 DQN,我们需要采用离散化动作的技巧。例如,下面的代码将连续的动作空间离散为 11 个动作。动作 [ 0 , 1 , 2 , … , 9 , 10 ] [0,1,2,\dots,9,10] [0,1,2,,9,10]分别代表力矩为 [ − 2 , − 1.6 , − 1.2 , … , 1.2 , 1.6 , 2.0 ] [-2,-1.6,-1.2,\dots,1.2,1.6,2.0] [2,1.6,1.2,,1.2,1.6,2.0]

代码

PS:
torch.max()[0], 只返回最大值的每个数
troch.max()[1], 只返回最大值的每个索引

class DQN:
    ''' DQN算法 '''
    def __init__(self, state_dim, hidden_dim, action_dim, learning_rate, gamma,
                 epsilon, target_update_rate, device, numOfEpisodes, env,
                 buffer_size, minimal_size, batch_size, DQNtype="DQN"):
        self.action_dim = action_dim
        # Q网络
        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)
        # 使用Adam优化器
        self.optimizer = torch.optim.Adam(self.q_net.parameters(),
                                          lr=learning_rate)
        self.gamma = gamma
        self.epsilon = epsilon
        # 目标网络更新频率
        self.target_update_rate = target_update_rate
        # 计数器,记录更新次数
        self.count = 0
        self.device = device
        self.numOfEpisodes = numOfEpisodes
        self.env = env
        self.buffer_size = buffer_size
        self.minimal_size = minimal_size
        self.batch_size = batch_size
        self.DQNtype = DQNtype

    # Choose A from S using policy derived from Q (e.g., epsilon-greedy)
    def ChooseAction(self, state):
        if np.random.random() < self.epsilon:
            action = np.random.randint(self.action_dim)
        else:
            state = torch.tensor(np.array([state]), dtype=torch.float).to(self.device)
            action = self.q_net(state).argmax().item()
        return action

    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)
        terminateds = torch.tensor(transition_dict['terminateds'],
                                   dtype=torch.float).view(-1, 1).to(self.device)
        truncateds = torch.tensor(transition_dict['truncateds'],
                                   dtype=torch.float).view(-1, 1).to(self.device)
        #Q值
        q_values = self.q_net(states).gather(1, actions)
        # 下个状态的最大Q值
        if self.DQNtype == "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: # DQN的情况
            max_next_q_values = self.target_q_net(next_states).max(1)[0].view(-1, 1)
        # TD误差目标
        q_targets = rewards + self.gamma * max_next_q_values * (1 - terminateds + truncateds)
        # 均方误差损失函数
        dqn_loss = torch.mean(F.mse_loss(q_values, q_targets))
        # PyTorch中默认梯度会累积,这里需要显式将梯度置为0
        self.optimizer.zero_grad()
        # 反向传播更新参数
        dqn_loss.backward()
        self.optimizer.step()

        if self.count % self.target_update_rate == 0:
            self.target_q_net.load_state_dict(
                self.q_net.state_dict())  # 更新目标网络
        self.count += 1

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

    # 离散动作转回连续的函数
    def dis_to_con(self, discrete_action, env, action_dim):
        action_lowbound = env.action_space.low[0]
        actoin_upbound = env.action_space.high[0]
        return action_lowbound + (discrete_action / (action_dim - 1)) \
                               * (actoin_upbound - action_lowbound)

    def DQNtrain(self):
        replay_buffer = util.ReplayBuffer(self.buffer_size)
        returnList = []
        max_q_valueList = []
        max_q_value = 0
        for i in range(10):
            with tqdm(total=int(self.numOfEpisodes / 10), desc='Iteration %d' % i) as pbar:
                for episode in range(int(self.numOfEpisodes / 10)):
                    # initialize state
                    state, info = self.env.reset()
                    terminated = False
                    truncated = False
                    episodeReward = 0
                    # Loop for each step of episode:
                    while (not terminated) or (not truncated):
                        action = self.ChooseAction(state)
                        # 平滑处理
                        max_q_value = self.max_q_value(state) * 0.005 + max_q_value * 0.995
                        max_q_valueList.append(max_q_value)
                        action_continuous = self.dis_to_con(action, self.env, self.action_dim)
                        next_state, reward, terminated, truncated, info = self.env.step([action_continuous])
                        replay_buffer.add(state, action, reward, next_state, terminated, truncated)
                        if terminated or truncated:
                            break
                        state = next_state
                        episodeReward += reward
                        # 当buffer数据的数量超过一定值后,才进行Q网络训练
                        if replay_buffer.size() > self.minimal_size:
                            b_s, b_a, b_r, b_ns, b_te, b_tr = replay_buffer.sample(self.batch_size)
                            transition_dict = {
                                'states': b_s,
                                'actions': b_a,
                                'next_states': b_ns,
                                'rewards': b_r,
                                'terminateds': b_te,
                                'truncateds': b_tr
                            }
                            self.Update(transition_dict)
                    returnList.append(episodeReward)
                    if (episode + 1) % 10 == 0:  # 每10条序列打印一下这10条序列的平均回报
                        pbar.set_postfix({
                            'episode':
                                '%d' % (self.numOfEpisodes / 10 * i + episode + 1),
                            'return':
                                '%.3f' % np.mean(returnList[-10:])
                        })
                    pbar.update(1)
        return returnList, max_q_valueList

结果

在这里插入图片描述
在这里插入图片描述
如图所示,DQN相比DoubleDQN能够获得更多的回报。
在这里插入图片描述
如图所示,DQN算法会产生过高估计(超越黄色虚线0),甚至部分Q值超过了10。而DoubleDQN算法则能有效缓解过高估计这一问题。

Dueling DQN

Dueling DQN 是 DQN 另一种的改进算法,它在传统 DQN 的基础上只进行了微小的改动,但却能大幅提升 DQN 的表现。在强化学习中,我们将状态动作价值函数 Q Q Q减去状态价值函数 V V V的结果定义为优势函数 A A A,即 A ( s , a ) = Q ( s , a ) − V ( s ) A(s,a)=Q(s,a)-V(s) A(s,a)=Q(s,a)V(s).在同一个状态下,所有动作的优势值之和为 0,因为所有动作的动作价值的期望就是这个状态的状态价值。据此,在 Dueling DQN 中,Q 网络被建模为: Q η , α , β ( s , a ) = V η , α ( s ) + A η , β ( s , a ) Q_{\eta,\alpha,\beta}(s,a)=V_{\eta,\alpha}(s)+A_{\eta,\beta}(s,a) Qη,α,β(s,a)=Vη,α(s)+Aη,β(s,a)
其中, V η , α ( s ) V_{\eta,\alpha}(s) Vη,α(s)为状态价值函数,而 A η , β ( s , a ) A_{\eta,\beta}(s,a) Aη,β(s,a)则为该状态下采取不同动作的优势函数,表示采取不同动作的差异性; η \eta η是状态价值函数和优势函数共享的网络参数,一般用在神经网络中,用来提取特征的前几层;而 α , β \alpha,\beta α,β分别为状态价值函数和优势函数的参数。

不同的Advantage聚合形式:

  • Q η , α , β ( s , a ) = V η , α ( s ) + A η , β ( s , a ) − max ⁡ a ′ A η , β ( s , a ′ ) Q_{\eta,\alpha,\beta}(s,a)=V_{\eta,\alpha}(s)+A_{\eta,\beta}(s,a)-\max_{a'}A_{\eta,\beta}\left(s,a'\right) Qη,α,β(s,a)=Vη,α(s)+Aη,β(s,a)amaxAη,β(s,a)
  • Q η , α , β ( s , a ) = V η , α ( s ) + A η , β ( s , a ) − 1 ∣ A ∣ ∑ a ′ A η , β ( s , a ′ ) Q_{\eta,\alpha,\beta}(s,a)=V_{\eta,\alpha}(s)+A_{\eta,\beta}(s,a)-\frac{1}{|\mathcal{A}|}\sum_{a'}A_{\eta,\beta}\left(s,a'\right) Qη,α,β(s,a)=Vη,α(s)+Aη,β(s,a)A1aAη,β(s,a)

Dueling DQN网络结构

在这里插入图片描述

Dueling DQN优点

  • 能够处理与动作关联较小的状态在这里插入图片描述
  • Dueling DQN 能更高效学习状态价值函数。每一次更新时, V V V函数都会被更新,这也会影响到其他动作的 Q Q Q值。而传统的 DQN 只会更新某个动作的 Q Q Q值,其他动作的 Q Q Q值就不会更新。因此,Dueling DQN 能够更加频繁、准确地学习状态价值函数。

Dueling DQN 代码实践

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 = A + V - A.mean(1).view(-1, 1)
        return Q

class DQN:
    ''' DQN算法 '''
    def __init__(self, state_dim, hidden_dim, action_dim, learning_rate, gamma,
                 epsilon, target_update_rate, device, numOfEpisodes, env,
                 buffer_size, minimal_size, batch_size, DQNtype="DQN"):
        self.action_dim = action_dim
        if DQNtype == "DuelingDQN":
            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:
            # Q网络
            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)
        # 使用Adam优化器
        self.optimizer = torch.optim.Adam(self.q_net.parameters(),
                                          lr=learning_rate)
        self.gamma = gamma
        self.epsilon = epsilon
        # 目标网络更新频率
        self.target_update_rate = target_update_rate
        # 计数器,记录更新次数
        self.count = 0
        self.device = device
        self.numOfEpisodes = numOfEpisodes
        self.env = env
        self.buffer_size = buffer_size
        self.minimal_size = minimal_size
        self.batch_size = batch_size
        self.DQNtype = DQNtype

结果

在这里插入图片描述
同样,DQN相比Dueling DQN能够获得更多的回报。
在这里插入图片描述
Dueling DQN可以缓解过高估计这一问题。

参考

[1] 伯禹AI
[2] https://www.davidsilver.uk/teaching/
[3] 动手学强化学习
[4] Reinforcement Learning


http://www.niftyadmin.cn/n/5134661.html

相关文章

MySql数据库问题

1、MySQL 连接出现 Authentication plugin ‘caching_sha2_password‘ cannot be loaded 翻译&#xff1a;MySQL 连接出现 无法加载身份验证插件“caching_sha2_password” ————————————————————————————————————————— 原因分析…

超声波清洗机清洗原理是什么、热门超声波清洗机品牌推荐

为什么要选择超声波清洗机呢&#xff1f;因为像剃须刀的剃头位置不好清理的时候&#xff0c;超声波清洗机就可以360度无死角的给你清理&#xff0c;而且清理的会很干净&#xff0c;相比人手的清洗速度&#xff0c;机器的清洗也会更加快一些&#xff0c;超声波清洗机不单单只能清…

CDH大数据平台 28Cloudera Manager Console之superset相关包安装(markdown新版二)

💖个人主页:@与自己作战 💯作者简介:CSDN@博客专家、CSDN@大数据领域优质创作者、CSDN@内容合伙人、阿里云@专家博主 🆘希望大佬们多多支持,携手共进 📝 如果文章对你有帮助的话,欢迎评论💬点赞👍收藏📂加关注 ⛔如需要支持请私信我,💯必支持 文章目录 一…

MySQL篇---第七篇

系列文章目录 文章目录 系列文章目录一、说说 InnoDB 与 MyISAM 有什么区别?二、MySQL 索引类型有哪些?三、什么时候不要使用索引?一、说说 InnoDB 与 MyISAM 有什么区别? 在 MySQL 5.1 及之前的版本中,MyISAM 是默认的存储引擎,而在 MySQL 5.5 版本以后,默 认使用 Inn…

如何提升ERP的实施成功率?这5个阶段的重点要注意!

目录 花了70%预算的ERP系统&#xff0c; 只有30%的概率实施成功可不行&#xff01; 鼎捷专家解惑&#xff1a;ERP实施别踩雷&#xff01; ERP实施分阶段&#xff0c;重点清晰才好办 01 项目启动阶段工作重点 02 机制流程规划阶段工作重点 03 流程和数据验证重点 04 实施…

fopen()返回值

函数 fopen() 在成功打开文件时会返回一个指向 FILE 结构的指针&#xff0c;该指针用于后续对文件进行读写操作。在打开文件失败时&#xff0c;fopen() 返回 NULL 指针。 下面是一个示例代码&#xff0c;展示了如何使用 fopen() 函数并检查其返回值&#xff1a; #include <…

【C++杂货铺】一文总结C++11新特性:右值引用 | 移动语义 | 完美转发

文章目录 一、左值引用和右值引用二、什么是左值&#xff1f;什么是左值引用&#xff1f;三、什么是右值&#xff1f;什么是右值引用&#xff1f;四、左值引用与右值引用的比较4.1 左值引用总结4.2 右值引用总结 五、左值引用的使用场景和意义六、右值引用的使用场景和意义七、…

[移动通讯]【Carrier Aggregation-8】【 E-UTRAN aspects】

参考&#xff1a; 《Understanding Carrier Aggregation》 目录&#xff1a; Impact of Carrier aggregation on signaling aspects Transport (MAC) layer aspects Carrier activation/deactivation and Discontinuous Reception DRX Cross-Carrier scheduling 一 …