初识遗传算法(一): 基本概念

通过借鉴大自然中物种的繁衍概念,演化算法通过在计算机中模拟每个个体的基因序列(和该个体的表现型),并通过组合多个个体,形成种群。对种群中的每个个体,采用类似大自然的自然选择,基因突变等手段促使种群的繁衍,最终达到想要的目标。

构成演化算法的四大要素: 1.代表个体的方法,2.测量个体适应度的函数,3.选择算法,4.后代变异(包含交叉遗传crossover,基因突变mutation)


1.代表个体的方法


对于自然中每个生物而言,决定其适应大自然的程度的一般是基因序列,即基因序列决定了该生物的种种特性(即表现型), 而这些特性又决定了其是否适应环境。而对于计算机模拟的遗传算法而言,如果用真实的基因序列来表达个体未免太过复杂,所以也就有了如下一些表达个体基因的方法。
(1)基因型的结构化表达
对于这种表达方式,最出名的就是神经网络了(ANNs), 利用模拟神经的结构,搭建网络。除了神经网络外,有限状态机也是常用的一种方法。
(2)树状图表达法
对于这种表达法,较为出名的有John Koza的genetic programming,这种表达法较为显著的一个优势是很直观,很好解释,但随着时间的推移,树的不断增大,会导致’膨胀’(bloat),虽然膨胀能很好的削弱突变带来的干扰性,但是与此同时会使树的直观可解释性大幅下降并会导致树的功能紊乱。通过惩罚项能很好地缓解’bloat’。
(3)基于语法的表达方式
'Lindenmayer' 系统是其中的一种,S对应着ABA,A对应着aBa,B对应着AbA,先由一个S开始,即ABA, 其中的A接着换位aBa, B换为AbA,所以即
S->ABA->aBaAbAaBa->aAbAaaBabaBaaAbAa … 一直循环下去知道达到要求的迭代数为止,并去掉其中的大写字母
(4)基于String的表达方式
这是最为常见的一种表达方式,接下来也将默认使用这种表达方式来讲述其他的内容,此处的string既可以是字符也可以是数字或者是二进制的数字串。

2.测量个体适应度的函数


个体适应度,顾名思义,就是看这个生物是否适合当前的这个环境,也就是说测量它能不不能很好地活在当前这个环境下。其实大自然中测量个体适应度一般是根据这个个体的繁殖能力来判断,繁殖能力高,意味着基因传给了很多后代,自然适应度就高。
但在演化算法中,是通过程序计算这个个体的表现好不不好,只有表现好才能有机会将基因传给下一代。譬如我假定当前环境下最完美适应的基因型是11001, 如果我现在有一个个体的基因型是10000, 那它跟最优解基因型的汉明距离是2,也就是有两个位置的基因不相同,而有三个位置的基因是相符合的,那么简单来说我这个基因型为10000的个体的适应度就是3。当然这是最简单的一个适应度函数,我也可以把这个函数设定为指数型的,譬如有三个位置符合,那么它的适应度就是$$e^3$$当然我也可以设置为log型的,那就是$$log_23$$至于怎么设置,一般根据实验目的和实验要求来。


3.选择算法


来到了重头戏环节,选择算法。像自然选择一样,在一个种群中,不同个体的适应度有高有低,那么怎么样选择个体让他们繁衍并将基因传递下去呢,此时就需要选择算法。较为常用的选择算法如下所示
(1)轮盘赌选择(Proportionate)
轮盘赌选择,顾名思义,转一个轮盘,指针指到哪个区间就选哪个个体,如图所示

而首先第一步就是做出一个基于fitness的轮盘。
伪代码如下:
SUM=0
For i=1 to P:
  SUM=SUM+Fitness(pop[i])
  Wheel[i]=SUM
END
其中i是代表着种群中从第一个到第P个的所有个体,pop代表的是种群,pop[i]即该种群中第i个个体。Fitness是计算该个体的适应度,其实这个轮盘算法相当与累计概率,而选择的过程就是:
pick=(从1到P的随机一个整数)*SUM
i=1
While(Wheel[i]<pick):
  i++

