evolutionary-policy-optimization 0.3.1__tar.gz → 0.4.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: evolutionary-policy-optimization
3
- Version: 0.3.1
3
+ Version: 0.4.0
4
4
  Summary: EPO - Pytorch
5
5
  Project-URL: Homepage, https://pypi.org/project/evolutionary-policy-optimization/
6
6
  Project-URL: Repository, https://github.com/lucidrains/evolutionary-policy-optimization
@@ -33,13 +33,14 @@ Classifier: Intended Audience :: Developers
33
33
  Classifier: License :: OSI Approved :: MIT License
34
34
  Classifier: Programming Language :: Python :: 3.8
35
35
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
36
- Requires-Python: >=3.9
36
+ Requires-Python: >=3.10
37
37
  Requires-Dist: accelerate>=1.6.0
38
38
  Requires-Dist: assoc-scan>=0.0.2
39
39
  Requires-Dist: einops>=0.8.1
40
40
  Requires-Dist: einx>=0.3.0
41
41
  Requires-Dist: ema-pytorch>=0.7.7
42
42
  Requires-Dist: hl-gauss-pytorch>=0.2.8
43
+ Requires-Dist: mean-conc-beta>=0.0.7
43
44
  Requires-Dist: torch>=2.2
44
45
  Requires-Dist: tqdm
45
46
  Requires-Dist: x-mlps-pytorch>=0.3.0
@@ -366,6 +367,16 @@ That's it
366
367
  }
