多变量标量函数的梯度下降优化

时间:2017-03-14 15:36:30

标签: python numpy optimization scipy gradient-descent

我试图在rosenbrock函数上测试我的梯度下降程序。但无论我如何调整我的学习率(step参数),精度(precision参数)和迭代次数(iteration参数),我都无法得到非常接近的结果。

import numpy as np

def minimize(f, f_grad, x, step=1e-3, iterations=1e3, precision=1e-3):
    count = 0
    while True:
        last_x = x
        x = x - step * f_grad(x)
        count += 1
        if count > iterations or np.linalg.norm(x - last_x) < precision:
            break
    return x

def rosenbrock(x):
    """The Rosenbrock function"""
    return sum(100.0*(x[1:]-x[:-1]**2.0)**2.0 + (1-x[:-1])**2.0)

def rosenbrock_grad(x):
    """Gradient of Rosenbrock function"""
    xm = x[1:-1]
    xm_m1 = x[:-2]
    xm_p1 = x[2:]
    der = np.zeros_like(x)
    der[1:-1] = 200*(xm-xm_m1**2) - 400*(xm_p1 - xm**2)*xm - 2*(1-xm)
    der[0] = -400*x[0]*(x[1]-x[0]**2) - 2*(1-x[0])
    der[-1] = 200*(x[-1]-x[-2]**2)
    return der

x0 = np.array([1.3, 0.7, 0.8, 1.9, 1.2])
minimize(rosenbrock, rosenbrock_grad, x0, step=1e-6, iterations=1e4, precision=1e-6)

例如,上面的代码给了我array([ 1.01723267, 1.03694999, 1.07870143, 1.16693184, 1.36404334])。但如果我在scipy.optimize中使用任何内置的优化方法,我可以得到非常接近的答案或完全相等的array([ 1., 1., 1., 1., 1.])(这是真正的答案)。

但是,如果我在程序中使用非常小的stepprecision和非常大的iterations,那么计算就会永远存在于我的计算机上。

我想知道这是否是由于

  

我程序中的任何错误

或仅仅因为

  

梯度下降在这里效率低,要求非常小   stepprecision和非常大的iterations非常接近   溶液

或因为

  

我需要做一些特殊功能扩展。

(Ps。我也试图绘制二维图,其中函数的值在y轴上,迭代次数在x轴上,以“调试”梯度下降,但即使我看起来很漂亮下行图,解决方案仍然不是很接近。)

3 个答案:

答案 0 :(得分:2)

您的方法很容易出现过冲。在瞬间高梯度的情况下,您的解决方案将跳得很远。在优化时,通常不适合在不能降低成本的情况下采取措施。

搜索下的

通过计算渐变选择方向后,沿方向搜索,直到您将成本降低一定的渐变范数。

即。从$ x _ {[n + 1]} = x - \ alpha * gradient $

开始

并将$ \ alpha $从1.0变为0.0,如果将成本降低了梯度的一小部分,则接受x的值。这是一个很好的收敛规则,称为Armijo规则。

其他建议

首先考虑优化2D Rosenbrock函数,并在该成本字段上绘制路径。

考虑用数字验证渐变实现是否正确。通常情况下,这就是问题所在。

答案 1 :(得分:2)

引用Rosenbrock Wikipedia page

  

全球最小值位于一个狭长的抛物线状平坦山谷内。找到山谷是微不足道的。然而,收敛到全球最小值是困难的。

梯度下降是一种简单的算法,因此它找不到最小值也就不足为奇了。让我们看看2D中出现的不同起点:

enter image description here

正如维基百科所说:它很容易找到山谷但却无法进一步收敛。与其余功能相比,沿山谷的坡度非常平坦。

我会得出结论,你的实现工作正常,但Rosenbrock函数可能不是测试它的最合适的函数。

与其他答案相反,我进一步认为步长太小而不是太大。问题不是超调,而是算法卡住了。如果我将步长设置为1e-3而不更改其他设置,则算法会收敛到两位数内的最大值。尽管在2D情况下从一些起始位置超过山谷,但仍然会发生这种情况 - 但是你需要速度不要在以后卡住,所以说。

以下是重现上图的修改后的代码:

import numpy as np
import matplotlib.pyplot as plt

def minimize(f, f_grad, x, step=1e-3, iterations=1e3, precision=1e-3):
    count = 0
    while True:
        last_x = x
        x_hist.append(x)
        x = x - step * f_grad(x)
        count += 1
        if count > iterations or np.linalg.norm(x - last_x) < precision:
            x_hist.append(x)
            break
    return x

def rosenbrock(x):
    """The Rosenbrock function"""
    return sum(100.0*(x[1:]-x[:-1]**2.0)**2.0 + (1-x[:-1])**2.0)

def rosenbrock_grad(x):
    """Gradient of Rosenbrock function"""
    xm = x[1:-1]
    xm_m1 = x[:-2]
    xm_p1 = x[2:]
    der = np.zeros_like(x)
    der[1:-1] = 200*(xm-xm_m1**2) - 400*(xm_p1 - xm**2)*xm - 2*(1-xm)
    der[0] = -400*x[0]*(x[1]-x[0]**2) - 2*(1-x[0])
    der[-1] = 200*(x[-1]-x[-2]**2)
    return der


k = np.linspace(0, 2, 101)
f = np.empty((k.shape[0], k.shape[0]))
for i, y in enumerate(k):
    for j, x in enumerate(k):
        f[i, j] = rosenbrock(np.array([x, y]))
plt.imshow(np.log10(f), extent=[k[0], k[-1], k[-1], k[0]], cmap='autumn')

for start in [[0.5, 0.5], [1.0, 0.5], [1.5, 0.5],
              [0.5, 1.0], [1.0, 1.0], [1.5, 1.0],
              [0.5, 1.5], [1.0, 1.5], [1.5, 1.5]]:

    x0 = np.array(start)

    x_hist = []

    minimize(rosenbrock, rosenbrock_grad, x0, step=1e-6, iterations=1e4, precision=1e-9)


    x_hist = np.array(x_hist)
    plt.plot(x_hist[:, 0], x_hist[:, 1], 'k')
    plt.plot(x0[0], x0[1], 'ok')

答案 2 :(得分:0)

想象一下,你正在徒步旅行 knife-edge 越来越窄的山路。 一个常数步长将带你越过边缘,aieeeee; 当你攀爬时,你想要采取更短,更小心的步骤。 同样,要遵循Rosenbrock山谷,一个项目必须 当山谷变窄时,采取更短,更小心的步骤。 将步长减小为step0 / t ^ 0.5或0.25 帮助GD在Rosenbrock上, 但仍然非常对step0敏感。

实际步长(又称学习率)必须适应到问题地形,例如 行搜索顺利问题,Ada * for SGD

顺便说一句,Rosenbrock函数是一个平方和, 并且有强有力的方法;看到 scipy.optimize.least_squares