参考官网:Scipy.
无约束的多变量标量函数的最小化
为解决多变量标量函数的无约束和有约束最小化问题,Scipy
提供了minimize
这个包。
考虑最小化N个变量的Rosenbrock函数的问题。(当所有x都等于1的时候,这个函数有最小值为0)
PS:Rosenbrock函数及其导数已经包含在scipy.optimize中。
以下各节所示的实现方式提供了如何定义目标函数、雅可比函数和黑塞函数的例子。
Nelder-Mead 算法(method=‘Nelder-Mead’)
import numpy as np
from scipy.optimize import minimize
def fun(x):
"""The Rosenbrock function"""
return sum(100.0*(x[1:]-x[:-1]**2.0)**2.0 + (1-x[:-1])**2.0)
#设立初始值
x0 = np.array([1.3, 0.7, 0.8, 1.9, 1.2])
res = minimize(fun, x0, method='nelder-mead',options={'xatol': 1e-8, 'disp': True})
print(res.x)
计算结果
[1. 1. 1. 1. 1.]
对于一个很好求解的方程,这是最简单的方法,但不是最好的。它只需要函数评估,对于简单的最小化问题是一个不错的选择。但由于它不使用任何梯度评价,它可能需要更长的时间来找到最小值。
BFGS(method=‘BFGS’)
为了更快的收敛,应考虑目标函数的梯度。如果用户没有给出梯度,那么就用一阶差分进行近似。
Broyden-Fletcher-Goldfarb-Shanno (BFGS)方法通常比单纯的算法需要更少的函数调用(即使在必须估计梯度的情况下)。
还是使用Rosenbrock函数举例:
先对其求导:
首先用python写出这个导数:
def fun_d(x):
xm = x[1:-1]
xm_m1 = x[:-2]
xm_p1 = x[2:]
der = np.zeros_like(x)
der[1:-1] = 200*(xm-xm_m1**2) - 400*(xm_p1 - xm**2)*xm - 2*(1-xm)
der[0] = -400*x[0]*(x[1]-x[0]**2) - 2*(1-x[0])
der[-1] = 200*(x[-1]-x[-2]**2)
return der
梯度信息通过jac
传入方程,完整代码如下:
import numpy as np
from scipy.optimize import minimize
def fun(x):
"""The Rosenbrock function"""
return sum(100.0*(x[1:]-x[:-1]**2.0)**2.0 + (1-x[:-1])**2.0)
def fun_d(x):
xm = x[1:-1]
xm_m1 = x[:-2]
xm_p1 = x[2:]
der = np.zeros_like(x)
der[1:-1] = 200*(xm-xm_m1**2) - 400*(xm_p1 - xm**2)*xm - 2*(1-xm)
der[0] = -400*x[0]*(x[1]-x[0]**2) - 2*(1-x[0])
der[-1] = 200*(x[-1]-x[-2]**2)
return der
#初始值
x0 = np.array([1.3, 0.7, 0.8, 1.9, 1.2])
res = minimize(fun, x0, method='BFGS', jac=fun_d, options={'disp': True})
print(res.x)
Optimization terminated successfully.
Current function value: 0.000000
Iterations: 25
Function evaluations: 30
Gradient evaluations: 30
[1.00000004 1.0000001 1.00000021 1.00000044 1.00000092]
如果不给jac初始值也可以计算:
import numpy as np
from scipy.optimize import minimize
def fun(x):
"""The Rosenbrock function"""
return sum(100.0*(x[1:]-x[:-1]**2.0)**2.0 + (1-x[:-1])**2.0)
#初始值
x0 = np.array([1.3, 0.7, 0.8, 1.9, 1.2])
res = minimize(fun, x0, method='BFGS', options={'disp': True})
print(res.x)
Optimization terminated successfully.
Current function value: 0.000000
Iterations: 25
Function evaluations: 180
Gradient evaluations: 30
[0.99999925 0.99999852 0.99999706 0.99999416 0.99998833]
牛顿共轭梯度法(method=‘Newton-CG’)
牛顿-共轭梯度算法是一种改进的牛顿方法,使用共轭梯度算法来(近似)局部Hessian的逆。牛顿方法的基础是将函数局部拟合为一个二次函数形式:
如果Hessian(即
H
(
x
0
)
H(x_0)
H(x0))是正定的,那么这个函数的局部最小值可以通过设置二次函数的梯度为零来找到。
这里Hessian矩阵的逆是用共轭梯度的方法来评估的。比方说本例中,如果变量是5,那么Hessian矩阵为:
import numpy as np
from scipy.optimize import minimize
def fun(x):
"""The Rosenbrock function"""
return sum(100.0*(x[1:]-x[:-1]**2.0)**2.0 + (1-x[:-1])**2.0)
#梯度
def fun_d(x):
xm = x[1:-1]
xm_m1 = x[:-2]
xm_p1 = x[2:]
der = np.zeros_like(x)
der[1:-1] = 200*(xm-xm_m1**2) - 400*(xm_p1 - xm**2)*xm - 2*(1-xm)
der[0] = -400*x[0]*(x[1]-x[0]**2) - 2*(1-x[0])
der[-1] = 200*(x[-1]-x[-2]**2)
return der
#黑塞矩阵
def fun_hess(x):
x = np.asarray(x)
H = np.diag(-400*x[:-1],1) - np.diag(400*x[:-1],-1)
diagonal = np.zeros_like(x)
diagonal[0] = 1200*x[0]**2-400*x[1]+2
diagonal[-1] = 200
diagonal[1:-1] = 202 + 1200*x[1:-1]**2 - 400*x[2:]
H = H + np.diag(diagonal)
return H
#初始值
x0 = np.array([1.3, 0.7, 0.8, 1.9, 1.2])
res = minimize(fun, x0, method='Newton-CG',jac=fun_d, hess=fun_hess,options={'xtol': 1e-8, 'disp': True})
print(res.x)
Optimization terminated successfully.
Current function value: 0.000000
Iterations: 24
Function evaluations: 33
Gradient evaluations: 33
Hessian evaluations: 24
[1. 1. 1. 0.99999999 0.99999999]
对于大规模的优化问题,存储整个Hessian矩阵会消耗大量的时间和内存。而牛顿-CG算法只需要Hessian与一个任意矢量的乘积。因此,可以提供代码来计算这个乘积,而不是将完整的Hessian直接送到优化器中。
Rosenbrock的Hessian矩阵与一个任意矢量的乘积并不难计算,对于本例来说:
import numpy as np
from scipy.optimize import minimize
def fun(x):
"""The Rosenbrock function"""
return sum(100.0*(x[1:]-x[:-1]**2.0)**2.0 + (1-x[:-1])**2.0)
#梯度
def fun_d(x):
xm = x[1:-1]
xm_m1 = x[:-2]
xm_p1 = x[2:]
der = np.zeros_like(x)
der[1:-1] = 200*(xm-xm_m1**2) - 400*(xm_p1 - xm**2)*xm - 2*(1-xm)
der[0] = -400*x[0]*(x[1]-x[0]**2) - 2*(1-x[0])
der[-1] = 200*(x[-1]-x[-2]**2)
return der
#黑塞矩阵与向量乘积
def fun_hess_p(x, p):
x = np.asarray(x)
Hp = np.zeros_like(x)
Hp[0] = (1200*x[0]**2 - 400*x[1] + 2)*p[0] - 400*x[0]*p[1]
Hp[1:-1] = -400*x[:-2]*p[:-2]+(202+1200*x[1:-1]**2-400*x[2:])*p[1:-1] \
-400*x[1:-1]*p[2:]
Hp[-1] = -400*x[-2]*p[-2] + 200*p[-1]
return Hp
#初始值
x0 = np.array([1.3, 0.7, 0.8, 1.9, 1.2])
res = minimize(fun, x0, method='Newton-CG',jac=fun_d, hessp=fun_hess_p,options={'xtol': 1e-8, 'disp': True})
print(res.x)
Optimization terminated successfully.
Current function value: 0.000000
Iterations: 24
Function evaluations: 33
Gradient evaluations: 33
Hessian evaluations: 66
[1. 1. 1. 0.99999999 0.99999999]
信赖域牛顿共轭梯度法(method=‘trust-ncg’)
牛顿-CG方法是一种线搜索方法:它找到一个搜索方向,使函数的二次方近似值最小,然后用一维搜索算法找到该方向的(近似)最佳步长。
另一种方法是,首先,固定步长限制Δ,然后通过解决以下二次方子问题,在给定的信任半径内找到最佳步骤:
然后进行迭代:
x
k
+
1
=
x
k
+
p
x_{k+1} = x_k + p
xk+1=xk+p,信任半径Δ根据二次模型与实际函数的一致程度进行调整。这一类型的方法被称为信任区域方法。Trust-ncg算法是一种信任区域方法,使用共轭梯度算法来解决信任区域子问题 。
使用Hessian矩阵求解:
res = minimize(fun, x0, method='trust-ncg',
jac=fun_d, hess=fun_hess,
options={'gtol': 1e-8, 'disp': True})
Optimization terminated successfully.
Current function value: 0.000000
Iterations: 20
Function evaluations: 21
Gradient evaluations: 20
Hessian evaluations: 19
[1. 1. 1. 1. 1.]
使用Hessian矩阵点乘向量求解:
res = minimize(fun, x0, method='trust-ncg',
jac=fun_d, hessp=fun_hess_p,
options={'gtol': 1e-8, 'disp': True})
Optimization terminated successfully.
Current function value: 0.000000
Iterations: 20
Function evaluations: 21
Gradient evaluations: 20
Hessian evaluations: 74
[1. 1. 1. 1. 1.]
信赖域截断广义兰佐斯算法/共轭梯度法(method=‘trust-krylov’)
与trust-ncg方法类似,trust-krylov方法是一种适用于大规模问题的方法,因为它通过矩阵-向量乘积的方式,仅将Hessian作为线性算子。它比Trust-ncg方法更精准地求解二次方子问题。对于不确定的问题,通常使用这种方法更好,因为与trust-ncg方法相比,它减少了非线性迭代的数量,但代价是每个子问题的求解要多一些矩阵-向量乘积。
使用Hessian矩阵求解:
res = minimize(fun, x0, method='trust-krylov',
jac=fun_d, hess=fun_hess,
options={'gtol': 1e-8, 'disp': True})
Optimization terminated successfully.
Current function value: 0.000000
Iterations: 19
Function evaluations: 20
Gradient evaluations: 20
Hessian evaluations: 18
[1. 1. 1. 1. 1.]
使用Hessian矩阵点乘向量求解:
res = minimize(fun, x0, method='trust-krylov',
jac=fun_d, hessp=fun_hess_p,
options={'gtol': 1e-8, 'disp': True})
Optimization terminated successfully.
Current function value: 0.000000
Iterations: 19
Function evaluations: 20
Gradient evaluations: 20
Hessian evaluations: 65
[1. 1. 1. 1. 1.]
信赖域近似精确算法 (method=‘trust-exact’)
前面所有的算法,如Newton-CG、trust-ncg和trust-krylov方法都适用于处理大规模问题(有数千个变量的问题)。这是因为共轭梯度算法通过迭代近似解决了信任区域的子问题(或Hessian矩阵的逆),而不需要明确的Hessian矩阵。由于只需要Hessian矩阵与任意向量的乘积,该算法特别适合于处理稀疏的Hessians矩阵,使那些稀疏问题的存储要求低,并能大大节省时间。
对于中等规模的问题,Hessian的存储和因子化成本并不重要,通过解决信任区域的子问题,有可能在较少的迭代中获得一个解决方案。
为了实现这一目标,对每个二次元子问题迭代求解某些非线性方程,这个解决方案通常需要对Hessian矩阵进行3或4次Cholesky分解。因此,该方法以较少的迭代次数收敛,与其他已实现的信任区域方法相比,需要较少的目标函数评估。该算法不支持Hessian点乘。下面是一个使用Rosenbrock函数的例子:
res = minimize(fun, x0, method='trust-exact',
jac=fun_d, hess=fun_hess,
options={'gtol': 1e-8, 'disp': True})
Optimization terminated successfully.
Current function value: 0.000000
Iterations: 13
Function evaluations: 14
Gradient evaluations: 13
Hessian evaluations: 14
[1. 1. 1. 1. 1.]