在机器学习学习的早期阶段,梯度下降法(Gradient Descent, GD)可能是较难理解的概念之一。虽然”爬山”类比直观易懂,但往往让人感觉似懂非懂。当梯度下降与损失函数和反向传播一起讨论时,理解起来就更加困难了。我认为,要真正理解梯度下降,学习其数学基础是不可避免的。虽然这听起来有些吓人,但所需的数学知识其实相当基础。
基础:单变量微积分
梯度来源于多元微积分,而多元微积分又是单变量微积分的延伸。在单变量微积分中,我们处理单变量函数,记作\(f(x)\)。函数\(f(x)\)的导数,记作\(f'(x)\)或\(\frac{df}{dx}\),表示函数在特定点\(x\)处的变化率。从几何上看,它表示函数在该点切线的斜率。
例如,如果\(f(x) = x^2\),那么\(f'(x) = 2x\)。在\(x = 5\)处,导数为\(f'(5) = 10\),意味着函数在该点的变化率是每单位\(x\)变化10个单位。
扩展到多变量
当我们将函数从单变量扩展到多变量时,就进入了多元微积分的领域。考虑一个二元函数:
\[f(x, y) = x^2 + xy + y^2\]在这个上下文中,我们使用偏导数。函数\(f\)关于\(x\)的偏导数记作\(\frac{\partial f}{\partial x}\),它衡量了当只有\(x\)变化而\(y\)保持不变时,\(f\)的变化情况。类似地,\(\frac{\partial f}{\partial y}\)衡量了\(f\)关于\(y\)的变化率。
对于上面的简单二元函数,我们得到以下两个偏导数:
\[\frac{\partial f}{\partial x} = 2x + y\] \[\frac{\partial f}{\partial y} = x + 2y\]使用向量表示法,我们可以将上述函数表示为:
\[\nabla f(x, y) = \left[\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}\right]\]其中,\(\nabla f\)读作”del f”或”grad f”,称为梯度向量。这个向量指向函数在该点处增长最快的方向,其大小表示增长的速率。
使用向量表示法,我们可以将\(f\)扩展到任意数量的变量。
假设我们有一个表示二元函数\(f(x, y)\)的三维曲面。在这个曲面上任意一点\((x, y)\)处,梯度\(\nabla f(x, y)\)是一个向量,它:
- 指向函数增长最快的方向
- 其大小与该增长的速率成正比
如果你在曲面的高处放一个球,重力会使它沿着最陡峭的下降方向滚动,也就是梯度的反方向。
梯度下降在优化中的应用
在优化损失函数时,我们按以下步骤应用梯度下降:
- 从某个初始点开始
- 计算该点的梯度
- 按照以下规则沿梯度反方向移动: \(x_{t+1} = x_t - \eta \nabla f(x_t)\) 其中,\(\eta\)(eta)是学习率,决定了步长大小
- 重复直到收敛
学习率\(\eta\)的选择对梯度下降的收敛至关重要。如果学习率太小,收敛会很慢(训练模型需要太长时间)。如果学习率太大,可能会越过最小值点导致无法收敛。
在神经网络的反向传播中,我们通过应用微积分链式法则计算损失函数对网络中每个权重的梯度:
- 前向传播:通过网络每一层,计算输出和损失
- 反向传播:将误差反向传播,逐层计算梯度
- 使用计算得到的梯度更新权重
梯度下降中的常见挑战
在深度学习中,使用梯度时可能会出现几个问题:
- 梯度消失:当梯度在反向传播过程中变得极小,导致浅层网络学习非常缓慢
- 梯度爆炸:梯度变得极大,导致更新不稳定
- 鞍点:梯度接近零的平坦区域,但不一定是局部最小值
- 梯度噪声:特别是在随机方法中,梯度可能是真实梯度的有噪声估计
梯度下降的变体
为了克服上述问题,我们可以使用以下几种梯度下降的变体:
- 批量梯度下降:使用整个数据集计算梯度
- 随机梯度下降(SGD):每次使用一个随机样本来更新参数
- 小批量梯度下降:每次使用一小批随机样本来更新参数
- 自适应方法(AdaGrad, Adam等):根据历史梯度信息为每个参数调整学习率
TensorFlow 实践示例
下面是一个使用 TensorFlow 实现梯度下降的代码示例:
import tensorflow as tf
# 使用 TensorFlow 操作定义函数
@tf.function
def f(x, y):
return x**2 + x*y + y**2
# 要计算梯度的点
x = tf.Variable(2.0)
y = tf.Variable(1.0)
# 使用梯度带来记录自动微分操作
with tf.GradientTape() as tape:
z = f(x, y)
# 计算 z 对 [x, y] 的梯度
gradient = tape.gradient(z, [x, y])
print(f"函数在 (2, 1) 处的值: {z.numpy()}")
print(f"梯度: [df/dx, df/dy] = [{gradient[0].numpy()}, {gradient[1].numpy()}]")
# 使用 TensorFlow 实现梯度下降
def tf_gradient_descent(start_point, learning_rate=0.1, iterations=100):
x_value, y_value = start_point
x = tf.Variable(float(x_value))
y = tf.Variable(float(y_value))
path = [(x.numpy(), y.numpy())]
for i in range(iterations):
with tf.GradientTape() as tape:
z = f(x, y)
# 获取梯度
[dx, dy] = tape.gradient(z, [x, y])
# 更新变量
x.assign_sub(learning_rate * dx)
y.assign_sub(learning_rate * dy)
path.append((x.numpy(), y.numpy()))
# 停止条件
if tf.sqrt(dx**2 + dy**2) < 1e-6:
break
return path
# 运行基于 TensorFlow 的梯度下降
tf_path = tf_gradient_descent((3.0, 4.0))
print(f"TF 起始点: {tf_path[0]}")
print(f"TF 结束点: {tf_path[-1]}")
print(f"TF 最终函数值: {f(tf.constant(tf_path[-1][0]), tf.constant(tf_path[-1][1])).numpy()}")
在这个例子中:
- 我们使用 GradientTape 进行自动微分
- 我们无需手动推导就能计算梯度
- 我们使用 TensorFlow 的自动微分功能实现了梯度下降
NumPy、PyTorch 等库也提供了方便的梯度下降实现方式。我们可能会在后续的博客中讨论这些内容。
感谢阅读,祝您周末愉快!