参考官网:Scipy.
对于有约束的最小化问题,Scipy
提供的minimize
这个包有三个:trust-constr
, SLSQP'
和COBYLA
。它们要求使用稍微不同的结构来定义约束。
trust-constr
需要要求约束被定义成一系列的LinearConstraint
和 NonlinearConstraint
两种类型。
SLSQP'
和COBYLA
需要要求约束条件被定义为一连串的字典,其键为type
、fun
和jac
。
考虑有约束的最小化2个变量的Rosenbrock函数
这个问题有唯一解: [ x 0 , x 1 ] = [ 0.4149 , 0.1701 ] [x_0,x_1]=[0.4149,0.1701] [x0,x1]=[0.4149,0.1701]。
信任域约束方法处理的是约束性最小化问题,其形式为:
除此之外,单边约束可以通过对np.inf设置上限或下限并加上适当的符号来指定。
边界限制 0 ≤ x 0 ≤ 1 , − 0.5 ≤ x 1 ≤ 2.0 0≤x_0≤1,-0.5≤x_1≤2.0 0≤x0≤1,−0.5≤x1≤2.0被定义如下:
from scipy.optimize import Bounds
bounds = Bounds([0, -0.5], [1.0, 2.0])
约束 x 0 + 2 x 1 ≤ 1 , 2 x 0 + x 1 = 1 x_0+2x_1≤1,2x_0+x_1=1 x0+2x1≤1,2x0+x1=1可以写成如下形式:
用LinearConstraint
去定义:
from scipy.optimize import LinearConstraint
linear_constraint = LinearConstraint([[1, 2], [2, 1]], [-np.inf, 1], [1, 1])
非线性约束为:
其雅可比矩阵(对每个变量求导)为:
黑塞矩阵的线性组合:
用NonlinearConstraint
去定义:
#定义非线性约束
def cons_f(x):
return [x[0]**2 + x[1], x[0]**2 - x[1]]
#定义导数
def cons_J(x):
return [[2*x[0], 1], [2*x[0], -1]]
#定义二阶导数
def cons_H(x, v):
return v[0]*np.array([[2, 0], [0, 0]]) + v[1]*np.array([[2, 0], [0, 0]])
from scipy.optimize import NonlinearConstraint
nonlinear_constraint = NonlinearConstraint(cons_f, -np.inf, 1, jac=cons_J, hess=cons_H)
另外,也可以将Hessian定义为一个稀疏矩阵。
from scipy.sparse import csc_matrix
def cons_H_sparse(x, v):
return v[0]*csc_matrix([[2, 0], [0, 0]]) + v[1]*csc_matrix([[2, 0], [0, 0]])
nonlinear_constraint = NonlinearConstraint(cons_f, -np.inf, 1,
jac=cons_J, hess=cons_H_sparse)
当黑塞矩阵难以计算的时候,可以使用HessianUpdateStrategy
,目前可用的策略有:BFGS
和SR1
。
from scipy.optimize import BFGS
nonlinear_constraint = NonlinearConstraint(cons_f, -np.inf, 1, jac=cons_J, hess=BFGS())
另外,Hessian可以用有限差分法进行近似。
nonlinear_constraint = NonlinearConstraint(cons_f, -np.inf, 1, jac=cons_J, hess='2-point')
雅可比矩阵也可以用有限差分法估计,然而,在这种情况下,黑塞不能用有限差分计算,需要由用户提供或用之前介绍的HessianUpdateStrategy
定义:
nonlinear_constraint = NonlinearConstraint(cons_f, -np.inf, 1, jac='2-point', hess=BFGS())
#设置初值
x0 = np.array([0.5, 0])
#这个需要结合之前无约束的算法来计算
res = minimize(rosen, x0, method='trust-constr', jac=rosen_der, hess=rosen_hess,
constraints=[linear_constraint, nonlinear_constraint],
options={'verbose': 1}, bounds=bounds)
print(res.x)
无约束代码在这里
`gtol` termination condition is satisfied.
Number of iterations: 12, function evaluations: 8, CG iterations: 7, optimality: 2.99e-09, constraint violation: 0.00e+00, execution time: 0.014 s.
[0.41494531 0.17010937]
import numpy as np
from scipy.optimize import minimize
#无约束
def rosen(x):
"""The Rosenbrock function"""
return sum(100.0*(x[1:]-x[:-1]**2.0)**2.0 + (1-x[:-1])**2.0)
def rosen_der(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 rosen_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
#设置约束
from scipy.optimize import Bounds
bounds = Bounds([0, -0.5], [1.0, 2.0])
from scipy.optimize import LinearConstraint
linear_constraint = LinearConstraint([[1, 2], [2, 1]], [-np.inf, 1], [1, 1])
def cons_f(x):
return [x[0]**2 + x[1], x[0]**2 - x[1]]
#定义导数
def cons_J(x):
return [[2*x[0], 1], [2*x[0], -1]]
#定义二阶导数
def cons_H(x, v):
return v[0]*np.array([[2, 0], [0, 0]]) + v[1]*np.array([[2, 0], [0, 0]])
from scipy.optimize import NonlinearConstraint
nonlinear_constraint = NonlinearConstraint(cons_f, -np.inf, 1, jac=cons_J, hess=cons_H)
#求解
x0 = np.array([0.5, 0])
res = minimize(rosen, x0, method='trust-constr', jac=rosen_der, hess=rosen_hess,
constraints=[linear_constraint, nonlinear_constraint],
options={'verbose': 1}, bounds=bounds)
print(res.x)
另外也可以对目标函数的第一和第二导数进行近似,例如,黑塞矩阵可以用SR1
准牛顿法近似,梯度可以用有限差分法近似:
from scipy.optimize import SR1
res = minimize(rosen, x0, method='trust-constr', jac="2-point", hess=SR1(),
constraints=[linear_constraint, nonlinear_constraint],
options={'verbose': 1}, bounds=bounds)
`gtol` termination condition is satisfied.
Number of iterations: 12, function evaluations: 24, CG iterations: 7, optimality: 4.48e-09, constraint violation: 0.00e+00, execution time: 0.02 s.
[0.41494531 0.17010937]
线性和非线性约束都被定义为字典,其键为type、fun和jac:
首先是定义域:
from scipy.optimize import Bounds
bounds = Bounds([0, -0.5], [1.0, 2.0])
等式与不等式约束:
#不等式约束
ineq_cons = {'type': 'ineq',
'fun' : lambda x: np.array([1 - x[0] - 2*x[1],
1 - x[0]**2 - x[1],
1 - x[0]**2 + x[1]]),
#jac是对fun的求导
'jac' : lambda x: np.array([[-1.0, -2.0],
[-2*x[0], -1.0],
[-2*x[0], 1.0]])}
#等式约束
eq_cons = {'type': 'eq',
'fun' : lambda x: np.array([2*x[0] + x[1] - 1]),
'jac' : lambda x: np.array([2.0, 1.0])}
x0 = np.array([0.5, 0])
res = minimize(rosen, x0, method='SLSQP', jac=rosen_der,
constraints=[eq_cons, ineq_cons], options={'ftol': 1e-9, 'disp': True},
bounds=bounds)
print(res.x)
Optimization terminated successfully (Exit mode 0)
Current function value: 0.34271757499419825
Iterations: 4
Function evaluations: 5
Gradient evaluations: 4
[0.41494475 0.1701105 ]
import numpy as np
from scipy.optimize import minimize
#定义无约束的目标函数
def rosen(x):
"""The Rosenbrock function"""
return sum(100.0*(x[1:]-x[:-1]**2.0)**2.0 + (1-x[:-1])**2.0)
def rosen_der(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 rosen_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
#定义约束
from scipy.optimize import Bounds
bounds = Bounds([0, -0.5], [1.0, 2.0])
ineq_cons = {'type': 'ineq',
'fun' : lambda x: np.array([1 - x[0] - 2*x[1],
1 - x[0]**2 - x[1],
1 - x[0]**2 + x[1]]),
'jac' : lambda x: np.array([[-1.0, -2.0],
[-2*x[0], -1.0],
[-2*x[0], 1.0]])}
eq_cons = {'type': 'eq',
'fun' : lambda x: np.array([2*x[0] + x[1] - 1]),
'jac' : lambda x: np.array([2.0, 1.0])}
#求解
x0 = np.array([0.5, 0])
res = minimize(rosen, x0, method='SLSQP', jac=rosen_der,
constraints=[eq_cons, ineq_cons], options={'ftol': 1e-9, 'disp': True},
bounds=bounds)
print(res.x)
PS:大部分 trust-constr
方法可用的选项对 SLSQP
来说是不可用的。