接上文继续.......
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
- 奖励:代理表现出明显的学习效果。每集的总奖励(左图)一开始非常低(大约 -1500),然后稳步增加,到第 100 集时移动平均值达到大约 -250。这表明在控制钟摆以最小化成本方面取得了显著的进步。性能有些嘈杂,但趋势非常积极。
- 评论家表现:平均评论家损失(中心图)在整个训练过程中稳步增加。这似乎违反直觉,但在 DDPG 中,随着参与者变得越来越好并达到更高价值的状态,目标 Q 值也会增加。评论家不断尝试学习这些不断发展的更高价值,因此在成功学习的同时可能会出现损失增加,这反映了被近似的价值函数的规模/复杂性不断增加。
- 演员表现:演员平均“损失”(右图,标记为平均 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创建)
- 奖励: SAC 表现出强大的学习能力,经过大约 40-50 轮训练后,总奖励从 -1500 左右迅速增加到 -200 左右。收敛后,性能相对稳定。
- 评论家表现:平均评论家损失最初显着增加,然后以一定的差异趋于稳定,类似于 DDPG,这反映了随着演员的进步,评论家学习到越来越高的状态动作值。
- 演员表现:演员损失最初迅速增加(表明成功向更高的 Q 值进行策略更新),在第 40 集左右达到峰值,然后随着策略可能稳定下来并且熵正则化发挥更大的作用而下降。
- 熵/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.ipynb或a3c_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)
- 学习进度和表现: TRPO 表现出极快的学习速度。平均奖励在约 20-30 次迭代内迅速攀升至接近最优值并保持稳定。同样,平均情节长度急剧下降并很快稳定在较低水平。
- 评论稳定性:评论损失在一些初始波动之后相对较快地稳定下来,表明价值函数得到有效且一致的学习。
- 策略更新约束 (KL): “实际 KL 散度”图显示每次迭代中策略的变化。TRPO 旨在将其保持在较小水平(低于红色最大 KL 线,通常约为 0.01),以确保稳定更新。绘制的值不寻常(通常为负值,这对于 KL 来说不应该发生),但目的是实现稳定、受约束的更新。
- 优化目标: “替代目标”图显示了在采取步骤之前 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创建)
- 学习进度: DQN 表现出清晰的学习效果。平均奖励从负值显著增加,在约 200-250 集后趋向稳定的正奖励。
- 效率:随着时间的推移,情节长度大幅减少,与奖励增加相关,表明代理学会更快地达到目标。
- 探索: epsilon 衰减图显示探索逐渐减少,使得代理能够在后续情节中有效地利用其学到的知识。
- 学习策略:最终的策略网格展示了一个连贯的策略,其中的动作通常会引导代理走向目标状态“G”(右下)。
DQN 成功学会了在自定义网格环境中导航,并找到了通往目标的有效路径。以奖励和情节长度衡量的性能显著提高,并且随着探索次数的减少而趋于稳定。
未完待续......