本文目录导读:

在策略梯度方法中,高方差是一个核心问题,对于Python脚本(通常使用PyTorch/TensorFlow实现)出现高方差,根本原因在于策略梯度的估计方式。
策略梯度通过采样来估计期望,而采样天然带有随机性,这种随机性由多个因素放大,导致方差高。
以下是导致高方差的几个关键原因,以及相应的Python代码层面的解释和解决方案:
核心原因:单步或有限步采样导致噪声巨大
策略梯度的标准形式是:
∇J(θ) ≈ 1/N * Σ [ ∇log π(a_t|s_t) * R_t ]
R_t 是累计奖励。
- 问题:如果只用一条轨迹(N=1,即
on-policy最典型的设置)来更新梯度,R_t的方差会极大,一条路径运气好,奖励高;运气不好,奖励低,用这种「样本噪声」去更新模型,参数会剧烈抖动。 - Python表现:在训练循环中,你会发现 Loss 曲线剧烈跳动,甚至直接
NaN(梯度爆炸)。
解决思路:
-
增加批大小 (Batch Size):并行采样多个环境,计算平均梯度,用
multiprocessing或vectorized environments(如gym.vector.make)。# 坏的例子:只用一个环境采样一条轨迹就更新 obs = env.reset() for step in range(update_freq): action = policy(obs) obs, reward, done, _ = env.step(action) # ... 存储轨迹 loss = compute_loss(trajectory) optimizer.step(loss) # 方差极高 # 好的例子:矢量化环境,并行采样多条轨迹 num_envs = 16 envs = gym.vector.make("CartPole-v1", num_envs=num_envs) obs = envs.reset() for step in range(update_freq): actions = policy(obs) # 一次输出N个动作 obs, rewards, dones, _ = envs.step(actions) # ... 存储N条轨迹 loss = compute_loss(all_trajectories) # 平均梯度,方差降低 sqrt(N) 倍 optimizer.step(loss)
关键因素:奖励 (Reward) 的尺度与稀疏性
- 问题:如果奖励范围很大(-1000 到 +1000),或者奖励非常稀疏(只有最后一步有非零奖励),会导致
R_t的方差极大。 - Python表现:梯度更新步长不一致,参数一会儿被推往一个方向,一会儿被拉回。
解决思路:
-
奖励归一化 (Reward Normalization):在训练过程中,维持一个
Running mean和std,对奖励进行标准化。 -
裁剪奖励 (Reward Clipping):简单粗暴,将奖励限制在 [-1, 1] 之间。
# 在存储奖励时处理 reward_batch = np.array(rewards) # 方式1: 除以标准差 (常用) reward_std = np.std(reward_batch) + 1e-8 normalized_rewards = (reward_batch - np.mean(reward_batch)) / reward_std # 方式2: 简单裁剪 clipped_rewards = np.clip(reward_batch, -1, 1)
算法设计缺陷:使用 Monte Carlo (MC) 回报
如果你使用 REINFORCE 算法,它直接使用完整的轨迹回报 G_t(从当前时刻到结束的总奖励)。
- 问题:
G_t包含了未来所有步骤的随机性,方差极大。 - Python表现:更新时,梯度方差非常高。
解决思路:
-
使用 Actor-Critic 架构:引入一个价值网络
V(s)作为基线 (Baseline),降低方差,梯度变成:∇J(θ) ≈ Σ [ ∇log π(a_t|s_t) * (R_t - V(s_t)) ]R_t - V(s_t)被称为 Advantage (优势函数),它的方差比原始R_t小得多。# 纯REINFORCE (高方差) returns = discounted_cumulative_sum(rewards, gamma) # G_t loss = - (log_probs * returns).mean() # Actor-Critic (低方差) values = value_net(states) # G_t 或 TD-error 作为 target returns = discounted_cumulative_sum(rewards, gamma) advantages = returns - values.detach() # 优势函数 policy_loss = - (log_probs * advantages).mean() value_loss = F.mse_loss(values, returns) loss = policy_loss + value_loss
训练稳定性问题:重要性采样与信任区域
对于 PPO 或 TRPO 等高级算法,虽然它们旨在解决方差问题,但如果 Python 实现不当,方差会飙升。
- 问题:
- 重要性采样比率过大:
ratio = π_new / π_old如果太大(> 10),哪怕一个样本有微小偏差,也会导致梯度爆炸。 - KL散度失控:新旧策略差异过大。
- 重要性采样比率过大:
- Python表现:
policy_loss突然跳高,然后参数崩溃。
解决思路:
-
PPO-Clipping 要生效:确保
epsilon参数(如 0.2)正常使用,且ratio被限制在[1-ε, 1+ε]之间。 -
梯度裁剪 (Gradient Clipping):这是最后的防线。
# 在 optimizer.step() 之前 total_loss.backward() torch.nn.utils.clip_grad_norm_(policy.parameters(), max_norm=0.5) optimizer.step()
排错流程图
如果你的 Python 脚本方差高,请按以下优先级检查:
- 算法类型:是否在用纯
REINFORCE? -> 升级到 Actor-Critic (A2C/PPO)。 - 基线 (Baseline):Actor-Critic 里是否正确地减去了
Value函数? -> 计算 Advantage。 - 奖励处理:奖励范围是否太大? -> 添加 Reward Normalization 或 Clipping。
- 批大小与并行:是否使用单环境单步更新? -> 增加并行环境数 (如 16个)。
- 超参数:学习率是否过高? -> 降低学习率 (3e-4 或 1e-4 起步)。
- 训练稳定性:梯度是否爆炸? -> 添加 Gradient Clipping。
第2点(引入优势函数) 和 第4点(向量化环境) 是解决高方差最有效的方法。