引言
最近有些朋友总来问我有关遗传算法的东西,我是在大学搞数学建模的时候接触过一些最优化和进化算法方面的东西,以前也写过几篇博客记录过,比如遗传算法的C语言实现(一):以非线性函数求极值为例和C语言实现粒子群算法(PSO)一等,如果对原理有兴趣的话可以去我的博客具体查看:Lyrichu’s Blog。所以突发奇想,干脆把以前写的一些进化算法比如遗传算法(GA),粒子群算法(PSO),模拟退火算法(SA)以及最近看的基于梯度的一些优化算法比如Gradient Descent,SGD,Momentum等整理一下,写成一个python库,方便那些有需要的朋友使用。断断续续花了几天的时间,初步完成了一些基本功能,这里简单介绍一下。
sopt 简介
sopt是simple optimization的简称,目前我已经将代码托管到pypi,地址是sopt,可以直接通过pip命令下载安装使用,由于项目只依赖numpy,所以在windows和linux环境下安装都很方便,直接
pip install sopt
即可。项目的github地址是sopt。目前sopt包含的优化方法如下:
- 遗传算法(Genetic Algorithm,GA)
- 粒子群算法(Particle Swarm Optimization,PSO)
- 模拟退火算法(Simulated Anealing,SA)
- 随机游走算法(Random Walk):
- 梯度下降法(Gradient Descent,GD)
- 动量优化算法(Momentum)
- 自适应梯度算法(AdaGrad)
- RMSProp
- Adam
由于只是一个初步的版本,后续如果有时间的话,会加上更多的优化算法进去。目前所有的优化算法暂时只支持连续实函数的优化;除了基于梯度的几个优化算法,GA、PSO、SA以及Random Walk都同时支持无约束优化,线性约束优化以及非线性约束优化。具体我会在下面详细说明。sopt 使用详解以及实例演示
1.SGA 使用
SGA是Simple Genetic Algorithm的简称,是最基本的遗传算法。其编码方式采用二进制编码,选择方法采用轮盘赌法,交叉方法采用单点交叉,变异方式采用均匀变异,默认是求函数的最小值。下面是sopt中SGA的一个简单使用实例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16from sopt.SGA import SGA
from math import sin
def func1(x):
return (x[0]-1)**2 + (sin(x[1])-0.5)**4 + 2
if __name__ == '__main__':
sga = SGA.SGA(func = func1,func_type = 'min',variables_num = 2,
lower_bound = 0,upper_bound = 2,generations = 20,
binary_code_length = 10)
# run SGA
sga.run()
# show the SGA optimization result in figure
sga.save_plot()
# print the result
sga.show_result()
运行结果如下:
1 | -------------------- SGA config is: -------------------- |
用图像展示为图1所示:

