使用scipy最小化多变量函数。衍生物未知

时间:2015-04-24 19:25:48

标签: python scipy mathematical-optimization minimization

我有一个函数实际上是对另一个程序的调用(一些Fortran代码)。当我调用这个函数(run_moog)时,我可以解析4个变量,并返回6个值。这些值都应该接近0(为了最小化)。但是,我将它们合并为:np.sum(results**2)。现在我有一个标量函数。我想尽量减少这个功能,即让np.sum(results**2)尽可能接近零 注意:当此函数(run_moog)获取4个输入参数时,它会根据这些参数为Fortran代码创建一个输入文件。

我尝试了几种方法来优化the scipy docs。但没有一个按预期工作。最小化应该能够对4个变量进行限制。这是一次尝试:

from scipy.optimize import minimize # Tried others as well from the docs
x0 = 4435, 3.54, 0.13, 2.4
bounds = [(4000, 6000), (3.00, 4.50), (-0.1, 0.1), (0.0, None)]
a = minimize(fun_mmog, x0, bounds=bounds, method='L-BFGS-B')  # I've tried several different methods here
print a

然后给我

  status: 0
 success: True
    nfev: 5
     fun: 2.3194639999999964
       x: array([  4.43500000e+03,   3.54000000e+00,   1.00000000e-01,
         2.40000000e+00])
 message: 'CONVERGENCE: NORM_OF_PROJECTED_GRADIENT_<=_PGTOL'
     jac: array([ 0., 0., -54090399.99999981, 0.])
     nit: 0

第三个参数略有变化,而其他参数完全相同。还有5个函数调用(nfev)但没有迭代(nit)。 output from scipy is shown here.

4 个答案:

答案 0 :(得分:7)

几种可能性:

  1. 尝试COBYLA。它应该是无衍生的,并支持不等式约束。
  2. 您不能通过普通界面使用不同的epsilons;所以尝试用1e4缩放你的第一个变量。 (将它分开,然后再回来。)
  3. 跳过普通的自动jacobian构造函数,然后创建自己的构造函数:
  4. 假设您正在尝试使用SLSQP,并且您没有提供雅各比功能。它适合你。它的代码位于slsqp.py中的approx_jacobian。这是一个精简版本:

    def approx_jacobian(x,func,epsilon,*args):
        x0 = asfarray(x)
        f0 = atleast_1d(func(*((x0,)+args)))
        jac = zeros([len(x0),len(f0)])
        dx = zeros(len(x0))
        for i in range(len(x0)):
            dx[i] = epsilon
            jac[i] = (func(*((x0+dx,)+args)) - f0)/epsilon
            dx[i] = 0.0
    
        return jac.transpose()
    

    您可以尝试用以下代码替换该循环:

        for (i, e) in zip(range(len(x0)), epsilon):
            dx[i] = e
            jac[i] = (func(*((x0+dx,)+args)) - f0)/e
            dx[i] = 0.0
    

    你不能将此作为jacobian提供给minimize,但为此修复它很简单:

    def construct_jacobian(func,epsilon):
        def jac(x, *args):
            x0 = asfarray(x)
            f0 = atleast_1d(func(*((x0,)+args)))
            jac = zeros([len(x0),len(f0)])
            dx = zeros(len(x0))
            for i in range(len(x0)):
                dx[i] = epsilon
                jac[i] = (func(*((x0+dx,)+args)) - f0)/epsilon
                dx[i] = 0.0
    
            return jac.transpose()
        return jac
    

    然后,您可以拨打minimize,如:

    minimize(fun_mmog, x0,
             jac=construct_jacobian(fun_mmog, [1e0, 1e-4, 1e-4, 1e-4]),
             bounds=bounds, method='SLSQP')
    

答案 1 :(得分:2)

听起来你的目标函数并没有表现良好的衍生品。输出jac: array([ 0., 0., -54090399.99999981, 0.])中的行意味着仅更改第三个变量值很重要。因为衍生物w.r.t.对于这个变量实际上是无限的,函数中可能存在一些错误。这也是第三个变量值以最大值结束的原因。

我建议您先查看衍生物,至少在参数空间的几个点上。使用有限差分和SciPy的fmin_l_bfgs_b1e-8的默认步长来计算它们。 Here是如何计算派生的一个示例。

尝试也绘制目标函数。例如,保持两个参数不变,让另外两个参数变化。如果函数有多个局部最优,那么你不应该使用像BFGS这样的基于梯度的方法。

答案 2 :(得分:1)

获得渐变的分析表达式有多难?如果你有,你可以使用有限差分逼近Hessian的乘积。然后,您可以使用其他可用的优化例程。

在SciPy中可用的各种优化程序中,名为TNC(Newton Conjugate Gradient with Truncation)的程序对与该问题相关的数值非常稳健。

答案 3 :(得分:1)

Nelder-Mead Simplex Method(上面评论中Cristián Antuña建议)众所周知,在不了解衍生物的情况下优化(明显不良)函数是一个不错的选择(参见Numerical Recipies In C, Chapter 10 })。

您的问题有两个具体方面。第一个是对输入的约束,第二个是缩放问题。以下建议解决这些问题,但您可能需要在它们之间手动迭代几次,直到工作正常。

