返回代码索引

worker.py

CFM 上游 rollout 与 success_rate 定义

原文件 · 224 行 · 11392 字节

third_party/heteromrta/worker.py
SHA-256: b1a285190d25a191a88634a5e177245f252cf5679095523173139daef2499dc3

1import pickle2import time3import torch4import numpy as np5import random6from env.task_env import TaskEnv7from attention import AttentionNet8import scipy.signal as signal9from parameters import *10import copy11from torch.nn import functional as F12from torch.distributions import Categorical131415def discount(x, gamma):16    """从后向前计算折扣累计值。"""17    return signal.lfilter([1], [1, -gamma], x[::-1], axis=0)[::-1]181920def zero_padding(x, padding_size, length):21    pad = torch.nn.ZeroPad2d((0, 0, 0, padding_size - length))22    x = pad(x)23    return x242526class Worker:27    """运行一个 CFM 环境副本并收集任务选择记录。"""28    def __init__(self, mete_agent_id, local_network, local_baseline, global_step,29                 device='cuda', save_image=False, seed=None, env_params=None):3031        self.device = device32        self.metaAgentID = mete_agent_id33        self.global_step = global_step34        self.save_image = save_image35        if env_params is None:36            env_params = [EnvParams.SPECIES_AGENTS_RANGE, EnvParams.SPECIES_RANGE, EnvParams.TASKS_RANGE]37        self.env = TaskEnv(*env_params, EnvParams.TRAIT_DIM, EnvParams.DECISION_DIM, seed=seed, plot_figure=save_image)38        self.baseline_env = copy.deepcopy(self.env)39        self.local_baseline = local_baseline40        self.local_net = local_network41        self.experience = {idx:[] for idx in range(7)}42        self.episode_number = None43        self.perf_metrics = {}44        self.p_rnn_state = {}45        self.max_time = EnvParams.MAX_TIME46        # 默认最多记录 300 次有效选择;短测试可以通过实例字段调低上限。47        self.max_decisions = 30048        self.max_decision_steps = None4950    def run_episode(self, training=True, sample=False, max_waiting=False):51        """按环境给出的决策时间依次处理可行动的智能体。"""52        buffer_dict = {idx:[] for idx in range(7)}53        perf_metrics = {}54        current_action_index = 055        decision_step = 056        while (57            not self.env.finished58            and self.env.current_time < EnvParams.MAX_TIME59            and current_action_index < self.max_decisions60            and (61                self.max_decision_steps is None62                or decision_step < self.max_decision_steps63            )64        ):65            # next_decision 跳到下一个有智能体需要重新分配的时间点。66            with torch.no_grad():67                release_agents, current_time = self.env.next_decision()68                self.env.current_time = current_time69                random.shuffle(release_agents[0])70                finished_task = []71                while release_agents[0] or release_agents[1]:72                    # 同一时刻可能有多个智能体需要选择任务,逐个调用网络并更新环境。73                    agent_id = release_agents[0].pop(0) if release_agents[0] else release_agents[1].pop(0)74                    agent = self.env.agent_dic[agent_id]75                    task_info, total_agents, mask = self.convert_torch(self.env.agent_observe(agent_id, max_waiting))76                    block_flag = mask[0, 1:].all().item()77                    # 没有可行任务时跳过本次选择,环境继续推进时间。78                    if block_flag and not np.all(self.env.get_matrix(self.env.task_dic, 'feasible_assignment')):79                        agent['no_choice'] = block_flag80                        continue81                    elif block_flag and np.all(self.env.get_matrix(self.env.task_dic, 'feasible_assignment')) and agent['current_task'] < 0:82                        continue83                    if training:84                        task_info, total_agents, mask = self.obs_padding(task_info, total_agents, mask)85                    index = torch.LongTensor([agent_id]).reshape(1, 1, 1).to(self.device)86                    probs, _ = self.local_net(task_info, total_agents, mask, index)87                    if training:88                        action = Categorical(probs).sample()89                        while action.item() > self.env.tasks_num:90                            action = Categorical(probs).sample()91                    else:92                        if sample:93                            action = Categorical(probs).sample()94                        else:95                            action = torch.argmax(probs, dim=1)96                    r, doable, f_t = self.env.agent_step(agent_id, action.item(), decision_step)97                    agent['current_action_index'] = current_action_index98                    finished_task.append(f_t)99                    if training and doable:100                        # 只记录环境接受的动作,稍后统一填入整条 rollout 的回报差值。101                        buffer_dict[0] += total_agents102                        buffer_dict[1] += task_info103                        buffer_dict[2] += action.unsqueeze(0)104                        buffer_dict[3] += mask105                        buffer_dict[4] += torch.FloatTensor([[0]]).to(self.device)  # reward106                        buffer_dict[5] += index107                        buffer_dict[6] += torch.FloatTensor([[0]]).to(self.device)108                        current_action_index += 1109                self.env.finished = self.env.check_finished()110                decision_step += 1111112        terminal_reward, finished_tasks = self.env.get_episode_reward(self.max_time)113114        # 指标在 episode 结束后统一从环境状态中读取。115        perf_metrics['success_rate'] = [np.sum(finished_tasks)/len(finished_tasks)]116        perf_metrics['makespan'] = [self.env.current_time]117        perf_metrics['time_cost'] = [np.nanmean(self.env.get_matrix(self.env.task_dic, 'time_start'))]118        perf_metrics['waiting_time'] = [np.mean(self.env.get_matrix(self.env.agent_dic, 'sum_waiting_time'))]119        perf_metrics['travel_dist'] = [np.sum(self.env.get_matrix(self.env.agent_dic, 'travel_dist'))]120        perf_metrics['efficiency'] = [self.env.get_efficiency()]121        return terminal_reward, buffer_dict, perf_metrics122123    def baseline_test(self):124        """在相同初始场景上用 baseline 网络贪心选择任务。"""125        self.baseline_env.plot_figure = False126        perf_metrics = {}127        current_action_index = 0128        start = time.time()129        while not self.baseline_env.finished and self.baseline_env.current_time < self.max_time and current_action_index < 300:130            with torch.no_grad():131                release_agents, current_time = self.baseline_env.next_decision()132                random.shuffle(release_agents[0])133                self.baseline_env.current_time = current_time134                if time.time() - start > 30:135                    break136                while release_agents[0] or release_agents[1]:137                    agent_id = release_agents[0].pop(0) if release_agents[0] else release_agents[1].pop(0)138                    agent = self.baseline_env.agent_dic[agent_id]139                    task_info, total_agents, mask = self.convert_torch(self.baseline_env.agent_observe(agent_id, False))140                    return_flag = mask[0, 1:].all().item()141                    if return_flag and not np.all(self.baseline_env.get_matrix(self.baseline_env.task_dic, 'feasible_assignment')): ## add condition on returning to depot142                        self.baseline_env.agent_dic[agent_id]['no_choice'] = return_flag143                        continue144                    elif return_flag and np.all(self.baseline_env.get_matrix(self.baseline_env.task_dic, 'feasible_assignment')) and agent['current_task'] < 0:145                        continue146                    task_info, total_agents, mask = self.obs_padding(task_info, total_agents, mask)147                    index = torch.LongTensor([agent_id]).reshape(1, 1, 1).to(self.device)148                    probs, _ = self.local_baseline(task_info, total_agents, mask, index)149                    action = torch.argmax(probs, 1)150                    self.baseline_env.agent_step(agent_id, action.item(), None)151                    current_action_index += 1152                self.baseline_env.finished = self.baseline_env.check_finished()153154        reward, finished_tasks = self.baseline_env.get_episode_reward(self.max_time)155        return reward156157    def work(self, episode_number):158        """在同一设置下运行多份样本,并用组内平均回报作为 baseline。"""159        baseline_rewards = []160        buffers = []161        metrics = []162        max_waiting = TrainParams.FORCE_MAX_OPEN_TASK163        for _ in range(TrainParams.POMO_SIZE):164            # 多次重置到同一分布并采样动作,得到一组可相互比较的回报。165            self.env.init_state()166            terminal_reward, buffer, perf_metrics = self.run_episode(episode_number,True, max_waiting)167            if terminal_reward is np.nan:168                max_waiting = True169                continue170            baseline_rewards.append(terminal_reward)171            buffers.append(buffer)172            metrics.append(perf_metrics)173        baseline_reward = np.nanmean(baseline_rewards)174175        for idx, buffer in enumerate(buffers):176            # 第 6 项保存当前样本回报减去组内平均回报,训练时作为权重使用。177            for key in buffer.keys():178                if key == 6:179                    for i in range(len(buffer[key])):180                        buffer[key][i] += baseline_rewards[idx] - baseline_reward181                if key not in self.experience.keys():182                    self.experience[key] = buffer[key]183                else:184                    self.experience[key] += buffer[key]185186        for metric in metrics:187            for key in metric.keys():188                if key not in self.perf_metrics.keys():189                    self.perf_metrics[key] = metric[key]190                else:191                    self.perf_metrics[key] += metric[key]192193        if self.save_image:194            try:195                self.env.plot_animation(SaverParams.GIFS_PATH, episode_number)196            except:197                pass198        self.episode_number = episode_number199200    def convert_torch(self, args):201        data = []202        for arg in args:203            data.append(torch.tensor(arg, dtype=torch.float).to(self.device))204        return data205206    @staticmethod207    def obs_padding(task_info, agents, mask):208        """将任务和智能体数量补到网络初始化时使用的固定上限。"""209        task_info = F.pad(task_info, (0, 0, 0, EnvParams.TASKS_RANGE[1] + 1 - task_info.shape[1]), 'constant', 0)210        agents = F.pad(agents, (0, 0, 0, EnvParams.SPECIES_AGENTS_RANGE[1] * EnvParams.SPECIES_RANGE[1] - agents.shape[1]), 'constant', 0)211        mask = F.pad(mask, (0, EnvParams.TASKS_RANGE[1] + 1 - mask.shape[1]), 'constant', 1)212        return task_info, agents, mask213214215if __name__ == '__main__':216    device = torch.device('cuda')217    # torch.manual_seed(9)218    # checkpoint = torch.load(SaverParams.MODEL_PATH + '/checkpoint.pth')219    localNetwork = AttentionNet(TrainParams.AGENT_INPUT_DIM, TrainParams.TASK_INPUT_DIM, TrainParams.EMBEDDING_DIM).to(device)220    # localNetwork.load_state_dict(checkpoint['best_model'])221    for i in range(100):222        worker = Worker(1, localNetwork, localNetwork, 0, device=device, seed=i, save_image=False)223        worker.work(i)224        print(i)