文章目录
前言
前几天通过阅读这篇文献:
《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()
总结
没有什么比数学本身牛皮,只要理解了,就好办了。毕竟写代码这种粗活还是容易一点的。