上面定义的目标函数为f(x1,x2)=(x1−1)2+(sin(x2)−0.5)4+2,其中0<x1,x2<2,函数的最小值点为(1,0.5236),最小值为2,与寻找到的最小值点(1.00488759 0.45356794)以及最小值2.00003849823336是非常接近的。
SGA类的全部参数定义如下:
- lower_bound:一个int或者float的数字,或者是长度为
variables_num
的ndarray,表示每个变量的下界,如果是一个数字的话,我们认为所有的下界都是一样的(必填); - upper_bound:一个int或者float的数字,或者是长度为
variables_num
的ndarray,表示每个变量的上界,如果是一个数字的话,我们认为所有的上界都是一样的(必填); - variables_num:表示目标函数变量的个数(必填);
- func(必填):表示目标函数;
- cross_rate:GA的交叉率,默认为0.7;
- mutation_rate:变异率,默认为0.1;
- population_size:种群数目,默认为100;
- generations:进化代数,默认是200;
- binary_code_length:GA二进制编码的长度,默认是10;
- func_type:函数优化类型,求最小值取值为’min’,求最大值取值为’max’,默认是’min’;
2. GA 使用
GA是相比SGA更加一般的遗传算法实现,其对于编码方式,选择方式,交叉方式以变异方式等会有更多的支持。首先还是看一个例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47from sopt.GA.GA import GA
from sopt.util.functions import *
from sopt.util.ga_config import *
from sopt.util.constraints import *
class TestGA:
def __init__(self):
self.func = quadratic11
self.func_type = quadratic11_func_type
self.variables_num = quadratic11_variables_num
self.lower_bound = quadratic11_lower_bound
self.upper_bound = quadratic11_upper_bound
self.cross_rate = 0.8
self.mutation_rate = 0.05
self.generations = 200
self.population_size = 100
self.binary_code_length = 20
self.cross_rate_exp = 1
self.mutation_rate_exp = 1
self.code_type = code_type.binary
self.cross_code = False
self.select_method = select_method.proportion
self.rank_select_probs = None
self.tournament_num = 2
self.cross_method = cross_method.uniform
self.arithmetic_cross_alpha = 0.1
self.arithmetic_cross_exp = 1
self.mutation_method = mutation_method.uniform
self.none_uniform_mutation_rate = 1
#self.complex_constraints = [constraints1,constraints2,constraints3]
self.complex_constraints = None
self.complex_constraints_method = complex_constraints_method.penalty
self.complex_constraints_C = 1e6
self.M = 1e8
self.GA = GA(**self.__dict__)
def test(self):
start_time = time()
self.GA.run()
print("GA costs %.4f seconds!" % (time()-start_time))
self.GA.save_plot()
self.GA.show_result()
if __name__ == '__main__':
TestGA().test()
上面代码的运行结果为:
1 | GA costs 6.8320 seconds! |
其中sopt.util.functions
预定义了一些测试函数,quadratic11
是一个11元变量的二次函数,函数原型为:quadratic11(x1,…,x11)=(x1−1)2+(x2−2)2+…+(x11−11)2+1,其中1≤x1,…,x11≤11,函数的最小值点在(1,2,…,11)处取得,最小值为1。另外还定义了其他几个测试函数为:
- quadratic50:和quadratic11类似,只是变量个数变为50,取值范围变为1-50;
- quadratic100:和quadratic11类似,只是变量个数变为100,取值范围变为1-100;
- quadratic500:和quadratic11类似,只是变量个数变为500,取值范围变为1-500;
- quadratic1000:和quadratic1000类似,只是变量个数变为1000,取值范围变为1-1000;
- Rosenbrock:函数原型为:Rosenbrock(x1,x2)=100(x21−x2)2+(1−x1)2,其中−2.048≤x1,x2≤2.048,这个函数有很多局部极小值点,函数在(-2.048,-2.048)处取得最大值;
- shubert2函数:函数定义为,shubert2(x1,x2)=∏2i=1(∑5j=1(jcos((j+1)∗xi+j)),其中−10≤x1,x2≤10,函数有760个局部极小值点,全局极小值为-186.731,这里为了将适应度函数变换为正,我们在原函数的基础上加上1000,这样函数的全局极小值点就变为813.269;
- shubert函数,将shubert2函数中的两个变量拓展为n个变量(∏ni=1)就可以得到一般的shubert函数了。
对于每一个优化方法,我们都预定了一个形如xx_config
的模块,其中定义了该优化方法的一些常用默认参数,比如ga_config
中就定义了一些GA的一些常用优化参数,ga_config.basic_config
定义了一些基础参数设置,比如basic_config.generations
是一个默认进化代数,basic_config.mutation_rate
是默认的变异参数;而ga_config.cross_method
则预定义了所有支持的交叉方法,比如cross_method.uniform
表示均匀交叉,cross_method.one_point
表示单点交叉等;ga_config.mutation_method
等也是类似的。有了这些预定义变量,可以免去我们手动输入很多参数取值以及传入方法字符串的麻烦(有时候可能会写错)。
观察上面的运行结果,我们发现对于quadratic11函数,最终找到的全局极小值为2.2685208668743204,和真实的全局极小值点1已经比较接近了;运行耗时6秒多,似乎有些长,这是因为我们种群数目设为100,进化代数设为200,比较大,而且又是采用二进制20位编码,再加上python脚本语言的运行效率问题,所以稍慢。为了做一个对比,这里我们将目标函数从quadratic11变为Rosenbrock函数,其他参数设置保持不变,得到的结果如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32GA costs 1.7245 seconds!
-------------------- GA config is: --------------------
population_size:100
lower_bound:[-2.048, -2.048]
mutation_rate_exp:1
select_method:proportion
code_type:binary
global_generations_step:200
generations:200
mutation_method:uniform
complex_constraints_method:penalty
binary_code_length:20
cross_method:uniform
arithmetic_cross_alpha:0.1
func:<function Rosenbrock at 0x7fe5fd538a60>
upper_bound:[2.048, 2.048]
cross_code:False
mutation_rate:0.05
cross_rate_exp:1
complex_constraints_C:1000000.0
cross_rate:0.8
variables_num:2
M:100000000.0
complex_constraints:None
none_uniform_mutation_rate:1
tournament_num:2
arithmetic_cross_exp:1
func_type:max
-------------------- GA caculation result is: --------------------
global best target generation index/total generations:75/200
global best point:[-2.04776953 -2.04537109]
global best target:3901.4655271502425
图2是每代最优值的计算结果:

替换成Rosenbrock函数之后,函数的运行时间从6秒多减少到1秒多(函数变量个数明显减少了),这说明GA的运行时间与目标函数的变量个数是显著相关的。最后找到的全局极大值点为(-2.04776953,-2.04537109)和真实全局极大值点(-2.048,-2.048)已经很接近了。
SGA类是GA类的父类,所以GA类和SGA类有一些公共的属性,比如
generations
,population_size
,func_type
等。和SGA一样的参数这里就不再列举了,如下是GA特有的一些参数:
- cross_rate_exp:cross_rate指数递增的参数取值,即变异率按照rt=r0βt,这里rt表示t次迭代的交叉率,r0是初始的交叉率,β就是这里的
cross_rate_exp
,默认取值为1,一般设置为一个比1稍大的数字,比如1.0001等; - mutation_rate_exp:mutation_rate指数递增参数取值,具体含义和
cross_rate_exp
类似; - code_type:采用什么样的编码方式,有三种取值:’binary’表示二进制编码(默认),’gray’表示采用格雷编码,’real’表示采用实数编码。其中格雷编码是一种改进的二进制编码,其优点在于进行交叉、变异等操作时,改变了染色体基因的某几个位置,二进制编码对应的实数值可能会发生非常大的变化,而格雷编码一般不会。二进制编码与格雷编码的转换可以参考附录;
- cross_code:这是一个布尔值,表示是否对二进制编码或格雷码采用交叉编码的方式。具体说明参考附录;
- select_method:选择操作方法,有如下的6种取值:
- ‘proportion’:表示比例选择(轮盘赌法)
- ‘keep_best’:表示保留最优个体
- ‘determinate_sampling’:表示确定性采样
- ‘rssr’:remainder stochastic sampling with replacement的缩写,表示无放回余数随机选择
- ‘rank’:表示排序选择
- ‘stochastic_tournament’:表示随机锦标赛法(以上6种选择方式详细说明请参考附录)
- rank_select_probs:仅当select_method取值为’rank’时,该参数才起作用,表示采用排序选择时,适应度从低到高每个个体被选择的概率,默认为None,采用pi=i∑nj=1j,i=1,2,…,n的方式计算,其中n表示种群个数,pi表示第i个个体被选择的概率,所有概率之和为1,如果取值不为None,你应该传入一个size为
population_size
的ndarray,所有元素按照递增排序,数组和为1; - tournament_num:如果select_method取值为’stochastic_tournament’,该值表示锦标赛竞争者数量;
- cross_method:交叉方法,有如下4种取值:
- ‘one_point’:表示单点交叉
- ‘two_point’:表示双点交叉
- ‘uniform’:表示均匀交叉
- ‘arithmetic’:表示算术交叉,只有当编码采用实数编码时,才能使用算术交叉
- arithmetic_cross_alpha:算术交叉的系数,算术交叉的公式为:xnew1=αx1+(1−α)x2,xnew2=αx2+(1−α)x1,这里的α即表示
arithmetic_cross_alpha
,其默认值为0.1; - ‘arithmetic_cross_exp’:算术交叉系数按照指数递增,即αt=αrt,这里αt表示第t代的算术交叉系数,α是初始交叉系数,r即是这里的
arithmetic_cross_exp
,默认取值为1,一般取一个比1稍大的数,比如1.0001,t是进化代数; - ‘mutation_method’:变异方法,有如下5种取值:
- simple:即简单变异,随机选择一个染色体上的基因,按照变异概率进行变异
- uniform:即均匀变异,每个染色体上的每个基因都按照变异概率进行变异
- boundary:边界变异,每个基因进行变异以后的取值只能去边界值,比如说各以0.5的概率取得其上界和下界,这种变异方式仅适用于实数编码的情况,一般在目标函数的最优点靠近边界时使用
- none_uniform:非均匀变异,我们不是取均匀分布的随机数去替换原来的基因,而是在原来基因的基础上做一点微小的随机扰动,扰动以后的结果作为新的基因值,具体来说,我们采用的是这样的变异方式:1)if random(0,1) = 0,x‘k=xk+△(t,Ukmax−xk);2)if random(0,1) = 1,xk‘=xk−△(t,xk−Umink) 其中 △(t,y)=y(1−r(1−t/T)b),r是0-1均匀分布的一个随机数,T是最大进化代数,b是一个系统参数,表示随机扰动对于进化代数的依赖长度,默认值为1
- gaussian:高斯变异,即使用高斯分布去替代均匀分布来进行变异,由高斯分布的特点可以知道,这种变异方式也是在原个体区域附近的某个局部区域进行重点搜索,这里高斯分布的均值μ定义为μ=Ukmin+Ukmax2,标准差σ=Ukmax−Ukmin6
- none_uniform_mutation_rate:即均匀分布
none_uniform
中定义的系统参数b; complex_constraints:复杂约束,默认值为None,即没有复杂约束,只有简单的边界约束,如果有复杂约束,其取值应该是[func1,func2,…,funcn],
其中funci表示第i个复杂约束函数名,比如对于一个复杂约束函数func1:x21+x22<3,其复杂约束函数应该这样定义:1
2
3
4def func1(x):
x1 = x[0]
x2 = x[1]
return x1**2 + x2**2 - 3complex_constraints_method:复杂约束求解的方法,默认是
penalty
即惩罚函数法,暂时不支持其他的求解方式;- complex_constraints_C:采用
penalty
求解复杂约束的系数C,比如对于某一个约束x21+x22<3,GA在求解过程中,违反了该约束,即解满足x21+x22≥3,那么我们对目标函数增加一个惩罚项: C(x21+x22−3),C一般取一个很大的正数,默认值为106。
GA类的参数很多可以调节,所以相比之下使用起来更加麻烦,特别是对于复杂函数的寻优,默认参数未必是最好的,可能需要你根据目标函数的特点自行尝试,选择最优的参数组合。3. PSO 使用
PSO算法的原理可以参考我之前的博客C语言实现粒子群算法(PSO)一和C语言实现粒子群算法(PSO)二,这里不再赘述。还是先看一个实例代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38from time import time
from sopt.util.functions import *
from sopt.util.pso_config import *
from sopt.PSO.PSO import PSO
from sopt.util.constraints import *
class TestPSO:
def __init__(self):
self.func = quadratic11
self.func_type = quadratic11_func_type
self.variables_num = quadratic11_variables_num
self.lower_bound = quadratic11_lower_bound
self.upper_bound = quadratic11_upper_bound
self.c1 = basic_config.c1
self.c2 = basic_config.c2
self.generations = 200
self.population_size = 100
self.vmax = 1
self.vmin = -1
self.w = 1
self.w_start = 0.9
self.w_end = 0.4
self.w_method = pso_w_method.linear_decrease
#self.complex_constraints = [constraints1,constraints2,constraints3]
self.complex_constraints = None
self.complex_constraints_method = complex_constraints_method.loop
self.PSO = PSO(**self.__dict__)
def test(self):
start_time = time()
self.PSO.run()
print("PSO costs %.4f seconds!" %(time()-start_time))
self.PSO.save_plot()
self.PSO.show_result()
if __name__ == '__main__':
TestPSO().test()
运行结果为:
1 | PSO costs 1.1731 seconds! |

