DDPG神经网络实战(基于强化学习优化粒子群算法)

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第20天,点击查看活动详情

前言

前几天通过阅读这篇文献: 《Reinforcement learning based parameters adaption method for particleswarm optimization》 发现有些点还是比较新颖的,所以今天对论文的代码进行了整体的复现。整个过程大概花费了1天半(编码调试,不包括实验)

(PS:如果 不想看论文的话,请查看这篇博客:关于强化学习优化粒子群算法的论文解读(全) 在本篇博文将完整分析这篇论文的思路以及工作流程。而且说实话这篇论文其实我感觉复现起来没有一点难度,有些点还是比较新颖的,可以玩玩,顺便作为一个强化学习项目练练手。

版权

郑重提示:本文版权归本人所有,任何人不得抄袭,搬运,使用需征得本人同意!

2022.6.27~2022.6.28

日期:2022.6.27~2022.6.28

项目结构

整个的项目结构如下: 在这里插入图片描述

这里不做过多的解释了,不过值的一提的是,我这里是没有使用矩阵的写法的,因为整个项目一开始的目的就是为了使用Python作为实验,然后把Python代码转换为Java代码上Flink的,所以设计之初就是使用一个对象来存储一个粒子的,这样做的好处就是使用一个对象代替了好几个大的矩阵,也就是说不需要去维护矩阵了,而且写出来的代码可读性很高,并且刚好论文当中有使用CLPSO的速度更新方程来进行变体,所以他这里实现的话,也是很难直接使用矩阵来实现这个粒子之间的跟踪,以及锦标赛选择滴。

哦,对了额外说明一下这篇论文是发在arxiv上面的,不是什么IEEE这种顶刊,所以有些地方,他的描述是不严谨的,所以代码的总体的设计是按照论文来的,但是有些细节是不太一样的,不然代码都跑不起来。

然后这个项目也是验证跑了一下的,发现效果真的挺厉害的,说实话如果不是因为这个东西加了个DDPG,我想要玩玩这个神经网络,我根本就不会去想要复现这个玩意,而且一开始也是抱着怀疑的态度编写的,不过现在来看,还是挺厉害的,我一共训练了300轮每一轮PSO算法跑1000次。也就是说这里是跑了30万然后100个粒子,也就是3000万,本来的话,上午我是可以发出这篇文章记录一下的,但是后来改了几个bug然后调了几个参数,其实原来我还在训练3亿次的网络,但是实在顶不住了,最后改到0.3亿。

传统PSO(这里我没有展示优化过后的(原来我优化的)因为结果都一样被吊打) 都是跑1000次 传统: 在这里插入图片描述

我自己优化的只能到-58 DDPG优化: 在这里插入图片描述 而且是在50多次就出来了: 在这里插入图片描述 这怎么玩?!!! 说实话,没有想过会有这种效果。

当然废话不多说了,我们直接来看实现。

数据结构定义

这里的话,基于先去的数据结构,这里定义了一个Bird专门用来储存一些信息。

#coding=utf-8
#这里我们使用定义Class的方式实现PSO,便于后面调整

from ONEPSO.Config import *
import random

class Bird(object):

    #这个是从1开始的
    ID = 1
    #这个是划分多种群时的种群ID.他是从0开始的
    CID = 0
    #用于存放粒子到每个子种群的粒子的距离
    DIST = []
    Iterate = 0
    Y = None
    X = None
    #记录个体的最优值,这个关系到后面求取全局最优值
    # 因为论文当中也采用了CLPSO,所以需要这个破玩意
    # 不过在这里还是需要Pbest与Gbest
    Follow = None
    #这个玩意是用来判断当前粒子是不是需要换一个追随者了的
    NoChange = 0
    PbestY = None
    PBestX = None
    PBestV = None
    GBestX = None
    GBestY = None
    GBestV = None
    CBestY=None
    CBestX=None
    CBestV =None
    V = None

    def __init__(self,ID):
        self.ID = ID
        self.V = [random.random() *(V_max-V_min) + V_min for _ in range(DIM)]
        self.X = [random.random() *(X_up-X_down) + X_down for _ in range(DIM)]

    def __str__(self):
        return "ID:"+str(self.ID)+" -Fintess:%.2e:"%(self.Y)+" -X"+str(self.X)+" -PBestFitness:%.2e"%(self.PbestY)+" -PBestX:"+str(self.PBestX)+\
            "\n -GBestFitness:%.2e"%(self.GBestY)+" -GBestX:"+str(self.GBestX)

基本粒子群实现

这里的基本的粒子群实现其实和原来的是现实是一样的,只是我这里为了做到通用,我是直接在上面加功能。

#coding=utf-8
#这个是最基础的PSO算法实现,用于测试当前算法架构的合理性
#此外这个玩意还是用来优化的工具类,整个算法是为了求取最小值
import sys
import os
sys.path.append(os.path.abspath(os.path.dirname(os.getcwd())))
import math
from ONEPSO.RLPSOEmersion.TargetRL import Target
from ONEPSO.RLPSOEmersion.ConfigRL import *
from ONEPSO.RLPSOEmersion.BirdRL import Bird
import random
import time
class NewPsoRL(object):

    #这里是用来记录上一轮的GBestY的,主要是为了给ENV用(不影响这个算法的正常的基础PSO使用)
    GBestYLast = None
    #这个是用来记录全局最优的,为了记录停摆的
    GBestY = None
    NoChangeFlag = False

    Population = None
    Iterate_num = 0
    Random = random.random
    target = Target()
    W_RL = W
    C1_RL = C1
    C2_RL = C2
    C3_RL = C3
    #先实例出来一个对象,避免下次再启动它很慢
    Math = math
    Count_Div = 0
    ##################################
    """
    这三个破玩意是要输入神经网络的几个参数
    这三个玩意后面每一个玩意展开都是5维的
    """
    F_iterate_num = None
    F_diversity = None
    F_no_increase = None
    """
    接下来这些是为了得到上面三个破玩意而设置的辅助变量
    """
    F_no_increase_time = 0
    ##################################


    def __init__(self):
        #为了方便,我们这边直接先从1开始
        self.Population = [Bird(ID) for ID in range(1,PopulationSize+1)]
    def __GetDiversity(self,Population):
        #先计算出平均数
        x_pa = [0 for _ in range(DIM)]
        for bird in Population:
            for i in range(DIM):
                x_pa[i]+=bird.X[i]
        for i in range(DIM):
            x_pa[i]/=PopulationSize

        #现在计算出Diversity
        Diversity = 0.
        for bird in Population:
            sum = 0
            for i in range(DIM):
                sum+=abs(bird.X[i]-x_pa[i])

            Diversity+=self.Math.sqrt(sum)
        Diversity  = Diversity/PopulationSize
        return Diversity
    def __SinX(self,params):
        """
        这个就是论文提到的那个sin方法映射
        这个在论文里面是说从0-4,也就是5个参数返回
        :param params:
        :return:
        """
        res = []
        for i in range(5):
            tem_ = self.Math.sin(params*self.Math.pow(2,i))
            res.append(tem_)
        return res

    def __cat(self,states,params):
        for param in params:
            states.append(param)

    def GetStatus(self,Population):
        """
        按照要求,我们将在这里实现对现在粒子群的一个状态的输入
        :return:
        """
        self.F_iterate_num = self.Iterate_num / IterationsNumber
        self.F_diversity = self.__GetDiversity(Population)
        self.F_no_increase = (self.Iterate_num-self.F_no_increase_time)/IterationsNumber
        #此时经过这个sin函数进行映射
        self.F_iterate_num = self.__SinX(self.F_iterate_num)
        self.F_diversity = self.__SinX(self.F_diversity)
        self.F_no_increase = self.__SinX(self.F_no_increase)

        states  = []

        self.__cat(states,self.F_iterate_num);self.__cat(states,self.F_diversity);self.__cat(states,self.F_no_increase)
        return states

    def ChangeBird(self,bird,Population):
        #这个主要是实现锦标赛法来对粒子的跟踪对象进行更新

        while True:
            #被跟踪的粒子不能和自己一样,也不能和上一个一样
            a,b = random.sample(range(PopulationSize),2)
            a = Population[a];b=Population[b]
            follow = a
            if(a.PbestY>b.PbestY):
                follow = b
            if(follow.ID!=bird.ID):
                if(bird.Follow):
                    if(bird.Follow.ID !=follow.ID):
                        bird.Follow = follow
                        return
                else:
                    bird.Follow = follow
                    return

    def __PCi(self,i,ps):
        """
        论文当中的PCi的算子
        :return:
        """
        pci = 0.05+0.45*((self.Math.exp(10*(i-1)/(ps-1)))/(self.Math.exp(10)-1))
        return pci
    def NewComputeV(self,bird,params):
        """

        :param bird:
        :param params: 传入的数据格式为:[[w,c1,c2,c3],[],[],[],[]] 这里一共是5组共设置100个粒子
        :return:
        这里按照ID的顺序来调用不同的参数
        """
        NewV = []
        w, c1, c2, c3 = params[self.Count_Div]
        if(bird.ID%ClusterSize==0):
            if(self.Count_Div<ClusterNumber-1):
                self.Count_Div+=1

        for i in range(DIM):
            v = bird.V[i]*w
            if(self.Random()<self.__PCi((i+1),PopulationSize)):
                pbestfi = bird.PBestX[i]
            else:
                pbestfi=bird.PBestX[i]
            v=v+c1*self.Random()*(pbestfi-bird.X[i])+c2*self.Random()*(bird.GBestX[i]-bird.X[i])\
            +c3*self.Random()*(bird.PBestX[i]-bird.X[i])

            if(v>V_max):
                v = V_max
            elif(v<V_min):
                v = V_min
            NewV.append(v)

        return NewV

    def NewComputeX(self,bird:Bird,params):
        NewX = []
        NewV = self.NewComputeV(bird,params)
        bird.V = NewV
        for i in range(DIM):
            x = bird.X[i]+NewV[i]
            if(x>X_up):
                x = X_up
            elif(x<X_down):
                x = X_down
            NewX.append(x)
        return NewX



    def ComputeV(self,bird):
        #这个方法是用来计算速度滴
        #现在这个粒子群的这个计算V的算法需要进行改动,但是改动不会太大。
        NewV=[]
        for i in range(DIM):
            v = bird.V[i]*self.W_RL + self.C1_RL*self.Random()*(bird.PBestX[i]-bird.X[i])\
            +self.C2_RL*self.Random()*(bird.GBestX[i]-bird.X[i])
            #这里注意判断是否超出了范围
            if(v>V_max):
                v = V_max
            elif(v<V_min):
                v = V_min
            NewV.append(v)

        return NewV

    def ComputeX(self,bird:Bird):
        NewX = []
        NewV = self.ComputeV(bird)
        bird.V = NewV
        for i in range(DIM):
            x = bird.X[i]+NewV[i]
            if(x>X_up):
                x = X_up
            elif(x<X_down):
                x = X_down
            NewX.append(x)
        return NewX

    def InitPopulation(self):
        #初始化种群
        GBestX = [0. for _ in range(DIM)]
        Flag = float("inf")
        for bird in self.Population:
            bird.PBestX = bird.X
            bird.Y = self.target.SquareSum(bird.X)
            bird.PbestY = bird.Y
            if(bird.Y<=Flag):
                GBestX = bird.X
                Flag = bird.Y
        #便利了一遍我们得到了全局最优的种群
        self.GBestY = Flag
        for bird in self.Population:
            bird.GBestX = GBestX
            bird.GBestY = Flag

        self.Iterate_num+=1

    def InitPopulationRL(self):
        #初始化种群,不过是给ENV调用的,因为这个里面有一个CLPSO的思想
        GBestX = [0. for _ in range(DIM)]
        Flag = float("inf")
        for bird in self.Population:
            bird.PBestX = bird.X
            bird.Y = self.target.SquareSum(bird.X)
            bird.PbestY = bird.Y
            if(bird.Y<=Flag):
                GBestX = bird.X
                Flag = bird.Y

        #便利了一遍我们得到了全局最优的种群
        self.GBestY = Flag
        for bird in self.Population:
            bird.GBestX = GBestX
            bird.GBestY = Flag
            #现在是初始化,所以这个这样算是没问题的
            self.GBestYLast = Flag
            #给每一个粒子找到一个追随者
            self.ChangeBird(bird,self.Population)



    def Running(self):
        """
        这个方法是用来正常运行基础版本的PSO算法的
        这里没有必要删除这些算法,最起码可以做对比
        :return:
        """
        for iterate in range(1,IterationsNumber+1):
            w = LinearW(iterate)
            #这个算的GBestX其实始终是在算下一轮的最好的玩意
            GBestX = [0. for _ in range(DIM)]
            Flag = float("inf")
            for bird in self.Population:
                #更改为线性权重
                self.W_RL = w
                x = self.ComputeX(bird)
                y = self.target.SquareSum(x)
                # 这里还是要无条件更细的,不然这里的话C1就失效了
                # if(y<=bird.Y):
                #     bird.X = x
                #     bird.Y = y
                bird.X = x
                bird.Y = y
                if(bird.Y<=bird.PbestY):
                    bird.PBestX=bird.X
                    bird.PbestY = bird.Y

                #个体中的最优一定包含了全局经历过的最优值
                if(bird.PbestY<=Flag):
                    GBestX = bird.PBestX
                    Flag = bird.PbestY
            for bird in self.Population:
                bird.GBestX = GBestX
                bird.GBestY=Flag

if __name__ == '__main__':

    start = time.time()
    basePso = NewPsoRL()
    basePso.InitPopulation()
    basePso.Running()
    end = time.time()
    # for bird in basePso.Population:
    #     print(bird)
    print(basePso.Population[0])
    print("花费时长:",end-start)



这个类其实是有两个功能的,一个是传统的粒子群算法,对每次这里是封装了一个传统粒子群算法的,然后就是基于论文改造的一些方法,那么这部分的调用是通过别的类来调用的。这玩意可以提取出来,但是也有重复的地方(和基本的粒子群算法)所以我就没有单独提取出来了。

配置

这里也是有一个统一的配置中心进行管控的。 不过其实还有一些东西我是没有提取到这个配置里面的,懒得搞了。

#coding=utf-8
# 相关参数的设置通过配置中心完成
import sys
import os
sys.path.append(os.path.abspath(os.path.dirname(os.getcwd())))

C1=2.0
C2=2.0
C3=2.0
#如果超过四轮还没有得到更新的话,那么需要重新选择一个follow对于当前的粒子来说
M_follow = 4
W = 0.4
#单目标下定义的维度
DIM =5
#运行1000次(可以理解为训练1次这个粒子群要跑一千次)
IterationsNumber = 1000
X_down = -10.0
X_up = 10

V_min = -5.0
V_max = 5

#这个是按照论文进行的描述,分为5组,100个粒子是自己后面加的
ClusterSize = 20
ClusterNumber = 5
EPOCH = 20 #训练N轮
PopulationSize = 100

def LinearW(iterate):
    #传入迭代次数
    Wmax = 0.9
    Wmin = 0.4

    w = Wmax-(iterate*((Wmax-Wmin)/IterationsNumber))

    return w


训练实现

那么接下来就是如何实现论文当中的训练部分了。 因为论文也是说要先预训练嘛,所以你懂的。

神经网络的定义

这里我们想要实现这个玩意,我们需要先定义两个神经网络。

class Actor(nn.Module):
    """
    生成动作网络输入三个参数
    """
    def __init__(self,state):
        super(Actor, self).__init__()
        self.fc1 = nn.Linear(state,64)
        self.fc1.weight.data.normal_(0, 0.1)
        self.fc2 = nn.Linear(64,64)
        self.fc2.weight.data.normal_(0, 0.1)
        self.fc3 = nn.Linear(64,64)
        self.fc3.weight.data.normal_(0,0.1)
        #这里和破论文说的不一样,实际上应该是20不是25
        self.out = nn.Linear(64,20)
        self.out.weight.data.normal_(0, 0.1)

    def forward(self,x):
        x = self.fc1(x)
        x = F.leaky_relu(x)
        x = self.fc2(x)
        x = F.leaky_relu(x)
        x = self.fc3(x)
        x = F.leaky_relu(x)
        x = self.out(x)
        x = torch.tanh(x)
        return x

class Critic(nn.Module):
    """
    这个是Critic网络(DQN)
    """
    def __init__(self,state_action):
        super(Critic,self).__init__()
        self.fc1 = nn.Linear(state_action,64)
        self.fc1.weight.data.normal_(0,0.1)
        self.fc2 = nn.Linear(64,64)
        self.fc2.weight.data.normal_(0,0.1)
        self.fc3 = nn.Linear(64,32)
        self.fc3.weight.data.normal_(0,0.1)
        self.fc4 = nn.Linear(32,32)
        self.fc4.weight.data.normal_(0,0.1)
        self.fc5 = nn.Linear(32,16)
        self.fc5.weight.data.normal_(0,0.1)
        self.out = nn.Linear(16,1)
        self.out.weight.data.normal_(0,0.1)

    def forward(self,x):
        x = self.fc1(x)
        x = F.leaky_relu(x)
        x = self.fc2(x)
        x = F.leaky_relu(x)
        x = self.fc3(x)
        x = F.leaky_relu(x)
        x = self.fc4(x)
        x = F.leaky_relu(x)
        x = self.fc4(x)
        x = F.leaky_relu(x)
        x = self.fc5(x)
        x = F.leaky_relu(x)
        x = self.out(x)
        return x

这个就是论文当中的,只是actor在这里应该是20个输出,而不是25.

损失函数

这个损失函数其实就是和那个DDPG的一样的,没啥区别。 在这里插入图片描述

在这里插入图片描述

环境的编写

这个环境其实就是和PSO算法深度绑定的,其实编写倒也简单,因为按照强化学习的框架来看,你只需要实现,getRward() 和 reset() 方法,基本上这两个方法就够了,一个方法得到当前情况下,动作的奖励,以及下一步的状态,reset就直接是初始化的,返回当前的状态。

"""
基于PSO算法设计的环境,这里做大一点,现在
是基于PSO,以后可以加入GA,EDA等等算法作为环境进行编写
"""

from ONEPSO.RLPSOEmersion.PSORL import NewPsoRL
from ONEPSO.RLPSOEmersion.ConfigRL import *
class ENV(object):
    def __init__(self):
        self.PsoRL = NewPsoRL()
    def GetReward(self,state,action,i=0):
        """
        输入当前的state和action返回一个打分
        这个打分其实通过运算以后得到的,并且状态其实也是这样滴
        :param state: 这个state其实用不上在这里,只是当时设计的时候是这样的
        :param action: 注意这里的action是通过翻译以后的参数
        它是这样的[[],[],[],[],[]]
        :return: 下一个状态和当前的奖励
        """

        # 这个算的GBestX其实始终是在算下一轮的最好的玩意
        GBestX = [0. for _ in range(DIM)]
        Flag = float("inf")#这个是用来记录全局最优解的
        CurrentBest = float("inf")
        for bird in self.PsoRL.Population:

            x = self.PsoRL.NewComputeX(bird,action)
            y = self.PsoRL.target.SquareSum(x)

            bird.X = x
            bird.Y = y
            if (bird.Y < bird.PbestY):
                bird.PBestX = bird.X
                bird.PbestY = bird.Y
            elif(bird.Y==bird.PbestY):
                bird.NoChange+=1
                if(bird.NoChange==M_follow):
                    self.PsoRL.ChangeBird(bird,self.PsoRL.Population)
                    bird.NoChange=0

            # 个体中的最优一定包含了全局经历过的最优值
            #这里是搜寻当前这一轮的全局最优解
            if (bird.PbestY < Flag):
                GBestX = bird.PBestX
                Flag = bird.PbestY

            #选择当前次数的最解(全局)
            if(bird.Y<CurrentBest):
                CurrentBest = bird.Y
        #这段代码是用来确定停摆时间的
        if(self.PsoRL.GBestY==Flag and not self.PsoRL.NoChangeFlag):
            self.PsoRL.F_no_increase_time = i
            self.PsoRL.NoChangeFlag = True
        elif(self.PsoRL.GBestY!=Flag and self.PsoRL.NoChangeFlag):
            self.PsoRL.F_no_increase_time = i
            self.PsoRL.NoChangeFlag = False
        elif(self.PsoRL.GBestY!=Flag and not self.PsoRL.NoChangeFlag):
            self.PsoRL.F_no_increase_time = i

        self.PsoRL.GBestY = Flag

        for bird in self.PsoRL.Population:
            bird.GBestX = GBestX
            bird.GBestY = Flag
        #此时Flag存储的是当前的全局最优解的解值
        self.PsoRL.Iterate_num += 1

        if(self.PsoRL.GBestYLast<CurrentBest):
            R = 1
        else:
            R = -1
        self.PsoRL.GBestYLast = CurrentBest
        next_state = self.PsoRL.GetStatus(self.PsoRL.Population)
        return next_state, R


    def reset(self):
        """
        完成初始化操作
        :return:
        """
        self.PsoRL.InitPopulationRL()
        return self.PsoRL.GetStatus(self.PsoRL.Population)

状态的获取

这个其实是环境的编写重点之一。 那么这个也是按照论文来的,获取三个参数,当前迭代次数,当前的离散度,是当前最近空转的最长长度 这个部分其实在前面的PSORL实现了。看里面的 注释其实应该是知道的。

然后这里吗除了中间那个处理起来比较麻烦,其他的其实处理起来都简单。

   def __GetDiversity(self,Population):
        #先计算出平均数
        x_pa = [0 for _ in range(DIM)]
        for bird in Population:
            for i in range(DIM):
                x_pa[i]+=bird.X[i]
        for i in range(DIM):
            x_pa[i]/=PopulationSize

        #现在计算出Diversity
        Diversity = 0.
        for bird in Population:
            sum = 0
            for i in range(DIM):
                sum+=abs(bird.X[i]-x_pa[i])

            Diversity+=self.Math.sqrt(sum)
        Diversity  = Diversity/PopulationSize
        return Diversity

在这里插入图片描述 至于当前迭代次数,这个我想都不用说了吧,传进来的i是啥(当然实际上,我一开始没有设计到那个参数i(那个停摆的意思理解错了一开始,后来仔细看我博客才反应过来,是当前最近空转的最长长度),我自己PSORL是有一个计数器的)。

奖励的编写

这个就更加简单了。 在这里插入图片描述 直接: 在这里插入图片描述

目标函数

我们的粒子群是需要这个待优化函数的。那么这里的待优化函数也是封装在Target里面。

#coding=utf-8
#这个玩意是用来定义需要优化的函数的地方
import sys
import os
sys.path.append(os.path.abspath(os.path.dirname(os.getcwd())))
class Target(object):
    def SquareSum(self,X):
        res = 0
        for x in X:
            res+=x*x
        return res


训练

终于到了训练了。 现在目光移到这个玩意里面。 在这里插入图片描述 这里面包含了actor 和 critic的训练。


class DDPGNetWorkPSO(object):
    """
    提供模型训练,模型保存,模型加载功能,模型调用功能
    """
    def __init__(self,N_state,N_action):
        """
        :param state: 这个是当前PSO状态的维度(把那三个参数搞在一起的长度)
        :param state_action: 这个是输出的动作,在这里的话,应该是要转换会w c1 c2不然环境很难给分(同样这里是长度)
        """
        self.N_Status = N_state
        self.N_action = N_action
        self.GAMMA = 0.9
        self.ENV = ENV()
        self.LR_actor = 0.0001
        self.LR_critic = 0.0001
        self.MEMORY_CAPACITY_CRITIC = 100 #记忆体的大小是100
        self.TARGET_REPLACE_ITER = 10 #这里设置10次一换
        self.BATCH_SIZE_CRITIC = 20 # 一个Batchsize大小是20
        self.Env = ENV()


        self.actor = Actor(self.N_Status)
        self.eval_net_critic, self.target_net_critic = Critic(self.N_Status+self.N_action), Critic(self.N_Status+self.N_action)

        self.opt_actor = torch.optim.Adam(self.actor.parameters(), lr=self.LR_actor)
        self.opt_critic = torch.optim.Adam(self.eval_net_critic.parameters(), lr=self.LR_critic)

        self.LearnStepCount = 0
        self.MemoryCount = 0  # 记录了几条记忆
        """
        从0-statu是当前的然后是reward,然后是下一个的
        """
        self.Memory = np.zeros((self.MEMORY_CAPACITY_CRITIC, self.N_Status * 2 + self.N_action+1))  # 初始化记忆
        #(s, [a, r], s_) 最好的动作对应的reward

        self.loss_func_critic = nn.MSELoss()  # 这里使用平方差

    def Remember(self, s, a, r, s_):
        #这个是我们的记忆体

        transition = np.hstack((s, a.detach().numpy(), [r], s_))
        index = self.MemoryCount % self.MEMORY_CAPACITY_CRITIC
        self.Memory[index, :] = transition
        self.MemoryCount += 1


    def Train_Critic(self):
        """
        :s_a: 这个参数就是Status和Action整合后的参数,DQN是有一个临时缓存表的,满足缓存之后他才会更新
        但是我们的Actor网络不是(粒子训练完一轮就要更新一次,然后存储 s a r s_next
        这个是训练方法,负责完成当前的Critic网络的训练
        :return:
        """
        if self.LearnStepCount % self.TARGET_REPLACE_ITER == 0:
            self.target_net_critic.load_state_dict(self.eval_net_critic.state_dict())
        self.LearnStepCount += 1

        # 选择记忆体
        SelectMemory = np.random.choice(self.MEMORY_CAPACITY_CRITIC, self.BATCH_SIZE_CRITIC)
        selectM = self.Memory[SelectMemory, :]
        S_s = torch.FloatTensor(selectM[:, :self.N_Status])
        S_a = torch.LongTensor(selectM[:, self.N_Status:self.N_Status+self.N_action].astype(int))
        S_r = torch.FloatTensor(selectM[:, self.N_Status+self.N_action:self.N_Status+self.N_action+1])
        S_s_ = torch.FloatTensor(selectM[:, -self.N_Status:])

        #这一步得到了我们一个batch_size的最佳值(最佳)

        real_input = torch.cat([S_s,S_a],dim=1)
        q_eval = self.eval_net_critic(real_input)
        # q_eval = q_eval.gather(1,S_a)
        # #按照非转置的顺序来得到对应动作的价值,但是这里论文比较提示,输出值是1,而不是动作的大小
        #这个是下一步动作对应的值

        S_a_ = self.actor(S_s_)
        #这里输出的动作的维度是20,
        b = torch.normal(mean=torch.full((self.BATCH_SIZE_CRITIC, 20), 0.0), std=torch.full((self.BATCH_SIZE_CRITIC, 20), 0.5))
        S_a_ = S_a_+ b
        next_input = torch.cat([S_s_,S_a_],dim=1)
        q_next = self.target_net_critic(next_input).detach()
        #更新我们的Q网络
        # shape (batch, 1)
        q_target = S_r + self.GAMMA * q_next.max(1)[0].view(self.BATCH_SIZE_CRITIC, 1)
        loss = self.loss_func_critic(q_eval, q_target)

        self.opt_critic.zero_grad()
        loss.backward()
        self.opt_critic.step()



    def TranslateAction(self,actions):
        """
        现在我要把action转化为5组参数,这样才能给粒子群反馈
        :param actions:
        :return: [[],[],[],[],[]]
        """
        length  = 4
        params = []
        a = []

        for i in range(1,len(actions)+1):
            a.append(actions[i-1])
            if(i%length==0):
                w = a[0]*0.8 +0.1
                scale = 1/(a[1]+a[2]+0.00001)*a[3]*8
                c1 = scale*a[1]
                c2 = scale*a[2]
                c3 = scale*a[3]
                temp = [w,c1,c2,c3]
                params.append(temp)
                a = []
        return params

    def SavaMould(self,net,path):
        """
        保存网络,输入一个路径,自动保存两个
        :return:
        """
        torch.save(net.state_dict(), path)

    def LoadMould(self,path_actor,path_critic):
        """
        这个会返回两个加载好的网络
        :return:
        """
        self.actor.load_state_dict(torch.load(path_actor))
        self.eval_net_critic.load_state_dict(torch.load(path_critic))
    def LoadMouldActor(self,path_actor):
        """
        加载Actor网络
        :return:
        """
        self.actor.load_state_dict(torch.load(path_actor))
    def LoadMouldCritic(self,path_critic):
        """
        加载Critic网络
        :return:
        """
        self.eval_net_critic.load_state_dict(torch.load(path_critic))

    def TrainPSORL(self):
        """
        这个部分是用来完成我们整个的PSO的训练代码的
        :return:
        """
        state = self.Env.reset()
        for epoch in range(1,EPOCH+1):
            for i in range(1,IterationsNumber+1):
                #在这里完成Actor和Critic的训练
                state_ = torch.tensor(state, dtype=torch.float)
                out_actor = self.actor(state_)
                out_actor = out_actor + torch.normal(mean=torch.full((1, 20), 0.0), std=torch.full((1, 20), 0.5))[0]
                in_critic_ = torch.cat([state_,out_actor])
                loss = -self.eval_net_critic(in_critic_)
                self.opt_actor.zero_grad()
                loss.backward()
                self.opt_actor.step()
                a = out_actor.tolist()
                a = self.TranslateAction(a)
                s_, r =self.Env.GetReward(state, a,i)
                # 这里训练更新Critic网络
                self.Remember(state,out_actor,r,s_)
                if (self.MemoryCount > self.MEMORY_CAPACITY_CRITIC):
                    self.Train_Critic()
                state = s_
                print("正在执行第",epoch,"轮的第",i,"次训练!")
        self.SavaMould(self.actor,"./module/actor.pth")
        self.SavaMould(self.eval_net_critic,"./module/critic.pth")
        print("模型保存完毕!")

之后训练完毕后,你将在这里见到你的权重文件 在这里插入图片描述

RLPSO

终于到了调用阶段喽。

在这里插入图片描述

"""
这个部分的代码是用来调用训练好之后的Actor网络
然后组成真正的RLPSO算法,这里的代码其实和ENV的编写很像
"""
import torch
from ONEPSO.RLPSOEmersion.ConfigRL import *
from ONEPSO.RLPSOEmersion.ENV import ENV
from ONEPSO.RLPSOEmersion.DDPGNetwoekPSO import DDPGNetWorkPSO
class RLPSOGo(object):
    env = ENV()
    ddpg  = DDPGNetWorkPSO(15,20)

    def __init__(self,path_actor):
        self.ddpg.LoadMouldActor(path_actor)

    def __get_params(self,state):
        """

        :param state: 传入由当前的ENV(PSO)返回的状态
        :return:
        """
        state_ = torch.tensor(state, dtype=torch.float)
        out_actor = self.ddpg.actor(state_)
        out_actor = out_actor.tolist()
        out_actor = self.ddpg.TranslateAction(out_actor)
        return out_actor
    def RUNINGRLPSO(self):
        """
        这里是RLPSO启动,和环境的GETReward有点类似
        :return:
        """
        state = self.env.reset()

        for i in range(1,IterationsNumber+1):
            out_params = self.__get_params(state)
            #其实这两个s_,r,r在这里用不上,我只是想要单纯地跑PSORL
            s_,r = self.env.GetReward(state,out_params,i)
            #这个是下一轮的
            state = s_
            print("当前迭代次数为:",i,"最佳适应值为%.2e"%self.env.PsoRL.GBestY)



if __name__ == '__main__':
    rLPso = RLPSOGo("./module/actor.pth")
    rLPso.RUNINGRLPSO()

总结

没有什么比数学本身牛皮,只要理解了,就好办了。毕竟写代码这种粗活还是容易一点的。

猜你喜欢

转载自juejin.im/post/7114239596170313759
今日推荐