用scipy.optimize.least_squares解决带有变量边界的非线性最小二乘问题
scipy.optimize.least_squares
是一个功能强大的工具,用于解决带有变量边界的非线性最小二乘问题。它通过给定残差和损失函数,寻找目标函数的局部最小值。
目标函数及损失函数
这个函数的主要目标是最小化成本函数 F(x),表示为:
该函数最小化残差平方和,受限于 lb <= x <= ub 的变量边界。
参数及其功能
least_squares
函数具有各种参数,以促进优化过程:
- func: 一个可调用的函数,用于计算残差向量。
- x0: 独立变量的初始猜测值。
- jac: 计算雅可比矩阵(残差的偏导数)的方法。
- bounds: 指定独立变量的边界。
- method: 执行最小化的算法。
- ftol 、xtol 、gtol: 基于成本函数变化、变量变化和梯度范数的终止条件的容差。
- x_scale: 每个变量的特征尺度,以提高收敛性。
- loss: 确定损失函数,减少异常值对解决方案的影响。
返回结果
least_squares
函数的返回结果包括 OptimizeResult
中定义的以下字段:
- x: 找到的解决方案。
- cost: 解决方案处的成本函数值。
- fun: 解决方案处的残差向量。
- jac: 解决方案处的修改雅可比矩阵。
- grad: 解决方案处的成本函数梯度。
- optimality: 一阶最优性度量。
- active_mask: 活动约束的二进制掩码。
- nfev 、njev: 函数和雅可比矩阵的评估次数。
- status: 算法终止的原因。
- message: 终止原因的文本描述。
- success: 是否满足收敛条件。
scipy.optimize.least_squares
是解决非线性最小二乘问题的重要工具,它的参数和返回结果提供了广泛的灵活性和功能性,可用于各种优化问题的求解。
优化
scipy.optimize.least_squares优化方法详解
scipy.optimize.least_squares
模块提供了多种优化方法,每种方法针对特定问题类型。了解这些方法的细微差别对根据问题性质选择合适的方法至关重要。让我们深入了解下面提供的各种方法:
Levenberg-Marquardt方法('lm')
'lm'方法调用MINPACK中实现的最小二乘算法(lmder,lmdif)的包装器。它运行的是Levenberg-Marquardt算法,作为一种信赖域类型算法进行构建。这个实现基于文献[JJMore],非常强大、高效,具备许多巧妙的技巧。对于无约束问题,这应该是您的首选。但需要注意的是,它不支持约束条件。另外,当方程数(m)小于变量数(n)时,该方法无法工作。
Trust Region Reflective方法('trf')
'trf'方法的动机来自解决方程组,这些方程组构成了边界约束最小化问题的一阶最优性条件,详见[STIR]。该算法通过迭代求解增加了特殊对角二次项的信赖域子问题,并且信赖域的形状由到边界的距离和梯度方向确定。这些增强功能有助于避免直接走向边界,并有效地探索所有变量空间。为了进一步提高收敛性,该算法考虑从边界反射的搜索方向。为了满足理论要求,该算法始终保持迭代的严格可行性。对于稠密雅可比矩阵,信赖域子问题的求解方法与[JJMore]中描述的方法非常相似(并在MINPACK中实现)。与MINPACK实现的不同之处在于,每次迭代只进行一次雅可比矩阵的奇异值分解,而不是QR分解和一系列的Givens旋转消元。对于大型稀疏雅可比矩阵,采用了一个二维子空间方法来求解信赖域子问题[STIR],[Byrd]。这个子空间由一个缩放梯度和由scipy.sparse.linalg.lsmr提供的近似Gauss-Newton解构成。当没有约束条件时,该算法与MINPACK非常相似,并且通常具有可比较的性能。该算法在无约束和有界问题中表现出相当的鲁棒性,因此被选择为默认算法。
Dogbox方法
'dogbox'方法在信赖域框架内运行,但考虑的是矩形信赖域,而不是传统的椭球体[Voglis]。当前信赖域和初始边界的交集也是矩形的,因此在每次迭代中,通过Powell的dogleg方法[NumOpt]近似地求解受边界约束的二次最小化问题。对于稠密雅可比矩阵,可以精确计算所需的Gauss-Newton步长,或者对于大型稀疏雅可比矩阵,可以近似计算,使用scipy.sparse.linalg.lsmr。当雅可比矩阵的秩小于变量数时,该算法可能收敛较慢。然而,该算法在变量较少的有界问题中通常优于'trf'方法。
鲁棒损失函数
鲁棒损失函数的实现如[BA]所述。其思想是在每次迭代中修改残差向量和雅可比矩阵,使计算出的梯度和Gauss-Newton Hessian近似值与成本函数的真实梯度和Hessian近似值相匹配。然后算法继续以正常方式运行,也就是说,鲁棒损失函数被实现为对标准最小二乘算法的简单包装器。
例子1
- 计算 <math xmlns="http://www.w3.org/1998/Math/MathML"> y ^ = A K a L b \hat{y} = AK^aL^b </math>y^=AKaLb
py
from scipy.optimize import least_squares
import numpy as np
def func3(params ,x,y):
A,a, b = params
return y - (A * x.iloc[:,0] ** a * x.iloc[:,1] ** b)
init_vals = [0.5, 0.5, 0.5] # 对应参数的初始值
# 进行拟合
ret = least_squares(func3, x0=init_vals, args=(X,y))
ret
message: `ftol` termination condition is satisfied.
success: True
status: 2
fun: [-1.614e+02 -9.926e+00 ... 5.070e+02 -1.831e+02]
x: [ 4.074e-01 8.683e-01 2.698e-01]
cost: 4324284.827539758
jac: [[-9.291e+03 -2.736e+04 -4.013e+04]
[-9.751e+03 -2.891e+04 -4.220e+04]
...
[-5.338e+04 -1.970e+05 -2.436e+05]
[-5.996e+04 -2.244e+05 -2.738e+05]]
grad: [-2.737e-01 -7.448e-01 -4.288e-01]
optimality: 0.7448098897038893
active_mask: [ 0.000e+00 0.000e+00 0.000e+00]
nfev: 38
njev: 31
官方例子
以下是关于scipy.optimize.least_squares
模块的一些示例的中文解释:
在第一个示例中,我们使用无约束独立变量找到了Rosenbrock函数的最小值。
Rosenbrock函数如下:
python
pythonCopy code
import numpy as np
def fun_rosenbrock(x):
return np.array([10 * (x[1] - x[0]**2), (1 - x[0])])
需要注意的是,我们只提供了残差向量。算法将构建成本函数作为残差平方和,得到Rosenbrock函数。精确的最小值在 x = [1.0, 1.0] 处。
ini
from scipy.optimize import least_squares
x0_rosenbrock = np.array([2, 2])
res_1 = least_squares(fun_rosenbrock, x0_rosenbrock)
res_1.x # 最小值点
res_1.cost # 成本函数值
res_1.optimality # 最优性度量
接下来,我们对变量进行约束,使得先前的解决方案变得不可行。具体而言,我们要求 x[1] >= 1.5,而 x[0] 保持不受约束。为此,在least_squares
中指定bounds参数以进行约束。
同时,我们还提供了解析雅可比矩阵:
python
def jac_rosenbrock(x):
return np.array([
[-20 * x[0], 10],
[-1, 0]])
将所有这些内容放在一起,我们发现新的解决方案位于边界上:
ini
res_2 = least_squares(fun_rosenbrock, x0_rosenbrock, jac_rosenbrock,
bounds=([-np.inf, 1.5], np.inf))
res_2.x # 新的最小值点
res_2.cost # 新的成本函数值
res_2.optimality # 新的最优性度量
接着,我们解决了一个包含10万个变量的Broyden三对角向量值函数组成的方程组(即在最小值处,成本函数应为零):
ini
def fun_broyden(x):
f = (3 - x) * x + 1
f[1:] -= x[:-1]
f[:-1] -= 2 * x[1:]
return f
# 针对稀疏雅可比矩阵,通过有限差分来估计,并提供雅可比矩阵的稀疏结构以显著加快此过程。
from scipy.sparse import lil_matrix
def sparsity_broyden(n):
sparsity = lil_matrix((n, n), dtype=int)
i = np.arange(n)
sparsity[i, i] = 1
i = np.arange(1, n)
sparsity[i, i - 1] = 1
i = np.arange(n - 1)
sparsity[i, i + 1] = 1
return sparsity
n = 100000
x0_broyden = -np.ones(n)
res_3 = least_squares(fun_broyden, x0_broyden,
jac_sparsity=sparsity_broyden(n))
res_3.cost # 成本函数值
res_3.optimality # 最优性度量
res_3.cost
4.5687069299604613e-23
>>> res_3.optimality
1.1650454296851518e-11
在下一个示例中,我们展示了如何使用least_squares()
优化具有复变量的复值残差函数。考虑以下函数:
python
def f(z):
return z - (0.5 + 0.5j)
我们将其包装成一个关于实变量的函数,通过简单处理实部和虚部作为独立变量:
scss
def f_wrap(x):
fx = f(x[0] + 1j*x[1])
return np.array([fx.real, fx.imag])
因此,我们将原始的n个复变量的m维复函数优化为2n个实变量的2m维实函数:
scss
res_wrapped = least_squares(f_wrap, (0.1, 0.1), bounds=([0, 0], [1, 1]))
z = res_wrapped.x[0] + res_wrapped.x[1]*1j # 得到最小值点
(0.49999999999925893+0.49999999999925893j)