rl-mind 0.1.0__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.
- rl_mind-0.1.0/PKG-INFO +313 -0
- rl_mind-0.1.0/README.md +85 -0
- rl_mind-0.1.0/pyproject.toml +74 -0
- rl_mind-0.1.0/rl_mind.md +290 -0
- rl_mind-0.1.0/setup.cfg +4 -0
- rl_mind-0.1.0/src/rl_mind/__init__.py +55 -0
- rl_mind-0.1.0/src/rl_mind/collectors.py +195 -0
- rl_mind-0.1.0/src/rl_mind/core.py +152 -0
- rl_mind-0.1.0/src/rl_mind/data.py +179 -0
- rl_mind-0.1.0/src/rl_mind/env.py +282 -0
- rl_mind-0.1.0/src/rl_mind/envs.py +76 -0
- rl_mind-0.1.0/src/rl_mind/evaluation.py +167 -0
- rl_mind-0.1.0/src/rl_mind/mazemdp_env.py +182 -0
- rl_mind-0.1.0/src/rl_mind/nn.py +44 -0
- rl_mind-0.1.0/src/rl_mind/notebook.py +118 -0
- rl_mind-0.1.0/src/rl_mind/py.typed +0 -0
- rl_mind-0.1.0/src/rl_mind.egg-info/PKG-INFO +313 -0
- rl_mind-0.1.0/src/rl_mind.egg-info/SOURCES.txt +20 -0
- rl_mind-0.1.0/src/rl_mind.egg-info/dependency_links.txt +1 -0
- rl_mind-0.1.0/src/rl_mind.egg-info/requires.txt +7 -0
- rl_mind-0.1.0/src/rl_mind.egg-info/top_level.txt +1 -0
- rl_mind-0.1.0/tests/test_rl_mind.py +406 -0
rl_mind-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rl-mind
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A small, typed reinforcement-learning toolkit for the Master MIND RL practicals
|
|
5
|
+
Author-email: Benjamin Piwowarski <benjamin@piwowarski.fr>
|
|
6
|
+
Project-URL: Documentation, https://pypi.org/project/rl-mind/
|
|
7
|
+
Keywords: reinforcement-learning,gymnasium,torch,teaching
|
|
8
|
+
Classifier: Intended Audience :: Education
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Topic :: Education
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: gymnasium[box2d,classic-control,mujoco]>=1.2.0
|
|
17
|
+
Requires-Dist: ipython>=8
|
|
18
|
+
Requires-Dist: mazemdp>=1.3.0
|
|
19
|
+
Requires-Dist: moviepy>=1.0
|
|
20
|
+
Requires-Dist: numpy>=1.26
|
|
21
|
+
Requires-Dist: tensorboard>=2.19
|
|
22
|
+
Requires-Dist: torch>=2.4
|
|
23
|
+
|
|
24
|
+
# `rl_mind` — a small, typed RL toolkit
|
|
25
|
+
|
|
26
|
+
`rl_mind` is the minimal reinforcement-learning library used by the RL
|
|
27
|
+
practicals. It replaces the heavier BBRL machinery with a handful of **typed**
|
|
28
|
+
building blocks: every piece of data an agent exchanges with its environment is
|
|
29
|
+
a plain (frozen) dataclass of `torch.Tensor`s, so there are no untyped string
|
|
30
|
+
dictionaries and the editor can autocomplete field names.
|
|
31
|
+
|
|
32
|
+
The library is published on PyPI as
|
|
33
|
+
[`rl-mind`](https://pypi.org/project/rl-mind/) (`pip install rl-mind`; the
|
|
34
|
+
practicals install it for you) and the notebooks simply import it. Importing the
|
|
35
|
+
package has **no side effect** — in particular the extra gymnasium environments
|
|
36
|
+
are only registered by an explicit `import rl_mind.envs` (see below).
|
|
37
|
+
|
|
38
|
+
- [Actors and actions](#actors-and-actions) (`rl_mind.core`)
|
|
39
|
+
- [Environments](#environments) (`rl_mind.env`, `rl_mind.envs`)
|
|
40
|
+
- [Data containers](#data-containers) (`rl_mind.data`)
|
|
41
|
+
- [Collectors](#collectors) (`rl_mind.collectors`) — with the comparison table
|
|
42
|
+
- [Evaluation](#evaluation) (`rl_mind.evaluation`)
|
|
43
|
+
- [Helpers](#helpers) (`rl_mind.nn`, `rl_mind.notebook`)
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Actors and actions
|
|
48
|
+
|
|
49
|
+
`rl_mind.core`
|
|
50
|
+
|
|
51
|
+
| Class | Role |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `TensorStruct` | Base class: a frozen dataclass of tensors. Supports tensor-like operations applied field by field — `struct[idx]` (index/slice/mask), `TensorStruct.cat([...])`, `TensorStruct.stack([...])`, `struct.set_(idx, value)`. Recurses into nested `TensorStruct` fields. |
|
|
54
|
+
| `Action` | What an actor returns for a batch of observations. One field: `value` — the action tensor (`[B, action_dim]` for continuous actions, `[B]` for discrete). |
|
|
55
|
+
| `StochasticAction` | An `Action` that also stores `log_prob` (`[B]`), the log-probability of the sampled action. Used by REINFORCE / PPO / SAC. |
|
|
56
|
+
| `Actor[A]` | A `torch.nn.Module` mapping a batch of observations to an action of type `A`. `actor(obs)` is the **training-time** behavior (sampling, exploration noise); `actor.act(obs)` is the **deterministic evaluation** behavior (defaults to `forward().value`). |
|
|
57
|
+
| `ActionT` | The generic type variable (`TypeVar` bound to `Action`) that parameterizes `Actor`, `Transitions`, collectors, ... so the action type flows through the API. |
|
|
58
|
+
|
|
59
|
+
### `TensorStruct` in practice
|
|
60
|
+
|
|
61
|
+
`TensorStruct` is the foundation of every data container in the library
|
|
62
|
+
(`Action`, `Transitions`, `Episode`, `Rollout`, ...). The idea: you write a
|
|
63
|
+
plain frozen dataclass whose fields are tensors sharing a common leading (batch)
|
|
64
|
+
dimension, and you get tensor-like operations that apply to **all fields at
|
|
65
|
+
once**, while each field keeps its name and type.
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
@dataclass(frozen=True)
|
|
69
|
+
class Transitions(TensorStruct):
|
|
70
|
+
obs: Tensor # [N, obs_dim]
|
|
71
|
+
action: Action # a nested TensorStruct
|
|
72
|
+
reward: Tensor # [N]
|
|
73
|
+
next_obs: Tensor # [N, obs_dim]
|
|
74
|
+
terminated: Tensor # [N] (bool)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Indexing / slicing / masking** — `struct[index]` applies `index` to every
|
|
78
|
+
tensor field along the batch dimension and returns a new struct. `index` can be
|
|
79
|
+
anything a tensor accepts:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
batch = buffer.sample(64) # a Transitions with len(batch) == 64
|
|
83
|
+
batch[0] # int -> a single transition
|
|
84
|
+
batch[:32] # slice -> first 32 transitions
|
|
85
|
+
batch[torch.tensor([0, 5, 9])] # fancy -> transitions 0, 5, 9
|
|
86
|
+
batch[~batch.terminated] # bool mask-> only the non-terminal ones
|
|
87
|
+
len(batch) # 64 (size of the leading dimension)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Concatenating and stacking** — the two class methods build a big struct from
|
|
91
|
+
small ones:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
Transitions.cat([chunk_a, chunk_b]) # concatenate along the batch dim: N_a + N_b
|
|
95
|
+
Action.stack([a0, a1, a2]) # add a NEW leading dim: 3 actions -> [3, ...]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`stack` is exactly how the collectors turn a list of per-step actions into a
|
|
99
|
+
time-indexed `[T, ...]` tensor; `cat` is how `TransitionCollector` merges the
|
|
100
|
+
per-step chunks it records into one flat batch.
|
|
101
|
+
|
|
102
|
+
**Nesting recurses automatically** — a field that is itself a `TensorStruct`
|
|
103
|
+
(here `action`) is sliced/stacked along with the rest, so the alignment between
|
|
104
|
+
observations and actions can never drift:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
sub = batch[mask] # slices batch.obs AND batch.action.value together
|
|
108
|
+
sub.action.log_prob # still lined up with sub.obs, sub.reward, ...
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**In-place writes** — instances are frozen (immutable), so `[]`, `cat` and
|
|
112
|
+
`stack` all return *new* structs. The one mutating operation is `set_(index,
|
|
113
|
+
value)`, used by `ReplayBuffer` to overwrite slots of its preallocated storage:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
storage.set_(indices, transitions) # write a batch of transitions at `indices`
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Because every container shares this behaviour, the learning code reads the same
|
|
120
|
+
way whether the batch came from a replay buffer, an episode or a rollout —
|
|
121
|
+
`batch.reward`, `batch.action.value`, `batch.terminated` are always named,
|
|
122
|
+
typed, and mutually aligned.
|
|
123
|
+
|
|
124
|
+
The generic parameter is what makes the typing pay off: an `Actor[StochasticAction]`
|
|
125
|
+
guarantees that the actions it produces carry a `.log_prob`, and the type
|
|
126
|
+
checker will flag a `TransitionCollector[StochasticAction]` whose batches you try
|
|
127
|
+
to use as if they had none.
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
class DiscretePolicy(Actor[StochasticAction]):
|
|
131
|
+
def dist(self, obs): return torch.distributions.Categorical(logits=self.model(obs))
|
|
132
|
+
def forward(self, obs):
|
|
133
|
+
d = self.dist(obs); a = d.sample()
|
|
134
|
+
return StochasticAction(value=a, log_prob=d.log_prob(a))
|
|
135
|
+
def act(self, obs): return self.model(obs).argmax(-1) # deterministic
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Environments
|
|
141
|
+
|
|
142
|
+
`rl_mind.env`, `rl_mind.envs`
|
|
143
|
+
|
|
144
|
+
| Class / symbol | Role |
|
|
145
|
+
|---|---|
|
|
146
|
+
| `VecEnv` | Runs `num_envs` copies of a gymnasium environment in parallel, talking **torch tensors**. `reset()` → `[B, obs_dim]`; `step(actions)` → `EnvStep`. Exposes `observation_dim` (flat `Box` spaces) or `n_states` (tabular `Discrete` spaces), `action_dim` / `n_actions`, `is_continuous`, `num_envs`, `env_name`, `same_step_reset`. Extra keyword arguments (and `wrappers=`) are forwarded to each sub-environment. |
|
|
147
|
+
| `EnvStep` | Result of one step (all fields `[B, ...]`): `obs` (what to act on next), `next_obs` (the true successor $s_{t+1}$), `reward`, `terminated`, `truncated`, and the derived `done = terminated | truncated`. |
|
|
148
|
+
| `ContinuousCartPoleEnv` | `CartPole-v1` with a continuous force action in $[-1, 1]$. **Importing `rl_mind.envs` registers `CartPoleContinuous-v1`** in gymnasium (the opt-in side effect). |
|
|
149
|
+
|
|
150
|
+
### Observation spaces
|
|
151
|
+
|
|
152
|
+
`VecEnv` adapts what it returns to the gymnasium observation space:
|
|
153
|
+
|
|
154
|
+
- **`Box`** (the usual case) → a `[B, obs_dim]` float tensor; `observation_dim`
|
|
155
|
+
gives `obs_dim`.
|
|
156
|
+
- **`Discrete`** (tabular environments) → a `[B]` tensor of **state indices**
|
|
157
|
+
(`torch.long`), which index a Q-table directly (`q_table[obs, actions]`);
|
|
158
|
+
`n_states` gives the number of states.
|
|
159
|
+
- **`Dict`** (structured observations) → a `TensorStruct` with one named tensor
|
|
160
|
+
field per key; inspect `observation_space` to write the encoder.
|
|
161
|
+
|
|
162
|
+
### `terminated` vs `truncated`
|
|
163
|
+
|
|
164
|
+
Both flags end an episode, but they mean different things for learning:
|
|
165
|
+
|
|
166
|
+
- **`terminated`** — a real terminal state (the pole fell). The future is worth
|
|
167
|
+
0, so you **do not bootstrap**.
|
|
168
|
+
- **`truncated`** — the episode was cut short, e.g. a time limit. The agent
|
|
169
|
+
could have continued, so you **do bootstrap** with the value of `next_obs`.
|
|
170
|
+
|
|
171
|
+
### Auto-reset modes
|
|
172
|
+
|
|
173
|
+
When an episode ends, `VecEnv` resets it automatically, in one of two modes:
|
|
174
|
+
|
|
175
|
+
- **next-step reset** (default): the ending step returns the episode's final
|
|
176
|
+
observation; the *following* `step` ignores its action and returns the first
|
|
177
|
+
observation of a fresh episode.
|
|
178
|
+
- **same-step reset** (`same_step_reset=True`): the ending step already returns
|
|
179
|
+
the fresh episode's first observation in `obs`, while `next_obs` holds the
|
|
180
|
+
final observation of the episode that just ended. **Every step is then a valid
|
|
181
|
+
transition** — this is what `RolloutCollector` needs.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Data containers
|
|
186
|
+
|
|
187
|
+
`rl_mind.data`
|
|
188
|
+
|
|
189
|
+
| Class / symbol | Shape | Role |
|
|
190
|
+
|---|---|---|
|
|
191
|
+
| `Transitions[A]` | flat `[N, ...]` | A batch of independent transitions $(s, a, r, s', \text{terminated})$: `obs`, `action`, `reward`, `next_obs`, `terminated`. Off-policy data. |
|
|
192
|
+
| `ReplayBuffer[A]` | — | A fixed-capacity ring buffer of `Transitions`. `add(transitions)`, `sample(batch_size)` (uniform, with replacement), `len(buffer)`. |
|
|
193
|
+
| `Episode[A]` | time `[T, ...]` | One full episode: `obs` (`[T, obs_dim]`), `action` (`[T, ...]`), `reward` (`[T]`), `final_obs` (`[obs_dim]`), `terminated` (bool). Plus `len(ep)`, `ep.cumulated_reward` and `ep.all_obs` (`[T+1, obs_dim]`): the observations $s_0, \ldots, s_T$, i.e. `obs` with `final_obs` appended — `all_obs[1:]` are the successors of `obs`, and `critic(all_obs)` gives $V(s_0), \ldots, V(s_T)$. |
|
|
194
|
+
| `Rollout[A]` | time×env `[T, B, ...]` | A fixed-length on-policy segment: `obs`, `action`, `reward`, `next_obs`, `terminated`, `truncated`, the derived `done`, and `flatten()` → `[T*B, ...]`. |
|
|
195
|
+
| `minibatches(data, size)` | — | Iterate over random minibatches of any `TensorStruct`, using each sample **exactly once** per pass (the last batch may be smaller). |
|
|
196
|
+
|
|
197
|
+
`Transitions` and `Rollout` are `TensorStruct`s, so indexing, slicing and
|
|
198
|
+
stacking work uniformly: `batch.action.log_prob`, `rollout[t]`,
|
|
199
|
+
`transitions[mask]`, ... `ReplayBuffer` wraps one; `Episode` is a plain
|
|
200
|
+
dataclass, because its fields do not share a batch dimension (`final_obs` has
|
|
201
|
+
no time axis and `terminated` is a Python `bool`).
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Collectors
|
|
206
|
+
|
|
207
|
+
`rl_mind.collectors`
|
|
208
|
+
|
|
209
|
+
The three collectors all run an actor in a `VecEnv` and keep the environment
|
|
210
|
+
state across successive `.collect()` calls, counting total steps in `.steps`.
|
|
211
|
+
They differ in the *shape* of data they produce, matching the three families of
|
|
212
|
+
algorithms:
|
|
213
|
+
|
|
214
|
+
| | `TransitionCollector` | `EpisodeCollector` | `RolloutCollector` |
|
|
215
|
+
|---|---|---|---|
|
|
216
|
+
| Returns | `Transitions` — flat `[N, …]` | `list[Episode]` | `Rollout` — time×env `[T, B, …]` |
|
|
217
|
+
| `collect(...)` arg | `n_steps` | `n_episodes` | `n_steps` |
|
|
218
|
+
| For | off-policy (DQN, DDPG, TD3, SAC) | episodic (REINFORCE) | on-policy (A2C, PPO) |
|
|
219
|
+
| Time structure | none (goes to a shuffled buffer) | whole episodes | preserved (needed for GAE) |
|
|
220
|
+
| `VecEnv` reset mode | next-step (default) | next-step (default) | requires `same_step_reset=True` |
|
|
221
|
+
| Length | `≤ n_steps × num_envs` (reset steps dropped) | `≥ n_episodes` whole episodes | exactly `[n_steps, num_envs]` |
|
|
222
|
+
| Ending flags kept | `terminated` | `terminated` (per episode) | `terminated` **and** `truncated` |
|
|
223
|
+
| Env state across calls | kept | reset at each call (on-policy) | kept |
|
|
224
|
+
|
|
225
|
+
### Why two step-based collectors (the subtle part)
|
|
226
|
+
|
|
227
|
+
`TransitionCollector` and `RolloutCollector` both walk a fixed number of steps,
|
|
228
|
+
but they treat episode boundaries differently — which is exactly why both exist:
|
|
229
|
+
|
|
230
|
+
- **`TransitionCollector`** produces an *unordered bag* of transitions that will
|
|
231
|
+
be shuffled in a replay buffer. When an episode ends under next-step reset, the
|
|
232
|
+
following "reset" step is invalid, so the collector simply **drops** those rows
|
|
233
|
+
(hence `≤ n_steps × num_envs`). Order doesn't matter, so holes are fine.
|
|
234
|
+
|
|
235
|
+
- **`RolloutCollector`** produces a *rectangular `[T, B]` block* whose time axis
|
|
236
|
+
must stay intact: on-policy algorithms compute GAE as a **backward recursion
|
|
237
|
+
over time**, and need to know at each step whether the episode ended. Dropping
|
|
238
|
+
rows would punch holes in the grid, so it instead requires
|
|
239
|
+
`same_step_reset=True`: the environment resets *within* the ending step, so
|
|
240
|
+
every row is a valid transition (`obs` = fresh state, `next_obs` = true final
|
|
241
|
+
state) and the block stays dense. This is why `Rollout` also carries
|
|
242
|
+
`truncated` — GAE must stop propagating across boundaries.
|
|
243
|
+
|
|
244
|
+
`EpisodeCollector` is the odd one out: it returns *variable-length whole
|
|
245
|
+
episodes* and resets the environments at the start of each `collect()`, so the
|
|
246
|
+
episodes are strictly on-policy (all collected with the current actor).
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Evaluation
|
|
251
|
+
|
|
252
|
+
`rl_mind.evaluation`
|
|
253
|
+
|
|
254
|
+
| Class / symbol | Role |
|
|
255
|
+
|---|---|
|
|
256
|
+
| `Evaluator[A]` | Periodically evaluates the current actor on a separate `VecEnv` (using `actor.act`, the deterministic behavior) and keeps a **copy of the best actor so far**. Call `run_if_needed(steps, actor)` in the training loop — it no-ops until `every` steps have passed. Exposes `best_actor`, `best_reward`, `history`, an optional tensorboard `writer`, and `visualize_best()`. |
|
|
257
|
+
| `EvalResult` | One evaluation: `step`, `rewards` (`[n_eval_envs]`), `is_best`, and the derived `.mean`. `Evaluator.history` is a list of these — handy for learning-curve plots and Welch t-tests. |
|
|
258
|
+
| `record_video(actor, env_name, directory)` | Record a video of one deterministic episode and return the video path. |
|
|
259
|
+
|
|
260
|
+
The evaluation environment is intentionally *separate* from the training env
|
|
261
|
+
(different seed, its own episode count, no `same_step_reset`) so that evaluation
|
|
262
|
+
is independent of the data the agent is training on.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Helpers
|
|
267
|
+
|
|
268
|
+
`rl_mind.nn`
|
|
269
|
+
|
|
270
|
+
| Symbol | Role |
|
|
271
|
+
|---|---|
|
|
272
|
+
| `build_mlp(sizes, activation=ReLU(), output_activation=None)` | Build a `nn.Sequential` MLP from a list of layer sizes. |
|
|
273
|
+
| `soft_update(source, target, tau)` | Polyak update of a target network: $\theta' \leftarrow \tau\theta + (1-\tau)\theta'$. |
|
|
274
|
+
|
|
275
|
+
`rl_mind.notebook`
|
|
276
|
+
|
|
277
|
+
| Symbol | Role |
|
|
278
|
+
|---|---|
|
|
279
|
+
| `run_directory(name)` | Create and return a fresh timestamped output directory for a run (under `outputs/`, or `outputs-testing/` in test mode). |
|
|
280
|
+
| `outputs_directory()` | The base output directory (test-mode aware). |
|
|
281
|
+
| `setup_tensorboard()` | Show the tensorboard dashboard inline (Jupyter, Colab), or print the command to launch it from a shell. Warns if the `tensorboard` package is missing, and always prints the **absolute** log directory (it is `outputs/` relative to the kernel's working directory). |
|
|
282
|
+
| `silence_known_warnings()` | Silence the `pkg_resources` deprecation warning emitted by `pygame` and `tensorboard`. |
|
|
283
|
+
| `video_display(path)` | Display a video in the notebook, or print its path when run as a script. |
|
|
284
|
+
| `is_notebook()` | True when running inside Jupyter / Colab. |
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## A minimal off-policy loop
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
import rl_mind.envs # register CartPoleContinuous-v1 (explicit opt-in)
|
|
292
|
+
from rl_mind.env import VecEnv
|
|
293
|
+
from rl_mind.data import ReplayBuffer
|
|
294
|
+
from rl_mind.collectors import TransitionCollector
|
|
295
|
+
from rl_mind.evaluation import Evaluator
|
|
296
|
+
|
|
297
|
+
env = VecEnv("CartPoleContinuous-v1", num_envs=1, seed=1)
|
|
298
|
+
collector = TransitionCollector(env, GaussianNoise(actor, sigma=0.1))
|
|
299
|
+
buffer = ReplayBuffer(200_000)
|
|
300
|
+
evaluator = Evaluator(VecEnv("CartPoleContinuous-v1", 10, seed=101), every=2_000)
|
|
301
|
+
|
|
302
|
+
while collector.steps < 30_000:
|
|
303
|
+
buffer.add(collector.collect(1))
|
|
304
|
+
if len(buffer) < 1_000:
|
|
305
|
+
continue
|
|
306
|
+
batch = buffer.sample(64) # Transitions[Action]
|
|
307
|
+
target = batch.reward + gamma * next_q * (~batch.terminated).float()
|
|
308
|
+
... # critic / actor updates
|
|
309
|
+
evaluator.run_if_needed(collector.steps, actor)
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
The on-policy notebooks (A2C, PPO) swap the replay buffer for a
|
|
313
|
+
`RolloutCollector` + `minibatches`; REINFORCE uses an `EpisodeCollector`.
|
rl_mind-0.1.0/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# BBRL Notebooks
|
|
2
|
+
|
|
3
|
+
## Notebooks
|
|
4
|
+
|
|
5
|
+
The notebooks are the python files in [percent
|
|
6
|
+
format](https://jupytext.readthedocs.io/en/latest/formats.html) which are
|
|
7
|
+
transformed into student (with a Colab version) and teacher versions using
|
|
8
|
+
special markers (as described below). Images are inlined.
|
|
9
|
+
|
|
10
|
+
### Imports
|
|
11
|
+
|
|
12
|
+
To regroup all local imports, use
|
|
13
|
+
|
|
14
|
+
```py3
|
|
15
|
+
# %% tags=["imports"]
|
|
16
|
+
import numpy as np
|
|
17
|
+
import torch.nn as nn
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
And in the place where all the imports should be placed, put this instruction
|
|
21
|
+
```py3
|
|
22
|
+
# [[imports]]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Collapsing a cell
|
|
26
|
+
|
|
27
|
+
To collapse a cell in the notebook, use the `hide-input` tag
|
|
28
|
+
|
|
29
|
+
```md
|
|
30
|
+
# %% tags=["hide-input"]
|
|
31
|
+
|
|
32
|
+
some_not_interesting_code()
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Including a file
|
|
36
|
+
|
|
37
|
+
To include all the cells from another `py:percent` file, use
|
|
38
|
+
a `copy` tag with a `from ... import ...`
|
|
39
|
+
|
|
40
|
+
```py3
|
|
41
|
+
# %% tags=["copy"]
|
|
42
|
+
|
|
43
|
+
from common.env import *
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Filtering cells
|
|
47
|
+
|
|
48
|
+
Tags can be used to filter cells depending on the output:
|
|
49
|
+
|
|
50
|
+
- `teacher` removes the cell when generating a student version
|
|
51
|
+
- `colab` removes the cell when not generating a google Colab version
|
|
52
|
+
- `not-colab` removes the cell when generating a google Colab version
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
### Filtering within cells
|
|
56
|
+
|
|
57
|
+
Within a cell, special markers can be used to transform the text.
|
|
58
|
+
|
|
59
|
+
To remove altogether the content, use `[[remove]]`:
|
|
60
|
+
|
|
61
|
+
```py3
|
|
62
|
+
# [[remove]]
|
|
63
|
+
test_mode = True
|
|
64
|
+
# [[/remove]]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
To remove the content only for student, use `[[student]]`:
|
|
68
|
+
```py3
|
|
69
|
+
# [[student]] Your code to obtain an optimal policy here
|
|
70
|
+
def value_iteration_q(mdp: MazeMDPEnv, render: bool = True) -> Tuple[np.ndarray, List[float]]:
|
|
71
|
+
q = np.zeros((mdp.nb_states, mdp.action_space.n)) # initial action values are set to 0
|
|
72
|
+
q_list = []
|
|
73
|
+
# ># Set stop to False
|
|
74
|
+
# >stop = ...
|
|
75
|
+
stop = False
|
|
76
|
+
...
|
|
77
|
+
# [[/student]]
|
|
78
|
+
```
|
|
79
|
+
is transformed into
|
|
80
|
+
```py3
|
|
81
|
+
# Your code to obtain an optimal policy here
|
|
82
|
+
assert False, "Your code to obtain an optimal policy here"
|
|
83
|
+
# Set stop to False
|
|
84
|
+
stop =
|
|
85
|
+
```
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "rl-mind"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A small, typed reinforcement-learning toolkit for the Master MIND RL practicals"
|
|
5
|
+
readme = "rl_mind.md"
|
|
6
|
+
authors = [{ name = "Benjamin Piwowarski", email = "benjamin@piwowarski.fr" }]
|
|
7
|
+
keywords = ["reinforcement-learning", "gymnasium", "torch", "teaching"]
|
|
8
|
+
classifiers = [
|
|
9
|
+
"Intended Audience :: Education",
|
|
10
|
+
"Programming Language :: Python :: 3",
|
|
11
|
+
"Programming Language :: Python :: 3.11",
|
|
12
|
+
"Programming Language :: Python :: 3.12",
|
|
13
|
+
"Topic :: Education",
|
|
14
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
15
|
+
]
|
|
16
|
+
requires-python = ">=3.11"
|
|
17
|
+
# Runtime dependencies of the library only (what `import rl_mind` needs). The
|
|
18
|
+
# gymnasium extras cover every environment used by the practicals, so a bare
|
|
19
|
+
# `pip install rl-mind` is enough to run the notebooks.
|
|
20
|
+
dependencies = [
|
|
21
|
+
"gymnasium[mujoco,classic-control,box2d]>=1.2.0",
|
|
22
|
+
"ipython>=8",
|
|
23
|
+
"mazemdp>=1.3.0",
|
|
24
|
+
"moviepy>=1.0",
|
|
25
|
+
"numpy>=1.26",
|
|
26
|
+
"tensorboard>=2.19",
|
|
27
|
+
"torch>=2.4",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Documentation = "https://pypi.org/project/rl-mind/"
|
|
32
|
+
|
|
33
|
+
[build-system]
|
|
34
|
+
# swig is not needed to build rl-mind itself: it is listed here so that the
|
|
35
|
+
# generated Colab install cell installs it first (box2d-py builds from source).
|
|
36
|
+
requires = ["swig", "setuptools>=61"]
|
|
37
|
+
build-backend = "setuptools.build_meta"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["src"]
|
|
41
|
+
include = ["rl_mind*"]
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.package-data]
|
|
44
|
+
rl_mind = ["py.typed"]
|
|
45
|
+
|
|
46
|
+
[tool.pytest.ini_options]
|
|
47
|
+
# Only our own tests: `stk-competition/repositories/` holds student projects
|
|
48
|
+
# (unrelated dependencies, not collectable)
|
|
49
|
+
testpaths = ["tests"]
|
|
50
|
+
|
|
51
|
+
[dependency-groups]
|
|
52
|
+
# What the notebooks need on top of the library (jupyter, plotting, the
|
|
53
|
+
# instructor build helper, ...). Installed by `uv sync` (default groups) but
|
|
54
|
+
# not by `pip install rl-mind`.
|
|
55
|
+
notebooks = [
|
|
56
|
+
"hydra-core>=1.3.2",
|
|
57
|
+
"jupyter>=1.1.1",
|
|
58
|
+
"jupyterlab>=4.4.4",
|
|
59
|
+
"jupytext-notebook-helper>=0.4.1",
|
|
60
|
+
"matplotlib>=3.8",
|
|
61
|
+
"optuna>=4.9.0",
|
|
62
|
+
"pip>=25.2",
|
|
63
|
+
"scipy>=1.11",
|
|
64
|
+
"tqdm>=4.66",
|
|
65
|
+
]
|
|
66
|
+
dev = [
|
|
67
|
+
"imgcat>=0.5.0",
|
|
68
|
+
"jupytext>=1.17.2",
|
|
69
|
+
"pre-commit>=4.2.0",
|
|
70
|
+
"pytest>=8",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
[tool.uv]
|
|
74
|
+
default-groups = ["notebooks", "dev"]
|