强化学习Part1:马尔可夫过程、DQN算法



【教程地址】https://github.com/datawhalechina/joyrl-book
【强化学习库JoyRL】https://github.com/datawhalechina/joyrl/tree/main



1.绪论

1.1试错学习and序列决策

对于任意问题,只要能够建模成序列决策问题或者带有鲜明的试错学习特征,就可以使用强化学习来解决。

试错学习(Trial-and-error learning)是一种基本的学习方法,涉及到通过重复尝试和从错误中学习来解决问题。这个过程通常包括以下几个步骤:尝试、错误、结果、学习。注意,试错学习虽然是强化学习中最鲜明的要素之一,但并不是强化学习的全部,强化学习还包含其它的学习形式例如观察学习(对应模仿学习、离线强化学习等技术)。

在学习过程中个人做出的每一次尝试都是是一次决策 ( decision ),每一次决策都会带来相应的后果。把好的结果称为奖励( reward ),坏的结果称为惩罚( punishment )或者负的奖励。最终通过一次次的决策来实现目标,这个目标通常是以最大化累积的奖励来呈现的,这个过程就是序列决策( sequential decision making )过程。

1.2强化学习应用

强化学习的典型应用主要如下:

  • 多智能体强化学习( multi-agent reinforcement learning,MARL )就是在多个智能体的环境下进行强化学习。与单智能体环境不同,在多智能体环境中通常存在非静态问题,即环境的状态不仅由智能体的动作决定,还受到其他智能体的动作的影响。其次存在信号问题,即智能体之间可能需要进行通信以合作或竞争,如何高效地通信并从信号中学习是一个难题。还存在信誉分配问题,在多智能体的合作任务中,确定每个智能体对于整体目标的贡献(或责任)是一个挑战。
  • 从数据中学习,或者从演示中学习(learn from demostration)包含丰富的门类,例如以模仿学习为代表的从专家数据中学习策略,指在奖励函数难以明确定义或者策略本身就很难学出来的情况下,我们可以通过模仿人类的行为来学习到一个较好的策略。
     逆强化学习是指通过观察人类的行为来学习到一个奖励函数,然后通过强化学习来学习一个策略。
  • 探索策略( exploration strategy )。在强化学习中,探索策略即如何在探索和利用之间做出权衡。在探索的过程中,智能体会尝试一些未知的动作,从而可能会获得更多的奖励,但同时也可能会遭受到惩罚。而在利用的过程中,智能体会选择已知的动作,从而可能会获得较少的奖励,但同时也可能会遭受较少的惩罚。
  • 实时环境( real-time environment )。在实际应用中,智能体往往需要在实时或者在线环境中进行决策,例如自动驾驶、机器人等等。在这种情况下训练不仅会降低效率(实时环境响应动作更慢),而且还会带来安全隐患(训练过程中可能会出现意外)。解决这一问题的思路之一就是离线强化学习( offline reinforcement learning ),即在离线环境中进行训练,然后将训练好的模型部署到在线环境中进行决策。
  • 世界模型( world model ),即在离线环境中训练一个世界模型,然后将世界模型部署到在线环境中进行决策。世界模型的思路是将环境分为两个部分,一个是世界模型,另一个是控制器。世界模型的作用是预测下一个状态,而控制器的作用是根据当前的状态来决策动作。这样就可以在离线环境中训练世界模型,然后将世界模型部署到在线环境中进行决策,从而避免了在线环境中的训练过程。
  • 多任务强化学习( multi-task reinforcement learning )。这个问题在深度学习中也较为常见,在实际应用中,智能体往往需要同时解决多个任务。联合训练的思路是将多个任务的奖励进行加权求和,然后通过强化学习来学习一个策略。分层强化学习的思路是将多个任务分为两个层次,一个是高层策略,另一个是低层策略。高层策略的作用是决策当前的任务,而低层策略的作用是决策当前任务的动作。

2.马尔可夫决策过程

