自从上两篇博客详细讲解了Python遗传和进化算法工具箱及其在带约束的单目标函数值优化中的应用以及利用遗传算法求解有向图的最短路径之后,我经过不断学习工具箱的官方文档以及对源码的研究,更加掌握如何利用遗传算法求解更多有趣的问题了。
与前面的文章不同,本篇采用差分进化算法来优化SVM中的参数C和Gamma。(用遗传算法也可以,下面会给出效果比较)
首先简单回顾一下Python高性能实用型遗传和进化算法工具箱的用法。对于一个优化问题,需要做两个步骤便可进行求解:Step1:自定义问题类;Step2:编写执行脚本调用Geatpy进化算法模板对问题进行求解。在上一篇博客曾“详细”介绍过具体的用法:https://blog.csdn.net/weixin_37790882/article/details/84034956,但完整的中文教程可以参考官方文档。
下面切入主题:
首先简单描述一下SVM的使用。本文采用Python的sklearn库来跑SVM算法。sklearn中SVM的算法库分为两类,一类是分类的算法库,包括:SVC、NuSVC和LinearSVC 3个类。另一类是回归算法库,包括:SVR、NuSVR和LinearSVR 3个类。相关的类都包裹在sklearn.svm模块之中。
本文重点讲解利用SVC这个类来对iris数据集的数据进行分类。iris数据集的数据格式如下:
前4列是特征数据,第5列是标签数据。整个数据集一共有3种标签:Iris-setosa、Iris-versicolor、Iris-virginica。
本文采用的是将iris数据集拆分成训练集(iris_train.data)和测试集(iris_test.data)两部分的数据,已经拆分好的数据文件详见https://github.com/geatpy-dev/geatpy/tree/master/geatpy/demo/soea_demo/soea_demo6
采用SVC对数据进行分类的一般步骤为:
Step1:读取训练集的特征数据,并保存在一个Numpy array类型“矩阵”中,使得每一列代表一个特征,每一行代表一组数据,并对数据进行标准化处理。
Step2:读取训练集的标签数据,保存在一个Numpy array类型的行向量中。
Step3:寻找最优参数C和Gamma。
Step4:使用最优参数实例化SVC类的对象(即创建分类器对象),并调用它的成员函数fit()利用训练集的数据来拟合分类器模型。
Step5:用与Step1和Step2同样的方法读取测试集的特征数据和标签数据,并对特征数据进行标准化处理。
Step6:用Step4中训练好的分类器对标准化后的特征数据进行预测,预测出每组特征数据对应的标签。
Step7:把预测出的标签数据和读取到的测试集标签数据进行对比,计算正确率。
在上面的步骤中,寻找最优参数C和Gamma是一个关键步骤,一般采用固定步长的网格搜索策略以及交叉验证来寻找最优参数。而本文采用差分进化算法来寻找该最优参数,把交叉验证的平均得分作为待优化的目标,把C和Gamma参数作为决策变量。差分进化算法基本流程和一般的进化算法相差无几,这里就不赘述了,权威又易懂的参考文献如下:
Price, K.V., Storn, R.N. and Lampinen, J.A.. Differential Evolution: A Practical Approach to Global Optimization. : Springer, 2005.
代码实现(摘自Geatpy网站中的单目标优化案例6)
1)首先把模型写到自定义问题类中,代码如下:
# -*- coding: utf-8 -*-
"""MyProblem.py"""
import numpy as np
import geatpy as ea
from sklearn import svm
from sklearn import preprocessing
from sklearn.model_selection import cross_val_score
from multiprocessing.dummy import Pool as ThreadPool
class MyProblem(ea.Problem): # 继承Problem父类
def __init__(self):
name = 'MyProblem' # 初始化name(函数名称,可以随意设置)
M = 1 # 初始化M(目标维数)
maxormins = [-1] # 初始化maxormins(目标最小最大化标记列表,1:最小化该目标;-1:最大化该目标)
Dim = 2 # 初始化Dim(决策变量维数)
varTypes = [0, 0] # 初始化varTypes(决策变量的类型,元素为0表示对应的变量是连续的;1表示是离散的)
lb = [2**(-8)] * Dim # 决策变量下界
ub = [2**8] * Dim # 决策变量上界
lbin = [1] * Dim # 决策变量下边界(0表示不包含该变量的下边界,1表示包含)
ubin = [1] * Dim # 决策变量上边界(0表示不包含该变量的上边界,1表示包含)
# 调用父类构造方法完成实例化
ea.Problem.__init__(self, name, M, maxormins, Dim, varTypes, lb, ub, lbin, ubin)
# 目标函数计算中用到的一些数据
fp = open('iris_train.data')
datas = []
data_targets = []
for line in fp.readlines():
line_data = line.strip('\n').split(',')
data = []
for i in line_data[0:4]:
data.append(float(i))
datas.append(data)
data_targets.append(line_data[4])
fp.close()
self.data = preprocessing.scale(np.array(datas)) # 训练集的特征数据(归一化)
self.dataTarget = np.array(data_targets)
def aimFunc(self, pop): # 目标函数,采用多线程加速计算
Vars = pop.Phen # 得到决策变量矩阵
pop.ObjV = np.zeros((pop.sizes, 1)) # 初始化种群个体目标函数值列向量
def subAimFunc(i):
C = Vars[i, 0]
G = Vars[i, 1]
svc = svm.SVC(C=C, kernel='rbf', gamma=G).fit(self.data, self.dataTarget) # 创建分类器对象并用训练集的数据拟合分类器模型
scores = cross_val_score(svc, self.data, self.dataTarget, cv=10) # 计算交叉验证的得分
pop.ObjV[i] = scores.mean() # 把交叉验证的平均得分作为目标函数值
pool = ThreadPool(2) # 设置池的大小
pool.map(subAimFunc, list(range(pop.sizes)))
def test(self, C, G): # 代入优化后的C、Gamma对测试集进行检验
# 读取测试集数据
fp = open('iris_test.data')
datas = []
data_targets = []
for line in fp.readlines():
line_data = line.strip('\n').split(',')
data = []
for i in line_data[0:4]:
data.append(float(i))
datas.append(data)
data_targets.append(line_data[4])
fp.close()
data_test = preprocessing.scale(np.array(datas)) # 测试集的特征数据(归一化)
dataTarget_test = np.array(data_targets) # 测试集的标签数据
svc = svm.SVC(C=C, kernel='rbf', gamma=G).fit(self.data, self.dataTarget) # 创建分类器对象并用训练集的数据拟合分类器模型
dataTarget_predict = svc.predict(data_test) # 采用训练好的分类器对象对测试集数据进行预测
print("测试集数据分类正确率 = %s%%"%(len(np.where(dataTarget_predict == dataTarget_test)[0]) / len(dataTarget_test) * 100))
上面的代码里利用交叉验证得到的平均得分作为待优化的目标函数值,因为分数越高表示参数越好,因此上面的maxormins设置为[-1],表示待优化的目标是个最大化的目标。设置C和Gamma的搜索区间均为2的负8次方到2的8次方。
2)然后创建执行脚本,调用差分进化算法模板soea_DE_rand_1_bin进行利用DE/rand/1/bin差分进化算法优化来优化上面定义好的待优化模型,代码如下:
# -*- coding: utf-8 -*-
"""main.py"""
import geatpy as ea # import geatpy
from MyProblem import MyProblem # 导入自定义问题接口
if __name__ == '__main__':
"""===============================实例化问题对象==========================="""
problem = MyProblem() # 生成问题对象
"""=================================种群设置==============================="""
Encoding = 'RI' # 编码方式
NIND = 20 # 种群规模
Field = ea.crtfld(Encoding, problem.varTypes, problem.ranges, problem.borders) # 创建区域描述器
population = ea.Population(Encoding, Field, NIND) # 实例化种群对象(此时种群还没被初始化,仅仅是完成种群对象的实例化)
"""===============================算法参数设置============================="""
myAlgorithm = ea.soea_DE_rand_1_bin_templet(problem, population) # 实例化一个算法模板对象
myAlgorithm.MAXGEN = 30 # 最大进化代数
myAlgorithm.trappedValue = 1e-6 # “进化停滞”判断阈值
myAlgorithm.maxTrappedCount = 10 # 进化停滞计数器最大上限值,如果连续maxTrappedCount代被判定进化陷入停滞,则终止进化
myAlgorithm.logTras = 1 # 设置每隔多少代记录日志,若设置成0则表示不记录日志
myAlgorithm.verbose = True # 设置是否打印输出日志信息
myAlgorithm.drawing = 1 # 设置绘图方式(0:不绘图;1:绘制结果图;2:绘制目标空间过程动画;3:绘制决策空间过程动画)
"""===========================调用算法模板进行种群进化======================="""
[BestIndi, population] = myAlgorithm.run() # 执行算法模板,得到最优个体以及最后一代种群
BestIndi.save() # 把最优个体的信息保存到文件中
"""==================================输出结果============================="""
print('用时:%f 秒' % myAlgorithm.passTime)
print('评价次数:%d 次' % myAlgorithm.evalsNum)
if BestIndi.sizes != 0:
print('最优的目标函数值为:%s' % BestIndi.ObjV[0][0])
print('最优的控制变量值为:')
for i in range(BestIndi.Phen.shape[1]):
print(BestIndi.Phen[0, i])
else:
print('没找到可行解。')
"""=================================检验结果==============================="""
problem.test(C = BestIndi.Phen[0][0], G = BestIndi.Phen[0][1])
运行结果如下:
上面的执行脚本中调用了DE/rand/1/bin的差分进化算法进行进化优化,实际上还可以调用其他的比如遗传算法、遗传策略等的算法模板进行进化优化。例如调用最经典的遗传算法:
# 把这一行替换上面main.py中的soea_DE_rand_1_bin_templet...那一行
myAlgorithm = ea.soea_SGA_templet(problem, population)
按照上面注释中的描述修改main.py代码后运行结果如下:
可见采用SGA进行参数优化的效果稍微比不上采用DE/rand/1/bin差分进化算法,这意味着对于训练集的数据而言,后者分类的准确率比前者低;但对于测试集而言,两者分类的正确率一样。
最后回顾一下上一篇博客提到的”进化算法套路“(上篇写的是“遗传”,这篇拓展为“进化”):
该套路实现了具体问题、使用的算法以及所调用的相关算子之间的脱耦。而Geatpy工具箱已经内置了众多进化算法模板类以及相关的算子,直接调用即可。对于实际问题的求解,只需关心如何把问题写在自定义问题类中就好了。
本文所用到的实验代码很好地体现了这个流程,整个过程里面我没有关心进化算法的具体实现,只管怎么把待优化的模型写在自定义问题类MyProblem中。
更多详细的教程可以详见:http://geatpy.com/index.php/geatpy%E6%95%99%E7%A8%8B/
后续我将继续学习和挖掘该工具箱的更多深入的用法。希望这篇文章在帮助自己记录学习点滴之余,也能帮助大家!