由该工具创作数学建模学习平台
差分进化法(Differential Evolution, DE)小白入门教程
一、背景溯源:从进化算法到差分进化
要理解差分进化,先得从进化算法 (Evolutionary Algorithms, EAs)说起------这是一类模拟生物进化规律的启发式算法,核心逻辑是:用"种群"(一组可能的解)通过"变异-交叉-选择"的迭代,像生物进化一样"淘汰差的、保留好的",最终找到最优解。
早期的进化算法以**遗传算法(Genetic Algorithm, GA)**为代表,它把解编码成二进制串(比如"0101"代表一个解),通过"交叉"(交换两个二进制串的部分基因)和"变异"(翻转某个二进制位)生成新解。但GA有个明显的缺点:二进制编码会损失精度(比如用8位二进制表示0-255的实数,精度只有1/255),而且编码/解码过程麻烦。
1995年,美国学者Kenneth Price 和Rainer Storn 为了解决实数域连续优化问题 (比如"调整无人机的3个控制参数让航程最长""优化5个化工参数让产量最高"),提出了差分进化法 。它直接用实数向量表示解(不需要编码),用**"差分向量"**(两个个体的差)代替GA的二进制变异,不仅保留了进化算法的鲁棒性,还大大简化了操作,成为连续优化领域的"爆款算法"。
二、核心思想:用"差异"引导进化
差分进化的核心可以用一个比喻理解:假设你和一群人在森林里找"宝藏"(最优解),每个人的位置代表一个"候选解"。怎么找到宝藏?
- 你不会瞎逛------而是看别人的位置差:比如看到甲在你东边100米,乙在你西边50米,你会想"甲和乙的位置差是150米东",于是你往东边走一段(比如走50米,即"变异因子"F=50/150=0.33);
- 你不会完全照搬别人的路线------而是和别人交换信息:比如你和丙交换"东边有没有河""北边有没有树"的信息(交叉操作);
- 你只会保留更好的选择------比如走完之后发现新位置比原来近,就留在新位置;否则回到原位置(选择操作)。
总结差分进化的核心思想:
用"种群中个体的差异"(差分向量)作为"变异的方向和步长",通过"变异-交叉-选择"的循环,让种群一步步向最优解进化。
三、基础概念铺垫:先搞懂这些术语
在正式讲算法前,先明确几个关键术语(用"找宝藏"的例子对应):
- 种群(Population) :一群找宝藏的人,对应一组候选解 ,用集合表示为 P = { x 1 , x ∗ 2 , . . . , x ∗ N p } P = \{\mathbf{x}_1, \mathbf{x}*2, ..., \mathbf{x}*{N_p}\} P={x1,x∗2,...,x∗Np},其中 N p N_p Np 是种群大小(比如10个人)。
- 个体(Individual) :一个找宝藏的人,对应一个候选解 ,用D维实数向量 表示: x ∗ i = [ x ∗ i , 1 , x i , 2 , . . . , x i , D ] \mathbf{x}*i = [x*{i,1}, x_{i,2}, ..., x_{i,D}] x∗i=[x∗i,1,xi,2,...,xi,D]。其中:
- D D D 是问题维度(比如优化"无人机高度+速度"两个参数,D=2);
- x i , j x_{i,j} xi,j 是第i个个体的第j个维度的值(比如"高度=100米"对应j=1的值为100)。
- 适应度(Fitness) :这个人离宝藏的距离,对应解的优劣程度 。对于最小化问题 (比如"距离越小越好"),适应度就是目标函数值 f ( x i ) f(\mathbf{x}_i) f(xi);对于最大化问题 (比如"产量越高越好"),适应度可以取 − f ( x i ) -f(\mathbf{x}_i) −f(xi) 或 1 / f ( x i ) 1/f(\mathbf{x}_i) 1/f(xi)(把最大化转成最小化)。
- 变量边界 :每个人的位置不能超出森林范围,对应解的可行域 。比如高度不能低于0米、不超过500米,即 x m i n , 1 = 0 x_{min,1}=0 xmin,1=0, x m a x , 1 = 500 x_{max,1}=500 xmax,1=500;速度不能低于10m/s、不超过200m/s,即 x m i n , 2 = 10 x_{min,2}=10 xmin,2=10, x m a x , 2 = 200 x_{max,2}=200 xmax,2=200。所有维度的边界合并为: x ∗ m i n = [ x ∗ m i n , 1 , . . . , x m i n , D ] \mathbf{x}*{min} = [x*{min,1}, ..., x_{min,D}] x∗min=[x∗min,1,...,xmin,D], x ∗ m a x = [ x ∗ m a x , 1 , . . . , x m a x , D ] \mathbf{x}*{max} = [x*{max,1}, ..., x_{max,D}] x∗max=[x∗max,1,...,xmax,D]。
四、算法原理深度拆解:变异-交叉-选择的底层逻辑
差分进化的迭代过程可以概括为"初始化种群 → 变异 → 交叉 → 选择 → 重复直到收敛 "。下面逐个拆解每一步的目的、操作和公式。
1. 第一步:初始化种群------随机撒"找宝藏的人"
目的 :生成初始的候选解集合,让种群覆盖整个可行域。操作 :随机生成 N p N_p Np 个D维向量,每个维度的值都在变量边界内。公式如下: x ∗ i ( 0 ) = x ∗ m i n + rand ( 0 , 1 ) D ⋅ ( x ∗ m a x − x ∗ m i n ) ( i = 1 , 2 , . . . , N p ) \mathbf{x}*i^{(0)} = \mathbf{x}*{min} + \text{rand}(0,1)^D \cdot (\mathbf{x}*{max} - \mathbf{x}*{min}) \quad (i=1,2,...,N_p) x∗i(0)=x∗min+rand(0,1)D⋅(x∗max−x∗min)(i=1,2,...,Np)
- x i ( 0 ) \mathbf{x}_i^{(0)} xi(0):第0代(初始代)的第i个个体;
- rand ( 0 , 1 ) D \text{rand}(0,1)^D rand(0,1)D:生成一个D维向量,每个元素是0到1的随机数;
- x ∗ m a x − x ∗ m i n \mathbf{x}*{max} - \mathbf{x}*{min} x∗max−x∗min:可行域的"跨度"(比如高度跨度500米,速度跨度190m/s)。
例子 :假设D=2(高度+速度), x ∗ m i n = [ 0 , 10 ] \mathbf{x}*{min}=[0,10] x∗min=[0,10], x ∗ m a x = [ 500 , 200 ] \mathbf{x}*{max}=[500,200] x∗max=[500,200],生成一个个体: rand ( 0 , 1 ) 2 = [ 0.3 , 0.6 ] \text{rand}(0,1)^2 = [0.3, 0.6] rand(0,1)2=[0.3,0.6],则 x 1 ( 0 ) = [ 0 + 0.3 × 500 , 10 + 0.6 × 190 ] = [ 150 , 124 ] \mathbf{x}_1^{(0)} = [0 + 0.3×500, 10 + 0.6×190] = [150, 124] x1(0)=[0+0.3×500,10+0.6×190]=[150,124](高度150米,速度124m/s)。
2. 第二步:变异------用"别人的差异"调整自己的位置
变异是差分进化的核心 ,它决定了种群进化的方向。目的 :通过"参考其他个体的位置差",让当前个体"向更优的方向移动"。操作逻辑 :从种群中选3个不同的随机个体 (比如甲、乙、丙),用"甲的位置 + 乙和丙的位置差 × 变异因子"生成变异向量(新的候选位置)。
(1)基础变异策略:DE/rand/1(最常用)
公式: v ∗ i ( t ) = x ∗ r 1 ( t ) + F ⋅ ( x ∗ r 2 ( t ) − x ∗ r 3 ( t ) ) \mathbf{v}*i^{(t)} = \mathbf{x}*{r1}^{(t)} + F \cdot (\mathbf{x}*{r2}^{(t)} - \mathbf{x}*{r3}^{(t)}) v∗i(t)=x∗r1(t)+F⋅(x∗r2(t)−x∗r3(t))
符号解释:
- v i ( t ) \mathbf{v}_i^{(t)} vi(t):第t代第i个个体的变异向量(新的候选位置);
- x ∗ r 1 , x ∗ r 2 , x r 3 \mathbf{x}*{r1}, \mathbf{x}*{r2}, \mathbf{x}_{r3} x∗r1,x∗r2,xr3:从种群中随机选的3个不同个体(r1≠r2≠r3≠i);
- F F F:变异因子 (步长),控制"位置差的放大倍数",一般取 0.4 ≤ F ≤ 0.8 0.4 \leq F \leq 0.8 0.4≤F≤0.8。
比喻理解:比如你是第i个人,看到甲在东边,乙在甲东边100米,丙在甲西边50米------乙和丙的差是"东边150米",你取F=0.5,就往东边走75米(甲的位置 + 150×0.5)。
(2)其他常见变异策略
为了平衡"收敛速度"和"种群多样性"(避免过早陷入局部最优),DE还有多种变异策略:
- DE/best/1 (快收敛,但易早熟):用当前最好个体 代替随机个体r1,直接向最优方向移动: v ∗ i ( t ) = x ∗ b e s t ( t ) + F ⋅ ( x ∗ r 2 ( t ) − x ∗ r 3 ( t ) ) \mathbf{v}*i^{(t)} = \mathbf{x}*{best}^{(t)} + F \cdot (\mathbf{x}*{r2}^{(t)} - \mathbf{x}*{r3}^{(t)}) v∗i(t)=x∗best(t)+F⋅(x∗r2(t)−x∗r3(t))其中 x b e s t ( t ) \mathbf{x}_{best}^{(t)} xbest(t) 是第t代适应度最好的个体(离宝藏最近的人)。
- DE/rand-to-best/1 (平衡型):既向最好个体靠近,又保留随机差异: v ∗ i ( t ) = x ∗ i ( t ) + F ⋅ ( x ∗ b e s t ( t ) − x ∗ i ( t ) ) + F ⋅ ( x ∗ r 1 ( t ) − x ∗ r 2 ( t ) ) \mathbf{v}*i^{(t)} = \mathbf{x}*i^{(t)} + F \cdot (\mathbf{x}*{best}^{(t)} - \mathbf{x}*i^{(t)}) + F \cdot (\mathbf{x}*{r1}^{(t)} - \mathbf{x}*{r2}^{(t)}) v∗i(t)=x∗i(t)+F⋅(x∗best(t)−x∗i(t))+F⋅(x∗r1(t)−x∗r2(t))
3. 第三步:交叉------交换信息,保持多样性
变异后的向量可能"太激进"(比如走了太远),交叉的作用是把原个体和变异向量的信息结合 ,避免种群"同质化"。目的 :让新解同时保留"原个体的优点"和"变异向量的新信息"。操作逻辑 :对每个维度j(比如高度、速度),用交叉概率 Cr决定"取原个体的值还是变异向量的值",同时强制至少有一个维度取变异向量的值(避免交叉后和原个体完全一样)。
公式: u i , j ( t ) = { v i , j ( t ) , 如果 rand ( 0 , 1 ) ≤ C r 或 j = j r a n d x i , j ( t ) , 否则 u_{i,j}^{(t)} = \begin{cases} v_{i,j}^{(t)}, & \text{如果} \ \text{rand}(0,1) \leq Cr \ 或 \ j = j_{rand} \\ x_{i,j}^{(t)}, & \text{否则} \end{cases} ui,j(t)={vi,j(t),xi,j(t),如果 rand(0,1)≤Cr 或 j=jrand否则
符号解释:
- u ∗ i ( t ) = [ u ∗ i , 1 , . . . , u i , D ] \mathbf{u}*i^{(t)} = [u*{i,1}, ..., u_{i,D}] u∗i(t)=[u∗i,1,...,ui,D]:第t代第i个个体的试验向量(交叉后的新解);
- C r Cr Cr:交叉概率 ,控制"取变异向量值的概率",一般取 0.1 ≤ C r ≤ 0.9 0.1 \leq Cr \leq 0.9 0.1≤Cr≤0.9;
- j r a n d j_{rand} jrand:从1到D中随机选的一个维度(强制至少有一个维度取变异向量的值);
- rand ( 0 , 1 ) \text{rand}(0,1) rand(0,1):0到1的均匀随机数。
例子:假设你是第i个人,原位置是[150, 124](高度150,速度124),变异向量是[200, 150](高度200,速度150),Cr=0.7,j_rand=2:
- 对j=1(高度):rand(0,1)=0.6 ≤ 0.7 → 取变异向量的值200;
- 对j=2(速度):因为j=j_rand → 强制取变异向量的值150;
- 试验向量就是[200, 150]。
4. 第四步:选择------只保留更好的解
选择是进化的"方向舵",它保证种群只会往"更好"的方向发展。目的 :淘汰差的解,保留好的解,让种群逐步优化。操作逻辑 :用贪心策略 ------比较原个体 x i ( t ) \mathbf{x}_i^{(t)} xi(t) 和试验向量 u i ( t ) \mathbf{u}_i^{(t)} ui(t) 的适应度,保留适应度更好的那个作为下一代的个体。
公式(以最小化问题 为例,即适应度越小越好): x i ( t + 1 ) = { u i ( t ) , 如果 f ( u i ( t ) ) ≤ f ( x i ( t ) ) x i ( t ) , 否则 \mathbf{x}_i^{(t+1)} = \begin{cases} \mathbf{u}_i^{(t)}, & \text{如果} \ f(\mathbf{u}_i^{(t)}) \leq f(\mathbf{x}_i^{(t)}) \\ \mathbf{x}_i^{(t)}, & \text{否则} \end{cases} xi(t+1)={ui(t),xi(t),如果 f(ui(t))≤f(xi(t))否则
符号解释:
- x i ( t + 1 ) \mathbf{x}_i^{(t+1)} xi(t+1):第t+1代第i个个体(下一代的位置);
- f ( ⋅ ) f(\cdot) f(⋅):目标函数(比如"距离宝藏的距离")。
例子:原个体的适应度是f([150,124])=100(距离100米),试验向量的适应度是f([200,150])=80(距离80米)→ 下一代保留试验向量[200,150]。
五、完整算法流程:一步一步走
现在把前面的步骤串起来,给出差分进化的完整迭代流程(以最基础的DE/rand/1策略为例):
1. 步骤0:设定参数与初始化
首先明确问题的输入:
- 目标函数: min f ( x ) \min f(\mathbf{x}) minf(x)(或 max f ( x ) \max f(\mathbf{x}) maxf(x));
- 变量边界: x ∗ m i n \mathbf{x}*{min} x∗min(下限)、 x ∗ m a x \mathbf{x}*{max} x∗max(上限);
- 算法参数:种群大小 N p N_p Np(一般取 5 D ≤ N p ≤ 10 D 5D \leq N_p \leq 10D 5D≤Np≤10D)、变异因子F(0.4-0.8)、交叉概率Cr(0.1-0.9)、最大迭代次数 G m a x G_{max} Gmax(一般取100-1000)。
然后初始化种群 :用第一节的公式生成第0代的Np个个体 x i ( 0 ) \mathbf{x}_i^{(0)} xi(0)(i=1到Np)。
2. 步骤1:计算初始适应度
对每个初始个体 x i ( 0 ) \mathbf{x}_i^{(0)} xi(0),计算其适应度 f ( x ∗ i ( 0 ) ) f(\mathbf{x}*i^{(0)}) f(x∗i(0)),并记录当前最好个体 x ∗ b e s t ( 0 ) \mathbf{x}*{best}^{(0)} x∗best(0)(适应度最小的那个)。
3. 步骤2:迭代进化(t从0到Gmax-1)
循环执行"变异→交叉→选择",直到达到最大迭代次数:
(1)变异:生成所有个体的变异向量
对每个个体i(1到Np):
- 随机选3个不同的个体 x ∗ r 1 , x ∗ r 2 , x r 3 \mathbf{x}*{r1}, \mathbf{x}*{r2}, \mathbf{x}_{r3} x∗r1,x∗r2,xr3(r1≠r2≠r3≠i);
- 用DE/rand/1策略生成变异向量 v i ( t ) \mathbf{v}_i^{(t)} vi(t)。
(2)交叉:生成所有个体的试验向量
对每个个体i(1到Np):
- 随机选一个维度 j r a n d j_{rand} jrand(1到D);
- 对每个维度j(1到D):
- 如果rand(0,1) ≤ Cr 或 j=j_rand → 取变异向量的值 v i , j ( t ) v_{i,j}^{(t)} vi,j(t);
- 否则 → 取原个体的值 x i , j ( t ) x_{i,j}^{(t)} xi,j(t);
- 得到试验向量 u i ( t ) \mathbf{u}_i^{(t)} ui(t)。
(3)选择:更新下一代种群
对每个个体i(1到Np):
- 计算试验向量的适应度 f ( u i ( t ) ) f(\mathbf{u}_i^{(t)}) f(ui(t));
- 比较 f ( u i ( t ) ) f(\mathbf{u}_i^{(t)}) f(ui(t)) 和原个体的适应度 f ( x i ( t ) ) f(\mathbf{x}_i^{(t)}) f(xi(t)):
- 如果试验向量更好 → 下一代个体 x i ( t + 1 ) = u i ( t ) \mathbf{x}_i^{(t+1)} = \mathbf{u}_i^{(t)} xi(t+1)=ui(t);
- 否则 → 下一代个体 x i ( t + 1 ) = x i ( t ) \mathbf{x}_i^{(t+1)} = \mathbf{x}_i^{(t)} xi(t+1)=xi(t)。
(4)更新当前最好个体
从下一代种群 { x ∗ 1 ( t + 1 ) , . . . , x ∗ N p ( t + 1 ) } \{\mathbf{x}*1^{(t+1)}, ..., \mathbf{x}*{N_p}^{(t+1)}\} {x∗1(t+1),...,x∗Np(t+1)} 中找到适应度最小的个体,更新 x b e s t ( t + 1 ) \mathbf{x}_{best}^{(t+1)} xbest(t+1)。
4. 步骤3:输出结果
迭代结束后,输出最终最好个体 x ∗ b e s t ( G m a x ) \mathbf{x}*{best}^{(Gmax)} x∗best(Gmax) 及其适应度 f ( x ∗ b e s t ( G m a x ) ) f(\mathbf{x}*{best}^{(Gmax)}) f(x∗best(Gmax)),这就是DE找到的近似最优解。
六、适用边界与参数调优:什么时候用?怎么调?
1. 最适合的问题类型
DE是连续优化问题的"瑞士军刀",最适合以下场景:
- 实数域连续优化(比如调整神经网络权重、优化化工反应参数、设计飞机翼型);
- 高维问题(D≥50,比如优化100个变量的函数);
- 非凸/多峰问题(目标函数有多个局部最优,比如"地形有多个小山峰");
- 不可微问题(目标函数没有导数,无法用梯度下降等算法)。
2. 不适合的问题类型
- 离散优化(比如0-1变量、整数变量、旅行商问题TSP):原生DE用实数向量,处理离散问题需要额外编码(比如用实数映射到离散值),效率不如专门的离散算法(如遗传算法);
- 线性规划(有明确约束的线性问题):有更高效的算法(如单纯形法);
- 需要精确解的问题 :DE是启发式算法,只能找到近似最优解,不适合需要精确解的场景(比如数学证明)。
3. 参数调优技巧(小白必看)
DE的性能高度依赖3个核心参数(Np、F、Cr),以下是经验法则:
-
种群大小Np:
- 太小(比如Np<5D):种群多样性不足,容易早熟;
- 太大(比如Np>20D):计算量太大,收敛慢;
- 推荐: N p = 5 D ∼ 10 D Np = 5D \sim 10D Np=5D∼10D(比如D=10 → Np=50~100)。
-
变异因子F:
- 太小(F<0.4):变异步长太小,收敛慢;
- 太大(F>0.8):变异步长太大,容易跳过最优解;
- 推荐:固定F=0.6,或用自适应F(比如每代随机选0.5~0.9的F)。
-
交叉概率Cr:
- 太小(Cr<0.3):交叉后和原个体太像,变异效果弱;
- 太大(Cr>0.8):交叉后破坏原个体的好特征,容易不稳定;
- 推荐:固定Cr=0.7,或对高维问题 取大Cr(比如Cr=0.9),对低维问题取小Cr(比如Cr=0.5)。
七、总结:差分进化的"优"与"缺"
优点
- 简单易实现:只有"变异-交叉-选择"三步,不需要复杂的数学推导;
- 鲁棒性强:对初始种群不敏感,即使初始解很差,也能逐步优化;
- 通用性好:不需要目标函数的导数,适合各种连续优化问题;
- 高维表现好:在高维问题(D=100+)上的性能优于很多进化算法。
缺点
- 离散问题需改进:原生DE不支持离散变量,处理离散问题需要额外编码;
- 参数敏感:参数调优需要经验,新手可能需要多次尝试;
- 收敛速度慢:相比梯度下降等算法,DE的收敛速度较慢(但梯度下降需要导数)。
八、小试牛刀:用DE优化一个简单函数
最后用一个极简例子 帮你巩固流程:问题 :最小化函数 f ( x , y ) = x 2 + y 2 f(x,y) = x^2 + y^2 f(x,y)=x2+y2(最优解是(0,0),适应度0)。参数:Np=10,F=0.5,Cr=0.7,Gmax=50,变量边界x∈[-5,5],y∈[-5,5]。
步骤1:初始化种群
生成10个2维向量,比如其中一个是[1.2, -0.8](x=1.2,y=-0.8),适应度是1.2²+(-0.8)²=2.08。
步骤2:变异(DE/rand/1)
选3个随机个体[2.3, 1.5]、[-3.4, 2.1]、[0.5, -1.3],生成变异向量:$
\mathbf{v} = [2.3] + 0.5×([-3.4]-[0.5], [2.1]-[-1.3]) = [2.3 - 1.95, 1.5 + 1.7] = [0.35, 3.2]
$
步骤3:交叉
原个体是[1.2, -0.8],Cr=0.7,j_rand=2:
- j=1:rand(0,1)=0.6 ≤0.7 → 取变异向量的0.35;
- j=2:j=j_rand → 取变异向量的3.2;
- 试验向量是[0.35, 3.2],适应度是0.35²+3.2²=0.1225+10.24=10.3625(比原个体的2.08差,所以选择原个体)。
步骤4:迭代50次
经过50次迭代后,最好的个体可能是[0.01, -0.02],适应度≈0.0005,非常接近最优解(0,0)。
九、结语:差分进化的"小白哲学"
差分进化的本质是**"简单的力量"**------没有复杂的编码,没有高深的数学,只用"个体的差异"引导进化,却能解决很多复杂问题。
对小白来说,学习DE的关键不是记住公式,而是理解**"变异用差异、交叉保多样、选择定方向"**的核心逻辑。当你遇到"不知道怎么求导""变量太多""函数有多个峰"的问题时,不妨试试DE------它可能不是最完美的,但一定是最"接地气"的进化算法。
现在,你已经掌握了DE的全部核心知识,接下来可以找一个简单的连续优化问题(比如优化 f ( x ) = x 2 + 2 x + 1 f(x) = x^2 + 2x + 1 f(x)=x2+2x+1),按照流程手动走一遍------实践是最好的老师!
案例介绍
最小化二维函数 ( f(x,y) = x^2 + y^2 ),最优解为 (0,0),适应度为 0。参数设置:种群大小Np=10,变异因子F=0.5,交叉概率Cr=0.7,最大迭代次数Gmax=50,变量边界x∈[-5,5],y∈[-5,5]。
python
import random
import numpy as np
def init_population(Np, D, x_min, x_max):
"""
初始化种群
:param Np: 种群大小
:param D: 问题维度
:param x_min: 变量下限向量
:param x_max: 变量上限向量
:return: 初始种群,shape=(Np, D)
"""
population = np.zeros((Np, D))
for i in range(Np):
# 随机生成每个维度的初始值,在[x_min, x_max]范围内
population[i, :] = x_min + np.random.rand(D) * (x_max - x_min)
return population
def evaluate_fitness(population, objective_func):
"""
计算种群中每个个体的适应度
:param population: 种群矩阵,shape=(Np, D)
:param objective_func: 目标函数
:return: 适应度向量,shape=(Np,)
"""
Np = population.shape[0]
fitness = np.zeros(Np)
for i in range(Np):
fitness[i] = objective_func(population[i, :])
return fitness
def select_best_individual(population, fitness):
"""
选择种群中的最优个体
:param population: 种群矩阵
:param fitness: 适应度向量
:return: 最优个体向量,最优适应度值
"""
best_idx = np.argmin(fitness) # 最小化问题,取适应度最小的索引
best_individual = population[best_idx, :].copy()
best_fitness = fitness[best_idx]
return best_individual, best_fitness
def mutate(population, F):
"""
变异操作,采用DE/rand/1策略
:param population: 种群矩阵
:param F: 变异因子
:return: 变异向量矩阵,shape=(Np, D)
"""
Np, D = population.shape
mutated = np.zeros_like(population)
for i in range(Np):
# 随机选择3个不同的个体,且不等于当前个体i
r1 = r2 = r3 = i
while r1 == i:
r1 = random.randint(0, Np - 1)
while r2 == i or r2 == r1:
r2 = random.randint(0, Np - 1)
while r3 == i or r3 == r1 or r3 == r2:
r3 = random.randint(0, Np - 1)
# 计算变异向量
mutated[i, :] = population[r1, :] + F * (population[r2, :] - population[r3, :])
return mutated
def crossover(population, mutated, Cr):
"""
交叉操作,采用二项式交叉
:param population: 原种群矩阵
:param mutated: 变异向量矩阵
:param Cr: 交叉概率
:return: 试验向量矩阵,shape=(Np, D)
"""
Np, D = population.shape
trial = np.zeros_like(population)
for i in range(Np):
# 随机选择一个强制交叉的维度
j_rand = random.randint(0, D - 1)
for j in range(D):
# 二项式交叉规则
if random.random() <= Cr or j == j_rand:
trial[i, j] = mutated[i, j]
else:
trial[i, j] = population[i, j]
return trial
def select_next_generation(population, trial, fitness_org, fitness_trial):
"""
选择操作,采用贪心策略生成下一代种群
:param population: 原种群矩阵
:param trial: 试验向量矩阵
:param fitness_org: 原种群适应度向量
:param fitness_trial: 试验向量适应度向量
:return: 下一代种群矩阵,下一代种群适应度向量
"""
Np = population.shape[0]
next_pop = np.zeros_like(population)
next_fitness = np.zeros(Np)
for i in range(Np):
# 最小化问题,选择适应度更小的个体
if fitness_trial[i] <= fitness_org[i]:
next_pop[i, :] = trial[i, :]
next_fitness[i] = fitness_trial[i]
else:
next_pop[i, :] = population[i, :]
next_fitness[i] = fitness_org[i]
return next_pop, next_fitness
def differential_evolution(objective_func, D, x_min, x_max, Np, F, Cr, Gmax):
"""
差分进化算法主函数
:param objective_func: 目标函数(最小化)
:param D: 问题维度
:param x_min: 变量下限向量,shape=(D,)
:param x_max: 变量上限向量,shape=(D,)
:param Np: 种群大小
:param F: 变异因子
:param Cr: 交叉概率
:param Gmax: 最大迭代次数
:return: 最优个体,最优适应度,迭代历史最优适应度
"""
# 步骤1: 初始化种群
population = init_population(Np, D, x_min, x_max)
# 步骤2: 计算初始适应度
fitness = evaluate_fitness(population, objective_func)
# 记录初始最优个体
best_individual, best_fitness = select_best_individual(population, fitness)
# 存储迭代历史最优适应度
history_best = [best_fitness]
# 步骤3: 迭代进化
for t in range(Gmax):
# 3.1 变异操作
mutated = mutate(population, F)
# 3.2 交叉操作
trial = crossover(population, mutated, Cr)
# 3.3 计算试验向量的适应度
fitness_trial = evaluate_fitness(trial, objective_func)
# 3.4 选择操作生成下一代
population, fitness = select_next_generation(population, trial, fitness, fitness_trial)
# 3.5 更新当前最优个体
current_best_ind, current_best_fit = select_best_individual(population, fitness)
if current_best_fit < best_fitness:
best_fitness = current_best_fit
best_individual = current_best_ind.copy()
# 记录历史最优
history_best.append(best_fitness)
# 步骤4: 输出结果
return best_individual, best_fitness, history_best
def objective_func(X):
"""
目标函数:f(x,y) = x^2 + y^2
:param X: 输入向量,shape=(2,)
:return: 函数值
"""
return X[0] ** 2 + X[1] ** 2
if __name__ == "__main__":
# 设置问题参数
D = 2 # 二维问题
x_min = np.array([-5.0, -5.0]) # 变量下限
x_max = np.array([5.0, 5.0]) # 变量上限
# 设置DE算法参数
Np = 10 # 种群大小
F = 0.5 # 变异因子
Cr = 0.7 # 交叉概率
Gmax = 50 # 最大迭代次数
# 运行DE算法
best_ind, best_fit, history = differential_evolution(objective_func, D, x_min, x_max, Np, F, Cr, Gmax)
# 打印结果
print("最优个体:", best_ind)
print("最优适应度:", best_fit)
代码整体定位与算法框架
这段代码实现了差分进化算法(DE, Differential Evolution) 中的 DE/rand/1/bin 策略,用于无约束连续优化问题 ------最小化二维凸函数 ( f(x,y)=x2+y2 )(最优解为 ( (0,0) ),适应度为0)。DE是一种基于群体智能的全局优化算法,核心思想是通过差分变异 产生新解,结合交叉 增加多样性,最后通过贪心选择保留优解。
1. 环境与依赖解析
python
import random
import numpy as np
random:用于生成离散随机数(如交叉维度、变异个体索引)。numpy:用于高效的向量/矩阵运算(如种群初始化、差分计算),是DE等数值优化算法的必备依赖。
2. 核心子函数解析(数学原理+代码逐行)
2.1 种群初始化 init_population
数学原理 :在可行域内均匀随机抽样生成 ( N_p ) 个个体,每个个体为 ( D ) 维向量,分量满足 ( x_j \in [x_{\text{min}}(j), x_{\text{max}}(j)] )。
python
def init_population(Np, D, x_min, x_max):
# 1. 创建Np行D列的全零矩阵存储种群
population = np.zeros((Np, D))
# 2. 遍历每个个体i
for i in range(Np):
# 3. 逐分量生成[min, max]内的随机值:x_min + rand(0,1)*(max-min)
population[i, :] = x_min + np.random.rand(D) * (x_max - x_min)
return population # 返回shape=(Np,D)的初始种群
np.random.rand(D):生成 ( D ) 个**[0,1)均匀分布**的随机数(向量)。- 向量运算高效性:
x_min + rand*(x_max-x_min)是逐分量计算,无需循环遍历维度,这是numpy的优势。
2.2 适应度计算 evaluate_fitness
数学原理 :适应度是个体对环境的"适应程度",对于最小化问题,适应度直接等于目标函数值。
python
def evaluate_fitness(population, objective_func):
Np = population.shape[0] # 获取种群大小
fitness = np.zeros(Np) # 创建适应度向量
# 遍历每个个体,计算其目标函数值(适应度)
for i in range(Np):
fitness[i] = objective_func(population[i, :])
return fitness # 返回shape=(Np,)的适应度向量
- 通用性:通过传入
objective_func参数,支持任意目标函数的适配。
2.3 最优个体选择 select_best_individual
数学原理 :在当前种群中,找出适应度最小的个体(最小化问题)。
python
def select_best_individual(population, fitness):
# 1. 找到适应度最小的个体索引(argmin是numpy的最小值索引函数)
best_idx = np.argmin(fitness)
# 2. 深拷贝最优个体:避免后续修改种群时影响最优解的存储
best_individual = population[best_idx, :].copy()
best_fitness = fitness[best_idx] # 最优适应度
return best_individual, best_fitness # 返回最优个体和适应度
- 注意事项:使用
.copy()是关键,否则best_individual会随种群矩阵的修改而变化(numpy数组默认是浅拷贝)。
2.4 变异操作 mutate(核心!DE/rand/1策略)
数学原理 :采用 DE/rand/1 变异策略,通过3个随机个体的差分 生成变异向量:
v ∗ i = x ∗ r 1 + F ⋅ ( x ∗ r 2 − x ∗ r 3 ) \boldsymbol{v}*i = \boldsymbol{x}*{r_1} + F \cdot (\boldsymbol{x}*{r_2} - \boldsymbol{x}*{r_3}) v∗i=x∗r1+F⋅(x∗r2−x∗r3)
其中:
- ( r_1, r_2, r_3 ):随机选择的3个互不相同 且不等于当前个体i的索引(保证变异多样性);
- ( F \in (0,2) ):变异因子,控制差分步长(本例取0.5,经验值为0.4-0.9)。
python
def mutate(population, F):
Np, D = population.shape # 获取种群大小和维度
mutated = np.zeros_like(population) # 创建与种群同形状的变异矩阵
# 遍历每个个体i,生成对应的变异向量v_i
for i in range(Np):
# 1. 初始化r1/r2/r3为i,然后通过while循环确保:
# r1≠i,r2≠i且≠r1,r3≠i且≠r1且≠r2
r1 = r2 = r3 = i
while r1 == i: r1 = random.randint(0, Np-1)
while r2 == i or r2 == r1: r2 = random.randint(0, Np-1)
while r3 == i or r3 == r1 or r3 == r2: r3 = random.randint(0, Np-1)
# 2. 按DE/rand/1公式计算变异向量
mutated[i, :] = population[r1, :] + F * (population[r2, :] - population[r3, :])
return mutated # 返回变异向量矩阵
- 多样性保障:
while循环严格确保3个随机个体互不重复且非当前个体,避免变异退化。
2.5 交叉操作 crossover(核心!二项式交叉)
数学原理 :采用 二项式交叉(bin) 策略,将原个体 ( \boldsymbol{x}_i ) 与变异向量 ( \boldsymbol{v}_i ) 按概率混合,生成试验向量 ( \boldsymbol{u}_i )。规则:
- 随机选择一个强制交叉维度 ( j_{\text{rand}} )(保证试验向量至少有一个维度与原个体不同);
- 对其他维度 ( j ),若 ( \text{rand}(0,1) \leq Cr )(交叉概率)或 ( j = j_{\text{rand}} ),则 ( u_{ij} = v_{ij} ),否则 ( u_{ij} = x_{ij} )。
python
def crossover(population, mutated, Cr):
Np, D = population.shape # 获取种群大小和维度
trial = np.zeros_like(population) # 创建试验向量矩阵
# 遍历每个个体i,生成对应的试验向量u_i
for i in range(Np):
# 1. 随机选择一个强制交叉的维度
j_rand = random.randint(0, D-1)
# 2. 遍历每个维度j,按二项式交叉规则混合
for j in range(D):
if random.random() <= Cr or j == j_rand:
trial[i, j] = mutated[i, j] # 用变异向量的分量
else:
trial[i, j] = population[i, j] # 用原个体的分量
return trial # 返回试验向量矩阵
- 关键设计:强制交叉维度 ( j_{\text{rand}} ) 避免了试验向量与原个体完全一致的情况,确保了变异的有效性。
2.6 下一代选择 select_next_generation(贪心选择)
数学原理 :采用贪心策略 生成下一代种群------对每个个体 ( i ),比较原个体 ( \boldsymbol{x}_i ) 和试验向量 ( \boldsymbol{u}_i ) 的适应度,选择适应度更小 的个体进入下一代:
x i ( g + 1 ) = { u i if f ( u i ) ≤ f ( x i ) x i otherwise \boldsymbol{x}_i^{(g+1)} = \begin{cases} \boldsymbol{u}_i & \text{if } f(\boldsymbol{u}_i) \leq f(\boldsymbol{x}_i) \\ \boldsymbol{x}_i & \text{otherwise} \end{cases} xi(g+1)={uixiif f(ui)≤f(xi)otherwise
python
def select_next_generation(population, trial, fitness_org, fitness_trial):
Np = population.shape[0]
next_pop = np.zeros_like(population) # 下一代种群矩阵
next_fitness = np.zeros(Np) # 下一代适应度向量
# 遍历每个个体i,贪心选择更优个体
for i in range(Np):
if fitness_trial[i] <= fitness_org[i]:
next_pop[i, :] = trial[i, :]
next_fitness[i] = fitness_trial[i]
else:
next_pop[i, :] = population[i, :]
next_fitness[i] = fitness_org[i]
return next_pop, next_fitness # 返回下一代种群和适应度
- 收敛性保障:贪心选择确保种群的最优适应度不会随迭代变差,这是DE收敛的重要保证。
2.7 DE主函数 differential_evolution
数学流程:完整实现DE的迭代进化逻辑,包含以下核心步骤:
- 初始化种群 → 2. 计算初始适应度 → 3. 记录初始最优 → 4. 迭代进化(变异→交叉→适应度→选择→更新最优)→ 5. 输出结果。
python
def differential_evolution(objective_func, D, x_min, x_max, Np, F, Cr, Gmax):
# 步骤1: 初始化种群
population = init_population(Np, D, x_min, x_max)
# 步骤2: 计算初始种群的适应度
fitness = evaluate_fitness(population, objective_func)
# 步骤3: 记录初始最优个体和适应度
best_individual, best_fitness = select_best_individual(population, fitness)
history_best = [best_fitness] # 存储迭代历史最优适应度(用于收敛性分析)
# 步骤4: 迭代进化(共Gmax次)
for t in range(Gmax):
# 4.1 变异操作:生成变异向量矩阵
mutated = mutate(population, F)
# 4.2 交叉操作:生成试验向量矩阵
trial = crossover(population, mutated, Cr)
# 4.3 计算试验向量的适应度
fitness_trial = evaluate_fitness(trial, objective_func)
# 4.4 贪心选择:生成下一代种群
population, fitness = select_next_generation(population, trial, fitness, fitness_trial)
# 4.5 更新全局最优个体和适应度
current_best_ind, current_best_fit = select_best_individual(population, fitness)
if current_best_fit < best_fitness: # 仅当当前最优更优时更新全局最优
best_fitness = current_best_fit
best_individual = current_best_ind.copy() # 深拷贝避免后续修改
# 4.6 记录当前迭代的全局最优适应度
history_best.append(best_fitness)
# 步骤5: 输出结果
return best_individual, best_fitness, history_best
2.8 目标函数 objective_func
数学定义 :本例优化的二维函数 ( f(x,y)=x2+y2 ),是典型的无约束凸函数,全局最优解为 ( (0,0) ),适应度为0。
python
def objective_func(X):
# X是shape=(D,)的numpy数组,X[0]是x,X[1]是y
return X[0] ** 2 + X[1] ** 2 # 返回目标函数值(适应度)
3. 主运行模块解析(参数设置与结果输出)
python
if __name__ == "__main__":
# ------------------------- 问题参数设置 -------------------------
D = 2 # 问题维度(二维:x,y)
x_min = np.array([-5.0, -5.0]) # 变量下限向量:x∈[-5,5], y∈[-5,5]
x_max = np.array([5.0, 5.0]) # 变量上限向量
# ------------------------- DE算法参数设置 -------------------------
Np = 10 # 种群大小(经验值:10~100,维度高时增大)
F = 0.5 # 变异因子(经验值:0.4~0.9)
Cr = 0.7 # 交叉概率(经验值:0.5~0.95)
Gmax = 50 # 最大迭代次数(经验值:50~500)
# ------------------------- 运行DE算法 -------------------------
best_ind, best_fit, history = differential_evolution(
objective_func, D, x_min, x_max, Np, F, Cr, Gmax)
# ------------------------- 输出结果 -------------------------
print("最优个体:", best_ind) # 接近(0,0)的向量
print("最优适应度:", best_fit) # 接近0的数值
4. 代码运行逻辑与预期结果
4.1 运行逻辑链
初始化种群 → 计算初始适应度 → 记录初始最优 →
迭代1: 变异 → 交叉 → 试验适应度 → 贪心选择 → 更新最优 → 记录历史最优 →
迭代2: 重复上述步骤 →
...
迭代Gmax: 重复上述步骤 → 输出最终最优解
4.2 预期结果
由于目标函数是凸函数且DE参数合理,运行后会得到:
- 最优个体:接近
[0.0, 0.0]的numpy数组(如[-1.2e-6, 8.5e-7]); - 最优适应度:接近
0的极小值(如1.9e-12); - 历史最优
history:是一个从初始适应度(约25左右,因为初始个体在[-5,5]均匀分布,x²+y²的均值约为25)逐渐下降到接近0的列表,体现收敛过程。
5. 代码优缺点与建模优化建议
5.1 优点
- 结构清晰:模块化设计,每个子函数对应DE的一个核心步骤,易于理解和修改;
- 通用性强:通过传入目标函数参数,支持任意维度和目标函数的优化;
- 实现规范:严格遵循DE/rand/1/bin的标准数学定义,结果可靠。
5.2 待优化点(建模比赛加分项)
-
边界处理 :当前代码未限制变异/交叉后的向量在可行域内,若目标函数在边界外有极值,会导致错误。建议在变异/交叉后加入截断处理:
python# 变异后截断边界(可添加到mutate或crossover函数末尾) mutated = np.clip(mutated, x_min, x_max) -
自适应参数 :固定的 ( F ) 和 ( Cr ) 可能限制收敛速度。建议采用自适应策略,如随迭代次数动态调整:
- ( F ):在
0.2~1.0间按适应度分散度自适应; - ( Cr ):在
0.5~0.95间随迭代次数递增。
- ( F ):在
-
并行化计算 :当种群较大或目标函数复杂时,可将适应度计算改为多进程并行,利用多核CPU加速。
-
终止条件 :除了最大迭代次数,可添加适应度变化阈值作为提前终止条件(如连续10次迭代最优适应度变化小于1e-8,则终止)。
6. 代码在数学建模中的应用场景
这段代码可直接用于:
- 连续优化问题(如参数拟合、资源分配、工程设计等);
- 凸/非凸函数优化(DE对非凸函数的全局寻优能力较强);
- 高维问题 (只需修改
D和x_min/x_max即可适配)。
在建模比赛中,可将目标函数替换为比赛问题的数学模型,调整DE参数后即可快速得到最优解。
附录:DE/rand/1/bin策略的数学符号定义
| 符号 | 含义 | 代码对应 |
|---|---|---|
| \( N_p \) | 种群大小 | `Np` |
| \( D \) | 问题维度 | `D` |
| \( \boldsymbol{x}_i \) | 第i个个体 | `population[i,:]` |
| \( F \) | 变异因子 | `F` |
| \( Cr \) | 交叉概率 | `Cr` |
| \( G_{\text{max}} \) | 最大迭代次数 | `Gmax` |
| \( \boldsymbol{v}_i \) | 变异向量 | `mutated[i,:]` |
| \( \boldsymbol{u}_i \) | 试验向量 | `trial[i,:]` |
| \( f(\cdot) \) | 目标函数 | `objective_func` |