遗传算法的Python实现


遗传算法是一种启发式算法,在优化问题中应用非常之广泛。由于是一种启发式算法,除了用于优化问题外,也有人将之应用到分类、聚类等问题的求解中,取得了非常好的效果。算法设计和参数选择对遗传算法来说非常重要,为此,笔者对遗传算法进行了实现,并比较了不同参数对问题求解的影响。

遗传算法的基本原理

遗传算法主要是受自然界“优胜劣汰、适者生存”启发而设计出来的一种算法,体现的思想是:如果每次都保留适应生存环境的个体,并使之繁衍生息,将以较大概率得到优质群体。需要厘清以下概念:

  1. 种群:个体的集合,可认为是全部可行解的一个子集(个人认为种群还应该包括各类算子、规则的定义);
  2. 个体 :代表着可行解(并不一定是最优解);
  3. 染色体:由基因组成,实质上是自变量的编码方式,在个体的本质特征;
  4. 选择:留下适应度高的个体,淘汰适应度低的个体;
  5. 交叉:两个父代染色体之间进行交叉,可以产生下一代(通常是一对);
  6. 变异:复制过程中可能会产生偏差,是保证种群多样性的重要操作。

优化目标

此程序主要是针对形如以下的函数优化问题。
maxf(x1,x2,....,xn)s.t.a1x1b1a2x2b2....anxnbn \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

种群建模

种群既要包含个体集合,也要设置一系列的算子,以使对每个个体是一视同仁的。因此,种群类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内呢?直观的看,个体是无法决定自身适应度的,在种群内必须有一个统一的规则。

染色体的编码

染色体编码是设计遗传算法的重要内容,一种好的编码不仅能使算法更易实现,还可以让各类操作算子效果更好。
采用二进制编码的益处是更加符合“积木块”假设,可以让好的模式传承下去。
采用浮点数编码的益处是可以有效减少计算量,尤其是对于大型问题,进行科学的浮点数编码,会使性价比更高。
本文采用二进制编码,由于目的是处理具有多个变量的函数优化问题,因此,将染色体建模为多重列表,相应变量依次对应子列表。即:
chromosome=[[x1],[x2],......,[xn]] chromosome = [\\ [x_1的染色体编码],\\ [x_2的染色体编码],\\ ......,\\ [x_n的染色体编码]]
由于每个自变量的编码长度由取值范围和精度决定,因此,每个子列表的长度也不相同。
这样进行构造的益处是比较形象,容易理解,代价则是处理起来相对麻烦一些。
另外,也可以连接每个自变量的编码成为一个长列表,但是,我认为这样不够清晰。

算法控制

基本过程

选择:从源种群中选择适当的父本和母本(假如种群的规模是nn,由选择出ceil(n/2n/2)对父本和母本)
交叉:采用交叉算子对父本和母本进行交叉,生成两个child
变异:对每个child进行变异操作
可见,如果nn为偶数,由经过以上操作后的种群规模是不变的,即保持规模为nn

保留上一代最优个体

在实验的过程中发现,强制保留每一代的最优个体到下一代是极为必要的,翻阅了几本书籍,也明确应当保留最优个体,有的文献将之称为精英保留。至于替换哪一个个体,我采用的是随机选择一个个体进行替换

算法分析

目标函数

设置两个目标函数:
formula1:f(x1,x2)=100(x12x2)2(1x1)2formula1:f(x_1,x_2) = -100(x_1^2-x_2)^2-(1-x_1)^2
其中,2.048x1,x22.048-2.048 \le x_1,x_2 \le 2.048。此函数在(1,1)处,取得最大值0
formula2:f(x1,x2)=20x12x22+10(cos2πx1+cos2πx2)) formula2:f(x_1,x_2) = -20-x_1^2-x_2^2 + 10(\cos2\pi x_1 + \cos2\pi x_2))
其中,5x1,x25-5 \le x_1,x_2 \le 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

展开阅读全文

没有更多推荐了,返回首页