Repository navigation
Score squashed gSDE actions on the pre-tanh sample - #2290
shaneraphel wants to merge 1 commit into
Conversation
A float32 tanh can round a large Gaussian sample to exactly 1, and the clipped inverse is then a different point. PPO and A2C keep the drawn sample and evaluate the likelihood there.
|
Hi @shaneraphel, thanks for taking this on! I originally reported #2285. I gave this branch a try (ea03e76) and found a weird bug where the stored pre-tanh sample doesn’t look right. Right now it’s read from With a callback that calls import numpy as np, torch as th
from stable_baselines3 import PPO
from stable_baselines3.common.callbacks import BaseCallback
class PredictInStep(BaseCallback):
def __init__(self, other_obs):
super().__init__(); self.other_obs = other_obs
def _on_step(self):
self.model.predict(self.other_obs, deterministic=True)
return True
def run(with_predict):
model = PPO("MlpPolicy", "Pendulum-v1", use_sde=True, n_steps=64, batch_size=64, n_epochs=1,
policy_kwargs=dict(squash_output=True), seed=0, device="cpu")
fwd, stored = [], []
orig_forward = model.policy.forward
def forward(obs, deterministic=False):
out = orig_forward(obs, deterministic)
fwd.append(model.policy.action_dist.gaussian_actions.detach().clone())
return out
model.policy.forward = forward
orig_add = model.rollout_buffer.add
def add(*a, gaussian_actions=None, **k):
stored.append(gaussian_actions.detach().clone())
return orig_add(*a, gaussian_actions=gaussian_actions, **k)
model.rollout_buffer.add = add
other = np.array([1.0, 0.0, 0.0], dtype=np.float32)
model.learn(64, callback=PredictInStep(other) if with_predict else None)
mism = sum(not th.allclose(f, s) for f, s in zip(fwd, stored))
print(f"predict-in-callback={with_predict}: {mism}/{len(stored)} stored samples differ")
run(False); run(True) |
|
I’d be happy to continue from your branch, as in fix this and add the missing tests. Would that be OK with you? |
Description
With
use_sde=Trueandsquash_output=True,StateDependentNoiseDistribution.log_probrecovered the Gaussian sample by inverting tanh of the stored action. In float32, tanh of a large sample rounds to exactly 1, and the clipped inverse (atanh(1 - eps)) is a different point. The PPO surrogate then differentiates that other point. At a unit-variance mean of 12 the reconstructed latent is about 8.32 instead of 12.78, and the mean gradient changes sign. The initial probability ratio stays 1 either way, because each route uses its own old likelihood.sampleandmodenow keep the pre-tanh sample when squashing. The rollout buffer stores it, and PPO and A2C pass it back intoevaluate_actions. Likelihoods that only have the squashed action still invert it, as before.Motivation and Context
Fixes the likelihood path reported in #2285. The unsaturated control is unchanged:
tests/test_sde.pypasses. A new test checks that a saturated float32 action inverts to a different point, and thatlog_probgiven the drawn sample matchesx - meanfor a unit-variance Normal, while inverting the stored action gives the opposite gradient sign.Types of changes
Checklist
docs/misc/changelog.md) (required).make format(required)make check-codestyleandmake lint(required)make pytestandmake typeboth pass. (required)make doc(required)tests/test_gsde_tanh_latent.pyandtests/test_sde.py: 15 passed, 2 skipped. I did not runmake format,make pytest, ormake doc.ruff checkon the edited files reported no errors.ruff format --checkalso wants to reflow lines inpolicies.pyandppo.pythat this change does not touch, so I left those as they are.