1. 梯度下降的本质与数学原理
梯度下降是深度学习中最为基础的优化算法,其核心思想是通过迭代方式逐步调整参数,使目标函数值不断减小。在PyTorch框架下实现梯度下降,我们需要深入理解其数学本质。
1.1 一维梯度下降的数学表达
对于一元函数f(x),泰勒展开给出:
f(x + ε) ≈ f(x) + εf'(x) + O(ε²)
当选择ε = -ηf'(x)时(η为学习率),我们得到:
f(x - ηf'(x)) ≈ f(x) - η[f'(x)]²
这意味着只要f'(x)≠0,适当选择η总能减小函数值。这个简单原理构成了梯度下降的基础。
1.2 多维情况的扩展
对于多元函数f: ℝᵈ→ℝ,梯度∇f(x)是一个由各偏导数组成的向量。此时更新规则变为:
x ← x - η∇f(x)
关键性质:
- 梯度方向是函数值增长最快的方向
- 负梯度方向是函数值下降最快的方向
- 学习率η控制每次更新的步长
2. PyTorch实现基础梯度下降
2.1 基本实现代码
python复制import torch
def gradient_descent(eta, steps, f, f_grad):
x = torch.tensor([10.0], requires_grad=True)
results = []
for _ in range(steps):
# 计算梯度
grad = f_grad(x)
# 更新参数
x.data -= eta * grad
results.append(x.item())
return results
2.2 示例:优化f(x)=x²
python复制def f(x): return x**2
def f_grad(x): return 2*x
results = gradient_descent(eta=0.2, steps=10, f=f, f_grad=f_grad)
print(f"Final x value: {results[-1]:.6f}")
2.3 可视化优化过程
python复制import matplotlib.pyplot as plt
def plot_trace(results, f):
x = torch.linspace(-11, 11, 100)
plt.plot(x.numpy(), f(x).numpy())
plt.plot(results, [f(torch.tensor([r])) for r in results], 'o-')
plt.xlabel('x')
plt.ylabel('f(x)')
plot_trace(results, f)
3. 学习率的影响与选择
3.1 学习率过大的问题
当η=1.1时,优化过程会发散:
python复制bad_results = gradient_descent(eta=1.1, steps=10, f=f, f_grad=f_grad)
plot_trace(bad_results, f) # 显示震荡发散
3.2 学习率过小的问题
当η=0.05时,收敛速度极慢:
python复制slow_results = gradient_descent(eta=0.05, steps=20, f=f, f_grad=f_grad)
plot_trace(slow_results, f) # 显示缓慢收敛
3.3 学习率选择经验
- 常用初始值:0.001、0.01、0.1
- 学习率衰减策略:
python复制eta = initial_eta / (1 + decay_rate * step) - 自适应方法:Adam等优化器自动调整
4. 局部最小值与鞍点问题
4.1 非凸函数的挑战
考虑f(x) = x·cos(cx):
python复制c = 0.15 * torch.pi
def f(x): return x * torch.cos(c * x)
def f_grad(x): return torch.cos(c*x) - c*x*torch.sin(c*x)
results = gradient_descent(eta=0.2, steps=20, f=f, f_grad=f_grad)
plot_trace(results, f) # 可能陷入局部极小值
4.2 解决方案
- 随机初始化多次
- 使用带动量的优化器
- 模拟退火技术
5. 多维梯度下降实践
5.1 二元函数示例
优化f(x₁,x₂) = x₁² + 2x₂²:
python复制def f_2d(x1, x2): return x1**2 + 2*x2**2
def f_2d_grad(x1, x2): return (2*x1, 4*x2)
def gd_2d(eta, steps):
x1, x2 = -5.0, -2.0
results = [(x1, x2)]
for _ in range(steps):
g1, g2 = f_2d_grad(x1, x2)
x1 -= eta * g1
x2 -= eta * g2
results.append((x1, x2))
return results
5.2 等高线可视化
python复制def plot_2d_trace(results):
x1 = torch.linspace(-5.5, 1.0, 100)
x2 = torch.linspace(-3.0, 1.0, 100)
X1, X2 = torch.meshgrid(x1, x2)
Z = f_2d(X1, X2)
plt.contour(X1, X2, Z, levels=20)
plt.plot(*zip(*results), '-o')
plt.xlabel('x1')
plt.ylabel('x2')
plot_2d_trace(gd_2d(eta=0.1, steps=20))
6. 梯度下降的改进方法
6.1 牛顿法原理
利用二阶导数信息:
x ← x - [f''(x)]⁻¹f'(x)
PyTorch实现:
python复制def newton_method(f, f_grad, f_hess, steps=10):
x = torch.tensor([10.0])
results = [x.item()]
for _ in range(steps):
grad = f_grad(x)
hess = f_hess(x)
x -= grad / hess
results.append(x.item())
return results
6.2 预处理技巧
对角预处理方法:
x ← x - η·diag(H)⁻¹∇f(x)
可以有效解决不同维度尺度差异问题。
7. 实际应用中的注意事项
7.1 梯度消失/爆炸
- 使用梯度裁剪:
python复制
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm)
7.2 批处理技巧
- 小批量梯度下降实现:
python复制for batch in dataloader: optimizer.zero_grad() loss = model(batch) loss.backward() optimizer.step()
7.3 PyTorch优化器使用
python复制import torch.optim as optim
model = ... # 定义模型
optimizer = optim.SGD(model.parameters(), lr=0.01)
for epoch in range(epochs):
for data, target in dataloader:
optimizer.zero_grad()
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
8. 性能优化技巧
8.1 向量化计算
避免循环,使用矩阵运算:
python复制# 差的做法
for i in range(n):
y[i] = w[i] * x[i] + b[i]
# 好的做法
y = w * x + b
8.2 自动混合精度
python复制scaler = torch.cuda.amp.GradScaler()
with torch.cuda.amp.autocast():
output = model(input)
loss = criterion(output, target)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
9. 常见问题排查
9.1 梯度为None
检查:
- requires_grad=True是否设置
- 是否调用了backward()
- 中间操作是否支持自动微分
9.2 训练不收敛
排查步骤:
- 检查学习率
- 验证模型结构
- 检查数据预处理
- 监控梯度幅度
9.3 数值不稳定
解决方案:
- 添加正则化项
- 使用梯度裁剪
- 调整初始化方法
10. 进阶话题
10.1 随机梯度下降
小批量实现的优势:
- 更快收敛
- 逃离局部极小值
- 适合大规模数据
10.2 动量方法
带动量的更新:
v = βv + (1-β)∇f(x)
x = x - ηv
PyTorch实现:
python复制optim.SGD(params, lr=0.01, momentum=0.9)
10.3 自适应方法
Adam优化器结合了动量与自适应学习率:
python复制optim.Adam(params, lr=0.001)
在实际项目中,我通常会先用Adam快速获得不错的结果,然后再尝试用SGD进行精细调优。对于特别深层的网络,Adam通常表现更稳定。记住要监控训练过程中的损失和指标变化,这是发现优化问题的第一道防线。
