《动手学强化学习》笔记7_DQN算法

目录

7.1简介

7.2DQN原理

7.2.1经验回放

7.2.2目标网络

7.3DQN代码实现


7.1简介

在Q-learning 算法中,我们以矩阵的方式建立了一张存储每个状态下所有动作Q值的表格。表格中的每一个动作价值Q(s,a)表示在状态s下选择动作a然后继续遵循某一策略预期能够得到的期望回报。然而,这种用表格存储动作价值的做法只在环境的状态和动作都是离散的,并且空间都比较小的情况下适用,我们之前进行代码实战的几个环境都是如此(如悬崖漫步)。当状态或者动作数量非常大的时候,这种做法就不适用了。例如,当状态是一张 RGB 图像时,假设图像大小是210*160*3,此时一共有256^{(210\times 60\times 3))}种状态,在计算机中存储这个数量级的Q值表格是不现实的。更甚者,当状态或者动作连续的时候,就有无限个状态动作对,我们更加无法使用这种表格形式来记录各个状态动作对的Q值。

对于这种情况,我们需要用函数拟合的方法来估计Q值,即将这个复杂的Q值表格视作数据,使用一个参数化的函数Q_{\theta }来拟合这些数据。很显然,这种函数拟合的方法存在一定的精度损失,因此被称为近似方法。我们今天要介绍的 DQN 算法便可以用来解决连续状态下离散动作的问题。

7.2DQN原理

DQN是深度神经网络与Q-learning相结合的一种基于价值的离线策略算法

DQN 体系结构主要包含:Q 网络、目标网络,以及经验回放组件。Q 网络是经过训练以生成最佳状态-动作值的 agent。经验回放单元的作用是与环境交互,生成数据以训练 Q 网络。目标网络与 Q 网络是结构相同权重不同的两网络,但在初始时是它们完全相同的

7.2.1经验回放

在一般的有监督学习中,假设训练数据是独立同分布的,我们每次训练神经网络的时候从训练数据中随机采样一个或若干个数据来进行梯度下降,随着学习的不断进行,每一个训练数据会被使用多次。在原来的 Q-learning 算法中,每一个数据只会用来更新一次值。为了更好地将 Q-learning 和深度神经网络结合,DQN 算法采用了经验回放(experience replay)方法,具体做法为维护一个回放缓冲区,将每次从环境中采样得到的四元组数据(状态、动作、奖励、下一状态)存储到回放缓冲区中(经验回放从当前状态中以贪婪策略 ε−greedy 选择一个动作,执行后从环境中获得奖励和下一步的状态。然后将此观测值另存为用于训练数据的样本),训练 Q 网络的时候再从回放缓冲区中随机采样若干数据来进行训练。这么做可以起到以下两个作用。

(1)使样本满足独立假设。在 MDP 中交互采样得到的数据本身不满足独立假设,因为这一时刻的状态和上一时刻的状态有关。非独立同分布的数据对训练神经网络有很大的影响,会使神经网络拟合到最近训练的数据上。采用经验回放可以打破样本之间的相关性,让其满足独立假设

(2)提高样本效率。每一个样本可以被使用多次,十分适合深度神经网络的梯度学习。

7.2.2目标网络

神经网络的训练是一个最优化问题,我们需要表示网络输出和标签值之间的差值,作为损失函数,目标是让损失函数最小化,手段是通过反向传播使用梯度下降的方法来更新神经网络的参数。

DQN 算法最终更新的目标是让逼近,由于 TD 误差目标本身就包含神经网络的输出,因此在更新网络参数的同时目标也在不断地改变,这非常容易造成神经网络训练的不稳定性。为了解决这一问题,DQN 便使用了目标网络(target network)的思想:既然训练过程中 Q 网络的不断更新会导致目标不断发生改变,不如暂时先将 TD 目标中的 Q 网络固定住。为了实现这一思想,我们需要利用两套 Q 网络。

这两个网络是结构相同参数不同的神经网络,区别是一个用于训练,另一个不会在短期内得到训练,这样设置是从考虑实际效果出发的必然需求。

Q 网络从每个数据样本中获取当前状态和操作,并预测该特定操作的 Q 值,这是“预测 Q 值”;目标网络从每个数据样本中获取下一个状态,并可以从该状态执行的所有操作中预测最佳 Q 值,这是“目标 Q 值”。

如果构建具有单个 Q 网络且不存在目标网络的 DQN,假设此网络应该如下工作:通过 Q 网络执行两次传递,首先输出 “预测 Q 值”,然后输出 “目标 Q 值”。这可能会产生一个潜在的问题:Q 网络的权重在每个时间步长都会更新,从而改进了对“预测 Q 值”的预测。但是,由于网络及其权重相同,因此它也改变了我们预测的“目标 Q 值”的方向。它们不会保持稳定,在每次更新后可能会波动,类似一直追逐一个移动着的目标。

通过采用第二个未经训练的网络,可以确保 “目标 Q 值” 至少在短时间内保持稳定。但这些“目标 Q 值”毕竟只是预测值,这是为改善它们的数值做出的妥协。所以在经过预先配置的时间步长后,需将 Q 网络中更新的权重复制到目标网络。

可以得出,使用目标网络可以带来更稳定的训练
                        
原文链接:https://blog.csdn.net/dgvv4/article/details/129447669

综上所述,DQN 算法的具体流程如下:

7.3DQN代码实现

首先定义经验回放池的类,主要包括加入数据、采样数据两大函数。

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)

然后定义一个只有一层隐藏层的 Q 网络。

class Qnet(torch.nn.Module):
    ''' 只有一层隐藏层的Q网络 '''
    def __init__(self, state_dim, hidden_dim, action_dim):
        super(Qnet, self).__init__()
        self.fc1 = torch.nn.Linear(state_dim, hidden_dim)
        self.fc2 = torch.nn.Linear(hidden_dim, action_dim)

    def forward(self, x):
        x = F.relu(self.fc1(x))  # 隐藏层使用ReLU激活函数
        return self.fc2(x)

有了这些基本组件之后,接来下开始实现 DQN 算法。

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

  • 18
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值