PyTorch 深度学习实战:梯度下降与随机梯度下降的代码解析
1. 梯度下降与随机梯度下降的核心区别
在深度学习中,梯度下降(Gradient Descent)和随机梯度下降(Stochastic Gradient Descent, SGD)是最基础的优化算法。它们的目标都是通过迭代调整模型参数,使得损失函数的值最小化。但两者的工作方式有着本质区别。
梯度下降在每次参数更新时,会计算整个训练集上的平均梯度。这意味着每次更新都需要遍历所有样本,计算量大但更新方向稳定。而随机梯度下降每次只随机选取一个样本计算梯度并更新参数,计算效率高但更新方向波动较大。
举个例子,假设我们有3个样本数据:
x_data = [1.0, 2.0, 3.0]
y_data = [2.0, 4.0, 6.0]
在梯度下降中,每次更新权重w时:
grad = 0
for x, y in zip(x_data, y_data):
grad += 2*x*(x*w - y) # 对每个样本计算梯度
w -= 0.01 * (grad / len(x_data)) # 使用平均梯度更新
而在随机梯度下降中:
for x, y in zip(x_data, y_data):
grad = 2*x*(x*w - y) # 只计算当前样本梯度
w -= 0.01 * grad # 立即更新
实际应用中,我们通常会采用折中的小批量梯度下降(Mini-batch Gradient Descent),既保证了计算效率,又使更新方向相对稳定。
2. PyTorch实现梯度下降
在PyTorch中实现梯度下降非常直观。首先我们需要明确几个关键点:
- 前向传播计算预测值
- 计算损失函数
- 反向传播计算梯度
- 手动更新参数
下面是一个完整的PyTorch实现示例:
import torch
import matplotlib.pyplot as plt
# 准备数据
x_data = torch.tensor([[1.0], [2.0], [3.0]])
y_data = torch.tensor([[2.0], [4.0], [6.0]])
# 初始化权重
w = torch.tensor([[1.0]], requires_grad=True)
# 学习率
learning_rate = 0.01
# 存储训练过程
loss_list = []
for epoch in range(100):
# 前向传播
y_pred = x_data * w
# 计算损失(MSE)
loss = ((y_pred - y_data) ** 2).mean()
loss_list.append(loss.item())
# 反向传播
loss.backward()
# 手动更新权重,不自动计算梯度
with torch.no_grad():
w -= learning_rate * w.grad
# 梯度清零
w.grad.zero_()
# 绘制损失曲线
plt.plot(loss_list)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.show()
这里有几个关键细节需要注意:
requires_grad=True告诉PyTorch需要跟踪该张量的梯度loss.backward()会自动计算所有需要梯度的张量的梯度- 更新参数时要使用
torch.no_grad()上下文,避免影响梯度计算 - 每次更新后必须手动清零梯度,否则梯度会累积
3. 随机梯度下降的PyTorch实现
随机梯度下降的实现与批量梯度下降类似,主要区别在于数据的处理方式。下面是SGD的完整实现:
import torch
import matplotlib.pyplot as plt
# 准备数据
x_data = torch.tensor([[1.0], [2.0], [3.0]])
y_data = torch.tensor([[2.0], [4.0], [6.0]])
# 初始化权重
w = torch.tensor([[1.0]], requires_grad=True)
# 学习率
learning_rate = 0.01
# 存储训练过程
loss_list = []
for epoch in range(100):
total_loss = 0
for x, y in zip(x_data, y_data):
# 前向传播
y_pred = x * w
# 计算损失
loss = (y_pred - y) ** 2
total_loss += loss.item()
# 反向传播
loss.backward()
# 更新权重
with torch.no_grad():
w -= learning_rate * w.grad
w.grad.zero_()
loss_list.append(total_loss / len(x_data))
# 绘制损失曲线
plt.plot(loss_list)
plt.xlabel('Epoch')
plt.ylabel('Average Loss')
plt.show()
随机梯度下降的特点:
- 每个epoch内遍历所有样本,但每次只用一个样本更新
- 梯度更新更频繁(样本数×epoch次)
- 损失曲线波动较大,但通常能更快收敛
- 对噪声数据更鲁棒,不容易陷入局部最优
4. 关键参数与调优技巧
在实际应用中,有几个关键参数会影响梯度下降算法的表现:
4.1 学习率选择
学习率是梯度下降中最重要的超参数之一。太小的学习率会导致收敛过慢,太大的学习率可能导致无法收敛。经验法则:
- 常见初始值:0.1, 0.01, 0.001
- 可以使用学习率查找器(LR Finder)确定合适范围
PyTorch实现学习率测试:
learning_rates = [1, 0.1, 0.01, 0.001, 0.0001]
for lr in learning_rates:
w = torch.tensor([[1.0]], requires_grad=True)
for epoch in range(100):
y_pred = x_data * w
loss = ((y_pred - y_data) ** 2).mean()
loss.backward()
with torch.no_grad():
w -= lr * w.grad
w.grad.zero_()
print(f"LR: {lr}, Final loss: {loss.item()}")
4.2 动量(Momentum)
动量方法可以帮助加速SGD在相关方向上的收敛,抑制震荡。PyTorch中实现带动量的SGD:
optimizer = torch.optim.SGD([w], lr=0.01, momentum=0.9)
动量原理:
v = momentum * v - learning_rate * gradient
w = w + v
4.3 批量大小选择
小批量梯度下降的批量大小也是一个重要参数:
- 批量太小:更新方向噪声大,收敛不稳定
- 批量太大:每次更新计算量大,可能陷入局部最优
- 常见选择:32, 64, 128, 256等2的幂次
PyTorch实现小批量梯度下降:
from torch.utils.data import DataLoader
dataset = torch.utils.data.TensorDataset(x_data, y_data)
dataloader = DataLoader(dataset, batch_size=2, shuffle=True)
for epoch in range(100):
for batch_x, batch_y in dataloader:
# 前向传播和反向传播
...
5. 实际应用中的注意事项
在真实项目中使用梯度下降算法时,有几个常见问题需要注意:
-
梯度消失/爆炸:特别是深层网络中,梯度可能变得极小或极大。解决方法包括:
- 梯度裁剪(
torch.nn.utils.clip_grad_norm_) - 使用ReLU等合适的激活函数
- 批归一化(BatchNorm)
- 梯度裁剪(
-
局部最优:神经网络损失函数通常有很多局部最优点。解决方法:
- 使用随机梯度下降增加随机性
- 尝试不同的初始化方法
- 使用模拟退火等技术
-
过拟合:模型在训练集上表现太好,泛化能力差。解决方法:
- 增加正则化(L1/L2)
- 使用Dropout
- 早停(Early Stopping)
-
学习率衰减:随着训练进行,逐渐减小学习率可以提升模型性能:
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1) -
数值稳定性:注意浮点数精度问题,特别是当学习率很小时:
- 使用双精度(
dtype=torch.float64) - 检查梯度值是否合理
- 使用双精度(
在实际项目中,我通常会先用小批量数据进行快速实验,确定合适的学习率和批量大小,然后再用全量数据进行训练。同时,使用TensorBoard或Weights & Biases等工具监控训练过程也非常有帮助。
更多推荐
所有评论(0)