跳至主要内容

机器学习中的梯度下降:深度解析

了解梯度下降如何为机器学习模型做优化。探索其在线性回归、逻辑回归、神经网络中的应用,以及批量、随机与小批量等关键类型。
· 15分钟 阅读

使用 AI 探索

梯度下降是整个机器学习和深度学习中最重要的算法之一。它是一种极其强大的优化算法,能训练线性回归、逻辑回归以及神经网络模型。如果您刚入门机器学习,那么深入理解梯度下降算法至关重要。

什么是梯度下降?

数据科学旨在通过大数据分析发现复杂的模式与行为,即所谓的“模式”,也就是反复出现的结构。借助机器学习,我们训练算法在数据分析中识别这些模式,以更好地完成特定任务。这意味着让软件能够自主执行任务或做出预测。为此,数据科学家需要选择并训练算法来执行数据分析,目标当然是让模型的预测能力随时间不断提升。

因此,机器学习在很大程度上依赖于对算法的训练。算法接触的数据越多,就越能在没有明确指令的情况下完成任务;它们通过经验来学习。机器学习中会用到不同类型的算法,其中,梯度下降是最有用、最流行的算法之一。

梯度下降是一种优化算法,用于更快速地找到某个函数的最小值。其定义相当简单:它是寻找凸函数最小值的算法。为此,它会对目标函数的参数进行迭代式的调整。例如,线性回归中就会使用该算法。

凸函数看起来像一个中间有全局最小值的漂亮山谷。相反,非凸函数会有多个局部最小值,梯度下降用于这类函数时有可能在遇到的第一个局部最小值处陷入停滞。

凸函数上梯度下降的示意图

梯度下降也被称为“最陡下降法”。它在机器学习中非常重要,被用来最小化代价函数,后者用于判定数据分析中最佳的预测模型。代价越小,模型就越能做出良好的预测。

常见的梯度下降有三种类型。我们来逐一了解:

批量梯度下降

批量梯度下降(也称 vanilla 梯度下降)会对训练数据集中每个样本的误差进行计算,但只有在彻底评估完所有训练样本之后才更新参数。可以把这一过程比作一个循环,有人也称之为一个训练纪元(epoch)。

批量下降有多项优点,尤其是其计算效率带来的稳定收敛与稳定误差梯度。然而,它也有缺点:稳定的误差梯度有时会导致不理想的收敛状态;此外,它需要在算法与内存中同时持有完整的训练数据集。

随机梯度下降

随机梯度下降会针对每个训练样本单独进行参数更新。这使得每个样本都能得到关注,尽量减少过程中的误差。视问题而定,这可以让 SGD 相较于批量梯度下降更快。其频繁的更新也能提供细粒度的改进速率。

但这些更新在计算上更昂贵,尤其与分步更新的方法相比。此外,更新频率高会带来噪声较大的梯度,可能阻碍误差率下降;误差率会跳动,长期来看会带来问题。

小批量梯度下降

研究者常将小批量梯度下降作为起步方法。原因在于它融合了随机下降与批量下降的优点。它将训练数据划分为多个批次,并对每个批次进行一次更新,从而在 BGD 的效率与 SGD 的鲁棒性之间取得平衡。

常见的小批量规模在五十到二百五十六之间,但与许多机器学习方法一样,并无固定规则,需视具体应用而定。它是训练神经网络的常用默认选项,也是深度学习领域中常见的一类“爬坡”方法。

为何梯度下降在机器学习中如此重要?

在机器学习中,我们在监督学习问题里使用梯度下降来最小化代价函数,该函数通常是凸函数(例如均方误差)。

借助该算法,模型通过寻找最佳的参数化形式来“学习”。请记住,最小化代价函数意味着找到参数 a、b、c 等,使得我们的模型与数据集中 y 点之间的误差最小。一旦代价函数被最小化,就为构建准确的语音识别、计算机视觉以及股票价格预测等应用铺平了道路。

这也解释了梯度下降为何是基础算法:模型正是依赖它来学习。

