Skip to content

SciPy - 优化

scipy.optimize 包提供了丰富的函数最小化(优化)、求解方程根和曲线拟合算法集。优化是寻找导致给定函数取得最小值(或最大值)的输入值(参数)的过程。

此模块包含以下工具:

  • 单变量函数最小化: 寻找单变量函数的最小值 (minimize_scalar)。
  • 多变量函数最小化: 寻找多变量函数的最小值,包括无约束和有约束的 (minimize)。提供了多种算法(例如,BFGS, Nelder-Mead, SLSQP)。
  • 全局优化: 旨在找到全局最小值的例程,可能避免局部最小值(例如,basinhopping, differential_evolution)。
  • 最小二乘最小化: 最小化残差平方和,常用于曲线拟合(least_squares, curve_fit)。
  • 求解方程: 求解 $f(x) = 0$ 形式的方程。用于单变量函数(newton, brentq)和多变量系统(root)。

使用 minimize() 最小化多变量标量函数

Section titled “使用 minimize() 最小化多变量标量函数”

minimize() 函数为最小化一个或多个变量的标量函数提供了统一的接口。让我们考虑 Rosenbrock 函数,它是优化算法的常见基准。对于 N 个变量,它定义为:

$$f(\mathbf{x}) = \sum_{i=0}^{N-2} [100(x_{i+1} - x_i^2)^2 + (1 - x_i)^2]$$

全局最小值是 $f(\mathbf{x}) = 0$,出现在 $\mathbf{x} = (1, 1, \ldots, 1)$ 处。

Nelder-Mead 算法是一种直接搜索方法,不需要梯度信息。它对于非光滑函数通常很鲁棒,但可能比基于梯度的算法慢。

import numpy as np
from scipy.optimize import minimize
# 定义 N 变量的 Rosenbrock 函数
def rosenbrock_func(x):
"""Rosenbrock 函数。"""
return sum(100.0 * (x[i+1] - x[i]**2.0)**2.0 + (1 - x[i])**2.0
for i in range(len(x)-1))
# 初始猜测(例如,对于 5 维 Rosenbrock 函数)
x_initial = np.array([1.5, 0.8, 1.2, 0.7, 1.1])
print(f"Initial guess x0: {x_initial}")
print(f"Rosenbrock function value at initial guess: {rosenbrock_func(x_initial):.4f}")
# 使用 Nelder-Mead 方法进行最小化
# `method='Nelder-Mead'`
result_nelder_mead = minimize(rosenbrock_func, x_initial, method='Nelder-Mead',
options={'xatol': 1e-8, 'disp': True})
print("\nResult from Nelder-Mead:")
print(f"Optimal x: {result_nelder_mead.x}")
print(f"Function value at optimum: {result_nelder_mead.fun}")
print(f"Success: {result_nelder_mead.success}")
# print(f"Message: {result_nelder_mead.message}")

输出将显示优化过程(如果 disp=True)和最终结果:

Initial guess x0: [1.5 0.8 1.2 0.7 1.1]
Rosenbrock function value at initial guess: 145.1000
Optimization terminated successfully.
Current function value: 0.000000
Iterations: 378
Function evaluations: 662
Result from Nelder-Mead:
Optimal x: [1. 1. 1. 1. 0.99999999]
Function value at optimum: 5.959159901745691e-17
Success: True

单纯形算法简单且不需要梯度,因此适用于某些问题。然而,对于更光滑的函数,基于梯度的算法如 BFGS (Broyden-Fletcher-Goldfarb-Shanno),可通过 method='BFGS' 使用,通常更快、更准确。要使用 BFGS,你可能需要提供函数的 Jacobian(梯度),或者算法可以对其进行近似。

使用 least_squares 进行最小二乘最小化

Section titled “使用 least_squares 进行最小二乘最小化”

最小二乘优化旨在找到参数 $\mathbf{x}$,使 $m$ 个残差函数 $r_i(\mathbf{x})$ 的平方和最小:$Cost(\mathbf{x}) = \sum_{i=0}^{m-1} r_i(\mathbf{x})^2$。这在数据拟合(曲线拟合)中非常常见。

