More than code

More Than Code
The efficiency of your iteration of reading, practicing and thinking decides your understanding of the world.
  1. 首页
  2. RL
  3. 正文

DQN

2025年7月27日 107点热度 0人点赞 0条评论

深度强化学习的基础,要好好看


CartPole环境中,动作只有两个,向左移动和向右移动,但是状态是无限的,因为是连续的。这种情况下使用Q learning中表格的方式来记录状态就是不太现实的。所以可以使用神经网络来做拟合。

如果动作是无限的,神经网络的输入可以是(s, a),而如果动作是离散的,则可以把当前状态输入进去,得到每一个动作的Q值。


DQN就是使用了神经网络的Q learning。
需要注意的是因为Q learning有在动作中取max的操作,所以没办法处理非离散的动作。

核心是使用神经网络去学习Q函数。在这里有两个用来优化的模块:


* 经验回放是把采样的数据保存下来用于后续训练,有点类似之前提到的Q planning。同时也是为了让样本满足独立假设。
* 目标网络是为了防止不够稳定,先冻结参数针对目标网络做训练,然后再去更新目标网络。

class DQN:
    ''' DQN算法 '''
    def __init__(self, state_dim, hidden_dim, action_dim, learning_rate, gamma,
                 epsilon, target_update, device):
        self.action_dim = action_dim
        self.q_net = Qnet(state_dim, hidden_dim,
                          self.action_dim).to(device)  # Q网络
        # 目标网络
        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  # epsilon-贪婪策略
        self.target_update = target_update  # 目标网络更新频率
        self.count = 0  # 计数器,记录更新次数
        self.device = device

    def take_action(self, state):  # epsilon-贪婪策略采取动作
        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 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)  # Q值
        # 下个状态的最大Q值
        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
                                                                )  # TD误差目标
        dqn_loss = torch.mean(F.mse_loss(q_values, q_targets))  # 均方误差损失函数
        self.optimizer.zero_grad()  # PyTorch中默认梯度会累积,这里需要显式将梯度置为0
        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

这里的一个关键是DQN的输入是state,输出的是所有action的价值。

有了这个能够处理连续状态空间的DQN之后,也可以用来处理一些视频游戏上的强化学习,输入图像,让DQN学习以图像为输入的强化学习。

DQN改进

DQN有两个简单的改进算法:double DQN和dueling DQN


* double DQN的目的是为了缓解DQN对Q值的过高估计问题。具体原因可以看中间那段解释,当神经网络出现误差的时候,会导致更新目标出现过高估计,而新的Q拟合之后,会进一步用来更新上一的状态的Q值。导致Q值的累积

所以double DQN是用两个独立的神经网络,一个用来选择最优动作,一个用来计算价值。对应到普通的DQN中,就是用训练网络来选择最优动作,而用目标网络计算价值。


Dueling DQN的核心思想则是把动作价值拆解成状态价值+动作优势,分别建模。

分别建模的好处在于,有的时候状态更加关键,而不在于动作。

有的读者可能会问:“为什么 Dueling DQN 会比 DQN 好?”部分原因在于 Dueling DQN 能更高效学习状态价值函数。每一次更新时,函数都会被更新,这也会影响到其他动作的值。而传统的 DQN 只会更新某个动作的值,其他动作的值就不会更新。因此,Dueling DQN 能够更加频繁、准确地学习状态价值函数。

在实现上还有一个特点需要注意:

因为拆解了动作价值函数后,无法保证V/A的唯一性,可能导致网络抖动。所以这里对A做了限制。

用了均值的这个式子之后,如果所有A值都减去C,就会影响Q值。

标签: 暂无
最后更新:2025年7月27日

sheep

think again

点赞
< 上一篇
下一篇 >

文章评论

取消回复

COPYRIGHT © 2021 heavensheep.xyz. ALL RIGHTS RESERVED.

THEME KRATOS MADE BY VTROIS