2.5 梯度下降#

在2.1.3节中,我们不假思索地直接给出了线性回归模型的目标函数$J(w,b)$,但并没有给出严格的数学定义。同时,在求解的过程中也是直接通过开源框架sklearn实现,也不知道其内部的真正原理,因此,在这一节内容中我们将会仔细地学习目标函数的求解过程及最小二乘法。

根据前面的介绍可以知道,梯度下降算法的目的是用来最小化目标函数,也就是说梯度下降算法是一个求解的工具。当目标函数取到(或接近)全局最小值时,我们也就求解得到了模型所对应的参数。不过那什么又是梯度下降(Gradient Descent)呢?如图2-9所示,假设有一个山谷,并且你此时处于位置A处,那么请问以什么样的方向(角度)往前跳,你才能最快地到达谷底B处呢?

图 2-9 跳跃方向

现在大致有3个方向可以选择,沿着$Y$轴的$\bf{V}_1$方向,沿着$X$轴的$\bf{V}_2$方向及沿着两者间的$\bf{l}$方向。其实不用问,大家都会选择$\bf{l}$所在的方向往前跳第一步,然后接着选类似的方向往前跳第二步直到谷底。可为什么都应该这样选呢?答: 这还用问一看就知,不信请读者自己试一试。

2.5.1 方向导数与梯度#

由一元函数导数的相关知识可知,$f(x)$在$x_0$处的导数反映的是$f(x)$在$x=x_0$处时的变化率;$|f^{\prime}(x_0)|$越大,也就意味着$f(x)$在该处的变化率越大,即移动$\Delta x$后产生的函数增量$\Delta y$越大。同理,在二元函数$z=f(x,y)$中,为了寻找$z$在A处的最大变化率,就应该计算函数$z$在该点的方向导数

$$ \frac{\partial f}{\partial \mathbf{l}}=\{\frac{\partial f}{\partial x},\frac{\partial f}{\partial y}\}\cdot \{cos\alpha ,cos\beta \}=|gradf|\cdot |\mathbf{l}|\cdot \cos \theta\tag{2-13} $$

其中,$\bf{l}$为单位向量; $\alpha$和$\beta$分别为$\bf{l}$与$x$轴和$y$轴的夹角; $\theta$为梯度方向与l的夹角。

根据式(2-13)可知,要想方向导数取得最大值,那么$\theta$必须为0。由此可知,只有当某点处方向导数的方向与梯度的方向一致时,方向导数在该点才会取得最大的变化率。

在图2-9中,已知$z=x^2+y^2+5$,A的坐标为$(-3,3,23)$,则,则$\partial z/\partial x=2x,\partial z/\partial y=2y$。由此可知,此时在点A处梯度的方向为$(-6,6)$,所以当你站在A点并沿各个方向往前跳跃同样大小的距离时,只有沿着$(\sqrt{2}/2,-\sqrt{2}/2)$这个方向(进行了单位化,并且同时取了相反方向,因为这里需要的是负增量)才会产生最大的函数增量$\Delta z$。

如图2-10所示,要想每次都能以最快的速度下降,则每次都必须向着梯度的反方向向前跳跃。

图 2-10 负梯度方向

2.5.2 梯度下降算法#

介绍这么多总算是把梯度的概念讲清楚了,那么如何用具体的数学表达式进行描述呢?总不能一个劲儿地喊它“跳”。为了方便后面的表述及将读者带入一个真实求解的过程中,这里先将图2-9中的字母替换成模型中的参数进行表述。

现在有一个模型的目标函数$J(w_1,w_2)=w_1^2+w_2^2+2w_2+5$(为了方便可视化,此处省略了参数$b$,但是原理都一样),其中$w_1$和$w_2$为待求解的权重参数,并且随机初始化点A为初始权重值。下面就一步步地通过梯度下降算法进行求解。

如图2-11所示,设初始点$A=(w_1,w_2)=(-2,3)$,则此时$J(-2,3)=24$,并且点$A$第一次往前跳的方向为 $-grad\;J=-(2{{w}_{1}},2{{w}_{2}}+2)=(1,-2)$ ,即$(1,-2)$这个方向。

图 2-11 梯度下降

