1.什么是遗传算法?
1.1.遗传算法的定义
遗传算法(Genetic Algorithm, GA)是受达尔文进化论的启示,模拟自然进化和遗传过程的计算模型。
遗传算法的主要特点是通过种群的遗传和变异,采用概率化的寻优方法,不需要有确定的规则就能自动获取和指导优化搜索空间,自适应的调整搜索方向。
1.2.遗传算法的相关术语
-
基因型(genotype):性状染色体的内部表现;
-
表现型(phenotype):染色体决定的性状的外部表现,或者说,根据基因型形成的个体的外部表现;
-
进化(evolution):种群逐渐适应生存环境,品质不断得到改良。生物的进化是以种群的形式进行的。
-
适应度(fitness):度量某个物种对于生存环境的适应程度。
-
选择(selection):以一定的概率从种群中选择若干个个体。一般,选择过程是一种基于适应度的优胜劣汰的过程。
-
复制(reproduction):细胞分裂时,遗传物质 DNA通过复制而转移到新产生的细胞中,新细胞就继承了旧细胞的基因。
-
交叉(crossover):两个染色体的某一相同位置处 DNA被切断,前后两串分别交叉组合形成两个新的染色体。也称基因重组或杂交;
-
变异(mutation):复制时可能(很小的概率)产生某些复制差错,变异产生新的染色体,表现出新的性状。
-
编码(coding):DNA中遗传信息在一个长链上按一定的模式排列。遗传编码可看作从表现型到基因型的映射。
-
解码(decoding):基因型到表现型的映射。
-
个体(individual):指染色体带有特征的实体;
-
种群(population):个体的集合,该集合内个体数称为种群的大小。
1.3.遗传算法过程图
2.遗传算法的应用
2.1.实际应用
遗传算法可用在 寻路问题、8数码问题、囚犯困境、动作控制、找函数最值(函数的最大最小值)。
遗传算法中的每条染色体对应着遗传算法的一个解决方案,一般用适应性函数来评估解决方案的优劣。所以从一个基因组到其解的适应形成一个映射,这个过程可以看成是在一个多元函数中寻找最优解。
2.1.1.举个例子
已知一元函数如下:
f
(
x
)
=
x
s
i
n
(
10
π
x
)
+
2
x
∈
[
−
1
,
2
]
f(x)=xsin(10\pi x)+2\\x∈[-1,2]
f(x)=xsin(10πx)+2x∈[−1,2]
在给定区间内找出函数的最大值。
2.1.2.解决方法
我们可以把这个种群想象为若干只袋鼠,并把求解这个最大值的问题想象成让这些袋鼠在若干个山峰中找出并爬到珠穆朗玛峰顶,但是袋鼠本身并不知道它们的任务是爬到珠穆朗玛峰顶,它们只是随机的攀爬。但是每过几年,我们就要在低海拔的地方射杀一些袋鼠,并希望存活下来的袋鼠是多产的,处于海拔越高的袋鼠存活的时间更长,能生儿育女的机会也就越多。物竞天择,适者生存,经过很长时间的进化之后,这些袋鼠不约而同的聚集到一个个山峰上,而只有聚集到珠穆朗玛峰的袋鼠才是最后的胜利者。
2.1.3.方法设计
-
我们需要找到一种编码方案,建立基因型到表现型的映射关系。这里的基因型指的就是自变量 x的 基因序列,表现型指的就是该自变量 x所代表的实数。接着通过解码,得到 x的实际值,也可想象为袋鼠的位置,并用适应函数(例子中给的一元函数)对每一个基因个体(x的取值)进行评估,函数值越大(处于海拔越高的袋鼠),相应的适应度就越高。
-
用某种方式的选择函数进行择优,也就是说我们要每隔一段时间射杀那些不思进取,活跃于低海拔的袋鼠,以保证袋鼠总体数目的平衡。
-
让个体基因变异(也可以想象为让袋鼠随机跳一跳)产生子代
P S : 使 用 遗 传 算 法 的 最 大 优 点 在 于 你 不 用 考 虑 如 何 去 “ 找 ” 最 优 解 。 ( 你 不 必 去 指 导 袋 鼠 向 哪 边 跳 , 跳 多 远 。 ) 而 只 要 简 单 的 “ 否 定 ” 一 些 表 现 不 好 的 个 体 就 行 了 。 这 就 是 遗 传 算 法 的 精 髓 。 \textcolor{green}{PS: 使用遗传算法的最大优点在于你不用考虑如何去“找”最优解。(你不必去指导袋鼠向哪边跳,跳多远。)而只要简单的“否定”一些表现不好的个体就行了。这就是遗传算法的精髓。} PS:使用遗传算法的最大优点在于你不用考虑如何去“找”最优解。(你不必去指导袋鼠向哪边跳,跳多远。)而只要简单的“否定”一些表现不好的个体就行了。这就是遗传算法的精髓。
所以最终解决步骤如下:
- 初始化种群:随机编码个体的基因序列,直至个体数达到指定数目
- 评估每个个体的适应度:看看这些个体所处的海拔
- 按照适应度越高越容易被选择的原则,从种群中选择两个两个个体作为父方和母方
- 抽取父方和母方的染色体进行交叉,产生子代
- 对子代按概率进行变异
- 循环2,3,4,5直到迭代次数到达指定数量时结束循环
2.1.4.详细步骤的实现
■ 编码
编码方式有很多种,其中二进制编码比较简单且更方便计算机处理。二进制编码的结果是01的组合,0和1表示两种碱基,每种碱基能表现出一个bit的信息量,只要这条染色体足够长,就能描述一个个体的所有特征。
一个个体可以有很多特征,但不是每个特征都需要去关注,在这个问题中我们只关注一个特征就是袋鼠的位置,因为我们是通过袋鼠的位置来进行评估的。确定了我们要关注的特征,下一步就是确立基英型到表现型的映射关系了,这就是编码的目的!
一定长度的二进制编码序列只能表示一定精度的浮点数。如果我们要求精度达到 n位,由于区间长度为 2 − ( − 1 ) = 3 \textcolor{orange}{2-(-1)=3} 2−(−1)=3,那么至少要把区间 [ − 1 , 2 ] \textcolor{orange}{[-1,2]} [−1,2]平分为 3 × 1 0 n \textcolor{orange}{3×10^n} 3×10n份。然后根据算法 m = ⌈ l o g 2 ( 3 × 1 0 n ) ⌉ \textcolor{orange}{m=\lceil log_2(3×10^n)\rceil} m=⌈log2(3×10n)⌉得到m,表示二进制序列长度。
将一个二进制串转为10进制数:
( b 0 b 1 . . . b m ) 2 = ( ∑ i = 0 m − 1 b i ∗ 2 i ) 10 = X t (b_0b_1...b_m)_2=(∑_{i=0}^{m-1}b_i*2^i)_{10}=X^t (b0b1...bm)2=(i=0∑m−1bi∗2i)10=Xt
则
X
t
\textcolor{orange}{X^t}
Xt转换为区间内的实数的算法也就是最终的 解码方式:
x
=
−
1
+
x
t
3
2
m
−
1
x=-1+x^t\frac{3}{2^m-1}
x=−1+xt2m−13
在这个问题中,我们设定精度为小数点后 6位,所以 m = 22 \textcolor{orange}{m=22} m=22,例如一个基因序列为 ( 1010101110110101000111 ) 2 \textcolor{orange}{(1010101110110101000111)_2} (1010101110110101000111)2,对应区间中的实数为
(
1010101110110101000111
)
2
=
(
2813255
)
10
2813255
×
3
2
22
−
1
−
1
=
1.012197
(1010101110110101000111)_2=(2813255)_{10} \\ 2813255×\frac{3}{2^{22}-1}-1=1.012197
(1010101110110101000111)2=(2813255)102813255×222−13−1=1.012197
二进制串 0000000000000000000000和 1111111111111111111111则分别表示区间的两个端点值 -1和 2。
■ 选择
“物竞天择,适者生存”。为了更好的表现这句话,针对该问题,我们将具体设计适应函数和选择方法。首先适应函数比较简单,直接用例子给的一元函数就行。关键是选择函数的设计,这里采用最常用的 轮盘赌选择法。
比如有 5条染色体,他们所对应的适应度评分为5,7,10,13,15。
- 计算适应度评分总和: 5 + 7 + 10 + 13 + 15 = 50 \textcolor{orange}{5+7+10+13+15=50} 5+7+10+13+15=50
- 每个个体被选择的概率:
- P 1 = 5 50 = 0.1 \textcolor{orange}{P1=\frac{5}{50}=0.1} P1=505=0.1
- P 2 = 7 50 = 0.14 \textcolor{orange}{P2=\frac{7}{50}=0.14} P2=507=0.14
- P 3 = 10 50 = 0.2 \textcolor{orange}{P3=\frac{10}{50}=0.2} P3=5010=0.2
- P 4 = 13 50 = 0.26 \textcolor{orange}{P4=\frac{13}{50}=0.26} P4=5013=0.26
- P 5 = 15 50 = 0.3 \textcolor{orange}{P5=\frac{15}{50}=0.3} P5=5015=0.3
把这些概率表示成一个圆盘:
想象一下,当转动这个圆盘,圆盘停下的时候,指针落在的区域对应的个体就被选中。
■ 交叉
随机找到一个中间点 p,子代的基因序列 child由父代基因序列 f a t h e r [ 0 : p ] + m o t h e r [ p : e n d ] \textcolor{orange}{father[0:p]+mother[p:end]} father[0:p]+mother[p:end]组成
■ 变异
设定一个变异的概率,然后根据概率随机在基因序列中的某个位置进行突变。突变的方式就是假设该点处的碱基是“1”,则变成“0”,反之亦然。
2.1.5.代码
"""
f(x)=xsin(10πx)+2
x∈[-1,2]
求该区间下函数最大值
"""
import math
import random
def InitPopulation(units,lenth):# 初始化种群
# 种群
population=[]
gene = ''
i=1
while i<=units:
gene = ''
for j in range(lenth):
bit = random.uniform(-1,2)
if bit<0:
gene+='0'
else:
gene+='1'
if gene not in population and Evaluation(Decode(gene))>0:
population.append(gene)
i+=1
return population
def Choice(eva_list):# 选择
sum_eva=sum(eva_list)
probability=[]
# 产生一个概率
k = random.uniform(0,1)
the_sum=0
i=0
for eva in eva_list:
the_sum+=eva/sum_eva
probability.append(the_sum)
while k>probability[i]:
i+=1
return i
def CrossOver(father,mother,gene_lenth):# 交叉
child=''
visit=[]
loc = random.randint(0,gene_lenth-1)
visit.append(visit)
child+=father[0:loc]
child+=mother[loc:gene_lenth]
# 如果评估值小于0则说明是个失败的后代,需要重新交叉
while Evaluation(Decode(child))<0:
while loc in visit and len(visit)<gene_lenth:
loc = random.randint(0,gene_lenth-1)
child+=father[0:loc]
child+=mother[loc:gene_lenth]
visit.append(loc)
# 如果父母交叉的结果不尽人意,则直接由父代产生子代
if len(visit)==gene_lenth:
child=father
return child
def Variation(gene,gene_lenth,ratio):# 变异
prob = random.uniform(0,1)
if prob<ratio:
loc=random.randint(0,gene_lenth-1)
t_gene=gene[0:loc]
t_gene+=str(1-int(gene[loc]))
t_gene+=gene[loc+1:]
if Evaluation(Decode(t_gene))>Evaluation(Decode(gene)):
# 如果子代变异后的评估值大于未变异的,则返回变异后的子代
return t_gene
return gene
def Evaluation(x):# 评估
return x*math.sin(10*math.pi*x)+2
def Decode(s):# 解码
t = int(s,2)
return t*(3/((2**22)-1))-1
if __name__=='__main__':
# 种群数
population_num=1000
# 基因长度
gene_lenth=22
# 最好的评估值
theBestEva=0
final_best_eva=[]
final_best_person=[]
# 最好的个体
theBestPerson=''
# 种群,数量为1000,基因长度为22
populationList=InitPopulation(population_num,gene_lenth)
# 迭代次数
iteration = 50
for i in range(10):# 重复10次遗传算法,保证更好的收敛性
for j in range(iteration):
# 评估列表
eva_list=[]
next_population=[]
for person in populationList:
eva_list.append(Evaluation(Decode(person)))
maxEva=max(eva_list)
id=eva_list.index(maxEva)
# 将评估值最好的个体放入下一代种群中
next_population.append(populationList[id])
# 选择
for k in range(population_num):
father = populationList[Choice(eva_list)]
mother = populationList[Choice(eva_list)]
# 交叉
child = CrossOver(father,mother,gene_lenth)
# 变异
child = Variation(child,gene_lenth,0.9)
# 放入下一个种群中
next_population.append(child)
# 将其他不思进取的个体射杀
populationList=next_population
eva_list=[]
# 重新评估
for p in populationList:
eva_list.append(Evaluation(Decode(p)))
maxEva = max(eva_list)
if theBestEva<maxEva:
theBestEva = maxEva
id = eva_list.index(theBestEva)
theBestPerson = populationList[id]
final_best_person.append(theBestPerson)
final_best_eva.append(theBestEva)
print('第%i代:最好的个体 = %s(%1.6f),所处海拔:%f'%(j,theBestPerson,Decode(theBestPerson),theBestEva))
e = max(final_best_eva)
p = final_best_person[final_best_eva.index(e)]
print('final: %s(%1.6f) = %f'%(p,Decode(p),e))
2.1.6.最后
第0代:最好的个体 = 1111001101001010111011(1.851087),所处海拔:3.850008
第1代:最好的个体 = 1111001100111100110100(1.850441),所处海拔:3.850263
第4代:最好的个体 = 1111001100111110110001(1.850530),所处海拔:3.850273
第5代:最好的个体 = 1111001100111111001010(1.850548),所处海拔:3.850274
第13代:最好的个体 = 1111001100111111001001(1.850547),所处海拔:3.850274
...
最后得到函数最大值为 3.85
3.参考
[1] https://blog.csdn.net/u010451580/article/details/51178225
[2] https://www.jianshu.com/p/ae5157c26af9