马尔可夫过程以数学的形式来描述智能体在与环境交互的过程中学到一个目标的过程。这里智能体充当的是作出决策或动作,并且在交互过程中学习的角色,环境指的是智能体与之交互的一切外在事物,不包括智能体本身。

马尔可夫决策过程中智能体与环境的交互过程

2.1马尔可夫性质

在给定历史状态 s_0,s_1,\cdots,s_t 的情况下,某个状态的未来只与当前状态有关,与历史的状态无关。即:

P(s_{t+1}|s_t)=P(s_{t+1}|s_0,s_1,\cdots,s_t)

2.2 回报

在马尔可夫决策过程中智能体的目标是最大化累积的奖励,通常我们把这个累积的奖励称为回报(Return),用 G_{t} 表示,带折扣项的回报公式可以写成:

G_t=r_{t+1}+\gamma r_{t+2}+\gamma^2r_{t+3}+\cdots=\sum_{k=0}^{T=\infty}\gamma^kr_{t+k+1}

当前时步的回报  G_{t} 跟下一个时步 G_{t+1} 的回报是有所关联的,即

\begin{aligned} G_{t}& \dot{=}r_{t+1}+\gamma r_{t+2}+\gamma^2r_{t+3}+\gamma^3r_{t+4}+\cdots \\ &=r_{t+1}+\gamma\left(r_{t+2}+\gamma r_{t+3}+\gamma^2r_{t+4}+\cdots\right) \\ &=r_{t+1}+\gamma G_{t+1} \end{aligned}

2.3状态转移矩阵

表示当前时步的状态是s,在下一个时步切换到s'的概率,把这个概率称为状态转移概率(State Transition Probability)。拓展到所有状态我们可以表示为式:

P_{ss^{\prime}}=P(S_{t+1}=s^{\prime}|S_t=s)

状态转移矩阵(State Transition Matrix)可表示为:

P_{ss^{\prime}}=\begin{pmatrix}p_{11}&p_{12}&\cdots&p_{1n}\\p_{21}&p_{22}&\cdots&p_{2n}\\\vdots&\vdots&\ddots&\vdots\\p_{n1}&p_{n2}&\cdots&p_{nn}\end{pmatrix}

对于同一个状态所有状态转移概率加起来是等于1的。在马尔可夫链(马尔可夫过程)的基础上增加奖励元素就会形成马尔可夫奖励过程,在马尔可夫奖励过程基础上增加动作的元素就会形成马尔可夫决策过程,也就是强化学习的基本问题模型之一。

马尔可夫决策过程可以用一个五元组 <S,A,R,P,\gamma> 来表示。其中S表示状态空间,即所有状态的集合,A表示动作空间,R表示奖励函数,P表示状态转移矩阵,\gamma表示折扣因子。


3.动态规划

动态规划具体指的是在某些复杂问题中,将问题转化为若干个子问题,并在求解每个子问题的过程中保存已经求解的结果,以便后续使用。强化学习中,动态规划被用于求解值函数和最优策略。常见的动态规划算法包括值迭代(value iteration, VI)、策略迭代(policy iteration, PI)和 Q-learning 算法等。

动态规划的解法主要有几个步骤:确定状态,写出状态转移方程和寻找边界条件。动态规划问题有三个性质,最优化原理、无后效性和有重叠子问题。其中有重叠子问题不是动态规划问题的必要条件。无后效性指的是即某阶段状态一旦确定,就不受这个状态以后决策的影响。这其实就是前面所说的马尔可夫性质。而最优化原理是指,如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。可联系上节的回报公式进行理解。

3.1价值函数

状态价值函数:在马尔可夫决策过程中,每个状态价值的定义为

\begin{aligned} V_{\pi}(s)& =\mathbb{E}_\pi[R_t+\gamma R_{t+1}+\gamma^2R_{t+2}+\cdots|S_t=s] \\ &=\mathbb{E}_\pi[G_t|S_t=s] \end{aligned}

动作价值函数:引入动作的元素后会有函数

Q_\pi(s,a)=\mathbb{E}_\pi\left[G_t\mid s_t=s,a_t=a\right]

