遗传算法是一种随机全局优化算法。连同人工神经网络,它可能是最流行和广为人知的生物学启发算法之一。该算法是一种进化算法,它通过自然选择,具有二进制表示形式和基于遗传重组和遗传突变的简单算子,来执行受进化生物学理论启发的优化过程。
在本教程中,您将发现遗传算法优化算法。完成本教程后,您将知道:
遗传算法是一种受进化启发的随机优化算法。
如何在Python中从头开始实现遗传算法。
如何将遗传算法应用于连续目标函数。
教程概述
本教程分为四个部分。他们是:
遗传算法
从零开始的遗传算法
OneMax的遗传算法
连续函数优化的遗传算法
遗传算法
遗传算法是一种随机全局搜索优化算法。它受到自然选择进化生物学理论的启发。具体来说,是将对遗传学的理解与理论相结合的新综合方法。
该算法使用遗传表示(位串),适应度(功能评估),基因重组(位串交叉)和突变(翻转位)的类似物。该算法的工作原理是首先创建固定大小的随机位串。重复算法的主循环固定次数的迭代,或者直到在给定迭代次数的最佳解决方案中看不到进一步的改善为止。该算法的一次迭代就像是进化的一代。
首先,使用目标函数评估总体位串(候选解决方案)。每个候选解决方案的目标函数评估被视为解决方案的适用性,可以将其最小化或最大化。然后,根据他们的健康状况选择父母。给定的候选解决方案可以用作父级零次或多次。一种简单有效的选择方法包括从总体中随机抽取k个候选者,并从适应性最好的组中选择成员。这就是所谓的锦标赛选择,其中k是一个超参数,并设置为诸如3的值。这种简单的方法模拟了成本更高的适应度成比例的选择方案。
父母被用作生成下一代候选点的基础,并且人口中的每个职位都需要一个父母。
然后将父母配对,并用来创建两个孩子。使用交叉算子执行重组。这涉及在位串上选择一个随机的分割点,然后创建一个子对象,该子对象的位从第一个父级到分割点直至从第二个父级到字符串的末尾。然后,为第二个孩子倒转此过程。
例如,两个父母:
parent1 = 00000
parent2 = 11111
可能会导致两个交叉孩子:
子1 = 00011
孩童2 = 11100
这称为单点交叉,并且操作员还有许多其他变体。
交叉概率是对每对父母概率应用的,这意味着在某些情况下,父母的副本将作为孩子而不是重组算子。交叉由设置为较大值(例如80%或90%)的超参数控制。变异涉及在已创建的子候选解决方案中翻转位。通常,将突变率设置为1 / L,其中L是位串的长度。
例如,如果问题使用具有20位的位串,则良好的默认突变率将是(1/20)= 0.05或5%的概率。
这定义了简单的遗传算法过程。这是一个很大的研究领域,并且对该算法进行了许多扩展。
现在我们已经熟悉了简单的遗传算法过程,下面让我们看一下如何从头开始实现它。
从零开始的遗传算法
在本节中,我们将开发遗传算法的实现。第一步是创建随机位串。我们可以使用布尔值True和False,字符串值“ 0”和“1”,或者整数值0和1。在这种情况下,我们将使用整数值。我们可以使用randint()
函数生成一个范围内的整数值数组,并且可以将范围指定为从0开始且小于2的值,例如 0或1。为了简化起见,我们还将候选解决方案表示为列表而不是NumPy数组。可以如下创建初始的随机位串填充,其中“n_pop”
是控制填充大小的超参数,“n_bits”
是定义单个候选解决方案中位数的超参数:
# initial population of random bitstring
pop = [randint(0, 2, n_bits).tolist() for _ in range(n_pop)]
接下来,我们可以枚举固定数量的算法迭代,在这种情况下,该迭代由名为“ n_iter”的超参数控制。
...
# enumerate generations
for gen in range(n_iter):
...
算法迭代的第一步是评估所有候选解。
我们将使用一个名为Objective()
的函数作为通用目标函数,并对其进行调用以获取适合度得分,我们将其最小化。
# evaluate all candidates in the population
scores = [objective(c) for c in pop]
然后,我们可以选择将用于创建子代的父代。
锦标赛选择过程可以实现为一种函数,该函数可以获取总体并返回一个选定的父级。使用默认参数将k值固定为3,但是您可以根据需要尝试使用不同的值。
# tournament selection
def selection(pop, scores, k=3):
# first random selection
selection_ix = randint(len(pop))
for ix in randint(0, len(pop), k-1):
# check if better (e.g. perform a tournament)
if scores[ix] < scores[selection_ix]:
selection_ix = ix
return pop[selection_ix]
然后,我们可以为总体中的每个位置调用一次此函数,以创建父母列表。
# select parents
selected = [selection(pop, scores) for _ in range(n_pop)]
然后,我们可以创建下一代。
这首先需要执行交叉的功能。此功能将占用两个父级和交叉率。交叉率是一个超参数,它确定是否执行交叉,如果不进行交叉,则将父级复制到下一代。这是一个概率,通常具有接近1.0的较大值。
下面的crossover()
函数使用范围为[0,1]
的随机数来实现交叉以确定是否执行了交叉,然后如果要进行交叉则选择有效的分割点。
# crossover two parents to create two children
def crossover(p1, p2, r_cross):
# children are copies of parents by default
c1, c2 = p1.copy(), p2.copy()
# check for recombination
if