简体   繁体   English

用Java编写的GA

[英]GA written in Java

I am attempting to write a Genetic Algorithm based on techniques I had picked up from the book "AI Techniques for Game Programmers" that uses a binary encoding and fitness proportionate selection (also known as roulette wheel selection) on the genes of the population that are randomly generated within the program in a two-dimensional array. 我试图根据我从“用于游戏程序员的AI技术”一书中选择的技术编写遗传算法,该技术使用二进制编码和适应度比例选择(也称为轮盘赌选择)对人群的基因进行在程序中以二维数组随机生成。

I recently came across a piece of pseudocode and have tried to implement it, but have come across some problems with the specifics of what I need to be doing. 我最近遇到了一个伪代码 ,并试图实现它,但是我遇到了一些问题,我需要做些什么。 I've checked a number of books and some open-source code and am still struggling to progress. 我检查过一些书籍和一些开源代码,但仍在努力取得进展。 I understand that I have to get the sum of the total fitness of the population, pick a random number between the sum and zero, then if the number is greater than the parents to overwrite it, but I am struggling with the implementation of these ideas. 我明白我必须得到总人口的总体适应度的总和,在总和与零之间选择一个随机数,然后如果数字大于父母要覆盖它,但我正在努力实施这些想法。

Any help in the implementation of these ideas would be very much appreciated as my Java is rusty. 由于我的Java生疏,因此非常感谢任何帮助实现这些想法。

The following is a complete outline of the GA. 以下是GA的完整概述。 I made sure to be very detailed so it can be easily coded to C/Java/Python/.. 我确保非常详细,因此可以很容易地编码为C / Java / Python / ..

/* 1. Init population */
POP_SIZE = number of individuals in the population
pop = newPop = []
for i=1 to POP_SIZE {
    pop.add( getRandomIndividual() )
}

/* 2. evaluate current population */
totalFitness = 0
for i=1 to POP_SIZE {
    fitness = pop[i].evaluate()
    totalFitness += fitness
}

