omnirl-framework 0.1.0__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.
- omnirl/__init__.py +1 -0
- omnirl/algorithms/__init__.py +1 -0
- omnirl/algorithms/a2c/__init__.py +8 -0
- omnirl/algorithms/a2c/learner.py +319 -0
- omnirl/algorithms/a2c/policy.py +100 -0
- omnirl/algorithms/c51_dqn/__init__.py +8 -0
- omnirl/algorithms/c51_dqn/learner.py +276 -0
- omnirl/algorithms/c51_dqn/policy.py +94 -0
- omnirl/algorithms/ddpg/__init__.py +8 -0
- omnirl/algorithms/ddpg/learner.py +271 -0
- omnirl/algorithms/ddpg/policy.py +91 -0
- omnirl/algorithms/ddqn/__init__.py +8 -0
- omnirl/algorithms/ddqn/learner.py +231 -0
- omnirl/algorithms/ddqn/policy.py +84 -0
- omnirl/algorithms/dqn/__init__.py +8 -0
- omnirl/algorithms/dqn/learner.py +229 -0
- omnirl/algorithms/dqn/policy.py +84 -0
- omnirl/algorithms/dueling_dqn/__init__.py +8 -0
- omnirl/algorithms/dueling_dqn/learner.py +231 -0
- omnirl/algorithms/dueling_dqn/policy.py +84 -0
- omnirl/algorithms/impala/__init__.py +8 -0
- omnirl/algorithms/impala/learner.py +334 -0
- omnirl/algorithms/impala/policy.py +109 -0
- omnirl/algorithms/per_dqn/__init__.py +8 -0
- omnirl/algorithms/per_dqn/learner.py +239 -0
- omnirl/algorithms/per_dqn/policy.py +84 -0
- omnirl/algorithms/ppo/__init__.py +8 -0
- omnirl/algorithms/ppo/learner.py +377 -0
- omnirl/algorithms/ppo/policy.py +109 -0
- omnirl/algorithms/qr_dqn/__init__.py +8 -0
- omnirl/algorithms/qr_dqn/learner.py +253 -0
- omnirl/algorithms/qr_dqn/policy.py +88 -0
- omnirl/algorithms/sac/__init__.py +8 -0
- omnirl/algorithms/sac/learner.py +409 -0
- omnirl/algorithms/sac/policy.py +114 -0
- omnirl/algorithms/td3/__init__.py +8 -0
- omnirl/algorithms/td3/learner.py +322 -0
- omnirl/algorithms/td3/policy.py +91 -0
- omnirl/algorithms/tqc/__init__.py +8 -0
- omnirl/algorithms/tqc/learner.py +403 -0
- omnirl/algorithms/tqc/policy.py +95 -0
- omnirl/buffers/__init__.py +7 -0
- omnirl/buffers/prioritized_replay_buffer.py +161 -0
- omnirl/buffers/replay_buffer.py +116 -0
- omnirl/buffers/rollout_buffer.py +146 -0
- omnirl/envs/__init__.py +1 -0
- omnirl/envs/gymnasium/__init__.py +14 -0
- omnirl/envs/gymnasium/adapter.py +38 -0
- omnirl/envs/gymnasium/builder.py +16 -0
- omnirl/envs/gymnasium/collection.py +54 -0
- omnirl/envs/gymnasium/evaluation.py +115 -0
- omnirl/envs/gymnasium/spec.py +47 -0
- omnirl/envs/vizdoom/__init__.py +14 -0
- omnirl/envs/vizdoom/adapter.py +74 -0
- omnirl/envs/vizdoom/builder.py +59 -0
- omnirl/envs/vizdoom/collection.py +67 -0
- omnirl/envs/vizdoom/evaluation.py +121 -0
- omnirl/envs/vizdoom/spec.py +21 -0
- omnirl/networks/__init__.py +27 -0
- omnirl/networks/actors.py +116 -0
- omnirl/networks/base/__init__.py +7 -0
- omnirl/networks/base/cnn.py +54 -0
- omnirl/networks/base/mlp.py +26 -0
- omnirl/networks/base/resnet.py +123 -0
- omnirl/networks/critics.py +298 -0
- omnirl/runtime/__init__.py +7 -0
- omnirl/runtime/evaluator.py +57 -0
- omnirl/runtime/file_writer.py +83 -0
- omnirl/runtime/logger.py +16 -0
- omnirl/runtime/runner.py +428 -0
- omnirl/runtime/sampler.py +66 -0
- omnirl/runtime/weight_store.py +29 -0
- omnirl/utils/__init__.py +5 -0
- omnirl/utils/seed.py +18 -0
- omnirl_framework-0.1.0.dist-info/METADATA +371 -0
- omnirl_framework-0.1.0.dist-info/RECORD +79 -0
- omnirl_framework-0.1.0.dist-info/WHEEL +5 -0
- omnirl_framework-0.1.0.dist-info/licenses/LICENSE +201 -0
- omnirl_framework-0.1.0.dist-info/top_level.txt +1 -0
omnirl/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""OmniRL: a distributed reinforcement learning framework."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Reinforcement learning algorithm implementations."""
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import ray
|
|
4
|
+
import torch
|
|
5
|
+
import torch.distributed as torch_dist
|
|
6
|
+
import torch.nn as nn
|
|
7
|
+
import torch.nn.functional as F
|
|
8
|
+
import torch.optim as optim
|
|
9
|
+
from torch.distributions import Categorical, Normal
|
|
10
|
+
from torch.nn.parallel import DistributedDataParallel as DDP
|
|
11
|
+
|
|
12
|
+
from omnirl.networks.actors import StochasticActorNetwork
|
|
13
|
+
from omnirl.networks.critics import ValueCriticNetwork
|
|
14
|
+
from omnirl.utils.seed import set_global_seed
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@ray.remote
|
|
18
|
+
class Learner:
|
|
19
|
+
"""
|
|
20
|
+
A2C learner agent.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
state_dim: Dimension of the environment state space.
|
|
24
|
+
action_dim: Dimension of the environment action space.
|
|
25
|
+
is_continuous: Whether the action space is continuous.
|
|
26
|
+
network_kwargs: Keyword arguments used to build the networks.
|
|
27
|
+
ckpt_dir: Directory path to save model checkpoints.
|
|
28
|
+
gamma: Discount factor.
|
|
29
|
+
lambda_: GAE lambda used for generalized advantage estimation.
|
|
30
|
+
ent_coef: Entropy regularization coefficient used to encourage exploration.
|
|
31
|
+
vf_coef: Coefficient for the critic loss in the total optimization objective.
|
|
32
|
+
actor_lr: Learning rate for the actor optimizer.
|
|
33
|
+
critic_lr: Learning rate for the critic optimizer.
|
|
34
|
+
max_grad_norm: Maximum gradient norm used for gradient clipping.
|
|
35
|
+
device: Torch device to run the networks on.
|
|
36
|
+
seed: Random seed used to ensure consistent results across runs.
|
|
37
|
+
rank: Distributed learner rank.
|
|
38
|
+
world_size: Total number of distributed learners.
|
|
39
|
+
master_addr: Master address used to initialize distributed training.
|
|
40
|
+
master_port: Master port used to initialize distributed training.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
state_dim: int | tuple[int, int, int],
|
|
46
|
+
action_dim: int,
|
|
47
|
+
is_continuous: bool = False,
|
|
48
|
+
network_kwargs: dict | None = None,
|
|
49
|
+
ckpt_dir: str = "checkpoints",
|
|
50
|
+
gamma: float = 0.99,
|
|
51
|
+
lambda_: float = 0.95,
|
|
52
|
+
ent_coef: float = 0.02,
|
|
53
|
+
vf_coef: float = 0.5,
|
|
54
|
+
actor_lr: float = 0.0003,
|
|
55
|
+
critic_lr: float = 0.0003,
|
|
56
|
+
max_grad_norm: float = 0.5,
|
|
57
|
+
device: str | None = None,
|
|
58
|
+
seed: int | None = None,
|
|
59
|
+
rank: int = 0,
|
|
60
|
+
world_size: int = 1,
|
|
61
|
+
master_addr: str = "127.0.0.1",
|
|
62
|
+
master_port: int = 29500,
|
|
63
|
+
):
|
|
64
|
+
# Limit PyTorch CPU threads
|
|
65
|
+
torch.set_num_threads(1)
|
|
66
|
+
torch.set_num_interop_threads(1)
|
|
67
|
+
|
|
68
|
+
# Initialize random seed
|
|
69
|
+
if seed is not None:
|
|
70
|
+
set_global_seed(seed)
|
|
71
|
+
|
|
72
|
+
# Create checkpoint directory
|
|
73
|
+
self.ckpt_dir = ckpt_dir
|
|
74
|
+
os.makedirs(self.ckpt_dir, exist_ok=True)
|
|
75
|
+
|
|
76
|
+
# Initialize device
|
|
77
|
+
if device is None:
|
|
78
|
+
self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
|
79
|
+
else:
|
|
80
|
+
self.device = torch.device("cuda:0" if device == "gpu" else "cpu")
|
|
81
|
+
|
|
82
|
+
# Initialize distributed training
|
|
83
|
+
self.rank = rank
|
|
84
|
+
is_distributed = world_size > 1
|
|
85
|
+
|
|
86
|
+
if is_distributed and not torch_dist.is_initialized():
|
|
87
|
+
backend = "nccl" if self.device.type == "cuda" else "gloo"
|
|
88
|
+
torch_dist.init_process_group(
|
|
89
|
+
backend=backend,
|
|
90
|
+
init_method=f"tcp://{master_addr}:{master_port}",
|
|
91
|
+
rank=self.rank,
|
|
92
|
+
world_size=world_size,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Initialize parameters
|
|
96
|
+
self.gamma = gamma
|
|
97
|
+
self.lambda_ = lambda_
|
|
98
|
+
self.ent_coef = ent_coef
|
|
99
|
+
self.vf_coef = vf_coef
|
|
100
|
+
self.is_continuous = is_continuous
|
|
101
|
+
self.max_grad_norm = max_grad_norm
|
|
102
|
+
|
|
103
|
+
# Initialize actor and critic networks
|
|
104
|
+
self.version = 0
|
|
105
|
+
network_kwargs = dict(network_kwargs or {})
|
|
106
|
+
actor_kwargs = dict(network_kwargs.get("actor", network_kwargs))
|
|
107
|
+
critic_kwargs = dict(network_kwargs.get("critic", network_kwargs))
|
|
108
|
+
actor_layer_type = actor_kwargs.pop("layer_type")
|
|
109
|
+
critic_layer_type = critic_kwargs.pop("layer_type")
|
|
110
|
+
log_std_min = actor_kwargs.pop("log_std_min", -20.0)
|
|
111
|
+
log_std_max = actor_kwargs.pop("log_std_max", 2.0)
|
|
112
|
+
|
|
113
|
+
actor = StochasticActorNetwork(
|
|
114
|
+
state_dim=state_dim,
|
|
115
|
+
action_dim=action_dim,
|
|
116
|
+
is_continuous=is_continuous,
|
|
117
|
+
layer_type=actor_layer_type,
|
|
118
|
+
log_std_min=log_std_min,
|
|
119
|
+
log_std_max=log_std_max,
|
|
120
|
+
network_kwargs=actor_kwargs,
|
|
121
|
+
)
|
|
122
|
+
critic = ValueCriticNetwork(
|
|
123
|
+
state_dim=state_dim,
|
|
124
|
+
layer_type=critic_layer_type,
|
|
125
|
+
network_kwargs=critic_kwargs,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
self.actor = actor.to(self.device)
|
|
129
|
+
self.critic = critic.to(self.device)
|
|
130
|
+
|
|
131
|
+
self.actor.train()
|
|
132
|
+
self.critic.train()
|
|
133
|
+
|
|
134
|
+
if is_distributed:
|
|
135
|
+
ddp_kwargs = {"device_ids": [0]} if self.device.type == "cuda" else {}
|
|
136
|
+
self.train_actor = DDP(self.actor, **ddp_kwargs)
|
|
137
|
+
self.train_critic = DDP(self.critic, **ddp_kwargs)
|
|
138
|
+
else:
|
|
139
|
+
self.train_actor = self.actor
|
|
140
|
+
self.train_critic = self.critic
|
|
141
|
+
|
|
142
|
+
# Initialize optimizer
|
|
143
|
+
self.actor_optimizer = optim.Adam(self.actor.parameters(), lr=actor_lr)
|
|
144
|
+
self.critic_optimizer = optim.Adam(self.critic.parameters(), lr=critic_lr)
|
|
145
|
+
|
|
146
|
+
def _state_dict(self, model):
|
|
147
|
+
"""Return a detached CPU copy of model state_dict."""
|
|
148
|
+
return {k: v.detach().cpu() for k, v in model.state_dict().items()}
|
|
149
|
+
|
|
150
|
+
def get_weights(self):
|
|
151
|
+
"""Export actor network weights."""
|
|
152
|
+
return self._state_dict(self.actor)
|
|
153
|
+
|
|
154
|
+
def get_version(self):
|
|
155
|
+
"""Export the current learner model version."""
|
|
156
|
+
return self.version
|
|
157
|
+
|
|
158
|
+
def _evaluate_action(self, states_tensor, actions_tensor):
|
|
159
|
+
"""Evaluate action log probabilities and entropies under the current policy."""
|
|
160
|
+
if self.is_continuous:
|
|
161
|
+
action_mean, action_log_std = self.train_actor(states_tensor)
|
|
162
|
+
|
|
163
|
+
action_std = action_log_std.exp()
|
|
164
|
+
dist = Normal(action_mean, action_std)
|
|
165
|
+
|
|
166
|
+
dist_entropy = dist.entropy().sum(dim=-1)
|
|
167
|
+
log_probs = dist.log_prob(actions_tensor).sum(dim=-1)
|
|
168
|
+
else:
|
|
169
|
+
action_logits = self.train_actor(states_tensor)
|
|
170
|
+
|
|
171
|
+
dist = Categorical(logits=action_logits)
|
|
172
|
+
|
|
173
|
+
dist_entropy = dist.entropy()
|
|
174
|
+
log_probs = dist.log_prob(actions_tensor)
|
|
175
|
+
|
|
176
|
+
state_values = self.train_critic(states_tensor)
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
"log_probs": log_probs.view(-1),
|
|
180
|
+
"dist_entropy": dist_entropy.view(-1),
|
|
181
|
+
"state_values": state_values.view(-1),
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
def learn(self, batch):
|
|
185
|
+
"""Sample a batch from rollout buffer and update actor and critic networks."""
|
|
186
|
+
states_tensor = torch.tensor(batch["states"], device=self.device)
|
|
187
|
+
actions_tensor = torch.tensor(batch["actions"], device=self.device)
|
|
188
|
+
rewards_tensor = torch.tensor(batch["rewards"], device=self.device)
|
|
189
|
+
next_states_tensor = torch.tensor(batch["next_states"], device=self.device)
|
|
190
|
+
terminals_tensor = torch.tensor(batch["terminals"], device=self.device)
|
|
191
|
+
dones_tensor = torch.tensor(batch["dones"], device=self.device)
|
|
192
|
+
|
|
193
|
+
states_tensor = states_tensor.float()
|
|
194
|
+
next_states_tensor = next_states_tensor.float()
|
|
195
|
+
terminals_tensor = terminals_tensor.float()
|
|
196
|
+
dones_tensor = dones_tensor.float()
|
|
197
|
+
|
|
198
|
+
B, T = rewards_tensor.shape
|
|
199
|
+
|
|
200
|
+
# Compute GAE
|
|
201
|
+
with torch.no_grad():
|
|
202
|
+
flat_states = states_tensor.view(B * T, *states_tensor.shape[2:])
|
|
203
|
+
flat_next_states = next_states_tensor.view(B * T, *states_tensor.shape[2:])
|
|
204
|
+
|
|
205
|
+
state_values = self.critic(flat_states).squeeze(-1)
|
|
206
|
+
next_state_values = self.critic(flat_next_states).squeeze(-1)
|
|
207
|
+
|
|
208
|
+
state_values = state_values.view(B, T)
|
|
209
|
+
next_state_values = next_state_values.view(B, T)
|
|
210
|
+
|
|
211
|
+
next_state_values = next_state_values * (1.0 - terminals_tensor)
|
|
212
|
+
td_target = rewards_tensor + self.gamma * next_state_values
|
|
213
|
+
deltas = td_target - state_values
|
|
214
|
+
|
|
215
|
+
advantages = torch.zeros_like(rewards_tensor, device=self.device)
|
|
216
|
+
gae = torch.zeros(B, device=self.device)
|
|
217
|
+
|
|
218
|
+
for t in reversed(range(T)):
|
|
219
|
+
gamma_lambda = self.gamma * self.lambda_ * (1.0 - dones_tensor[:, t])
|
|
220
|
+
gae = deltas[:, t] + gamma_lambda * gae
|
|
221
|
+
advantages[:, t] = gae
|
|
222
|
+
|
|
223
|
+
returns = advantages + state_values
|
|
224
|
+
advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8)
|
|
225
|
+
|
|
226
|
+
flat_states = states_tensor.view(B * T, *states_tensor.shape[2:])
|
|
227
|
+
if self.is_continuous:
|
|
228
|
+
flat_actions = actions_tensor.view(B * T, -1)
|
|
229
|
+
else:
|
|
230
|
+
flat_actions = actions_tensor.reshape(B * T)
|
|
231
|
+
flat_advantages = advantages.reshape(B * T)
|
|
232
|
+
flat_returns = returns.reshape(B * T)
|
|
233
|
+
|
|
234
|
+
# Compute action log-probabilities for the sampled actions.
|
|
235
|
+
results = self._evaluate_action(flat_states, flat_actions)
|
|
236
|
+
log_probs = results["log_probs"]
|
|
237
|
+
dist_entropy = results["dist_entropy"]
|
|
238
|
+
state_values = results["state_values"]
|
|
239
|
+
|
|
240
|
+
actor_loss = -(log_probs * flat_advantages).mean()
|
|
241
|
+
critic_loss = F.mse_loss(state_values, flat_returns)
|
|
242
|
+
entropy_loss = dist_entropy.mean()
|
|
243
|
+
|
|
244
|
+
loss = actor_loss + self.vf_coef * critic_loss - self.ent_coef * entropy_loss
|
|
245
|
+
|
|
246
|
+
value_error_var = torch.var(flat_returns - state_values.detach())
|
|
247
|
+
return_var = torch.var(flat_returns)
|
|
248
|
+
explained_var = 1 - value_error_var / (return_var + 1e-8)
|
|
249
|
+
|
|
250
|
+
self.actor_optimizer.zero_grad()
|
|
251
|
+
self.critic_optimizer.zero_grad()
|
|
252
|
+
loss.backward()
|
|
253
|
+
nn.utils.clip_grad_norm_(self.actor.parameters(), self.max_grad_norm)
|
|
254
|
+
nn.utils.clip_grad_norm_(self.critic.parameters(), self.max_grad_norm)
|
|
255
|
+
self.actor_optimizer.step()
|
|
256
|
+
self.critic_optimizer.step()
|
|
257
|
+
|
|
258
|
+
# Update model version
|
|
259
|
+
self.version += 1
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
"log": {
|
|
263
|
+
"actor_loss": actor_loss.item(),
|
|
264
|
+
"critic_loss": critic_loss.item(),
|
|
265
|
+
"entropy": entropy_loss.item(),
|
|
266
|
+
"explained_var": explained_var.item(),
|
|
267
|
+
},
|
|
268
|
+
"memory": {"version": self.version},
|
|
269
|
+
"version": self.get_version(),
|
|
270
|
+
"trained_samples": int(B * T),
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
def save_checkpoint(self):
|
|
274
|
+
"""Save actor, critic and optimizer into checkpoint file."""
|
|
275
|
+
if self.rank != 0:
|
|
276
|
+
return
|
|
277
|
+
|
|
278
|
+
path = os.path.join(self.ckpt_dir, "checkpoint.ckpt")
|
|
279
|
+
|
|
280
|
+
ckpt = {
|
|
281
|
+
"actor": self._state_dict(self.actor),
|
|
282
|
+
"critic": self._state_dict(self.critic),
|
|
283
|
+
"actor_optimizer": self.actor_optimizer.state_dict(),
|
|
284
|
+
"critic_optimizer": self.critic_optimizer.state_dict(),
|
|
285
|
+
"version": self.version,
|
|
286
|
+
}
|
|
287
|
+
torch.save(ckpt, path)
|
|
288
|
+
|
|
289
|
+
eval_path = os.path.join(self.ckpt_dir, f"agent_{self.version}.pth")
|
|
290
|
+
torch.save(self._state_dict(self.actor), eval_path)
|
|
291
|
+
|
|
292
|
+
def load_checkpoint(self):
|
|
293
|
+
"""Load actor, critic and optimizer from checkpoint file."""
|
|
294
|
+
path = os.path.join(self.ckpt_dir, "checkpoint.ckpt")
|
|
295
|
+
ckpt = torch.load(path, map_location=self.device)
|
|
296
|
+
|
|
297
|
+
# Load actor and critic networks
|
|
298
|
+
self.actor.load_state_dict(ckpt["actor"])
|
|
299
|
+
self.critic.load_state_dict(ckpt["critic"])
|
|
300
|
+
|
|
301
|
+
# Load optimizer state
|
|
302
|
+
self.actor_optimizer.load_state_dict(ckpt["actor_optimizer"])
|
|
303
|
+
self.critic_optimizer.load_state_dict(ckpt["critic_optimizer"])
|
|
304
|
+
|
|
305
|
+
# Load model version
|
|
306
|
+
self.version = ckpt["version"]
|
|
307
|
+
|
|
308
|
+
return {"version": self.get_version(), "weights": self.get_weights()}
|
|
309
|
+
|
|
310
|
+
def load_model(self, model_path):
|
|
311
|
+
"""Load actor from pre-trained model weights."""
|
|
312
|
+
path = os.path.join(self.ckpt_dir, model_path)
|
|
313
|
+
state_dict = torch.load(path, map_location=self.device)
|
|
314
|
+
|
|
315
|
+
# Load actor network
|
|
316
|
+
self.actor.load_state_dict(state_dict)
|
|
317
|
+
|
|
318
|
+
# Load model version
|
|
319
|
+
self.version = int(model_path.removeprefix("agent_").removesuffix(".pth"))
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import torch
|
|
3
|
+
from torch.distributions import Categorical, Normal
|
|
4
|
+
|
|
5
|
+
from omnirl.networks.actors import StochasticActorNetwork
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Policy:
|
|
9
|
+
"""
|
|
10
|
+
A2C policy used for local inference.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
state_dim: Dimension of the environment state space.
|
|
14
|
+
action_dim: Dimension of the environment action space.
|
|
15
|
+
is_continuous: Whether the action space is continuous.
|
|
16
|
+
action_low: Lower bounds of the continuous action space.
|
|
17
|
+
action_high: Upper bounds of the continuous action space.
|
|
18
|
+
network_kwargs: Keyword arguments used to build the actor network.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
state_dim: int | tuple[int, int, int],
|
|
24
|
+
action_dim: int,
|
|
25
|
+
is_continuous: bool = False,
|
|
26
|
+
action_low: np.ndarray | None = None,
|
|
27
|
+
action_high: np.ndarray | None = None,
|
|
28
|
+
network_kwargs: dict | None = None,
|
|
29
|
+
):
|
|
30
|
+
self.device = torch.device("cpu")
|
|
31
|
+
self.is_continuous = is_continuous
|
|
32
|
+
self.version = -1
|
|
33
|
+
|
|
34
|
+
if self.is_continuous:
|
|
35
|
+
action_low = np.asarray(action_low, dtype=np.float32)
|
|
36
|
+
action_high = np.asarray(action_high, dtype=np.float32)
|
|
37
|
+
|
|
38
|
+
action_low = torch.tensor(action_low, device=self.device)
|
|
39
|
+
action_high = torch.tensor(action_high, device=self.device)
|
|
40
|
+
self.action_scale = (action_high - action_low) / 2.0
|
|
41
|
+
self.action_bias = (action_high + action_low) / 2.0
|
|
42
|
+
|
|
43
|
+
network_kwargs = dict(network_kwargs or {})
|
|
44
|
+
actor_kwargs = dict(network_kwargs.get("actor", network_kwargs))
|
|
45
|
+
layer_type = actor_kwargs.pop("layer_type")
|
|
46
|
+
log_std_min = actor_kwargs.pop("log_std_min", -20.0)
|
|
47
|
+
log_std_max = actor_kwargs.pop("log_std_max", 2.0)
|
|
48
|
+
actor = StochasticActorNetwork(
|
|
49
|
+
state_dim=state_dim,
|
|
50
|
+
action_dim=action_dim,
|
|
51
|
+
is_continuous=is_continuous,
|
|
52
|
+
layer_type=layer_type,
|
|
53
|
+
log_std_min=log_std_min,
|
|
54
|
+
log_std_max=log_std_max,
|
|
55
|
+
network_kwargs=actor_kwargs,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
self.actor = actor.to(self.device)
|
|
59
|
+
self.actor.eval()
|
|
60
|
+
|
|
61
|
+
def set_weights(self, weights, version=None):
|
|
62
|
+
"""Load external network weights into the actor network."""
|
|
63
|
+
self.actor.load_state_dict(weights)
|
|
64
|
+
if version is not None:
|
|
65
|
+
self.version = version
|
|
66
|
+
|
|
67
|
+
def choose_action(self, observation):
|
|
68
|
+
"""Select a deterministic action for evaluation."""
|
|
69
|
+
observation = np.asarray(observation, dtype=np.float32)
|
|
70
|
+
state = torch.as_tensor(observation, device=self.device).unsqueeze(0)
|
|
71
|
+
|
|
72
|
+
with torch.no_grad():
|
|
73
|
+
if self.is_continuous:
|
|
74
|
+
action_mean, _ = self.actor(state)
|
|
75
|
+
action = torch.tanh(action_mean)
|
|
76
|
+
action = action * self.action_scale + self.action_bias
|
|
77
|
+
return action.squeeze(0).cpu().numpy()
|
|
78
|
+
else:
|
|
79
|
+
action_logits = self.actor(state)
|
|
80
|
+
action = torch.argmax(action_logits, dim=-1)
|
|
81
|
+
return int(action.item())
|
|
82
|
+
|
|
83
|
+
def sample_action(self, observation):
|
|
84
|
+
"""Select a probabilistic action during training."""
|
|
85
|
+
observation = np.asarray(observation, dtype=np.float32)
|
|
86
|
+
state = torch.as_tensor(observation, device=self.device).unsqueeze(0)
|
|
87
|
+
|
|
88
|
+
with torch.no_grad():
|
|
89
|
+
if self.is_continuous:
|
|
90
|
+
action_mean, action_log_std = self.actor(state)
|
|
91
|
+
action = Normal(action_mean, action_log_std.exp()).sample()
|
|
92
|
+
env_action = torch.tanh(action) * self.action_scale + self.action_bias
|
|
93
|
+
action = action.squeeze(0).cpu().numpy()
|
|
94
|
+
env_action = env_action.squeeze(0).cpu().numpy()
|
|
95
|
+
else:
|
|
96
|
+
action_logits = self.actor(state)
|
|
97
|
+
action = int(Categorical(logits=action_logits).sample().item())
|
|
98
|
+
env_action = action
|
|
99
|
+
|
|
100
|
+
return {"env_action": env_action, "data": {"action": action}}
|