evolutionary-policy-optimization 0.4.2__tar.gz → 0.5.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.4.2
3
+ Version: 0.5.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
@@ -44,7 +44,7 @@ Requires-Dist: mean-conc-beta>=0.2.7
44
44
  Requires-Dist: torch>=2.2
45
45
  Requires-Dist: tqdm
46
46
  Requires-Dist: x-mlps-pytorch>=0.3.0
47
- Requires-Dist: x-ppo>=0.0.16
47
+ Requires-Dist: x-ppo>=0.0.21
48
48
  Provides-Extra: examples
49
49
  Requires-Dist: fire; extra == 'examples'
50
50
  Requires-Dist: gymnasium[box2d]>=1.0.0; extra == 'examples'
@@ -15,7 +15,6 @@ 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 torch.optim import AdamW
19
18
  from assoc_scan import AssocScan
20
19
  from einops import einsum, rearrange, reduce, repeat
21
20
  from einops.layers.torch import Rearrange
@@ -25,6 +24,7 @@ from mean_conc_beta import Beta
25
24
  from torch import Tensor, cat, from_numpy, is_tensor, nn, stack, tensor
26
25
  from torch.distributions import Categorical, Distribution
27
26
  from torch.nn import Linear, Module, ModuleList
27
+ from torch.optim import AdamW
28
28
  from torch.utils._pytree import tree_map
29
29
  from torch.utils.data import DataLoader, TensorDataset
30
30
  from tqdm import tqdm
@@ -438,20 +438,32 @@ class MLP(Module):
438
438
  depth,
439
439
  dim_latent = 0,
440
440
  latent_mapping_network_depth = 2,
441
- expansion_factor = 2.
441
+ expansion_factor = 2.,
442
+ latent_conditioning = 'film', # film | concat (direct z injection, x-ppo style)
442
443
  ):
443
444
  super().__init__()
444
445
  dim_latent = default(dim_latent, 0)
445
446
 
446
447
  self.dim_latent = dim_latent
448
+ self.latent_conditioning = latent_conditioning
447
449
 
448
450
  self.needs_latent = dim_latent > 0
449
451
 
450
- self.encode_latent = nn.Sequential(
451
- LatentMappingNetwork(dim_latent, depth = latent_mapping_network_depth),
452
- Linear(dim_latent, dim * 2),
453
- nn.SiLU()
454
- ) if self.needs_latent else None
452
+ assert latent_conditioning in ('film', 'concat')
453
+
454
+ self.encode_latent = None
455
+
456
+ if self.needs_latent and latent_conditioning == 'film':
457
+ self.encode_latent = nn.Sequential(
458
+ LatentMappingNetwork(dim_latent, depth = latent_mapping_network_depth),
459
+ Linear(dim_latent, dim * 2),
460
+ nn.SiLU()
461
+ )
462
+
463
+ self.latent_proj = None
464
+
465
+ if self.needs_latent and latent_conditioning == 'concat':
466
+ self.latent_proj = nn.Linear(dim_latent, dim, bias = False)
455
467
 
456
468
  dim_hidden = int(dim * expansion_factor)
457
469
 
@@ -464,7 +476,7 @@ class MLP(Module):
464
476
 
465
477
  film = None
466
478
 
467
- if self.needs_latent:
479
+ if self.needs_latent and latent_conditioning == 'film':
468
480
  film = FiLM(dim * 2, dim)
469
481
 
470
482
  lime = DynamicLIMe(dim, num_layers = ind + 1) if not is_first else None
@@ -498,16 +510,17 @@ class MLP(Module):
498
510
  assert xnor(self.needs_latent, exists(latent))
499
511
 
500
512
  if exists(latent):
501
- # start with naive concatenative conditioning
502
- # but will also offer some alternatives once a spark is seen (film, adaptive linear from stylegan, etc)
503
-
504
- latent = self.encode_latent(latent)
513
+ if self.latent_conditioning == 'film':
514
+ latent = self.encode_latent(latent)
505
515
 
506
516
  if latent.ndim == 1:
