rlmodule 0.0.5__py3-none-any.whl
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.
- rlmodule/__init__.py +3 -0
- rlmodule/skrl/__init__.py +0 -0
- rlmodule/skrl/examples/gymnasium/skrl_gymnasium_pendulum.py +120 -0
- rlmodule/skrl/examples/gymnasium/skrl_gymnasium_pendulum_no_module.py +123 -0
- rlmodule/source/__init__.py +0 -0
- rlmodule/source/deprecated.py +378 -0
- rlmodule/source/model.py +840 -0
- rlmodule/source/modules.py +1003 -0
- rlmodule/source/rlmodel.py +771 -0
- rlmodule-0.0.5.dist-info/LICENSE +21 -0
- rlmodule-0.0.5.dist-info/METADATA +34 -0
- rlmodule-0.0.5.dist-info/RECORD +14 -0
- rlmodule-0.0.5.dist-info/WHEEL +5 -0
- rlmodule-0.0.5.dist-info/top_level.txt +1 -0
rlmodule/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import gymnasium as gym
|
|
2
|
+
|
|
3
|
+
import torch
|
|
4
|
+
import torch.nn as nn
|
|
5
|
+
|
|
6
|
+
from rlmodule import SharedRLModel, GaussianLayer, DeterministicLayer, MLP
|
|
7
|
+
|
|
8
|
+
# import the skrl components to build the RL system
|
|
9
|
+
from skrl.agents.torch.ppo import PPO, PPO_DEFAULT_CONFIG
|
|
10
|
+
|
|
11
|
+
from skrl.envs.wrappers.torch.gymnasium_envs import GymnasiumWrapper
|
|
12
|
+
#from skrl.envs.wrappers.torch import wrap_env
|
|
13
|
+
from skrl.memories.torch import RandomMemory
|
|
14
|
+
from skrl.resources.preprocessors.torch import RunningStandardScaler
|
|
15
|
+
from skrl.resources.schedulers.torch import KLAdaptiveRL
|
|
16
|
+
from skrl.trainers.torch import SequentialTrainer
|
|
17
|
+
from skrl.utils import set_seed
|
|
18
|
+
from skrl.envs.wrappers.torch.gymnasium_envs import GymnasiumWrapper
|
|
19
|
+
|
|
20
|
+
# todo set seed
|
|
21
|
+
# seed for reproducibility
|
|
22
|
+
set_seed() # e.g. `set_seed(42)` for fixed seed
|
|
23
|
+
|
|
24
|
+
def get_shared_model(env):
|
|
25
|
+
# instantiate the agent's models (function approximators).
|
|
26
|
+
params = {'input_size': env.observation_space.shape[0], 'hidden_units': [64, 64, 64], 'activations': ['relu', 'relu', 'relu']}
|
|
27
|
+
net = MLP(params)
|
|
28
|
+
|
|
29
|
+
model = SharedRLModel(
|
|
30
|
+
observation_space=env.observation_space.shape[0], # TODO not do shape[0] but process box normally
|
|
31
|
+
action_space=env.observation_space.shape[0],
|
|
32
|
+
device=device,
|
|
33
|
+
network=net,
|
|
34
|
+
output_layer={
|
|
35
|
+
'policy': GaussianLayer(
|
|
36
|
+
input_size=64, # TODO
|
|
37
|
+
output_size=env.action_space.shape[0],
|
|
38
|
+
device=device,
|
|
39
|
+
output_activation=nn.Tanh(),
|
|
40
|
+
clip_actions=False,
|
|
41
|
+
clip_log_std=True,
|
|
42
|
+
min_log_std=-1.2,
|
|
43
|
+
max_log_std=2,
|
|
44
|
+
initial_log_std=0.2,
|
|
45
|
+
),
|
|
46
|
+
'value': DeterministicLayer(
|
|
47
|
+
input_size=64,
|
|
48
|
+
output_size=1,
|
|
49
|
+
device=device,
|
|
50
|
+
output_activation=nn.Identity(),
|
|
51
|
+
),
|
|
52
|
+
},
|
|
53
|
+
).to(device) # TODO careful about device
|
|
54
|
+
|
|
55
|
+
return {'policy': model, 'value': model}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# load and wrap the gymnasium environment.
|
|
59
|
+
# note: the environment version may change depending on the gymnasium version
|
|
60
|
+
try:
|
|
61
|
+
env = gym.vector.make("Pendulum-v1", num_envs=4, asynchronous=False)
|
|
62
|
+
except (gym.error.DeprecatedEnv, gym.error.VersionNotFound) as e:
|
|
63
|
+
env_id = [spec for spec in gym.envs.registry if spec.startswith("Pendulum-v")][0]
|
|
64
|
+
print("Pendulum-v1 not found. Trying {}".format(env_id))
|
|
65
|
+
env = gym.vector.make(env_id, num_envs=4, asynchronous=False)
|
|
66
|
+
|
|
67
|
+
env = GymnasiumWrapper(env)
|
|
68
|
+
|
|
69
|
+
device = env.device
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# instantiate a memory as rollout buffer (any memory can be used for this)
|
|
73
|
+
memory = RandomMemory(memory_size=1024, num_envs=env.num_envs, device=device)
|
|
74
|
+
|
|
75
|
+
models = get_shared_model(env)
|
|
76
|
+
|
|
77
|
+
print(models)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# configure and instantiate the agent (visit its documentation to see all the options)
|
|
81
|
+
# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html#configuration-and-hyperparameters
|
|
82
|
+
cfg = PPO_DEFAULT_CONFIG.copy()
|
|
83
|
+
cfg["rollouts"] = 1024 # memory_size
|
|
84
|
+
cfg["learning_epochs"] = 10
|
|
85
|
+
cfg["mini_batches"] = 32
|
|
86
|
+
cfg["discount_factor"] = 0.9
|
|
87
|
+
cfg["lambda"] = 0.95
|
|
88
|
+
cfg["learning_rate"] = 1e-3
|
|
89
|
+
cfg["learning_rate_scheduler"] = KLAdaptiveRL
|
|
90
|
+
cfg["learning_rate_scheduler_kwargs"] = {"kl_threshold": 0.008}
|
|
91
|
+
cfg["grad_norm_clip"] = 0.5
|
|
92
|
+
cfg["ratio_clip"] = 0.2
|
|
93
|
+
cfg["value_clip"] = 0.2
|
|
94
|
+
cfg["clip_predicted_values"] = False
|
|
95
|
+
cfg["entropy_loss_scale"] = 0.0
|
|
96
|
+
cfg["value_loss_scale"] = 0.5
|
|
97
|
+
cfg["kl_threshold"] = 0
|
|
98
|
+
cfg["state_preprocessor"] = RunningStandardScaler
|
|
99
|
+
cfg["state_preprocessor_kwargs"] = {"size": env.observation_space, "device": device}
|
|
100
|
+
cfg["value_preprocessor"] = RunningStandardScaler
|
|
101
|
+
cfg["value_preprocessor_kwargs"] = {"size": 1, "device": device}
|
|
102
|
+
# logging to TensorBoard and write checkpoints (in timesteps)
|
|
103
|
+
cfg["experiment"]["write_interval"] = 500
|
|
104
|
+
cfg["experiment"]["checkpoint_interval"] = 5000
|
|
105
|
+
cfg["experiment"]["directory"] = "runs/torch/Pendulum"
|
|
106
|
+
|
|
107
|
+
agent = PPO(models=models,
|
|
108
|
+
memory=memory,
|
|
109
|
+
cfg=cfg,
|
|
110
|
+
observation_space=env.observation_space,
|
|
111
|
+
action_space=env.action_space,
|
|
112
|
+
device=device)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# configure and instantiate the RL trainer
|
|
116
|
+
cfg_trainer = {"timesteps": 100000, "headless": True}
|
|
117
|
+
trainer = SequentialTrainer(cfg=cfg_trainer, env=env, agents=[agent])
|
|
118
|
+
|
|
119
|
+
# start training
|
|
120
|
+
trainer.train()
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import gymnasium as gym
|
|
2
|
+
|
|
3
|
+
import torch
|
|
4
|
+
import torch.nn as nn
|
|
5
|
+
|
|
6
|
+
# import the skrl components to build the RL system
|
|
7
|
+
from skrl.agents.torch.ppo import PPO, PPO_DEFAULT_CONFIG
|
|
8
|
+
|
|
9
|
+
from skrl.envs.wrappers.torch.gymnasium_envs import GymnasiumWrapper
|
|
10
|
+
# from skrl.envs.wrappers.torch import wrap_env
|
|
11
|
+
from skrl.memories.torch import RandomMemory
|
|
12
|
+
from skrl.models.torch import DeterministicMixin, GaussianMixin, Model
|
|
13
|
+
from skrl.resources.preprocessors.torch import RunningStandardScaler
|
|
14
|
+
from skrl.resources.schedulers.torch import KLAdaptiveRL
|
|
15
|
+
from skrl.trainers.torch import SequentialTrainer
|
|
16
|
+
from skrl.utils import set_seed
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# seed for reproducibility
|
|
20
|
+
set_seed() # e.g. `set_seed(42)` for fixed seed
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# define models (stochastic and deterministic models) using mixins
|
|
24
|
+
class Policy(GaussianMixin, Model):
|
|
25
|
+
def __init__(self, observation_space, action_space, device, clip_actions=False,
|
|
26
|
+
clip_log_std=True, min_log_std=-20, max_log_std=2, reduction="sum"):
|
|
27
|
+
Model.__init__(self, observation_space, action_space, device)
|
|
28
|
+
GaussianMixin.__init__(self, clip_actions, clip_log_std, min_log_std, max_log_std, reduction)
|
|
29
|
+
|
|
30
|
+
self.net = nn.Sequential(nn.Linear(self.num_observations, 64),
|
|
31
|
+
nn.ReLU(),
|
|
32
|
+
nn.Linear(64, 64),
|
|
33
|
+
nn.ReLU(),
|
|
34
|
+
nn.Linear(64, self.num_actions))
|
|
35
|
+
self.log_std_parameter = nn.Parameter(torch.zeros(self.num_actions))
|
|
36
|
+
|
|
37
|
+
def compute(self, inputs, role):
|
|
38
|
+
# Pendulum-v1 action_space is -2 to 2
|
|
39
|
+
return 2 * torch.tanh(self.net(inputs["states"])), self.log_std_parameter, {}
|
|
40
|
+
|
|
41
|
+
class Value(DeterministicMixin, Model):
|
|
42
|
+
def __init__(self, observation_space, action_space, device, clip_actions=False):
|
|
43
|
+
Model.__init__(self, observation_space, action_space, device)
|
|
44
|
+
DeterministicMixin.__init__(self, clip_actions)
|
|
45
|
+
|
|
46
|
+
self.net = nn.Sequential(nn.Linear(self.num_observations, 64),
|
|
47
|
+
nn.ReLU(),
|
|
48
|
+
nn.Linear(64, 64),
|
|
49
|
+
nn.ReLU(),
|
|
50
|
+
nn.Linear(64, 1))
|
|
51
|
+
|
|
52
|
+
def compute(self, inputs, role):
|
|
53
|
+
return self.net(inputs["states"]), {}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# load and wrap the gymnasium environment.
|
|
57
|
+
# note: the environment version may change depending on the gymnasium version
|
|
58
|
+
try:
|
|
59
|
+
env = gym.vector.make("Pendulum-v1", num_envs=4, asynchronous=False)
|
|
60
|
+
except (gym.error.DeprecatedEnv, gym.error.VersionNotFound) as e:
|
|
61
|
+
env_id = [spec for spec in gym.envs.registry if spec.startswith("Pendulum-v")][0]
|
|
62
|
+
print("Pendulum-v1 not found. Trying {}".format(env_id))
|
|
63
|
+
env = gym.vector.make(env_id, num_envs=4, asynchronous=False)
|
|
64
|
+
|
|
65
|
+
env = GymnasiumWrapper(env)
|
|
66
|
+
|
|
67
|
+
device = env.device
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# instantiate a memory as rollout buffer (any memory can be used for this)
|
|
71
|
+
memory = RandomMemory(memory_size=1024, num_envs=env.num_envs, device=device)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# instantiate the agent's models (function approximators).
|
|
75
|
+
# PPO requires 2 models, visit its documentation for more details
|
|
76
|
+
# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html#models
|
|
77
|
+
models = {}
|
|
78
|
+
models["policy"] = Policy(env.observation_space, env.action_space, device, clip_actions=True)
|
|
79
|
+
models["value"] = Value(env.observation_space, env.action_space, device)
|
|
80
|
+
|
|
81
|
+
print(models)
|
|
82
|
+
|
|
83
|
+
# configure and instantiate the agent (visit its documentation to see all the options)
|
|
84
|
+
# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html#configuration-and-hyperparameters
|
|
85
|
+
cfg = PPO_DEFAULT_CONFIG.copy()
|
|
86
|
+
cfg["rollouts"] = 1024 # memory_size
|
|
87
|
+
cfg["learning_epochs"] = 10
|
|
88
|
+
cfg["mini_batches"] = 32
|
|
89
|
+
cfg["discount_factor"] = 0.9
|
|
90
|
+
cfg["lambda"] = 0.95
|
|
91
|
+
cfg["learning_rate"] = 1e-3
|
|
92
|
+
cfg["learning_rate_scheduler"] = KLAdaptiveRL
|
|
93
|
+
cfg["learning_rate_scheduler_kwargs"] = {"kl_threshold": 0.008}
|
|
94
|
+
cfg["grad_norm_clip"] = 0.5
|
|
95
|
+
cfg["ratio_clip"] = 0.2
|
|
96
|
+
cfg["value_clip"] = 0.2
|
|
97
|
+
cfg["clip_predicted_values"] = False
|
|
98
|
+
cfg["entropy_loss_scale"] = 0.0
|
|
99
|
+
cfg["value_loss_scale"] = 0.5
|
|
100
|
+
cfg["kl_threshold"] = 0
|
|
101
|
+
cfg["state_preprocessor"] = RunningStandardScaler
|
|
102
|
+
cfg["state_preprocessor_kwargs"] = {"size": env.observation_space, "device": device}
|
|
103
|
+
cfg["value_preprocessor"] = RunningStandardScaler
|
|
104
|
+
cfg["value_preprocessor_kwargs"] = {"size": 1, "device": device}
|
|
105
|
+
# logging to TensorBoard and write checkpoints (in timesteps)
|
|
106
|
+
cfg["experiment"]["write_interval"] = 500
|
|
107
|
+
cfg["experiment"]["checkpoint_interval"] = 5000
|
|
108
|
+
cfg["experiment"]["directory"] = "runs/torch/Pendulum"
|
|
109
|
+
|
|
110
|
+
agent = PPO(models=models,
|
|
111
|
+
memory=memory,
|
|
112
|
+
cfg=cfg,
|
|
113
|
+
observation_space=env.observation_space,
|
|
114
|
+
action_space=env.action_space,
|
|
115
|
+
device=device)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# configure and instantiate the RL trainer
|
|
119
|
+
cfg_trainer = {"timesteps": 100000, "headless": True}
|
|
120
|
+
trainer = SequentialTrainer(cfg=cfg_trainer, env=env, agents=[agent])
|
|
121
|
+
|
|
122
|
+
# start training
|
|
123
|
+
trainer.train()
|
|
File without changes
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
from typing import Any, Mapping, Tuple, Union
|
|
2
|
+
|
|
3
|
+
import gym
|
|
4
|
+
import gymnasium
|
|
5
|
+
|
|
6
|
+
import torch
|
|
7
|
+
from torch.distributions import Normal
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GaussianMixin:
|
|
11
|
+
def __init__(self,
|
|
12
|
+
clip_actions: bool = False,
|
|
13
|
+
clip_log_std: bool = True,
|
|
14
|
+
min_log_std: float = -20,
|
|
15
|
+
max_log_std: float = 2,
|
|
16
|
+
reduction: str = "sum",
|
|
17
|
+
role: str = "") -> None:
|
|
18
|
+
"""Gaussian mixin model (stochastic model)
|
|
19
|
+
|
|
20
|
+
:param clip_actions: Flag to indicate whether the actions should be clipped to the action space (default: ``False``)
|
|
21
|
+
:type clip_actions: bool, optional
|
|
22
|
+
:param clip_log_std: Flag to indicate whether the log standard deviations should be clipped (default: ``True``)
|
|
23
|
+
:type clip_log_std: bool, optional
|
|
24
|
+
:param min_log_std: Minimum value of the log standard deviation if ``clip_log_std`` is True (default: ``-20``)
|
|
25
|
+
:type min_log_std: float, optional
|
|
26
|
+
:param max_log_std: Maximum value of the log standard deviation if ``clip_log_std`` is True (default: ``2``)
|
|
27
|
+
:type max_log_std: float, optional
|
|
28
|
+
:param reduction: Reduction method for returning the log probability density function: (default: ``"sum"``).
|
|
29
|
+
Supported values are ``"mean"``, ``"sum"``, ``"prod"`` and ``"none"``. If "``none"``, the log probability density
|
|
30
|
+
function is returned as a tensor of shape ``(num_samples, num_actions)`` instead of ``(num_samples, 1)``
|
|
31
|
+
:type reduction: str, optional
|
|
32
|
+
:param role: Role play by the model (default: ``""``)
|
|
33
|
+
:type role: str, optional
|
|
34
|
+
|
|
35
|
+
:raises ValueError: If the reduction method is not valid
|
|
36
|
+
|
|
37
|
+
Example::
|
|
38
|
+
|
|
39
|
+
# define the model
|
|
40
|
+
>>> import torch
|
|
41
|
+
>>> import torch.nn as nn
|
|
42
|
+
>>> from skrl.models.torch import Model, GaussianMixin
|
|
43
|
+
>>>
|
|
44
|
+
>>> class Policy(GaussianMixin, Model):
|
|
45
|
+
... def __init__(self, observation_space, action_space, device="cuda:0",
|
|
46
|
+
... clip_actions=False, clip_log_std=True, min_log_std=-20, max_log_std=2, reduction="sum"):
|
|
47
|
+
... Model.__init__(self, observation_space, action_space, device)
|
|
48
|
+
... GaussianMixin.__init__(self, clip_actions, clip_log_std, min_log_std, max_log_std, reduction)
|
|
49
|
+
...
|
|
50
|
+
... self.net = nn.Sequential(nn.Linear(self.num_observations, 32),
|
|
51
|
+
... nn.ELU(),
|
|
52
|
+
... nn.Linear(32, 32),
|
|
53
|
+
... nn.ELU(),
|
|
54
|
+
... nn.Linear(32, self.num_actions))
|
|
55
|
+
... self.log_std_parameter = nn.Parameter(torch.zeros(self.num_actions))
|
|
56
|
+
...
|
|
57
|
+
... def compute(self, inputs, role):
|
|
58
|
+
... return self.net(inputs["states"]), self.log_std_parameter, {}
|
|
59
|
+
...
|
|
60
|
+
>>> # given an observation_space: gym.spaces.Box with shape (60,)
|
|
61
|
+
>>> # and an action_space: gym.spaces.Box with shape (8,)
|
|
62
|
+
>>> model = Policy(observation_space, action_space)
|
|
63
|
+
>>>
|
|
64
|
+
>>> print(model)
|
|
65
|
+
Policy(
|
|
66
|
+
(net): Sequential(
|
|
67
|
+
(0): Linear(in_features=60, out_features=32, bias=True)
|
|
68
|
+
(1): ELU(alpha=1.0)
|
|
69
|
+
(2): Linear(in_features=32, out_features=32, bias=True)
|
|
70
|
+
(3): ELU(alpha=1.0)
|
|
71
|
+
(4): Linear(in_features=32, out_features=8, bias=True)
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
"""
|
|
75
|
+
self._clip_actions = clip_actions and (issubclass(type(self.action_space), gym.Space) or \
|
|
76
|
+
issubclass(type(self.action_space), gymnasium.Space))
|
|
77
|
+
|
|
78
|
+
if self._clip_actions:
|
|
79
|
+
# TODO(ll) the code assume it will be inherited ... self.device is not set anywhere.
|
|
80
|
+
self._clip_actions_min = torch.tensor(self.action_space.low, device=self.device, dtype=torch.float32)
|
|
81
|
+
self._clip_actions_max = torch.tensor(self.action_space.high, device=self.device, dtype=torch.float32)
|
|
82
|
+
|
|
83
|
+
self._clip_log_std = clip_log_std
|
|
84
|
+
self._log_std_min = min_log_std
|
|
85
|
+
self._log_std_max = max_log_std
|
|
86
|
+
|
|
87
|
+
self._log_std = None
|
|
88
|
+
self._num_samples = None
|
|
89
|
+
self._distribution = None
|
|
90
|
+
|
|
91
|
+
if reduction not in ["mean", "sum", "prod", "none"]:
|
|
92
|
+
raise ValueError("reduction must be one of 'mean', 'sum', 'prod' or 'none'")
|
|
93
|
+
self._reduction = torch.mean if reduction == "mean" else torch.sum if reduction == "sum" \
|
|
94
|
+
else torch.prod if reduction == "prod" else None
|
|
95
|
+
|
|
96
|
+
# TODO(ll) keep for backward compatibility
|
|
97
|
+
def act(self,
|
|
98
|
+
inputs: Mapping[str, Union[torch.Tensor, Any]],
|
|
99
|
+
role: str = "") -> Tuple[torch.Tensor, Union[torch.Tensor, None], Mapping[str, Union[torch.Tensor, Any]]]:
|
|
100
|
+
"""Act stochastically in response to the state of the environment
|
|
101
|
+
|
|
102
|
+
:param inputs: Model inputs. The most common keys are:
|
|
103
|
+
|
|
104
|
+
- ``"states"``: state of the environment used to make the decision
|
|
105
|
+
- ``"taken_actions"``: actions taken by the policy for the given states
|
|
106
|
+
:type inputs: dict where the values are typically torch.Tensor
|
|
107
|
+
:param role: Role play by the model (default: ``""``)
|
|
108
|
+
:type role: str, optional
|
|
109
|
+
|
|
110
|
+
:return: Model output. The first component is the action to be taken by the agent.
|
|
111
|
+
The second component is the log of the probability density function.
|
|
112
|
+
The third component is a dictionary containing the mean actions ``"mean_actions"``
|
|
113
|
+
and extra output values
|
|
114
|
+
:rtype: tuple of torch.Tensor, torch.Tensor or None, and dict
|
|
115
|
+
|
|
116
|
+
Example::
|
|
117
|
+
|
|
118
|
+
>>> # given a batch of sample states with shape (4096, 60)
|
|
119
|
+
>>> actions, log_prob, outputs = model.act({"states": states})
|
|
120
|
+
>>> print(actions.shape, log_prob.shape, outputs["mean_actions"].shape)
|
|
121
|
+
torch.Size([4096, 8]) torch.Size([4096, 1]) torch.Size([4096, 8])
|
|
122
|
+
"""
|
|
123
|
+
# map from states/observations to mean actions and log standard deviations
|
|
124
|
+
mean_actions, log_std, outputs = self.compute(inputs, role)
|
|
125
|
+
|
|
126
|
+
return self.forward(mean_actions, inputs.get('taken_actions', None), log_std, outputs)
|
|
127
|
+
|
|
128
|
+
# # clamp log standard deviations
|
|
129
|
+
# if self._clip_log_std:
|
|
130
|
+
# log_std = torch.clamp(log_std, self._log_std_min, self._log_std_max)
|
|
131
|
+
|
|
132
|
+
# self._log_std = log_std
|
|
133
|
+
# self._num_samples = mean_actions.shape[0]
|
|
134
|
+
|
|
135
|
+
# # distribution
|
|
136
|
+
# self._distribution = Normal(mean_actions, log_std.exp())
|
|
137
|
+
|
|
138
|
+
# # sample using the reparameterization trick
|
|
139
|
+
# actions = self._distribution.rsample()
|
|
140
|
+
|
|
141
|
+
# # clip actions
|
|
142
|
+
# if self._clip_actions:
|
|
143
|
+
# actions = torch.clamp(actions, min=self._clip_actions_min, max=self._clip_actions_max)
|
|
144
|
+
|
|
145
|
+
# # log of the probability density function
|
|
146
|
+
# log_prob = self._distribution.log_prob(inputs.get("taken_actions", actions))
|
|
147
|
+
# if self._reduction is not None:
|
|
148
|
+
# log_prob = self._reduction(log_prob, dim=-1)
|
|
149
|
+
# if log_prob.dim() != actions.dim():
|
|
150
|
+
# log_prob = log_prob.unsqueeze(-1)
|
|
151
|
+
|
|
152
|
+
# outputs["mean_actions"] = mean_actions
|
|
153
|
+
# return actions, log_prob, outputs
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def forward(self,
|
|
157
|
+
mean_actions, taken_actions, log_std, outputs) -> Tuple[torch.Tensor, Union[torch.Tensor, None], Mapping[str, Union[torch.Tensor, Any]]]:
|
|
158
|
+
# TODO(ll) comment, input types
|
|
159
|
+
"""Act stochastically in response to the state of the environment
|
|
160
|
+
|
|
161
|
+
:param inputs: Model inputs. The most common keys are:
|
|
162
|
+
|
|
163
|
+
- ``"states"``: state of the environment used to make the decision
|
|
164
|
+
- ``"taken_actions"``: actions taken by the policy for the given states
|
|
165
|
+
:type inputs: dict where the values are typically torch.Tensor
|
|
166
|
+
:param role: Role play by the model (default: ``""``)
|
|
167
|
+
:type role: str, optional
|
|
168
|
+
|
|
169
|
+
:return: Model output. The first component is the action to be taken by the agent.
|
|
170
|
+
The second component is the log of the probability density function.
|
|
171
|
+
The third component is a dictionary containing the mean actions ``"mean_actions"``
|
|
172
|
+
and extra output values
|
|
173
|
+
:rtype: tuple of torch.Tensor, torch.Tensor or None, and dict
|
|
174
|
+
|
|
175
|
+
Example::
|
|
176
|
+
|
|
177
|
+
>>> # given a batch of sample states with shape (4096, 60)
|
|
178
|
+
>>> actions, log_prob, outputs = model.act({"states": states})
|
|
179
|
+
>>> print(actions.shape, log_prob.shape, outputs["mean_actions"].shape)
|
|
180
|
+
torch.Size([4096, 8]) torch.Size([4096, 1]) torch.Size([4096, 8])
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
# clamp log standard deviations
|
|
184
|
+
if self._clip_log_std:
|
|
185
|
+
log_std = torch.clamp(log_std, self._log_std_min, self._log_std_max)
|
|
186
|
+
|
|
187
|
+
self._log_std = log_std
|
|
188
|
+
self._num_samples = mean_actions.shape[0]
|
|
189
|
+
|
|
190
|
+
# distribution
|
|
191
|
+
self._distribution = Normal(mean_actions, log_std.exp())
|
|
192
|
+
|
|
193
|
+
# sample using the reparameterization trick
|
|
194
|
+
actions = self._distribution.rsample()
|
|
195
|
+
|
|
196
|
+
# clip actions
|
|
197
|
+
if self._clip_actions:
|
|
198
|
+
actions = torch.clamp(actions, min=self._clip_actions_min, max=self._clip_actions_max)
|
|
199
|
+
|
|
200
|
+
if taken_actions is None:
|
|
201
|
+
taken_actions = actions
|
|
202
|
+
|
|
203
|
+
# log of the probability density function
|
|
204
|
+
log_prob = self._distribution.log_prob(taken_actions)
|
|
205
|
+
if self._reduction is not None:
|
|
206
|
+
log_prob = self._reduction(log_prob, dim=-1)
|
|
207
|
+
if log_prob.dim() != actions.dim():
|
|
208
|
+
log_prob = log_prob.unsqueeze(-1)
|
|
209
|
+
|
|
210
|
+
outputs["mean_actions"] = mean_actions
|
|
211
|
+
return actions, log_prob, outputs
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def get_entropy(self, role: str = "") -> torch.Tensor:
|
|
215
|
+
"""Compute and return the entropy of the model
|
|
216
|
+
|
|
217
|
+
:return: Entropy of the model
|
|
218
|
+
:rtype: torch.Tensor
|
|
219
|
+
:param role: Role play by the model (default: ``""``)
|
|
220
|
+
:type role: str, optional
|
|
221
|
+
|
|
222
|
+
Example::
|
|
223
|
+
|
|
224
|
+
>>> entropy = model.get_entropy()
|
|
225
|
+
>>> print(entropy.shape)
|
|
226
|
+
torch.Size([4096, 8])
|
|
227
|
+
"""
|
|
228
|
+
if self._distribution is None:
|
|
229
|
+
return torch.tensor(0.0, device=self.device)
|
|
230
|
+
return self._distribution.entropy().to(self.device)
|
|
231
|
+
|
|
232
|
+
def get_log_std(self, role: str = "") -> torch.Tensor:
|
|
233
|
+
"""Return the log standard deviation of the model
|
|
234
|
+
|
|
235
|
+
:return: Log standard deviation of the model
|
|
236
|
+
:rtype: torch.Tensor
|
|
237
|
+
:param role: Role play by the model (default: ``""``)
|
|
238
|
+
:type role: str, optional
|
|
239
|
+
|
|
240
|
+
Example::
|
|
241
|
+
|
|
242
|
+
>>> log_std = model.get_log_std()
|
|
243
|
+
>>> print(log_std.shape)
|
|
244
|
+
torch.Size([4096, 8])
|
|
245
|
+
"""
|
|
246
|
+
return self._log_std.repeat(self._num_samples, 1)
|
|
247
|
+
|
|
248
|
+
def distribution(self, role: str = "") -> torch.distributions.Normal:
|
|
249
|
+
"""Get the current distribution of the model
|
|
250
|
+
|
|
251
|
+
:return: Distribution of the model
|
|
252
|
+
:rtype: torch.distributions.Normal
|
|
253
|
+
:param role: Role play by the model (default: ``""``)
|
|
254
|
+
:type role: str, optional
|
|
255
|
+
|
|
256
|
+
Example::
|
|
257
|
+
|
|
258
|
+
>>> distribution = model.distribution()
|
|
259
|
+
>>> print(distribution)
|
|
260
|
+
Normal(loc: torch.Size([4096, 8]), scale: torch.Size([4096, 8]))
|
|
261
|
+
"""
|
|
262
|
+
return self._distribution
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class DeterministicMixin:
|
|
268
|
+
def __init__(self, clip_actions: bool = False, role: str = "") -> None:
|
|
269
|
+
"""Deterministic mixin model (deterministic model)
|
|
270
|
+
|
|
271
|
+
:param clip_actions: Flag to indicate whether the actions should be clipped to the action space (default: ``False``)
|
|
272
|
+
:type clip_actions: bool, optional
|
|
273
|
+
:param role: Role play by the model (default: ``""``)
|
|
274
|
+
:type role: str, optional
|
|
275
|
+
|
|
276
|
+
Example::
|
|
277
|
+
|
|
278
|
+
# define the model
|
|
279
|
+
>>> import torch
|
|
280
|
+
>>> import torch.nn as nn
|
|
281
|
+
>>> from skrl.models.torch import Model, DeterministicMixin
|
|
282
|
+
>>>
|
|
283
|
+
>>> class Value(DeterministicMixin, Model):
|
|
284
|
+
... def __init__(self, observation_space, action_space, device="cuda:0", clip_actions=False):
|
|
285
|
+
... Model.__init__(self, observation_space, action_space, device)
|
|
286
|
+
... DeterministicMixin.__init__(self, clip_actions)
|
|
287
|
+
...
|
|
288
|
+
... self.net = nn.Sequential(nn.Linear(self.num_observations, 32),
|
|
289
|
+
... nn.ELU(),
|
|
290
|
+
... nn.Linear(32, 32),
|
|
291
|
+
... nn.ELU(),
|
|
292
|
+
... nn.Linear(32, 1))
|
|
293
|
+
...
|
|
294
|
+
... def compute(self, inputs, role):
|
|
295
|
+
... return self.net(inputs["states"]), {}
|
|
296
|
+
...
|
|
297
|
+
>>> # given an observation_space: gym.spaces.Box with shape (60,)
|
|
298
|
+
>>> # and an action_space: gym.spaces.Box with shape (8,)
|
|
299
|
+
>>> model = Value(observation_space, action_space)
|
|
300
|
+
>>>
|
|
301
|
+
>>> print(model)
|
|
302
|
+
Value(
|
|
303
|
+
(net): Sequential(
|
|
304
|
+
(0): Linear(in_features=60, out_features=32, bias=True)
|
|
305
|
+
(1): ELU(alpha=1.0)
|
|
306
|
+
(2): Linear(in_features=32, out_features=32, bias=True)
|
|
307
|
+
(3): ELU(alpha=1.0)
|
|
308
|
+
(4): Linear(in_features=32, out_features=1, bias=True)
|
|
309
|
+
)
|
|
310
|
+
)
|
|
311
|
+
"""
|
|
312
|
+
self._clip_actions = clip_actions and (issubclass(type(self.action_space), gym.Space) or \
|
|
313
|
+
issubclass(type(self.action_space), gymnasium.Space))
|
|
314
|
+
|
|
315
|
+
if self._clip_actions:
|
|
316
|
+
self._clip_actions_min = torch.tensor(self.action_space.low, device=self.device, dtype=torch.float32)
|
|
317
|
+
self._clip_actions_max = torch.tensor(self.action_space.high, device=self.device, dtype=torch.float32)
|
|
318
|
+
|
|
319
|
+
def act(self,
|
|
320
|
+
inputs: Mapping[str, Union[torch.Tensor, Any]],
|
|
321
|
+
role: str = "") -> Tuple[torch.Tensor, Union[torch.Tensor, None], Mapping[str, Union[torch.Tensor, Any]]]:
|
|
322
|
+
"""Act deterministically in response to the state of the environment
|
|
323
|
+
|
|
324
|
+
:param inputs: Model inputs. The most common keys are:
|
|
325
|
+
|
|
326
|
+
- ``"states"``: state of the environment used to make the decision
|
|
327
|
+
- ``"taken_actions"``: actions taken by the policy for the given states
|
|
328
|
+
:type inputs: dict where the values are typically torch.Tensor
|
|
329
|
+
:param role: Role play by the model (default: ``""``)
|
|
330
|
+
:type role: str, optional
|
|
331
|
+
|
|
332
|
+
:return: Model output. The first component is the action to be taken by the agent.
|
|
333
|
+
The second component is ``None``. The third component is a dictionary containing extra output values
|
|
334
|
+
:rtype: tuple of torch.Tensor, torch.Tensor or None, and dict
|
|
335
|
+
|
|
336
|
+
Example::
|
|
337
|
+
|
|
338
|
+
>>> # given a batch of sample states with shape (4096, 60)
|
|
339
|
+
>>> actions, _, outputs = model.act({"states": states})
|
|
340
|
+
>>> print(actions.shape, outputs)
|
|
341
|
+
torch.Size([4096, 1]) {}
|
|
342
|
+
"""
|
|
343
|
+
# map from observations/states to actions
|
|
344
|
+
actions, outputs = self.compute(inputs, role)
|
|
345
|
+
|
|
346
|
+
# clip actions
|
|
347
|
+
if self._clip_actions:
|
|
348
|
+
actions = torch.clamp(actions, min=self._clip_actions_min, max=self._clip_actions_max)
|
|
349
|
+
|
|
350
|
+
return actions, None, outputs
|
|
351
|
+
|
|
352
|
+
def forward(self, actions, outputs) -> Tuple[torch.Tensor, Union[torch.Tensor, None], Mapping[str, Union[torch.Tensor, Any]]]:
|
|
353
|
+
"""Act deterministically in response to the state of the environment
|
|
354
|
+
|
|
355
|
+
:param inputs: Model inputs. The most common keys are:
|
|
356
|
+
|
|
357
|
+
- ``"states"``: state of the environment used to make the decision
|
|
358
|
+
- ``"taken_actions"``: actions taken by the policy for the given states
|
|
359
|
+
:type inputs: dict where the values are typically torch.Tensor
|
|
360
|
+
:param role: Role play by the model (default: ``""``)
|
|
361
|
+
:type role: str, optional
|
|
362
|
+
|
|
363
|
+
:return: Model output. The first component is the action to be taken by the agent.
|
|
364
|
+
The second component is ``None``. The third component is a dictionary containing extra output values
|
|
365
|
+
:rtype: tuple of torch.Tensor, torch.Tensor or None, and dict
|
|
366
|
+
|
|
367
|
+
Example::
|
|
368
|
+
|
|
369
|
+
>>> # given a batch of sample states with shape (4096, 60)
|
|
370
|
+
>>> actions, _, outputs = model.act({"states": states})
|
|
371
|
+
>>> print(actions.shape, outputs)
|
|
372
|
+
torch.Size([4096, 1]) {}
|
|
373
|
+
"""
|
|
374
|
+
# clip actions
|
|
375
|
+
if self._clip_actions:
|
|
376
|
+
actions = torch.clamp(actions, min=self._clip_actions_min, max=self._clip_actions_max)
|
|
377
|
+
|
|
378
|
+
return actions, None, outputs
|