drlab 0.2.1__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. {drlab-0.2.1/src/drlab.egg-info → drlab-0.2.2}/PKG-INFO +1 -1
  2. {drlab-0.2.1 → drlab-0.2.2}/pyproject.toml +1 -1
  3. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/__init__.py +3 -1
  4. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/controllers/__init__.py +4 -2
  5. drlab-0.2.2/src/drlab/controllers/warmup.py +42 -0
  6. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/experiments/off_policy_experiment.py +43 -17
  7. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/learners/off_policy/sac.py +12 -2
  8. {drlab-0.2.1 → drlab-0.2.2/src/drlab.egg-info}/PKG-INFO +1 -1
  9. {drlab-0.2.1 → drlab-0.2.2}/src/drlab.egg-info/SOURCES.txt +4 -2
  10. {drlab-0.2.1 → drlab-0.2.2}/tests/test_controllers.py +46 -0
  11. {drlab-0.2.1 → drlab-0.2.2}/tests/test_learners.py +51 -1
  12. drlab-0.2.2/tests/test_off_policy_experiment.py +154 -0
  13. {drlab-0.2.1 → drlab-0.2.2}/tests/test_public_api.py +2 -1
  14. {drlab-0.2.1 → drlab-0.2.2}/LICENSE +0 -0
  15. {drlab-0.2.1 → drlab-0.2.2}/README.md +0 -0
  16. {drlab-0.2.1 → drlab-0.2.2}/setup.cfg +0 -0
  17. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/controllers/base.py +0 -0
  18. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/controllers/e_greedy.py +0 -0
  19. /drlab-0.2.1/src/drlab/controllers/gaussian_controller.py → /drlab-0.2.2/src/drlab/controllers/gaussian.py +0 -0
  20. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/controllers/greedy.py +0 -0
  21. /drlab-0.2.1/src/drlab/controllers/stochastic_controller.py → /drlab-0.2.2/src/drlab/controllers/stochastic.py +0 -0
  22. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/experiments/__init__.py +0 -0
  23. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/experiments/on_policy_experiment.py +0 -0
  24. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/learners/__init__.py +0 -0
  25. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/learners/off_policy/__init__.py +0 -0
  26. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/learners/off_policy/base.py +0 -0
  27. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/learners/off_policy/configs.py +0 -0
  28. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/learners/off_policy/dqn.py +0 -0
  29. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/learners/on_policy/__init__.py +0 -0
  30. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/learners/on_policy/actor_critic.py +0 -0
  31. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/learners/on_policy/base.py +0 -0
  32. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/learners/on_policy/configs.py +0 -0
  33. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/learners/on_policy/ppo.py +0 -0
  34. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/learners/on_policy/reinforce.py +0 -0
  35. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/replay/__init__.py +0 -0
  36. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/replay/replay_buffer.py +0 -0
  37. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/replay/transition_batch.py +0 -0
  38. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/runners/__init__.py +0 -0
  39. {drlab-0.2.1 → drlab-0.2.2}/src/drlab/runners/runner.py +0 -0
  40. {drlab-0.2.1 → drlab-0.2.2}/src/drlab.egg-info/dependency_links.txt +0 -0
  41. {drlab-0.2.1 → drlab-0.2.2}/src/drlab.egg-info/requires.txt +0 -0
  42. {drlab-0.2.1 → drlab-0.2.2}/src/drlab.egg-info/top_level.txt +0 -0
  43. {drlab-0.2.1 → drlab-0.2.2}/tests/test_replay.py +0 -0
  44. {drlab-0.2.1 → drlab-0.2.2}/tests/test_runner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drlab
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Deep Reinforcement Learning kit for research.
5
5
  Author: Tomas Osarte
6
6
  Keywords: reinforcement-learning,deep-learning,pytorch,gymnasium
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "drlab"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "Deep Reinforcement Learning kit for research."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -6,6 +6,7 @@ from drlab.controllers import (
6
6
  GaussianController,
7
7
  GreedyController,
8
8
  StochasticController,
9
+ WarmupController,
9
10
  )
