目标是求多元函数\(f(x)\)的极小值。梯度下降法是通过不断迭代得到函数极小值,即如能保证\(f(x +\Delta x)\)比\(f(x)\)小,则不断迭代,最终能得到极小值。想象你在山顶往山脚走,如果每一步到的位置比之前的位置低,就能走到山脚。问题是像哪个方向走,能最快到山脚呢? 由泰勒展开式得: \[f(x + \Delta x) - f(x) = (\nabla f(x))^T \Delta x + o(\Delta x) \] 如果\(\Delta x\)足够小,可以忽略\(o(\Delta x)\),则有: \[f(x + \Delta x) - f(x) \approx (\nabla f(x))^T \Delta x\] 于是只有: \[(\nabla f(x))^T \Delta x < 0 \] 能使 \[ f(x + \Delta x) < f(x) \] 因为\(\nabla f(x)\)与\(\Delta x\)均为向量,于是有: \[ (\nabla f(x))^T \Delta x = \| \nabla f(x)\|\|\Delta x\|cos\theta\] 其中,\(\theta\)是向量\(\nabla f(x)\)与\(\Delta x\)的夹角,\(\| \nabla f(x)\|\)与\(\|\Delta x\|\)是向量对应的模。可见只有当 \[cos\theta < 0\] 才能使得 \[ (\nabla f(x))^T \Delta x < 0 \] 又因 \[ cos\theta \ge -1 \] 可见,只有当 \[cos\theta = -1\] 即\(\theta = \pi\)时,函数数值降低最快。此时梯度和\(\Delta x\)反向,即夹角为180度。因此当向量\(\Delta x\)的模大小一定时,取 \[\Delta x = -\alpha \nabla f(x)\] 即在梯度相反的方向函数值下降的最快。此时函数的下降值为: \[ (\nabla f(x))^T \Delta x = -\| \nabla f(x)\|\|\Delta x\| = - \alpha \| \nabla f(x)\|^2 \] 只要梯度不为\(0\),往梯度的反方向走函数值一定是下降的。直接用可能会有问题,因为\(x+\Delta x\)可能会超出\(x\)的邻域范围之外,此时是不能忽略泰勒展开中的二次及以上的项的,因此步伐不能太大。 一般设: \[\Delta x = -\alpha \nabla f(x)\] 其中\(\alpha\)为一个接近于\(0\)的正数,称为步长,由人工设定,用于保证\(x+\Delta x\)在x的邻域内,从而可以忽略泰勒展开中二次及更高的项,则有: \[ (\nabla f(x))^T \Delta x = -\| \nabla f(x)\|\|\Delta x\| = - \alpha \| \nabla f(x)\|^2 < 0 \] 此时,\(x\)的迭代公式是: \[x_{k+1} = x_k - \alpha \nabla f(x_k)\] 只要没有到达梯度为\(0\)的点,则函数值会沿着序列\(x_{k}\)递减,最终会收敛到梯度为\(0\)的点,这就是梯度下降法。 迭代终止的条件是函数的梯度值为\(0\)(实际实现时是接近于\(0\)),此时认为已经达到极值点。注意我们找到的是梯度为\(0\)的点,这不一定就是极值点,后面会说明。
\(argmin\frac{1}{2}[(x_{1}+x_{2}-4)^2 + (2x_{1}+3x_{2}-7)^2 + (4x_{1}+x_{2}-9)^2]\)
以下只是为了演示计算过程,便于理解梯度下降,代码仅供参考。更好的代码我将在以后的文章中给出。
# 原函数
def argminf(x1, x2):
r = ((x1+x2-4)**2 + (2*x1+3*x2 - 7)**2 + (4*x1+x2-9)**2)*0.5
return r
# 全量计算一阶偏导的值
def deriv_x(x1, x2):
r1 = (x1+x2-4) + (2*x1+3*x2-7)*2 + (4*x1+x2-9)*4
r2 = (x1+x2-4) + (2*x1+3*x2-7)*3 + (4*x1+x2-9)
return r1, r2
# 梯度下降算法
def gradient_decs(n):
alpha = 0.01 # 学习率
x1, x2 = 0, 0 # 初始值
y1 = argminf(x1, x2)
for i in range(n):
deriv1, deriv2 = deriv_x(x1, x2)
x1 = x1 - alpha * deriv1
x2 = x2 - alpha * deriv2
y2 = argminf(x1, x2)
if y1 - y2 < 1e-6:
return x1, x2, y2
if y2 < y1:
y1 = y2
return x1, x2, y2
# 迭代1000次结果
gradient_decs(1000)
# (1.9987027392533656, 1.092923742270406, 0.4545566995437954)