1. 从“物竞天择”到代码:进化算法的核心思想
想象一下,你是一个游戏设计师,需要设计一个能在复杂迷宫里最快找到出口的虚拟机器人。迷宫地形未知,规则复杂,用传统的“如果-那么”逻辑去编程,几乎是个不可能完成的任务。这时候,你会怎么办?一个绝妙的思路是向大自然学习:我们不需要教机器人每一步该怎么走,而是创造一群“机器人”,让它们去尝试,把走得远的“优秀个体”留下来,让它们“生”出下一代,并在过程中加入一些随机变化。一代代下去,这群机器人就会越来越擅长走迷宫。这就是进化算法的核心魅力——让计算机自己通过“进化”来寻找答案。
遗传算法和进化策略,正是这个“向自然学习”的大家族里,最著名、也最实用的两位成员。它们都遵循“初始化种群 -> 评估 -> 选择 -> 繁殖(交叉/变异) -> 迭代”的基本流程,但具体怎么“编码”一个解决方案,怎么进行“交叉”和“变异”,两者有着根本性的不同。这就像同样是造车,一个团队用乐高积木(离散的模块)来拼装,另一个团队则用陶土(连续的材料)来塑形,工具和方法论自然大相径庭。
我刚开始接触这个领域时,常常把两者搞混。后来在几个实际项目里踩过坑才发现,用错了算法,优化过程要么停滞不前,要么效率极低。比如,当你需要调整神经网络的超参数(像学习率、层数这种连续值)时,用遗传算法那种“翻牌子”(0/1切换)的方式就特别别扭;反过来,如果要设计一个电路板的布线方案(很多开关是开或关的离散状态),用进化策略处理连续值的那套方法就使不上劲。所以,理解它们底层的设计哲学和实现细节,是你能在正确场景挥舞正确武器的前提。
简单来说,你可以这样理解它们的“人设”:
- 遗传算法:更像一位组合优化大师。它把问题解拆成一段“基因序列”(通常用0和1的二进制串表示),通过交换和翻转这些基因片段来探索解空间。它擅长处理离散的、组合类的问题,比如排班、路径规划、特征选择。
- 进化策略:更像一位精密调参工程师。它直接操作实数向量,认为解空间是连续的。它的核心黑科技是自适应变异强度——不仅告诉你参数该往哪调,还告诉你每次调整的“步长”应该多大。它天生适合处理连续的、数值优化的问题,比如机器人控制、连续函数求极值、训练某些神经网络。
下面,我们就深入这两位的“工作室”,看看它们到底是怎么工作的。
2. 遗传算法:像拼乐高一样构建解决方案
2.1 核心四步:选择、交叉、变异的交响乐
遗传算法的工作流程非常直观,就像管理一个不断进化的部落。假设我们要为一个外卖平台优化配送路线,目标是总里程最短。
第一步:编码与初始化——创造原始部落。 我们首先得把“一条配送路线”这个抽象概念,变成计算机能处理的“基因”。最常用的就是二进制编码。比如,有8个配送点,我们可以用3位二进制数给每个点一个编号(000, 001, 010...111),一条路线就是这些编号的一个排列。随机生成一堆这样的排列,比如50个,这就是我们的初始种群,也就是原始部落。
import numpy as np
# 假设有5个配送点,用0-4表示,我们随机生成10条路线(种群规模=10)
num_points = 5
pop_size = 10
# 每条路线是0-4的一个随机排列
initial_population = [np.random.permutation(num_points) for _ in range(pop_size)]
print("初始种群(前3个个体):")
for i in range(3):
print(f" 个体{i}: {initial_population[i]}")
第二步:适应度评估——谁是优秀骑手? 我们需要一个标准来评判每条路线的优劣,这就是适应度函数。在这个例子里,适应度就是路线总距离的倒数(距离越短,适应度越高)。计算每个个体的适应度,相当于给每个外卖骑手打分。
第三步:选择——举办“精英选拔赛”。 根据适应度分数,我们要选出优秀的“父母”来产生后代。最常用的方法是轮盘赌选择。想象一个转盘,每个个体占据的扇形面积与其适应度成正比。适应度高的个体,面积大,被选中的概率就高。这保证了优秀基因有更大机会传承,同时又给了一些“平庸”个体机会,保持了种群的多样性,避免过早陷入局部最优。
def roulette_wheel_selection(population, fitness_values):
"""轮盘赌选择"""
total_fitness = sum(fitness_values)
# 计算每个个体的选择概率
probs = [f / total_fitness for f in fitness_values]
# 根据概率随机选择两个父母索引
parent_indices = np.random.choice(len(population), size=2, p=probs, replace=False)
return population[parent_indices[0]], population[parent_indices[1]]
第四步:交叉与变异——创造“混血”与“天才”。 这是产生新个体的关键。
- 交叉:模拟基因重组。从选出的两个父代路线中,随机截取一段,交换给对方,生成两条子代路线。例如,父代A是[0,1,2,3,4],父代B是[4,3,2,1,0],随机选择中间3个位置交换,可能得到子代[0,3,2,1,4]和[4,1,2,3,0]。
- 变异:模拟基因突变。在子代路线中,以很小的概率随机交换两个配送点的位置,例如[0,1,2,3,4]可能突变成[0,3,2,1,4]。变异提供了随机探索的能力,是跳出局部最优的关键。
这个过程反复迭代,每一代都评估、选择、繁殖。你会发现,种群的平均适应度会逐渐上升,最优个体的路线会越来越合理。我最早用GA解决一个简单的调度问题时,看着打印出来的每一代最优解慢慢变好,那种感觉就像在培育一个生命,非常神奇。
2.2 实战陷阱与调参心得
纸上谈兵总是容易,真正把GA用起来,你会发现几个常见的“坑”。
第一个坑:编码方式决定上限。 二进制编码不是万能的。对于像旅行商问题这样的排列问题,直接排列编码比二进制编码更自然、高效。选择编码方式时,一定要让它贴近问题的本质结构。
第二个坑:参数设置是门艺术。 种群大小、交叉概率、变异概率,这几个参数对结果影响巨大。
- 种群大小:太小,多样性不足,容易早熟;太大,计算开销剧增。我的经验是,对于中等复杂度问题,从50-200开始尝试。
- 交叉概率:通常设置较高(0.6-0.9),因为它是产生新个体的主要动力。
- 变异概率:必须设置得很低(0.001-0.05),它是个“微调”和“探索”机制,概率太高会破坏已有好解,让算法变成随机搜索。
第三个坑:早熟收敛。</


3659

被折叠的 条评论
为什么被折叠?



