基于多种群机制的PSO算法Python实现(优化与探索二)

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

前言

今天的工作做的不多,主要是后面又玩云服务器去了,那个东西配置了小半天,华为的,学校花了大价钱搞过来的320G运行内存,结果因为是centos7,还是arch的 搞的我连anaconda的官方版都没有,最后只能去GitHub搞一个适用arch的,Python版本只到3.7.1,然后我的代码又是按照新语法来的,有类型,又不支持。

所以今天只做了基于Kmeans++ 来分种群,本来还想搞拓扑结构的,但是,那几篇中文论文质量真的底下,也没说清楚,我知道拓扑结构是咋样的,但是用的是什么策略获取的相邻的点的,看了半天啥也没有,如果是基于距离的话,高纬度就不用做了。目前有可能的是按照粒子的ID来的。如果是那样的话,我感觉不如直接乱来算了。

版权

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

2022.6.21

日期:2022.6.21 DAY 2

误区

昨天的时候编码有个问题,一直没发现,今天跑了几次发现结果对不到,于是我拿来以前同学写过的PSO对比了一下,一开始以为是np的问题,可能是哪里深浅复制有问题,仔细看发现,深浅复制的话我使用的都是深拷贝(通过生成新的list实现),然后以为是np的问题,然后,我又按照我的代码格式去写(因为要上分布式,所以能不用矩阵就不用)后来发现其实是我的速度忘记更新给粒子了。

#coding=utf-8
#这个是最基础的PSO算法实现,用于测试当前算法架构的合理性
#此外这个玩意还是用来优化的工具类,整个算法是为了求取最小值
import sys
import os
sys.path.append(os.path.abspath(os.path.dirname(os.getcwd())))

from ONEPSO.Bird import Bird
from ONEPSO.Config import *
from ONEPSO.Target import Target
import random
import time
class BasePso(object):

    Population = None
    Random = random.random
    target = Target()
    W = W

    def __init__(self):
        #为了方便,我们这边直接先从1开始
        self.Population = [Bird(ID) for ID in range(1,PopulationSize+1)]

    def ComputeV(self,bird):
        #这个方法是用来计算速度滴
        NewV=[]
        for i in range(DIM):
            v = bird.V[i]*self.W + C1*self.Random()*(bird.PBestX[i]-bird.X[i])\
            +C2*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
        #便利了一遍我们得到了全局最优的种群
        for bird in self.Population:
            bird.GBestX = GBestX
            bird.GBestY = Flag


    def Running(self):
        #这里开始进入迭代运算
        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 = 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 = BasePso()
    basePso.InitPopulation()
    basePso.Running()
    end = time.time()
    # for bird in basePso.Population:
    #     print(bird)
    print(basePso.Population[0])
    print("花费时长:",end-start)





基于Kmeans++ 分类子种群

这部分修改还是参考原来的那几篇文献来做的。

首先是Kmeans++的实现。

