遗传算法的Python实现
遗传算法是一种启发式算法,在优化问题中应用非常之广泛。由于是一种启发式算法,除了用于优化问题外,也有人将之应用到分类、聚类等问题的求解中,取得了非常好的效果。算法设计和参数选择对遗传算法来说非常重要,为此,笔者对遗传算法进行了实现,并比较了不同参数对问题求解的影响。
遗传算法的基本原理
遗传算法主要是受自然界“优胜劣汰、适者生存”启发而设计出来的一种算法,体现的思想是:如果每次都保留适应生存环境的个体,并使之繁衍生息,将以较大概率得到优质群体。需要厘清以下概念:
- 种群:个体的集合,可认为是全部可行解的一个子集(个人认为种群还应该包括各类算子、规则的定义);
- 个体 :代表着可行解(并不一定是最优解);
- 染色体:由基因组成,实质上是自变量的编码方式,在个体的本质特征;
- 选择:留下适应度高的个体,淘汰适应度低的个体;
- 交叉:两个父代染色体之间进行交叉,可以产生下一代(通常是一对);
- 变异:复制过程中可能会产生偏差,是保证种群多样性的重要操作。
优化目标
此程序主要是针对形如以下的函数优化问题。
max f ( x 1 , x 2 , . . . . , x n ) s . t . a 1 ≤ x 1 ≤ b 1 a 2 ≤ x 2 ≤ b 2 . . . . a n ≤ x n ≤ b n \max f(x_1,x_2,….,x_n) \\ s.t. a_1 \le x_1 \le b_1 \\ a_2 \le x_2 \le b_2 \\ ….\\ a_n \le x_n \le b_n maxf(x1,x2,....,xn)s.t.a1≤x1≤b1a2≤x2≤b2....an≤xn≤bn
种群建模
种群既要包含个体集合,也要设置一系列的算子,以使对每个个体是一视同仁的。因此,种群类Population主要有以下属性:
- size:种群的规模
- individuals:个体的集合
- p_cross:交叉概率
- p_exchange:交换概率
- p_mutate:变异概率
另外,还有一些用于优化计算过程的中间量。
种群还需要有一定的方法: - init_pop:初始化种群
- get_fitness:获取适应度值
- get_all_fitness:获取所有个体的适应度值
- get_best_indv:获取本轮最优个体
- selection:选择算子
- cross:交叉算子
- mutate:变异算子
种群采用的以下三个算子,在实现的过程中参考了gaft工具箱,很易懂,可以直接看代码。
选择算子
采用轮盘赌选择方法选择出一个父本(father),再随便选择一个母本(mother)。
交叉算子
均匀交叉。两个小步骤:一是当随机数小于交叉概率时,再进行交叉操作;二是对于父本和母本中每个基因座,如果随机数大于交换概率,则交换,反之则不交换。
变异算子
采用均匀多点变异方法。对每个基因座均判断,若随机数小于变异概率值,再对基因座取反。
个体建模
个体是基因型、表现型的综合体,并且每个个体都应当对应一个适应度值。因此,类Individual包含有三个属性:
- chromosome:染色体
- decode:解码值(实际值)
- fitness:适应度
怎样由解码值得到适应度值?直接将解码值代入种群的get_fitness函数即可。为什么不将该函数设计到Individual内呢?直观的看,个体是无法决定自身适应度的,在种群内必须有一个统一的规则。
染色体的编码
染色体编码是设计遗传算法的重要内容,一种好的编码不仅能使算法更易实现,还可以让各类操作算子效果更好。
采用二进制编码的益处是更加符合“积木块”假设,可以让好的模式传承下去。
采用浮点数编码的益处是可以有效减少计算量,尤其是对于大型问题,进行科学的浮点数编码,会使性价比更高。
本文采用二进制编码,由于目的是处理具有多个变量的函数优化问题,因此,将染色体建模为多重列表,相应变量依次对应子列表。即:
c h r o m o s o m e = [ [ x 1 的 染 色 体 编 码 ] , [ x 2 的 染 色 体 编 码 ] , . . . . . . , [ x n 的 染 色 体 编 码 ] ] chromosome = [\\ [x_1的染色体编码],\\ [x_2的染色体编码],\\ ……,\\ [x_n的染色体编码]] chromosome=[[x1的染色体编码],[x2的染色体编码],......,[xn的染色体编码]]
由于每个自变量的编码长度由取值范围和精度决定,因此,每个子列表的长度也不相同。
这样进行构造的益处是比较形象,容易理解,代价则是处理起来相对麻烦一些。
另外,也可以连接每个自变量的编码成为一个长列表,但是,我认为这样不够清晰。
算法控制
基本过程
选择:从源种群中选择适当的父本和母本(假如种群的规模是 n n n,由选择出ceil( n / 2 n/2 n/2)对父本和母本)
交叉:采用交叉算子对父本和母本进行交叉,生成两个child
变异:对每个child进行变异操作
可见,如果 n n n为偶数,由经过以上操作后的种群规模是不变的,即保持规模为 n n n
保留上一代最优个体
在实验的过程中发现,强制保留每一代的最优个体到下一代是极为必要的,翻阅了几本书籍,也明确应当保留最优个体,有的文献将之称为精英保留。至于替换哪一个个体,我采用的是随机选择一个个体进行替换
算法分析
目标函数
设置两个目标函数:
f o r m u l a 1 : f ( x 1 , x 2 ) = − 100 ( x 1 2 − x 2 ) 2 − ( 1 − x 1 ) 2 formula1:f(x_1,x_2) = -100(x_1^2-x_2)^2-(1-x_1)^2 formula1:f(x1,x2)=−100(x12−x2)2−(1−x1)2
其中, − 2.048 ≤ x 1 , x 2 ≤ 2.048 -2.048 \le x_1,x_2 \le 2.048 −2.048≤x1,x2≤2.048。此函数在(1,1)处,取得最大值0
f o r m u l a 2 : f ( x 1 , x 2 ) = − 20 − x 1 2 − x 2 2 + 10 ( cos 2 π x 1 + cos 2 π x 2 ) ) formula2:f(x_1,x_2) = -20-x_1^2-x_2^2 + 10(\cos2\pi x_1 + \cos2\pi x_2)) formula2:f(x1,x2)=−20−x12−x22+10(cos2πx1+cos2πx2))
其中, − 5 ≤ x 1 , x 2 ≤ 5 -5 \le x_1,x_2 \le 5 −5≤x1,x2≤5。此函数在(0,0)处,取得最大值0,且此函数有多个局部极值,在求解时极为困难
在测试中发现,对于这两个函数,遗传算法均能在极大的概率上得到最优值或者极其逼近最优值。
程序中所用的分析方法
遗传算法最经常用最优值的收敛曲线来进行分析,对于低维的情况,还可以绘出峰值图,更加直观。
参数的影响
参数对遗传算法的效果影响甚巨,比如,我发现对于大多数问题来说,交叉概率设为0.8的时候,比设为0.9的时候效果要更好一些。
另外,很多时候可以让算法收敛到“最高峰”上,但是,有时候经过几百代也无法到达“峰尖”,这是以后需要研究的问题。
说明
需要注意的问题
在复制个体的时候,一定要严格区分什么时候直接赋值,什么时候采用deepcopy,否则可能会掉到坑里。
致谢
参考了很多GAFT工具箱的内容,特此说明。
源码下载
本文所用源码已上传到遗传算法的python实现。文中和程序中不免有很多不合理、不科学的地方,恳请批评指正。
[相关参考]
[1]: https://github.com/PytLab/gaft
[2]: http://python.jobbole.com/88069/
[3]: https://blog.csdn.net/u010902721/article/details/23531359