evolutionary-policy-optimization 0.4.1__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.
- {evolutionary_policy_optimization-0.4.1 → evolutionary_policy_optimization-0.5.0}/PKG-INFO +2 -1
- {evolutionary_policy_optimization-0.4.1 → evolutionary_policy_optimization-0.5.0}/evolutionary_policy_optimization/epo.py +208 -84
- {evolutionary_policy_optimization-0.4.1 → evolutionary_policy_optimization-0.5.0}/pyproject.toml +2 -1
- {evolutionary_policy_optimization-0.4.1 → evolutionary_policy_optimization-0.5.0}/.gitignore +0 -0
- {evolutionary_policy_optimization-0.4.1 → evolutionary_policy_optimization-0.5.0}/LICENSE +0 -0
- {evolutionary_policy_optimization-0.4.1 → evolutionary_policy_optimization-0.5.0}/README.md +0 -0
- {evolutionary_policy_optimization-0.4.1 → evolutionary_policy_optimization-0.5.0}/evolutionary_policy_optimization/__init__.py +0 -0
- {evolutionary_policy_optimization-0.4.1 → evolutionary_policy_optimization-0.5.0}/evolutionary_policy_optimization/distributed.py +0 -0
- {evolutionary_policy_optimization-0.4.1 → evolutionary_policy_optimization-0.5.0}/evolutionary_policy_optimization/env_wrappers.py +0 -0
- {evolutionary_policy_optimization-0.4.1 → evolutionary_policy_optimization-0.5.0}/evolutionary_policy_optimization/experimental.py +0 -0
- {evolutionary_policy_optimization-0.4.1 → evolutionary_policy_optimization-0.5.0}/evolutionary_policy_optimization/mock_env.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: evolutionary-policy-optimization
|
|
3
|
-
Version: 0.
|
|
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,6 +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.21
|
|
47
48
|
Provides-Extra: examples
|
|
48
49
|
Requires-Dist: fire; extra == 'examples'
|
|
49
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,10 +24,12 @@ 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
|
|
31
31
|
from x_mlps_pytorch import AttnResidualNormedMLP
|
|
32
|
+
from x_ppo import hpo_actor_loss, ppo_actor_loss, spo_actor_loss
|
|
32
33
|
|
|
33
34
|
from evolutionary_policy_optimization.distributed import all_gather, get_world_and_rank, is_distributed, maybe_barrier, maybe_sync_seed
|
|
34
35
|
|
|
@@ -437,20 +438,32 @@ class MLP(Module):
|
|
|
437
438
|
depth,
|
|
438
439
|
dim_latent = 0,
|
|
439
440
|
latent_mapping_network_depth = 2,
|
|
440
|
-
expansion_factor = 2
|
|
441
|
+
expansion_factor = 2.,
|
|
442
|
+
latent_conditioning = 'film', # film | concat (direct z injection, x-ppo style)
|
|
441
443
|
):
|
|
442
444
|
super().__init__()
|
|
443
445
|
dim_latent = default(dim_latent, 0)
|
|
444
446
|
|
|
445
447
|
self.dim_latent = dim_latent
|
|
448
|
+
self.latent_conditioning = latent_conditioning
|
|
446
449
|
|
|
447
450
|
self.needs_latent = dim_latent > 0
|
|
448
451
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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)
|
|
454
467
|
|
|
455
468
|
dim_hidden = int(dim * expansion_factor)
|
|
456
469
|
|
|
@@ -463,7 +476,7 @@ class MLP(Module):
|
|
|
463
476
|
|
|
464
477
|
film = None
|
|
465
478
|
|
|
466
|
-
if self.needs_latent:
|
|
479
|
+
if self.needs_latent and latent_conditioning == 'film':
|
|
467
480
|
film = FiLM(dim * 2, dim)
|
|
468
481
|
|
|
469
482
|
lime = DynamicLIMe(dim, num_layers = ind + 1) if not is_first else None
|
|
@@ -497,16 +510,17 @@ class MLP(Module):
|
|
|
497
510
|
assert xnor(self.needs_latent, exists(latent))
|
|
498
511
|
|
|
499
512
|
if exists(latent):
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
latent = self.encode_latent(latent)
|
|
513
|
+
if self.latent_conditioning == 'film':
|
|
514
|
+
latent = self.encode_latent(latent)
|
|
504
515
|
|
|
505
516
|
if latent.ndim == 1:
|
|
506
517
|
latent = repeat(latent, 'd -> b d', b = batch)
|
|
507
518
|
|
|
508
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]}'
|
|
509
520
|
|
|
521
|
+
if self.latent_conditioning == 'concat':
|
|
522
|
+
x = x + self.latent_proj(latent)
|
|
523
|
+
|
|
510
524
|
# layers
|
|
511
525
|
|
|
512
526
|
prev_layer_inputs = [x]
|
|
@@ -527,43 +541,66 @@ class MLP(Module):
|
|
|
527
541
|
|
|
528
542
|
return self.final_lime(x, prev_layer_inputs)
|
|
529
543
|
|
|
530
|
-
# discriminator for predicting latent code from states
|
|
544
|
+
# discriminator for predicting latent code from states, transitions, and optionally actions (DIAYN / DADS)
|
|
531
545
|
|
|
532
546
|
class DiversityDiscr(Module):
|
|
533
547
|
def __init__(
|
|
534
548
|
self,
|
|
535
549
|
dim_state,
|
|
536
|
-
num_latents,
|
|
550
|
+
num_latents = None,
|
|
537
551
|
dim = 64,
|
|
538
552
|
depth = 2,
|
|
539
|
-
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,
|
|
540
557
|
):
|
|
541
558
|
super().__init__()
|
|
542
|
-
|
|
559
|
+
|
|
560
|
+
assert exists(num_latents) ^ exists(dim_latent), 'give either num_latents (discrete) or dim_latent (continuous)'
|
|
561
|
+
|
|
543
562
|
self.state_transition = time_gap > 0
|
|
563
|
+
self.has_action = exists(dim_action)
|
|
564
|
+
self.action_is_continuous = action_is_continuous
|
|
544
565
|
|
|
545
566
|
self.state_proj = nn.Linear(dim_state, dim)
|
|
546
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
|
+
|
|
547
576
|
self.net = AttnResidualNormedMLP(
|
|
548
577
|
dim = dim,
|
|
549
578
|
depth = depth,
|
|
550
|
-
dim_in =
|
|
551
|
-
dim_out =
|
|
579
|
+
dim_in = dim_in,
|
|
580
|
+
dim_out = dim_out
|
|
552
581
|
)
|
|
553
582
|
|
|
554
583
|
def reset_parameters(self):
|
|
555
584
|
for module in self.modules():
|
|
556
|
-
if isinstance(module, (nn.Linear, nn.LayerNorm, nn.RMSNorm)):
|
|
585
|
+
if isinstance(module, (nn.Linear, nn.LayerNorm, nn.RMSNorm, nn.Embedding)):
|
|
557
586
|
module.reset_parameters()
|
|
558
587
|
|
|
559
|
-
def forward(self, state, next_state = None):
|
|
588
|
+
def forward(self, state, next_state = None, action = None):
|
|
560
589
|
state_embed = self.state_proj(state)
|
|
561
590
|
|
|
562
|
-
|
|
563
|
-
return self.net(state_embed)
|
|
591
|
+
inputs = [state_embed]
|
|
564
592
|
|
|
565
|
-
|
|
566
|
-
|
|
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'
|
|
598
|
+
|
|
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))
|
|
567
604
|
|
|
568
605
|
# action distributions - the actor always returns a `Distribution`, either
|
|
569
606
|
# categorical (discrete) or beta mean-conc (continuous). beta is defined over
|
|
@@ -660,6 +697,7 @@ class Actor(Module):
|
|
|
660
697
|
dim_latent = 0,
|
|
661
698
|
action_is_continuous = False, # continuous control - beta policy
|
|
662
699
|
beta_kwargs: dict = dict(),
|
|
700
|
+
latent_conditioning = 'film',
|
|
663
701
|
):
|
|
664
702
|
super().__init__()
|
|
665
703
|
|
|
@@ -675,7 +713,7 @@ class Actor(Module):
|
|
|
675
713
|
nn.SiLU()
|
|
676
714
|
)
|
|
677
715
|
|
|
678
|
-
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)
|
|
679
717
|
|
|
680
718
|
if self.beta_actions:
|
|
681
719
|
# beta head - (raw mean, raw concentration) per action dim
|
|
@@ -732,7 +770,8 @@ class Critic(Module):
|
|
|
732
770
|
min_value = 0.,
|
|
733
771
|
max_value = 500.,
|
|
734
772
|
num_bins = 250
|
|
735
|
-
)
|
|
773
|
+
),
|
|
774
|
+
latent_conditioning = 'film',
|
|
736
775
|
):
|
|
737
776
|
super().__init__()
|
|
738
777
|
|
|
@@ -745,7 +784,7 @@ class Critic(Module):
|
|
|
745
784
|
nn.SiLU()
|
|
746
785
|
)
|
|
747
786
|
|
|
748
|
-
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)
|
|
749
788
|
|
|
750
789
|
self.final_norm = nn.RMSNorm(dim)
|
|
751
790
|
|
|
@@ -1252,6 +1291,8 @@ class Agent(Module):
|
|
|
1252
1291
|
accelerate_kwargs: dict = dict(),
|
|
1253
1292
|
accelerator = None,
|
|
1254
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
|
|
1255
1296
|
):
|
|
1256
1297
|
super().__init__()
|
|
1257
1298
|
|
|
@@ -1289,14 +1330,28 @@ class Agent(Module):
|
|
|
1289
1330
|
**ema_kwargs
|
|
1290
1331
|
) if use_critic_ema else None
|
|
1291
1332
|
|
|
1333
|
+
assert latent_mode in ('gene_pool', 'fixed')
|
|
1334
|
+
|
|
1335
|
+
self.latent_mode = latent_mode
|
|
1292
1336
|
self.latent_gene_pool = latent_gene_pool
|
|
1293
|
-
self.num_latents = latent_gene_pool.num_latents if exists(latent_gene_pool) else 1
|
|
1294
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)
|
|
1295
1341
|
|
|
1342
|
+
assert not (self.has_latent_genes and self.has_fixed_latents)
|
|
1296
1343
|
assert actor.dim_latent == critic.dim_latent
|
|
1297
1344
|
|
|
1298
1345
|
if self.has_latent_genes:
|
|
1299
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
|
+
)
|
|
1300
1355
|
|
|
1301
1356
|
# gae function
|
|
1302
1357
|
|
|
@@ -1332,13 +1387,27 @@ class Agent(Module):
|
|
|
1332
1387
|
self.use_diversity_discr = use_diversity_discr
|
|
1333
1388
|
self.diversity_time_gap = diversity_time_gap
|
|
1334
1389
|
|
|
1390
|
+
self.diversity_discr_loss_fn = F.mse_loss if self.has_fixed_latents else F.cross_entropy
|
|
1391
|
+
|
|
1335
1392
|
if use_diversity_discr:
|
|
1336
|
-
assert
|
|
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
|
+
|
|
1337
1398
|
dim_state = actor.init_layer[0].in_features
|
|
1338
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
|
+
|
|
1339
1409
|
self.diversity_discr = DiversityDiscr(
|
|
1340
1410
|
dim_state = dim_state,
|
|
1341
|
-
num_latents = latent_gene_pool.num_latents,
|
|
1342
1411
|
time_gap = diversity_time_gap,
|
|
1343
1412
|
**diversity_discr_kwargs
|
|
1344
1413
|
)
|
|
@@ -1349,7 +1418,6 @@ class Agent(Module):
|
|
|
1349
1418
|
self.diversity_discr_optim = None
|
|
1350
1419
|
|
|
1351
1420
|
self.clip_value = clip_value
|
|
1352
|
-
self.critic_loss_kwargs = critic_loss_kwargs
|
|
1353
1421
|
|
|
1354
1422
|
# self-predictive representations (SPR)
|
|
1355
1423
|
|
|
@@ -1372,6 +1440,7 @@ class Agent(Module):
|
|
|
1372
1440
|
|
|
1373
1441
|
self.register_buffer('has_diversity_discr_warmed_up', tensor(False))
|
|
1374
1442
|
self.register_buffer('zero', tensor(0.))
|
|
1443
|
+
self.last_diversity_discr_loss = 0.
|
|
1375
1444
|
|
|
1376
1445
|
# shrink and perturb every
|
|
1377
1446
|
|
|
@@ -1381,7 +1450,7 @@ class Agent(Module):
|
|
|
1381
1450
|
|
|
1382
1451
|
# promotes latents to be farther apart for diversity maintenance
|
|
1383
1452
|
|
|
1384
|
-
self.has_diversity_loss = diversity_aux_loss_weight > 0.
|
|
1453
|
+
self.has_diversity_loss = diversity_aux_loss_weight > 0. and self.has_latent_genes
|
|
1385
1454
|
self.diversity_aux_loss_weight = diversity_aux_loss_weight
|
|
1386
1455
|
|
|
1387
1456
|
# wrap with accelerate
|
|
@@ -1468,6 +1537,9 @@ class Agent(Module):
|
|
|
1468
1537
|
actor = unwrap(self.actor).state_dict(),
|
|
1469
1538
|
critic = unwrap(self.critic).state_dict(),
|
|
1470
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,
|
|
1471
1543
|
latents = unwrap(self.latent_gene_pool).state_dict() if self.has_latent_genes else None,
|
|
1472
1544
|
diversity_discr = unwrap(self.diversity_discr).state_dict() if self.use_diversity_discr else None,
|
|
1473
1545
|
diversity_time_gap = self.diversity_time_gap,
|
|
@@ -1491,6 +1563,9 @@ class Agent(Module):
|
|
|
1491
1563
|
|
|
1492
1564
|
pkg = torch.load(str(path), weights_only = True)
|
|
1493
1565
|
|
|
1566
|
+
if 'latent_mode' in pkg:
|
|
1567
|
+
assert pkg['latent_mode'] == self.latent_mode, 'checkpoint latent_mode does not match the agent'
|
|
1568
|
+
|
|
1494
1569
|
unwrap(self.actor).load_state_dict(pkg['actor'])
|
|
1495
1570
|
unwrap(self.critic).load_state_dict(pkg['critic'])
|
|
1496
1571
|
|
|
@@ -1498,6 +1573,7 @@ class Agent(Module):
|
|
|
1498
1573
|
self.critic_ema.load_state_dict(pkg['critic_ema'])
|
|
1499
1574
|
|
|
1500
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'
|
|
1501
1577
|
unwrap(self.latent_gene_pool).load_state_dict(pkg['latents'])
|
|
1502
1578
|
|
|
1503
1579
|
if self.use_diversity_discr and 'diversity_discr' in pkg and exists(pkg['diversity_discr']):
|
|
@@ -1540,6 +1616,8 @@ class Agent(Module):
|
|
|
1540
1616
|
if not exists(latent) and exists(latent_id) and exists(self.latent_gene_pool):
|
|
1541
1617
|
latent = maybe_unwrap(self.latent_gene_pool)(latent_id = latent_id)
|
|
1542
1618
|
|
|
1619
|
+
assert not self.has_fixed_latents or exists(latent), 'fixed latent mode needs a latent sampled from N(0, I)'
|
|
1620
|
+
|
|
1543
1621
|
return maybe_unwrap(self.actor)(state, latent, temperature = temperature)
|
|
1544
1622
|
|
|
1545
1623
|
@move_input_tensors_to_device
|
|
@@ -1586,6 +1664,8 @@ class Agent(Module):
|
|
|
1586
1664
|
if not exists(latent) and exists(latent_id) and exists(self.latent_gene_pool):
|
|
1587
1665
|
latent = maybe_unwrap(self.latent_gene_pool)(latent_id = latent_id)
|
|
1588
1666
|
|
|
1667
|
+
assert not self.has_fixed_latents or exists(latent), 'fixed latent mode needs a latent sampled from N(0, I)'
|
|
1668
|
+
|
|
1589
1669
|
critic_forward = maybe_unwrap(self.critic)
|
|
1590
1670
|
|
|
1591
1671
|
if use_ema_if_available and self.use_critic_ema:
|
|
@@ -1633,7 +1713,7 @@ class Agent(Module):
|
|
|
1633
1713
|
episode_ids,
|
|
1634
1714
|
states,
|
|
1635
1715
|
next_states,
|
|
1636
|
-
|
|
1716
|
+
latent_refs,
|
|
1637
1717
|
actions,
|
|
1638
1718
|
log_probs,
|
|
1639
1719
|
rewards,
|
|
@@ -1655,7 +1735,7 @@ class Agent(Module):
|
|
|
1655
1735
|
|
|
1656
1736
|
valid_episode = episode_ids >= 0
|
|
1657
1737
|
|
|
1658
|
-
fields = tuple(t[valid_episode] for t in (advantages, states, next_states,
|
|
1738
|
+
fields = tuple(t[valid_episode] for t in (advantages, states, next_states, latent_refs, actions, log_probs, values, dones))
|
|
1659
1739
|
|
|
1660
1740
|
if self.wrap_with_accelerate or self.use_dataloader:
|
|
1661
1741
|
dataloader = DataLoader(TensorDataset(*fields), batch_size = self.batch_size, shuffle = True)
|
|
@@ -1670,12 +1750,15 @@ class Agent(Module):
|
|
|
1670
1750
|
self.actor.train()
|
|
1671
1751
|
self.critic.train()
|
|
1672
1752
|
|
|
1753
|
+
if self.use_diversity_discr:
|
|
1754
|
+
self.diversity_discr.train()
|
|
1755
|
+
|
|
1673
1756
|
for _ in tqdm(range(epochs), desc = 'learning actor/critic epoch', disable = self.quiet or not self.is_main_process):
|
|
1674
1757
|
for (
|
|
1675
1758
|
advantages,
|
|
1676
1759
|
states,
|
|
1677
1760
|
next_states,
|
|
1678
|
-
|
|
1761
|
+
latent_refs,
|
|
1679
1762
|
actions,
|
|
1680
1763
|
log_probs,
|
|
1681
1764
|
old_values,
|
|
@@ -1683,11 +1766,13 @@ class Agent(Module):
|
|
|
1683
1766
|
) in dataloader:
|
|
1684
1767
|
|
|
1685
1768
|
if self.has_latent_genes:
|
|
1686
|
-
latents = self.latent_gene_pool(latent_id =
|
|
1769
|
+
latents = self.latent_gene_pool(latent_id = latent_refs)
|
|
1687
1770
|
|
|
1688
1771
|
orig_latents = latents
|
|
1689
1772
|
latents = latents.detach()
|
|
1690
1773
|
latents.requires_grad_()
|
|
1774
|
+
elif self.has_fixed_latents:
|
|
1775
|
+
latents = latent_refs
|
|
1691
1776
|
else:
|
|
1692
1777
|
latents = None
|
|
1693
1778
|
|
|
@@ -1756,8 +1841,10 @@ class Agent(Module):
|
|
|
1756
1841
|
|
|
1757
1842
|
if self.use_diversity_discr:
|
|
1758
1843
|
discr_inp = (states,) if self.diversity_time_gap == 0 else (states, next_states)
|
|
1759
|
-
|
|
1760
|
-
diversity_discr_loss =
|
|
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())
|
|
1761
1848
|
|
|
1762
1849
|
diversity_discr_loss.backward()
|
|
1763
1850
|
|
|
@@ -1863,39 +1950,31 @@ def actor_loss(
|
|
|
1863
1950
|
advantages, # Float[b]
|
|
1864
1951
|
eps_clip = 0.2,
|
|
1865
1952
|
entropy_weight = .01,
|
|
1866
|
-
eps = 1e-5,
|
|
1867
1953
|
norm_advantages = True,
|
|
1868
|
-
use_spo = False
|
|
1954
|
+
use_spo = False,
|
|
1955
|
+
use_hpo = False
|
|
1869
1956
|
):
|
|
1870
|
-
batch = advantages.shape[0]
|
|
1871
|
-
|
|
1872
1957
|
log_probs = sum_to_batch(distr.log_prob(actions))
|
|
1873
1958
|
entropy = sum_to_batch(distr.entropy())
|
|
1874
1959
|
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
if use_spo:
|
|
1881
|
-
# simple policy optimization - line 14 Algorithm 1 https://arxiv.org/abs/2401.16025v9
|
|
1882
|
-
|
|
1883
|
-
actor_loss = - (
|
|
1884
|
-
ratio * advantages -
|
|
1885
|
-
advantages.abs() / (2 * eps_clip) * (ratio - 1.).square()
|
|
1886
|
-
)
|
|
1960
|
+
if use_hpo:
|
|
1961
|
+
loss_fn = hpo_actor_loss
|
|
1962
|
+
elif use_spo:
|
|
1963
|
+
loss_fn = spo_actor_loss
|
|
1887
1964
|
else:
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1965
|
+
loss_fn = ppo_actor_loss
|
|
1966
|
+
|
|
1967
|
+
loss = loss_fn(
|
|
1968
|
+
log_probs,
|
|
1969
|
+
old_log_probs,
|
|
1970
|
+
advantages,
|
|
1971
|
+
eps_clip = eps_clip,
|
|
1972
|
+
normalize_advantages = norm_advantages,
|
|
1973
|
+
)
|
|
1895
1974
|
|
|
1896
|
-
|
|
1975
|
+
# entropy bonus
|
|
1897
1976
|
|
|
1898
|
-
return (
|
|
1977
|
+
return (loss - entropy_weight * entropy).mean()
|
|
1899
1978
|
|
|
1900
1979
|
# agent contains the actor, critic, and the latent genetic pool
|
|
1901
1980
|
|
|
@@ -1912,6 +1991,7 @@ def create_agent(
|
|
|
1912
1991
|
use_critic_ema = True,
|
|
1913
1992
|
use_state_norm = False,
|
|
1914
1993
|
action_is_continuous = False, # continuous control - beta policy
|
|
1994
|
+
latent_mode = 'gene_pool', # gene_pool (evolved latents) | fixed (sample z ~ N(0, I))
|
|
1915
1995
|
diversity_time_gap = 1,
|
|
1916
1996
|
latent_gene_pool_kwargs: dict = dict(),
|
|
1917
1997
|
actor_kwargs: dict = dict(),
|
|
@@ -1919,9 +1999,13 @@ def create_agent(
|
|
|
1919
1999
|
**kwargs
|
|
1920
2000
|
) -> Agent:
|
|
1921
2001
|
|
|
1922
|
-
|
|
2002
|
+
assert latent_mode in ('gene_pool', 'fixed')
|
|
1923
2003
|
|
|
1924
|
-
|
|
2004
|
+
has_latent_genes = latent_mode == 'gene_pool' and num_latents > 1
|
|
2005
|
+
|
|
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:
|
|
1925
2009
|
dim_latent = None
|
|
1926
2010
|
|
|
1927
2011
|
latent_gene_pool = LatentGenePool(
|
|
@@ -1932,6 +2016,14 @@ def create_agent(
|
|
|
1932
2016
|
|
|
1933
2017
|
state_norm = StateNorm(dim = dim_state) if use_state_norm else None
|
|
1934
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
|
+
|
|
1935
2027
|
actor = Actor(
|
|
1936
2028
|
num_actions = actor_num_actions,
|
|
1937
2029
|
dim_state = dim_state,
|
|
@@ -1957,6 +2049,8 @@ def create_agent(
|
|
|
1957
2049
|
critic = critic,
|
|
1958
2050
|
state_norm = state_norm,
|
|
1959
2051
|
latent_gene_pool = latent_gene_pool,
|
|
2052
|
+
latent_mode = latent_mode,
|
|
2053
|
+
num_latents = num_latents,
|
|
1960
2054
|
use_critic_ema = use_critic_ema,
|
|
1961
2055
|
diversity_time_gap = diversity_time_gap,
|
|
1962
2056
|
**kwargs
|
|
@@ -1967,6 +2061,8 @@ def create_agent(
|
|
|
1967
2061
|
# EPO - which is just PPO with natural selection of a population of latent variables conditioning the agent
|
|
1968
2062
|
# the tricky part is that the latent ids for each episode / trajectory needs to be tracked
|
|
1969
2063
|
|
|
2064
|
+
# latent_gene_id is a pool index in gene_pool mode, or the sampled z in fixed mode
|
|
2065
|
+
|
|
1970
2066
|
Memory = namedtuple('Memory', [
|
|
1971
2067
|
'episode_id',
|
|
1972
2068
|
'state',
|
|
@@ -2055,7 +2151,7 @@ class EPO(Module):
|
|
|
2055
2151
|
self.action_sample_temperature = action_sample_temperature
|
|
2056
2152
|
self.diversity_reward_weight = diversity_reward_weight
|
|
2057
2153
|
|
|
2058
|
-
self.num_latents = agent.
|
|
2154
|
+
self.num_latents = agent.num_latents
|
|
2059
2155
|
self.episodes_per_latent = episodes_per_latent
|
|
2060
2156
|
self.max_episode_length = max_episode_length
|
|
2061
2157
|
self.fix_environ_across_latents = fix_environ_across_latents
|
|
@@ -2151,7 +2247,12 @@ class EPO(Module):
|
|
|
2151
2247
|
|
|
2152
2248
|
latent_id, episode_id, maybe_seed = rollout
|
|
2153
2249
|
|
|
2154
|
-
|
|
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
|
|
2155
2256
|
|
|
2156
2257
|
seed = maybe_seed if fix_environ_across_latents else None
|
|
2157
2258
|
state, _ = interface_torch_numpy(env.reset_one, device = self.device)(i, seed = seed)
|
|
@@ -2163,6 +2264,12 @@ class EPO(Module):
|
|
|
2163
2264
|
|
|
2164
2265
|
pbar = tqdm(total = num_episodes, desc = 'rollout', disable = self.agent.quiet or not self.agent.is_main_process)
|
|
2165
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
|
+
|
|
2166
2273
|
# each iteration, batch all active slots through the actor and critic,
|
|
2167
2274
|
# then step the env - slots that finish are refilled and the batch
|
|
2168
2275
|
# stays full until all episodes have been rolled out
|
|
@@ -2171,17 +2278,38 @@ class EPO(Module):
|
|
|
2171
2278
|
active = [i for i, slot in enumerate(slots) if exists(slot)]
|
|
2172
2279
|
|
|
2173
2280
|
obs = stack([slots[i].state for i in active])
|
|
2174
|
-
latents = stack([slots[i].latent for i in active]) if self.agent.
|
|
2281
|
+
latents = stack([slots[i].latent for i in active]) if self.agent.has_latents else None
|
|
2175
2282
|
|
|
2176
2283
|
actions, log_probs = self.agent.get_actor_actions(obs, latent = latents, sample = True, temperature = self.action_sample_temperature, use_unwrapped_model = True)
|
|
2177
2284
|
values = self.agent.get_critic_values(obs, latent = latents, use_ema_if_available = True, use_unwrapped_model = True)
|
|
2178
2285
|
|
|
2179
2286
|
next_obs, rewards, terminated, truncated = env.step_batch(actions.cpu().numpy(), worker_ids = active)[:4]
|
|
2180
2287
|
|
|
2181
|
-
next_states = from_numpy(np.ascontiguousarray(next_obs)).float().to(self.device)
|
|
2182
|
-
rewards = from_numpy(
|
|
2183
|
-
terminated = from_numpy(
|
|
2184
|
-
truncated = from_numpy(
|
|
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)
|
|
2185
2313
|
|
|
2186
2314
|
for k, worker in enumerate(active):
|
|
2187
2315
|
slot = slots[worker]
|
|
@@ -2194,19 +2322,16 @@ class EPO(Module):
|
|
|
2194
2322
|
|
|
2195
2323
|
# maybe diversity reward for the latent
|
|
2196
2324
|
|
|
2197
|
-
if
|
|
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
|
+
|
|
2198
2330
|
with torch.no_grad():
|
|
2199
2331
|
discr = self.agent.unwrap_model(self.agent.diversity_discr)
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
if time_gap == 0:
|
|
2203
|
-
logits = discr(rearrange(state, '... -> 1 ...'))
|
|
2204
|
-
elif time_gap == 1 or len(slot.memories) < (time_gap - 1):
|
|
2205
|
-
logits = discr(rearrange(state, '... -> 1 ...'), rearrange(next_state, '... -> 1 ...'))
|
|
2206
|
-
else:
|
|
2207
|
-
past_state = slot.memories[-(time_gap - 1)].state
|
|
2208
|
-
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
|
|
2209
2333
|
|
|
2334
|
+
logits = discr(rearrange(past_state, '... -> 1 ...'), rearrange(next_state, '... -> 1 ...'))
|
|
2210
2335
|
latent_log_probs = logits.log_softmax(dim = -1)
|
|
2211
2336
|
diversity_reward = latent_log_probs[0, latent_id] + math.log(self.agent.num_latents)
|
|
2212
2337
|
|
|
@@ -2218,7 +2343,7 @@ class EPO(Module):
|
|
|
2218
2343
|
tensor(episode_id),
|
|
2219
2344
|
state,
|
|
2220
2345
|
next_state,
|
|
2221
|
-
tensor(latent_id),
|
|
2346
|
+
latent.detach().cpu() if self.agent.has_fixed_latents else tensor(latent_id),
|
|
2222
2347
|
actions[k],
|
|
2223
2348
|
log_probs[k],
|
|
2224
2349
|
reward,
|
|
@@ -2231,7 +2356,6 @@ class EPO(Module):
|
|
|
2231
2356
|
slot.memories.append(memory)
|
|
2232
2357
|
|
|
2233
2358
|
if self.agent.use_diversity_discr and self.agent.diversity_time_gap > 1:
|
|
2234
|
-
time_gap = self.agent.diversity_time_gap
|
|
2235
2359
|
if len(slot.memories) >= time_gap:
|
|
2236
2360
|
slot.memories[-time_gap] = slot.memories[-time_gap]._replace(next_state = next_state)
|
|
2237
2361
|
|
{evolutionary_policy_optimization-0.4.1 → evolutionary_policy_optimization-0.5.0}/pyproject.toml
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "evolutionary-policy-optimization"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.5.0"
|
|
4
4
|
description = "EPO - Pytorch"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Phil Wang", email = "lucidrains@gmail.com" }
|
|
@@ -34,6 +34,7 @@ dependencies = [
|
|
|
34
34
|
'torch>=2.2',
|
|
35
35
|
'tqdm',
|
|
36
36
|
"x-mlps-pytorch>=0.3.0",
|
|
37
|
+
"x-ppo>=0.0.21",
|
|
37
38
|
]
|
|
38
39
|
|
|
39
40
|
[project.urls]
|
{evolutionary_policy_optimization-0.4.1 → evolutionary_policy_optimization-0.5.0}/.gitignore
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|