非线性规划(Nonlinear Programming,简称NLP)是一种优化问题的数学形式,其中目标函数或约束条件中至少有一个是非线性的。优化问题的目标是找到一组变量的取值,使得目标函数在满足一系列约束条件的情况下达到最小值或最大值。在非线性规划中,目标函数和约束条件可以包含平方项、绝对值、指数函数等非线性项,与线性规划相比,这使得问题更为复杂。非线性规划问题的求解可以借助各种优化算法,其中包括梯度下降法、拟牛顿法、全局优化方法等。这些方法旨在在搜索空间中寻找局部或全局最优解,具体选择取决于问题的性质和求解的需求。非线性规划在众多实际问题中发挥着重要作用,例如在工程、经济、金融和科学领域。非线性规划(求极值),从处理方法来说,可以尝试以下几种:纯数学方法,求导求极值;使用神经网络,深度学习来处理,可参考反向传播算法中链式求导的过程;寻找一些python库来做,这里介绍scipy.optimize.minimize的使用方法在Python中,scipy.optimize模块提供了用于求解非线性规划问题的丰富工具。
一、非线性规划的标准型
非线性规划的标准形式:
\[minf(x) \\ s.t.\begin{cases} h_j(x) \geq 0, &j=1,q\\ g_i(x) = 0, &i=1,p \end{cases} \]其中:\(x=[x_1,...,x_n]^T\)为决策变量,\(f(x)\) 为目标函数,\(h_j(x)\) 和 \(g_i(x)\) 为约束条件。
非线性规划问题实际上就是带有约束条件的非线性函数优化问题。按照我们的学习模式,非线性规划问题的建模和求解与线性规划问题是类似的,按照以下步骤进行:
问题定义,确定决策变量、目标函数和约束条件;
模型构建,由问题描述建立数学方程,并转化为标准形式的数学模型;
模型求解,用标准模型的优化算法对模型求解,得到优化结果。
二、Scipy库简介
Scipy 是一个基于 NumPy 构建的开源科学计算库,提供了许多用于数学、科学和工程的高级工具和算法。它包含了众多子模块,包括插值、积分、优化、信号处理、统计等,涵盖了科学计算的广泛领域。scipy 的目标是为科学家、工程师和研究人员提供强大而灵活的工具,帮助他们解决复杂的问题。通过 scipy,用户可以方便地访问许多数值计算和优化算法,以及统计和信号处理等领域的功能,从而加速科学研究和工程开发。
Scipy库在线帮助网址
2.1 Scipy 求解非线性规划问题的函数
Scipy 是 Python 算法库和数学工具包,包括最优化、线性代数、积分、插值、特殊函数、傅里叶变换、信号和图像处理、常微分方程求解等模块。Scipy 工具包中的 optimize 模块求解常见的非线性规划问题。
- brent():单变量无约束优化问题,混合使用牛顿法/二分法。
- fmin():多变量无约束优化问题,使用单纯性法,只需要利用函数值,不需要函数的导数或二阶导数。
- leatsq():非线性最小二乘问题,用于求解非线性最小二乘拟合问题。
- minimize():约束优化问题,使用拉格朗日乘子法将约束优化转化为无约束优化问题。
2.2 scipy.optimize.brent()求解单变量无约束优化问题
非线性规划最简单的形式是一维搜索,一维搜索的常用方法是函数逼近法和区间收缩法。brent() 函数是 SciPy.optimize 模块中求解单变量无约束优化问题最小值的首选方法。这是牛顿法和二分法的混合方法,既能保证稳定性又能快速收敛。
scipy.optimize.brent(func, args=(), brack=None, tol=1.48e-08, full_output=0, maxiter=500)
optimize.brent() 的主要参数:
- func: callable f(x,*args):目标函数 \(f(x)\),以函数形式表示,可以通过 *args 传递参数;
- args: tuple: 可选项,以$f(x,*args) \(的形式将可变参数\)p\(传递给目标函数\)f(x,p)$;
- brack: tuple:可选项,搜索算法的开始区间(不是指\(x\)的上下限)
optimize.brent() 的主要返回值:
- **xmin: **返回函数达到最小值时的 \(x\)(注意是局部最优,不一定是全局最优);
- **fval: **返回函数的最优值(默认不返回,仅当full_output 为 1 时返回)。
from scipy.optimize import brent, fmin_ncg, minimize
import numpy as np
#Demo1:单变量无约束优化问题(Scipy.optimize.brent)
def objf(x): # 目标函数
fx = x**2 - 8*np.sin(2*x+np.pi)
return fx
xIni = -5.0
xOpt= brent(objf, brack=(xIni,2))
print("xIni={:.4f}\tfxIni={:.4f}".format(xIni,objf(xIni))
print("xOpt={:.4f}\tfxOpt={:.4f}".format(xOpt,objf(xOpt)))
2.3 scipy.optimize.fmin() 求解多变量无约束优化问题
多变量无约束优化问题的算法很多,分类方式也很多。从使用者的角度来说可以分为:只使用目标函数值、使用导数(梯度下降法)、使用二阶导数。大体来说,使用导数的算法收敛较快,使用二阶导数收敛更快,但是收敛快也容易陷入局部最优。fmin() 函数是 SciPy.optimize 模块中求解多变量无约束优化问题(最小值)的首选方法,采用下山单纯性方法。下山单纯性方法又称 Nelder-Mead 法,只使用目标函数值,不需要导数或二阶导数值,是最重要的多维无约束优化问题数值方法之一。
scipy.optimize.fmin(func, x0, args=(), xtol=0.0001, ftol=0.0001, maxiter=None, maxfun=None, full_output=0, disp=1, retall=0, callback=None, initial_simplex=None)
optimize.fmin() 的主要参数
- func: callable f(x,*args): 目标函数\(f(x)\)以函数形式表示,可以通过 *args 传递参数。
- x0: nadarray搜索算法的初值。
- args: tuple可选项,以 f(x,*args) 的形式将可变参数\(p\)传递给目标函数\(f(x,p)\)。
optimize.fmin() 的主要返回值
- **xopt: **返回最小值时的 \(x\) 值;
- **fopt: **返回最小值时的目标函数值,fopt=func(xopt)。
from scipy.optimize import fmin
import numpy as np
# Rosenbrock 测试函数
def objf2(x): # Rosenbrock benchmark function
fx = sum(100.0 * (x[1:] - x[:-1] ** 2.0) ** 2.0 + (1 - x[:-1]) ** 2.0)
return fx
# 初始化优化变量
xIni = .array([-2, -2])
# 使用 fmin 函数进行优化
xOpt = fmin(objf2, xIni)
# 打印结果
print("Initial guess: xIni={}, fxIni={:.4f}".format(xIni, objf2(xIni)))
print("Optimal solution: xOpt={}, fxOpt={:.4f}".format(xOpt, objf2(xOpt)))
三、Scipy.optimize.minimize() 求解非线性规划问题
3.1 scipy.optimize.minimize() 函数说明
minimize() 函数是 SciPy.optimize 模块中求解多变量优化问题的通用方法,可以调用多种算法,支持约束优化和无约束优化。
scipy.optimize.minimize(fun, x0, args=(), method=None, jac=None, hess=None, hessp=None, bounds=None, constraints=(), tol=None, callback=None, options=None)
optimize.minimize() 的主要参数:
参数 | 说明 |
---|---|
fun: callable f(x,*args) | 目标函数\(f(x)\),以函数形式表示,可以通过 args 传递参数 |
x0: nadarray, shape(n,) | 搜索算法的初值,n 是决策变量个数 |
args: tuple | 可选项,将可变参数传递给目标函数 fun、导数函数 jac 和二阶导数函数 hess |
method: str | 可选项,选择优化算法。默认算法为 BFGS, L-BFGS-B, SLSQP(取决于问题有没有边界条件和约束条件) |
jac | 可选项,梯度计算方法。可以以函数形式表示,或选择 '2-point', '3-point', 'cs'。该选项只能用于 CG, BFGS, Newton-CG, L-BFGS-B, TNC, SLSQP, dogleg, trust-ncg, trust-krylov, trust-exact 和 trust-constr 算法 |
hess | 可选项,Hessian 矩阵计算方法。可以以函数形式表示,或选择 '2-point', '3-point', 'cs'。该选项只能用于 Newton-CG, dogleg, trust-ncg, trust-krylov, trust-exact 和 trust-constr 算法 |
bounds | 可选项,变量的边界条件(上下限,lb<=x<=ub)。该选项只能用于 Nelder-Mead, L-BFGS-B, TNC, SLSQP, Powell 和 trust-constr 算法 |
constraints | 可选项,定义约束条件 f(x)>=0。该选项只能用于 COBYLA, SLSQP 和 trust-constr 算法,注意不同算法中对于约束条件的定义是不同的 |
optimize.minimize() 的主要返回值:
参数 | 解释 |
---|---|
res | 返回优化结果,以对象方式表示,主要包括优化是否成功、决策变量的优化值 xOpt |
optimize.minimize() | 默认算法为 BFGS, L-BFGS-B, SLSQP(取决于问题有没有边界条件和约束条件),可以通过 "method=None" 选项调用多种算法 |
method=‘CG’ | 非线性共轭梯度算法,只能处理无约束优化问题,需要使用一阶导数函数 |
method=‘BFGS’ | BFGS 拟牛顿法,只能处理无约束优化问题,需要使用一阶导数函数。BFGS 算法性能良好,是无约束优化问题的默认算法 |
method=‘Newton-CG’ | 截断牛顿法,只能处理无约束优化问题,需要使用一阶导数函数,适合处理大规模问题 |
method=‘dogleg’ | dog-leg 信赖域算法,需要使用梯度和 Hessian(必须正定),只能处理无约束优化问题 |
method=‘trust-ncg’ | 采用牛顿共轭梯度信赖域算法,需要使用梯度和 Hessian(必须正定),只能处理无约束优化问题,适合大规模问题 |
method=‘trust-exact’ | 求解无约束极小化问题的信赖域方法,需要梯度和Hessian(不需要正定) |
method=‘trust-krylov’ | 使用Newton-GLTR 信赖域算法度,需要使用梯度和 Hessian(必须正定),只能处理无约束优化问题,适合中大规模问题 |
method=‘Nelder-Mead’ | 下山单纯性法,可以处理边界约束条件(决策变量的上下限),只使用目标函数,不使用导数函数、二阶导数,鲁棒性强 |
method=‘L-BFGS-B’ | 改进的 BFGS 拟牛顿法,L- 指有限内存,-B 指边界约束,可以处理边界约束条件,需要使用一阶导数函数。L-BFGS_B 算法性能良好,消耗内存量很小,适合处理大规模问题,是边界约束优化问题的默认算法 |
method=‘Powell’ | 改进的共轭方向法,可以处理边界约束条件(决策变量的上下限) |
method=‘TNC’ | 截断牛顿法,可以处理边界约束条件 |
method=‘COBYLA’ | 线性近似约束优化方法,通过对目标函数和约束条件的线性逼近处理非线性问题。只使用目标函数,不需要导数或二阶导数值,可以处理约束条件 |
method=‘SLSQP’ | 序贯最小二乘规划算法,可以处理边界约束、等式约束和不等式约束条件。SLSQP 算法性能良好,是带有约束条件优化问题的默认算法 |
method=‘trust-constr’ | 信赖域算法,通用的约束最优化方法,适合处理大规模问题 |
由于 optimize.minimize() 实际是多种算法的集成接口,各种算法对于问题、约束条件和参数的定义并不完全相同。我们还是针对数学建模的常用需求和小白的特点,结合实际案例来学习基本应用。
3.2Scipy.optimize.minimize()函数的应用
最小化目标函数:\(f(x) = 5x_1 - 2x_2 + x_3\)
约束条件:
在这个例子中,目标函数是一个线性函数,但是约束条件包括非线性函数。特别地,第三个约束条件是一个二次函数。这个问题可以通过使用数值优化算法来解决,使用scipy.optimize.minimize()函数。
import numpy as np
from scipy.optimize import minimize
# 定义目标函数和约束条件
def objective(x):
return 5 * x[0] - 2 * x[1] + x[2]
def constraint1(x):
return x[0] + x[1] + x[2] - 1
def constraint2(x):
return 1 - x[0] ** 2 - x[1] ** 2 - x[2] ** 2
def constraint3(x):
return x[0] - x[2] ** 2
def constraint4(x):
return -x[0] - x[1] - x[2] + 1
# 定义初始点
x0 = np.array([0, 0, 0])
# 使用 SLSQP 算法求解非线性规划问题
solution = minimize(objective, x0, method='SLSQP', constraints=[
{'fun': constraint1, 'type': 'eq'},
{'fun': constraint2, 'type': 'ineq'},
{'fun': constraint3, 'type': 'ineq'},
{'fun': constraint4, 'type': 'ineq'}
])
print("Optimal solution:", solution.x)
print("Optimal value:", solution.fun)
四、约束非线性规划问题实例
4.1 非线性规划问题的数学模型1
\[minf(x) = ax_1^2 + bx_2^2 + cx_3^2 + d\\ s.t.\begin{cases} x_1^2 - x_2 + x_3^2 \geq 0\\ x_1 + x_2^2 + x_3^3 \leq 20\\ -x_1 - x_2^2 + 2 = 0\\ x_2 + 2x_3^2 = 3\\ x_1, x_2, x_3 \geq 0 \end{cases} \]由于 minimize() 函数中对约束条件的形式定义为 f(x)>=0,因此要将问题的数学模型转换为标准形式:
\[minf(x) = ax_1^2 + bx_2^2 + cx_3^2 + d\\ s.t.\begin{cases} x_1^2 - x_2 + x_3^2 \geq 0\\ -x_1 - x_2^2 - x_3^3 +20\geq 0\\ -x_1 - x_2^2 + 2 = 0\\ x_2 + 2x_3^2 = 3\\ x_1, x_2, x_3 \geq 0 \end{cases} \]程序说明:
在本例程中,目标函数中的参数 a, b, c, d 在子程序中直接赋值,这种实现方式最简单;
定义边界约束,即优化变量的上下限,用 minimize() 函数中的选项 bounds=bnds 进行定义。
定义约束条件:
- 本案例有 4个约束条件,2个等式约束、2个不等式约束,上节中已写成标准形式;
- 本例程将每个约束条件作为一个子函数定义,
- minimize() 函数对约束条件按照字典格式: {'type': 'ineq', 'fun': functionname} 进行定义。'type' 的键值可选 'eq' 和 'ineq',分别表示的是约束和不等式约束;functionname是定义约束条件的函数名。
求解最小化问题 res,其中目标函数 objF4 和搜索的初值点 x0 是必需的,指定优化方法和边界条件、约束条件是可选项。
通过调用最小化问题的返回值可以得到优化是否成功的说明(res.message)、自变量的优化值(res.x)和目标函数的优化值(res.fun)。
from scipy.optimize import brent, fmin, minimize
import numpy as np
#约束非线性规划问题(Scipy.optimize.minimize)
def objF4(x): # 定义目标函数
a, b, c, d = 1, 2, 3, 8
fx = a*x[0]**2 + b*x[1]**2 + c*x[2]**2 + d
return fx
# 定义约束条件函数
def constraint1(x): # 不等式约束 f(x)>=0
return x[0]** 2 - x[1] + x[2]**2
def constraint2(x): # 不等式约束 转换为标准形式
return -(x[0] + x[1]**2 + x[2]**3 - 20)
def constraint3(x): # 等式约束
return -x[0] - x[1]**2 + 2
def constraint4(x): # 等式约束
return x[1] + 2*x[2]**2 -3
# 定义边界约束
b = (0.0, None)
bnds = (b, b, b)
# 定义约束条件
con1 = {'type': 'ineq', 'fun': constraint1}
con2 = {'type': 'ineq', 'fun': constraint2}
con3 = {'type': 'eq', 'fun': constraint3}
con4 = {'type': 'eq', 'fun': constraint4}
cons = ([con1, con2, con3,con4]) # 3个约束条件
# 求解优化问题
x0 = np.array([1., 2., 3.]) # 定义搜索的初值
res = minimize(objF4, x0, method='SLSQP', bounds=bnds, constraints=cons)
print("Optimization problem (res):\t{}".format(res.message)) # 优化是否成功
print("xOpt = {}".format(res.x)) # 自变量的优化值
print("min f(x) = {:.4f}".format(res.fun)) # 目标函数的优化值
4.2 非线性规划的数学模型2
\[\min \quad \log_2(1+\frac{2x}{3})+\log_2(1+\frac{3y}{4}) \\\text{s.t.} \quad \log_2(1+\frac{2x}{5}) ≥ 5 \\\quad \quad \log_2(1+\frac{3y}{2}) ≥ 5 \]from scipy.optimize import minimize
import numpy as np
fun = lambda x: np.log2(1+x[0]*2/3)+np.log2(1+x[1]*3/4)
x0 = np.array([0.5,0.5])
cons = [{'type':'ineq', 'fun':lambda x:np.log2(1+x[0]*2/5)-5},
{'type':'ineq', 'fun':lambda x:np.log2(1+x[1]*3/2)-5}]
res = minimize(fun, x0, method='SLSQP', constraints=cons)
print(res.fun) # 9.763212360886708
print(res.x) # [77.5 20.66666658]
4.3 非线性规划问题的数学模型3
\[minf(x) = 10-x_1^2 -x_2^2\\ s.t.\begin{cases} -x_1^2 + x_2 \geq 0\\ x_1 + x_2 = 0\end{cases} \]from scipy.optimize import minimize
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import pyplot as plt
# 目标函数
def func(x):
return 10 - x[0]**2 - x[1]**2
# 约束条件,包括不等式约束和等式约束
def con(x):
return [x[1] - x[0]**2, x[0] + x[1]]
# 画三维模式图
def draw3D():
fig = plt.figure()
ax = Axes3D(fig)
x_arange = np.arange(-5.0, 5.0)
y_arange = np.arange(-5.0, 5.0)
X, Y = np.meshgrid(x_arange, y_arange)
Z1 = 10 - X**2 - Y**2
Z2 = Y - X**2
Z3 = X + Y
plt.xlabel('x')
plt.ylabel('y')
ax.plot_surface(X, Y, Z1, rstride=1, cstride=1, cmap='rainbow')
ax.plot_surface(X, Y, Z2, rstride=1, cstride=1, cmap='rainbow')
ax.plot_surface(X, Y, Z3, rstride=1, cstride=1, cmap='rainbow')
plt.show()
# 画等高线图
def drawContour():
x_arange = np.linspace(-3.0, 4.0, 256)
y_arange = np.linspace(-3.0, 4.0, 256)
X, Y = np.meshgrid(x_arange, y_arange)
Z1 = 10 - X**2 - Y**2
Z2 = Y - X**2
Z3 = X + Y
plt.xlabel('x')
plt.ylabel('y')
plt.contourf(X, Y, Z1, 8, alpha=0.75, cmap='rainbow')
plt.contourf(X, Y, Z2, 8, alpha=0.75, cmap='rainbow')
plt.contourf(X, Y, Z3, 8, alpha=0.75, cmap='rainbow')
C1 = plt.contour(X, Y, Z1, 8, colors='black')
C2 = plt.contour(X, Y, Z2, 8, colors='blue')
C3 = plt.contour(X, Y, Z3, 8, colors='red')
plt.clabel(C1, inline=1, fontsize=10)
plt.clabel(C2, inline=1, fontsize=10)
plt.clabel(C3, inline=1, fontsize=10)
plt.show()
if __name__ == "__main__":
x0 = np.array((1.0, 2.0)) # 设置初始值
res = minimize(func, x0, method='SLSQP', constraints={'type': 'eq', 'fun': con})
print("Optimal value:", res.fun)
print("Success:", res.success)
print("Optimal solution:", res.x)
# draw3D()
drawContour()
总结
Scipy 工具包中的 minimize() 函数集成了多种求解线性规划问题的算法,可以处理边界条件和等式、不等式约束,对于常见的非线性规划问题都能获得较好的解。scipy.optimize 模块中的 minimize 函数是用于求解最小化目标函数的多功能工具。该函数支持对标量目标函数进行最小化,同时考虑等式和不等式约束。minimize 函数可以用于各种实际问题,包括机器学习、数据拟合、物理建模等。用户可以通过指定不同的算法、约束条件和初始猜测值来调整优化过程。一些常用的优化算法包括共轭梯度法(Conjugate Gradient)、BFGS(Broyden-Fletcher-Goldfarb-Shanno)等。