在机器学习中,我们经常会说一句话:模型训练的目标,是让损失函数尽可能小(在深度学习中也经常使用梯度下降)。

但问题是:参数那么多,损失函数又可能很复杂,模型到底应该怎样找到那个“比较好”的参数呢?

梯度下降法就是最常用的答案之一。它的思想并不神秘:先站在一个位置,看哪里下降最快,然后朝那个方向走一小步;重复很多次以后,就逐渐接近损失函数的低谷。

1. 为什么需要梯度下降

以线性回归为例,模型可以写成:

$$
\hat{y} = wx + b
$$

其中:

  • $x$ 是输入特征。
  • $\hat{y}$ 是模型预测值。
  • $w$ 是权重。
  • $b$ 是偏置。

我们希望预测值 $\hat{y}$ 尽量接近真实值 $y$,因此可以使用均方误差作为损失函数:

$$
J(w, b) = \frac{1}{m}\sum_{i=1}^{m}(\hat{y}^{(i)} - y^{(i)})^2
$$

因为 $\hat{y}^{(i)} = wx^{(i)} + b$,所以也可以写成:

$$
J(w, b) = \frac{1}{m}\sum_{i=1}^{m}(wx^{(i)} + b - y^{(i)})^2
$$

训练模型的过程,其实就是寻找一组 $w$ 和 $b$,让 $J(w,b)$ 尽可能小。

如果只有一个参数,损失函数可能像一条碗形曲线;如果有很多参数,它就会变成高维空间里的曲面。梯度下降法的任务,就是在这张曲面上一步步走向低处。

梯度下降沿着损失函数曲面逐步接近最低点

2. 从“下山”理解梯度下降

可以把损失函数想象成一座山:

  • 当前参数,就是你站在山上的位置。
  • 损失函数值,就是你所在位置的海拔。
  • 训练目标,就是走到尽可能低的山谷。

如果你想下山,最自然的做法是观察当前坡度,然后朝下降最快的方向走一步。

梯度下降法也是这样:

  1. 随机初始化参数。
  2. 计算当前参数位置的梯度。
  3. 朝梯度的反方向更新参数。
  4. 重新计算损失。
  5. 重复以上过程,直到损失不再明显下降。

这里有一个关键点:梯度指向函数上升最快的方向,所以我们要朝梯度的反方向走。

3. 什么是梯度

在一元函数中,导数表示函数在某一点的变化率。

例如:

$$
f(x) = x^2
$$

它的导数是:

$$
f’(x) = 2x
$$

当 $x = 3$ 时:

$$
f’(3) = 6
$$

这说明在 $x=3$ 附近,函数值随着 $x$ 增大而快速上升。为了让函数值变小,我们应该让 $x$ 减小。

在多参数模型中,参数不止一个,例如 $w$ 和 $b$。这时我们需要分别计算损失函数对每个参数的偏导数:

$$
\frac{\partial J}{\partial w}, \quad \frac{\partial J}{\partial b}
$$

这些偏导数组成的向量,就是梯度:

$$
\nabla J(w,b) =
\left[
\frac{\partial J}{\partial w},
\frac{\partial J}{\partial b}
\right]
$$

梯度告诉我们:如果想让损失函数上升得最快,参数应该往哪个方向变。梯度下降则反过来,朝它的相反方向更新参数。

4. 梯度下降的更新公式

梯度下降的通用更新公式是:

$$
\theta := \theta - \alpha \nabla J(\theta)
$$

其中:

  • $\theta$ 表示模型参数,可以是一个参数,也可以是一组参数。
  • $\alpha$ 是学习率,控制每次迈多大的步子。
  • $\nabla J(\theta)$ 是损失函数对参数的梯度。

对于线性回归中的 $w$ 和 $b$,更新公式可以写成:

$$
w := w - \alpha \frac{\partial J}{\partial w}
$$

$$
b := b - \alpha \frac{\partial J}{\partial b}
$$

这两个式子非常重要。机器学习中的很多优化过程,本质上都可以看成它的变体。

5. 在线性回归中推导梯度

线性回归的损失函数为:

$$
J(w,b) = \frac{1}{m}\sum_{i=1}^{m}(wx^{(i)} + b - y^{(i)})^2
$$

为了简化表达,令第 $i$ 个样本的误差为:

$$
e^{(i)} = wx^{(i)} + b - y^{(i)}
$$

那么损失函数可以写成:

$$
J(w,b) = \frac{1}{m}\sum_{i=1}^{m}(e^{(i)})^2
$$

对 $w$ 求偏导:

$$
\frac{\partial J}{\partial w}
= \frac{2}{m}\sum_{i=1}^{m}(wx^{(i)} + b - y^{(i)})x^{(i)}
$$

对 $b$ 求偏导:

$$
\frac{\partial J}{\partial b}
= \frac{2}{m}\sum_{i=1}^{m}(wx^{(i)} + b - y^{(i)})
$$

所以线性回归的参数更新过程为:

$$
w := w - \alpha \frac{2}{m}\sum_{i=1}^{m}(wx^{(i)} + b - y^{(i)})x^{(i)}
$$

$$
b := b - \alpha \frac{2}{m}\sum_{i=1}^{m}(wx^{(i)} + b - y^{(i)})
$$

从这个推导可以看出,梯度下降并不是凭感觉调参数,而是根据损失函数的变化方向,有依据地调整参数。

6. 学习率为什么重要

学习率 $\alpha$ 决定每一步走多远。

不同学习率会导致不同的收敛效果

如果学习率太小:

  • 每次更新很保守。
  • 损失会下降,但速度很慢。
  • 可能需要训练很多轮。

