遗传算法(Genetic algorithm)属于演化计算( evolutionary computing),是随着人工智能领域发展而来的一种智能算法。正如它的名字所示,遗传算法是受达尔文进化论启发。简单来说,它是一种通过模拟自然进化过程搜索最优解的方法。
如果你想了解遗传算法相关的知识,可以学习实验楼上的教程:【Python实现遗传算法求解n-queens问题】,该实验分两节:
这篇文章主要介绍遗传算法的理论知识,并用Python实现简单遗传算法求解函数极值。希望对于想了解学习遗传算法的小伙伴有所帮助~
通过该文章,可以学习到以下知识点:
比较适合以下用户:
以下是【Python实现遗传算法求解n-queens问题】教程的第一节内容,基于实验楼在线开发环境制作。
#安装virtualenv
sudo pip install virtualenv
#创建虚拟环境
virtualenv genetic_python
#cd genetic_python/bin/
source ./activate
配置成功之后的效果图:
此处输入图片的描述
$ sudo pip install numpy
$ sudo apt-get install python-matplotlib
cd Code/Code_genetic
Python binary_genetic.py
遗传学背景知识
1993年,John Koza使用遗传算法写成演化程序来解决特定的问题。他称这种程序为遗传编程(GP)。使用的编程语言是LISP,因为这种编程语言可以用解析树(parse tree)的形式呈现,而解析树问题也是遗传算法的工作对象。
染色体(Chromosome):
所有的生物体都是由细胞组成,每一个细胞中都有一定数量的染色体。染色体是DNA的序列和遗传信息(基因)的主要载体。每一个基因对特定的蛋白质进行编码,通常来说就是每一个基因会控制一种特征,例如眼睛的颜色。用来控制同一个特征(蓝色或棕色)的基因被称为等位基因(alleles)。每一个基因在染色体上的位置是固定的,这个位置被称为基因座(locus). 完整的遗传物质(所有染色体)称为基因组。基因组中的特定基因称为基因型。基因型是个体表现型的有关基因组成,包括生理和心理特征,如眼睛颜色、智力等。
繁殖(Reproduction):
在繁殖过程中,首先发生的是重组(或交叉(crossover))。父母的基因在以某种方式形成了整个新染色体。新创建的后代可以突变。突变意味着DNA的组成基因有些变化。这种变化主要是由于父母复制基因的错误造成的。 个体的适应性是个体对于生存环境的适应程度决定。
搜索空间(search space):
如果我们解决一些问题,我们通常做的就是寻找它的解,并且力求找到最优解。一个问题的所有可能解称为搜索空间,搜索空间中的每一个点都代表一个可能解。一个可能解的可能程度是通过他所对应的计算值或适应度评估。
此处输入图片的描述
大多时候,我们所求的最优解是找寻搜索空间中函数极大值或极小值所对应的解。这个过程还是很复杂的,因为我们不知道从何搜起。现有一些找寻合适解的方法:爬山法(hill climbing),禁忌搜索(tabu search),模拟退火法(simulated annealing),遗传算法等。这些方法都是被认为比较好的方法,因为我们不需要证明所得到的解是最优的。
适应度函数(Fitness):
适应度用于评价个体的优劣程度,适应度越大个体越好,反之适应度越小则个体越差;根据适应度的大小对个体进行选择,以保证适应性能好的个体有更多的机会繁殖后代,使优良特性得以遗传.因此,遗传算法要求适应度函数值必须是非负数,而在许多实际问题中,求解的目标通常是费用最小,而不是效益最大,因此需要将求最小的目标根据适应度函数非负原则转换为求最大目标的形式。
这里我要对代码中的一些变量进行说明:
先导入需要用到的库:
from numpy import random
import numpy as np
import matplotlib.pyplot as plt
from math import pi,sin
import copy
二进制编码是最常用的的编码方式(另外,十进制编码等),每个个体都是由0,1组成。 表现型:一个具体的实数x 基因型:二进制编码(串长取决于求解精度) 我们可以随机生存0,1数组,然后在对随机数组对应到实数空间中进行解密。 编码长度越长,精度越高。如将6位二进制编码映射到$[-1,1]$内,则精度为:
此处输入图片的描述
,
则先将二进制转化为十进制,如
此处输入图片的描述
,
转化为$[-1,1]内为
此处输入图片的描述
。
初始化种群:
def initialpop(self):
pop = random.randint(0,2,size =(self.popsize,self.chrosize))
return pop
初始化产生10个个体如下,其中黄色标记为最优个体,即对应实数的函数适应度最大:
此处输入图片的描述
对群组在实数空间内解码:
#对十进制进行转换到求解空间中的数值
def get_declist(self,chroms):
step =(self.xrangemax - self.xrangemin)/float(2**self.chrosize-1)
self.chroms_declist =[]
for i in xrange(self.popsize):
chrom_dec =self.xrangemin+step*self.chromtodec(chroms[i])
self.chroms_declist.append(chrom_dec)
return self.chroms_declist
#将二进制数组转化为十进制
def chromtodec(self,chrom):
m = 1
r = 0
for i in xrange(self.chrosize):
r = r + m * chrom[i]
m = m * 2
return r
对应的实数空间为: [-0.9501466275659824, -0.7419354838709677, -0.7741935483870968, 1.63049853372434, -0.3724340175953079, -0.6744868035190617, 0.24926686217008798, 1.3870967741935485, 0.9736070381231672, 0.16715542521994142] 以下内容涉及到遗传算法的核心部分,有必要先了解一些具体流程和优化目标:
算法流程图:
此处输入图片的描述
我们的目标是求解函数:
此处输入图片的描述
上最大值,从函数曲线图可以看出,函数值跳跃性很强,用一般的穷举法求解较为困难。通过求导法,可以求解出函数的最优解为:f(1.85)=3.85。
此处输入图片的描述
原函数与适应度函数:
# 对传入的每个变量,计算函数值
def fun(self,x1):
return x1*sin(10*pi*x1)+2.0
#由于我们所求的是最大值,所有可以用函数值代替适应度
def get_fitness(self,x):
fitness = []
for x1 in x:
fitness.append(self.fun(x1))
return fitness
下一代染色体是从进行交叉的父代个体中选择而来。问题在于,如何选择。根据达尔文的生物进化理论,适应性较好的个体会生存下来并且创造后代。有很多种方法来选择适应性较好的个体。例如:轮盘赌选择(roulette wheel selection),玻尔兹曼选择(Boltzman selection),联赛选择(tournament selection),排序选择(rank selection),稳定状态选择(steady state selection)等。
轮盘赌选择(Roulette Wheel Selection ):
又称比例选择方法.其基本思想是:各个个体被选中的概率与其适应度大小成正比。 从父代个体的适应度选择,适应度越好,被选择的概率越大。想象眼前有一个轮盘,把所有染色体放在轮盘上,每一个个体都有和适应度相关比例的空间大小。
此处输入图片的描述
很显然个体1的适应度最大,所有对应的空间大小也最大。但是其他较低适应度的个体也有被选择到的概率。 这样有什么好处?避免了所有的后代都选择概率最大的个体1(所谓的最优值问题),换句话,各个概率(各种情况)都相应的被使用到了,避免了陷入局部最优的问题。
下面具体介绍一下轮盘赌选择的实现过程:
(1) 计算群体中每个个体的适应度f(i =1,2,…….N);
(2) 计算出每一个个体被遗传到下一代群体中的概率:
此处输入图片的描述
(3) 计算每个个体的;累计概率:
此处输入图片的描述
其中q(xi)称为染色体x[i] (i=1, 2, …, n)的积累概率;
(4) 在[0,1]区间内产生一个均匀分布的伪随机数r;
(5) 若r
(6) 重复(4)、(5)共M次。
下面给出轮盘赌算法的代码:
#输入参数为上一代的种群,和上一代种群的适应度列表
def selection(self,popsel,fitvalue):
new_fitvalue = []
totalfit = sum(fitvalue)
accumulator = 0.0
for val in fitvalue:
#对每一个适应度除以总适应度,然后累加,这样可以使适应度大
#的个体获得更大的比例空间。
new_val =(val*1.0/totalfit)
accumulator += new_val
new_fitvalue.append(accumulator)
ms = []
for i in xrange(self.popsize):
#随机生成0,1之间的随机数
ms.append(random.random())
ms.sort() #对随机数进行排序
fitin = 0
newin = 0
newpop = popsel
while newin < self.popsize:
#随机投掷,选择落入个体所占轮盘空间的个体
if(ms[newin] < new_fitvalue[fitin]):
newpop[newin] = popsel[fitin]
newin = newin + 1
else:
fitin = fitin + 1
#适应度大的个体会被选择的概率较大
#使得新种群中,会有重复的较优个体
pop = newpop
return pop
根据轮盘赌选择的原理,适应度高的个体将会被选择下来的可能性更大,所以我们可以打印出初代选择之后的种群发现,最优个体被五次选择:
此处输入图片的描述
排序选择算法(Rank Selection ):
轮盘赌选择算法可能会在个体适应度相差很大的时候带来一些问题。比如,最好个体的适应度是占据了轮盘面积的90%,那么剩余的个体的生存几率将会很小,显然这会影响物种多样性。 排序选择首先会将按照个体适应度进行排序,按照排序的结果,每个个体将会重新获得一个数值。最差的个体的适应度为1,第二差的为2,以此类推最好的个体的适应度为N(种群个体的大小)。 可以对比一下按照个体适应度和排序数值的不同结果:
此处输入图片的描述
图:按照适应度分配空间
轮盘赌选择:
1 | 2 |
---|---|
Chromosomes | rate |
Chromosome1 | 0.90 |
Chromosome2 | 0.06 |
Chromosome3 | 0.03 |
Chromosome4 | 0.01 |
此处输入图片的描述
图:按照排序值分配空间
排序选择
Chromosomes | ranking | rate |
---|---|---|
Chromosome1 | 1 | 4/10=0.4 |
Chromosome2 | 2 | 0.3 |
Chromosome3 | 3 | 0.2 |
Chromosome4 | 4 | 0.1 |
排序选择将使所有个体都有将会被选择。但是这样也会导致种群不容易收敛,因为最好的个体与其他个体的差别减小。
交叉与变异是遗传算法的两个基本算子。遗传算法的结果主要由这两个算子决定。算子的编写和执行主要取决于编码方式和实际问题。实现交叉和变异的方式有很多种。
单点交叉(Single point crossover): 指在个体编码串中只随机设置一个交叉点,然后再该点相互交换两个配对个体的部分染色体。
此处输入图片的描述
11001011 + 11011111 = 11011111
def crossover(self,pop):
for i in xrange(self.popsize-1):
#近邻个体交叉,若随机数小于交叉率
if(random.random()
两点交叉(Two point crossover): 在个体编码串中随机设置了两个交叉点,然后再进行部分基因交换。
此处输入图片的描述
11001011 + 11011111 = 11011111
均匀交叉(Uniform crossover): 两个配对个体的每个基因座上的基因都以相同的交叉概率进行交换,从而形成两个新个体。
此处输入图片的描述
11001011 + 11011101 = 11011111
算术交叉(Arithmetic crossover): 由两个个体的线性组合而产生出两个新的个体。该操作对象一般是由浮点数编码表示的个体。
此处输入图片的描述
11001011 + 11011111 = 11001001 (AND)
基因反转(Bit inversion)
此处输入图片的描述
11001001 => 10001001
def mutation(self,pop):
for i in xrange(self.popsize):
#反转变异,随机数小于变异率,进行变异
if (random.random()< self.mutationrate):
mpoint = random.randint(0,self.chrosize-1)
#将随机点上的基因进行反转。
if(pop[i][mpoint]==1):
pop[i][mpoint] = 0
else:
pop[mpoint] =1
return pop
通过选择,交叉和变异创造新个体的时候,有可能会失去一部分优秀的个体。 总体思想是,判断经过选择,交叉,变异之后的种群中,是否产生了更优秀的个体,如果没有,则将上一代的精英个体替换较差个体。
精英保留策略会快速提高遗传算法性能,因为它可以防止最优个体的丢失。
def elitism(self,pop,popbest,nextbestfit,fitbest):
#输入参数为上一代最优个体,变异之后的种群,
#上一代的最优适应度,本代最优适应度。这些变量是在主函数中生成的。
if nextbestfit-fitbest <0:
#满足精英策略后,找到最差个体的索引,进行替换。
pop_worst =nextfitvalue.index(min(nextfitvalue))
pop[pop_worst] = popbest
return pop
首先定义一个Gas类,并初始化变量:
class Gas():
#初始化一些变量
def __init__(self,popsize,chrosize,xrangemin,xrangemax):
self.popsize =popsize
self.chrosize =chrosize
self.xrangemin =xrangemin
self.xrangemax =xrangemax
self.crossrate =1
self.mutationrate =0.01
然后依次在class中添加上面介绍的算子,此处为了更好理解整个流程,只写出函数关键字。函数具体实现上面有介绍:
class Gas():
def __init__(self)#具体参见上面
...
def def initialpop(self):#初始化种群
...
def fun(self,x1): #定义函数
...
def get_fitness(self,x): #适应度函数
...
def selection(self,popsel,fitvalue): #选择
...
def crossover(self,pop): #交叉
...
def mutation(self,pop): #变异
...
def elitism(self,pop,popbest,nextbestfit,fitbest):#精英保留
...
def get_declist(self,chroms): #解码
...
def chromtodec(self,chrom): #二进制转十进制
...
运行主函数:
if __name__ == '__main__':
generation = 100 # 遗传代数
#引入Gas类,传入参数:种群大小,编码长度,变量范围
mainGas =Gas(100,10,-1,2)
pop =mainGas.initialpop() #种群初始化
pop_best = [] #每代最优个体
for i in xrange(generation):
#在遗传代数内进行迭代
declist =mainGas.get_declist(pop)#解码
fitvalue =mainGas.get_fitness(declist)#适应度函数
#选择适应度函数最高个体
popbest = pop[fitvalue.index(max(fitvalue))]
#对popbest进行深复制,以为后面精英选择做准备
popbest =copy.deepcopy(popbest)
#最高适应度
fitbest = max(fitvalue)
#保存每代最高适应度值
pop_best.append(fitbest)
################################进行算子操作,并不断更新pop
mainGas.selection(pop,fitvalue) #选择
mainGas.crossover(pop) # 交叉
mainGas.mutation(pop) #变异
################################精英策略前的准备
对变异之后的pop,求解最大适应度
nextdeclist = mainGas.get_declist(pop)
nextfitvalue =mainGas.get_fitness(nextdeclist)
nextbestfit = max(nextfitvalue)
################################精英策略
#比较深复制的个体适应度和变异之后的适应度
mainGas.elitism(pop,popbest,nextbestfit,fitbest)
对结果进行可视化分析:
if __name__ == '__main__':
...
t = [x for x in xrange(generation)]
s = pop_best
plt.plot(t,s)
plt.show()
plt.close()
最后可以通过matplot打印出计划图,个体从较小的适应度值,通过遗传进化爬升到较大值,并达到收敛。
关于遗传算法的框架有很多,matlab中专业的工具箱。但是由于代码都被封装,不易理解。本节通过理论与编程结合,在理论基础上实现了遗传算法的整体框架,并在实验中得到了较好的结果。
如果你想查看第二节内容:利用pyevolve库解决一些复杂的优化问题。 点击【Python实现遗传算法求解n-queens问题】即可查看~
来啊,来shiyanlou.com学IT呀,反正有大把时间~
赞赏支持
Python
© 著作权归作者所有
举报文章
写了 542192 字,被 3842 人关注,获得了 4859 个喜欢
实验楼是专业的IT在线实训平台,不但提供海量的IT教程,更有在线开发环境,可以随时动手操作,实战式的学习IT
喜欢
18
更多分享
被以下专题收入,发现更多相似内容
IT课程分享
Python语...
Deep Le...
看场电影
姓名:李嘉蔚 学号16020520034 【嵌牛导读】:简单地说,遗传算法是一种解决问题的方法。它模拟大自然中种群在选择压力下的演化,从而得到问题的一个近似解。在二十世纪五十年代,生物学家已经知道基因在自然演化过程中的作用了,而他们也希望能在新出现的计算机上模拟这个过程,用...
物竞天择 适者生存 非常佩服那些能够把不同领域的知识融会贯通,找到其核心思想并把它在其他领域应用的人,他们都棒棒的 (๑•̀ㅂ•́)و✧ 遗传算法 ( GA , Genetic Algorithm ,也叫进化算法)就是这样一种算法。它是受达尔文的进化论的启发,借鉴生物进化过...
草稿纸反面遗传算法简介
姓名:彭帅 学号:17021210850 参考:http://blog.csdn.net/emiyasstar__/article/details/6938608/ 【嵌牛导读】:我们经常会遇到求一个函数最大或最小值的问题,即优化问题,对简单的一元函数可以...
重露成涓滴各式各样的遗传算法
本文最早发表在本人博客:http://www.gotoli.us/?p=1518 我们接着上面的例子。上面例子是一个分配问题:两位文秘a和b需要处理n件文书;由于两位文秘熟悉领域不一样,两人处理同一件文书所需要的时间不同;怎么分配文书,使得总工作时间最短。遗传算法可以解决这...
姓名:张艺伦 学号:17011210282 转载自:https://www.zhihu.com/question/23293449/answer/120220974,有删节 【嵌牛导读】:本文简单的介绍了遗传算法的思想,之后进一步介绍了它的具体步骤,最后给出了它的流程以及...
DZNGGZGY