507
517
  latent = repeat(latent, 'd -> b d', b = batch)
508
518
 
509
519
  assert latent.shape[0] == x.shape[0], f'received state with batch size {x.shape[0]} but latent ids received had batch size {latent.shape[0]}'
510
520
 
521
+ if self.latent_conditioning == 'concat':
522
+ x = x + self.latent_proj(latent)
523
+
511
524
  # layers
512
525
 
513
526
  prev_layer_inputs = [x]
@@ -528,43 +541,66 @@ class MLP(Module):
528
541
 
529
542
  return self.final_lime(x, prev_layer_inputs)
530
543
 
531
- # discriminator for predicting latent code from states or state transitions (DIAYN / DADS)
544
+ # discriminator for predicting latent code from states, transitions, and optionally actions (DIAYN / DADS)
532
545
 
533
546
  class DiversityDiscr(Module):
534
547
  def __init__(
535
548
  self,
536
549
  dim_state,
537
- num_latents,
550
+ num_latents = None,
538
551
  dim = 64,
539
552
  depth = 2,
540
- time_gap = 1
553
+ time_gap = 1,
554
+ dim_latent = None, # continuous latent -> regression head, DADS style
555
+ dim_action = None, # condition on action q(z | s, a, s')
556
+ action_is_continuous = False,
541
557
  ):
542
558
  super().__init__()
543
- self.time_gap = time_gap
559
+
560
+ assert exists(num_latents) ^ exists(dim_latent), 'give either num_latents (discrete) or dim_latent (continuous)'
561
+
544
562
  self.state_transition = time_gap > 0
563
+ self.has_action = exists(dim_action)
564
+ self.action_is_continuous = action_is_continuous
545
565
 
546
566
  self.state_proj = nn.Linear(dim_state, dim)
547
567
 
568
+ self.action_proj = None
569
+
570
+ if self.has_action:
571
+ self.action_proj = nn.Linear(dim_action, dim) if action_is_continuous else nn.Embedding(dim_action, dim)
572
+
573
+ dim_in = dim * (2 if self.state_transition else 1) + (dim if self.has_action else 0)
574
+ dim_out = default(dim_latent, num_latents)
575
+
548
576
  self.net = AttnResidualNormedMLP(
549
577
  dim = dim,
550
578
  depth = depth,
551
- dim_in = dim * 2 if self.state_transition else dim,
552
- dim_out = num_latents
579
+ dim_in = dim_in,
580
+ dim_out = dim_out
553
581
  )
554
582
 
555
583
  def reset_parameters(self):
556
584
  for module in self.modules():
557
- if isinstance(module, (nn.Linear, nn.LayerNorm, nn.RMSNorm)):
585
+ if isinstance(module, (nn.Linear, nn.LayerNorm, nn.RMSNorm, nn.Embedding)):
558
586
  module.reset_parameters()
559
587
 
560
- def forward(self, state, next_state = None):
588
+ def forward(self, state, next_state = None, action = None):
561
589
  state_embed = self.state_proj(state)
562
590
 
563
- if not self.state_transition or not exists(next_state):
564
- return self.net(state_embed)
591
+ inputs = [state_embed]
592
+
593
+ if self.state_transition:
594
+ assert exists(next_state), 'discriminator was built with transitions but no next_state was given'
595
+ inputs.append(self.state_proj(next_state))
596
+
597
+ assert not self.has_action or exists(action), 'discriminator was built with actions but none were given'
565
598
 
566
- next_state_embed = self.state_proj(next_state)
567
- return self.net((state_embed, next_state_embed))
599
+ if self.has_action:
600
+ action_inp = action if self.action_is_continuous else action.long()
601
+ inputs.append(self.action_proj(action_inp))
602
+
603
+ return self.net(inputs[0] if len(inputs) == 1 else tuple(inputs))
568
604
 
569
605
  # action distributions - the actor always returns a `Distribution`, either
570
606
  # categorical (discrete) or beta mean-conc (continuous). beta is defined over
@@ -661,6 +697,7 @@ class Actor(Module):
661
697
  dim_latent = 0,