如果学习率合适:

  • 损失能够稳定下降。
  • 收敛速度比较快。
  • 参数逐渐接近较优解。

如果学习率太大:

  • 参数可能直接越过最低点。
  • 损失可能来回震荡。
  • 严重时损失会越来越大,导致发散。

所以在训练模型时,学习率通常是最重要的超参数之一。

7. 三种常见梯度下降方式

根据每次更新参数时使用多少样本,梯度下降可以分为三类。

7.1 批量梯度下降

批量梯度下降,也叫 Batch Gradient Descent。它每次使用全部训练样本来计算梯度。

优点:

  • 梯度方向比较稳定。
  • 损失下降曲线通常比较平滑。

缺点:

  • 数据量很大时,单次更新计算成本高。
  • 参数更新频率低。

适合数据量不大、希望训练过程稳定的场景。

7.2 随机梯度下降

随机梯度下降,也叫 Stochastic Gradient Descent,简称 SGD。它每次只随机使用一个样本来更新参数。

优点:

  • 单次更新很快。
  • 可以更频繁地更新参数。
  • 有时能跳出局部较差的位置。

缺点:

  • 梯度方向噪声较大。
  • 损失曲线会明显波动。

适合大规模数据或在线学习场景。

7.3 小批量梯度下降

小批量梯度下降,也叫 Mini-batch Gradient Descent。它每次使用一小批样本来计算梯度,例如 32、64 或 128 个样本。

这是深度学习中最常见的训练方式。

它在稳定性和计算效率之间取得了平衡:

  • 比批量梯度下降更新更频繁。
  • 比随机梯度下降方向更稳定。
  • 更适合 GPU 并行计算。

8. 为什么特征缩放会影响梯度下降

假设一个房价预测模型有两个特征:

  • 面积:几十到几百。
  • 距离地铁距离:几百到几千。

如果不同特征的数值范围差异很大,损失函数的曲面可能会变得很“狭长”。梯度下降在这种曲面上容易来回摆动,收敛速度变慢。

因此,使用梯度下降训练模型前,经常会对特征做标准化:

$$
z = \frac{x - \mu}{\sigma}
$$

其中:

  • $\mu$ 是特征均值。
  • $\sigma$ 是特征标准差。

标准化以后,不同特征处在更接近的尺度上,梯度下降通常会更容易收敛。

9. Python 手写实现

下面用一元线性回归演示梯度下降的完整过程。

import numpy as np

# 构造一组简单数据:大致满足 y = 2x + 1
x = np.array([1, 2, 3, 4, 5], dtype=float)
y = np.array([3, 5, 7, 9, 11], dtype=float)

# 初始化参数。这里从 0 开始,实际任务中也可以使用随机初始化
w = 0.0
b = 0.0

# 学习率控制每次参数更新的步长
learning_rate = 0.01

# 训练轮数表示完整更新多少次参数
epochs = 1000

# 样本数量用于计算平均梯度
m = len(x)

for epoch in range(epochs):
    # 根据当前参数计算预测值
    y_pred = w * x + b

    # 计算预测误差
    error = y_pred - y

    # 分别计算损失函数对 w 和 b 的梯度
    dw = (2 / m) * np.sum(error * x)
    db = (2 / m) * np.sum(error)

    # 沿着梯度的反方向更新参数
    w = w - learning_rate * dw
    b = b - learning_rate * db

    # 每隔 100 轮观察一次损失,确认模型是否在逐渐收敛
    if epoch % 100 == 0:
        loss = np.mean(error ** 2)
        print(f"epoch={epoch}, loss={loss:.6f}, w={w:.4f}, b={b:.4f}")

print(f"最终参数:w={w:.4f}, b={b:.4f}")

如果学习率设置合理,你会看到损失值逐渐减小,$w$ 接近 2,$b$ 接近 1。

10. 如何判断梯度下降是否正常工作

训练时可以重点观察损失曲线:

  • 如果损失稳定下降,说明学习率和数据处理通常比较合理。
  • 如果损失下降很慢,可能是学习率太小,或者特征没有缩放。
  • 如果损失剧烈震荡,可能是学习率偏大。
  • 如果损失变成 nan,通常说明学习率过大、数据异常或数值溢出。

还可以观察参数变化:

  • 如果参数几乎不变,更新步子可能太小。
  • 如果参数绝对值越来越大,模型可能正在发散。

11. 梯度下降一定能找到全局最优解吗

不一定。

对于线性回归的均方误差损失函数,它通常是凸函数。在这种情况下,只要学习率合适,梯度下降可以收敛到全局最优解。

但在神经网络中,损失函数往往是非凸的,曲面会更复杂,可能存在很多局部低谷、鞍点和平坦区域。此时梯度下降通常不能保证找到全局最优解,但在实践中仍然可以找到效果足够好的参数。

这也是为什么深度学习中会出现很多优化器,例如 Momentum、RMSprop、Adam 等。它们都可以看成是在基础梯度下降上做改进。

12. 总结

梯度下降法的核心可以概括为一句话:

根据损失函数对参数的梯度,沿着让损失下降的方向,不断更新参数。

理解梯度下降时,可以抓住四个关键词:

  • 损失函数:告诉模型现在错得有多离谱。
  • 梯度:告诉参数往哪个方向变化会让损失上升最快。
  • 反方向:为了减小损失,需要朝梯度的反方向更新。
  • 学习率:控制每次更新走多大一步。

线性回归、逻辑回归、神经网络虽然模型形式不同,但背后都离不开“定义损失函数,再用优化算法降低损失”这个基本思路。学会梯度下降以后,再看很多机器学习算法的训练过程,就会清晰很多。