也就是说随着i值的不断增大, 总会有一个Wheel[i]的值大于pick的值,那么我们就返回这个个体pop[i],一般来说,fitness越大的个体其在轮盘上对应的区域也就越大,就越容易被选中。
或者在python中可用bisect中bisect_right来快速得到结果。

(2)基于排名的选择(Rank based)
其实基于排名的选择与轮盘赌选择法绝大部分相同,只是在计算SUM的时候不再是 SUM=SUM+Fitness(pop[i]),而是SUM=SUM+c,而这个c是一个常量,那么c是怎么得到的呢,首先第一步先将整个种群中的所有个体按照适应度排个序,生成一个排完序后的列表,不要再管每个个体的适应度了,给每个个体按等差数列赋个值,譬如排名最后的个体赋值为1,倒数第二赋值为2,以此类推,排名最高的赋值即为p(设种群中共有p个个体),此时我们的c为1,因为我们设置的等差数列的差值为1。故此时公式为SUM=SUM+1。其他部分内容都跟轮盘赌选择法一样。

(3)锦标赛选择(Tournament)
锦标赛选择十分简单,首先设置一个锦标赛的规模,一般来说规模为2,如果为2的话就是从种群中随机抽取两个个体,每个个体的取法用伪代码表示即round(rand()*p),rand是从0到1的一个随机数,p是种群的体积,round即为小数部分四舍五入。取出两个个体后分别比较其适应度,留下较大的那个即为最后结果,这是规模为2的情况,如果锦标赛规模是s,那么则取出s个个体,留下适应度最大的那个即可

(4)截断选择法(Truncation)
顾名思义,截断选择法先将整个种群根据适应度排序,接着取前面k个个体,k是自己设定的参数

4.后代变异


大体来说,后代变异可分为两个部分: 交叉遗传(crossover)和基因突变(mutation),其中交叉遗传是属于基因重组的部分,即有性繁殖,一般只有遗传算法包含这个概念,而基因突变是既可有性繁殖也可无性繁殖,即随机突变爬山算法也包含基因突变,但不包含交叉遗传。
首先对于交叉遗传而言,有多种方式(1)一点交叉遗传 (2)n点交叉遗传 (3)均匀交叉遗传
譬如我现在有两个选出来的个体,其基因序列分别为
个体1: 10011
个体2: 00000

(1)一点交叉遗传: 这两个个体的基因序列长度为5,随机从1到5取一个整数,譬如3吧,然后在3这个位置把每个基因序列分为两半
个体1:100 | 01
个体2:  000 | 00
接着分别从这两个个体中分别取出前一半和后一半分别构成两个子代
子代1:100 | 00
子代2:  000 | 01

最后留下子代1和子代2中适应度高的那个。

(2)n点交叉遗传
n点交叉遗传类似一点遗传,将基因序列分为n份,每一份分别从父代1或者父代2继承,最后留下适应度高的那个

(3)均匀交叉遗传
均匀交叉遗传相当于将基因序列上每一个基因都看作独立的,随机从父代1或2抽取
父代1:10001
父代2:  00000

对于子代中第一个基因,若rand()>0.5则继承父代1中第一个基因,否则父代2,接着对第一个基因,若rand()>0.5则继承父代1中第二个基因,以此类推,直到子代生成完整的基因序列

对于基因突变而言,有很多种方法,譬如二元翻转(适用于用二进制表达的基因序列如0101100),随机重置,交换变异法,区域打乱法,反转突变法。
二元翻转(Bit Flip Mutation): 对于每一位在基因序列中的基因,如果rand()<p则将0变成1,或1变成0,其中p是自己设置的突变率,这个突变率一般设置为1/L,L是基因序列的长度。

随机重置(Random Resetting): 这是对于那些非二进制编码的序列,方法与二元翻转相似,只不过将0变1,1变0这个过程改为从有效的字符中选取一个,譬如一个基因序列为9e03a变为9s03a。
交换变异法(Swap Mutation): 随机交换两个位置的基因
区域打乱法(Scramble Mutation):随机选取一段区域,将其中的基因打乱
倒转变异法(Inversion Mutation): 随机选取一段区域,将其中的基因倒过来


下一章,我们研究研究适应度地形(fitness landscape)和协同进化(coevolution)