662
698
  action_is_continuous = False, # continuous control - beta policy
663
699
  beta_kwargs: dict = dict(),
700
+ latent_conditioning = 'film',
664
701
  ):
665
702
  super().__init__()
666
703
 
@@ -676,7 +713,7 @@ class Actor(Module):
676
713
  nn.SiLU()
677
714
  )
678
715
 
679
- self.mlp = MLP(dim = dim, depth = mlp_depth, dim_latent = dim_latent)
716
+ self.mlp = MLP(dim = dim, depth = mlp_depth, dim_latent = dim_latent, latent_conditioning = latent_conditioning)
680
717
 
681
718
  if self.beta_actions:
682
719
  # beta head - (raw mean, raw concentration) per action dim
@@ -733,7 +770,8 @@ class Critic(Module):
733
770
  min_value = 0.,
734
771
  max_value = 500.,
735
772
  num_bins = 250
736
- )
773
+ ),
774
+ latent_conditioning = 'film',
737
775
  ):
738
776
  super().__init__()
739
777
 
@@ -746,7 +784,7 @@ class Critic(Module):
746
784
  nn.SiLU()
747
785
  )
748
786
 
749
- self.mlp = MLP(dim = dim, depth = mlp_depth, dim_latent = dim_latent)
787
+ self.mlp = MLP(dim = dim, depth = mlp_depth, dim_latent = dim_latent, latent_conditioning = latent_conditioning)
750
788
 
751
789
  self.final_norm = nn.RMSNorm(dim)
752
790
 
@@ -1253,6 +1291,8 @@ class Agent(Module):
1253
1291
  accelerate_kwargs: dict = dict(),
1254
1292
  accelerator = None,
1255
1293
  quiet: bool = False,
1294
+ latent_mode = 'gene_pool', # gene_pool (evolved latents) | fixed (sample z ~ N(0, I))
1295
+ num_latents = 1, # fixed mode: latent samples per rollout cycle
1256
1296
  ):
1257
1297
  super().__init__()
1258
1298
 
@@ -1290,14 +1330,28 @@ class Agent(Module):
1290
1330
  **ema_kwargs
1291
1331
  ) if use_critic_ema else None
1292
1332
 
1333
+ assert latent_mode in ('gene_pool', 'fixed')
1334
+
1335
+ self.latent_mode = latent_mode
1293
1336
  self.latent_gene_pool = latent_gene_pool
1294
- self.num_latents = latent_gene_pool.num_latents if exists(latent_gene_pool) else 1
1295
1337
  self.has_latent_genes = exists(latent_gene_pool)
1338
+ self.has_fixed_latents = latent_mode == 'fixed'
1339
+ self.has_latents = self.has_latent_genes or self.has_fixed_latents
1340
+ self.dim_latent = default(actor.dim_latent, 0)
1296
1341
 
1342
+ assert not (self.has_latent_genes and self.has_fixed_latents)
1297
1343
  assert actor.dim_latent == critic.dim_latent
1298
1344
 
1299
1345
  if self.has_latent_genes:
1300
1346
  assert latent_gene_pool.dim_latent == actor.dim_latent
1347
+ elif self.has_fixed_latents:
1348
+ assert self.dim_latent > 0, 'fixed latent mode needs dim_latent > 0'
1349
+
1350
+ self.num_latents = (
1351
+ latent_gene_pool.num_latents if self.has_latent_genes
1352
+ else num_latents if self.has_fixed_latents
1353
+ else 1
1354
+ )
1301
1355
 
1302
1356
  # gae function
1303
1357
 
@@ -1333,13 +1387,27 @@ class Agent(Module):
1333
1387
  self.use_diversity_discr = use_diversity_discr
1334
1388
  self.diversity_time_gap = diversity_time_gap
1335
1389
 
1390
+ self.diversity_discr_loss_fn = F.mse_loss if self.has_fixed_latents else F.cross_entropy
1391
+
1336
1392
  if use_diversity_discr:
