scipy.optimize.leastsq带有边界约束

24

我正在寻找在scipy/numpy中可以解决非线性最小二乘问题的优化例程(例如,将参数函数拟合到大型数据集中),但包括界限和约束(例如要优化的参数的最小值和最大值)。目前,我正在使用mpfit的Python版本(从idl翻译而来...):尽管它表现良好,但显然不是最优选择。

在python/scipy等中实现高效的例程将会非常棒!

非常感谢!

4个回答

22

scipy.optimize.least_squares在scipy 0.17(2016年1月)中,处理边界;使用它,而不是这个hack。


边界限制可以轻松地变为二次型,并与其余部分一起通过leastsq进行最小化。
假设你想最小化10个平方和Σf_i(p)^2的总和,因此您的func(p)是一个10向量[f0(p) ... f9(p)],
同时还想要3个参数满足0 <= p_i <= 1。
考虑“管函数”max(-p, 0, p-1),它在0..1内为0,在外部为正,就像\_____/管子一样。
如果我们给leastsq提供一个13维向量

[ f0(p), f1(p), ... f9(p), w*tub(p0), w*tub(p1), w*tub(p2) ]

如果w = 100,它将最小化一系列数字的平方和,这些数字受到以下约束:0≤p≤1。类似地,当lo≤p≤hi时也是如此。
下面的代码只是一个包装器,它运行 leastsq 来最小化一个长度为13的向量。

# leastsq_bounds.py
# see also test_leastsq_bounds.py on gist.github.com/denis-bz

from __future__ import division
import numpy as np
from scipy.optimize import leastsq

__version__ = "2015-01-10 jan  denis"  # orig 2012


#...............................................................................
def leastsq_bounds( func, x0, bounds, boundsweight=10, **kwargs ):
    """ leastsq with bound conatraints lo <= p <= hi
    run leastsq with additional constraints to minimize the sum of squares of
        [func(p) ...]
        + boundsweight * [max( lo_i - p_i, 0, p_i - hi_i ) ...]

    Parameters
    ----------
    func() : a list of function of parameters `p`, [err0 err1 ...]
    bounds : an n x 2 list or array `[[lo_0,hi_0], [lo_1, hi_1] ...]`.
        Use e.g. [0, inf]; do not use NaNs.
        A bound e.g. [2,2] pins that x_j == 2.
    boundsweight : weights the bounds constraints
    kwargs : keyword args passed on to leastsq

    Returns
    -------
    exactly as for leastsq,
http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.leastsq.html

    Notes
    -----
    The bounds may not be met if boundsweight is too small;
    check that with e.g. check_bounds( p, bounds ) below.

    To access `x` in `func(p)`, `def func( p, x=xouter )`
    or make it global, or `self.x` in a class.

    There are quite a few methods for box constraints;
    you'll maybe sing a longer song ...
    Comments are welcome, test cases most welcome.

"""
    # Example: test_leastsq_bounds.py

    if bounds is not None  and  boundsweight > 0:
        check_bounds( x0, bounds )
        if "args" in kwargs:  # 8jan 2015
            args = kwargs["args"]
            del kwargs["args"]
        else:
            args = ()
#...............................................................................
        funcbox = lambda p: \
            np.hstack(( func( p, *args ),
                        _inbox( p, bounds, boundsweight ))) 
    else:
        funcbox = func
    return leastsq( funcbox, x0, **kwargs )


def _inbox( X, box, weight=1 ):
    """ -> [tub( Xj, loj, hij ) ... ]
        all 0  <=>  X in box, lo <= X <= hi
    """
    assert len(X) == len(box), \
        "len X %d != len box %d" % (len(X), len(box))
    return weight * np.array([
        np.fmax( lo - x, 0 ) + np.fmax( 0, x - hi )
            for x, (lo,hi) in zip( X, box )])

# def tub( x, lo, hi ):
#     """ \___/  down to lo, 0 lo .. hi, up from hi """
#     return np.fmax( lo - x, 0 ) + np.fmax( 0, x - hi )

#...............................................................................
def check_bounds( X, box ):
    """ print Xj not in box, loj <= Xj <= hij
        return nr not in
    """
    nX, nbox = len(X), len(box)
    assert nX == nbox, \
        "len X %d != len box %d" % (nX, nbox)
    nnotin = 0
    for j, x, (lo,hi) in zip( range(nX), X, box ):
        if not (lo <= x <= hi):
            print "check_bounds: x[%d] %g is not in box %g .. %g" % (j, x, lo, hi)
            nnotin += 1
    return nnotin

