机器学习算法笔记(八):梯度下降法初探

梯度下降法(Gradient Descent)不是一个机器学习算法,是一种基于搜索的最优化方法。它的作用是最小化一个损失函数(与之对应的,梯度上升法的作用是最大化一个效用函数)。机器学习中,熟练的使用梯度法(下降法、上升法)求取目标函数的最优解,是非常重要的思想。

一、什么是梯度下降法

简而言之,梯度下降法就是寻找一个函数最小值的方法。我们在损失函数中取一个合适的点作为起始点,每次改变一点参数θ,目标函数 J 也会跟着改变。我们不断的递进改变参数值,得到目标函数的极值;经过多次运行,每次随机选取起始点,得出不同的局部最优解(极值),比较所有最优解,最小的合格值就是目标函数的最小值。

● 其中 θ是模型中的参数,而不是模型中的变量。为了便于理解,可以以线性回归为例:线性回归的目标是使损失函数尽可能小,也就是求一个参数 θ,使得上面的损失函数尽可能小。

● 梯度下降法的初始点是一个梯度下降法的超参数,起始点对于一个梯度下降算法是非常重要的。

之所以被称为“梯度下降法”,是因为在多元函数中,对各个方向的分量分别求导,最终得到的方向就是梯度(参见同济版《高等数学 · 下》的“方向导数与梯度”部分)。梯度代表函数变化的方向,对应就 J 增大/减小的方向。特别地,在一元函数中,梯度就是我们所熟悉的“导数”。

梯度下降法图示

以一元函数为例,我们通常将 θ 每次的改变量设为。其中, dJ / dθ 表示 J 对 θ 的导数,而 η 代表学习率(Learning rate)。η 是梯度下降法的一个超参数,它的取值影响获得最优解的速度,取值不合适,甚至得不到最优解。η 若太小,会减慢收敛学习速度;η 太大,会导致不收敛或者 J 的变化有减有增。我们通常需要以调参的方式确定最适合的 η 。

“收敛”是指通过梯度下降得到极值的过程

学习率 η 过小与过大的后果

二、梯度下降法的具体实现

下面我们以一个简单的一元函数 y = (x – 2.5)– 1 为例,通过梯度下降法来求这个函数的极小值。新建一个PyCharm工程,创建一个main.py文件,输入以下代码:

import numpy as np
import matplotlib.pyplot as plt

#定义函数J
def J(theta):
    try:
        return (theta-2.5)**2 - 1.
    except:
        return float('inf')

#定义J的导函数dJ
def dJ(theta):
    return 2*(theta-2.5)

#定义一系列满足函数J的点
plot_x = np.linspace(-1., 6., 141)
plot_y = (plot_x-2.5)**2 - 1.

theta_history = [] #记录梯度下降的历史数据

#实现梯度下降法
def gradient_descent(initial_theta, eta, n_iters=1e4, epsilon=1e-8):
    """
    :param initial_theta: 梯度下降的起始点
    :param eta: η的值
    :param n_iters: 梯度下降循环的最大次数,避免死循环
    :param epsilon: 定义的一个无穷小量
    """
    theta = initial_theta #记录先前的θ以便记录在theta_history中
    i_iter = 0
    theta_history.append(initial_theta)

    while i_iter < n_iters: #避免梯度下降发散后陷入死循环,加入一个循环上限
        gradient = dJ(theta) #求当前的梯度(导数)
        last_theta = theta
        theta = theta - eta * gradient #根据公式给出的θ改变量改变θ
        theta_history.append(theta)

        #当J(theta)与J(last_theta)的值相差一个无穷小量时,我们就认为它就取到了极小值,退出循环
        if (abs(J(theta) - J(last_theta)) < epsilon):
            break

        i_iter += 1

    return

#画出梯度下降的步骤图像
def plot_theta_history():
    plt.plot(plot_x, J(plot_x))
    plt.plot(np.array(theta_history), J(np.array(theta_history)), color="r", marker='+')
    plt.show()

eta = [0.01, 0.1, 0.8]

for i in eta:
    theta_history = []
    gradient_descent(0, i)
    plot_theta_history()

运行代码,绘制的图像如下:

从左到右分别是η取0.01、0.1、0.8的情形,可以看出随着η变大,点集逐渐稀疏。

若我们取一个会使得梯度下降发散的 η(例如1.1),我们的编译器会直接报出RuntimeWarning: overflow encountered in multiply警告,因为若结果不收敛,J (θ)计算的值会成几何形式暴增,进而迅速达到float的最大值“inf”,也就永远取不到我们想要的“最小值”了。尽管我们在定义函数 J 时考虑了这一点,但我们还是无法绘制出图像,因为matplotlib的库会检测到异常,并中断我们的程序。

结果不收敛时matplotlib的异常信息

我们可以将循环次数设为10,看一下当结果不收敛时,J (θ)的值到底是什么情况:

图像呈现这个样子,显然我们的 η 太大了。

当然,η 也不是越小越好,η 过小也拖慢了算法的执行效率。前面也提到,η 是梯度下降的一个超参数,我们需要通过调参来确定最佳的η值。

我们在实践后面的机器学习算法理论的时候,可以默认取η为0.01。这相对是一个保险的数值,对于大部分函数都是足以胜任的。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注