遗传算法入门笔记(一)概念及简单算法示例


前言

遗传算法是一种群体搜索技术,主要针对于有多个局部最优解的问题有较好的效果,通俗的说遗传算法就是将解空间中明显不符合的解去除,然后在剩余解空间中通过随机搜索得到全局最优解。其中群体作为一组解空间,对群体不断进行交叉、变异、选择操作,不断更新群体,直至近似到最优解。


一、遗传算法基本概念

1.基本名词:

遗传算法将目标函数的求解过程代入到自然界生物的遗传过程中,所以在概念中有部分遗传生物学的概念,其对应关系如下表:

遗传学对应算法操作
群体对应上述可行解集,如求解函数的最值对应的自变量,则可行解集就是函数的定义域
个体即可行解集中的一个可行解,对应到求解函数最值对应自变量即为自变量在定义域中的某个取值
染色体可行解的编码,个体在遗传算法中的表现形式,将可行解集合中的单个个体编码,方便进行遗传操作
基因可行解编码的分量,对应到染色体上就是一条染色体上的一对碱基对
适应度评价函数值,用于评价个体的适合程度
选择按照个体的适合程度选择较为合适的个体,将它们的优秀基因遗传下去
交叉根据预定义参数交叉概率(群体中每个个体被选中进行交叉的概率),选择两个个体,随机交换两方的部分基因
变异根据预定义参数变异概率(群体中每个个体被选中进行变异的概率) 选取某个或某些个体,随机的改变其基因的某点或某部分

2.基本流程:

(1).编码与解码

在遗传算法进行迭代前,要将原解空间进行映射,如求下列函数的最大值:
f ( x ) = 10 s i n ( 5 x ) + 7 c o s ( 4 x ) , x ∈ [ 0 , 10 ] f(x)=10sin(5x)+7cos(4x),x\in [0,10] f(x)=10sin(5x)+7cos(4x),x[0,10]
在正常的迭代算法中我们直接在函数的定义域中取某个或某些值,按照算法流程进行迭代,评价,按照迭代条件结束整个流程,但在遗传算法中我们要将原本的可行解集中可行解编码成为一种能进行遗传操作的形式,如此问题中我们需要将定义域中的实数转为用适合的编码序列表示,本文只着重介绍二进制编码形式。从而对应的,在遗传结束后我们得到最优的个体,还要反向由编码形式转化为我们原问题中需要的形式,我们称之为解码。

编码

三种编码方法:
1)二进制编码:将可行解空间中的可行解转化为二进制序列的格式:如上述问题中x的取值范围为[0,10],二进制编码的方式是用一个定长的二进制序列如[0,0,0,1,0]对应的表示每个值,序列长度取决于我们的可行解空间大小,如我们要求得精度为0.01的近似解,即将区间中两相邻整数之间划分为100个值,每个值都用二进制形式表示,总可行解个体数为:区间长度*精度,二进制序列长度决定了其能表示的数据个数,对序列长度的要求一般为:
2 n − 1 < s i z e < 2 n 2^{n-1}<size<2^{n} 2n1<size<2n
其中size是可行解个体总数,n为二进制编码序列长度。长度为n的二进制序列能表示的最大的数为2^n次方。
2)格雷编码:是在二进制编码的基础上进行改进,其本质还是一种二进制编码。为了解决二进制编码的海明悬崖问题,即相邻实数编码差距过大,如8的二进制:1000,7的二进制:0111,汉明距离为4(对应位置编码不同个数),这种情况下交叉和突变会较为困难,或许经过多次变异交叉,但在实数空间上前进的距离很短。而格雷编码相邻实数的距离为1:
b i = { a 1 i = 0 a i − 1 ⊕ a i σ ( t ) = 0 \begin{equation}b_i=\left\{\begin{array}{cl}a_1 & i= 0 \\a_{i-1}\oplus a_i & \sigma (t) = 0 \\\end{array} \right.\end{equation} bi={a1ai1aii=0σ(t)=0
其中   b i \ b_{i}  bi为格雷编码i位置的编码值,   a i \ a_{i}  ai为二进制编码i位置的编码值,其中   ⊕ \ \oplus  为异或操作,即只有两值相同时为1,其他全为0.
3)实数编码:
只能用于连续值问题。在复杂连续值问题中由于在实数转化为二进制编码时会有一定的误差,且在解空间过大时编码长度过长,算法效率下降。所以在一些连续值问题中采用实数编码,并且便于和其他算法结合使用。
4)符号编码:
使用不同的符号表示不同的基因,不同的符号之间并不能比较大小,说明我们放弃了这方面的信息,但是符号能用于表示顺序信息,这很符合路径求解等规划问题,使用不同符号的顺序代表不同的规划方案,交叉操作等改变的是个体代表的路径信息而不是符号本身的意义。

解码

在迭代结束后,从上述编码形式要转换为我们实际需要的形式,在上述问题中我们将解空间中的所有可行解编码为二进制序列形式,在得到最终解后要将反向将二进制序列转化为实数形式,和编码过程刚好相反。首先将二进制序列所代表的二进制数值转化为十进制实数num,之后按照:
n u m ∗ b − a 2 n − 1 + a num*\frac{b-a}{2^{n}-1 } +a num2n1ba+a
其中a、b分别为参数定义域的左右端点,n为二进制序列的长度,我们在编码时将定义域区间划分为2^n-1个小区间,区间编号从0开始,对应的二进制序列为[0,0,0,0,0…],所以在解码时得到二进制序列对应的实数实际为对应的为小区间编号,此后还需要乘以小区间长度,由于时从0开始,还需要加上实际区间的左端点值,得到真实可行解空间中的实数解。
2)克雷格编码
3)实数编码

