机器学习之遗传算法
前言
起源
1975年,遗传算法是美国J.Holland教授在《自然界和人工系统的适应性》中首先提出的。遗传算法受自然界中生物的进化学说和遗传学说的启发而来。遗传算法借鉴自然界中的物竞天择、适者生存、繁殖、竞争、变异、进化、凋亡而来。
总之如果将生物种群看成一个整体,那么对于这个整体来说每一步都是在为了成为更好的自己。
遗传算法原理
遗传算法是一种智能式搜索算法,适合于解决各种非线性、多变量、多目标、复杂的自适应系统问题。同时遗传算法也是一种渐进式的优化算法,每一次的更新迭代都在逼近最优解,也就是说每次得出的结果不一定式最优解,但是从整体上来看遗传算法是接近最优解的。
遗传算法与生物概念中的关系
生物概念 | 遗传算法 |
---|---|
个体 | 单个解 |
染色体 | 解的编码 |
基因 | 解中每一个分量特征 |
染色体交换 | 取两个解随机交换部分编码 |
变异 | 随机改变解分量中的某个或某些值 |
适应性 | 适应函数值(最终解) |
群体或种群 | 全部解 |
遗传算法特点: 智能式搜索、渐进式优化、接近全局最优解、黑箱式结构、通用性强、并行式运算、
遗传算法流程
基因编码
针对问题的不同,采取的编码方式也不一样。遗传算法是对字符串进行操作,对于字符串的编码要求:
- 字符串要反映研究问题的性质
- 字符串表达式应当便于计算机处理
在编码中,编码左边数字越小,则其适应度越大。这种字符串编码的形式特征叫做编码的模式。模式中确定字符的个数称为模式的阶次例如: 。
模式中最前面和最后面两个确定字符的距离成为模式的长度。例如:
编码原则
- 有意义积木块编码原则,应使用易于产生于所求问题相关的、且具有低阶、短定义长度模式的编码方案。
- 最小字符集编码原则,使用的编码方案应能使得问题得到自然表示或描述的具有最小编码字符集
编码方案
1.十进制编码(使用较少)
优点:无需解码
缺点:突变可能性多,交换较粗略、收敛较慢
2.二进制编码(正整数转化的二进制码)
优点:唯一缺点、精确变化、收敛速度快。
缺点:需要解码,加大计算量
直接二进制码:十进制数转化为二进制(在交换和变异时需要对符号位和小数点位进行规避)
间接二进制码:每一个二进制码全部为正整数
间接二进制码的位数计算公式:
其中d为所需要计算的精度,是解的范围,由此得到的间接二进制码的位数为m+1。
解码公式:
其中是二进制码中的每一位。
3. 格雷码
优点:增强局部搜索能力,便于对连续函数局部空间搜索,使用广泛。
二进制转换为格雷码步骤,例如有二进制数转换为格雷码则使用异或运算。 所以其格雷码为:110101。
解码时将格雷码转换为二进制再转换为十进制数或者直接转换为十进制数。同样使用异或运算规则为:
其中为二进制码和格雷码第位的值。
4. 浮点数编码(精确度高)
5. 符号编码
编码时使用一个无数值含义,而只有代码含义的符号集。(类似旅行商问题)
6.多参数级联编码
将所有参数分别编码,编码后按一定顺序将参数连接在一起。
7.多参数交叉编码
先将各参数分别编码,再将其起主要作用的码集中在一起,并按一定顺序连接。例如:
其中为第一个参数中的二进制值,其他参数类似。
产生初始种群
针对之前选择的编码方式,产生预定数量的初始种群
计算种群适应度
针对要求解的问题计算每个个体的适应度。
选择复制
物竞天择、适者生存,针对计算出来的每个个体的适应度,选择合适的复制算子进行有选择的复制。原则就是适应度大的复制的多,适应度小的复制的少,最后保证种群总的数量保持不变。其中复制方法有一下几种:
- 轮盘赌方法
将种群中的所有个体适应度累加其值为,在中随机选择个数,并将个个体中第一个个体的适应度比第个适应度的值大,则挑选出来,直到挑选出个个体为止。 - 锦标赛选择法
每次从种群中去除一定数量个体 ,选择其中最好的一个进入子代种群,知道新种群达到原始种群数量。 - 随机遍历抽样法
染色体交换
从上一步中得到的新种群,一定概率选择其中两个个体进行染色体交换。所谓染色体交换就是,随机选择两个个体,随机选择个体上的位进行编码基因的交换。
个体变异
在完成上一步的染色体交换后的到的种群,以一定概率随机选择其中的某几个个体,对其编码基因进行突变。
程序终止条件
在完成以上步骤后,再次计算种群中所有的适应度值,看其中是否有满足条件的个体,如果不满足终止条件则从复制继续迭代循环。
其中终止条件有一下几种:
- 获得问题最优解
- 循环迭代次数达到预定值
- 适应度值几乎不再变化
程序代码实现
程序存在近缘杂交的问题,同时编译器显示有红色但是能正常运行,所以没有深究。如果结果不理想多运行几次就行了。
'''
code by young_monkeysun 2019
算法要解决的问题:
f(x,y) = 21.5+xsin(4Πx)+ysin(20Πy)
该函数从值域从正无穷到负无穷,定义域从正无穷到负无穷
求:
在给定区间内,满足给定精度内的最大值(最优化问题)
'''
import random
import math
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
class SGA(object):
'''初始化种群X与y的区间和精度'''
def __init__(self , start_x , end_x , start_y , end_y,d_x , d_y ,n,iteration):
self.start_x = start_x
self.end_x = end_x
self.start_y = start_y
self.end_y = end_y
self.d_x = d_x
self.d_y = d_y
self.n = n #种群数
self.iteration = iteration #迭代数
'''计算符合要求的二进制码位数'''
def binary_bites(self,start , end , d ):
m = 0
while 2**m < (end-start)/d:
m +=1
return m
'''初始化种群,随机产生n个群体,基因型随机self.genes'''
def gene_codes(self):
#对种群x进行间接二进制编码时所需要的位数
self.m_x = self.binary_bites(self.start_x,self.end_x,self.d_x)
#对种群y进行间接二进制编码时所需要的位数
self.m_y = self.binary_bites(self.start_y,self.end_y,self.d_y)
#随机产生n组【x,y】基因型
genes = [[[random.randint(0, 1) for i in range(self.m_x)],[random.randint(0, 1) for i in range(self.m_y)]] for j in range(self.n)]
return genes
'''对基因解码,根据基因型计算出每个个体的表现型'''
def gene_decode(self ,genes):
phenotype = []
for i in range(len(genes)):
li = []
genes_x = genes[i][0]
genes_y = genes[i][1]
Sigma_x = 0
Sigma_y = 0
for j in range(len(genes_x)):
Sigma_x +=genes_x[j]*(2**(len(genes_x)-j-1))
x = self.start_x+(self.end_x - self.start_x)/(2**self.m_x-1)*Sigma_x
li.append(x)
for j in range(len(genes_y)):
Sigma_y +=genes_y[j]*(2**(len(genes_y)-j-1))
y = self.start_y+(self.end_y - self.start_y)/(2**self.m_y-1)*Sigma_y
li.append(y)
phenotype.append(li)
return phenotype
'''适应性算子:
(1)根据表现型,求出每个个体得适应度
(2)对适应度进行重排序,再根据适应度得重排序对个体进行重排序
(3)针对重排序后得种群个体进行选择复制'''
def adaption(self,phenotype,genes):
adapt = []
#计算每个个体的适应性
for i in range(len(phenotype)):
adaptive = 21.5+phenotype[i][0]*math.sin(4*math.pi*phenotype[i][0])+phenotype[i][1]*math.sin(20*math.pi*phenotype[i][1])
adapt.append(adaptive)
#依据个体适应性对个体(基因型),与对应的适应性进行排序进行排序
for i in range(len(adapt)):
for j in range(i,len(adapt)):
if adapt[i]<=adapt[j]:
genes[i] , genes[j] = genes[j] , genes[i]
adapt[i] , adapt[j] = adapt[j] , adapt[i]
#返回排序后的适应度
return adapt
'''选择复制算子(锦标赛选择法)'''
def chose_copy(self , genes , adapt):
#初始化下一代基因型
CC_genes = []
#设置选择次数
for i in range(len(genes)):
#从上一代基因型中选择int(len(genes)/4)个基因型参加竞标赛
champ_member = random.sample(adapt,k = int(len(adapt)/4))
winner = max(champ_member)
locate = adapt.index(winner)
CC_genes.append(genes[locate])
return CC_genes
'''
交换算子,随机选择种群中的两个基因型,随机对基因型上对应的m个基因进行交换
注意:每次须选择不同的个体进行交换,每次进行交换的位置须对应。参加过交换的
基因型不应当参加到下一次基因交换中
'''
def gene_change(self,gene):
#-----------------------基因交换-----------------------------------------#
#交换概率设置75%
is_exchange = random.randint(0,4)
if is_exchange:
#设置交换数量,种群数量的一半
exchange_N = int(len(gene)/2)
if exchange_N%2 != 0:
exchange_N +=1
#在父代中随机选择一半的种群进行基因交换
exchange_members = random.sample(gene , k = exchange_N)
#在父代中移除用于交换的基因型
for i in range(len(exchange_members)):
gene.remove(exchange_members[i])
#基因交换
while exchange_members:
exchange_member = random.sample(exchange_members,k=2)
for i in range(len(exchange_member)):
exchange_members.remove(exchange_member[i])
#基因编码位数的三分之一进行交换
exchange_start_x = random.randint(int(self.m_x/2),int(self.m_x*2/3))
exchange_start_y = random.randint(int(self.m_y/2),int(self.m_y*2/3))
#种群X上基因位交换
exchange_part_x = exchange_member[0][0][exchange_start_x:exchange_start_x+int(self.m_x/3)]
exchange_member[0][0][exchange_start_x:exchange_start_x+int(self.m_x/3)] = exchange_member[1][0][exchange_start_x:exchange_start_x+int(self.m_x/3)]
exchange_member[1][0][exchange_start_x:exchange_start_x+int(self.m_x/3)] = exchange_part_x
#种群y上基因位交换
exchange_part_y = exchange_member[0][1][exchange_start_y:exchange_start_y+int(self.m_y/3)]
exchange_member[0][1][exchange_start_y:exchange_start_y+int(self.m_y/3)] = exchange_member[1][1][exchange_start_y:exchange_start_y+int(self.m_y/3)]
exchange_member[1][1][exchange_start_y:exchange_start_y+int(self.m_y/3)] = exchange_part_y
#种群x , y交换完成,添加回父代中
gene.append(exchange_member[0])
gene.append(exchange_member[1])
#-----------------基因交换完成------------------#
#----------------------基因变异-----------------------------#
'''
基因变异:
种群完成选择复制,基因交换后,概率随机选择相应种群个体,对其基因随机选择位置变异
变异算子:随机选取基因序列的两个位置k和m,逆转其k~m间的城市编号
'''
#设置基因变异概率10%
is_variation = random.randint(0,10)
if 0==is_variation:
#随机选取种群数量的10%进行变异
variat_members = random.sample(gene,k = int(len(gene)*0.1))
#原始种群中删除要进行变异的个体
for i in range(len(variat_members)):
gene.remove(variat_members[i])
#对需要变异的个体进行基因变异
for variat_member in variat_members:
'''随机产生1-len(variat_members)之间的两个整数m , k
如果m<k,则将个体中对应的基因进行reverse变换
'''
#对种群x进行基因变异
m_x = random.randint(int(len(variat_member)/2),len(variat_member))
k_x = random.randint(int(len(variat_member)/2),len(variat_member))
if m_x<k_x:
gene_reverse = variat_member[0][m_x:k_x]
gene_reverse.reverse()
variat_member[0] = variat_member[0][0:m_x]+gene_reverse+variat_member[0][k_x:]
#对种群y进行基因变异
m_y = random.randint(int(len(variat_member)/2),len(variat_member))
k_y = random.randint(int(len(variat_member)/2),len(variat_member))
if m_y<k_y:
gene_reverse = variat_member[1][m_y:k_y]
gene_reverse.reverse()
variat_member[1] = variat_member[1][0:m_y]+gene_reverse+variat_member[1][k_y:]
#个体变异完成,添加回基因种群中
gene.append(variat_member)
return gene
'''
种群迭代
'''
def gene_iteration(self):
max_adapt = []
#初始化第一代
genes = self.gene_codes()
#开始迭代
for i in range(self.iteration):
#计算基因表现型
phenotype = self.gene_decode(genes)
#计算种群的适应性
adapt = self.adaption(phenotype,genes)
#记录本代种群适应性的最大值
max_adapt.append(max(adapt))
#种群进化
genes = self.chose_copy(genes,adapt) #选择复制父代基因种群
#基因交换和基因变异
genes = self.gene_change(genes)
#迭代完成,返回每一代种群中适应度最大的列表记录
return max_adapt
'''test:
x∈[-5.23,2.45],y∈[3.56,6.21]
问题函数:f(x,y) = 21.5+xsin(4Πx)+ysin(20Πy)
'''
#以下参数均可随意设置
start_x = -5.23
end_x = 2.45
start_y =3.56
end_y = 6.21
d_x = 0.01 #x精度
d_y = 0.01 #y精度
n = 50 #每代种群数量
iteration =30 #迭代次数
sga = SGA(start_x , end_x , start_y , end_y,d_x , d_y ,n,iteration)
max_adapt = sga.gene_iteration()
plt.plot(max_adapt,marker ='x',label = '适应度最大值变化')
plt.xlabel("迭代次数")
plt.ylabel("适应度最大值")
plt.legend()
plt.show()
总结
遗传算法没有固定的套路,只有一个纲领性的原则。其中的各种操作的可以以实际情况进行改变,例如其中的编码,可以选择各种编码方式,甚至可以选择混合编码方式,在选择复制时,在不同阶段可以使用不同的选择复制方法。遗传算法中的各种概率都不是一成不变的,可以根据实际情况选择。