基于粒子群算法求解指定算法的最优参数

基于粒子群算法求解指定算法的最优参数

引言
粒子群优化算法(Particle Swarm Optimization Algorithm,PSO)是由Eberhart和kennedy提出的一种基于群体协作的随机搜索算法。其原理来源于对鸟群捕食行为的研究,基本思想是通过群体中个体之间的协作和信息共享来寻找最优解。PSO的优势在于其简单的算法实现和简洁的参数设置。目前已经广泛应用于函数优化、图像处理等方面,但PSO也存在着早熟收敛、容易陷入局部最优、维数灾难等缺点,这也是需要解决的问题。

算法主要流程

开始
初始化粒子群
计算每个粒子的适应度
更新每个例子的速度和位置
达到全局最优或最大迭代次数
结束,输出全局最优位置和速度

算法公式

此处介绍的是惯性权重线性递减的PSO算法。这是Yuhui Shi于1998年提出的改进粒子群算法。除此之外,还有另外,还有带收缩因子的PSO算法(见附章[3])

粒子速度更新公式
v i = ω v i + c 1 r 1 ( p b e s t d − x i ) + c 2 r 2 ( g b e s t d − x i ) v_i = \omega v_i + c_1r_1(p_{bestd} - x_i) + c_2r_2(g_{bestd} - x_i) vi=ωvi+c1r1(pbestdxi)+c2r2(gbestdxi)

粒子位置更新公式
x i = x i + v i x_i = x_i + v_i xi=xi+vi

其中:

  1. ω v i \omega v_i ωvi理解为粒子先前的速度和惯性,这是其自身属性的体现; c 1 r 1 ( p b e s t d − x i ) c_1r_1(p_{bestd} - x_i) c1r1(pbestdxi)是粒子自身的学习性、思考性的体现; c 2 r 2 ( g b e s t d − x i ) c_2r_2(g_{bestd} - x_i) c2r2(gbestdxi)可以理解为粒子的信息共享、合作等社会性的体现。而其位置计算公式也是表明由于粒子间的相互影响,导致其位置的最终变化。

  2. v i v_i vi 为第 i i i 个粒子的速度

  3. ω \omega ω 为惯性因子,其较大时,全局收敛能力较强,局部收敛能力较弱,反之全局收敛能力较弱,局部收敛能力较强。其计算公式为:
    ω = ω m a x + ( i t e r − i t e r i ) × ( ω m a x − ω m i n ) i t e r \omega = \frac{\omega_{max} + (iter - iter_i) \times (\omega_{max} - \omega_{min})}{iter} ω=iterωmax+(iteriteri)×(ωmaxωmin)
    2.1 其中: i t e r iter iter 为最大迭代次数, i t e r i iter_i iteri为当前迭代次数, ω m a x \omega_{max} ωmax ω \omega ω最大值, ω m i n \omega_{min} ωmin ω \omega ω最小值

    2.2 当然, ω \omega ω有多重取法,包括自适应权重(见文附章[1])、随机权重取法等。

    2.3 根据以往实验经验, ω \omega ω通常取值范围在 [ 0.6 , 1.2 ] [0.6, 1.2] [0.6,1.2]时,算法具有较快的收敛速度。而当 ω = 1 \omega = 1 ω=1时,与基本PSO算法无异。

  4.  c 1 c_1 c1 c 2 c_2 c2为x学习因子,通常也称为加速常数, c 1 c_1 c1为每个粒子的个体学习因子, c 2 c_2 c2为每个粒子的社会学习因子。根据以往经验,通常其取值范围为 [ 0 , 4 ] [0, 4] [0,4],一般去 c 1 = c 2 = 2 c_1=c_2=2 c1=c2=2,但也不一定必须为2。

  5. r 1 r_1 r1 r 2 r_2 r2为区间 ( 0 , 1 ] (0, 1] (0,1]上的随机数。

  6. p b e s t d p_{bestd} pbestd和 g b e s t d g_{bestd} gbestd分别为粒子个体极值的第 d d d维和和粒子群极值的第 d d d维。

常见应用:Python3-PSO实现优化SVM参数

思路:
这里选择优化SVM的 γ \gamma γ C C C参数,介于粒子本身就有位置的属性,所以粒子的位置正好可以代表待优化的这两个参数。
而适应度,即目标函数的计算,就是求一次SVM的预测结果的某种指标评估。常见的有MSE、NRMSE、 R 2 R^2 R2等。

注意,此处代码仅是主要流程,其中适应度函数模型是使用的项目中其他模块,就不便放上来了
只要训练后可以返回一个numpy数组,里面是对应每个粒子的适应度值就可以。可以自己修改

import numpy as np
import matplotlib.pyplot as plt
from fitness import R2
from models.prep import Prep
from models.train import Train
from models.predict import Predict


