引言
粒子群优化算法(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(pbestd−xi)+c2r2(gbestd−xi)
粒子位置更新公式
x i = x i + v i x_i = x_i + v_i xi=xi+vi
其中:
ω 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(pbestd−xi)是粒子自身的学习性、思考性的体现; c 2 r 2 ( g b e s t d − x i ) c_2r_2(g_{bestd} - x_i) c2r2(gbestd−xi)可以理解为粒子的信息共享、合作等社会性的体现。而其位置计算公式也是表明由于粒子间的相互影响,导致其位置的最终变化。
v i v_i vi 为第 i i i 个粒子的速度
ω \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+(iter−iteri)×(ω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算法无异。
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。
r 1 r_1 r1和 r 2 r_2 r2为区间 ( 0 , 1 ] (0, 1] (0,1]上的随机数。
p b e s t d p_{bestd} pbestd和 g b e s t d g_{bestd} gbestd分别为粒子个体极值的第 d d d维和和粒子群极值的第 d d d维。
思路:
这里选择优化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]李龙澍,张效见.一种新的自适应惯性权重混沌PSO算法[J].计算机工程与应用,2018,54(09):139-144.
该方法目的主要是保证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(pbestd−xi)+c2r2(gbestd−xi)}
其中 φ \varphi φ为收缩因子,其计算方式如下:
φ = 2 ∣ 2 − c − c 2 − 4 c ∣ \varphi = \frac{2}{|2 - c - \sqrt{c^2 - 4c}|} φ=∣2−c−c2−4c∣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,谢谢~