from RL_brain_dueling import DuelingDQN #导入DuelingDQN模块
from env import Scenario #导入Scenaio模块
import matplotlib.pyplot as plt #作为python的绘图库,用plt作为简写
import scipy.io as scio
import tensorflow as tf #深度学习平台
import numpy as np #它是一个由多维数组对象和用于处理数组的例程集合组成的库
import seaborn as sns #作为pyhton的一个数据可视化的库,用sns简写
# def plot_reward(reward_ws, fig):
#
# plt.figure(fig)
# plt.plot(np.arange(len(reward_ws)),reward_ws)
# plt.ylabel('hit_num')
# plt.xlabel('content_num')
def plot_rewards(rewards, fig, tag="train", save=True): #定义函数,存储到的总奖励?图像
sns.set() #制图的代码,用户快速设置seaborn的默认风格
plt.figure(fig) #创建一个画布?对象是fig,fig是?图像编号?
plt.title("average cumulative rewards") #标题,平均积累奖励
plt.xlabel('epsiodes') #
plt.plot(rewards, label='rewards') #系统在后台绘制关于奖励变动的图像,标题为“奖励”,是纵坐标为rewards值变动,横坐标就是0.1.2.3....这图会在上面的figure显示出来
plt.legend() #图列参数设置?默认?
if save:
plt.savefig("reward/rewards_curve_{}".format(tag)) #将图片存储
plt.show() #把之前在后台挥着的图像表露出来
def plot_hit_num(num, fig, name="none"): #命中率的函数、图表
sns.set() #seaborn默认风格
plt.figure(fig)
plt.title("hit numbers")
plt.xlabel('time slots')
plt.plot(num, label=name)
plt.legend() #设置图例?
def plot_loss(loss, fig): #loss,Q预测与Q网络的差值,就是目标
plt.figure(fig)
plt.plot(loss)
plt.ylabel('Loss')
plt.xlabel('training steps')
plt.show()
RL_flag = 1 #作为一个旗标量
train_flag=1
if __name__ == '__main__': #表示正式的代码入口
sc = Scenario(ap_num=5, ue_num=10, time_slots=1000, buffer_size=25, content_size=1) #将一个向量用一个简单的值表达出来
#当决定要使用Dueling DQN,就会输入各种参数,并且初始化总行动步数,奖励空间,命中空间,以及反馈的偏差空间。
#没有决定要训练时,判断是否准备好了存储的空间,并告知。
#当决定开始训练的时候,设定总回合数。设定执行回合数,每个回合中重置用户关联的F—APs以及F-AP中缓存的文件。设定这个回合的总奖励为0,以及初始化观测状态矩阵,初始化step、用户变换位置次数、cost。
#在所执行的每个回合里,要执行1000个时隙,其中,每隔一定的时隙会让各个用户变换一比位置,并查看变换位置后的命中情况,把这个情况放入命中矩阵当中。
#在每个时隙中,都会对每个用户(10个)所请求的文件是否在关键的F-APs中缓存进行判断,如果都在,则总步数加一;如果都不在,则判断是否可以进行缓存,可以则总步数加一;如果已经存满,执行DQN,选择动作,并由动作得到各种观测值,将这些存储。并判断是否总步数>20000、启用了训练并且总步数可以整除4(既当总步数上20000后,每隔4步训练一次),则开始训练,训练结果进行存储,本回合的奖励进行相加。
#只要对用户进行了一次操作,就会让总步数加一。也就是说当执行了200时隙时,就开始训练了。
#当完成了一次回合,就把这个回合的奖励进行存储,用于之后训练。
if RL_flag == 1:
RL = DuelingDQN(n_actions=sc.buffer_size*sc.relevant_num, dueling=True,
n_features=2 * (sc.relevant_num * sc.buffer_size),
learning_rate=0.001, e_greedy=0.9, reward_decay=0.9,
replace_target_iter=5000, memory_size=20000, batch_size=32,
e_greedy_increment=0.00001,
n_point= 2*(sc.relevant_num * sc.buffer_size), output_graph=False) #对DuelingDQN进行一个设置
total_steps = 1
rewards = [] #奖励
hit_num = [] #命中
loss = [] #Q估计与Q表格的差值
saver = tf.train.Saver(max_to_keep=1000) #保存和恢复变量,max_to_keep表明保存的最大checkpoint 文件数。用于神经网络训练
if train_flag == 0: #表示刚刚开始
np.random.seed(2) #随机产生2个小于1数字,其中seed是随机生成器的种子
episode = 1 #第一回合
checkpoint = tf.train.get_checkpoint_state('model/') #通过tf.train.get_checkpoint_state函数,通过checkpoint找到"model"这个文件名
if checkpoint and checkpoint.model_checkpoint_path: #model_checkpoint_path保存了最新的tensorflow模型文件的文件名
saver.restore(RL.sess, checkpoint.model_checkpoint_path) #saver.restore重新加载数据模型。tf.train.Saver与saver.restore进行参数的保存与重现
RL.epsilon = 1 #贪婪算法,为1的话,前后的选择没有衰减,从长远的分析
print('model loaded! start running!')
else:
exit('model loaded failed!')
else:
episode = 100 #设定回合数为100个回合
np.random.seed(1) #随机产生1个数字
# sc.interval_length = sc.ue_num*sc.ue_content_num
# for ep in range(episode):
for ep in range(5): #也是循环结构的一种,经常用于遍历字符串、列表,元组,字典,含义,ep依次表示range(5),即5个随机数,遍历完成后结束循环
print('ep=',ep) #将每次遍历的ep值输出
sc.episode = ep #设定系统的回合数为ep
observation = sc.reset() #调用env函数的Scenario中的reset函数,表示重置整个系统,每个F-AP以及每个F-AP内部缓存的文件,还有请求文件的概率都进行了重置
# ob[0] = observation
# observation = sc.fifo()
ep_reward = 0 #奖励为0
ep_observation = [] #这个回合的观测设置
# sum_reward = 0
ch=0 #用户变换位置的次数
# xlist = [x for x in range(sc.ap_num)]
steps = 0 #时隙为0
cost = 0 #误差
for t1 in range(sc.time_slots-1): #遍历所设定的每一个时隙
# for t1 in range(50):
# for t1 in range(5):
if t1 % sc.duration == 0: #每隔一段时间,进行一次操作
# if ep==0:
# sc.change_relevant(ch)
# else:
sc.assign_relevant(ch) #实现对用户关联的F-APs进行更新
ch+=1 #变换次数加一
hit_num_tmp = sc.hit_num(slot=t1) #设定hit_num_tmp为在t1时刻的命中情况
hit_num.append(hit_num_tmp) #给命中的矩阵时刻更新从0时刻到1000时刻,命中的情况
for t2 in range(sc.ue_num): #遍历每一个用户
if not sc.all_request[t2 * sc.time_slots + t1] in sc.ap[sc.ue[t2].relevant[0]].cache_buffer.cache: #在总的请求文件中,该用户在t1时刻请求的文件不在他关联的F-AP0这里缓存
if not sc.all_request[t2 * sc.time_slots + t1] in sc.ap[sc.ue[t2].relevant[1]].cache_buffer.cache: #也不在他关联的F-AP1这里缓存时
if not sc.ap[sc.ue[t2].relevant[0]].cache_buffer.is_full(): #判断F-AP0是否缓存文件已满
sc.ap[sc.ue[t2].relevant[0]].cache_buffer.add_cache(sc.all_request[t2 * sc.time_slots + t1]) #没有满,则缓存用户请求的该文件
elif not sc.ap[sc.ue[t2].relevant[1]].cache_buffer.is_full(): #判断F-AP1是否缓存文件已满
sc.ap[sc.ue[t2].relevant[1]].cache_buffer.add_cache(sc.all_request[t2 * sc.time_slots + t1]) #没有满,则缓存用户请求的该文件
else: #若即没有存储,又缓存文件已满的情况出现了
action = RL.choose_action(observation) #执行RL中选择动作的函数,大概率选择价值最好的,小概率随机选择
observation_, reward = sc.execute(action=action, ue_id=t2, slot=t1) #将选择的动作放入环境的execute中,获得下一个状态以及这次的收获
RL.store_transition(s=observation, a=action, r=reward/sc.ue_num, s_=observation_) #将这次的执行动作后的结果,放入存储库中,进行存储,方便之后随机抽出用于训练
if total_steps > 20000 and train_flag == 1 and total_steps % 4 == 0: #开始训练
# print('learn\n')
RL.learn() #运行学习
loss.append(RL.cost) #把误差记录在loss矩阵中
cost = RL.cost #把误差记录下来,等会打出来
# print('learn finish\n')
observation = observation_
# print(ob[t2])
# total_steps += 1
# sum_reward += reward
ep_reward += reward
steps += 1
print('--------------------')
print('Episode:{}/{}, steps:{}/10000, Reward:{}, Loss:{}'.format(ep + 1, episode, steps, ep_reward, cost))
# ep_reward.append(sum_reward)
total_steps += 1 #只要轮到一个用户进行文件请求检查时,总步数就加一
# ep_r.append(reward)
rewards.append(ep_reward) #将该回合的总收获进行存储
# reward_ws.append(ep_r)
# ep_observation.append(observation)
for k in range(sc.ap_num): #对每个F-AP进行遍历
print('ap', k, 'is', sc.ap[k].cache_buffer.cache) #将所存储的文件输出
# print(sc.all_request)
# for j in range(sc.bbu_num):
# print('bbu', j, 'is', sc.bbu[j].cache_buffer.cache)
# print(sc.all_request)
for i in range(sc.ue_num): #遍历每一个用户
sc.ue[i].log_request() #将用户请求的文件遍历出来
print(sc.ue[i].relevant) #输出用户关联的F-APs
# sc.arrangement()
# sc.calculate_delay()
sc.reboot() #分别对比4种方式的命中程度
number = sc.fifo()
sc.reboot()
number2 = sc.lru()
sc.reboot()
number3 = sc.lfu()
print(np.mean(hit_num), np.mean(number), np.mean(number2), np.mean(number3))
# for k in range(sc.ap_num):
# print('ap', k, 'is', sc.ap[k].cache_buffer.cache)
plot_hit_num(hit_num, 1, "Dueling")
plot_hit_num(number3, 1, "LFU")
plt.savefig("figures/hit_num_figure")
plt.show()
# plt.show()
if train_flag == 1: #将文件进行保存,并把loss以及奖励用图片表示出来
saver.save(RL.sess, 'model/savefiles', global_step=total_steps)
scio.savemat('reward/reward.mat', {'rewards': rewards})
# RL.plot_cost()
# print(RL.cost_his)
# plot_reward(ep_r, 2)
# plot_reward(number, 2)
plot_loss(loss, 3)
plot_rewards(rewards, 2)
# plt.show()
else: #当不绝对用Dueling DQN时的操作,对比2种方式的命中以及把F-AP的缓存文件以及用户的缓存文件输出
# np.random.seed(2)
sc.reboot()
sc.fifo()
sc.reboot()
sc.lfu()
for k in range(sc.ap_num):
print('ap', k, 'is', sc.ap[k].cache_buffer.cache)
# for j in range(sc.bbu_num):
# print('bbu', j, 'is', sc.bbu[j].cache_buffer.cache)
print(sc.all_request)
for i in range(sc.ue_num):
# sc.ue[i].log_request()
print(sc.ue[i].relevant)
上面是主函数,下面则是Dueling DQN
"""
The Dueling DQN based on this paper: https://arxiv.org/abs/1511.06581
View more on my tutorial page: https://morvanzhou.github.io/tutorials/
Using:
Tensorflow: 1.0
gym: 0.8.0
"""
#个人认为代码中关于Dueling DQN的操作时通过经常性的更新Q估计,间断性的更新Q目标
# Q目标的更新方式是直接把Q估计赋予Q目标,然后Q估计继续运作,Q目标保存不边,在运行的过程种,不断用LOSS以及梯度下降法,去对反馈回动作,去反作用于Q估计,从而让Q估计于Q目标收敛,同时,在Q估计运行一定次数后,Q目标又会再次进行更新。
#需要注意的是,强化学习都是在运行一定的回合后,有了一定的经验基础,才有了一定的学习能力,才知道哪里是有价值的,是正确的路
import numpy as np
import tensorflow as tf
np.random.seed(3) #产生3个随机序列,之后再产生3个随机序列时,则和这里一样
tf.set_random_seed(1) #
class DuelingDQN: #DQN的初始化
def __init__(
self,
n_actions, #动作
n_features, #状态?
learning_rate=0.001, #NN中learning_rate学习速率
reward_decay=0.9, #奖励衰减函数
e_greedy=0.9, #贪婪算法
replace_target_iter=200, #更新Q现实网络参数的步骤数
memory_size=500, #记忆库尺寸
batch_size=32, #每次从记忆库中取的样本数量
e_greedy_increment=None,
output_graph=True, #可以看流程图
dueling=True, #用于判别是否使用了dueling DQN
n_point=20,
sess=None,
):
self.n_actions = n_actions #动作
self.n_features = n_features #观测的相关特诊?
self.lr = learning_rate #学习速率
self.gamma = reward_decay #奖励衰减
self.epsilon_max = e_greedy #贪婪算法?
self.replace_target_iter = replace_target_iter #隔多少步后将target net 的参数更新为最新的参数,replace_target_iter为更新target network的步数,防止target network和eval network差别过大
self.memory_size = memory_size #整个记忆库得容量
self.batch_size = batch_size #样本数量,在随机降低梯度中会用到
self.n_points=n_point
self.epsilon_increment = e_greedy_increment #表示不断扩大epsilon,以便有更大的概率拿到好的值
self.epsilon = 0 if e_greedy_increment is not None else self.epsilon_max ## 是否开启探索模式, 并逐步减少探索次数
self.dueling = dueling # decide to use dueling DQN or not 是否决定使用DQN
self.learn_step_counter = 0 #用这个记录学习了多少步,可以让self.epsilon根据这个步数来不断提高
self.memory = np.zeros((self.memory_size, n_features*2+2))#行(高度)为存储记忆的数量,列为(observation, action, reward, observation_)的长度,即存储的整体容量
self._build_net() #建立DQN
t_params = tf.get_collection('target_net_params') #该函数可以用来获取key集合中的所有元素,返回一个列表。返回具有给定名称的集合中的值列表(目标Q),提取 target_net 的参数
e_params = tf.get_collection('eval_net_params') #该函数可以用来获取key集合中的所有元素,返回一个列表。(估计Q),提取 eval_net 的参数
# 将eval_network中每一个variable的值赋值给target network的对应变量
self.replace_target_op = [tf.assign(t, e) for t, e in zip(t_params, e_params)] # 这个函数的功能主要是把t的值变为e,t和e都是从zip中按顺序排出,应该是DQN中把更新的值去代替原来的,替换变量
if sess is None: #绘图?全局初始化变量?
self.sess = tf.Session() #操作指令
self.sess.run(tf.global_variables_initializer()) #对全局进行一个初始化 #用于记录# 记录所有 cost 变化
else:
self.sess = sess
if output_graph: #输出图片?
self.merged_loss = tf.summary.merge([self.aa]) #tf.summary.merge一般选择要保存的信息,其中还需要用到tf.get_collection()函数,保存aa,即保存损失函数
self.merged_W = tf.summary.merge([self.log_Q1,self.log_Q2,self.log_Q3,self.log_Q4,self.log_Q5]) #同理用于保存信息,记录Q表格中的各种信息
self.log=1
self.writer = tf.summary.FileWriter("logs/", self.sess.graph) #指定一个文件用来保存图,这里用于看流程图。。
print('log created!')
else:
self.log=0
self.cost_his = []
def _build_net(self): #构建DQN
def build_layers(s, c_names, n_l1, w_initializer, b_initializer): #initializer初始化,即输入各种初始化,s是名称,c_name是图片存储位置,用于在一定步数之后更新target network,w..是权值,b..是偏置向量
with tf.variable_scope('l1'): #用于定义创建变量,来操作文件上下层管理器? 在l1这个变量域中
w1 = tf.get_variable('w1', [self.n_features, n_l1], initializer=w_initializer, collections=c_names) #定义一个tensorflow变量,矩阵行为feature,竖为l1,数值从w_initializer中出来,图形集中在collection中,权值矩阵
b1 = tf.get_variable('b1', [1, n_l1], initializer=b_initializer, collections=c_names) #初始化偏置向量,形状为[1,神经元个数],通过下面的函数看出,#n_l1为network隐藏层神经元的个数
l1 = tf.nn.relu(tf.matmul(s, w1) + b1) #线性整流函数(Rectified Linear Unit, ReLU),又称修正线性单元,用于卷积神经网络中的第二个步骤,tf.nn.relu()函数的目的是,将输入小于0的值幅值为0,输入大于0的值不变,tf.matmul中s与w相乘
# with tf.variable_scope('l2'):
# w2 = tf.get_variable('w2', [n_l1, n_l1], initializer=w_initializer, collections=c_names)
# b2 = tf.get_variable('b2', [1, n_l1], initializer=b_initializer, collections=c_names)
# l2 = tf.nn.relu(tf.matmul(l1, w2) + b2)
# with tf.variable_scope('l3'):
# w3 = tf.get_variable('w3', [n_l1, n_l1], initializer=w_initializer, collections=c_names)
# b3 = tf.get_variable('b3', [1, n_l1], initializer=b_initializer, collections=c_names)
# l3 = tf.nn.relu(tf.matmul(l2, w3) + b3)
if self.dueling: #决定使用duling DQN时候
# Dueling DQN
with tf.variable_scope('Value'): #创建变量,下面都是在这个变量领域中,这是duling DQN中的V,即价值那一块,是数值部分
wV = tf.get_variable('wV', [n_l1, 1], initializer=w_initializer, collections=c_names) #构建变量,这里是全职向量
bV = tf.get_variable('bV', [1, 1], initializer=b_initializer, collections=c_names) #同理,构建变量,这里是偏置向量
self.V = tf.matmul(l1, wV) + bV #把价值用等式求出
with tf.variable_scope('Advantage'): #创建变量,下面都是在这个变量领域中,这是duling DQN中的另一部分,用于判断决策是否好
wA = tf.get_variable('wA', [n_l1, self.n_actions], initializer=w_initializer, collections=c_names)
bA = tf.get_variable('bA', [1, self.n_actions], initializer=b_initializer, collections=c_names)
self.A = tf.matmul(l1, wA) + bA
with tf.variable_scope('Q'): #合并V和A,为了不让A直接学成Q,减去了一个A的均值(相当于一种约束,让A是所有值和为零)
out = self.V + (self.A - tf.reduce_mean(self.A, axis=1, keep_dims=True)) # Q = V(s) + A(s,a),为了防止V等于0时候,Q就等于A了,所以要减去一个值,tf.reduce_mean()函数是计算平均值的
else: #当不适用duling DQN时,则采用DQN算法
with tf.variable_scope('Q'): #不使用duling DQN时候,就直接用DQN计算法则
w2 = tf.get_variable('w2', [n_l1, self.n_actions], initializer=w_initializer, collections=c_names)
b2 = tf.get_variable('b2', [1, self.n_actions], initializer=b_initializer, collections=c_names)
out = tf.matmul(l1, w2) + b2
return out #结束
# ------------------ build evaluate_net ------------------
self.s = tf.placeholder(tf.float32, [None, self.n_features], name='s') # input在神经网络构建graph的时候在模型中的占位,相当于提前站空前,状态等各种参数的构建
self.q_target = tf.placeholder(tf.float32, [None, self.n_actions], name='Q_target') # for calculating loss 目标Q值的构建
with tf.variable_scope('eval_net'): #创建一个变量,下面所有函数在这个变量内,估计网络
c_names, n_l1, w_initializer, b_initializer = \
['eval_net_params', tf.GraphKeys.GLOBAL_VARIABLES], self.n_points, \ #??????tf.GraphKeys收集所有的图片,并且在后面方便调用,而GLOBAL_VARIABLES则代表默认收集了所有的变量对象。
tf.random_normal_initializer(0., 0.3), tf.constant_initializer(0.1) # config of layers tf.random_normal_initializer(0., 0.3)表示正态分布产生张量的初始化器,均值为0,方差为0.3,给权值赋予了正态分布的情况。tf.constant_initializer表示所有的偏差都是0.1
self.q_eval = build_layers(self.s, c_names, n_l1, w_initializer, b_initializer) #构建估计Q的duling DQN了
#self.max_q=np.max(self.q_eval)
#self.min_q=np.min(self.q_eval)
self.log_Q1 = tf.summary.scalar('Q_max', (tf.reduce_max(self.q_eval))) #tf.summary.scalar用来显示标量信息,名称是Q_max,后面则是实数标量值,取最大
self.log_Q2 = tf.summary.scalar('Q_min', (tf.reduce_min(self.q_eval))) #名称是Q_min,后面则是实数标量值,取最小
self.log_Q3 = tf.summary.scalar('Q_mean', (tf.reduce_mean(self.q_eval))) #取平均
self.log_Q4 = tf.summary.scalar('Q_d', (tf.reduce_max(self.q_eval)-tf.reduce_min(self.q_eval))) #取最大与最小的相差
self.log_Q5 = tf.summary.scalar('Q_def', (tf.reduce_max(self.q_eval) - tf.reduce_min(self.q_eval))/tf.reduce_mean(abs(self.q_eval))) #最大与最小的差去除以平均
#self.log_Q2 = tf.summary.scalar('Q2', self.min_q)
with tf.variable_scope('loss'): #LOSS,来源于Q目标与Q估计的相减,最后再反馈回Q目标,去降低.损失函数的存在,是告诉我们,当训练出错时候,可以告知错误,错误之后就是损失函数,这个可以再重新反向传播,从而训练网络
self.loss = tf.reduce_mean(tf.squared_difference(self.q_target, self.q_eval)) #让两个张量,即Q目标与Q估计做差的平方,得到的结果取平均,这个平均可以有效降低维度
self.aa=tf.summary.scalar('loss', self.loss) #让aa设置为LOSS的标量信息
with tf.variable_scope('train'): #定义一个“训练”的变量,这就是梯度下降
self._train_op = tf.train.AdamOptimizer(self.lr).minimize(self.loss) #lr是学习速率,tf.train.AdamOptimizer自适应矩估计,是一个寻找全局最优点的优化算法,引入了二次方梯度校正。这里是优化器,对应的是梯度下降函数那块
# self._train_op = tf.train.RMSPropOptimizer(self.lr).minimize(self.loss)
# ------------------ build target_net ------------------ 构建Q目标
self.s_ = tf.placeholder(tf.float32, [None, self.n_features], name='s_') # input 在神经网络构建graph的时候在模型中的占位,相当于提前站空前,状态等各种参数的构建,None, self.n_features表示列是n_features,行不定
with tf.variable_scope('target_net'): #定义一个变量,名称叫目标Q网络
c_names = ['target_net_params', tf.GraphKeys.GLOBAL_VARIABLES] #将这里的流程图等打包放入taget_net_params中
self.q_next = build_layers(self.s_, c_names, n_l1, w_initializer, b_initializer) #构建Q目标的dueling DQN了
def store_transition(self, s, a, r, s_): #存储记忆
if not hasattr(self, 'memory_counter'): #hasattr() 函数用于判断对象是否包含对应的属性。判断self是否有memory_counter,有则返回TRUE,没有则返回FALSE
self.memory_counter = 0 #如果没有,则设置一个,为0,存储记忆的回合数
transition = np.hstack((s, [a, r], s_)) #将所有消息进行整合堆叠,是这个回合的状态,动作,奖励,以及下一个状态存储
# 总 memory 大小是固定的, 如果超出总大小, 旧 memory 就被新 memory 替换
index = self.memory_counter % self.memory_size #%相除返回余数
self.memory[index, :] = transition #index为索引,在index这一行中,把所有整合信息放入记忆库中,替换原来的存储,如果没有,就相当于赋值了
self.memory_counter += 1 #记忆次数加一
def choose_action(self, observation): #选择动作
# 根据observation(state)选行为
# 使用eval network选出state下的行为估计
# 将observation的shape变为(1, size_of_observation),行向量变为列向量才能与NN维度统一
observation = observation[np.newaxis, :] #np.newaxis表示增加维度,目的是为了之后可以进行权值分配
if np.random.uniform() < self.epsilon: #np.random.uniform生成均匀分布的随机数,默认0-1,大概率选择actions_value最大下的动作,小概率选择随机动作
if (self.log): #已有流程图的情况下
actions_value,summary = self.sess.run([self.q_eval,self.merged_W], feed_dict={self.s: observation}) #动作价值是由Q估计中的各种Q的最大最小,差值等决定出来的
self.writer.add_summary(summary, global_step=self.learn_step_counter) #add_summary()方法将训练过程数据保存在filewriter指定的文件中,global_step代表全局步数,也代表学习了多少步
else:
actions_value= self.sess.run(self.q_eval, feed_dict={self.s: observation}) #没有流程图的情况下,说明没有运行结束,所以单纯建立Q估计,填入的参数就是observation
action = np.argmax(actions_value) #np.argmax返回一个numpy数组中最大值的索引值,这就是Q-learning的行动决策,选择动作价值进行行动。
else: #如果选不出动作价值最大的
action = np.random.randint(0, self.n_actions) #np.random.randint函数的作用是,返回一个随机整型数,即随机选择动作
return action
def learn(self): #学习部分
if self.learn_step_counter % self.replace_target_iter == 0: #代表了每隔一段所设定的需要更新目标Q的时间
self.sess.run(self.replace_target_op) #当时间达到,或者说运行次数达到后,便进行一次更新目标Q,把Q估计赋予Q目标
print('\ntarget_params_replaced\n') #告知我们,已经对目标Q进行了更新
sample_index = np.random.choice(self.memory_size, size=self.batch_size) #样本的索引,从500个中,随机的抽出batch,即32个样本出来,生成一个随机的样本(数)
batch_memory = self.memory[sample_index, :] #定义一个样本的容量,抽取记忆表self.memory中前sample_index行(容量)
# print(tmp)
#print(tmp[0])
#batch_memory,range,min=self.autoNorm(tmp)
#print(batch_memory)
q_next = self.sess.run(self.q_next, feed_dict={self.s_: batch_memory[:, -self.n_features:]}) # next observation...q_next由目标值网络用记忆库中倒数n_features个列(observation_)的值做输入
q_eval = self.sess.run(self.q_eval, {self.s: batch_memory[:, :self.n_features]}) #上下都是运行2个神经网络,参数都是根据样本里面的各种参数进行输入决定...q_eval由预测值网络用记忆库中正数n_features个列(observation)的值做输入
q_target = q_eval.copy() #让Q目标对Q估计进行一个复制,父目录复制,子目录会随原始改变而变化
batch_index = np.arange(self.batch_size, dtype=np.int32) #设定一个被抽取样本的索引,从0开始,每个样本下标
eval_act_index = batch_memory[:, self.n_features].astype(int) #返回一个长度为32的动作列表,从记忆库batch_memory中提取,记录每个样本执行的动作
reward = batch_memory[:, self.n_features + 1] #返回一个长度为32奖励的列表,提取出记忆库中的reward,记录每个样本动作的奖励
q_target[batch_index, eval_act_index] = reward + self.gamma * np.max(q_next, axis=1) #对Q目标的表格进行更新,生成每个样本中q值对应动作的更新,即生成的q现实,
# 假如在这个 batch 中, 我们有2个提取的记忆, 根据每个记忆可以生产3个 action 的值:
# q_eval =[[1, 2, 3],[4, 5, 6]], 另q_target = q_eval.copy()
# 然后根据 memory 当中的具体 action 位置来修改 q_target 对应 action 上的值:
# 比如在:记忆 0 的 q_target 计算值是 -1, 而且我用了 action 0;忆1的 q_target 计算值是-2, 而且我用了 action 2:
# q_target =[[-1, 2, 3],[4, 5, -2]]
# 所以 (q_target - q_eval) 就变成了:[[(-1)-(1), 0, 0],[0, 0, (-2)-(6)]]
# 最后我们将这个 (q_target - q_eval) 当成误差, 反向传递会神经网络
# 所有为 0 的 action 值是当时没有选择的 action, 之前有选择的 action 才有不为0的值.
# 我们只反向传递之前选择的 action 的值
if(self.log): #当有流程图时候
_, summary,self.cost = self.sess.run([self._train_op,self.merged_loss ,self.loss], #根据上面注释,将变换后的Q目标去更新LOSS以及用于训练当中,并进行存储到summary中,从而反馈给动作
feed_dict={self.s: batch_memory[:, :self.n_features],
self.q_target: q_target})
self.writer.add_summary(summary, global_step=self.learn_step_counter) #将信息整合进文件夹中,从而在流程图可以看到
else:
_, self.cost = self.sess.run([self._train_op, self.loss],
feed_dict={self.s: batch_memory[:, :self.n_features],
self.q_target: q_target})
self.cost_his.append(self.cost) #记录 cost 误差
self.epsilon = self.epsilon + self.epsilon_increment if self.epsilon < self.epsilon_max else self.epsilon_max #?#每调用一次learn,降低一次epsilon,即行为随机性
self.learn_step_counter += 1 #学习步数加一
def plot_cost(self): #画图,画出误差的图像
import matplotlib.pyplot as plt # #作为python的绘图库,用plt作为简写
if self.log: #有流程图时
self.writer.close()
#self.writer.add_summary(rs, self.learn_step_counter)
plt.plot(np.arange(len(self.cost_his)), self.cost_his)
plt.ylabel('Cost')
plt.xlabel('training steps')
plt.show()
下面是外部环境
import numpy as np #可以很自然地使用数组和矩阵,NumPy包含很多实用的数学函数,涵盖线性代数运算、傅里叶变换和随机数生成等功能
class UE: #定义一个类别,UE指用户
def __init__(self, ue_id, time_slots=1000, content_size=1, relevant_num=2): #哪个调用了这个类别,哪个就是self,或者说self就是调用得对象。__init__() 是类的初始化方法;它在类的实例化操作后 会自动调用,不需要手动调用;具体设定是调度时间为1000s,每个用户关联2个F-APS,索要内容为1个文件
self.ue_id = ue_id #用户ID
self.time_slots = time_slots #调度时隙
self.content_size = content_size #用户内容??
self.relevant = np.zeros(relevant_num, dtype=np.int_) #关联的矩阵,用于判定在哪个F-APS中
self.movement = np.zeros((1000, relevant_num), dtype=np.int_) #用户移动,用户随机变动矩阵,数据类型为整数长形
self.relevant_num = relevant_num #设定每个用户都固定关联2个F-APS
self.request = np.zeros(self.time_slots, dtype=np.int_) #用户需求
def log_request(self): #定义函数名称为log_request,用户请求的文件?
print('request_array=', self.request) #将用户所要请求的文件一一列举出来
class AP: #接入点,与用户相接入的第一个F-AP
def __init__(self, ap_id, buffer_size=25, content_size=1):
self.ap_id = ap_id #对于用户来说,第一个所与他连接的电脑
self.buffer_size = buffer_size #系统可以缓存的文件
self.cache_buffer = Buffer(self.buffer_size) #系统缓存的文件
self.cache_buffer.zeros_init_buffer() #对系统容量进行一个预先分配?
self.content_size = content_size #文件的大小,横为1,即假设文件大小一致
class AP2: #与用户相接入的第二个F-AP
def __init__(self, ap_id, buffer_size=25, content_size=1):
self.ap_id = ap_id
self.buffer_size = buffer_size
self.cache_buffer = Buffer(self.buffer_size)
# self.cache_buffer.zeros_init_buffer()
self.content_size = content_size
class Buffer: #定义一个新的类
def __init__(self, buffer_size=25):
self.cache = [] #缓存文件为空集
self.current_size = 0 #此时缓存的文件数量
self.buffer_size = buffer_size #系统可以缓存的文件个数
def is_empty(self): #如果系统为空的
return len(self.cache) == 0 #返回一个长度为0,即缓存文件的长度是0
def is_full(self): #如果系统判断为满的
return len(self.cache) == self.buffer_size #返回一个长度为系统可以缓存的最大文件数,先赋予系统缓存文件的长度是这个系统所能容纳的最大长度
def clear(self): #清除已经缓存的文件
self.cache = [] #将缓存的文件变为空集
self.current_size = 0.0 #此时系统中的文件个数为0
def delete_cache(self): #删除缓存的文件
if not self.is_empty(): #判断,如果对象不是空的
self.cache = self.cache[1:] #把缓存的文件的第一个删除
self.current_size -= 1 #缓存个数减少一个
else:
print('empty!!!') #此时判断为空集,直接打出“empty”
return -1 #返回-1
def add_cache(self, content): #如果要增加文件量
if not self.is_full(): #首先先判断对象是否已经是满的,即是否缓存文件达到能缓存的最大值
self.cache.append(content) #在缓存的末尾增加一个元素,也就是文件
self.current_size += 1 #缓存个数增加1个
else:
self.delete_cache() #但判断其是满的时候,回到delete函数,进行删除
self.add_cache(content) #接下来在进行增加文件
def resort(self, content): #手段,对文件处理的手段
self.cache.remove(content) #对缓存的文件进行移除。但这和delete有什么区别?
self.cache.append(content) #对缓存的文件进行增加,增加在当前已经缓存的文件末尾
def update(self, change_id=0, content=0):#用于更新基站缓存,替换或者增加,用请求更新(看不懂)
if not change_id > self.current_size:#chang_id比当前的缓存长度短,那就替换掉对应id的内容
self.cache[change_id] = content
else: #chang_id比当前存在的缓存长度长,若系统缓存未满就依次存放,满了就替换最久的
self.add_cache(content)
def zeros_init_buffer(self): #归零?初始化
self.cache = np.zeros(self.buffer_size, dtype=int) #给系统预先分配系统所能缓存文件的容量
self.current_size = self.buffer_size #告知现在缓存的文件个数就是系统最大的容量
# print('init buffer is', self.cache, 'and size is', self.buffer_size)
class Scenario: #初始化定义
def __init__(self, ap_num=5, ue_num=10, time_slots=1000, buffer_size=25, content_size=1):#__init__,两个下划线开头的init具备私有的含义,这是不能在类的外部被使用或者访问,第一个参数必须是self
self.ap_num = ap_num #设置了AP的数量
self.ue_num = ue_num #设置了用户的数量
self.time_slots = time_slots #总的时间,每1秒会有一次调度,总共调度1000次
self.buffer_size = buffer_size #每个AP所能缓存的最大文件设置
self.content_size = content_size #设置每个文件的大小,统一为1,即是大家文件都一样
self.episode = 1 #迭代次数,回合数
self.relevant_num = 2 #每个用户关联的电脑数为2
self.duration=10 #作用与用户变化的时刻
self.ap_hit_num = np.zeros(ue_num, dtype=np.float_) #ap的命中文件数量,以用户的个数为最大命中次数
self.zipf=1.1 #公式
self.ob_steps=125 #总状态?
# self.all_request = np.random.zipf(1.3,size=self.time_slots*self.ue_num)
# self.bbu = {}
self.all_request = np.zeros(self.time_slots*self.ue_num, dtype=int) #总的用户数去乘上时间就是所有要求的数量,这是假定了每个用户每次就需求一个文件
self.request_frequency1 = np.zeros((self.relevant_num*self.buffer_size), dtype=np.int_) #每个用户关联的数量*系统能存的最大容量,这应该是对于用户来说能请求的最大文件数,每个用户每次请求1个,那么请求的次数,最大就是其关联F-APs的容量
# self.request_frequency2 = np.zeros((self.ue_num), dtype=np.int)
self.ap = {} #设定了用户的集合
self.ue = {} #{}与()的区别是什么?
# self.init_bbu()
self.init_ap() #定义函数?
self.init_ue() #调用函数?
self.request_sequence() #请求文件的次序
self.change_relevant() #用户变化位置后,与之相关联的F-APs
self.request_sorted = np.zeros((2, self.relevant_num*self.buffer_size), dtype=np.int_) #需求的分类,应该是对应用户连接的2个F-APs所设置的,但是为什么是相关数量*缓存容量,并且数据类型定义为整数
self.observation = np.zeros(2 * (self.relevant_num*self.buffer_size), dtype=np.float_) #????
# self.observation = np.zeros(1 * (self.relevant_num * self.buffer_size), dtype=np.float)
def init_ap(self): #定义每一个F-AP
for i in range(self.ap_num):
self.ap[i] = AP(ap_id=i, buffer_size=self.buffer_size, content_size=self.content_size)
def init_ap2(self): #每一个F-AP都可以去变成用户所连接的另一个F-AP,但是这样设置是否会出现一个用户同时连2次相同的F-APs,这与上面就是少了预先分配,但是有什么不同?
for i in range(self.ap_num):
self.ap[i] = AP2(ap_id=i, buffer_size=self.buffer_size, content_size=self.content_size)
def init_ue(self): #定义用户的函数
# n = random.randint(1, 10)
# np.random.seed(3)
# xlist = [x for x in range(self.ap_num)]
xlist = list(range(self.ap_num)) #list是创建列表,range是创建一个整数列表,一般用在for中,从0开始一直到ap_num-1
for i in range(self.ue_num): #让i=每个用户
self.ue[i] = UE(ue_id=i, time_slots=self.time_slots, content_size=self.content_size, relevant_num=self.relevant_num) #表达UE的设置概念,时隙、文件大小,每个用户关连的F-AP固定为2,对上面的函数进行调用,并设置好参数
self.ue[i].relevant = np.random.choice(xlist, replace=False, size=self.relevant_num) #用户随机选择与之关联F-APs,用np.random.choice表示从数组中随机抽出样本,从xlist中随机抽出数字,并组成size所设置的2个,即抽2个随机数,replace中,ture表示可以取相同,false表示不可取相同数字。
# print(xlist) 上面就是随机化与用户相连接的F-APs,是初始情况
# self.ue[i].request = np.random.zipf(1.3, size=self.time_slots)
# print(self.ue[i].relevant)
def change_relevant(self): #每过一段时隙后,用户变换位置所连接的F-APs可能发送变化所设置的函数
# xlist = [x for x in range(self.ap_num)] #[0,1,2,3,4]
xlist = list(range(self.ap_num)) #同上,建立一个列表,列表里面是从0到ap_num-1的数字排序
for i in range(self.ue_num): #定义i经历了每个用户序列,从而达到让每个用户都进行变化
for ch in range(self.time_slots//self.duration): #设置了变化的次数
self.ue[i].movement[ch] = np.random.choice(xlist, replace=False, size=self.relevant_num) #让每个用户的每次变化存储到movement这个向量中,从而完成对用户变化位置的一个记录
def assign_relevant(self,ch): #把上面经过移动后,与用户相关联的F-APs替换成目前用户相关联的F-APs
# xlist = [x for x in range(self.ap_num)]
for i in range(self.ue_num): #对每一个用户进行一个遍历,从而完成对每一个用户信息的修改
self.ue[i].relevant = self.ue[i].movement[ch] #通过这里,完成对用户移动后关联的新F-APs的修改
def request_sequence(self): #用户请求的次序?看不懂,请求序列。用于齐夫分布
# self.all_request = np.random.zipf(1.3, self.time_slots*self.ue_num)
for i in range(self.ue_num): #[0,10) 遍历每一个用户,对每一个用户的信息都进行修改
self.ue[i].request = np.random.zipf(self.zipf, self.time_slots)*(i+1) #用户请求满足齐夫分布,设定参数为1.1,返回数组形状为1000个点,即采样1000个点,后面为啥乘(i+1),是否是因为齐夫分布的公式让它乘的?????
self.all_request[i*self.time_slots:(i+1)*self.time_slots] = np.copy(self.ue[i].request ) #copy是拷贝,深拷贝,这里的改变不会影响上面,以不同调度时隙进行划分看总请求,相当于说,每次调度时隙,都可以重新计算用户的请求[:]表示从前面都后面一次列出
# n = random.randint(1, 10)
# np.random.seed(1)
# self.all_request=np.random.zipf(1.3, self.time_slots*self.ue_num)
def reset(self): #初始化所有
for j in range(self.ap_num): #让j去遍历每一个F-AP,从而完成对其的设置
self.ap[j].cache_buffer.clear() #让每个F-AP去运行上面的clear函数,从而完成对容量的清除,但是调用函数是这样用的吗?
# self.init_bbu()
self.init_ap() #重新定义每一个F-AP,去设置他的容量这种参数
# self.init_ue()
self.observe(step=0,ue_id=0) #初始化观测,相当于第0个时隙观测,用户为0个
return np.array(self.observation) #返回一个数组
def reboot(self): #重新启动F-AP
for j in range(self.ap_num): #让j遍历每一个F-AP
self.ap[j].cache_buffer.clear() #让每个F-AP去运行上面的clear函数,从而完成对容量的清除
self.init_ap2() #执行函数,对这个进行参数设置,但是与上面不同在哪?为什么这样?对比算法,与其他算法对比。
def hit_num(self,slot): #命中
self.ap_hit_num = np.zeros(self.ue_num, dtype=np.float_) #对命中的容量,预先分配容量,可以命中的最大值就是总体用户,也就是说,每个用户都被命中
for i in range(self.ue_num): #对每个用户进行遍历
ap_relevant = self.ue[i].relevant #对F-AP来说,其关联的ue设置,直接设置一个参量,对关联考察,进入if查看情况
if self.ue[i].request[slot] in self.ap[ap_relevant[0]].cache_buffer.cache: #判断ue的请求文件是否在用户关联的其中一个F-AP缓存的其中。(看不懂这里的操作)
self.ap_hit_num[i] = 1 #命中数加1
else:
if self.ue[i].request[slot] in self.ap[ap_relevant[1]].cache_buffer.cache: #判断ue的请求是否在用户关联的另一个F-AP缓存的其中
self.ap_hit_num[i] = 1 #命中+1
tmp = 0 #设置参量,便于观察总的命中多少
for l in range(self.ue_num): #对每个用户进行遍历
tmp += self.ap_hit_num[l] #让每个用户的命中个数相加
return tmp #返回一个总体命中数
def action_transaction(self, action=0): #行动决策,0是一个默认值,具体要看输入的参数
action1 = self.request_sorted[0][action] #去取要求的第一行的元素,即索引
relevant_id = action1 // self.buffer_size #返回商的整数部分,向下取整
location_id = action1 % self.buffer_size #取模,返回一个返回除法的余数
return relevant_id, location_id #表达一个是用户相关的哪个F-AP,一个是在这个F-AP中的哪个位置
def execute(self, action, ue_id, slot): #执行动作
relevant_id, location_id = self.action_transaction(action) #将上面函数的返回值赋予下面此处,从而确定与用户相关的F-AP的id,以及用户的位置
ap_id = self.ue[ue_id].relevant[relevant_id] #赋予用户相关联的F-AP的序列号
# if interval_id<self.time_slots-6:
# reward2 = self.ap_hit_rate(interval_id + 1)
# reward3 = self.ap_hit_rate(interval_id+2)+self.ap_hit_rate(interval_id+3)+self.ap_hit_rate(interval_id+4)+self.ap_hit_rate(interval_id+5)+self.ap_hit_rate(interval_id+6)
# reward1 = reward2+0.2*reward3
# else:
# reward1 = self.ap_hit_rate(interval_id + 1)
reward1 = self.hit_num(slot + 1) #奖励,奖励的含义是下一次命中反馈得到奖励
self.ap[ap_id].cache_buffer.update(change_id=location_id, content=self.ue[ue_id].request[slot]) #让用户关联的F-AP进行文件更新
# print(self.ap[ap_id].cache_buffer.cache)
self.observe(step=slot, ue_id=(ue_id+1)%self.ue_num) #时隙变成这个,并且进行下一个用户的,执行动作是获得下一个时隙的各个状态
# self.observe(step=interval_id, ue_iid=ue_id)
return np.array(self.observation), reward1 #执行动作,返回状态与奖励
def observe(self, step, ue_id): #状态,还是观察总存储文件?
"""
step: 时隙索引
ue_id: 用户的ID
"""
ob_steps = self.ob_steps #总的状态值
self.request_frequency1 = np.zeros((self.relevant_num * self.buffer_size), dtype=np.int_) #定义了用户所能请求的最大容量,以及其在这个容量内请求的次数。向量
self.request_sorted = np.zeros((2, self.relevant_num * self.buffer_size), dtype=np.int_) #定义了,第0行是请求文件索引,第1行是请求概率
if step < ob_steps: #执行的状态小于总状态值时,即一定要执行这么多词
past_request = np.zeros(self.ue_num * (step)) #过去的请求的文件
for m in range(self.ue_num): #遍历每一个用户
past_request[m*(step):(m+1)*(step)] = np.copy(self.ue[m].request[0:step]) #让过去请求的文件进行一个总和,放在同一个矩阵中
for i in range(self.relevant_num): #遍历用户相关联的F-AP
for j in range(len(self.ap[self.ue[ue_id].relevant[i]].cache_buffer.cache)): #遍历该用户的F-AP的缓存文件
for k in range(self.ue_num * (step)): #在上一个循环的基础上,锁定该用户,并遍历用户的每一个时隙(请求频率是从总体用户层面上看一个文件请求的次数吧)
# if self.ap[i].cache_buffer.cache[j] == self.all_request[k]:
if self.ap[self.ue[ue_id].relevant[i]].cache_buffer.cache[j] == past_request[k]: #如果该用户所关联的F-AP的内容与过去请求的文件(分别与过去的每一个时隙中,所有用户所请求的文件进行对比)相同
self.request_frequency1[i*self.buffer_size+j] += 1 #请求的频率加1,i*self.buffer_size决定了是哪个F-AP,j则决定了是这个F-AP中的第几个序列,就可以看出哪个文件请求频率高,也是从总体的层面上,去看一个文件的请求次数、
else:
past_request = np.zeros(self.ue_num * ob_steps) #此时,执行的次数要大于总状态值了,让过去的请求等于空集
for m in range(self.ue_num): #遍历每一个用户
past_request[m * (ob_steps):(m + 1) * (ob_steps)] = np.copy(self.ue[m].request[step-ob_steps:step]) #把每个用户的请求总和起来,step-ob_steps好像有问题?
for i in range(self.relevant_num): #遍历每一个相关联的F-AP
for j in range(len(self.ap[self.ue[ue_id].relevant[i]].cache_buffer.cache)): #遍历每一个用户关联的每一个F-AP中所储存的每一个文件,其中len是返回长度的意思
for k in range(self.ue_num * ob_steps): #遍历所有用户的调度
if self.ap[self.ue[ue_id].relevant[i]].cache_buffer.cache[j] == past_request[k]: #查看每一个用户的每一个F-AP中的各个文件是否等于所有用户请求的文件。(这里是不是都点非常规了,这样的话就是看只观察总体命中的情况了)
self.request_frequency1[i*self.buffer_size+j] += 1 #从总体上查看一个文件的请求次数,但前提是已经缓存于系统中的。
tmp_a = sorted(enumerate(self.request_frequency1), key=lambda x: x[1]) #enumerate创建索引,有多种用法,sort是对迭代完的对象进行一个排序,key=lambda x: x[1]代表enumerate中出来的索引以及内容从小到大进行排序
for p in range(len(self.request_frequency1)): #对所有缓存文件数量进行一个遍历
self.request_sorted[0][p] = tmp_a[p][0] #让sort中的第一行作为文件的记录,enumerate是纵向
self.request_sorted[1][p] = tmp_a[p][1] #让sort中的第二行作为文件申请频率的记录,记录为从小到达
for m in range(len(self.request_frequency1)): #对缓存文件数量进行一个遍历,一个循环
self.observation[2 * m] = self.request_sorted[1][m] / ob_steps #将请求次数去除以总请求次数,进行归一化处理,随后放入observation中的偶数项中,作为观察
def fifo(self): #用户请求文件时,其关联的F-AP是没有该文件后,向上级请求从而增加该文件的过程
number = np.zeros(self.time_slots, dtype= float)
judge=0
ch=0 #用户变换位置的次数
for i in range(self.time_slots): #遍历每一次调度时隙,即总共请求次数,对于for,谁前谁后,谁在谁中间怎么看?
number[i] = self.hit_num(slot=i) #将这个调度时隙中,命中的次数赋予number中
if i % self.duration == 0: #没过一个duration的调度间隔后,对用户周围的F-AP进行一个变动
self.assign_relevant(ch) #变换每个用户关联的F-AP
ch+=1 #变换次数增加
print(self.ue[0].relevant) #将一个用户的关联列出来?
for j in range(self.ue_num): #遍历每一个用户
if not self.all_request[j*self.time_slots+i] in self.ap[self.ue[j].relevant[0]].cache_buffer.cache: #该用户的请求文件不在其关联的其中F-AP中有缓存,则往下
if not self.all_request[j*self.time_slots+i] in self.ap[self.ue[j].relevant[1]].cache_buffer.cache: #如果该用户的请求文件不在其关联的编号为0的F-AP中有缓存,则往下
if not self.ap[self.ue[j].relevant[0]].cache_buffer.is_full(): #该用户关联的其中一个F-AP缓存的文件容量没有达到F-AP最大的缓存容量
self.ap[self.ue[j].relevant[0]].cache_buffer.add_cache(self.all_request[j*self.time_slots+i]) #把用户请求的文件加到这个F-AP中
elif not self.ap[self.ue[j].relevant[1]].cache_buffer.is_full(): #那如果该用户请求的文件不再编号为1的F-AP中有缓存,则往下
self.ap[self.ue[j].relevant[1]].cache_buffer.add_cache(self.all_request[j*self.time_slots+i]) #把该文件加到这个F-AP中
# elif not self.ap[self.ue[i].relevant[2]].cache_buffer.is_full():
# self.ap[self.ue[i].relevant[2]].cache_buffer.add_cache(self.ue[i].all_request[j])
else: #对上面的if的反面
self.ap[self.ue[j].relevant[judge]].cache_buffer.add_cache(self.all_request[j*self.time_slots+i]) #把第一个文件删除,然后在最后加入这个文件,在F-AP0中进行
print(number) #写出命中矩阵,即每个时隙命中的情况
# self.observe()
return number #返回一个命中值
def lru(self): #对比算法?
number = np.zeros(self.time_slots, dtype=float)
judge = 0
ch=0
for i in range(self.time_slots):
number[i] = self.hit_num(slot=i)
if i % self.duration == 0:
self.assign_relevant(ch)
ch+=1
print(self.ue[0].relevant) #与上面一样,是否专门用于对比?
for j in range(self.ue_num):
if self.all_request[j * self.time_slots + i] in self.ap[self.ue[j].relevant[0]].cache_buffer.cache: #判断每个用户请求的第i个文件是否在其关联的编号为0的F-AP上有缓存
self.ap[self.ue[j].relevant[0]].cache_buffer.resort(self.all_request[j * self.time_slots + i]) #把请求的文件进行一个先后排序?
judge = 0 #判断文件在编号0的F-AP这个位置
if self.all_request[j * self.time_slots + i] in self.ap[self.ue[j].relevant[1]].cache_buffer.cache: #判断每个用户请求的第i个文件是否在其关联的编号为0的F-AP上有缓存
self.ap[self.ue[j].relevant[1]].cache_buffer.resort(self.all_request[j * self.time_slots + i]) #把请求的文件进行一个先后排序?
judge = 1 #判断在1这个位置
if not self.all_request[j * self.time_slots + i] in self.ap[self.ue[j].relevant[0]].cache_buffer.cache: #判断,当请求文件不再编号为0的F-AP缓存中
if not self.all_request[j * self.time_slots + i] in self.ap[self.ue[j].relevant[1]].cache_buffer.cache: #判断,当亲求文件也不在编号为1的F-AP中
if not self.ap[self.ue[j].relevant[0]].cache_buffer.is_full(): #与上面一样
self.ap[self.ue[j].relevant[0]].cache_buffer.add_cache(self.all_request[j * self.time_slots + i]) #与上面一样
elif not self.ap[self.ue[j].relevant[1]].cache_buffer.is_full(): #与上面一样
self.ap[self.ue[j].relevant[1]].cache_buffer.add_cache(self.all_request[j * self.time_slots + i]) #与上面一样
else:
self.ap[self.ue[j].relevant[judge]].cache_buffer.add_cache(self.all_request[j * self.time_slots + i]) #把第一个文件删除,然后在最后加入这个文件,在F-AP0中进行
print(number)
return number
def lfu(self):
hit_num = np.zeros((self.ap_num, self.buffer_size)) #定义了一个命中的矩阵,是2维的,(究竟是一个什么样子的矩阵?)但是根据下面出现的情况来看,不能存在重复缓存的文件,要不会出现资源浪费
number = np.zeros(self.time_slots, dtype=float)
ch=0
for i in range(self.time_slots):
number[i] = self.hit_num(slot=i) #与上面一样,应该也是用于对比
if i % self.duration == 0:
self.assign_relevant(ch) #重新指派每个用户周围的F-AP
ch+=1 #变化次数加一
print(self.ue[0].relevant) #?把第一个用户的相关联的F-AP输出?
for j in range(self.ue_num): #遍历每一个用户
if self.ue[j].request[i] in self.ap[self.ue[j].relevant[0]].cache_buffer.cache: #如果用户的请求文件在与用户关联的编号为0的F-AP缓存中
index_tmp = self.ap[self.ue[j].relevant[0]].cache_buffer.cache.index(self.ue[j].request[i]) #查找编号为0的F-AP中首次出现用户想要请求的文件的位置
hit_num[self.ue[j].relevant[0]][index_tmp] += 1 #命中次数加一,此处的命中给了详细的位置,并且没有去调用函数,单纯增加
else:
if self.ue[j].request[i] in self.ap[self.ue[j].relevant[1]].cache_buffer.cache: #如果用户的请求文件在与用户关联的编号为1的F-AP的缓存中
index_tmp = self.ap[self.ue[j].relevant[1]].cache_buffer.cache.index(self.ue[j].request[i]) #用index函数找出编号为1的F-AP中首次出现用户想要请求的文件的位置
hit_num[self.ue[j].relevant[1]][index_tmp] += 1 #对这个命中+1,设定的命中没有调用函数,且对命中的位置进行了定位ue[j].relevant[1]]确定是哪个F-AP,后面的确定是这个F-AP的哪个位置
else: #当所请求的文件都不在与用户关联的F-AP的缓存空集中,往下走
if not self.ap[self.ue[j].relevant[0]].cache_buffer.is_full(): #判断编号为0的F-AP的缓存空间是否没满
self.ap[self.ue[j].relevant[0]].cache_buffer.add_cache(self.ue[j].request[i]) #没满,则把用户请求的文件加入编号为0的F-AP中
index_tmp = self.ap[self.ue[j].relevant[0]].cache_buffer.cache.index(self.ue[j].request[i]) #在文件缓存后,查找出缓存的位置
hit_num[self.ue[j].relevant[0]][index_tmp] = 1 #在定义的命中矩阵的相应位置上加1,代表命中次数加一
else: #如果F-AP0空间满了
if not self.ap[self.ue[j].relevant[1]].cache_buffer.is_full(): #判断是否F-AP1的空间是否没满
self.ap[self.ue[j].relevant[1]].cache_buffer.add_cache(self.ue[j].request[i]) #没满则把请求文件加入这里
index_tmp = self.ap[self.ue[j].relevant[1]].cache_buffer.cache.index(self.ue[j].request[i]) #并把加入的位置记录下来
hit_num[self.ue[j].relevant[1]][index_tmp] = 1 #在命中空间相应的空间中记录以命中
else: #如果既没有命中,且F-APs的空间都已经满了
a = hit_num[self.ue[j].relevant[0]].min() #找到F-AP0中命中次数最多的值
b = hit_num[self.ue[j].relevant[1]].min() #找到F-AP1中命中次数最少的值
if a < b: #判断谁最小,最小的往下
c = np.argmin(hit_num[self.ue[j].relevant[0]]) #把这个命中次数最小的下标,或者说文件找到,赋予c
ap_id = self.ue[j].relevant[0] #并标记好命中次数最少的文件所在地是F-AP0中
else: #a >= b
c = np.argmin(hit_num[self.ue[j].relevant[1]]) #同上,找出对应的下标
ap_id = self.ue[j].relevant[1] #并标记好最少的是在F-AP1中
self.ap[ap_id].cache_buffer.cache[c] = self.ue[j].request[i] #把这个请求最少的文件给替换成用户请求的文件
hit_num[self.ue[j].relevant[1]][c] = 1 #在命中矩阵中,让这个位置命中加一
print(number) #打出总命中次数
return number