class PSO(object):
    ''' 
    PSO 优化SVM类

    Params:
        train_data_path: str 训练文件路径
        pred_data_path: str 预测文件路径
        particle_size: Int 粒子群数量
        iteration_steps: Int 迭代次数
        train_row: tuple (0, 0) (起始值,结束值)
        pred_row: tuple (0, 0) (起始值,结束值)
        col: tuple (0, 0) (起始值,结束值)
        w: float 惯性权重
        c: tuple (c1,c2) 学习因子
        dim: int 空间维度
        c_range: tuple SVM C区间
        gamma_range: SVM gamma区间
        model: dict 目标函数模型配置
    '''

    def __init__(
        self,
        train_data_path,
        pred_data_path,
        particle_size,
        iteration_steps,
        train_row=(0, 0),
        pred_row=(0, 0),
        col=(0, 0),
        w=(0.6, 0.9),
        c=(2, 2),
        dim=2,
        c_range=(1, 10e8),
        gamma_range=(0, 0.1),
        model={
            'modelName': 'SVM',
            'fullParams': [{
                'key': 'C',
                'value': None
            }, {
                'key': 'gamma',
                'value': None
            }, {
                'key': 'kernal',
                'value': 'rbf'
            }, {
                'key': 'cache',
                'value': 512
            }]
        }):
        # 设置不以科学计数法输出
        np.set_printoptions(suppress=True, threshold=np.inf)

        # 适应度相应模型参数
        self.model = model

        # 训练/预测数据地址
        self.path = train_data_path
        self.pred = pred_data_path

        # 行/列(起始,结束)
        self.row = train_row
        self.col = col

        # 求取当前参数下的预测结果
        self.prep = self.model_prep()

        self.w = w
        self.c1 = c[0]
        self.c2 = c[1]
        self.dim = dim
        self.c_range = c_range
        self.gamma_range = gamma_range
        self.particle_size = particle_size
        self.iteration_steps = iteration_steps

        # 根据C范围初始化粒子位置x
        self.rf_mln = np.random.randint(
            self.c_range[0],
            self.c_range[1],
            self.particle_size,
            dtype='i'
        )

        # 根据gamma范围初始化粒子位置y
        self.svm_g = np.random.randint(
            self.gamma_range[0],
            self.gamma_range[1],
            self.particle_size,
            dtype="i"
        )
        # 初始化粒子位置
        self.x = self.init_position()

        # 初始化粒子速度
        self.v = np.random.rand(self.particle_size, self.dim)

        # 初始化适应度值
        self.fitness = self.calc_fitness(self.x)['fitness']

        # 初始化局部最佳位置
        self.p_best = self.x

        # 初始化全局最佳位置
        self.g_best = self.x[np.argmin(self.fitness)]

        # 初始化局部最佳适应度
        self.p_best_fitness = self.fitness

        # 初始化全局最佳适应度
        self.g_best_fitness = np.max(self.fitness)

        # 最佳模型
        self.g_best_model = ''

    def init_position(self):
        '''
        初始化粒子群位置,即待优化参数
        Return x_pos 粒子位置
        '''
        x_pos = []
        for index, i in enumerate(self.rf_mln):
            x_pos.append([self.rf_mln[index], self.svm_g[index]])
        x_pos = np.array(x_pos)

        return x_pos

    def calc_fitness(self, x):
        '''
        计算粒子适应度
        Params: 
            x list 粒子群位置
        Return 适应度list
        '''
        fitness_list = []
        model_list = []
        for index, val in enumerate(x):
            # 调配最新参数
            self.model[0]['fullParams'][0]['value'] = x[index][0]
            self.model[0]['fullParams'][0]['value'] = x[index][1]
            
            # 训练
            res = self.model_train()
            fitness_list.append(res[1])
            model_list.append(res[2][0])

        fitness_list = np.array(fitness_list)
        model_list = np.array(model_list)
        return {'fitness': fitness_list, 'models': model_list}

    def pso_algor(self, r1, r2, c_iter):
        '''
        粒子群算法公式
        Param: 
            r1 [0,1)Float 之间的伪随机数
            r2 [0,1)Float 之间的伪随机数
            c_iter  Int    当前迭代次数
        '''
        w_max = self.w[0]
        w_min = self.w[1]

        # 更新速度
        self.v = self.w * self.v + self.c1 * r1 * \
            (self.p_best - self.x) + self.c2 * r2 * \
            (self.g_best - self.x)
        # 更新例子位置
        self.x = self.x + self.v

        # 递减W,暂时未启用
        w = w_max + (self.iteration_steps - c_iter) * \
                    (w_max - w_min) / self.iteration_steps
        self.w = w

    def update(self, fitness, model):
        '''
        更新粒子位置和速度
        Params: 
            fitness: list 粒子当前适应度
        '''
        # 需要更新的个体
        pb_fitness_minus = abs(self.p_best_fitness - 1)
        new_fitness_minus = abs(fitness - 1)
        update_id = np.greater(pb_fitness_minus, new_fitness_minus)

        # 更新个体最佳位置
        self.p_best[update_id] = self.x[update_id]

        # 更新个体最佳适应度
        self.p_best_fitness[update_id] = fitness[update_id]

        p_best_fitness_minus = np.array(abs(self.p_best_fitness - 1))
        min_minus_index = np.where(
            p_best_fitness_minus == np.min(abs(self.p_best_fitness - 1)))

        # 当出现更小的 fitness 时更新全局最佳 fitness 和最佳model
        if np.min(p_best_fitness_minus) < abs(self.g_best_fitness - 1):
            self.g_best = self.x[np.argmin(fitness)]
            self.g_best_fitness = self.p_best_fitness[min_minus_index]
            self.g_best_model = model[min_minus_index]

    def dfigure(self):
        '''
        图像准备
        '''
        plt.clf()
        plt.scatter(self.x[:, 0], self.x[:, 1], s=10, color='k')
        plt.xlim(-100, 1000)
        plt.ylim(-100, 100)
        plt.xlabel('c')
        plt.ylabel('gamma')
        plt.pause(0.01)

    def iteration(self):
        '''
        进行迭代
        '''
        plt.figure()
        for step in range(self.iteration_steps):
            r1 = np.random.rand(self.particle_size, self.dim)
            r2 = np.random.rand(self.particle_size, self.dim)
            self.pso_algor(r1, r2, step)
            self.dfigure()
            fitness = self.calc_fitness(self.x)
            fitness_list = fitness['fitness']  # ndArray
            models_list = fitness['models']  # list
            self.update(fitness_list, models_list)

            print('最佳适应度:', self.g_best_fitness,
                  '\n', '最佳位置:', self.g_best, '\n')
        print('PSO迭代结束,即将使用最优参数直接预测...')

    def model_train(self):
        '''
        模型训练,里面调用的是另一个模块,就不方便放上来了
        '''
        ret = Train().flow(self.model, self.path, cv=0, row=self.row, col=self.col)
        predict = Predict().multi_predict(
            ret['trainlist'], self.prep['predict']['X']['data'], self.prep['predict']['y']['std_obj'])
        ytrue = Predict()._inverse_transform(
            self.prep['predict']['y']['std_obj'], self.prep['predict']['y']['data'])
        score = R2(ytrue, predict[0])

        return np.array([
            ret['data'],
            score,
            ret['trainlist']
        ])

    def model_prep(self):
        '''
        数据预处理,里面调用的是另一个模块,就不方便放上来了
        '''
        train_data = Prep().dataset_load(
            self.path, self.row[0], self.row[1], self.col[0], self.col[1], dev=True)
        predi_data = Prep().dataset_load(
            self.pred, self.row[0], self.row[1], self.col[0], self.col[1], dev=True)
        train_std_x = Prep().data_preprocess(train_data[0])
        train_std_y = Prep().data_preprocess(train_data[1])
        predi_std_x = Prep().data_preprocess(predi_data[0])
        predi_std_y = Prep().data_preprocess(predi_data[1])
        train_data = {
            'X': {'std_obj': train_std_x[0], 'data': train_std_x[1]},
            'y': {'std_obj': train_std_y[0], 'data': train_std_y[1]}
        }
        predi_data = {
            'X': {'std_obj': predi_std_x[0], 'data': predi_std_x[1]},
            'y': {'std_obj': predi_std_y[0], 'data': predi_std_y[1]}
        }
        return{
            'train': train_data,
            'predict': predi_data
        }

