遗传算法与深度学习实战(14)——进化策略详解与实现
- 0. 前言
- 1. 进化策略
- 1.1 进化策略原理
- 1.2 将进化策略应用于函数逼近
- 2. 实现进化策略
- 小结
- 系列链接
0. 前言
进化策略 (Evolutionary Strategies
, ES
) 是进化计算和遗传方法的扩展,增加了控制亚基因或表现型(称为策略)的内容。这些策略只是一个额外的向量,用于控制或影响突变算子。这为 ES
提供了更有效地解决各种复杂问题的能力,包括函数逼近。在本节中,我们将使用 ES
探索函数逼近问题。简单起见,首先尝试逼近已知连续多项式解的函数参数。然后,再转向更复杂的不连续解,观察 ES
的表现。
1. 进化策略
1.1 进化策略原理
进化策略 (Evolutionary Strategies
, ES
) 是一类基于生物进化原理的优化算法,主要用于解决复杂的优化问题。ES
与“纯粹”的遗传算法不同之处在于,个体携带额外的基因序列或向量,称为策略。在进化过程中,这个策略向量学习调整并应用更好、更精细的突变到个体进化中。我们已经知道,突变和突变率就像深度学习 ( Deep learning
, DL
) 中的学习率,突变控制了进化过程中种群的变异能力。突变率越高,种群的变异能力和多样性就越大。在迭代过程中控制和学习突变率,能够更有效地确定解决方案。
1.2 将进化策略应用于函数逼近
进化策略与其他进化算法(如遗传算法)的主要区别在于其更专注于实值优化问题,并且强调策略参数的自适应性,这使得它们在处理复杂的、多维的优化问题时表现出色。
接下来,实现一个 ES
算法来逼近已知解,还将讨论如何随着迭代的进行学习优化突变,使种群更好地收敛和逼近解。
2. 实现进化策略
(1) 首先导入所需库,并定义超参数,IND_SIZE
值控制解决多项式函数的维度,或者说基因大小;MAX_TIME
超参数用于控制运行进化的总时间,这是控制进化运行时间的有效方法,而并不依赖于世代数;最后,策略分配超参数 MIN_VALUE、MAX_VALUE
、MIN_STRATEGY
和 MAX_STRATEGY
控制突变向量:
import array
import random
import time
import numpy as np
from deap import algorithms
from deap import base
from deap import benchmarks
from deap import creator
from deap import tools
import matplotlib.pyplot as plt
from IPython.display import clear_output
IND_SIZE = 6
NGEN = 1000
MIN_VALUE = 4
MAX_VALUE = 5
MIN_STRATEGY = 0.5
MAX_STRATEGY = 3
CXPB = .6
MUTPB = .3
GEN_OUTPUT = 25
MAX_TIME = 5
(2) 构建初始目标数据集。在本节中,使用三个方程进行评估:一个 5
次多项式连续函数和两个不连续函数( abs
和 step
)。使用范围参数处理数据以生成 X
和 Y
值,并将它们压缩到列表 data
中。最后,绘制折线图来可视化目标函数:
equation_form = "polynomial" #@param ["polynomial", "abs", "step"]
X_START = -5
X_END = 5
X_STEP = 0.5
def equation(x):
if equation_form == "polynomial":
return (2*x + 3*x**2 + 4*x**3 + 5*x**4 + 6*x**5 + 10)
elif equation_form == "abs":
return abs(x)
else:
return np.where(x>1, 1, 0)
X = np.array([x for x in np.arange(X_START, X_END, X_STEP)])
Y = equation(X)
data = list(zip(X, Y))
plt.plot(X,Y)
下图显示了 5
次多项式函数以及 step
和 abs
函数的折线图。首先针对连续多项式函数,观察 ES
如何高效地逼近解决方案。另外两个函数代表不连续函数,它们是不可微分的,因此通常无法通过深度学习 ( Deep learning
, DL
) 网络解决。
(3) 接下来,设定 creator
,观察进化策略 (Evolutionary Strategies
, ES
) 与典型遗传算法 (Genetic Algorithms
, GA
) 的区别。注册 FitnessMin
和 Individual
,不同之处在于,在注册 Individual
时,添加了一个额外的属性 Strategy
,设置为 None
,最后注册列表类型 (array.array
) 为 double(d)
的 Strategy
:
creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
creator.create("Individual", array.array, typecode="d", fitness=creator.FitnessMin, strategy=None)
creator.create("Strategy", array.array, typecode="d")
(4) 定义 generateES()
和 checkStrategy()
函数,generateES()
函数使用传入的参数创建个体,其中 icls
表示用于构建个体的类,scls
表示用于构建策略的类,checkStrategy()
函数使用装饰器模式检查策略,以确保向量保持在某个最小值以上。通过初始化个体设置,基因序列中的每个随机值都设置在最小值和最大值之间。同样,策略的初始化遵循相同的模式,但使用不同的最小/最大值。这样就生成了大小分别为 IND_SIZE
和 NDIM
的两个向量,其中一个用于定义主要基因序列,另一个用于在交叉和突变操作期间应用于每个基因的学习突变和交叉率:
def generateES(icls, scls, size, imin, imax, smin, smax):
ind = icls(random.uniform(imin, imax) for _ in range(size))
ind.strategy = scls(random.uniform(smin, smax) for _ in range(size))
return ind
def checkStrategy(minstrategy):
def decorator(func):
def wrappper(*args, **kargs):
children = func(*args, **kargs)
for child in children:
for i, s in enumerate(child.strategy):
if s < minstrategy:
child.strategy[i] = minstrategy
return children
return wrappper
return decorator
(5) 设置 toolbox
,使用 generatES()
函数初始化一个个体,其中输入参数包括 creator.Individual
、creator.Strategy
、IND_SIZE
、MIN_VALUE
、MAX_VALUE
、MIN_STRATEGY
和 MAX_STRATEGY
。交叉操作使用一个特殊的 ES
混合操作符来组合父代,而不是常规交叉中的替换操作符。同样,突变操作使用 ES
对数正态操作来控制包含策略的突变,然后对交叉和突变操作应用一个装饰器 checkStrategy
,装饰器为输入提供了一个过滤机制:
print(checkStrategy(MIN_STRATEGY))
toolbox = base.Toolbox()
toolbox.register("individual", generateES, creator.Individual, creator.Strategy,
IND_SIZE, MIN_VALUE, MAX_VALUE, MIN_STRATEGY, MAX_STRATEGY)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
toolbox.register("mate", tools.cxESBlend, alpha=0.1)
toolbox.register("mutate", tools.mutESLogNormal, c=1.0, indpb=0.03)
toolbox.register("select", tools.selTournament, tournsize=3)
toolbox.decorate("mate", checkStrategy(MIN_STRATEGY))
toolbox.decorate("mutate", checkStrategy(MIN_STRATEGY))
(6) 在 toolbox
中添加适应度评估函数。函数 pred()
用于通过循环遍历个体基因,并将它们与 x
的 i
次方相乘来得出一个值;另一个函数 fitness()
循环遍历 data
中的 x
、y
值,使用 pred
函数来确定均方误差 (Mean-square Error
, MSE
),返回的最终值是平均 MSE
,在本节中,我们通过将数据集作为参数传递给 register()
函数来将数据集传递给 evaluate()
函数:
def pred(ind, x):
y_ = 0.0
for i in range(1,IND_SIZE):
y_ += ind[i-1]*x**i
y_ += ind[IND_SIZE-1]
return y_
def fitness(ind, data):
mse = 0.0
for x, y in data:
y_ = pred(ind, x)
mse += (y - y_)**2
return mse/len(data),
# fitness eval
toolbox.register("evaluate", fitness, data=data)
def plot_fitness(g, best, pop, logbook):
Y_ = np.array([pred(best, x) for x in X])
clear_output()
best = [round(b,1) for b in best]
print(f"Generation {g}, Best {best}")
print(logbook.stream)
fits = [f.fitness.values[0] for f in pop]
plt.hist(fits)
plt.show()
plt.scatter(X,Y)
plt.plot(X,Y_, 'r')
plt.show()
(7) 执行进化过程,首先定义超参数 MU
和 LAMBDA
,它们表示父代的种群和派生后代的数量。这意味着在选择过程中,选择 MU
个父代,使用 DEAP
算法 eaMuCommaLambda()
生成 LAMBDA
个后代。在本节中,我们不仅限制总的代数,还限制了算法运行的时间。如果运行的时间(以秒为单位)超过了阈值 MAX_TIME
,则进化停止,跟踪运行的时间使我们能够评估比较进化计算方法:
MU, LAMBDA = 250, 1000
pop = toolbox.population(n=MU)
hof = tools.HallOfFame(1)
stats = tools.Statistics(lambda ind: ind.fitness.values)
stats.register("avg", np.mean)
stats.register("std", np.std)
stats.register("min", np.min)
stats.register("max", np.max)
start = time.time()
for g in range(NGEN):
pop, logbook = algorithms.eaMuCommaLambda(pop, toolbox, mu=MU, lambda_=LAMBDA,
cxpb=CXPB, mutpb=MUTPB, ngen=1, stats=stats, halloffame=hof, verbose=False)
if (g+1) % GEN_OUTPUT == 0:
plot_fitness(g, hof[0], pop, logbook)
end = time.time()
if end-start > MAX_TIME:
break
best = [round(i,1) for i in hof[0]]
print("Best individual is ", best, round(hof[0].fitness.values[0],2))
下图显示了将进化运行到最长时间( 5
秒)后的最终输出示例。可以返回并修改进化运行的时间,看看是否可以获得更好的结果,或者尝试其他函数如 abs()
和 step()
。我们可能会发现 ES
在不连续解中的效果不如预期,这主要是由于算法对函数的逼近方式有关。
然而,将 ES
与经典遗传算法进行比较,我们会发现它在连续问题上收敛更快。这是因为 ES
通过学习的突变和交叉策略来管理种群的多样性。可以通过完成以下问题进一步理解进化策略的基本概念:
- 修改目标函数,然后重新运行,观察效果
- 修改代码中的超参数,观察每种变化对结果演化的影响
虽然 ES
是对经典遗传算法的一个很好的改进,但它仍然缺乏快速高效地收敛不连续解的能力。
小结
进化策略与遗传算法的主要区别在于其专注于实值优化问题和策略参数的自适应性,适用于复杂的目标函数和高维空间中的优化问题。在工程设计、机器人、经济学等领域,进化策略已被广泛应用,为在复杂环境中搜索最优解决方案提供了有效的工具。在本节中,我们使用 DEAP
实现了进化策略探索函数逼近问题。
系列链接
遗传算法与深度学习实战(1)——进化深度学习
遗传算法与深度学习实战(2)——生命模拟及其应用
遗传算法与深度学习实战(3)——生命模拟与进化论
遗传算法与深度学习实战(4)——遗传算法详解与实现
遗传算法与深度学习实战(5)——遗传算法中常用遗传算子
遗传算法与深度学习实战(6)——遗传算法框架DEAP
遗传算法与深度学习实战(7)——DEAP框架初体验
遗传算法与深度学习实战(8)——使用遗传算法解决N皇后问题
遗传算法与深度学习实战(9)——使用遗传算法解决旅行商问题
遗传算法与深度学习实战(10)——使用遗传算法重建图像
遗传算法与深度学习实战(11)——遗传编程详解与实现
遗传算法与深度学习实战(12)——粒子群优化详解与实现
遗传算法与深度学习实战(13)——协同进化详解与实现