为更好地说明该算法的工作机制与实用性,我们常用“下山”类比。想象一个人在山里迷路了。要回到正途,首先要寻找最陡的下坡方向。沿着这个方向前进一段距离后,重复这个过程,直到到达山谷(最低点)。在机器学习中,梯度下降就是不断重复这一过程,直到找到代价函数的一个最小值。因此它被称为迭代算法,并且需要大量计算。

如果您在山中迷路,可以参考这两步策略:

  • 从当前位置环顾四周,寻找坡度下降最陡的方向。
  • 找到方向后,沿该方向前进一段距离(比如走 300 米),然后重复第 1 步。

反复循环第 1、2 步,您就能确保收敛到山谷的最低点。这一策略正是梯度下降的本质。

步骤 1:计算代价函数的导数

从一个随机初始点开始,测量该点的斜率。在数学上,我们通过计算函数的导数来度量斜率。

步骤 2:更新模型参数

随后沿着下降的斜率方向前进一定距离 d,这次不是 300 米。这个距离称为“学习率”。这一操作的结果是改变我们模型参数的取值(就像我们在山谷中的坐标随移动而改变)。

梯度下降应用于哪些领域?

梯度下降算法主要用于机器学习与深度学习。后者可视为机器学习的进阶版本,能捕捉更细微的模式。这些学科通常需要扎实的数学功底与一定的 Python 基础。

该编程语言拥有多个便于实施机器学习的库。机器学习对于快速、准确地分析海量数据非常有用,并能基于既往趋势或事件进行预测分析。

机器学习与大数据密切相关,能帮助突破人工在大规模数据流分析方面的局限。凭借在线可获取的海量数据,人工智能(AI)无需人工干预即可自主学习。机器学习被广泛应用于物联网领域。例如,AI 能根据智能家居中住户的习惯进行自适应,并据此执行任务。

例如,AI 可以根据天气调整房间的供暖。正是这门技术让扫地机器人越来越智能。通过机器学习中的梯度下降,人工智能取得了长足进步。在实践中,工程师与 AI 专家在诸多场景中使用梯度下降。

它对 Google 等搜索引擎、以及 YouTube、Netflix、Amazon 等推荐系统都很有用。基于用户数据,算法会尝试理解网民的兴趣,从而提供更相关的搜索结果与更优的推荐。

机器学习使计算机能够理解与处理人类语言。这一应用催生了 Alexa、Google Assistant 和 Siri 等数字助理。梯度下降及其在机器学习中的应用对电子游戏开发者也非常有帮助。其目标是让 AI 在更具人类特征的任务上表现出色,以便人们将精力集中在更高价值的活动上。人工智能与机器学习还能帮助企业预判客户需求与未来趋势。

如何实现梯度下降

由于计算复杂度的原因,梯度下降常用于线性回归。梯度下降的一般数学公式为 xt+1= xt- η∆xt,其中 η 表示学习率,∆xt 表示下降方向。梯度下降适用于凸函数。若以 ƒ 表示待最小化的凸函数,则目标是在每次迭代中满足 ƒ(xt+1) ≤ ƒ (xt)。

这里的思路是使用该算法逐步求出某个数学函数的最小值。面对某些方程时,这是最佳的求解方法。谈及梯度下降时,还需要理解代价函数的概念。在监督学习中,代价函数用于衡量估计值与真实值之间的误差幅度。在线性回归问题中,均方梯度的计算公式如下:

梯度下降的应用还涉及学习率这一概念。学习率是一个超参数,用于控制相对于损失梯度对网络权重的调整幅度。需要注意,合适的学习率对于更快、更高效地找到最小值至关重要,既不能过高,也不能过低。

随着取值下降,意味着我们正沿着下坡逐步前进。RMSprop、Adam、SGD 等多种优化方法都使用了梯度下降。为避免误用该算法,建议谨慎选择其参数。同时要牢记,得到的最小值未必是全局最小值。

