一、概述
遗传算法是模拟生物在自然环境中的遗传和进化的过程,从而形成的“自适应全局优化搜索”算法。
1.1 遗传算法的生物学基础
自然选择学说认为适者生存,生物要存活下去,就必须进行生存斗争。在生存斗争中,更适合环境,具有有利变异的个体容易存活下来。因此,凡是在生存斗争中获胜的个体都是对环境适应性较强的个体。达尔文把这种在生存斗争中适者生存,不适者淘汰的过程叫做自然选择。
由于生物在繁殖中可能发生基因交叉和变异,引起生物性状的连续微弱改变,为外界环境的定向选择提供了物质条件和基础,使生物的进化成为可能。人们正式通过对环境的选择、基因的交叉和变异这一生物演化的迭代过程的模仿,才提出了能够用户求解最优化问题的强鲁棒性和自适应性的遗传算法。
生物遗传和进化的规律有:
(1)生物的所有遗传信息都包含在其染色体中,染色体决定了生物的性状。染色体是由基因及其有规律的排列所构成的。
(2)生物的繁殖过程是由其基因的复制过程来完成的。同源染色体的交叉或变异会产生新的物种,使生物呈现新的性状。
(3)对环境适应能力强的基因或染色体,比适应能力差的基因或染色体有更多的机会遗传到下一代。
1.2 遗传算法的基本概念
简单来说,遗传算法使用群体搜索技术(循环+条件判断),将种群代表一组问题解,通过对当前种群施加选择、交叉、变异等一系列遗传操作来产生新一代的种群,并逐步使种群进化到包含近似最优解的状态(最终生成的是一个种群,然后可根据适应度最高个体找到最优解)。遗传学属于和遗传算法术语对照表如下:
遗传学术语 | 遗传算法术语 |
---|---|
群体 | 可行解集 |
个体 | 可行解 |
染色体 | 可行解编码 |
基因 | 可行解编码的分量 |
基因形式 | 遗传编码 |
适应度 | 评价函数值 |
选择 | 选择操作 |
交叉 | 交叉操作 |
变异 | 变异操作 |
二、基于遗传算法实现求解函数最优解(python)
问题:用遗传算法求函数
f
(
x
)
=
x
+
10
sin
(
x
)
+
7
cos
(
4
x
)
f(x)=x+10\sin(x)+7\cos(4x)
f(x)=x+10sin(x)+7cos(4x)的最大值,其中
x
x
x的取值范围为
[
0
,
10
]
[0,10]
[0,10]。函数图像如下图所示:
附上python代码:
x = np.arange(0,10,0.01)
y = x+10*np.sin(5*x)+7*np.cos(4*x)
plt.plot(x,y)
plt.title("f(x) = x+10*sin(5x)+7*cos(4x)")
plt.xlabel("x")
plt.ylabel("y")
plt.savefig('fx.png')
plt.show()
2.1建模流程
1)初始化种群数目为Np=50,染色体二进制编码长度为L=20,最大进化代数为G=100,交叉概率为Pc=0.8,变异概率为Pm=0.1。
2)产生初始种群,将二进制编码转换成十进制,计算个体适应度值,并进行归一化;采用基于轮盘赌的选择操作、基于概率的交叉和变异操作,产生新的种群,并把历代的最优个体保留在新种群中,进行下一步遗传操作。
3)判断是否满足中止条件:若满足,则结束搜索过程,输出优化值;若不满足,则继续进行迭代优化。
2.2关键函数代码
2.2.1 创建种群函数
def create_population(population_nums,chromosome_length):
return np.random.randint(0,2,(population_nums,chromosome_length))#生成含有population_nums个个体的染色体长度chromosome_length的种群
函数解释:np.random.randint()函数可生成按传入参数形状的随机整数,整数区间用前两个参数确定,左闭右开。
2.2.2二进制转换十进制函数
# 二进制转换为十进制
def b2d(b,max_value,chromosome_length):
total=0
for i in np.arange(chromosome_length):
total=total+b[i]*math.pow(2,chromosome_length-i-1)
total=total*max_value/(math.pow(2,chromosome_length)-1)
return total
函数解释:二进制十进制转换,首先确定二进制位数,按位取2的n次方相乘相加,例如 1010 = 0 ∗ 2 0 + 1 ∗ 2 1 + 0 ∗ 2 2 + 1 ∗ 2 3 = 10 1010 = 0*2^0+1*2^1+0*2^2+1*2^3=10 1010=0∗20+1∗21+0∗22+1∗23=10
2.2.3目标函数/适应度函数
# 目标函数相当于环境 对染色体进行筛选,这里是2*sin(x)+cos(x)
def fitness(population,chromosome_length,min_value,max_value):
population_cp = population.copy()#不能直接赋值,赋值的话被赋值的变量更改,population也会一起更改
d = np.zeros(len(population))
for i in np.arange(chromosome_length):
d = d+population_cp[:,i]*np.power(2,chromosome_length-i-1)
middle = min_value+d*max_value/(np.power(2,chromosome_length)-1)
return middle+10*np.sin(5*middle)+7*np.cos(4*middle)
函数解释:本函数就是为了生成目标函数或者叫做适应度函数。以上目标函数的middle(中间变量)不太好理解,由于我们基因编码设定为20位,那么20位编码所转换的区间范围有 [ 0 , 2 20 − 1 ] [0,2^{20}-1] [0,220−1],而我们要求的x的范围在 [ 0 , 10 ] [0,10] [0,10]区间范围内。对应x所属区间范围内,我们可以计算当前编码占20位编码的百分数,并乘以区间范围最大值,得到x的真实取值。
2.2.4自然选择函数
def selection(population,fitness,min_fitness,max_fitness):
#归一化适应度值
fitness_min_max = (fitness1-min_fitness)/(max_fitness-min_fitness)
sum_fit = sum(fitness_min_max)
fit_value = fitness_min_max/sum_fit
#适应度划分区间
fit_value = np.cumsum(fit_value)
random_list = np.sort(np.random.random(size=len(population)))
#轮盘赌
newin = 0
fitin = 0
new_population = population.copy()
while newin<len(population):
if(random_list[newin]<fit_value[fitin]):
new_population[newin]=population[fitin]
newin+=1#控制新数组的一个一个遍历
else:
fitin+=1#控制可遍历所有个体的适应度
return new_population
函数解释:首先对适应度值进行操作,将适应度值进行归一化之后,划分成区间。np.cumsum()函数主要是用来做累加,例如[1,2,3]的数组,经过累加之后变为[1,3,6]。最终将适应度使用轮盘赌的方式来进行筛选。
轮盘赌筛选:
归一化适应度之后,会将适应度值压缩到0-1范围内,将适应度变为概率。同时生成的0-1范围内的随机数。适应度数量等于随机数数量。接下来对每一个个体被选中的概率及生成的随机数进行比较,若此个体概率大于随机数,则保留。
2.2.5交叉函数
def crossover(population,pc):
for i in np.arange(0,len(population),2):
p = np.random.random()
if p < pc:
cpoint = np.random.randint(0,2,len(population[0]))
for j in range(0,len(population[0])-1):
if cpoint[j] == 1:
temp = population[i+1][j]
population[i+1][j] = population[i][j]
population[i][j] = temp
return population
函数解释:此函数用来交叉i与i+1两个样本的基因。首先按2步长生成数组并循环,随后生成随机数,当随机数小于交叉率pc,执行交叉操作。交叉操作中,首先生成基因长度的0/1随机数,当随机数为1时选择第i+1个样本的第j个基因赋值给临时变量中。接下来,将第i个样本的第j个基因赋值给第i+1个样本的第j个基因,最终将临时变量赋值给第i个样本的第j个基因。
2.2.6变异函数
def mutation(population,pm):
i = 0
while i < np.round(len(population)*pm):
h = np.random.randint(0,len(population))
for j in range(len(population[0])):
g = np.random.randint(0,len(population[0]))
population[h][g] = ~population[h][g]
i+=1
return population
函数解释:首先用np.round(len(population)*pm),确定有几个样本需要变异(pm为变异率)。随后生成在[0,样本个数]区间内的随机数,作为需要变异的样本。生成在[0,基因长度]区间内的随机数作为需要变异的基因。将基因取反即可。
2.2.7 主函数
population_size = 50
chromosome_length = 20
pc = 0.8
pm = 0.1
G = 1000
max_value = 10
min_value = 0
population = create_population(population_size,chromosome_length)
best_fitness1 = []
best_indivadual1 = []
for i in range(G):
#计算出来适应度函数值
fitness1 = fitness(population,chromosome_length,min_value,max_value)
#计算最好适应度及其最好个体
best_indivadual,max_fitness,min_fitness = population[np.where(fitness1==max(fitness1))][0],max(fitness1),min(fitness1)
best_fitness1.append(max_fitness)
best_indivadual1.append(b2d(best_indivadual,max_value,chromosome_length))
#进行轮盘赌选择
selected_population = selection(population,fitness1,min_fitness,max_fitness)
#交叉
crossed_population = crossover(selected_population,pc)
#变异
mutationed_population = mutation(crossed_population,pm)
#保留历代最优个体到种群中
mutationed_population[1,:] = best_indivadual
population = mutationed_population
函数解释:此主函数虽只是用来调用的,但是里面几个关键点也要指出。计算最好/最差适应度及其最优个体是为了归一化适应度值和保留最优个体**(很重要)**,此外要按照选择、交叉、变异的操作流程来对种群进行操作。
2.2.8 结果展示
x = np.arange(len(best_fitness1))
y = best_fitness1
print("best indivadual",best_indivadual1[-1],"best fitness",best_fitness1[-1])
plt.plot(x,y)
plt.title("适应度随遗传代数迭代曲线")
plt.xlabel("遗传代数")
plt.ylabel("适应度")
plt.show()
2.3 关键参数说明
群体规模(population_size):一般取10~200;
交叉概率(pc):一般取0.25~1.00;
变异概率(pm):一般取0.001~0.1;
总体进化代数(G):一般取100-1000之间;