如何构建遗传算法类层次结构?
Posted
技术标签:
【中文标题】如何构建遗传算法类层次结构?【英文标题】:How to structure a Genetic Algorithm class hierarchy? 【发布时间】:2009-11-27 17:43:30 【问题描述】:我正在使用遗传算法做一些工作,并想编写自己的 GA 类。由于 GA 可以有不同的方法来进行选择、变异、交叉、生成初始种群、计算适应度和终止算法,我需要一种方法来插入这些不同的组合。我最初的方法是创建一个抽象类,将所有这些方法定义为纯虚拟,并且任何具体类都必须实现它们。例如,如果我想尝试两个相同但具有不同交叉方法的 GA,我将必须创建一个继承自 GeneticAlgorithm 并实现除交叉方法之外的所有方法的抽象类,然后是两个具体类从这个类继承并且只实现交叉方法。这样做的缺点是,每次我想换出一两个方法来尝试新的东西时,我都必须创建一个或多个新类。
是否有另一种方法可以更好地解决这个问题?
【问题讨论】:
【参考方案1】:我在实施 GA 框架时采用的方法如下: 创建以下类: 一代 遗传算法 遗传算法适配器 遗传算法参数 人口 个人
虽然我没有为各种操作实现策略模式,但我相信在遗传算法实例上创建各种 GA 操作实现作为参数将是微不足道的。
GeneticAlgorithm 类捕获基本算法。它实际上只是定义了各个步骤(种群创建、个体随机化、选择、交叉、突变等),并在算法运行时管理个体种群。我想如果你愿意的话,你可以在这里插入不同的操作。
真正的魔力在于适配器。这就是使问题域(个人的特定子类,及其所有相关数据)适应遗传算法的原因。我在这里大量使用泛型,以便将人口、参数和个体的特定类型传递到实现中。这给了我对适配器实现的智能感知和强类型检查。适配器基本上需要定义如何为给定的个体(及其基因组)执行特定的操作。例如,这里是适配器的接口:
/// <summary>
/// The interface for an adapter that adapts a domain problem so that it can be optimised with a genetic algorithm.
/// It is a strongly typed version of the adapter.
/// </summary>
/// <typeparam name="TGA"></typeparam>
/// <typeparam name="TIndividual"></typeparam>
/// <typeparam name="TPopulation"></typeparam>
public interface IGeneticAlgorithmAdapter<TGA, TIndividual, TGeneration, TPopulation> : IGeneticAlgorithmAdapter
where TGA : IGeneticAlgorithm
where TIndividual : class, IIndividual, new()
where TGeneration : class, IGeneration<TIndividual>, new()
where TPopulation : class, IPopulation<TIndividual, TGeneration>, new()
/// <summary>
/// This gets called before the adapter is used for an optimisation.
/// </summary>
/// <param name="pso"></param>
void InitialiseAdapter(TGA ga);
/// <summary>
/// This initialises the individual so that it is ready to be used for the genetic algorithm.
/// It gets randomised in the RandomiseIndividual method.
/// </summary>
/// <param name="ga">The genetic algorithm that is running.</param>
/// <param name="individual">The individual to initialise.</param>
void InitialiseIndividual(TGA ga, TIndividual individual);
/// <summary>
/// This initialises the generation so that it is ready to be used for the genetic algorithm.
/// </summary>
/// <param name="ga">The genetic algorithm that is running.</param>
/// <param name="generation">The generation to initialise.</param>
void InitialiseGeneration(TGA ga, TGeneration generation);
/// <summary>
/// This initialises the population so that it is ready to be used for the genetic algorithm.
/// </summary>
/// <param name="ga">The genetic algorithm that is running.</param>
/// <param name="population">The population to initialise.</param>
void InitialisePopulation(TGA ga, TPopulation population);
void RandomiseIndividual(TGA ga, TIndividual individual);
void BeforeIndividualUpdated(TGA ga, TIndividual individual);
void AfterIndividualUpdated(TGA ga, TIndividual individual);
void BeforeGenerationUpdated(TGA ga, TGeneration generation);
void AfterGenerationUpdated(TGA ga, TGeneration generation);
void BeforePopulationUpdated(TGA ga, TPopulation population);
void AfterPopulationUpdated(TGA ga, TPopulation population);
double CalculateFitness(TGA ga, TIndividual individual);
void CloneIndividualValues(TIndividual from, TIndividual to);
/// <summary>
/// This selects an individual from the population for the given generation.
/// </summary>
/// <param name="ga">The genetic algorithm that is running.</param>
/// <param name="generation">The generation to select the individual from.</param>
/// <returns>The selected individual.</returns>
TIndividual SelectIndividual(TGA ga, TGeneration generation);
/// <summary>
/// This crosses over two parents to create two children.
/// </summary>
/// <param name="ga">The genetic algorithm that is running.</param>
/// <param name="parentsGeneration">The generation that the parent individuals belong to.</param>
/// <param name="childsGeneration">The generation that the child individuals belong to.</param>
/// <param name="parent1">The first parent to cross over.</param>
/// <param name="parent2">The second parent to cross over.</param>
/// <param name="child">The child that must be updated.</param>
void CrossOver(TGA ga, TGeneration parentsGeneration, TIndividual parent1, TIndividual parent2, TGeneration childsGeneration, TIndividual child);
/// <summary>
/// This mutates the given individual.
/// </summary>
/// <param name="ga">The genetic algorithm that is running.</param>
/// <param name="generation">The individuals generation.</param>
/// <param name="individual">The individual to mutate.</param>
void Mutate(TGA ga, TGeneration generation, TIndividual individual);
/// <summary>
/// This gets the size of the next generation to create.
/// Typically, this is the same size as the current generation.
/// </summary>
/// <param name="ga">The genetic algorithm that is running.</param>
/// <param name="currentGeneration">The current generation.</param>
/// <returns>The size of the next generation to create.</returns>
int GetNextGenerationSize(TGA ga, TGeneration currentGeneration);
/// <summary>
/// This gets whether a cross over should be performed when creating a child from this individual.
/// </summary>
/// <param name="ga">The genetic algorithm that is running.</param>
/// <param name="currentGeneration">The current generation.</param>
/// <param name="individual">The individual to determine whether it needs a cross over.</param>
/// <returns>True to perform a cross over. False to allow the individual through to the next generation un-altered.</returns>
bool ShouldPerformCrossOver(TGA ga, TGeneration generation, TIndividual individual);
/// <summary>
/// This gets whether a mutation should be performed when creating a child from this individual.
/// </summary>
/// <param name="ga">The genetic algorithm that is running.</param>
/// <param name="currentGeneration">The current generation.</param>
/// <param name="individual">The individual to determine whether it needs a mutation.</param>
/// <returns>True to perform a mutation. False to allow the individual through to the next generation un-altered.</returns>
bool ShouldPerformMutation(TGA ga, TGeneration generation, TIndividual individual);
我发现这种方法对我很有效,因为我可以轻松地为不同的问题域重用 GA 实现,只需编写适当的适配器。对于不同的选择、交叉或变异实现,适配器可以调用它感兴趣的实现。我通常做的是在我研究合适的策略时注释掉适配器中的不同想法。
希望这会有所帮助。我可以在必要时提供更多指导。很难做到这样的设计正义。
【讨论】:
【参考方案2】:我会将 GA 视为许多对象的协作,而不是一个封装整个算法的大类。基本上,你可以为每一个大的 变化点,以及您想要的每个实现选择的具体类。然后,您将所需的具体类组合成多种 GA。
另外,您可能想熟悉一下策略模式: http://en.wikipedia.org/wiki/Strategy_pattern
【讨论】:
【参考方案3】:我认为你的方法过于复杂了。建议您下载GAlib 包。即使您只以 html 或 pdf 格式提取文档。这些库已经存在了一段时间,我确信您将通过查看 GAlib 中的完成方式来学习如何构建您的库。
【讨论】:
【参考方案4】:我的一些随机位:
您应该检查的项目(作为一种方法)是watchmaker 构建 GA 最困难的部分是为您的问题找到合理的遗传表示,并为给定的群体构建具有良好分布的适应度函数适应度 在处理 (m) 任何硬约束时,您可以考虑引入一个处理硬约束的 Translator 类,但代价是(可能的)垃圾 DNA 和一点性能【讨论】:
【参考方案5】:您的实现看起来像 Decorator pattern。
【讨论】:
我认为装饰器是添加功能,而不是改变功能【参考方案6】:正如人们所说,不要把它变成一个大类。那将是可怕的。将行为封装在不同的类中。战略是一种解决方案。 如果您需要示例下载源和JGAP 的示例。它支持遗传编程和遗传算法。你会看到那里很好的设计。 Mutation,Crossover,Selection,Population,Gene - 所有这些都是独立的类。您只需设置配置对象,在其中启动定义的接口与您想要使用的实现,传递适当的算法参数并运行它。确实包很大,javadoc 很好,您可以随时查看源代码或查看邮件组以获取一些答案。当我在寻找 GA 包时,我看到了 GAlib 和其他包,但我认为这个包是最完整的,设计非常好。
【讨论】:
以上是关于如何构建遗传算法类层次结构?的主要内容,如果未能解决你的问题,请参考以下文章