多目标粒子群算法python实现(双目标)

一. 内容简介

多目标粒子群算法python实现(双目标)

二. 软件环境

2.1vsCode

2.2Anaconda

version: conda 22.9.0

三. 代码

3.1 代码

import numpy as np
import matplotlib.pyplot as plt
from itertools import combinations
import warnings

# 主函数
def MOPSO(params, MultiObj):
    Np = params['Np']
    Nr = params['Nr']
    maxgen = params['maxgen']
    W = params['W']
    C1 = params['C1']
    C2 = params['C2']
    ngrid = params['ngrid']
    maxvel = params['maxvel']
    u_mut = params['u_mut']
    fun = MultiObj['fun']
    # 表示有几个变量,
    nVar = MultiObj['nVar']
    # 变量的最小值,
    var_min = MultiObj['var_min']
    # 变量的最大值
    var_max = MultiObj['var_max']

    # 初始化例子的取值,位置代表变量取值
    # 200x3
    POS = np.random.rand(Np, nVar) * (var_max - var_min) + var_min
    # 每个粒子的每个方向速度
    # 200x3
    # 前面行,后面列
    VEL = np.zeros((Np, nVar))
    # 代表每个粒子的适应度,有两个目标的适应度
    # 200x2
    POS_fit = fun(POS)
    # 第一轮都是最优
    # 用于记录每个粒子的最优参数
    PBEST = POS.copy()
    # 用于记录每个粒子的最优适应度
    # 适应度要尽可能的小
    PBEST_fit = POS_fit.copy()
    
    # 检查支配关系
    # DOMINATED里面=1的点,是要被淘汰的,
    DOMINATED = checkDomination(POS_fit)
    
    # 精英库,将最优节点添加进精英库
    REP = {}
    # 精英库参数添加,~DOMINATED = 1 时候,会被添加进去
    REP['pos'] = POS[~DOMINATED, :]
    # 最优适应度添加
    REP['pos_fit'] = POS_fit[~DOMINATED, :]
    # REP['pos'] = POS[np.logical_not(DOMINATED), :]
    # REP['pos_fit'] = POS_fit[np.logical_not(DOMINATED), :]
    # 更新了网格质量
    REP = updateGrid(REP, ngrid)
    # 求出最大速度
    maxvel = (var_max - var_min) * maxvel / 100
    # 代数
    gen = 1

    # 打印代数,以及精英粒子数
    print(f"Generation #0 - Repository size: {REP['pos'].shape[0]}")

    stopCondition = False
    while not stopCondition:
        # 选取领头节点
        h = selectLeader(REP)
        # 更新速度
        VEL = W * VEL + C1 * np.random.rand(Np, nVar) * (PBEST - POS) + C2 * np.random.rand(Np, nVar) * (np.tile(REP['pos'][h, :], (Np, 1)) - POS)
        # 更新位置
        POS = POS + VEL
        # 粒子变异
        POS = mutation(POS, gen, maxgen, Np, var_max, var_min, nVar, u_mut)
        # 超出边界的,对速度和边界做一个修正
        POS, VEL = checkBoundaries(POS, VEL, maxvel, var_max, var_min)
        # 计算适应度
        POS_fit = fun(POS)

        # 更新精英库
        REP = updateRepository(REP, POS, POS_fit, ngrid)
        REP['reduce'] = 0
        if REP['pos'].shape[0] > Nr:
            # 精英库满了,在比较,删除相对不好的
            REP = deleteFromRepository(REP, REP['pos'].shape[0] - Nr, ngrid)

        # 返回1说明,说明前面那个点要更优秀
        # 也就是说 pos_best 中 1 的参数,是要比PBEST_fit更好的
        pos_best = dominates(POS_fit, PBEST_fit)
        # dominates返回值中1是可以明确比出好坏的,而0是不可以的
        # ~就相当于给1变成00变成1,也就是说,best_pos中1是说明比不出来好坏的参数
        best_pos = ~dominates(PBEST_fit, POS_fit)
        # 那么就让其中接近一半的点都为0,因为比不出来好坏,就部分替换,本轮最优解部分替换之前的最优解
        best_pos[np.random.rand(Np) >= 0.5] = False
        
        # pos_best中为1的点是更好的点
        if np.sum(pos_best) > 1:
            # 先给表现好的点进行更新
            PBEST_fit[pos_best, :] = POS_fit[pos_best, :]
            PBEST[pos_best, :] = POS[pos_best, :]
        if np.sum(best_pos) > 1:
            # 给比不出来好坏的点更新
            PBEST_fit[best_pos, :] = POS_fit[best_pos, :]
            PBEST[best_pos, :] = POS[best_pos, :]
        if REP['reduce'] > 0:
            # 都是从0开始的
            print(f"Generation #{gen} - Repository size: {REP['pos'].shape[0]} - Eliminate size: {REP['eliminate']} - Add size: {REP['add']} - Delete size: {REP['reduce']}")
        else:
            print(f"Generation #{gen} - Repository size: {REP['pos'].shape[0]} - Eliminate size: {REP['eliminate']} - Add size: {REP['add']} - Delete size: {0}")
        gen += 1
        if gen > maxgen:
            stopCondition = True
    
    h_rep = plt.plot(REP['pos_fit'][:, 0], REP['pos_fit'][:, 1], 'ok')

