【Scipy优化使用教程】二、Scipy中有约束优化的两种算法

参考官网:Scipy.

有约束的多变量标量函数的最小化

  • 信赖域约束算法(method='trust-constr')
    • 定义边界约束
    • 定义线性约束
    • 定义非线性约束
    • 求解
    • 完整代码
  • 序列最小二乘(SLSQP) (method='SLSQP')
    • 设置约束
    • 求解
    • 完整代码

对于有约束的最小化问题,Scipy提供的minimize这个包有三个:trust-constr, SLSQP'COBYLA。它们要求使用稍微不同的结构来定义约束。
trust-constr需要要求约束被定义成一系列的LinearConstraintNonlinearConstraint两种类型。
SLSQP'COBYLA需要要求约束条件被定义为一连串的字典,其键为typefunjac
考虑有约束的最小化2个变量的Rosenbrock函数
【Scipy优化使用教程】二、Scipy中有约束优化的两种算法_第1张图片
这个问题有唯一解: [ x 0 , x 1 ] = [ 0.4149 , 0.1701 ] [x_0,x_1]=[0.4149,0.1701] [x0,x1]=[0.4149,0.1701]

信赖域约束算法(method=‘trust-constr’)

信任域约束方法处理的是约束性最小化问题,其形式为:
【Scipy优化使用教程】二、Scipy中有约束优化的两种算法_第2张图片
除此之外,单边约束可以通过对np.inf设置上限或下限并加上适当的符号来指定。

定义边界约束

边界限制 0 ≤ x 0 ≤ 1 , − 0.5 ≤ x 1 ≤ 2.0 0≤x_0≤1,-0.5≤x_1≤2.0 0x01,0.5x12.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+2x112x0+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,目前可用的策略有:BFGSSR1

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]

序列最小二乘(SLSQP) (method=‘SLSQP’)

SLSQP需要如下形式:
【Scipy优化使用教程】二、Scipy中有约束优化的两种算法_第3张图片

设置约束

【Scipy优化使用教程】二、Scipy中有约束优化的两种算法_第4张图片
线性和非线性约束都被定义为字典,其键为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来说是不可用的。

你可能感兴趣的:(优化求解工具,算法,python,学习,动态规划,自动驾驶)