scipy.optimize.minimize 的优化算法(1): Nelder–Mead Simplex

Nelder–Mead Simplex Algorithm

Reference:
http://home.agh.edu.pl/~pba/pdfdoc/Numerical_Optimization.pdf
https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html
http://www.jasoncantarella.com/downloads/NelderMeadProof.pdf

算法

先直接来看算法:

scipy.optimize.minimize 的优化算法(1): Nelder–Mead Simplex_第1张图片
scipy.optimize.minimize 的优化算法(1): Nelder–Mead Simplex_第2张图片
scipy.optimize.minimize 的优化算法(1): Nelder–Mead Simplex_第3张图片

解释

对于我们要求解最小值的函数 f(x) , 我们有 n+1 个点以及对应的函数值使其满足 f(x1)<f(x2)<...<f(xn)<f(xn+1) . 每一次我们都对值最大的点进行优化,具体的算法如上图所示。

x¯=Σnixi 是前n个点的质心(centroid),找到对于质心 xn+1 的反射点(reflection point): x¯(t)=x¯+t(xn+1x¯)

如下图所示, x¯(1) 就是反射点, x¯(2) 就是对应的两倍路径的反射点,而 x¯(1/2) xn+1 x¯ 的中点。

再来看算法的具体步骤,每一次我们都找到最差点 xn+1 ,然后求出 x¯(1) (对于蓝字注释的变换,请见源代码一节前的图示)

  1. 如果 x(1) 的值 f1 f(x1) f(xn) 之间,也就是说新找到的点不是最好的也不是最坏的,那么将其替换最差点,进行下一次循环。(reflection)

  2. 如果 x(1) 的值 f1<f(x1) ,那么我们找到了一个新的最优点,也就是说这个方向可能是找到最小值的最佳方向。想象一个山丘中出现的峡谷,这个方向就好比通往峡谷的下坡,所以我们沿这个方向再看一看有没有更好的解,继续计算 f2 (reflection and expansion)
    2.1 如果 f2<f1 ,那么将 x2 替换 xn+1 进行下一次循环。
    2.2 如果 f2>f1 ,那么将 x1 替换 xn+1 进行下一次循环。

  3. 如果 x(1) 的值 f1>f(xn) ,即新的点仍然比 x1 xn 都差
    3.1 找到 x(1/2) ,如果 f1/2<f1 ,那么用 x(1/2) 替代 xn+1 进行下一次循环。(contraction)
    3.2 如果 x(1) 的值 f1>f(xn+1) ,即新的点比现有所有点都差,这大概率说明我们的simplex可能已经到了最低点的周围,这时候应该向simplex内部找最优解。找到内部点 x(1/2) ,如果 f1/2<fn+1 ,那么用 x(1/2) 替代 xn+1 。进行下一次循环。

  4. 如果按照以上方法找到的内外部的点全部没有更优化,那么将整体simplex向 x(1) 收缩,因为 x(1) 的函数值是目前我们知道最小的。(multiple contraction)

scipy.optimize.minimize 的优化算法(1): Nelder–Mead Simplex_第4张图片

scipy.optimize.minimize 的优化算法(1): Nelder–Mead Simplex_第5张图片

源代码(scipy)

optimize.py 中的_minimize_neldermead() 是 Nelder–Mead Simplex 算法的核心程序,迭代算法集中体现在以下的这个循环中。

    while (fcalls[0] < maxfun and iterations < maxiter):
        if (numpy.max(numpy.ravel(numpy.abs(sim[1:] - sim[0]))) <= xatol and
                numpy.max(numpy.abs(fsim[0] - fsim[1:])) <= fatol):
            break

        xbar = numpy.add.reduce(sim[:-1], 0) / N #得到centroid
        xr = (1 + rho) * xbar - rho * sim[-1] #rho=1 得到新的点xr
        fxr = func(xr) // 对应的fxr函数值
        doshrink = 0

        if fxr < fsim[0]:
            xe = (1 + rho * chi) * xbar - rho * chi * sim[-1] # chi=2
            fxe = func(xe) # xe 就是x(-2)

            if fxe < fxr: # 算法步骤2.1
                sim[-1] = xe
                fsim[-1] = fxe
            else:  # 算法步骤2.2
                sim[-1] = xr
                fsim[-1] = fxr
        else:  # fsim[0] <= fxr
            if fxr < fsim[-2]: # 算法步骤1
                sim[-1] = xr
                fsim[-1] = fxr
            else:  # fxr >= fsim[-2]
                # Perform contraction # 算法步骤3
                if fxr < fsim[-1]:
                    xc = (1 + psi * rho) * xbar - psi * rho * sim[-1] # psi = 0.5 算法步骤3.1
                    fxc = func(xc)

                    if fxc <= fxr:
                        sim[-1] = xc
                        fsim[-1] = fxc
                    else:
                        doshrink = 1
                else:
                    # Perform an inside contraction
                    xcc = (1 - psi) * xbar + psi * sim[-1]
                    fxcc = func(xcc) # 算法步骤3.2

                    if fxcc < fsim[-1]:
                        sim[-1] = xcc
                        fsim[-1] = fxcc
                    else:
                        doshrink = 1

                if doshrink: #算法步骤4 整体收缩
                    for j in one2np1:
                        sim[j] = sim[0] + sigma * (sim[j] - sim[0])
                        fsim[j] = func(sim[j])

        ind = numpy.argsort(fsim)
        sim = numpy.take(sim, ind, 0)
        fsim = numpy.take(fsim, ind, 0)
        if callback is not None:
            callback(sim[0])
        iterations += 1
        if retall:
            allvecs.append(sim[0])

你可能感兴趣的:(Machine,Learning)