def updateRepository(REP, POS, POS_fit, ngrid):
    # DOMINATED里面=1的点,是要被淘汰的,也就是说1是不好的点
    DOMINATED = checkDomination(POS_fit)
    # 获取原来精英库的粒子库
    # REP['pos'].shape[0]看的这个数组的行数,从1开始
    countElite = REP['pos'].shape[0]
    countCivilian = POS.shape[0] - sum(DOMINATED)
    # 将这些优秀的点添加进入精英库中,axis表示拼接的方向,axis = 0 表示要从行方向拼接
    REP['pos'] = np.concatenate((REP['pos'], POS[~DOMINATED, :]), axis=0)
    REP['pos_fit'] = np.concatenate((REP['pos_fit'], POS_fit[~DOMINATED, :]), axis=0)
    # 在对精英库,进行支配排序
    DOMINATED = checkDomination(REP['pos_fit'])
    # 求原来库中被淘汰的个数,从0到countElite,但是并不包含countElite,刚好可以给原来的精英库覆盖,因为数组从0开始
    REP['eliminate'] = sum(DOMINATED[:countElite])
    REP['add'] = countCivilian - sum(DOMINATED[countElite:])
    # 用优秀点覆盖原来的点
    REP['pos_fit'] = REP['pos_fit'][~DOMINATED, :]
    REP['pos'] = REP['pos'][~DOMINATED, :]
    # 更新网格质量
    REP = updateGrid(REP, ngrid)
    # 返回精英库
    return REP

def checkBoundaries(POS, VEL, maxvel, var_max, var_min):
    # 和malba 中 repmat 类似,功能一样
    # np.tile(array, (m, n))
    # 相当于array在行方向变为m个
    # 相当于array在列方向变成n个
    MAXLIM = np.tile(var_max, (POS.shape[0], 1))
    MINLIM = np.tile(var_min, (POS.shape[0], 1))
    MAXVEL = np.tile(maxvel, (POS.shape[0], 1))
    MINVEL = -MAXVEL

    # 如果速度过大过小的的,都会拿一个数值限制
    VEL[VEL > MAXVEL] = MAXVEL[VEL > MAXVEL]
    VEL[VEL < MINVEL] = MINVEL[VEL < MINVEL]
    # 碰壁以后,速度变反
    VEL[POS > MAXLIM] = -VEL[POS > MAXLIM]
    # 修正为边界
    POS[POS > MAXLIM] = MAXLIM[POS > MAXLIM]
    
    # 碰壁以后,速度变反
    VEL[POS < MINLIM] = -VEL[POS < MINLIM]
    # 修正为边界
    POS[POS < MINLIM] = MINLIM[POS < MINLIM]

    # 返回位置,速度
    return POS, VEL

def checkDomination(fitness):
    # 粒子个数
    Np = fitness.shape[0]
    # 用于存放支配关系
    dom_vector = np.zeros(Np)
    # 排列组合算是
    # all_perm = list(combinations(range(1, Np+1), 2))
    # matlab1开始,python从0开始
    all_perm = list(combinations(range(0, Np+0), 2))
    # 给上下拼接到一起
    all_perm = np.concatenate((all_perm, np.flip(all_perm, axis=1)), axis=0)
    c = all_perm[:, 0]
    # 返回1说明,说明前面那个点要更优秀
    d = dominates(fitness[all_perm[:, 0], :], fitness[all_perm[:, 1], :])


    # 并不能根据直接的支配关系来找比较好的点,A>B,C>A,这中A就会被淘汰的,所以只能通过d==1找出一定会被淘汰的点
    dominated_particles = np.unique(all_perm[d == 1, 1])
    # 把要淘汰的点标记为1
    dom_vector[dominated_particles] = 1
    # 转化为布尔值,不然后边没办法按位取反
    dom_vector = dom_vector.astype(bool)
    # 返回要淘汰的点
    return dom_vector