if __name__ == "__main__":
    pvpso = PSO('路径1', '路径2', 20, 10)
    pvpso.iteration()
    plt.show()

附章

[1]. ω \omega ω的自适应权重取值法

此方法主要是为了平衡全局收敛能力和局部收敛能力,

可参考文献:[1]李龙澍,张效见.一种新的自适应惯性权重混沌PSO算法[J].计算机工程与应用,2018,54(09):139-144.

[2]. 带收缩因子的PSO算法

该方法目的主要是保证PSO的收敛性

其速度公式如下:
v i = φ { v i + c 1 r 1 ( p b e s t d − x i ) + c 2 r 2 ( g b e s t d − x i ) } v_i = \varphi\{v_i + c_1r_1(p_{bestd} - x_i) + c_2r_2(g_{bestd} - x_i)\} vi=φ{vi+c1r1(pbestdxi)+c2r2(gbestdxi)}
其中 φ \varphi φ为收缩因子,其计算方式如下:

φ = 2 ∣ 2 − c − c 2 − 4 c ∣ \varphi = \frac{2}{|2 - c - \sqrt{c^2 - 4c}|} φ=2cc24c 2
在这里对于 c c c c = c 1 + c 2 c = c_1+c_2 c=c1+c2 c > 4 c > 4 c>4

更多文章,欢迎查看我的博客或我的git仓库
如果有帮到你的话,欢迎给个Star,谢谢~

你可能感兴趣的:(PSO)