文章目录
参考博客:https://blog.csdn.net/weixin_37790882/article/details/84034956
遗传算法
被用来进行目标优化的,即选择什幺样的个体时,可以使目标值最高,优胜劣汰
遗传算法中每一条染色体,对应着遗传算法的一个解决方案,一般我们用适应性函数(fitness function)来衡量这个解决方案的优劣。所以从一个基因组到其解的适应度形成一个映射。可以把遗传算法的过程看作是一个在多元函数里面求最优解的过程。 可以这样想象,这个多维曲面里面有数不清的“山峰”,而这些山峰所对应的就是局部最优解。而其中也会有一个“山峰”的海拔最高的,那幺这个就是全局最优解。而遗传算法的任务就是尽量爬到最高峰,而不是陷落在一些小山峰。(另外,值得注意的是遗传算法不一定要找“最高的山峰”,如果问题的适应度评价越小越好的话,那幺全局最优解就是函数的最小值,对应的,遗传算法所要找的就是“最深的谷底”
计算 x + 16 ∗ s i n ( 5 ∗ x ) + 10 ∗ c o s ( 4 ∗ x ) x + 16 * sin(5 * x) + 10 * cos(4 * x) 1 6 ∗ s i n ( 5 ∗ x ) + 1 0 ∗ c o s ( 4 ∗ x ) 的最大值,x在区间(5,20)内,
# -*- coding: utf-8 -*- import numpy as np import matplotlib.pyplot as plt # 适应度函数,目标函数,求取最大值 def fitness(x): return x + 16 * np.sin(5 * x) + 10 * np.cos(4 * x) # 个体类 class indivdual: def __init__(self): self.x = 0 # 染色体编码 self.fitness = 0 # 个体适应度值 def __eq__(self, other): self.x = other.x self.fitness = other.fitness # 初始化种群 # pop为种群适应度存储数组,N为个体数 def initPopulation(pop, N): for i in range(N): ind = indivdual() # 个体初始化 ind.x = np.random.uniform(-10, 10) # 个体编码。-10,10的正态分布,可以自己设定限定边界 ind.fitness = fitness(ind.x) # 计算个体适应度函数值 pop.append(ind) # 将个体适应度函数值添加进种群适应度数组pop # 选择过程 def selection(N): # 种群中随机选择2个个体进行变异(这里没有用轮盘赌,直接用的随机选择) return np.random.choice(N, 2) # 交叉/重组过程 def crossover(parent1, parent2): ''' 这里选用了线性交叉重组VarChild[i] = VarParent1[i] ·α + VarParent2[i] ·(1−α),i ∈ [1,2,...,N] :param parent1: 父辈1 :param parent2: 父辈2 :return: 经过交叉后的子辈1,子辈2 ''' child1, child2 = indivdual(), indivdual() # 父亲,母亲初始化 child1.x = 0.9 * parent1.x + 0.1 * parent2.x # 交叉0.9,0.1,可以设置其他系数 child2.x = 0.1 * parent1.x + 0.9 * parent2.x child1.fitness = fitness(child1.x) # 子1适应度函数值 child2.fitness = fitness(child2.x) # 子2适应度函数值 return child1, child2 # 变异过程 def mutation(pop): ''' :param pop: 种群列表 :return: 从种群中挑选一个个体进行变异 ''' # 种群中随机选择一个进行变异 ind = np.random.choice(pop) # 用随机赋值的方式进行变异 ind.x = np.random.uniform(5, 20) # 均匀分布 ind.fitness = fitness(ind.x) # 最终执行 def implement(): # 种群中个体数量 N = 40 # 种群 POP = [] # 迭代次数 iter_N = 400 # 初始化种群 initPopulation(POP, N) # 进化过程 for it in range(iter_N): # 遍历每一代 a, b = selection(N) # 随机选择两个个体作为父辈 if np.random.random() < 0.65: # 以0.65的概率进行交叉结合 child1, child2 = crossover(POP[a], POP[b]) new = sorted([POP[a], POP[b], child1, child2], key=lambda ind: ind.fitness, reverse=True) # 依据函数值,将父母亲和子代进行比较,保留最好的两个(值最大的) POP[a], POP[b] = new[0], new[1] # 种群总数量不变,优胜劣汰 if np.random.random() < 0.1: # 以0.1的概率进行变异 mutation(POP) return POP if __name__ == '__main__': pop = implement() # 绘图代码 def func(x): return x + 16 * np.sin(5 * x) + 10 * np.cos(4 * x) x = np.linspace(5, 20, 100000) y = func(x) scatter_x = np.array([ind.x for ind in pop]) scatter_y = np.array([ind.fitness for ind in pop]) best = sorted(pop, key=lambda pop: pop.fitness, reverse=True)[0] # 最佳点 print('best_x', best.x) print('best_y', best.fitness) plt.plot(x, y) # plt.scatter(scatter_x, scatter_y, c='r') plt.scatter(best.x, best.fitness, c='g', label='best point') plt.legend() plt.show()
best_x 14.155236796527797 best_y 40.06386706919228
geatpy实现遗传算法
geatpy的官方文档:http://geatpy.com/index.php/details/
geatpy官网: http://www.geatpy.com
文章参考:https://blog.csdn.net/weixin_37790882/article/details/84034956
准备工作:安装geatpy包
pip install geatpy
一、基础术语
个体染色体:决策变量编码后得到的行向量,如二级制编码(决策变量)x1=010,x2=001,个体[x1,x2]
个体表现型:对个体染色体进行解码,如上述解码x1=2,x2=1; “1 2”,它就是个体的表现型
染色体区域描述器
注:可以进行实际值编码,此时个体染色体等同个体表现型。
种群染色体矩阵:矩阵每一行代表一个个体
种群表现型矩阵:对种群染色体矩阵解码
种群个体违反约束矩阵(CV):它每一行对应一个个体,每一列对应一种约束条件(可以是等式约束或不等式约束)。CV矩阵中元素小于或等于0表示对应个体满足对应的约束条件,大于0则表示不满足,且越大表示违反约束条件的程度越高。比如有两个约束条件:
x 1 + 2 x 3 < = 2 x_1+2x_3<=2 2 x 3 < 2
x 1 + x 2 + x 3 = 1 x_1+x_2+x_3=1 1
如何计算CV矩阵?可以创建两个列向量CV1和CV2,然后把它们左右拼合而成一个CV矩阵。
import numpy as np """phen为种群表现性矩阵,由4个个体组成""" phen = np.array([[1, 0, 0], [1.1, 1, 2], [2, -3.1, 2.1], [1, -1, 0]]) """x1,x2,x3为决策变量""" x1 = phen[:, 0] x2 = phen[:, 1] x3 = phen[:, 2] """计算CV矩阵""" cv1 = x1 + 2*x2 - 2 # 不等式约束 cv2 = np.abs(x1 + x2 + x3 - 1) # 等式约束,加绝对值,就可以保证CV矩阵的值只有等于0满足约束 CV = np.vstack((cv1,cv2)).T # 上下合并两个行向量,并转置 print("种群约束矩阵: ",CV)
种群约束矩阵: [[-1. 0. ] [ 1.1 3.1] [-6.2 0. ] [-3. 1. ]]
由此可见,第一个个体满足两个约束条件;第二个个体违反了2个约束条件;第三和第四个个体满足第一个约束条件但违反了第二个约束条件。
下面学一下:geatpy包创建真实值染色体矩阵
import numpy as np from geatpy import crtpc # help(crtpc) # 查看帮助 Nind = 7 # 定义种群规模(个体数目) Encoding = 'RI' # 表示采用“实整数编码”,即变量可以是连续的也可以是离散的 # 创建“区域描述器”,表明有4个决策变量,范围分别是[-3.1, 4.2], [-2, 2],[0, 1],[3, 3], # FieldDR第三行[0,0,1,1]表示前两个决策变量是连续型的,后两个变量是离散型的 FieldDR=np.array([[-3.1, -2, 0, 3], [ 4.2, 2, 1, 5], [ 0, 0, 1, 1]]) # 调用crtri函数创建实数值种群 Chrom=crtpc(Encoding, Nind, FieldDR) print("实值染色体矩阵: ",Chrom)
实值染色体矩阵: [[-3.05633545 0.20690918 -0. 5. ] [ 1.90761108 -0.27868652 1. 5. ] [ 3.21710205 0.72753906 -0. 3. ] [-2.36126709 1.80065918 1. 5. ] [ 0.56381226 -0.3190918 0. 5. ] [ 2.30549316 -0.3861084 0. 5. ] [ 1.51663513 -1.08215332 -0. 3. ]]
这里要插入讲一下**“区域描述器” (见上述代码中的FieldDR),它是用于描述种群染色体所表示的决策变量的一些信息,如变量范围、连续/离散性。另外还有一种区域描述器(FIeldD),用于描述二进制/格雷码的种群染色体。 FieldDR 和 FieldD 两个合称 “Field” ,又可以认为它们是 “译码矩阵”**。FieldDR具有以下的数据结构:
代码1中的FieldDR矩阵的第三行即为这里的 varTypes 。它如果为0,表示对应的决策变量是连续型的变量;为1表示对应的是离散型变量。
***下面学习一下构建一个二进制种群染色体矩阵:***
lens: 每个决策变量编码后在染色体中所占的长度。
lb: 指明每个变量使用的下界。
ub: 指明每个变量使用的上界。
codes: (0:binary | 1:gray) 指明子串是怎幺编码的,0为标准二进制编码,1为格雷编码。
scales: (0: rithmetic | 1:logarithmic) 指明每个子串是否使用对数或算术刻度,1为使用对数刻度,0为使用算术刻度。
lbin: (0:excluded | 1:included)。
ubin: (0:excluded | 1:included)。
varTypes: (0:continued | 1:discrete)。
lbin和ubin指明范围中是否包含每个边界。lbin=0或ubin=0,表示范围中不包含相应边界;lbin=1或ubin=1,表示范围中包含相应边界。
注意:对于二进制编码,二进制种群的染色体具体代表决策变量的什幺含义是不由染色体本身决定的,而是由解码方式决定的。因此在创建二进制种群染色体之初就要设定好译码矩阵(又称“区域描述器”)。
import numpy as np from geatpy import crtpc # help(crtpc) # 查看帮助 # 定义种群规模(个体数目) Nind = 4 Encoding = 'BG' # 表示采用“二进制/格雷”编码 # 创建“译码矩阵” FieldD_2 = np.array([[3, 2,3], # 各决策变量编码后所占二进制位数,3个决策变量,长度3,2,3 [0, 0,0], # 各决策变量的范围下界 [7, 3,7], # 各决策变量的范围上界 [0, 0,0], # 各决策变量采用什幺编码方式(0为二进制编码,1为格雷编码) [0, 0,0], # 各决策变量是否采用对数刻度(0为采用算术刻度) [1, 1,1], # 各决策变量的范围是否包含下界(对bs2int实际无效,详见help(bs2int)) [1, 1,1], # 各决策变量的范围是否包含上界(对bs2int实际无效) [0, 0,0]])# 表示两个决策变量都是连续型变量(0为连续1为离散) # 调用crtpc函数来根据编码方式和译码矩阵来创建种群染色体矩阵 Chrom_2=crtpc(Encoding, Nind, FieldD_2) print("二进制染色体矩阵: ", Chrom_2)
二进制染色体矩阵: [[0 0 0 1 0 0 0 0] [0 1 0 0 1 1 1 1] [0 0 1 0 0 0 1 0] [1 1 0 1 1 0 1 0]]
3、编码和解码
对于二进制编码染色体矩阵需要解码,实值染色体矩阵未编码,因此不需要解码。
对上述的二进制种群染色体矩阵Chrom_2和FieldD译码矩阵进行译码可得:
from geatpy import bs2ri # help(bs2ri) Phen = bs2ri(Chrom_2, FieldD_2) print('表现型矩阵 = ', Phen)
表现型矩阵 = [[0. 2. 0.] [2. 1. 7.] [1. 0. 2.] [6. 3. 2.]]
4、目标函数值
种群的目标函数值存在一个矩阵里面(一般命名为ObjV),它每一行对应一个个体的目标函数值。对于单目标而言,这个目标函数值矩阵只有1列,而对于多目标而言,就有多列了,比如下面就是一个含两个目标的种群目标函数值矩阵:
(这里Nind表示种群的规模,即种群含多少个个体;Nvar表示决策变量的个数)
def get_aim(phen): x1=phen[:,[0]] # 所有的个体决策变量x1 x2=phen[:,[1]] # 所有的个体决策变量x2 x3=phen[:,[2]] # 所有的个体决策变量x3 f=x1+x2+x3 # 目标函数值f cv= np.abs(x1+x2+x3-2) # 种群个体约束条件矩阵 return f,cv f,cv = get_aim(Phen) print("目标函数值f: ",f," 个体约束条件矩阵cv: ",cv)
目标函数值f: [[ 2.] [10.] [ 3.] [11.]] 个体约束条件矩阵cv: [[0.] [8.] [1.] [9.]]
由上述可以看出,个体1的目标值为2,满足cv约束条件
5、适应度值
适应度值通俗来说就是对种群个体的”生存能力的评价“。对于简单的 单目标优化 ,我们可以简单地把 目标函数值直接当作是适应度值 (注意:当用geatpy遗传和进化算法工具箱时,则需要对目标函数值加个负号才能简单地把它当作适应度值,因为geatpy遵循的是”目标函数值越小,适应度值越大“的约定。)
对于多目标优化,则需要根据“ 非支配排序 ”或是其他方法来确定种群个体的适应度值
种群适应度:(FitnV):它是一个列向量,每一行代表一个个体的适应度值:
Nind表示种群含多少个个体
from geatpy import ranking # help(ranking) f= np.array([[8.],[70.],[10.],[8.]]) cv =np.array([[0.],[0.],[0.1],[1.]]) FitnV = ranking(f, cv) print('种群适应度矩阵 = ', FitnV)
由上述结果可知,在个体cv都满足约束(值<=0)的情况下,目标函数值越大,适应度越低,上述适应度为1>2>3>4。
二、遗传算法基本算子
在进化算法中存在两个阶段的选择。第一次是参与进化操作的个体的选择。这个阶段的选择可以是基于个体适应度的、也可以是完全随机地选择交配个体。一旦个体被选中,那幺它们就会参与交叉、变异等进化操作。未被选中的个体不会参与到进化操作中。
第二次是常被称为“重插入”或“环境选择”的选择,它是指在个体经过交叉、变异等进化操作所形成的子代(或称“育种个体”)后用某种方法来保留到下一代从而形成新一代种群的过程。这个选择过程对应的是生物学中的” 自然选择”。它可以是显性地根据适应度(再次注意:适应度并不等价于目标函数值)来进行选择的,也可以是隐性地根据适应度(即不刻意去计算个体适应度)来选择。例如在多目标优化的NSGA-II 算法中,父代与子代合并后,处于帕累托分层中第一层级的个体以及处于临界层中的
且拥挤距离最大的若干个个体被保留到下一代。这个过程就没有显性地去计算每个个体的适应度。
经典的选择算子有:“轮盘赌选择”、“随机抽样选择”、“锦标赛选择”、“本地选择”、“截断选择”、“一对一生存者竞争选择”等等
"""举例:锦标赛选择""" import numpy as np from geatpy import selecting # help(selecting) Chrom=np.array([[1,11,21], [2,12,22], [3,13,23], [4,14,24], [5,15,25], [6,16,26], [7,17,27], [8,18,28]]) FitnV = np.array([[1.2],[0.8],[2.1], [3.2],[0.6],[2.2],[1.7],[0.2]]) SelCh = Chrom[selecting('tour', FitnV, 6), :] # 使用'tour'锦标赛选择算子,同时片取Chrom得到所选择个体的染色体 print('个体的适应度为: ', FitnV) print('选择后得到的种群染色矩阵为: ', SelCh)
2、交叉重组
遗传算法的“交叉”是属于“重组”算子里面的。因为交叉算子经常使用,而对于“离散重组”、“中间重组”、“线性重组”等等,因为用的很少,所以我们常常只谈“交叉”算子了。交叉算子实际上是“值互换重组”(Values exchanged recombination)。
与重组有关的遗传算法参数是“重组概率”(对于交叉而言就是“交叉概率”),它是指两个个体的染色体之间发生重组的概率。
from geatpy import xovdp # 两点交叉 import numpy as np # help(xovdp) OldChrom=np.array([[1,1,1,1,1],[1,1,1,1,1],[0,0,0,0,0],[0,0,0,0,0]]) #创建一个二进制种群染色体矩阵 print('交叉前种群染色矩阵为: ', OldChrom) NewChrom = xovdp(OldChrom, XOVR=0.7) # 设交叉概率为1 print('交叉后种群染色矩阵为: ', NewChrom)
变异是指通过改变染色体中的一部分元素来形成新的染色体的过程。它能够提高种群的多样性,降低进化算法陷入局部最优解的风险。
from geatpy import mutuni # 均匀变异 import numpy as np # help(mutuni) # 自定义种群染色体矩阵,表示有3个个体,且染色体元素直接表示变量的值(即实值编码) OldChrom = np.array([[9,10], [10,10], [10,10]]) # 创建区域描述器(又称译码矩阵) FieldDR = np.array([[7,8], [10,10], [1, 1]]) # 此处设编码方式为实值编码中的“实整数编码”RI,表示染色体可代表实数和整数 NewChrom = mutuni('RI', OldChrom, FieldDR, 0.7) print(NewChrom)
4、重插入
经典遗传算法通过选择、重组和变异后,我们得到的是育种后代,此时育种后代的个体数有可能会跟父代种群的个体数不相同。这时,为了保持种群的规模,这些育种后代可以重新插入到父代中,替换父代种群的一部分个体,或者丢弃一部分育种个体,最终形成新一代种群。前面曾提到过**“重插入”也是一种选择,但它是环境选择,是用于生成新一代种群的;而前面在交叉变异之前的选择是用于选择个体参与交叉变异,那个选择常被称作“抽样”。**
以精英保留策略(EGA)为例,来讲述经过交叉变异后的后代,和子代染色体,怎幺重新选择,形成新一代的种群的染色体矩阵
EGA:假设父代种群规模为N,则选择出(N-1)个个体进行交叉变异,然后找出父代的精英个体,用着个精英个体和交叉变异得到的子代个体进行“拼合”,得到新一代种群。
from mutuni import mutuni import numpy as np # 自定义父代种群染色体(仅作为例子): Chrom = np.array([[1.1, 1.3], [2.4, 1.2], [3, 2.1], [4, 3.1]]) # 若父代个体的适应度为: FitnV = np.array([[1], [2], [3], [4]]) # 考虑采用“精英保留策略”的遗传算法,此时从父代选择出4-1=3个个体,经过交叉变异后假设子代的染色体为: offChrom = np.array([[2.1, 2.3], [2.3, 2.2], [3.4, 1.1]]) # 假设直接把目标函数值当作适应度,且认为适应度越大越好。则通过以下代码重插入生成新一代种群: bestIdx = np.argmax(FitnV) # 得到父代精英个体的索引 NewChrom = np.vstack([Chrom[bestIdx, :], offChrom]) # 得到新一代种群的染色体矩阵 print('新一代种群的染色体矩阵为: ', NewChrom)
三、完整实现遗传算法
优化目标:
“””
aimfunc.py – 目标函数文件
描述:
目标:max f = 21.5 + x1 * np.sin(4 * np.pi * x1) + x2 * np.sin(20 * np.pi * x2)
约束条件:
x1 != 10
x2 != 5
x1 ∈ [-3, 12.1] # 变量范围是写在遗传算法的参数设置里面
x2 ∈ [4.1, 5.8]
“””
1.1 为了学习,我这里先不采用框架,直接利用工具箱提供的库函数来写一个带精英个体保留的遗传算法。这样代码量比较大,但有利于入门。
(目标函数aimfuc.py)(这里要回顾一下前面,Phen是种群表现型矩阵,存储的是种群所有个体的表现型,而不是单个个体。因而计算得到的目标函数值矩阵也是包含所有个体的目标函数值):
import numpy as np def aimfunc(Phen, CV): x1 = Phen[:, [0]] # 获取表现型矩阵的第一列,得到所有个体的x1的值 x2 = Phen[:, [1]] f = 21.5 + x1 * np.sin(4 * np.pi * x1) + x2 * np.sin(20 * np.pi * x2) exIdx1 = np.where(x1 == 10)[0] # 因为约束条件之一是x1不能为10,这里把x1等于10的个体找到 exIdx2 = np.where(x2 == 5)[0] CV[exIdx1] = 1 CV[exIdx2] = 1 return [f, CV]
然后是编写算法:
# -*- coding: utf-8 -*- """main.py""" import numpy as np import geatpy as ea # 导入geatpy库 from aimfunc import aimfunc # 导入自定义的目标函数 import time """============================变量设置============================""" x1 = [-3, 12.1] # 第一个决策变量范围 x2 = [4.1, 5.8] # 第二个决策变量范围 b1 = [1, 1] # 第一个决策变量边界,1表示包含范围的边界,0表示不包含 b2 = [1, 1] # 第二个决策变量边界,1表示包含范围的边界,0表示不包含 ranges=np.vstack([x1, x2]).T # 生成自变量的范围矩阵,使得第一行为所有决策变量的下界,第二行为上界 borders=np.vstack([b1, b2]).T # 生成自变量的边界矩阵 varTypes = np.array([0, 0]) # 决策变量的类型,0表示连续,1表示离散 """==========================染色体编码设置=========================""" Encoding = 'BG' # 'BG'表示采用二进制/格雷编码 codes = [0, 0] # 决策变量的编码方式,设置两个0表示两个决策变量均使用二进制编码 precisions =[4, 4] # 决策变量的编码精度,表示二进制编码串解码后能表示的决策变量的精度可达到小数点后4位 scales = [0, 0] # 0表示采用算术刻度,1表示采用对数刻度 FieldD = ea.crtfld(Encoding,varTypes,ranges,borders,precisions,codes,scales) # 调用函数创建译码矩阵 """=========================遗传算法参数设置========================""" NIND = 100; # 种群个体数目 MAXGEN = 200; # 最大遗传代数 maxormins = [-1] # 列表元素为1则表示对应的目标函数是最小化,元素为-1则表示对应的目标函数是最大化 selectStyle = 'rws' # 采用轮盘赌选择 recStyle = 'xovdp' # 采用两点交叉 mutStyle = 'mutbin' # 采用二进制染色体的变异算子 pc = 0.7 # 交叉概率 pm = 1 # 整条染色体的变异概率(每一位的变异概率=pm/染色体长度) Lind = int(np.sum(FieldD[0, :])) # 计算染色体长度 obj_trace = np.zeros((MAXGEN, 2)) # 定义目标函数值记录器 var_trace = np.zeros((MAXGEN, Lind)) # 染色体记录器,记录历代最优个体的染色体 """=========================开始遗传算法进化========================""" start_time = time.time() # 开始计时 Chrom = ea.crtpc(Encoding, NIND, FieldD) # 生成种群染色体矩阵 variable = ea.bs2ri(Chrom, FieldD) # 对初始种群进行解码 CV = np.zeros((NIND, 1)) # 初始化一个CV矩阵(此时因为未确定个体是否满足约束条件,因此初始化元素为0,暂认为所有个体是可行解个体) ObjV, CV = aimfunc(variable, CV) # 计算初始种群个体的目标函数值 FitnV = ea.ranking(maxormins * ObjV, CV) # 根据目标函数大小分配适应度值 best_ind = np.argmax(FitnV) # 计算当代最优个体的序号 # 开始进化 for gen in range(MAXGEN): SelCh = Chrom[ea.selecting(selectStyle,FitnV,NIND-1),:] # 选择 SelCh = ea.recombin(recStyle, SelCh, pc) # 重组 SelCh = ea.mutate(mutStyle, Encoding, SelCh, pm) # 变异 # 把父代精英个体与子代的染色体进行合并,得到新一代种群 Chrom = np.vstack([Chrom[best_ind, :].astype(int), SelCh]) Phen = ea.bs2ri(Chrom, FieldD) # 对种群进行解码(二进制转十进制) ObjV, CV = aimfunc(Phen, CV) # 求种群个体的目标函数值 FitnV = ea.ranking(maxormins * ObjV, CV) # 根据目标函数大小分配适应度值 # 记录 best_ind = np.argmax(FitnV) # 计算当代最优个体的序号 obj_trace[gen,0]=np.sum(ObjV)/ObjV.shape[0] #记录当代种群的目标函数均值 obj_trace[gen,1]=ObjV[best_ind] #记录当代种群最优个体目标函数值 var_trace[gen,:]=Chrom[best_ind,:] #记录当代种群最优个体的染色体 # 进化完成 end_time = time.time() # 结束计时 ea.trcplot(obj_trace, [['种群个体平均目标函数值', '种群最优个体目标函数值']]) # 绘制图像 """============================输出结果============================""" best_gen = np.argmax(obj_trace[:, [1]]) print('最优解的目标函数值:', obj_trace[best_gen, 1]) variable = ea.bs2ri(var_trace[[best_gen], :], FieldD) # 解码得到表现型(即对应的决策变量值) print('最优解的决策变量值为:') for i in range(variable.shape[1]): print('x'+str(i)+'=',variable[0, i]) print('用时:', end_time - start_time, '秒')
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3YBhqxF7-1640660677653)(…/…/AppData/Roaming/Typora/typora-user-images/image-20211227105011152.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jbF45sgq-1640660677654)(…/…/AppData/Roaming/Typora/typora-user-images/image-20211227105051681.png)]
1.2 直接采用geatpy封装好的框架
问题类”MyProblem”的编写:
# -*- coding: utf-8 -*- import numpy as np import geatpy as ea class MyProblem(ea.Problem): # 继承Problem父类 def __init__(self): name = 'MyProblem' # 初始化name(函数名称,可以随意设置) M = 1 # 初始化M(目标维数) maxormins = [-1] # 初始化maxormins(目标最小最大化标记列表,1:最小化该目标;-1:最大化该目标) Dim = 2 # 初始化Dim(决策变量维数) varTypes = [0] * Dim # 初始化varTypes(决策变量的类型,元素为0表示对应的变量是连续的;1表示是离散的) lb = [-3, 4.1] # 决策变量下界 ub = [12.1, 5.8] # 决策变量上界 lbin = [1] * Dim # 决策变量下边界 ubin = [1] * Dim # 决策变量上边界 # 调用父类构造方法完成实例化 ea.Problem.__init__(self, name, M, maxormins, Dim, varTypes, lb, ub, lbin, ubin) def aimFunc(self, pop): # 目标函数 x1 = pop.Phen[:, [0]] # 获取表现型矩阵的第一列,得到所有个体的x1的值 x2 = pop.Phen[:, [1]] f = 21.5 + x1 * np.sin(4 * np.pi * x1) + x2 * np.sin(20 * np.pi * x2) exIdx1 = np.where(x1 == 10)[0] # 因为约束条件之一是x1不能为10,这里把x1等于10的个体找到 exIdx2 = np.where(x2 == 5)[0] pop.CV = np.zeros((pop.sizes, 2)) pop.CV[exIdx1, 0] = 1 pop.CV[exIdx2, 1] = 1 pop.ObjV = f # 计算目标函数值,赋值给pop种群对象的ObjV属性
编写算法main
# -*- coding: utf-8 -*- """main.py""" import geatpy as ea # import geatpy from MyProblem import MyProblem # 导入自定义问题接口 """===============================实例化问题对象================================""" problem = MyProblem() # 生成问题对象 """==================================种群设置==================================""" Encoding = 'BG' # 编码方式 NIND = 100 # 种群规模 Field = ea.crtfld(Encoding, problem.varTypes, problem.ranges, problem.borders) # 创建区域描述器 population = ea.Population(Encoding, Field, NIND) # 实例化种群对象(此时种群还没被初始化,仅仅是完成种群对象的实例化) """================================算法参数设置=================================""" myAlgorithm = ea.soea_EGA_templet(problem, population) # 实例化一个算法模板对象 myAlgorithm.MAXGEN = 200 # 最大进化代数 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('没找到可行解。')
代码解析:在“main.py”执行脚本中,一开始需要实例化一个问题对象。然后是种群对象的实例化。在实例化种群对象前,需要设定种群的编码方式Encoding、种群规模NIND,并且生成区域描述器Field(或称译码矩阵),因为种群类的构造方法中需要至少用到这三个参数(详见“Population.py”中种群类的构造方法)。
在完成了问题类对象和种群对象的实例化后,将其传入算法模板类的构造方法来实例化一个算法模板对象。这里我实例化的是单目标优化的EGA算法(即带精英个体保留的遗传算法)的模板类对象,即代码中的”soea_EGA_templet”。里面的进化算法具体是如何操作的,可详见https://github.com/geatpy-dev/geatpy/blob/master/geatpy/templates/soeas/GA/EGA/soea_EGA_templet.py
采用Geatpy提供的进化算法框架可以既能最大程度地描述清楚所要求解的问题,而且与进化算法是高度脱耦的,即上面在编写问题类的时候完全不需要管后面采用什幺算法、采用什幺样编码的种群,只需把问题描述清楚即可。
而且,遗传算法有个好处是:目标函数可以写得相当复杂,可以解决各种复杂的问题,比如神经网络。以BP神经网络为例,可以把神经网络的参数作为决策变量,神经网络的训练误差作为目标函数值,只需把上面的例子修改一下就行了。
而且,一般而言我们不需要像我刚刚最开始那样刻意去手写进化算法,可以直接调用geatpy内置的算法模板就可以解决问题了。geatpy工具箱提供这些内置的算法模板:
官网:http://geatpy.com/index.php/quickstart/
单目标进化官网案例:https://github.com/geatpy-dev/geatpy/tree/master/geatpy/demo/soea_demo
多目标进化官网案例:https://github.com/geatpy-dev/geatpy/tree/master/geatpy/demo/moea_demo
多目标优化
Be First to Comment