every blog every motto: You can do more than you think.
遗传算法是一种基于自然选择和遗传机制的优化算法,因此它通常被用于求解各种最优化问题,例如函数优化、特征选择、图像处理等。
一言以蔽之: 将数学中的优化问题,首先通过“编码”将数字变成“0101”类似这种二进制形式(不绝对),然后对其进行变换(“变异”),根据提前指定的“目标函数”(适应度)对这组数学进行筛选,重复这个过程一定次数(“迭代进化”),最终找到最优解
遗传算法(Genetic Algorithm,简称GA)受自然进化理论启发的一系列搜索算法,起源于对生物系统所进行的计算机模拟研究,是一种随机全局搜索优化方法,它模拟了自然选择和遗传中发生的复制、交叉(crossover)和变异(mutation)等现象,从任一初始种群(Population)出发,通过随机选择、交叉和变异操作,产生一群更适合环境的个体,使群体进化到搜索空间中越来越好的区域,这样一代一代不断繁衍进化,最后收敛到一群最适应环境的个体(Individual),从而求得问题的优质解。
达尔文进化论原理概括为:
群体规模将影响遗传优化的最终结果以及遗传算法的执行效率。当群体规模 NP太小时,遗传优化性能一般不会太好。采用较大的群体规模可以减小遗传算法陷入局部最优解的机会,但较大的群体规模意味着计算复杂度较高。一般 NP 取 10~20010~200。
交叉概率 P c P_c Pc?控制着交叉操作被使用的频度。较大的交叉概率可以增强遗传算法开辟新的搜索区域的能力,但高性能的模式遭到破坏的可能性增大;若交叉概率太低,遗传算法搜索可能陷入迟钝状态。一般 P c P_c Pc?取 0.25~1.000.25~1.00。
变异在遗传算法中属于辅助性的搜索操作,它的主要目的是保持群体的多样性。一般低频度的变异可防止群体中重要基因的可能丢失,高频度的变异将使遗传算法趋于纯粹的随机搜索。通常 P m P_m Pm?取 0.001~0.10.001~0.1。
终止进化代数 G是表示遗传算法运行结束条件的一个参数,它表示遗传算法运行到指定的进化代数之后就停止运行,并将当前群体中的最佳个体作为所求问题的最优解输出。一般视具体问题而定,G的取值可在 100~1000100~1000 之间。
在计算中处理的数据,而对于具体的遗传算法 需要将常见的数据转换成“基因”,即0101这种形式,常见的编码方式有:
遗传算法染色体向问题解的转换
适应度函数表明个体或解的优劣性。对于不同的问题,适应度函数的定义方式不同。根据具体问题,计算群体P(t)中各个个体的适应度。
适应度尺度变换: 一般来讲,是指算法迭代的不同阶段,能够通过适当改变个体的适应度大小,进而避免群体间适应度相当而造成的竞争减弱,导致种群收敛于局部最优解。包括:
包括以下三种遗传算子:
以下是一个简单的遗传算法的 Python 代码示例,用于解决旅行商问题(Traveling Salesman Problem)
import random
# 定义城市列表和距离矩阵
city_list = ['A', 'B', 'C', 'D', 'E']
distance_matrix = {
'A': {'A': 0, 'B': 2, 'C': 9, 'D': 10, 'E': 5},
'B': {'A': 1, 'B': 0, 'C': 6, 'D': 4, 'E': 8},
'C': {'A': 9, 'B': 6, 'C': 0, 'D': 3, 'E': 7},
'D': {'A': 10, 'B': 4, 'C': 3, 'D': 0, 'E': 6},
'E': {'A': 5, 'B': 8, 'C': 7, 'D': 6, 'E': 0}
}
# 遗传算法参数设置
population_size = 50
elite_size = 10
mutation_rate = 0.01
generations = 100
# 创建一个随机个体(路径)
def create_individual(city_list):
individual = random.sample(city_list, len(city_list))
return individual
# 创建初始种群
def create_population(city_list, population_size):
population = []
for _ in range(population_size):
individual = create_individual(city_list)
population.append(individual)
return population
# 计算路径的总距离
def calculate_fitness(individual):
total_distance = 0
for i in range(len(individual)-1):
city1 = individual[i]
city2 = individual[i+1]
total_distance += distance_matrix[city1][city2]
return total_distance
# 选择精英个体
def select_elite(population, elite_size):
population_fitness = [(individual, calculate_fitness(individual)) for individual in population]
population_fitness = sorted(population_fitness, key=lambda x: x[1])
elite = [individual for individual, _ in population_fitness[:elite_size]]
return elite
# 进行交叉操作
def crossover(parent1, parent2):
child = [None] * len(parent1)
geneA = random.randint(0, len(parent1)-1)
geneB = random.randint(0, len(parent1)-1)
start_gene = min(geneA, geneB)
end_gene = max(geneA, geneB)
for i in range(start_gene, end_gene+1):
child[i] = parent1[i]
for i in range(len(parent2)):
if parent2[i] not in child:
for j in range(len(child)):
if child[j] is None:
child[j] = parent2[i]
break
return child
# 进行变异操作
def mutate(individual):
for i in range(len(individual)):
if random.random() < mutation_rate:
j = random.randint(0, len(individual)-1)
individual[i], individual[j] = individual[j], individual[i]
return individual
# 进化函数
def evolve_population(population, elite_size):
elite = select_elite(population, elite_size)
population_size = len(population)
children = []
while len(children) < population_size:
parent1 = random.choice(elite)
parent2 = random.choice(elite)
child = crossover(parent1, parent2)
child = mutate(child)
children.append(child)
return children
# 主函数
def main():
population = create_population(city_list, population_size)
for i in range(generations):
population = evolve_population(population, elite_size)
best_individual = min(population, key=calculate_fitness)
best_distance = calculate_fitness(best_individual)
print("最佳路径:", best_individual)
print("最短距离:", best_distance)
if __name__ == '__main__':
main()
以下是求 f = x 2 f=x^2 f=x2的极值问题,设自变量x介于0~31之间,求二次函数的最大值
步骤:
(1)编码 遗传算法首先要对实际问题进行编码,用字符串表达问题。这种字符串相当于遗传学中的染色体。每一代所产生的字符串个体总和称为群体。为了实现的方便,通常字符串长度固定,字符选0或1。 本例中,利用5位二进制数表示x值,采用随机产生的方法,假设得出拥有四个个体的初始群体,即:01101,11000,01000,10011。x值相应为13,24,8,19。
(2)计算适应度 衡量字符串(染色体)好坏的指标是适应度,它也就是遗传算法的目标函数。本例中用 x 2 x^2 x2计算。
(3)复制 根据相对适应度的大小对个体进行取舍,2号个体性能最优,予以复制繁殖。3号个体性能最差,将它删除,使之死亡,表中的M表示传递给下一代的个体数目,其中2号个体占2个,3号个体为0,1号、4号个体保持为1个。这样,就产生了下一代群体
复制后产生的新一代群体的平均适应度明显增加,由原来的293增加到421 (4)交换 利用随机配对的方法,决定1号和2号个体、3号和4号个体分别交换,如表中第5列。再利用随机定位的方法,确定这两对母体交叉换位的位置分别从字符长度的第4位及第3位开始。如:3号、4号个体从字符长度第3位开始交换。交换开始的位置称交换点
(5)突变 将个体字符串某位符号进行逆变,即由1变为0或由0变为1。例如,下式左侧的个体于第3位突变,得到新个体如右侧所示。
遗传算法中,个体是否进行突变以及在哪个部位突变,都由事先给定的概率决定。通常,突变概率很小,本例的第一代中就没有发生突变。
上述(2)~(5)反复执行,直至得出满意的最优解。
综上可以看出,遗传算法参考生物中有关进化与遗传的过程,利用复制、交换、突变等操作,不断循环执行,逐渐逼近全局最优解。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
DNA_SIZE = 24
POP_SIZE = 80
CROSSOVER_RATE = 0.6
MUTATION_RATE = 0.01
N_GENERATIONS = 100
X_BOUND = [-2.048, 2.048]
Y_BOUND = [-2.048, 2.048]
def F(x, y):
return 100.0 * (y - x ** 2.0) ** 2.0 + (1 - x) ** 2.0 # 以香蕉函数为例
def plot_3d(ax):
X = np.linspace(*X_BOUND, 100)
Y = np.linspace(*Y_BOUND, 100)
X, Y = np.meshgrid(X, Y)
Z = F(X, Y)
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.pause(3)
plt.show()
def get_fitness(pop):
x, y = translateDNA(pop)
pred = F(x, y)
return pred
# return pred - np.min(pred)+1e-3 # 求最大值时的适应度
# return np.max(pred) - pred + 1e-3 # 求最小值时的适应度,通过这一步fitness的范围为[0, np.max(pred)-np.min(pred)]
def translateDNA(pop): # pop表示种群矩阵,一行表示一个二进制编码表示的DNA,矩阵的行数为种群数目
x_pop = pop[:, 0:DNA_SIZE] # 前DNA_SIZE位表示X
y_pop = pop[:, DNA_SIZE:] # 后DNA_SIZE位表示Y
x = x_pop.dot(2 ** np.arange(DNA_SIZE)[::-1]) / float(2 ** DNA_SIZE - 1) * (X_BOUND[1] - X_BOUND[0]) + X_BOUND[0]
y = y_pop.dot(2 ** np.arange(DNA_SIZE)[::-1]) / float(2 ** DNA_SIZE - 1) * (Y_BOUND[1] - Y_BOUND[0]) + Y_BOUND[0]
return x, y
def crossover_and_mutation(pop, CROSSOVER_RATE=0.8):
new_pop = []
for father in pop: # 遍历种群中的每一个个体,将该个体作为父亲
child = father # 孩子先得到父亲的全部基因(这里我把一串二进制串的那些0,1称为基因)
if np.random.rand() < CROSSOVER_RATE: # 产生子代时不是必然发生交叉,而是以一定的概率发生交叉
mother = pop[np.random.randint(POP_SIZE)] # 再种群中选择另一个个体,并将该个体作为母亲
cross_points = np.random.randint(low=0, high=DNA_SIZE * 2) # 随机产生交叉的点
child[cross_points:] = mother[cross_points:] # 孩子得到位于交叉点后的母亲的基因
mutation(child) # 每个后代有一定的机率发生变异
new_pop.append(child)
return new_pop
def mutation(child, MUTATION_RATE=0.003):
if np.random.rand() < MUTATION_RATE: # 以MUTATION_RATE的概率进行变异
mutate_point = np.random.randint(0, DNA_SIZE) # 随机产生一个实数,代表要变异基因的位置
child[mutate_point] = child[mutate_point] ^ 1 # 将变异点的二进制为反转
def select(pop, fitness): # nature selection wrt pop's fitness
idx = np.random.choice(np.arange(POP_SIZE), size=POP_SIZE, replace=True,
p=(fitness) / (fitness.sum()))
return pop[idx]
def print_info(pop):
fitness = get_fitness(pop)
max_fitness_index = np.argmax(fitness)
print("max_fitness:", fitness[max_fitness_index])
x, y = translateDNA(pop)
print("最优的基因型:", pop[max_fitness_index])
print("(x, y):", (x[max_fitness_index], y[max_fitness_index]))
print(F(x[max_fitness_index], y[max_fitness_index]))
if __name__ == "__main__":
fig = plt.figure()
# ax = Axes3D(fig)
ax = fig.add_axes(Axes3D(fig))
plt.ion() # 将画图模式改为交互模式,程序遇到plt.show不会暂停,而是继续执行
plot_3d(ax)
pop = np.random.randint(2, size=(POP_SIZE, DNA_SIZE * 2)) # matrix (POP_SIZE, DNA_SIZE)
for _ in range(N_GENERATIONS): # 迭代N代
x, y = translateDNA(pop)
if 'sca' in locals():
sca.remove()
sca = ax.scatter(x, y, F(x, y), c='black', marker='o')
plt.show()
plt.pause(0.1)
pop = np.array(crossover_and_mutation(pop, CROSSOVER_RATE))
fitness = get_fitness(pop)
pop = select(pop, fitness) # 选择生成新的种群
print_info(pop)
plt.ioff()
plot_3d(ax)
具体过程是一个动图,如下仅截几张作为示例:
[1] https://zhuanlan.zhihu.com/p/378906456
[2] https://blog.csdn.net/liuxin0108/article/details/115923169
[3] https://zhuanlan.zhihu.com/p/100337680