人工智能 遗传算法
题目
用遗传算法求解函数
20 + (x ^ 2) + (y ^ 2) - 10 * (cos(2 * π * x) + cos(2 * π * y)) (-5 < x, y <5)
的最小值
思路
分析问题,确定了至少需要两个类,即种群(Group)与个体(Individual)。同时为了稍微提高一点泛用性,还增加了一个函数(Function)类存放要求解的函数的相关信息。
class Group:
def __init__(self, group_size, mutation_rate, crossover_rate, dna_length, function):
'''
构造函数
:param group_size: 种群大小
:param mutation_rate: 变异率
:param crossover_rate: 交叉率
:param dna_length: DNA序列长度,即基因长度
:param function: 要求解的函数
'''
self.group_size = group_size
self.mutation_rate = mutation_rate
self.crossover_rate = crossover_rate
self.function = function
self.individuals = [] # 所有个体
self.best_individual = None # 最优个体
self.dna_length = dna_length
self.init_group()
class Individual:
def __init__(self, gene):
'''
构造函数
:param gene: 基因序列
'''
self.gene = gene
self.fit = 0 # 适应度
class Function:
def __init__(self, function, max_variable, min_variable, find_min=True):
'''
构造函数
:param function: 要求解的函数
:param max_variable: 函数自变量的最大值
:param min_variable: 函数自变量的最小值
:param find_min: 求最小值,默认为True,False即为求最大值
'''
self.function = function
self.max_variable = max_variable
self.min_variable = min_variable
self.find_min = find_min
首先需要构造一个种群,在构造函数为各种变量赋初值,然后进行初始化种群操作,即为种群中的所有个体创建基因序列
def generate_gene(self):
'''
生成一个基因序列
:return: 返回一个由0,1组成的序列
'''
gene = []
for i in range(self.dna_length):
gene.append(random.randint(0, 1))
return gene
def init_group(self):
'''
初始化种群的所有个体
'''
for i in range(self.group_size):
self.individuals.append(Individual.Individual(self.generate_gene()))
然后就要开始繁衍获得下一代
获得下一代需要经过以下两个步骤
- 产生孩子
种群中的每一个个体与种群中的另一个随机个体繁衍获得孩子。暂时先默认孩子接收父亲的基因。但是繁殖过程中孩子的基因可能发生交叉和变异。
交叉:从孩子的基因的一个随机位置开始,该位置后的基因变为母亲在同样位置之后的基因
变异:孩子的基因的某个随机位置取反
def crossover(self, father, mother):
'''
交叉
:param father: 父亲
:param mother: 母亲
:return: 孩子
'''
gene = [i for i in father.gene] # 首先默认孩子接收父亲的基因
child = Individual.Individual(gene) # 直接传入father.gene会导致更改child的基因时father的基因也会修改,种群基因变得不稳定
if self.crossover_rate > random.random(): # 判断是否发生交叉
cross_point = random.randint(1, self.dna_length - 1) # 交叉发生的位置
child.gene[cross_point:] = mother.gene[cross_point:] # 交叉位置后的基因变为母亲的基因
return child
def muter(self, individual):
'''
变异
:param individual: 可能变异的个体
:return: 变异之后的个体或原个体
'''
if self.mutation_rate > random.random(): # 判断是否变异
index = random.randint(0, self.dna_length - 1) # 变异的位置
individual.gene[index] = 1 - individual.gene[index] # 取反
return individual
def get_child(self, father, mother):
'''
产生一个孩子
:param father: 父亲
:param mother: 母亲
:return: 孩子
'''
child = self.crossover(father, mother)
child = self.muter(child)
return child
- 自然选择
此时的个体数已经变为原本的两倍,接下来要进行选择。选择适应度较高的一半个体存活。
首先计算所有个体的适应度
个体的适应度用函数的计算结果表示,但个体的基因是0,1组成的序列,无法直接带入到函数的自变量中求解,所以需要先把个体的基因转换成十进制的数字。
转换方式:- 将序列按权展开,得到对应的十进制数
- 十进制数除以序列长度的二进制数所能表示的最大值,此时得到的是0~1之间的小数,相当于一个比例系数
- 将小数乘以待求解函数自变量的取值范围的长度可以得到一个从0开始的在取值范围长度大小内的一个数
- 将得到的数加上取值范围的最小值就可以得到规定范围内的数
- 将基因序列的前一半作为x,后一半作为y,就可以代入函数求得结果作为适应度
def calculate_variable(self, x):
'''
将0,1序列转换成自变量值域内的一个十进制数
:param x: 要转换的序列
:return: 转换后的十进制数
'''
r = 0
for i in range(len(x)): # 序列的每一位
r += ((2 ** i) * x[i]) # 乘以权重
r /= ((2 ** len(x)) - 1) # 变成0~1之间的小数,相当于一个比例系数
r *= (self.function.max_variable - self.function.min_variable) + self.function.min_variable # 控制在函数自变量值域
return r
def set_fit(self, individual):
'''
求出适应度
:param individual: 要求的个体
'''
x = individual.gene[0:self.dna_length // 2 + 1] # 基因序列的前面一半代表x,后面一半代表y
y = individual.gene[self.dna_length // 2 + 1:]
x = self.calculate_variable(x) # 计算x,y对应的十进制数值
y = self.calculate_variable(y)
r = self.function.function(x, y) # 计算适应度
individual.fit = r
求得适应度之后进行排序
若要求计算最小值,则将所有个体按适应度升序排列;若要求计算最大值,则按适应度降序排列。排序之后,越前面的个体就是越符合要求的个体,此时只要选择前面一半个体作为新一代个体即可。
这种方法较为简单,并且可以保证最优秀的个体一定可以被保留下来。但是自然界中最优秀的个体却不一定能保留下来。若想模拟自然界的自然选择,就需要改进选择的方式。可以选择轮盘赌等方法。
def select_next_generation(self):
'''
选择合适的下一代
:return: 下一代
'''
for i in range(len(self.individuals)): # 计算所有个体的适应度,应注意此时个体数量已经是种群大小的两倍
self.set_fit(self.individuals[i])
self.individuals.sort(key=lambda individual: individual.fit, reverse=not self.function.find_min) # 按适应度排列
self.best_individual = self.individuals[0] # 排在越前的个体越符合要求
new_individuals = self.individuals[0:self.group_size] # 选择种群大小数量的个体
return new_individuals
将以上两个步骤链接起来,就可以得到获取下一代的方法
def get_next_generation(self):
'''
获取下一代
'''
next_generation = []
for i in range(self.group_size): # 每个个体与一个随机个体繁衍
father = self.individuals[i]
mother = self.individuals[random.randint(0, self.group_size - 1)]
child = self.get_child(father, mother)
next_generation.append(child)
self.individuals += next_generation # 此时的种群数量已经变为两倍
self.individuals = self.select_next_generation() # 选择出其中更符合要求的一半群体
输出每一代最优秀个体的信息
def print_best_individual_info(self):
'''
打印相关信息
'''
x = self.best_individual.gene[0:self.dna_length // 2 + 1]
y = self.best_individual.gene[self.dna_length // 2 + 1:]
x = self.calculate_variable(x)
y = self.calculate_variable(y)
print("x:", x, " y:", y, " 结果:", self.best_individual.fit)
将所有方法联合起来,提供一个对外接口
def start_evolution(self, generation):
'''
开始繁衍进化
:param generation: 要繁衍的代数
'''
for i in range(generation):
print("第", i + 1, "代最优个体", end="\t")
self.get_next_generation()
self.print_best_individual_info()
结果
开始测试
首先定义要求解的函数,作为参数构造一个函数类的对象,并设定最大值为5,最小值为-5,要求其最小值
然后定义一个种群,种群大小300,变异率0.05,交叉率0.8,个体的基因序列长度128,要求解的函数即为前一步创建的函数对象
注意序列长度如果太短反而会很快收敛到0,但这只是因为结果恰巧为0。如果是要求解最大值,那么基因序列太短精度就会非常差。
进行100代繁衍进化
def function(x, y): # 要求解的函数
return 20 + (x ** 2) + (y ** 2) - 10 * (math.cos(2 * math.pi * x) + math.cos(2 * math.pi * y))
if __name__ == '__main__':
func = Function.Function(function, 5, -5, find_min=True) # 求function的最小值,自变量范围-5~5
group = Group.Group(300, 0.05, 0.8, 128, func) # 基因长度取太短会很快收敛到0,但这是凑巧答案为0,如果改成求最大值精度就很低
group.start_evolution(100)
下面尝试求解最大值,并对比不同基因序列长度的精度
求最大值只需将find_min的值改为False即可
def function(x, y): # 要求解的函数
return 20 + (x ** 2) + (y ** 2) - 10 * (math.cos(2 * math.pi * x) + math.cos(2 * math.pi * y))
if __name__ == '__main__':
func = Function.Function(function, 5, -5, find_min=False) # 求function的最小值,自变量范围-5~5
group = Group.Group(300, 0.05, 0.8, 128, func) # 基因长度取太短会很快收敛到0,但这是凑巧答案为0,如果改成求最大值精度就很低
group.start_evolution(100)
序列长度为8
序列长度为16
序列长度为64
序列长度为128
完整代码
# Group.py
import random
import Individual
class Group:
def __init__(self, group_size, mutation_rate, crossover_rate, dna_length, function):
'''
构造函数
:param group_size: 种群大小
:param mutation_rate: 变异率
:param crossover_rate: 交叉率
:param dna_length: DNA序列长度,即基因长度
:param function: 要求解的函数
'''
self.group_size = group_size
self.mutation_rate = mutation_rate
self.crossover_rate = crossover_rate
self.function = function
self.individuals = [] # 所有个体
self.best_individual = None # 最优个体
self.dna_length = dna_length
self.init_group()
def generate_gene(self):
'''
生成一个基因序列
:return: 返回一个由0,1组成的序列
'''
gene = []
for i in range(self.dna_length):
gene.append(random.randint(0, 1))
return gene
def init_group(self):
'''
初始化种群的所有个体
'''
for i in range(self.group_size):
self.individuals.append(Individual.Individual(self.generate_gene()))
def crossover(self, father, mother):
'''
交叉
:param father: 父亲
:param mother: 母亲
:return: 孩子
'''
gene = [i for i in father.gene] # 首先默认孩子接收父亲的基因
child = Individual.Individual(gene) # 直接传入father.gene会导致更改child的基因时father的基因也会修改,种群基因变得不稳定
if self.crossover_rate > random.random(): # 判断是否发生交叉
cross_point = random.randint(1, self.dna_length - 1) # 交叉发生的位置
child.gene[cross_point:] = mother.gene[cross_point:] # 交叉位置后的基因变为母亲的基因
return child
def muter(self, individual):
'''
变异
:param individual: 可能变异的个体
:return: 变异之后的个体或原个体
'''
if self.mutation_rate > random.random(): # 判断是否变异
index = random.randint(0, self.dna_length - 1) # 变异的位置
individual.gene[index] = 1 - individual.gene[index] # 取反
return individual
def get_child(self, father, mother):
'''
产生一个孩子
:param father: 父亲
:param mother: 母亲
:return: 孩子
'''
child = self.crossover(father, mother)
child = self.muter(child)
return child
def calculate_variable(self, x):
'''
将0,1序列转换成自变量值域内的一个十进制数
:param x: 要转换的序列
:return: 转换后的十进制数
'''
r = 0
for i in range(len(x)): # 序列的每一位
r += ((2 ** i) * x[i]) # 乘以权重
r /= ((2 ** len(x)) - 1) # 变成0~1之间的小数,相当于一个比例系数
r *= (self.function.max_variable - self.function.min_variable) + self.function.min_variable # 控制在函数自变量值域
return r
def set_fit(self, individual):
'''
求出适应度
:param individual: 要求的个体
'''
x = individual.gene[0:self.dna_length // 2 + 1] # 基因序列的前面一半代表x,后面一半代表y
y = individual.gene[self.dna_length // 2 + 1:]
x = self.calculate_variable(x) # 计算x,y对应的十进制数值
y = self.calculate_variable(y)
r = self.function.function(x, y) # 计算适应度
individual.fit = r
def select_next_generation(self):
'''
选择合适的下一代
:return: 下一代
'''
for i in range(len(self.individuals)): # 计算所有个体的适应度,应注意此时个体数量已经是种群大小的两倍
self.set_fit(self.individuals[i])
self.individuals.sort(key=lambda individual: individual.fit, reverse=not self.function.find_min) # 按适应度排列
self.best_individual = self.individuals[0] # 排在越前的个体越符合要求
new_individuals = self.individuals[0:self.group_size] # 选择种群大小数量的个体
return new_individuals
def get_next_generation(self):
'''
获取下一代
'''
next_generation = []
for i in range(self.group_size): # 每个个体与一个随机个体繁衍
father = self.individuals[i]
mother = self.individuals[random.randint(0, self.group_size - 1)]
child = self.get_child(father, mother)
next_generation.append(child)
self.individuals += next_generation # 此时的种群数量已经变为两倍
self.individuals = self.select_next_generation() # 选择出其中更符合要求的一半群体
def print_best_individual_info(self):
'''
打印相关信息
'''
x = self.best_individual.gene[0:self.dna_length // 2 + 1]
y = self.best_individual.gene[self.dna_length // 2 + 1:]
x = self.calculate_variable(x)
y = self.calculate_variable(y)
print("x:", x, " y:", y, " 结果:", self.best_individual.fit)
def start_evolution(self, generation):
'''
开始繁衍进化
:param generation: 要繁衍的代数
'''
for i in range(generation):
print("第", i + 1, "代最优个体", end="\t")
self.get_next_generation()
self.print_best_individual_info()
# Individual.py
class Individual:
def __init__(self, gene):
'''
构造函数
:param gene: 基因序列
'''
self.gene = gene
self.fit = 0 # 适应度
# Function.py
class Function:
def __init__(self, function, max_variable, min_variable, find_min=True):
'''
构造函数
:param function: 要求解的函数
:param max_variable: 函数自变量的最大值
:param min_variable: 函数自变量的最小值
:param find_min: 求最小值,默认为True,False即为求最大值
'''
self.function = function
self.max_variable = max_variable
self.min_variable = min_variable
self.find_min = find_min
# main.py
import Group
import Function
import math
def function(x, y): # 要求解的函数
return 20 + (x ** 2) + (y ** 2) - 10 * (math.cos(2 * math.pi * x) + math.cos(2 * math.pi * y))
if __name__ == '__main__':
func = Function.Function(function, 5, -5, find_min=True) # 求function的最小值,自变量范围-5~5
group = Group.Group(300, 0.05, 0.8, 128, func) # 基因长度取太短会很快收敛到0,但这是凑巧答案为0,如果改成求最大值精度就很低
group.start_evolution(100)
参考资料
https://blog.csdn/weixin_43666945/article/details/90287775
https://blog.csdn/ha_ha_ha233/article/details/91364937
更多推荐
人工智能 遗传算法
发布评论