Skip to content

Commit 0cb72a7

Browse files
taufeeque9ernestum
authored andcommitted
Update adversarial algorithm
1 parent 40d87ef commit 0cb72a7

2 files changed

Lines changed: 124 additions & 8 deletions

File tree

src/imitation/algorithms/adversarial/common.py

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@
22
import abc
33
import dataclasses
44
import logging
5-
from typing import Callable, Iterable, Iterator, Mapping, Optional, Type, overload
5+
from typing import Callable, Iterable, Iterator, List, Mapping, Optional, Type, overload
66

77
import numpy as np
88
import torch as th
99
import torch.utils.tensorboard as thboard
1010
import tqdm
1111
from stable_baselines3.common import (
1212
base_class,
13+
callbacks,
1314
distributions,
15+
off_policy_algorithm,
1416
on_policy_algorithm,
1517
policies,
1618
vec_env,
@@ -20,6 +22,7 @@
2022

2123
from imitation.algorithms import base
2224
from imitation.data import buffer, rollout, types, wrappers
25+
from imitation.policies import replay_buffer_wrapper
2326
from imitation.rewards import reward_nets, reward_wrapper
2427
from imitation.util import logger, networks, util
2528

@@ -92,6 +95,47 @@ def compute_train_stats(
9295
}
9396

9497

98+
class TrainDiscriminatorCallback(callbacks.BaseCallback):
99+
"""Callback for training discriminator after collecting rollouts."""
100+
101+
def __init__(self, adversarial_trainer, *args, **kwargs):
102+
"""Builds TrainDiscriminatorCallback.
103+
104+
Args:
105+
*args: Passed through to `callbacks.BaseCallback`.
106+
**kwargs: Passed through to `callbacks.BaseCallback`.
107+
"""
108+
self.adversarial_trainer = adversarial_trainer
109+
self.gen_ctx_manager = None
110+
super().__init__(*args, **kwargs)
111+
112+
def _on_step(self) -> bool:
113+
return True
114+
115+
def _on_rollout_end(self) -> None:
116+
gen_trajs, ep_lens = self.adversarial_trainer.venv_buffering.pop_trajectories()
117+
self.adversarial_trainer._check_fixed_horizon(ep_lens)
118+
gen_samples = rollout.flatten_trajectories_with_rew(gen_trajs)
119+
self.adversarial_trainer._gen_replay_buffer.store(gen_samples)
120+
121+
for _ in range(self.adversarial_trainer.n_disc_updates_per_round):
122+
with networks.training(self.adversarial_trainer.reward_train):
123+
# switch to training mode (affects dropout, normalization)
124+
self.adversarial_trainer.train_disc()
125+
126+
# update the rollouts with the reward of the latest discriminator
127+
self.adversarial_trainer.update_rewards_of_rollouts()
128+
129+
# This is a hacky way to enable logger.accumulate_means for generator
130+
# This is done to avoid nested loggers of discriminator and generator
131+
self.gen_ctx_manager = self.adversarial_trainer.logger.accumulate_means("gen")
132+
self.gen_ctx_manager.__enter__()
133+
134+
def _on_training_end(self) -> None:
135+
assert self.gen_ctx_manager is not None
136+
self.gen_ctx_manager.__exit__(None, None, None)
137+
138+
95139
class AdversarialTrainer(base.DemonstrationAlgorithm[types.Transitions]):
96140
"""Base class for adversarial imitation learning algorithms like GAIL and AIRL."""
97141

@@ -228,16 +272,22 @@ def __init__(
228272

229273
self.venv_buffering = wrappers.BufferingWrapper(self.venv)
230274

275+
self.disc_trainer_callback = TrainDiscriminatorCallback(self)
231276
if debug_use_ground_truth:
232277
# Would use an identity reward fn here, but RewardFns can't see rewards.
233278
self.venv_wrapped = self.venv_buffering
234-
self.gen_callback = None
279+
self.gen_callback: List[callbacks.BaseCallback] = [
280+
self.disc_trainer_callback
281+
]
235282
else:
236283
self.venv_wrapped = reward_wrapper.RewardVecEnvWrapper(
237284
self.venv_buffering,
238285
reward_fn=self.reward_train.predict_processed,
239286
)
240-
self.gen_callback = self.venv_wrapped.make_log_callback()
287+
self.gen_callback = [
288+
self.venv_wrapped.make_log_callback(),
289+
self.disc_trainer_callback,
290+
]
241291
self.venv_train = self.venv_wrapped
242292

243293
self.gen_algo.set_env(self.venv_train)
@@ -314,6 +364,34 @@ def _next_expert_batch(self) -> Mapping:
314364
assert self._endless_expert_iterator is not None
315365
return next(self._endless_expert_iterator)
316366

367+
def update_rewards_of_rollouts(self) -> None:
368+
"""Updates the rewards of the rollouts using the latest discriminator."""
369+
if isinstance(self.gen_algo, on_policy_algorithm.OnPolicyAlgorithm):
370+
buffer = self.gen_algo.rollout_buffer
371+
assert buffer is not None
372+
reward_fn_inputs = replay_buffer_wrapper._rollout_buffer_to_reward_fn_input(
373+
self.gen_algo.rollout_buffer
374+
)
375+
rewards = self._reward_net.predict(**reward_fn_inputs)
376+
rewards = rewards.reshape(buffer.rewards.shape)
377+
last_values = buffer.advantages[-1] - buffer.rewards[-1] + buffer.values[-1]
378+
last_values = last_values / buffer.gamma
379+
# here we assume that the actual last_values cannot exactly be 0.0 and so if
380+
# last_values is 0.0 then we know that the episode terminated
381+
last_dones = last_values == 0.0
382+
self.gen_algo.rollout_buffer.rewards[:] = rewards
383+
self.gen_algo.rollout_buffer.compute_returns_and_advantage(
384+
th.tensor(last_values), last_dones
385+
)
386+
elif isinstance(self.gen_algo, off_policy_algorithm.OffPolicyAlgorithm):
387+
buffer = self.gen_algo.replay_buffer
388+
assert buffer is not None
389+
reward_fn_inputs = replay_buffer_wrapper._replay_buffer_to_reward_fn_input(
390+
buffer
391+
)
392+
rewards = self._reward_net.predict(**reward_fn_inputs)
393+
buffer.rewards[:] = rewards.reshape(buffer.rewards.shape)
394+
317395
def train_disc(
318396
self,
319397
*,
@@ -452,10 +530,6 @@ def train(
452530
)
453531
for r in tqdm.tqdm(range(0, n_rounds), desc="round"):
454532
self.train_gen(self.gen_train_timesteps)
455-
for _ in range(self.n_disc_updates_per_round):
456-
with networks.training(self.reward_train):
457-
# switch to training mode (affects dropout, normalization)
458-
self.train_disc()
459533
if callback:
460534
callback(r)
461535
self.logger.dump(self._global_step)

src/imitation/policies/replay_buffer_wrapper.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import numpy as np
66
from gymnasium import spaces
7-
from stable_baselines3.common.buffers import ReplayBuffer
7+
from stable_baselines3.common.buffers import ReplayBuffer, RolloutBuffer
88
from stable_baselines3.common.type_aliases import ReplayBufferSamples
99

1010
from imitation.rewards.reward_function import RewardFn
@@ -23,6 +23,48 @@ def _samples_to_reward_fn_input(
2323
)
2424

2525

26+
def _rollout_buffer_to_reward_fn_input(
27+
buffer: RolloutBuffer,
28+
) -> Mapping[str, np.ndarray]:
29+
"""Convert a sample from a rollout buffer to a numpy array."""
30+
assert buffer.observations is not None
31+
assert buffer.actions is not None
32+
obs = buffer.observations
33+
next_obs = obs[1:]
34+
next_obs = np.concatenate([next_obs, obs[-1:]], axis=0) # last obs not available
35+
actions = buffer.actions
36+
dones = buffer.episode_starts
37+
dones = np.roll(dones, -1, axis=0)
38+
dones[-1] = np.ones_like(dones[-1]) # last dones not available
39+
40+
return dict(
41+
state=obs.reshape(-1, *obs.shape[2:]),
42+
action=actions.reshape(-1, *actions.shape[2:]),
43+
next_state=next_obs.reshape(-1, *next_obs.shape[2:]),
44+
done=dones.reshape(-1),
45+
)
46+
47+
48+
def _replay_buffer_to_reward_fn_input(
49+
buffer: ReplayBuffer,
50+
) -> Mapping[str, np.ndarray]:
51+
"""Convert a sample from a replay buffer to a numpy array."""
52+
assert buffer.observations is not None
53+
assert buffer.next_observations is not None
54+
assert buffer.actions is not None
55+
obs = buffer.observations
56+
next_obs = buffer.next_observations
57+
actions = buffer.actions
58+
dones = buffer.dones
59+
60+
return dict(
61+
state=obs.reshape(-1, *obs.shape[2:]),
62+
action=actions.reshape(-1, *actions.shape[2:]),
63+
next_state=next_obs.reshape(-1, *next_obs.shape[2:]),
64+
done=dones.reshape(-1),
65+
)
66+
67+
2668
class ReplayBufferRewardWrapper(ReplayBuffer):
2769
"""Relabel the rewards in transitions sampled from a ReplayBuffer."""
2870

0 commit comments

Comments
 (0)