遗传算法原理
- 遗传算法的难点,我认为集中在抽象过程的编码。遗传算法对问题的编码过程有实数值编码和二进制编码。两种编码方式。
- 遗传算法具体实施有三个步骤:1,选择;2,交叉;3,变异。用自己的话把这三个步骤描述一下,如下:
-
- 1,选择操作,根据物竞天择适者生存的法则,种群最终得以生存的多数个体的基因相对来说都很优良,身强体壮。这个选择操作就是根据个体适应度函数值进行排序。假设种群数量为n,则选择操作就要求选出适应度最好的前k个个体。
-
- 2,交叉操作,就是高中的染色体碱基对配对。数学描述是说,n维的两个解向量(两个个体),假设交叉概率为 P c P_c Pc,则n维的解向量有 n ∗ P c n*P_c n∗Pc的解分量需要产生交叉。但并不是说解分量的位置一定是相同的,这个和碱基对配对不同,因为碱基对的位置是固定的。
-
- 3,变异操作,假设变异概率是 P m P_m Pm,则有 n ∗ P m n*P_m n∗Pm的解分量按照一定的规则发生改变。
遗传算法生物学术语对照表
遗传算法流程图
仿真实例
跟前面的模拟退火算法一样,都是仿真第二个函数;
f ( x ) = ∑ i = 1 n x i 2 ( − 20 ⩽ x i ⩽ 20 ) f(x)=\sum_{i=1}^{n} x_{i}^{2}\left(-20 \leqslant x_{i} \leqslant 20\right) f(x)=∑i=1nxi2(−20⩽xi⩽20)
Python实战
说明和感想
几乎每一行都有注释,所以不再解释;放在这里做分享。主要说明几点,相比较于书上给的例子,我这里做了几点改进:
1.书上以人为的方式强制迭代1000轮,而我是用精度控制的方式控制迭代次数;说人话就是书上的例子用for循环,我用的是while,因此实际上如果精度要求不高,完全不需要1000次迭代,0.001的精度500~600次就够了;
2.我的绘图比较好看,这个绘图真有意思,以后写论文用得到;
3.书上的代码,染色体基因做交叉时,用的君主方式配对交叉,但是不做判断不管好坏都交叉;虽然经过实验发现也能收敛,但是加上条件判断之后,更稳定。至于为什么也能收敛,而且效果不错。我不知道,可能任务太简单了。遇上复杂任务,不做条件的做交叉,会有更大概率陷入局部最优,这部分可以做改进。可以和模拟退火算法融合,用模拟退火算法概率接受差值解的方式扩充搜索空间。
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
from numpy import random
class GA:
def __init__(self,D,NP,Xs,Xx,E,Pc,Pm):
'''
参数初始化
:param D:解维度
:param NP: 解数量
:param Xs: 解上限
:param Xx: 解下限
:param G: 迭代次数
:param Pc: 交叉概率
:param Pm: 变异概率
'''
self.D = D
self.NP = NP
self.Xs = Xs
self.Xx = Xx
self.E = E
self.Pc = Pc
self.Pm = Pm
def func(self,x):
return sum(x**2)
def ga(self):
# 初始化解空间
x = random.rand(self.NP,self.D)*(self.Xs-self.Xx)+self.Xx
# 计算适应度
y = self.func(x.T)
yk = np.sort(y) # 排序初始解的适应度
# 取下标
k = np.argsort(y)
# 解空间排序
xk = x[k]
# 验证排序后的解计算的适应度是否与排序后的yk相等
# y1 = self.func(xk.T)==yk
# return y1 # 正确
trace = [] # 记录最优适应度
trace.append(yk[0])
number = 0
while True:
# 采用君主方案选择交叉
emperor = xk[0]
cross_num = round(self.Pc*self.D)
cross_loc = random.randint(0,10,(int(self.NP/2),self.D))
nx = xk.copy() # 复制一份数组,生成新的内存空间;这里不做拷贝,全错
odd = [i for i in range(self.NP) if (i+1)%2==1] # 奇数位下标
nx[odd] = emperor # 奇数位置染色体全部赋予初始解的最优
even = [i for i in range(self.NP) if (i+1)%2==0] # 偶数位下标
# 做交叉操作
'''
前一个奇数位置和后一个偶数位置位一对,每一对需要做的交叉操作有cross_num次;
这里有个改进,交叉之前判断交叉点是不是优解;不是则不交叉
'''
N = int(self.NP/2)
for n in range(N):
for num in range(cross_num):
k1 = odd[n]
k2 = even[n]
k3 = cross_loc[n,num]
k4 = nx[k1,k3]
k5 = nx[k2,k3]
k6 = emperor[k3]
if abs(k5) < abs(k4): # 奇数位解分量比偶数位解分量大才交换
nx[odd[n], cross_loc[n, num]] = nx[even[n],cross_loc[n,num]]
if abs(k5) > abs(k6): # 同理偶数位解分量比奇数位解分量大才交换
nx[even[n], cross_loc[n, num]] = emperor[cross_loc[n, num]]
# nx[odd[n], cross_loc[n, num]] = nx[even[n], cross_loc[n, num]]
# nx[even[n], cross_loc[n, num]] = emperor[cross_loc[n, num]]
# 变异操作
r = []
for i in range(self.NP*self.D):
r.append(True if np.random.rand() < self.Pm else False)
r = np.array(r)
r = r.reshape(self.NP,self.D)
p = []
for i in range(np.sum(r == True)):
p.append(np.random.rand()*(self.Xs-self.Xx)+self.Xx)
p = np.array(p)
nx[r] = p
# 计算子代适应度
ny = self.func(nx.T)
# 合并种群并排序
x_sum = np.concatenate((nx,xk))
y_sum = np.concatenate((ny,yk))
rank_y = np.sort(y_sum) # 适应度重排
nk = np.argsort(y_sum)
rank_x = x_sum[nk] # 解空间重新排序
xk = rank_x[:self.NP] #取前NP个最优解
yk = rank_y[:self.NP] #取前NP个最适应度
trace.append(yk[0])
a = trace[number]
b = trace[number+1]
# if abs(b-a) < self.E: # 精度控制方式1,比较差
# break
if yk[0] < self.E: # 精度控制方式2,更好
break
else:
number += 1
return trace ,xk
def drawer(self):
y, x = self.ga()
plt.rc('legend', fontsize=16)
matplotlib.rcParams['font.family'] = 'Kaiti'
fig = plt.figure(figsize=(12,6),dpi=100)
plt.plot(y, 'b:d', markerfacecolor='red',markersize=3,linewidth=1.5, label='遗传算法仿真重写和改进2') # 1.线型颜色、线型风格、标记风格;2.线宽 3.标记颜色填充
plt.xlabel('迭代次数', fontproperties='Kaiti', fontsize=14) # 坐标轴标签设置
plt.ylabel('适应度函数值', fontproperties='Kaiti', fontsize=14)
plt.legend() # 添加图例
# 图题
plt.title(r'遗传算法求解$f(x)=\sum_{i=1}^{n} x_{i}^{2}\left(-20 \leqslant x_{i} \leqslant 20\right)$',
fontproperties='Kaiti', fontsize=14)
plt.grid(True) # 网格
plt.show()
ga = GA(10,100,20,-20,0.009,0.8,0.1)
ga.drawer()
y,x = ga.ga()
print(x[0],y[-5:])
实验结果
- 由于仿真函数很简单,遗传算法用20步就可以找到最优解了。