现在位置: 首页 > SciPy 教程 > 正文

SciPy 优化器

SciPy 的 optimize 模块提供了常用的数值优化与方程求解算法实现。 我们可以直接调用这些函数来解决实际问题,例如查找函数的最小值、最大值,或者求解方程的根等。

NumPy 本身主要关注数组计算和线性代数运算,它提供了用于多项式求根的工具(如 numpy.roots), 但并未提供通用的非线性方程数值求根接口。 当我们需要求解一般形式的非线性方程时,就需要使用 SciPy 的优化模块。

例如下面的非线性方程:

x + cos(x)

对于这类问题,可以使用 scipy.optimize.root 函数来求解。 该函数常用的两个核心参数如下:

  • fun - 表示方程的函数(返回值为 0 时即表示找到方程的根)
  • x0 - 根的初始猜测值

root 函数返回一个结果对象(OptimizeResult), 其中包含求解状态、是否成功、函数值以及最终解等信息。

实际求得的解保存在返回对象的 x 属性中,示例如下:

实例

查找 x + cos(x) = 0 方程的根:

from scipy.optimize import root
from math import cos

def eqn(x):
    return x + cos(x)

myroot = root(eqn, 0)

print(myroot.x)
# 查看更多信息
# print(myroot)

执行以上代码,输出结果如下:

[-0.73908513]

如果直接打印返回对象,可以查看更完整的求解信息:

实例

from scipy.optimize import root
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

from scipy.optimize import minimize

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 scipy.optimize import fsolve
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 的两个根:

实例

from scipy.optimize import root

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 参数将额外参数传递给目标函数:

实例

from scipy.optimize import root

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

from scipy.optimize import minimize

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

from scipy.optimize import minimize

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 scipy.optimize import minimize
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)、全局优化(basinhoppingdifferential_evolution)等高级功能。