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

时间:2018-04-28 23:33:00

标签: python numpy minimization

我想要什么

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

我的功能如下:

import numpy as np

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

方法

以下是我正在考虑使用的方法:

  1. 我实际上已经在Mathematica上做了类似的事情。我将功能区分为一次然后两次。我查看一阶导数为0的点,计算它们的值及其位置。然后我在这些位置采用二阶导数并检查它们是最小值还是最大值。

  2. 我也想知道是否只是在x和y中创建函数值的二维数组,并找到该数组的最大值和最小值。但这需要我知道如何精确地定义x和y网格以可靠地捕获函数的行为

  3. 对于后一种情况,我已经找到了一些方法,如this one

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

1 个答案:

答案 0 :(得分:1)

  

找到静止点的列表,它们的值和位置,以及它们是最小值还是最大值。

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

SymPy的符号解决方案

对于像您的示例这样的简单函数,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是一个符号矩阵2乘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。

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具有相同的Hessian at(0,0)但行为不同)。输出:

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提供了很多numerical minimization routines,包括brute force(这是你的方法2;通常它非常慢)。这些是强大的方法,但请考虑这些要点。

  1. 每次运行只会找到一个最小值。
  2. 用-f替换f也可以找到最大值。
  3. 更改搜索的起点(minimize的参数x0)可能会产生另一个最大值或最小值。尽管如此,你永远不会知道还有其他你还未见过的极值。
  4. 这些都不会找到鞍点。
  5. 混合策略

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

    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的每次运行只会带来一个。