ここに役立つかもしれないいくつかのコードがあります(私が書いたばかりです):1.0 間隔で10個の値を注文するためのGA。100 個の完全にランダムな対立遺伝子の集団から始まります。これはまさにコードの開始方法です。
解決するために GA に与えた目標は、値を 1.0 の間隔で昇順に並べることでした。これはEval_OrderedDistance
、サンプルの各ペアの標準偏差を 1.0 から計算することにより、フィットネス関数で行われます。適合度が 0.0 に近づくにつれて、対立遺伝子が順番に表示され始めるはずです。
ジェネレーション 0 の最も適合した染色体は、残りの染色体と同様に完全にランダムでした。フィットネス値が非常に高い (つまり、悪い) ことがわかります。
GEN: fitness (allele, ...)
0: 375.47460 (583.640, -4.215, -78.418, 164.228, -243.982, -250.237, 354.559, 374.306, 709.859, 115.323)
世代が進むにつれて、適合度 (1.0 からの標準偏差) は減少し、世代 100,000 でほぼ完全になります。
100: 68.11683 (-154.818, -173.378, -170.846, -193.750, -198.722, -396.502, -464.710, -450.014, -422.194, -407.162)
...
10000: 6.01724 (-269.681, -267.947, -273.282, -281.582, -287.407, -293.622, -302.050, -307.582, -308.198, -308.648)
...
99999: 0.67262 (-294.746, -293.906, -293.114, -292.632, -292.596, -292.911, -292.808, -292.039, -291.112, -290.928)
コードの興味深い部分は、フィットネス関数です。
// try to pack the aleles together spaced apart by 1.0
// returns the standard deviation of the samples from 1.0
static float Eval_OrderedDistance(Chromosome c) {
float sum = 0;
int n = c.alele.Length;
for(int i=1; i<n; i++) {
float diff = (c.alele[i] - c.alele[i-1]) - 1.0f;
sum += diff*diff; // variance from 1.0
}
return (float)Math.Sqrt(sum/n);
}
そして突然変異。単純なクロスオーバーと「1 つの対立遺伝子を完全に変異させる」を使用しました。
Chromosome ChangeOne(Chromosome c) {
Chromosome d = c.Clone();
int i = rand.Next() % d.alele.Length;
d.alele[i] = (float)(rand.NextDouble()*2000-1000);
return d;
}
私はエリート主義を使用して、常に最高の染色体の正確なコピーを 1 つ保持しました。次に、突然変異と交差を使用して 100 個の新しい染色体を生成しました。
フィットネスの分散を計算しているように聞こえますが、これはもちろん、母集団のフィットネスがすべてほぼ同じであることを示しています。フィットネス関数をどのように定義するかが非常に重要であることがわかりました。フィットネス関数が細かくなればなるほど、染色体をより識別できるようになります。gen 0 は 68e-19 のフィットネス分散を返すため、明らかに、フィットネス関数は完全に異なる染色体に対して同様の値を返しています。
フィットネス計算を共有できますか? または、GAに解決するように依頼している問題は何ですか? それは私たちがあなたを助けるのに役立つかもしれないと思います.
[編集: 明示的なフィットネス共有/Niching の追加]
これを少し考え直し、コードを更新しました。固有の染色体を維持しようとしている場合は、その内容を比較する必要があります (他の人が述べているように)。これを行う 1 つの方法は、それらの間の標準偏差を計算することです。ある閾値よりも小さい場合は、それらを同じと見なすことができます。クラス染色体から:
// compute the population standard deviation
public float StdDev(Chromosome other) {
float sum = 0.0f;
for(int i=0; i<alele.Length; i++) {
float diff = other.alele[i] - alele[i];
sum += diff*diff;
}
return (float)Math.Sqrt(sum);
}
Niching はあなたが望むものを与えてくれると思います。集団内のすべての染色体を比較して類似性を判断し、それぞれに「ニッチ」値を割り当てます。次に、明示的なフィットネス共有と呼ばれる手法を使用して、染色体がニッチに属することに対して「罰せられる」. フィットネス値は、各ニッチの染色体数で割られます。したがって、ニッチ グループ A (A、A、A) に 3 つある場合、そのニッチが選択される可能性が 3 倍になるのではなく、1 つのエンティティとして扱われます。
サンプルを Explicit Fitness Sharing のオンとオフで比較しました。最大 STDDEV が 500 で Niching がオフの場合、約 18 ~ 20 のニッチがありました (つまり、基本的に 100 の母集団で各項目の 5 つの重複)。ニッチをオンにすると、約 85 のニッチがありました。それは人口の 85% の固有の染色体です。私のテストの出力では、 17000 世代後の多様性を確認できます。
ニッチコードは次のとおりです。
// returns: total number of niches in this population
// max_stddev -- any two chromosomes with population stddev less than this max
// will be grouped together
int ComputeNiches(float max_stddev) {
List<int> niches = new List<int>();
// clear niches
foreach(var c in population) {
c.niche = -1;
}
// calculate niches
for(int i=0; i<population.Count; i++) {
var c = population[i];
if( c.niche != -1) continue; // niche already set
// compute the niche by finding the stddev between the two chromosomes
c.niche = niches.Count;
int count_in_niche = 1; // includes the curent Chromosome
for(int j=i+1; j<population.Count; j++) {
var d = population[j];
float stddev = c.StdDev(d);
if(stddev < max_stddev) {
d.niche = c.niche; // same niche
++count_in_niche;
}
}
niches.Add(count_in_niche);
}
// penalize Chromosomes by their niche size
foreach(var c in population) {
c.niche_scaled_fitness = c.scaled_fitness / niches[c.niche];
}
return niches.Count;
}
[編集: アントンのコードの事後分析と更新]
これはおそらく宿題の問題に対処するための適切なフォーラムではないことはわかっていますが、これを知る前に努力をしたし、それをするのがとても楽しかったので、それはアントンにとってのみ役立つと思います.
Genotip.cs、Kromosom.cs、KromoMain.cs
このコードは良好な多様性を維持しており、1回の実行で「生のフィットネス」を47まで下げることができました。これは、あなたの場合、平均二乗誤差です。それはかなり近かったです!
私のコメントで述べたように、宿題を手伝うだけでなく、プログラミングを手伝いたいと思います。あなたの仕事のこれらの分析を読んでください。
予想通り、最初から「より多様な」集団を作る必要はありませんでした。完全にランダムな染色体を生成するだけです。
あなたの突然変異とクロスオーバーは非常に破壊的であり、それらのいくつかしかありませんでした. この問題によりうまく機能すると思われるいくつかの新しい演算子を追加しました。
あなたは最善の解決策を捨てていました。Tournament Selection のみでコードを実行したところ、他のすべてよりも 99% 優れた Kromo が 1 つありました。トーナメントの選択では、その最高の価値は忘れられる可能性が非常に高かった. 次世代のためにその価値のコピーを保持する「エリート主義」を少し追加しました。
オブジェクト指向の手法を検討してください。私があなたに送った書き直したコードと私の元のコードを比較してください。
コードを複製しないでください。2 つの異なるクラスにサンプリング パラメーターがありました。
コードをきれいに保ちます。コードの未使用部分がいくつかありました。特に SO に質問を送信する場合は、質問を絞り込み、未使用のコードを削除し、クリーンアップを行ってください。
コードにコメントしてください!私は再作業を大幅にコメントしました。それがセルビア語であることは知っていますが、ほんの少しのコメントでも、他の人があなたが何をしていて、何をしようとしているのかを理解するのに役立ちます.
全体として、Tournament Selection などのより洗練された機能の実装は素晴らしい仕事でした
List ではなく double[] 配列を優先します。オーバーヘッドが少なくなります。また、List temp 変数のいくつかは必要ありませんでした。あなたの構造
リスト一時 = 新しいリスト(); for(...) { temp.add(値); } for(temp の各値) { sum += value } average = sum / temp.Count
次のように簡単に記述できます。
sum = 0
for(...) {
sum += value;
}
average = sum / count;
いくつかの場所で、ループ変数を初期化するのを忘れていたため、問題が簡単に追加された可能性があります。このようなことは重大な問題を引き起こす可能性があり、他の 1 つまたは 2 つの場所と一緒にフィットネスコードに含まれていました。
ダブルフィット = 0; for(各染色体) { // 初期化する必要があります ここでループ内に適合 for(各対立遺伝子) { 適合 += ...; フィット/=カウント; }
頑張れプログラミング!