二者之间存在关系:

V_\pi(s)=\sum_{a\in A}\pi(a\mid s)Q_\pi(s,a)

一般指在状态下执行动作的概率分布。该式表示在给定状态的情况下,智能体所有动作的价值期望(所有动作价值函数乘以对应动作的概率之和)就等于该状态的价值。

3.2贝尔曼方程

在满足动态规划的最优化原理情况下,状态价值函数和动作价值函数可以将前后两个状态之间联系起来,递归地解决问题。类比G_t=R_{t+1}+\gamma G_{t+1}

\begin{aligned} V_{\pi}(s)& =\mathbb{E}_\pi\left[G_t\mid S_t=s\right] \\ &=\mathbb{E}_\pi\left[R_{t+1}+\gamma R_{t+2}+\gamma^2R_{t+3}+\cdots\mid S_t=s\right] \\ &=R(s)+\gamma\mathbb{E}\left[G_{t+1}\mid S_t=s\right] \\ &=R(s)+\gamma\mathbb{E}\left[V_{\pi}\left(s_{t+1}\right)\mid S_{t}=s\right] \\ &=R(s)+\gamma\sum_{s^{\prime}\in S}P\left(S_{t+1}=s^{\prime}\mid S_t=s\right)V_\pi\left(s^{\prime}\right) \\ &=R(s)+\gamma\sum_{s^{\prime}\in S}p\left(s^{\prime}\mid s\right)V_{\pi}\left(s^{\prime}\right) \end{aligned}

最优策略下的状态价值函数可以表示为:

\begin{gathered} V^{*}(s) =\max_a\mathbb{E}\left[R_{t+1}+\gamma V^*\left(S_{t+1}\right)\mid S_t=s,A_t=a\right] \\ =\max_a\sum_{s^{\prime},r}p(s^{\prime},r|s,a)[r+\gamma V^*(s^{\prime})] \end{gathered}

同理

Q_\pi(s,a)=R(s,a)+\gamma\sum_{s^{\prime}\in S}p\left(s^{\prime}\mid s,a\right)\sum_{a^{\prime}\in A}\pi\left(a^{\prime}\mid s^{\prime}\right)Q_\pi\left(s^{\prime},a^{\prime}\right)

最优策略下的动作价值函数可以表示为:

\begin{gathered} Q^*(s,a) =\mathbb{E}\left[R_{t+1}+\gamma\max_{a^{\prime}}Q^*\left(S_{t+1},a^{\prime}\right)\mid S_t=s,A_t=a\right] \\ =\sum_{s^{\prime},r}p\left(s^{\prime},r\mid s,a\right)\left[r+\gamma\max_{a^{\prime}}Q^*\left(s^{\prime},a^{\prime}\right)\right] \end{gathered}


4.免模型预测

两种免模型预测方法,蒙特卡洛方法( Monte Carlo,MC )和时序差分方法(temporal-difference,TD)。

4.1有模型和免模型、预测和控制

状态转移概率是已知的,这种情况下使用的算法称之为有模型算法,尝试先学习一个环境模型,它可以是环境的动态(例如,给定一个状态和一个动作,预测下一个状态)或奖励(给定一个状态和一个动作,预测奖励),智能体可以使用它来计划最佳的行动策略。大部分情况下对于智能体来说,环境是未知的,这种情况下的算法就称之为免模型算法,直接学习在特定状态下执行特定动作的价值或优化策略。它直接从与环境的交互中学习,不需要建立任何预测环境动态的模型。

预测的主要目的是估计或计算环境中的某种期望值,比如状态价值函数或动作价值函数。控制的目标则是找到一个最优策略,该策略可以最大化期望的回报。在实际应用中,预测和控制问题经常交织在一起。

4.2蒙特卡洛估计

蒙特卡洛方法的思路是我们可以采样大量的轨迹,对于每个轨迹计算对应状态的回报然后取平均近似,称之为经验平均回报( empirical mean return )。根据大数定律,只要采样的轨迹数量足够多,计算出的经验平均回报就能趋近于实际的状态价值函数。更新公式可表示为式:

