这里有一些可能有帮助的代码(我刚刚写的):GA for ordering 10 values spaced by 1.0。它从 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);
}
还有突变。我使用了一个简单的交叉和“完全突变一个等位基因”:
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;
}
我使用精英主义来始终保留一份最好的染色体的精确副本。然后使用突变和交叉生成 100 条新染色体。
听起来您确实在计算适应度的方差,这当然会告诉您总体中的适应度几乎相同。我发现如何定义健身功能非常重要。适应度函数越精细,您就越能区分染色体。显然,您的适应度函数为完全不同的染色体返回相似的值,因为您的第 0 代返回的适应度方差为 68e-19。
你能分享你的健身计算吗?或者您要求 GA 解决什么问题?我认为这可能会帮助我们帮助你。
[编辑:添加明确的健身分享/利基]
我重新考虑了一下并更新了我的代码。如果你想保持独特的染色体,你必须比较它们的内容(正如其他人提到的那样)。一种方法是计算它们之间的标准偏差。如果它小于某个阈值,则可以将它们视为相同。从染色体类:
// 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 倍,则它被视为单个实体。
我将我的样本与 Explicit Fitness Sharing 开启和关闭进行了比较。最大 STDDEV 为 500 且 Niching 关闭时,大约有 18-20 个壁龛(所以基本上 100 人口中每个项目有 5 个重复)。启用 Niching 后,大约有 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;
}
[编辑:安东代码的后期分析和更新]
我知道这可能不是解决家庭作业问题的正确论坛,但由于我在知道这一点之前就做了努力,而且我从中获得了很多乐趣,我认为它只会对 Anton 有所帮助。
Genotip.cs , Kromosom.cs , KromoMain.cs
这段代码保持了良好的多样性,我能够在一次运行中将“原始适应度”降低到 47,在你的情况下,这是平均平方误差。那是相当接近!
正如我在评论中指出的那样,我想尝试帮助您进行编程,而不仅仅是帮助您完成作业。请阅读这些对您工作的分析。
正如我们所料,没有必要从一开始就制造“更加多样化”的人口。只需生成一些完全随机的 Kromosomes。
你的突变和交叉具有很强的破坏性,而你只有其中的几个。我添加了几个似乎更适合解决此问题的新运算符。
你扔掉了最好的解决方案。当我让您的代码仅使用 Tournament Selection 运行时,将会有一个 Kromo 比其他所有的要好 99%。通过锦标赛选择,这个最佳价值很可能被遗忘。我添加了一些“精英主义”,为下一代保留了该价值的副本。
考虑面向对象的技术。将我发送给您的重写代码与我的原始代码进行比较。
不要重复代码。您有两个不同类别的采样参数。
保持代码干净。有几个未使用的代码部分。特别是在向 SO 提交问题时,尝试缩小范围,删除未使用的代码,并进行一些清理。
评论你的代码!我对重做的工作发表了重要的评论。我知道这是塞尔维亚语,但即使是一些评论也会帮助其他人了解你在做什么以及你打算做什么。
总的来说,很好地实现了一些更复杂的东西,比如锦标赛选择
更喜欢 double[] 数组而不是 List。开销更少。此外,甚至不需要您的几个 List 临时变量。你的结构
列表临时 = 新列表();for(...) { temp.add(value); } for(temp 中的每个值) { sum += value } average = sum / temp.Count
可以很容易地写成:
sum = 0
for(...) {
sum += value;
}
average = sum / count;
在几个地方,您忘记初始化循环变量,这很容易添加到您的问题中。这样的事情会导致严重的问题,而且它在你的健身代码中以及其他一两个地方
双重拟合 = 0; for(每个染色体) { // 你应该在循环内初始化 fit 这里for(each allele) { fit += ...; } 适合 /= 计数;}
祝编程好运!