367
368
  ```
368
369
 
370
+ ```bibtex
371
+ @inproceedings{Sharma2020DynamicsAwareUD,
372
+ title = {Dynamics-Aware Unsupervised Discovery of Skills},
373
+ author = {Archit Sharma and Shixiang Gu and Sergey Levine and Vikash Kumar and Karol Hausman},
374
+ booktitle = {International Conference on Learning Representations},
375
+ year = {2020},
376
+ url = {https://arxiv.org/abs/1907.01657}
377
+ }
378
+ ```
379
+
369
380
  ```bibtex
370
381
  @article{Zhu2025Hybrid,
371
382
  author = {Zhu, Zimo and Yu, Chuanqiang and Wang, Junti},
@@ -305,6 +305,16 @@ That's it
305
305
  }
306
306
  ```
307
307
 
308
+ ```bibtex
309
+ @inproceedings{Sharma2020DynamicsAwareUD,
310
+ title = {Dynamics-Aware Unsupervised Discovery of Skills},
311
+ author = {Archit Sharma and Shixiang Gu and Sergey Levine and Vikash Kumar and Karol Hausman},
312
+ booktitle = {International Conference on Learning Representations},
313
+ year = {2020},
314
+ url = {https://arxiv.org/abs/1907.01657}
315
+ }
316
+ ```
317
+
308
318
  ```bibtex
309
319
  @article{Zhu2025Hybrid,
310
320
  author = {Zhu, Zimo and Yu, Chuanqiang and Wang, Junti},
@@ -1,3 +1,3 @@
1
1
  from evolutionary_policy_optimization.env_wrappers import GymnasiumEnvWrapper, rescale_from_to
2
- from evolutionary_policy_optimization.epo import EPO, MLP, Actor, Agent, BetaActionDistr, CategoricalActionDistr, Critic, LatentGenePool, create_agent
2
+ from evolutionary_policy_optimization.epo import EPO, MLP, Actor, Agent, Beta, CategoricalActionDistr, Critic, DiversityDiscr, LatentGenePool, create_agent
3
3
  from evolutionary_policy_optimization.mock_env import Env, VectorEnv
@@ -5,9 +5,8 @@ from torch.nn import Module
5
5
 
6
6
  from evolutionary_policy_optimization.epo import Agent, create_agent, exists
7
7
 
8
-
9
- def rescale_from_to(x, from_range = (0., 1.), to_range = (-1., 1.)):
10
- # e.g. beta actions on (0, 1) -> the env's action bounds
8
+ def rescale_from_to(x, from_range = (-1., 1.), to_range = (-1., 1.)):
9
+ # e.g. beta actions on (-1, 1) -> the env's action bounds
11
10
 
12
11
  from_low, from_high = from_range
13
12
  to_low, to_high = to_range
@@ -36,7 +35,7 @@ class GymnasiumEnvWrapper(Module):
36
35
  return self.env.reset(*args, **kwargs)
37
36
 
38
37
  def step(self, actions, *args, **kwargs):
39
- # beta lives on (0, 1) - rescale to the env's bounds at the interface
38
+ # continuous actions are on (-1, 1) - rescale to env bounds at interface
40
39
 
41
40
  if exists(self.rescale_to):
42
41
  actions = rescale_from_to(actions, to_range = self.rescale_to)
@@ -15,15 +15,15 @@ import torch
15
15
  import torch.distributed as dist
16
16
  import torch.nn.functional as F
17
17
  from accelerate import Accelerator
18
- from adam_atan2_pytorch import AdoptAtan2
18
+ from torch.optim import AdamW
19
19
  from assoc_scan import AssocScan
20
20
  from einops import einsum, rearrange, reduce, repeat
21
21
  from einops.layers.torch import Rearrange
22
22
  from ema_pytorch import EMA
23
23
  from hl_gauss_pytorch import HLGaussLayer
24
+ from mean_conc_beta import Beta
24
25
  from torch import Tensor, cat, from_numpy, is_tensor, nn, stack, tensor
25
- from torch.distributions import Beta, Categorical, Distribution
26
-
26
+ from torch.distributions import Categorical, Distribution
27
27
  from torch.nn import Linear, Module, ModuleList
28
28
  from torch.utils._pytree import tree_map
29
29
  from torch.utils.data import DataLoader, TensorDataset
@@ -513,7 +513,7 @@ class MLP(Module):
513
513
 
514
514
  return self.final_lime(x, prev_layer_inputs)
515
515
 
516
- # discriminator for predicting latent code from states and actions (DIAYN - Eysenbach et al. 2018)
516
+ # discriminator for predicting latent code from states or state transitions (DIAYN / DADS)
517
517
 
518
518
  class DiversityDiscr(Module):
519
519
  def __init__(
@@ -521,15 +521,19 @@ class DiversityDiscr(Module):
521
521
  dim_state,
522
522
  num_latents,
523
523
  dim = 64,
524
- depth = 2
524
+ depth = 2,
525
+ time_gap = 1
525
526
  ):
526
527
  super().__init__()
528
+ self.time_gap = time_gap
529
+ self.state_transition = time_gap > 0
530
+
527
531
  self.state_proj = nn.Linear(dim_state, dim)
528
532
 
529
533
  self.net = AttnResidualNormedMLP(
530
534
  dim = dim,
531
535
  depth = depth,
532
- dim_in = dim * 2,
536
+ dim_in = dim * 2 if self.state_transition else dim,
533
537
  dim_out = num_latents
534
538
  )
535
539
 
@@ -538,14 +542,18 @@ class DiversityDiscr(Module):
538
542
  if isinstance(module, (nn.Linear, nn.LayerNorm, nn.RMSNorm)):
539
543
  module.reset_parameters()
540
544
 
541
- def forward(self, state, next_state):
545
+ def forward(self, state, next_state = None):
542
546
  state_embed = self.state_proj(state)
547
+
548
+ if not self.state_transition or not exists(next_state):
549
+ return self.net(state_embed)
550
+
543
551
  next_state_embed = self.state_proj(next_state)
544
552
  return self.net((state_embed, next_state_embed))
545
553
 
546
554
  # action distributions - the actor always returns a `Distribution`, either
547
555
  # categorical (discrete) or beta mean-conc (continuous). beta is defined over
548
- # (0, 1) - rescale to the env's action range at the interface
556
+ # (-1, 1) - multiplied by some constant usually (e.g. 0.4) for the env action range
549
557
 
550
558
  class CategoricalActionDistr(Module):
551
559
  def forward(self, logits, temperature = 1.):
@@ -554,58 +562,6 @@ class CategoricalActionDistr(Module):
554
562
 
555
563
  return Categorical(logits = logits)
556
564
 
557
- class BetaActionDistr(Module):
558
- def __init__(
559
- self,
560
- init_conc = 2.,
561
- min_conc = 0.,
562
- eps = 1e-5
563
- ):
564
- super().__init__()
565
- assert init_conc > min_conc
566
-
567
- self.init_conc = init_conc
568
- self.min_conc = min_conc
569
- self.eps = eps
570
-
571
- # softplus offset so the concentration at raw_conc = 0 is exactly init_conc
572
-
573
- self.raw_init_conc = math.log(math.expm1(init_conc - min_conc))
574
-
575
- def mean(self, params):
576
- # the beta mean is exactly (tanh(raw_mean) + 1) / 2 by construction
577
-
578
- raw_mean, _ = params.unbind(dim = -1)
579
- return ((torch.tanh(raw_mean) + 1.) * 0.5).clamp(min = self.eps, max = 1. - self.eps)
580
-
581
- def entropy(self, params, temperature = 1.):
582
- # entropy is over the rescaled (2x - 1) in [-1, 1], with log 2 jacobian
583
- # adjustment for the differential entropy of an affine transform
584
-
585
- distr = self.forward(params, temperature = temperature)
586
- return distr.entropy() + math.log(2.)
587
-
588
- def forward(self, params, temperature = 1.):
589
- _, raw_conc = params.unbind(dim = -1)
590
-
591
- mean = self.mean(params)
592
-
593
- conc = F.softplus(raw_conc + self.raw_init_conc) + self.min_conc
594
-
595
- # concentration floor - unimodal (alpha > 1 and beta > 1), mean kept exact
596
-
597
- conc = conc + 1. / torch.minimum(mean, 1. - mean).clamp(min = self.eps)
598
-
599
- # temperature scales the concentration - lower temperature, sharper policy
600
-
601
- if temperature > 0. and temperature != 1.:
602
- conc = conc / temperature
603
-
604
- alpha = mean * conc
605
- beta = (1. - mean) * conc
606
-
607
- return Beta(alpha, beta)
608
-
609
565
  # self-predictive representations (SPR) over the actor's hidden embedding
610
566
 
611
567
  class HiddenSpr(Module):
@@ -689,6 +645,7 @@ class Actor(Module):
689
645
  state_norm: StateNorm | None = None,
690
646
  dim_latent = 0,
691
647
  action_is_continuous = False, # continuous control - beta policy
648
+ beta_kwargs: dict = dict(),
692
649
  ):
693
650
  super().__init__()
694
651
 
@@ -720,7 +677,7 @@ class Actor(Module):
720
677
  nn.Linear(dim, num_actions, bias = False),
721
678
  )
722
679
 
723
- self.action_distr = BetaActionDistr() if self.beta_actions else CategoricalActionDistr()
680
+ self.action_distr = Beta(**beta_kwargs) if self.beta_actions else CategoricalActionDistr()
724
681
 
725
682
  def latent(
726
683
  self,
@@ -1079,6 +1036,8 @@ class LatentGenePool(Module):
1079
1036
  should_update_per_island = self.should_run_genetic_algorithm(fitness)
1080
1037
 
1081
1038
  if not should_update_per_island.any():
1039
+ self.advance_step_()
1040
+
1082
1041
  if inplace:
1083
1042
  return False, None
1084
1043
 
@@ -1229,13 +1188,13 @@ class Agent(Module):
1229
1188
  actor: Actor,
1230
1189
  critic: Critic,
1231
1190
  latent_gene_pool: LatentGenePool | None,
1232
- optim_klass = AdoptAtan2,
1191
+ optim_klass = AdamW,
1233
1192
  state_norm: StateNorm | None = None,
1234
1193
  actor_lr = 8e-4,
1235
1194
  critic_lr = 8e-4,
1236
1195
  latent_lr = 1e-5,
1237
- actor_weight_decay = 5e-4,
1238
- critic_weight_decay = 5e-4,
1196
+ actor_weight_decay = 0.,
1197
+ critic_weight_decay = 0.,
1239
1198
  diversity_aux_loss_weight = 0.,
1240
1199
  use_critic_ema = True,
1241
1200
  critic_ema_beta = 0.95,
@@ -1264,6 +1223,7 @@ class Agent(Module):
1264
1223
  critic_optim_kwargs: dict = dict(),
1265
1224
  latent_optim_kwargs: dict = dict(),
1266
1225
  use_diversity_discr = False,
1226
+ diversity_time_gap = 1, # 0 for DIAYN (state only), >= 1 for DADS (state transition with given time gap)
1267
1227
  diversity_discr_kwargs: dict = dict(dim = 64, depth = 2),
1268
1228
  diversity_discr_lr = 3e-4,
1269
1229
  diversity_discr_optim_kwargs: dict = dict(),
@@ -1354,14 +1314,16 @@ class Agent(Module):
1354
1314
  # diversity discriminator
1355
1315
 
1356
1316
  self.use_diversity_discr = use_diversity_discr
1317
+ self.diversity_time_gap = diversity_time_gap
1357
1318
 
1358
1319
  if use_diversity_discr:
1359
- assert exists(latent_gene_pool), 'latent_gene_pool must be present to use DIAYN'
1320
+ assert exists(latent_gene_pool), 'latent_gene_pool must be present to use DIAYN / DADS'
1360
1321
  dim_state = actor.init_layer[0].in_features
1361
1322
 
1362
1323
  self.diversity_discr = DiversityDiscr(
1363
1324
  dim_state = dim_state,
1364
1325
  num_latents = latent_gene_pool.num_latents,
1326
+ time_gap = diversity_time_gap,
1365
1327
  **diversity_discr_kwargs
1366
1328
  )
1367
1329
 
@@ -1492,6 +1454,7 @@ class Agent(Module):
1492
1454
  critic_ema = self.critic_ema.state_dict() if self.use_critic_ema else None,
1493
1455
  latents = unwrap(self.latent_gene_pool).state_dict() if self.has_latent_genes else None,
1494
1456
  diversity_discr = unwrap(self.diversity_discr).state_dict() if self.use_diversity_discr else None,
1457
+ diversity_time_gap = self.diversity_time_gap,
1495
1458
  has_diversity_discr_warmed_up = self.has_diversity_discr_warmed_up.item(),
1496
1459
  actor_optim = unwrap_optim(self.actor_optim).state_dict(),
1497
1460
  critic_optim = unwrap_optim(self.critic_optim).state_dict(),
@@ -1524,6 +1487,9 @@ class Agent(Module):
1524
1487
  if self.use_diversity_discr and 'diversity_discr' in pkg and exists(pkg['diversity_discr']):
1525
1488
  unwrap(self.diversity_discr).load_state_dict(pkg['diversity_discr'])
1526
1489
 
1490
+ if 'diversity_time_gap' in pkg:
1491
+ self.diversity_time_gap = pkg['diversity_time_gap']
1492
+
1527
1493
  if 'has_diversity_discr_warmed_up' in pkg:
1528
1494
  self.has_diversity_discr_warmed_up.copy_(tensor(pkg['has_diversity_discr_warmed_up']))
1529
1495
 
@@ -1771,7 +1737,8 @@ class Agent(Module):
1771
1737
  diversity_discr_loss = self.zero
1772
1738
 
1773
1739
  if self.use_diversity_discr:
1774
- diversity_discr_logits = self.diversity_discr(states, next_states)
1740
+ discr_inp = (states,) if self.diversity_time_gap == 0 else (states, next_states)
1741
+ diversity_discr_logits = self.diversity_discr(*discr_inp)
1775
1742
  diversity_discr_loss = F.cross_entropy(diversity_discr_logits, latent_gene_ids)
1776
1743
 
1777
1744
  diversity_discr_loss.backward()
@@ -1927,6 +1894,7 @@ def create_agent(
1927
1894
  use_critic_ema = True,
1928
1895
  use_state_norm = False,
1929
1896
  action_is_continuous = False, # continuous control - beta policy
1897
+ diversity_time_gap = 1,
1930
1898
  latent_gene_pool_kwargs: dict = dict(),
1931
1899
  actor_kwargs: dict = dict(),
1932
1900
  critic_kwargs: dict = dict(),
@@ -1972,6 +1940,7 @@ def create_agent(
1972
1940
  state_norm = state_norm,
1973
1941
  latent_gene_pool = latent_gene_pool,
1974
1942
  use_critic_ema = use_critic_ema,
1943
+ diversity_time_gap = diversity_time_gap,
1975
1944
  **kwargs
1976
1945
  )
1977
1946
 
@@ -2208,7 +2177,16 @@ class EPO(Module):
2208
2177
 
2209
2178
  if self.agent.use_diversity_discr and self.agent.has_diversity_discr_warmed_up.item():
2210
2179
  with torch.no_grad():
2211
- logits = self.agent.unwrap_model(self.agent.diversity_discr)(rearrange(state, '... -> 1 ...'), rearrange(next_state, '... -> 1 ...'))
2180
+ discr = self.agent.unwrap_model(self.agent.diversity_discr)
2181
+ time_gap = self.agent.diversity_time_gap
2182
+
2183
+ if time_gap == 0:
2184
+ logits = discr(rearrange(state, '... -> 1 ...'))
2185
+ elif time_gap == 1 or len(slot.memories) < (time_gap - 1):
2186
+ logits = discr(rearrange(state, '... -> 1 ...'), rearrange(next_state, '... -> 1 ...'))
2187
+ else:
2188
+ past_state = slot.memories[-(time_gap - 1)].state
2189
+ logits = discr(rearrange(past_state, '... -> 1 ...'), rearrange(next_state, '... -> 1 ...'))
2212
2190
 
2213
2191
  latent_log_probs = logits.log_softmax(dim = -1)
2214
2192
  diversity_reward = latent_log_probs[0, latent_id] + math.log(self.agent.num_latents)
@@ -2233,6 +2211,11 @@ class EPO(Module):
2233
2211
 
2234
2212
  slot.memories.append(memory)
2235
2213
 
2214
+ if self.agent.use_diversity_discr and self.agent.diversity_time_gap > 1:
2215
+ time_gap = self.agent.diversity_time_gap
2216
+ if len(slot.memories) >= time_gap:
2217
+ slot.memories[-time_gap] = slot.memories[-time_gap]._replace(next_state = next_state)
2218
+
2236
2219
  rewards_per_latent_episode[latent_id, episode_id] += reward
2237
2220
 
2238
2221
  # episode is done - bootstrap value if truncated, then refill
@@ -1,12 +1,12 @@
1
1
  [project]
2
2
  name = "evolutionary-policy-optimization"
3
- version = "0.3.1"
3
+ version = "0.4.0"
4
4
  description = "EPO - Pytorch"
5
5
  authors = [
6
6
  { name = "Phil Wang", email = "lucidrains@gmail.com" }
7
7
  ]
8
8
  readme = "README.md"
9
- requires-python = ">= 3.9"
9
+ requires-python = ">= 3.10"
10
10
  license = { file = "LICENSE" }
11
11
  keywords = [
12
12
  'artificial intelligence',
@@ -30,6 +30,7 @@ dependencies = [
30
30
  'einops>=0.8.1',
31
31
  'ema-pytorch>=0.7.7',
32
32
  'hl-gauss-pytorch>=0.2.8',
33
+ 'mean-conc-beta>=0.0.7',
33
34
  'torch>=2.2',
34
35
  'tqdm',
35
36
  "x-mlps-pytorch>=0.3.0",