V(s_t)\leftarrow V(s_t)+\alpha[G_t-V(s_{t+1})]

蒙特卡洛方法主要分成两种算法,一种是首次访问蒙特卡洛(first-visit Monte Carlo,FVMC)方法,另外一种是每次访问蒙特卡洛(every-visit Monte Carlo,EVMC)方法。FVMC 方法主要包含两个步骤,首先是产生一个回合的完整轨迹,然后遍历轨迹计算每个状态的回报。注意,只在第一次遍历到某个状态时会记录并计算对应的回报,而在 EVMC 方法中不会忽略统一状态的多个回报。

FVMC 是一种基于回合的增量式方法,具有无偏性和收敛快的优点,但是在状态空间较大的情况下,依然需要训练很多个回合才能达到稳定的结果。而 EVMC 则是更为精确的预测方法,但是计算的成本相对也更高。

4.3时序差分估计方法

时序差分估计方法结合了蒙特卡洛和动态规划的思想,是一种基于经验的动态规划方法。单步时序差分( one-step TD ),即 TD(0)。在这个更新过程中使用了当前奖励和后继状态的估计,同时将下一状态的值函数作为现有状态值函数的一部分估计来更新现有状态的值函数。

利用n步得到的回报来更新状态的价值,调整 n步就是 n步时序差分(n-step TD)。n为无穷时过渡到蒙特卡罗方法。

5.免模型控制

两种基础的免模型算法,Q-learning 和 Sarsa ,都是基于时序差分的方法。

5.1Q-learning 算法

为了解决控制问题,我们只需要直接预测动作价值函数,然后在决策时选择动作价值即 Q 值最大对应的动作即可。算法更新公式:

Q(s_t,a_t)\leftarrow Q(s_t,a_t)+\alpha[r_t+\gamma\max_aQ(s_{t+1},a)-Q(s_t,a_t)]

5.2Sarsa 算法

Sarsa 算法是直接用下一个状态和动作对应的 Q 值来作为估计值的,而 Q-learning 算法则是用下一个状态对应的最大 Q 值。

Q(s_t,a_t)\leftarrow Q(s_t,a_t)+\alpha[r_t+\gamma Q(s_{t+1},a_{t+1})-Q(s_t,a_t)]

尽管 Q-learning 算法和 Sarsa 算法仅在一行更新公式上有所区别,但这两种算法代表的是截然不同的两类算法。我们注意到,Sarsa 算法在训练的过程中当前策略来生成数据样本,并在其基础上进行更新。换句话说,策略评估和策略改进过程是基于相同的策略完成的,这就是同策略算法。相应地,像 Q-learning 算法这样从其他策略中获取样本然后利用它们来更新目标策略,我们称作异策略算法。也就是说,异策略算法基本上是从经验池或者历史数据中进行学习的。

6.DQN算法

DQN 算法,英文全称 Deep Q-Network , 它的主要贡献就是在 Q-learning 算法的基础上引入了深度神经网络来近似动作价值函数,从而能够处理高维的状态空间。

DQN算法的关键特点包括:

1. 深度神经网络:DQN使用深度神经网络作为函数逼近器来近似Q函数,这允许算法处理高维的状态空间。

2. 经验回放(Experience Replay):DQN算法维护一个回放缓冲区(replay buffer),用于存储智能体的经验(即状态转换样本)。在训练过程中,智能体会从这个缓冲区中随机抽取一批样本进行学习。这有助于打破样本间的时间相关性,提高学习的稳定性和效率。

3. 目标网络(Target Network):为了提高学习的稳定性,DQN算法使用两个结构相同但参数不同的神经网络:一个是用于预测Q值的网络(称为在线网络),另一个是用于计算目标Q值的网络(称为目标网络)。目标网络的参数会定期(而非每步)更新,这有助于稳定训练过程。

4. Q学习更新:DQN的训练过程基于Q学习的更新规则。算法会计算预测的Q值和目标Q值之间的均方误差,并通过梯度下降法更新在线网络的参数。

