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

发布于 2024-08-12 06:27:11 字数 316 浏览 7 评论 0原文

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

是否有另一种方法可以更好地解决这个问题?

I'm doing some work with Genetic Algorithms and want to write my own GA classes. Since a GA can have different ways of doing selection, mutation, cross-over, generating an initial population, calculating fitness, and terminating the algorithm, I need a way to plug in different combinations of these. My initial approach was to have an abstract class that had all of these methods defined as pure virtual, and any concrete class would have to implement them. If I want to try out two GAs that are the same but with different cross-over methods for example, I would have to make an abstract class that inherits from GeneticAlgorithm and implements all the methods except the cross-over method, then two concrete classes that inherit from this class and only implement the cross-over method. The downside to this is that every time I want to swap out a method or two to try out something new I have to make one or more new classes.

Is there another approach that might apply better to this problem?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

混浊又暗下来 2024-08-19 06:27:11

我在实现 GA 框架时采取的方法如下:
创建以下类:
一代
遗传算法
遗传算法适配器
遗传算法参数
人口
个别

虽然我没有为各种操作实现策略模式,但我确信在 GeneticAlgorithm 实例上创建各种 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 实现,只需编写适当的适配器即可。对于不同的选择、交叉或变异实现,适配器可以调用它感兴趣的实现。我通常做的是在研究合适的策略时注释掉适配器中的不同想法。

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

The approach I took when implementing my GA framework was as follows:
Create the following classes:
Generation
GeneticAlgorithm
GeneticAlgorithmAdapter
GeneticAlgorithmParameters
Population
Individual

Although I didn't implement the strategy pattern for the various operations, I am sure it would be trivial to create various GA operation implementations as parameters on the GeneticAlgorithm instance.

The GeneticAlgorithm class captures the basic algorithm. It really just defines the various steps (population creation, individual randomisation, selection, cross-over, mutation, etc) and manages the populations of individuals as the algorithm runs. I imagine that here you could plug in different operations if you wanted to.

The real magic lies in the adapter. This is what adapts the problem domain (your specific sub classes of the individuals, with all their relevant data) to the genetic algorithm. I use generics a lot here so that the specific types of the population, parameters and individuals are passed into the implementation. This gives me intellisense and strong-type checking for the implementation of the adapter. The adapter basically needs to define how to perform the specific operations for the given individuals (and their genomes). For example, here is the interface for the adapter:

/// <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);
    }

I have found that this approach works nicely for me because I can easily reuse the GA implementation for different problem domains, just be writing the appropriate adapter. In terms of the different selection, cross-over or mutation implementations, the adapter can call the implementation that it is interested in. What I normally do is comment out different ideas in the adapter while I am investigating an appropriate strategy.

Hope this helps. I can give more guidance where necessary. It's hard to do the design justice like this.

长发绾君心 2024-08-19 06:27:11

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

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

I would approach the GA as a collaboration of many objects, rather than one big Class encapsulating the whole algorithm. Basically, you could have an abstract class for every big
point of variation, and concrete classes for every implementation choice you want. You then combine the concrete classes you want into many varieties of GA.

Also, you might want to familiarize yourself with the Strategy Pattern:
http://en.wikipedia.org/wiki/Strategy_pattern

再可℃爱ぅ一点好了 2024-08-19 06:27:11

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

I think you are over complicating things in your approach. Suggest you download the GAlib package. Even if you only pull the doc in html or pdf format. These libs have been around for a while and I am real sure that you will learn how to structure your lib from looking how is has been done in GAlib.

甜扑 2024-08-19 06:27:11

我的一些随机部分:

  • 您应该检查的一个项目(作为一种方法)是 watchmaker
  • 构建 GA 最难的部分是为您的问题找到合理的遗传表示,并为给定群体构建具有良好分布的适应度函数
  • 在处理(m)任何硬约束时,您可以考虑引入一个翻译器 处理硬约束的类,但代价是(可能的)垃圾 DNA 和一点性能

Some random bits from my part:

  • a project you should check out (as a approach) is watchmaker
  • the hardest part of building GAs is to find a sensible genetic representation for your problem and building a fitness functions with a good distribution of fitness for a given population
  • when dealing with (m)any hard constraints, you could think about introducing a Translator class wich handles the hard constraints, at the cost of (possible) junk DNA and a little performance
葬﹪忆之殇 2024-08-19 06:27:11

您的实现看起来像装饰器模式

your implementation looks like a Decorator pattern.

傾旎 2024-08-19 06:27:11

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

As people say, don't make it one giant class. That would be horrible. Encapsulate behavior in different classes. Strategy is a solution.
If you need examples download sources and examples of JGAP. It has support for Genetic Programming and Genetic Algorithms. You will see there nice nice design. Mutation,Crossover,Selection,Population,Gene - all this are separate classes. You just setup Configuration object where you initiate defined interfaces with implementations you want to use, pass proper algorithm parameters and you run it. Really package is huge, javadoc nice, and you can always look into the source or check mail group for some answers. When I was looking for GA package I saw GAlib and others but I think this one is most complete with really nice design.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文