1337
- assert exists(latent_gene_pool), 'latent_gene_pool must be present to use DIAYN / DADS'
1393
+ assert self.has_latents, 'latent gene pool or fixed latents must be present to use DIAYN / DADS'
1394
+
1395
+ if self.has_fixed_latents:
1396
+ assert diversity_time_gap in (0, 1), 'fixed latents support DIAYN (0) or DADS (1) transitions'
1397
+
1338
1398
  dim_state = actor.init_layer[0].in_features
1339
1399
 
1400
+ diversity_discr_kwargs = dict(diversity_discr_kwargs)
1401
+
1402
+ if self.has_fixed_latents:
1403
+ diversity_discr_kwargs['dim_latent'] = self.dim_latent
1404
+ diversity_discr_kwargs['dim_action'] = actor.num_actions
1405
+ diversity_discr_kwargs['action_is_continuous'] = actor.beta_actions
1406
+ else:
1407
+ diversity_discr_kwargs['num_latents'] = latent_gene_pool.num_latents
1408
+
1340
1409
  self.diversity_discr = DiversityDiscr(
1341
1410
  dim_state = dim_state,
1342
- num_latents = latent_gene_pool.num_latents,
1343
1411
  time_gap = diversity_time_gap,
1344
1412
  **diversity_discr_kwargs
1345
1413
  )
@@ -1350,7 +1418,6 @@ class Agent(Module):
1350
1418
  self.diversity_discr_optim = None
1351
1419
 
1352
1420
  self.clip_value = clip_value
1353
- self.critic_loss_kwargs = critic_loss_kwargs
1354
1421
 
1355
1422
  # self-predictive representations (SPR)
1356
1423
 
@@ -1373,6 +1440,7 @@ class Agent(Module):
1373
1440
 
1374
1441
  self.register_buffer('has_diversity_discr_warmed_up', tensor(False))
1375
1442
  self.register_buffer('zero', tensor(0.))
1443
+ self.last_diversity_discr_loss = 0.
1376
1444
 
1377
1445
  # shrink and perturb every
1378
1446
 
@@ -1382,7 +1450,7 @@ class Agent(Module):
1382
1450
 
1383
1451
  # promotes latents to be farther apart for diversity maintenance
1384
1452
 
1385
- self.has_diversity_loss = diversity_aux_loss_weight > 0.
1453
+ self.has_diversity_loss = diversity_aux_loss_weight > 0. and self.has_latent_genes
1386
1454
  self.diversity_aux_loss_weight = diversity_aux_loss_weight
1387
1455
 
1388
1456
  # wrap with accelerate
@@ -1469,6 +1537,9 @@ class Agent(Module):
1469
1537
  actor = unwrap(self.actor).state_dict(),
1470
1538
  critic = unwrap(self.critic).state_dict(),
1471
1539
  critic_ema = self.critic_ema.state_dict() if self.use_critic_ema else None,
1540
+ latent_mode = self.latent_mode,
1541
+ num_latents = self.num_latents,
1542
+ dim_latent = self.dim_latent,
1472
1543
  latents = unwrap(self.latent_gene_pool).state_dict() if self.has_latent_genes else None,
1473
1544
  diversity_discr = unwrap(self.diversity_discr).state_dict() if self.use_diversity_discr else None,
1474
1545
  diversity_time_gap = self.diversity_time_gap,
@@ -1492,6 +1563,9 @@ class Agent(Module):
1492
1563
 
1493
1564
  pkg = torch.load(str(path), weights_only = True)
1494
1565
 
1566
+ if 'latent_mode' in pkg:
1567
+ assert pkg['latent_mode'] == self.latent_mode, 'checkpoint latent_mode does not match the agent'
1568
+
1495
1569
  unwrap(self.actor).load_state_dict(pkg['actor'])
1496
1570
  unwrap(self.critic).load_state_dict(pkg['critic'])
1497
1571
 
@@ -1499,6 +1573,7 @@ class Agent(Module):
1499
1573
  self.critic_ema.load_state_dict(pkg['critic_ema'])
1500
1574
 
1501
1575
  if 'latents' in pkg and exists(pkg['latents']):