while not end_condition (best fitness, #iterations, no improvement...)
{
    // build new population
    // optional: Elitism: copy best K from current pop to newPop
    while newPop.size()<POP_SIZE
    {
        /* 3. roulette wheel selection */
        // select 1st individual
        rnd = getRandomDouble([0,1]) * totalFitness
        for(idx=0; idx<POP_SIZE && rnd>0; idx++) {
            rnd -= pop[idx].fitness
        }
        indiv1 = pop[idx-1]
        // select 2nd individual
        rnd = getRandomDouble([0,1]) * totalFitness
        for(idx=0; idx<POP_SIZE && rnd>0; idx++) {
            rnd -= pop[idx].fitness
        }
        indiv2 = pop[idx-1]

        /* 4. crossover */
        indiv1, indiv2 = crossover(indiv1, indiv2)

        /* 5. mutation */
        indiv1.mutate()
        indiv2.mutate()

        // add to new population
        newPop.add(indiv1)
        newPop.add(indiv2)
    }
    pop = newPop
    newPop = []

    /* re-evaluate current population */
    totalFitness = 0
    for i=1 to POP_SIZE {
        fitness = pop[i].evaluate()
        totalFitness += fitness
    }
}

// return best genome
bestIndividual = pop.bestIndiv()     // max/min fitness indiv

Note that currently you're missing a fitness function (depends on the domain). 请注意,目前您缺少适应度函数(取决于域)。 The crossover would be a simple one point crossover (since you are using a binary representation). 交叉将是一个简单的单点交叉(因为您使用的是二进制表示)。 Mutation could be a simple flip of a bit at random. 突变可以是随机的一点点简单的翻转。


EDIT : I have implemented the above pseudocode in Java taking into consideration your current code structure and notations (keep in mind i am more of ac/c++ guy than java). 编辑 :我已经在Java中实现了上面的伪代码考虑到你当前的代码结构和符号(请记住,我比ac / c ++更多的是java)。 Note this is in no way the most efficient or complete implementation, I admit I wrote it rather quickly: 请注意,这绝不是最有效或最完整的实现,我承认我写得很快:

Individual.java Individual.java

import java.util.Random;

public class Individual
{
    public static final int SIZE = 500;
    private int[] genes = new int[SIZE];
    private int fitnessValue;

    public Individual() {}

    public int getFitnessValue() {
        return fitnessValue;
    }

    public void setFitnessValue(int fitnessValue) {
        this.fitnessValue = fitnessValue;
    }

    public int getGene(int index) {
        return genes[index];
    }

    public void setGene(int index, int gene) {
        this.genes[index] = gene;
    }

    public void randGenes() {
        Random rand = new Random();
        for(int i=0; i<SIZE; ++i) {
            this.setGene(i, rand.nextInt(2));
        }
    }

    public void mutate() {
        Random rand = new Random();
        int index = rand.nextInt(SIZE);
        this.setGene(index, 1-this.getGene(index));    // flip
    }

    public int evaluate() {
        int fitness = 0;
        for(int i=0; i<SIZE; ++i) {
            fitness += this.getGene(i);
        }
        this.setFitnessValue(fitness);

        return fitness;
    }
}

Population.java Population.java

import java.util.Random;

public class Population
{
    final static int ELITISM_K = 5;
    final static int POP_SIZE = 200 + ELITISM_K;  // population size
    final static int MAX_ITER = 2000;             // max number of iterations
    final static double MUTATION_RATE = 0.05;     // probability of mutation
    final static double CROSSOVER_RATE = 0.7;     // probability of crossover

    private static Random m_rand = new Random();  // random-number generator
    private Individual[] m_population;
    private double totalFitness;

    public Population() {
        m_population = new Individual[POP_SIZE];

        // init population
        for (int i = 0; i < POP_SIZE; i++) {
            m_population[i] = new Individual();
            m_population[i].randGenes();
        }

        // evaluate current population
        this.evaluate();
    }

    public void setPopulation(Individual[] newPop) {
        // this.m_population = newPop;
        System.arraycopy(newPop, 0, this.m_population, 0, POP_SIZE);
    }

    public Individual[] getPopulation() {
        return this.m_population;
    }

    public double evaluate() {
        this.totalFitness = 0.0;
        for (int i = 0; i < POP_SIZE; i++) {
            this.totalFitness += m_population[i].evaluate();
        }
        return this.totalFitness;
    }

    public Individual rouletteWheelSelection() {
        double randNum = m_rand.nextDouble() * this.totalFitness;
        int idx;
        for (idx=0; idx<POP_SIZE && randNum>0; ++idx) {
            randNum -= m_population[idx].getFitnessValue();
        }
        return m_population[idx-1];
    }

    public Individual findBestIndividual() {
        int idxMax = 0, idxMin = 0;
        double currentMax = 0.0;
        double currentMin = 1.0;
        double currentVal;

        for (int idx=0; idx<POP_SIZE; ++idx) {
            currentVal = m_population[idx].getFitnessValue();
            if (currentMax < currentMin) {
                currentMax = currentMin = currentVal;
                idxMax = idxMin = idx;
            }
            if (currentVal > currentMax) {
                currentMax = currentVal;
                idxMax = idx;
            }
            if (currentVal < currentMin) {
                currentMin = currentVal;
                idxMin = idx;
            }
        }

        //return m_population[idxMin];      // minimization
        return m_population[idxMax];        // maximization
    }

    public static Individual[] crossover(Individual indiv1,Individual indiv2) {
        Individual[] newIndiv = new Individual[2];
        newIndiv[0] = new Individual();
        newIndiv[1] = new Individual();

        int randPoint = m_rand.nextInt(Individual.SIZE);
        int i;
        for (i=0; i<randPoint; ++i) {
            newIndiv[0].setGene(i, indiv1.getGene(i));
            newIndiv[1].setGene(i, indiv2.getGene(i));
        }
        for (; i<Individual.SIZE; ++i) {
            newIndiv[0].setGene(i, indiv2.getGene(i));
            newIndiv[1].setGene(i, indiv1.getGene(i));
        }

        return newIndiv;
    }


    public static void main(String[] args) {
        Population pop = new Population();
        Individual[] newPop = new Individual[POP_SIZE];
        Individual[] indiv = new Individual[2];

        // current population
        System.out.print("Total Fitness = " + pop.totalFitness);
        System.out.println(" ; Best Fitness = " + 
            pop.findBestIndividual().getFitnessValue());

        // main loop
        int count;
        for (int iter = 0; iter < MAX_ITER; iter++) {
            count = 0;

            // Elitism
            for (int i=0; i<ELITISM_K; ++i) {
                newPop[count] = pop.findBestIndividual();
                count++;
            }

            // build new Population
            while (count < POP_SIZE) {
                // Selection
                indiv[0] = pop.rouletteWheelSelection();
                indiv[1] = pop.rouletteWheelSelection();

                // Crossover
                if ( m_rand.nextDouble() < CROSSOVER_RATE ) {
                    indiv = crossover(indiv[0], indiv[1]);
                }

                // Mutation
                if ( m_rand.nextDouble() < MUTATION_RATE ) {
                    indiv[0].mutate();
                }
                if ( m_rand.nextDouble() < MUTATION_RATE ) {
                    indiv[1].mutate();
                }

                // add to new population
                newPop[count] = indiv[0];
                newPop[count+1] = indiv[1];
                count += 2;
            }
            pop.setPopulation(newPop);

            // reevaluate current population
            pop.evaluate();
            System.out.print("Total Fitness = " + pop.totalFitness);
            System.out.println(" ; Best Fitness = " +
                pop.findBestIndividual().getFitnessValue()); 
        }

        // best indiv
        Individual bestIndiv = pop.findBestIndividual();
    }
}

为什么不使用像JGAP这样的开源框架: http ://jgap.sf.net

I have implemented this algorithm by creating a "cumulative fitness array" and binary search , thus reducing the need to iterate through each element in the array during the selection: 我通过创建“累积适应度数组”和二进制搜索来实现此算法,从而减少了在选择期间迭代遍历数组中每个元素的需要

  1. For population size N create cumulative fitness array: arr[N]. 对于种群大小N,创建累积适应度数组:arr [N]。
  2. Set arr[0] := computeFitness(individual[0]). 设置arr [0]:= computeFitness(个体[0])。
  3. Then, for each subsequent element: X, arr[X] = arr[X-1] + computeFitness(individual[X]). 然后,对于每个后续元素:X,arr [X] = arr [X-1] + computeFitness(个体[X])。
  4. Generate a random number between 0 and arr[N] (ie the total fitness). 生成0到arr [N]之间的随机数(即总适​​应度)。
  5. Use a binary search (eg Collections.binarySearch) to locate the appropriate index in the cumulative fitness array, and use this index to select the individual. 使用二进制搜索(例如Collections.binarySearch)在累积适应度数组中找到适当的索引,并使用此索引选择个体。

Note that you only need to create the fitness array at the start of the reproduction phase, and can then re-use it multiple times to perform selections in O(log N) time. 请注意,您只需要在再现阶段开始时创建适应度数组,然后可以多次重复使用它以在O(log N)时间内执行选择。

As an aside, note that tournament selection is far easier to implement! 另外,请注意比赛选择更容易实现!

The concept you're looking for is called "roulette wheel selection." 您正在寻找的概念称为“轮盘赌轮选择”。 You don't have an established fitness function yet (you may be implying that the fitness of each individual is the integral value of its chromosome), but when you do a general plan is: 您还没有已建立的适应度函数(您可能暗示每个人的适应度是其染色体的整数值),但是当您做一般计划时:

  1. Sum the fitness of the entire population. 总结整个人口的适应性。
  2. Get a random number (call it x) between 0 and the total fitness. 获取0和总体适应度之间的随机数(称之为x)。
  3. Iterate through the population. 通过人口迭代。 For each member: 对于每个成员:
    1. Subtract the member's fitness from x. 从x中减去成员的适应度。
    2. If x is now less or equal to zero, select the current member. 如果x现在小于或等于零,请选择当前成员。

There are other equivalent implementations, but the general idea is to select members with a probability proportional to their fitness. 还有其他等效实现,但一般的想法是选择具有与其适应度成比例的概率的成员。

Edit: A few notes on fitness functions. 编辑:关于健身功能的一些注释。 The representation of a chromosome (in your case as a 32-bit integer) is independent of the fitness function used to evaluate it. 染色体的表示(在您的情况下为32位整数)与用于评估染色体的适应度函数无关。 For example, binary encodings typically treat the chromosome as a set of bitfields packed into an integral value of appropriate size. 例如,二进制编码通常将染色体视为一组打包成适当大小的整数值的位域。 Crossover and mutation can then be accomplished by the appropriate bit-masking operations. 然后可以通过适当的位掩码操作来完成交叉和变异。 If you're interested, I can post some simple GA code I have laying around (in C and Python) which uses bitwise operations to implement these functions. 如果你有兴趣,我可以发布一些简单的GA代码(在C和Python中),它使用按位运算来实现这些功能。 I'm not sure how comfortable you are with these languages. 我不确定你对这些语言的满意度。

I made an extensible implementation in java, in which operators and individual structure is well defined by interfaces that work together. 我在java中做了一个可扩展的实现,其中运算符和单个结构由一起工作的接口很好地定义。 Github repo here https://github.com/juanmf/ga Github repo在这里https://github.com/juanmf/ga

It has a standard implementation for each operator, and an example problem implementation with a particular Individual/Population structure and a Fitness meter. 它具有每个操作员的标准实现,以及具有特定个人/人口结构和健身计的示例问题实现。 The example problem Implementation is to find the a good soccer team with players among 20 teams and a budget restriction. 实例问题的实施是找到一支优秀的足球队,其中包括20支队伍中的球员和预算限制。

To adapt it to your current problem you need to provide implementations of these interfaces: 要使其适应您当前的问题,您需要提供这些接口的实现:

juanmf.ga.structure.Gen;
juanmf.ga.structure.Individual;
juanmf.ga.structure.IndividualFactory; 
juanmf.ga.structure.Population;  // Has a basic implementation already
juanmf.ga.structure.PopulationFactory;

In pkg juanmf.grandt you have the example problem implementation classes, and how to publish them, as shown in the code snippet below. pkg juanmf.grandt您有示例问题实现类,以及如何发布它们,如下面的代码片段所示。

To publish you implementations you just have to return the proper classes from this Spring beans: 要发布实现,您只需从此Spring bean返回正确的类:

/**
 * Make sure @ComponentScan("pkg") in juanmf.ga.App.java includes this class' pkg 
 * so that these beans get registered.
 */
@Configuration
public class Config {

    @Bean(name="individualFactory")
    public IndividualFactory getIndividualFactory() {
        return new Team.TeamFactory();
    }

    @Bean(name="populationFactory")
    public PopulationFactory getPopulationFactory() {
        return new Team.TeamPopulationFactory();
    }

    @Bean(name="fitnessMeter")
    public FitnessMeter getFitnessMeter() {
        return new TeamAptitudeMeter();
    }
} 

Crosser operator has two implementations for the same technique, one sequential and one concurrent which outperforms sequential by far. Crosser运算符有两种相同技术的实现,一种顺序,一种并发,远远超过顺序。

Stop condition can be specified. 可以指定停止条件。 If none is given, it has a default stop condition that stops after 100 generations with no improvements (here you must be careful with elitist, not to loose the best of each generation so as to trigger this stop condition effectively). 如果没有给出,它有一个默认停止条件,在100代之后停止而没有任何改进(这里你必须小心精英,不要放松每一代的最佳状态,以便有效地触发这个停止条件)。

So if anyone is willing to give it a try, I'd be glad to help. 所以,如果有人愿意试一试,我很乐意提供帮助。 Anyone is welcome to offer suggestions, and better yet operator implementations :D or any improving pull request. 欢迎任何人提供建议,以及更好的运营商实施:D或任何改进拉动请求。

These other questions about roulette wheel selection should help: 关于轮盘赌选择的其他问题应该有助于:

In the first one, I've tried to explain how the roulette wheel works. 在第一个中, 我试图解释轮盘赌轮是如何工作的。 In the second, Jarod Elliott has provided some pseudocode . 第二, Jarod Elliott提供了一些伪代码 Combined with Adamski's description of an efficient implementation , these should be sufficient to get something working. 结合Adamski对高效实施的描述 ,这些应该足以使某些事情发挥作用。

Just a point worth mentioning. 只是值得一提的一点。 The Roulette wheel selection (as indicated in the pseudo-code) will not work for minimization problems - it is, however, valid for maximization problems. 轮盘赌选择(如伪代码所示)不适用于最小化问题 - 但它对最大化问题有效。

Due to the manner in which the most fit individual is selected, the minimization case will not hold. 由于选择最适合的个体的方式,最小化情况将不成立。 Details are provided in: Computational Intelligence: 2nd edition 详细信息请参阅计算智能:第2版

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM