C++用遺伝的アルゴリズムライブラリ GAlib の使い方

この前はpyevolveを使いましたが、今回はC++用の遺伝的アルゴリズムライブラリであるGAlibというものを使ってみたいと思います。

GAlib: Matthew's Genetic Algorithms Library

予め言っておくと、pyevolveの使い方とGAlibは似てます。GAlibのソースにも多数のexampleが付属しているので、それが参考になります。

まぁ説明するより普通にソース見た方が早いと思うのでPyevolveと同じように
・要素数20の配列xがあるとき、以下の関数f(x)が最大となるxを求めよ
{\Large f(x)=\sum_{k=1}^{n} (x[i] == 0 ? 1 : 0)}
を解きたいと思います。
まずは個体を評価する評価関数を作成します。この評価関数の事をGAlibのドキュメントではObjective Functionとしています。

float Objective(GAGenome &g){
  GA1DArrayGenome<int>& genome = (GA1DArrayGenome<int>&)g;
  float value=0.0;
  for(int i=0; i<genome.length(); i++)
    if (genome.gene(i) == 0)
	value += 1.0;
  return value;
}

GAGenome というのは仮想クラスで、全てのゲノム(つまりデータ構造)はこれを継承します。評価関数を作る際には中で適当なクラスにキャストして評価をします。評価関数の戻り値はfloatであると決められています。今回はゲノムとしてGA1DArrayGenome、つまりint型の1次元配列を利用しています。評価関数では配列中の0の値をカウントし、それがそのまま評価値(適応度)になります。
また、GA1DArrayGenomeを使う場合は、初期化関数を定義する必要があるので、これも定義しておきます。

// 初期化関数
void Initializer(GAGenome& g)
{
  GA1DArrayGenome<int>& genome = (GA1DArrayGenome<int>&)g;
	for(int i = 0;i < genome.length();i++){
  	genome.gene(i, GARandomInt(-100,100)); // -100〜100までのランダムな値に
}
}

さて、それではmain関数の中になります。

  GA1DArrayGenome<int> genome(20,Objective);
  genome.crossover(GA1DArrayGenome<int>::UniformCrossover);
  genome.initializer(::Initializer);

まずはgenomeを作ります。GA1DArrayGenomeコンストラクタの第一引数は要素数、第二引数は評価関数です。また、genome.crossover()、genome.initialize()で交叉関数と初期化関数を定義しています。

  GASimpleGA ga(genome);
  ga.populationSize(100); // 集団数
  ga.nGenerations(200);   // 世代数
  ga.pMutation(0.01);     // 突然変異率
  ga.pCrossover(0.9);     // 交叉率

GASimpleGAで実際に評価をおこなうインスタンスを生成、そしてそれに設定を追加していきます。ちなみにnGenerationsのnはnumber,pMutationとpCrossoverのpはprobabilityの略です。
ところで、このGASimpleGAという名前ですが、これは遺伝的アルゴリズムで有名なDavid E. Goldberg氏が書いた本(これ)に由来するもので、世代によって集団数が変わらないもののことをいうようです。
ここまで来たら、後は実行するだけです。

  // 評価開始
  ga.evolve();
	// 最も評価の高い個体を表示
  std::cout << ga.statistics().bestIndividual() << "\n";

全体のコードはこんな感じです。

#include <ga/GASimpleGA.h>
#include <ga/GA1DArrayGenome.h>

#include <iostream>

// 評価関数
float Objective(GAGenome &g){
  GA1DArrayGenome<int>& genome = (GA1DArrayGenome<int>&)g;
  float value=0.0;
  for(int i=0; i<genome.length(); i++)
    if (genome.gene(i) == 0)
	value += 1.0;
  return value;
}

// 初期化関数
void Initializer(GAGenome& g)
{
  GA1DArrayGenome<int>& genome = (GA1DArrayGenome<int>&)g;
	for(int i = 0;i < genome.length();i++){
  	genome.gene(i, GARandomInt(-100,100));
	}
}

int main(int argc, char const* argv[])
{

  // ゲノムのインスタンスを生成
  GA1DArrayGenome<int> genome(20,Objective);
  genome.crossover(GA1DArrayGenome<int>::UniformCrossover);
  genome.initializer(::Initializer);

  GASimpleGA ga(genome);
  ga.populationSize(100);
  ga.nGenerations(200);
  ga.pMutation(0.01);
  ga.pCrossover(0.9);

  // 評価開始
  ga.evolve();

  // 最も評価の高い個体を表示 
  std::cout << ga.statistics().bestIndividual() << "\n";

  return 0;
}

こんな感じでだいたいの流れは把握できると思います。Pyevolveと同様に、多くの設定にはデフォルトの値があります。一部を以下に示します。

#define name        full name                   short name  default value

