├── GA_exp.py ├── LICENSE ├── README.md ├── arguments.py ├── critic_train_exp.py ├── layouts.py ├── multi_scales_test.py ├── off_loading_models.py └── pretrain.py /GA_exp.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import random 3 | from tqdm import tqdm 4 | from arguments import args 5 | import numpy as np 6 | from layouts import generate_layouts 7 | from torch_geometric.loader import DataLoader 8 | from torch_geometric.utils import softmax 9 | from time import time 10 | 11 | 12 | 13 | class Evolution(): 14 | def __init__(self, N, M, pop_num, total_epochs, compute_resource, path_losses, task_size, edge_index) -> None: 15 | 16 | self.N = N # user数量 17 | self.M = M # server数量 18 | self.pop_num = pop_num # 初始种群规模 19 | self.retain_rate = 0.4 # 保存率 20 | self.mutate_rate = 0.2 21 | self.random_select_rate = 0.1 22 | # self.locations = locations 23 | self.total_epoch = total_epochs 24 | self.b = 2 25 | 26 | 27 | self.compute_resource = compute_resource 28 | self.path_losses = path_losses 29 | self.task_size = task_size 30 | self.edge_index = edge_index 31 | 32 | def evolution(self): 33 | populication = self.populication_create() 34 | loss_list = [] 35 | time_list = [] 36 | for i in range(self.total_epoch): 37 | # print(populication.shape) 38 | start = time() 39 | parents, output_i = self.selection(populication) 40 | cs = self.cross_over(parents) 41 | cs = self.mutation(cs, i) 42 | populication = torch.cat([parents, cs], dim=0) 43 | # output_i = self.adaptbility(populication) 44 | 45 | min_time_loss = torch.min(output_i) # 最好的一个对应的time loss 46 | 47 | loss_list.append(min_time_loss) 48 | # print('epoch == {}, time of best_one == {}'.format(i, min_time_loss)) 49 | end = time() 50 | time_list.append(end-start) 51 | loss_list = torch.stack(loss_list) 52 | time_list = np.array(time_list) 53 | return loss_list, time_list 54 | 55 | 56 | 57 | def cross_over(self, parent): 58 | # 交叉, 单点交叉 59 | ''' 60 | #选择父代 61 | male = [] 62 | female = [] 63 | # 选择基因位并交叉 //单点交叉 64 | ''' 65 | 66 | # 均匀交叉 67 | children = [] 68 | get_child_num = self.pop_num-len(parent) 69 | while len(children) < get_child_num: 70 | i = random.randint(0, len(parent)-1) 71 | j = random.randint(0, len(parent)-1) 72 | male = parent[i] 73 | female = parent[j] 74 | select_p = torch.rand(len(male), device=args.device) 75 | select_p[torch.where(select_p < 0.5)] = 0 76 | select_p[torch.where(select_p >= 0.5)] = 1 77 | child1 = select_p * male + (1-select_p) * female 78 | child2 = (1 - select_p) * male + select_p * female 79 | children.append(child1.reshape(1, len(child1))) 80 | children.append(child2.reshape(1, len(child2))) 81 | if len(children) != 0: 82 | children = torch.cat(children, dim=0) 83 | if get_child_num < len(children): 84 | children = children[:-1] 85 | return children 86 | 87 | def populication_create(self): 88 | # 生成种群 89 | self.populication = torch.rand((self.pop_num, 3*self.M*self.N), device=args.device) 90 | # self.users = torch.tensor(self.features[:2*self.N], device=args.device) 91 | 92 | return self.populication 93 | 94 | def mutation(self, cs, i): 95 | # 变异 96 | 97 | # 采用非一致性变异,每个位置都进行变异 98 | new_cs = cs.clone() 99 | for idx, c in enumerate(cs): 100 | if random.random() < self.mutate_rate: 101 | r = random.random() 102 | mut1 = (1-c)*torch.rand(len(c), device=args.device)*(1-i/self.total_epoch)**self.b 103 | mut2 = torch.rand(len(c), device=args.device)*(1-i/self.total_epoch)**self.b 104 | # print(mut1) 105 | if random.random() > 0.5: 106 | c = c + mut1 107 | else: 108 | c = c - mut2 109 | # print(c) 110 | new_cs[idx] = c 111 | # print(c) 112 | return new_cs 113 | 114 | 115 | def selection(self, populication): 116 | # 选择 117 | 118 | # 选择最佳的rate率的个体 119 | # 对种群从小到大进行排序 120 | adpt = self.adaptbility(populication) 121 | # grabed = [[ad, one] for ad, one in zip(adpt, populication)] 122 | 123 | sort_index = torch.argsort(adpt) 124 | grabed = populication[sort_index] 125 | sorted_adpt = adpt[sort_index] 126 | # sorted_grabed = sorted(grabed, key=lambda x: x[0]) 127 | # grabed = torch.tensor([x[1] for x in sorted_grabed], device=args.device) 128 | index = int(len(populication)*self.retain_rate) 129 | 130 | live = grabed[:index] 131 | 132 | live_adpt = sorted_adpt[:index] 133 | 134 | # 选择幸运个体 135 | for i, ad_i in zip(grabed[index:], adpt[index:]): 136 | if random.random() < self.random_select_rate: 137 | live = torch.cat([live, i.reshape(1, len(i))], dim=0) 138 | live_adpt = torch.cat([live_adpt, ad_i.unsqueeze(0)], dim=0) 139 | # live_adpt = torch.stack([live_adpt, ad_i.unsqueeze(0)]) 140 | 141 | return live, adpt 142 | 143 | def adaptbility(self, populication): 144 | 145 | task_allocation = populication[:, :self.M*self.N] 146 | task_allocation = softmax(task_allocation, index=self.edge_index[0], dim=1) 147 | power_allocation = populication[:, self.M*self.N:2*self.M*self.N] 148 | power_allocation = softmax(power_allocation, index=self.edge_index[0], dim=1) 149 | comp_allocation = populication[:, 2*self.N*self.M:] 150 | comp_allocation = softmax(comp_allocation, index=self.edge_index[1], dim=1) 151 | 152 | 153 | 154 | time_losses = self.compute_loss(task_allocation, power_allocation, comp_allocation) 155 | 156 | # max_dist = [] 157 | # for p in torch.FloatTensor(populication).to(args.device): 158 | # p = p.reshape(int(len(p)/2), 2) 159 | # # p = torch.FloatTensor(p).to(device) 160 | # users_uav = torch.cat([self.users, p], dim=0) 161 | # max_dist.append(self.flow_loss(users_uav).cpu().data.numpy()) 162 | return time_losses.mean(-1) 163 | 164 | def compute_loss(self, task_allocation, power_allocation, comp_allocation): 165 | 166 | # task_size : vector N 167 | # task_allocation: mat pop_num x 3*M*N 168 | # index: vector 3*M*N 169 | 170 | epsilon = 1e-9 171 | extre = 1e-20 172 | user_index = self.edge_index[0] # s2u中源节点的索引 173 | server_index = self.edge_index[1] # s2u中目标节点的索引 174 | 175 | task_size = self.task_size[user_index] # M*N 176 | # task_size = task_size[user_index]*args.tasksize_cof # 重复采样映射到边中 177 | 178 | tasks = task_size * task_allocation # mat pop_num x M*N 179 | 180 | compute_resource = self.compute_resource[server_index] 181 | # compute_resource = compute_resource[server_index]*args.comp_cof # 182 | 183 | comp = compute_resource * comp_allocation 184 | 185 | pw = power_allocation * self.path_losses # mat pop_num x M*N 186 | # pw = torch.clamp(pw, 1e-5, 1) 187 | 188 | pw_list = torch.zeros((pw.shape[0], pw.shape[1], server_index[-1]+1), device=args.device) # mat pop_num x MN x N 189 | pw_list.scatter_(2, server_index.repeat((self.pop_num, 1)).unsqueeze(2), pw.unsqueeze(2)) 190 | pws_list = pw_list.sum(1)[:, server_index] # mat pop_num x MN 191 | 192 | 193 | interference = pws_list-pw 194 | rate = torch.log2(1+torch.div(pw, interference+epsilon)) 195 | # rate = args.band_width * torch.log2(1+torch.div(pw, interference+epsilon)) 196 | # offloading_time = torch.div(tasks, rate+extre) * (args.tasksize_cof/args.band_width) 197 | offloading_time = torch.div(tasks, rate+extre) 198 | 199 | # compute_time = torch.div(tasks, comp+extre) * (args.tasksize_cof*args.cons_factor/args.comp_cof) 200 | compute_time = torch.div(tasks, comp+extre) 201 | 202 | time_loss = offloading_time + compute_time # pop_num x MN 203 | assert torch.isnan(time_loss).sum()==0 204 | 205 | 206 | time_loss_list = torch.zeros((time_loss.shape[0], time_loss.shape[1], user_index[-1]+1), device=args.device) 207 | time_loss_list.scatter_(2, user_index.repeat((self.pop_num, 1)).unsqueeze(2), time_loss.unsqueeze(2)) 208 | time_loss_list = time_loss_list.sum(1) # pop_num x MN 209 | 210 | return time_loss_list 211 | 212 | 213 | if __name__=='__main__': 214 | server_num = 5 215 | 216 | pop_num = 200 217 | epochs = 600 218 | 219 | sample_num = 5 220 | 221 | 222 | train_user_nums = np.random.randint(server_num*3, server_num*3+1, sample_num) 223 | train_server_nums = np.random.randint(server_num, server_num+1, sample_num) 224 | # test_user_nums = np.random.randint(server_num*3, server_num*3+1, args.test_layouts) 225 | # test_server_nums = np.random.randint(server_num, server_num+1, args.test_layouts) 226 | 227 | # env_max_length = np.sqrt(server_num * 300) 228 | 229 | train_layouts = generate_layouts(train_user_nums, train_server_nums, args) 230 | # test_layouts = generate_layouts(test_user_nums, test_server_nums, args) 231 | 232 | train_loader = DataLoader(train_layouts, batch_size=args.batch_size, shuffle=True) 233 | # test_loader = DataLoader(test_layouts, batch_size=args.batch_size, shuffle=True) 234 | loss_list = [] 235 | for data in train_layouts: 236 | compute_resource = data['server'].x[:, 0].squeeze() 237 | path_losses = data['user', 'u2s', 'server'].path_loss.squeeze() 238 | task_size = data['user'].x[:, 0].squeeze() 239 | edge_index = data['user', 'u2s', 'server'].edge_index 240 | ga = Evolution(server_num, server_num*3, pop_num, epochs, compute_resource=compute_resource, path_losses=path_losses, task_size=task_size, edge_index=edge_index) 241 | loss, _ = ga.evolution() 242 | loss_list.append(loss.item()[-1]) 243 | print(np.mean(loss_list)) 244 | 245 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 UNIC Lab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scalable Resource Management for Dynamic MEC: An Unsupervised Link-Output Graph Neural Network Approach 2 | This is the code for paper "Scalable Resource Management for Dynamic MEC: An Unsupervised Link-Output Graph Neural Network Approach" 3 | [Paper](https://arxiv.org/pdf/2306.08938.pdf) 4 | -------------------------------------------------------------------------------- /arguments.py: -------------------------------------------------------------------------------- 1 | 2 | import argparse 3 | args = argparse.ArgumentParser() 4 | #offloading 模型参数 5 | args.add_argument('--learning_rate', default=1e-4) 6 | args.add_argument('--critic_lr', default=5e-4) 7 | args.add_argument('--input_dim', default=2) 8 | args.add_argument('--state_dim', default=3) 9 | args.add_argument('--action_dim', default=2) 10 | args.add_argument('--hidden_dim', default=64) 11 | args.add_argument('--alpha', default=0.2) 12 | args.add_argument('--device', default='cuda:0') 13 | args.add_argument('--load_pretrained', default=False) 14 | args.add_argument('--num_layers', default=2) 15 | 16 | # 实验环境参数 17 | args.add_argument('--user_num', default=20) 18 | args.add_argument('--server_num', default=5) 19 | args.add_argument('--test_user_num', default=20) 20 | args.add_argument('--test_server_num', default=5) 21 | args.add_argument('--p_max', default=1) 22 | args.add_argument('--pw_threshold', default=1e-6) 23 | args.add_argument('--train_layouts', default=128) 24 | args.add_argument('--test_layouts', default=64) 25 | args.add_argument('--env_max_length', default=400) 26 | args.add_argument('--server_height', default=20) 27 | args.add_argument('--carrier_f_start', default=2.4e9) 28 | args.add_argument('--carrier_f_end', default=2.4835e9) 29 | args.add_argument('--signal_cof', default=4.11) 30 | args.add_argument('--band_width', default=1e6) 31 | args.add_argument('--batch_size', default=32) 32 | args.add_argument('--max_server_num', default=15) 33 | args.add_argument('--init_min_size', default=2) 34 | args.add_argument('--init_max_size', default=8) 35 | 36 | args.add_argument('--cons_factor', default=10) 37 | args.add_argument('--init_min_comp', default=0.1) 38 | args.add_argument('--init_max_comp', default=1) 39 | args.add_argument('--comp_cof', default=1024**2) 40 | args.add_argument('--tasksize_cof', default=1024*100) 41 | 42 | 43 | 44 | args.add_argument('--multi_scales_train', default=False) 45 | 46 | args.add_argument('--multi_scales_test', default=False) 47 | 48 | args.add_argument('--single_scale_test', default=True) 49 | 50 | args.add_argument('--comparison_hgnn', default=True) 51 | args.add_argument('--comparison_pcnet', default=True) 52 | args.add_argument('--comparison_pcnetCritic', default=False) 53 | 54 | 55 | args.add_argument('--train_steps', default=600) 56 | args.add_argument('--evaluate_steps', default=10) 57 | args.add_argument('--save_steps', default=50) 58 | 59 | 60 | 61 | args = args.parse_args() 62 | 63 | -------------------------------------------------------------------------------- /critic_train_exp.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | import torch 4 | import torch.nn as nn 5 | import matplotlib.pyplot as plt 6 | from off_loading_models import PCNet, PCNetCritic, MMSE, GnnCritic, TaskLoad 7 | from arguments import args 8 | from torch_geometric.loader import DataLoader 9 | from layouts import generate_layouts 10 | from tqdm import tqdm 11 | 12 | 13 | def compute_loss_nn(task_allocation, power_allocation, comp_allocation, task_size, compute_resource, path_losses, user_index, server_index): 14 | 15 | # task_size : vector N 16 | # task_allocation: mat pop_num x 3*M*N 17 | # index: vector 3*M*N 18 | 19 | epsilon = 1e-9 20 | extre = 1e-20 21 | server_index_first = server_index.reshape((batch_size, -1))[0] 22 | user_index_first = user_index.reshape((batch_size, -1))[0] 23 | # user_index = edge_index[0] # s2u中源节点的索引 24 | # server_index = edge_index[1] # s2u中目标节点的索引 25 | 26 | # power_allocation = torch.clamp(power_allocation, 1e-5, 1) 27 | pw_ini = power_allocation * path_losses # mat pop_num x M*N 28 | 29 | # 将信道状态过小的设置为0 30 | mask_pw = torch.where(pw_ini[m, 1] 16 | sq_b = b**2 17 | sum_sq_b = np.sum(sq_b, axis=1) 18 | sum_sq_b = np.expand_dims(sum_sq_b, axis=0) # n->[1, n] 19 | bt = b.T 20 | distance = np.sqrt(np.abs(sum_sq_a+sum_sq_b-2*np.matmul(a, bt))) 21 | return distance 22 | 23 | 24 | def build_graph(user, server, s2u_idx, u2s_index, u2u_idx, s2s_idx, u2u_distance, u2s_path_loss, u2s_path_loss_feat, env_len): 25 | 26 | user_feat = user 27 | server_feat = server 28 | user_ones = np.zeros((len(user), 1)) 29 | server_ones = np.zeros((len(server), 1)) 30 | user_feat = np.concatenate((user_feat, user_ones), axis=1) 31 | server_feat = np.concatenate((server_feat, server_ones), axis=1) 32 | user_feat = torch.tensor(user_feat, dtype=torch.float).to(args.device) 33 | server_feat = torch.tensor(server_feat, dtype=torch.float).to(args.device) 34 | u2s_path_loss = torch.tensor(u2s_path_loss, dtype=torch.float).to(args.device) 35 | 36 | u2s_path_loss_feat = torch.tensor(u2s_path_loss_feat, dtype=torch.float).to(args.device) 37 | 38 | s2u_attr = u2s_path_loss_feat.reshape((-1, 1)) 39 | u2s_attr = u2s_path_loss_feat.reshape((-1, 1)) 40 | 41 | u2u_attr = torch.tensor(u2u_distance.reshape((-1, 1)), dtype=torch.float).to(args.device) 42 | 43 | 44 | s2u_idx = torch.tensor(s2u_idx, dtype=torch.long).to(args.device) 45 | u2s_index = torch.tensor(u2s_index, dtype=torch.long).to(args.device) 46 | u2u_idx = torch.tensor(u2u_idx, dtype=torch.long).to(args.device) 47 | s2s_idx = torch.tensor(s2s_idx, dtype=torch.long).to(args.device) 48 | 49 | data = HeteroData().to(args.device) 50 | 51 | data['env_len'].x = torch.tensor([env_len]) 52 | 53 | 54 | data['user'].x = user_feat # locations of users,size of task to offloading 55 | data['server'].x = server_feat 56 | data['user', 'u2u', 'user'].edge_index = u2u_idx 57 | data['server', 's2u', 'user'].edge_index = s2u_idx 58 | data['user', 'u2s', 'server'].edge_index = u2s_index 59 | 60 | data['user', 'u2s', 'server'].path_loss = u2s_path_loss_feat.reshape((-1, 1)) 61 | 62 | data['server', 's2s', 'server'].edge_index = s2s_idx 63 | data['server', 's2u', 'user'].edge_attr = s2u_attr 64 | data['user', 'u2s', 'server'].edge_attr = u2s_attr 65 | data['user', 'u2u', 'user'].edge_attr = u2u_attr 66 | 67 | return data 68 | 69 | def compute_path_losses(args, distances): 70 | carrier_f = (args.carrier_f_start+args.carrier_f_end)/2 71 | carrier_lam = 2.998e8 / carrier_f 72 | signal_cof = args.signal_cof 73 | path_losses = (signal_cof * carrier_lam) / (distances**2) 74 | 75 | return path_losses 76 | 77 | def generate_layouts(user_nums, server_nums,args): 78 | 79 | graphs = [] 80 | 81 | for idx in range(len(server_nums)): 82 | 83 | env_len = np.sqrt(server_nums[idx]*50) 84 | 85 | # 归一化位置, tasksize, computing resource 86 | user_idx_feat = np.random.random([user_nums[idx], 2]) 87 | user_idx = user_idx_feat * env_len 88 | 89 | # user_idx_tasksize = np.random.random((user_nums[idx], 1))*(args.init_max_size-args.init_min_size) + args.init_min_size 90 | user_idx_tasksize = np.random.random((user_nums[idx], 1)) 91 | user_idx_feat = user_idx_tasksize.copy() 92 | 93 | # server_idx_comp = np.random.random((server_nums[idx], 1))*(args.init_max_comp - args.init_min_comp) + args.init_min_comp 94 | server_idx_comp = np.random.random((server_nums[idx], 1)) 95 | server_idx_feat = server_idx_comp.copy() 96 | 97 | server_idx_feat = np.random.random([server_nums[idx], 2]) 98 | server_idx = server_idx_feat * env_len 99 | 100 | 101 | mask_ones = np.ones(server_nums[idx]) 102 | mask_idx = np.repeat(mask_ones[np.newaxis, :], repeats=user_nums[idx], axis=0) 103 | 104 | 105 | user_num_idx = user_nums[idx] 106 | # server_num_idx = server_nums[idx] 107 | 108 | # edge_index of users to users 109 | index_src = np.arange(user_num_idx).repeat(repeats=user_num_idx) 110 | index_dst = np.tile(np.arange(user_num_idx), reps=user_num_idx) 111 | u2u_index = np.concatenate([index_src[np.newaxis, :], index_dst[np.newaxis, :]], axis=0) 112 | 113 | # edge_index of servers to users 114 | index_s2u_dst, index_s2u_src = np.nonzero(mask_idx) 115 | s2u_index = np.concatenate([index_s2u_src[np.newaxis, :], index_s2u_dst[np.newaxis, :]], axis=0) 116 | 117 | # edge_index of users to servers 118 | index_u2s_src, index_u2s_dst = np.nonzero(mask_idx) 119 | u2s_index = np.concatenate([index_u2s_src[np.newaxis, :], index_u2s_dst[np.newaxis, :]], axis=0) 120 | 121 | # edge_index of servers to servers 122 | edge_mat = mask_ones[np.newaxis, :].repeat(repeats=server_nums[idx], axis=0) 123 | index_s2s_src, index_s2s_dst = np.nonzero(edge_mat) 124 | s2s_index = np.concatenate([index_s2s_src[np.newaxis, :], index_s2s_dst[np.newaxis, :]], axis=0) 125 | 126 | u2u_distances_idx = EuclideanDistances(user_idx, user_idx) 127 | u2s_distances_idx = EuclideanDistances(user_idx, server_idx) 128 | u2s_path_loss = compute_path_losses(args, u2s_distances_idx) 129 | 130 | 131 | # generate normalized channels randomly 132 | u2s_path_loss_feat = torch.rand((user_nums[idx], server_nums[idx]), device=args.device) 133 | 134 | # normalize the distances 135 | distance_mean = np.mean(u2s_distances_idx) 136 | distance_var = np.sqrt(np.mean(np.square(u2s_distances_idx-distance_mean))) 137 | u2s_distances_idx = (u2s_distances_idx-distance_mean)/distance_var 138 | 139 | user_idx_feat = user_idx_tasksize 140 | server_idx_feat = server_idx_comp 141 | 142 | graph = build_graph(user_idx_feat, server_idx_feat, s2u_index, u2s_index, u2u_index, s2s_index, u2u_distances_idx, u2s_path_loss, u2s_path_loss_feat, env_len) 143 | graphs.append(graph) 144 | 145 | return graphs 146 | -------------------------------------------------------------------------------- /multi_scales_test.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from arguments import args 3 | import numpy as np 4 | from torch_geometric.loader import DataLoader 5 | from layouts import generate_layouts 6 | from off_loading_models import TaskLoad, PCNet 7 | from tqdm import tqdm 8 | import random 9 | from time import time 10 | import pandas as pd 11 | from GA_exp import Evolution 12 | 13 | 14 | def compute_loss_nn(task_allocation, power_allocation, comp_allocation, task_size, compute_resource, path_losses, user_index, server_index): 15 | 16 | # task_size : vector N 17 | # task_allocation: mat pop_num x 3*M*N 18 | # index: vector 3*M*N 19 | 20 | epsilon = 1e-9 21 | extre = 1e-20 22 | server_index_first = server_index.reshape((batch_size, -1))[0] 23 | user_index_first = user_index.reshape((batch_size, -1))[0] 24 | # user_index = edge_index[0] # s2u中源节点的索引 25 | # server_index = edge_index[1] # s2u中目标节点的索引 26 | 27 | # power_allocation = torch.clamp(power_allocation, 1e-5, 1) 28 | pw_ini = power_allocation * path_losses # mat pop_num x M*N 29 | # pw = torch.clamp(pw, 1e-5, 1) 30 | 31 | # 将信道状态过小的设置为0 32 | mask_pw = torch.where(pw_ini None: 16 | super(TaskLoad, self).__init__() 17 | self.user_encoder = nn.Sequential( 18 | nn.Linear(input_dim, hidden_dim), 19 | nn.ReLU(), 20 | nn.Linear(hidden_dim, hidden_dim), 21 | nn.ReLU() 22 | ) 23 | self.server_encoder = nn.Sequential( 24 | nn.Linear(input_dim, hidden_dim), 25 | nn.ReLU(), 26 | nn.Linear(hidden_dim, hidden_dim), 27 | nn.ReLU() 28 | ) 29 | self.convs = nn.ModuleList() 30 | 31 | for layer in range(num_layers): 32 | conv = HeteroConv({ 33 | ('server', 's2u', 'user'): S2UGNN(input_dim, hidden_dim, output_dim, alpha), 34 | ('user', 'u2s', 'server'): U2SGNN(input_dim, hidden_dim, output_dim, alpha) 35 | }) 36 | self.convs.append(conv) 37 | 38 | self.task_allocation_mlp = nn.Sequential( 39 | nn.Linear(hidden_dim, hidden_dim), 40 | nn.ReLU(), 41 | nn.Linear(hidden_dim, 1), 42 | nn.LeakyReLU(alpha) 43 | ) 44 | 45 | self.power_alloc_mlp = nn.Sequential( 46 | nn.Linear(hidden_dim, hidden_dim), 47 | nn.ReLU(), 48 | nn.Linear(hidden_dim, 1), 49 | nn.LeakyReLU(alpha) 50 | ) 51 | 52 | self.comp_power_alloc_mlp = nn.Sequential( 53 | nn.Linear(hidden_dim, hidden_dim), 54 | nn.ReLU(), 55 | nn.Linear(hidden_dim, 1), 56 | nn.LeakyReLU(alpha) 57 | ) 58 | 59 | 60 | 61 | def forward(self, x_dict, edge_index_dict, edge_attr_dict): 62 | x_dict['user'] = self.user_encoder(x_dict['user']) 63 | x_dict['server'] = self.server_encoder(x_dict['server']) 64 | x_dict_0 = x_dict.copy() 65 | for conv in self.convs: 66 | x_dict = conv(x_dict, edge_index_dict, edge_attr_dict) 67 | # x_dict['user'] = x_dict['user'] + x_dict_0['user'] 68 | # x_dict['server'] = x_dict['server'] + x_dict_0['server'] 69 | # x_dict_0 = x_dict.copy() 70 | s2u_attr = self.convs[-1].convs['server__s2u__user'].message_attr 71 | u2s_attr = self.convs[-1].convs['user__u2s__server'].message_attr 72 | 73 | user_index = edge_index_dict['user', 'u2s', 'server'][0] 74 | # server_index = edge_index_dict['user', 'u2s', 'server'][1] 75 | server_index = edge_index_dict['server', 's2u', 'user'][0] 76 | 77 | task_allocation = self.task_allocation_mlp(u2s_attr) 78 | task_allocation = softmax(task_allocation, index=user_index) 79 | 80 | power_allocation = self.power_alloc_mlp(u2s_attr) 81 | power_allocation = softmax(power_allocation, index=user_index) 82 | 83 | comp_allocation = self.comp_power_alloc_mlp(s2u_attr) 84 | comp_allocation = softmax(comp_allocation, index=server_index) 85 | 86 | return task_allocation, power_allocation, comp_allocation 87 | 88 | class U2SGNN(MessagePassing): 89 | def __init__(self, input_dim, hidden_dim, output_dim, alpha, aggr: Optional[str] = "add", flow: str = "source_to_target", node_dim: int = -2, decomposed_layers: int = 1): 90 | super(U2SGNN, self).__init__() 91 | self.linear1 = nn.Linear(hidden_dim, hidden_dim) 92 | self.linear2 = nn.Linear(hidden_dim, hidden_dim) 93 | self.message_mlp = nn.Sequential( 94 | nn.Linear(hidden_dim+1, hidden_dim), 95 | nn.ReLU(), 96 | ) 97 | self.update_lin = nn.Linear(2*hidden_dim, hidden_dim) 98 | self.att = nn.Sequential( 99 | nn.Linear(2*hidden_dim, hidden_dim), 100 | nn.ReLU(), 101 | nn.Linear(hidden_dim, 1), 102 | nn.LeakyReLU(alpha) 103 | ) 104 | 105 | self.relation_mlp = nn.Sequential( 106 | nn.Linear(2*hidden_dim+1, hidden_dim), 107 | nn.ReLU(), 108 | nn.Linear(hidden_dim, hidden_dim), 109 | nn.ReLU() 110 | ) 111 | 112 | # self.task_alloc_mlp = nn.Sequential( 113 | # nn.Linear(hidden_dim, hidden_dim), 114 | # nn.ReLU(), 115 | # nn.Linear(hidden_dim, hidden_dim), 116 | # nn.LeakyReLU(alpha) 117 | # ) 118 | 119 | self.Wq = nn.Linear(hidden_dim, hidden_dim) 120 | self.Wr = nn.Linear(hidden_dim, hidden_dim) 121 | def forward(self, x, edge_index, edge_attr): 122 | x_src = x[0] # user 123 | x_dst = x[1] # server 124 | # x_src = F.relu(self.linear1(x_src)) 125 | # x_dst = F.relu(self.linear2(x_dst)) 126 | return self.propagate(x=(x_src, x_dst), edge_index=edge_index, edge_attr=edge_attr) 127 | def message(self, x_i, x_j, edge_index, edge_attr) -> Tensor: 128 | # 消息传播机制 129 | 130 | # message_mlp计算用户到server的信息传递 131 | tmp = torch.cat([x_i, edge_attr], dim=1) 132 | # 计算注意力 133 | att_weight = self.att(torch.cat([self.Wq(x_i), self.Wr(x_j)], dim=1)) 134 | att_weight = softmax(att_weight, index=edge_index[0], dim=0) # 根据user进行softmax 135 | 136 | outputs = self.message_mlp(tmp) 137 | outputs = att_weight*outputs # 注意力 138 | 139 | # 将注意力特征与边特征结合得到user到server的关系特征 140 | tmp = torch.cat([x_j, outputs, edge_attr], dim=-1) # user的特征,server到user的关系特征与边特征 141 | self.message_attr = self.relation_mlp(tmp) 142 | 143 | # # 关系特征mlp, softmax后得到user对server的分配结果 144 | # self.task_allocation = self.task_alloc_mlp(message_attr) # 每个user到server的分配信息 145 | 146 | return outputs 147 | def update(self, aggr_out, x) -> Tensor: 148 | 149 | output = F.relu(self.update_lin(torch.column_stack([aggr_out, x[1]]))) 150 | 151 | return output 152 | 153 | 154 | 155 | class S2UGNN(MessagePassing): 156 | def __init__(self, input_dim, hidden_dim, output_dim, alpha, aggr: Optional[str] = "add", flow: str = "source_to_target", node_dim: int = -2, decomposed_layers: int = 1): 157 | super(S2UGNN, self).__init__() 158 | self.linear1 = nn.Linear(hidden_dim, hidden_dim) 159 | self.linear2 = nn.Linear(hidden_dim, hidden_dim) 160 | self.message_mlp = nn.Sequential( 161 | nn.Linear(hidden_dim+1, hidden_dim), 162 | nn.ReLU(), 163 | nn.Linear(hidden_dim, hidden_dim), 164 | nn.ReLU() 165 | ) 166 | self.update_lin = nn.Linear(2*hidden_dim, hidden_dim) 167 | self.att = nn.Sequential( 168 | nn.Linear(2*hidden_dim, 1), 169 | nn.LeakyReLU(alpha) 170 | ) 171 | 172 | self.relation_mlp = nn.Sequential( 173 | nn.Linear(2*hidden_dim+1, hidden_dim), 174 | nn.ReLU(), 175 | nn.Linear(hidden_dim, hidden_dim), 176 | nn.ReLU() 177 | ) 178 | # self.comp_power_alloc_mlp = nn.Sequential( 179 | # nn.Linear(hidden_dim, hidden_dim), 180 | # nn.ReLU(), 181 | # nn.Linear(hidden_dim, 1), 182 | # nn.LeakyReLU(alpha) 183 | # ) 184 | 185 | self.Wq = nn.Linear(hidden_dim, hidden_dim) 186 | self.Wr = nn.Linear(hidden_dim, hidden_dim) 187 | def forward(self, x, edge_index, edge_attr): 188 | x_src = x[0] 189 | x_dst = x[1] 190 | # x_src = F.relu(self.linear1(x_src)) 191 | # x_dst = F.relu(self.linear2(x_dst)) 192 | return self.propagate(x=(x_src, x_dst), edge_index=edge_index, edge_attr=edge_attr) 193 | def message(self, x_i, x_j, edge_index, edge_attr) -> Tensor: 194 | # 消息传播机制 195 | 196 | # message_mlp计算server到user的信息传递 197 | tmp = torch.cat([x_i, edge_attr], dim=1) 198 | # 计算注意力 199 | att_weight = self.att(torch.cat([self.Wq(x_i), self.Wr(x_j)], dim=1)) 200 | att_weight = softmax(att_weight, index=edge_index[0], dim=0) 201 | 202 | outputs = self.message_mlp(tmp) 203 | outputs = att_weight*outputs 204 | 205 | # 将注意力特征与边特征结合得到user到server的关系特征 206 | tmp = torch.cat([x_j, outputs, edge_attr], dim=-1) # user的特征,server到user的关系特征与边特征 207 | self.message_attr = self.relation_mlp(tmp) 208 | 209 | return outputs 210 | def update(self, aggr_out, x) -> Tensor: 211 | output = F.relu(self.update_lin(torch.column_stack([aggr_out, x[1]]))) 212 | return output 213 | 214 | 215 | ''' 216 | class TaskLoad(nn.Module): 217 | def __init__(self, input_dim, hidden_dim, output_dim, alpha) -> None: 218 | super(TaskLoad, self).__init__() 219 | self.user_linear = nn.Linear(input_dim, hidden_dim) 220 | self.server_linear = nn.Linear(input_dim-1, hidden_dim) 221 | 222 | self.conv1 = HeteroConv({ 223 | ('server', 's2u' , 'user'): S2UGNN(input_dim, hidden_dim, output_dim, alpha) 224 | }) 225 | self.conv2 = HeteroConv({ 226 | ('user', 'u2u', 'user'): U2UGNN(hidden_dim, hidden_dim, output_dim, alpha) 227 | }) 228 | self.conv3 = HeteroConv({ # user到server,user根据server获得权值特征,输出user到server的边特征(分配方案) 229 | ('user', 'u2s', 'server'): S2UGNN_U_Allocation(input_dim, hidden_dim, output_dim, alpha) 230 | }) 231 | self.conv4 = HeteroConv({ # server到user,进行算力资源分配 232 | ('user', 'u2s', 'server'): S2UGNN_S_Allocation(input_dim, hidden_dim, output_dim, alpha) 233 | }) 234 | 235 | def forward(self, x_dict, edge_index_dict, edge_attr_dict): 236 | # s2u,user获取所有server的信息 237 | x_dict_1 = self.conv1(x_dict, edge_index_dict, edge_attr_dict) 238 | x_dict['user'] = x_dict_1['user'] 239 | # u2u, user间共享位置信息 240 | x_dict_2 = self.conv2(x_dict, edge_index_dict, edge_attr_dict) 241 | x_dict['user'] = x_dict_2['user'] 242 | # u2s, user将特征与server进行注意力获取,得到卸载分配与功率分配方案 243 | x_dict_3 = self.conv3(x_dict, edge_index_dict, edge_attr_dict) 244 | task_allocation = self.conv3.convs['user__u2s__server'].task_allocation 245 | power_allocation = self.conv3.convs['user__u2s__server'].power_allocation 246 | # 将卸载分配与功率分配方案作为边特征输入到s2u 247 | edge_attr_dict['user', 'u2s', 'server'] = torch.cat([edge_attr_dict['user', 'u2s', 'server'], task_allocation, power_allocation], dim=-1) 248 | x_dict_4 = self.conv4(x_dict, edge_index_dict, edge_attr_dict) 249 | comp_allocation = self.conv4.convs['user__u2s__server'].comp_power_allocation 250 | 251 | assert torch.isnan(task_allocation).sum()==0 252 | assert torch.isnan(power_allocation).sum()==0 253 | assert torch.isnan(comp_allocation).sum()==0 254 | 255 | return task_allocation, power_allocation, comp_allocation 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | class S2UGNN_U_Allocation(MessagePassing): 266 | def __init__(self, input_dim, hidden_dim, output_dim, alpha, aggr: Optional[str] = "add", flow: str = "source_to_target", node_dim: int = -2, decomposed_layers: int = 1): 267 | super().__init__() 268 | self.linear1 = nn.Linear(hidden_dim, hidden_dim) 269 | self.linear2 = nn.Linear(input_dim, hidden_dim) 270 | 271 | self.att = nn.Sequential( 272 | nn.Linear(2*hidden_dim, 1), 273 | nn.LeakyReLU(alpha) 274 | ) 275 | 276 | self.message_mlp = nn.Sequential( 277 | nn.Linear(hidden_dim+1, hidden_dim), 278 | nn.ReLU(), 279 | nn.Linear(hidden_dim, hidden_dim), 280 | nn.ReLU() 281 | ) 282 | self.relation_mlp = nn.Sequential( 283 | nn.Linear(2*hidden_dim+1, hidden_dim), 284 | nn.ReLU() 285 | ) 286 | self.task_alloc_mlp = nn.Sequential( 287 | nn.Linear(hidden_dim, hidden_dim), 288 | nn.ReLU(), 289 | nn.Linear(hidden_dim, 1), 290 | nn.LeakyReLU(alpha) 291 | ) 292 | self.power_alloc_mlp = nn.Sequential( 293 | nn.Linear(hidden_dim, hidden_dim), 294 | nn.ReLU(), 295 | nn.Linear(hidden_dim, 1), 296 | nn.LeakyReLU(alpha) 297 | ) 298 | self.Wq = nn.Linear(hidden_dim, hidden_dim) 299 | self.Wr = nn.Linear(hidden_dim, hidden_dim) 300 | def forward(self, x, edge_index, edge_attr): 301 | user = x[0] 302 | server = x[1] 303 | user = F.relu(self.linear1(user)) 304 | server = F.relu(self.linear2(server)) 305 | return self.propagate(x=(user, server), edge_index=edge_index, edge_attr=edge_attr) 306 | def message(self, x_i, x_j, edge_index, edge_attr) -> Tensor: 307 | src_tmp = torch.cat([x_i, edge_attr], dim=1) 308 | 309 | # 计算server对user的注意力特征 310 | att_weight = self.att(torch.cat([self.Wq(x_i), self.Wr(x_j)], dim=1)) 311 | att_weight = softmax(att_weight, index=edge_index[0]) 312 | 313 | outputs = self.message_mlp(src_tmp) 314 | 315 | outputs = att_weight * outputs # user到server的边特征,与edge_index维度相同 316 | 317 | # 将注意力特征与边特征结合得到user到server的关系特征 318 | tmp = torch.cat([x_j, outputs, edge_attr], dim=-1) # user的特征,server到user的关系特征与边特征 319 | message_attr = self.relation_mlp(tmp) 320 | 321 | # 关系特征mlp, softmax后得到user对server的分配结果 322 | self.task_allocation = self.task_alloc_mlp(message_attr) 323 | self.power_allocation = self.power_alloc_mlp(message_attr) 324 | 325 | self.task_allocation = softmax(self.task_allocation, index=edge_index[0], dim=0) 326 | self.power_allocation = softmax(self.power_allocation, index=edge_index[0], dim=0) 327 | 328 | 329 | return super().message(x_j) 330 | 331 | 332 | class S2UGNN_S_Allocation(MessagePassing): 333 | def __init__(self, input_dim, hidden_dim, output_dim, alpha, aggr: Optional[str] = "add", flow: str = "source_to_target", node_dim: int = -2, decomposed_layers: int = 1): 334 | super().__init__() 335 | self.linear1 = nn.Linear(hidden_dim, hidden_dim) 336 | self.linear2 = nn.Linear(input_dim, hidden_dim) 337 | self.message_mlp = nn.Sequential( 338 | nn.Linear(hidden_dim+3, hidden_dim), 339 | nn.ReLU() 340 | ) 341 | self.att = nn.Sequential( 342 | nn.Linear(2*hidden_dim, 1), 343 | nn.LeakyReLU(alpha) 344 | ) 345 | self.relation_mlp = nn.Sequential( 346 | nn.Linear(2*hidden_dim+3, hidden_dim), 347 | nn.ReLU(), 348 | nn.Linear(hidden_dim, hidden_dim), 349 | nn.ReLU() 350 | ) 351 | self.comp_power_alloc_mlp = nn.Sequential( 352 | nn.Linear(hidden_dim, hidden_dim), 353 | nn.ReLU(), 354 | nn.Linear(hidden_dim, 1), 355 | nn.LeakyReLU(alpha) 356 | ) 357 | self.Wq = nn.Linear(hidden_dim, hidden_dim) 358 | self.Wr = nn.Linear(hidden_dim, hidden_dim) 359 | def forward(self, x, edge_index, edge_attr): # edge_attr为功率分配与task offloading分配结果,均为softmax比例 360 | x_src = x[0] 361 | x_dst = x[1] 362 | self.tasksize = x_dst[:, 2] 363 | x_src = F.relu(self.linear1(x_src)) 364 | x_dst = F.relu(self.linear2(x_dst)) 365 | return self.propagate(x=(x_src, x_dst), edge_index=edge_index, edge_attr=edge_attr) 366 | def message(self, x_i, x_j, edge_index, edge_attr) -> Tensor: 367 | src_tmp = torch.cat([x_j, edge_attr], dim=1) 368 | 369 | # 计算server对user的注意力特征 370 | att_weight = self.att(torch.cat([self.Wq(x_i), self.Wr(x_j)], dim=1)) 371 | att_weight = softmax(att_weight, index=edge_index[1], dim=0) 372 | 373 | outputs = self.message_mlp(src_tmp) 374 | 375 | outputs = att_weight * outputs # user到server的边特征,与edge_index维度相同 376 | 377 | # 将注意力特征与边特征结合得到user到server的关系特征 378 | tmp = torch.cat([x_i, outputs, edge_attr], dim=-1) # server的特征,server到user的关系特征与边特征 379 | message_attr = self.relation_mlp(tmp) # 380 | 381 | # 关系特征mlp, softmax后得到server对user的算力分配结果 382 | self.comp_power_allocation = self.comp_power_alloc_mlp(message_attr) 383 | self.comp_power_allocation = softmax(self.comp_power_allocation, index=edge_index[1], dim=0) 384 | 385 | return super().message(x_j) 386 | 387 | 388 | 389 | 390 | class S2UGNN(MessagePassing): 391 | def __init__(self, input_dim, hidden_dim, output_dim, alpha, aggr: Optional[str] = "add", flow: str = "source_to_target", node_dim: int = -2, decomposed_layers: int = 1): 392 | super(S2UGNN, self).__init__() 393 | self.linear1 = nn.Linear(input_dim, hidden_dim) 394 | self.linear2 = nn.Linear(input_dim, hidden_dim) 395 | self.message_mlp = nn.Sequential( 396 | nn.Linear(hidden_dim+1, hidden_dim), 397 | nn.ReLU(), 398 | ) 399 | self.update_lin = nn.Linear(2*hidden_dim, hidden_dim) 400 | self.att = nn.Sequential( 401 | nn.Linear(2*hidden_dim, 1), 402 | nn.LeakyReLU(alpha) 403 | ) 404 | self.Wq = nn.Linear(hidden_dim, hidden_dim) 405 | self.Wr = nn.Linear(hidden_dim, hidden_dim) 406 | def forward(self, x, edge_index, edge_attr): 407 | x_src = x[0] 408 | x_dst = x[1] 409 | x_src = F.relu(self.linear1(x_src)) 410 | x_dst = F.relu(self.linear2(x_dst)) 411 | return self.propagate(x=(x_src, x_dst), edge_index=edge_index, edge_attr=edge_attr) 412 | def message(self, x_i, x_j, edge_index, edge_attr) -> Tensor: 413 | # 消息传播机制 414 | 415 | # message_mlp计算用户到uav的信息传递 416 | tmp = torch.cat([x_i, edge_attr], dim=1) 417 | # 计算注意力 418 | att_weight = self.att(torch.cat([self.Wq(x_i), self.Wr(x_j)], dim=1)) 419 | att_weight = softmax(att_weight, index=edge_index[0], dim=0) 420 | 421 | outputs = self.message_mlp(tmp) 422 | outputs = att_weight*outputs 423 | return outputs 424 | def update(self, aggr_out, x) -> Tensor: 425 | output = F.relu(self.update_lin(torch.column_stack([aggr_out, x[1]]))) 426 | return output 427 | 428 | class U2UGNN(MessagePassing): 429 | def __init__(self, input_dim, hidden_dim, ouput_dim, alpha, aggr: Optional[str] = "add", flow: str = "source_to_target", node_dim: int = -2, decomposed_layers: int = 1): 430 | super().__init__() 431 | self.linear1 = nn.Linear(input_dim, hidden_dim) 432 | # self.allocation_linear = nn.Linear(hidden_dim, ouput_dim) 433 | # self.power_linear = nn.Linear(hidden_dim, ouput_dim) 434 | self.att = nn.Sequential( 435 | nn.Linear(2*hidden_dim, 1), 436 | nn.LeakyReLU(alpha) 437 | ) 438 | self.message_mlp = nn.Sequential( 439 | nn.Linear(2*hidden_dim, hidden_dim), 440 | nn.ReLU(), 441 | ) 442 | # self.update_mlp = nn.Sequential( 443 | # nn.Linear(2*hidden_dim, hidden_dim), 444 | # nn.ReLU(), 445 | # ) 446 | self.update_lin = nn.Linear(2*hidden_dim, hidden_dim) 447 | self.Wq = nn.Linear(hidden_dim, hidden_dim) 448 | self.Wr = nn.Linear(hidden_dim, hidden_dim) 449 | def forward(self, x, edge_index, edge_attr): 450 | x = F.relu(self.linear1(x)) 451 | # self.user_num = edge_index[0][-1] + 1 452 | return self.propagate(edge_index=edge_index, x=x) 453 | def message(self, x_i, x_j, edge_index) -> torch.Tensor: 454 | # 消息传播机制 455 | 456 | # message_mlp计算用户到uav的信息传递 457 | self.outputs = self.message_mlp(torch.column_stack([x_i, x_j])) 458 | 459 | # 计算注意力 460 | self.att_weight = self.att(torch.cat([self.Wq(x_i), self.Wr(x_j)], dim=1)) 461 | self.att_weight = softmax(self.att_weight, index=edge_index[0], dim=0) 462 | self.outputs = self.att_weight * self.outputs 463 | return self.outputs 464 | 465 | def aggregate(self, inputs: Tensor, index: Tensor, ptr: Optional[Tensor] = None, dim_size: Optional[int] = None) -> Tensor: 466 | # 消息聚合 467 | outputs = super().aggregate(inputs, index, ptr, dim_size) # 进行aggr操作 468 | return outputs 469 | 470 | def update(self, aggr_out, x): 471 | 472 | output = F.relu(self.update_lin(torch.column_stack([aggr_out, x]))) 473 | # 映射到01之间 474 | # power = F.softmax(self.power_linear(x)) 475 | # allocation = F.softmax(self.allocation_linear(x)) 476 | 477 | # ouput = torch.cat([allocation, power], dim=1) 478 | 479 | return output 480 | 481 | ''' 482 | 483 | 484 | class GnnCritic(nn.Module): 485 | def __init__(self, num_layers, input_dim, hidden_dim, output_dim, alpha) -> None: 486 | super(GnnCritic, self).__init__() 487 | # output_dim: edge_index的数量 --> server_num x user_num 488 | self.gnn = TaskLoad(num_layers, input_dim, hidden_dim, output_dim, alpha) 489 | 490 | self.critic_mlp = nn.Sequential( 491 | nn.Linear(output_dim*3, hidden_dim), 492 | nn.ReLU(), 493 | nn.Linear(hidden_dim, hidden_dim), 494 | nn.ReLU(), 495 | nn.Linear(hidden_dim, 1), 496 | nn.LeakyReLU(alpha) 497 | ) 498 | 499 | def forward(self, x_dict, edge_index_dict, edge_attr_dict): 500 | # s2u,user获取所有server的信息 501 | x_dict_1 = self.gnn.conv1(x_dict, edge_index_dict, edge_attr_dict) 502 | x_dict['user'] = x_dict_1['user'] 503 | # u2u, user间共享位置信息 504 | x_dict_2 = self.gnn.conv2(x_dict, edge_index_dict, edge_attr_dict) 505 | x_dict['user'] = x_dict_2['user'] 506 | # u2s, user将特征与server进行注意力获取,得到卸载分配与功率分配方案 507 | x_dict_3 = self.gnn.conv3(x_dict, edge_index_dict, edge_attr_dict) 508 | task_allocation = self.gnn.conv3.convs['user__u2s__server'].task_allocation 509 | power_allocation = self.gnn.conv3.convs['user__u2s__server'].power_allocation 510 | # 将卸载分配与功率分配方案作为边特征输入到s2u 511 | edge_attr_dict['user', 'u2s', 'server'] = torch.cat([edge_attr_dict['user', 'u2s', 'server'], task_allocation, power_allocation], dim=-1) 512 | x_dict_4 = self.gnn.conv4(x_dict, edge_index_dict, edge_attr_dict) 513 | comp_allocation = self.gnn.conv4.convs['user__u2s__server'].comp_power_allocation 514 | 515 | sches = torch.cat([task_allocation, power_allocation, comp_allocation], dim=0).squeeze() 516 | critic_value = self.critic_mlp(sches.detach()) 517 | 518 | return task_allocation, power_allocation, comp_allocation, sches, critic_value 519 | 520 | 521 | 522 | 523 | 524 | 525 | class PCNet(nn.Module): 526 | def __init__(self, input_dim, hidden_dim, output_dim, alpha) -> None: 527 | super(PCNet, self).__init__() 528 | 529 | self.output_dim = output_dim 530 | 531 | self.encoder = nn.Sequential( 532 | nn.Linear(input_dim, hidden_dim), 533 | nn.ReLU(), 534 | nn.Linear(hidden_dim, hidden_dim), 535 | nn.ReLU() 536 | ) 537 | 538 | self.sche_mlp = nn.Sequential( 539 | nn.Linear(hidden_dim, hidden_dim), 540 | nn.ReLU(), 541 | nn.Linear(hidden_dim, 3*output_dim), 542 | # nn.LeakyReLU(alpha) 543 | # nn.Sigmoid() 544 | # nn.Tanh() 545 | ) 546 | 547 | 548 | 549 | 550 | def forward(self, u2s_path_loss, user_tasksize, server_comp_resource, edge_index): 551 | ''' 552 | input: path_losses of users to servers 553 | output: power_sche, task_sche, comp_sche 554 | ''' 555 | feats = torch.cat([u2s_path_loss, user_tasksize, server_comp_resource], dim=1) 556 | batch_num = feats.shape[0] 557 | 558 | embeddings = self.encoder(feats) 559 | 560 | sches = self.sche_mlp(embeddings) 561 | task_sche = sches[:, :self.output_dim] 562 | task_sche = softmax(task_sche.reshape((-1, 1)), index=edge_index[0]) 563 | task_sche = task_sche.reshape((batch_num, -1)) 564 | 565 | power_sche = sches[:, self.output_dim:2*self.output_dim] 566 | power_sche = softmax(power_sche.reshape((-1, 1)), edge_index[0]) 567 | power_sche = power_sche.reshape((batch_num, -1)) 568 | 569 | comp_sche = sches[:, 2*self.output_dim:] 570 | comp_sche = softmax(comp_sche.reshape((-1, 1)), index=edge_index[1]) 571 | comp_sche = comp_sche.reshape((batch_num, -1)) 572 | 573 | # power_sche = self.power_mlp(embeddings) 574 | # power_sche = softmax(power_sche, edge_index[0]) 575 | # task_sche = self.task_mlp(embeddings) 576 | # task_sche = softmax(task_sche, index=edge_index[0]) 577 | # comp_sche = self.comp_mlp(embeddings) 578 | # comp_sche = softmax(comp_sche, index=edge_index[1]) 579 | 580 | return task_sche, power_sche, comp_sche 581 | 582 | 583 | class PCNetCritic(nn.Module): 584 | def __init__(self, input_dim, hidden_dim, output_dim, alpha, args) -> None: 585 | super(PCNetCritic, self).__init__() 586 | 587 | self.output_dim = output_dim 588 | 589 | self.encoder = nn.Sequential( 590 | nn.Linear(input_dim, hidden_dim), 591 | nn.ReLU(), 592 | nn.Linear(hidden_dim, hidden_dim), 593 | nn.ReLU() 594 | ) 595 | 596 | self.sche_mlp = nn.Sequential( 597 | nn.Linear(hidden_dim, hidden_dim), 598 | nn.ReLU(), 599 | nn.Linear(hidden_dim, 3*output_dim), 600 | nn.Tanh() 601 | ) 602 | 603 | 604 | self.critic_mlp = nn.Sequential( 605 | nn.Linear(output_dim*3, hidden_dim), 606 | nn.ReLU(), 607 | nn.Linear(hidden_dim, hidden_dim), 608 | nn.ReLU(), 609 | nn.Linear(hidden_dim, 1), 610 | nn.LeakyReLU(alpha) 611 | ) 612 | 613 | def forward(self, u2s_path_loss, user_tasksize, server_comp_resource, edge_index): 614 | 615 | ''' 616 | input: path_losses of users to servers 617 | output: power_sche, task_sche, comp_sche 618 | ''' 619 | 620 | feats = torch.cat([u2s_path_loss, user_tasksize, server_comp_resource], dim=0) 621 | embeddings = self.encoder(feats) 622 | 623 | sches = self.sche_mlp(embeddings) 624 | task_sche = sches[:self.output_dim] 625 | task_sche = softmax(task_sche, index=edge_index[0]) 626 | 627 | power_sche = sches[self.output_dim:2*self.output_dim] 628 | power_sche = softmax(power_sche, edge_index[0]) 629 | 630 | comp_sche = sches[2*self.output_dim:] 631 | comp_sche = softmax(comp_sche, index=edge_index[1]) 632 | 633 | sches = torch.cat([task_sche, power_sche, comp_sche], dim=-1) 634 | critic_value = self.critic_mlp(sches.detach()) 635 | 636 | return task_sche, power_sche, comp_sche, sches, critic_value 637 | 638 | 639 | 640 | 641 | 642 | class MMSE(nn.Module): 643 | def __init__(self, input_shape, args) -> None: 644 | super(MMSE, self).__init__() 645 | 646 | self.args = args 647 | 648 | self.task_allocation = nn.Parameter(torch.rand(input_shape, requires_grad=True)) 649 | self.power_allocation = nn.Parameter(torch.rand(input_shape, requires_grad=True)) 650 | self.comp_allocation = nn.Parameter(torch.rand(input_shape, requires_grad=True)) 651 | 652 | 653 | def forward(self, compute_resource, path_losses, task_size, edge_index): 654 | epsilon = 1e-9 655 | extre = 1e-20 656 | user_index = edge_index[0] # s2u中源节点的索引 657 | server_index = edge_index[1] # s2u中目标节点的索引 658 | 659 | task_sche = softmax(self.task_allocation, edge_index[0]) 660 | power_sche = softmax(self.power_allocation, edge_index[0]) 661 | comp_sche = softmax(self.comp_allocation, edge_index[0]) 662 | 663 | task_size = task_size[user_index] 664 | # task_size = task_size[user_index]*args.tasksize_cof # 重复采样映射到边中 665 | 666 | tasks = task_size * task_sche.squeeze() 667 | compute_resource = compute_resource[server_index] 668 | comp = compute_resource * comp_sche.squeeze() 669 | 670 | # power_sche = torch.clamp(power_sche, 1e-5, 1) 671 | pw = power_sche.squeeze() * path_losses.squeeze() 672 | 673 | 674 | pws_list = [] 675 | for idx in range(server_index[-1]+1): 676 | # 取出所有到同一个server的pw加和 677 | pws_idx = pw[torch.where(server_index==idx)].sum() 678 | pws_list.append(pws_idx) 679 | pws_list = torch.stack(pws_list) 680 | pws_list = pws_list[server_index] 681 | interference = pws_list-pw 682 | rate = torch.log2(1 + torch.div(pw, interference+epsilon)) 683 | # rate = args.band_width * torch.log2(1+torch.div(pw, interference+epsilon)) 684 | 685 | offloading_time = torch.div(tasks, rate+extre) 686 | # offloading_time = torch.div(tasks, rate+extre) * (self.args.tasksize_cof/self.args.band_width) 687 | 688 | # compute_time = torch.div(tasks, comp+extre) * (self.args.tasksize_cof*self.args.cons_factor/self.args.comp_cof) 689 | compute_time = torch.div(tasks, comp+extre) 690 | 691 | time_loss = offloading_time + compute_time 692 | assert torch.isnan(time_loss).sum()==0 693 | 694 | time_loss_list = [] 695 | for idx in range(user_index[-1]+1): 696 | tl_idx = time_loss[torch.where(user_index==idx)].sum() 697 | time_loss_list.append(tl_idx) 698 | time_loss_list = torch.stack(time_loss_list) 699 | 700 | return time_loss_list.mean() 701 | -------------------------------------------------------------------------------- /pretrain.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from arguments import args 3 | import numpy as np 4 | from torch_geometric.loader import DataLoader 5 | from layouts import generate_layouts 6 | from off_loading_models import TaskLoad, PCNet 7 | from tqdm import tqdm 8 | import random 9 | 10 | def compute_loss_nn(task_allocation, power_allocation, comp_allocation, task_size, compute_resource, path_losses, user_index, server_index): 11 | 12 | # task_size : vector N 13 | # task_allocation: mat pop_num x 3*M*N 14 | # index: vector 3*M*N 15 | 16 | epsilon = 1e-9 17 | extre = 1e-20 18 | server_index_first = server_index.reshape((batch_size, -1))[0] 19 | user_index_first = user_index.reshape((batch_size, -1))[0] 20 | # user_index = edge_index[0] # s2u中源节点的索引 21 | # server_index = edge_index[1] # s2u中目标节点的索引 22 | 23 | # power_allocation = torch.clamp(power_allocation, 1e-5, 1) 24 | pw_ini = power_allocation * path_losses # mat pop_num x M*N 25 | # pw = torch.clamp(pw, 1e-5, 1) 26 | 27 | # 将信道状态过小的设置为0 28 | mask_pw = torch.where(pw_ini