DQN代码注释

Intelligent Power Control for Spectrum Sharing in Cognitive Radios:A Deep Reinforcement Learning Approach——源码和注释

简介:(1)主要为文献《Intelligent Power Control for Spectrum Sharing in Cognitive Radios:A Deep Reinforcement Learning Approach》在github上的开源代码进行了注释,注释中添加了自己对于问题的一些理解和疑问。这个Python源码的程序入口为main函数。main函数中调用了DQN、Power_control两个作者独立完成的包和numpy、random两个python自带的包。对于这个源码,还有部分位置没有添加好注释,需要再花一点时间理解和消化代码的意义。(2)除了为这部分源码添加注释以外,最近还学习了教材《Python编程:从入门到实践》的一部分,尝试构造几个DQN和深度学习的模板,方便以后使用。

第一部分:DQN 包

import tensorflow as tf 
import numpy as np 
import random
from collections import deque #后面的部分可以直接调用,不在需要使用collections.deque了

GAMMA = 0.8  #在更新Q函数时的折损参数
OBSERVE = 300 
EXPLORE = 100000 
FINAL_EPSILON = 0.0 
INITIAL_EPSILON = 0.8 
REPLAY_MEMORY = 400 
BATCH_SIZE = 256 

class BrainDQN:

   def __init__(self,actions,Sensor):
        
      self.replayMemory = deque()  #replayMemory是一个双向进出的队列
      self.timeStep = 0
      self.epsilon = INITIAL_EPSILON
      self.recording = EXPLORE
      self.sensor_dim = Sensor
      self.actions = actions
      self.hidden1 = 256
      self.hidden2 = 256
      self.hidden3 = 512
        
      self.createQNetwork()

   def createQNetwork(self):  #以tf的方式搭建graph,然后完成初始化,这里的步骤只是在create,并没有开始跑

      W_fc1 = self.weight_variable([self.sensor_dim,self.hidden1])
      b_fc1 = self.bias_variable([self.hidden1])

      W_fc2 = self.weight_variable([self.hidden1,self.hidden2])
      b_fc2 = self.bias_variable([self.hidden2])
        
      W_fc3 = self.weight_variable([self.hidden2,self.hidden3])
      b_fc3 = self.bias_variable([self.hidden3])
        
      W_fc4 = self.weight_variable([self.hidden3,self.actions])
      b_fc4 = self.bias_variable([self.actions])  #设置Q网络的权重

      self.stateInput = tf.placeholder("float",[None,self.sensor_dim])      #DQN的输入值:是这10个感应器的结果
                                                                            #placeholder和feed_dic是一对相互搭配使用的函数,前者申请了一个必要的空间来放置需要的数据。后者则实时输入数据

      h_fc1 = tf.nn.relu(tf.matmul(self.stateInput,W_fc1) + b_fc1)
      h_fc2 = tf.nn.relu(tf.matmul(h_fc1,W_fc2) + b_fc2)
      h_fc3 = tf.nn.tanh(tf.matmul(h_fc2,W_fc3) + b_fc3)               #前面是在赋初始值,现在便把这个网络的隐藏层搭建好了
        
      self.QValue = tf.matmul(h_fc3,W_fc4) + b_fc4                     #现在是确定了输出值,输出值是QValue

      self.actionInput = tf.placeholder("float",[None,self.actions])
      self.yInput = tf.placeholder("float", [None])     #????这里又定义了yInput和actionInput
      Q_action = tf.reduce_sum(tf.multiply(self.QValue, self.actionInput), reduction_indices = 1) #reduce_sum函数求和,后面的reduction_indices代表了求和的维度
      self.cost = tf.reduce_mean(tf.square(self.yInput - Q_action))  #这个self.cost就是我们使用adam算法要优化的东西,这里采用了一个求平均值函数
      self.trainStep = tf.train.AdamOptimizer(learning_rate=10**-5).minimize(self.cost)  #攒够数据调参的时候,使用的就是这个地方的Adam函数,learning_rate定义的是学习率,后面的minimize是指明了优化的变量是self.cost???那么在这里,调谁的参?

      self.session = tf.InteractiveSession()
      self.session.run(tf.global_variables_initializer())  #初始化语句

   def trainQNetwork(self):  #数据攒够了以后,就开始调用这个部分来训练q网络,没有输入的参数,故直接调用。返回值是self.loss,这里已经开始跑这个程序了
      minibatch = random.sample(self.replayMemory,BATCH_SIZE)  #随机地从replayMemory中取出BATCH_SIZE的数据,minibatch是一个BATCH_SIZE的数组,这里还没有对replayMemory进行赋值
      state_batch = [data[0] for data in minibatch]
      action_batch = [data[1] for data in minibatch]
      reward_batch = [data[2] for data in minibatch]
      nextState_batch = [data[3] for data in minibatch]  #猜测:定义了data数组,这个数组的0.1.2.3号元素分别存放了从minibatch中取得的s、a、r、s+1

      y_batch = []
      QValue_batch = self.QValue.eval(feed_dict={self.stateInput:nextState_batch})  #????这里的.eval是什么意思
      for i in range(0,BATCH_SIZE):

         y_batch.append(reward_batch[i] + GAMMA * np.max(QValue_batch[i]))

      _, self.loss = self.session.run([self.trainStep,self.cost],feed_dict={
         self.yInput : y_batch,
         self.actionInput : action_batch,
         self.stateInput : state_batch
         })   #self.loss = self.session.run([self.trainStep,self.cost],feed_dict={self.yInput : y_batch,self.actionInput : action_batch,self.stateInput : state_batch}) ,要run的是trainStep和self.cost,但输入数据的数组不明白为什么是三组,且使用冒号连接在了一起。
      return self.loss
       #这之前的两个大函数trainqnetwork和creatqnetwork都没有调用后面的函数。

   def setPerception(self,nextObservation,action,reward):
      loss = 0
      newState = nextObservation
      self.replayMemory.append((self.currentState,action,reward,newState))
      if len(self.replayMemory) > REPLAY_MEMORY:
         self.replayMemory.popleft()
      if self.timeStep > OBSERVE:
            
         loss = self.trainQNetwork()    #如果时间步大于300,那么训练一次q网络???如何和积攒够400个数据联系在一起

      self.currentState = newState
      self.timeStep += 1  #时间步加1
      return loss  #loss=self.loss
        

   def getAction(self):  #Q网络已经生成结果,此时便是要决定是选择Q网络的输出结果还是按照某种概率去探索新的世界:得到当前时刻应该采取的动作
      QValue = self.QValue.eval(feed_dict= {self.stateInput:[self.currentState]})  #????这里的.eval是什么意思
      action = np.zeros(self.actions)
      if random.random() <= self.epsilon:
         action_index = random.randrange(self.actions)  #rando.randrange函数返回递增集合中的一个随机数,这里的action_index相当于从action许用的下标中随机选中了一个,然后在下面的语句action[action_index]中调用
         action[action_index] = 1  #action集中某个元素被置为1表示选用这个集合中的这个元素作为当前的动作
      else:
         action_index = np.argmax(QValue)  #这时选用Q网络输出值中最大值作为当前的动作
         action[action_index] = 1
         
      if self.epsilon > FINAL_EPSILON and self.timeStep > OBSERVE:
         self.epsilon -= (INITIAL_EPSILON - FINAL_EPSILON)/EXPLORE   #根据步骤来更新epsilon
         self.recording = self.recording-1  #迭代的次数加一。换言之,每次迭代都需要调用这个getaction函数一次,迭代标志就是在这里改变的。

      return action,self.recording  #返回action表(本质上就是告知了应该选择哪个动作,然后再参照这个动作和环境进行互动)和剩余迭代次数。
    
   def getAction_test(self,observation):  #这里是所有的迭代次数已经完成,把agent投放到了环境当中实打实地去进行工作时的调用
      QValue = self.QValue.eval(feed_dict= {self.stateInput:[observation]})
      action = np.zeros(self.actions)
      action_index = np.argmax(QValue)   #这里不再采用贪心算法,直接采用q网络输出值中的最好动作即可
      action[action_index] = 1

      return action
    
   def setInitState(self,observation):  #把观察到的情况输入到这个函数中作为当前时间的状态值,然后参与到上面replayMemory的工作中去。
      self.currentState = observation

   def weight_variable(self,shape):  #和下面的bias_variable一样,都是在createQnetwork时使用的辅助函数,需要调用TF包中的相关函数
      initial = tf.truncated_normal(shape)
      return tf.Variable(initial)

   def bias_variable(self,shape):
      initial = tf.constant(0.01, shape = shape)
      return tf.Variable(initial

第二部分:power_control包

import random
import numpy as np
import math

class GameState: #环境生成模拟器
    def __init__(self,P_1,P_2,Noise,Sensor):  #初始化函数,将需要的增益、噪声、数量等参数输入
        
        self.P_1 = P_1
        self.P_2 = P_2
        self.length_P_1 = len(self.P_1)
        self.length_P_2 = len(self.P_2)
        
        self.h_11 = 1;self.h_12 = 1
        self.h_21 = 1;self.h_22 = 1
        
        self.ita_1 = 1.2
        self.ita_2 = 0.7
        
        self.sigma_sq_1 = 0.01
        self.sigma_sq_2 = 0.01
        
        self.lam = 0.1
        self.alpha = 0.5  #这个alpha是什么?

        self.num_sensor = Sensor
        self.noise = Noise
        
        self.P,self.sigma = self.dis()  #初始化时级调用了self.dis
        
    def dis(self):  #猜测:这个diss就是代表了discover,就是这个sense node感知到的具体结果,具体结果的表达式就是38行的sigma数组,这个数组中存放了感知的所有结果
        d = [[random.uniform(100,300) for _ in range(self.num_sensor)] for _ in range(2)]  #d阵代表了距离
        P = np.zeros((2,self.num_sensor))
        for i in range(0,self.num_sensor):
            P[0][i] = ((self.lam/(4*math.pi)/d[0][i])**2)  #P阵是一个二维数组,第一行存放的是主用户的信道衰落,第二行存放的是次用户的信道衰落
            P[1][i] = ((self.lam/(4*math.pi)/d[1][i])**2)
        sigma = np.zeros((self.num_sensor))
        for i in range(0,self.num_sensor):
            sigma[i] = ( P[0][i]*self.P_1[0]+P[1][i]*self.P_2[0] )/ self.noise
        return P,sigma

        # 猜测,感知节点也不知道PUS和SUS使用功率的具体情况,它所能测到的其实就是这个表达是的“值”,只不过我们在仿真的过程中是知道这个值诞生的原因的。
        # 换言之,我们在构造DQN时,这种输入的state是可以很模糊的。在这里我们不需要知道PUS和SUs具体的情况,我们只需要知道当前固定频段上的噪声结果(包括了环境噪声和使用这个频段所有用户通信功率造成的噪声)。
        # 再进一步考虑,我们可不可以设置这样一种并行的DQN?比如现在我们面临5个频段,在每个时刻,都在每个频段上去训练这个DQN网络,单个的DQN网络输出的结果是在这个频段上最优的许用功率,然后在这个DQN外面
        # 再嵌套一个?优先级列表?选择器来决定使用哪个频段。从而把这个问题分解为了2个步骤,第一步先求每个频段的最优功率,第二步再选择最优频段
        # 需要解决的问题:
        # (1)可否参考ADMM算法,分步解决需要解决的问题,细化更多的部分
        # (2)分步在每个频段上做DQN时,环境的变化如何处理?换言之,这个MDP过程需要输出一个结果,但这个action结果是需要优先级选择器来决定的
        # (3)是否存在分部DQN的东西?
    def ini(self):
        self.p_1 = self.P_1[random.randint(0,self.length_P_1-1)]
        self.p_2 = self.P_2[random.randint(0,self.length_P_2-1)]

    def ini_test(self):
        self.p_1_test = self.P_1[random.randint(0,self.length_P_1-1)]
        self.p_2_test = self.P_2[random.randint(0,self.length_P_2-1)]
    
    def frame_step(self, input_actions, policy, i):
        if i == True:
            if policy == 1:
                self.p_1 = self.update_p1_v1(self.p_2)
            if policy == 2:
                self.p_1 = self.update_p1_v2(self.p_1,self.p_2)
            action = np.flatnonzero(input_actions)[0]   # Return indices that are non-zero in the flattened version of a.
            # 返回非零元素的下标。
            self.p_2 = self.P_2[action]
        observation = self.compute_observation(self.p_1,self.p_2)
        reward = self.compute_reward(self.p_1,self.p_2)
        
        terminal = (reward==10)
        
        return observation,reward,terminal
    
    def frame_step_test(self, input_actions, policy, i):
        if i == True:
            if policy == 1:
                self.p_1_test = self.update_p1_v1(self.p_2_test)
            if policy == 2:
                self.p_1_test = self.update_p1_v2(self.p_1_test,self.p_2_test)
            action = np.flatnonzero(input_actions)[0]
            self.p_2_test = self.P_2[action]
        observation = self.compute_observation(self.p_1_test,self.p_2_test)
        reward = self.compute_reward(self.p_1_test,self.p_2_test)
        
        terminal = (reward==10)  # 当reward==10时,作为terminal的标志。
        
        return observation,reward,terminal
    
    def compute_observation(self,x,y):   # 感知器观察到的环境状态,存储在observation数组中,输入是PU和SU正在使用的功率
        observation = np.zeros((self.num_sensor))
        for i in range(0,self.num_sensor):
            observation[i] = self.P[0][i] * x + self.P[1][i] * y + random.gauss(0,self.sigma[i])  #Pnrk表示目前的状态空间
            if observation[i]<0:
                observation[i] =0
            observation[i] = observation[i]*(10**7)#乘了10的七次方
        return observation
    
    def compute_reward(self,x,y):
        success_1,success_2 = self.compute_SINR(x,y)
        reward = self.alpha * success_1 + (1-self.alpha) * success_2
        if reward == 0.5:
            reward = 0
        if reward == 1:
            reward = 10
        return reward
    
    def update_p1_v1(self,y):  #主用户的第一种更新方式
        p_1_n = self.ita_1/((abs(self.h_11)**2)/((abs(self.h_21)**2)*y + self.sigma_sq_1))
        v = []
        for ind in range(self.length_P_1):
            v.append(max(p_1_n-self.P_1[ind],0))
        p_1_new = self.P_1[v.index(min(v))]
        return p_1_new
    
    def update_p1_v2(self,x,y):  #主用户的第二种更新方式
        ind_p_1 = self.P_1.index(x)
        tSINR_1 = ((abs(self.h_11)**2)*x/((abs(self.h_21)**2)*y + self.sigma_sq_1))
        tao = x * self.ita_1 / tSINR_1
        if tao>=x and ind_p_1+1<=self.length_P_1-1 and tao<=self.P_1[ind_p_1+1] :
            x = self.P_1[ind_p_1+1]
        elif ind_p_1-1>=0 and tao<=self.P_1[ind_p_1-1] :
            x = self.P_1[ind_p_1-1]
        return x
    
    def compute_SINR(self,x,y):
        success_1 = ( (abs(self.h_11)**2)*x/((abs(self.h_21)**2)*y + self.sigma_sq_1)) >= self.ita_1
        success_2 = ( (abs(self.h_22)**2)*y/((abs(self.h_12)**2)*x + self.sigma_sq_2)) >= self.ita_2
        return success_1,success_2

第三部分:main函数

from power_control import GameState
from DQN import BrainDQN
import numpy as np
import matplotlib.pyplot as plt

P_1 = [round(0.1*i/2.0,2) for i in range(1,9)]
P_2 = [round(0.1*i/2.0,2) for i in range(1,9)]  #round返回浮点数的四舍五入值
actions = len(P_2) #返回数组P_2的长度,这里的长度是9

Loss = []
Success = []
Fre = []

noise = 3      
num_sensor = 10  # N 
policy = 2      # choose power change policy for PU, it should be 1(Multi-step) or 2(Single step)

brain = BrainDQN(actions,num_sensor) #DQN模拟生成器,需要告知动作空间的个数、感知器的数量
com = GameState(P_1,P_2,noise,num_sensor)#一个环境模拟生成器,告知PU和SU的功率表,噪声,感知器的数量。
terminal =True
recording = 100000  #迭代次数标记,这里需要迭代100000次,然后使用下面的100000-recording来表示当前正在迭代的次数

while(recording>0):   #每次迭代都需要执行这个函数段
    # initialization
    if(terminal ==True):
        com.ini() #环境模拟生成器的初始化
        observation0, reward0, terminal = com.frame_step(np.zeros(actions),policy,False)
        brain.setInitState(observation0)

    # train  train和test是分开的,train完以后,Q网络就训练完了,这时候可以直接投放到环境中去运作
    action,recording = brain.getAction()  #每次迭代都按照DQN包中的函数来得到本次迭代选择的action(返回值是一个数组,这个数组里面被赋值为1的action便是本次迭代选择的动作)
    nextObservation,reward,terminal = com.frame_step(action,policy,True)  #取得了action列表,我们就要按照和这个action和环境互动了
    loss = brain.setPerception(nextObservation,action,reward)  #得到本次的loss值

    # test  只有在500的整数倍时,才test一次。????那么攒够一定的次数才调参是怎样呢?
    if (recording+1)%500==0:
        
        Loss.append(loss) #append把元素添加到列表末尾
        print("iteration : %d , loss : %f ." %(100000-recording, loss))
        
        success = 0.0
        fre = 0
        num = 1000.0
        for ind in range(1000):   #每次test阶段,都使用1000个时间点,来得到把agent放到环境里时的实际情况,这里的run和最终的test不一样,这里只是验证当前训练步骤下能不能收敛(通过成功率、平均步数来衡量成功率)
            T = 0
            com.ini_test()
            observation0_test, reward_test, terminal_test = com.frame_step_test(np.zeros(actions),policy,False)
            while (terminal_test !=True) and T<20:
                action_test = brain.getAction_test(observation0_test)
                observation0_test,reward_test,terminal_test = com.frame_step_test(action_test,policy,True)
                T +=1
            if terminal_test==True:
                success +=1
                fre +=T
        if success == 0:
            fre = 0
        else:
            fre = fre/success
        success = success/num
        Success.append(success)
        Fre.append(fre)
        print("success : %f , step : %f ." %(success , fre))
        
plt.plot(Loss)    #最后一次test时生成Loss的结果生成图片
plt.show()

plt.plot(Success)
plt.show()

plt.plot(Fre)
plt.show()
  • 3
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值