x-evolution 0.1.30__tar.gz → 0.1.32__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: x-evolution
3
- Version: 0.1.30
3
+ Version: 0.1.32
4
4
  Summary: x-evolution
5
5
  Project-URL: Homepage, https://pypi.org/project/x-evolution/
6
6
  Project-URL: Repository, https://github.com/lucidrains/x-evolution
@@ -37,6 +37,7 @@ Requires-Python: >=3.9
37
37
  Requires-Dist: accelerate
38
38
  Requires-Dist: beartype
39
39
  Requires-Dist: einops>=0.8.0
40
+ Requires-Dist: torch-einops-utils
40
41
  Requires-Dist: torch>=2.4
41
42
  Requires-Dist: x-mlps-pytorch>=0.2.0
42
43
  Requires-Dist: x-transformers>=2.11.23
@@ -52,6 +53,8 @@ Description-Content-Type: text/markdown
52
53
 
53
54
  Implementation of various evolutionary algorithms, starting with [evolutionary strategies](https://blog.otoro.net/2017/10/29/visual-evolution-strategies/)
54
55
 
56
+ [explained by @bycloud](https://www.youtube.com/watch?v=lLkE9w1NJs0)
57
+
55
58
  ## Install
56
59
 
57
60
  ```bash
@@ -95,19 +98,29 @@ evo_strat()
95
98
 
96
99
  ## Distributed
97
100
 
98
- Using the CLI from 🤗
101
+ Using the CLI from 🤗
99
102
 
100
103
  ```shell
101
- $ accelerate config
104
+ $ uv run accelerate config
102
105
  ```
103
106
 
104
107
  Then
105
108
 
106
109
  ```shell
107
- $ accelerate launch train.py
110
+ $ uv run accelerate launch train.py
111
+ ```
112
+
113
+ Or with `torchrun` (which can do multiple CPU processes for testing)
114
+
115
+ ```shell
116
+ $ uv run torchrun --nproc_per_node=<N> train.py --cpu True
108
117
  ```
109
118
 
110
- For gymnasium simulations, first run `pip install '[examples]'`
119
+ For a quick demo on LunarLander with 8 processes
120
+
121
+ ```shell
122
+ $ uv run torchrun --nproc_per_node=8 train_lunar.py --cpu True --noise_population_size 100 --fitness_to_weighted_factor centered_rank
123
+ ```
111
124
 
112
125
  ## Citations
113
126
 
@@ -124,25 +137,25 @@ For gymnasium simulations, first run `pip install '[examples]'`
124
137
 
125
138
  ```bibtex
126
139
  @misc{sarkar2025evolutionstrategieshyperscale,
127
- title = {Evolution Strategies at the Hyperscale},
140
+ title = {Evolution Strategies at the Hyperscale},
128
141
  author = {Bidipta Sarkar and Mattie Fellows and Juan Agustin Duque and Alistair Letcher and Antonio León Villares and Anya Sims and Dylan Cope and Jarek Liesen and Lukas Seier and Theo Wolf and Uljad Berdica and Alexander David Goldie and Aaron Courville and Karin Sevegnani and Shimon Whiteson and Jakob Nicolaus Foerster},
129
142
  year = {2025},
130
143
  eprint = {2511.16652},
131
144
  archivePrefix = {arXiv},
132
145
  primaryClass = {cs.LG},
133
- url = {https://arxiv.org/abs/2511.16652},
146
+ url = {https://arxiv.org/abs/2511.16652},
134
147
  }
135
148
  ```
136
149
 
137
150
  ```bibtex
138
151
  @misc{fortunato2019noisynetworksexploration,
139
- title = {Noisy Networks for Exploration},
152
+ title = {Noisy Networks for Exploration},
140
153
  author = {Meire Fortunato and Mohammad Gheshlaghi Azar and Bilal Piot and Jacob Menick and Ian Osband and Alex Graves and Vlad Mnih and Remi Munos and Demis Hassabis and Olivier Pietquin and Charles Blundell and Shane Legg},
141
154
  year = {2019},
142
155
  eprint = {1706.10295},
143
156
  archivePrefix = {arXiv},
144
157
  primaryClass = {cs.LG},
145
- url = {https://arxiv.org/abs/1706.10295},
158
+ url = {https://arxiv.org/abs/1706.10295},
146
159
  }
147
160
  ```
148
161
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  Implementation of various evolutionary algorithms, starting with [evolutionary strategies](https://blog.otoro.net/2017/10/29/visual-evolution-strategies/)
4
4
 
5
+ [explained by @bycloud](https://www.youtube.com/watch?v=lLkE9w1NJs0)
6
+
5
7
  ## Install
6
8
 
7
9
  ```bash
@@ -45,19 +47,29 @@ evo_strat()
45
47
 
46
48
  ## Distributed
47
49
 
48
- Using the CLI from 🤗
50
+ Using the CLI from 🤗
49
51
 
50
52
  ```shell
51
- $ accelerate config
53
+ $ uv run accelerate config
52
54
  ```
53
55
 
54
56
  Then
55
57
 
56
58
  ```shell
57
- $ accelerate launch train.py
59
+ $ uv run accelerate launch train.py
60
+ ```
61
+
62
+ Or with `torchrun` (which can do multiple CPU processes for testing)
63
+
64
+ ```shell
65
+ $ uv run torchrun --nproc_per_node=<N> train.py --cpu True
58
66
  ```
59
67
 
60
- For gymnasium simulations, first run `pip install '[examples]'`
68
+ For a quick demo on LunarLander with 8 processes
69
+
70
+ ```shell
71
+ $ uv run torchrun --nproc_per_node=8 train_lunar.py --cpu True --noise_population_size 100 --fitness_to_weighted_factor centered_rank
72
+ ```
61
73
 
62
74
  ## Citations
63
75
 
@@ -74,25 +86,25 @@ For gymnasium simulations, first run `pip install '[examples]'`
74
86
 
75
87
  ```bibtex
76
88
  @misc{sarkar2025evolutionstrategieshyperscale,
77
- title = {Evolution Strategies at the Hyperscale},
89
+ title = {Evolution Strategies at the Hyperscale},
78
90
  author = {Bidipta Sarkar and Mattie Fellows and Juan Agustin Duque and Alistair Letcher and Antonio León Villares and Anya Sims and Dylan Cope and Jarek Liesen and Lukas Seier and Theo Wolf and Uljad Berdica and Alexander David Goldie and Aaron Courville and Karin Sevegnani and Shimon Whiteson and Jakob Nicolaus Foerster},
79
91
  year = {2025},
80
92
  eprint = {2511.16652},
81
93
  archivePrefix = {arXiv},
82
94
  primaryClass = {cs.LG},
83
- url = {https://arxiv.org/abs/2511.16652},
95
+ url = {https://arxiv.org/abs/2511.16652},
84
96
  }
85
97
  ```
86
98
 
87
99
  ```bibtex
88
100
  @misc{fortunato2019noisynetworksexploration,
89
- title = {Noisy Networks for Exploration},
101
+ title = {Noisy Networks for Exploration},
90
102
  author = {Meire Fortunato and Mohammad Gheshlaghi Azar and Bilal Piot and Jacob Menick and Ian Osband and Alex Graves and Vlad Mnih and Remi Munos and Demis Hassabis and Olivier Pietquin and Charles Blundell and Shane Legg},
91
103
  year = {2019},
92
104
  eprint = {1706.10295},
93
105
  archivePrefix = {arXiv},
94
106
  primaryClass = {cs.LG},
95
- url = {https://arxiv.org/abs/1706.10295},
107
+ url = {https://arxiv.org/abs/1706.10295},
96
108
  }
97
109
  ```
98
110
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "x-evolution"
3
- version = "0.1.30"
3
+ version = "0.1.32"
4
4
  description = "x-evolution"
5
5
  authors = [
6
6
  { name = "Phil Wang", email = "lucidrains@gmail.com" }
@@ -27,6 +27,7 @@ dependencies = [
27
27
  "beartype",
28
28
  "einops>=0.8.0",
29
29
  "torch>=2.4",
30
+ "torch-einops-utils",
30
31
  "x-mlps-pytorch>=0.2.0",
31
32
  "x-transformers>=2.11.23"
32
33
  ]
@@ -65,3 +66,11 @@ allow-direct-references = true
65
66
 
66
67
  [tool.hatch.build.targets.wheel]
67
68
  packages = ["x_evolution"]
69
+
70
+ [tool.hatch.build.targets.sdist]
71
+ include = [
72
+ "x_evolution",
73
+ "README.md",
74
+ "LICENSE",
75
+ "pyproject.toml",
76
+ ]
@@ -1,5 +1,5 @@
1
1
  from __future__ import annotations
2
- from typing import Callable
2
+ from typing import Callable, Literal
3
3
 
4
4
  from math import ceil
5
5
  from pathlib import Path
@@ -27,6 +27,7 @@ from x_mlps_pytorch.noisable import (
27
27
  )
28
28
 
29
29
  from einops import pack
30
+ from torch_einops_utils import pack_with_inverse
30
31
 
31
32
  # constants
32
33
 
@@ -46,15 +47,31 @@ def identity(t):
46
47
  def divisible_by(num, den):
47
48
  return (num % den) == 0
48
49
 
49
- def normalize(t, eps = 1e-6):
50
- return F.layer_norm(t, t.shape, eps = eps)
51
-
52
50
  def accum_grad_(t, value):
53
51
  if not exists(t.grad):
54
52
  t.grad = value.clone()
55
53
  else:
56
54
  t.grad.add_(value)
57
55
 
56
+ # fitness weighting factors
57
+
58
+ def normalize(t, eps = 1e-6):
59
+ return F.layer_norm(t, t.shape, eps = eps)
60
+
61
+ def centered_rank_normalize(fitnesses):
62
+ dtype, device, n = fitnesses.dtype, fitnesses.device, fitnesses.numel()
63
+ flat_fitnesses, inverse = pack_with_inverse(fitnesses, '*')
64
+ ranks = flat_fitnesses.argsort().argsort().to(dtype = dtype)
65
+ ranks = (ranks / (n - 1)) - 0.5
66
+ return inverse(ranks)
67
+
68
+ FITNESS_WEIGHT_FACTORS = dict(
69
+ normalize = normalize,
70
+ centered_rank = centered_rank_normalize
71
+ )
72
+
73
+ FitnessWeightFactorType = Literal['normalize', 'centered_rank']
74
+
58
75
  # class
59
76
 
60
77
  class EvoStrategy(Module):
@@ -89,7 +106,7 @@ class EvoStrategy(Module):
89
106
  sigma_scheduler_klass: type[LRScheduler] | None = None,
90
107
  sigma_scheduler_kwargs: dict = dict(),
91
108
  transform_fitness: Callable = identity,
92
- fitness_to_weighted_factor: Callable[[Tensor], Tensor] = normalize,
109
+ fitness_to_weighted_factor: Callable[[Tensor], Tensor] | FitnessWeightFactorType = 'normalize',
93
110
  checkpoint_every = None, # saving every number of generations
94
111
  checkpoint_path = './checkpoints',
95
112
  cpu = False,
@@ -97,6 +114,7 @@ class EvoStrategy(Module):
97
114
  accelerator: Accelerator | None = None,
98
115
  accelerate_kwargs: dict = dict(),
99
116
  reject_generation_fitnesses_if: Callable[[Tensor], bool] | None = None,
117
+ on_result: Callable | None = None,
100
118
  vectorized = False,
101
119
  vector_size: int | None = None,
102
120
  sync_on_init = True
@@ -106,6 +124,7 @@ class EvoStrategy(Module):
106
124
 
107
125
  self.vectorized = vectorized
108
126
  self.vector_size = vector_size
127
+ self.on_result = on_result
109
128
 
110
129
  if not exists(accelerator):
111
130
  accelerator = Accelerator(cpu = cpu, **accelerate_kwargs)
@@ -179,7 +198,7 @@ class EvoStrategy(Module):
179
198
  # dtypes
180
199
 
181
200
  self.param_dtypes = {name: param.dtype for name, param in named_parameters_dict.items()}
182
-
201
+
183
202
  # hyperparameters
184
203
 
185
204
  self.noise_population_size = noise_population_size
@@ -191,6 +210,11 @@ class EvoStrategy(Module):
191
210
 
192
211
  self.transform_fitness = transform_fitness # a function that gets called before converting to weights for the weighted noise update - eventually get rank normalization
193
212
 
213
+ if isinstance(fitness_to_weighted_factor, str):
214
+ if fitness_to_weighted_factor not in FITNESS_WEIGHT_FACTORS:
215
+ raise ValueError(f'fitness_to_weighted_factor must be one of {list(FITNESS_WEIGHT_FACTORS.keys())}')
216
+ fitness_to_weighted_factor = FITNESS_WEIGHT_FACTORS[fitness_to_weighted_factor]
217
+
194
218
  self.fitness_to_weighted_factor = fitness_to_weighted_factor
195
219
 
196
220
  self.mirror_sampling = mirror_sampling # mirror / antithetical sampling - reducing variance by doing positive + negative of noise and subtracting
@@ -522,7 +546,10 @@ class EvoStrategy(Module):
522
546
 
523
547
  if not self.mirror_sampling:
524
548
  fitnesses.append(fitness)
525
- pbar.set_postfix(reward = f'{fitness:.3f}')
549
+
550
+ if exists(self.on_result):
551
+ self.on_result(fitness, pbar)
552
+
526
553
  continue
527
554
 
528
555
  # handle mirror sampling
@@ -530,7 +557,10 @@ class EvoStrategy(Module):
530
557
  fitness_mirrored = get_fitness(negate = True)
531
558
 
532
559
  fitnesses.append([fitness, fitness_mirrored])
533
- pbar.set_postfix(reward = f'{(fitness + fitness_mirrored) / 2:.3f}')
560
+ avg_fitness = (fitness + fitness_mirrored) / 2
561
+
562
+ if exists(self.on_result):
563
+ self.on_result(avg_fitness, pbar)
534
564
 
535
565
  pbar.close()
536
566
 
@@ -1,36 +0,0 @@
1
- # This workflow will upload a Python Package using Twine when a release is created
2
- # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
3
-
4
- # This workflow uses actions that are not certified by GitHub.
5
- # They are provided by a third-party and are governed by
6
- # separate terms of service, privacy policy, and support
7
- # documentation.
8
-
9
- name: Upload Python Package
10
-
11
- on:
12
- release:
13
- types: [published]
14
-
15
- jobs:
16
- deploy:
17
-
18
- runs-on: ubuntu-latest
19
-
20
- steps:
21
- - uses: actions/checkout@v2
22
- - name: Set up Python
23
- uses: actions/setup-python@v2
24
- with:
25
- python-version: '3.x'
26
- - name: Install dependencies
27
- run: |
28
- python -m pip install --upgrade pip
29
- pip install build
30
- - name: Build package
31
- run: python -m build
32
- - name: Publish package
33
- uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
34
- with:
35
- user: __token__
36
- password: ${{ secrets.PYPI_API_TOKEN }}
@@ -1,21 +0,0 @@
1
- name: Pytest
2
- on: [push, pull_request]
3
-
4
- jobs:
5
- build:
6
-
7
- runs-on: ubuntu-latest
8
-
9
- steps:
10
- - uses: actions/checkout@v4
11
- - name: Set up Python 3.10
12
- uses: actions/setup-python@v5
13
- with:
14
- python-version: "3.10"
15
- - name: Install dependencies
16
- run: |
17
- python -m pip install --upgrade pip
18
- python -m pip install -e .[test]
19
- - name: Test with pytest
20
- run: |
21
- python -m pytest tests/
@@ -1,27 +0,0 @@
1
- image: python:3.10
2
-
3
- stages:
4
- - test
5
- - publish
6
-
7
- test:
8
- stage: test
9
- before_script:
10
- - python -m pip install --upgrade pip
11
- - python -m pip install -e .[test]
12
- script:
13
- - python -m pytest tests/
14
- only:
15
- - pushes
16
- - merge_requests
17
-
18
- pypi:
19
- stage: publish
20
- before_script:
21
- - python -m pip install --upgrade pip
22
- - pip install build twine
23
- script:
24
- - python -m build
25
- - TWINE_PASSWORD=$PYPI_API_TOKEN TWINE_USERNAME=__token__ python -m twine upload dist/*
26
- only:
27
- - tags
@@ -1,2 +0,0 @@
1
- #!/bin/bash
2
- apt-get update && apt-get install -y libegl1 libgl1-mesa-glx libosmesa6-dev xvfb
@@ -1,70 +0,0 @@
1
- import pytest
2
- param = pytest.mark.parametrize
3
-
4
- import torch
5
- from x_mlps_pytorch import MLP
6
-
7
- model = MLP(8, 16, 4)
8
-
9
- @param('params_to_optimize', (None, [model.layers[1].weight]))
10
- @param('use_optimizer', (False, True))
11
- @param('noise_low_rank', (None, 1))
12
- @param('mirror_sampling', (False, True))
13
- @param('multi_models', (False, True))
14
- @param('learned_sigma', (False, True))
15
- @param('use_sigma_optimizer', (False, True))
16
- def test_evo_strat(
17
- params_to_optimize,
18
- use_optimizer,
19
- noise_low_rank,
20
- mirror_sampling,
21
- multi_models,
22
- learned_sigma,
23
- use_sigma_optimizer
24
- ):
25
- from random import randrange
26
-
27
- from x_evolution.x_evolution import EvoStrategy
28
-
29
- to_optim = model
30
-
31
- if multi_models:
32
- to_optim = [model, MLP(8, 1)]
33
-
34
- evo_strat = EvoStrategy(
35
- to_optim,
36
- environment = lambda model: float(randrange(100)),
37
- num_generations = 2,
38
- learned_noise_scale = learned_sigma,
39
- params_to_optimize = params_to_optimize,
40
- use_optimizer = use_optimizer,
41
- noise_low_rank = noise_low_rank,
42
- mirror_sampling = mirror_sampling,
43
- use_sigma_optimizer = use_sigma_optimizer
44
- )
45
-
46
- evo_strat('evolve', 1)
47
- evo_strat('more.evolve', 1)
48
-
49
- fitnesses = evo_strat('more.evolve', 2, rollback_model_at_end = True)
50
-
51
- @param('vector_size', (2, 4))
52
- def test_evo_strat_vectorized(vector_size):
53
- from x_evolution.x_evolution import EvoStrategy
54
-
55
- model = MLP(8, 16, 4)
56
-
57
- def environment(model):
58
- # mock a vectorized environment returning multiple fitness scores
59
- return torch.randn(vector_size)
60
-
61
- evo_strat = EvoStrategy(
62
- model,
63
- environment = environment,
64
- num_generations = 2,
65
- vectorized = True,
66
- vector_size = vector_size,
67
- noise_population_size = 4
68
- )
69
-
70
- evo_strat()
@@ -1,211 +0,0 @@
1
- # /// script
2
- # dependencies = [
3
- # "fire",
4
- # "gymnasium[mujoco]>=1.0.0",
5
- # "gymnasium-robotics",
6
- # "moviepy",
7
- # "tqdm",
8
- # "x-evolution>=0.1.27",
9
- # "x-mlps-pytorch"
10
- # ]
11
- # ///
12
-
13
- import fire
14
- from shutil import rmtree
15
- import gymnasium as gym
16
- import gymnasium_robotics
17
- import numpy as np
18
- from tqdm import tqdm
19
-
20
- import torch
21
- from torch import nn
22
- from torch.nn import Module, GRU, Linear
23
- import torch.nn.functional as F
24
-
25
- # functions
26
-
27
- def exists(v):
28
- return v is not None
29
-
30
- def softclamp(t, value):
31
- return (t / value).tanh() * value
32
-
33
- class AntMazeEnvironment(Module):
34
- def __init__(
35
- self,
36
- env_id = 'AntMaze_UMazeDense-v5',
37
- video_folder = './recordings_ant_maze',
38
- render_every_eps = 100,
39
- max_steps = 1000,
40
- repeats = 1,
41
- verbose = False
42
- ):
43
- super().__init__()
44
-
45
- self.verbose = verbose
46
-
47
- env = gym.make(env_id, render_mode = 'rgb_array')
48
-
49
- self.env = env
50
- self.max_steps = max_steps
51
- self.repeats = repeats
52
- self.video_folder = video_folder
53
- self.render_every_eps = render_every_eps
54
-
55
- def pre_main_callback(self):
56
- rmtree(self.video_folder, ignore_errors = True)
57
-
58
- self.env = gym.wrappers.RecordVideo(
59
- env = self.env,
60
- video_folder = self.video_folder,
61
- name_prefix = 'recording',
62
- episode_trigger = lambda eps_num: (eps_num % self.render_every_eps) == 0,
63
- disable_logger = True
64
- )
65
-
66
- def forward(self, model):
67
-
68
- device = next(model.parameters()).device
69
-
70
- seed = torch.randint(0, int(1e6), ())
71
-
72
- num_envs = 1
73
- cum_reward = torch.zeros(num_envs, device = device)
74
-
75
- for _ in range(self.repeats):
76
- obs, _ = self.env.reset(seed = seed.item())
77
-
78
- step = 0
79
- hiddens = None
80
-
81
- dones = torch.zeros(num_envs, device = device, dtype = torch.bool)
82
-
83
- while step < self.max_steps and not dones.all():
84
-
85
- state = obs['observation']
86
- goal = obs['desired_goal']
87
-
88
- state_torch = torch.from_numpy(state).float().to(device)
89
- goal_torch = torch.from_numpy(goal).float().to(device)
90
-
91
- state_goal = torch.cat((state_torch, goal_torch), dim = -1)
92
-
93
- action_logits, hiddens = model(state_goal, hiddens)
94
-
95
- mean, log_var = action_logits.chunk(2, dim = -1)
96
-
97
- std = (0.5 * softclamp(log_var, 5.)).exp()
98
- sampled = mean + torch.randn_like(mean) * std
99
- action = sampled.tanh()
100
-
101
- next_obs, reward, truncated, terminated, info = self.env.step(action.detach().cpu().numpy())
102
-
103
- reward_np = np.array(reward) if not isinstance(reward, np.ndarray) else reward
104
- total_reward_base = torch.from_numpy(reward_np).float().to(device)
105
-
106
- exploration_bonus = std.mean(dim = -1) * 0.01
107
- penalize_extreme_actions = (mean.abs() > 1.).float().mean(dim = -1) * 0.01
108
-
109
- total_reward = total_reward_base + exploration_bonus - penalize_extreme_actions
110
-
111
- mask = (~dones).float()
112
- cum_reward += total_reward * mask
113
-
114
- dones_np = np.array(truncated | terminated) if not isinstance(truncated | terminated, np.ndarray) else (truncated | terminated)
115
- dones |= torch.from_numpy(dones_np).to(device)
116
-
117
- step += 1
118
- obs = next_obs
119
-
120
- return cum_reward.item() / self.repeats
121
-
122
- # model
123
-
124
- from x_mlps_pytorch.residual_normed_mlp import ResidualNormedMLP
125
-
126
- class Model(Module):
127
-
128
- def __init__(self, state_dim = 27 + 2, action_dim = 8):
129
- super().__init__()
130
-
131
- self.deep_mlp = ResidualNormedMLP(
132
- dim_in = state_dim,
133
- dim = 256,
134
- depth = 8,
135
- residual_every = 2
136
- )
137
-
138
- self.gru = GRU(256, 256, batch_first = True)
139
-
140
- self.to_pred = Linear(256, action_dim * 2, bias = False)
141
-
142
- def forward(self, state, hiddens = None):
143
-
144
- x = self.deep_mlp(state)
145
-
146
- is_batched = x.ndim > 1
147
-
148
- if not is_batched:
149
- x = x.unsqueeze(0)
150
-
151
- x = x.unsqueeze(-2)
152
- gru_out, hiddens = self.gru(x, hiddens)
153
- x = x + gru_out
154
- x = x.squeeze(-2)
155
-
156
- if not is_batched:
157
- x = x.squeeze(0)
158
-
159
- return self.to_pred(x), hiddens
160
-
161
- # evo strategy
162
-
163
- from x_evolution import EvoStrategy
164
- from torch.optim.lr_scheduler import CosineAnnealingLR
165
-
166
- def main(
167
- env_id = 'AntMaze_UMazeDense-v5',
168
- num_generations = 50_000,
169
- max_steps = 250,
170
- noise_population_size = 150,
171
- verbose = True
172
- ):
173
- # Determine dims from temp env
174
- temp_env = gym.make(env_id)
175
- obs_space = temp_env.observation_space
176
- action_space = temp_env.action_space
177
-
178
- state_dim = obs_space['observation'].shape[0] + obs_space['desired_goal'].shape[0]
179
- action_dim = action_space.shape[0]
180
- temp_env.close()
181
-
182
- model = Model(state_dim = state_dim, action_dim = action_dim)
183
-
184
- evo_strat = EvoStrategy(
185
- model,
186
- environment = AntMazeEnvironment(
187
- env_id = env_id,
188
- max_steps = max_steps,
189
- repeats = 1,
190
- render_every_eps = 200,
191
- verbose = verbose
192
- ),
193
- num_generations = num_generations,
194
- noise_population_size = noise_population_size,
195
- noise_low_rank = 1,
196
- noise_scale = 1e-2,
197
- noise_scale_clamp_range = (5e-3, 2e-2),
198
- learned_noise_scale = True,
199
- use_sigma_optimizer = True,
200
- learning_rate = 3e-4,
201
- noise_scale_learning_rate = 1e-4,
202
- use_scheduler = True,
203
- scheduler_klass = CosineAnnealingLR,
204
- scheduler_kwargs = dict(T_max = num_generations),
205
- verbose = verbose
206
- )
207
-
208
- evo_strat()
209
-
210
- if __name__ == '__main__':
211
- fire.Fire(main)
@@ -1,215 +0,0 @@
1
- # /// script
2
- # dependencies = [
3
- # "fire",
4
- # "gymnasium[mujoco]>=1.0.0",
5
- # "gymnasium[other]",
6
- # "x-evolution>=0.0.20",
7
- # "x-mlps-pytorch"
8
- # ]
9
- # ///
10
-
11
- # import os
12
- # os.environ["NCCL_P2P_DISABLE"] = "1"
13
- # os.environ["NCCL_IB_DISABLE"] = "1"
14
- # os.environ["MUJOCO_GL"] = "osmesa"
15
-
16
- import fire
17
- from shutil import rmtree
18
- import gymnasium as gym
19
- import numpy as np
20
-
21
- import torch
22
- from torch.nn import Module, GRU, Linear
23
- import torch.nn.functional as F
24
-
25
- # functions
26
-
27
- def exists(v):
28
- return v is not None
29
-
30
- def softclamp(t, value):
31
- return (t / value).tanh() * value
32
-
33
- class HumanoidEnvironment(Module):
34
- def __init__(
35
- self,
36
- video_folder = './recordings_humanoid',
37
- render_every_eps = 100,
38
- max_steps = 1000,
39
- repeats = 1,
40
- vectorized = False,
41
- num_envs = 1
42
- ):
43
- super().__init__()
44
-
45
- self.vectorized = vectorized
46
- self.num_envs = num_envs
47
-
48
- if vectorized:
49
- env = gym.make_vec('Humanoid-v5', num_envs = num_envs, render_mode = 'rgb_array')
50
- else:
51
- env = gym.make('Humanoid-v5', render_mode = 'rgb_array')
52
-
53
- self.env = env
54
- self.max_steps = max_steps
55
- self.repeats = repeats
56
- self.video_folder = video_folder
57
- self.render_every_eps = render_every_eps
58
-
59
- def pre_main_callback(self):
60
- # the `pre_main_callback` on the environment passed in is called before the start of the evolutionary strategies loop
61
-
62
- rmtree(self.video_folder, ignore_errors = True)
63
-
64
- if not self.vectorized:
65
- self.env = gym.wrappers.RecordVideo(
66
- env = self.env,
67
- video_folder = self.video_folder,
68
- name_prefix = 'recording',
69
- episode_trigger = lambda eps_num: (eps_num % self.render_every_eps) == 0,
70
- disable_logger = True
71
- )
72
-
73
- def forward(self, model):
74
-
75
- device = next(model.parameters()).device
76
-
77
- seed = torch.randint(0, int(1e6), ())
78
-
79
- num_envs = self.num_envs if self.vectorized else 1
80
- cum_reward = torch.zeros(num_envs, device = device)
81
-
82
- for _ in range(self.repeats):
83
- state, _ = self.env.reset(seed = seed.item())
84
-
85
- step = 0
86
- hiddens = None
87
- last_action = None
88
-
89
- dones = torch.zeros(num_envs, device = device, dtype = torch.bool)
90
-
91
- while step < self.max_steps and not dones.all():
92
-
93
- state_torch = torch.from_numpy(state).float().to(device)
94
-
95
- action_logits, hiddens = model(state_torch, hiddens)
96
-
97
- mean, log_var = action_logits.chunk(2, dim = -1)
98
-
99
- # sample and then bound and scale to -0.4 to 0.4
100
-
101
- std = (0.5 * softclamp(log_var, 5.)).exp()
102
- sampled = mean + torch.randn_like(mean) * std
103
- action = sampled.tanh() * 0.4
104
-
105
- next_state, reward, truncated, terminated, info = self.env.step(action.detach().cpu().numpy() if self.vectorized else action.item())
106
-
107
- reward_np = np.array(reward) if not isinstance(reward, np.ndarray) else reward
108
- total_reward_base = torch.from_numpy(reward_np).float().to(device)
109
-
110
- # reward functions
111
-
112
- # encouraged to move forward (1.0) and stay upright (> 1.2 meters)
113
-
114
- z_pos = torch.from_numpy(next_state[..., 0]).float().to(device)
115
- x_vel = torch.from_numpy(next_state[..., 5]).float().to(device)
116
-
117
- reward_forward = x_vel
118
- reward_upright = (z_pos > 1.2).float()
119
-
120
- exploration_bonus = std.mean(dim = -1) * 0.05
121
- penalize_extreme_actions = (mean.abs() > 1.).float().mean(dim = -1) * 0.05
122
-
123
- penalize_action_change = 0.
124
- if exists(last_action):
125
- penalize_action_change = (last_action - action).abs().mean(dim = -1) * 0.1
126
-
127
- total_reward = total_reward_base + reward_forward + reward_upright + exploration_bonus - penalize_extreme_actions - penalize_action_change
128
-
129
- # only add reward if not done
130
-
131
- mask = (~dones).float()
132
- cum_reward += total_reward * mask
133
-
134
- # update dones
135
-
136
- dones_np = np.array(truncated | terminated) if not isinstance(truncated | terminated, np.ndarray) else (truncated | terminated)
137
- dones |= torch.from_numpy(dones_np).to(device)
138
-
139
- step += 1
140
-
141
- state = next_state
142
- last_action = action
143
-
144
- if not self.vectorized:
145
- return cum_reward.item() / self.repeats
146
-
147
- return cum_reward / self.repeats
148
-
149
- # evo strategy
150
-
151
- from x_evolution import EvoStrategy
152
-
153
- from x_mlps_pytorch.residual_normed_mlp import ResidualNormedMLP
154
-
155
- class Model(Module):
156
-
157
- def __init__(self):
158
- super().__init__()
159
-
160
- self.deep_mlp = ResidualNormedMLP(
161
- dim_in = 348,
162
- dim = 256,
163
- depth = 8,
164
- residual_every = 2
165
- )
166
-
167
- self.gru = GRU(256, 256, batch_first = True)
168
-
169
- self.to_pred = Linear(256, 17 * 2, bias = False)
170
-
171
- def forward(self, state, hiddens = None):
172
-
173
- x = self.deep_mlp(state)
174
-
175
- x = x.unsqueeze(-2)
176
- gru_out, hiddens = self.gru(x, hiddens)
177
- x = x + gru_out
178
- x = x.squeeze(-2)
179
-
180
- return self.to_pred(x), hiddens
181
-
182
- from torch.optim.lr_scheduler import CosineAnnealingLR
183
-
184
- def main(
185
- vectorized = False,
186
- num_envs = 8
187
- ):
188
- evo_strat = EvoStrategy(
189
- Model(),
190
- environment = HumanoidEnvironment(
191
- repeats = 1,
192
- render_every_eps = 200,
193
- vectorized = vectorized,
194
- num_envs = num_envs
195
- ),
196
- vectorized = vectorized,
197
- vector_size = num_envs,
198
- num_generations = 50_000,
199
- noise_population_size = 200,
200
- noise_low_rank = 1,
201
- noise_scale = 1e-2,
202
- noise_scale_clamp_range = (5e-3, 2e-2),
203
- learned_noise_scale = True,
204
- use_sigma_optimizer = True,
205
- learning_rate = 1e-3,
206
- noise_scale_learning_rate = 1e-4,
207
- use_scheduler = True,
208
- scheduler_klass = CosineAnnealingLR,
209
- scheduler_kwargs = dict(T_max = 50_000)
210
- )
211
-
212
- evo_strat()
213
-
214
- if __name__ == '__main__':
215
- fire.Fire(main)
@@ -1,141 +0,0 @@
1
- # /// script
2
- # dependencies = [
3
- # "fire",
4
- # "gymnasium[box2d]>=1.0.0",
5
- # "gymnasium[other]",
6
- # "x-evolution>=0.0.20",
7
- # "x-mlps-pytorch>=0.2.0"
8
- # ]
9
- # ///
10
-
11
- import fire
12
- from shutil import rmtree
13
- import gymnasium as gym
14
- import numpy as np
15
-
16
- import torch
17
- from torch.nn import Module
18
- import torch.nn.functional as F
19
- from x_mlps_pytorch.residual_normed_mlp import ResidualNormedMLP
20
- from torch.optim.lr_scheduler import CosineAnnealingLR
21
-
22
- class LunarEnvironment(Module):
23
- def __init__(
24
- self,
25
- video_folder = './recordings',
26
- render_every_eps = 500,
27
- max_steps = 500,
28
- repeats = 1,
29
- vectorized = False,
30
- num_envs = 1
31
- ):
32
- super().__init__()
33
-
34
- self.vectorized = vectorized
35
- self.num_envs = num_envs
36
-
37
- if vectorized:
38
- env = gym.make_vec('LunarLander-v3', num_envs = num_envs, render_mode = 'rgb_array')
39
- else:
40
- env = gym.make('LunarLander-v3', render_mode = 'rgb_array')
41
-
42
- self.env = env
43
- self.max_steps = max_steps
44
- self.repeats = repeats
45
- self.video_folder = video_folder
46
- self.render_every_eps = render_every_eps
47
-
48
- def pre_main_callback(self):
49
- # the `pre_main_callback` on the environment passed in is called before the start of the evolutionary strategies loop
50
-
51
- rmtree(self.video_folder, ignore_errors = True)
52
-
53
- if not self.vectorized:
54
- self.env = gym.wrappers.RecordVideo(
55
- env = self.env,
56
- video_folder = self.video_folder,
57
- name_prefix = 'recording',
58
- episode_trigger = lambda eps_num: (eps_num % self.render_every_eps) == 0,
59
- disable_logger = True
60
- )
61
-
62
- def forward(self, model):
63
-
64
- device = next(model.parameters()).device
65
-
66
- seed = torch.randint(0, int(1e6), ())
67
-
68
- num_envs = self.num_envs if self.vectorized else 1
69
- cum_reward = torch.zeros(num_envs, device = device)
70
-
71
- for _ in range(self.repeats):
72
- state, _ = self.env.reset(seed = seed.item())
73
-
74
- step = 0
75
- dones = torch.zeros(num_envs, device = device, dtype = torch.bool)
76
-
77
- while step < self.max_steps and not dones.all():
78
-
79
- state_torch = torch.from_numpy(state).to(device)
80
-
81
- action_logits = model(state_torch)
82
-
83
- action = F.gumbel_softmax(action_logits, hard = True).argmax(dim = -1)
84
-
85
- next_state, reward, truncated, terminated, *_ = self.env.step(action.detach().cpu().numpy() if self.vectorized else action.item())
86
-
87
- reward_np = np.array(reward) if not isinstance(reward, np.ndarray) else reward
88
- total_reward = torch.from_numpy(reward_np).float().to(device)
89
-
90
- mask = (~dones).float()
91
- cum_reward += total_reward * mask
92
-
93
- dones_np = np.array(truncated | terminated) if not isinstance(truncated | terminated, np.ndarray) else (truncated | terminated)
94
- dones |= torch.from_numpy(dones_np).to(device)
95
-
96
- step += 1
97
-
98
- state = next_state
99
-
100
- if not self.vectorized:
101
- return cum_reward.item() / self.repeats
102
-
103
- return cum_reward / self.repeats
104
-
105
- # evo strategy
106
-
107
- from x_evolution import EvoStrategy
108
-
109
- def main(
110
- vectorized = False,
111
- num_envs = 8
112
- ):
113
- actor = ResidualNormedMLP(dim_in = 8, dim = 24, depth = 2, residual_every = 1, dim_out = 4)
114
-
115
- evo_strat = EvoStrategy(
116
- actor,
117
- environment = LunarEnvironment(
118
- repeats = 2,
119
- vectorized = vectorized,
120
- num_envs = num_envs
121
- ),
122
- vectorized = vectorized,
123
- vector_size = num_envs,
124
- num_generations = 50_000,
125
- noise_population_size = 50,
126
- noise_low_rank = 1,
127
- noise_scale = 1e-2,
128
- noise_scale_clamp_range = (5e-3, 2e-2),
129
- learned_noise_scale = True,
130
- use_sigma_optimizer = True,
131
- learning_rate = 1e-3,
132
- noise_scale_learning_rate = 1e-4,
133
- use_scheduler = True,
134
- scheduler_klass = CosineAnnealingLR,
135
- scheduler_kwargs = dict(T_max = 50_000)
136
- )
137
-
138
- evo_strat()
139
-
140
- if __name__ == '__main__':
141
- fire.Fire(main)
@@ -1,91 +0,0 @@
1
- # /// script
2
- # dependencies = [
3
- # "fire",
4
- # "torchvision",
5
- # "x-mlps-pytorch>=0.2.0",
6
- # "x-evolution>=0.0.20"
7
- # ]
8
- # ///
9
-
10
- import fire
11
- import torch
12
- from torch import nn
13
- import torch.nn.functional as F
14
- from torchvision import datasets, transforms
15
- from torch.utils.data import DataLoader
16
-
17
- # model
18
-
19
- from x_mlps_pytorch.residual_normed_mlp import ResidualNormedMLP
20
-
21
- model = nn.Sequential(
22
- nn.Flatten(),
23
- ResidualNormedMLP(dim_in = 784, dim = 512, depth = 8, residual_every = 2, dim_out = 10)
24
- ).half()
25
-
26
- batch_size = 256
27
-
28
- # data
29
-
30
- dataset = datasets.MNIST('./data', train = True, download = True, transform = transforms.ToTensor())
31
-
32
- # fitness as inverse of loss
33
-
34
- def mnist_environment(
35
- model,
36
- num_envs = 1,
37
- vectorized = False,
38
- batch_size = 256
39
- ):
40
- device = next(model.parameters()).device
41
-
42
- iters = num_envs if vectorized else 1
43
-
44
- losses = []
45
-
46
- for _ in range(iters):
47
- dataloader = DataLoader(dataset, batch_size = batch_size, shuffle = True)
48
- data_iterator = iter(dataloader)
49
- data, target = next(data_iterator)
50
-
51
- data, target = data.to(device), target.to(device)
52
-
53
- with torch.no_grad():
54
- logits = model(data.half())
55
- loss = F.cross_entropy(logits, target)
56
-
57
- losses.append(-loss)
58
-
59
- if not vectorized:
60
- return losses[0]
61
-
62
- return torch.stack(losses)
63
-
64
- # evo
65
-
66
- from x_evolution import EvoStrategy
67
-
68
- def main(
69
- vectorized = False,
70
- num_envs = 8,
71
- batch_size = 256
72
- ):
73
- evo_strat = EvoStrategy(
74
- model,
75
- environment = lambda model: mnist_environment(model, num_envs = num_envs, vectorized = vectorized, batch_size = batch_size),
76
- vectorized = vectorized,
77
- vector_size = num_envs,
78
- noise_population_size = 100,
79
- noise_scale = 1e-2,
80
- noise_scale_clamp_range = (8e-3, 2e-2),
81
- noise_low_rank = 1,
82
- num_generations = 10_000,
83
- learning_rate = 1e-3,
84
- learned_noise_scale = True,
85
- noise_scale_learning_rate = 2e-5
86
- )
87
-
88
- evo_strat()
89
-
90
- if __name__ == '__main__':
91
- fire.Fire(main)
@@ -1,83 +0,0 @@
1
- import fire
2
- import torch
3
- from torch import nn
4
- import torch.nn.functional as F
5
- from torch.optim.lr_scheduler import LambdaLR
6
-
7
- # model
8
-
9
- from torch import nn
10
-
11
- model = nn.Sequential(
12
- nn.Linear(2, 16),
13
- nn.ReLU(),
14
- nn.Linear(16, 2)
15
- ).half()
16
-
17
- batch_size = 128
18
-
19
- # fitness as inverse of loss
20
-
21
- from x_evolution import EvoStrategy
22
-
23
- def xor_environment(
24
- model,
25
- num_envs = 1,
26
- vectorized = False,
27
- batch_size = 128
28
- ):
29
- device = next(model.parameters()).device
30
-
31
- iters = num_envs if vectorized else 1
32
-
33
- losses = []
34
-
35
- for _ in range(iters):
36
- data = torch.randint(0, 2, (batch_size, 2))
37
- labels = data[:, 0] ^ data[:, 1]
38
-
39
- data, labels = tuple(t.to(device) for t in (data, labels))
40
-
41
- with torch.no_grad():
42
- logits = model(data.half())
43
- loss = F.cross_entropy(logits, labels)
44
-
45
- losses.append(-loss)
46
-
47
- if not vectorized:
48
- return losses[0]
49
-
50
- return torch.stack(losses)
51
-
52
- # evo
53
-
54
- def main(
55
- vectorized = False,
56
- num_envs = 8,
57
- batch_size = 128
58
- ):
59
- evo_strat = EvoStrategy(
60
- model,
61
- environment = lambda model: xor_environment(model, num_envs = num_envs, vectorized = vectorized, batch_size = batch_size),
62
- vectorized = vectorized,
63
- vector_size = num_envs,
64
- noise_population_size = 100,
65
- noise_low_rank = 1,
66
- num_generations = 100_000,
67
- learning_rate = 1e-1,
68
- noise_scale = 1e-1,
69
- noise_scale_clamp_range = (0.05, 0.2),
70
- learned_noise_scale = True,
71
- noise_scale_learning_rate = 5e-4,
72
- use_scheduler = True,
73
- scheduler_klass = LambdaLR,
74
- scheduler_kwargs = dict(lr_lambda = lambda step: min(1., step / 10.)),
75
- use_sigma_scheduler = True,
76
- sigma_scheduler_klass = LambdaLR,
77
- sigma_scheduler_kwargs = dict(lr_lambda = lambda step: min(1., step / 10.))
78
- )
79
-
80
- evo_strat()
81
-
82
- if __name__ == '__main__':
83
- fire.Fire(main)
File without changes
File without changes