で。 5 章 2 節 「遺伝的アルゴリズムによる知識獲得」には、簡単な GA (SGA: Simple GA)の実装例が掲載されている。
今回は、その挙動を Processing で可視化してみた。
完成品はこんな感じ。
![]() |
各世代における適応度の推移グラフをProcessingで可視化したもの。 赤はエリート個体の適応度、青は当該世代における適応度の平均値。 |
【例題】
下の表に示した 20 個の整数から 1 つ以上の整数を選び出し、それらの合計値が 499 にもっとも近くなる組合せを求めよ。
|
|
---|
たとえば、1 番目の数値である 31 と 6 番目の数値の 58 を選ぶと、合計値は 31 + 58 = 89 となる。
これが、目標値である 499 に近いほど優良な解となるため、適応度のパラメータには、499 との差の絶対値 |499 - s| を用いる。ただし、このままでは値の小さい方が適応度が高くなるため、以下のように充分に大きな数 fmax から差の絶対値を引くことにする。
適応度 = fmax - |499 - s|
遺伝子座の設計に関する詳細は、当該書籍を参照するか、ソースコードを解読してほしい(あまり詳しく書くと著者に迷惑がかかるため)。
【実装】
Processing で動くように適当に書き換えたところ、こんな感じになった(ただしコンソール)。
- final int DATANO = 20; // データ数
- final int POOLSIZE = 30; // プールサイズ
- final int LASTG = 200; // 打ち切り世代
- final double MRATE = 0.01; // 突然変異の確率
- final int YES = 1; // yesに対応する整数値
- final int NO = 0; // noに対応する整数値
- final int VALUE = 499; // 目標値
- final int MAXFIT = 1000; // 適応度の計算に用いる数値
- /* 問題の数値の設定 */
- int[] q = { 31, 41, 59, 26, 53, 58, 97, 93, 23, 84,
- -62, -64, -33, -83, -27, -95, -2, -88, -41, -97};
- void setup() {
- int[][] pool = new int[POOLSIZE][DATANO]; // 遺伝子プール
- int generation; // 現在の遺伝子数
- /* 初期集団の生成 */
- initpool(pool);
- /* 打ち切り世代まで繰り返し */
- for(generation = 0; generation < LASTG; ++generation) {
- println(generation + "世代");
- mating(pool);
- mutation(pool);
- printp(pool);
- }
- }
- /* ======================================== */
- /* select() メソッド */
- /* 選択 */
- /* ======================================== */
- int select(int[] roulette, int totalfitness) {
- int ball; // 玉(選択位置の数値)
- int acc = 0; // 適応度の積算値
- ball = (int)random(totalfitness);
- int i;
- for(i = 0; i < POOLSIZE; ++i) {
- acc += roulette[i]; // 適応度を積算
- if(acc > ball) break; // 対応する遺伝子
- }
- return i;
- }
- /* ======================================== */
- /* mating() メソッド */
- /* 交叉 */
- /* ======================================== */
- void mating(int[][] pool) {
- int totalfitness = 0; // 適応度の合計値
- int[][] nextpool = new int[POOLSIZE][DATANO];
- // 子の世代
- int[] roulette = new int[POOLSIZE];
- // 適応度を格納
- int mama, papa; // 親の遺伝子の番号
- /* ルーレットの作成 */
- for(int i = 0; i < POOLSIZE; ++i) {
- roulette[i] = evalfit(pool[i]);
- /* 適応度の合計値を計算 */
- totalfitness += roulette[i];
- }
- /* 選択と交叉を繰り返す */
- for(int i = 0; i < POOLSIZE / 2; ++i) {
- do { /* 親の選択 */
- mama = select(roulette, totalfitness);
- papa = select(roulette, totalfitness);
- } while (mama == papa); // 重複の削除
- /* 特定の2遺伝子の交叉 */
- crossing(pool[mama], pool[papa],
- nextpool[i*2], nextpool[i*2+1]);
- }
- /* 次世代のプールをコピー */
- copypool(pool, nextpool);
- }
- /* ======================================== */
- /* crossing() メソッド */
- /* 特定の2遺伝子の交叉 */
- /* ======================================== */
- void crossing(int[] m, int[] p, int[] c1, int[] c2) {
- int cp; // 交叉する点
- /* 交叉点の決定 */
- cp = (int)random(DATANO);
- int j;
- /* 遺伝子の前半部分をコピー */
- for(j = 0; j < cp; ++j) {
- c1[j] = m[j];
- c2[j] = p[j];
- }
- /* 遺伝子の後半部分をコピー */
- for(; j < DATANO; ++j) {
- c2[j] = m[j];
- c1[j] = p[j];
- }
- }
- /* ======================================== */
- /* copypool() メソッド */
- /* 次世代のプールをコピー */
- /* ======================================== */
- void copypool(int[][] pool, int[][] nextpool) {
- for(int i = 0; i < POOLSIZE; ++i) {
- for(int j = 0; j < DATANO; ++j) {
- pool[i][j] = nextpool[i][j];
- }
- }
- }
- /* ======================================== */
- /* evalfit() メソッド */
- /* 適応度の計算 */
- /* ======================================== */
- int evalfit(int[] g) {
- int fitness = 0; // 適応度の途中値
- for(int i = 0; i < DATANO; ++i) {
- fitness += g[i] * q[i];
- }
- return MAXFIT - abs(VALUE - fitness);
- }
- /* ======================================== */
- /* printp() メソッド */
- /* 結果出力 */
- /* ======================================== */
- void printp(int[][] pool) {
- int fitness; // 適応度
- double totalfitness = 0; // 適応度の合計値
- int elite = 0; // エリート遺伝子の処理用変数
- int bestfit = 0;
- for(int i = 0; i < POOLSIZE; ++i) {
- for(int j = 0; j < DATANO; ++j) {
- print(pool[i][j]);
- }
- fitness = evalfit(pool[i]);
- println("\t" + fitness);
- if(fitness > bestfit) { /* エリート解 */
- bestfit = fitness;
- elite = i;
- }
- totalfitness += fitness;
- }
- /* エリート解の適応度を出力 */
- print(elite + "\t" + bestfit + "\t");
- /* 平均の適応度を出力 */
- println(totalfitness / POOLSIZE);
- }
- /* ======================================== */
- /* initpool() メソッド */
- /* 初期集団の生成 */
- /* ======================================== */
- void initpool(int[][] pool) {
- for(int i = 0; i < POOLSIZE; ++i) {
- for(int j = 0; j < DATANO; ++j) {
- pool[i][j] = (int)random(2);
- }
- }
- }
- /* ======================================== */
- /* mutation() メソッド */
- /* 突然変異 */
- /* ======================================== */
- void mutation(int[][] pool) {
- for(int i = 0; i < POOLSIZE; ++i) {
- for(int j = 0; j < DATANO; ++j) {
- if(random(100) / 100.0 <= MRATE) {
- /* 反転の突然変異 */
- pool[i][j] = notval(pool[i][j]);
- }
- }
- }
- }
- /* ======================================== */
- /* notval() メソッド */
- /* 真理値の反転 */
- /* ======================================== */
- int notval(int v) {
- if(v == YES) return NO;
- else return YES;
- }
ここまではコンソールアプリケーションなので、実行してもあまり見栄えはよくない。
グラフ描画に必要なのは、各世代におけるエリート個体の適応度、および(各世代における)全個体の適応度の平均値なので、それらを得るべく printp メソッドを改造してこんなものを作った。
- /* ======================================== */
- /* getFitnessValueOfElite() メソッド */
- /* エリート個体の適応度を得る */
- /* ======================================== */
- int getFitnessValueOfElite(int[][] pool) {
- int fitness; // 適応度
- int elite = 0; // エリート遺伝子の処理用変数
- int bestfit = 0;
- for(int i = 0; i < POOLSIZE; ++i) {
- fitness = evalfit(pool[i]);
- if(fitness > bestfit) { /* エリート解 */
- bestfit = fitness;
- elite = i;
- }
- }
- return bestfit;
- }
- /* ======================================== */
- /* getAvarageFitnessValue() メソッド */
- /* 適応度の平均を得る */
- /* ======================================== */
- float getAvarageFitnessValue(int[][] pool) {
- double totalfitness = 0; // 適応度の合計値
- for(int i = 0; i < POOLSIZE; ++i) {
- totalfitness += evalfit(pool[i]);
- }
- /* 平均の適応度を出力 */
- return (float)totalfitness / POOLSIZE;
- }
各世代における適応度を配列に保存しておき、draw メソッドで描画する。
- void draw() {
- background(0);
- smooth();
- // 目盛り表示
- stroke(50);
- for(int i = 0; i < width; i += 50) {
- line(i, 0, i, height);
- }
- for(int i = 0; i < height; i += 50) {
- line(0, i, width, i);
- }
- for(int i = 0; i < LASTG-1; ++i) {
- stroke(255, 0, 0);
- line(i , MAXFIT - eliteFitness[i],
- i + 1, MAXFIT - eliteFitness[i+1]);
- stroke(0, 0, 255);
- line(i , MAXFIT - avgFitness[i],
- i + 1, MAXFIT - avgFitness[i+1]);
- }
- // ガイドライン表示
- if(0 <= mouseX && mouseX < LASTG) {
- stroke(255);
- line(mouseX, mouseY,
- mouseX, MAXFIT - eliteFitness[mouseX]);
- // 結果文字列表示
- if(mouseX < width / 2) textAlign(LEFT);
- else textAlign(RIGHT);
- text("第" + mouseX + "世代(適応度:" + eliteFitness[mouseX]+")",
- mouseX,
- MAXFIT - eliteFitness[mouseX] + 12);
- }
- }
これでよし。
ちなみにフルバージョンのソースコードはこちら。
- final int DATANO = 20; // データ数
- final int POOLSIZE = 30; // プールサイズ
- final int LASTG = 600; // 打ち切り世代
- final double MRATE = 0.01; // 突然変異の確率
- final int YES = 1; // yesに対応する整数値
- final int NO = 0; // noに対応する整数値
- final int VALUE = 499; // 目標値
- final int MAXFIT = 1000; // 適応度の計算に用いる数値
- /* 問題の数値の設定 */
- int[] q = { 31, 41, 59, 26, 53, 58, 97, 93, 23, 84,
- -62, -64, -33, -83, -27, -95, -2, -88, -41, -97};
- /* 適応度の推移を格納する配列 */
- int[] eliteFitness;
- float[] avgFitness;
- void setup() {
- int[][] pool = new int[POOLSIZE][DATANO]; // 遺伝子プール
- int generation; // 現在の遺伝子数
- size(LASTG, VALUE);
- /* 初期集団の生成 */
- initpool(pool);
- /* 配列を初期化 */
- eliteFitness = new int[LASTG];
- avgFitness = new float[LASTG];
- /* 打ち切り世代まで繰り返し */
- for(generation = 0; generation < LASTG; ++generation) {
- mating(pool);
- mutation(pool);
- eliteFitness[generation] = getFitnessValueOfElite(pool);
- avgFitness[generation] = getAvarageFitnessValue(pool);
- }
- }
- void draw() {
- background(0);
- smooth();
- // 目盛り表示
- stroke(50);
- for(int i = 0; i < width; i += 50) {
- line(i, 0, i, height);
- }
- for(int i = 0; i < height; i += 50) {
- line(0, i, width, i);
- }
- for(int i = 0; i < LASTG-1; ++i) {
- stroke(255, 0, 0);
- line(i , MAXFIT - eliteFitness[i],
- i + 1, MAXFIT - eliteFitness[i+1]);
- stroke(0, 0, 255);
- line(i , MAXFIT - avgFitness[i],
- i + 1, MAXFIT - avgFitness[i+1]);
- }
- // ガイドライン表示
- if(0 <= mouseX && mouseX < LASTG) {
- stroke(255);
- line(mouseX, mouseY,
- mouseX, MAXFIT - eliteFitness[mouseX]);
- // 結果文字列表示
- if(mouseX < width / 2) textAlign(LEFT);
- else textAlign(RIGHT);
- text("第" + mouseX + "世代(適応度:" + eliteFitness[mouseX]+")",
- mouseX,
- MAXFIT - eliteFitness[mouseX] + 12);
- }
- }
- /* ======================================== */
- /* select() メソッド */
- /* 選択 */
- /* ======================================== */
- int select(int[] roulette, int totalfitness) {
- int ball; // 玉(選択位置の数値)
- int acc = 0; // 適応度の積算値
- ball = (int)random(totalfitness);
- int i;
- for(i = 0; i < POOLSIZE; ++i) {
- acc += roulette[i]; // 適応度を積算
- if(acc > ball) break; // 対応する遺伝子
- }
- return i;
- }
- /* ======================================== */
- /* mating() メソッド */
- /* 交叉 */
- /* ======================================== */
- void mating(int[][] pool) {
- int totalfitness = 0; // 適応度の合計値
- int[][] nextpool = new int[POOLSIZE][DATANO];
- // 子の世代
- int[] roulette = new int[POOLSIZE];
- // 適応度を格納
- int mama, papa; // 親の遺伝子の番号
- /* ルーレットの作成 */
- for(int i = 0; i < POOLSIZE; ++i) {
- roulette[i] = evalfit(pool[i]);
- /* 適応度の合計値を計算 */
- totalfitness += roulette[i];
- }
- /* 選択と交叉を繰り返す */
- for(int i = 0; i < POOLSIZE / 2; ++i) {
- do { /* 親の選択 */
- mama = select(roulette, totalfitness);
- papa = select(roulette, totalfitness);
- } while (mama == papa); // 重複の削除
- /* 特定の2遺伝子の交叉 */
- crossing(pool[mama], pool[papa],
- nextpool[i*2], nextpool[i*2+1]);
- }
- /* 次世代のプールをコピー */
- copypool(pool, nextpool);
- }
- /* ======================================== */
- /* crossing() メソッド */
- /* 特定の2遺伝子の交叉 */
- /* ======================================== */
- void crossing(int[] m, int[] p, int[] c1, int[] c2) {
- int cp; // 交叉する点
- /* 交叉点の決定 */
- cp = (int)random(DATANO);
- int j;
- /* 遺伝子の前半部分をコピー */
- for(j = 0; j < cp; ++j) {
- c1[j] = m[j];
- c2[j] = p[j];
- }
- /* 遺伝子の後半部分をコピー */
- for(; j < DATANO; ++j) {
- c2[j] = m[j];
- c1[j] = p[j];
- }
- }
- /* ======================================== */
- /* copypool() メソッド */
- /* 次世代のプールをコピー */
- /* ======================================== */
- void copypool(int[][] pool, int[][] nextpool) {
- for(int i = 0; i < POOLSIZE; ++i) {
- for(int j = 0; j < DATANO; ++j) {
- pool[i][j] = nextpool[i][j];
- }
- }
- }
- /* ======================================== */
- /* evalfit() メソッド */
- /* 適応度の計算 */
- /* ======================================== */
- int evalfit(int[] g) {
- int fitness = 0; // 適応度の途中値
- for(int i = 0; i < DATANO; ++i) {
- fitness += g[i] * q[i];
- }
- return MAXFIT - abs(VALUE - fitness);
- }
- /* ======================================== */
- /* printp() メソッド */
- /* 結果出力 */
- /* ======================================== */
- void printp(int[][] pool) {
- int fitness; // 適応度
- double totalfitness = 0; // 適応度の合計値
- int elite = 0; // エリート遺伝子の処理用変数
- int bestfit = 0;
- for(int i = 0; i < POOLSIZE; ++i) {
- for(int j = 0; j < DATANO; ++j) {
- print(pool[i][j]);
- }
- fitness = evalfit(pool[i]);
- println("\t" + fitness);
- if(fitness > bestfit) { /* エリート解 */
- bestfit = fitness;
- elite = i;
- }
- totalfitness += fitness;
- }
- /* エリート解の適応度を出力 */
- print(elite + "\t" + bestfit + "\t");
- /* 平均の適応度を出力 */
- println(totalfitness / POOLSIZE);
- }
- /* ======================================== */
- /* getFitnessValueOfElite() メソッド */
- /* エリート個体の適応度を得る */
- /* ======================================== */
- int getFitnessValueOfElite(int[][] pool) {
- int fitness; // 適応度
- int elite = 0; // エリート遺伝子の処理用変数
- int bestfit = 0;
- for(int i = 0; i < POOLSIZE; ++i) {
- fitness = evalfit(pool[i]);
- if(fitness > bestfit) { /* エリート解 */
- bestfit = fitness;
- elite = i;
- }
- }
- return bestfit;
- }
- /* ======================================== */
- /* getAvarageFitnessValue() メソッド */
- /* 適応度の平均を得る */
- /* ======================================== */
- float getAvarageFitnessValue(int[][] pool) {
- double totalfitness = 0; // 適応度の合計値
- for(int i = 0; i < POOLSIZE; ++i) {
- totalfitness += evalfit(pool[i]);
- }
- /* 平均の適応度を出力 */
- return (float)totalfitness / POOLSIZE;
- }
- /* ======================================== */
- /* initpool() メソッド */
- /* 初期集団の生成 */
- /* ======================================== */
- void initpool(int[][] pool) {
- for(int i = 0; i < POOLSIZE; ++i) {
- for(int j = 0; j < DATANO; ++j) {
- pool[i][j] = (int)random(2);
- }
- }
- }
- /* ======================================== */
- /* mutation() メソッド */
- /* 突然変異 */
- /* ======================================== */
- void mutation(int[][] pool) {
- for(int i = 0; i < POOLSIZE; ++i) {
- for(int j = 0; j < DATANO; ++j) {
- if(random(100) / 100.0 <= MRATE) {
- /* 反転の突然変異 */
- pool[i][j] = notval(pool[i][j]);
- }
- }
- }
- }
- /* ======================================== */
- /* notval() メソッド */
- /* 真理値の反転 */
- /* ======================================== */
- int notval(int v) {
- if(v == YES) return NO;
- else return YES;
- }
0 件のコメント:
コメントを投稿
ひとことどうぞφ(・ω・,,)