上面的代码意图应该是非常明显的,目标函数是quadratic11,最终求得的最小值点几乎就是全局最小值点。下面是PSO类中所有参数的具体定义:
- variables_num:变量个数(必填);
- lower_bound:一个int或者float的数字,或者是长度为
variables_num
的ndarray,表示每个变量的下界,如果是一个数字的话,我们认为所有的下界都是一样的(必填); - upper_bound:一个int或者float的数字,或者是长度为
variables_num
的ndarray,表示每个变量的上界,如果是一个数字的话,我们认为所有的上界都是一样的(必填); - func:目标函数名(必填);
- func_type:函数优化类型,求最小值取值为’min’,求最大值取值为’max’,默认是’min’;
- c1:PSO 参数c1,默认值为1.49445;
- c2:PSO 参数c2,默认值为1.49445;
- generations:进化代数,默认值为100;
- population_size:种群数量,默认为50;
- vmax:粒子最大速度,默认值为1;
- vmin:粒子最小速度,默认值为-1;
- w:粒子惯性权重,默认为1;
- w_start:如果不是使用恒定的惯性权重话,比如使用权重递减策略,w_start表示初始权重;
- w_end:相应的,w_end表示末尾权重;
- w_method:权重递减的方式,有如下5种取值:
- ‘constant’:权重恒定
- ‘linear_decrease’:线性递减,即wt=wend+(wstart−wend)(T−t)T,其中T表示最大进化代数,wt表示第t代权重
- ‘square1_decrease’:第一种平方递减,即:wt=wstart−(wstart−wend)(tT)2
- ‘square2_decrease’:第二种平方递减,即:wt=wstart−(wstart−wend)(2tT−(tT)2)
- ‘exp_decrease’:指数递减,即:wt=wend(wstartwend)11+10tT
- complex_constraints:复杂约束,默认为None,具体含义参考GA类的complex_constraints
- complex_constraints_method:求解复杂约束的方法,默认为’loop’,即如果解不满足复杂约束,则再次随机产生解,直到满足约束,暂时不支持其他的求解方式。
4. SA 使用
SA,即模拟退火算法,是基于概率的一种寻优方法,具体原理可以参考我的博客模拟退火算法(SA)求解TSP 问题(C语言实现)。sopt中SA的使用实例如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33from time import time
from sopt.util.functions import *
from sopt.Optimizers.SA import SA
from sopt.util.sa_config import *
from sopt.util.constraints import *
class TestSA:
def __init__(self):
self.func = Rosenbrock
self.func_type = Rosenbrock_func_type
self.variables_num = Rosenbrock_variables_num
self.lower_bound = Rosenbrock_lower_bound
self.upper_bound = Rosenbrock_upper_bound
self.T_start = 100
self.T_end = 1e-6
self.q = 0.9
self.L = 100
self.init_pos = None
#self.complex_constraints = [constraints1,constraints2,constraints3]
self.complex_constraints_method = complex_constraints_method.loop
self.SA = SA(**self.__dict__)
def test(self):
start_time = time()
self.SA.run()
print("SA costs %.4f seconds!" %(time()-start_time))
self.SA.save_plot()
self.SA.show_result()
if __name__ == '__main__':
TestSA().test()
运行结果如下:
1 | SA costs 0.2039 seconds! |