我在尝试实现它(Python 2.7)时收到了以下错误:File "[...]/leastsq_bounds.py", line 49, in leastsq_bounds return leastsq( funcbox, x0, **kwargs ) File "[...]/minpack.py", line 369, in leastsq shape, dtype = _check_func('leastsq', 'func', func, x0, args, n) File "[...]/minpack.py", line 20, in _check_func res = atleast_1d(thefunc(*((x0[:numinputs],) + args))) TypeError: <lambda>() takes exactly 1 argument (5 given) - redcrow
1
@f_ficarola,抱歉,args=有问题,请剪切/粘贴并重试。 - denis
无论如何,我现在收到了不同的错误:File "leastsq_bounds.py", line 58, in leastsq_bounds return leastsq( funcbox, x0, **kwargs ) File "minpack.py", line 369, in leastsq shape, dtype = _check_func('leastsq', 'func', func, x0, args, n) File "minpack.py", line 20, in _check_func res = atleast_1d(thefunc(*((x0[:numinputs],) + args))) File "leastsq_bounds.py", line 55, in <lambda> _inbox( p, bounds, boundsweight ))) File "leastsq_bounds.py", line 66, in _inbox "len X %d != len box %d" % (len(X), len(box)) AssertionError: len X 1 != len box 2 - redcrow
3
1)SLSQP 直接处理边界(包括盒式边界和“<=”类型的边界),但最小化的是标量函数;leastsq 最小化平方和,两者差别很大。请问您使用的是哪种方法?有多少参数和变量?2)np.shape(bounds) 的输出结果应该是 nparam x 2,请先运行 leastsq_bounds.check_bounds(x0, bounds) - denis
是的,抱歉...你绝对是正确的。我以错误的方式传递了我的边界(即[xmin,xmax]而不是[[xmin,xmax]])。再次感谢你,丹尼斯。 - redcrow
显示剩余4条评论

9

谢谢!我会在接下来的几天内测试这个方案和mpfit,然后尽快汇报结果! - user1293231
刚试了一下slsqp。可能我没有正确使用它,但基本上它并没有什么用处。当使用过低的epsilon值时,它似乎会崩溃。否则,它几乎不会改变我的输入参数(或者说根本没有改变)。我会进行一些调试,但看起来它并不容易使用(到目前为止)。会继续尝试。 - user1293231
实际上,我只是得到了以下错误 ==> 正向线搜索的正向导数(退出模式8)。这就是为什么我一无所获......没有什么用。有什么提示吗? - user1293231
你能否让它处理一个简单的问题,比如拟合y = mx + b + 噪声?http://www.scipy.org/Cookbook/FittingData 是一个相当全面的scipy.optimize指南,并包含示例。 - matt

5
解决带限制的非线性最小二乘问题的能力一直是Scipy所缺少的,就像mpfit所做的那样,这种能力可以以最佳方式进行。在Scipy 0.17中,这个备受请求的功能终于被引入,使用新函数scipy.optimize.least_squares

这个新函数可以使用适当的信任区域算法处理边界约束,并充分利用非线性函数的平方和特性进行优化。

注意:

@denis提出的解决方案存在一个主要问题,即引入了不连续的“管函数”。当越过边界时,这使得为平滑函数设计的scipy.optimize.leastsq优化非常低效,可能不稳定。

使用scipy.optimize.minimizemethod='SLSQP'(如@f_ficarola建议的)或scipy.optimize.fmin_slsqp(如@matt建议的),主要问题在于它们没有利用要最小化的函数的平方和性质。这些函数都是设计用来最小化标量函数的(即使对于fmin_slsqp也是如此,尽管名称有误导性)。这些方法比一个合适的方法更低效、不准确。

3

谢谢你的建议:我的一个问题是,我想要能够拥有一个自洽的Python模块,包含有界非线性最小二乘部分。这意味着用户要么必须也安装lmfit,要么我必须在我的模块中包含整个软件包。因此,我首先尝试使用fmin_slsqp,因为这是scipy中已经集成的函数。但lmfit似乎恰好满足了我所需要的! - user1293231
考虑到您已经依赖于 SciPy,而它并不在标准库中。lmfit 在 pypi 上,并且对于大多数用户来说安装应该很容易。 - chthonicdaemon
链接已损坏! - denfromufa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接