私は、「ゲームプログラマーのためのAIテクニック」という本から習得した手法に基づいて、遺伝的アルゴリズムを作成しようとしています。この手法では、バイナリエンコーディングと、プログラム内で2次元配列でランダムに生成されます。
私は最近、 擬似コードの一部 に遭遇し、それを実装しようとしましたが、私がしなければならないことの詳細に関していくつかの問題に遭遇しました。私はいくつかの本といくつかのオープンソースコードをチェックしましたが、まだ進歩に苦労しています。母集団の総適合度の合計を取得し、合計とゼロの間の乱数を選択し、その数が親よりも大きい場合はそれを上書きする必要があることを理解していますが、これらのアイデアの実装に苦労しています。
私のJavaはさびているので、これらのアイデアの実装に助けをいただければ幸いです。
以下は、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();
}
}
JGAPのようなオープンソースフレームワークを使用してみませんか: http://jgap.sf.net
このアルゴリズムは、「累積フィットネス配列」と二分探索を作成することで実装しました。したがって、選択中に配列内で各要素を反復処理する必要性を減らします:
再現フェーズの開始時にフィットネス配列を作成するだけでよく、それを複数回再利用して、O(log N)時間で選択を実行できることに注意してください。
余談ですが、トーナメントの選択ははるかに簡単に実装できることに注意してください。
あなたが探しているコンセプトは「ルーレットホイールセレクション」と呼ばれています。あなたはまだ確立された適応度関数を持っていません(あなたは各個人の適応度がその染色体の積分値であることを暗示しているかもしれません)が、あなたが行うときの一般的な計画は次のとおりです:
他にも同等の実装がありますが、一般的な考え方は、適合性に比例する確率でメンバーを選択することです。
編集:適応度関数に関するいくつかのメモ。染色体の表現(この場合は32ビット整数)は、それを評価するために使用される適応度関数とは無関係です。たとえば、バイナリエンコーディングは通常、染色体を適切なサイズの整数値にパックされたビットフィールドのセットとして扱います。クロスオーバーとミューテーションは、適切なビットマスキング操作によって実行できます。興味があれば、ビット演算を使用してこれらの関数を実装する簡単なGAコード(CおよびPython))を投稿できます。あなたがどれほど快適かはわかりません。これらの言語で。
私は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または改善されたプルリクエストです。
言及する価値のあるポイントです。ルーレットホイールの選択(擬似コードで示されている)は、最小化の問題では機能しませんが、最大化の問題では有効です。
最も適した個人が選択される方法のために、最小化のケースは成り立たないでしょう。詳細は以下に記載されています: 計算知能:第2版
ルーレットホイールの選択に関するこれらの他の質問は、役立つはずです。
最初のものでは、 私は説明しようとしました ルーレットホイールがどのように機能するか。 2番目では、 Jarod Elliottがいくつかの擬似コードを提供しました 。 効率的な実装に関するAdamskiの説明 と組み合わせると、これらは何かを機能させるのに十分なはずです。