DQN算法解决了传统强化学习在面对高维状态空间时的困难,并展示了深度学习在强化学习领域的潜力。此后,基于DQN的许多变种和改进算法被相继提出,进一步提升了性能并扩展了应用范围。

6.1伪代码

6.2DQN算法实战
 

定义模型

import torch.nn as nn
import torch.nn.functional as F

class MLP(nn.Module):
    def __init__(self, state_dim, action_dim, hidden_dim=128):
        """初始化Q网络,这是一个全连接神经网络
        Args:
            state_dim (int): 状态空间的维度,即输入层的节点数
            action_dim (int): 动作空间的维度,即输出层的节点数
            hidden_dim (int): 隐藏层的节点数,默认为128
        """
        super(MLP, self).__init__()
        self.fc1 = nn.Linear(state_dim, hidden_dim)  # 第一层:输入层到隐藏层
        self.fc2 = nn.Linear(hidden_dim, hidden_dim)  # 第二层:隐藏层到隐藏层
        self.fc3 = nn.Linear(hidden_dim, action_dim)  # 第三层:隐藏层到输出层

    def forward(self, x):
        """定义前向传播的过程
        Args:
            x (tensor): 输入状态
        Returns:
            tensor: 对应每个动作的Q值
        """
        x = F.relu(self.fc1(x))  # 第一层的输出,并使用ReLU激活函数
        x = F.relu(self.fc2(x))  # 第二层的输出,并使用ReLU激活函数
        return self.fc3(x)  # 第三层的输出,直接输出每个动作的Q值

定义经验回放

from collections import deque
import random

class ReplayBuffer(object):
    def __init__(self, capacity: int) -> None:
        """初始化经验回放缓冲区
        Args:
            capacity (int): 缓冲区的容量
        """
        self.capacity = capacity  # 缓冲区容量
        self.buffer = deque(maxlen=self.capacity)  # 使用双端队列存储经验,当达到最大容量时自动丢弃旧的经验

    def push(self, transitions):
        """存储transition(状态转换)到经验回放缓冲区中
        Args:
            transitions: 状态转换,通常是(state, action, reward, next_state, done)的元组
        """
        self.buffer.append(transitions)

    def sample(self, batch_size: int, sequential: bool = False):
        """从经验回放缓冲区中抽取一批经验
        Args:
            batch_size (int): 抽取的批量大小
            sequential (bool): 是否顺序抽取,如果为True,则顺序抽取,否则随机抽取,默认为False
        Returns:
            zip(*batch): 解压缩后的经验批量,通常包括states, actions, rewards, next_states, dones
        """
        if batch_size > len(self.buffer):  # 如果请求的批量大小大于缓冲区中的经验数量,则调整批量大小
            batch_size = len(self.buffer)
        if sequential:  # 顺序采样
            rand = random.randint(0, len(self.buffer) - batch_size)
            batch = [self.buffer[i] for i in range(rand, rand + batch_size)]
            return zip(*batch)
        else:  # 随机采样
            batch = random.sample(self.buffer, batch_size)
            return zip(*batch)

    def clear(self):
        """清空经验回放缓冲区
        """
        self.buffer.clear()

    def __len__(self):
        """返回缓冲区中当前存储的经验数量
        Returns:
            int: 缓冲区中的经验数量
        """
        return len(self.buffer)

定义算法

import torch
import torch.optim as optim
import math
import numpy as np

