1. 术语解释
1.1 基础强化学习术语
Term | Symbol | Variable |
---|---|---|
observations(观察) | o t \mathbf{o}_t ot | obs |
action/actions(动作) | a t \mathbf{a}_t at | |
reward/rewards(奖励) | r t r_t rt | |
returns(回报) | R t R_t Rt |
1.2 PPO算法特有术语
- entropy/entropy loss (熵/熵损失)
- GAE (Generalized Advantage Estimation, 广义优势估计)
- clip/clipping (裁剪)
- ratio (新旧策略比率)
- KL divergence/approx_kl (KL散度/近似KL散度)
1.3 算法超参数相关
- gamma (折扣因子)
- lambda (GAE参数)
- clip coefficient (裁剪系数)
- target KL (目标KL散度)
- batch size (批量大小)
- minibatch size (小批量大小)
- 神经网络相关:
- actor (演员网络,用于策略)
- critic (评论家网络,用于价值估计)
- logprob/logprobs (对数概率)
- deterministic (确定性策略)
- 训练相关:
- iteration (迭代次数)
- gradient norm (梯度范数)
1.4 每个epoch仅运行一个episode
2. 脚本说明
2.1 ppo.py
是专门为连续动作空间设计的
所以不支持离散动作空间的环境;
具体可参考代码
3. Class Args: args
[note]
4. if __name__ == “__main__”
[L209] save_video_trigger
用于控制何时保存训练视频的触发函数。
该函数通过检查当前迭代次数是否满足保存频率要求来决定是否保存训练视频。
[L212] envs: ManiSkillVectorEnv
仿真机器人训练营;
[L216] max_episode_steps: int
每个episode的最大步数限制;其主要作用是设置一个episode的终止条件,从而防止智能体(agent)无限执行下去。
其中,
(1)max_episode_steps限制的重置是由envs.step()
函数触发的[code]
此时,
envs: ManiSkillVectorEnv(PushCube-v1, 2048)
所以
(2)实际上执行的是ManiSkillVectorEnv.step()函数
而在ManiSkillVectorEnv.step()
内部实际调用的是私有成员self._env
的step()
函数,
此时self._env
的数据类型:
self._env: <TimeLimitWrapper<OrderEnforcing<PushCubeEnv<PushCube-v1>>>>
Note:这里实际上调用的是“TimeLimitWrapper.step()”
其调用链形式如下:
TimeLimitWrapper
└── OrderEnforcing
└── PushCubeEnv
[L248] obs: torch.Tensor[P,E,O]
用于存储观察数据的张量缓冲区。
NOTE
真正在参与计算是实际上使用的是flattened的张量 b_obs。
[L250] logprobs: torch.Tensor
动作概率对数和张量。
logprobs
是各动作维度概率对数的和。
在PPO等策略梯度算法中用于存储和计算连续动作空间中策略(policy)对所选动作的对数概率值。
NOTE:数据点是一维的
logprobs
的数据点维度是1,因为其实际上是动作各维度的概率的对数求和得来的,
即 probs.log_prob(action).sum(1)
[L252] dones: torch.Tensor
用于标记env是否处于结束状态。
dones
中的每个元素都是一个布尔值,表示env在特定时间步上是否已达到终止条件(例如,任务完成、失败或达到最大步数)。
NOTE:结束状态包含且仅包含以下三种情况:success, fail, truncated
[L256] global_step: int = 0
用于追踪训练过程中的总环境交互步数。这个变量在训练开始时初始化为0,并在每次环境交互时增加num_envs
。它被用作记录训练进度和日志记录的全局计数器。
主要用途
- 作为TensorBoard和W&B日志的x轴步数
- 用于计算训练的SPS (Steps Per Second)
[L258] next_obs: torch.Tensor[E,O]
下一个时刻的观察值,即执行动作后环境返回的新观察值。
在PPO算法中,next_obs
通过envs.reset()
和envs.step()
获得,代表从当前状态执行动作后到达的新状态的观察值。
它将在下一个时间步作为当前观察值使用。
NOTE:其实准确的含义就是:current observation.
[L259] eval_obs: torch.Tensor[A,O]
评估环境(evaluation environment)的观察值(observations)。
A
取自“evaluation”的“a”,表示并行eval环境的数量;
NOTE:在
ppo.py
中仅在评估时用作observations的缓存张量。
[L261] eps_returns: legacy
我们已经在ManiSkill主页上提交了issue;
[L266] action_space_low: torch.Tensor
环境动作空间的下界。
该张量包含了环境中每个动作维度允许的最小值。它从 Gymnasium 环境的 single_action_space.low
属性转换获得。
[L266] action_space_high: torch.Tensor
环境动作空间的上界。
该张量包含了环境中每个动作维度允许的最大值。它从 Gymnasium 环境的 single_action_space.high
属性转换获得。
[L275] final_values: unused
当args.finite_horizon_gae
为True
时,final_values
仅执行了赋值操作,但其结果并未参与后续的计算;即此时可以认为final_values
在脚本中并未发挥作用。
[L338] lastgaelam: unused
当args.finite_horizon_gae
为True
时,lastgaelam
仅进行了初始化,但其结果并未参与后续的计算;即此时可以认为lastgaelam
在脚本中并未发挥作用。
[L379] b_logprobs: torch.Tensor[ST*E]
所有bare的动作对数概率值。
形状:(batch_size,)
,其中 batch_size = num_steps * num_envs。
NOTE:
- 这是通过将原始的二维logprobs张量展平(flatten)得到的一维张量
- 用于存储“旧策略(old policy)”对每个动作的对数概率评估
- 用于计算新旧策略之间的概率比率(probability ratio)
[L396] newvalue: Tensor
Critic网络针对当前observation预测的价值张量。
形状:
- 原始形状:
(minibatch_size, 1)
- 展平后形状:
(minibatch_size,)
[L398] ratio: torch.Tensor
新旧策略概率比(policy probability ratio)。计算公式为:
ratio
=
exp
(
newlogprob
−
oldlogprob
)
=
π
θ
new
(
a
∣
s
)
π
θ
old
(
a
∣
s
)
\text{ratio} = \exp(\text{newlogprob} - \text{oldlogprob}) = \frac{\pi_{\theta_{\text{new}}}(a|s)}{\pi_{\theta_{\text{old}}}(a|s)}
ratio=exp(newlogprob−oldlogprob)=πθold(a∣s)πθnew(a∣s)
其中:
- newlogprob (
newlogprob
):新策略网络动作的对数概率 - oldlogprob (
b_logprobs
):旧策略网络动作的对数概率
该变量是PPO算法的核心组件,用于:
- 衡量策略更新前后的差异程度
- 通过clip操作限制在[1-ε, 1+ε]区间(ε为
clip_coef
超参数) - 在 surrogate objective function 中作为优势函数(advantage)的权重
数学背景参考PPO论文的 surrogate objective:
L
CLIP
(
θ
)
=
E
t
[
min
(
ratio
t
⋅
A
t
,
clip
(
ratio
t
,
1
−
ϵ
,
1
+
ϵ
)
⋅
A
t
)
]
L^{\text{CLIP}}(\theta) = \mathbb{E}_t [\min(\text{ratio}_t \cdot A_t, \text{clip}(\text{ratio}_t, 1-\epsilon, 1+\epsilon) \cdot A_t)]
LCLIP(θ)=Et[min(ratiot⋅At,clip(ratiot,1−ϵ,1+ϵ)⋅At)]
[L402] old_approx_kl
Old近似KL散度估计值,通过(-logratio).mean()
计算。
该指标衡量新旧策略之间的差异程度,值越大表示策略更新幅度越大。
[L403] approx_kl
近似KL散度(approximating kullback-leibler divergence),用于衡量新旧策略分布之间的差异。该实现基于John Schulman提出的近似公式:
approx_kl
=
E
[
(
ratio
−
1
)
−
log
(
ratio
)
]
\text{approx\_kl} = E[(\text{ratio} - 1) - \log(\text{ratio})]
approx_kl=E[(ratio−1)−log(ratio)]
其中:
ratio = exp(new_logprob - old_logprob)
表示新旧策略概率比- 该度量用于检测策略更新幅度,当超过
target_kl
阈值时会提前终止参数更新 - 与
old_approx_kl
的区别:本实现使用更精确的二次近似,而old_approx_kl
使用一阶近似
作用
- 监控策略更新的稳定性
- 作为 early-stopping 条件防止过大的策略变化
- 与clipfrac共同构成PPO算法的核心监控指标
[L404] clipfracs: list[float]
计算当前mini-batch中新旧策略概率比(ratio)被clipped的比例。
公式: (|ratio - 1| > clip_coef) 的样本比例。
[L409] mb_advantages: Tensor[minibatch_size,]
当前mini-batch 的优势函数估计值张量。该张量包含以下特性:
- 计算来源
通过广义优势估计(GAE)算法计算得到,反映每个状态-动作对的相对优势值
公式推导:
A t = ∑ l = 0 T − t ( γ λ ) l δ t + l A_t = \sum_{l=0}^{T-t} (\gamma\lambda)^l \delta_{t+l} At=∑l=0T−t(γλ)lδt+l
其中 δ t = r t + γ V ( s t + 1 ) − V ( s t ) \delta_t = r_t + \gamma V(s_{t+1}) - V(s_t) δt=rt+γV(st+1)−V(st) - 算法作用:
在PPO的策略优化阶段用于计算策略梯度损失(policy gradient loss) - 数值特性
- 已进行标准化处理(默认开启
args.norm_adv=True
) - 数值范围受
gae_lambda
和gamma
超参数控制
- 已进行标准化处理(默认开启
v_loss
The value network loss measures the difference between value network predictions and target estimates.
These target estimates are computed using GAE based on collected trajectory data.
[L447] explained_var: float
In fact, explained_var
here is Explained Variance Ratio, instead of explained variance.
This metric quantifies the proportion of variance explained by the critic network’s predictions.
ExplainedVarianceRatio
=
1
−
Var
[
y
true
−
y
pred
]
Var
[
y
true
]
\text{ExplainedVarianceRatio} = 1 - \frac{\text{Var}[y_{\text{true}} - y_{\text{pred}}]}{\text{Var}[y_{\text{true}}]}
ExplainedVarianceRatio=1−Var[ytrue]Var[ytrue−ypred]
Where:
-
y
true
y_{\text{true}}
ytrue (
Tensor
): Ground truth returns with shape(batch_size,)
-
y
pred
y_{\text{pred}}
ypred (
Tensor
): Predicted values from critic network with shape(batch_size,)
决定系数
ChatGPT-o3-mini-high:
这里的表达式实际对应的是决定系数,也就是我们常说的 R 2 R^2 R2分数。其含义如下:
- 当 R 2 = 1 R^2 = 1 R2=1时,说明模型能够完美解释数据中的方差。
- 当 R 2 = 0 R^2 = 0 R2=0时,说明模型的预测效果与仅使用均值作为预测值一样。
- 如果 R 2 R^2 R2为负值,则说明模型的表现甚至比简单的均值预测还要差。
实际上就是将残差的方差与 y true y_{\text{true}} ytrue的总方差做了比较,得到了模型解释方差的比例。
“简单的均值预测”
“简单的均值预测”指的是使用数据集的平均值作为预测值的一种基准方法。在回归问题中,这种方法不考虑输入特征,只是简单地用所有目标值的平均值来进行预测。
具体解释
假设我们有一组真实值 y true y_{\text{true}} ytrue,其平均值为 y ˉ \bar{y} yˉ。简单的平均值预测就是用这个平均值 y ˉ \bar{y} yˉ来预测每一个样本的目标值。
例子
假设我们有以下真实值:
y
true
=
[
3
,
5
,
2
,
8
,
7
]
y_{\text{true}} = [3, 5, 2, 8, 7]
ytrue=[3,5,2,8,7]
计算平均值:
y
ˉ
=
3
+
5
+
2
+
8
+
7
5
=
5
\bar{y} = \frac{3 + 5 + 2 + 8 + 7}{5} = 5
yˉ=53+5+2+8+7=5
使用简单的平均值预测,每个样本的预测值都是5:
y
pred
=
[
5
,
5
,
5
,
5
,
5
]
y_{\text{pred}} = [5, 5, 5, 5, 5]
ypred=[5,5,5,5,5]
举个实际例子帮助理解
假设真实值(考试分数):
y_true = [80, 85, 90] # 方差是 (25 + 0 + 25)/3 ≈ 16.67
情况1:完美预测
y_pred = [80, 85, 90]
误差方差 = 0 → 解释方差 = 1 - 0/16.67 = 1
情况2:均值预测(解释方差=0)
# 真实值
y_true = [80, 85, 90] # 均值85,方差16.67
# 预测值始终等于真实值的均值
y_pred = [85, 85, 85]
# 计算过程:
误差 = [-5, 0, 5] → 误差方差 = (25 + 0 + 25)/3 = 16.67
解释方差 = 1 - 16.67/16.67 = 0
这表示:
- 当解释方差=0时,说明预测模型的表现和直接使用均值预测的效果一样
- 模型完全没有捕捉到数据的变化规律
- 在强化学习中,如果critic网络的解释方差接近0,说明它没有学会预测回报的变化
类比理解:
假设老师让全班猜考试平均分:
- 如果你每次都猜平均分(比如85),那么你的预测误差方差会等于真实分数方差 → 解释方差=0
- 这相当于"躺平式预测",没有真正学习到分数变化的规律
情况3:普通预测
y_pred = [78, 84, 88]
误差 = [2, 1, 2] → 误差方差 = (4 + 1 + 4)/3 = 3
解释方差 = 1 - 3/16.67 ≈ 0.82(还不如“躺平”)
情况4:糟糕预测
y_pred = [70, 70, 70]
误差 = [10, 15, 20] → 误差方差 = (100 + 225 + 400)/3 ≈ 241.67
解释方差 = 1 - 241.67/16.67 ≈ -13.5
5. 笔记
5.1 Policy-loss 在 mb_advantage 为负时,会将小ratio进行放大
南溪:这一点是在 #Policy-loss 看到的;
这里采用了一种比较复杂的实现,并且是特别针对劣势动作的情况;
我们感觉这个似乎有些复杂设计,感觉直接限制“过高的ratio”就行了,即简化为:
ratio = torch.clamp(ratio, 0, 1 + args.clip_coef)
pg_loss = (-mb_advantages*ratio).mean()
具体示例说明(from Cursor-deepseek-r1)
假设:
mb_advantage = -0.5
→ \rightarrow →-mb_advantage = 0.5
(正数)ratio = 0.5
(新策略的概率是旧策略的一半)ε = 0.2
→ \rightarrow → 裁剪范围是 [0.8, 1.2]
计算:
pg_loss1 = 0.5 * 0.5 = 0.25
pg_loss2 = 0.5 * 0.8 = 0.4 # 因为ratio=0.5 < 0.8,被clamp到0.8
最终损失 = max(0.25, 0.4) = 0.4
关键发现
- 当
ratio < 1-ε
(新策略概率显著低于旧策略)时:pg_loss2
会使用裁剪后的下限值1-ε
- 这会导致损失被放大(示例中从 0.25 → 0.4)
- 优化器为了最小化这个被放大的损失,会推动 ratio 增大(即让新策略的概率向旧策略靠拢)
5.2 Critic和actor网络共用一个optimizer
Chatgpt-4o-Cursor:
是的,在这个脚本中,共用一个优化器主要是为了简化编程实现。这样做有以下几个好处:
- 简化代码:只需要管理一个优化器对象,代码更简洁。
- 同步更新:确保Actor和Critic网络在同一时间步进行更新,保持训练过程的同步性。
- 共享超参数:如学习率等超参数可以统一管理,减少调参的复杂性。
虽然在某些情况下,可能需要为Actor和Critic 网络使用不同的优化策略(例如不同的学习率),但在这个实现中,使用一个优化器已经足够,实现了代码结构的简化。