关于python:查找用户定义函数的局部最大值和最小值

Finding local maxima and minima of user defined functions

我想要什么

我想找到一个固定点的列表,它们的值和位置,以及它们是最小值还是最大值。

我的功能如下:

1
2
3
4
import numpy as np

def func(x,y):
  return (np.cos(x*10))**2 + (np.sin(y*10))**2

方法

以下是我想使用的方法:

  • 实际上我已经在Mathematica上做了类似的事情。我将函数分一次,然后再分两次。我看一阶导数为0的点,计算它们的值和位置。然后在这些位置取二阶导数,检查它们是极小值还是极大值。

  • 我还想知道,是否只制作一个以x和y为单位的函数值的二维数组,并找到该数组的最大值和最小值。但这需要我知道如何精确地定义x和y网格,以便可靠地捕获函数的行为。

  • 对于后一种情况,我已经找到了类似这样的方法。

    我只是想知道,在Python中,哪种方法在效率、速度、准确性甚至优雅方面更有意义?


    find a list of the stationary points, of their values and locations, and of whether they are minima or maxima.

    这通常是一个无法解决的问题。方法1(符号)适用于此,但对于复杂的函数,不存在静止点的符号解(没有符号解两个方程的一般系统的方法)。

    符号解与辛

    对于像您的示例这样的简单函数,sympy可以正常工作。这是一个完整的例子,找到了平稳点,并根据黑森特征值对其进行分类。

    1
    2
    3
    4
    5
    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是一个符号矩阵2:[[200*sin(10*x)**2 - 200*cos(10*x)**2, 0], [0, -200*sin(10*y)**2 + 200*cos(10*y)**2]]。接下来,我们将gradient等效为零,找到平稳点,并将它们逐个插入到hessian中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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只是半定的时候,我们不知道什么样的固定点是(x**2 + y**4x**2 - y**4在(0,0)处有相同的hessian,但行为不同)。输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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并没有找到所有的解决方案(其中有无限多的解决方案)。考虑求解与求解集,但在任何情况下,处理无穷多的解都是困难的。

    用scipy进行数值优化

    Scipy提供了很多数值最小化的程序,包括蛮力(这是您的方法2;通常非常慢)。这些方法很有效,但要考虑这些要点。

  • 每次运行只能找到一个最小值。
  • 用-f替换f也可以找到最大值。
  • 更改搜索的起始点(minimize的参数x0)可能会产生另一个最大值或最小值。不过,你永远不会知道还有没有其他你还没有看到的极端。
  • 这些都找不到鞍点。
  • 混合策略

    使用lambdify可以将符号表达式转换为可以传递给scipy数值解算器的python函数。

    1
    2
    3
    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只会出现一个解决方案。