如何使用牛顿方法(代码非线性代数)找到最小的非线性,多元函数

时间:2008-12-24 20:23:38

标签: minimization newtons-method nonlinear-functions

我正在尝试进行一些参数估计,并希望选择最小化预测方程超过约30个变量的平方误差的参数估计。如果方程是线性的,我只计算30个偏导数,将它们全部设为零,并使用线性方程求解器。但不幸的是,这个等式是非线性的,它的衍生物也是如此。

如果等式超过单个变量,我只会使用Newton's method(也称为Newton-Raphson)。 Web上有丰富的示例和代码来实现Newton的方法,用于单个变量的函数

鉴于我有大约30个变量,如何使用牛顿方法为这个问题编写数值解?我有闭合形式的方程,可以计算一阶和二阶导数,但我不知道如何从那里开始。我在网上发现了大量的治疗方法,但很快就会进入重基质表示法。我在维基百科上找到了something moderately helpful,但是我无法将其翻译成代码。

我担心崩溃的是矩阵代数和矩阵求逆。我可以使用线性方程求解器反转矩阵,但我担心得到正确的行和列,避免换位错误,等等。

非常具体:

  • 我想使用表将变量映射到它们的值。我可以写一个这样一个表的函数,它返回给出这样一个表作为参数的平方误差。我还可以创建相对于任何给定变量返回偏导数的函数。

  • 我对表中的值有一个合理的初始估计值,所以我不担心收敛。

  • 我不确定如何编写使用估计的循环(每个变量的值表),函数和偏导函数表以产生新的估计值。

最后一点是我要帮助的。任何直接的帮助或指向好的来源都将受到热烈的赞赏。


编辑:由于我有封闭形式的第一和第二衍生物,我想利用它们并避免更简单的融合方法,如单纯形搜索。

7 个答案:

答案 0 :(得分:4)

数字食谱链接最有帮助。我最终象征性地区分我的误差估计以产生30个偏导数,然后使用牛顿方法将它们全部设置为零。以下是代码的重点:

__doc.findzero = [[function(functions, partials, point, [epsilon, steps]) returns table, boolean
Where
  point       is a table mapping variable names to real numbers 
              (a point in N-dimensional space)
  functions   is a list of functions, each of which takes a table like
              point as an argument
  partials    is a list of tables; partials[i].x is the partial derivative
              of functions[i] with respect to 'x'
  epilson     is a number that says how close to zero we're trying to get
  steps       is max number of steps to take (defaults to infinity)
  result      is a table like 'point', boolean that says 'converged'
]]

-- See Numerical Recipes in C, Section 9.6 [http://www.nrbook.com/a/bookcpdf.php]




function findzero(functions, partials, point, epsilon, steps)
  epsilon = epsilon or 1.0e-6
  steps = steps or 1/0
  assert(#functions > 0)
  assert(table.numpairs(partials[1]) == #functions,
         'number of functions not equal to number of variables')
  local equations = { }
  repeat
    if Linf(functions, point) <= epsilon then
      return point, true
    end
    for i = 1, #functions do
      local F = functions[i](point)
      local zero = F
      for x, partial in pairs(partials[i]) do
        zero = zero + lineq.var(x) * partial(point)
      end
      equations[i] = lineq.eqn(zero, 0)
    end
    local delta = table.map(lineq.tonumber, lineq.solve(equations, {}).answers)
    point = table.map(function(v, x) return v + delta[x] end, point)
    steps = steps - 1
  until steps <= 0
  return point, false
end


function Linf(functions, point)
  -- distance using L-infinity norm
  assert(#functions > 0)
  local max = 0
  for i = 1, #functions do
    local z = functions[i](point)
    max = math.max(max, math.abs(z))
  end
  return max
end

答案 1 :(得分:3)

您可以在C网页的Numerical Recipes中找到所需内容。有一个free version available onlineHere(PDF)是包含用C实现的Newton-Raphson方法的章节。您可能还想查看Netlib(LINPack等人)可用的内容。

答案 2 :(得分:3)

作为使用牛顿方法的替代方法,Simplex Method of Nelder-Mead非常适合这个问题,并在C中的数值重复中引用。

罗布

答案 3 :(得分:3)

您要求的是函数最小化算法。有两个主要类:本地和全球。您的问题是最小二乘,因此局部和全局最小化算法应该收敛到相同的唯一解。局部最小化比全局最有效,所以选择它。

有许多局部最小化算法,但一个特别适合最小二乘问题的算法是Levenberg-Marquardt。如果你没有这样的求解器(例如来自MINPACK)那么你可能可以使用牛顿的方法:

x <- x - (hessian x)^-1 * grad x

使用线性求解器计算逆矩阵乘以向量。

答案 4 :(得分:1)

既然你已经有了偏导数,那么一般的梯度下降方法呢?

答案 5 :(得分:1)

也许你认为你有一个足够好的解决方案,但对我来说,最简单的思考方法是首先在1变量的情况下理解它,然后将其扩展到矩阵的情况。

在1变量的情况下,如果将一阶导数除以二阶导数,则得到(负)步长到下一个试验点,例如: -V / A

在N变量情况下,一阶导数是矢量,二阶导数是矩阵(Hessian)。您将导数向量乘以二阶导数的倒数,结果是下一个试验点的负步长向量,例如, -V *(1 / A)

我假设你可以得到二阶导数的Hessian矩阵。你需要一个例程来反转它。在各种线性代数包中有很多这些,它们非常快。

(对于不熟悉这个想法的读者,假设两个变量是x和y,表面是v(x,y)。那么一阶导数就是向量:

 V = [ dv/dx, dv/dy ]

,二阶导数是矩阵:

 A = [dV/dx]
    [dV/dy]

或:

 A = [ d(dv/dx)/dx, d(dv/dy)/dx]
    [ d(dv/dx)/dy, d(dv/dy)/dy]

或:

 A = [d^2v/dx^2, d^2v/dydx]
    [d^2v/dxdy, d^2v/dy^2]

是对称的。)

如果表面是抛物线(常数二阶导数),它将在一步得到答案。另一方面,如果二阶导数非常不恒定,则可能会遇到振荡。将每一步切成两半(或一些部分)应使其稳定。

如果N == 1,你会发现它与1变量的情况相同。

祝你好运。

补充:你想要代码:

 double X[N];
// Set X to initial estimate
while(!done){
    double V[N];    // 1st derivative "velocity" vector
    double A[N*N];  // 2nd derivative "acceleration" matrix
    double A1[N*N]; // inverse of A
    double S[N];    // step vector
    CalculateFirstDerivative(V, X);
    CalculateSecondDerivative(A, X);
    // A1 = 1/A
    GetMatrixInverse(A, A1);
    // S = V*(1/A)
    VectorTimesMatrix(V, A1, S);
    // if S is small enough, stop
    // X -= S
    VectorMinusVector(X, S, X);
}

答案 6 :(得分:0)

我的意见是使用随机优化器,例如粒子群方法。