遗传算法:在最基本的数学计算中找不到最佳解决方案

时间:2019-06-17 10:53:48

标签: algorithm genetic-algorithm

我正在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,这是可用的最佳解决方案之一。

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

我现在对这个问题的猜测

我认为这与我的跨界车风格有关。我要做的交叉:

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

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

我用来参考的交叉方法(我正在使用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。

1 个答案:

答案 0 :(得分:2)

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

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

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

通常情况下,随机选择要复制的个体仍然有用,因为这将导致结果并不总是最好的,但会探索更大范围的寻找区域

执行此操作的一种常见方法是使用fitness-proportional selection,它将根据适应度值随机选择要复制的个体。因此,适应度高的个体(导致结果接近您示例中正确的个体)繁殖的可能性更高

另一种常见的方式是stochastically distributed 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包中找到解决方案。
  • elitism在这里非常有用,它不会丢失您已经拥有的最佳解决方案(至少如果您不将其保留在总体中,请保留回去以在计算后返回)