【数模修炼之旅】10 遗传算法 深度解析(教程+代码)
接下来 C君将会用至少30个小节来为大家深度解析数模领域常用的算法,大家可以关注这个专栏,持续学习哦,对于大家的能力提高会有极大的帮助。
1 遗传算法介绍及应用
1.1 遗传算法介绍
遗传算法通过模拟自然界的遗传机制(如选择、交叉、变异等)来优化问题的解。它从一组随机生成的初始解(种群)开始,通过迭代的方式不断优化种群中的个体,以期望找到问题的最优解或近似最优解

核心要素:
-  
  
- 编码:将问题的解表示为某种形式的字符串(如二进制串),称为染色体或基因型。
 - 初始种群:随机生成一组初始解作为种群的开始。
 - 适应度函数:用于评估种群中每个个体的优劣,通常与目标函数相关。
 - 选择操作:根据适应度函数选择优秀的个体作为父代,用于生成下一代。
 - 交叉操作:通过交叉父代的染色体来生成新的子代个体。
 - 变异操作:以一定的概率对子代个体的染色体进行随机变异,以增加种群的多样性。
 
 - 特点有:
 - 直接对结构对象进行操作,不存在求导和函数连续性的限定。
 - 具有内在的隐并行性和更好的全局寻优能力。
 - 采用概率化的寻优方法,不需要确定的规则就能自动获取和指导优化的搜索空间。
 
1.2 遗传算法在数模中的应用
- 组合优化问题:
 - 如旅行商问题(TSP)、背包问题、调度问题等。遗传算法可以通过编码解空间中的不同组合,并通过选择、交叉和变异操作来寻找最优解或近似最优解。
 - 函数优化问题:
 - 包括求解函数的最大值、最小值等。遗传算法可以直接对函数的自变量进行编码,并通过不断迭代优化来逼近最优解。
 - 参数优化问题:
 - 在机器学习、神经网络等领域中,经常需要优化模型的参数以提高性能。遗传算法可以通过对模型参数进行编码,并利用适应度函数来评估不同参数组合的效果,从而找到最优的参数组合。
 - 多目标优化问题:
 - 在许多实际问题中,往往需要同时优化多个目标。遗传算法可以通过引入多目标进化算法(如NSGA-II)来处理这类问题,通过比较不同解之间的Pareto优劣关系来指导搜索过程。
 
2 遗传算法的基本步骤
它通过模拟自然界的进化过程,如选择、交叉(也称杂交)、变异等,来寻找问题的最优解。遗传算法的基本步骤可以详细归纳如下:




3 遗传算法代码(matlab+python)
3.1 python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
DNA_SIZE = 24
POP_SIZE = 200
CROSSOVER_RATE = 0.8
MUTATION_RATE = 0.005
N_GENERATIONS = 50
X_BOUND = [-3, 3]
Y_BOUND = [-3, 3]
def F(x, y):
	return 3*(1-x)**2*np.exp(-(x**2)-(y+1)**2)- 10*(x/5 - x**3 - y**5)*np.exp(-x**2-y**2)- 1/3**np.exp(-(x+1)**2 - y**2)
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_zlim(-10,10)
	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 - np.min(pred)) + 1e-3 #减去最小的适应度是为了防止适应度出现负数,通过这一步fitness的范围为[0, np.max(pred)-np.min(pred)],最后在加上一个很小的数防止出现为0的适应度
def translateDNA(pop): #pop表示种群矩阵,一行表示一个二进制编码表示的DNA,矩阵的行数为种群数目
	x_pop = pop[:,1::2]#奇数列表示X
	y_pop = pop[:,::2] #偶数列表示y
	
	#pop:(POP_SIZE,DNA_SIZE)*(DNA_SIZE,1) --> (POP_SIZE,1)
	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]))
if __name__ == "__main__":
	fig = plt.figure()
	ax = 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))
		#F_values = F(translateDNA(pop)[0], translateDNA(pop)[1])#x, y --> Z matrix
		fitness = get_fitness(pop)
		pop = select(pop, fitness) #选择生成新的种群
	
	print_info(pop)
	plt.ioff()
	plot_3d(ax)
 
3.2 matlab
使用MATLAB内置的遗传算法函数,如ga(全局优化工具箱中的函数)。以下是一个简单的MATLAB代码示例,展示了如何使用ga函数来解决一个优化问题。

然后,编写:
function genetic_algorithm_example  
    % 定义目标函数(注意:ga默认是最小化问题,所以我们需要取反来最大化)  
    fitnessFunction = @(x) -(-sin(x(1)) - cos(x(2)));  % 取反来最大化  
  
    % 定义变量的边界  
    nvars = 2;  % 变量数量  
    LB = -pi * ones(nvars, 1);  % 下界  
    UB = pi * ones(nvars, 1);   % 上界  
  
    % 遗传算法选项  
    options = optimoptions('ga', 'PlotFcn', @gaplotbestf, ...  
                           'MaxGenerations', 100, ...  
                           'PopulationSize', 100, ...  
                           'Display', 'iter');  
  
    % 运行遗传算法  
    [x, fval] = ga(fitnessFunction, nvars, [], [], [], [], LB, UB, [], options);  
  
    % 输出结果(注意:因为我们在目标函数中取了反,所以这里要再次取反)  
    fprintf('最优解: x = %.4f, y = %.4f\n', x(1), x(2));  
    fprintf('最大函数值: %f\n', -fval);  % 取反回真实最大值  
end 
 
在这个例子中,fitnessFunction 是我们要优化的目标函数,但因为我们想要最大化这个函数,而ga默认是最小化问题,所以我们通过取反(-(-sin(x(1)) - cos(x(2))))来转换问题。
nvars 指定了变量的数量,这里是2(x和y)。
LB 和 UB 分别定义了变量的下界和上界。
options 是一个结构体,用于设置遗传算法的选项,如绘图函数、最大代数、种群大小等。
最后,ga 函数被调用,并传入目标函数、变量数量、边界和选项。它返回最优解 x 和对应的目标函数值 fval(注意这里已经是取反后的值,所以我们需要再次取反以得到真实的最大值)。
需要参加数模竞赛的同学,可以看下面的名片,会有最新的助攻哦:(大型比赛前会对名片进行更新)


















