【问题标题】:AI algorithm for multi dimension solution optimization / prediction多维解优化/预测的AI算法
【发布时间】:2018-03-17 00:48:44
【问题描述】:

我有 6 个 int 参数,范围从 0 到 100

数字的总组合为 100^6,每个组合给出的结果范围约为。从 -10000 到 100000 甚至更多。

Input data example:
MySimulation (57, 78, 20, 10, 90, 50) = 300  <- Best Result
MySimulation (50, 80, 10, 90, 35, 8) = 200
MySimulation (4, 55, 40, 99, 40, 50) = -50 <- Worst Result

结果越高,数字组合越好,我已经有计算得出结果,我只需要AI找到更好的数字组合,得出更高的结果。

Output data example:
55, 70, 25, 15, 95, 52   <- Let's say these combination of
                            numbers was choosen by AI and would 
                            give a result of 400 with my simulation

注意:数字的顺序也很重要。

如何减少 100^6 与 AI 的总组合,以便在不迭代所有 100^6 组合的情况下获得最佳结果?

我打算在 C# 中使用 Accord.NET(或者有更好的方法吗?),代码示例会很有帮助,因为我是 AI 新手。

【问题讨论】:

  • 您是在问如何对Calculation 进行逆向工程以找到 最佳解决方案,还是您在问如何找到“相当不错”的解决方案?您可以使用 AI 框架来找到“相当不错”的解决方案,但这基本上是一个局部最大类型的问题,此外,它的域很小。典型的方法是(非常粗略地)尝试随机数,获取最佳结果,然后尝试附近数字的其他组合并重复直到您满意为止。
  • 我已经有了计算结果,我只需要一个“相当不错”的解决方案。
  • 没有假设,你无法击败暴力搜索。我在您的帖子中没有看到任何假设/先验信息。
  • 不要使用 Accord.NET。这不是机器学习问题。看看遗传算法:stackoverflow.com/questions/14008/…。或模拟退火。
  • accord.net 也有 GA 并且比 aforge 更新得最近

标签: c# artificial-intelligence permutation accord.net


【解决方案1】:

欢迎来到多目标优化领域。这是我写论文的一个领域。解决这类问题的算法有很多,但最著名的两种可能是 NSGA-II 和 SPEA2。

当然,您只有一个目标:无论您的评分函数是什么,都会产生影响。我认为多目标算法也适用,因为您不仅对单一解决方案感兴趣,而且对它们的总体感兴趣。

我可以给你指点http://jmetalnet.sourceforge.net/吗?

我们的想法是,您将生成包含输入的随机向量群体,这些输入范围跨越您的 100^6 个可能的解决方案的域。这些种群将发生突变并相互交配以产生新的解决方案,并从这些新种群中向下选择它们,以使更优选的解决方案被选择保留(并在进化过程中幸存下来)。

在多世界中,比较不同解决方案的适用性可能会遇到挑战。但是在你的单一目标世界中,比较适应度很容易:你只需要决定你想要更高的数字还是更低的数字。看来你想要更高。

概述

  1. 创建随机的解决方案群体。
  2. 在您的解决方案中随机变异/交叉。
  3. 计算每个人的健康状况并进行排序。
  4. 下采样回到最佳解决方案的初始总体规模。
  5. 重复步骤 2-4 [直到收敛:直到平均适应度 > 阈值?]
  6. 输出最后一代。

结果:

这是一个糟糕的分析,请注意,您可以通过在每个参数级别平均(例如)20 次运行的结果来做得更好。马上,您可以看出突变率应该保持在较低水平,显然,更大的种群规模会有所帮助(达到收敛点)。

结果格式为之前、之后的平均分,最高为 600

PopSize=100,NumGens=50,MutRate=0.2,CrossRate=0.8
295.23,542.12

PopSize=100,NumGens=500,MutRate=0.2,CrossRate=0.8
298.53,565

PopSize=1000,NumGens=50,MutRate=0.2,CrossRate=0.8
301.814,579.334

PopSize=10000,NumGens=500,MutRate=0.2,CrossRate=0.8
299.8901,588

PopSize=1000,NumGens=50,MutRate=0.4,CrossRate=0.8
306.22,385.55

代码