输入约束

假设您的输入约束形成convex region(如上面的示例所示,但我想稍微概括一下),那么您可以编写一个函数

is_in_bounds(p):
    # Return if p is in the bounds

使用此函数,假设算法想要从点from_移动到点to,其中from_已知在该区域中。然后,以下函数将有效地找到它可以继续的两点之间的直线上的最后一点:

from numpy.linalg import norm

def progress_within_bounds(from_, to, eps):
    """
    from_ -- source (in region)
    to -- target point
    eps -- Eucliedan precision along the line
    """

    if norm(from_, to) < eps:
        return from_
    mid = (from_ + to) / 2
    if is_in_bounds(mid):
        return progress_within_bounds(mid, to, eps)
    return progress_within_bounds(from_, mid, eps)

(注意,这个功能可以针对某些地区进行优化,但是它几乎不值得打扰,因为它甚至不会调用你原来的对象函数,这是一个很昂贵的函数。)

Nelder-Mead的一个不错的方面是该功能执行了一系列非常直观的步骤。其中一些点显然会让你离开这个区域,但很容易修改它。这是一个implementation of Nelder Mead,其中的修改标记在##################################################################形式的行对之间:

import copy

'''
    Pure Python/Numpy implementation of the Nelder-Mead algorithm.
    Reference: https://en.wikipedia.org/wiki/Nelder%E2%80%93Mead_method
'''


def nelder_mead(f, x_start, 
        step=0.1, no_improve_thr=10e-6, no_improv_break=10, max_iter=0,
        alpha = 1., gamma = 2., rho = -0.5, sigma = 0.5):
    '''
        @param f (function): function to optimize, must return a scalar score 
            and operate over a numpy array of the same dimensions as x_start
        @param x_start (numpy array): initial position
        @param step (float): look-around radius in initial step
        @no_improv_thr,  no_improv_break (float, int): break after no_improv_break iterations with 
            an improvement lower than no_improv_thr
        @max_iter (int): always break after this number of iterations.
            Set it to 0 to loop indefinitely.
        @alpha, gamma, rho, sigma (floats): parameters of the algorithm 
            (see Wikipedia page for reference)
    '''

    # init
    dim = len(x_start)
    prev_best = f(x_start)
    no_improv = 0
    res = [[x_start, prev_best]]

    for i in range(dim):
        x = copy.copy(x_start)
        x[i] = x[i] + step
        score = f(x)
        res.append([x, score])

    # simplex iter
    iters = 0
    while 1:
        # order
        res.sort(key = lambda x: x[1])
        best = res[0][1]

        # break after max_iter
        if max_iter and iters >= max_iter:
            return res[0]
        iters += 1

        # break after no_improv_break iterations with no improvement
        print '...best so far:', best

        if best < prev_best - no_improve_thr:
            no_improv = 0
            prev_best = best
        else:
            no_improv += 1

        if no_improv >= no_improv_break:
            return res[0]

        # centroid
        x0 = [0.] * dim
        for tup in res[:-1]:
            for i, c in enumerate(tup[0]):
                x0[i] += c / (len(res)-1)

        # reflection
        xr = x0 + alpha*(x0 - res[-1][0])
        ##################################################################
        ##################################################################
        xr = progress_within_bounds(x0, x0 + alpha*(x0 - res[-1][0]), prog_eps)
        ##################################################################
        ##################################################################
        rscore = f(xr)
        if res[0][1] <= rscore < res[-2][1]:
            del res[-1]
            res.append([xr, rscore])
            continue

        # expansion
        if rscore < res[0][1]:
            xe = x0 + gamma*(x0 - res[-1][0])
            ##################################################################
            ##################################################################
            xe = progress_within_bounds(x0, x0 + gamma*(x0 - res[-1][0]), prog_eps)
            ##################################################################
            ################################################################## 
            escore = f(xe)
            if escore < rscore:
                del res[-1]
                res.append([xe, escore])
                continue
            else:
                del res[-1]
                res.append([xr, rscore])
                continue

        # contraction
        xc = x0 + rho*(x0 - res[-1][0])
        ##################################################################
        ##################################################################
        xc = progress_within_bounds(x0, x0 + rho*(x0 - res[-1][0]), prog_eps)
        ##################################################################
        ################################################################## 
        cscore = f(xc)
        if cscore < res[-1][1]:
            del res[-1]
            res.append([xc, cscore])
            continue

        # reduction
        x1 = res[0][0]
        nres = []
        for tup in res:
            redx = x1 + sigma*(tup[0] - x1)
            score = f(redx)
            nres.append([redx, score])
        res = nres

注意此实施是GPL,这对您或不适合。但是,从任何伪代码修改NM非常容易,并且在任何情况下你都可能想要输入simulated annealing

<强>缩放

这是一个棘手的问题,但jasaarim对此提出了一个有趣的观点。一旦修改后的NM算法找到了一个点,您可能希望在修复几个维度时运行matplotlib.contour,以便查看该函数的行为方式。此时,您可能希望重新缩放一个或多个维度,然后重新运行修改后的NM。

-