gaNminimaxi         minimaxi                    mm          int   gaDefMiniMaxi        = 1      //適応度を最大化しようとする(最小化する場合は -1)
gaNnGenerations     number_of_generations       ngen        int   gaDefNumGen          = 250    //世代数
gaNpConvergence     convergence_percentage      pconv       float gaDefPConv           = 0.99   //収束%
laNnConvergence     generations_to_convergence  nconv       int   gaDefNConv           = 20     //世代就職%
gaNpCrossover       crossover_probability       pcross      float gaDefPCross          = 0.9    //交叉確率
gaNpMutation        mutation_probability        pmut        float gaDefPMut            = 0.01   //突然変異率
gaNpopulationSize   population_size             popsize     int   gaDefPopSize         = 30     //集団数(個体数)
gaNnPopulations     number_of_populations       npop        int   gaDefNPop            = 10     //集団そのものの数(GADemeGaで使う)
gaNpReplacement     replacement_percentage      prepl       float gaDefPRepl           = 0.25   //交換率(GASteadyStateGAで使う)
gaNnReplacement     replacement_number          nrepl       int   gaDefNRepl           = 5      //交換数(上と同じ)
gaNnBestGenomes     number_of_best              nbest       int   gaDefNumBestGenomes  = 1      //最善として選ぶゲノムの数
gaNscoreFrequency   score_frequency             sfreq       int   gaDefScoreFrequency1 = 1      //記録する頻度
gaNflushFrequency   flush_frequency             ffreq       int   gaDefFlushFrequency  = 0      //書き込み頻度
gaNscoreFilename    score_filename              sfile       char* gaDefScoreFilename   = "generations.dat" //統計を保存するファイル名
gaNselectScores     select_scores               sscores     int   gaDefSelectScores    = GAStatistics::Maximum //どのスコアを保存するか
gaNelitism          elitism                     el          GABoolean gaDefElitism     = gaTrue //各世代における最も優秀なゲノムを引き継ぐかどうか

(しまった崩れたorz … ドキュメントみてください…)

使用できるゲノムの種類

・ 1D binary string genome
・ 2D binary string genome
・ 3D binary string genome
・ binary-to-decimal genome
・ 1D array genome
・ 1D array genome with alleles
・ 2D array genome
・ 2D array genome with alleles
・ 3D array genome
・ 3D array genome with alleles
・ string genome
・ real number genome
・ list genome
・ tree genome

まぁ名前のとおりです。遺伝子の値の範囲を決めるには、GAAlleleSetを使う必要があります。alleleの設定方法はいろいろとありますが、基本的な流れとしては、alleleのインスタンスを作成、それにalleleを登録し、ゲノムのコンストラクタにそれを渡します。
GAAllelSetの使い方例(ex21.cより)

  //それぞれの遺伝子は-10,0.1,1.0,10,100のいずれかの値を取る
  GARealAlleleSet alleles1;
  alleles1.add(-10);
  alleles1.add(0.1);
  alleles1.add(1.0);
  alleles1.add(10);
  alleles1.add(100);
  GARealGenome genome1(length, alleles1, Objective1);


  //遺伝子は0.0以上、10より小さい0.5刻みの数 (0とか0.5とか1.5とか)
  GARealAlleleSet alleles3(0,10,0.5,GAAllele::INCLUSIVE,GAAllele::EXCLUSIVE);
  GARealGenome genome3(length, alleles3, Objective3);

  //一番目の遺伝子は0以上10以下の実数、二番目の遺伝子は50以上100以下の実数…
  GARealAlleleSetArray alleles4;
  alleles4.add(0,10);
  alleles4.add(50,100);
  alleles4.add(-10,-5);
  alleles4.add(-0.01,-0.0001);
  alleles4.add(10000,11000);
  GARealGenome genome4(alleles4, Objective4);

  //遺伝子の値は-1,0,1のいずれか
  int aset[] = {-1,0,1};
  GAAlleleSet<int> alleles(3, aset);
  GA2DArrayAlleleGenome<int> genome(20, 20, alleles, objective);
使用できるアルゴリズム

・ GA with overlapping populations (steady-state)
・ GA with non-overlapping populations (simple)
・ GA with 1 or 2 children per generation (incremental)
・ GA with parallel, migrating populations (deme)

いろいろとあるんですが…自分はまだ2つ目のGASimpleGAしか使ってないというか分かってないです^^;

選択方法の決め方

GAlibでは選択方法はそれぞれクラスとして用意されているので、それのインスタンスをGAに登録します。

  GASimpleGA ga(genome);
  GATournamentSelector selector;
  ga.selector(selector);

デフォルトで用意されている選択方法は以下のとおりです。
・ GARankSelector
・ GARouletteWheelSelector (多分デフォルト)
・ GATournamentSelector
・ GADSSSelector
・ GASRSSelector
・ GAUniformSelector


ヘッダファイルについて

GAlibを使用する場合には適当なヘッダをインクルードする必要がある訳ですが、基本的に名前は (使いたいもの).h です。例えば、ga/GASimpleGA.h とか ga/GA1DArray.h です。
ただこれだと面倒なので、 ga/ga.h をインクルードすると中で全ての 遺伝的アルゴリズムのヘッダとゲノムのヘッダをインクルードしてくます。




…ということでおおざっぱにGAlibの使い方を見てきました。いろいろと機能があるんですが、いかんせん自分の知識不足のため表面的なことしか説明できません(汗)。やはり一番参考になるのは付属のexampleだと思うので、使い方知りたい人はそれを読んでみてください。Pyevolveはかなり手軽な感じでしたが、こちらも手軽に利用できるんじゃないでしょうか。何より実行速度が早いです。


それではまたいつか…