我在大约 20 分钟内编写了这段代码,所以它并不意味着优雅或出色。我希望它只是明白这一点。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace moo_in_csharp
{
    internal class Individual
    {
        public int[] Decisions;
        public double Fitness;
        private int _numdecisions = 6;

        /// <summary>
        /// Default constructor.
        /// </summary>
        public Individual()
        {
            Decisions = new int[_numdecisions];
        }

        /// <summary>
        /// Replaces first half of decisions with those of the mate.
        /// </summary>
        /// <param name="mate"></param>
        public void Crossover(Individual mate)
        {
            int crossoverPoint = _numdecisions / 2;
            for (int i = 0; i < crossoverPoint; i++)
            {
                Decisions[i] = mate.Decisions[i];
            }
        }

        /// <summary>
        /// Simple fitness function that computes sum of a+b+c+d+e+f.
        /// </summary>
        public double Evaluate()
        {
            Fitness = Decisions.Sum();
            return Fitness;
        }

        /// <summary>
        /// Assigns random values to its decisions.
        /// </summary>
        public void Generate()
        {
            for (int i = 0; i < _numdecisions; i++)
            {
                Decisions[i] = Program.rand.Next(0, 101);
            }
        }

        /// <summary>
        /// Randomly mutate select decisions.
        /// </summary>
        public void Mutate()
        {
            for (int i = 0; i < _numdecisions; i++)
            {
                Decisions[i] = Program.rand.Next(0, 101);
            }
        }
    }

    internal class Program
    {
        public static Random rand = new Random();

        private static void Main(string[] args)
        {
            //parameters
            int populationSize = 100;
            int numGenerations = 50;
            double mutationRate = 0.2;
            double crossoverRate = 0.8;

            //build initial population
            List<Individual> solutions = new List<Individual>();
            for (int i = 0; i < populationSize; i++)
            {
                var solution = new Individual();
                solution.Generate();
                solution.Evaluate();
                solutions.Add(solution);
            }

            //calculate average score of initial population
            var averageScoreBefore = solutions.Select(x => x.Evaluate()).Average();

            //iterate across number of generations
            for (int i = 0; i < numGenerations; i++)
            {
                //build offspring by mating sequential pairs of solutions
                var offspring = new List<Individual>();
                for (int e = 0; e < solutions.Count() - 1; e += 2)
                {
                    if (rand.NextDouble() < crossoverRate)
                    {
                        var newIndividual = new Individual();
                        solutions[e].Decisions.CopyTo(newIndividual.Decisions, 0);
                        newIndividual.Crossover(solutions[e + 1]);
                        offspring.Add(newIndividual);
                    }
                }

                //add our offspring to our solutions
                solutions.AddRange(offspring);

                //mutate solutions at a low rate
                foreach (var solution in solutions)
                {
                    if (rand.NextDouble() < mutationRate)
                    {
                        solution.Mutate();
                    }
                }

                //sort our solutions and down-sample to initial population size
                solutions = solutions.OrderByDescending(x => x.Evaluate()).ToList();
                solutions = solutions.Take(populationSize).ToList();
            }

            //calculate average score after
            var averageScoreAfter = solutions.Select(x => x.Evaluate()).Average();
            Debug.WriteLine(averageScoreBefore + "," + averageScoreAfter);
        }
    }
}

其他说明

您的运行时间主要取决于您的健康评分功能。对于简单的数学函数,这个运行时并不难。显然,如果涉及到一个过程,您希望将评估次数保持在最低限度。这是我攻读博士学位时研究的,并开发了一种名为 GALE 的新算法:

【讨论】:

  • 谢谢!我将在我的应用程序中尝试代码并查看它的位置。
  • 您的样本使用的是 NSGA-II、SPEA2 还是 GALE?
  • 这里的代码不是其中任何一个。这里的情况更简单,因为你只有一个客观的适应度分数,所以更容易起草一个快速而肮脏的程序。
  • 我已经实现了代码,这很神奇,在 5 分钟内我的适应度比 6400 万高出 30%。分辨率较低的蛮力,耗时超过 30 分钟。但我看到它并不总是保持最高分。达到 470 之后,经过几代之后,最佳解决方案得分达到 390,这正常吗?
  • 好的解决方案有时会变成更差的解决方案。这是遗传算法的魔力的一部分。此代码生成人口,因此您最终得到了一个好的解决方案池。您可以添加停止标准,或者您可以将突变率设置得更低。如果您愿意,您还可以将每一代之后的最佳评分解决方案存档为单独的列表。
【解决方案2】:

您可以尝试使用元启发式/随机局部搜索算法来解决许多 cmets 中提到的此类问题以及 BurnsCA 的解决方案。

模拟退火和遗传算法是示例,但还有更多示例。 这种方法是否可行,取决于您计算目标函数变化的速度,即评估解决方案的质量及其变化。

如果您的模拟输出具有微小变化会极大地改变结果的特性,那么它可能是也可能不是比强制执行一些随机分配并获取最佳分配更好的方法。您必须进行实验。

这些算法本身的实现通常不是很复杂,我认为你甚至可以使用像 NMath 这样的库,例如来看看

http://www.centerspace.net/doc/NMath/user/simulated-annealing-85273.htm

您的“目标函数”,即您试图最大化(或最小化)的值是模拟的输出。

虽然算法本身的实现并不难,但它们各个方面的高效实现却是。