def dominates(x, y):
    # all axis = 1 说明要从行方向来看,1行都是1才返回1
    # any axis = 1 说明要从行方向来看,1行有1就返回1
    # 这块是要x全部都要小于y,而且x和y还不能说两个维度都相等
    # 返回1说明,x这个点要更优秀
    result = np.all(x <= y, axis=1) & np.any(x < y, axis=1)
    return result

def updateGrid(REP, ngrid):
    # 目标适应度个数
    ndim = REP['pos_fit'].shape[1]
    # 20个网格,21和节点,REP里面的点就是帕利托前沿的点了
    REP['hypercube_limits'] = np.zeros((ngrid + 1, ndim))
    # 把两个适应度均分ngrid份
    for dim in range(ndim):
        REP['hypercube_limits'][:, dim] = np.linspace(np.min(REP['pos_fit'][:, dim]), np.max(REP['pos_fit'][:, dim]), ngrid + 1)
    # 查看精英库里面的节点个数,0代表行数,1代表列数
    npar = REP['pos_fit'].shape[0]
    # 记录索引,将下标转换为矩阵的线性索引,
    REP['grid_idx'] = np.zeros(npar, dtype=int) # 先行后列
    # 网格位置
    REP['grid_subidx'] = np.zeros((npar, ndim), dtype=int)
    for n in range(npar):
        idnames = ""
        # 这块算是计算了两个方向的拥挤度了
        for d in range(ndim):
            # 这是找这个适应度在第几格,这个相比于matlab代码去除了减一,因为python从0开始的
            REP['grid_subidx'][n, d] = np.where(REP['pos_fit'][n, d] <= REP['hypercube_limits'][:, d])[0][0]
            # 这块是有一个边缘处理的
            if REP['grid_subidx'][n, d] == -1:
                REP['grid_subidx'][n, d] = 0
            # 这个就不用了
            # idnames += "," + str(REP['grid_subidx'][n, d])
        # REP['grid_idx'][n] = eval(f"np.ravel_multi_index([{idnames[1:]}], [ngrid] * ndim)")
        row = REP['grid_subidx'][n, 0]
        col = REP['grid_subidx'][n, 1]
        if ndim == 2:
            array_shape = (ngrid,ngrid)
        if ndim == 3:
            array_shape = (ngrid,ngrid,ngrid)
        # 获得索引位置
        REP['grid_idx'][n] = sub2ind(array_shape,row,col)
    
    # 网格质量这块,20不够的,原来的代码可能是只考虑一个维度的网格,后来考虑;两个维度的网格,可能忘记改了
    REP['quality'] = np.zeros((ngrid*ngrid, 2))
    # 去除重复的,而且还会唯一元素出现的个数,
    ids, counts = np.unique(REP['grid_idx'], return_counts=True)
    for i in range(len(ids)):
        REP['quality'][i, 0] = ids[i]
        # 这个是用来统计同一个网格出现的次数,这个可以用其他来替代,
        # REP['quality'][i, 1] = 10 / np.sum(REP['grid_idx'] == ids[i])
        # 这是我自己修改的
        REP['quality'][i, 1] = 10 / counts[i]
    return REP

def selectLeader(REP):
    # 按概率走的,都能选到,只是说,点少的,更容易选中,有点轮盘赌的味道
    prob = np.cumsum(REP['quality'][:, 1])
    # 按概率选一个网格
    sel_hyp = REP['quality'][np.where(np.random.rand() * np.max(prob) <= prob)[0][0], 0]
    # idx是点的顺序
    idx = np.arange(len(REP['grid_idx']))
    # 把这个网格里面的点选出来
    selected = idx[REP['grid_idx'] == sel_hyp]
    # 再从选出来的点中,随机取一个
    selected = selected[np.random.randint(len(selected))]
    # 返回选的点
    return selected