梯度的主要作用是衡量每个权重的变化对误差变化的影响。可以把梯度理解为函数的斜率。梯度越大,斜率越陡,这对模型是有利的,因为模型能更快学习。然而,如果斜率为零,模型就会停止学习。从数学角度看,梯度可被描述为相对于输入的有限导数。

三维空间中的梯度下降可视化

在实现部分,我们将编写两个函数。其一是代价函数,输入真实输出与预测输出,返回损失;其二是真正的梯度下降函数,输入自变量与目标变量(因变量),并通过梯度下降算法找到最优拟合直线。

迭代次数、学习率与停止阈值是梯度下降算法的可调参数,可由用户设定。在主函数中,我们将初始化一组线性相关的随机数据,并对其应用梯度下降算法以找到最佳拟合直线。随后,将使用通过梯度下降得到的最优权重与偏置,在主函数中绘制最佳拟合直线。

#Importing libraries
import numpy as np
import matplotlib as pyplot

def mean_squared_error(y_true, y_predicted):

   # Calculating the loss or cost
   cost = np.sum((y_true-y_predicted)**2) / len(y_true)
   return cost

# Gradient descent function
# Here iterations, learning rate, stopping threshold are hyperparameters that can be tuned
def gradient_descent((x, y, iterations = 1000, learning_rate = 0.0001, stopping_theshold = 1e-6):

   #Initialzing rate, bias, learning rate, and iterations
   current_rate = 0.1
   current_bias = 0.01
   iterations = iterations
   learning_rate = learning_rate
   n = float(len(x))

   costs = []
   weights = []
   previous_cost = None

   # Estimation of optimal parameters
   for i in range(iterations):
      
      # Making predictions
      y_predicted = (current_weight * x) + current_bias

      # Calculating the current cost
      current_cost = mean_squared_error(y, y_predicted)

      # If the change in cost is less than or equal to stopping threshold we stop the gradient descent
      if previous_cost and abs(previous_cost - current_cost) <= stopping_threshold: 
         break

      previous_cost = current_cost

      costs.append(current_cost)
      weights.append(current_weight)

      # Calculating the gradients
      weight_derivative = -(2/n) * sum(x * (y-y_predicted))
      bias_derivative = -(2/n) * sum(y-y_predicted)

      # Updating weights and bias
      current_weight = current_weight - (learning_rate * weight_derivative)
      current_bias = current_bias - (learning_rate * bias_derivative)

      # Printing the parameters for each 1000th iteration
      print(f"Iteration {i+1}: Cost {current_cost}, Weight \n {current_weight}, Bias {current_bias}")

   # Visualizing the weights and cost for all iterations
   plt.figure(figsize = = (8,6))
   plt.plot(weights, cost)
   plt.scatter(weights, cost, marker = 'o', color = 'red')
   plt.title("Cost vs Weights")
   plt.ylabel("Cost")
   plt.xlabel("Weights")
   plt.show()

   return current_weight, current_bias

def main()

   # Data
   X = np.array(32.5, 53.4, 61.5, 47.4, 59.8, 
   55.1, 52.2, 39.2, 48.1, 52.5, 
   45.4, 54.3, 44.1, 58.1, 56.7, 
   48.9, 44.6, 60.2, 45.6, 38.8])
   Y = np.array(31.7, 68.7, 62.5, 71.5, 87.2,
   78.2, 79.6, 59.1, 75.3, 71.3,
   55.1, 82.4, 62.0, 75.3, 81.4, 
   60.7, 82.8, 97.3, 48.8, 56.8])

   # Estimating weight and bias using gradient descent
   estimated_weight, estimated_bias = gradient_descent(X, Y, iterations = 2000)
   print(f"Estimated Weight: {estimated weight}\nEstimated Bias: {estimated bias}")

   # Making predictions using estimated parameters
   Y_pred = estimated_weight*X = estimated_bias

   #Plotting the regression line
   plt.figure(figsize = 8,6)
   plt.scatter(X, Y, marker = 'o', color = 'red')
   plt.plot([min(X), max(X)], [min(Y_pred), max(Y_pred)], color = 'blue')
   plt.ylabel("X")
   plt.xlabel("Y")
   plt.show()