SA类的具体参数含义如下:
- variables_num:变量个数(必填);
- lower_bound:一个int或者float的数字,或者是长度为
variables_num
的ndarray,表示每个变量的下界,如果是一个数字的话,我们认为所有的下界都是一样的(必填); - upper_bound:一个int或者float的数字,或者是长度为
variables_num
的ndarray,表示每个变量的上界,如果是一个数字的话,我们认为所有的上界都是一样的(必填); - func:目标函数名(必填);
- func_type:函数优化类型,求最小值取值为’min’,求最大值取值为’max’,默认是’min’;
- T_start:初始温度,默认值为100;
- T_end:末尾温度,默认值为10−6;
- q:退火系数,默认值为0.9,一般取一个在[0.9,1)之间的数字;
- L:每个温度时的迭代次数,即链长,默认值为100;
- init_pos:初始解的取值,默认值为None,此时初始解是随机生成的,或者你也可以指定一个用ndarray表示的初始解;
- complex_constraints:复杂约束,默认为None,表示没有约束,具体定义同GA 的 complex_constraints;
- complex_constraints_method:求解复杂约束的方法,默认为’loop’,即如果解不满足复杂约束,则再次随机产生解,直到满足约束,暂时不支持其他的求解方式。
5. Random Walk 使用
Random Walk 即随机游走算法,是一种全局随机搜索算法,具体原理可以参考我的博客介绍一个全局最优化的方法:随机游走算法(Random Walk)。sopt中Random Walk的使用实例如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33from time import time
from sopt.util.functions import *
from sopt.util.constraints import *
from sopt.util.random_walk_config import *
from sopt.Optimizers.RandomWalk import RandomWalk
class TestRandomWalk:
def __init__(self):
self.func = quadratic50
self.func_type = quadratic50_func_type
self.variables_num = quadratic50_variables_num
self.lower_bound = quadratic50_lower_bound
self.upper_bound = quadratic50_upper_bound
self.generations = 100
self.init_step = 10
self.eps = 1e-4
self.vectors_num = 10
self.init_pos = None
# self.complex_constraints = [constraints1,constraints2,constraints3]
self.complex_constraints = None
self.complex_constraints_method = complex_constraints_method.loop
self.RandomWalk = RandomWalk(**self.__dict__)
def test(self):
start_time = time()
self.RandomWalk.random_walk()
print("random walk costs %.4f seconds!" %(time() - start_time))
self.RandomWalk.save_plot()
self.RandomWalk.show_result()
if __name__ == '__main__':
TestRandomWalk().test()
运行结果为:
1 | Finish 1 random walk! |

