|
import copy |
|
from typing import List, Dict, Any, Tuple, Union |
|
|
|
import numpy as np |
|
import torch |
|
import torch.optim as optim |
|
from ding.model import model_wrap |
|
from ding.torch_utils import to_tensor |
|
from ding.utils import POLICY_REGISTRY |
|
from torch.nn import L1Loss, KLDivLoss |
|
|
|
from lzero.mcts import GumbelMuZeroMCTSCtree as MCTSCtree |
|
from lzero.mcts import MuZeroMCTSPtree as MCTSPtree |
|
from lzero.model import ImageTransforms |
|
from lzero.policy import scalar_transform, InverseScalarTransform, cross_entropy_loss, phi_transform, \ |
|
DiscreteSupport, to_torch_float_tensor, mz_network_output_unpack, select_action, negative_cosine_similarity, \ |
|
prepare_obs, \ |
|
configure_optimizers |
|
from lzero.policy.muzero import MuZeroPolicy |
|
|
|
|
|
@POLICY_REGISTRY.register('gumbel_muzero') |
|
class GumbelMuZeroPolicy(MuZeroPolicy): |
|
""" |
|
Overview: |
|
The policy class for Gumbel MuZero proposed in the paper https://openreview.net/forum?id=bERaNdoegnO. |
|
""" |
|
|
|
|
|
config = dict( |
|
model=dict( |
|
|
|
model_type='conv', |
|
|
|
continuous_action_space=False, |
|
|
|
|
|
observation_shape=(4, 96, 96), |
|
|
|
self_supervised_learning_loss=False, |
|
|
|
categorical_distribution=True, |
|
|
|
image_channel=1, |
|
|
|
frame_stack_num=1, |
|
|
|
num_res_blocks=1, |
|
|
|
num_channels=64, |
|
|
|
|
|
support_scale=300, |
|
|
|
bias=True, |
|
|
|
discrete_action_encoding_type='one_hot', |
|
|
|
res_connection_in_dynamics=True, |
|
|
|
norm_type='BN', |
|
), |
|
|
|
|
|
multi_gpu=False, |
|
|
|
|
|
sampled_algo=False, |
|
|
|
gumbel_algo=True, |
|
|
|
mcts_ctree=True, |
|
|
|
cuda=True, |
|
|
|
collector_env_num=8, |
|
|
|
evaluator_env_num=3, |
|
|
|
env_type='not_board_games', |
|
|
|
action_type='fixed_action_space', |
|
|
|
battle_mode='play_with_bot_mode', |
|
|
|
monitor_extra_statistics=True, |
|
|
|
game_segment_length=200, |
|
|
|
|
|
|
|
transform2string=False, |
|
|
|
gray_scale=False, |
|
|
|
use_augmentation=False, |
|
|
|
augmentation=['shift', 'intensity'], |
|
|
|
|
|
|
|
|
|
|
|
ignore_done=False, |
|
|
|
|
|
|
|
|
|
|
|
update_per_collect=None, |
|
|
|
model_update_ratio=0.1, |
|
|
|
batch_size=256, |
|
|
|
optim_type='SGD', |
|
|
|
learning_rate=0.2, |
|
|
|
target_update_freq=100, |
|
|
|
weight_decay=1e-4, |
|
|
|
momentum=0.9, |
|
|
|
grad_clip_value=10, |
|
|
|
n_episode=8, |
|
|
|
num_simulations=50, |
|
|
|
max_num_considered_actions=4, |
|
|
|
discount_factor=0.997, |
|
|
|
td_steps=5, |
|
|
|
num_unroll_steps=5, |
|
|
|
reward_loss_weight=1, |
|
|
|
value_loss_weight=0.25, |
|
|
|
policy_loss_weight=1, |
|
|
|
ssl_loss_weight=0, |
|
|
|
|
|
lr_piecewise_constant_decay=True, |
|
|
|
threshold_training_steps_for_final_lr=int(5e4), |
|
|
|
manual_temperature_decay=False, |
|
|
|
threshold_training_steps_for_final_temperature=int(1e5), |
|
|
|
|
|
fixed_temperature_value=0.25, |
|
|
|
|
|
|
|
use_priority=True, |
|
|
|
|
|
priority_prob_alpha=0.6, |
|
|
|
|
|
priority_prob_beta=0.4, |
|
|
|
|
|
|
|
root_dirichlet_alpha=0.3, |
|
|
|
root_noise_weight=0.25, |
|
|
|
|
|
|
|
random_collect_episode_num=0, |
|
|
|
|
|
eps=dict( |
|
|
|
eps_greedy_exploration_in_collect=False, |
|
|
|
type='linear', |
|
|
|
start=1., |
|
|
|
end=0.05, |
|
|
|
decay=int(1e5), |
|
), |
|
) |
|
|
|
def default_model(self) -> Tuple[str, List[str]]: |
|
""" |
|
Overview: |
|
Return this algorithm default model setting for demonstration. |
|
Returns: |
|
- model_info (:obj:`Tuple[str, List[str]]`): model name and model import_names. |
|
- model_type (:obj:`str`): The model type used in this algorithm, which is registered in ModelRegistry. |
|
- import_names (:obj:`List[str]`): The model class path list used in this algorithm. |
|
.. note:: |
|
The user can define and use customized network model but must obey the same interface definition indicated \ |
|
by import_names path. For MuZero, ``lzero.model.muzero_model.MuZeroModel`` |
|
""" |
|
if self._cfg.model.model_type == "conv": |
|
return 'MuZeroModel', ['lzero.model.muzero_model'] |
|
elif self._cfg.model.model_type == "mlp": |
|
return 'MuZeroModelMLP', ['lzero.model.muzero_model_mlp'] |
|
else: |
|
raise ValueError("model type {} is not supported".format(self._cfg.model.model_type)) |
|
|
|
def _init_learn(self) -> None: |
|
""" |
|
Overview: |
|
Learn mode init method. Called by ``self.__init__``. Initialize the learn model, optimizer and MCTS utils. |
|
""" |
|
assert self._cfg.optim_type in ['SGD', 'Adam', 'AdamW'], self._cfg.optim_type |
|
|
|
if self._cfg.optim_type == 'SGD': |
|
self._optimizer = optim.SGD( |
|
self._model.parameters(), |
|
lr=self._cfg.learning_rate, |
|
momentum=self._cfg.momentum, |
|
weight_decay=self._cfg.weight_decay, |
|
) |
|
elif self._cfg.optim_type == 'Adam': |
|
self._optimizer = optim.Adam( |
|
self._model.parameters(), lr=self._cfg.learning_rate, weight_decay=self._cfg.weight_decay |
|
) |
|
elif self._cfg.optim_type == 'AdamW': |
|
self._optimizer = configure_optimizers(model=self._model, weight_decay=self._cfg.weight_decay, |
|
learning_rate=self._cfg.learning_rate, device_type=self._cfg.device) |
|
|
|
if self._cfg.lr_piecewise_constant_decay: |
|
from torch.optim.lr_scheduler import LambdaLR |
|
max_step = self._cfg.threshold_training_steps_for_final_lr |
|
|
|
lr_lambda = lambda step: 1 if step < max_step * 0.5 else (0.1 if step < max_step else 0.01) |
|
self.lr_scheduler = LambdaLR(self._optimizer, lr_lambda=lr_lambda) |
|
|
|
|
|
self._target_model = copy.deepcopy(self._model) |
|
self._target_model = model_wrap( |
|
self._target_model, |
|
wrapper_name='target', |
|
update_type='assign', |
|
update_kwargs={'freq': self._cfg.target_update_freq} |
|
) |
|
self._learn_model = self._model |
|
|
|
if self._cfg.use_augmentation: |
|
self.image_transforms = ImageTransforms( |
|
self._cfg.augmentation, |
|
image_shape=(self._cfg.model.observation_shape[1], self._cfg.model.observation_shape[2]) |
|
) |
|
self.value_support = DiscreteSupport(-self._cfg.model.support_scale, self._cfg.model.support_scale, delta=1) |
|
self.reward_support = DiscreteSupport(-self._cfg.model.support_scale, self._cfg.model.support_scale, delta=1) |
|
self.inverse_scalar_transform_handle = InverseScalarTransform( |
|
self._cfg.model.support_scale, self._cfg.device, self._cfg.model.categorical_distribution |
|
) |
|
self.kl_loss = KLDivLoss(reduction='none') |
|
|
|
def _forward_learn(self, data: torch.Tensor) -> Dict[str, Union[float, int]]: |
|
""" |
|
Overview: |
|
The forward function for learning policy in learn mode, which is the core of the learning process. |
|
The data is sampled from replay buffer. |
|
The loss is calculated by the loss function and the loss is backpropagated to update the model. |
|
Arguments: |
|
- data (:obj:`Tuple[torch.Tensor]`): The data sampled from replay buffer, which is a tuple of tensors. |
|
The first tensor is the current_batch, the second tensor is the target_batch. |
|
Returns: |
|
- info_dict (:obj:`Dict[str, Union[float, int]]`): The information dict to be logged, which contains \ |
|
current learning loss and learning statistics. |
|
""" |
|
self._learn_model.train() |
|
self._target_model.train() |
|
|
|
current_batch, target_batch = data |
|
obs_batch_ori, action_batch, improved_policy_batch, mask_batch, indices, weights, make_time = current_batch |
|
target_reward, target_value, target_policy = target_batch |
|
|
|
obs_batch, obs_target_batch = prepare_obs(obs_batch_ori, self._cfg) |
|
|
|
|
|
if self._cfg.use_augmentation: |
|
obs_batch = self.image_transforms.transform(obs_batch) |
|
if self._cfg.model.self_supervised_learning_loss: |
|
obs_target_batch = self.image_transforms.transform(obs_target_batch) |
|
|
|
|
|
|
|
action_batch = torch.from_numpy(action_batch).to(self._cfg.device).unsqueeze(-1).long() |
|
data_list = [ |
|
mask_batch, |
|
target_reward.astype('float32'), |
|
target_value.astype('float32'), target_policy, weights |
|
] |
|
[mask_batch, target_reward, target_value, target_policy, |
|
weights] = to_torch_float_tensor(data_list, self._cfg.device) |
|
|
|
target_reward = target_reward.view(self._cfg.batch_size, -1) |
|
target_value = target_value.view(self._cfg.batch_size, -1) |
|
|
|
assert obs_batch.size(0) == self._cfg.batch_size == target_reward.size(0) |
|
|
|
|
|
|
|
transformed_target_reward = scalar_transform(target_reward) |
|
transformed_target_value = scalar_transform(target_value) |
|
|
|
|
|
|
|
target_reward_categorical = phi_transform(self.reward_support, transformed_target_reward) |
|
target_value_categorical = phi_transform(self.value_support, transformed_target_value) |
|
|
|
|
|
|
|
|
|
network_output = self._learn_model.initial_inference(obs_batch) |
|
|
|
|
|
hidden_state, reward, value, policy_logits = mz_network_output_unpack(network_output) |
|
|
|
|
|
|
|
original_value = self.inverse_scalar_transform_handle(value) |
|
|
|
|
|
predicted_rewards = [] |
|
if self._cfg.monitor_extra_statistics: |
|
hidden_state_list = hidden_state.detach().cpu().numpy() |
|
predicted_values, predicted_policies = original_value.detach().cpu(), torch.softmax( |
|
policy_logits, dim=1 |
|
).detach().cpu() |
|
|
|
|
|
value_priority = L1Loss(reduction='none')(original_value.squeeze(-1), target_value[:, 0]) |
|
value_priority = value_priority.data.cpu().numpy() + 1e-6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
policy_loss = self.kl_loss(torch.log(torch.softmax(policy_logits, dim=1)), |
|
torch.from_numpy(improved_policy_batch[:, 0]).to(self._cfg.device).detach().float()) |
|
policy_loss = policy_loss.mean(dim=-1) * mask_batch[:, 0] |
|
|
|
entropy_loss = -torch.sum(torch.softmax(policy_logits, dim=1) * torch.log(torch.softmax(policy_logits, dim=1)), |
|
dim=-1) |
|
|
|
value_loss = cross_entropy_loss(value, target_value_categorical[:, 0]) |
|
|
|
reward_loss = torch.zeros(self._cfg.batch_size, device=self._cfg.device) |
|
consistency_loss = torch.zeros(self._cfg.batch_size, device=self._cfg.device) |
|
|
|
|
|
|
|
|
|
for step_k in range(self._cfg.num_unroll_steps): |
|
|
|
|
|
|
|
network_output = self._learn_model.recurrent_inference(hidden_state, action_batch[:, step_k]) |
|
hidden_state, reward, value, policy_logits = mz_network_output_unpack(network_output) |
|
|
|
|
|
|
|
original_value = self.inverse_scalar_transform_handle(value) |
|
|
|
if self._cfg.model.self_supervised_learning_loss: |
|
|
|
|
|
|
|
if self._cfg.ssl_loss_weight > 0: |
|
|
|
beg_index, end_index = self._get_target_obs_index_in_step_k(step_k) |
|
network_output = self._learn_model.initial_inference(obs_target_batch[:, beg_index:end_index]) |
|
|
|
hidden_state = to_tensor(hidden_state) |
|
representation_state = to_tensor(network_output.latent_state) |
|
|
|
|
|
dynamic_proj = self._learn_model.project(hidden_state, with_grad=True) |
|
observation_proj = self._learn_model.project(representation_state, with_grad=False) |
|
temp_loss = negative_cosine_similarity(dynamic_proj, observation_proj) * mask_batch[:, step_k] |
|
consistency_loss += temp_loss |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
policy_loss += self.kl_loss(torch.log(torch.softmax(policy_logits, dim=1)), |
|
torch.from_numpy(improved_policy_batch[:, step_k + 1]).to( |
|
self._cfg.device).detach().float()).mean(dim=-1) * mask_batch[:, step_k + 1] |
|
value_loss += cross_entropy_loss(value, target_value_categorical[:, step_k + 1]) |
|
reward_loss += cross_entropy_loss(reward, target_reward_categorical[:, step_k]) |
|
entropy_loss += -torch.sum( |
|
torch.softmax(policy_logits, dim=1) * torch.log(torch.softmax(policy_logits, dim=1)), dim=-1) |
|
|
|
|
|
|
|
|
|
if self._cfg.monitor_extra_statistics: |
|
original_rewards = self.inverse_scalar_transform_handle(reward) |
|
original_rewards_cpu = original_rewards.detach().cpu() |
|
|
|
predicted_values = torch.cat( |
|
(predicted_values, self.inverse_scalar_transform_handle(value).detach().cpu()) |
|
) |
|
predicted_rewards.append(original_rewards_cpu) |
|
predicted_policies = torch.cat((predicted_policies, torch.softmax(policy_logits, dim=1).detach().cpu())) |
|
hidden_state_list = np.concatenate((hidden_state_list, hidden_state.detach().cpu().numpy())) |
|
|
|
|
|
|
|
|
|
|
|
loss = ( |
|
self._cfg.ssl_loss_weight * consistency_loss + self._cfg.policy_loss_weight * policy_loss + |
|
self._cfg.value_loss_weight * value_loss + self._cfg.reward_loss_weight * reward_loss |
|
) |
|
weighted_total_loss = (weights * loss).mean() |
|
|
|
gradient_scale = 1 / self._cfg.num_unroll_steps |
|
weighted_total_loss.register_hook(lambda grad: grad * gradient_scale) |
|
self._optimizer.zero_grad() |
|
weighted_total_loss.backward() |
|
if self._cfg.multi_gpu: |
|
self.sync_gradients(self._learn_model) |
|
total_grad_norm_before_clip = torch.nn.utils.clip_grad_norm_( |
|
self._learn_model.parameters(), self._cfg.grad_clip_value |
|
) |
|
self._optimizer.step() |
|
if self._cfg.lr_piecewise_constant_decay: |
|
self.lr_scheduler.step() |
|
|
|
|
|
|
|
|
|
self._target_model.update(self._learn_model.state_dict()) |
|
|
|
if self._cfg.monitor_extra_statistics: |
|
predicted_rewards = torch.stack(predicted_rewards).transpose(1, 0).squeeze(-1) |
|
predicted_rewards = predicted_rewards.reshape(-1).unsqueeze(-1) |
|
|
|
return { |
|
'collect_mcts_temperature': self._collect_mcts_temperature, |
|
'cur_lr': self._optimizer.param_groups[0]['lr'], |
|
'weighted_total_loss': weighted_total_loss.item(), |
|
'total_loss': loss.mean().item(), |
|
'policy_loss': policy_loss.mean().item(), |
|
'reward_loss': reward_loss.mean().item(), |
|
'value_loss': value_loss.mean().item(), |
|
'consistency_loss': consistency_loss.mean().item() / self._cfg.num_unroll_steps, |
|
'entropy_loss': entropy_loss.mean().item(), |
|
|
|
|
|
|
|
|
|
'value_priority_orig': value_priority, |
|
'value_priority': value_priority.mean().item(), |
|
'target_reward': target_reward.detach().cpu().numpy().mean().item(), |
|
'target_value': target_value.detach().cpu().numpy().mean().item(), |
|
'transformed_target_reward': transformed_target_reward.detach().cpu().numpy().mean().item(), |
|
'transformed_target_value': transformed_target_value.detach().cpu().numpy().mean().item(), |
|
'predicted_rewards': predicted_rewards.detach().cpu().numpy().mean().item(), |
|
'predicted_values': predicted_values.detach().cpu().numpy().mean().item(), |
|
'total_grad_norm_before_clip': total_grad_norm_before_clip.item() |
|
} |
|
|
|
def _init_collect(self) -> None: |
|
""" |
|
Overview: |
|
Collect mode init method. Called by ``self.__init__``. Initialize the collect model and MCTS utils. |
|
""" |
|
self._collect_model = self._model |
|
if self._cfg.mcts_ctree: |
|
self._mcts_collect = MCTSCtree(self._cfg) |
|
else: |
|
self._mcts_collect = MCTSPtree(self._cfg) |
|
self._collect_mcts_temperature = 1 |
|
|
|
def _forward_collect( |
|
self, |
|
data: torch.Tensor, |
|
action_mask: list = None, |
|
temperature: float = 1, |
|
to_play: List = [-1], |
|
epsilon: float = 0.25, |
|
ready_env_id: np.array = None, |
|
) -> Dict: |
|
""" |
|
Overview: |
|
The forward function for collecting data in collect mode. Use model to execute MCTS search. |
|
Choosing the action through sampling during the collect mode. |
|
Arguments: |
|
- data (:obj:`torch.Tensor`): The input data, i.e. the observation. |
|
- action_mask (:obj:`list`): The action mask, i.e. the action that cannot be selected. |
|
- temperature (:obj:`float`): The temperature of the policy. |
|
- to_play (:obj:`int`): The player to play. |
|
- ready_env_id (:obj:`list`): The id of the env that is ready to collect. |
|
Shape: |
|
- data (:obj:`torch.Tensor`): |
|
- For Atari, :math:`(N, C*S, H, W)`, where N is the number of collect_env, C is the number of channels, \ |
|
S is the number of stacked frames, H is the height of the image, W is the width of the image. |
|
- For lunarlander, :math:`(N, O)`, where N is the number of collect_env, O is the observation space size. |
|
- action_mask: :math:`(N, action_space_size)`, where N is the number of collect_env. |
|
- temperature: :math:`(1, )`. |
|
- to_play: :math:`(N, 1)`, where N is the number of collect_env. |
|
- ready_env_id: None |
|
Returns: |
|
- output (:obj:`Dict[int, Any]`): Dict type data, the keys including ``action``, ``distributions``, \ |
|
``visit_count_distribution_entropy``, ``value``, ``roots_completed_value``, ``improved_policy_probs``, \ |
|
``pred_value``, ``policy_logits``. |
|
""" |
|
self._collect_model.eval() |
|
self._collect_mcts_temperature = temperature |
|
self.collect_epsilon = epsilon |
|
active_collect_env_num = data.shape[0] |
|
with torch.no_grad(): |
|
|
|
network_output = self._collect_model.initial_inference(data) |
|
latent_state_roots, reward_roots, pred_values, policy_logits = mz_network_output_unpack(network_output) |
|
|
|
pred_values = self.inverse_scalar_transform_handle(pred_values).detach().cpu().numpy() |
|
latent_state_roots = latent_state_roots.detach().cpu().numpy() |
|
policy_logits = policy_logits.detach().cpu().numpy().tolist() |
|
|
|
legal_actions = [[i for i, x in enumerate(action_mask[j]) if x == 1] for j in range(active_collect_env_num)] |
|
|
|
noises = [ |
|
np.random.dirichlet([self._cfg.root_dirichlet_alpha] * int(sum(action_mask[j])) |
|
).astype(np.float32).tolist() for j in range(active_collect_env_num) |
|
] |
|
if self._cfg.mcts_ctree: |
|
|
|
roots = MCTSCtree.roots(active_collect_env_num, legal_actions) |
|
else: |
|
|
|
roots = MCTSPtree.roots(active_collect_env_num, legal_actions) |
|
|
|
roots.prepare(self._cfg.root_noise_weight, noises, reward_roots, list(pred_values), policy_logits, to_play) |
|
self._mcts_collect.search(roots, self._collect_model, latent_state_roots, to_play) |
|
|
|
|
|
roots_visit_count_distributions = roots.get_distributions() |
|
roots_values = roots.get_values() |
|
roots_completed_values = roots.get_children_values(self._cfg.discount_factor, |
|
self._cfg.model.action_space_size) |
|
|
|
|
|
|
|
|
|
|
|
roots_improved_policy_probs = roots.get_policies(self._cfg.discount_factor, |
|
self._cfg.model.action_space_size) |
|
roots_improved_policy_probs = np.array(roots_improved_policy_probs) |
|
|
|
data_id = [i for i in range(active_collect_env_num)] |
|
output = {i: None for i in data_id} |
|
|
|
if ready_env_id is None: |
|
ready_env_id = np.arange(active_collect_env_num) |
|
|
|
for i, env_id in enumerate(ready_env_id): |
|
distributions, value, improved_policy_probs = roots_visit_count_distributions[i], roots_values[i], \ |
|
roots_improved_policy_probs[i] |
|
|
|
roots_completed_value = roots_completed_values[i] |
|
|
|
|
|
action_index_in_legal_action_set, visit_count_distribution_entropy = select_action( |
|
distributions, temperature=self._collect_mcts_temperature, deterministic=False |
|
) |
|
|
|
|
|
valid_value = np.where(action_mask[i] == 1.0, improved_policy_probs, 0.0) |
|
action = np.argmax([v for v in valid_value]) |
|
roots_completed_value = np.where(action_mask[i] == 1.0, roots_completed_value, 0.0) |
|
|
|
output[env_id] = { |
|
'action': action, |
|
'visit_count_distributions': distributions, |
|
'visit_count_distribution_entropy': visit_count_distribution_entropy, |
|
'searched_value': value, |
|
'roots_completed_value': roots_completed_value, |
|
'improved_policy_probs': improved_policy_probs, |
|
'predicted_value': pred_values[i], |
|
'predicted_policy_logits': policy_logits[i], |
|
} |
|
|
|
return output |
|
|
|
def _init_eval(self) -> None: |
|
""" |
|
Overview: |
|
Evaluate mode init method. Called by ``self.__init__``. Initialize the eval model and MCTS utils. |
|
""" |
|
self._eval_model = self._model |
|
if self._cfg.mcts_ctree: |
|
self._mcts_eval = MCTSCtree(self._cfg) |
|
else: |
|
self._mcts_eval = MCTSPtree(self._cfg) |
|
|
|
def _forward_eval(self, data: torch.Tensor, action_mask: list, to_play: int = -1, |
|
ready_env_id: np.array = None, ) -> Dict: |
|
""" |
|
Overview: |
|
The forward function for evaluating the current policy in eval mode. Use model to execute MCTS search. |
|
Choosing the action with the highest value (argmax) rather than sampling during the eval mode. |
|
Arguments: |
|
- data (:obj:`torch.Tensor`): The input data, i.e. the observation. |
|
- action_mask (:obj:`list`): The action mask, i.e. the action that cannot be selected. |
|
- to_play (:obj:`int`): The player to play. |
|
- ready_env_id (:obj:`list`): The id of the env that is ready to collect. |
|
Shape: |
|
- data (:obj:`torch.Tensor`): |
|
- For Atari, :math:`(N, C*S, H, W)`, where N is the number of collect_env, C is the number of channels, \ |
|
S is the number of stacked frames, H is the height of the image, W is the width of the image. |
|
- For lunarlander, :math:`(N, O)`, where N is the number of collect_env, O is the observation space size. |
|
- action_mask: :math:`(N, action_space_size)`, where N is the number of collect_env. |
|
- to_play: :math:`(N, 1)`, where N is the number of collect_env. |
|
- ready_env_id: None |
|
Returns: |
|
- output (:obj:`Dict[int, Any]`): Dict type data, the keys including ``action``, ``distributions``, \ |
|
``visit_count_distribution_entropy``, ``value``, ``pred_value``, ``policy_logits``. |
|
""" |
|
self._eval_model.eval() |
|
active_eval_env_num = data.shape[0] |
|
with torch.no_grad(): |
|
|
|
network_output = self._collect_model.initial_inference(data) |
|
latent_state_roots, reward_roots, pred_values, policy_logits = mz_network_output_unpack(network_output) |
|
|
|
if not self._eval_model.training: |
|
|
|
pred_values = self.inverse_scalar_transform_handle(pred_values).detach().cpu().numpy() |
|
latent_state_roots = latent_state_roots.detach().cpu().numpy() |
|
policy_logits = policy_logits.detach().cpu().numpy().tolist() |
|
|
|
legal_actions = [[i for i, x in enumerate(action_mask[j]) if x == 1] for j in range(active_eval_env_num)] |
|
if self._cfg.mcts_ctree: |
|
|
|
roots = MCTSCtree.roots(active_eval_env_num, legal_actions) |
|
else: |
|
|
|
roots = MCTSPtree.roots(active_eval_env_num, legal_actions) |
|
roots.prepare_no_noise(reward_roots, list(pred_values), policy_logits, to_play) |
|
self._mcts_eval.search(roots, self._eval_model, latent_state_roots, to_play) |
|
|
|
|
|
roots_visit_count_distributions = roots.get_distributions() |
|
roots_values = roots.get_values() |
|
|
|
|
|
|
|
|
|
|
|
roots_improved_policy_probs = roots.get_policies(self._cfg.discount_factor, |
|
self._cfg.model.action_space_size) |
|
roots_improved_policy_probs = np.array(roots_improved_policy_probs) |
|
|
|
data_id = [i for i in range(active_eval_env_num)] |
|
output = {i: None for i in data_id} |
|
|
|
if ready_env_id is None: |
|
ready_env_id = np.arange(active_eval_env_num) |
|
|
|
for i, env_id in enumerate(ready_env_id): |
|
distributions, value, improved_policy_probs = roots_visit_count_distributions[i], roots_values[i], \ |
|
roots_improved_policy_probs[i] |
|
|
|
|
|
|
|
|
|
action_index_in_legal_action_set, visit_count_distribution_entropy = select_action( |
|
distributions, temperature=1, deterministic=True |
|
) |
|
|
|
|
|
|
|
|
|
valid_value = np.where(action_mask[i] == 1.0, improved_policy_probs, 0.0) |
|
action = np.argmax([v for v in valid_value]) |
|
|
|
output[env_id] = { |
|
'action': action, |
|
'visit_count_distributions': distributions, |
|
'visit_count_distribution_entropy': visit_count_distribution_entropy, |
|
'searched_value': value, |
|
'predicted_value': pred_values[i], |
|
'predicted_policy_logits': policy_logits[i], |
|
} |
|
|
|
return output |
|
|
|
def _monitor_vars_learn(self) -> List[str]: |
|
""" |
|
Overview: |
|
Register the variables to be monitored in learn mode. The registered variables will be logged in |
|
tensorboard according to the return value ``_forward_learn``. |
|
""" |
|
return [ |
|
'collect_mcts_temperature', |
|
'cur_lr', |
|
'weighted_total_loss', |
|
'total_loss', |
|
'policy_loss', |
|
'reward_loss', |
|
'value_loss', |
|
'consistency_loss', |
|
'entropy_loss', |
|
'value_priority', |
|
'target_reward', |
|
'target_value', |
|
'predicted_rewards', |
|
'predicted_values', |
|
'transformed_target_reward', |
|
'transformed_target_value', |
|
'total_grad_norm_before_clip', |
|
] |
|
|
|
def _state_dict_learn(self) -> Dict[str, Any]: |
|
""" |
|
Overview: |
|
Return the state_dict of learn mode, usually including model, target_model and optimizer. |
|
Returns: |
|
- state_dict (:obj:`Dict[str, Any]`): The dict of current policy learn state, for saving and restoring. |
|
""" |
|
return { |
|
'model': self._learn_model.state_dict(), |
|
'target_model': self._target_model.state_dict(), |
|
'optimizer': self._optimizer.state_dict(), |
|
} |
|
|
|
def _load_state_dict_learn(self, state_dict: Dict[str, Any]) -> None: |
|
""" |
|
Overview: |
|
Load the state_dict variable into policy learn mode. |
|
Arguments: |
|
- state_dict (:obj:`Dict[str, Any]`): The dict of policy learn state saved before. |
|
""" |
|
self._learn_model.load_state_dict(state_dict['model']) |
|
self._target_model.load_state_dict(state_dict['target_model']) |
|
self._optimizer.load_state_dict(state_dict['optimizer']) |
|
|
|
def _process_transition(self, obs, policy_output, timestep): |
|
|
|
pass |
|
|
|
def _get_train_sample(self, data): |
|
|
|
pass |
|
|