SciPy 优化器
SciPy 的 optimize 模块提供了常用的数值优化与方程求解算法实现。
我们可以直接调用这些函数来解决实际问题,例如查找函数的最小值、最大值,或者求解方程的根等。
NumPy 本身主要关注数组计算和线性代数运算,它提供了用于多项式求根的工具(如 numpy.roots),
但并未提供通用的非线性方程数值求根接口。
当我们需要求解一般形式的非线性方程时,就需要使用 SciPy 的优化模块。
例如下面的非线性方程:
x + cos(x)
对于这类问题,可以使用 scipy.optimize.root 函数来求解。
该函数常用的两个核心参数如下:
- fun - 表示方程的函数(返回值为 0 时即表示找到方程的根)
- x0 - 根的初始猜测值
root 函数返回一个结果对象(OptimizeResult),
其中包含求解状态、是否成功、函数值以及最终解等信息。
实际求得的解保存在返回对象的 x 属性中,示例如下:
实例
查找 x + cos(x) = 0 方程的根:
from math import cos
def eqn(x):
return x + cos(x)
myroot = root(eqn, 0)
print(myroot.x)
# 查看更多信息
# print(myroot)
执行以上代码,输出结果如下:
[-0.73908513]
如果直接打印返回对象,可以查看更完整的求解信息:
实例
from math import cos
def eqn(x):
return x + cos(x)
myroot = root(eqn, 0)
print(myroot)
执行以上代码,输出结果如下:
fjac: array([[-1.]])
fun: array([0.])
message: 'The solution converged.'
nfev: 9
qtf: array([-2.66786593e-13])
r: array([-1.67361202])
status: 1
success: True
x: array([-0.73908513])
其中:
- success 表示算法是否成功收敛
- message 描述求解过程的结果说明
- x 为最终求得的方程根
最小化函数
在数学和工程问题中,函数通常可以表示为一条曲线, 而曲线往往存在高点和低点。
曲线的高点称为最大值。
曲线的低点称为最小值。
整条曲线中的最高点称为全局最大值, 其余较高但不是最高的点称为局部最大值。
同理,整条曲线中的最低点称为全局最小值, 其余较低但不是最低的点称为局部最小值。
在 SciPy 中,可以使用 scipy.optimize.minimize() 函数来寻找函数的最小值。
minimize() 函数常用参数说明如下:
-
fun - 需要最小化的目标函数
-
x0 - 自变量的初始猜测值
-
method - 使用的优化算法名称, 常见可选值包括:
'CG'、'BFGS'、'Newton-CG'、'L-BFGS-B'、'TNC'、'COBYLA'、'SLSQP' -
callback - 每次迭代结束后调用的回调函数
-
options - 其他控制参数的字典,例如:
{ "disp": boolean, # 是否打印详细的优化过程信息 "gtol": number # 梯度收敛容差(部分算法适用) }
实例
使用 BFGS 方法最小化函数 x² + x + 2:
def eqn(x):
return x**2 + x + 2
mymin = minimize(eqn, 0, method='BFGS')
print(mymin)
执行以上代码,输出结果如下:
fun: 1.75
hess_inv: array([[0.50000001]])
jac: array([0.])
message: 'Optimization terminated successfully.'
nfev: 8
nit: 2
njev: 4
status: 0
success: True
x: array([-0.50000001])
从结果可以看到,函数在 x ≈ -0.5 处取得最小值,
对应的最小函数值为 1.75,
这与解析计算的结果一致。
minimize() 函数还支持带约束的优化问题,
可以通过 constraints 参数添加等式或不等式约束条件,
适用于更复杂的实际应用场景。
当目标函数无法直接求导时,可以选择 method='Nelder-Mead' 等无需梯度信息的优化算法,
这类方法通过单纯形搜索逐步逼近最优解。
在实际使用中,success 字段表示优化是否成功收敛,
fun 字段为最优目标函数值,
x 字段为最优解。
建议始终检查 success 状态,以确保求解结果是可靠的。
方程求根进阶
除了 root() 函数外,SciPy 还提供了 fsolve() 函数用于求解方程根,
它的使用更加简洁,适合快速求解简单方程。
实例
使用 fsolve 求解 x + cos(x) = 0:
from math import cos
def eqn(x):
return x + cos(x)
result = fsolve(eqn, 0)
print(result)
执行以上代码,输出结果如下:
[-0.73908513]
fsolve() 直接返回解的数组,而 root() 返回包含更多信息的结果对象。
对于只需要解的简单场景,fsolve 更加方便。
多个初始值求解
对于存在多个根的方程,从不同的初始值出发可能得到不同的根。
以下是求解 x² - 4 = 0 的两个根:
实例
def eqn(x):
return x**2 - 4
# 从不同初始值求解
for guess in [-3, 0, 3]:
result = root(eqn, guess)
if result.success:
print(f"初始值 {guess:2d} -> 根: {result.x[0]:.6f}")
执行以上代码,输出结果如下:
初始值 -3 -> 根: -2.000000 初始值 0 -> 根: 2.000000 初始值 3 -> 根: 2.000000
带参数的方程求解
在实际问题中,方程往往包含参数。我们可以通过 args 参数将额外参数传递给目标函数:
实例
def eqn(x, a, b):
return a * x + b * cos(x)
# 求解 2x + cos(x) = 0
result = root(eqn, 0, args=(2, 1))
print(f"解: {result.x[0]:.6f}")
执行以上代码,输出结果如下:
解: -0.450184
约束优化
minimize() 函数支持添加约束条件,包括等式约束和不等式约束。
以下是带约束优化问题的示例:
实例
最小化 x² + y²,约束条件为 x + y = 1:
def objective(x):
return x[0]**2 + x[1]**2
def constraint(x):
return x[0] + x[1] - 1
# 定义约束
con = {'type': 'eq', 'fun': constraint}
result = minimize(objective, [0, 0], constraints=con)
print(f"最优解: x={result.x[0]:.6f}, y={result.x[1]:.6f}")
print(f"最小值: {result.fun:.6f}")
执行以上代码,输出结果如下:
最优解: x=0.500000, y=0.500000 最小值: 0.500000
约束类型 'eq' 表示等式约束(约束函数必须等于 0),
'ineq' 表示不等式约束(约束函数必须大于等于 0)。
边界约束优化
除了函数约束外,还可以通过 bounds 参数限制自变量的取值范围:
实例
在 x ∈ [0, 2] 范围内最小化 (x - 1)² + 2:
def objective(x):
return (x - 1)**2 + 2
# 定义边界
bounds = [(0, 2)]
result = minimize(objective, 0, bounds=bounds)
print(f"最优解: x={result.x[0]:.6f}")
print(f"最小值: {result.fun:.6f}")
执行以上代码,输出结果如下:
最优解: x=1.000000 最小值: 2.000000
无梯度优化
当目标函数不可导或导数难以计算时,可以使用无需梯度信息的优化算法,
如 Nelder-Mead 方法:
实例
使用 Nelder-Mead 方法最小化 sin(x) + 0.5*x:
from math import sin
def objective(x):
return sin(x) + 0.5 * x
result = minimize(objective, 0, method='Nelder-Mead')
print(f"最优解: x={result.x[0]:.6f}")
print(f"最小值: {result.fun:.6f}")
print(f"是否成功: {result.success}")
执行以上代码,输出结果如下:
最优解: x=-1.047198 最小值: -1.523599 是否成功: True
常见问题与建议
- 选择合适的初始值 - 初始值的选择对求解结果影响很大, 建议在求解前对目标函数进行简单的可视化或分析。
-
检查收敛状态 - 始终检查
success字段, 确保算法成功收敛。如果求解失败,可以尝试更换初始值或优化算法。 -
选择合适的算法 - 对于无约束问题,
BFGS是较好的默认选择; 对于大维度问题,可以尝试L-BFGS-B; 对于不可导函数,使用Nelder-Mead。 - 处理多根问题 - 对于存在多个根的问题,建议从多个不同初始值出发进行求解。
-
参数传递 - 使用
args参数传递额外参数, 避免使用全局变量。
总结
SciPy 的 optimize 模块提供了强大的优化和方程求解工具:
- 方程求根:
root()和fsolve()用于求解非线性方程 - 无约束优化:
minimize()配合method参数实现多种算法 - 约束优化:通过
constraints参数添加等式和不等式约束 - 边界限制:使用
bounds参数限制变量取值范围 - 无梯度方法:
Nelder-Mead适用于不可导函数
掌握这些基本功能后,读者可以进一步探索 optimize 模块的更多特性,
如曲线拟合(curve_fit)、全局优化(basinhopping、differential_evolution)等高级功能。