1576
+ assert self.has_latent_genes, 'checkpoint carries a latent gene pool but agent is in fixed latent mode'
1502
1577
  unwrap(self.latent_gene_pool).load_state_dict(pkg['latents'])
1503
1578
 
1504
1579
  if self.use_diversity_discr and 'diversity_discr' in pkg and exists(pkg['diversity_discr']):
@@ -1541,6 +1616,8 @@ class Agent(Module):
1541
1616
  if not exists(latent) and exists(latent_id) and exists(self.latent_gene_pool):
1542
1617
  latent = maybe_unwrap(self.latent_gene_pool)(latent_id = latent_id)
1543
1618
 
1619
+ assert not self.has_fixed_latents or exists(latent), 'fixed latent mode needs a latent sampled from N(0, I)'
1620
+
1544
1621
  return maybe_unwrap(self.actor)(state, latent, temperature = temperature)
1545
1622
 
1546
1623
  @move_input_tensors_to_device
@@ -1587,6 +1664,8 @@ class Agent(Module):
1587
1664
  if not exists(latent) and exists(latent_id) and exists(self.latent_gene_pool):
1588
1665
  latent = maybe_unwrap(self.latent_gene_pool)(latent_id = latent_id)
1589
1666
 
1667
+ assert not self.has_fixed_latents or exists(latent), 'fixed latent mode needs a latent sampled from N(0, I)'
1668
+
1590
1669
  critic_forward = maybe_unwrap(self.critic)
1591
1670
 
1592
1671
  if use_ema_if_available and self.use_critic_ema:
@@ -1634,7 +1713,7 @@ class Agent(Module):
1634
1713
  episode_ids,
1635
1714
  states,
1636
1715
  next_states,
1637
- latent_gene_ids,
1716
+ latent_refs,
1638
1717
  actions,
1639
1718
  log_probs,
1640
1719
  rewards,
@@ -1656,7 +1735,7 @@ class Agent(Module):
1656
1735
 
1657
1736
  valid_episode = episode_ids >= 0
1658
1737
 
1659
- fields = tuple(t[valid_episode] for t in (advantages, states, next_states, latent_gene_ids, actions, log_probs, values, dones))
1738
+ fields = tuple(t[valid_episode] for t in (advantages, states, next_states, latent_refs, actions, log_probs, values, dones))
1660
1739
 
1661
1740
  if self.wrap_with_accelerate or self.use_dataloader:
1662
1741
  dataloader = DataLoader(TensorDataset(*fields), batch_size = self.batch_size, shuffle = True)
@@ -1671,12 +1750,15 @@ class Agent(Module):
1671
1750
  self.actor.train()
1672
1751
  self.critic.train()
1673
1752
 
1753
+ if self.use_diversity_discr:
1754
+ self.diversity_discr.train()
1755
+
1674
1756
  for _ in tqdm(range(epochs), desc = 'learning actor/critic epoch', disable = self.quiet or not self.is_main_process):
1675
1757
  for (
1676
1758
  advantages,
1677
1759
  states,
1678
1760
  next_states,
1679
- latent_gene_ids,
1761
+ latent_refs,
1680
1762
  actions,
1681
1763
  log_probs,
1682
1764
  old_values,
@@ -1684,11 +1766,13 @@ class Agent(Module):
1684
1766
  ) in dataloader:
1685
1767
 
1686
1768
  if self.has_latent_genes:
1687
- latents = self.latent_gene_pool(latent_id = latent_gene_ids)
1769
+ latents = self.latent_gene_pool(latent_id = latent_refs)
1688
1770
 
1689
1771
  orig_latents = latents
1690
1772
  latents = latents.detach()
1691
1773
  latents.requires_grad_()
1774
+ elif self.has_fixed_latents:
1775
+ latents = latent_refs
1692
1776
  else:
1693
1777
  latents = None
1694
1778
 
@@ -1757,8 +1841,10 @@ class Agent(Module):
1757
1841
 
1758
1842
  if self.use_diversity_discr:
1759
1843
  discr_inp = (states,) if self.diversity_time_gap == 0 else (states, next_states)