经过实验发现,Random Walk 具有非常强的全局寻优能力,对于quadratic50这种具有50个变量的复杂目标函数,它也可以很快找到其全局最优点,而且运行速度也很快。RandomWalk类的具体参数含义如下:
- variables_num:变量个数(必填);
- lower_bound:一个int或者float的数字,或者是长度为
variables_num
的ndarray,表示每个变量的下界,如果是一个数字的话,我们认为所有的下界都是一样的(必填); - upper_bound:一个int或者float的数字,或者是长度为
variables_num
的ndarray,表示每个变量的上界,如果是一个数字的话,我们认为所有的上界都是一样的(必填); - func:目标函数名(必填);
- func_type:函数优化类型,求最小值取值为’min’,求最大值取值为’max’,默认是’min’;
- generations:每个step的最大迭代次数,默认是100;
- init_step:初始步长(step),默认值为10.0;
- eps:终止迭代的步长,默认为10−4;
- vectors_num:使用 improved_random_walk时随机产生向量的个数,默认为10;
- init_pos:初始解的取值,默认值为None,此时初始解是随机生成的,或者你也可以指定一个用ndarray表示的初始解;
- complex_constraints:复杂约束,默认为None,表示没有约束,具体定义同GA 的 complex_constraints;
- complex_constraints_method:求解复杂约束的方法,默认为’loop’,即如果解不满足复杂约束,则再次随机产生解,直到满足约束,暂时不支持其他的求解方式。
RandomWalk 除了提供基本的random_walk函数之外,还提供了一个更加强大的improved_random_walk函数,后者的全局寻优能力要更强。6. 求解带复杂约束的目标函数
上面所述的各种优化方法求解的都是变量仅有简单边界约束(形如a≤xi≤b),下面介绍如何使用各种优化方法求解带有复杂约束条件的目标函数。其实,求解方法也非常简单,以GA为例,下面的例子即对Rosenbrock函数求解了带有三个复杂约束条件的最优值:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47from time import time
from sopt.GA.GA import GA
from sopt.util.functions import *
from sopt.util.ga_config import *
from sopt.util.constraints import *
class TestGA:
def __init__(self):
self.func = Rosenbrock
self.func_type = Rosenbrock_func_type
self.variables_num = Rosenbrock_variables_num
self.lower_bound = Rosenbrock_lower_bound
self.upper_bound = Rosenbrock_upper_bound
self.cross_rate = 0.8
self.mutation_rate = 0.1
self.generations = 300
self.population_size = 200
self.binary_code_length = 20
self.cross_rate_exp = 1
self.mutation_rate_exp = 1
self.code_type = code_type.real
self.cross_code = False
self.select_method = select_method.proportion
self.rank_select_probs = None
self.tournament_num = 2
self.cross_method = cross_method.uniform
self.arithmetic_cross_alpha = 0.1
self.arithmetic_cross_exp = 1
self.mutation_method = mutation_method.uniform
self.none_uniform_mutation_rate = 1
self.complex_constraints = [constraints1,constraints2,constraints3]
self.complex_constraints_method = complex_constraints_method.penalty
self.complex_constraints_C = 1e8
self.M = 1e8
self.GA = GA(**self.__dict__)
def test(self):
start_time = time()
self.GA.run()
print("GA costs %.4f seconds!" % (time()-start_time))
self.GA.save_plot()
self.GA.show_result()
if __name__ == '__main__':
TestGA().test()
运行结果如下:
1 | GA costs 1.9957 seconds! |