(2).适应度

适应度对应算法中的评价函数,我们需要有一个评价,在每次迭代后评价个体的好坏,这将作为我们更新群体的参考。一般情况下适应度越高,说明个体更符合我们的要求,跟我们在其他优化算法中的损失函数刚好相反。适应度函数需要我们根据具体问题设定,如在上述求解函数最值得问题,我们得适应度可以选择为对应个体解码为实数后求得得函数值,如果个体对应得函数值越大,则个体更符合我们的要求即适应度越高。

(3).选择算子、交叉算子、变异算子
.选择算子:

在种群中按照一定得策略或概率,选择一部分个体,将他们的基因遗传下去,这里的策略一般是按照适应度的大小,选择更为优秀的个体进行遗传,但这有部分可能会使我们的求解过程陷入一个局部最优而最终不能得到全局最优解。
这里介绍两种交换策略:
轮盘赌策略:
按照适应度的大小决定个体被选择的概率,按照轮盘赌策略,我们将个体适应度占种群适应度的比例作为该个体被选择的概率:
p i = f i t i ∑ j = 0 n f i t j p_{i}= \frac{fit_{i}}{\sum_{j=0}^{n}fit_{j} } pi=j=0nfitjfiti
其中   p i \ p_{i}  pi为编号为i的个体被选择的概率,   f i t i \ fit_{i}  fiti为其适应度.
精英策略:
将当前种群中适应度最高的一个或部分群体保留,不进行后续的交叉变异操作。将剩余个体经过交叉变异操作后计算适应度,将保留的“精英”替换剩余其中适应度较低的个体。这可能会使某个局部最优解不易被剔除,从而得不到全局最优解。

.交叉算子:

在群体中按照交叉概率选择一定数量的个体,将它们两两配对,按照一定策略(随机的选择某些基因进行交换)进行交叉操作,将群体的基因进行结合。其中的交叉概率按照我的理解是群体中每个个体被选中进行交叉的概率,每个个体被选中的概率相同,则最终被选中的个体数量占种群总数量的比例是是一定并等于交叉概率的。pc为交叉概率、N为种群大小的话,交叉操作就是随机选取N*pc个个体进行交叉操作,选取的数量应为偶数,因为要两两配对。

.变异算子:

与交叉操作类似,但这里操作的对象时单个个体,按照变异概率,选择一定数量的群体,随机的改变其基因中的一个或多个编码。在二进制编码和克雷格编码中,变异操作即将所选位置的二进制0或1取反。
**基本算法流程:
在这里插入图片描述

二、简单算法示例(求函数最大值)

1.编码与解码

编码(针对上述问题,初代群体的生成在代码中体现为生成50个长度为10的二进制序列),按照概念,序列长度为10,则其能表达的最大种群大小为   2 n − 1 = 2 10 − 1 = 1023 \ 2_{n}-1=2_{10}-1=1023  2n1=2101=1023,但按照我们的精度要求,我们需要为1000个数编号,但如果n=9,则其能表示的最大数为512,达不到要求,所以本次选择n=10,这并不过多影响我们的结果,只是将原本要[0,10]划分为1000个小区间,此处划分为了1023个。

