【问题标题】:Python's scipy.optimize.minimize with SLSQP fails with "Positive directional derivative for linesearch"带有 SLSQP 的 Python scipy.optimize.minimize 因“线搜索的正向导数”而失败
【发布时间】:2018-05-06 16:32:10
【问题描述】:

我有一个受不等式约束的最小二乘最小化问题,我正在尝试使用 scipy.optimize.minimize 来解决这个问题。似乎有两个不等式约束选项:COBYLA 和 SLSQP。

我首先尝试了 SLSQP,因为它允许函数的显式偏导数最小化。根据问题的规模,它会失败并出现错误:

Positive directional derivative for linesearch    (Exit mode 8)

每当施加区间或更一般的不等式约束时。

之前已经观察到这一点,例如here。手动缩放要最小化的函数(以及相关的偏导数)似乎可以解决这个问题,但我无法通过更改选项中的 ftol 来达到相同的效果。

总的来说,这整件事让我对日常工作的稳健方式产生怀疑。这是一个简化的示例:

import numpy as np
import scipy.optimize as sp_optimize

def cost(x, A, y):

    e = y - A.dot(x)
    rss = np.sum(e ** 2)

    return rss

def cost_deriv(x, A, y):

    e = y - A.dot(x)
    deriv0 = -2 * e.dot(A[:,0])
    deriv1 = -2 * e.dot(A[:,1])

    deriv = np.array([deriv0, deriv1])

    return deriv


A = np.ones((10,2)); A[:,0] = np.linspace(-5,5, 10)
x_true = np.array([2, 2/20])
y = A.dot(x_true)
x_guess = x_true / 2

prm_bounds = ((0, 3), (0,1))

cons_SLSQP = ({'type': 'ineq', 'fun' : lambda x: np.array([x[0] - x[1]]),
               'jac' : lambda x: np.array([1.0, -1.0])})

# works correctly
min_res_SLSQP = sp_optimize.minimize(cost, x_guess, args=(A, y), jac=cost_deriv, bounds=prm_bounds, method='SLSQP', constraints=cons_SLSQP, options={'disp': True})
print(min_res_SLSQP)

# fails
A = 100 * A
y = A.dot(x_true)
min_res_SLSQP = sp_optimize.minimize(cost, x_guess, args=(A, y), jac=cost_deriv, bounds=prm_bounds, method='SLSQP', constraints=cons_SLSQP, options={'disp': True})
print(min_res_SLSQP)

# works if bounds and inequality constraints removed
min_res_SLSQP = sp_optimize.minimize(cost, x_guess, args=(A, y), jac=cost_deriv,
method='SLSQP', options={'disp': True})
print(min_res_SLSQP)

应该如何设置 ftol 以避免失败?更一般地说,COBYLA 会出现类似的问题吗?对于这种不等式约束的最小二乘优化问题,COBYLA 是更好的选择吗?

发现在成本函数中使用平方根可以提高性能。但是,对于问题的非线性重新参数化(更简单但更接近我在实践中需要做的事情),它再次失败。以下是详细信息:

import numpy as np
import scipy.optimize as sp_optimize


def cost(x, y, g):

    e = ((y - x[1]) / x[0]) - g

    rss = np.sqrt(np.sum(e ** 2))

    return rss


def cost_deriv(x, y, g):

    e = ((y- x[1]) / x[0]) - g

    factor = 0.5 / np.sqrt(e.dot(e))
    deriv0 = -2 * factor * e.dot(y - x[1]) / (x[0]**2)
    deriv1 = -2 * factor * np.sum(e) / x[0]

    deriv = np.array([deriv0, deriv1])

    return deriv


x_true = np.array([1/300, .1])
N = 20
t = 20 * np.arange(N)
g = 100 * np.cos(2 * np.pi * 1e-3 * (t - t[-1] / 2))
y = g * x_true[0] + x_true[1]

x_guess = x_true / 2
prm_bounds = ((1e-4, 1e-2), (0, .4))

# check derivatives
delta = 1e-9
C0 = cost(x_guess, y, g)
C1 = cost(x_guess + np.array([delta, 0]), y, g)
approx_deriv0 = (C1 - C0) / delta
C1 = cost(x_guess + np.array([0, delta]), y, g)
approx_deriv1 = (C1 - C0) / delta
approx_deriv = np.array([approx_deriv0, approx_deriv1])
deriv = cost_deriv(x_guess, y, g)

# fails
min_res_SLSQP = sp_optimize.minimize(cost, x_guess, args=(y, g), jac=cost_deriv,
bounds=prm_bounds, method='SLSQP', options={'disp': True})
print(min_res_SLSQP)

