3. 进化策略的算法流程:从初始化到终止

进化策略(Evolution Strategies, ES)这个算法,说白了就是模拟自然界那套「优胜劣汰」的逻辑。我刚开始接触它的时候,觉得这东西挺玄乎的——不就是随机生成一堆解,然后挑好的吗?后来在项目中跑过几次才发现,里面的门道比想象中深得多。

今天咱们就把这六个核心步骤掰开揉碎了讲。你跟着我的思路走一遍,保证能上手。

3.1 初始化种群:第一代「种子」怎么撒?

进化策略的第一步,就是生成初始种群。这就像种地之前先撒种子——种子撒得好不好,直接决定收成。

具体怎么做?

  • 确定个体编码方式:每个个体就是一个候选解。我习惯用实数向量表示,比如一个三维优化问题,个体就是 (x₁, x₂, x₃)。
  • 设定种群大小 μ:这个值很关键。太小了容易早熟,太大了计算量爆炸。我个人经验是:问题维度在10以下时,μ=20~50就够;维度上百了,μ=100~200起步。
  • 随机生成初始个体:在搜索空间内均匀采样。比如每个维度范围是 [-5, 5],那就用均匀分布 U(-5, 5) 生成。
重要提醒:初始种群的分布范围要覆盖整个可行域。我曾经犯过这个错——采样范围设得太窄,结果算法一直在一个小区域里打转,死活找不到全局最优。
# 初始化种群示例(Python风格)
import numpy as np

def initialize_population(mu, dim, lower_bound, upper_bound):
    population = np.random.uniform(lower_bound, upper_bound, (mu, dim))
    return population

# 使用:生成50个个体,每个个体3维,范围[-5, 5]
pop = initialize_population(50, 3, -5.0, 5.0)

3.2 评估适应度:谁好谁坏,拿数据说话

有了种群,下一步就是给每个个体打分。这个「分数」就是适应度(fitness)。

核心逻辑:适应度函数就是你的优化目标。比如你要最小化某个函数 f(x),那适应度就可以直接取 -f(x),或者 1/(1+f(x))。值越大,个体越优秀。

嗯,这里要注意:适应度函数的设计直接影响算法收敛速度。我在项目中遇到过一个问题——目标函数值范围特别大(从0到10⁶),直接取负值会导致适应度差异不明显。后来我做了归一化处理,效果立竿见影。

实战技巧:如果目标函数计算很耗时(比如需要跑仿真),建议先在小样本上测试适应度函数的合理性。别一上来就全量跑,浪费时间。
def evaluate_fitness(population, objective_func):
    fitness = np.array([objective_func(ind) for ind in population])
    return fitness

# 假设目标函数是 Rosenbrock 函数
def rosenbrock(x):
    return sum(100*(x[1:]-x[:-1]**2)**2 + (1-x[:-1])**2)

fitness_values = evaluate_fitness(pop, rosenbrock)

3.3 变异操作:给个体加点「随机扰动」

变异是进化策略的核心操作。说白了,就是在现有解的基础上,加一点随机噪声,生成新解。

标准做法:每个个体除了有决策变量 x,还附带一个「步长参数」σ(标准差)。变异时:

  • x' = x + σ · N(0, 1),其中 N(0,1) 是标准正态分布
  • σ 本身也会变异:σ' = σ · exp(τ · N(0,1)),τ 是学习率

为什么要让 σ 也进化?因为算法需要自适应调整搜索步长。一开始大步探索,后期小步精细搜索。这个机制我特别喜欢——它让算法自己学会了「什么时候该收,什么时候该放」。

避坑指南:我曾经把 σ 初始值设得太大,结果变异出来的个体全飞到搜索空间外面去了。后来我加了个边界检查,把越界的个体拉回边界内。记住:变异不是乱变,要在合理范围内变。
def mutate(individual, sigma, tau):
    # 变异步长
    sigma_new = sigma * np.exp(tau * np.random.randn())
    # 变异决策变量
    x_new = individual + sigma_new * np.random.randn(len(individual))
    return x_new, sigma_new

3.4 重组操作:把好基因「混」在一起