您需要做的是定义邻域函数,即从解决方案(或您喜欢的状态)到另一个解决方案的方法。 在您的情况下,这可能涉及 BurnsCA 建议的代码示例,这将是一个 1-opt 移动,因为您将为 one 参数随机选择另一个值。如果 1-opt 没有足够快地显示出改进,您也可以尝试 2-opt 移动或更多。

接下来您需要一种方法来评估采取行动的效果。换句话说,您当前的价值与您采取行动将拥有的价值之间的目标函数有什么区别。 如果可能,您需要一种评估移动的方法,而不必每次都重新执行整个模拟。

最简单的方法(通常称为下降)是随机移动到相邻解决方案,如果找到更好的(在您的情况下目标函数更高)值,则将其设为新解决方案,然后重复该步骤直到找不到更多改进为止。

这种方法的问题在于,您很快就会陷入局部最大值。模拟退火提供了一种尝试避免这种情况的方法,它不仅选择改进,而且选择非改进移动,其概率取决于当前的“温度”,这只是一个变量,您根据某些退火每次迭代都会减少时间表您定义。

由于实现这些方法的关键不在于整体算法本身(尽管确实需要一些时间),而是在于您的社区和社区评估功能的实现,我个人认为没有很多通过使用一些框架来节省时间。

如果这是一次性的事情并且上述方法不可行,您还可以考虑在数千台机器上并行计算以获得最佳解决方案。例如。 Azure Batch 或类似服务。由于您可以在 30 分钟内测试 50 个 mio 组合(在一台机器上没有并行化?),您原则上可以提供 20 000 个虚拟机并在 30 分钟内测试所有组合。

【讨论】:

  • 谢谢,但问题是必须多次找到最佳解决方案,而不仅仅是一次,以获得更多产品,而且如果我使用 AI 获得比当前蛮力 600 万自定义选择组合更好的结果,我可以添加更多参数,进一步改进计算。
  • 不客气,在这种情况下,模拟退火(或类似)之类的(元)启发式可能值得一试。我想指出的一个细节是,没有人工智能或本地搜索算法会为您提供有保证的全局最优解决方案,只有局部最优可能会或可能会不是全局最优的(除非你通过例如暴力破解将问题解决到最优,否则你不会知道)。
  • 我知道,但至少我可以与 6000 万的最佳结果进行比较。我现在拥有的蛮力组合并取得了不错的结果,所以如果我得到至少 20-30% 更好的结果是好的
【解决方案3】:

您不需要机器学习框架来实现本地优化算法。

// Example linear calculation
public int Calculation(int a, int b, int c, int d, int e, int f)
{
    int result = 0;
    unchecked
    {
        result = (int)((double)a * Math.Tan((double)b * Math.PI / ((double)c + 0.001)));
        result += d + e + f;
    }

    return result;
}


var rand = new Random();

// The currently best known solution set
var best = new int[6] { 1, 1, 1, 1, 1, 1 };

// Score for best known solution set
int bestScore = int.MinValue;

// loop variables
int score;
var work = new int[6];

// Loop as appropriate.
for (int i=0; i<500; i++)
{
    // Copy over the best solution to modify it
    best.CopyTo(work, 0);

    // Change one of the parameters of the best solution
    work[rand.Next() % 6] = rand.Next() % 101;

    // Calculate new score with modified solution
    score = Calculation(work[0], work[1], work[2], work[3], work[4], work[5]);

    // Only keep this solution if it's better than anything else
    if (score > bestScore)
    {
        work.CopyTo(best, 0);
        bestScore = score;
    }
}

以上内容很快就收敛到一个解决方案,主要是因为计算功能非常友好。 500 次迭代后:

int[6] { 98, 1, 2, 98, 99, 100 }

最佳解决方案是{ 100, 1, 2, 100, 100, 100 }

请注意,这种局部优化方法仅适用于大多数线性问题。

未显示,但您还希望查看不同的起始值,或者多次重新运行整个程序。

在维基百科页面上有一个nifty image hill climbing 算法,它显示了这种方法试图做的事情的本质。

【讨论】:

  • 那张漂亮的图片并没有描述您的算法(动画看起来会非常不同),而是稍微复杂一点的模拟退火方法。
  • 我不知道这是否可行,这只是一个随机试验,它不会尝试接近最佳结果的数字。另请注意,更改这 6 个数字中的单个数字会得到与其他数字完全不同的结果。
  • 好吧,如果您的潜在问题是非线性的,您将需要一种完全不同的方法。
  • 它不是非常线性的,因为通过改变某个数字可以在改变其他数字时得到更好的结果。
  • 搜索算法的改进方法有很多种,但确实取决于问题,并且没有给出问题的细节。您可以在每个循环中修改上述示例中的多个参数,或者仅将它们改变 10%,或者通过任何其他有意义的方式更改它们。但同样,这取决于Calculation,并且没有发布任何详细信息。
猜你喜欢
  • 2023-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多