#coding=utf-8
#由于中心点的选取对于PSO多种群来说比较重要
#所以这里选择Kmeans++ 来慎重选择中心点
#距离公式采用欧式距离
import sys
import os
sys.path.append(os.path.abspath(os.path.dirname(os.getcwd())))
from ONEPSO.Bird import Bird
from ONEPSO.Config import *
import random
import math
class KmeansAdd(object):

    choice = random.sample
    ClusterCenters = []

    def Divide(self,Popluations,Centers):
        #划分,这边也是如果不满足条件就按照最后一个族群来划分
        #这里注意一点原来的CID的编号是多少压根就不重要,只要后面准确分类就没有问题
        for bird in Popluations:
            BirdCID = -1
            Flag = float("inf")
            for center in Centers:
                dist = self.EuclideanDistan(bird,center)
                if(dist<=Flag):
                    Flag=dist
                    BirdCID+=1
            bird.CID=BirdCID
            bird.DIST=Flag


    def EuclideanDistan(self,bird,center):
        #欧式距离公式
        x1 = bird.X
        x2 = center
        dist = 0
        for index in range(len(x1)):
            dist+=math.pow((x1[index]-x2[index]),2)
        return dist


    def InitChoiceCenter(self,Population):
        #初始化选取中心点,先选择一个,然后帮我完成初始化分类,此时的ClusterCenters存储的就是
        #我们初始化得到的中心点
        K = 1
        self.ClusterCenters = self.choice(Population,K)
        RelCenters = []
        for i in range(K):
            #分配CID
            self.ClusterCenters[i].CID=(i)
            RelCenters.append(self.ClusterCenters[i].X)
        #这个中心点存的是可行域内的点坐标
        self.ClusterCenters=RelCenters
        #此时会完成第一次划分
        self.Divide(Population,self.ClusterCenters)
        #构造权重模型
        Tim = ClusterNumber-K
        for i in range(Tim):
            K+=1
            Weights = [bird.DIST for bird in Population]
            Total = sum(Weights)
            Weights=[weight/Total for weight in Weights]
            #在采用轮盘算法
            x = -1
            i = 0
            Due = random.random()
            while(i<Due):
                x+=1
                i+=Weights[x]
            self.ClusterCenters.append(Population[x].X)
            #再次划分中心点,之后通过增加中心点,我们可以完成对子种群的划分
            self.Divide(Population, self.ClusterCenters)

    def RunningChoiceCenter(self,Population):
        #运行时选取中心点,返回计算后的新的中心点
        NewClusterCenterPoints={}
        Counter = {}
        #先初始化一下计数器,新的中心点
        for CID in range(ClusterNumber):
            Counter[CID]=0

        for bird in Population:
            if(not NewClusterCenterPoints.get(bird.CID)):
                NewClusterCenterPoints[bird.CID]=bird.X
            else:
                NewClusterCenterPoints[bird.CID] = self.__ADD_X(NewClusterCenterPoints.get(bird.CID),bird.X)

            Counter[bird.CID]=Counter[bird.CID] + 1

        #计算出新的中心点
        for CID in NewClusterCenterPoints.keys():
            NewClusterCenterPoints[CID] = self.__Division_X(NewClusterCenterPoints.get(CID),Counter.get(CID))

        return list(NewClusterCenterPoints.values())

    def RunningDivide(self,Poplution,Iterate=0):
        self.InitChoiceCenter(Poplution)
        for epoch in range(K_Iterate):
            NewClusterCenters = self.RunningChoiceCenter(Poplution)
            if(
                len(self.ClusterCenters)!=ClusterNumber or self.__SAME_Pred(self.ClusterCenters,NewClusterCenters)>=SAEMPRED
            ):
                # print("Bad",epoch,Iterate,len(self.ClusterCenters))
                break
            else:
                self.Divide(Poplution,NewClusterCenters)
                self.ClusterCenters=NewClusterCenters

    def __SAME_Pred(self,LastCenters,NewCenters):
        #这个玩意是用来计算相似度的,如果发现相似度超过阈值,那么停止迭代
        res = 0.
        count=0
        if(len(LastCenters)!=len(NewCenters)):
            return False
        for i in range(len(LastCenters)):
            for j in range(len(LastCenters[i])):
                res+=math.pow((LastCenters[i][j]-NewCenters[i][j]),2)
                count+=1
        res = 1-(res/count)
        return res

    def __ADD_X(self,X1,X2):
        res = []
        for i in range(len(X1)):
            res.append(X1[i]+X2[i])
        return res
    def __Division_X(self,X1,X2):
        res =[]
        for i in range(len(X1)):
            res.append(X1[i]/X2)
        return res



这个还是比较简单的,主要是注意初始化和一般的Kmeans不一样。然后这里还是使用欧氏距离来做的,哪一个距离公式会好一点,这个要测试,我感觉是这个会好一点,因为,这个PSO本来就是基于空间的。

之后整合代码如下:

