醋醋百科网

Good Luck To You!

从头开始绘制和编码18种RL算法(3/4)

接上文继续.......

DDPG(Actor-Critic for Continuous Actions)

深度确定性策略梯度 (DDPG) 将actor-critic的思想扩展到具有连续动作空间的环境(例如,施加特定的扭矩、设置速度)。

它巧妙地将 DQN 的思想(如重放缓冲区和目标网络)与确定性参与者策略结合起来。

核心思想是:

  • 确定性行动者:与输出动作概率的 REINFORCE/A2C/PPO 不同,DDPG 参与者网络为给定状态输出特定的确定性动作。
  • Q-Critic:批评家网络学习一个 Q 函数 Q(s, a),类似于 Q 学习,评估在状态下采取参与者选择的连续动作(a)的值。
  • Off-Policy:使用重放缓冲区来存储经验并采样小批量,从而可以从过去的数据中进行稳定的学习,类似于DQN
  • 目标网络:演员评论家采用单独的目标网络,以稳定评论家的目标值的学习。
  • 探索:由于策略是确定性的,因此需要手动添加探索,通常是在训练期间向参与者的输出动作添加噪声(例如高斯噪声Ornstein-Uhlenbeck 噪声)。

DDPG(由Fareed Khan创建)

交互过程中,Actor网络根据当前状态(s )产生确定性的动作

