寻找静止点的列表,它们的值和位置,以及它们是极小值还是极大值。这通常是一个无法解决的问题。方法1(符号)适用于此,但对于复杂的函数,静止点没有符号解(一般情况下没有方法可以符号地解决两个方程组的问题)。对于像您的示例这样的简单函数,SymPy将很好地工作。以下是一个完整的示例,介绍如何通过Hessian矩阵的特征值来找到静止点并将其分类。
import sympy as sym
x, y = sym.symbols("x y")
f = sym.cos(x*10)**2 + sym.sin(y*10)**2
gradient = sym.derive_by_array(f, (x, y))
hessian = sym.Matrix(2, 2, sym.derive_by_array(gradient, (x, y)))
到目前为止,Hessian是一个2x2的符号矩阵:
[[200*sin(10*x)**2 - 200*cos(10*x)**2, 0], [0, -200*sin(10*y)**2 + 200*cos(10*y)**2]]
。接下来,我们通过将
gradient
等于零来找到静止点,并逐个将它们代入到Hessian中。
stationary_points = sym.solve(gradient, (x, y))
for p in stationary_points:
value = f.subs({x: p[0], y: p[1]})
hess = hessian.subs({x: p[0], y: p[1]})
eigenvals = hess.eigenvals()
if all(ev > 0 for ev in eigenvals):
print("Local minimum at {} with value {}".format(p, value))
elif all(ev < 0 for ev in eigenvals):
print("Local maximum at {} with value {}".format(p, value))
elif any(ev > 0 for ev in eigenvals) and any(ev < 0 for ev in eigenvals):
print("Saddle point at {} with value {}".format(p, value))
else:
print("Could not classify the stationary point at {} with value {}".format(p, value))
最后一个从句是必要的,因为当Hessian矩阵只是半正定时,我们无法确定这是什么类型的稳定点(在(0, 0)处,
x**2 + y**4
和
x**2 - y**4
具有相同的Hessian矩阵,但行为不同)。输出:
Saddle point at (0, 0) with value 1
Local maximum at (0, pi/20) with value 2
Saddle point at (0, pi/10) with value 1
Local maximum at (0, 3*pi/20) with value 2
Local minimum at (pi/20, 0) with value 0
Saddle point at (pi/20, pi/20) with value 1
Local minimum at (pi/20, pi/10) with value 0
Saddle point at (pi/20, 3*pi/20) with value 1
Saddle point at (pi/10, 0) with value 1
Local maximum at (pi/10, pi/20) with value 2
Saddle point at (pi/10, pi/10) with value 1
Local maximum at (pi/10, 3*pi/20) with value 2
Local minimum at (3*pi/20, 0) with value 0
Saddle point at (3*pi/20, pi/20) with value 1
Local minimum at (3*pi/20, pi/10) with value 0
Saddle point at (3*pi/20, 3*pi/20) with value 1
显然,
solve
没有找到
所有的解(因为这些解是无限多的)。考虑使用
solve vs solveset,但在任何情况下,处理无限多的解都很困难。
使用 SciPy 进行数值优化
SciPy 提供了许多数值最小化例程,包括暴力搜索(也就是方法 2;通常速度非常慢)。这些是强大的方法,但需要考虑以下几点。
- 每次运行只能找到一个最小值。
- 用 -f 替换 f,你也可以找到一个最大值。
- 改变搜索的起始点(
minimize
的参数 x0)可能会得到另一个最大值或最小值。但你永远不会知道是否还有其他未被发现的极值。
- 这些方法都无法找到鞍点。
混合策略
使用
lambdify
可以将符号表达式转换为 Python 函数,该函数可以传递给 SciPy 数值求解器。
from scipy.optimize import fsolve
grad = sym.lambdify((x, y), gradient)
fsolve(lambda v: grad(v[0], v[1]), (1, 2))
这将返回一些静止点,例如在这个例子中是
[0.9424778, 2.04203522]
。它是哪一个取决于初始猜测,这里是(1, 2)。通常(但并非总是),你会得到一个接近初始猜测的解。相比直接最小化方法,这种方法的优势在于可以检测到鞍点。然而,要找到所有的解仍然很困难,因为每次运行
fsolve
只能得到一个解。