colony=np.random.randint(0, 2, (50,10))

解码

#解码单个个体:将二进制编码转为十进制,再得到定义域中对应的十进制数
def decoder(code,lnum,rnum):
    num_10=sum([int(code[i])*pow(2,len(code)-i-1) for i in range(len(code))]) #得到二进制编码对应的十进制数
    num_10=num_10*((rnum-lnum)/(2**10-1))+lnum #得到对应定义域区间中的数
    return num_10

2.计算适应度

此处按照群体为单位计算适应度,并进行了归一化,即将一个群体中的每个个体的适应度都转化为[0,1]区间上的数,避免因为适应度为负值在后续选择操作时对应个体概率为负而导致的程序报错。
适应度归一化:
f i = f i t i − m i n ( F i t ) m a x ( F i t ) − m i n ( F i t ) f_{i}=\frac{fit_{i}-min(Fit)}{max(Fit)-min(Fit)} fi=max(Fit)min(Fit)fitimin(Fit)
其中   f i \ f_{i}  fi为种群中编号为i的个体的适应度归一化后的值,   f i t i \ fit_{i}  fiti为该个体的实际适应度,Fit为当前种群全部个体的适应度,max和min求得其最大值和最小值。

#计算适应度度(适应度决定被选择的概率,与我们最终的目标解有关,因此本问题的适应度选择对应个体的函数值):
def fit(colony):
    nums=[decoder(i,0,10) for i in colony]
    fits=[f(i) for i in nums]
    max_fit=max(fits)
    min_fit=min(fits)
    Fit=[(i-min_fit)/(max_fit-min_fit) for i in fits] #归一化适应度
    return Fit

3.选择、交叉、变异

选择算子:
以群体为单位,计算其中每个个体适应度占总体适应度的比例,作为该个体被选中的概率,使用np.random.choice在整个种群中按照参数p的值(对应被选数组中每个元素被选择的概率)选取和种群大小相同的新种群,replace=true代表着可以重复选择,所以最终结果种群虽然大小和原种群一致,但因为可以重复选择,其中“优秀”的个体即适应度占总体适应度大的个体被选择次数比其他个体多,其中的优秀基因被遗传下来。

#选择算子:轮盘赌策略(由适应度的大小决定被选择的概率)
def choice(colony):
    #计算概率:
    sums=sum(fit(colony)) #计算群体总适应度
    choice_probability=[i/sums for i in fit(colony)] #计算单个个体适应度占群体总适应度的比例,作为个体被选择的概率
    individuals=np.random.choice(np.arange(len(colony)),size=100,replace=True,p=choice_probability) #根据选择概率选择的个体对应在群体中的索引值,replace=True代表是否可以重复选择,此处选择个数等于群体大小
    return colony[individuals]

交叉算子:
上述概念中说到交叉概率是种群中每个个体被选中的概率,对整个种群来说,种群概率即每次进行交叉的个体占总体的比例,所以我们进行种群大小*交叉概率/2次选择,每次随机在种群中选择一对个体,并随机的选择一个交叉开始位置index,交换两个个体的[index:]部分。