class DQN:
    def __init__(self, model, memory, cfg):
        """初始化DQN智能体
        Args:
            model (nn.Module): 神经网络模型,用于近似Q函数
            memory (ReplayBuffer): 经验回放缓冲区
            cfg: 配置参数
        """
        self.action_dim = cfg.action_dim  # 动作空间的维度
        self.device = torch.device(cfg.device)  # 计算设备(CPU或GPU)
        self.gamma = cfg.gamma  # 奖励的折扣因子
        # e-greedy策略相关参数
        self.sample_count = 0  # 用于epsilon衰减的采样计数
        self.epsilon_start = cfg.epsilon_start  # epsilon初始值
        self.epsilon_end = cfg.epsilon_end  # epsilon最终值
        self.epsilon_decay = cfg.epsilon_decay  # epsilon衰减速度
        self.batch_size = cfg.batch_size  # 训练批次大小
        self.policy_net = model.to(self.device)  # 策略网络
        self.target_net = model.to(self.device)  # 目标网络
        # 复制策略网络参数到目标网络
        for target_param, param in zip(self.target_net.parameters(), self.policy_net.parameters()): 
            target_param.data.copy_(param.data)
        self.optimizer = optim.Adam(self.policy_net.parameters(), lr=cfg.lr)  # 定义优化器
        self.memory = memory  # 经验回放缓冲区

    def sample_action(self, state):
        """根据当前状态采样动作
        Args:
            state: 当前状态
        Returns:
            action (int): 选择的动作
        """
        self.sample_count += 1
        # 计算epsilon,实现epsilon-greedy策略
        self.epsilon = self.epsilon_end + (self.epsilon_start - self.epsilon_end) * \
            math.exp(-1. * self.sample_count / self.epsilon_decay)
        if random.random() > self.epsilon:
            with torch.no_grad():
                state = torch.tensor(state, device=self.device, dtype=torch.float32).unsqueeze(dim=0)
                q_values = self.policy_net(state)
                action = q_values.max(1)[1].item()  # 选择具有最大Q值的动作
        else:
            action = random.randrange(self.action_dim)  # 随机选择动作
        return action

    @torch.no_grad()  # 不计算梯度,加速和节省内存
    def predict_action(self, state):
        """根据当前状态预测动作(通常用于测试)
        Args:
            state: 当前状态
        Returns:
            action (int): 预测的动作
        """
        state = torch.tensor(state, device=self.device, dtype=torch.float32).unsqueeze(dim=0)
        q_values = self.policy_net(state)
        action = q_values.max(1)[1].item()  # 选择具有最大Q值的动作
        return action

    def update(self):
        """更新智能体的策略(Q函数)
        """
        if len(self.memory) < self.batch_size:  # 如果经验回放中的样本不足一个批次,则不进行更新
            return
        # 从经验回放中随机采样一个批次的转移(transition)
        state_batch, action_batch, reward_batch, next_state_batch, done_batch = self.memory.sample(self.batch_size)
        # 将numpy数组转换为tensor
        state_batch = torch.tensor(np.array(state_batch), device=self.device, dtype=torch.float)
        action_batch = torch.tensor(action_batch, device=self.device).unsqueeze(1)
        reward_batch = torch.tensor(reward_batch, device=self.device, dtype=torch.float)
        next_state_batch = torch.tensor(np.array(next_state_batch), device=self.device, dtype=torch.float)
        done_batch = torch.tensor(np.float32(done_batch), device=self.device)
        # 计算当前状态-动作对应的Q值
        q_values = self.policy_net(state_batch).gather(dim=1, index=action_batch)
        # 计算下一状态对应的最大Q值,并断开反向传播路径
        next_q_values = self.target_net(next_state_batch).max(1)[0].detach()
        # 计算期望的Q值(对于终止状态,expected_q_values等于reward)
        expected_q_values = reward_batch + self.gamma * next_q_values * (1 - done_batch)
        # 计算均方误差损失
        loss = nn.MSELoss()(q_values, expected_q_values.unsqueeze(1))
        # 优化更新模型
        self.optimizer.zero_grad()
        loss.backward()
        # 防止梯度爆炸,对梯度进行裁剪
        for param in self.policy_net.parameters():
            param.grad.data.clamp_(-1, 1)
        self.optimizer.step()

定义环境

