User Allocation In MEC: A DRL Approach 论文笔记
论文:ICWS 2021 移动边缘计算中的用户分配:一种深度强化学习方法
代码地址:使用强化学习在移动边缘计算环境中进行用户分配
目录
Ⅰ.Introduction
II. MOTIVATION-A.验证假设的观察结果
II. MOTIVATION-A Motivating Example
数据驱动方法的基本思想
III.强化学习分配
RL框架
RL Allocation Algorithm代码
IV.确定性方法作为BASELINE
ILP Algorithm整数线性规划(ILP)算法代码
Nearest Neighbourhood Greedy Algorithm代码
整体框架代码:
框架中用到的函数:
对比实验:
V.实验与结果分析
Ⅰ.Introduction
随着对低延迟需求的增加,边缘计算或雾计算逐渐成为主流。当前最先进的技术假设边缘服务器上的总资源利用率等于从边缘服务器提供的所有服务的资源利用率的总和。然而,边缘服务器的资源利用率与服务请求数量之间通常存在高度非线性的关系,尤其CPU-GPU协同执行,使资源利用率的数学建模异常复杂。
Motivation:目前用于解决边缘用户分配(EUA)问题的算法普遍假设服务的资源利用率与边缘服务器上提供的服务数呈线性关系,假设服务器的总资源利用率是每个服务请求的资源占用量的累积总和。然而,实际服务过程中资源使用量是高度动态的,难以通过数学建模精确描述。
Method:提出一种设备端深度强化学习(DRL)框架来解决边缘用户分配(EUA)问题,基于与 MEC 系统的经验和交互逐步学习适当的资源分配。DRL Agent在服务延迟阈值约束下学习在某边缘服务器上服务的用户数量。DRL Agent通过同时观察系统参数,直接从边缘服务器中学习非线性依赖关系。
Conclusion:实验评估表明,DRL框架在用户分配数量方面优于传统的确定性方法。
II. MOTIVATION-A.验证假设的观察结果
分别使用 GPU 和不使用 GPU 观察目标检测应用程序 YOLO处理图像的服务执行时间:

非线性关系:YOLO的执行时间与CPU和GPU参数之间的关系是非线性的。表明执行时间不仅仅取决于单一的参数变化,还受到许多隐含因素的影响,例如CPU/GPU的可用性。
复杂性:由于存在多个隐藏参数,精确建模YOLO执行时间与系统资源之间的关系是相对困难的。
建模服务执行时间困难:
•可用处理器资源和执行时间之间的非线性关系:可用处理器资源和执行时间之间存在非线性关系,内核数量少时减少内核数量对执行时间的影响更显著,在高工作负载情况下增加后台工作负载会显著减慢执行速度。
• 跨时间的变化:相同配置的同一台机器上执行时间也存在显着差异,受服务调用模式和温度等多个隐藏参数影响。
• 跨服务的变化:不同服务的执行时间模式各异,如 Yolo 执行时间随用户增加近似线性增长,而 MobileNet 表现为非线性,使建模任务复杂化。
II. MOTIVATION-A Motivating Example
七个移动用户 U1、U2 .. U7和两个边缘服务器 e1 和 e2,每个用户请求边缘服务器上可用的两个服务 s1 和 s2 之一,用户U1、U2、U4和U6请求服务s1,其余用户请求服务s2,每个边缘服务器由一个资源向量4 元组(Available RAM、Core 的数量、CPU 后台工作负载%、GPU Utilized%)。
解释传统的确定性方法如何导致用户到服务器的分配效率低下:


如对于边缘服务器 e1,服务s1的单个用户请求的执行时间为 3.12s,四个用户的预期执行时间用线性插值得到12.48 s。然而实际 3.468 s。
假设延迟阈值6.58 秒,用仅考虑服务单个请求的执行时间的确定性方法将U1、U2和U3分配给e1,只会给s1分配2个用户(每个用户3.12s),给s2分配1个用户(6.32s)
使用确定性方法分配的用户总数为 3
数据驱动方法的基本思想
数据驱动方法基于实际的执行时间数据和更精确的资源利用模型进行用户到服务器的分配,克服确定性方法的缺点,提供更有效的资源分配。
运行 YOLO 的四个用户的执行时间低于 6.55 的延迟阈值。实际上可以在 e1 上分配用户 U1、U2、U4(只需 3.35)。e2 可以容纳 U5 和 U7(只需 6.12)。
使用数据驱动的方法分配总共 5 个用户(比确定性方法多 2 个用户),更准确地建模资源利用率。
III.强化学习分配
MEC环境中每个边缘服务器
的覆盖率半径为
。边缘服务器覆盖半径内的移动users
可以请求托管在该服务器上的服务
。每个边缘服务器上可用资源(RAM、Cores、CPU 背景工作负载%、GPU Utilized%)
分配策略根据动态因素确定用户与特定边缘服务器的绑定:
a)新用户加入边缘服务器的覆盖区域 (b)用户远离边缘服务器的覆盖区域 (c)用户服务请求更改 (d)边缘服务器或移动用户离线
分配策略目标是在遵守服务执行的延迟阈值𝛤Γ的同时尽可能多满足服务请求。传统的确定性方法依赖历史数据预测执行时间,但由于执行时间的动态特性,可能导致资源分配过度或不足。提出的RL学习框架通过从边缘服务器的实际经验中学习服务执行模式,实时优化用户-服务器绑定决策,更有效地应对动态环境。
RL框架
RL 框架中的Agent通过探索环境并从动作接收反馈来学习环境以选择更好的动作选择,RL可以在不需要大量标记数据的情况下学习底层环境。
在这个 RL 框架中,Agent不断地与边缘服务器交互以采取行动,执行多个服务请求并根据执行占用空间获得相应的奖励。
状态:边缘服务器的资源向量以及服务请求的数量
动作:等待在边缘服务器上执行的服务请求集。



