返回代码索引

shared_gat_buffer.py

Dict on-policy 数据与 GAE

原文件 · 593 行 · 32258 字节

utils/shared_gat_buffer.py
SHA-256: 6ae61dc38042189dc2bfd236e3c7c019b1eddf73fe31d82e6c67f3cdaced5b1f

1import torch2import numpy as np3from utils.util import get_shape_from_obs_space, get_shape_from_act_space4from utils.obs_utils import POLICY_OBS_KEYS567def _flatten(T, N, x):8    return x.reshape(T * N, *x.shape[2:])91011def _cast(x):12    return x.transpose(1, 2, 0, *range(3, x.ndim)).reshape(-1, *x.shape[3:])131415def _normalize_shape(shape):16    if type(shape[-1]) == list:17        return shape[:1]18    return shape192021def _main_obs_shape(obs_shape):22    if isinstance(obs_shape, dict):23        return (int(sum(np.prod(obs_shape[k]) for k in POLICY_OBS_KEYS)),)24    return _normalize_shape(obs_shape)252627class SharedGatReplayBuffer(object):28    """29    Buffer to store training data.30    :param args: (argparse.Namespace) arguments containing relevant model, policy, and env information.31    :param num_agents: (int) number of agents in the env.32    :param obs_space: (gym.Space) observation space of agents.33    :param cent_obs_space: (gym.Space) centralized observation space of agents.34    :param act_space: (gym.Space) action space for agents.35    """3637    def __init__(self, args, num_agents, obs_space, cent_obs_space, act_space):38        self.episode_length = args.episode_length39        self.n_rollout_threads = args.n_rollout_threads40        self.hidden_size = args.hidden_size41        self.recurrent_N = args.recurrent_N42        self.gamma = args.gamma43        self.gae_lambda = args.gae_lambda44        self._use_gae = args.use_gae45        self._use_popart = args.use_popart46        self._use_valuenorm = args.use_valuenorm47        self._use_proper_time_limits = args.use_proper_time_limits4849        obs_shape_dict = get_shape_from_obs_space(obs_space)   # dict for Dict space50        share_obs_shape = get_shape_from_obs_space(cent_obs_space)5152        if isinstance(share_obs_shape, dict):53            self.share_obs = {54                k: np.zeros(55                    (self.episode_length + 1, self.n_rollout_threads, num_agents, *_normalize_shape(v)),56                    dtype=np.float32,57                )58                for k, v in share_obs_shape.items()59            }60        else:61            share_obs_shape = _main_obs_shape(share_obs_shape)62            self.share_obs = np.zeros(63                (self.episode_length + 1, self.n_rollout_threads, num_agents, *share_obs_shape),64                dtype=np.float32,65            )66        if isinstance(obs_shape_dict, dict):67            self.obs = {68                k: np.zeros((self.episode_length + 1, self.n_rollout_threads, num_agents, *_normalize_shape(v)),69                            dtype=np.float32)70                for k, v in obs_shape_dict.items()71            }72        else:73            obs_shape = _normalize_shape(obs_shape_dict)74            self.obs = {75                "obs": np.zeros((self.episode_length + 1, self.n_rollout_threads, num_agents, *obs_shape),76                                dtype=np.float32)77            }7879        self.rnn_states = np.zeros(80            (self.episode_length + 1, self.n_rollout_threads, num_agents, self.recurrent_N, self.hidden_size),81            dtype=np.float32)82        self.rnn_states_critic = np.zeros_like(self.rnn_states)8384        self.value_preds = np.zeros(85            (self.episode_length + 1, self.n_rollout_threads, num_agents, 1), dtype=np.float32)86        self.returns = np.zeros_like(self.value_preds)8788        # 为啥只有 Discrete 才有 available_actions ?给你注释了89        # if act_space.__class__.__name__ == 'Discrete':90        self.available_actions = np.ones((self.episode_length + 1, self.n_rollout_threads, num_agents, act_space.n),91                                            dtype=np.float32)92        # else:93        #     self.available_actions = None9495        act_shape = get_shape_from_act_space(act_space)9697        self.actions = np.zeros(98            (self.episode_length, self.n_rollout_threads, num_agents, act_shape), dtype=np.float32)99        self.action_log_probs = np.zeros(100            (self.episode_length, self.n_rollout_threads, num_agents, act_shape), dtype=np.float32)101        # hierarchical reconfiguration gate 的 z 动作必须像 task action 一样存入102        # rollout buffer;PPO update 会用它重放 joint logprob。103        self.gate_actions = np.zeros(104            (self.episode_length, self.n_rollout_threads, num_agents, 1), dtype=np.float32)105        self.rewards = np.zeros(106            (self.episode_length, self.n_rollout_threads, num_agents, 1), dtype=np.float32)107108        self.masks = np.ones((self.episode_length + 1, self.n_rollout_threads, num_agents, 1), dtype=np.float32)109        self.bad_masks = np.ones_like(self.masks)110        self.active_masks = np.ones_like(self.masks)111112        self.step = 0113114    def insert(self, share_obs, obs, rnn_states_actor, rnn_states_critic, actions, action_log_probs,115               gate_actions,116               value_preds, rewards, masks, bad_masks=None, active_masks=None, available_actions=None):117        """118        Insert data into the buffer.119        :param share_obs: (argparse.Namespace) arguments containing relevant model, policy, and env information.120        :param obs: (np.ndarray) local agent observations.121        :param rnn_states_actor: (np.ndarray) RNN states for actor network.122        :param rnn_states_critic: (np.ndarray) RNN states for critic network.123        :param actions:(np.ndarray) actions taken by agents.124        :param action_log_probs:(np.ndarray) log probs of actions taken by agents125        :param value_preds: (np.ndarray) value function prediction at each step.126        :param rewards: (np.ndarray) reward collected at each step.127        :param masks: (np.ndarray) denotes whether the environment has terminated or not.128        :param bad_masks: (np.ndarray) action space for agents.129        :param active_masks: (np.ndarray) denotes whether an agent is active or dead in the env.130        :param available_actions: (np.ndarray) actions available to each agent. If None, all actions are available.131        """132        if isinstance(self.share_obs, dict):133            for k in self.share_obs:134                self.share_obs[k][self.step + 1] = share_obs[k].copy()135        else:136            self.share_obs[self.step + 1] = share_obs.copy()137        for k in self.obs:138            self.obs[k][self.step + 1] = obs[k].copy()139        self.rnn_states[self.step + 1] = rnn_states_actor.copy()140        self.rnn_states_critic[self.step + 1] = rnn_states_critic.copy()141        self.actions[self.step] = actions.copy()142        self.action_log_probs[self.step] = action_log_probs.copy()143        # 非 hierarchical 运行写入零占位,保证 generator 的 sample tuple 不分叉。144        self.gate_actions[self.step] = gate_actions.copy()145        self.value_preds[self.step] = value_preds.copy()146        self.rewards[self.step] = rewards.copy()147        self.masks[self.step + 1] = masks.copy()148        if bad_masks is not None:149            self.bad_masks[self.step + 1] = bad_masks.copy()150        if active_masks is not None:151            self.active_masks[self.step + 1] = active_masks.copy()152        if available_actions is not None:153            self.available_actions[self.step + 1] = available_actions.copy()154155        self.step = (self.step + 1) % self.episode_length156157    def chooseinsert(self, share_obs, obs, rnn_states, rnn_states_critic, actions, action_log_probs,158                     value_preds, rewards, masks, bad_masks=None, active_masks=None, available_actions=None):159        """160        Insert data into the buffer. This insert function is used specifically for Hanabi, which is turn based.161        :param share_obs: (argparse.Namespace) arguments containing relevant model, policy, and env information.162        :param obs: (np.ndarray) local agent observations.163        :param rnn_states_actor: (np.ndarray) RNN states for actor network.164        :param rnn_states_critic: (np.ndarray) RNN states for critic network.165        :param actions:(np.ndarray) actions taken by agents.166        :param action_log_probs:(np.ndarray) log probs of actions taken by agents167        :param value_preds: (np.ndarray) value function prediction at each step.168        :param rewards: (np.ndarray) reward collected at each step.169        :param masks: (np.ndarray) denotes whether the environment has terminated or not.170        :param bad_masks: (np.ndarray) denotes indicate whether whether true terminal state or due to episode limit171        :param active_masks: (np.ndarray) denotes whether an agent is active or dead in the env.172        :param available_actions: (np.ndarray) actions available to each agent. If None, all actions are available.173        """174        if isinstance(self.share_obs, dict):175            for k in self.share_obs:176                self.share_obs[k][self.step] = share_obs[k].copy()177        else:178            self.share_obs[self.step] = share_obs.copy()179        for k in self.obs:180            self.obs[k][self.step] = obs[k].copy()181        self.rnn_states[self.step + 1] = rnn_states.copy()182        self.rnn_states_critic[self.step + 1] = rnn_states_critic.copy()183        self.actions[self.step] = actions.copy()184        self.action_log_probs[self.step] = action_log_probs.copy()185        self.value_preds[self.step] = value_preds.copy()186        self.rewards[self.step] = rewards.copy()187        self.masks[self.step + 1] = masks.copy()188        if bad_masks is not None:189            self.bad_masks[self.step + 1] = bad_masks.copy()190        if active_masks is not None:191            self.active_masks[self.step] = active_masks.copy()192        if available_actions is not None:193            self.available_actions[self.step] = available_actions.copy()194195        self.step = (self.step + 1) % self.episode_length196197    def after_update(self):198        """Copy last timestep data to first index. Called after update to model."""199        if isinstance(self.share_obs, dict):200            for k in self.share_obs:201                self.share_obs[k][0] = self.share_obs[k][-1].copy()202        else:203            self.share_obs[0] = self.share_obs[-1].copy()204        for k in self.obs:205            self.obs[k][0] = self.obs[k][-1].copy()206        self.rnn_states[0] = self.rnn_states[-1].copy()207        self.rnn_states_critic[0] = self.rnn_states_critic[-1].copy()208        self.masks[0] = self.masks[-1].copy()209        self.bad_masks[0] = self.bad_masks[-1].copy()210        self.active_masks[0] = self.active_masks[-1].copy()211        if self.available_actions is not None:212            self.available_actions[0] = self.available_actions[-1].copy()213214    def chooseafter_update(self):215        """Copy last timestep data to first index. This method is used for Hanabi."""216        self.rnn_states[0] = self.rnn_states[-1].copy()217        self.rnn_states_critic[0] = self.rnn_states_critic[-1].copy()218        self.masks[0] = self.masks[-1].copy()219        self.bad_masks[0] = self.bad_masks[-1].copy()220221    def compute_returns(self, next_value, value_normalizer=None):222        """223        Compute returns either as discounted sum of rewards, or using GAE.224        :param next_value: (np.ndarray) value predictions for the step after the last episode step.225        :param value_normalizer: (PopArt) If not None, PopArt value normalizer instance.226        """227        if self._use_proper_time_limits:228            if self._use_gae:229                self.value_preds[-1] = next_value230                gae = 0231                for step in reversed(range(self.rewards.shape[0])):232                    if self._use_popart or self._use_valuenorm:233                        # step + 1234                        delta = self.rewards[step] + self.gamma * value_normalizer.denormalize(235                            self.value_preds[step + 1]) * self.masks[step + 1] \236                                - value_normalizer.denormalize(self.value_preds[step])237                        gae = delta + self.gamma * self.gae_lambda * gae * self.masks[step + 1]238                        gae = gae * self.bad_masks[step + 1]239                        self.returns[step] = gae + value_normalizer.denormalize(self.value_preds[step])240                    else:241                        delta = self.rewards[step] + self.gamma * self.value_preds[step + 1] * self.masks[step + 1] - \242                                self.value_preds[step]243                        gae = delta + self.gamma * self.gae_lambda * self.masks[step + 1] * gae244                        gae = gae * self.bad_masks[step + 1]245                        self.returns[step] = gae + self.value_preds[step]246            else:247                self.returns[-1] = next_value248                for step in reversed(range(self.rewards.shape[0])):249                    if self._use_popart or self._use_valuenorm:250                        self.returns[step] = (self.returns[step + 1] * self.gamma * self.masks[step + 1] + self.rewards[251                            step]) * self.bad_masks[step + 1] \252                                             + (1 - self.bad_masks[step + 1]) * value_normalizer.denormalize(253                            self.value_preds[step])254                    else:255                        self.returns[step] = (self.returns[step + 1] * self.gamma * self.masks[step + 1] + self.rewards[256                            step]) * self.bad_masks[step + 1] \257                                             + (1 - self.bad_masks[step + 1]) * self.value_preds[step]258        else:259            if self._use_gae:260                self.value_preds[-1] = next_value261                gae = 0262                for step in reversed(range(self.rewards.shape[0])):263                    if self._use_popart or self._use_valuenorm:264                        delta = self.rewards[step] + self.gamma * value_normalizer.denormalize(265                            self.value_preds[step + 1]) * self.masks[step + 1] \266                                - value_normalizer.denormalize(self.value_preds[step])267                        gae = delta + self.gamma * self.gae_lambda * self.masks[step + 1] * gae268                        self.returns[step] = gae + value_normalizer.denormalize(self.value_preds[step])269                    else:270                        delta = self.rewards[step] + self.gamma * self.value_preds[step + 1] * self.masks[step + 1] - \271                                self.value_preds[step]272                        gae = delta + self.gamma * self.gae_lambda * self.masks[step + 1] * gae273                        self.returns[step] = gae + self.value_preds[step]274            else:275                self.returns[-1] = next_value276                for step in reversed(range(self.rewards.shape[0])):277                    self.returns[step] = self.returns[step + 1] * self.gamma * self.masks[step + 1] + self.rewards[step]278279    def feed_forward_generator(self, advantages, num_mini_batch=None, mini_batch_size=None):280        """281        Yield training data for MLP policies.282        :param advantages: (np.ndarray) advantage estimates.283        :param num_mini_batch: (int) number of minibatches to split the batch into.284        :param mini_batch_size: (int) number of samples in each minibatch.285        """286        episode_length, n_rollout_threads, num_agents = self.rewards.shape[0:3]287        batch_size = n_rollout_threads * episode_length # * num_agents288289        if mini_batch_size is None:290            assert batch_size >= num_mini_batch, (291                "PPO requires the number of processes ({}) "292                "* number of steps ({}) * number of agents ({}) = {} "293                "to be greater than or equal to the number of PPO mini batches ({})."294                "".format(n_rollout_threads, episode_length, num_agents,295                          n_rollout_threads * episode_length * num_agents,296                          num_mini_batch))297            mini_batch_size = batch_size // num_mini_batch298299        rand = torch.randperm(batch_size).numpy()300        sampler = [rand[i * mini_batch_size:(i + 1) * mini_batch_size] for i in range(num_mini_batch)]301302        if isinstance(self.share_obs, dict):303            share_obs = {304                k: v[:-1].reshape(-1, *v.shape[2:])305                for k, v in self.share_obs.items()306            }307        else:308            share_obs = self.share_obs[:-1].reshape(-1, *self.share_obs.shape[2:])309        obs = {310            k: v[:-1].reshape(-1, *v.shape[2:])311            for k, v in self.obs.items()312        }313        rnn_states = self.rnn_states[:-1].reshape(-1, *self.rnn_states.shape[2:])314        rnn_states_critic = self.rnn_states_critic[:-1].reshape(-1, *self.rnn_states_critic.shape[2:])315        actions = self.actions.reshape(-1, *self.actions.shape[2:])316        if self.available_actions is not None:317            available_actions = self.available_actions[:-1].reshape(-1, *self.available_actions.shape[2:])318        value_preds = self.value_preds[:-1].reshape(-1, *self.value_preds.shape[2:])319        returns = self.returns[:-1].reshape(-1, *self.returns.shape[2:])320        masks = self.masks[:-1].reshape(-1, *self.masks.shape[2:])321        active_masks = self.active_masks[:-1].reshape(-1, *self.active_masks.shape[2:])322        action_log_probs = self.action_log_probs.reshape(-1, *self.action_log_probs.shape[2:])323        gate_actions = self.gate_actions.reshape(-1, *self.gate_actions.shape[2:])324        advantages = advantages.reshape(-1, *advantages.shape[2:])325326        for indices in sampler:327            # obs size [T+1 N M Dim]-->[T N M Dim]-->[T*N*M,Dim]-->[index,Dim]328            share_obs_batch = (329                {k: v[indices] for k, v in share_obs.items()}330                if isinstance(share_obs, dict)331                else share_obs[indices]332            )333            obs_batch = {k: v[indices] for k, v in obs.items()}334335            rnn_states_batch = rnn_states[indices].reshape(-1, rnn_states.shape[-1])336            rnn_states_critic_batch = rnn_states_critic[indices].reshape(-1, rnn_states_critic.shape[-1])337            actions_batch = actions[indices].reshape(-1, actions.shape[-1])338            if self.available_actions is not None:339                available_actions_batch = available_actions[indices].reshape(-1, available_actions.shape[-1])340            else:341                available_actions_batch = None342            value_preds_batch = value_preds[indices].reshape(-1, value_preds.shape[-1])343            return_batch = returns[indices].reshape(-1, returns.shape[-1])344            masks_batch = masks[indices].reshape(-1, masks.shape[-1])345            active_masks_batch = active_masks[indices].reshape(-1, active_masks.shape[-1])346            old_action_log_probs_batch = action_log_probs[indices].reshape(-1, action_log_probs.shape[-1])347            gate_actions_batch = gate_actions[indices].reshape(-1, gate_actions.shape[-1])348            if advantages is None:349                adv_targ = None350            else:351                adv_targ = advantages[indices].reshape(-1, advantages.shape[-1])352353            yield share_obs_batch, obs_batch, rnn_states_batch, rnn_states_critic_batch, actions_batch,\354                  value_preds_batch, return_batch, masks_batch, active_masks_batch, old_action_log_probs_batch,\355                  adv_targ, available_actions_batch, gate_actions_batch356357    def naive_recurrent_generator(self, advantages, num_mini_batch):358        """359        Yield training data for non-chunked RNN training.360        :param advantages: (np.ndarray) advantage estimates.361        :param num_mini_batch: (int) number of minibatches to split the batch into.362        """363        episode_length, n_rollout_threads, num_agents = self.rewards.shape[0:3]364        batch_size = n_rollout_threads * num_agents365        assert n_rollout_threads * num_agents >= num_mini_batch, (366            "PPO requires the number of processes ({})* number of agents ({}) "367            "to be greater than or equal to the number of "368            "PPO mini batches ({}).".format(n_rollout_threads, num_agents, num_mini_batch))369        num_envs_per_batch = batch_size // num_mini_batch370        perm = torch.randperm(batch_size).numpy()371372        if isinstance(self.share_obs, dict):373            share_obs = {374                k: v.reshape(-1, batch_size, *v.shape[3:])375                for k, v in self.share_obs.items()376            }377        else:378            share_obs = self.share_obs.reshape(-1, batch_size, *self.share_obs.shape[3:])379        obs = {380            k: v.reshape(-1, batch_size, *v.shape[3:])381            for k, v in self.obs.items()382        }383        rnn_states = self.rnn_states.reshape(-1, batch_size, *self.rnn_states.shape[3:])384        rnn_states_critic = self.rnn_states_critic.reshape(-1, batch_size, *self.rnn_states_critic.shape[3:])385        actions = self.actions.reshape(-1, batch_size, self.actions.shape[-1])386        if self.available_actions is not None:387            available_actions = self.available_actions.reshape(-1, batch_size, self.available_actions.shape[-1])388        value_preds = self.value_preds.reshape(-1, batch_size, 1)389        returns = self.returns.reshape(-1, batch_size, 1)390        masks = self.masks.reshape(-1, batch_size, 1)391        active_masks = self.active_masks.reshape(-1, batch_size, 1)392        action_log_probs = self.action_log_probs.reshape(-1, batch_size, self.action_log_probs.shape[-1])393        gate_actions = self.gate_actions.reshape(-1, batch_size, 1)394        advantages = advantages.reshape(-1, batch_size, 1)395396        for start_ind in range(0, batch_size, num_envs_per_batch):397            share_obs_batch = {k: [] for k in self.share_obs} if isinstance(self.share_obs, dict) else []398            obs_batch = {k: [] for k in self.obs}399            rnn_states_batch = []400            rnn_states_critic_batch = []401            actions_batch = []402            available_actions_batch = []403            value_preds_batch = []404            return_batch = []405            masks_batch = []406            active_masks_batch = []407            old_action_log_probs_batch = []408            gate_actions_batch = []409            adv_targ = []410411            for offset in range(num_envs_per_batch):412                ind = perm[start_ind + offset]413                if isinstance(self.share_obs, dict):414                    for k in share_obs_batch:415                        share_obs_batch[k].append(share_obs[k][:-1, ind])416                else:417                    share_obs_batch.append(share_obs[:-1, ind])418                for k in obs_batch:419                    obs_batch[k].append(obs[k][:-1, ind])420                rnn_states_batch.append(rnn_states[0:1, ind])421                rnn_states_critic_batch.append(rnn_states_critic[0:1, ind])422                actions_batch.append(actions[:, ind])423                if self.available_actions is not None:424                    available_actions_batch.append(available_actions[:-1, ind])425                value_preds_batch.append(value_preds[:-1, ind])426                return_batch.append(returns[:-1, ind])427                masks_batch.append(masks[:-1, ind])428                active_masks_batch.append(active_masks[:-1, ind])429                old_action_log_probs_batch.append(action_log_probs[:, ind])430                gate_actions_batch.append(gate_actions[:, ind])431                adv_targ.append(advantages[:, ind])432433            # [N[T, dim]]434            T, N = self.episode_length, num_envs_per_batch435            # These are all from_numpys of size (T, N, -1)436            if isinstance(share_obs_batch, dict):437                share_obs_batch = {k: np.stack(v, 1) for k, v in share_obs_batch.items()}438            else:439                share_obs_batch = np.stack(share_obs_batch, 1)440            obs_batch = {k: np.stack(v, 1) for k, v in obs_batch.items()}441            actions_batch = np.stack(actions_batch, 1)442            if self.available_actions is not None:443                available_actions_batch = np.stack(available_actions_batch, 1)444            value_preds_batch = np.stack(value_preds_batch, 1)445            return_batch = np.stack(return_batch, 1)446            masks_batch = np.stack(masks_batch, 1)447            active_masks_batch = np.stack(active_masks_batch, 1)448            old_action_log_probs_batch = np.stack(old_action_log_probs_batch, 1)449            gate_actions_batch = np.stack(gate_actions_batch, 1)450            adv_targ = np.stack(adv_targ, 1)451452            # States is just a (N, dim) from_numpy [N[1,dim]]453            rnn_states_batch = np.stack(rnn_states_batch).reshape(N, *self.rnn_states.shape[3:])454            rnn_states_critic_batch = np.stack(rnn_states_critic_batch).reshape(N, *self.rnn_states_critic.shape[3:])455456            # Flatten the (T, N, ...) from_numpys to (T * N, ...)457            if isinstance(share_obs_batch, dict):458                share_obs_batch = {k: _flatten(T, N, v) for k, v in share_obs_batch.items()}459            else:460                share_obs_batch = _flatten(T, N, share_obs_batch)461            obs_batch = {k: _flatten(T, N, v) for k, v in obs_batch.items()}462            actions_batch = _flatten(T, N, actions_batch)463            if self.available_actions is not None:464                available_actions_batch = _flatten(T, N, available_actions_batch)465            else:466                available_actions_batch = None467            value_preds_batch = _flatten(T, N, value_preds_batch)468            return_batch = _flatten(T, N, return_batch)469            masks_batch = _flatten(T, N, masks_batch)470            active_masks_batch = _flatten(T, N, active_masks_batch)471            old_action_log_probs_batch = _flatten(T, N, old_action_log_probs_batch)472            gate_actions_batch = _flatten(T, N, gate_actions_batch)473            adv_targ = _flatten(T, N, adv_targ)474475            yield share_obs_batch, obs_batch, rnn_states_batch, rnn_states_critic_batch, actions_batch,\476                  value_preds_batch, return_batch, masks_batch, active_masks_batch, old_action_log_probs_batch,\477                  adv_targ, available_actions_batch, gate_actions_batch478479    def recurrent_generator(self, advantages, num_mini_batch, data_chunk_length):480        """481        Yield training data for chunked RNN training.482        :param advantages: (np.ndarray) advantage estimates.483        :param num_mini_batch: (int) number of minibatches to split the batch into.484        :param data_chunk_length: (int) length of sequence chunks with which to train RNN.485        """486        episode_length, n_rollout_threads, num_agents = self.rewards.shape[0:3]487        chunks_per_env = episode_length // data_chunk_length488        data_chunks = n_rollout_threads * chunks_per_env489        mini_batch_size = data_chunks // num_mini_batch490491        assert mini_batch_size > 0, (492            "PPO requires rollout_threads ({}) * chunks_per_env ({}) to be "493            "greater than or equal to num_mini_batch ({}).".format(494                n_rollout_threads, chunks_per_env, num_mini_batch495            )496        )497498        rand = torch.randperm(data_chunks).numpy()499        sampler = [rand[i * mini_batch_size:(i + 1) * mini_batch_size] for i in range(num_mini_batch)]500501        for indices in sampler:502            share_obs_batch = {k: [] for k in self.share_obs} if isinstance(self.share_obs, dict) else []503            obs_batch = {k: [] for k in self.obs}504            rnn_states_batch = []505            rnn_states_critic_batch = []506            actions_batch = []507            available_actions_batch = []508            value_preds_batch = []509            return_batch = []510            masks_batch = []511            active_masks_batch = []512            old_action_log_probs_batch = []513            gate_actions_batch = []514            adv_targ = []515516            for index in indices:517                env_ind = index // chunks_per_env518                chunk_ind = index % chunks_per_env519                start = chunk_ind * data_chunk_length520                end = start + data_chunk_length521522                if isinstance(self.share_obs, dict):523                    for k in share_obs_batch:524                        share_obs_batch[k].append(self.share_obs[k][start:end, env_ind])525                else:526                    share_obs_batch.append(self.share_obs[start:end, env_ind])527                for k in obs_batch:528                    obs_batch[k].append(self.obs[k][start:end, env_ind])529                actions_batch.append(self.actions[start:end, env_ind])530                if self.available_actions is not None:531                    available_actions_batch.append(self.available_actions[start:end, env_ind])532                value_preds_batch.append(self.value_preds[start:end, env_ind])533                return_batch.append(self.returns[start:end, env_ind])534                masks_batch.append(self.masks[start:end, env_ind])535                active_masks_batch.append(self.active_masks[start:end, env_ind])536                old_action_log_probs_batch.append(self.action_log_probs[start:end, env_ind])537                gate_actions_batch.append(self.gate_actions[start:end, env_ind])538                adv_targ.append(advantages[start:end, env_ind])539                rnn_states_batch.append(self.rnn_states[start, env_ind])540                rnn_states_critic_batch.append(self.rnn_states_critic[start, env_ind])541542            L, N = data_chunk_length, mini_batch_size543544            # Keep the agent axis grouped for graph encoders: [L, N, A, ...].545            if isinstance(share_obs_batch, dict):546                share_obs_batch = {k: np.stack(v, axis=1) for k, v in share_obs_batch.items()}547            else:548                share_obs_batch = np.stack(share_obs_batch, axis=1)549            obs_batch = {k: np.stack(v, axis=1) for k, v in obs_batch.items()}550551            actions_batch = np.stack(actions_batch, axis=1)552            if self.available_actions is not None:553                available_actions_batch = np.stack(available_actions_batch, axis=1)554            value_preds_batch = np.stack(value_preds_batch, axis=1)555            return_batch = np.stack(return_batch, axis=1)556            masks_batch = np.stack(masks_batch, axis=1)557            active_masks_batch = np.stack(active_masks_batch, axis=1)558            old_action_log_probs_batch = np.stack(old_action_log_probs_batch, axis=1)559            gate_actions_batch = np.stack(gate_actions_batch, axis=1)560            adv_targ = np.stack(adv_targ, axis=1)561562            # One initial RNN state per sampled sequence and per agent.563            rnn_states_batch = np.stack(rnn_states_batch).reshape(N * num_agents, *self.rnn_states.shape[3:])564            rnn_states_critic_batch = np.stack(rnn_states_critic_batch).reshape(565                N * num_agents, *self.rnn_states_critic.shape[3:]566            )567568            # obs/share_obs stay grouped as [L * N, A, ...]; PPO targets are flat [L * N * A, ...].569            if isinstance(share_obs_batch, dict):570                share_obs_batch = {k: _flatten(L, N, v) for k, v in share_obs_batch.items()}571            else:572                share_obs_batch = _flatten(L, N, share_obs_batch)573            obs_batch = {k: _flatten(L, N, v) for k, v in obs_batch.items()}574            actions_batch = _flatten(L, N, actions_batch).reshape(-1, actions_batch.shape[-1])575            if self.available_actions is not None:576                available_actions_batch = _flatten(L, N, available_actions_batch).reshape(577                    -1, available_actions_batch.shape[-1]578                )579            else:580                available_actions_batch = None581            value_preds_batch = _flatten(L, N, value_preds_batch).reshape(-1, value_preds_batch.shape[-1])582            return_batch = _flatten(L, N, return_batch).reshape(-1, return_batch.shape[-1])583            masks_batch = _flatten(L, N, masks_batch).reshape(-1, masks_batch.shape[-1])584            active_masks_batch = _flatten(L, N, active_masks_batch).reshape(-1, active_masks_batch.shape[-1])585            old_action_log_probs_batch = _flatten(L, N, old_action_log_probs_batch).reshape(586                -1, old_action_log_probs_batch.shape[-1]587            )588            gate_actions_batch = _flatten(L, N, gate_actions_batch).reshape(-1, 1)589            adv_targ = _flatten(L, N, adv_targ).reshape(-1, adv_targ.shape[-1])590591            yield share_obs_batch, obs_batch, rnn_states_batch, rnn_states_critic_batch, actions_batch,\592                  value_preds_batch, return_batch, masks_batch, active_masks_batch, old_action_log_probs_batch,\593                  adv_targ, available_actions_batch, gate_actions_batch