【问题讨论】:

    标签: python optimization scipy mathematical-optimization nonlinear-optimization


    【解决方案1】:

    不是最小化np.sum(e ** 2),而是最小化sqrt(np.sum(e ** 2)),或者更好(在计算方面):np.linalg.norm(e)

    本次修改:

    • 不会改变您对x 的解决方案
    • 如果需要原始目标,则需要后处理(可能不需要)
    • 更加健壮

    有了这个变化,所有情况都可以工作,甚至使用数值微分(我懒得修改渐变,需要反映这一点!)。

    示例输出(func-evals 的数量给出了 num-diff):

    Optimization terminated successfully.    (Exit mode 0)
                Current function value: 3.815547437029837e-06
                Iterations: 16
                Function evaluations: 88
                Gradient evaluations: 16
         fun: 3.815547437029837e-06
         jac: array([-6.09663382, -2.48862544])
     message: 'Optimization terminated successfully.'
        nfev: 88
         nit: 16
        njev: 16
      status: 0
     success: True
           x: array([ 2.00000037,  0.10000018])
    Optimization terminated successfully.    (Exit mode 0)
                Current function value: 0.0002354577991007501
                Iterations: 23
                Function evaluations: 114
                Gradient evaluations: 23
         fun: 0.0002354577991007501
         jac: array([ 435.97259208,  288.7483819 ])
     message: 'Optimization terminated successfully.'
        nfev: 114
         nit: 23
        njev: 23
      status: 0
     success: True
           x: array([ 1.99999977,  0.10000014])
    Optimization terminated successfully.    (Exit mode 0)
                Current function value: 0.0003392807206384532
                Iterations: 21
                Function evaluations: 112
                Gradient evaluations: 21
         fun: 0.0003392807206384532
         jac: array([ 996.57340243,   51.19298764])
     message: 'Optimization terminated successfully.'
        nfev: 112
         nit: 21
        njev: 21
      status: 0
     success: True
           x: array([ 2.00000008,  0.10000104])
    

    虽然 SLSQP 可能存在一些问题,但考虑到广泛的应用范围,它仍然是经过最多测试和最强大的代码之一!

    与 COBYLA 相比,我还希望 SLSQP 在这里要好得多,因为后者主要基于线性化。 (但只是猜测;考虑到最小化界面很容易尝试!)

    替代方案

    一般来说,凸二次规划的基于内点的求解器将是这里的最佳方法。但是为此,您需要离开 scipy。 (或者也许 SOCP 求解器会更好......我不确定)。

    cvxpy 带来了一个很好的建模系统和一个优秀的开源求解器(ECOS;虽然从技术上讲是一个圆锥求解器 -> 更通用且不太健壮;但应该胜过 SLSQP)。

    使用 cvxpy 和 ECOS,如下所示:

    import numpy as np
    import cvxpy as cvx
    
    """ Problem data """
    A = np.ones((10,2)); A[:,0] = np.linspace(-5,5, 10)
    x_true = np.array([2, 2/20])
    y = A.dot(x_true)
    x_guess = x_true / 2
    
    prm_bounds = ((0, 3), (0,1))
    
    # problematic case
    A = 100 * A
    y = A.dot(x_true)
    
    """ Solve """
    x = cvx.Variable(len(x_true))
    constraints = [x[0] >= x[1]]
    for ind, (lb, ub) in enumerate(prm_bounds):  # ineffecient -> matrix-based expr better!
        constraints.append(x[ind] >= lb)
        constraints.append(x[ind] <= ub)
    
    objective = cvx.Minimize(cvx.norm(A*x - y))
    problem = cvx.Problem(objective, constraints)
    problem.solve(solver=cvx.ECOS, verbose=False)
    print(problem.status)
    print(problem.value)
    print(x.value.T)
    
    # optimal
    # -6.67593652593801e-10
    # [[ 2.   0.1]]
    

    【讨论】:

    • 好的,这行得通。您能否简要解释一下为什么包含平方根会使解决方案更加健壮?我现在将在实际(而不是上面简化的)问题上尝试这个。此外,如果我对参数有直接的区间约束,则可以通过 bounds 参数或通过显式编写上限和下限的不等式约束将其传达给优化器。这两种方法在性能上是否有任何预期差异?
    • (1) 因为每个非平凡的优化算法都在与有限精度浮点引入的数值问题作斗争。所以有(至少是隐含的)关于数字大小/大小的假设。一个正方形爆炸得非常快! (2) 一般来说:使用边界! 100%!这取决于求解器是否明确处理这些问题,或者是否转换为不等式或使用其他替换。 (我不知道 SLSQP 在内部做什么;它可能很容易被发现;但使用 bounds 参数永远不会更糟)
    • 有道理。顺便说一句,我真正的问题是受区间和不等式约束的非线性最小二乘最小化。我假设 SLSQP 的平方根版本和 cvxpy 都应该处理这个问题,对吧? (现在为 SLSQP 验证这一点)。
    • 取决于问题是否凸出。可能不是。这意味着:凸/圆锥方法不起作用(没有 cvxpy!)。这也意味着:SLSQP 可以,但您的解决方案只能保证局部最优。
    • 我尝试了不同的问题参数化(更接近我在实践中遇到的问题)并以相同的错误结束(即使在成本函数中使用了平方根)。关于这里可能发生的事情的任何线索?
    猜你喜欢
    • 2016-10-13
    • 2021-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-04
    • 2021-03-28
    • 2016-02-04
    相关资源
    最近更新 更多