def train(cfg, env, agent):
    """训练函数
    Args:
        cfg: 配置参数
        env: 环境
        agent: DQN智能体
    Returns:
        字典:包含所有回合的奖励
    """
    print("开始训练!")
    rewards = []  # 记录所有回合的累计奖励
    steps = []  # 记录每个回合的步数
    for i_ep in range(cfg.train_eps):  # 迭代回合
        ep_reward = 0  # 记录单个回合的奖励
        ep_step = 0  # 记录单个回合的步数
        state, info = env.reset(seed=cfg.seed)  # 重置环境,获取初始状态
        for _ in range(cfg.max_steps):  # 迭代步骤
            ep_step += 1
            action = agent.sample_action(state)  # 智能体根据当前状态采样动作
            next_state, reward, terminated, truncated, info = env.step(action)  # 执行动作,获得环境反馈
            agent.memory.push((state, action, reward, next_state, terminated))  # 将转换保存到经验回放缓冲区
            state = next_state  # 更新状态
            agent.update()  # 更新智能体(包括策略网络)
            ep_reward += reward  # 累计奖励
            if terminated:  # 如果达到终止状态,结束本回合
                break
        if (i_ep + 1) % cfg.target_update == 0:  # 按配置的频率更新目标网络
            agent.target_net.load_state_dict(agent.policy_net.state_dict())
        steps.append(ep_step)  # 记录步数
        rewards.append(ep_reward)  # 记录奖励
        if (i_ep + 1) % 10 == 0:  # 每10回合输出一次进度
            print(f"回合:{i_ep+1}/{cfg.train_eps},奖励:{ep_reward:.2f},Epsilon:{agent.epsilon:.3f}")
    print("完成训练!")
    env.close()  # 关闭环境
    return {'rewards': rewards}

def test(cfg, env, agent):
    """测试函数
    Args:
        cfg: 配置参数
        env: 环境
        agent: DQN智能体
    Returns:
        字典:包含所有回合的奖励
    """
    print("开始测试!")
    rewards = []  # 记录所有回合的累计奖励
    steps = []  # 记录每个回合的步数
    for i_ep in range(cfg.test_eps):  # 迭代回合
        ep_reward = 0  # 记录单个回合的奖励
        ep_step = 0  # 记录单个回合的步数
        state, info = env.reset(seed=cfg.seed)  # 重置环境,获取初始状态
        for _ in range(cfg.max_steps):  # 迭代步骤
            ep_step += 1
            action = agent.predict_action(state)  # 智能体根据当前状态预测动作
            next_state, reward, terminated, truncated, info = env.step(action)  # 执行动作,获得环境反馈
            state = next_state  # 更新状态
            ep_reward += reward  # 累计奖励
            if terminated:  # 如果达到终止状态,结束本回合
                break
        steps.append(ep_step)  # 记录步数
        rewards.append(ep_reward)  # 记录奖励
        print(f"回合:{i_ep+1}/{cfg.test_eps},奖励:{ep_reward:.2f}")
    print("完成测试")
    env.close()  # 关闭环境
    return {'rewards': rewards}

设置参数

import argparse
import matplotlib.pyplot as plt
import seaborn as sns

class Config:
    def __init__(self) -> None:
        # DQN算法和环境的配置参数
        self.algo_name = 'DQN'  # 算法名称
        self.env_id = 'CartPole-v1'  # OpenAI Gym环境的ID
        self.seed = 1  # 随机种子,便于结果复现,0表示不设置
        self.train_eps = 100  # 训练的总回合数
        self.test_eps = 20  # 测试的总回合数
        self.max_steps = 200  # 每个回合的最大步数,超过则游戏强制终止
        self.gamma = 0.95  # 折扣因子
        self.epsilon_start = 0.95  # e-greedy策略中初始epsilon
        self.epsilon_end = 0.01  # e-greedy策略中的终止epsilon
        self.epsilon_decay = 500  # e-greedy策略中epsilon的衰减率
        self.memory_capacity = 100000  # 经验回放池的容量
        self.hidden_dim = 256  # 神经网络的隐藏层维度
        self.batch_size = 64  # 每次训练的批量大小
        self.target_update = 4  # 目标网络的更新频率(每隔多少回合)
        self.lr = 0.0001  # 学习率
        self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")  # 使用GPU或CPU