重组就是让两个或多个父代个体「生孩子」。进化策略里常用的重组方式有两种:

  • 离散重组:每个维度随机从父代中选一个。比如父代A=(1,2),父代B=(3,4),子代可能是(1,4)或(3,2)。
  • 中间重组:每个维度取父代的平均值。还是上面例子,子代=(2,3)。

我个人更推荐中间重组。为什么?因为它保留了更多父代信息,收敛更稳定。离散重组有时候会丢掉好基因,你想想看,如果父代A的第一个维度特别好,离散重组有50%概率丢掉它。

关键点:重组不是必须的!有些进化策略变体(比如(1+1)-ES)只用变异,不用重组。但如果你种群规模大(μ>10),重组能显著加速收敛。
def intermediate_recombination(parents):
    # parents 是选出的两个父代
    child = np.mean(parents, axis=0)
    return child

3.5 选择操作:优胜劣汰,留下精英

选择操作决定了哪些个体能进入下一代。进化策略里最经典的选择机制有两种:

选择策略 符号表示 怎么选 特点
(μ, λ)-选择 逗号策略 从 λ 个子代中选 μ 个最好的 父代全部淘汰,适合动态环境
(μ+λ)-选择 加号策略 从 μ 个父代 + λ 个子代中选 μ 个最好的 保留精英,收敛更快

我刚开始做项目时,无脑用 (μ+λ) 策略,觉得保留精英肯定好。后来发现不对——如果问题有多个局部最优,加号策略容易陷入局部最优出不来。逗号策略虽然「残忍」,但能保持种群多样性,反而更容易找到全局最优。

我的建议:如果问题比较平滑(没有太多局部最优),用 (μ+λ);如果问题崎岖不平(多峰函数),用 (μ,λ)。
def comma_selection(population, fitness, mu):
    # 从 λ 个子代中选 μ 个最好的
    indices = np.argsort(fitness)[-mu:]  # 假设适应度越大越好
    return population[indices]

def plus_selection(parents, offspring, parent_fitness, offspring_fitness, mu):
    combined = np.vstack([parents, offspring])
    combined_fitness = np.concatenate([parent_fitness, offspring_fitness])
    indices = np.argsort(combined_fitness)[-mu:]
    return combined[indices]

3.6 终止条件判断:什么时候停?

算法不能无限跑下去,得有个「喊停」的机制。常用的终止条件有:

  • 最大迭代次数:比如跑满1000代就停。最简单粗暴。
  • 适应度收敛:连续N代适应度变化小于阈值ε。比如连续20代最优适应度变化小于1e-6。
  • 目标值达到:找到了足够好的解。比如误差小于0.001。
  • 计算资源耗尽:比如跑了10小时,或者评估了100万次。

我一般会组合使用:先设一个最大迭代次数兜底,再配合适应度收敛检测。这样既不会无限跑,也不会因为收敛太慢而浪费资源。

实战经验:别把收敛阈值设得太小。我曾经设了1e-10,结果算法跑了三天三夜还没收敛。后来改成1e-6,半小时就出结果了,而且解的质量几乎没差别。
def check_termination(generation, max_gen, fitness_history, epsilon=1e-6, patience=20):
    if generation >= max_gen:
        return True
    if len(fitness_history) >= patience:
        recent = fitness_history[-patience:]
        if max(recent) - min(recent) < epsilon:
            return True
    return False

3.7 完整流程:把六步串起来

好了,六个步骤都讲完了。咱们把它们串成一个完整的流程图,你一看就明白。

进化策略算法流程图 1. 初始化种群 2. 评估适应度 3. 变异操作 4. 重组操作 5. 选择操作 终止? 输出结果 否,继续迭代

看到这个流程图,你应该能感受到:进化策略其实就是一个「生成-评估-进化」的循环。每次迭代,种群都在向更好的方向移动。我做了这么多年优化,最深的体会就是——这个框架简单,但威力巨大。

好了,这一章的内容就到这里。记住这六个步骤,你就掌握了进化策略的核心骨架。下一章咱们会深入讲变异策略的细节,到时候再细聊。


专注资料整理