web-dev-qa-db-ja.com

GAで書かれたJava

私は、「ゲームプログラマーのためのAIテクニック」という本から習得した手法に基づいて、遺伝的アルゴリズムを作成しようとしています。この手法では、バイナリエンコーディングと、プログラム内で2次元配列でランダムに生成されます。

私は最近、 擬似コードの一部 に遭遇し、それを実装しようとしましたが、私がしなければならないことの詳細に関していくつかの問題に遭遇しました。私はいくつかの本といくつかのオープンソースコードをチェックしましたが、まだ進歩に苦労しています。母集団の総適合度の合計を取得し、合計とゼロの間の乱数を選択し、その数が親よりも大きい場合はそれを上書きする必要があることを理解していますが、これらのアイデアの実装に苦労しています。

私のJavaはさびているので、これらのアイデアの実装に助けをいただければ幸いです。

15
Mike B

以下は、GAの完全な概要です。 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

現在、適応度関数が欠落していることに注意してください(ドメインによって異なります)。クロスオーバーは単純な1ポイントクロスオーバーになります(バイナリ表現を使用しているため)。突然変異は、ランダムに少しだけ単純に反転する可能性があります。


[〜#〜] edit [〜#〜]:現在のコード構造と表記法を考慮してJavaに上記の擬似コードを実装しました(覚えておいてくださいi Javaよりもac/c ++の人です)。これは決して最も効率的または完全な実装ではないことに注意してください。私はそれをかなり迅速に書いたことを認めます。

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

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();
    }
}
51
Amro

JGAPのようなオープンソースフレームワークを使用してみませんか: http://jgap.sf.net

4
Klaus

このアルゴリズムは、「累積フィットネス配列」と二分探索を作成することで実装しました。したがって、選択中に配列内で各要素を反復処理する必要性を減らします

  1. 母集団サイズNの場合、累積フィットネス配列arr [N]を作成します。
  2. Arr [0]:= computeFitness(individual [0])を設定します。
  3. 次に、後続の各要素について、X、arr [X] = arr [X-1] + computeFitness(individual [X])。
  4. 0からarr [N]までの乱数を生成します(つまり、総適合度)。
  5. 二分探索(Collections.binarySearchなど)を使用して累積フィットネス配列内の適切なインデックスを見つけ、このインデックスを使用して個人を選択します。

再現フェーズの開始時にフィットネス配列を作成するだけでよく、それを複数回再利用して、O(log N)時間で選択を実行できることに注意してください。

余談ですが、トーナメントの選択ははるかに簡単に実装できることに注意してください。

3
Adamski

あなたが探しているコンセプトは「ルーレットホイールセレクション」と呼ばれています。あなたはまだ確立された適応度関数を持っていません(あなたは各個人の適応度がその染色体の積分値であることを暗示しているかもしれません)が、あなたが行うときの一般的な計画は次のとおりです:

  1. 母集団全体の適応度を合計します。
  2. 0と総適応度の間の乱数(xと呼びます)を取得します。
  3. 母集団を反復処理します。各メンバーの場合:
    1. Xからメンバーのフィットネスを引きます。
    2. Xがゼロ以下の場合は、現在のメンバーを選択します。

他にも同等の実装がありますが、一般的な考え方は、適合性に比例する確率でメンバーを選択することです。

編集:適応度関数に関するいくつかのメモ。染色体の表現(この場合は32ビット整数)は、それを評価するために使用される適応度関数とは無関係です。たとえば、バイナリエンコーディングは通常、染色体を適切なサイズの整数値にパックされたビットフィールドのセットとして扱います。クロスオーバーとミューテーションは、適切なビットマスキング操作によって実行できます。興味があれば、ビット演算を使用してこれらの関数を実装する簡単なGAコード(CおよびPython))を投稿できます。あなたがどれほど快適かはわかりません。これらの言語で。

1
Derrick Turk

私はJavaで拡張可能な実装を作成しました。この実装では、演算子と個々の構造が、連携して機能するインターフェースによって明確に定義されています。ここにGithubリポジトリ https://github.com/juanmf/ga

オペレーターごとに標準の実装があり、特定の個人/人口構造とフィットネスメーターを使用した問題の実装例があります。問題の実装の例は、20チームのプレーヤーと予算制限のある優れたサッカーチームを見つけることです。

現在の問題に適応させるには、次のインターフェイスの実装を提供する必要があります。

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;

pkg juanmf.grandt以下のコードスニペットに示すように、問題の実装クラスの例とそれらを公開する方法があります。

実装を公開するには、このSpringBeanから適切なクラスを返す必要があります。

/**
 * 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オペレーターには、同じ手法に対して2つの実装があります。1つはシーケンシャルで、もう1つはコンカレントで、シーケンシャルよりもはるかに優れています。

停止条件を指定できます。何も指定されていない場合、デフォルトの停止条件があり、100世代後に改善なしで停止します(ここでは、この停止条件を効果的にトリガーするために、各世代の最良のものを失わないように、エリート主義者に注意する必要があります)。

ですから、誰かがそれを試してみる気があるなら、私は喜んで助けてくれるでしょう。誰でも提案を提供することを歓迎し、さらに良いのはオペレーターの実装:Dまたは改善されたプルリクエストです。

1
juanmf

言及する価値のあるポイントです。ルーレットホイールの選択(擬似コードで示されている)は、最小化の問題では機能しませんが、最大化の問題では有効です。

最も適した個人が選択される方法のために、最小化のケースは成り立たないでしょう。詳細は以下に記載されています: 計算知能:第2版

0
gpampara

ルーレットホイールの選択に関するこれらの他の質問は、役立つはずです。

最初のものでは、 私は説明しようとしました ルーレットホイールがどのように機能するか。 2番目では、 Jarod Elliottがいくつかの擬似コードを提供しました効率的な実装に関するAdamskiの説明 と組み合わせると、これらは何かを機能させるのに十分なはずです。

0
Dan Dyer