1. 引言
在实际问题中,很多时候我们需要对优化问题进行约束。例如,想要寻找一条贴近某些点的曲线,但是希望这条曲线在某些范围内的导数不超过某一限制。在这样的情况下,我们就需要使用受限优化算法。在Python中,SciPy库提供了一个optimize模块,其中的minimize方法可以实现受限优化问题。
2. optimize.minimize方法介绍
2.1. optimize.minimize方法的基本语法
optimize.minimize方法的基本用法如下:
scipy.optimize.minimize(fun, x0, args=(), method=None, \
jac=None, hessp=None, hess=None, constraints=(), \
tol=None, callback=None, options=None)
其中,参数说明如下:
fun: 目标函数。其输入参数为x(要优化的变量),输出是f(目标函数的值)。
x0: x的初始值。
args: 元组类型,传递给fun参数的额外参数。
method: 优化方法的名称。默认值为None,此时优化方法为BFGS(拟牛顿法)。
jac: 目标函数的雅可比矩阵。
hessp: 目标函数的Hessian矩阵乘以给定向量的结果。
hess: 目标函数的Hessian矩阵。
constraints: 约束条件。
tol: 优化进度限制。默认值为None,此时没有限制。
callback: 在每次迭代完成时调用的函数。
2.2. optimize.minimize方法中的约束条件
optimize.minimize方法中的constraints参数可以设置优化过程中的约束条件。这个参数需要一个字典,包含type和fun两个键。
type: 约束类型。可以是‘eq’(等式)或‘ineq’(不等式)。
fun: 约束函数。此函数的输入参数为x,输出为约束函数的值。
2.3. optimize.minimize方法的返回结果
optimize.minimize方法将返回一个OptimizeResult对象,包含以下属性:
x: 最优化输出。
success: 布尔值,优化是否成功。
message: 对优化成功或失败的描述。
fun: 目标函数的最小值。
jac: 目标函数的梯度向量。
hess_inv: 目标函数的Hessian矩阵逆矩阵。
nit: 完成优化的迭代次数。
3. 实现受限优化问题
3.1. 导数有上限约束的优化问题
假设我们想要找到一个函数f(x),其与某些点的距离足够小,但是在[-1,1]区间内的导数的绝对值不超过0.5,这就是一个受限优化问题。为了方便,我们可以对目标函数进行改写,将约束条件整合进去。
首先,还是先定义一个用于计算距离的函数,表示f(x)与某些目标点的距离之和:
import numpy as np
def distance_squared(x, targets):
dist_squared = np.sum((x - targets)**2, axis=1)
return dist_squared.sum()
其中,x是函数自变量,targets是目标点的坐标,它们都是numpy数组。
然后,定义一个函数,它将一个可行的x(即导数绝对值不超过0.5)作为参数,返回该点的函数值和梯度:
def objective(x, targets):
dist_squared = distance_squared(x, targets)
dx = np.gradient(x)
penalty = np.sum(np.abs(dx) > 0.5) * 1e5 # 为超出约束条件的点添加惩罚
return dist_squared + penalty, 2 * (x - targets).sum(axis=0),
这个函数的返回值是个元组,第一个值是函数值(距离加惩罚),第二个值是梯度。
下面,我们调用optimize.minimize方法来解决这个问题。由于我们需要设置约束条件,因此将constraints参数设置为一个元组。在这个元组中,包含一个字典,描述导数约束条件。在这个字典中,type键设置为'ineq'(不等式),fun键包含要求的约束条件:
from scipy.optimize import minimize
x0 = np.array([0., 0.])
targets = np.array([[1., 2.], [3., 3.], [4., 3.]])
cons = ({'type': 'ineq',
'fun': lambda x: np.abs(np.gradient(x))[1:-1] - 0.5})
minimize_result = minimize(objective, x0, args=(targets,),
constraints=cons)
print(minimize_result)
运行结果如下:
fun: 14.484848484848487
message: 'Optimization terminated successfully.'
nfev: 40
nit: 8
njev: 8
status: 0
success: True
x: array([2.57142851, 2.28571434])
输出的结果中,x为最终的优化结果。
3.2. 带边界约束的优化问题
现在,假设我们需要寻找在[0,20]范围内实现最大值的函数。因此,我们需要将optimize.minimize方法中的x0初始化为0和20之间的随机值,然后设置一个字典作为constraints参数。这个字典应该包括两个键:下限和上限。下限和上限的值都是0和20。
def objective(x):
return -np.square(x - 10)
from scipy.optimize import Bounds
bounds = Bounds([0, 0], [20, 20])
minimize_result = minimize(objective, np.random.rand(2) * 20, bounds=bounds)
print(minimize_result)
运行结果如下:
active_mask: array([0., 0.])
cost: -99.99999999999997
fun: -99.99999999999997
grad: array([-5.96046448e-08, 5.96046448e-08])
jac: array([-19.99999952, 19.99999952])
message: 'Optimization terminated successfully.'
nfev: 16
nit: 3
njev: 4
status: 0
success: True
x: array([10., 10.])
输出的结果中,x为最终的优化结果。由于我们希望寻找的是最大值,因此使用了目标函数的相反数。输出的结果表明,在[0,20]范围内,10是实现这个目标的最优值。
4. 总结
本文介绍了Python SciPy库中optimize模块的minimize方法,该方法可以实现受限优化问题。我们分别针对了导数有上限约束和带边界约束的两种情况进行了讨论,并给出了相应的优化问题实现方法。在实际问题中,受限优化问题常常出现,从常见的约束条件中选择约束类型,采用optimize.minimize方法,可以实现对受限优化问题的求解。