Skip to content

Commit 1074bab

Browse files
authored
feature(zp): add dreamerv3 algorithm (#652)
* merge dreamer ConvEncoder to model/encoder.py * polish format * add DenseHead of dreamerv3 to world_model/model * add different Dist unittest * add dreamer pipeline and dreamer world model * add dreamervac * relocate world model network to torch_utils/dreamer * first version pipeline required polish * fix multi-env collect latent-state and add action affine_transform in dmc2gym_env * fix reset bugs in collector and evaluator * fix cuda/cpu bugs * add action repeat wrapper and fix discount lambda_return * change replay buffer and add slow target update * polish
1 parent 28289f0 commit 1074bab

30 files changed

Lines changed: 2579 additions & 20 deletions

‎ding/entry/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,6 @@
2525
from .serial_entry_preference_based_irl_onpolicy \
2626
import serial_pipeline_preference_based_irl_onpolicy
2727
from .application_entry_drex_collect_data import drex_collecting_data
28-
from .serial_entry_mbrl import serial_pipeline_dyna, serial_pipeline_dream
28+
from .serial_entry_mbrl import serial_pipeline_dyna, serial_pipeline_dream, serial_pipeline_dreamer
2929
from .serial_entry_bco import serial_pipeline_bco
3030
from .serial_entry_pc import serial_pipeline_pc

‎ding/entry/serial_entry_mbrl.py‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,3 +243,74 @@ def serial_pipeline_dream(
243243
learner.call_hook('after_run')
244244

245245
return policy
246+
247+
248+
def serial_pipeline_dreamer(
249+
input_cfg: Union[str, Tuple[dict, dict]],
250+
seed: int = 0,
251+
env_setting: Optional[List[Any]] = None,
252+
model: Optional[torch.nn.Module] = None,
253+
max_train_iter: Optional[int] = int(1e10),
254+
max_env_step: Optional[int] = int(1e10),
255+
) -> 'Policy': # noqa
256+
"""
257+
Overview:
258+
Serial pipeline entry for dreamerv3.
259+
Arguments:
260+
- input_cfg (:obj:`Union[str, Tuple[dict, dict]]`): Config in dict type. \
261+
``str`` type means config file path. \
262+
``Tuple[dict, dict]`` type means [user_config, create_cfg].
263+
- seed (:obj:`int`): Random seed.
264+
- env_setting (:obj:`Optional[List[Any]]`): A list with 3 elements: \
265+
``BaseEnv`` subclass, collector env config, and evaluator env config.
266+
- model (:obj:`Optional[torch.nn.Module]`): Instance of torch.nn.Module.
267+
- max_train_iter (:obj:`Optional[int]`): Maximum policy update iterations in training.
268+
- max_env_step (:obj:`Optional[int]`): Maximum collected environment interaction steps.
269+
Returns:
270+
- policy (:obj:`Policy`): Converged policy.
271+
"""
272+
cfg, policy, world_model, env_buffer, learner, collector, collector_env, evaluator, commander, tb_logger = \
273+
mbrl_entry_setup(input_cfg, seed, env_setting, model)
274+
275+
learner.call_hook('before_run')
276+
277+
# prefill environment buffer
278+
if cfg.policy.get('random_collect_size', 0) > 0:
279+
cfg.policy.random_collect_size = cfg.policy.random_collect_size // cfg.policy.collect.unroll_len
280+
random_collect(cfg.policy, policy, collector, collector_env, commander, env_buffer)
281+
282+
while True:
283+
collect_kwargs = commander.step()
284+
# eval the policy
285+
if evaluator.should_eval(collector.envstep):
286+
stop, reward = evaluator.eval(learner.save_checkpoint, learner.train_iter, collector.envstep, policy_kwargs=dict(world_model=world_model))
287+
if stop:
288+
break
289+
290+
# train world model and fill imagination buffer
291+
steps = (
292+
cfg.world_model.pretrain
293+
if world_model.should_pretrain()
294+
else int(world_model.should_train(collector.envstep))
295+
)
296+
for _ in range(steps):
297+
batch_size = learner.policy.get_attribute('batch_size')
298+
batch_length = cfg.policy.learn.batch_length
299+
post, context = world_model.train(env_buffer, collector.envstep, learner.train_iter, batch_size, batch_length)
300+
301+
start = post
302+
303+
learner.train(
304+
start, collector.envstep, policy_kwargs=dict(world_model=world_model, envstep=collector.envstep)
305+
)
306+
307+
# fill environment buffer
308+
data = collector.collect(train_iter=learner.train_iter, policy_kwargs=dict(world_model=world_model, envstep=collector.envstep, **collect_kwargs))
309+
env_buffer.push(data, cur_collector_envstep=collector.envstep)
310+
311+
if collector.envstep >= max_env_step or learner.train_iter >= max_train_iter:
312+
break
313+
314+
learner.call_hook('after_run')
315+
316+
return policy

‎ding/entry/utils.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ def random_collect(
6060
new_data = collector.collect(n_episode=policy_cfg.random_collect_size, policy_kwargs=collect_kwargs)
6161
else:
6262
new_data = collector.collect(
63-
n_sample=policy_cfg.random_collect_size, record_random_collect=False, policy_kwargs=collect_kwargs
63+
n_sample=policy_cfg.random_collect_size, random_collect=True,
64+
record_random_collect=False, policy_kwargs=collect_kwargs
6465
) # 'record_random_collect=False' means random collect without output log
6566
if postprocess_data_fn is not None:
6667
new_data = postprocess_data_fn(new_data)

‎ding/envs/env_wrappers/env_wrappers.py‎

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,17 @@ def observation(self, frame):
182182
import sys
183183
logging.warning("Please install opencv-python first.")
184184
sys.exit(1)
185-
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
186-
return cv2.resize(frame, (self.size, self.size), interpolation=cv2.INTER_AREA)
185+
# to do
186+
# channel_first
187+
if frame.shape[0] < 10:
188+
frame = frame.transpose(1, 2, 0)
189+
frame = cv2.resize(frame, (self.size, self.size), interpolation=cv2.INTER_AREA)
190+
frame = frame.transpose(2, 0, 1)
191+
else:
192+
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
193+
frame = cv2.resize(frame, (self.size, self.size), interpolation=cv2.INTER_AREA)
194+
195+
return frame
187196

188197

189198
@ENV_WRAPPER_REGISTRY.register('scaled_float_frame')
@@ -256,6 +265,38 @@ def reward(self, reward):
256265
"""
257266
return np.sign(reward)
258267

268+
@ENV_WRAPPER_REGISTRY.register('action_repeat')
269+
class ActionRepeatWrapper(gym.Wrapper):
270+
"""
271+
Overview:
272+
Repeat the action to step with env.
273+
Interface:
274+
``__init__``, ``step``
275+
Properties:
276+
- env (:obj:`gym.Env`): the environment to wrap.
277+
- ``action_repeat``
278+
279+
"""
280+
281+
def __init__(self, env, action_repeat=1):
282+
"""
283+
Overview:
284+
Initialize ``self.`` See ``help(type(self))`` for accurate signature; setup the properties.
285+
Arguments:
286+
- env (:obj:`gym.Env`): the environment to wrap.
287+
"""
288+
super().__init__(env)
289+
self.action_repeat = action_repeat
290+
291+
def step(self, action):
292+
reward = 0
293+
for _ in range(self.action_repeat):
294+
obs, rew, done, info = self.env.step(action)
295+
reward += rew or 0
296+
if done:
297+
break
298+
return obs, reward, done, info
299+
259300

260301
@ENV_WRAPPER_REGISTRY.register('delay_reward')
261302
class DelayRewardWrapper(gym.Wrapper):

‎ding/model/common/encoder.py‎

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from torch.nn import functional as F
88

99
from ding.torch_utils import ResFCBlock, ResBlock, Flatten, normed_linear, normed_conv2d
10+
from ding.torch_utils.network.dreamer import Conv2dSame, DreamerLayerNorm
1011
from ding.utils import SequenceType
1112

1213

@@ -35,6 +36,7 @@ def __init__(
3536
kernel_size: SequenceType = [8, 4, 3],
3637
stride: SequenceType = [4, 2, 1],
3738
padding: Optional[SequenceType] = None,
39+
layer_norm: Optional[bool] = False,
3840
norm_type: Optional[str] = None
3941
) -> None:
4042
"""
@@ -50,6 +52,7 @@ def __init__(
5052
- stride (:obj:`SequenceType`): Sequence of ``stride`` of subsequent conv layers.
5153
- padding (:obj:`SequenceType`): Padding added to all four sides of the input for each conv layer. \
5254
See ``nn.Conv2d`` for more details. Default is ``None``.
55+
- layer_norm (:obj:`bool`): Whether to use ``DreamerLayerNorm``.
5356
- norm_type (:obj:`str`): Type of normalization to use. See ``ding.torch_utils.network.ResBlock`` \
5457
for more details. Default is ``None``.
5558
"""
@@ -63,17 +66,35 @@ def __init__(
6366
layers = []
6467
input_size = obs_shape[0] # in_channel
6568
for i in range(len(kernel_size)):
66-
layers.append(nn.Conv2d(input_size, hidden_size_list[i], kernel_size[i], stride[i], padding[i]))
67-
layers.append(self.act)
69+
if layer_norm:
70+
layers.append(
71+
Conv2dSame(
72+
in_channels=input_size,
73+
out_channels=hidden_size_list[i],
74+
kernel_size=(kernel_size[i], kernel_size[i]),
75+
stride=(2, 2),
76+
bias=False,
77+
)
78+
)
79+
layers.append(DreamerLayerNorm(hidden_size_list[i]))
80+
layers.append(self.act)
81+
else:
82+
layers.append(nn.Conv2d(input_size, hidden_size_list[i], kernel_size[i], stride[i], padding[i]))
83+
layers.append(self.act)
6884
input_size = hidden_size_list[i]
69-
assert len(set(hidden_size_list[3:-1])) <= 1, "Please indicate the same hidden size for res block parts"
70-
for i in range(3, len(self.hidden_size_list) - 1):
71-
layers.append(ResBlock(self.hidden_size_list[i], activation=self.act, norm_type=norm_type))
85+
if len(self.hidden_size_list) >= len(kernel_size) + 2:
86+
assert self.hidden_size_list[len(kernel_size) - 1] == self.hidden_size_list[
87+
len(kernel_size)], "Please indicate the same hidden size between conv and res block"
88+
assert len(
89+
set(hidden_size_list[len(kernel_size):-1])
90+
) <= 1, "Please indicate the same hidden size for res block parts"
91+
for i in range(len(kernel_size), len(self.hidden_size_list) - 1):
92+
layers.append(ResBlock(self.hidden_size_list[i - 1], activation=self.act, norm_type=norm_type))
7293
layers.append(Flatten())
7394
self.main = nn.Sequential(*layers)
7495

7596
flatten_size = self._get_flatten_size()
76-
self.output_size = hidden_size_list[-1]
97+
self.output_size = hidden_size_list[-1] # outside to use
7798
self.mid = nn.Linear(flatten_size, hidden_size_list[-1])
7899

79100
def _get_flatten_size(self) -> int:

‎ding/model/common/tests/test_encoder.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,20 @@ def test_conv_encoder(self):
2424
self.output_check(model, outputs)
2525
assert outputs.shape == (B, 128)
2626

27+
def test_dreamer_conv_encoder(self):
28+
inputs = torch.randn(B, C, H, W)
29+
model = ConvEncoder(
30+
(C, H, W),
31+
hidden_size_list=[32, 64, 128, 256, 128],
32+
activation=torch.nn.SiLU(),
33+
kernel_size=[4, 4, 4, 4],
34+
layer_norm=True
35+
)
36+
print(model)
37+
outputs = model(inputs)
38+
self.output_check(model, outputs)
39+
assert outputs.shape == (B, 128)
40+
2741
def test_fc_encoder(self):
2842
inputs = torch.randn(B, 32)
2943
hidden_size_list = [128 for _ in range(3)]

‎ding/model/template/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from .q_learning import DQN, RainbowDQN, QRDQN, IQN, FQF, DRQN, C51DQN, BDQ
33
from .qac import QAC, DiscreteQAC
44
from .pdqn import PDQN
5-
from .vac import VAC
5+
from .vac import VAC, DREAMERVAC
66
from .bc import DiscreteBC, ContinuousBC
77
from .pg import PG
88
# algorithm-specific

‎ding/model/template/vac.py‎

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from ding.utils import SequenceType, squeeze, MODEL_REGISTRY
77
from ..common import ReparameterizationHead, RegressionHead, DiscreteHead, MultiHead, \
88
FCEncoder, ConvEncoder, IMPALAConvEncoder
9+
from ding.torch_utils.network.dreamer import ActionHead, DenseHead
910

1011

1112
@MODEL_REGISTRY.register('vac')
@@ -356,3 +357,90 @@ def compute_actor_critic(self, x: torch.Tensor) -> Dict:
356357
action_type = self.actor_head[0](actor_embedding)
357358
action_args = self.actor_head[1](actor_embedding)
358359
return {'logit': {'action_type': action_type['logit'], 'action_args': action_args}, 'value': value}
360+
361+
362+
@MODEL_REGISTRY.register('dreamervac')
363+
class DREAMERVAC(nn.Module):
364+
r"""
365+
Overview:
366+
The VAC model.
367+
Interfaces:
368+
``__init__``, ``forward``, ``compute_actor``, ``compute_critic``
369+
"""
370+
mode = ['compute_actor', 'compute_critic', 'compute_actor_critic']
371+
372+
def __init__(
373+
self,
374+
obs_shape: Union[int, SequenceType],
375+
action_shape: Union[int, SequenceType, EasyDict],
376+
dyn_stoch=32,
377+
dyn_deter=512,
378+
dyn_discrete=32,
379+
actor_layers=2,
380+
value_layers=2,
381+
units=512,
382+
act='SiLU',
383+
norm='LayerNorm',
384+
actor_dist='normal',
385+
actor_init_std=1.0,
386+
actor_min_std=0.1,
387+
actor_max_std=1.0,
388+
actor_temp=0.1,
389+
action_unimix_ratio=0.01,
390+
) -> None:
391+
r"""
392+
Overview:
393+
Init the VAC Model according to arguments.
394+
Arguments:
395+
- obs_shape (:obj:`Union[int, SequenceType]`): Observation's space.
396+
- action_shape (:obj:`Union[int, SequenceType]`): Action's space.
397+
- action_space (:obj:`str`): Choose action head in ['discrete', 'continuous', 'hybrid']
398+
- share_encoder (:obj:`bool`): Whether share encoder.
399+
- encoder_hidden_size_list (:obj:`SequenceType`): Collection of ``hidden_size`` to pass to ``Encoder``
400+
- actor_head_hidden_size (:obj:`Optional[int]`): The ``hidden_size`` to pass to actor-nn's ``Head``.
401+
- actor_head_layer_num (:obj:`int`):
402+
The num of layers used in the network to compute Q value output for actor's nn.
403+
- critic_head_hidden_size (:obj:`Optional[int]`): The ``hidden_size`` to pass to critic-nn's ``Head``.
404+
- critic_head_layer_num (:obj:`int`):
405+
The num of layers used in the network to compute Q value output for critic's nn.
406+
- activation (:obj:`Optional[nn.Module]`):
407+
The type of activation function to use in ``MLP`` the after ``layer_fn``,
408+
if ``None`` then default set to ``nn.ReLU()``
409+
- norm_type (:obj:`Optional[str]`):
410+
The type of normalization to use, see ``ding.torch_utils.fc_block`` for more details`
411+
"""
412+
super(DREAMERVAC, self).__init__()
413+
obs_shape: int = squeeze(obs_shape)
414+
action_shape = squeeze(action_shape)
415+
self.obs_shape, self.action_shape = obs_shape, action_shape
416+
417+
if dyn_discrete:
418+
feat_size = dyn_stoch * dyn_discrete + dyn_deter
419+
else:
420+
feat_size = dyn_stoch + dyn_deter
421+
self.actor = ActionHead(
422+
feat_size, # pytorch version
423+
action_shape,
424+
actor_layers,
425+
units,
426+
act,
427+
norm,
428+
actor_dist,
429+
actor_init_std,
430+
actor_min_std,
431+
actor_max_std,
432+
actor_temp,
433+
outscale=1.0,
434+
unimix_ratio=action_unimix_ratio,
435+
)
436+
self.critic = DenseHead(
437+
feat_size, # pytorch version
438+
(255, ),
439+
value_layers,
440+
units,
441+
'SiLU', # act
442+
'LN', # norm
443+
'twohot_symlog',
444+
outscale=0.0,
445+
device='cuda' if torch.cuda.is_available() else 'cpu',
446+
)

‎ding/policy/command_mode_policy_instance.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from .td3_bc import TD3BCPolicy
2727
from .sac import SACPolicy, SACDiscretePolicy
2828
from .mbpolicy.mbsac import MBSACPolicy, STEVESACPolicy
29+
from .mbpolicy.dreamer import DREAMERPolicy
2930
from .qmix import QMIXPolicy
3031
from .wqmix import WQMIXPolicy
3132
from .collaq import CollaQPolicy
@@ -302,6 +303,10 @@ class MBSACCommandModePolicy(MBSACPolicy, DummyCommandModePolicy):
302303
class STEVESACCommandModePolicy(STEVESACPolicy, DummyCommandModePolicy):
303304
pass
304305

306+
@POLICY_REGISTRY.register('dreamer_command')
307+
class DREAMERCommandModePolicy(DREAMERPolicy, DummyCommandModePolicy):
308+
pass
309+
305310

306311
@POLICY_REGISTRY.register('cql_command')
307312
class CQLCommandModePolicy(CQLPolicy, DummyCommandModePolicy):

‎ding/policy/mbpolicy/__init__.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
from .mbsac import MBSACPolicy
2+
from .dreamer import DREAMERPolicy

0 commit comments

Comments
 (0)