如何构建遗传算法类层次结构?

时间:2009-11-27 17:43:30

标签: c++ inheritance oop polymorphism genetic-algorithm

我正在做遗传算法的一些工作,并希望编写我自己的GA类。由于GA可以有不同的方式进行选择,变异,交叉,生成初始种群,计算适应度和终止算法,我需要一种方法来插入这些的不同组合。我最初的方法是创建一个抽象类,将所有这些方法定义为纯虚拟,任何具体的类都必须实现它们。如果我想尝试两个相同但具有不同交叉方法的GA,我将不得不创建一个继承自GeneticAlgorithm的抽象类,并实现除交叉方法之外的所有方法,然后是两个具体类继承自此类并仅实现交叉方法。这样做的缺点是,每次我想换出一两个方法来尝试新的东西时,我必须创建一个或多个新类。

是否有其他方法可能更适用于此问题?

6 个答案:

答案 0 :(得分:2)

我在实施GA框架时采用的方法如下: 创建以下类: 代 GeneticAlgorithm GeneticAlgorithmAdapter GeneticAlgorithmParameters 人口 个体

虽然我没有为各种操作实现策略模式,但我确信在GeneticAlgorithm实例上创建各种GA操作实现作为参数是微不足道的。

GeneticAlgorithm类捕获基本算法。它实际上只是定义了各种步骤(人口创建,个体随机化,选择,交叉,变异等),并在算法运行时管理个体群体。我想在这里你可以插入不同的操作。

真正的魔力在于适配器。这就是将问题域(个体的特定子类及其所有相关数据)与遗传算法相适应的原因。我在这里使用泛型,以便将特定类型的种群,参数和个体传递给实现。这给了我intellisense和强类型检查适配器的实现。适配器基本上需要定义如何为给定的个体(及其基因组)执行特定操作。例如,以下是适配器的接口:

/// <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实现重用于不同的问题域,只需编写适当的适配器即可。在不同的选择,交叉或变异实现方面,适配器可以调用它感兴趣的实现。我通常做的是在调查适当的策略时在适配器中注释掉不同的想法。

希望这会有所帮助。我可以在必要时提供更多指导。像这样做设计公正很难。

答案 1 :(得分:1)

我会将GA作为许多对象的协作,而不是封装整个算法的一个大类。基本上,你可以为每个大的都有一个抽象类 变化点,以及您想要的每个实现选择的具体类。然后,将所需的具体类组合成多种GA。

此外,您可能希望熟悉策略模式: http://en.wikipedia.org/wiki/Strategy_pattern

答案 2 :(得分:1)

我认为你的方法过于复杂。建议你下载GAlib包。即使您只以html或pdf格式提取文档。这些库已经存在了一段时间,我确信你将学习如何构建你的lib,看看如何在GAlib中完成。

答案 3 :(得分:1)

我的一些随机位:

  • 您应该检查的项目(作为方法)是watchmaker
  • 构建GA的最难的部分是为您的问题找到一个合理的遗传表示,并为特定人群建立适当分布适应性的适应度函数
  • 在处理(m)任何硬约束时,你可以考虑引入一个 Translator 类来处理硬约束,代价是(可能的)垃圾DNA和一点性能

答案 4 :(得分:0)

您的实施看起来像Decorator pattern

答案 5 :(得分:0)

正如人们所说,不要把它变成一个巨大的阶级。那太可怕了。封装不同类中的行为。战略是一种解决方案 如果您需要示例下载JGAP的源代码和示例。它支持遗传编程和遗传算法。你会看到漂亮漂亮的设计。突变,交叉,选择,人口,基因 - 所有这些都是单独的类。您只需设置Configuration对象,您可以在其中使用要使用的实现启动已定义的接口,传递正确的算法参数并运行它。真正的包是巨大的,javadoc很好,你可以随时查看源或检查邮件组的一些答案。当我正在寻找GA包时,我看到了GAlib和其他人,但我认为这个是最完整的,非常漂亮的设计。