18

计划目的:整合。我正在为高维(高达 100)实现自适应正交(又名数值积分)算法。这个想法是通过使用与该点的误差估计成比例的采样密度来评估点,将体积随机分成更小的部分。早期我“老化”了一个均匀的样本,然后根据估计误差的高斯分布随机选择点。以类似于模拟退火的方式,我“降低温度”并随着时间的推移降低我的高斯标准偏差,因此低误差点最初有一个公平的机会被选择,但后来被选择为稳步下降可能性。这使程序能够偶然发现由于误差函数的缺陷而可能遗漏的尖峰。(我的算法在精神上类似于马尔可夫链蒙特卡洛积分。)

功能特点。要集成的功能是估计因自然灾害导致的多栋建筑物的保险单损失。保单功能不流畅:有免赔额、最高赔付、分层(如损失100万美元零赔付、1-200万美元100%赔付、200万美元以上零赔付)等奇数保单条款。这引入了在许多平面上没有导数的非线性行为和函数。在策略函数之上是破坏函数,它因建筑物类型和飓风强度而异,绝对不是钟形的。

问题上下文:错误函数。困难在于选择一个好的误差函数。对于每个点,我都记录了似乎对此有用的度量:函数的大小,由于先前的度量(一阶导数的代理)而改变了多少,该点占据的区域的体积(更大的体积可以更好地隐藏错误),以及与区域形状相关的几何因素。我的误差函数将是这些度量的线性组合,其中每个度量被分配不同的权重。(如果我得到不好的结果,我会考虑非线性函数。)为了帮助我完成这项工作,我决定对每个权重的各种可能值进行优化,因此微软解决方案基金会。

优化什么:错误等级。我的度量是标准化的,从零到一。随着集成的进行,这些错误值会逐渐修改,以反映函数值、更改等的最近平均值。因此,我不是试图创建一个产生实际错误值的函数,而是产生一个排序相同的数字真正的误差,即如果所有采样点都按这个估计的误差值排序,它们应该收到一个类似于如果按真正的误差排序时它们将收到的排名。

并非所有点都是平等的。我非常关心具有 #1 真实错误的点区域是否排名 #1000(反之亦然),但很少关心 #500 点是否排名 #1000。我衡量成功的标准是在算法执行的中途将多个区域的以下总和最小化:

ABS(Log2(trueErrorRank) - Log2(estimatedErrorRank))

对于 Log2,我使用了一个函数,它返回小于或等于该数字的 2 的最大幂。从这个定义,得出有用的结果。交换#1 和#2 需要一分,但交换#2 和#3 不需要任何费用。这具有将点分层为两个范围的幂的效果。在一个范围内交换的点不会添加到函数中。

我如何评价。我已经构建了一个名为Rank的类来执行此操作:

  1. 按真实错误对所有区域进行一次排名。

  2. 对于每组单独的参数化权重,它计算该区域的试验(估计)误差。

  3. 按该试验错误对区域进行排序。

  4. 计算每个区域的试验等级。

  5. 将两个等级的对数的绝对差相加,并将其称为参数化的值,因此要最小化的值。

C# 代码。完成所有这些后,我只需要一种方法来设置 Microsoft Solver Foundation 来为我找到最佳参数。语法让我难住了。这是我到目前为止的 C# 代码。在其中,您将看到对我已确定的三个问题的评论。也许你可以发现更多!任何想法如何使这项工作?