Reward的计算:
#compute latencydef get_reward(self, state, action):#将动作 action 转换为两个用户数量 u1 和 u2u1 = action//5 + 1u2 = (action+1) - (u1-1)*5#sample time from dataframegram = state[0]gcores = state[1]gwl_c = state[2]gwl_g = state[3]gs1 = u1*100gs2 = u2*100#查找与当前状态和动作匹配的记录fetch_state = self.df.loc[ (self.df['ram'] == gram) & (self.df['cores']== gcores) & (self.df['workload_cpu']==gwl_c) & (self.df['workload_gpu']==gwl_g) & (self.df['users_yolo']==gs1) & (self.df['users_mnet']==gs2)]#计算奖励: if fetch_state.empty:#找不到匹配的状态信息,则返回较大的负奖励,表示这是一个不利的动作选择return -20 # 计算网络延迟:time1 = fetch_state.sample().iloc[0]['time_yolo'] #从匹配的状态信息中随机选择一个样本time2 = fetch_state.sample().iloc[0]['time_mnet']#获取 time_yolo 和 time_mnet 的延迟时间tm = max(time1, time2)#两者中较大的延迟时间用作网络延迟的阈值#add total latencies due to network based on number of u1 and u2if (tm <= latency_threshold): #用户数量的变化对应的奖励,以及动作本身的基础奖励(u1 + u2)return 0.01*(gs1 - state[4]) + 0.01*(gs2 - state[5]) + u1 + u2 else:return -5 - u1 - u2
训练RL Agent
RL环境:
import numpy as np
import gym
from gym import spaces
from gym.utils import seedingclass yolosystem(gym.Env):metadata = {'render.modes': ['human']}def __init__(self, n_actions, filename):super(yolosystem, self).__init__()self.n_actions = n_actions #total number of action space after ranging [10, 20, 30 ...]self.action_space = spaces.Discrete(self.n_actions) #total number of users in the action space; starts with zeroself.observation_space = spaces.Box(low=np.array([0,0,0,0,0,0]), high=np.array([11000]*6), shape=(6, ), dtype=np.int32) #<RAM, Core, Workload>self.seed()self.current_obs = np.array( [3000, 2, 40, 2, 100, 100] ) #current observation = <ram, cores, workload%>#Load datasetself.df = pd.read_csv(filename)# computer percentage of GPU usage from actual useself.df['workload_gpu'] = self.df['workload_gpu'].multiply(1/80).round(0).astype(int) #round gpu workload#get unique data in setself.ram = self.df.ram.unique()self.cores = self.df.cores.unique()self.workload_cpu = self.df.workload_cpu.unique()print(self.df) #print datasetdef seed(self, seed=1010):self.np_random, seed = seeding.np_random(seed)return [seed]def step(self, action):assert self.action_space.contains(action) #action should be in action spacestate = self.current_obsdone = True #Episodes ends after each action#compute latecy from the number of usersreward = self.get_reward(state, action) #linear latency
# print(action, reward)self.current_obs = self.get_random_state() #go to a random state# print(self.current_obs)return self.current_obs, reward, done, {} #no-states, reward, episode-done, no-infodef reset(self):self.current_obs = self.get_random_state()return self.current_obs #current state of the system with no loaddef render(self, mode='human', close=False):print(f"Current State:<{self.current_obs}>")#compute latencydef get_reward(self, state, action):#将动作 action 转换为两个用户数量 u1 和 u2u1 = action//5 + 1u2 = (action+1) - (u1-1)*5#sample time from dataframegram = state[0]gcores = state[1]gwl_c = state[2]gwl_g = state[3]gs1 = u1*100gs2 = u2*100#查找与当前状态和动作匹配的记录fetch_state = self.df.loc[ (self.df['ram'] == gram) & (self.df['cores']== gcores) & (self.df['workload_cpu']==gwl_c) & (self.df['workload_gpu']==gwl_g) & (self.df['users_yolo']==gs1) & (self.df['users_mnet']==gs2)]#计算奖励: if fetch_state.empty:#找不到匹配的状态信息,则返回较大的负奖励,表示这是一个不利的动作选择return -20 # 计算网络延迟:time1 = fetch_state.sample().iloc[0]['time_yolo'] #从匹配的状态信息中随机选择一个样本time2 = fetch_state.sample().iloc[0]['time_mnet']#获取 time_yolo 和 time_mnet 的延迟时间tm = max(time1, time2)#两者中较大的延迟时间用作网络延迟的阈值#add total latencies due to network based on number of u1 and u2if (tm <= latency_threshold): #用户数量的变化对应的奖励,以及动作本身的基础奖励(u1 + u2)return 0.01*(gs1 - state[4]) + 0.01*(gs2 - state[5]) + u1 + u2 else:return -5 - u1 - u2 #get to some random state after taking an actiondef get_random_state(self):#generate state randomlygram = np.random.choice(self.ram, 1)[0]gcores = np.random.choice(self.cores, 1)[0]gwl_c = np.random.choice(self.workload_cpu, 1)[0]#fetch gamma for the statefetch_state = self.df.loc[ (self.df['ram'] == gram) & (self.df['cores']== gcores) & (self.df['workload_cpu']==gwl_c) ]gwl_g = fetch_state.sample().iloc[0]['workload_gpu'] #fetch workload randmolygs1 = random.randrange(50, 550, 50)gs2 = random.randrange(50, 550, 50)return np.array( [gram, gcores, gwl_c, gwl_g, gs1, gs2] )
from stable_baselines3.common.monitor import Monitor
import os
# Create log dir
log_dir = './agent_tensorboard/'
os.makedirs(log_dir, exist_ok=True)env = Monitor(env, log_dir)from stable_baselines3 import DQN
from stable_baselines3.dqn import MlpPolicy
from stable_baselines3.common.vec_env import DummyVecEnv# wrap it非向量化的环境 env 转换为一个向量化的环境 env
env = DummyVecEnv([lambda: env])
训练
model = DQN(MlpPolicy, env, verbose=0, tensorboard_log = log_dir, exploration_fraction=0.4, learning_starts=150000, train_freq=30, target_update_interval=30000, exploration_final_eps=0.07)begin = time.time()
model.learn(total_timesteps=500000) #reset_num_timesteps=False
end = time.time()
training_time = end-begin