在环境中执行之前,会将噪声添加到此操作中进行探索。生成的转换(s, a,r,s',完成)存储在重播缓冲区中。

在训练期间,从缓冲区中抽取一批数据。

  • Critic Update:使用奖励(r)和在下一个状态(s')评估的目标参与者和目标批评家网络计算目标 Q 值(TD Target y)。主 Critic 网络计算采样状态和动作的 Q(s,a)。目标(y)和 Q(s,a)之间的差异(MSE 损失)用于更新主要 Critic 参数 (φ).
  • Actor Update:Actor 网络计算采样状态的动作。这些动作被馈送到主 Critic 网络以获取它们的 Q 值。Actor 被更新以产生最大化该 Q 值的动作(通常通过最小化负 Q 值,L_θ=-Q)。
  • 目标更新:目标参与者和目标批评家网络 (θ', φ') 使用软更新向主要网络参数缓慢更新。

DDPG 需要单独的 Actor 和 Critic 网络。Actor 输出特定的连续动作,通常使用 tanh 进行缩放。

评论家将状态和动作都作为输入。

# 简化的 DDPG Actor Network(输出连续动作)
class  ActorNetworkDDPG (nn.Module): 
    def  __init__ ( self, state_dim: int , action_dim: int , max_action: float ): 
        super (ActorNetworkDDPG, self).__init__() 
        self.layer1 = nn.Linear(state_dim, 256 ) 
        self.layer2 = nn.Linear( 256 , 256 ) 
        self.layer3 = nn.Linear( 256 , action_dim) 
        self.max_action = max_action # 缩放输出

    def  forward ( self, state: torch.Tensor ) -> torch.Tensor: 
        x = F.relu(self.layer1(state)) 
        x = F.relu(self.layer2(x)) 
        # 使用 tanh 输出 -1 到 1 之间的值,然后缩放
        action = self.max_action * torch.tanh(self.layer3(x)) 
        return action 

# 简化的 DDPG 评论网络(采用状态和动作)
class  CriticNetworkDDPG (nn.Module): 
    def  __init__ ( self, state_dim: int , action_dim: int ): 
        super (CriticNetworkDDPG, self).__init__() 
        # 分别或一起处理状态和动作
        self.layer1 = nn.Linear(state_dim + action_dim, 256 ) 
        self.layer2 = nn.Linear( 256 , 256 ) 
        self.layer3 = nn.Linear( 256 , 1 ) # 输出单个 Q 值

    def  forward ( self, state: torch.Tensor, action: torch.Tensor ) -> torch.Tensor: 
        # 连接状态和动作
        x = torch.cat([state, action], dim= 1 ) 
        x = F.relu(self.layer1(x)) 
        x = F.relu(self.layer2(x)) 
        q_value = self.layer3(x)
        return q_value

这些网络定义了核心组件。Actor 学习要做什么,而 Critic 学习这些动作的效果如何

我们还需要一个重放缓冲区(类似于 DQN,存储(状态、动作、奖励、下一个状态、完成))和目标网络,它们是缓慢更新的主网络的副本。

# 重放缓冲区(概念 - 使用双端队列或列表)
# 目标网络(概念 - 创建演员/评论家的副本)
 target_actor = ActorNetworkDDPG(...)
target_critic = CriticNetworkDDPG(...)
target_actor.load_state_dict(actor.state_dict())# 初始化
target_critic.load_state_dict(critic.state_dict())

核心更新逻辑涉及计算两个网络的损失并对目标应用软更新。

# --- DDPG 更新逻辑大纲 --- 
#(假设优化器:定义了 actor_optimizer、critic_optimizer)
#(假设 tau:软更新率,gamma:折扣因子已定义)

# 1. 从 replay_buffer 中抽取一批:states、actions、rewards、next_states、dones 

# --- 评论家更新 --- 
# 从目标参与者获取下一个动作:next_actions = target_actor(next_states) 
# 从目标评论家获取目标 Q 值:target_q = target_critic(next_states, next_actions) 
# 计算 TD 目标:td_target = rewards + gamma * (1 - dones) * target_q 
# 获取当前 Q 值估计:current_q = critical(states, action) 
# 计算评论家损失(MSE):critic_loss = F.mse_loss(current_q, td_target.detach()) 
# 更新评论家:
 critical_optimizer.zero_grad() 
critical_loss.backward() 
critical_optimizer.step() 

# --- 演员更新 --- 
# 从主演员获取当前状态的动作: actor_actions = actor(states) 
# 计算演员损失(来自主评论家的负 Q 值): actor_loss = -critic(states, actor_actions).mean() 
# 更新演员:
 actor_optimizer.zero_grad() 
actor_loss.backward() 
actor_optimizer.step() 

# --- 软更新目标网络 ---
 soft_update(target_critic, critical, tau) 
soft_update(target_actor, actor, tau)

这个大纲展示了离线策略数据、目标网络以及针对参与者和评论家的单独更新如何在 DDPG 中结合在一起,以实现在连续动作空间中的学习。

请记住,实际互动需要在演员的输出中添加噪音以供探索。

DDPG

  1. 奖励:代理表现出明显的学习效果。每集的总奖励(左图)一开始非常低(大约 -1500),然后稳步增加,到第 100 集时移动平均值达到大约 -250。这表明在控制钟摆以最小化成本方面取得了显著的进步。性能有些嘈杂,但趋势非常积极。
  2. 评论家表现:平均评论家损失(中心图)在整个训练过程中稳步增加。这似乎违反直觉,但在 DDPG 中,随着参与者变得越来越好并达到更高价值的状态,目标 Q 值也会增加。评论家不断尝试学习这些不断发展的更高价值,因此在成功学习的同时可能会出现损失增加,这反映了被近似的价值函数的规模/复杂性不断增加。
  3. 演员表现:演员平均“损失”(右图,标记为平均 Q 值,可能显示演员实现的平均Q 值)呈现出强劲的上升趋势,与奖励有很好的相关性。这意味着演员成功地学会了选择评论家估计会带来更高奖励(更低成本)的行动,从而推动了性能的提升。它似乎正在向终点靠拢。

DDPG 成功学会了控制钟摆,在 100 集的比赛中显著提高了总奖励。尽管在这种设置中经常观察到批评家损失增加,但演员(寻找更好的行动)和批评家(估算价值)都表现出有效学习的迹象。

SAC(Maximum Entropy Actor-Critic)

软Actor-Critic (SAC) 是另一种先进的演员-评论家算法,专为连续动作空间而设计,基于 DDPG 和 TD3。其定义特征是结合了最大熵强化学习框架。

这意味着代理不仅受到激励去最大化奖励,而且还会在这样做的同时尽可能随机地行动(最大化策略熵)。

这种熵最大化鼓励更广泛的探索,提高鲁棒性,并且与 DDPG 相比,通常可以实现更快、更稳定的学习。SAC 也是离线策略的,使用像 DDPG 这样的重放缓冲区。

SAC

交互类似于其他演员 - 评论家,但是演员 (π_θ) 是随机的 —— 它输出一个采样动作的分布。经验(s,a,r,s',完成)存储在重放缓冲区中。

在训练期间(抽样一批

  • 批评家更新:目标 Q 值计算是唯一的。它使用目标网络来获得从当前策略中采样的下一个动作(a')的最小 Q 值估计(Min Q')(在下一个状态 s' 评估)。
  • 然后它从这个最小 Q 值中减去熵项(α*logπ(a'|s'))以获得 “软目标”。TD 目标(y)是使用奖励和这个软目标形成的。更新主要批评家(Q_,通常是双胞胎批评家 Q1、Q2),以最大限度地减少对 y 的 MSE 损失。
  • Actor 更新:Actor 对当前批处理状态 s 的一个动作(a_π)进行采样。损失鼓励导致高 Q 值(由主要批评家估计)并具有高熵(高 logπ)的动作。损失大约为
(α * log π - min(Q1, Q2)).mean()
  • Alpha 更新(可选):可以通过定义目标熵级别和更新 α 来自动调整熵温度(α),以鼓励策略的实际熵与目标匹配。
  • 目标更新:软更新应用于目标批评家网络。SAC 通常不使用目标参与者,使用当前参与者进行目标动作采样。

核心概念是这样的:

  • 最大熵:将策略的熵H(π(·∣s))H(π(·∣s))添加到标准奖励目标中。
  • 随机行为者:输出连续动作概率分布(如高斯分布)的参数(例如平均值、标准差)。动作是采样的。
  • 双重评论家:使用两个 Q 网络并取其目标值中的最小值来对抗 Q 值高估(类似于 TD3)。
  • 熵温度:平衡奖励和熵目标。可以固定或自动调整。
LOG_STD_MAX = 2
 LOG_STD_MIN = - 20
 EPSILON = 1e-6  # 用于log_prob计算中的数值稳定性

class  ActorNetworkSAC (nn.Module): 
    """ Stochastic Gaussian Actor for SAC. """ 
    def  __init__ ( self, state_dim: int , action_dim: int , max_action: float ): 
        super (ActorNetworkSAC, self).__init__() 
        self.layer1 = nn.Linear(state_dim, 256 ) 
        self.layer2 = nn.Linear( 256 , 256 ) 
        self.mean_layer = nn.Linear( 256 , action_dim)     # 输出平均值
        self.log_std_layer = nn.Linear( 256 , action_dim) # 输出对数标准差
        self.max_action = max_action 

    def  forward ( self, state: torch.Tensor ) -> Tuple [torch.Tensor, torch.Tensor]: 
        """ 输出压缩动作及其对数概率。 """
         x = F.relu(self.layer1(state)) 
        x = F.relu(self.layer2(x)) 
        mean = self.mean_layer(x) 
        log_std = self.log_std_layer(x) 
        log_std = torch.clamp(log_std, LOG_STD_MIN, LOG_STD_MAX) # 限制稳定性
        std = torch.exp(log_std) 

        # 使用重新参数化技巧创建分布和样本
        normal_dist = Normal(mean, std) 
        z = normal_dist.rsample() # 可微分样本(预压缩)
         action_squashed = torch.tanh(z) # 应用 tanh 压缩

        # 计算带有 tanh 校正的 log_prob 
        # log_prob = log_normal(z) - log(1 - tanh(z)^2 + eps)
         log_prob = normal_dist.log_prob(z) - torch.log( 1 - action_squashed. pow ( 2 ) + EPSILON) 
        # 如果 action_dim > 1,则在动作维度上对 log_prob
        求和 log_prob = log_prob. sum (dim=- 1 , keepdim= True ) 

        # 将动作缩放到环境边界
        action_scaled = action_squashed * self.max_action 

        return action_scaled, log_prob

Critic 网络通常采用 Twin Q 结构,类似于 TD3,以状态和动作作为输入。

# Critic Network (Twin Q) for SAC
class CriticNetworkSAC(nn.Module):
    def __init__(self, state_dim: int, action_dim: int):
        super(CriticNetworkSAC, self).__init__()
        # Q1 architecture
        self.l1_q1 = nn.Linear(state_dim + action_dim, 256)
        self.l2_q1 = nn.Linear(256, 256)
        self.l3_q1 = nn.Linear(256, 1)
        # Q2 architecture
        self.l1_q2 = nn.Linear(state_dim + action_dim, 256)
        self.l2_q2 = nn.Linear(256, 256)
        self.l3_q2 = nn.Linear(256, 1)

    def forward(self, state: torch.Tensor, action: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
        sa = torch.cat([state, action], 1)
        q1 = F.relu(self.l1_q1(sa))
        q1 = F.relu(self.l2_q1(q1))
        q1 = self.l3_q1(q1)

        q2 = F.relu(self.l1_q2(sa))
        q2 = F.relu(self.l2_q2(q2))
        q2 = self.l3_q2(q2)
        return q1, q2     

更新逻辑结合了演员、评论家和可选的 alpha 的损失。此大纲捕获了 SAC 的关键计算,强调了目标 Q 值中的熵项和演员损失,以及双胞胎评论家和可选 alpha 调整的使用。

SAC(由Fareed Khan创建)

  1. 奖励: SAC 表现出强大的学习能力,经过大约 40-50 轮训练后,总奖励从 -1500 左右迅速增加到 -200 左右。收敛后,性能相对稳定。
  2. 评论家表现:平均评论家损失最初显着增加,然后以一定的差异趋于稳定,类似于 DDPG,这反映了随着演员的进步,评论家学习到越来越高的状态动作值。
  3. 演员表现:演员损失最初迅速增加(表明成功向更高的 Q 值进行策略更新),在第 40 集左右达到峰值,然后随着策略可能稳定下来并且熵正则化发挥更大的作用而下降。
  4. 熵/Alpha:熵温度 (Alpha) 会自动调整。它最初会降低,然后在主要学习阶段 (ep 20-55) 大幅增加,然后随着策略在最后变得更加确定和自信而再次降低。Alpha 损失图在零附近波动,证实了调整机制正在发挥作用以维持目标熵。

SAC 有效地解决了 Pendulum 任务,表现出快速收敛到高回报的能力。熵系数 (alpha) 的自动调整在整个训练过程中动态平衡探索和利用,有助于实现稳定高效的学习。

TRPO(Constrained Policy Updates)

信赖域策略优化(TRPO)是一种较早的策略梯度算法,专注于实现单调的策略改进,确保(以高概率)每个策略更新步骤不会降低性能。

它通过限制单次更新中策略可以改变的程度来实现这种稳定性,以新旧策略之间的 KL 散度来衡量。

TRPO 是一种基于策略的演员评论家算法(尽管评论家主要用于优势估计,而不是在核心 TRPO 约束内更新)。

其主要挑战在于有效地解决约束优化问题。

TRPO(由Fareed Khan创作)

与其他在线策略方法一样,TRPO 使用当前策略 (πold) 收集一批经验并计算优势 (A)。然后,它计算标准策略梯度 ('g')。

TRPO 的核心在于找到更新步骤。它不是简单地沿着“g”前进,而是旨在解决一个受约束的问题:在保持新旧策略之间的 KL 散度低于阈值(δ)的前提下,最大化策略改进。

这可以通过找到满足 Fs≈g 的步进方向“s”来近似解决,其中 F 是 Fisher 信息矩阵(近似 KL 约束曲率)。

TRPO 使用共轭梯度(CG)算法来有效地找到“s”,而无需明确形成或反转 F。CG 只需要计算Fisher-Vector Products (FVP)

一旦找到“s”,回溯线搜索就会确定最大步长 (α),以便更新满足实际的 KL 约束和替代优势改进条件

TRPO 更新方程

  • 信任区域:将策略更新限制在性能改进近似成立的区域,确保稳定性。
  • KL 散度约束:使用平均 Kullback-Leibler 散度DKL(πold∣∣πnew)≤δDKL(πold∣∣πnew)≤δ作为策略变化的衡量标准。
  • 费舍尔信息矩阵 (FIM):表示策略分布空间的曲率。TRPO 使用涉及 FIM 的二次形式来近似 KL 约束
  • 费舍尔向量积 (FVP):一种使用自动微分有效计算 FIM 与任意向量乘积的技术,避免计算和存储完整的 FIM。
  • 共轭梯度(CG):一种仅使用 FVP 解决线性系统Fs≈g 以找到更新方向的迭代算法。
  • 线搜索:确保采取的最后一步实际上满足 KL 约束并根据替代目标提高性能。

TRPO 需要 Actor(策略网络)和 Critic(价值网络)定义(与 A2C/PPO 相同)。

复杂的部分是 FVP、CG 和线搜索。以下是概念大纲(有关更详细、可运行但复杂的代码,请参阅12_trpo.ipynba3c_training.py )。

# Computes the Fisher vector product using Hessian-vector product approximation
# This is used in the conjugate gradient method for computing the search direction
# in the TRPO update step.
def fisher_vector_product(actor, states, vector, cg_damping):
    log_probs = actor.get_log_probs(states).detach()
    kl = (log_probs.exp() * (log_probs - log_probs.detach())).sum()
    grads = torch.autograd.grad(kl, actor.parameters(), create_graph=True)
    flat_grads = torch.cat([g.view(-1) for g in grads])
    
    gv = torch.dot(flat_grads, vector)
    hv = torch.autograd.grad(gv, actor.parameters())
    flat_hv = torch.cat([h.view(-1) for h in hv])
    
    # Adds a damping term to improve numerical stability
    return flat_hv + cg_damping * vector

# Implements the conjugate gradient method to solve Ax = b
# This is used to approximate the natural gradient direction
# in the TRPO update step.
def conjugate_gradient(fvp_func, b, cg_iters=10, tol=1e-10):
    x = torch.zeros_like(b)  # Initialize solution vector
    r = b.clone()  # Residual
    p = b.clone()  # Search direction
    rs_old = torch.dot(r, r)
    
    for _ in range(cg_iters):
        Ap = fvp_func(p)
        alpha = rs_old / torch.dot(p, Ap)
        x += alpha * p
        r -= alpha * Ap
        rs_new = torch.dot(r, r)
        
        if rs_new < tol:
            break
        
        p = r + (rs_new / rs_old) * p
        rs_old = rs_new
    
    return x

# Performs backtracking line search to find an acceptable step size
# Ensures that the KL divergence constraint is satisfied
# and that the new policy improves the surrogate loss.
def backtracking_line_search(actor, states, actions, advantages, old_log_probs,
                             step_direction, initial_step_size, max_kl, decay=0.8, max_iters=10):
    theta_old = {name: param.clone() for name, param in actor.named_parameters()}  # Store old parameters
    
    for i in range(max_iters):
        step_size = initial_step_size * (decay ** i)  # Reduce step size progressively
        
        # Apply the step to actor parameters
        for param, step in zip(actor.parameters(), step_size * step_direction):
            param.data.add_(step)
        
        # Compute KL divergence and surrogate loss
        kl = actor.kl_divergence(states, old_log_probs)
        surrogate = actor.surrogate_loss(states, actions, advantages, old_log_probs)
        
        # Check if KL is within constraint and surrogate loss has improved
        if kl <= max_kl and surrogate>= 0:
            return step_size * step_direction, True
        
        # Restore old parameters if step is not successful
        for name, param in actor.named_parameters():
            param.data.copy_(theta_old[name])
    
    return None, False  # Return failure if no valid step found

# Updates the actor (policy) and critic (value function) using TRPO algorithm.
def update_trpo(actor, critic, actor_optimizer, critic_optimizer,
                states, actions, advantages, returns_to_go, log_probs_old,
                max_kl=0.01, cg_iters=10, cg_damping=0.1, line_search_decay=0.8,
                value_loss_coeff=0.5, entropy_coeff=0.01):
    
    # Compute policy gradient
    policy_loss = actor.surrogate_loss(states, actions, advantages, log_probs_old)
    grads = torch.autograd.grad(policy_loss, actor.parameters())
    g = torch.cat([grad.view(-1) for grad in grads])
    
    # Compute the natural gradient direction using conjugate gradient
    fvp_func = lambda v: fisher_vector_product(actor, states, v, cg_damping)
    step_direction = conjugate_gradient(fvp_func, g, cg_iters)
    
    # Compute step size based on KL constraint
    sAs = torch.dot(step_direction, fvp_func(step_direction))
    step_size = torch.sqrt(2 * max_kl / (sAs + 1e-8))
    
    # Perform backtracking line search to ensure KL constraint is satisfied
    step, success = backtracking_line_search(actor, states, actions, advantages, log_probs_old,
                                             step_direction, step_size, max_kl, line_search_decay)
    
    # Apply the step if successful
    if success:
        with torch.no_grad():
            for param, step_val in zip(actor.parameters(), step):
                param.data.add_(step_val)
    
    # Compute and update value function using MSE loss
    value_loss = nn.MSELoss()(critic(states), returns_to_go)
    critic_optimizer.zero_grad()
    value_loss.backward()
    critic_optimizer.step()
    
    return policy_loss.item(), value_loss.item()  # Return loss values for monitoring

TRPO 通过使用二阶信息(由 FIM 近似)明确约束策略更新大小来确保稳定性,通过 CG 和 FVP 有效计算,并通过线搜索进行验证。

TRPO(创作者Fareed Khan)

  1. 学习进度和表现: TRPO 表现出极快的学习速度。平均奖励在约 20-30 次迭代内迅速攀升至接近最优值并保持稳定。同样,平均情节长度急剧下降并很快稳定在较低水平。
  2. 评论稳定性:评论损失在一些初始波动之后相对较快地稳定下来,表明价值函数得到有效且一致的学习。
  3. 策略更新约束 (KL): “实际 KL 散度”图显示每次迭代中策略的变化。TRPO 旨在将其保持在较小水平(低于红色最大 KL 线,通常约为 0.01),以确保稳定更新。绘制的值不寻常(通常为负值,这对于 KL 来说不应该发生),但目的是实现稳定、受约束的更新。
  4. 优化目标: “替代目标”图显示了在采取步骤之前 TRPO 计算出的预期改进。它在零附近波动表明逐步改进的保证并不总是完美实现,但总体结果还是不错的。

TRPO 表现出了出色的样本效率,在此网格任务上快速收敛到高性能且稳定的策略。尽管内部优化指标图(KL、替代目标)中存在一些噪声/异常,但核心信任区域方法有效地引导学习走向强大的解决方案。

DQN(Deep Q-Learning)

Q-Learning 非常适合具有少量且可管理的离散状态的环境。

然而,当状态空间变得非常大或连续时(例如处理游戏像素或机器人传感器数据),它就会遇到困难。

深度 Q 网络(DQN)通过用近似Q 值深度神经网络替换 Q 表来解决此问题,Q(s,a;θ) 表示网络的参数。

DQN 引入了两项关键创新来在使用 Q 学习的神经网络时稳定学习:经验重放目标网络

DQN(由Fareed Khan创建)

代理使用基于其主 Q 网络 ( Q_θ) 的 epsilon-greedy 策略与环境进行交互。

每个经验元组(s, a, r, s', done)都存储在重放缓冲区 (Replay Buffer)中。

对于训练,代理从缓冲区中采样这些经验的小批量。对于批次中的每个经验,使用观察到的奖励 r 和下一个状态 s 可实现的最大 Q 值计算目标 Q 值(“TD 目标 y”),由单独的、缓慢更新的目标网络(Q_θ)估计。

主 Q 网络预测状态 s 中实际采取的动作 a 的 Q 值。计算目标 y 与预测 Q 值之间的差异(MSE 或 Huber 损失),并使用梯度下降来更新主 Q 网络的参数(θ)。

定期地,主网络的权重被复制到目标网络:θ←θ。

DQN的核心概念包含:

  • 近似的神经网络(例如 MLP、CNN)Q(s, a)。它通常将状态s作为输入,并输出所有离散动作的Q 值。
  • 存储过去转换的缓冲区。随机小批量采样打破了数据相关性并提高了稳定性和效率(s, a, r, s', done)
  • Q 网络单独副本,其权重 ( ) 更新频率较低(例如,每一步或通过缓慢的“软”更新)。它提供稳定的目标θC

DQN 更新方程

它为训练主 Q 网络提供了稳定的目标,防止出现振荡。

首先,定义 Q 网络。对于简单的矢量状态(例如我们的网格世界的归一化坐标),MLP 就足够了。

# DQN 网络(MLP)
class  DQN(nn.Module):
    def  __init__ ( self, n_observations: int , n_actions: int ): 
        super (DQN, self).__init__() 
        self.layer1 = nn.Linear(n_observations, 128 ) 
        self.layer2 = nn.Linear( 128 , 128 ) 
        self.layer3 = nn.Linear( 128 , n_actions) # 输出每个动作的 Q 值

    def  forward ( self, x: torch.Tensor ) -> torch.Tensor: 
        """ 前向传递以获取 Q 值。 """ 
        # 确保输入是正确设备上的浮点张量
        if  not  isinstance (x, torch.Tensor): 
            x = torch.tensor(x, dtype=torch.float32, device=x.device) 
        elif x.dtype != torch.float32:
            x = x.to(dtype=torch.float32)

        x = F.relu(self.layer1(x))
        x = F.relu(self.layer2(x))
        return self.layer3(x)# 原始 Q 值

该网络学习从状态到动作值的映射。

接下来,重播记忆存储经验。

# 用于存储转换的结构
Transition = namedtuple( 'Transition' , 
                        ( 'state' , 'action' , 'next_state' , 'reward' , 'done' )) 

# 重放内存缓冲区
class  ReplayMemory : 
    def  __init__ ( self , capacity: int ): 
        self .memory = deque([], maxlen=capacity) 

    def  push ( self , * args: Any ) -> None: 
        "" " 保存转换元组 (s, a, s', r, done)。" "" 
        # 确保张量存储在 CPU 上,以避免缓冲区出现 GPU 内存问题
        processing_args = [] 
        for arg in  args: 
            if isinstance(arg, torch.Tensor): 
                processing_args.append(arg.cpu()) 
            elif isinstance(arg, bool): # 将完成标志存储为张量以保持一致性
                 processing_args.append(torch.tensor([arg], dtype=torch.bool)) 
            else:
                 processing_args.append(arg) 

        self .memory.append(Transition(*processed_args)) 


    def  sample ( self , batch_size: int ) -> Optional[List[Transition]]: 
        "" " 随机抽样一批转换。 " "" 
        if len( self .memory) < batch_size: return none return random.sample self .memory batch_size def __len__ self -> int: 
        return len( self .memory)

此缓冲区允许对不相关的批次进行采样以进行训练更新。动作选择使用基于主 Q 网络输出的 epsilon-greedy。

# 动作选择(使用 DQN 的 Epsilon-Greedy)
def  select_action_dqn ( state: torch.Tensor, 
                        policy_net: nn.Module, 
                        epsilon: float , 
                        n_actions: int , 
                        device: torch.device ) -> torch.Tensor: 
    """ 使用策略 Q 网络以 epsilon-greedily 方式选择动作。 """ 
    if random.random() < epsilon: 
        # 探索:选择一个随机动作
        action = torch.tensor([[random.randrange(n_actions)]], device=device, dtype=torch.long) 
    else : 
        # 利用:根据 Q 网络选择最佳动作
        with torch.no_grad(): 
            # 如果需要,添加批量暗淡,确保张量在正确的设备上
            state = state.unsqueeze( 0 ) if state.dim() == 1  else state 
            state = state.to(device) 
            # 获取 Q 值并使用最大值选择动作Q
             action = policy_net(state). max ( 1 )[ 1 ].view( 1 , 1 )
    return action

优化步骤利用目标网络计算TD目标并更新主策略网络

# DQN 优化步骤概述
def  optimize_model_dqn ( memory: ReplayMemory, 
                         policy_net: DQN, 
                         target_net: DQN, 
                         optimizer: optim.Optimizer, 
                         batch_size: int , 
                         gamma: float , 
                         device: torch.device ): 
    """ 在 DQN 策略网络上执行一步优化。 """ 
    # 1. 从内存中采样批次
    # 2. 在“设备”上准备批次张量(状态、动作、奖励、下一个状态、完成)
    # 3. 使用 policy_net 计算所采取动作的 Q(s_t, a_t)
            state_action_values = policy_net(state_batch).gather( 1 , action_batch) 
    # 4. 计算 V(s_{t+1}) = max_{a'} Q(s_{t+1}, a'; θ) 使用 target_net
           与torch.no_grad():下一个状态值 = 目标网络 (非最终下一个状态)。max ( 1 )[ 0 ] 
    # 5. 计算 TD 目标 y = 奖励 + gamma * V(s_{t+1})(处理终端状态)
               expected_state_action_values = (next_state_values * gamma) + reward_batch 
    # 6. 计算 Q(s_t, a_t) 和 TD 目标 y 之间的损失(例如,Huber 损失)
               loss = F.smooth_l1_loss(state_action_values, expected_state_action_values.unsqueeze( 1 )) 
    # 7. 优化 policy_net
               optimizer.zero_grad() 
              loss.backward() 
              torch.nn.utils.clip_grad_value_(policy_net.parameters(), 100 ) # 可选的梯度裁剪
              optimizer.step() 
    
# 请参阅 13_dqn.ipynb 中的完整实现

这个核心循环与目标网络的定期更新相结合,使得 DQN 即便使用神经网络等复杂的函数逼近器也能够有效地学习。

DQN(由Fareed Khan创建)

  1. 学习进度: DQN 表现出清晰的学习效果。平均奖励从负值显著增加,在约 200-250 集后趋向稳定的正奖励。
  2. 效率:随着时间的推移,情节长度大幅减少,与奖励增加相关,表明代理学会更快地达到目标。
  3. 探索: epsilon 衰减图显示探索逐渐减少,使得代理能够在后续情节中有效地利用其学到的知识。
  4. 学习策略:最终的策略网格展示了一个连贯的策略,其中的动作通常会引导代理走向目标状态“G”(右下)。

DQN 成功学会了在自定义网格环境中导航,并找到了通往目标的有效路径。以奖励和情节长度衡量的性能显著提高,并且随着探索次数的减少而趋于稳定。

未完待续......

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言