美文网首页机器学习与数据挖掘
12 梯度下降番外:非常有用的调试方式及总结

12 梯度下降番外:非常有用的调试方式及总结

作者: Japson | 来源:发表于2020-01-19 16:03 被阅读0次

    0 前言

    梯度下降法的使用,一个非常重要的步骤是:我们要求出定义的损失函数某一点\theta上对应的梯度是什么。在复杂函数的情况下,求导得到梯度并不容易。如果我们梯度的计算错误了,在后续的程序中也不会报错。那么我们如何去发现这个错误呢?

    介绍一种简单的方法,能够对梯度下降法中求梯度的公式推导进行调试。

    1 调试原理

    以一维为例,求某一点(红色)相应的梯度值(导数),就是曲线在这个点上切线的斜率。我们可以使用距离该点左右两侧\theta+\varepsilon, \theta-\varepsilon的两个蓝色点的连线的斜率,作为红点处切线斜率。

    15754467107669.jpg

    这样就可以将近似地得到点\theta的梯度为,两个蓝点纵向坐标差除以横向坐标差:
    \frac {dJ} {d\theta} = \frac {J(\theta+\varepsilon)-J(\theta-\varepsilon)} {2\varepsilon}

    推广到多维函数中,对于点\theta = (\theta_0,\theta_1,...,\theta_n),则对损失函数进行求导,为:\frac {\partial J} {\partial\theta} = (\frac {\partial J} {\partial\theta_0}, \frac {\partial J} {\partial\theta_1},...,\frac {\partial J} {\partial\theta_n})

    在计算时,要分别计算每个维度上的点,以维度\theta_0为例,得到在该维度上距离该店非常距离非常近的左右两点:\theta_0^+=(\theta_0+\varepsilon,\theta_1,...,\theta_n)\theta_0^-=(\theta_0-\varepsilon,\theta_1,...,\theta_n)。这样可以得到:\frac {\partial J} {\partial\theta} = \frac {J(\theta_0^+)-J(\theta_0^-)} {2\varepsilon}

    对于每一个维度,都按照上述的方法计算梯度,最后再组合起来,得到最终的梯度。但是这样的求法,从数学上来看比较直观。但是因为每个维度上都要求两次带入,因此时间复杂度变高了。

    这种方法作为一个调试的手段,在还未完成时,可以使用小数据量,进行计算,得到最终结果。然后再通过推导公式的方式得到的梯度结果,是否和其相同。

    2 使用展示

    下面我们在一组数据上,分别使用数学公式法和调试法来计算梯度,主要观察其结果与所消耗的时间。

    # 首先定义损失函数
    def J(theta, X_b, y):
        try:
            return np.sum((y - X_b.dot(theta))**2) / len(X_b)
        except:
            return float('inf')
    
    # 使用数学公式推导的方式,求损失函数J在参数theta上的梯度
    def dJ_math(theta, X_b, y):
        return X_b.T.dot(X_b.dot(theta) - y) * 2. / len(y)
    
    # 使用上文提到的梯度调试的方法
    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
    
    # 梯度下降的过程
    def gradient_descent(dJ, X_b, y, initial_theta, eta, n_iters = 1e4, epsilon=1e-8):
        theta = initial_theta
        cur_iter = 0
        while cur_iter < n_iters:
            gradient = dJ(theta, X_b, y)
            last_theta = theta
            theta = theta - eta * gradient
            if(abs(J(theta, X_b, y) - J(last_theta, X_b, y)) < epsilon):
                break          
            cur_iter += 1
        return theta
    

    然后我们先调用dJ_debug函数,看看正确的梯度结果是什么。

    X_b = np.hstack([np.ones((len(X), 1)), X])
    initial_theta = np.zeros(X_b.shape[1])
    eta = 0.01
    %time theta = gradient_descent(dJ_debug, X_b, y, initial_theta, eta)
    theta
    
    15755568341916.jpg

    然后我们再调用dJ_math,来检验我们的求导公式对不对:

    %time theta = gradient_descent(dJ_math, X_b, y, initial_theta, eta)
    theta
    
    15755568929237.jpg

    最终发现,我们求得的梯度公式得到的答案是正确的。

    但是观察到,使用debug的方式要比用求导公式的执行时间慢很多,如果在真实数据集上,可能会差的更多了。因此我们在求梯度的时候,可以用这种通用的debug方式先在小数据集上对求导公式进行检验。

    3 对梯度下降法的总结

    在之前的系列文章中,我们介绍了两种梯度下降法:

    • 批量梯度下降法 Batch Gradient Descent
    • 随机梯度下降法 Stochastic Gradient Descent

    批量梯度下降法每次对所有样本都看一遍,缺点是慢,缺点是稳定。随机梯度下降法每次随机看一个,优点是快,缺点是不稳定。

    其实还有一种中和二者优缺点的方法小批量梯度下降法 MBGD(Mini-Batch Gradient Descent):在每次更新时用b个样本,其实批量的梯度下降就是一种折中的方法,用一些小样本来近似全部。优点:减少了计算的开销量,降低了随机性。

    在机器学习领域,“随机”具有非常大的意义,因为计算速度很快。对于复杂的损失函数来说,随机可以跳出局部最优解,并且有更快的速度。

    公众号二维码.jpeg

    相关文章

      网友评论

        本文标题:12 梯度下降番外:非常有用的调试方式及总结

        本文链接:https://www.haomeiwen.com/subject/wyirzctx.html