进化策略的数学基础:四大核心机制

大家好,我是你们的老朋友。今天咱们来聊聊进化策略的数学基础。说实话,很多初学者一听到「数学基础」四个字就头大,觉得全是公式推导。其实不然,进化策略的数学核心,说白了就是四个问题:怎么评价个体好坏?怎么产生新个体?怎么组合两个个体?怎么淘汰差的个体?

我当年刚入行时,也在这四个问题上栽过跟头。有一次做参数优化,适应度函数设计得不对,结果算法跑了三天三夜,愣是没找到最优解。后来才发现,问题出在适应度函数上。嗯,咱们今天就好好聊聊这四个核心机制。

核心要点:适应度函数、变异算子、重组算子、选择机制,这四者构成了进化策略的数学骨架。缺一不可,顺序也不能乱。

2.1 适应度函数设计

适应度函数,就是用来评价个体「好坏」的标尺。你想想看,如果没有这把尺子,算法怎么知道哪个个体该保留,哪个该淘汰?

我个人习惯把适应度函数分为两类:

  • 直接型:目标函数本身就是适应度。比如求最大值问题,f(x) 越大越好。
  • 间接型:需要把目标函数转换成适应度。比如求最小值问题,通常用 1/f(x) 或 -f(x) 来转换。

这里有个坑,我必须要提醒大家。我曾经在项目中遇到过一个问题:适应度函数的值域范围太大,导致算法早期就收敛到局部最优。为什么会这样?因为适应度差异过大,好的个体太突出,差的个体直接被忽略,多样性就没了。

避坑指南:我曾经吃过一次大亏。做多目标优化时,直接把两个目标函数相加作为适应度。结果两个目标的量纲差了一千倍,大目标完全主导了进化方向。后来我改用归一化处理,才解决了问题。

适应度函数设计的原则,我总结为三点:

  1. 单调性:适应度值必须与个体优劣正相关
  2. 区分度:不同个体之间要有足够的差异
  3. 稳定性:不能因为微小变化导致适应度剧烈波动

来看一个简单的代码示例:

# 适应度函数设计示例
def fitness_function(x):
    # 假设我们要最大化 f(x) = -x^2 + 10x + 5
    # 这是一个开口向下的抛物线,最大值在 x=5 处
    return -x**2 + 10*x + 5

# 归一化处理,避免值域过大
def normalized_fitness(x):
    raw = -x**2 + 10*x + 5
    # 映射到 [0, 1] 区间
    return (raw - (-20)) / (30 - (-20))  # 假设值域在 [-20, 30]

2.2 变异算子原理

变异算子,就是给个体「加点随机扰动」。你想想看,如果没有变异,所有个体都一模一样,那还进化个啥?

在进化策略中,最常用的变异算子是高斯变异。说白了,就是在当前个体的基础上,加上一个服从正态分布的随机数。

我记得有一次做参数调优,变异步长设得太大了。结果算法就像喝醉了酒,个体到处乱跳,根本收敛不了。后来我把步长设小,效果立竿见影。

变异算子的数学表达式很简单:

# 高斯变异
def gaussian_mutation(individual, sigma=0.1):
    """
    individual: 当前个体(实数向量)
    sigma: 变异步长(标准差)
    """
    mutated = [x + random.gauss(0, sigma) for x in individual]
    return mutated

实战技巧:我个人习惯用自适应变异步长。算法初期步长大一些,增加探索能力;后期步长小一些,精细搜索。你可以试试 1/t 衰减策略,其中 t 是进化代数。

变异算子的核心参数有两个:

参数 作用 常见取值
变异概率 控制个体发生变异的可能性 0.1 ~ 0.5
变异步长 控制变异幅度的大小 0.01 ~ 0.2(归一化后)

2.3 重组算子原理

重组算子,就是「生娃」的过程。两个父本个体,通过某种方式组合,产生子代个体。这玩意儿在遗传算法里叫交叉,在进化策略里叫重组。