public void Optimize()
{
    // Get the parameters from the GUI and figures out the low and high values for each weight.
    ParseParameters();

    // Computes the true rank for each region according to true error.
    var myRanker = new Rank(ErrorData, false);

    // Obtain Microsoft Solver Foundation's core solver object.
    var solver = SolverContext.GetContext();
    var model = solver.CreateModel();

    // Create a delegate that can extract the current value of each solver parameter
    // and stuff it in to a double array so we can later use it to call LinearTrial.
    Func<Model, double[]> marshalWeights = (Model m) =>
    {
        var i = 0;
        var weights = new double[myRanker.ParameterCount];
        foreach (var d in m.Decisions)
        {
            weights[i] = d.ToDouble();
            i++;
        }
        return weights;
    };

    // Make a solver decision for each GUI defined parameter.
    // Parameters is a Dictionary whose Key is the parameter name, and whose 
    // value is a Tuple of two doubles, the low and high values for the range.
    // All are Real numbers constrained to fall between a defined low and high value.
    foreach (var pair in Parameters)
    {
        // PROBLEM 1! Should I be using Decisions or Parameters here?
        var decision = new Decision(Domain.RealRange(ToRational(pair.Value.Item1), ToRational(pair.Value.Item2)), pair.Key);
        model.AddDecision(decision);
    }

    // PROBLEM 2! This calls myRanker.LinearTrial immediately, 
    // before the Decisions have values. Also, it does not return a Term.
    // I want to pass it in a lambda to be evaluated by the solver for each attempted set
    // of decision values.
    model.AddGoal("Goal", GoalKind.Minimize,

        myRanker.LinearTrial(marshalWeights(model), false)
    );
    // PROBLEM 3! Should I use a directive, like SimplexDirective? What type of solver is best?
    var solution = solver.Solve();
    var report = solution.GetReport();
    foreach (var d in model.Decisions)
    {
        Debug.WriteLine("Decision " + d.Name + ": " + d.ToDouble());
    }
    Debug.WriteLine(report);

    // Enable/disable buttons.
    UpdateButtons();
}

更新:我决定寻找另一个库作为后备,并找到了 DotNumerics ( http://dotnumerics.com/ )。他们的 Nelder-Mead Simplex 求解器很容易调用:

    Simplex simplex = new Simplex()
    {
        MaxFunEvaluations = 20000,
        Tolerance = 0.001
    };
    int numVariables = Parameters.Count();
    OptBoundVariable[] variables = new OptBoundVariable[numVariables];

    //Constrained Minimization on the intervals specified by the user, initial Guess = 1;
    foreach (var x in Parameters.Select((parameter, index) => new { parameter, index }))
    {
        variables[x.index] = new OptBoundVariable(x.parameter.Key, 1, x.parameter.Value.Item1, x.parameter.Value.Item2);
    }


    double[] minimum = simplex.ComputeMin(ObjectiveFunction, variables);

    Debug.WriteLine("Simplex Method. Constrained Minimization.");
    for (int i = 0; i < minimum.Length; i++)
        Debug.WriteLine(Parameters[i].Key + " = " + minimum[i].ToString());

我所需要的只是将 ObjectiveFunction 实现为采用双数组的方法:

private double ObjectiveFunction(double[] weights)
{
    return Ranker.LinearTrial(weights, false);
}

我没有针对真实数据尝试过,但我在 Excel 中创建了一个模拟来设置测试数据并对其进行评分。他们的算法得到的结果并不完美,但给出了一个很好的解决方案。

4

1 回答 1

5

这是我的 TL;DR 总结:他不知道如何最小化 的返回值LinearTrial,它需要一个双精度数组。这个数组中的每个值都有自己的最小值/最大值,他正在使用Decisions.

如果这是正确的,您似乎可以执行以下操作:

double[] minimums = Parameters.Select(p => p.Value.Item1).ToArray();
double[] maximums = Parameters.Select(p => p.Value.Item2).ToArray();
// Some initial values, here it's a quick and dirty average
double[] initials = Parameters.Select(p => (p.Item1 + p.Item2)/2.0).ToArray();

var solution = NelderMeadSolver.Solve(
    x => myRanker.LinearTrial(x, false), initials, minimums, maximums);

// Make sure you check solution.Result to ensure that it found a solution.
// For this, I'll assume it did.

// Value 0 is the minimized value of LinearTrial
int i = 1;
foreach (var param in Parameters)
{
    Console.WriteLine("{0}: {1}", param.Key, solution.GetValue(i));
    i++;
}        

NelderMeadSolver是 MSF 3.0 中的新功能。根据SolveMSF 程序集中的文档,静态方法“查找指定函数的最小值”(尽管 MSDN 文档为空白并显示错误的函数签名)。

免责声明: 我不是 MSF 专家,但以上内容对我和我的测试目标函数(权重求和)都有效。

于 2012-11-16T01:08:25.077 回答