在最少的计算次数中找到全局最大值

时间:2016-04-14 10:52:29

标签: optimization numerical-methods

假设我在区间f上定义了一个函数[0,1],它是平滑的并且会增加到某个点a,之后它会开始递减。我在这个时间间隔内有一个网格x[i],例如具有恒定步长dx = 0.01,并且我想通过在最坏情况下进行最小数量的f评估来找到哪些点具有最高值。我认为通过应用类似渐变方法的灵感,我可以比穷举搜索更好。有任何想法吗?我想的可能是二进制搜索,或抛物线方法。

这是我编码的类似二分的方法:

def optimize(f, a, b, fa, fb, dx):
    if b - a <= dx:
        return a if fa > fb else b
    else:
        m1 = 0.5*(a + b)
        m1 = _round(m1, a, dx)
        fm1 = fa if m1 == a else f(m1)
        m2 = m1 + dx
        fm2 = fb if m2 == b else f(m2)
        if fm2 >= fm1:
            return optimize(f, m2, b, fm2, fb, dx)
        else:
            return optimize(f, a, m1, fa, fm1, dx)

def _round(x, a, dx, right = False):
    return a + dx*(floor((x - a)/dx) + right)

这个想法是:找到间隔的中间位置并计算m1m2 - 它们右侧和左侧的点。如果方向增加,则选择正确的间隔并执行相同的操作,否则请转到左侧。只要间隔太小,只需比较两端的数字即可。但是,该算法仍然没有在我计算的点处使用导数的强度。

4 个答案:

答案 0 :(得分:2)

这种功能称为单峰。

如果不计算衍生品,您可以通过

工作
  • 通过二分法找出增量x [i + 1] -x [i]改变符号的位置(增量是正数,然后是最大值后的负数);这需要Log2(n)比较;这种方法非常接近你描述的内容;

  • 使Golden section方法适应离散情况;需要Logφ(n)比较(φ~1.618)。

显然,黄金分割的成本更高,因为φ<2,但实际上二分法搜索一次需要两次函数评估,因此2Log2(n)=Log√2(n)。

可以证明这是最优的,即对于任意单峰函数,你不能比O(Log(n))更快。

如果您的功能非常规律,增量会顺利变化。你可以想象interpolation search,它试图通过线性插值而不是简单的减半来更好地预测搜索位置。在有利条件下,它可以达到O(Log(Log(n))性能。我不知道这个原理是否适用于Golden搜索。

实际上,增量上的线性插值非常接近函数值的抛物线插值。后一种方法对你来说可能是最好的,但你需要注意角落的情况。

如果允许导数,则可以在一阶导数上使用任何root solving方法,因为知道给定间隔中存在孤立的零。

如果只有一阶导数可用,请使用regular falsi。如果二阶导数也是可能的,你可以考虑牛顿,但更喜欢安全的包围方法。

我想这些方法的好处(超线性和二次收敛)因你在网格上工作而变得毫无用处。

答案 1 :(得分:0)

免责声明:没有测试代码。把它当作“灵感”。

假设你有以下11点

x,f(x) = (0,3),(1,7),(2,9),(3,11),(4,13),(5,14),(6,16),(7,5),(8,3)(9,1)(1,-1)

你可以做一些类似于二分法的启发

a = 0 ,f(a) = 3  | b=10,f(b)=-1 | c=(0+10/2) f(5)=14 

从这里你可以看到增加的间隔是[a,c [并且没有必要为最大值,因为我们知道在该间隔中函数正在增加。最大值必须在区间[c,b]中。所以在下一次迭代中你改变了s.t.的值。一个= C

a = 5 ,f(a) = 14  | b=10,f(b)=-1 | c=(5+10/2) f(6)=16

再次[a,c]正在增加,因此a在右侧移动

您可以将流程重复到a=b=c

这是实现这个想法的代码。更多信息here

int main(){
#define STEP (0.01)
#define SIZE (1/STEP)
    double vals[(int)SIZE];
    for (int i = 0; i < SIZE; ++i) {
        double x = i*STEP;
        vals[i] = -(x*x*x*x - (0.6)*(x*x));
    }
    for (int i = 0; i < SIZE; ++i) {
        printf("%f ",vals[i]);
    }
    printf("\n");

    int a=0,b=SIZE-1,c;
    double fa=vals[a],fb=vals[b] ,fc;
    c=(a+b)/2;
    fc = vals[c];

    while( a!=b && b!=c && a!=c){

        printf("%i %i %i - %f %f %f\n",a,c,b, vals[a],  vals[c],vals[b]);


        if(fc - vals[c-1] > 0){ //is the function increasing in [a,c]
            a = c;
        }else{
            b=c;
        }
        c=(a+b)/2;
        fa=vals[a];
        fb=vals[b];
        fc = vals[c];
    }
    printf("The maximum is %i=%f with %f\n", c,(c*STEP),vals[a]);


}

答案 2 :(得分:0)

找出导数(f(x))=(df / dx)= 0

的点
  • 对于衍生物,您可以使用五点模板或类似算法。
    • 应为O(n)
  • 然后在多项式回归/最小二乘回归上拟合那些多个点(其中d = 0)。
    • 也应该是O(N)。假设所有数字都是邻居。
  • 然后找到该曲线的顶部
    • 不应超过O(M),其中M是拟合函数试验的分辨率。

在获取衍生物时,你可以跳过k长度的步骤,直到衍生物改变符号。

当导数变化符号时,取k的平方根并继续反向。

再一次,导数变化符号,再取新k的平方根,改变方向。

示例:跳过100个元素,找到符号更改,闰= 10和反向,下一个更改==&gt; leap = 3 ...然后可以将每步固定为1个元素以找到确切的位置。

答案 3 :(得分:0)

我假设功能评估非常昂贵。

在特殊情况下,您的函数可以近似拟合多项式,您可以在最少的函数求值中轻松计算极值。既然你知道只有一个最大值,那么2(二次)的多项式可能是理想的。

例如:如果f(x)可以用某个已知程度的多项式表示,比如说2,那么,你可以在任意3点评估你的函数并计算多项式系数使用牛顿差分或拉格朗日插值方法。

然后简单地求解这个多项式的最大值。对于2度,您可以轻松获得最大的闭合表单。

要获得最终答案,您可以在解决方案附近搜索。