【问题标题】:Genetic Algorithm: Optimal solution not found in very basic mathematical computation遗传算法:在非常基本的数学计算中找不到最佳解决方案
【发布时间】:2019-10-30 23:02:21
【问题描述】:

我正在 C# WinForms 中实现遗传算法,以找到一个非常简单的数学方程的最优解。这里的“简单”一词意味着方程必须具有以下性质:

  • 仅由整数正数组成
  • 等式的最终结果必须是小于 1001 的正数
  • 允许最少 2 个变量,最多 7 个变量
  • 每个变量的系数必须在 1 到 10 之间(包括下限和上限)
  • 变量值从 0 到 500
  • 只允许添加

有人会说,我认为方程式如此“简单”,我将能够达到最佳解。但是,我总是只能得到一个非常接近最优解的解。为了评估解决方案是否最优,我使用以下公式:

f(x) = absolute((sum_of_all_variables) - equation_result)

例如,我有以下等式:

1a + 1b = 12

如果a2 并且b3,则该个人的f(x) 将是abs(2 + 3 - 12) = 7。当f(x) 到达0 或者我已经生成了 50 代时,我的代码就会停止。

我目前的突变率是 50%,我目前的交叉率是 25%。使用的选择方法是轮盘赌选择。突变方法是随机突变,即我只是在我的基因库中随机选择一个基因来改变它的值,每代最多改变50%的基因。

我的期望:代码生成的解决方案(个人)的 f(x) 值为 0,这是可用的最佳解决方案之一。

当前结果:代码产生的解决方案几乎是最优的。 我发现的一个常见模式是,几乎最佳的解决方案通常会在下一代中完全复制。

我现在对问题的猜测

我认为这与我如何跨界有关。我要跨界做什么:

  • 为每个人分配一个随机值
  • 如果所述随机值小于交叉率,则保存该个人的数据以供以后用于交叉
  • 收集所有要交叉的个体后,我为每个个体随机选择一个交叉点
  • 我认为这是问题所在:我在第一个个体上与第二个个体进行交叉,将第一个个体的基因和第二个将要交叉的个体的基因合并-ed。然后,继续第二个人和第三个人,依此类推。最后一个人将与第一个人交叉。

但是,正如我所说,它不会产生最佳结果。是我的逻辑有问题,还是遗传算法在这个简单的数学方程上的预期行为?

