Logo
Section 1 / 6

使用遗传算法模拟进化

学习如何使用遗传算法来模拟进化。

导入

使用遗传算法模拟进化

学习如何使用遗传算法来模拟进化。

使用指标追踪进展

到本章为止,我们一直专注于学习遗传算法的细节和复杂机制。我们了解了如何表示解决方案、如何评估解决方案,以及如何通过选择、交叉、变异和重插入来改变种群。

我们解决的所有问题的目标都是优化某个目标函数。在所有编写的算法中,我们定义问题,配置算法,并运行算法直到获得解。虽然获取解的过程最为重要,但有时我们也需要一种方法来追踪进化过程随时间的变化。

...
追踪你的进展

想象一下,如果我们想分析种群整体适应度随时间的增长情况。或者,我们可能希望可视化每代之间适应度分布的变化。甚至,当算法返回结果时,我们还可能希望追踪最优解的基因谱系。

指标非常重要,因为它们提供了有助于我们决定如何重新配置或调整算法的洞察。如果没有详细的指标进行分析,就很难做出决策或识别算法中的瓶颈。

在本章中,我们将学习如何集成一些工具,使我们能够在框架中追踪各种指标。我们将围绕遗传算法的一个独特应用来构建这些工具。

模拟进化

遗传算法一个更有趣的应用尚未被发现,那就是它们模拟真实进化过程的能力。遗传算法受到进化的启发,尽管引导遗传算法的内部过程(如选择、交叉和变异)仅与科学有松散关联,但它们仍然可以为进化过程提供有价值的见解。

假设你被一位生物学家委托编写一个关于老虎在不同环境条件下如何进化的模拟程序。显然,在沙漠与北极苔原环境中生存所需的特征截然不同。目标是编写一个模拟程序,展示老虎在热带和苔原两种环境中经过1000代的基本进化过程。此外,模拟还需要记录一些有价值的数据,例如平均适应度、平均年龄、基因谱系,以及每一代中最适应的染色体。

利用遗传算法和一些关于老虎的知识,这项任务可以迅速完成。

首先,在 scripts 目录中创建一个名为 tiger_simulation.exs 的新文件。接着,在 tiger_simulation.exs 中创建一个 Problem 的模板,如下所示:

1defmodule TigerSimulation do 2 @behaviour Problem 3 alias Types.Chromosome 4 5 @impl true 6 def genotype, do: # ... 7 8 @impl true 9 def fitness_function(c), do: # ... 10 11 @impl true 12 def terminate?(population, generation), do: # ... 13end

此时,这段代码应该已经很熟悉了。接下来,我们需要确定如何将我们的模拟整合到 Problem 行为中。

将老虎表示为染色体

第一步是确定如何将老虎表示为染色体。

虽然没有绝对正确或错误的答案,最简单的方法是使用一个二进制基因型来表示单个老虎所具有的多种特征。这些特征以某种方式影响老虎在不同环境中的生存能力。

我们将监控下表所示的八个特征:

老虎特征

特征01
体型较小较大
游泳能力弱强
毛色浅色深色
脂肪储备较少较多
活动周期昼行性夜行性
狩猎范围较小较大
毛发厚度较薄较厚
尾巴长度较短较长

由于我们监控八个特征,因此每个染色体将由八个二进制基因组成。实现 genotype 函数如下:

1def genotype do 2 genes = for _ <- 1..8, do: Enum.random(0..1) 3 %Chromosome{genes: genes, size: 8} 4end

正如我们之前看到的,这是一个大小为 8 的基本二进制基因型。初始种群将包含具有不同特征组合的老虎。

接下来,我们需要确定如何根据老虎在热带和苔原环境中的特征来评估其适应度。

在不同环境中评估适应度

记住,目标是确定老虎在每种环境中的进化方式。由于每个特征在不同环境中的重要性不同,我们需要根据环境的不同来分别评估染色体。

最简单的方法是为每个特征分配权重或分数,以表明该特征对生存是有利还是不利的。权重或分数的大小表示该特征在特定环境中的相对重要性。

下表显示了我们在两种环境中为每个特征分配的分数:

两种环境下的特征分数

特征热带苔原
体型0.01.0
游泳能力3.03.0
毛色2.0-2.0
脂肪储备-1.01.0
活动周期0.50.5
狩猎范围1.02.0
毛发厚度-1.01.0
尾巴长度0.00.0

注意: 请注意,某些分数为负值,表示对生存有负面影响;某些为零,表示对生存无影响;某些为正值,表示对生存有正面影响。

现在,将这些分数转换为适应度函数,将以下代码添加到 tiger_simulation.exs 中:

1def fitness_function(chromosome) do 2 tropic_scores = [0.0, 3.0, 2.0, -1.0, 0.5, 1.0, -1.0, 0.0] 3 tundra_scores = [1.0, 3.0, -2.0, -1.0, 0.5, 2.0, 1.0, 0.0] 4 traits = chromosome.genes 5 6 traits 7 |> Enum.zip(tropic_scores) 8 |> Enum.map(fn {t, s} -> t*s end) 9 |> Enum.sum() 10end

在第6至9行,适应度函数将特征与其对应的分数配对,相乘后返回总和,以表示老虎在给定环境中的生存能力。为简化起见,在测试不同环境时,可将 tropic_scores 替换为 tundra_scores。在实际应用中,我们应能动态切换并同时运行多个实验。

完成并运行模拟

我们最后要做的就是定义终止条件。我们希望在1000代后停止进化。实现终止条件如下:

1def terminate?(_population, generation), do: generation == 1000

接下来,在 TigerSimulation 模块下方添加以下内容:

1tiger = Genetic.run(TigerSimulation, 2 population_size: 20, 3 selection_rate: 0.9, 4 mutation_rate: 0.1) 5 6IO.write("\n") 7IO.inspect(tiger)

将 TigerSimulation 传入 Genetic.run/2,并指定种群大小为 20,选择率为 0.9,变异率为 0.1。

记住,Genetic.run/2 在满足终止条件后返回种群中最优的染色体。这意味着 tiger 将是经过1000代后种群中的当前最优染色体。

现在,在热带环境中运行遗传算法(使用 tropic_scores):

No configuration found for ID: ce814a56195289f24509833029de61c0595a2316b46de059b12fa159a6b45f96

注意: 若要使用 tundra_scores 运行上述应用,请进入 scripts/tiger_simulation.exs,将第18行的代码 Enum.zip(tropic_scores) 替换为 Enum.zip(tundra_scores),然后点击“运行”按钮进行测试。

目前出现的唯一警告信息来自 opts 参数和未使用的 tundra_scores 列表。如果将 tropic_scores 替换为 tundra_scores,则会出现 tropic_scores 未被使用的警告。

我们已成功分析并生成了每种环境下的最适老虎。可以看出,在热带环境中,最优老虎体型较大,游泳能力强,毛色深,狩猎范围较广。而在苔原环境中,最优老虎体型较大,游泳能力强,毛色较浅,脂肪储备较少。

达到这一结果可能看起来并不令人印象深刻——我们或许可以通过直觉或简单的暴力搜索自行推导出这些结论。然而,这个实验最重要的部分并非最终结果,而是结果产生之前的过程。我们需要一种方法能够“窥探”其中的细节。

替换与重插入

使用 ETS 记录统计信息