让我们再次使用 Rosenbrock 函数,但将其构建为一个最小二乘问题。Rosenbrock 函数可以写成 $f(x_0, x_1) = r_0(x_0, x_1)^2 + r_1(x_0, x_1)^2$,其中 $r_0 = 10(x_1 - x_0^2)$ 且 $r_1 = (1 - x_0)$。

import numpy as np
from scipy.optimize import least_squares
# 定义 Rosenbrock 的残差函数(此处为简化起见使用 2D 情况)
def rosenbrock_residuals(x):
# x 预期是一个数组 [x0, x1]
r0 = 10 * (x[1] - x[0]**2)
r1 = (1 - x[0])
return np.array([r0, r1])
# x = [x0, x1] 的初始猜测
x_initial_ls = np.array([2.0, 2.0])
print(f"Initial guess for least_squares: {x_initial_ls}")
print(f"Residuals at initial guess: {rosenbrock_residuals(x_initial_ls)}")
# 求解最小二乘问题
result_least_squares = least_squares(rosenbrock_residuals, x_initial_ls)
print("\nResult from least_squares:")
print(f"Optimal x: {result_least_squares.x}")
print(f"Value of residuals at optimum: {result_least_squares.fun}") # These are the r_i values
print(f"Cost function (sum of squares of residuals): {result_least_squares.cost}")
print(f"Success: {result_least_squares.success}")
# print(f"Message: {result_least_squares.message}")

least_squares 函数需要一个返回残差向量的函数。然后它最小化这些残差的平方和。2D Rosenbrock 函数的精确最小值在 $x = [1.0, 1.0]$ 处。

输出显示收敛到已知最小值:

Initial guess for least_squares: [2. 2.]
Residuals at initial guess: [-20. -1.]
Result from least_squares:
Optimal x: [1. 1.]
Value of residuals at optimum: [4.4408921e-15 0.0000000e+00]
Cost function (sum of squares of residuals): 9.866924291084687e-30
Success: True

对于曲线拟合,scipy.optimize.curve_fit 是一个专门且通常更方便的函数,它使用最小二乘法将用户定义的函数拟合到数据。

求解方程根(Root finding)涉及求解 $f(x) = 0$(对于标量函数)或 $\mathbf{f}(\mathbf{x}) = \mathbf{0}$(对于方程组)形式的方程。

对于单变量方程,scipy.optimize 提供了几种算法,如 brentq(如果已知包含根的区间,这通常是最佳选择)、newton(需要导数或对其进行近似)和 bisect。

要找到一组非线性方程 $\mathbf{f}(\mathbf{x}) = \mathbf{0}$ 的根,请使用 root() 函数。它支持多种方法,包括 ‘hybr’ (Powell 的混合方法,默认) 和 ‘lm’ (Levenberg-Marquardt)。

考虑求解单变量超越方程 $2x + 2\cos(x) = 0$。我们正在寻找满足 $f(x) = 2x + 2\cos(x)$ 为零的 $x$。

import numpy as np
from scipy.optimize import root
# 定义我们要找到根的函数
def func_for_root(x):
# 注意:即使是单变量问题,使用 root() 时 x 也需要是一个数组
return 2 * x[0] + 2 * np.cos(x[0])
# 方程根的初始猜测
initial_guess_root = np.array([0.0]) # 对于 root() 必须是一个数组
solution = root(func_for_root, initial_guess_root)
print("Solution from root():")
print(f"Root x: {solution.x}")
print(f"Function value at root f(x): {solution.fun}") # 应该接近 0
print(f"Success: {solution.success}")
# print(f"Message: {solution.message}")

输出将显示找到的根:

Solution from root():
Root x: [-0.73908513]
Function value at root f(x): [0.]
Success: True

这意味着 $x approx -0.739$ 是方程 $2x + 2\cos(x) = 0$ 的一个根。

优化技术在以下领域至关重要:

  • 机器学习: 通过最小化损失函数来训练模型。
  • 工程: 设计结构、流程或系统以实现最大效率或最小成本。
  • 金融: 投资组合优化、风险管理。
  • 物流: 路径规划、资源分配。
  • 科学研究: 模型中的参数估计,以拟合实验数据。

选择合适的优化算法取决于问题的特征:是约束问题还是无约束问题,是否可获得导数,函数的平滑度,以及是寻求全局最小值还是局部最小值。始终查阅 SciPy 文档,获取关于每个优化器的优势和劣势的详细指导。