利用遗传算法计算函数最值
在对人工智能的学习过程中,初次接触到遗传算法,在这里记录一下我的学习过程和一些思考。
以下是我学习和参考的资料,下文中涉及的问题、内容和求解方法均来源于以下资料:
关于利用遗传算法解决函数最值问题的相关博客
关于遗传算法原理的讲解
刚开始接触遗传算法时,我觉得很抽象,难以理解,但经过实际问题的演练后,逐渐明白了其中的一点点原理。特别是函数最值问题,可以形象地类比为种群在环境中的生存问题。在遗传算法中,确定适应度函数是非常关键的。针对函数最值问题,适应度函数可以简单地选择原函数,自变量则对应种群中的个体。在求最大值的问题中,函数值即对应个体的适应度,适应度越高的个体就越容易存活。每一轮的迭代过程中,适应度低的个体会被淘汰。同时,为了削减初始值对最终结果的影响,需设定个体突变的概率。简单来说,适应度对应函数值,适应度函数对应目标函数,个体染色体编码对应自变量的值,生存环境对应我们设计的筛选规则。下面开始解决一个小问题。
考虑以下函数:
在区间(0,9)求函数最大值。
以下是我学习参考的代码,在原代码(上文第一个链接)基础上做了一点点修改:
import numpy as np
import matplotlib.pyplot as plt
# 适应度函数,即目标函数
def fitness(x):
return x + 10 * np.sin(5 * x) + 7 * np.cos(4 * x)
# 个体类
class indivdual:
def __init__(self): #初始化定义
self.x = 0 # 染色体编码,自变量
self.fitness = 0 # 适应度值,因变量
def __eq__(self, other): #赋值定义
self.x = other.x
self.fitness = other.fitness
# 初始化种群函数,N为种群内个体数,pop为种群
def initPopulation(pop, N):
for i in range(N):
ind = indivdual()
ind.x = np.random.uniform(0, 9)
ind.fitness = fitness(ind.x)
pop.append(ind) #个体加入种群中
# 选择过程
def selection(N):
# 种群中随机选择2个个体进行变异(这里没有用轮盘赌,直接用的随机选择)
return np.random.choice(N, 2)
# 结合/交叉过程,采用算数交叉的方法(两个个体线性组合)
def crossover(parent1, parent2):
# 染色体保留百分比
save1,save2 = 0.9 , 0.1
child1, child2 = indivdual(), indivdual()
child1.x = save1 * parent1.x + save2 * parent2.x
child2.x = save2 * parent1.x + save1 * parent2.x
child1.fitness = fitness(child1.x)
child2.fitness = fitness(child2.x)
return child1, child2
# 变异过程
def mutation(pop):
# 种群中随机选择一个进行变异
ind = np.random.choice(pop)
# 用随机赋值的方式进行变异
ind.x = np.random.uniform(0, 9)
ind.fitness = fitness(ind.x)
# 最终执行
def implement():
# 种群中个体数量
N = 20
# 种群
POP = []
#记录种群初始状态
POP_init = []
# 迭代次数
iter_N =1800
# 初始化种群
initPopulation(POP, N)
# 记录种群初始个体
POP_init = POP.copy()
#个体间交叉结合概率
crosscover_pro = 0.75
#个体变异概率
mutation_pro = 0.1
# 进化过程
for it in range(iter_N):
a, b = selection(N)
#交叉结合
if np.random.random() < crosscover_pro:
child1, child2 = crossover(POP[a], POP[b])
#对交叉结合后的四个个体进行排序
new = sorted([POP[a], POP[b], child1, child2], key=lambda ind: ind.fitness, reverse=True)
#留下四个个体中适应度较高的两个
POP[a], POP[b] = new[0], new[1]
#变异
if np.random.random() < mutation_pro:
mutation(POP)
POP.sort(key=lambda ind: ind.fitness, reverse=True) #key指定某一项进行比较
return POP,POP_init
pop,pop_init = implement()
# 绘图代码
def func(x):
return x + 10 * np.sin(5 * x) + 7 * np.cos(4 * x)
x = np.linspace(0, 9, 10000)
y = func(x)
scatter_x = np.array([ind.x for ind in pop])
scatter_y = np.array([ind.fitness for ind in pop])
#初始个体
init_x = np.array([ind.x for ind in pop_init])
init_y = np.array([ind.fitness for ind in pop_init])
print(scatter_x)
print(scatter_y)
plt.plot(x, y)
plt.scatter(scatter_x, scatter_y, c='r')
plt.scatter(init_x, init_y, c='b')
plt.show()
下图为代码运行结果
图中的蓝色曲线代表目标函数,蓝色点代表种群中的初始个体,红色点代表计算后剩下的个体,两个数组的第一个值分别代表最大值处自变量的值和函数最大值。
在得到结果后,我开始尝试调整运算过程中的参数值,以此达到检验结果的目的。
(1)将个体变异概率调节为0(之前为0.1)
从图中可以看到,剩余的个体集中在函数的几个极大值点处,但并没有我们想要的最大值。这里可以考虑将函数极大值处当作山峰。如果不进行变异,个体很难逾越到旁边的山峰,因为即使是他们的后代也继承了其父辈的部分染色体编码(x的值)。因此在变异概率为0时,初始化时个体的染色体编码对后续运算起到决定性作用。既然变异概率为0时得到的结果不理想,如果设置为较大会怎样呢?
(2)将变异概率设置为0.5(之前为0.1)
从图中可以看出,相比于变异概率0.1,红色点更加分散,但仍能够得到我们想要的结果。
(3)降低交叉结合概率至0.3,保持变异概率为0.1(之前为0.75)
由图可见,下调交叉结合概率后,剩余个体的分布更加分散,甚至无法保证得到我们想要的结果。
从以上的尝试中,可以发现在遗传算法中,个体间交叉结合的概率、个体变异的概率均会对结果有较大影响。在设计算法前,应对不同问题合理分析,设置合理的参数值。完成代码后,可以多次修改这些参数值,通过对比检验结果的准确性。当然对于遗传算法,影响运算结果的因素有很多,这里只是列举两个最简单的,毕竟我也是刚刚接触。