共计 2130 个字符,预计需要花费 6 分钟才能阅读完成。
数学原理:理解梯度下降的核心
梯度下降是机器学习中用来最小化代价函数的迭代优化算法。它的核心思想可以概括为: 沿着代价函数梯度的反方向,逐步调整参数 ,直到找到最小值点。

代价函数与梯度
假设我们有代价函数 $J(\theta)$,其中 $\theta$ 是模型参数。梯度下降的更新规则为:
$$\theta_{t+1} = \theta_t – \eta \cdot \nabla_\theta J(\theta_t)$$
其中:
- $\eta$ 是学习率,控制每次更新的步长
- $\nabla_\theta J(\theta_t)$ 是代价函数在当前参数处的梯度
梯度实际上是一个向量,指向函数值增长最快的方向。因此,沿着梯度的反方向更新参数,可以确保代价函数值逐渐减小。
标准梯度下降的三大痛点
虽然理论上简单,标准梯度下降(Batch Gradient Descent)在实践中面临几个主要挑战:
-
计算开销大 :每次迭代需要计算整个数据集的梯度,当数据量大时计算成本高昂
-
易陷入局部最优 :特别是在非凸函数中,算法可能收敛到局部最小值而非全局最优
-
学习率敏感 :学习率设置不当会导致收敛缓慢(太小)或震荡(太大)
改进方案:从 SGD 到带动量的优化
随机梯度下降(SGD)
SGD 每次只用一个样本来估计梯度:
$$\theta_{t+1} = \theta_t – \eta \cdot \nabla_\theta J(\theta_t; x_i, y_i)$$
优点 :
- 计算速度快,适合大数据集
- 随机性有助于跳出局部最优
缺点 :
- 更新方向噪声大,收敛路径曲折
- 可能最终在最小值附近震荡
带动量的优化方法
动量法通过引入速度变量 $v$ 来平滑更新方向:
$$v_{t+1} = \gamma v_t + \eta \nabla_\theta J(\theta_t)$$
$$\theta_{t+1} = \theta_t – v_{t+1}$$
其中 $\gamma$ 是动量系数(通常 0.9)。这种方法能:
- 加速在平坦方向的收敛
- 减少震荡
Python 实现:带学习率衰减的线性回归
import numpy as np
import matplotlib.pyplot as plt
# 生成模拟数据
np.random.seed(42)
X = 2 * np.random.rand(100, 1)
y = 4 + 3 * X + np.random.randn(100, 1)
# 添加偏置项
X_b = np.c_[np.ones((100, 1)), X]
# 初始化参数
theta = np.random.randn(2, 1)
# 梯度下降参数
eta = 0.1 # 初始学习率
n_iterations = 1000
m = len(X)
def learning_rate_schedule(t):
"""学习率衰减函数"""
return eta / (1 + 0.1 * t)
# 存储损失历史
loss_history = []
for iteration in range(n_iterations):
gradients = 2/m * X_b.T.dot(X_b.dot(theta) - y)
current_eta = learning_rate_schedule(iteration)
theta = theta - current_eta * gradients
loss = np.mean((X_b.dot(theta) - y)**2)
loss_history.append(loss)
# 可视化损失曲线
plt.plot(range(n_iterations), loss_history)
plt.xlabel('Iterations')
plt.ylabel('Loss')
plt.title('Loss during Gradient Descent')
plt.show()
print("Final parameters:", theta.ravel())
生产环境建议
学习率选择
- 初始学习率 :可以先尝试 0.1,然后根据表现调整。常用策略:
- 从大到小尝试(如 0.1, 0.01, 0.001)
-
使用学习率扫描(Learning Rate Finder)
-
学习率衰减 :
- 时间衰减:$\eta_t = \eta_0 / (1 + decay_rate \times t)$
- 指数衰减:$\eta_t = \eta_0 \times 0.1^{t/N}$
批量大小
- 小批量(32-256)通常表现最好
- 太大:收敛慢,内存需求高
- 太小:更新噪声大
早停法实现
best_loss = np.inf
patience = 10 # 连续多少次不改进就停止
no_improve = 0
for iteration in range(n_iterations):
# ... 训练代码...
if loss < best_loss:
best_loss = loss
no_improve = 0
else:
no_improve += 1
if no_improve >= patience:
print(f"Early stopping at iteration {iteration}")
break
思考题
当特征尺度差异大时,梯度下降会出现什么问题?如何解决?
问题 :不同特征的梯度幅度差异大,导致收敛路径曲折,需要更小的学习率来避免震荡。
解决方案 :
- 特征标准化(如 Z -score 标准化)
- 使用自适应优化算法(如 Adam、RMSprop)
- 为不同参数设置不同的学习率
这些方法的本质都是让不同特征的更新步长更加均衡。