1760
- diversity_discr_logits = self.diversity_discr(*discr_inp)
1761
- diversity_discr_loss = F.cross_entropy(diversity_discr_logits, latent_gene_ids)
1844
+ diversity_discr_pred = self.diversity_discr(*discr_inp, action = actions)
1845
+ diversity_discr_loss = self.diversity_discr_loss_fn(diversity_discr_pred, latent_refs)
1846
+
1847
+ self.last_diversity_discr_loss = float(diversity_discr_loss.detach())
1762
1848
 
1763
1849
  diversity_discr_loss.backward()
1764
1850
 
@@ -1905,6 +1991,7 @@ def create_agent(
1905
1991
  use_critic_ema = True,
1906
1992
  use_state_norm = False,
1907
1993
  action_is_continuous = False, # continuous control - beta policy
1994
+ latent_mode = 'gene_pool', # gene_pool (evolved latents) | fixed (sample z ~ N(0, I))
1908
1995
  diversity_time_gap = 1,
1909
1996
  latent_gene_pool_kwargs: dict = dict(),
1910
1997
  actor_kwargs: dict = dict(),
@@ -1912,9 +1999,13 @@ def create_agent(
1912
1999
  **kwargs
1913
2000
  ) -> Agent:
1914
2001
 
1915
- has_latent_genes = num_latents > 1
2002
+ assert latent_mode in ('gene_pool', 'fixed')
2003
+
2004
+ has_latent_genes = latent_mode == 'gene_pool' and num_latents > 1
1916
2005
 
1917
- if not has_latent_genes:
2006
+ if latent_mode == 'fixed':
2007
+ assert exists(dim_latent) and dim_latent > 0, 'fixed latent mode needs dim_latent > 0'
2008
+ elif not has_latent_genes:
1918
2009
  dim_latent = None
1919
2010
 
1920
2011
  latent_gene_pool = LatentGenePool(
@@ -1925,6 +2016,14 @@ def create_agent(
1925
2016
 
1926
2017
  state_norm = StateNorm(dim = dim_state) if use_state_norm else None
1927
2018
 
2019
+ actor_kwargs = dict(actor_kwargs)
2020
+ critic_kwargs = dict(critic_kwargs)
2021
+
2022
+ latent_conditioning = 'concat' if latent_mode == 'fixed' else 'film'
2023
+
2024
+ actor_kwargs.setdefault('latent_conditioning', latent_conditioning)
2025
+ critic_kwargs.setdefault('latent_conditioning', latent_conditioning)
2026
+
1928
2027
  actor = Actor(
1929
2028
  num_actions = actor_num_actions,
1930
2029
  dim_state = dim_state,
@@ -1950,6 +2049,8 @@ def create_agent(
1950
2049
  critic = critic,
1951
2050
  state_norm = state_norm,
1952
2051
  latent_gene_pool = latent_gene_pool,
2052
+ latent_mode = latent_mode,
2053
+ num_latents = num_latents,
1953
2054
  use_critic_ema = use_critic_ema,
1954
2055
  diversity_time_gap = diversity_time_gap,
1955
2056
  **kwargs
@@ -1960,6 +2061,8 @@ def create_agent(
1960
2061
  # EPO - which is just PPO with natural selection of a population of latent variables conditioning the agent
1961
2062
  # the tricky part is that the latent ids for each episode / trajectory needs to be tracked
1962
2063
 
2064
+ # latent_gene_id is a pool index in gene_pool mode, or the sampled z in fixed mode
2065
+
1963
2066
  Memory = namedtuple('Memory', [
1964
2067
  'episode_id',
1965
2068
  'state',
@@ -2048,7 +2151,7 @@ class EPO(Module):
2048
2151
  self.action_sample_temperature = action_sample_temperature
2049
2152
  self.diversity_reward_weight = diversity_reward_weight
2050
2153
 
2051
- self.num_latents = agent.latent_gene_pool.num_latents if agent.has_latent_genes else 1
2154
+ self.num_latents = agent.num_latents
2052
2155
  self.episodes_per_latent = episodes_per_latent
2053
2156
  self.max_episode_length = max_episode_length
2054
2157
  self.fix_environ_across_latents = fix_environ_across_latents
@@ -2144,7 +2247,12 @@ class EPO(Module):
2144
2247
 
2145
2248
  latent_id, episode_id, maybe_seed = rollout
2146
2249
 
2147
- latent = self.agent.unwrapped_latent_gene_pool(latent_id = latent_id) if self.agent.has_latent_genes else None
2250
+ if self.agent.has_latent_genes:
2251
+ latent = self.agent.unwrapped_latent_gene_pool(latent_id = latent_id)
2252
+ elif self.agent.has_fixed_latents:
2253
+ latent = torch.randn(self.agent.dim_latent, device = self.device)
2254
+ else:
2255
+ latent = None
2148
2256
 
2149
2257
  seed = maybe_seed if fix_environ_across_latents else None
2150
2258
  state, _ = interface_torch_numpy(env.reset_one, device = self.device)(i, seed = seed)
@@ -2156,6 +2264,12 @@ class EPO(Module):
2156
2264
 
2157
2265
  pbar = tqdm(total = num_episodes, desc = 'rollout', disable = self.agent.quiet or not self.agent.is_main_process)
2158
2266
 
2267
+ # some vec envs return the full batch even when only `active` rows were stepped
2268
+
2269
+ def active_rows(x, active):
2270
+ x = np.asarray(x)
2271
+ return x[active] if x.shape[0] != len(active) else x
2272
+
2159
2273
  # each iteration, batch all active slots through the actor and critic,
2160
2274
  # then step the env - slots that finish are refilled and the batch
2161
2275
  # stays full until all episodes have been rolled out
@@ -2164,17 +2278,38 @@ class EPO(Module):
2164
2278
  active = [i for i, slot in enumerate(slots) if exists(slot)]
2165
2279
 
2166
2280
  obs = stack([slots[i].state for i in active])
2167
- latents = stack([slots[i].latent for i in active]) if self.agent.has_latent_genes else None
2281
+ latents = stack([slots[i].latent for i in active]) if self.agent.has_latents else None
2168
2282
 
2169
2283
  actions, log_probs = self.agent.get_actor_actions(obs, latent = latents, sample = True, temperature = self.action_sample_temperature, use_unwrapped_model = True)
2170
2284
  values = self.agent.get_critic_values(obs, latent = latents, use_ema_if_available = True, use_unwrapped_model = True)
2171
2285
 
2172
2286
  next_obs, rewards, terminated, truncated = env.step_batch(actions.cpu().numpy(), worker_ids = active)[:4]
2173
2287
 
2174
- next_states = from_numpy(np.ascontiguousarray(next_obs)).float().to(self.device)
2175
- rewards = from_numpy(np.asarray(rewards)).float().to(self.device)
2176
- terminated = from_numpy(np.asarray(terminated)).to(self.device)
2177
- truncated = from_numpy(np.asarray(truncated)).to(self.device)
2288
+ next_states = from_numpy(np.ascontiguousarray(active_rows(next_obs, active))).float().to(self.device)
2289
+ rewards = from_numpy(active_rows(rewards, active)).float().to(self.device)
2290
+ terminated = from_numpy(active_rows(terminated, active)).to(self.device)
2291
+ truncated = from_numpy(active_rows(truncated, active)).to(self.device)
2292
+
2293
+ # DADS / DIAYN intrinsic reward, batched over every active env
2294
+
2295
+ diversity_rewards = None
2296
+ time_gap = self.agent.diversity_time_gap
2297
+
2298
+ if self.agent.use_diversity_discr and self.agent.has_diversity_discr_warmed_up.item() and time_gap <= 1:
2299
+ with torch.no_grad():
2300
+ discr = self.agent.unwrap_model(self.agent.diversity_discr)
2301
+
2302
+ if self.agent.has_fixed_latents:
2303
+ # -mse(q(z | s, a, s'), z) - Sharma et al. https://arxiv.org/abs/1907.01657
2304
+
2305
+ pred = discr(obs, action = actions) if time_gap == 0 else discr(obs, next_states, action = actions)
2306
+ diversity_rewards = -F.mse_loss(pred, latents, reduction = 'none').mean(dim = -1)
2307
+ else:
2308
+ logits = discr(obs) if time_gap == 0 else discr(obs, next_states)
2309
+ latent_log_probs = logits.log_softmax(dim = -1)
2310
+
2311
+ latent_ids = tensor([slots[i].latent_id for i in active], device = self.device)
2312
+ diversity_rewards = latent_log_probs.gather(1, latent_ids[:, None]).squeeze(1) + math.log(self.agent.num_latents)
2178
2313
 
2179
2314
  for k, worker in enumerate(active):
2180
2315
  slot = slots[worker]
@@ -2187,19 +2322,16 @@ class EPO(Module):
2187
2322
 
2188
2323
  # maybe diversity reward for the latent
2189
2324
 
2190
- if self.agent.use_diversity_discr and self.agent.has_diversity_discr_warmed_up.item():
2325
+ if exists(diversity_rewards):
2326
+ reward = reward + self.diversity_reward_weight * diversity_rewards[k].item()
2327
+ elif self.agent.use_diversity_discr and self.agent.has_diversity_discr_warmed_up.item():
2328
+ # only the discrete > 1 time-gap case falls back to per-transition
2329
+
2191
2330
  with torch.no_grad():
2192
2331
  discr = self.agent.unwrap_model(self.agent.diversity_discr)
2193
- time_gap = self.agent.diversity_time_gap
2194
-
2195
- if time_gap == 0:
2196
- logits = discr(rearrange(state, '... -> 1 ...'))
2197
- elif time_gap == 1 or len(slot.memories) < (time_gap - 1):
2198
- logits = discr(rearrange(state, '... -> 1 ...'), rearrange(next_state, '... -> 1 ...'))
2199
- else:
2200
- past_state = slot.memories[-(time_gap - 1)].state
2201
- logits = discr(rearrange(past_state, '... -> 1 ...'), rearrange(next_state, '... -> 1 ...'))
2332
+ past_state = slot.memories[-(time_gap - 1)].state if len(slot.memories) >= (time_gap - 1) else state
2202
2333
 
2334
+ logits = discr(rearrange(past_state, '... -> 1 ...'), rearrange(next_state, '... -> 1 ...'))
2203
2335
  latent_log_probs = logits.log_softmax(dim = -1)
2204
2336
  diversity_reward = latent_log_probs[0, latent_id] + math.log(self.agent.num_latents)
2205
2337
 
@@ -2211,7 +2343,7 @@ class EPO(Module):
2211
2343
  tensor(episode_id),
2212
2344
  state,
2213
2345
  next_state,
2214
- tensor(latent_id),
2346
+ latent.detach().cpu() if self.agent.has_fixed_latents else tensor(latent_id),
2215
2347
  actions[k],
2216
2348
  log_probs[k],
2217
2349
  reward,
@@ -2224,7 +2356,6 @@ class EPO(Module):
2224
2356
  slot.memories.append(memory)
2225
2357
 
2226
2358
  if self.agent.use_diversity_discr and self.agent.diversity_time_gap > 1:
2227
- time_gap = self.agent.diversity_time_gap
2228
2359
  if len(slot.memories) >= time_gap:
2229
2360
  slot.memories[-time_gap] = slot.memories[-time_gap]._replace(next_state = next_state)
2230
2361
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "evolutionary-policy-optimization"
3
- version = "0.4.2"
3
+ version = "0.5.0"
4
4
  description = "EPO - Pytorch"
5
5
  authors = [
6
6
  { name = "Phil Wang", email = "lucidrains@gmail.com" }
@@ -34,7 +34,7 @@ dependencies = [
34
34
  'torch>=2.2',
35
35
  'tqdm',
36
36
  "x-mlps-pytorch>=0.3.0",
37
- "x-ppo>=0.0.16",
37
+ "x-ppo>=0.0.21",
38
38
  ]
39
39
 
40
40
  [project.urls]