#交叉算子:
def cross(colony,cross_probability):
    colony_copy=colony.copy()
    #随机选取(群体大小*交叉概率)次,每次随机选取两个个体进行交叉操作
    for i in range(int(len(colony_copy)*cross_probability//2)):
        #选择交叉个体
        cross_individuals=np.random.choice(range(len(colony_copy)),size=2)
        #选择交换发生起点位置
        index=np.random.choice(range(len(colony_copy[0])),size=1)[0]
        meso=colony_copy[cross_individuals[0]][index:]
        colony_copy[cross_individuals[0]][index:]=colony_copy[cross_individuals[1]][index:]
        colony_copy[cross_individuals[1]][index:]=meso
    return colony_copy

变异算子:
与交叉操作类似,循环进行种群大小*变异概率次选择,每次在种群中随机选择一个个体,随机选择一个变异位置,将该位置的二进制编码值取反。

#变异算子
def variation(colony,variation_probability):
    colony_copy=colony.copy()
    individual_code=np.random.choice(range(len(colony)),size=int(len(colony)*variation_probability))
    for i in individual_code:
        variation_code = np.random.choice(range(len(colony[0])), size=1)
        colony_copy[i][variation_code]=1 if colony[i][variation_code]==0 else 1
    return colony_copy

4.总代码

#遗传算法求f(x)=x+10sin(5x)+7cos(4x)的最大值,x的取值范围[0,10],有多个局部极值的函数

import math
import numpy as np

def f(x):
    return x+10*math.sin(5*x)+7*math.cos(4*x)
#解码单个个体:将二进制编码转为十进制,再得到定义域中对应的十进制数
def decoder(code,lnum,rnum):
    num_10=sum([int(code[i])*pow(2,len(code)-i-1) for i in range(len(code))]) #得到二进制编码对应的十进制数
    num_10=num_10*((rnum-lnum)/(2**10-1))+lnum #得到对应定义域区间中的数
    return num_10
    
#计算适应度度(适应度决定被选择的概率,与我们最终的目标解有关,因此本问题的适应度选择对应个体的函数值):
def fit(colony):
    nums=[decoder(i,0,10) for i in colony]
    fits=[f(i) for i in nums]
    max_fit=max(fits)
    min_fit=min(fits)
    Fit=[(i-min_fit)/(max_fit-min_fit) for i in fits] #归一化适应度
    return Fit

#选择算子:轮盘赌策略(由适应度的大小决定被选择的概率)
def choice(colony):
    #计算概率:
    sums=sum(fit(colony)) #计算群体总适应度
    choice_probability=[i/sums for i in fit(colony)] #计算单个个体适应度占群体总适应度的比例,作为个体被选择的概率
    individuals=np.random.choice(np.arange(len(colony)),size=100,replace=True,p=choice_probability) #根据选择概率选择的个体对应在群体中的索引值,replace=True代表是否可以重复选择,此处选择个数等于群体大小
    return colony[individuals]
    
#交叉算子:
def cross(colony,cross_probability):
    colony_copy=colony.copy()
    #随机选取(群体大小*交叉概率)次,每次随机选取两个个体进行交叉操作
    for i in range(int(len(colony_copy)*cross_probability//2)):
        #选择交叉个体
        cross_individuals=np.random.choice(range(len(colony_copy)),size=2)
        #选择交换发生起点位置
        index=np.random.choice(range(len(colony_copy[0])),size=1)[0]
        meso=colony_copy[cross_individuals[0]][index:]
        colony_copy[cross_individuals[0]][index:]=colony_copy[cross_individuals[1]][index:]
        colony_copy[cross_individuals[1]][index:]=meso
    return colony_copy
#变异算子
def variation(colony,variation_probability):
    colony_copy=colony.copy()
    individual_code=np.random.choice(range(len(colony)),size=int(len(colony)*variation_probability))
    for i in individual_code:
        variation_code = np.random.choice(range(len(colony[0])), size=1)
        colony_copy[i][variation_code]=1 if colony[i][variation_code]==0 else 1
    return colony_copy
    
if __name__ == '__main__':
    # 初始化
    cross_probability = 0.6 #交叉概率
    variation_probability = 0.3 #变异概率
    colony=np.random.randint(0, 2, (50,10)) #初代群体
    g=0 #迭代计数器
    max_value=-1000
    max_value_x=0
    #开始遗传迭代:
    while g<500:
        g+=1
        colony=choice(colony) #选择
        colony=cross(colony,cross_probability)  #交叉
        colony=variation(colony,variation_probability)  #变异
        Fit=fit(colony) #计算适应度
        colony_copy=colony.copy()
        conlon_sort_byFit=colony_copy[np.array(Fit).argsort()] #将当前群体按照适应度大小排序
        #计算适应度最大个体对应的函数值,如果大于当前最大值则更新,max_value_x为最大值对应的x
        max_value_x = decoder(conlon_sort_byFit[-1], 0, 10) if f(decoder(conlon_sort_byFit[-1], 0, 10)) > max_value else max_value_x
        max_value=f(max_value_x)

        print("第{}次迭代,最佳适应度个体对应的个体为{},最大值为{}".format(g,max_value_x,max_value))

总结

通过简单的求函数最大值问题,简单介绍遗传算法的基本概念,个人学习用,仅供参考(欢迎讨论)
引用参考:万字字符长文带你了解遗传算法

  • 22
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值