常用的重组算子有两种:

  • 离散重组:每个基因位随机从两个父本中选择一个
  • 中间重组:每个基因位取两个父本的平均值

我刚开始做进化策略时,总觉得离散重组更好,因为它保留了父本的多样性。后来发现,对于连续优化问题,中间重组往往更稳定。你想想看,平均值能平滑掉极端值,不容易产生「怪胎」。

# 离散重组
def discrete_recombination(parent1, parent2):
    child = []
    for p1, p2 in zip(parent1, parent2):
        if random.random() < 0.5:
            child.append(p1)
        else:
            child.append(p2)
    return child

# 中间重组
def intermediate_recombination(parent1, parent2):
    child = [(p1 + p2) / 2 for p1, p2 in zip(parent1, parent2)]
    return child

注意:我曾经在项目中遇到过一个问题——重组算子产生的子代,适应度反而比父本还差。这是因为重组破坏了父本中已经形成的优良基因组合。解决办法是引入精英保留策略,把最好的父本直接复制到下一代。

2.4 选择机制原理

选择机制,就是「优胜劣汰」的具体实现。进化策略中最经典的选择机制是 (μ, λ) 选择和 (μ + λ) 选择。

这两种选择的区别,我一句话就能说清楚:

  • (μ, λ) 选择:从 λ 个子代中选出 μ 个最好的,父本全部淘汰
  • (μ + λ) 选择:从 μ 个父本和 λ 个子代中,一起选出 μ 个最好的

我个人更倾向于 (μ, λ) 选择。为什么?因为它能更好地保持种群多样性。父本全部淘汰,意味着算法不会「恋旧」,更容易跳出局部最优。

但 (μ + λ) 选择也有它的优势——收敛速度更快。因为好的父本可以一直保留,不会丢失已经找到的优秀解。

# (μ, λ) 选择
def mu_lambda_selection(population, offspring, mu):
    """
    population: 父本种群(会被全部淘汰)
    offspring: 子代种群
    mu: 要选择的个体数量
    """
    combined = offspring  # 只从子代中选择
    combined.sort(key=lambda ind: ind.fitness, reverse=True)
    return combined[:mu]

# (μ + λ) 选择
def mu_plus_lambda_selection(population, offspring, mu):
    combined = population + offspring  # 父本和子代一起选
    combined.sort(key=lambda ind: ind.fitness, reverse=True)
    return combined[:mu]

核心总结:选择机制决定了算法的「探索」与「利用」平衡。(μ, λ) 偏向探索,(μ + λ) 偏向利用。具体用哪个,取决于你的问题特性。

知识体系总览

下面这张图,是我手绘的进化策略数学基础框架。你可以把它当作一张「地图」,随时回来对照。

进化策略数学基础 适应度函数设计 • 直接型 vs 间接型 • 单调性、区分度、稳定性 • 归一化处理避免值域问题 • 多目标加权需注意量纲 变异算子原理 • 高斯变异:加正态随机扰动 • 变异概率控制发生可能性 • 变异步长控制幅度大小 • 自适应步长:初期大后期小 重组算子原理 • 离散重组:随机选父本基因 • 中间重组:取父本平均值 • 连续问题推荐中间重组 • 精英保留防止优良基因丢失 选择机制原理 • (μ, λ):子代选优,父本全淘汰 • (μ+λ):父本+子代一起选优 • (μ, λ) 偏向探索,多样性好 • (μ+λ) 偏向利用,收敛更快 四者协同:适应度评价 → 变异/重组产生新个体 → 选择淘汰劣质个体

好了,以上就是进化策略的四大数学基础。适应度函数是「标尺」,变异和重组是「引擎」,选择机制是「方向盘」。四者配合好了,算法才能跑得又快又稳。

我建议你动手写一个简单的进化策略代码,把这四个模块都实现一遍。纸上得来终觉浅,绝知此事要躬行。遇到问题随时回来翻翻这张图,应该能帮你理清思路。

专注资料整理