27-调试梯度下降法

说在前面

  前面我们已经系统的学习了什么叫做梯度下降法,那么对于梯度下降法的使用,一个非常重要的步骤,就是我们要求出我们定义的那个损失函数在某一个 θ θ θ 上,对应的那个梯度是什么?我们之前也花了很多时间来进行公式的推导。这可能对有些小伙伴来说很容易,可能对有些小伙伴来说有很困难。尤其是当我们推导出公式之后还要对其进行向量化。更不用讲,我们在这里只是对线性回归法进行相应的梯度的求解,如果以后我们遇到更加复杂的函数的时候,很有可能求解梯度并不容易。在这种情况下,我们推导出来公式以后,我们对它实现,然后真正的运行我们的程序,也许并不会报错,但是我们的梯度的求解结果却是错误的。在这种情况下,我们怎样才能发现我们的错误呢?

  在这篇博客中就向大家介绍一种最简单的方法,能够调试我们的梯度下降法。


具体方法

  这个方法是这样的,对于一个曲线,我们要求其在某一点相应的梯度值。我们先使用一维为例,也就是我们要求出 A A A 点导数值:
27-调试梯度下降法_第1张图片

  那么这个导数对应的其实就是这个曲线在该点的切线的斜率是什么?那么,我们可以使用一种方式来模拟这根直线的斜率。我们使用的方式是这样的,我们在该点的正方向上取一个点 B B B,在负方向上取一点 C C C,那么就说这两个点之间连线的斜率和我们在 A A A 点处切线的斜率大体上是相等的。而且取的间距越小,它们越相等。
27-调试梯度下降法_第2张图片
  实际上,如果学过高数的小伙伴可以回忆一下,我使用的这种方法整体上近乎就是在曲线上某一点的导数的定义,只不过在高数中严格的推导对于这个定义而言,我要让 A A A 点距离 B B B 点和 C C C 点的距离趋近于 0,也就是求一个极限。但是我们在计算机具体实现的时候,完全可以取一个特别小的值来真正把这两个点连线的斜率真正算出来作为 A A A 点的导数的一个取代值。那么这个计算方法也特别的容易,其实就是 B B B 点和 C C C 点在纵方向上的差除以在横方向上的差,相应的我们就可以写出这样的式子( A A A 点记作 θ θ θ):
在这里插入图片描述
  这样的一个模拟同样适用于高维的场景,如果我们的 θ θ θ 是一个向量:
在这里插入图片描述
  相应的,我们要想求出损失函数 J J J θ θ θ 对应的梯度的话,那我们的求法就是分别对 J J J 这个函数和 θ θ θ 中每一分量去求导。
27-调试梯度下降法_第3张图片

  我们以对 θ 0 θ_0 θ0 为例,我们就可以设置这样一个 θ 0 + θ_0^+ θ0+
在这里插入图片描述
  它就等于原来的 θ 0 θ_0 θ0 ,只不过在 θ 0 θ_0 θ0 这个维度上加上了一个 ε ε ε ,同时我们设置一个 θ 0 − θ_0^- θ0,只不过在 θ 0 θ_0 θ0 这个维度上减了一个 ε ε ε
27-调试梯度下降法_第4张图片
  那么此时对 θ 0 θ_0 θ0 求导的结果就可以写成这样:
27-调试梯度下降法_第5张图片
  同理,我们想求 J J J θ 1 θ_1 θ1 的导数的话:
27-调试梯度下降法_第6张图片
  以此类推,我们对每一个维度都可以使用这样的方式来求出它对应的导数。那么将它们合起来,就是 J J J θ θ θ 上的导数。很显然,这样的求法比我们之前做的推导公式的求法从数学的意义解释上是简单很多的。不过也可以看出来,这样做时间复杂度是非常高的,因为我们每求一个维度对应的这个导数我们都需要相应的求两遍把某一个 θ θ θ 值带进 J J J 进行求解,同时还得除以 2 倍的 ε ε ε,如果我们的 J J J 的复杂度非常高的话,那么我们每求一次梯度都将消耗相当多的时间,也正是因为如此,这个方法是作为调试的一种手段。也就是说,我们还没有在完成我们算法的时候,可以使用小数据量使用这种方法得到最终的结果,那么我们肯定知道这个结果是对的,然后我们再通过推到公式的方式来看我们最终所求的那个梯度和我们使用这种方法求出来的梯度值是否吻合。


具体实现

下面我们具体编程实现一下。

27-调试梯度下降法_第7张图片
27-调试梯度下降法_第8张图片
27-调试梯度下降法_第9张图片
27-调试梯度下降法_第10张图片


总结

这个例子其实是想告诉我们两件事情。

  • dJ_debug 这种求解方式是可以的,它最终能够得到正确的结果
  • 当我们使用 dJ_debug 的时候,我们最终训练的速度会慢很多。

  所以,如果这个机器学习的算法涉及到梯度的求法的时候,我们完全可以先使用 dJ_debug 这个函数作为梯度的求法,通过这个方式我们先得到我们想要做的这个机器学习算法的一个正确的结果,然后我们再来推导公式,求出这个梯度计算这个相应的数学解之后我们将我们实现的数学解带入到我们的机器学习算法中,可以通过最终得到的结果和我们使用 dJ_debug 得到的结果来验证我们推导的那个数学解是否是一个正确的推导。

  其实回过头看我们的 dJ_debug 函数的代码:

def dJ_debug(theta, X_b, y, epsilon=0.01): # 使用调试的方法求导数
    res = np.empty(len(theta))
    for i in range(len(theta)):
        theta_1 = theta.copy()
        theta_1[i] += epsilon
        theta_2 = theta.copy()
        theta_2[i] -= epsilon
        res[i] = (J(theta_1, X_b, y) - J(theta_2, X_b, y)) / (2 * epsilon)
    return res

  我们发现 dJ_debug 函数是和我们 J 函数什么样子无关的,它适用于所有的函数,这个函数完全是可以复用的,不像 dJ_math 函数:

def dJ_math(theta, X_b, y): # 使用数学公式计算导数
    return X_b.T.dot(X_b.dot(theta) - y) * 2 / len(y)

  只适用于我们当前任务中的 J J J 函数,因为 dJ_math 基于我们当前函数 J J J 进行推导才可以得到的。


  这篇博客具体介绍了梯度下降法中梯度求法的一个调试方式,在下一篇博客中将会对之前学习的所有的梯度下降法进行一个总结~~

  具体代码见 27 如何调试梯度.ipynb

你可能感兴趣的:(机器学习)