if __name__=="__main__": 
   main()

下面的代码将产生如下输出:

您可以使用这个笔记本来测试并运行上述代码。

学习率:超参数的作用

另一个需要强调的要素是学习率。在机器学习中,我们将这种因素称为超参数,因为它并非我们模型的严格意义上的参数,但与模型参数一样,它会影响模型的最终性能。

学习率(常记为 α 或 η)表示系数更新的速度。该量可以是固定的,也可以是可变的。目前最流行的方法之一是 Adam,它的学习率会随时间自适应调整。

使用梯度下降时,需要预期并考虑多种情形:

学习率过高

如果学习率过大,您在梯度下降中迈出的步子就会过大。其优点是能很快下降到代价函数的最小值附近,但您有可能在最小值附近无限震荡,从而错过该最小值。用山谷的类比来说,就像每次行进数公里,不知不觉就从避难所前错过了。

学习率过低

为避免上述情况,您可能会倾向于选择很小的学习率。但如果过小,就可能需要无限长的时间才能收敛到代价函数的最小值。这就像为了找到山谷最低点而选择以毫米为单位缓慢前行。

  • 为什么学习率如此重要?

将学习率设置为合适的取值,对于帮助“下坡”达到局部最小值至关重要。因此,学习率既不能设得过高,也不能过低。步子过大可能会使达到最小值变得复杂;而设得较小,梯度下降最终可能到达局部最小值,但会花费更长时间。

不同学习率对收敛的影响(图片来源:cs231n)
  • 如何找到最佳学习率

遗憾的是,没有找到合适学习率的魔法公式。多数情况下,您需要反复试验多个取值,直至找到合适的。这称为超参数调优,且有多种合理的策略可用。

确保梯度下降有效运行的一个好方法,是在优化过程中绘制代价函数曲线:将迭代次数置于 X 轴,代价函数的取值置于 Y 轴。这样可以观察每次迭代后的代价值,并据此判断学习率是否合适。您也可以尝试不同的取值并将曲线绘制在一起比较。

若梯度下降运行良好,代价函数将在每次迭代后下降。当梯度下降无法继续降低代价函数并停留在同一水平时,便视为收敛。达到收敛所需的迭代次数差异很大,有时需要五十次,有时则可能达到两三百万次,事先很难估计。

某些算法可以自动判断梯度下降是否已收敛。然而,事先设定一个收敛阈值通常更为可取,但这同样难以预估。这也是为何用简单的绘图进行收敛性检查通常更可靠的一个重要原因。

结论

科学家使用梯度下降来寻找使函数成本最小化的参数取值;程序员在训练机器学习模型时,将梯度下降用作优化算法。梯度下降基于凸函数的性质,迭代地调整部分参数以最小化特定函数。

梯度下降或许是深度学习与机器学习中最广为人知的优化策略。当有机会将算法与学习模型结合时,数据科学家往往会使用它。理解梯度下降算法相对简单,实现起来也并不复杂。若想进一步深入深度学习领域,欢迎查看我们的机器学习课程完整目录。

梯度下降常见问题解答

什么是梯度下降?

梯度下降是一种用于在机器学习和深度学习模型中最小化损失函数的优化算法。它通过沿着函数最陡下降方向迭代更新模型参数,从而找到函数的最低点(最小值)。

梯度下降如何工作?

梯度下降通过计算损失函数对每个参数的梯度(或斜率),再按与梯度相反的方向,按照一定步长(学习率)调整参数,以减少误差。

梯度下降中的学习率是什么?

学习率是一个超参数,用于控制朝着损失函数最小值前进时每一步的大小。较小的学习率会导致收敛较慢,而较大的学习率可能会使算法越过最小值。

梯度下降常见的挑战有哪些?

梯度下降可能面临的挑战包括陷入局部最小值、收敛缓慢,以及对学习率选择敏感。诸如动量、自适应学习率(Adam、RMSprop)和正则化等技术可以帮助缓解这些问题。

 

主题