def smooth(data, weight=0.9):  
    """用于平滑曲线,类似于Tensorboard中的smooth曲线
    Args:
        data (list): 原始数据列表
        weight (float): 平滑因子,权重越大曲线越平滑
    Returns:
        list: 平滑处理后的数据列表
    """
    last = data[0]  # 初始值
    smoothed = []  # 平滑处理后的数据
    for point in data:
        smoothed_val = last * weight + (1 - weight) * point  # 计算平滑值
        smoothed.append(smoothed_val)  # 添加到列表                    
        last = smoothed_val  # 更新last值                                
    return smoothed

def plot_rewards(rewards, cfg, tag='train'):
    """绘制奖励曲线
    Args:
        rewards (list): 奖励值列表
        cfg (Config): 配置参数
        tag (str): 标签('train'或'test'),用于区分是训练还是测试
    """
    sns.set()  # 设置绘图样式
    plt.figure()  # 创建图形实例
    plt.title(f"{tag}ing curve on {cfg.device} of {cfg.algo_name} for {cfg.env_id}")  # 设置图表标题
    plt.xlabel('episodes')  # 设置x轴标签
    plt.plot(rewards, label='rewards')  # 绘制原始奖励曲线
    plt.plot(smooth(rewards), label='smoothed')  # 绘制平滑处理后的奖励曲线
    plt.legend()  # 显示图例
    plt.show()  # 显示图形

开始训练

# 获取参数
cfg = Config() 
# 训练
env, agent = env_agent_config(cfg)
res_dic = train(cfg, env, agent)
 
plot_rewards(res_dic['rewards'], cfg, tag="train")  
# 测试
res_dic = test(cfg, env, agent)
plot_rewards(res_dic['rewards'], cfg, tag="test")  # 画出结果

6.3 DQN改进
 

Double DQN、Dueling DQN、Noisy DQN和PER DQN是DQN算法的几种改进版本,每种算法都针对原始DQN的某些限制或不足提出了改进策略。

1. Double DQN(DDQN)

**问题**:在标准的DQN中,选择和评估动作都使用相同的Q网络,这可能导致对Q值的过高估计。
**解决方案**:Double DQN通过使用两个Q网络(一个策略网络和一个目标网络)来减少估计偏差。在动作选择时使用策略网络,而在动作评估时使用目标网络。具体来说,在更新阶段,DDQN选择动作时依赖于策略网络,而在计算这个动作的目标Q值时使用目标网络。

2. Dueling DQN

**问题**:在一些状态下,动作的选择实际上并不会对环境造成太大影响。
**解决方案**:Dueling DQN将Q函数拆分为两部分:状态值函数(V)和动作优势函数(A)。这种架构允许模型分别学习在特定状态下的价值和在特定状态下选择特定动作的额外价值。这样,即使在不需要对动作做出明确区分的情况下,模型也能有效地学习状态的价值。

3. Noisy DQN

 **问题**:标准的DQN使用epsilon-greedy策略来平衡探索和利用,但这种方法有时不够高效。
 **解决方案**:Noisy DQN引入了参数噪声,使网络自适应地调整其探索策略。通过给网络的权重添加噪声,Noisy DQN能够在训练过程中自动并持续地探索环境,而不是依赖于外部的策略(如epsilon-greedy)。

4. Prioritized Experience Replay (PER) DQN
 **问题**:在标准的DQN中,经验回放池中的转移是均匀随机采样的,这意味着所有的转移都被赋予了相同的重要性。
 **解决方案**:PER DQN通过修改经验回放机制来优先考虑那些有更大的TD误差的转移。具有更大TD误差的转移被认为是更有价值的,因为它们代表了智能体可以从中学到更多东西的转移。这种方法旨在使智能体更有效地从其经验中学习。

每种改进策略都旨在解决DQN算法中的特定问题,从而提高智能体的学习效率和性能。这些方法可以单独使用,也可以结合使用,以进一步提高智能体的性能。

  • 20
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值