首页/知识库/PPO:近端策略优化

PPO:近端策略优化

✍️ AI Master📅 创建 2026-04-12📖 20 min 阅读
💡

文章摘要

从裁剪目标到信任区域,掌握最流行的强化学习算法

1TRPO 的局限:为什么需要 PPO

TRPO(Trust Region Policy Optimization)在 2015 年提出,通过 KL 散度约束确保策略更新不会偏离旧策略太远。它使用共轭梯度法求解大规模二阶优化问题,理论上保证了单调改进。然而,TRPO 的实际应用面临三大难题:实现复杂度极高,需要手动实现共轭梯度法和线搜索;计算开销大,Fisher 信息矩阵的逆运算在高维参数空间中代价昂贵;与网络结构兼容性差,难以与 LayerNorm、Dropout 等现代神经网络技术结合。

PPO(Proximal Policy Optimization)由 OpenAI 在 2017 年提出,核心思想是用一阶优化方法近似 TRPO 的约束效果。通过裁剪目标函数或自适应惩罚项,PPO 在保持策略更新稳定性的同时,大幅降低了实现难度和计算成本。实验表明,PPO 在多数连续控制和离散控制任务上能达到与 TRPO 相当甚至更好的性能,成为此后几年强化学习的事实标准。

python
# 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
python
# 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()
特性TRPOPPO

优化方法

共轭梯度 + 线搜索

一阶 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)]。这种设计确保策略在信任区域内优化,同时保持了一阶优化的简单性。

python
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_kl
python
def 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 更适合对策略稳定性有极高要求的场景,如机器人控制或金融交易。

python
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()
python
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-ClipPPO-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 的结合使得策略更新既稳定又高效。

python
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)
python
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 之间,过小导致梯度估计噪声大,过大则单次更新计算开销高且可能超出信任区域约束的有效范围。

python
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, obs
python
def 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。

python
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,
    },
}
python
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 万步内收敛到解决标准。

python
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, value
python
def 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 新版本)的奖励函数有变化,可能需要调整训练参数。

继续你的 AI 学习之旅

浏览更多 AI 知识库文章,或者探索 GitHub 上的优质 AI 项目