10
11
  from drlab.experiments import (
11
12
  OnPolicyExperiment,
@@ -34,7 +35,7 @@ from drlab.learners import (
34
35
  from drlab.replay import ReplayBuffer, TransitionBatch
35
36
  from drlab.runners import Runner
36
37
 
37
- __version__ = "0.2.1"
38
+ __version__ = "0.2.2"
38
39
 
39
40
  __all__ = [
40
41
  "__version__",
@@ -68,4 +69,5 @@ __all__ = [
68
69
  "StochasticController",
69
70
  "TransitionBatch",
70
71
  "ValueTargets",
72
+ "WarmupController",
71
73
  ]
@@ -1,8 +1,9 @@
1
1
  from .base import ContinuousActionController, Controller, DiscreteActionController
2
2
  from .greedy import GreedyController
3
3
  from .e_greedy import EpsilonGreedyController
4
- from .stochastic_controller import StochasticController
5
- from .gaussian_controller import GaussianController
4
+ from .stochastic import StochasticController
5
+ from .gaussian import GaussianController
6
+ from .warmup import WarmupController
6
7
 
7
8
  __all__ = [
8
9
  "ContinuousActionController",
@@ -12,4 +13,5 @@ __all__ = [
12
13
  "GaussianController",
13
14
  "GreedyController",
14
15
  "StochasticController",
16
+ "WarmupController",
15
17
  ]
@@ -0,0 +1,42 @@
1
+ from typing import Any
2
+
3
+ import numpy as np
4
+ import torch as th
5
+
6
+ from .base import ContinuousActionController, Controller, DiscreteActionController
7
+
8
+
9
+ class WarmupController(ContinuousActionController, DiscreteActionController):
10
+ def __init__(self, controller: Controller, action_space: Any, warmup_steps: int):
11
+ self.controller = controller
12
+ self.action_space = action_space
13
+ self.warmup_steps = warmup_steps
14
+ self.steps = 0
15
+ self.model = controller.model
16
+
17
+ if hasattr(controller, "num_actions"):
18
+ self.num_actions = controller.num_actions
19
+ if hasattr(controller, "action_dim"):
20
+ self.action_dim = controller.action_dim
21
+
22
+ if warmup_steps < 0:
23
+ raise ValueError("warmup_steps must be >= 0.")
24
+
25
+ def choose(
26
+ self,
27
+ obs: th.Tensor,
28
+ **kwargs,
29
+ ) -> th.Tensor:
30
+ if self.steps >= self.warmup_steps:
31
+ return self.controller.choose(obs, **kwargs)
32
+
33
+ batch_size = obs.shape[0]
34
+ self.steps += batch_size
35
+ actions = [self.action_space.sample() for _ in range(batch_size)]
36
+ return th.as_tensor(np.asarray(actions), device=obs.device)
37
+
38
+ def probabilities(self, obs: th.Tensor, **kwargs) -> th.Tensor:
39
+ if self.steps >= self.warmup_steps:
40
+ return self.controller.probabilities(obs, **kwargs)
41
+
42
+ return th.ones((obs.shape[0], self.num_actions), device=obs.device) / self.num_actions
@@ -2,12 +2,12 @@ import numpy as np
2
2
  from tqdm import tqdm
3
3
  import gymnasium as gym
4
4
  from dataclasses import dataclass
5
- from typing import Callable, Tuple
5
+ from typing import Callable
6
6
  from torch.utils.tensorboard import SummaryWriter
7
7
 
8
8
  from drlab.learners import OffPolicyLearner, SACLearner
9
9
  from drlab.runners import Runner
10
- from drlab.controllers import Controller
10
+ from drlab.controllers import Controller, WarmupController
11
11
  from drlab.replay import TransitionBatch, ReplayBuffer
12
12
 
13
13
  @dataclass
@@ -24,6 +24,8 @@ class OffPolicyExperimentConfig:
24
24
  grad_repeats: int = 1
25
25
  step_callback: Callable[[int], None] | None = None
26
26
  step_callback_interval: int | None = None
27
+ learning_starts: int | None = None
28
+ warmup_steps: int = 0
27
29
 
28
30
 
29
31
  class OffPolicyExperiment:
@@ -44,9 +46,22 @@ class OffPolicyExperiment:
44
46
  self.run_steps = config.run_steps
45
47
  self.step_callback = config.step_callback
46
48
  self.step_callback_interval = config.step_callback_interval
49
+ self.steps = 0
50
+ self.learning_starts = (
51
+ config.batch_size if config.learning_starts is None else config.learning_starts
52
+ )
53
+ self.warmup_steps = config.warmup_steps
54
+
55
+ self._validate_config()
47
56
 
48
57
  # Init drl components
49
58
  continous_actions = isinstance(learner, SACLearner)
59
+ if self.warmup_steps > 0:
60
+ controller = WarmupController(
61
+ controller,
62
+ env.action_space,
63
+ warmup_steps=self.warmup_steps,
64
+ )
50
65
  self.runner = Runner(
51
66
  env,
52
67
  controller,
@@ -73,9 +88,17 @@ class OffPolicyExperiment:
73
88
  self.writer = SummaryWriter(log_dir=config.log_dir)
74
89
  self.experiment_name = config.experiment_name
75
90
 
91
+
92
+ def _validate_config(self):
76
93
  if self.step_callback is not None and self.step_callback_interval is None:
77
94
  raise ValueError("step_callback_interval must be set when step_callback is provided.")
78
95
 
96
+ if self.learning_starts < self.batch_size:
97
+ raise ValueError("learning_starts must be greater than or equal to batch_size.")
98
+
99
+ if self.warmup_steps < 0:
100
+ raise ValueError("warmup_steps must be greater than or equal to 0.")
101
+
79
102
  def _make_minibatch(self, batch: TransitionBatch, last_episode: TransitionBatch | None) -> TransitionBatch:
80
103
 
81
104
  # No replay buffer
@@ -97,7 +120,11 @@ class OffPolicyExperiment:
97
120
  rest = self.replay_buffer.sample(self.batch_size - ep_len)
98
121
  return rest.cat(episode)
99
122
 
100
- def _learn_from_batch(self, batch: TransitionBatch, last_episode: TransitionBatch | None) -> float:
123
+ def _learn_from_batch(
124
+ self,
125
+ batch: TransitionBatch,
126
+ last_episode: TransitionBatch | None,
127
+ ) -> float:
101
128
  # 1) Add batch to replay buffer
102
129
  self.replay_buffer.add(
103
130
  states=batch.states,
@@ -107,7 +134,7 @@ class OffPolicyExperiment:
107
134
  next_states=batch.next_states,
108
135
  returns=batch.returns,
109
136
  )
110
- if self.replay_buffer.size < self.batch_size:
137
+ if self.steps < self.learning_starts:
111
138
  return 0.0
112
139
 
113
140
  # 2) train repeats
@@ -121,13 +148,13 @@ class OffPolicyExperiment:
121
148
  return total_loss / self.grad_repeats
122
149
 
123
150
  def run(self):
124
-
125
- steps = 0
151
+
152
+ self.steps = 0
126
153
  if self.step_callback is not None:
127
- self.step_callback(steps)
154
+ self.step_callback(self.steps)
128
155
  next_callback_step = self.step_callback_interval
129
156
  pbar = tqdm(total=self.max_steps, desc=self.experiment_name, dynamic_ncols=True, mininterval=1.0)
130
- while steps < self.max_steps:
157
+ while self.steps < self.max_steps:
131
158
 
132
159
  # 1. Run batch of transitions
133
160
  batch, ep_returns, ep_lengths, last_episode = self.runner.run(
@@ -135,16 +162,20 @@ class OffPolicyExperiment:
135
162
  as_numpy=True,
136
163
  )
137
164
 
165
+ step_inc = batch.states.shape[0]
166
+ self.steps += step_inc
167
+ pbar.update(step_inc)
168
+
138
169
  # 2. Learn from batch
139
170
  loss = self._learn_from_batch(batch, last_episode)
140
171
 
141
172
  # 3. Log results
142
- self.writer.add_scalar("Loss", loss, steps)
173
+ self.writer.add_scalar("Loss", loss, self.steps)
143
174
  if ep_returns:
144
175
  mean_return = np.mean(ep_returns)
145
176
  mean_length = np.mean(ep_lengths)
146
- self.writer.add_scalar("Episode return", mean_return, steps)
147
- self.writer.add_scalar("Episode Length", mean_length, steps)
177
+ self.writer.add_scalar("Episode return", mean_return, self.steps)
178
+ self.writer.add_scalar("Episode Length", mean_length, self.steps)
148
179
 
149
180
  pbar.set_postfix({
150
181
  "loss": f"{loss:.3f}",
@@ -152,12 +183,7 @@ class OffPolicyExperiment:
152
183
  "len": f"{mean_length:.1f}",
153
184
  }, refresh=False)
154
185
 
155
- # 3. Update steps & progress bar
156
- step_inc = batch.states.shape[0]
157
- steps += step_inc
158
- pbar.update(step_inc)
159
-
160
- while self.step_callback is not None and steps >= next_callback_step:
186
+ while self.step_callback is not None and self.steps >= next_callback_step:
161
187
  self.step_callback(next_callback_step)
162
188
  next_callback_step += self.step_callback_interval
163
189
 
@@ -145,7 +145,16 @@ class SACLearner(OffPolicyLearner):
145
145
  new_q2 = self.critic2(new_action_states)
146
146
  new_q = th.min(new_q1, new_q2)
147
147
 
148
- actor_loss = (self.alpha.detach() * log_probs - new_q).mean()
148
+ policy_loss = (self.alpha.detach() * log_probs - new_q).mean()
149
+ reg_loss = self.regularization_loss(
150
+ self.actor,
151
+ rewards=rewards,
152
+ dones=dones,
153
+ states=states,
154
+ actions=actions,
155
+ next_states=next_states,
156
+ )
157
+ actor_loss = policy_loss + reg_loss
149
158
  self.optimize(
150
159
  actor_loss,
151
160
  optimizer=self.actor_optimizer,
@@ -176,9 +185,10 @@ class SACLearner(OffPolicyLearner):
176
185
 
177
186
  loss = actor_loss + critic_loss + alpha_loss
178
187
  self.last_losses = {
179
- "actor": float(actor_loss.item()),
188
+ "actor": float(policy_loss.item()),
180
189
  "critic": float(critic_loss.item()),
181
190
  "alpha": float(alpha_loss.item()),
191
+ "regularization": float(reg_loss.item()),
182
192
  "total": float(loss.item()),
183
193
  }
184
194
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drlab
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Deep Reinforcement Learning kit for research.
5
5
  Author: Tomas Osarte
6
6
  Keywords: reinforcement-learning,deep-learning,pytorch,gymnasium
@@ -10,9 +10,10 @@ src/drlab.egg-info/top_level.txt
10
10
  src/drlab/controllers/__init__.py
11
11
  src/drlab/controllers/base.py
12
12
  src/drlab/controllers/e_greedy.py
13
- src/drlab/controllers/gaussian_controller.py
13
+ src/drlab/controllers/gaussian.py
14
14
  src/drlab/controllers/greedy.py
15
- src/drlab/controllers/stochastic_controller.py
15
+ src/drlab/controllers/stochastic.py
16
+ src/drlab/controllers/warmup.py
16
17
  src/drlab/experiments/__init__.py
17
18
  src/drlab/experiments/off_policy_experiment.py
18
19
  src/drlab/experiments/on_policy_experiment.py
@@ -35,6 +36,7 @@ src/drlab/runners/__init__.py
35
36
  src/drlab/runners/runner.py
36
37
  tests/test_controllers.py
37
38
  tests/test_learners.py
39
+ tests/test_off_policy_experiment.py
38
40
  tests/test_public_api.py
39
41
  tests/test_replay.py
40
42
  tests/test_runner.py
@@ -1,5 +1,6 @@
1
1
  import unittest
2
2
 
3
+ import numpy as np
3
4
  import torch as th
4
5
 
5
6
  from drlab.controllers import (
@@ -9,6 +10,7 @@ from drlab.controllers import (
9
10
  GaussianController,
10
11
  GreedyController,
11
12
  StochasticController,
13
+ WarmupController,
12
14
  )
13
15
 
14
16
 
@@ -25,6 +27,14 @@ class FixedGaussianOutput(FixedLogits):
25
27
  pass
26
28
 
27
29
 
30
+ class FixedActionSpace:
31
+ def __init__(self, action):
32
+ self.action = action
33
+
34
+ def sample(self):
35
+ return self.action
36
+
37
+
28
38
  class ControllerTest(unittest.TestCase):
29
39
  def test_discrete_controllers_share_discrete_base(self):
30
40
  model = FixedLogits([0.1, 2.0, -1.0])
@@ -115,3 +125,39 @@ class ControllerTest(unittest.TestCase):
115
125
  self.assertTrue(th.allclose(probs.sum(dim=-1), th.ones(5)))
116
126
  self.assertEqual(actions.shape, (5,))
117
127
  self.assertTrue(th.all((0 <= actions) & (actions < 3)))
128
+
129
+ def test_warmup_controller_samples_actions_before_delegating(self):
130
+ model = FixedLogits([1.0, 0.0, 0.0])
131
+ greedy = GreedyController(model, num_actions=3)
132
+ controller = WarmupController(greedy, FixedActionSpace(2), warmup_steps=2)
133
+ obs = th.zeros(1, 2)
134
+
135
+ self.assertIsInstance(controller, ContinuousActionController)
136
+ self.assertIsInstance(controller, DiscreteActionController)
137
+ self.assertTrue(th.equal(controller.choose(obs), th.tensor([2])))
138
+ self.assertTrue(th.equal(controller.choose(obs), th.tensor([2])))
139
+ self.assertTrue(th.equal(controller.choose(obs), th.tensor([0])))
140
+ self.assertEqual(controller.steps, 2)
141
+
142
+ def test_warmup_controller_returns_uniform_discrete_probabilities(self):
143
+ model = FixedLogits([1.0, 0.0, 0.0])
144
+ greedy = GreedyController(model, num_actions=3)
145
+ controller = WarmupController(greedy, FixedActionSpace(2), warmup_steps=1)
146
+
147
+ probs = controller.probabilities(th.zeros(1, 2))
148
+
149
+ self.assertTrue(th.allclose(probs, th.full((1, 3), 1 / 3)))
150
+
151
+ def test_warmup_controller_samples_continuous_actions(self):
152
+ model = FixedGaussianOutput([0.0, 0.0, -3.0, -3.0])
153
+ gaussian = GaussianController(model, action_dim=2, deterministic=True)
154
+ action_space = FixedActionSpace(np.asarray([0.5, -0.25], dtype=np.float32))
155
+ controller = WarmupController(gaussian, action_space, warmup_steps=1)
156
+ obs = th.zeros(2, 2)
157
+
158
+ warmup_actions = controller.choose(obs)
159
+ policy_actions = controller.choose(obs)
160
+
161
+ self.assertEqual(warmup_actions.shape, (2, 2))
162
+ self.assertTrue(th.allclose(warmup_actions[0], th.tensor([0.5, -0.25])))
163
+ self.assertTrue(th.allclose(policy_actions, th.zeros(2, 2)))
@@ -101,9 +101,59 @@ class LearnerSmokeTest(unittest.TestCase):
101
101
  self.assertTrue(parameters_changed(actor, before))
102
102
  self.assertEqual(
103
103
  set(learner.last_losses),
104
- {"actor", "critic", "alpha", "total"},
104
+ {"actor", "critic", "alpha", "regularization", "total"},
105
105
  )
106
106
 
107
+ def test_sac_actor_regularizer_contributes_to_loss(self):
108
+ th.manual_seed(0)
109
+ obs_dim = 4
110
+ action_dim = 2
111
+ actor = th.nn.Sequential(
112
+ th.nn.Linear(obs_dim, 8),
113
+ th.nn.ReLU(),
114
+ th.nn.Linear(8, 2 * action_dim),
115
+ )
116
+ critic1 = th.nn.Sequential(
117
+ th.nn.Linear(obs_dim + action_dim, 8),
118
+ th.nn.ReLU(),
119
+ th.nn.Linear(8, 1),
120
+ )
121
+ critic2 = th.nn.Sequential(
122
+ th.nn.Linear(obs_dim + action_dim, 8),
123
+ th.nn.ReLU(),
124
+ th.nn.Linear(8, 1),
125
+ )
126
+ calls = []
127
+
128
+ def actor_l2(model, rewards, dones, states, actions, next_states):
129
+ calls.append(model)
130
+ return sum(param.square().sum() for param in model.parameters())
131
+
132
+ learner = SACLearner(
133
+ actor=actor,
134
+ critic1=critic1,
135
+ critic2=critic2,
136
+ actor_optimizer=th.optim.SGD(actor.parameters(), lr=0.05),
137
+ critic1_optimizer=th.optim.SGD(critic1.parameters(), lr=0.05),
138
+ critic2_optimizer=th.optim.SGD(critic2.parameters(), lr=0.05),
139
+ config=SACConfig(
140
+ action_shape=(action_dim,),
141
+ regularizers=[actor_l2],
142
+ reg_lams=[0.01],
143
+ ),
144
+ )
145
+
146
+ learner.train(
147
+ rewards=th.tensor([[1.0], [0.0], [1.0]]),
148
+ dones=th.tensor([[False], [True], [False]]),
149
+ states=th.randn(3, obs_dim),
150
+ actions=th.randn(3, action_dim).tanh(),
151
+ next_states=th.randn(3, obs_dim),
152
+ )
153
+
154
+ self.assertEqual(calls, [actor])
155
+ self.assertGreater(learner.last_losses["regularization"], 0.0)
156
+
107
157
  def test_reinforce_train_step_returns_float_and_updates_parameters(self):
108
158
  th.manual_seed(0)
109
159
  actor = th.nn.Sequential(
@@ -0,0 +1,154 @@
1
+ import tempfile
2
+ import unittest
3
+
4
+ import gymnasium as gym
5
+ import numpy as np
6
+ import torch as th
7
+
8
+ from drlab.controllers import GreedyController, WarmupController
9
+ from drlab.experiments import OffPolicyExperiment, OffPolicyExperimentConfig
10
+ from drlab.learners import OffPolicyConfig, OffPolicyLearner
11
+ from drlab.replay import TransitionBatch
12
+
13
+
14
+ class FixedLogits(th.nn.Module):
15
+ def __init__(self):
16
+ super().__init__()
17
+ self.register_buffer("logits", th.tensor([1.0, 0.0]))
18
+
19
+ def forward(self, obs):
20
+ return self.logits.unsqueeze(0).expand(obs.shape[0], -1)
21
+
22
+
23
+ class OneStepEnv(gym.Env):
24
+ metadata = {}
25
+
26
+ def __init__(self):
27
+ self.observation_space = gym.spaces.Box(
28
+ low=-np.inf,
29
+ high=np.inf,
30
+ shape=(1,),
31
+ dtype=np.float32,
32
+ )
33
+ self.action_space = gym.spaces.Discrete(2)
34
+
35
+ def reset(self, *, seed=None, options=None):
36
+ super().reset(seed=seed)
37
+ return np.asarray([0.0], dtype=np.float32), {}
38
+
39
+ def step(self, action):
40
+ obs = np.asarray([0.0], dtype=np.float32)
41
+ return obs, 0.0, True, False, {}
42
+
43
+
44
+ class CountingLearner(OffPolicyLearner):
45
+ def __init__(self):
46
+ super().__init__(OffPolicyConfig())
47
+ self.train_calls = 0
48
+
49
+ def train(
50
+ self,
51
+ rewards: th.Tensor,
52
+ dones: th.Tensor,
53
+ states: th.Tensor,
54
+ actions: th.Tensor,
55
+ next_states: th.Tensor,
56
+ ) -> float:
57
+ self.train_calls += 1
58
+ return 7.0
59
+
60
+
61
+ def one_transition() -> TransitionBatch:
62
+ return TransitionBatch(
63
+ states=np.asarray([[0.0]], dtype=np.float32),
64
+ actions=np.asarray([[0]], dtype=np.int64),
65
+ rewards=np.asarray([[1.0]], dtype=np.float32),
66
+ dones=np.asarray([[False]], dtype=np.bool_),
67
+ next_states=np.asarray([[1.0]], dtype=np.float32),
68
+ returns=np.asarray([[1.0]], dtype=np.float32),
69
+ )
70
+
71
+
72
+ class OffPolicyExperimentTest(unittest.TestCase):
73
+ def make_experiment(self, config: OffPolicyExperimentConfig) -> OffPolicyExperiment:
74
+ env = OneStepEnv()
75
+ learner = CountingLearner()
76
+ controller = GreedyController(FixedLogits(), num_actions=2)
77
+ return OffPolicyExperiment(env, controller, learner, config)
78
+
79
+ def test_learning_starts_default_tracks_batch_size(self):
80
+ with tempfile.TemporaryDirectory() as log_dir:
81
+ experiment = self.make_experiment(
82
+ OffPolicyExperimentConfig(
83
+ max_steps=1,
84
+ batch_size=256,
85
+ log_dir=log_dir,
86
+ )
87
+ )
88
+ try:
89
+ self.assertEqual(experiment.learning_starts, 256)
90
+ finally:
91
+ experiment.writer.close()
92
+
93
+ def test_learning_starts_uses_current_steps_after_increment(self):
94
+ with tempfile.TemporaryDirectory() as log_dir:
95
+ experiment = self.make_experiment(
96
+ OffPolicyExperimentConfig(
97
+ max_steps=2,
98
+ batch_size=2,
99
+ replay_buffer_size=4,
100
+ use_last_episode=False,
101
+ log_dir=log_dir,
102
+ )
103
+ )
104
+ try:
105
+ batch = one_transition()
106
+
107
+ experiment.steps = 1
108
+ loss = experiment._learn_from_batch(batch, None)
109
+ self.assertEqual(loss, 0.0)
110
+ self.assertEqual(experiment.learner.train_calls, 0)
111
+
112
+ experiment.steps = 2
113
+ loss = experiment._learn_from_batch(batch, None)
114
+ self.assertEqual(loss, 7.0)
115
+ self.assertEqual(experiment.learner.train_calls, 1)
116
+ finally:
117
+ experiment.writer.close()
118
+
119
+ def test_warmup_steps_wraps_controller(self):
120
+ with tempfile.TemporaryDirectory() as log_dir:
121
+ experiment = self.make_experiment(
122
+ OffPolicyExperimentConfig(
123
+ max_steps=1,
124
+ warmup_steps=10,
125
+ log_dir=log_dir,
126
+ )
127
+ )
128
+ try:
129
+ self.assertEqual(experiment.warmup_steps, 10)
130
+ self.assertIsInstance(experiment.runner.controller, WarmupController)
131
+ self.assertEqual(experiment.runner.controller.warmup_steps, 10)
132
+ finally:
133
+ experiment.writer.close()
134
+
135
+ def test_zero_warmup_steps_keeps_original_controller(self):
136
+ with tempfile.TemporaryDirectory() as log_dir:
137
+ experiment = self.make_experiment(
138
+ OffPolicyExperimentConfig(max_steps=1, log_dir=log_dir)
139
+ )
140
+ try:
141
+ self.assertNotIsInstance(experiment.runner.controller, WarmupController)
142
+ finally:
143
+ experiment.writer.close()
144
+
145
+ def test_negative_warmup_steps_is_rejected(self):
146
+ with tempfile.TemporaryDirectory() as log_dir:
147
+ with self.assertRaisesRegex(ValueError, "warmup_steps"):
148
+ self.make_experiment(
149
+ OffPolicyExperimentConfig(
150
+ max_steps=1,
151
+ warmup_steps=-1,
152
+ log_dir=log_dir,
153
+ )
154
+ )
@@ -34,9 +34,10 @@ class PublicAPITest(unittest.TestCase):
34
34
  "StochasticController",
35
35
  "TransitionBatch",
36
36
  "ValueTargets",
37
+ "WarmupController",
37
38
  }
38
39
 
39
- self.assertEqual(drlab.__version__, "0.2.1")
40
+ self.assertEqual(drlab.__version__, "0.2.2")
40
41
  self.assertTrue(expected.issubset(set(drlab.__all__)))
41
42
  for name in expected:
42
43
  self.assertTrue(hasattr(drlab, name), name)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes