feat: AdaBoot — adaptive velocity bootstrapping (paper Section II-C)

This commit is contained in:
8x54zj-m
2026-07-01 15:44:30 +08:00
parent 308f2090a5
commit 5cbc8e2ca4
2 changed files with 34 additions and 4 deletions

View File

@@ -114,6 +114,9 @@ class CENetActorModel(MLPModel):
# 在 nn.Module.__init__ 之后创建 VAE 子模块
self.vae = CENetVAE(cenet_in_dim, cenet_out_dim, activation)
self._last_cenet_output = None
# AdaBoot: 自适应 bootstrapping论文 Section II-C
self._adaboot_cv_buffer = [] # 速度估计误差的 CV 历史
self._adaboot_prob = 1.0 # 当前 bootstrap 概率1.0 = 完全信任 GT
def _update_distribution(self, obs: torch.Tensor) -> None:
"""覆盖父类 — 强制 std > 0 再创建 Normal 分布(防止 NaN"""
@@ -129,12 +132,22 @@ class CENetActorModel(MLPModel):
super()._update_distribution(obs)
def _get_latent_dim(self) -> int:
"""Actor 实际输入code(19) + policy(45) = 64。"""
return self.obs_dim - self._history_dim + self._code_dim
"""Actor 实际输入code(19) + policy(45) = 64。
privileged_obs 只用于 AdaBoot不计入 latent dim。
"""
# obs_groups 可能包含 privileged_obsAdaBoot需排除
extra_dims = 0
if "privileged_obs" in self.obs_groups:
extra_dims = 247 # privileged_obs 维度
return self.obs_dim - self._history_dim - extra_dims + self._code_dim
def get_latent(self, obs: TensorDict, masks: torch.Tensor | None = None,
hidden_state: HiddenState = None) -> torch.Tensor:
"""提取观测 → VAE 编码 → 拼接 code + policy → 返回 latent(64)。"""
"""提取观测 → VAE 编码 → 拼接 code + policy → 返回 latent(64)。
AdaBoot: 训练时以概率 p 用 ground truth velocity 替换 CENet 的速度估计。
"""
policy_obs = obs["policy"] # (N, 45)
obs_history = obs["obs_history"] # (N, 225)
@@ -142,6 +155,23 @@ class CENetActorModel(MLPModel):
self._last_cenet_output = out
code, code_vel, decode, mean_vel, logvar_vel, mean_latent, logvar_latent = out
# ── AdaBoot: 自适应速度 Bootstrap论文 Section II-C──
if self.training and "privileged_obs" in obs:
gt_vel = obs["privileged_obs"][:, 45:48] # ground truth body velocity
# 先计算 CENet 原始误差的 CV替换前
with torch.no_grad():
raw_error = code_vel - gt_vel
cv = raw_error.std() / (raw_error.abs().mean() + 1e-6)
self._adaboot_cv_buffer.append(cv.item())
if len(self._adaboot_cv_buffer) > 1000:
self._adaboot_cv_buffer = self._adaboot_cv_buffer[-1000:]
mean_cv = sum(self._adaboot_cv_buffer) / len(self._adaboot_cv_buffer)
self._adaboot_prob = max(0.0, min(1.0, mean_cv * 5.0))
# 以概率 _adaboot_prob 用 GT 替换 CENet 的速度估计
mask = torch.rand(code_vel.shape[0], 1, device=code_vel.device) < self._adaboot_prob
code_vel = torch.where(mask, gt_vel, code_vel)
code = torch.cat([code_vel, code[:, 3:]], dim=-1)
# 防止 VAE NaN 传播到下游
if torch.isnan(code).any():
code = torch.nan_to_num(code, nan=0.0)

View File

@@ -144,7 +144,7 @@ class rslrl:
# 观测分组actor 用 policy+historycritic 用 privileged_obs
runner.obs_groups = {
"actor": ["policy", "obs_history"],
"actor": ["policy", "obs_history", "privileged_obs"], # privileged_obs 用于 AdaBoot
"critic": ["privileged_obs"],
}