def deleteFromRepository(REP, n_extra, ngrid):
    REP['reduce'] = n_extra
    # 先确定有多少个点
    crowding = np.zeros(REP['pos'].shape[0])
    for m in range(REP['pos_fit'].shape[1]):
        # 对适应度排序,排序是为了算距离
        m_fit = REP['pos_fit'][:, m]
        # id记录的是原来的位置
        idx = np.argsort(m_fit)
        # 排序完的,1234,这种是对应的距离
        m_fit_sorted = m_fit[idx]

        # 计算点和相邻两个点的距离,np.inf表示无限大
        m_up = np.concatenate((m_fit_sorted[1:], [np.inf]))
        m_down = np.concatenate(([np.inf], m_fit_sorted[:-1]))
        distance = (m_up - m_down) / (max(m_fit_sorted) - min(m_fit_sorted))
        
        # 再把顺序换回去,idx_back就是每个粒子距离再distance中的位置
        idx_back = np.argsort(idx)

        # 因为 inf - inf 会变成 NAN 会报错,所以要忽略这个,后边会对这个进行处理
        # 忽略 'invalid value encountered in add' 类型的 RuntimeWarning
        warnings.filterwarnings('ignore', category=RuntimeWarning, message="invalid value encountered in add")
        crowding = crowding + distance[idx_back]
        # 恢复 RuntimeWarning
        warnings.resetwarnings()

    # 给nan变成inf,方便排序
    crowding[np.isnan(crowding)] = np.inf
    # del_idx = np.argsort(crowding)
    # del_idx = del_idx[:n_extra]
    # 排序,选出距离最小的点
    del_idx = np.argsort(crowding)[:n_extra]


    # axis = 0 表示沿行进行,np.delete 是 NumPy 库中的一个函数,用于删除数组中指定的元素。
    REP['pos'] = np.delete(REP['pos'], del_idx, axis=0)
    REP['pos_fit'] = np.delete(REP['pos_fit'], del_idx, axis=0)
    # 更新网格
    REP = updateGrid(REP, ngrid)
    return REP

def mutation(POS, gen, maxgen, Np, var_max, var_min, nVar, u_mut):
    # 给种群数分三份,np.floor 用于取整
    fract = Np / 3 - np.floor(Np / 3)
    if fract < 0.5:
        # np.round 四舍五入 np.ceil向上取整
        sub_sizes = [np.ceil(Np / 3), np.round(Np / 3), np.round(Np / 3)]
    else:
        sub_sizes = [np.round(Np / 3), np.round(Np / 3), np.floor(Np / 3)]
    # 用来确定粒子
    cum_sizes = np.cumsum(sub_sizes)
    
    # 第二个种群全部变异
    nmut = np.round(u_mut * sub_sizes[1])
    if nmut > 0:
        # 按概率抽选出要变异的粒子,replace=false表示是不放回抽样
        idx = cum_sizes[0] + np.random.choice(np.arange(sub_sizes[1]), size=int(nmut), replace=False)
        # 给取整,map可以对数组批量处理,list转换结果
        idx = list(map(int, idx))
        # 对位置进行随机
        POS[idx, :] = np.random.rand(int(nmut), nVar) * (var_max - var_min) + var_min
    
    # 第三个种群部分变异,**是指数
    per_mut = (1 - gen / maxgen) ** (5 * nVar)
    nmut = np.round(per_mut * sub_sizes[2])
    if nmut > 0:
        idx = cum_sizes[1] + np.random.choice(np.arange(sub_sizes[2]), size=int(nmut), replace=False)
        # 给取整,map可以对数组批量处理,list转换结果
        idx = list(map(int, idx))
        # 对位置进行随机
        POS[idx, :] = np.random.rand(int(nmut), nVar) * (var_max - var_min) + var_min
    # 返回位置
    return POS

def sub2ind(shape, row, col):
    # 将行索引和列索引转换为线性索引
    return row * shape[1] + col
# 设置参数
params = {
    # 粒子数
    'Np': 500,
    # 精英库
    'Nr': 1000,
    'maxgen': 200,
    'W': 0.4,
    'C1': 2,
    'C2': 2,
    # 网格个数
    'ngrid': 20,
    'maxvel': 5,
    'u_mut': 0.5 
}

# 设置多目标函数
MultiObj = {
    'fun': lambda x: np.column_stack(
        (-10 * (np.exp(-0.2 * np.sqrt(x[:, 0] ** 2 + x[:, 1] ** 2)) + np.exp(-0.2 * np.sqrt(x[:, 1] ** 2 + x[:, 2] ** 2))),
         np.sum(np.abs(x) ** 0.8 + 5 * np.sin(x ** 3), axis=1))
    ),
    'nVar': 3,
    # 这块是直接处理了
    'var_min': np.array([-5, -5, -5]),
    'var_max': np.array([5, 5, 5])
}
REP = MOPSO(params, MultiObj)

3.2 效果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值