我用于参考的交叉方法(我使用 C# WinForms 在 ListView 中显示数据):

private static void Crossover(List<Chromosome> chromosomes, Random seed)
{            
    List<int> crossoverChromosome = new List<int>();

    for (int i = 0; i < 50; i++)
    {
        decimal randomedValue = RandomizeValue(seed);            
        if (randomedValue < Population.CrossoverRate) crossoverChromosome.Add(i);                
    }

    for (int i = 0; i < crossoverChromosome.Count; i++)
    {
        int crossoverPoint = seed.Next(0, chromosomes[0].GeneValues.Count);
        chromosomes[crossoverChromosome[i]] = chromosomes[crossoverChromosome[i]].MixChromosome(chromosomes[crossoverChromosome[(i + 1) % crossoverChromosome.Count]], crossoverPoint);
    }
}

private static decimal RandomizeValue(Random seed)
{
    return Math.Round((decimal)seed.NextDouble(), 5);
}

public Chromosome MixChromosome(Chromosome mixture, int crossoverPoint)
{
    List<Gene> newGenes = new List<Gene>();
    newGenes.AddRange(this.GetGenes(0, crossoverPoint));
    newGenes.AddRange(mixture.GetGenes(crossoverPoint, this.GeneValues.Count));

    return new Chromosome(DesiredValue, OperatorData, newGenes); // Ignore the DesiredValue and Operator Data, it has nothing to do with crossover
}

private List<Gene> GetGenes(int firstIndex, int lastIndex)
{
    List<Gene> slicedGenes = new List<Gene>();

    for (int i = firstIndex; i < lastIndex; i++)
    {
        slicedGenes.Add(Genes[i].CloneGene());
    }

    return slicedGenes;
}

我只对包含两个变量的方程进行了大量测试。

编辑

附加信息:

  • 我不使用精英主义
  • 初始种群规模 = 50 人
  • 每代人口规模 = 50 人

在方程 1a + 1b = 10 上重新运行 10 次会在 50 代后产生以下最佳适应度值:

  • 第一次重播:24
  • 第二次重播:11
  • 第三次重播:42
  • 第四次重播:13
  • 第五次重播:5
  • 第六次重播:19
  • 第七次重播:7
  • 第八次重播:1
  • 第九次重播:6
  • 第十次重播:29
  • 每次重新运行的附加信息:在 50 代中产生最佳适应度值的染色体在种群中经常重复。例如,在第八次重新运行时,我发现染色体上出现了很多值为a 4,值为b 7。

【问题讨论】:

  • 有许多参数在构建遗传算法中发挥作用。通常会涉及大量的试错,即使这样,由于随机性,也不能保证找到最佳答案。如果您清楚地说明您正在使用的所有参数,您的问题可以得到改善。例如:初始人口有多大?你使用任何一种精英主义吗?你跑了多少次,跑步的典型/最佳/最差适应性是什么?
  • @tucuxi 我添加了更多信息。请看一看。
  • 您只发布了一小部分代码,问题很可能是您如何为这个特定问题定义类或您如何调用您发布的方法。您最好只发布minimal reproducible example。理想情况下,这还包括对您的代码正在执行的操作的详细高级描述。虽然通常这些问题最好通过调试来解决,看看你的代码在做什么。但是 50% 的突变率是巨大的 - 这应该低于 10%,可能接近 1%。
  • @Dukeling 我将我的突变率调整为 50%,因为每次重新运行的最后一代(第 50 代)中的所有染色体都大量重复(超过 50%)。即使突变率如此之高,我仍然有大量重复最接近 f(x) = 0 的相似染色体。我认为我的解决方案正在收敛到局部最大值解决方案,而不是全局最大值。

标签: algorithm genetic-algorithm


【解决方案1】:

一个市长问题是(就像你假设的那样)交叉。以您描述它的方式(或至少据我了解),您对随机选择以生成下一个种群的解决方案的每个个体进行交叉。

遗传算法背后的基本思想是达尔文的适者生存,而不是一些随机生存(或繁殖)。

所以我认为问题在于每个人都有相同的繁殖机会,这在遗传算法中没有多大意义。更好的解决方案是让导致结果接近正确结果的个体比不会导致良好结果的个体繁殖更多。否则它会非常类似于随机搜索

通常,随机选择个体进行繁殖仍然很有用,因为这将导致结果并不总是最好的,但探索更大范围的寻找区域。。 p>

一种常见的方法是使用fitness-proportional selection,它将随机选择个体进行繁殖,但基于适应度值。所以适应度高的个体(在你的例子中导致结果接近正确的个体)有更高的繁殖机会

另一种常见的方法是stochastically distributed selection,它也会选择随机个体进行繁殖,更好的个体的机会更高,但这也将保证更好的个体比平均适应度至少会复制一次

Fitness-Proportional-Selection 的示例实现可能如下所示(不幸的是,它是 java 代码,没有 C#,但它们非常相似......):

import java.util.concurrent.ThreadLocalRandom;

import com.google.common.annotations.VisibleForTesting;

/**
 * A selector that randomly chooses pairs to be selected for reproduction based on their probability to be selected.
 */
public class FitnessProportionalSelector implements Selector {

    /**
     * Select pairs of parents (by index) that are combined to build the next generation.
     * 
     * @param selectionProbability
     *        The probability to be selected for every DNA in the current population (sums up to 1).
     * 
     * @param numPairs
     *        The number of pairs needed (or the number of individuals needed in the next generation).
     * 
     * @return Returns an int-array of size [numPairs * 2] including the pairs that are to be combined to create the next population (a pair is on
     *         position [i, i+1] for i % 2 = 0).
     */
    @Override
    public int[] select(double[] selectionProbability, int numPairs) {
        double[] summedProbabilities = Selector.toSummedProbabilities(selectionProbability);
        int[] selectionPairs = new int[numPairs * 2];
        double chosenProbability;

        for (int i = 0; i < numPairs * 2; i++) {
            chosenProbability = getRandomNumber();
            selectionPairs[i] = Selector.getSelectedIndexByBisectionSearch(summedProbabilities, chosenProbability);
        }

        return selectionPairs;
    }

    @Override
    public String toString() {
        return "FitnessProportionalSelector []";
    }

    @VisibleForTesting
    /*private*/ double getRandomNumber() {
        return ThreadLocalRandom.current().nextDouble();
    }
}

或者随机分布选择的解决方案(也在java中):

import java.util.concurrent.ThreadLocalRandom;

import com.google.common.annotations.VisibleForTesting;

/**
 * A selector that chooses the pairs to be reproduced by a stochastically distributed selection method.
 * 
 * The selection probability is proportional to the given probability, but it's ensured, that individuals with a probability above average are chosen
 * at least once.
 */
public class StochasticallyDistributedSelector implements Selector {

    /**
     * Select pairs of parents (by index) that are combined to build the next generation.
     * 
     * @param selectionProbability
     *        The probability to be selected for every DNA in the current population (sums up to 1).
     * 
     * @param numPairs
     *        The number of pairs needed (or the number of individuals needed in the next generation).
     * 
     * @return Returns an int-array of size [numPairs * 2] including the pairs that are to be combined to create the next population (a pair is on
     *         position [i, i+1] for i % 2 = 0).
     */
    @Override
    public int[] select(double[] selectionProbability, int numPairs) {
        double[] summedProbability = Selector.toSummedProbabilities(selectionProbability);
        int[] selectedPairs = new int[2 * numPairs];
        double startPoint = getRandomNumber();
        double addedAverage = 1d / (2d * numPairs);
        double stochasticallySelectedProbability;

        for (int i = 0; i < numPairs * 2; i++) {
            //select the pairs stochastically
            stochasticallySelectedProbability = startPoint + i * addedAverage;
            stochasticallySelectedProbability %= 1;
            selectedPairs[i] = Selector.getSelectedIndexByBisectionSearch(summedProbability, stochasticallySelectedProbability);
        }

        //shuffle the pairs to distribute them stochastically
        shuffle(selectedPairs);

        return selectedPairs;
    }

    @VisibleForTesting
    /*private*/ void shuffle(int[] selectedPairs) {
        //shuffle the selected pairs in place
        int swapIndex;
        int tmp;
        for (int i = selectedPairs.length - 1; i > 0; i--) {
            swapIndex = (int) (getRandomNumber() * (i + 1));

            tmp = selectedPairs[i];
            selectedPairs[i] = selectedPairs[swapIndex];
            selectedPairs[swapIndex] = tmp;
        }
    }

    @VisibleForTesting
    /*private*/ double getRandomNumber() {
        return ThreadLocalRandom.current().nextDouble();
    }

    @Override
    public String toString() {
        return "StochasticallyDistributedSelector []";
    }
}

我从我为硕士论文创建的遗传优化器项目中获取了这些示例代码。想看的话可以找on my github account

一些进一步的改进

  • 尝试使用mean square error 代替绝对值 (f(x) = absolute((sum_of_all_variables) - equation_result))
  • 使用选择压力是另一种选择合适个体进行繁殖(并使参数收敛)的好方法;如果您需要示例,可以在 github 项目中的 generic_optimizer.selection 包中找到解决方案。
  • 精英主义在这里非常有用,可以避免丢失您已有的最佳解决方案(至少如果您不将其保留在总体中,请保留它以在计算后将其返回)

【讨论】:

  • 您好,我想我得到了您的答复!所以基本上,您使用轮盘赌选择来确定使用哪对基因来进行交叉,对吗?在您的实现中,您使用累积概率和随机选择的值来确定对中第一个染色体的位置。然后,您使用染色体列表中该染色体之后的染色体进行交叉,对吗?我用的是单点分频器,可以吗?
  • 托比亚斯,我还有一个问题。 在我进行交叉过程之前,我要经过一个选择过程,基本上是选择染色体,这些染色体将立即进入下一代而无需修改,这意味着有些染色体被丢弃,一些被复制使用相同的原则:适应度比例选择。使用相同的选择方法进行交叉是多余的还是会导致错误的结果?这就是我目前正在做的事情,除了在交叉期间,我随机选择染色体。
  • 回读后,我是否误认为 selectioncrossover 实际上是两个不同的步骤? 我当前的代码:使用适应度比例选择进行选择,然后交叉随机选择的染色体。 阅读您的答案后我的想法:我应该使用适应度比例选择原则选择染色体进行交叉。但是,我有一个问题:根据您的回答,如果我每一代都有 50 条染色体,并且我得到了 25 对 所述染色体,我如何将它们变成 50 条个体? 继续...
  • 我是否使用任何交叉方法仅将第一个染色体与第二个染色体交叉直接将第二个染色体不加修改地放入种群中 b>,因此意味着每对产生 2 条染色体:第一个改变了,第二个没有改变?如果我的问题很长,我很抱歉,但我希望你能回答所有问题。如果您需要,请向我询问更多详细信息。我是一名正在尝试实现此算法的本科生,如果我听起来很无知,我很抱歉。
  • @Richard 当使用适应度比例(或rolette-wheel)选择时,我通过随机选择来选择所有个体(其中更好的个体更有可能被选择)。因此,在选择交叉的个人时,我不会选择一个,而是选择列表中的下一个。 OneCutPoint 交叉应该没问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-11-14
  • 2010-11-07
  • 1970-01-01
  • 2014-12-02
  • 2022-01-07
  • 1970-01-01
  • 2018-07-11
相关资源
最近更新 更多