上面的constraints1,constraints2,constraints3是三个预定义的约束条件函数,其定义分别为:constraints1:x21+x22−6≤0;constraints2:x1+x2≤0;constraints3:−2−x1−x2≤0,函数原型为:
1 | def constraints1(x): |
其实观察可以发现,上面的代码和原始的GA实例代码唯一的区别,就是其增加了self.complex_constraints = [constraints1,constraints2,constraints3]
这样一句,对于其他的优化方法,其都定义了complex_constraints
和complex_constraints_method
这两个属性,只要传入相应的约束条件函数列表以及求解约束条件的方法就可以求解带复杂约束的目标函数了。比如我们再用Random Walk求解和上面一样的带三个约束的Rosenbrock函数,代码及运行结果如下:
1 | from time import time |
运行结果:
1 | Finish 1 random walk! |

可以发现Random Walk 求解得到的最优解要比GA好,而且运行时间更快,经过实验发现,在所有的优化方法中,不论是求解带复杂约束还是不带复杂约束条件的目标函数,求解效果大体上排序是:Random Walk > PSO > GA > SA 。所以当你在求解具体问题时,不妨多试几种优化方法,然后择优选择。
7. 基于梯度的系列优化方法
上面所述的各种优化方法,比如GA,PSO,SA等都是基于随机搜索的优化算法,其计算是不依赖于目标函数的具体形式,也不需要知道其梯度的,更加传统的优化算法是基于梯度的算法,比如经典的梯度下降(上升)法(Gradient Descent)以及其一系列变种。下面就简要介绍sopt中GD,Momentum,AdaGrad,RMSProp以及Adam的实现。关于这些基于梯度的优化算法的具体原理,可以参考我之前的一篇博文深度学习中常用的优化方法。另外需要注意,以下所述的基于梯度的各种优化算法,一般都是用在无约束优化问题里面的,如果是有约束的问题,请选择上面其他的优化算法。下面是GradientDescent的一个使用实例:
1 | from time import time |
运行结果为:
1 | Gradient Descent costs 14.3231 seconds! |

