【数学建模学习笔记】启发式算法:遗传算法 - 详解
遗传算法(GA):零基础小白入门指南
一、什么是遗传算法?
简单说,遗传算法是一种模拟生物进化过程的计算机算法。它借鉴了自然界中 "物竞天择、适者生存" 的规律,通过模拟 "选择、交叉、变异" 这三个生物遗传过程,来解决复杂的优化问题(比如找一个函数的最小值或最大值)。
举个例子:假设我们要找一片山地中最低的山谷,遗传算法就像派一群人同时在不同位置探索,让 "站得越低(适应度越好)" 的人留下更多后代,后代还会继承父母的位置信息并偶尔随机变一点,最终逐渐逼近最低点。
二、遗传算法的核心步骤与代码实现
下面结合具体代码,一步步拆解遗传算法的工作流程(建议边看代码边理解逻辑):
1. 准备工具库
首先需要导入数值计算、随机数生成和绘图的工具:
import numpy as np # 用于数值计算(比如生成随机数、数组运算)
import random # 用于生成随机概率(比如变异时的随机判断)
import matplotlib.pyplot as plt # 用于绘制结果图表
2. 定义目标函数(我们要优化的问题)
遗传算法的目标是找到让某个函数(目标函数)取最小值(或最大值)的变量值。这里以一个三变量函数为例:
def fitness(x):
"""
x是一个列表,代表变量[x1, x2, x3]
目标函数:f(x) = -10*x1 - e^(-x2/10 - x3)
我们要找到让这个函数值最小的x1、x2、x3
"""
x1, x2, x3 = x # 从列表中取出三个变量
return -10 * x1 - np.exp(-x2/10 - x3) # 计算函数值
- 比如
x1=0.5, x2=10, x3=20时,函数值为-10*0.5 - e^(-10/10 -20) ≈ -5,我们要找到比这更小的值。
3. 初始化种群(第一批 "探索者")
种群是一群可能的解(比如 50 个探索者),每个探索者的位置由变量值表示,且变量值被限制在一定范围内:
def initialize_population(pop_size, var_ranges):
"""
pop_size:种群规模(有多少个探索者)
var_ranges:每个变量的范围,格式为[(x1_min, x1_max), (x2_min, x2_max), ...]
返回:一个包含pop_size个探索者的种群(二维数组)
"""
num_vars = len(var_ranges) # 变量数量(这里是3个:x1, x2, x3)
population = []
for _ in range(pop_size):
# 为每个变量随机生成一个在范围内的值
individual = [random.uniform(min_val, max_val) for (min_val, max_val) in var_ranges]
population.append(individual)
return np.array(population) # 转为数组方便后续计算
- 例如变量范围为
x1∈[0,1], x2∈[1,80], x3∈[0,120],则初始化的种群可能是[[0.3, 20, 50], [0.8, 10, 30], ...](50 个类似的列表)。
4. 选择操作(让优秀个体留下后代)
适应度好(目标函数值小)的探索者更有可能繁殖后代,就像自然界中更适应环境的生物更容易存活:
def selection(population, fitness_values, num_parents):
"""
population:当前种群
fitness_values:每个探索者的适应度值(目标函数值)
num_parents:要选出的优秀个体数量
返回:选中的优秀个体(父母)
"""
parents = []
# 复制适应度值,避免修改原数组
fitness_copy = fitness_values.copy()
for _ in range(num_parents):
# 找到适应度最小(最优)的个体索引
best_idx = np.argmin(fitness_copy)
# 选中这个个体作为父母
parents.append(population[best_idx])
# 把选中的个体适应度设为很大的值,避免重复选中
fitness_copy[best_idx] = float('inf')
return np.array(parents)
- 比如从 50 个探索者中选 25 个适应度最好的作为父母,让它们产生后代。
5. 交叉操作(父母结合产生后代)
父母的变量信息会交叉组合,生成既继承父母优点又有新特征的后代:
def crossover(parents, offspring_size):
"""
parents:选中的父母
offspring_size:要生成的后代数量
返回:交叉后的后代
"""
offspring = []
num_vars = parents.shape[1] # 变量数量(3个)
for i in range(offspring_size):
# 随机选两个不同的父母
parent1 = parents[random.randint(0, len(parents)-1)]
parent2 = parents[random.randint(0, len(parents)-1)]
# 交叉点:比如前1个变量来自父1,后2个来自父2
crossover_point = random.randint(1, num_vars-1)
child = np.concatenate([parent1[:crossover_point], parent2[crossover_point:]])
offspring.append(child)
return np.array(offspring)
- 例如父亲是
[0.9, 15, 60],母亲是[0.1, 30, 40],交叉点为 1,则后代可能是[0.9, 30, 40](继承父亲的 x1,母亲的 x2 和 x3)。
6. 变异操作(后代随机产生微小变化)
后代的变量值会以一定概率随机变化(类似基因突变),避免所有探索者都聚集在同一个区域,错过更好的解:
def mutation(offspring, var_ranges, mutation_rate=0.1):
"""
offspring:交叉后的后代
var_ranges:变量范围(用于限制变异后的取值)
mutation_rate:变异概率(每个变量有10%的概率变异)
返回:变异后的后代
"""
for i in range(len(offspring)): # 遍历每个后代
for j in range(len(offspring[i])): # 遍历每个变量
# 以mutation_rate的概率发生变异
if random.random() < mutation_rate:
# 变异后的值在变量范围内随机生成
min_val, max_val = var_ranges[j]
offspring[i][j] = random.uniform(min_val, max_val)
return offspring
- 比如后代原本是
[0.9, 30, 40],x2 有 10% 的概率变异为 25(仍在 [1,80] 范围内),变成[0.9, 25, 40]。
7. 算法主流程(重复迭代,不断进化)
将上述步骤组合,重复多代(比如 100 代),每代都更新种群并记录最优解:
def genetic_algorithm(fitness_func, var_ranges, pop_size=50, num_generations=100, mutation_rate=0.1):
"""
fitness_func:目标函数
var_ranges:变量范围
pop_size:种群规模
num_generations:迭代次数(进化多少代)
mutation_rate:变异概率
返回:最优解和对应的目标函数值
"""
# 初始化种群
population = initialize_population(pop_size, var_ranges)
best_scores = [] # 记录每一代的最优目标函数值
for generation in range(num_generations):
# 计算每个个体的适应度(目标函数值)
fitness_values = np.array([fitness_func(ind) for ind in population])
# 记录当前代的最优值
best_score = np.min(fitness_values)
best_scores.append(best_score)
print(f"第{generation+1}代,当前最优值:{best_score:.2f}")
# 选择父母(选种群一半的个体作为父母)
num_parents = pop_size // 2
parents = selection(population, fitness_values, num_parents)
# 交叉产生后代(后代数量 = 种群规模 - 父母数量)
offspring_size = pop_size - num_parents
offspring_crossover = crossover(parents, offspring_size)
# 后代变异
offspring_mutation = mutation(offspring_crossover, var_ranges, mutation_rate)
# 更新种群:父母 + 变异后的后代
population = np.concatenate([parents, offspring_mutation])
# 绘制进化过程(每代最优值变化)
plt.plot(best_scores)
plt.xlabel("进化代数")
plt.ylabel("最优目标函数值")
plt.title("遗传算法进化过程")
plt.show()
# 返回最终找到的最优解
final_fitness = np.array([fitness_func(ind) for ind in population])
best_idx = np.argmin(final_fitness)
best_solution = population[best_idx]
best_value = final_fitness[best_idx]
return best_solution, best_value
8. 运行算法,查看结果
定义变量范围并调用主函数,即可得到最优解:
if __name__ == "__main__":
# 定义变量范围:x1∈[0,1],x2∈[1,80],x3∈[0,120]
var_ranges = [(0, 1), (1, 80), (0, 120)]
# 运行算法
best_solution, best_value = genetic_algorithm(
fitness_func=fitness,
var_ranges=var_ranges,
pop_size=50, # 50个探索者
num_generations=100, # 进化100代
mutation_rate=0.1 # 10%的变异概率
)
# 输出结果
print("\n找到的最优解:")
print(f"x1 = {best_solution[0]:.4f}, x2 = {best_solution[1]:.4f}, x3 = {best_solution[2]:.4f}")
print(f"对应的目标函数最小值:{best_value:.4f}")
三、代码运行结果解读
- 输出示例:程序会打印每一代的最优值,最后输出类似
x1=0.98, x2=15.2, x3=60.5,对应的目标函数值约为-9.8(比初始的-5更小,说明找到了更优解)。 - 收敛曲线:绘制的图表展示每代最优值的变化,整体呈下降趋势(逐渐逼近最小值),中间的波动是因为变异带来的随机性。
四、总结
- 核心逻辑:遗传算法通过 "初始化种群→选优繁殖→交叉变异→迭代进化" 的过程,模拟生物进化找到最优解,不需要提前知道问题的复杂规律。
- 优点:能同时探索多个解,不容易陷入局部最优(比如误把小土坑当最低点),适合变量多、关系复杂的问题。
- 应用场景:工程设计(优化零件尺寸)、路径规划(找最短路线)、机器学习(调参)等。
- 关键参数:种群越大探索范围越广但计算越慢;迭代次数越多越可能找到好解;变异率太小容易停滞,太大则太混乱(通常设 0.01-0.2)。
通过这段代码,即使是零基础也能直观理解遗传算法的工作原理 ——让好的解留下,通过遗传和突变不断进化,最终逼近最优解。
浙公网安备 33010602011771号