如图2-12所示,$OQ$为平面上梯度的反方向,$AP$为其平移后的方向,但是长度为之前的$\alpha$倍,因此,根据梯度下降的原则,此时曲面上的$A$点就该沿着其梯度的反方向跳跃,而投影到平面则为$A$应该沿着$AP$的方向移动。假定曲面上从$A$点跳跃到了$P$点,那么对应在投影平面上就是图2-12中的$AP$部分,同时权重参数也从$A$的位置更新到了$P$点的位置。

图 2-12 梯度计算

从图2-12可以看出,向量$\mathbf{AP}$、$\mathbf{OA}$和$\mathbf{OP}$三者的关系为

$$ \mathbf{OP}=\mathbf{OA}-\mathbf{PA}\tag{2-14} $$

进一步,可以将式(2-14)改写成

$$ \mathbf{OP}=\mathbf{OA}-\alpha \cdot grad\ J\tag{2-15} $$

又由于$\mathbf{OP}$和$\mathbf{OA}$本质上就是权重参数$w_1$和$w_2$更新后与更新前的值,所以便可以得出梯度下降的更新公式为

$$ W=W-\alpha \cdot \frac{\partial J}{\partial W}\tag{2-16} $$

其中,$W=(w_1,w_2)$,$\partial J/\partial W$为权重的梯度方向; $\alpha$为步长,用来放缩每次向前跳跃的距离。同时,将式(2-16)代入具体数值后可以得出,曲面上的点A在第一次跳跃后的着落点为

$$ \begin{aligned} & {{w}_{1}}={{w}_{1}}-0.1\times 2\times {{w}_{1}}=-2-0.1\times 2\times (-2)=-1.6 \\ & {{w}_{2}}={{w}_{2}}-0.1\times (2\times {{w}_{2}}+2)=3-0.1\times (2\times 3+2)=2.2 \\ \end{aligned} $$

此时,权重参数便从$(-2,3)$更新为$(-1.6,2.2)$。当然其目标函数$J(w_1,w_2)$也从24更新为16.8。至此,我们便详细地完成了1轮梯度下降的计算。当$A$跳跃到$P$之后,又可以再次利用梯度下降算法进行跳跃,直到跳到谷底(或附近)为止,如图2-13所示。

图 2-13 梯度下降

最后,根据上述原理,还可以通过实际的代码将整个过程展示出来,完整代码见 `AllBookCode/Chapter02/C08_gradient_descent_visualization.py 文件,代码如下:

 1 def gradient_descent():
 2     w1, w2 = -2, 3
 3     jump_points = [[w1, w2]]
 4     costs,step = [cost_function(w1, w2)],0.1
 5     print("P:({},{})".format(w1, w2), end=' ')
 6     for i in range(20):
 7         gradients = compute_gradient(w1, w2)
 8         w1 = w1 - step * gradients[0]
 9         w2 = w2 - step * gradients[1]
10         jump_points.append([w1, w2])
11         costs.append(cost_function(w1, w2))
12         print("P{}:({},{})".format(i + 1, round(w1, 3), round(w2, 3)), end=' ')
13     return jump_points, costs

通过上述Python代码便可以详细展示跳向谷底时每一次的落脚点,并且可以看到谷底的位置就在$(-0.023,-0.954)$附近,如图2-14所示。至此,我们就介绍完了如何通过编码实现梯度下降算法的求解过程,等后续完成线性回归模型的推导后,我们再来自己编码完成线性回归模型的参数求解过程。

图 2-14 梯度下降可视化

2.5.3 小结#

在本节中,我们通过一个跳跃的例子详细地向大家介绍了什么是梯度,以及为什么要沿着梯度的反方向进行跳跃,然后通过图示导出了梯度下降的更新公式

$$ W=W-\alpha \cdot \frac{\partial J}{\partial W}\tag{2-17} $$

在这里我们又写了一遍梯度下降的更新公式是希望读者一定要记住这个公式,以及它的由来。因为它同时也是目前求解神经网络参数的主要工具。同时,可以看出,通过梯度下降算法来求解模型参数需要完成的一个核心任务就是计算参数的梯度。最后,虽然公式介绍完了,但公式中的步长$\alpha$也是一个十分重要的参数,这将在第4章中进行介绍。