drlab 0.2.3__tar.gz → 0.2.4__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.
- {drlab-0.2.3/src/drlab.egg-info → drlab-0.2.4}/PKG-INFO +1 -1
- {drlab-0.2.3 → drlab-0.2.4}/pyproject.toml +1 -1
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/__init__.py +1 -1
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/learners/off_policy/base.py +32 -16
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/learners/off_policy/configs.py +6 -3
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/learners/off_policy/dqn.py +1 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/learners/off_policy/sac.py +38 -30
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/learners/on_policy/base.py +1 -1
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/learners/on_policy/configs.py +1 -1
- {drlab-0.2.3 → drlab-0.2.4/src/drlab.egg-info}/PKG-INFO +1 -1
- {drlab-0.2.3 → drlab-0.2.4}/tests/test_learners.py +129 -16
- {drlab-0.2.3 → drlab-0.2.4}/tests/test_public_api.py +1 -1
- {drlab-0.2.3 → drlab-0.2.4}/LICENSE +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/README.md +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/setup.cfg +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/controllers/__init__.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/controllers/base.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/controllers/e_greedy.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/controllers/gaussian.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/controllers/greedy.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/controllers/stochastic.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/controllers/warmup.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/experiments/__init__.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/experiments/off_policy_experiment.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/experiments/on_policy_experiment.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/learners/__init__.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/learners/off_policy/__init__.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/learners/on_policy/__init__.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/learners/on_policy/actor_critic.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/learners/on_policy/ppo.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/learners/on_policy/reinforce.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/replay/__init__.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/replay/replay_buffer.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/replay/transition_batch.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/runners/__init__.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab/runners/runner.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab.egg-info/SOURCES.txt +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab.egg-info/dependency_links.txt +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab.egg-info/requires.txt +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/src/drlab.egg-info/top_level.txt +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/tests/test_controllers.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/tests/test_off_policy_experiment.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/tests/test_replay.py +0 -0
- {drlab-0.2.3 → drlab-0.2.4}/tests/test_runner.py +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import torch as th
|
|
2
2
|
from copy import deepcopy
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
+
from collections.abc import Iterable
|
|
4
5
|
|
|
5
6
|
from .configs import OffPolicyConfig, TargetUpdate
|
|
6
7
|
|
|
@@ -15,8 +16,7 @@ class OffPolicyLearner(ABC):
|
|
|
15
16
|
self.regularizers = config.regularizers
|
|
16
17
|
self.reg_lams = config.reg_lams
|
|
17
18
|
|
|
18
|
-
self.
|
|
19
|
-
self.grad_norm_clip = config.grad_norm_clip
|
|
19
|
+
self.clipnorm = config.clipnorm
|
|
20
20
|
|
|
21
21
|
self.use_target_model = config.use_target_model
|
|
22
22
|
self.target_update = TargetUpdate(config.target_update)
|
|
@@ -52,25 +52,39 @@ class OffPolicyLearner(ABC):
|
|
|
52
52
|
|
|
53
53
|
return target
|
|
54
54
|
|
|
55
|
-
def
|
|
55
|
+
def _hard_update(self, target: th.nn.Module, source: th.nn.Module):
|
|
56
56
|
target.load_state_dict(source.state_dict())
|
|
57
57
|
target.eval()
|
|
58
58
|
|
|
59
|
-
def
|
|
60
|
-
|
|
59
|
+
def _soft_update(
|
|
60
|
+
self,
|
|
61
|
+
pairs: list[tuple[th.nn.Module, th.nn.Module]],
|
|
62
|
+
) -> None:
|
|
63
|
+
target_parameters = [
|
|
64
|
+
parameter
|
|
65
|
+
for target, _ in pairs
|
|
66
|
+
for parameter in target.parameters()
|
|
67
|
+
]
|
|
68
|
+
source_parameters = [
|
|
69
|
+
parameter
|
|
70
|
+
for _, source in pairs
|
|
71
|
+
for parameter in source.parameters()
|
|
72
|
+
]
|
|
61
73
|
|
|
62
74
|
with th.no_grad():
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
th._foreach_mul_(target_parameters, 1.0 - self.soft_target_update_param)
|
|
76
|
+
scaled_sources = th._foreach_mul(
|
|
77
|
+
source_parameters,
|
|
78
|
+
self.soft_target_update_param,
|
|
79
|
+
)
|
|
80
|
+
th._foreach_add_(target_parameters, scaled_sources)
|
|
66
81
|
|
|
67
82
|
def update_targets(
|
|
68
83
|
self,
|
|
69
84
|
pairs: list[tuple[th.nn.Module, th.nn.Module]],
|
|
70
85
|
):
|
|
71
86
|
if self.target_update == TargetUpdate.SOFT:
|
|
72
|
-
|
|
73
|
-
self.soft_update(target, source)
|
|
87
|
+
self._soft_update(pairs)
|
|
74
88
|
return
|
|
75
89
|
|
|
76
90
|
if self.target_update == TargetUpdate.HARD:
|
|
@@ -78,7 +92,7 @@ class OffPolicyLearner(ABC):
|
|
|
78
92
|
|
|
79
93
|
if self.target_update_calls % self.target_update_interval == 0:
|
|
80
94
|
for target, source in pairs:
|
|
81
|
-
self.
|
|
95
|
+
self._hard_update(target, source)
|
|
82
96
|
|
|
83
97
|
return
|
|
84
98
|
|
|
@@ -119,15 +133,17 @@ class OffPolicyLearner(ABC):
|
|
|
119
133
|
self,
|
|
120
134
|
loss: th.Tensor,
|
|
121
135
|
optimizer: th.optim.Optimizer,
|
|
122
|
-
parameters:
|
|
123
|
-
|
|
136
|
+
parameters: Iterable[th.nn.Parameter],
|
|
137
|
+
clipnorm: float | None = None,
|
|
138
|
+
) -> None:
|
|
139
|
+
parameters = tuple(parameters)
|
|
124
140
|
optimizer.zero_grad(set_to_none=True)
|
|
125
|
-
loss.backward()
|
|
141
|
+
loss.backward(inputs=parameters)
|
|
126
142
|
|
|
127
|
-
if
|
|
143
|
+
if clipnorm is not None:
|
|
128
144
|
th.nn.utils.clip_grad_norm_(
|
|
129
145
|
parameters,
|
|
130
|
-
|
|
146
|
+
clipnorm,
|
|
131
147
|
)
|
|
132
148
|
|
|
133
149
|
optimizer.step()
|
|
@@ -20,8 +20,7 @@ class OffPolicyConfig:
|
|
|
20
20
|
regularizers: list[Callable[..., th.Tensor | float]] = field(default_factory=list)
|
|
21
21
|
reg_lams: list[float] = field(default_factory=list)
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
grad_norm_clip: float = 1.0
|
|
23
|
+
clipnorm: float | None = 1.0
|
|
25
24
|
|
|
26
25
|
use_target_model: bool = True
|
|
27
26
|
target_update: TargetUpdate | str = TargetUpdate.SOFT
|
|
@@ -47,6 +46,7 @@ class SACConfig(OffPolicyConfig):
|
|
|
47
46
|
criterion: th.nn.Module = field(default_factory=th.nn.MSELoss)
|
|
48
47
|
|
|
49
48
|
target_entropy: float | None = None
|
|
49
|
+
initial_alpha: float = 1.0
|
|
50
50
|
alpha_lr: float = 3e-4
|
|
51
51
|
|
|
52
52
|
min_log_std: float = -20.0
|
|
@@ -65,8 +65,11 @@ class SACConfig(OffPolicyConfig):
|
|
|
65
65
|
if self.action_dim <= 0:
|
|
66
66
|
raise ValueError("action_dim must be > 0.")
|
|
67
67
|
|
|
68
|
+
if self.initial_alpha <= 0.0:
|
|
69
|
+
raise ValueError("initial_alpha must be > 0.")
|
|
70
|
+
|
|
68
71
|
if self.alpha_lr <= 0.0:
|
|
69
72
|
raise ValueError("alpha_lr must be > 0.")
|
|
70
73
|
|
|
71
74
|
if self.min_log_std >= self.max_log_std:
|
|
72
|
-
raise ValueError("min_log_std must be < max_log_std.")
|
|
75
|
+
raise ValueError("min_log_std must be < max_log_std.")
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import math
|
|
2
|
+
|
|
1
3
|
import torch as th
|
|
2
|
-
from torch.distributions import Normal
|
|
3
4
|
|
|
4
5
|
from .base import OffPolicyLearner
|
|
5
6
|
from .configs import SACConfig
|
|
@@ -42,7 +43,7 @@ class SACLearner(OffPolicyLearner):
|
|
|
42
43
|
self.target_entropy = -float(config.action_dim)
|
|
43
44
|
|
|
44
45
|
self.log_alpha = th.nn.Parameter(
|
|
45
|
-
th.tensor(
|
|
46
|
+
th.tensor(math.log(config.initial_alpha), device=self.device)
|
|
46
47
|
)
|
|
47
48
|
|
|
48
49
|
self.alpha_optimizer = th.optim.Adam(
|
|
@@ -54,15 +55,15 @@ class SACLearner(OffPolicyLearner):
|
|
|
54
55
|
def alpha(self) -> th.Tensor:
|
|
55
56
|
return self.log_alpha.exp()
|
|
56
57
|
|
|
57
|
-
def
|
|
58
|
-
for param in model.parameters():
|
|
59
|
-
param.requires_grad_(requires_grad)
|
|
60
|
-
|
|
61
|
-
def get_policy_dist(self, states: th.Tensor):
|
|
58
|
+
def _get_policy_params(self, states: th.Tensor) -> tuple[th.Tensor, th.Tensor]:
|
|
62
59
|
output = self.actor(states)
|
|
63
60
|
mean = output[:, : self.config.action_dim]
|
|
64
61
|
log_std = output[:, self.config.action_dim :]
|
|
65
62
|
log_std = th.clamp(log_std, self.config.min_log_std, self.config.max_log_std)
|
|
63
|
+
return mean, log_std
|
|
64
|
+
|
|
65
|
+
def get_policy_dist(self, states: th.Tensor) -> tuple[th.Tensor, th.Tensor]:
|
|
66
|
+
mean, log_std = self._get_policy_params(states)
|
|
66
67
|
std = th.exp(log_std)
|
|
67
68
|
return mean, std
|
|
68
69
|
|
|
@@ -71,19 +72,23 @@ class SACLearner(OffPolicyLearner):
|
|
|
71
72
|
obs: th.Tensor,
|
|
72
73
|
deterministic: bool = False,
|
|
73
74
|
) -> tuple[th.Tensor, th.Tensor]:
|
|
74
|
-
mean,
|
|
75
|
+
mean, log_std = self._get_policy_params(obs)
|
|
75
76
|
|
|
76
77
|
if deterministic:
|
|
77
78
|
u = mean
|
|
78
79
|
return th.tanh(u), th.zeros(obs.shape[0], 1, device=obs.device)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
|
|
81
|
+
# Specialized reparameterized Gaussian sampling avoids the generic
|
|
82
|
+
# distribution object's redundant scale and normalized-noise work.
|
|
83
|
+
eps = th.randn_like(mean)
|
|
84
|
+
u = mean + th.exp(log_std) * eps
|
|
82
85
|
action = th.tanh(u)
|
|
83
86
|
|
|
84
|
-
log_prob =
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
log_prob = -0.5 * (
|
|
88
|
+
eps.square() + 2.0 * log_std + math.log(2.0 * math.pi)
|
|
89
|
+
)
|
|
90
|
+
log_prob -= th.log(1.0 - action.pow(2) + 1e-6) # Tanh correction
|
|
91
|
+
log_prob = log_prob.sum(dim=-1, keepdim=True) # Sum over action dimensions
|
|
87
92
|
|
|
88
93
|
return action, log_prob
|
|
89
94
|
|
|
@@ -122,23 +127,27 @@ class SACLearner(OffPolicyLearner):
|
|
|
122
127
|
critic1_loss = self.criterion(current_q1, targets)
|
|
123
128
|
critic2_loss = self.criterion(current_q2, targets)
|
|
124
129
|
critic_loss = critic1_loss + critic2_loss
|
|
125
|
-
self.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
130
|
+
self.critic1_optimizer.zero_grad(set_to_none=True)
|
|
131
|
+
self.critic2_optimizer.zero_grad(set_to_none=True)
|
|
132
|
+
critic_loss.backward()
|
|
133
|
+
|
|
134
|
+
if self.clipnorm is not None:
|
|
135
|
+
# Clip each critic independently to preserve the previous behavior.
|
|
136
|
+
th.nn.utils.clip_grad_norm_(
|
|
137
|
+
self.critic1.parameters(),
|
|
138
|
+
self.clipnorm,
|
|
139
|
+
)
|
|
140
|
+
th.nn.utils.clip_grad_norm_(
|
|
141
|
+
self.critic2.parameters(),
|
|
142
|
+
self.clipnorm,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
self.critic1_optimizer.step()
|
|
146
|
+
self.critic2_optimizer.step()
|
|
135
147
|
|
|
136
148
|
# --------------------------------------------------
|
|
137
149
|
# 2. Update actor
|
|
138
150
|
# --------------------------------------------------
|
|
139
|
-
self._set_requires_grad(self.critic1, False)
|
|
140
|
-
self._set_requires_grad(self.critic2, False)
|
|
141
|
-
|
|
142
151
|
new_actions, log_probs = self.sample_action_and_log_prob(states)
|
|
143
152
|
new_action_states = th.cat([states, new_actions], dim=-1)
|
|
144
153
|
new_q1 = self.critic1(new_action_states)
|
|
@@ -159,11 +168,9 @@ class SACLearner(OffPolicyLearner):
|
|
|
159
168
|
actor_loss,
|
|
160
169
|
optimizer=self.actor_optimizer,
|
|
161
170
|
parameters=self.actor.parameters(),
|
|
171
|
+
clipnorm=self.clipnorm,
|
|
162
172
|
)
|
|
163
173
|
|
|
164
|
-
self._set_requires_grad(self.critic1, True)
|
|
165
|
-
self._set_requires_grad(self.critic2, True)
|
|
166
|
-
|
|
167
174
|
# --------------------------------------------------
|
|
168
175
|
# 3. Update alpha
|
|
169
176
|
# --------------------------------------------------
|
|
@@ -175,6 +182,7 @@ class SACLearner(OffPolicyLearner):
|
|
|
175
182
|
alpha_loss,
|
|
176
183
|
optimizer=self.alpha_optimizer,
|
|
177
184
|
parameters=[self.log_alpha],
|
|
185
|
+
clipnorm=self.clipnorm,
|
|
178
186
|
)
|
|
179
187
|
|
|
180
188
|
# Update target networks
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import unittest
|
|
2
|
+
from unittest.mock import patch
|
|
2
3
|
|
|
3
4
|
import torch as th
|
|
4
5
|
|
|
@@ -25,6 +26,39 @@ def parameters_changed(model, before):
|
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
class LearnerSmokeTest(unittest.TestCase):
|
|
29
|
+
def test_soft_target_update_matches_original_arithmetic_exactly(self):
|
|
30
|
+
source1 = th.nn.Linear(4, 3)
|
|
31
|
+
source2 = th.nn.Linear(3, 2)
|
|
32
|
+
target1 = th.nn.Linear(4, 3)
|
|
33
|
+
target2 = th.nn.Linear(3, 2)
|
|
34
|
+
expected1 = th.nn.Linear(4, 3)
|
|
35
|
+
expected2 = th.nn.Linear(3, 2)
|
|
36
|
+
expected1.load_state_dict(target1.state_dict())
|
|
37
|
+
expected2.load_state_dict(target2.state_dict())
|
|
38
|
+
|
|
39
|
+
model = th.nn.Linear(1, 2)
|
|
40
|
+
learner = DQNLearner(
|
|
41
|
+
model,
|
|
42
|
+
th.optim.SGD(model.parameters(), lr=0.1),
|
|
43
|
+
DQNConfig(num_actions=2, soft_target_update_param=0.005),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
with th.no_grad():
|
|
47
|
+
for target, source in ((expected1, source1), (expected2, source2)):
|
|
48
|
+
for target_param, source_param in zip(
|
|
49
|
+
target.parameters(), source.parameters()
|
|
50
|
+
):
|
|
51
|
+
target_param.data.mul_(0.995)
|
|
52
|
+
target_param.data.add_(0.005 * source_param.data)
|
|
53
|
+
|
|
54
|
+
learner.update_targets([(target1, source1), (target2, source2)])
|
|
55
|
+
|
|
56
|
+
for actual, expected in zip(
|
|
57
|
+
[*target1.parameters(), *target2.parameters()],
|
|
58
|
+
[*expected1.parameters(), *expected2.parameters()],
|
|
59
|
+
):
|
|
60
|
+
self.assertTrue(th.equal(actual, expected))
|
|
61
|
+
|
|
28
62
|
def test_dqn_train_step_returns_float_and_updates_parameters(self):
|
|
29
63
|
th.manual_seed(0)
|
|
30
64
|
model = th.nn.Sequential(
|
|
@@ -45,18 +79,45 @@ class LearnerSmokeTest(unittest.TestCase):
|
|
|
45
79
|
)
|
|
46
80
|
before = [param.detach().clone() for param in model.parameters()]
|
|
47
81
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
82
|
+
with patch(
|
|
83
|
+
"torch.nn.utils.clip_grad_norm_",
|
|
84
|
+
wraps=th.nn.utils.clip_grad_norm_,
|
|
85
|
+
) as clip_grad_norm:
|
|
86
|
+
loss = learner.train(
|
|
87
|
+
rewards=th.tensor([[1.0], [0.0], [1.0]]),
|
|
88
|
+
terminated=th.tensor([[False], [True], [False]]),
|
|
89
|
+
states=th.randn(3, 4),
|
|
90
|
+
actions=th.tensor([[0], [1], [0]], dtype=th.long),
|
|
91
|
+
next_states=th.randn(3, 4),
|
|
92
|
+
)
|
|
55
93
|
|
|
56
94
|
self.assertIsInstance(loss, float)
|
|
57
95
|
self.assertIsInstance(learner, OffPolicyLearner)
|
|
58
96
|
self.assertTrue(th.isfinite(th.tensor(loss)))
|
|
59
97
|
self.assertTrue(parameters_changed(model, before))
|
|
98
|
+
self.assertEqual(clip_grad_norm.call_count, 1)
|
|
99
|
+
|
|
100
|
+
def test_dqn_gradient_clipping_can_be_disabled(self):
|
|
101
|
+
model = th.nn.Linear(4, 2)
|
|
102
|
+
learner = DQNLearner(
|
|
103
|
+
model,
|
|
104
|
+
th.optim.SGD(model.parameters(), lr=0.05),
|
|
105
|
+
DQNConfig(
|
|
106
|
+
num_actions=2,
|
|
107
|
+
clipnorm=None,
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
with patch("torch.nn.utils.clip_grad_norm_") as clip_grad_norm:
|
|
112
|
+
learner.train(
|
|
113
|
+
rewards=th.tensor([[1.0], [0.0]]),
|
|
114
|
+
terminated=th.tensor([[False], [True]]),
|
|
115
|
+
states=th.randn(2, 4),
|
|
116
|
+
actions=th.tensor([[0], [1]], dtype=th.long),
|
|
117
|
+
next_states=th.randn(2, 4),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
clip_grad_norm.assert_not_called()
|
|
60
121
|
|
|
61
122
|
def test_sac_train_step_returns_float_and_updates_parameters(self):
|
|
62
123
|
th.manual_seed(0)
|
|
@@ -84,25 +145,77 @@ class LearnerSmokeTest(unittest.TestCase):
|
|
|
84
145
|
actor_optimizer=th.optim.SGD(actor.parameters(), lr=0.05),
|
|
85
146
|
critic1_optimizer=th.optim.SGD(critic1.parameters(), lr=0.05),
|
|
86
147
|
critic2_optimizer=th.optim.SGD(critic2.parameters(), lr=0.05),
|
|
87
|
-
config=SACConfig(action_shape=(action_dim,)),
|
|
148
|
+
config=SACConfig(action_shape=(action_dim,), initial_alpha=0.2),
|
|
88
149
|
)
|
|
89
|
-
|
|
150
|
+
actor_before = [param.detach().clone() for param in actor.parameters()]
|
|
151
|
+
critic1_before = [param.detach().clone() for param in critic1.parameters()]
|
|
152
|
+
critic2_before = [param.detach().clone() for param in critic2.parameters()]
|
|
153
|
+
critic_grad_calls = {
|
|
154
|
+
id(parameter): 0
|
|
155
|
+
for parameter in (*critic1.parameters(), *critic2.parameters())
|
|
156
|
+
}
|
|
157
|
+
hooks = []
|
|
158
|
+
for parameter in (*critic1.parameters(), *critic2.parameters()):
|
|
159
|
+
parameter_id = id(parameter)
|
|
160
|
+
hooks.append(
|
|
161
|
+
parameter.register_hook(
|
|
162
|
+
lambda gradient, parameter_id=parameter_id: critic_grad_calls.__setitem__(
|
|
163
|
+
parameter_id,
|
|
164
|
+
critic_grad_calls[parameter_id] + 1,
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
)
|
|
90
168
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
169
|
+
self.assertAlmostEqual(learner.alpha.item(), 0.2)
|
|
170
|
+
|
|
171
|
+
probe_states = th.randn(3, obs_dim)
|
|
172
|
+
th.manual_seed(123)
|
|
173
|
+
sampled_actions, sampled_log_probs = learner.sample_action_and_log_prob(
|
|
174
|
+
probe_states
|
|
97
175
|
)
|
|
176
|
+
th.manual_seed(123)
|
|
177
|
+
mean, std = learner.get_policy_dist(probe_states)
|
|
178
|
+
dist = th.distributions.Normal(mean, std, validate_args=False)
|
|
179
|
+
unsquashed_actions = dist.rsample()
|
|
180
|
+
expected_actions = th.tanh(unsquashed_actions)
|
|
181
|
+
expected_log_probs = dist.log_prob(unsquashed_actions)
|
|
182
|
+
expected_log_probs -= th.log(1.0 - expected_actions.pow(2) + 1e-6)
|
|
183
|
+
expected_log_probs = expected_log_probs.sum(dim=-1, keepdim=True)
|
|
184
|
+
|
|
185
|
+
self.assertTrue(th.equal(sampled_actions, expected_actions))
|
|
186
|
+
self.assertTrue(
|
|
187
|
+
th.allclose(sampled_log_probs, expected_log_probs, atol=1e-6, rtol=1e-5)
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
with patch(
|
|
191
|
+
"torch.nn.utils.clip_grad_norm_",
|
|
192
|
+
wraps=th.nn.utils.clip_grad_norm_,
|
|
193
|
+
) as clip_grad_norm:
|
|
194
|
+
loss = learner.train(
|
|
195
|
+
rewards=th.tensor([[1.0], [0.0], [1.0]]),
|
|
196
|
+
terminated=th.tensor([[False], [True], [False]]),
|
|
197
|
+
states=th.randn(3, obs_dim),
|
|
198
|
+
actions=th.randn(3, action_dim).tanh(),
|
|
199
|
+
next_states=th.randn(3, obs_dim),
|
|
200
|
+
)
|
|
98
201
|
|
|
99
202
|
self.assertIsInstance(loss, float)
|
|
100
203
|
self.assertTrue(th.isfinite(th.tensor(loss)))
|
|
101
|
-
self.assertTrue(parameters_changed(actor,
|
|
204
|
+
self.assertTrue(parameters_changed(actor, actor_before))
|
|
205
|
+
self.assertTrue(parameters_changed(critic1, critic1_before))
|
|
206
|
+
self.assertTrue(parameters_changed(critic2, critic2_before))
|
|
207
|
+
self.assertTrue(all(call_count == 1 for call_count in critic_grad_calls.values()))
|
|
208
|
+
self.assertEqual(clip_grad_norm.call_count, 4)
|
|
102
209
|
self.assertEqual(
|
|
103
210
|
set(learner.last_losses),
|
|
104
211
|
{"actor", "critic", "alpha", "regularization", "total"},
|
|
105
212
|
)
|
|
213
|
+
for hook in hooks:
|
|
214
|
+
hook.remove()
|
|
215
|
+
|
|
216
|
+
def test_sac_initial_alpha_must_be_positive(self):
|
|
217
|
+
with self.assertRaisesRegex(ValueError, "initial_alpha must be > 0"):
|
|
218
|
+
SACConfig(initial_alpha=0.0)
|
|
106
219
|
|
|
107
220
|
def test_sac_actor_regularizer_contributes_to_loss(self):
|
|
108
221
|
th.manual_seed(0)
|
|
@@ -37,7 +37,7 @@ class PublicAPITest(unittest.TestCase):
|
|
|
37
37
|
"WarmupController",
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
self.assertEqual(drlab.__version__, "0.2.
|
|
40
|
+
self.assertEqual(drlab.__version__, "0.2.4")
|
|
41
41
|
self.assertTrue(expected.issubset(set(drlab.__all__)))
|
|
42
42
|
for name in expected:
|
|
43
43
|
self.assertTrue(hasattr(drlab, name), name)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|