下面简要说明以下GradientDescent,Momentum等类中的主要参数,像
func
,variables_num
等含义已经解释很多次了,不再赘述,这里主要介绍各类特有的一些参数。
- GradientDescent类:
- lr:学习率,默认是10−3;
- epochs:迭代次数,默认是100;
- Momentum类:
- lr:学习率,默认是10−3;
- beta:Momentum的β参数,默认是0.9;
- epochs:迭代次数,默认是100;
- AdaGrad类:
- lr:学习率,默认是10−3;
- eps:极小的一个正数(防止分母为0),默认为10−8;
- epochs:迭代次数,默认是100;
- RMSProp类:
- lr:学习率,默认是10−3;
- beta:RMSProp的β参数,默认是0.9;
- eps:极小的一个正数(防止分母为0),默认为10−8;
- epochs:迭代次数,默认是100;
- Adam类:
- lr:学习率,默认是10−3;
- beta1:Adam的β1参数,默认是0.5;
- beta2:Adam的β2参数,默认是0.9;
- eps:极小的一个正数(防止分母为0),默认为10−8;
- epochs:迭代次数,默认是100;
这里额外说一句,Adam原文作者给出的最优超参数为β1=0.9,β2=0.999,但是我在实际调试过程中发现,β1=0.5,β2=0.9才能达到比较好的效果,具体原因目前不是很清楚。如果想要学习各个优化函数类的具体用法,还可以使用from sopt.test import *
导入预定义的示例测试,来运行观察结果。比如下面的代码就运行了PSO的一个示例测试:1
2from sopt.test import test_PSO
test_PSO.TestPSO().test()
结果:
1 | PSO costs 3.4806 seconds! |
附录
附录会简要说明上文提到的一些概念,待有空更新。。。
To do …
- GA的并行化计算
- 小生境GA
- GA,PSO等求解离散函数最优解
- GA,PSO等实际应用举例
- 其他的一些最优化方法,比如牛顿法,拟牛顿法等
- 其他