1TRPO 的局限:为什么需要 PPO
TRPO(Trust Region Policy Optimization)在 2015 年提出,通过 KL 散度约束确保策略更新不会偏离旧策略太远。它使用共轭梯度法求解大规模二阶优化问题,理论上保证了单调改进。然而,TRPO 的实际应用面临三大难题:实现复杂度极高,需要手动实现共轭梯度法和线搜索;计算开销大,Fisher 信息矩阵的逆运算在高维参数空间中代价昂贵;与网络结构兼容性差,难以与 LayerNorm、Dropout 等现代神经网络技术结合。
PPO(Proximal Policy Optimization)由 OpenAI 在 2017 年提出,核心思想是用一阶优化方法近似 TRPO 的约束效果。通过裁剪目标函数或自适应惩罚项,PPO 在保持策略更新稳定性的同时,大幅降低了实现难度和计算成本。实验表明,PPO 在多数连续控制和离散控制任务上能达到与 TRPO 相当甚至更好的性能,成为此后几年强化学习的事实标准。
# TRPO 的共轭梯度求解(简化版)
def conjugate_gradient(fvp_fn, b, nsteps=10, residual_tol=1e-10):
"""Solve Ax = b using conjugate gradient"""
x = torch.zeros_like(b)
r = b.clone()
p = b.clone()
rdotr = torch.dot(r, r)
for _ in range(nsteps):
Av = fvp_fn(p)
alpha = rdotr / torch.dot(p, Av)
x += alpha * p
r -= alpha * Av
new_rdotr = torch.dot(r, r)
if new_rdotr < residual_tol:
break
p = r + (new_rdotr / rdotr) * p
rdotr = new_rdotr
return x# PPO 的简化实现对比
def ppo_update(policy, old_policy, states, actions, advantages,
clip_eps=0.2, lr=3e-4):
"""PPO 用一阶 SGD 替代 TRPO 的共轭梯度"""
optimizer = torch.optim.Adam(policy.parameters(), lr=lr)
for epoch in range(4): # 多次 epoch 提升样本效率
dist = policy(states)
old_dist = old_policy(states)
log_prob = dist.log_prob(actions)
old_log_prob = old_dist.log_prob(actions).detach()
ratio = torch.exp(log_prob - old_log_prob)
surr1 = ratio * advantages
surr2 = torch.clamp(ratio, 1 - clip_eps, 1 + clip_eps) * advantages
loss = -torch.min(surr1, surr2).mean()
optimizer.zero_grad()
loss.backward()
optimizer.step()| 特性 | TRPO | PPO |
|---|---|---|
优化方法 | 共轭梯度 + 线搜索 | 一阶 SGD/Adam |
实现难度 | 极高 | 低 |
计算复杂度 | O(n^2) | O(n) |
样本效率 | 高(单次更新) | 高(多次 epoch) |
网络兼容性 | 差 | 好 |
实际性能 | 基线水平 | 相当或更好 |
学习 PPO 之前建议先掌握策略梯度(PG)和 Actor-Critic,理解优势函数 Advantage 的概念。
TRPO 虽然理论优美,但工程实现极其繁琐。除非有特殊需求,否则优先选择 PPO。
2PPO 裁剪目标函数:核心机制
PPO 的核心创新在于裁剪目标函数(Clipped Surrogate Objective)。传统策略梯度方法中,如果新策略与旧策略差异过大,可能导致策略崩溃——即策略突然转向某个次优动作并陷入局部最优。TRPO 通过硬性 KL 约束解决这个问题,但代价是复杂的二阶优化。
PPO 采用了一种更优雅的方案:裁剪概率比率(clipped probability ratio)。定义比率 r_t(theta) = pi_theta(a_t|s_t) / pi_old(a_t|s_t),当优势 A_t 为正时,我们希望增大 r_t;当 A_t 为负时,我们希望减小 r_t。裁剪操作将 r_t 限制在 [1-epsilon, 1+epsilon] 范围内,防止策略更新过大。具体来说,目标函数取 unclipped 和 clipped 两者的最小值:L^CLIP = E[min(r_t * A_t, clip(r_t, 1-eps, 1+eps) * A_t)]。这种设计确保策略在信任区域内优化,同时保持了一阶优化的简单性。
import torch
def compute_ppo_loss(log_probs, old_log_probs, advantages, clip_eps=0.2):
"""PPO-Clip 核心:裁剪目标函数"""
ratio = torch.exp(log_probs - old_log_probs)
# 未裁剪的 surrogate
surr1 = ratio * advantages
# 裁剪后的 surrogate
clipped_ratio = torch.clamp(ratio, 1.0 - clip_eps, 1.0 + clip_eps)
surr2 = clipped_ratio * advantages
# 取最小值,确保策略更新不超过信任区域
loss = -torch.min(surr1, surr2)
# 近似 KL 散度(用于监控,不参与梯度)
approx_kl = (old_log_probs - log_probs).mean().item()
return loss.mean(), approx_kldef ppo_epoch(policy, old_policy, states, actions, advantages,
values, old_values, returns, clip_eps=0.2,
value_coef=0.5, ent_coef=0.01):
"""完整的 PPO epoch 更新"""
dist = policy.get_dist(states)
log_probs = dist.log_prob(actions)
entropy = dist.entropy().mean()
# Value loss with clipping
pred_values = policy.get_value(states)
value_clipped = old_values + (pred_values - old_values).clamp(
-clip_eps, clip_eps
)
value_loss1 = (pred_values - returns).pow(2)
value_loss2 = (value_clipped - returns).pow(2)
value_loss = 0.5 * torch.max(value_loss1, value_loss2).mean()
# Total loss
policy_loss, _ = compute_ppo_loss(
log_probs, old_log_probs, advantages, clip_eps
)
total_loss = policy_loss + value_coef * value_loss - ent_coef * entropy
return total_loss| 符号 | 含义 | 取值范围 |
|---|---|---|
r_t(theta) | 概率比率 pi_new / pi_old | (0, +inf) |
epsilon | 裁剪超参数 | 通常 0.1 ~ 0.3 |
A_t | 优势估计 | (-inf, +inf) |
clip(r_t) | 裁剪后的比率 | [1-eps, 1+eps] |
L^CLIP | 裁剪目标 | 标量 |
KL 近似 | 策略差异监控 | 通常 < 0.05 |
裁剪参数 epsilon 默认 0.2,对于对稳定性要求高的任务可降至 0.1。
裁剪操作只限制策略更新幅度,不保证 KL 散度绝对有界。需要监控 approx_kl 指标。
3PPO-Clip vs PPO-Penalty:两种变体
PPO 原始论文提出了两种实现信任区域约束的方式:PPO-Clip(裁剪法)和 PPO-Penalty(惩罚法)。PPO-Clip 通过在目标函数中直接裁剪概率比率来限制更新幅度,实现简单且无需额外超参数调优。PPO-Penalty 则在目标函数中添加 KL 散度惩罚项:L^KLPEN = E[r_t * A_t - beta * KL(pi_old, pi_new)],其中 beta 是自适应调整的惩罚系数。
PPO-Penalty 的优势在于能更精确地控制策略更新幅度。当 KL 散度超过目标值 KL_target 时,自动增大 beta;当 KL 过小时,减小 beta。但这也引入了额外的超参数(KL_target 和 beta 的调整速率),增加了调参难度。实践中,PPO-Clip 因其简单性和鲁棒性成为主流选择,OpenAI Baselines 和 Stable-Baselines3 等库默认使用 PPO-Clip。PPO-Penalty 更适合对策略稳定性有极高要求的场景,如机器人控制或金融交易。
class PPOPenalty:
"""PPO-Penalty 实现:自适应 KL 惩罚"""
def __init__(self, kl_target=0.01, beta_init=1.0,
beta_adjust_rate=2.0, clip_high=10.0):
self.kl_target = kl_target
self.beta = beta_init
self.adjust_rate = beta_adjust_rate
self.clip_high = clip_high
def compute_loss(self, log_probs, old_log_probs, advantages):
ratio = torch.exp(log_probs - old_log_probs)
kl_approx = (old_log_probs - log_probs).mean()
# 自适应调整 beta
if kl_approx > self.kl_target * 1.5:
self.beta = min(self.beta * self.adjust_rate, self.clip_high)
elif kl_approx < self.kl_target / 1.5:
self.beta = max(self.beta / self.adjust_rate, 1e-4)
# 带 KL 惩罚的目标
surr = ratio * advantages
penalty = self.beta * kl_approx
loss = -(surr.mean() - penalty)
return loss, kl_approx.item()def compare_ppo_variants(env_name="Hopper-v4", seed=42):
"""对比 PPO-Clip 和 PPO-Penalty 的训练效果"""
results = {"clip": [], "penalty": []}
for variant in ["clip", "penalty"]:
env = gym.make(env_name)
agent = PPOAgent(env, variant=variant, seed=seed)
for episode in range(500):
state, _ = env.reset(seed=seed)
episode_reward = 0
done = False
while not done:
action = agent.select_action(state)
next_state, reward, done, _, _ = env.step(action)
agent.store(state, action, reward, done)
state = next_state
episode_reward += reward
agent.update()
results[variant].append(episode_reward)
print(f"{variant}: ep={episode}, reward={episode_reward:.1f}")
return results| 特性 | PPO-Clip | PPO-Penalty |
|---|---|---|
约束方式 | 裁剪概率比率 | KL 散度惩罚项 |
超参数数量 | 1 个 (epsilon) | 3+ 个 (KL_target, beta, rate) |
实现难度 | 低 | 中 |
KL 控制精度 | 近似控制 | 精确自适应 |
稳定性 | 高 | 非常高 |
推荐使用场景 | 通用任务 | 高稳定性需求 |
如果不确定用哪个变体,选 PPO-Clip。它是绝大多数任务的默认选择。
PPO-Penalty 的 beta 初始化非常关键,过小导致约束失效,过大导致学习停滞。
4GAE 广义优势估计:降低方差的关键
GAE(Generalized Advantage Estimation)是 PPO 成功的关键技术之一。优势函数 A(s,a) = Q(s,a) - V(s) 衡量某个动作相对于平均水平的优劣程度。直接使用蒙特卡洛回报估计优势方差很大,而使用单步 TD 误差偏差很大。GAE 通过引入参数 lambda 在偏差和方差之间取得平衡。
GAE 的定义是指数加权的多步 TD 误差之和:A^GAE = (1-lambda) * (A^(1) + lambda * A^(2) + lambda^2 * A^(3) + ...),其中 A^(k) 是 k 步优势估计。当 lambda=0 时退化为单步 TD(低方差、高偏差);当 lambda=1 时等价于蒙特卡洛回报(高方差、低偏差)。实践中 lambda 通常取 0.95 左右,在大多数任务上都能取得良好的偏差-方差权衡。GAE 与 PPO 的结合使得策略更新既稳定又高效。
def compute_gae(rewards, values, dones, gamma=0.99, lam=0.95):
"""GAE 广义优势估计"""
advantages = []
gae = 0.0
n = len(rewards)
for t in reversed(range(n)):
if t == n - 1:
next_value = 0.0
else:
next_value = values[t + 1]
# TD error: delta_t = r_t + gamma * V(s_{t+1}) - V(s_t)
delta = rewards[t] + gamma * next_value * (1 - dones[t]) - values[t]
# GAE: A_t = delta_t + gamma * lambda * A_{t+1}
gae = delta + gamma * lam * (1 - dones[t]) * gae
advantages.insert(0, gae)
return torch.tensor(advantages, dtype=torch.float32)def compute_returns_gae(rewards, values, dones,
gamma=0.99, lam=0.95):
"""完整的 GAE + Returns 计算"""
advantages = compute_gae(rewards, values, dones, gamma, lam)
# Returns = advantages + values(用于 value loss)
returns = advantages + torch.tensor(values, dtype=torch.float32)
# 标准化优势(重要技巧:提升训练稳定性)
advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8)
return advantages, returns
def visualize_lambda_effect():
"""不同 lambda 值的偏差-方差权衡"""
lambdas = [0.0, 0.5, 0.95, 1.0]
for lam in lambdas:
advantages = compute_gae(rewards, values, dones, lam=lam)
print(f"lambda={lam:.2f}: mean={advantages.mean():.3f}, "
f"std={advantages.std():.3f}, "
f"range=[{advantages.min():.3f}, {advantages.max():.3f}]")| lambda 值 | 偏差 | 方差 | 适用场景 |
|---|---|---|---|
0.0 | 高 | 低 | 高噪声环境 |
0.5 | 中 | 中 | 平衡场景 |
0.95 | 低 | 中低 | 通用推荐 |
1.0 | 最低 | 最高 | 确定性环境 |
0.9 | 低 | 中 | 对方差敏感时 |
0.99 | 极低 | 中高 | 长 horizon 任务 |
GAE 标准化(减均值除标准差)是重要技巧,能显著提升训练稳定性。
GAE 需要完整的 trajectory 数据,因此 PPO 是 on-policy 算法,不能重用旧数据。
5多环境并行训练:提升样本效率
PPO 是 on-policy 算法,每个数据只能使用一次,因此样本效率是关键瓶颈。多环境并行训练通过在多个环境实例中同时收集经验,大幅增加每轮更新的数据量。常见的并行方案包括:向量环境(Vectorized Environments)在同一进程内并行运行多个环境实例;子进程并行(Subprocess Parallelism)利用多核 CPU 在独立进程中运行环境;GPU 并行(GPU-based Simulation)对于可 GPU 加速的环境(如 Isaac Gym),实现数千环境的同时仿真。
并行训练的关键在于保持环境的独立性和随机性。每个环境实例使用不同的随机种子,避免经验之间的相关性。此外,需要合理设置 rollout 步数(steps per env)和并行环境数(num envs),两者的乘积即为每轮更新的总样本数。经验法则是总样本数保持在 2048~8192 之间,过小导致梯度估计噪声大,过大则单次更新计算开销高且可能超出信任区域约束的有效范围。
import gymnasium as gym
from gymnasium.vector import AsyncVectorEnv
def create_parallel_envs(env_id, num_envs=8, seed=42):
"""创建并行向量环境"""
def make_env(env_id, idx):
def thunk():
env = gym.make(env_id)
env = gym.wrappers.RecordEpisodeStatistics(env)
if idx == 0:
env = gym.wrappers.RenderMode(env, "human")
return env
return thunk
envs = AsyncVectorEnv(
[make_env(env_id, i) for i in range(num_envs)],
seed=seed,
)
return envs
def parallel_rollout(agent, envs, num_steps=128):
"""在多个环境中并行收集经验"""
obs, _ = envs.reset()
states, actions, rewards, dones, log_probs, values = [], [], [], [], [], []
for _ in range(num_steps):
action, log_prob, value = agent.get_action_and_value(obs)
next_obs, reward, terminated, truncated, info = envs.step(action)
done = terminated | truncated
states.append(obs)
actions.append(action)
rewards.append(reward)
dones.append(done)
log_probs.append(log_prob)
values.append(value)
obs = next_obs
return states, actions, rewards, dones, log_probs, values, obsdef analyze_parallel_scaling():
"""分析并行环境数量对训练的影响"""
configs = [
{"num_envs": 4, "rollout": 512, "total": 2048},
{"num_envs": 8, "rollout": 256, "total": 2048},
{"num_envs": 16, "rollout": 128, "total": 2048},
{"num_envs": 32, "rollout": 64, "total": 2048},
{"num_envs": 64, "rollout": 128, "total": 8192},
]
print(f"{'Env数':>6} | {'Rollout':>8} | {'总样本':>8} | {'评估'}")
print("-" * 42)
for cfg in configs:
n = cfg["num_envs"]
r = cfg["rollout"]
t = cfg["total"]
comment = "推荐" if t == 2048 and 8 <= n <= 32 else "极端"
print(f"{n:>6} | {r:>8} | {t:>8} | {comment}")
# 评估经验法则:
# - num_envs=8~32, rollout=128~256 是最佳平衡点
# - 总样本 < 1024 时梯度方差过大
# - 总样本 > 16384 时单次更新可能超出信任区域| 并行方案 | 环境数 | CPU 核心需求 | 适用场景 |
|---|---|---|---|
单环境 | 1 | 1 | 调试/测试 |
SyncVectorEnv | 4~8 | 2~4 | 轻量任务 |
AsyncVectorEnv | 8~32 | 4~16 | 通用推荐 |
SubprocVecEnv | 16~64 | 8~32 | 大规模训练 |
GPU Parallel | 1024+ | GPU 为主 | Isaac Gym 等 |
保持总样本数(num_envs * rollout_steps)在 2048~8192 范围内,这是 PPO 论文推荐的经验值。
并行环境数过多会导致经验之间的相关性降低,梯度方差增大反而不利于训练。
6超参数调优:PPO 训练的关键因素
PPO 虽然比 TRPO 更容易使用,但超参数选择仍然对训练效果有决定性影响。关键超参数包括:学习率(learning rate)控制更新步长,通常使用线性衰减策略从初始值降到零;裁剪参数 epsilon 控制信任区域大小,默认 0.2 适用于大多数任务;GAE 参数 gamma 和 lambda 分别控制折扣因子和优势估计的偏差-方差权衡;网络架构(隐藏层大小和层数)影响策略表达能力;PPO epoch 数控制每批数据的重复使用次数,通常 3~10 次。
超参数调优的系统方法:首先使用推荐默认值作为基线,然后逐一调整关键参数。学习率是最重要的超参数,建议从 3e-4 开始,在 [1e-4, 1e-3] 范围内搜索。如果使用较大的 batch size,可以适当增大学习率。PPO epoch 数增加会提升样本效率但可能导致过拟合到当前 batch,建议配合 early stopping 使用——当 KL 散度超过阈值时提前终止当前 epoch。
PPO_CONFIGS = {
"default": {
"learning_rate": 3e-4,
"n_steps": 2048,
"batch_size": 64,
"n_epochs": 10,
"gamma": 0.99,
"gae_lambda": 0.95,
"clip_range": 0.2,
"ent_coef": 0.0,
"vf_coef": 0.5,
"max_grad_norm": 0.5,
"use_sde": False,
},
"continuous_control": {
"learning_rate": 3e-4,
"n_steps": 2048,
"batch_size": 64,
"n_epochs": 10,
"gamma": 0.99,
"gae_lambda": 0.95,
"clip_range": 0.2,
"ent_coef": 0.0,
"vf_coef": 0.5,
"max_grad_norm": 0.5,
"use_sde": True, # State-Dependent Exploration
},
"atari_discrete": {
"learning_rate": 2.5e-4,
"n_steps": 128,
"batch_size": 256,
"n_epochs": 4,
"gamma": 0.99,
"gae_lambda": 0.95,
"clip_range": 0.1,
"ent_coef": 0.01,
"vf_coef": 0.5,
"max_grad_norm": 0.5,
},
}class PPOWithEarlyStopping:
"""带 Early Stopping 的 PPO 实现"""
def __init__(self, clip_eps=0.2, target_kl=0.03):
self.clip_eps = clip_eps
self.target_kl = target_kl
def update(self, states, actions, advantages, returns,
old_log_probs, old_values, policy, value_net,
optimizer, n_epochs=10):
"""PPO 更新,当 KL 超过阈值时提前停止"""
batch_size = states.shape[0]
indices = np.arange(batch_size)
for epoch in range(n_epochs):
np.random.shuffle(indices)
for start in range(0, batch_size, self.batch_size):
end = start + self.batch_size
batch_idx = indices[start:end]
# 计算 KL 散度
with torch.no_grad():
new_dist = policy(states[batch_idx])
old_dist = old_policy(states[batch_idx])
kl = torch.distributions.kl_divergence(
old_dist, new_dist
).mean().item()
if kl > self.target_kl:
print(f"Early stop at epoch {epoch}, "
f"KL={kl:.4f} > {self.target_kl}")
return # 提前终止
# 正常 PPO 更新
loss = compute_ppo_loss(...)
optimizer.zero_grad()
loss.backward()
optimizer.step()| 超参数 | 推荐范围 | 影响 | 调优策略 |
|---|---|---|---|
learning_rate | 1e-4 ~ 1e-3 | 更新步长 | 线性衰减 |
clip_range | 0.1 ~ 0.3 | 信任区域 | 固定 0.2 或自适应 |
n_epochs | 3 ~ 10 | 样本重用 | 配合 early stopping |
gamma | 0.9 ~ 0.999 | 折扣因子 | 长任务用更大值 |
gae_lambda | 0.9 ~ 0.99 | 偏差-方差 | 0.95 通用 |
batch_size | 32 ~ 256 | 梯度稳定性 | 总样本/epochs |
使用 learning rate 线性衰减:lr = init_lr * (1 - progress),能显著提升最终性能。
过大的 n_epochs(>20)会导致过拟合到当前 batch,策略失去泛化能力。
7PyTorch 实战:LunarLander 环境完整实现
LunarLander-v2 是 Gym 中的经典离散控制任务,目标是控制登月舱安全着陆在两个旗帜之间。状态空间包含 8 维向量(位置、速度、角度、角速度、着陆腿接触状态),动作空间包含 4 个离散动作(无操作、左引擎、主引擎、右引擎)。奖励设计鼓励安全着陆并惩罚燃料消耗和坠毁。
下面给出一个完整的 PPO 实现,包含网络定义、经验收集、GAE 计算、PPO 更新和训练循环。这个实现可以直接运行并在 LunarLander 环境中达到 200+ 的平均奖励(解决阈值为 200)。实现的关键设计决策包括:使用两层 64 单元的 MLP 网络、Adam 优化器、线性学习率衰减、GAE 优势估计、多环境并行收集经验。训练通常在 50~100 万步内收敛到解决标准。
import torch
import torch.nn as nn
import torch.optim as optim
import gymnasium as gym
import numpy as np
from torch.distributions import Categorical
class PPONetwork(nn.Module):
"""Actor-Critic 网络"""
def __init__(self, state_dim, action_dim, hidden_dim=64):
super().__init__()
self.actor = nn.Sequential(
nn.Linear(state_dim, hidden_dim),
nn.Tanh(),
nn.Linear(hidden_dim, hidden_dim),
nn.Tanh(),
nn.Linear(hidden_dim, action_dim),
)
self.critic = nn.Sequential(
nn.Linear(state_dim, hidden_dim),
nn.Tanh(),
nn.Linear(hidden_dim, hidden_dim),
nn.Tanh(),
nn.Linear(hidden_dim, 1),
)
def forward(self, state):
logits = self.actor(state)
value = self.critic(state).squeeze(-1)
return logits, valuedef train_ppo_lunarlander(total_timesteps=500000, seed=42):
"""完整的 PPO LunarLander 训练"""
env = gym.make("LunarLander-v2", render_mode=None)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 初始化
net = PPONetwork(state_dim=8, action_dim=4).to(device)
optimizer = optim.Adam(net.parameters(), lr=2.5e-4, eps=1e-5)
# 训练循环
num_iterations = total_timesteps // 2048
for iteration in range(num_iterations):
# 线性衰减学习率
lr = 2.5e-4 * (1 - iteration / num_iterations)
for param_group in optimizer.param_groups:
param_group["lr"] = lr
# 收集经验 (rollout)
states, actions, rewards, dones, log_probs, values = [], [], [], [], [], []
state, _ = env.reset(seed=seed + iteration)
for _ in range(2048):
state_t = torch.FloatTensor(state).unsqueeze(0).to(device)
with torch.no_grad():
logits, value = net(state_t)
dist = Categorical(logits=logits)
action = dist.sample()
log_prob = dist.log_prob(action)
states.append(state)
actions.append(action.item())
log_probs.append(log_prob.item())
values.append(value.item())
next_state, reward, done, truncated, _ = env.step(action.item())
rewards.append(reward)
dones.append(done or truncated)
state = next_state if not (done or truncated) else env.reset()[0]
# GAE + PPO 更新
advantages = compute_gae(rewards, values, dones)
returns = torch.tensor(advantages) + torch.tensor(values)
advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8)
for epoch in range(10):
# 转换为 tensor
states_t = torch.FloatTensor(states).to(device)
actions_t = torch.LongTensor(actions).to(device)
advantages_t = advantages.to(device)
old_log_probs_t = torch.FloatTensor(log_probs).to(device)
logits, _ = net(states_t)
dist = Categorical(logits=logits)
new_log_probs = dist.log_prob(actions_t)
loss, kl = compute_ppo_loss(
new_log_probs, old_log_probs_t, advantages_t
)
optimizer.zero_grad()
loss.backward()
nn.utils.clip_grad_norm_(net.parameters(), 0.5)
optimizer.step()
if kl > 0.03:
break
if iteration % 10 == 0:
print(f"Iter {iteration}: loss={loss:.4f}, kl={kl:.4f}")
return net| 训练阶段 | 步数 | 关键操作 | 预期奖励 |
|---|---|---|---|
初始化 | 0 | 随机策略探索 | -500 ~ -200 |
早期探索 | 0~5万 | 学习基本控制 | -200 ~ -50 |
策略形成 | 5~20万 | 学会稳定降落 | -50 ~ 100 |
快速提升 | 20~40万 | 精准着陆优化 | 100 ~ 180 |
收敛阶段 | 40~60万 | 微调达到阈值 | 180 ~ 220 |
解决 | 50~80万 | 稳定超过 200 | 200 ~ 250 |
LunarLander 环境建议在 50 万步内达到 200+ 奖励。如果超过 100 万步仍未收敛,检查网络架构或学习率。
LunarLander-v3(Gymnasium 新版本)的奖励函数有变化,可能需要调整训练参数。