一、主要思想
遗传算法的工作方式源自于生物学,是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,是一种通过模拟自然进化过程搜索最优解的方法。其主要特点是直接对结构对象进行操作,不存在求导和函数连续性的限定;具有内在的隐并行性和更好的全局寻优能力;采用概率化的寻优方法,不需要确定的规则就能自动获取和指导优化的搜索空间,自适应地调整搜索方向。具体流程见下图:
传统上看,这些染色体可以被由数字 0 和 1 组成的字符串表达出来。
一条染色体由基因组成,这些基因其实就是组成 DNA 的基本结构,DNA 上的每个基因都编码了一个独特的性状。其特点是所找到的解是近似全局最优解,相对于蚁群算法可能出现的局部最优解是有优势的。它的基本特征:
1. 智能式搜索:依据适应度(目标函数)进行智能搜索
2. 渐进式优化:利用复制、交换、突变等操作,使下一代结果优于上一代
3. 全局最优解:采用交换和突变操作产生新个体,使得搜索得到的优化结果逼近全局最优解
4. 黑箱式结构:根据问题的特性进行编码(输入)和确定适应度(输出),即只考虑输入与输出关系的黑箱式就够了,并不深究输入与输出关系的原因
5. 通用性强:不要求明确的数学表达式,只需要一些简单的原则要求,可应用于解决离散问题、函数关系不明确的复杂问题
6. 并行式运算:每次迭代计算都是对群体中所有个体同时进行运算,是并行式运算方式,搜索速度快
二、主要名词
基因:一个基因代表具体问题解的一个决策变量。
基因型(genotype):性状染色体的内部表现;如二进制编码
表现型(phenotype):染色体决定的性状的外部表现,或者说,根据基因型形成的个体的外部表现;如十进制数值
编码(coding):DNA中遗传信息在一个长链上按一定的模式排列。遗传编码可看作从表现型到基因型的映射。
解码(decoding):基因型到表现型的映射。
进化(evolution):种群逐渐适应生存环境,品质不断得到改良。生物的进化是以种群的形式进行的。
适应度(fitness):度量某个物种对于生存环境的适应程度。
选择(selection):以一定的概率从种群中选择若干个个体。一般,选择过程是一种基于适应度的优胜劣汰的过程。
复制(reproduction):细胞分裂时,遗传物质DNA通过复制而转移到新产生的细胞中,新细胞就继承了旧细胞的基因。
交叉(crossover):两个染色体的某一相同位置处DNA被切断,前后两串分别交叉组合形成两个新的染色体。也称基因重组或杂交;
变异(mutation):复制时可能(很小的概率)产生某些复制差错,变异产生新的染色体,表现出新的性状。
个体(individual):指染色体带有特征的实体;一个染色体代表一个具体问题的一个解,一个染色体包含若干基因。
种群(population):多个个体(染色体)构成一个种群。即一个问题的多组解构成了解的种群。
染色体作为遗传物质的主要载体,即多个基因的集合,其内部表现(即基因型)是某种基因组合,它决定了个体的形状的外部表现,如黑头发的特征是由染色体中控制这一特征的某种基因组合决定的。因此,在一开始需要实现从表现型到基因型的映射即编码工作。由于仿照基因编码的工作很复杂,我们往往进行简化,如二进制编码。
遗传算法是从代表问题可能潜在的解集的一个种群(population)开始的,而一个种群则由经过基因(gene)编码的一定数目的个体(individual)组成。每个个体实际上是染色体(chromosome)带有特征的实体。
初代种群产生之后,按照适者生存和优胜劣汰的原理,逐代(generation)演化产生出越来越好的近似解,在每一代,根据问题域中个体的适应度(fitness)大小选择(selection)个体,并借助于自然遗传学的遗传算子(genetic operators)进行组合交叉(crossover)和变异(mutation),产生出代表新的解集的种群。
这个过程将导致种群像自然进化一样的后生代种群比前代更加适应于环境,末代种群中的最优个体经过解码(decoding),可以作为问题近似最优解。
三、编码方式对比
1. 二进制编码
二进制编码由二进制符号0和1所组成的二值符号集。它有以下一些优点:
1. 编码、解码操作简单易行
2. 交叉、变异等遗传操作便于实现
3. 合最小字符集编码原则
4. 利用模式定理对算法进行理论分析。
二进制编码的缺点是:对于一些连续函数的优化问题,由于其随机性使得其局部搜索能力较差,如对于一些高精度的问题,当解迫近于最优解后,由于其变异后表现型变化很大,不连续,所以会远离最优解,达不到稳定。
2. 格雷码
格雷码编码是其连续的两个整数所对应的编码之间只有一个码位是不同的,其余码位完全相同。
二进制码转为格雷码:异或运算:同则为0,异则为1。公式如下:
由格雷码转二进制的转换公式为:
优点:增强遗传算法的局部搜索能力,便于对连续函数进行局部空间搜索。使用非常广泛。
3. 浮点编码法
二进制编码虽然简单直观,但明显地存在着连续函数离散化时的映射误差。个体长度较短时,可能达不到精度要求,而个体编码长度较长时,虽然能提高精度,但增加了解码的难度,使遗传算法的搜索空间急剧扩大。
所谓浮点法,是指个体的每个基因值用某一范围内的一个浮点数来表示。编码长度等于决策变量的个数。 在浮点数编码方法中,必须保证基因值在给定的区间限制范围内,遗传算法中所使用的交叉、变异等遗传算子也必须保证其运算结果所产生的新个体的基因值也在这个区间限制范围内。
优点:
4. 符号编码法
符号编码法是指个体染色体编码串中的基因值取自一个无数值含义、而只有代码含义的符号集如{A,B,C…}。
优点:
1. 符合有意义积术块编码原则
2. 便于在遗传算法中利用所求解问题的专门知识
3. 便于遗传算法与相关近似算法之间的混合使用。
四、常用选择(selection)算子
1. 轮盘赌选择(Roulette Wheel Selection):是一种回放式随机采样方法。每个个体进入下一代的概率等于它的适应度值与整个种群中个体适应度值和的比例。选择误差较大。
2. 随机竞争选择(Stochastic Tournament):每次按轮盘赌选择一对个体,然后让这两个个体进行竞争,适应度高的被选中,如此反复,直到选满为止。
3. 最佳保留选择:首先按轮盘赌选择方法执行遗传算法的选择操作,然后将当前群体中适应度最高的个体结构完整地复制到下一代群体中。
4. 无回放随机选择(也叫期望值选择Excepted Value Selection):根据每个个体在下一代群体中的生存期望来进行随机选择运算。方法如下:
(1) 计算群体中每个个体在下一代群体中的生存期望数目N。
(2) 若某一个体被选中参与交叉运算,则它在下一代中的生存期望数目减去0.5,若某一个体未 被选中参与交叉运算,则它在下一代中的生存期望数目减去1.0。
(3) 随着选择过程的进行,若某一个体的生存期望数目小于0时,则该个体就不再有机会被选中。
5. 确定式选择:按照一种确定的方式来进行选择操作。具体操作过程如下:
(1) 计算群体中各个个体在下一代群体中的期望生存数目N。
(2) 用N的整数部分确定各个对应个体在下一代群体中的生存数目。
(3) 用N的小数部分对个体进行降序排列,顺序取前M个个体加入到下一代群体中。至此可完全确定出下一代群体中M个个体。
6. 无回放余数随机选择:可确保适应度比平均适应度大的一些个体能够被遗传到下一代群体中,因而选择误差比较小。
7. 均匀排序:对群体中的所有个体按期适应度大小进行排序,基于这个排序来分配各个个体被选中的概率。
8. 最佳保存策略:当前群体中适应度最高的个体不参与交叉运算和变异运算,而是用它来代替掉本代群体中经过交叉、变异等操作后所产生的适应度最低的个体。
9. 随机联赛选择:每次选取几个个体中适应度最高的一个个体遗传到下一代群体中。
10. 排挤选择:新生成的子代将代替或排挤相似的旧父代个体,提高群体的多样性。
五、主要步骤
1)种群初始化。我们需要首先通过随机生成的方式来创造一个种群,一般该种群的数量为100~500,这里我们采用二进制将一个染色体(解)编码为基因型。随后用进制转化,将二进制的基因型转化成十进制的表现型。
2)适应度计算(种群评估)。这里我们直接将目标函数值作为个体的适应度。
3)选择(复制)操作。根据种群中个体的适应度大小,通过轮盘赌等方式将适应度高的个体从当前种群中选择出来。其中轮盘赌即是与适应度成正比的概率来确定各个个体遗传到下一代群体中的数量。
具体步骤如下:
(1) 首先计算出所有个体的适应度总和。
(2) 其次计算出每个个体的相对适应度大小,类似于softmax。
(3) 再产生一个0到1之间的随机数,依据随机数出现在上述哪个概率区域内来确定各个个体被选中的次数。
4) 交叉(交配)运算。该步骤是遗传算法中产生新的个体的主要操作过程,它用一定的交配概率阈值(pc,一般是0.4到0.99)来控制是否采取单点交叉,多点交叉等方式生成新的交叉个体。
具体步骤如下:
a) 先对群体随机配对。
b) 再随机设定交叉点的位置。
c) 再互换配对染色体间的部分基因。
5) 变异运算。该步骤是产生新的个体的另一种操作。一般先随机产生变异点,再根据变异概率阈值(pm,一般是0.0001到0.1)将变异点的原有基因取反。
6) 终止判断。如果满足条件(1.继续迭代的总体变化不大;2.迭代次数(一般是200~500);3.适应度函数达到预期值)则终止算法,否则返回step2。
六、适应度变换
个体适应度与其对应的个体表现型x的目标函数值相关联,x越接近于目标函数的最优点,其适应度越大,从而其存活的概率越大。反之适应度越小,存活概率越小。这就引出一个问题关于适应度函数的选择,本例中,函数值总取非负值(删去了。。。),以函数最大值为优化目标,故直接将目标函数作为适应度函数。这里我们直接将目标函数作为个体适应度。如果,你想优化的是多元函数的话,需要将个体中基因型的每个变量提取出来,分别带入目标函数。比如说:我们想求x1+lnx2的最大值。基因编码为4位编码,其中前两位是x1,后两位是x2。那么我们在求适应度的时候,需要将这两个值分别带入,。再对和求和得到此个体的适应度。
关于x的生成方式,据资料说只是一个编码规则。规则对最后的优化结果都没有什么影响,但是对其优化速度和平滑性都有影响。这也正是适应度函数选择的意义,选的好的话就可以加快优化效果。
由下面的选择操作可知,适应度是选择操作的主要参考依据,适应度函数(Fitness Function)的选取直接影响到遗传算法的收敛速度以及能否找到最优解。因而适应度函数的选择问题在遗传算法中是一项很值得研究的课题。一般情况下,关于适应度与目标函数的选择有以下这两种关系:
(1)对于最小化问题,建立如下适应度函数和目标函数的映射关系:
其中可以是一个输入值或是理论上的最大值,或是当前所有代或最接近K代中的最大值,此时随着代数会有变化
(1)对于最大化问题,一般采用以下映射
其中, 可以是一个输入值,或是当前所有代或最接近K代中的最小值
也就是说,我们要在每一轮迭代(进化)时,要将所有个体的适应度函数值都要遍历,然后得到最大(小)值。此时每一轮的适应度函数都是在变化的。之前,我们程序中的个体适应度函数是不变化的。实际上要得到更准确的结果,除了以上基本变化,还有以下的适应度变化方法:
函数根据采用的形式不同会产生不同的变换方法,具体如下:
线性变换:
指数变换:
Boltzmann变换:
我们对个体的适应度调整的目的有两个:一是维持个体之间的合理差距,加速竞争。二是避免个体之间的差距过大,限制竞争。
七、优化方向
编码(decode)方法(二进制编码、灰色编码、动态参数编码、Delta编码等);
选择(selection)机制(选择压、秩选择、适应度函数的尺度变换、杰出者选择等);
杂交(crossover)和变异(mutation)机制(单点杂交、多点杂交、均匀杂交等);
执行策略的改进(如世代型GA、稳定状态型GA、并行GA、共存演化GA、混乱GA等等);
GA的过程是一个随机搜索过程,总希望这个过程在期望值意义下越来越好,这样自然应当是一个下鞅序列。为了保证遗传算法的收敛性,有两个参数是非常重要的:一是过程进入满意解后下一步脱离满意解集的可能性;二是过程未进入满意解时下一步仍不能进入满意解的可能性。
八、完整代码
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
from matplotlib import pyplot as plt
import math, random
class Population:
# 种群的设计
def __init__(self, interval, size, chrom_size, cp, mp, gen_max):
self.individuals = [] # 个体集合
self.fitness = [] # 个体适应度集合
self.selector_probability = [] # 个体选择概率集合
self.new_individuals = [] # 新一代个体集合
self.interval = interval # 变量区间
self.elitist = {'chromsome': [0, 0], 'fitness': 0, 'age': 0} # 最佳个体信息
self.size = size # 种群所包含的个体数
self.chromosome_size = chrom_size # 个体的染色体长度
self.crossover_probability = cp # 个体之间的交叉概率
self.mutation_probability = mp # 个体之间的变异概率
self.generation_max = gen_max # 种群进化的最大世代数
self.age = 0 # 种群当前所处世代
# 随机产生初始个体集,并将新一代个体、适应度、选择概率等集合以0值进行初始化
v = 2**self.chromosome_size - 1 # 染色体长度决定了二进制位数,对应十进制的最大值为v
for i in range(self.size):
self.individuals.append([random.randint(0,v), random.randint(0, v)])
self.new_individuals.append([0, 0])
self.fitness.append(0)
self.selector_probability.append(0)
# 基于轮盘赌的选择
def decode(self, interval, chromosome):
'''
将一个染色体chromosome映射为区间interval之内的数值
:param self:
:param interval:
:param chromosome:
:return:
'''
d = interval[1] - interval[0]
n = float(2**self.chromosome_size-1)
return (interval[0]+chromosome*d/n)
def fitness_func(self, interval, chrom1, chrom2):
'''
适应度函数,可以根据个体的两个染色体计算出该个体的适应度
:param self:
:param chrom1:
:param chrom2:
:return:
'''
(x, y) = (self.decode(interval, chrom1), self.decode(interval, chrom2))
n = lambda x, y: math.sin(math.sqrt(x**2+y**2))**2 - 0.5
d = lambda x, y: (1+0.001*(x**2+y**2)**2)
func = lambda x, y: 0.5 - n(x, y)/d(x, y)
return func(x, y)
def evaluate(self):
'''
用于评估种群中的个体集合self.individuals 中各个个体的适应度
:param self:
:return:
'''
sp = self.selector_probability
for i in range(self.size):
self.fitness[i] = self.fitness_func(self.interval, self.individuals[i][0], self.individuals[i][1]) # 每个个体适应度
ft_sum = sum(self.fitness)
for i in range(self.size):
sp[i] = self.fitness[i] / float(ft_sum) # 得到各个个体的生存概率
for i in range(1, self.size):
sp[i] = sp[i] + sp[i-1] # 将个体的生存概率进行叠加,从而计算出各个个体的选择概率
# 轮盘赌博(选择)
def select(self):
(t, i) = (random.random(), 0)
for p in self.selector_probability:
if p > t:
break
i += 1
return i
# 交叉
def crossover(self, chrom1, chrom2):
p = random.random()
n = 2 ** self.chromosome_size - 1
if chrom1 != chrom2 and p < self.crossover_probability:
t = random.randint(1, self.chromosome_size-1) # 随机选择一点(单点交叉)
mask = n << t # 左移运算符
(r1, r2) = (chrom1&mask, chrom2&mask) # &是按与运算符
mask = n >> (self.chromosome_size - t)
(l1, l2) = (chrom1&mask, chrom2&mask)
(chrom1, chrom2) = (r1+l2, r2+l1)
return (chrom1, chrom2)
# 变异
def mutate(self, chrom):
p = random.random()
if p < self.mutation_probability:
t = random.randint(1, self.chromosome_size)
mask1 = 1 << (t-1)
mask2 = chrom & mask1
if mask2 > 0:
chrom = chrom&(~mask2) # ~按位取反运算:对数据的每个二进制位取反
else:
chrom = chrom ^ mask1 # ^按位异或运算:当两对应的二进位相异时,结果为1
return chrom
# 保留最佳个体
def reproduct_elitist(self):
j = -1
for i in range(self.size):
if self.elitist['fitness'] < self.fitness[i]:
j = i
self.elitist['fitness'] = self.fitness[i]
if (j >= 0):
self.elitist['chromsome'][0] = self.individuals[j][0]
self.elitist['chromsome'][1] = self.individuals[j][1]
self.age += 1
self.elitist['age'] = self.age
# 进化
def evolve(self):
indvs = self.individuals
new_indvs = self.new_individuals
self.evaluate() # 计算个体适应度和选择概率
i=0
while True:
# 选择两个个体
idv1 = self.select()
idv2 = self.select()
# 交叉
(idv1_x, idv1_y) = (indvs[idv1][0], indvs[idv1][1])
(idv2_x, idv2_y) = (indvs[idv2][0], indvs[idv2][1])
(idv1_x, idv2_x) = self.crossover(idv1_x, idv2_x) # 对个体1和个体2的x染色体进行交叉
(idv1_y, idv2_y) = self.crossover(idv1_y, idv2_y)
# 变异
(idv1_x, idv1_y) = (self.mutate(idv1_x), self.mutate(idv1_y))
(idv2_x, idv2_y) = (self.mutate(idv2_x), self.mutate(idv2_y))
(new_indvs[i][0], new_indvs[i][1]) = (idv1_x, idv1_y)
(new_indvs[i+1][0], new_indvs[i+1][1]) = (idv2_x, idv2_y)
i += 2
if i >= self.size:
break
self.reproduct_elitist()
for i in range(self.size):
self.individuals[i][0] = self.new_individuals[i][0]
self.individuals[i][1] = self.new_individuals[i][1]
def run(self):
'''
根据种群最大进化世代数设定循环,调用evolve函数进行进化计算,并输出种群的每一代个体适应度最大值
:param self:
:return:
'''
for i in range(self.generation_max):
self.evolve()
# print(i, max(self.fitness), sum(self.fitness)/self.size, min(self.fitness))
print(self.elitist)
def pplot(self):
fig = plt.figure(figsize=(10, 6))
ax = Axes3D(fig)
x = np.arange(-10, 10, 0.1)
y = np.arange(-10, 10, 0.1)
X, Y = np.meshgrid(x, y) # 生成网格点坐标矩阵
Z = 0.5 - (np.sin(np.sqrt(X ** 2 + Y ** 2)) ** 2 - 0.5) / (1 + 0.001 * (x ** 2 + y ** 2) ** 2)
plt.xlabel('x')
plt.ylabel('y')
ax.set_zlim([-1, 5])
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='rainbow')
plt.show()
if __name__ == '__main__':
# 个体数量400,染色体长度25, 交叉概率0.8, 变异概率0.1, 最大世代数100
pop = Population([-10, 10], 200, 24, 0.8, 0.1, 100)
pop.run()
# pop.pplot()
最后的结果容易进入局部最优解出不来,结果示例(个体数量足够大时还是能得到近似最优解的)如下:
文章参考网址:https://zhuanlan.zhihu.com/p/43546261
其他启发式算法学习推荐:
模拟退火算法(Simulated Annealing,SA)的全面讲解及python实现
粒子群(PSO)优化算法(Particle Swarm Optimization)的全面讲解及python实现