#coding=utf-8
#现在是基于Kmeans++ 写的动态划分子种群的PSO算法。
import sys
import os
sys.path.append(os.path.abspath(os.path.dirname(os.getcwd())))
from ONEPSO.BasePso import BasePso
from ONEPSO.Config import *
from ONEPSO.Bird import Bird
import time
from ONEPSO.KmeansAdd import KmeansAdd
class DKMPSO(BasePso):

    kmeansAdd = KmeansAdd()

    def __init__(self):
        super(DKMPSO,self).__init__()
        self.kmeansAdd.RunningDivide(self.Population)


    def ComputeV(self,bird):
        #这个方法是用来计算速度滴
        NewV=[]
        for i in range(DIM):
            v = bird.V[i]*self.W + C1*self.Random()*(bird.PBestX[i]-bird.X[i])\
            +C2*self.Random()*(bird.GBestX[i]-bird.X[i]) + C3*self.Random()*(bird.CBestX[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):
        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")

        #还有一个是记录Cluster最优解的
        CBest = {}
        CFlag = {}
        for i in range(ClusterNumber):
            CFlag[i]=float("inf")


        for bird in self.Population:
            bird.PBestX = bird.X
            bird.Y = self.target.SquareSum(bird.X)
            bird.PbestY = bird.Y

            bird.CBestX = bird.X
            bird.CBestY = bird.Y

            if(bird.Y<=Flag):
                GBestX = bird.X
                Flag = bird.Y

            if(bird.Y<=CFlag.get(bird.CID)):
                CBest[bird.CID]=bird.X
                CFlag[bird.CID] = bird.Y

        #便利了一遍我们得到了全局最优的种群
        for bird in self.Population:
            bird.GBestX = GBestX
            bird.GBestY = Flag
            bird.CBestY=CFlag.get(bird.CID)
            bird.CBestX=CBest.get(bird.CID)



    def Running(self):
        #这里开始进入迭代运算
        for iterate in range(1,IterationsNumber+1):
            w = LinearW(iterate)
            #这个算的GBestX其实始终是在算下一轮的最好的玩意
            GBestX = [0. for _ in range(DIM)]
            Flag = float("inf")
            CBest = {}
            CFlag = {}
            for i in range(ClusterNumber):
                CFlag[i] = float("inf")

            for bird in self.Population:
                #更改为线性权重
                self.W = w
                x = self.ComputeX(bird)
                y = self.target.SquareSum(x)
                # 这里还是要无条件更细的,不然这里的话C1就失效了
                # if(y<=bird.Y):
                #     bird.X = x
                #     bird.PBestX = x
                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

                if (bird.Y <= CFlag.get(bird.CID)):
                    CBest[bird.CID] = bird.X
                    CFlag[bird.CID] = bird.Y

            for bird in self.Population:
                bird.GBestX = GBestX
                bird.GBestY=Flag
                bird.CBestY = CFlag.get(bird.CID)
                bird.CBestX = CBest.get(bird.CID)
            if((iterate+1)%100==0):
                self.kmeansAdd.RunningDivide(self.Population,iterate)


if __name__ == '__main__':
    start = time.time()
    dkmpso = DKMPSO()
    dkmpso.InitPopulation()
    dkmpso.Running()
    end = time.time()
    # for bird in dkmpso.Population:
    #     print(bird)
    print(dkmpso.Population[0])
    print("花费时长:",end-start)



其实和原来的直接分没太大区别,只是换了一个划分的标准,然后这里是每100次迭代重新划分一次,其实我感觉就一开始,划分后就不用划分会好一点,这个也是测试会好一点。

测试结果

首先,我们玩多种群的目的是,第一防止早熟,第二保证均匀性(后面上多目标,如果粒子都往一个方向跑,容前沿很难搞),第三 利用更多的信息,正对高纬度优化问题。

针对聚类划分问题

我这里针对10个维度,然后大概对100,200,1000,2000,10000...次迭代做了实验(大概20多个参数,然后每组大概10-20次实验)。 然后设置每轮K次重新计算中心。 首先,总体上看,初始化划分之后就不划分了,效果是最差的。

之后是针对多少轮一次进行测试,大概目前得到的设置方式是,按照迭代次数除以5来是比较好的,也是比较快的。也就是五倍左右 在这里插入图片描述 在这里插入图片描述

针对维度问题

低纬度<50

采用最传统的粒子群的效果是最好的

中纬度<500

采用我直接分子种群的效果不错

高纬度

采用这个有KMeans的效果和没有的区别不大,差距不大,综合情况下我推荐没有的。不过这个目前的话Kmeans其实没有用好,因为其实这个速度更新是和论文不一样的。

当然这些都是在有限的实验次数下,初步得到的结果,每个测试不跑个100+ 这个数据我说实话我是不太敢相信的。偶然性太高了。

之后是明天要研究这个拓扑结构的和强化学习的,那个拓扑结构的论文写得是真模棱两可,还有那篇介绍拓扑结构运用的,也是,不是看不起中文论文,是真的水分太大了。而且这中论文的学术造假可太容易做了,因为同样的代码,运行结果是不同的,稍微美化一下,数据就来了。

不过这里的话,我其实还是在做各种测试,说实话,要不断修改。

猜你喜欢

转载自juejin.im/post/7111719793173987365