で。 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 件のコメント:
コメントを投稿
ひとことどうぞφ(・ω・,,)