RL Allocation Algorithm代码
#Load model
def rl_algo():#对于每台服务器,使用RL预测每个服务器的容量server_capacity = np.zeros((N, S))for server_id in range(N):state = server_state[server_id]
# if model_type == 'lin':action = model_rl.predict(np.array(state), deterministic=True)
# if model_type == 'exp':
# action = model_exp.predict(np.array(state), deterministic=True)
# 根据action计算两种服务的预测容量 (u1 和 u2)u1 = action[0]//5 + 1u2 = (action[0]+1) - (u1-1)*5server_capacity[server_id][0] = u1*100 #model outputserver_capacity[server_id][1] = u2*100 #model output#根据每个用户的服务器数进行排序col1 = np.array([np.sum(ngb,axis=1)])col2 = np.array([np.arange(U)])sorted_ngb = np.concatenate((ngb, col1.T, col2.T), axis=1) #add rowsum and index column添加行和索引列sorted_ngb = sorted_ngb[np.argsort(sorted_ngb[:, N])] #sort the rows based on rowsum column根据行和列对行进行排序#分配用户到服务器#run allocation algorithmrl_allocation = []
# 遍历用户,根据用户连接的服务器列表和服务请求,选择最大预测容量的服务器分配。服务器有足够容量则更新服务器容量并记录分配结果for i in range(U):server_list = np.where(sorted_ngb[i, :N] == 1)[0] #获取用户连接到的服务器列表if len(server_list) == 0: #跳过没有服务器的用户continueser = int(service[i]) #用户正在请求哪个服务choosen_server = server_list[np.argmax(server_capacity[server_list, ser])] #找到所选服务器的 IDif server_capacity[choosen_server][ser] > 0: #将用户分配给choosen_serverserver_capacity[choosen_server][ser] -= 1 #减少服务器容量rl_allocation.append( (int(sorted_ngb[i, N+1]), choosen_server) ) #(user, server) alloc pairprint('RL Num of allocation: {}'.format(len(rl_allocation)))return rl_allocation
IV.确定性方法作为BASELINE
使用历史服务执行数据的平均值确定边缘服务器上服务
的执行时间
,进而确定可以分配到边缘服务器的用户数量的相应代码:allocation.ipynb def generate_server_state(num_server)
#获取与选择的 GPU 工作负载值匹配的行 计算YOLO 和 MNet 的平均时间time_yolo = fetch_time['time_yolo'].mean() #average of time for particular statetime_mnet = fetch_time['time_mnet'].mean()# 根据每个服务器的服务请求分配状态gs1 = server_service[s_id][0]gs2 = server_service[s_id][1] server_state.append( [gram, gcores, gwl_c, gwl_g, gs1, gs2] )# 追加每个服务器的 gamma 值gamma.append((time_yolo, time_mnet)) #append the gamma value of each server
ILP Algorithm整数线性规划(ILP)算法代码
解决用户分配到服务器的问题,目标是最大化覆盖的用户数量
def ilp_algo():## ===================================ILP with python mip# >> solver_name=GRB# >> Currently using CBCI = range(U) #user 用户的范围J = range(N) #server服务器的范围alloc = Model(sense=MAXIMIZE, name="alloc", solver_name=CBC)alloc.verbose = 0def coverage(user_ix, server_ix):if ngb[user_ix][server_ix]==1:return 1else:return 0#U: num of users, N: num of servers# 创建二进制变量矩阵 x,其中 x[i][j] 表示用户 i 是否被分配到服务器 jx = [[ alloc.add_var(f"x{i}{j}", var_type=BINARY) for j in J] for i in I]#Objective Equation# 目标函数:最大化分配的用户数量alloc.objective = xsum( x[i][j] for i in I for j in J )#1. 覆盖约束for i in I:for j in J: if not coverage(i,j):alloc += x[i][j] == 0# 2. 每个用户只能被分配到一个服务器for i in I:alloc += xsum( x[i][j] for j in J ) <=1# 3. 延迟约束for j in J:alloc += xsum( gamma[j][int(service[i])]*x[i][j] for i in I ) <=latency_threshold-network_latency[j] #alloc.write("test-model.lp")#===========Start Optimization=========alloc.optimize(max_seconds=25)# 优化模型#==========ILP Ends here#print(f"Number of Solutions:{qoe.num_solutions}")ilp_allocation = [ (i,j) for i in I for j in J if x[i][j].x >= 0.99] # 获取分配结果#print(f"Number of Solutions:{qoe.num_solutions}")#print(f"Objective Value:{qoe.objective_value}")allocated_num_users = len(ilp_allocation)print("ILP Allocated Num of Users: {}".format(allocated_num_users))# 输出分配的用户数量# selected.sort()return ilp_allocation
Nearest Neighbourhood Greedy Algorithm代码
def greedy_algo():server_capacity = np.zeros(N)# 初始化服务器容量数组rl_allocation = []for user_id in range(U):#获取与用户连接的服务器列表server_ngb_list = np.where(ngb[user_id, :N] == 1)[0] #get the list of server to which user is connectedif len(server_ngb_list) == 0: #ignore the users which are not under any serverscontinue # 计算每个用户到各个服务器的距离并排序#find the distance to each users in the server_ngb_listdist_list = np.array([ server_ngb_list, [server.iloc[i]['geometry'].centroid.distance(user.iloc[user_id]['geometry']) for i in server_ngb_list] ])# sorted list of servers based on the distance from userssorted_distance_list = dist_list[ :, dist_list[1].argsort()]#get the list of servers arranged in least to max distanceserver_list = sorted_distance_list[0].astype(int)# 分配算法lat = 0for server_id in server_list:lat = gamma[server_id][int(service[user_id])]#根据用户请求的服务类型和服务器,获取相应的服务延迟if server_capacity[server_id]+lat <= latency_threshold-network_latency[server_id]:server_capacity[server_id] += lat #increment the server_capacity of serverrl_allocation.append( (user_id, server_id) ) #(user, server) alloc pairbreakprint('Greedy-Ngb Num of allocation: {}'.format(len(rl_allocation)))return rl_allocation
整体框架代码:
固定服务器数量更改用户数量的情况:
对于不同用户数量,先拿到用户和服务器之间的邻居矩阵ngb,并计算网络延迟network_latency
之后生成服务器状态server_state 计算每个服务器的gamma 值 generate_server_state(num_server)
之后分别使用三种算法计算成功分配的数量
if alloc_type == 'server': #服务器固定,变化用户数量"for U in range(100, 600, 100):#用户数量100-500for epoch in range(50):print("User:", U, 'Server:', N, 'Epoch:', epoch)ngb, user, server, service, server_service, network_latency = ngb_matrix(U, N, S) #从EUA数据生成服务器和用户 # 确定邻域矩阵server_state, gamma = generate_server_state(N) #为每个用户分配状态和γ值#=======ILP startsstart = 0stop = 0execution_time_ilp = 0start = timeit.default_timer()ilp_aloc = ilp_algo() #call ILP algorithmstop = timeit.default_timer()execution_time_ilp = stop - start#========ILP ends#=======Greedy startsstart = 0stop = 0execution_time_greedy = 0start = timeit.default_timer()greedy_aloc = greedy_algo() #call ILP algorithmstop = timeit.default_timer()execution_time_greedy = stop - start#========Greedy ends#=======RL_linear startsstart = 0stop = 0execution_time_rl = 0start = timeit.default_timer()rl_aloc = rl_algo() #call ILP algorithmstop = timeit.default_timer()execution_time_rl = stop - start#========RL_linear ends#========Store results to fileto_append = [U, N,len(ilp_aloc), execution_time_ilp,len(greedy_aloc), execution_time_greedy,len(rl_aloc), execution_time_rl,] dseries = pd.Series(to_append, index = result_user.columns)result_user = result_user.append(dseries, ignore_index=True)print("epoch:", epoch)result_user.to_csv(result_file, index=False)
框架中用到的函数:
一、生成服务器状态计算每个服务器的 gamma 值
def generate_server_state(num_server):#生成服务器状态计算每个服务器的 gamma 值df = pd.read_csv(filename_base)# 将 GPU 工作负载的数值进行缩放和四舍五入
# df['ram'] = df['ram'].div(1000).round(0).astype(int)
# df['workload_cpu'] = df['workload_cpu'].div(10).round(0).astype(int)df['workload_gpu'] = df['workload_gpu'].multiply(1/80).round(0).astype(int) #round gpu workload
# df['users_yolo'] = df['users_yolo'].div(100).round(0).astype(int)
# df['users_mnet'] = df['users_mnet'].div(100).round(0).astype(int)#get unique data in set获取数据集中唯一的 RAM、核心数和 CPU 工作负载值ram = df.ram.unique()cores = df.cores.unique()workload_cpu = df.workload_cpu.unique()server_state = []#服务器状态gamma = []for s_id in range(num_server):#对于每一个服务器,随机选择一个 RAM、核心数和 CPU 工作负载值gram = np.random.choice(ram, 1)[0]gcores = np.random.choice(cores, 1)[0]gwl_c = np.random.choice(workload_cpu, 1)[0]#fetch gamma for the state获取对应状态的行fetch_state = df.loc[ (df['ram'] == gram) & (df['cores']== gcores) & (df['workload_cpu']==gwl_c) ]# 从匹配的状态中随机选择一个 GPU 工作负载值gwl_g = fetch_state.sample().iloc[0]['workload_gpu'] #fetch workload randmolyfetch_time = fetch_state.loc[ (df['workload_gpu'] == gwl_g) ]#获取与选择的 GPU 工作负载值匹配的行 计算YOLO 和 MNet 的平均时间time_yolo = fetch_time['time_yolo'].mean() #average of time for particular statetime_mnet = fetch_time['time_mnet'].mean()# 根据每个服务器的服务请求分配状态gs1 = server_service[s_id][0]gs2 = server_service[s_id][1] server_state.append( [gram, gcores, gwl_c, gwl_g, gs1, gs2] )# 追加每个服务器的 gamma 值gamma.append((time_yolo, time_mnet)) #append the gamma value of each serverreturn server_state, gamma
二、计算邻接矩阵
#================neighbourhood Computing
def ngb_matrix(U, N, S):
# 生成用户和服务器之间的邻居矩阵,并计算网络延迟 #U: number of users#N: number of servers#S: number of services# U X N matrixuser = load_users(U)server = load_servers(N)neighbourhood = np.zeros([U, N]) #用户和服务器之间的邻居矩阵network_latency = np.zeros(N) #每个服务器的网络延迟latency_data = load_planetlab() #加载 PlanetLab 数据,返回一个距离矩阵(bin size 150)# 检查每个用户是否在服务器的缓冲区内,并计算网络延迟for u in range(0, U):for n in range(0, N):#检查用户是否在服务器的缓冲区内(使用几何空间的 contains 方法)if server.iloc[n].geometry.contains(user.iloc[u].geometry):neighbourhood[u,n]=1#邻居矩阵中相应位置设为 1# 计算距离并分配延迟distance = server.iloc[n].geometry.centroid.distance(user.iloc[u].geometry)rep_lat = fetch_network_lat(int(distance), latency_data) #根据距离从 latency_data 中获取网络延迟if network_latency[n] < rep_lat:#最大可能延迟network_latency[n] = rep_latelse:neighbourhood[u,n]=0service = np.zeros(U)#为用户分配服务请求for u in range(0, U):#为每个用户随机分配一个从 0 到 S-1 的服务请求service[u] = random.randrange(0, S, 1) server_service = np.zeros((N, S))#统计每个服务器的服务请求数量for n in range(0, N):for u in range(0, U):if neighbourhood[u][n] == 1:server_service[n][int(service[u])] += 1return neighbourhood, user, server, service, server_service, network_latency
三、计算邻接矩阵中用到的函数
#================Load Planet Lab data
# 加载 PlanetLab 数据并转换为一个矩阵格式
def load_planetlab():#Convert to triangleldata = np.loadtxt('eua/PlanetLabData_1')[np.tril_indices(490)]ldata = ldata[ ldata != 0]#提取下三角矩阵的非零值ldata =np.unique(ldata)#去重并重置数据大小,使其符合150行的矩阵格式np.set_printoptions(suppress=True)length = ldata.shape[0]latency_row = 150latency_col = (length//latency_row) #Global Data usedldata = np.resize(ldata, latency_col*latency_row)latency = ldata.reshape(latency_row,-1)return latency#=================Fetch Network latency
# 根据距离从延迟数据中获取网络延迟
def fetch_network_lat(distance, latency_data):rep_lat = np.random.choice(latency_data[distance], size=1, replace=True)#根据距离从延迟数据中随机选择一个延迟值return rep_lat/1000 #将延迟值转换为秒#===============User Data
# 加载用户数据并转换为地理数据格式
def load_users(num_of_users):user_raw = pd.read_csv("eua/users.csv")user_raw = user_raw.rename_axis("UID")#将数据框的索引轴重命名为 "UID",即用户的唯一标识符df = user_raw.sample(num_of_users)#随机抽样指定数量的用户数据
# 创建地理数据框,使用Longitude和Latitude列创建点几何对象,并转换坐标参考系统(CRS)gdf = geopandas.GeoDataFrame(df, geometry = geopandas.points_from_xy(df.Longitude, df.Latitude), crs = 'epsg:4326')#创建地理数据框user = gdf [['geometry']] #保留geometry列user = user.to_crs(epsg=28355) #指定数据的坐标参考系统(WGS84投影)#Insert additional data to dataframe#user = user.apply(add_data, axis=1)return user#================Server Data
def load_servers(num_of_servers):# 加载服务器数据,并将其转换为地理数据格式server_raw = pd.read_csv("eua/servers.csv")server_raw = server_raw.rename_axis("SID")#将数据框的索引轴重命名为 "SID",即服务器的唯一标识符df = server_raw.sample(num_of_servers) #随机抽样指定数量的服务器gdf = geopandas.GeoDataFrame(df, geometry = geopandas.points_from_xy(df.LONGITUDE, df.LATITUDE), crs = 'epsg:4326')#创建地理数据框server = gdf [['geometry']] #Keep Geometry columnserver = server.to_crs(epsg=28355) #Cover to crs in Australian EPSGdef add_radius(series):
# radius = random.randrange(150, 250, 10)
# 为每个服务器添加一个固定半径的缓冲区radius = 150 #每个服务器的缓冲区半径设为固定值 150series.geometry = series.geometry.buffer(radius)series['radius'] = radius
# series['resource'] = tcompreturn seriesserver = server.apply(add_radius, axis = 1)return server#绘制用户和服务器数据在地图上的分布情况
def plot_data(user, server):%config InlineBackend.figure_format='retina'%matplotlib inlinecbd = geopandas.read_file('eua/maps', crs = {'init': 'epsg=28355'} ) #read cbd-australia location datafig, ax = plt.subplots(1, 1, figsize=(15,10))ax.set_aspect('equal')ax.set_xlim(319400, 322100)ax.set_ylim(5811900, 5813700)user.plot(ax=ax, marker='o', color='red', markersize=20, zorder=3, label="users")server.plot(ax =ax, linestyle='dashed', edgecolor='green', linewidth=1, facecolor="none", zorder=1)server.centroid.plot(ax=ax, marker='s', color='blue', markersize=50, zorder=2, label="server")cbd.plot(ax=ax, color='grey', zorder=0, alpha = 0.3);ax.set_title("MEC Environment(EUA): CBD Melbourne(Australia)")ax.legend(bbox_to_anchor=(1, 0), loc='lower left')plt.show()
运行结果:


对比实验:
一、对于RL算法 使用不同训练回合数:
rl_algo_prop()和rl_algo_und()只是用了两个不同程度训练的agent模型,其余部分完全一致,这里只展示rl_algo_prop()。
分别是训练30,000回合的RL Agent生成的分配数量和训练1,50,000回合
动作空间的量化大小= 2
model_und = DQN.load("trained_agents/edge_agent_under_train")
model_prop = DQN.load("trained_agents/edge_agent_proper_train")
#Load model
def rl_algo_prop():#...同rl_algo()#换个模型就OK action = model_prop.predict(np.array(state), deterministic=True)print('Actionprop: {}'.format(action))u1 = (action[0]//10)*2 + 1u2 = (action[0]%10)*2 + 1server_capacity[server_id][0] = u1 #model outputserver_capacity[server_id][1] = u2 #model output#...同rl_algo()
二、对于RL算法 使用不同量化因子:
其余两种只在模型和和动作空间映射改变
rl_algo_act() 代码中说是=5:
action = model_act.predict(np.array(state), deterministic=True)print('Actionact: {}'.format(action))u1 = (action[0]//5)*4 + 1 #25 action spaceu2 = (action[0]%5)*4 + 1server_capacity[server_id][0] = u1 #model outputserver_capacity[server_id][1] = u2 #model output
rl_algo_thres10()代码中说是=100:
action = model_thres10.predict(np.array(state), deterministic=True)print('Actionthres10: {}'.format(action))u1 = action[0]//5 + 1u2 = (action[0]+1) - (u1-1)*5server_capacity[server_id][0] = u1*100 #model outputserver_capacity[server_id][1] = u2*100 #model output
对于训练不同回合数rl_algo_prop中动作空间的量化大小= 2的个人理解,不一定对:
每次agent预测出action之后,从中还原出两个服务s1、s2上的服务请求数(动作)使用的方法不同,= 2时的映射方法如下,输出action
for action in range(25):#rl_algo_prop()u1 = (action//10)*2 + 1u2 = (action%10)*2 + 1print(f"Action: {action}, u1: {u1}, u2: {u2}")
Action: 0, u1: 1, u2: 1
Action: 1, u1: 1, u2: 3
Action: 2, u1: 1, u2: 5
Action: 3, u1: 1, u2: 7
Action: 4, u1: 1, u2: 9
Action: 5, u1: 1, u2: 11
Action: 6, u1: 1, u2: 13
Action: 7, u1: 1, u2: 15
Action: 8, u1: 1, u2: 17
Action: 9, u1: 1, u2: 19
Action: 10, u1: 3, u2: 1
Action: 11, u1: 3, u2: 3
Action: 12, u1: 3, u2: 5
Action: 13, u1: 3, u2: 7
Action: 14, u1: 3, u2: 9
Action: 15, u1: 3, u2: 11
Action: 16, u1: 3, u2: 13
Action: 17, u1: 3, u2: 15
Action: 18, u1: 3, u2: 17
Action: 19, u1: 3, u2: 19
Action: 20, u1: 5, u2: 1
Action: 21, u1: 5, u2: 3
Action: 22, u1: 5, u2: 5
Action: 23, u1: 5, u2: 7
Action: 24, u1: 5, u2: 9
文章提到:使用大小为的量化减少动作空间的基数,
=10时新的动作元组 (2,2) 表示旧动作空间中范围 (11 - 20,11 - 20) 中的所有动作
于是我暂且认为=2指的是,量化后的动作空间中的一个动作,代表原来动作空间中的两个动作,也就是,第一个动作中的 u1: 1是我们选择来代表原来动作空间中u1: 1、u1: 2;同理u2:1代表u2:1、u2:2。下一个动作就从3开始。
但无法解释后边=5,
=100情况:
=5:
for action in range(25):#rl_algo_act()u1 = (action//5)*4 + 1u2 = (action%5)*4 + 1print(f"Action: {action+1}, u1: {u1}, u2: {u2}")
Action: 0, u1: 1, u2: 1
Action: 1, u1: 1, u2: 5
Action: 2, u1: 1, u2: 9
Action: 3, u1: 1, u2: 13
Action: 4, u1: 1, u2: 17
Action: 5, u1: 5, u2: 1
Action: 6, u1: 5, u2: 5
Action: 7, u1: 5, u2: 9
Action: 8, u1: 5, u2: 13
Action: 9, u1: 5, u2: 17
Action: 10, u1: 9, u2: 1
Action: 11, u1: 9, u2: 5
Action: 12, u1: 9, u2: 9
Action: 13, u1: 9, u2: 13
Action: 14, u1: 9, u2: 17
Action: 15, u1: 13, u2: 1
Action: 16, u1: 13, u2: 5
Action: 17, u1: 13, u2: 9
Action: 18, u1: 13, u2: 13
Action: 19, u1: 13, u2: 17
Action: 20, u1: 17, u2: 1
Action: 21, u1: 17, u2: 5
Action: 22, u1: 17, u2: 9
Action: 23, u1: 17, u2: 13
Action: 24, u1: 17, u2: 17
=100:
for action in range(25):#rl_algo_thres10u1 = action//5 + 1u2 = (action+1) - (u1-1)*5print(f"Action: {action+1}, u1: {u1}, u2: {u2}")
Action: 1, u1: 1, u2: 1
Action: 2, u1: 1, u2: 2
Action: 3, u1: 1, u2: 3
Action: 4, u1: 1, u2: 4
Action: 5, u1: 1, u2: 5
Action: 6, u1: 2, u2: 1
Action: 7, u1: 2, u2: 2
Action: 8, u1: 2, u2: 3
Action: 9, u1: 2, u2: 4
Action: 10, u1: 2, u2: 5
Action: 11, u1: 3, u2: 1
Action: 12, u1: 3, u2: 2
Action: 13, u1: 3, u2: 3
Action: 14, u1: 3, u2: 4
Action: 15, u1: 3, u2: 5
Action: 16, u1: 4, u2: 1
Action: 17, u1: 4, u2: 2
Action: 18, u1: 4, u2: 3
Action: 19, u1: 4, u2: 4
Action: 20, u1: 4, u2: 5
Action: 21, u1: 5, u2: 1
Action: 22, u1: 5, u2: 2
Action: 23, u1: 5, u2: 3
Action: 24, u1: 5, u2: 4
Action: 25, u1: 5, u2: 5
暂且存疑
V.实验与结果分析
B. Experimental Results




相关文章:
User Allocation In MEC: A DRL Approach 论文笔记
论文:ICWS 2021 移动边缘计算中的用户分配:一种深度强化学习方法 代码地址:使用强化学习在移动边缘计算环境中进行用户分配 目录 Ⅰ.Introduction II. MOTIVATION-A.验证假设的观察结果 II. MOTIVATION-A Motivating Example 数据驱动…...
leetcode 69. x 的平方根
可以使用二分查找法或牛顿迭代法来实现 LeetCode 问题 69. x 的平方根。下面是使用二分查找法和牛顿迭代法的 C 实现。 二分查找法 #include <iostream>class Solution { public:int mySqrt(int x) {if (x 0) return 0;int left 1, right x, ans 0;while (left <…...
基于词级ngram的词袋模型对twitter数据进行情感分析
按照阿光的项目做出了学习笔记,pytorch深度学习实战项目100例 基于词级ngram的词袋模型对twitter数据进行情感分析 什么是 N 符? N 格是指给定文本或语音样本中 n 个项目的连续序列。这些项目可以是音素、音节、字母、单词或碱基对,具体取…...
Linux-Centos-改密码(单用户登陆)
笔记一: centos7单用户修改root密码 在CentOS 7中,如果您是唯一的用户或者您确信其他用户不会登录,您可以按照以下步骤来修改root密码: 1.重启系统。 2.启动时出现引导界面时,按任意键进入GRUB菜单。 3.选择要启动的内…...
java实现OCR图片识别,RapidOcr开源免费
先看一下识别效果(自我感觉很牛逼),比Tess4J Tesseract省事,这个还需要训练,安装软件、下载语言包什么的 很费事,关键识别率不高 RapidOcr不管文字的横竖,还是斜的都能识别(代码实现…...
PCB工艺边设计准则
在PCB设计时,通常会在电路板的边缘预留一定的空间,这部分空间被称为工艺边。它有助于在生产过程中确保电路板的尺寸和形状的准确性。以使得组装时更加顺畅、便捷。而工艺边的加工,使得线路板上的元件可以精准地与设备对接,从而提高…...
CTF-NSSCTF题单[GKCTF2020]
[GKCTF 2020]CheckIN 这道题目考察:php7-gc-bypass漏洞 打开这道题目,开始以为考察反序列化,但实际并不是,这里直接用$_REQUEST传入了参数便可以利用了。这里出现了一个eval()函数,猜测考察命…...
redis的分片集群(仅供自己参考)
前言:为什么使用分片集群:因为redis的主从和哨兵机制主要是用来解决redis的高并发读的问题,还有redis的高并发的写的问题没有解决。使用分片集群就可以很好的解决redis写的问题,有多个master就可以实现并发的写。同时,…...
自动驾驶-机器人-slam-定位面经和面试知识系列01之常考公式推导(01)
李群李代数扰动bundle adjustment 这个博客系列会分为C STL-面经、常考公式推导和SLAM面经面试题等三个系列进行更新,基本涵盖了自己秋招历程被问过的面试内容(除了实习和学校项目相关的具体细节)。在知乎和牛客也会同步更新,全网…...
netty入门-5 ServerBootstrap与Bootstarp
前言 本来这篇应该紧接着说明Future和Promise。 但是考虑前文第三篇即用到了ServerBootstrap来启动一个服务器,并且我读的闪电侠netty,先写的服务器与客户端启动这部分。索性就先写出来了。主要内容来自闪电侠netty ServerBootstrap ServerBootstrap就…...
JavaEE - Spring Boot 简介
1.Maven 1.1 什么是Maven 翻译过来就是: Maven是⼀个项⽬管理⼯具。基于POM(Project Object Model,项⽬对象模型)的概念,Maven可以通 过⼀⼩段描述信息来管理项⽬的构建,报告和⽂档的项⽬管理⼯具软件。 可以理解为:Maven是一个项目管理工具…...
SwiftUI革新:Xcode UI开发的新纪元
SwiftUI革新:Xcode UI开发的新纪元 SwiftUI作为Apple推出的声明式UI框架,彻底改变了在Xcode中构建用户界面的方式。它不仅简化了代码,还提高了开发效率,并且使得UI设计更加直观和灵活。本文将深入探讨如何在Xcode中使用SwiftUI进…...
22、基于共享内存的数据结构——用十个块来提高并发性
初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的,可以在任何平台上使用。 为了提高并发性,把…...
【ffmpeg命令入门】实现画中画
文章目录 前言画中画是什么画中画的外观描述效果展示为什么要用画中画应用场景示例 使用FFmpeg添加画中画示例命令参数解释调整嵌入视频的位置调整嵌入视频的大小处理音频 总结 前言 FFmpeg 是一款强大的多媒体处理工具,广泛用于音视频的录制、转换和流处理。它不仅…...
基于 LangChain+LangGraph 来实现一个翻译项目
相信大家在看文档的时候,有时会比较苦恼,比如 AI 相关的文档都是外文,中文文档比较少,看起来会比较吃力,有的时候会看不懂,翻译软件又翻得很乱,完全看不了,今天就基于 LangChain 和 …...
javascript 如何将 json 格式数组转为 excel 表格| sheetJS
案例 // https://unpkg.com/xlsx0.18.5/dist/xlsx.full.min.js function exportXlsx(jsonData, fileName , mine null) {const workbook XLSX.utils.book_new();// 将JSON数组转换成工作表const worksheet XLSX.utils.json_to_sheet(jsonData);// 向工作簿添加工作表XLSX.…...
网页制作技术在未来会如何影响人们的生活?
网页制作技术在未来会如何影响人们的生活? 李升伟 网页制作技术在未来可能会从以下几个方面显著影响人们的生活: 1. 工作与学习方式的变革:远程办公和在线教育将更加普及和高效。通过精心制作的网页,人们能够实现更便捷的协作…...
【计算机网络】网络层——IPv4地址(个人笔记)
学习日期:2024.7.24 内容摘要:IPv4地址,分类编址,子网,无分类编址 IPv4地址概述 在TCP/IP体系中,IP地址是一个最基本的概念,IPv4地址就是给因特网上的每一台主机的每一个接口分配一个在全世界…...
c++ 学习笔记之多线程:线程锁,条件变量,唤醒指定线程
基于CAS线程加锁方式 CAS(Compare-And-Swap)和 mutex 都是用于实现线程安全的技术,但它们适用于不同的场景,具有不同的性能和复杂性。下面是对两者的区别和使用场景的详细解释: CAS(Compare-And-Swap&…...
《0基础》学习Python——第二十三讲__网络爬虫/<6>爬取哔哩哔哩视频
一、在B站上爬取一段视频(B站视频有音频和视频两个部分) 1、获取URL 注意:很多平台都有反爬取的机制,B站也不例外 首先按下F12找到第一条复制URL 2、UA伪装,下列图片中(注意代码书写格式) 3、Co…...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...
AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...
算法:模拟
1.替换所有的问号 1576. 替换所有的问号 - 力扣(LeetCode) 遍历字符串:通过外层循环逐一检查每个字符。遇到 ? 时处理: 内层循环遍历小写字母(a 到 z)。对每个字母检查是否满足: 与…...
Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
BLEU评分:机器翻译质量评估的黄金标准
BLEU评分:机器翻译质量评估的黄金标准 1. 引言 在自然语言处理(NLP)领域,衡量一个机器翻译模型的性能至关重要。BLEU (Bilingual Evaluation Understudy) 作为一种自动化评估指标,自2002年由IBM的Kishore Papineni等人提出以来,…...


