算法步骤
- 初始化参数:随机选择一组模型参数的初始值。
- 迭代更新 :对参数进行多次迭代,每次迭代的目标是降低损失函数的值。
- 在每次迭代中,从固定数量的训练数据样本中随机抽取一个小批量(mini-batch)。
- 计算该小批量中所有样本的平均损失关于模型参数的导数(梯度)。
- 使用预先设定的学习率(一个正数),将梯度乘以学习率作为参数的减量,更新参数。
举例
假设你要训练一个模型,根据学生的"学习时间"和"刷题数量"预测"考试分数"。
- 数据:你有 1000 个学生的数据(比如学生 A 学习 5 小时,刷题 20 道,考了 80 分)。
- 目标 :找到最佳的参数(比如权重 <math xmlns="http://www.w3.org/1998/Math/MathML"> w 1 , w 2 w_1, w_2 </math>w1,w2 和偏置 <math xmlns="http://www.w3.org/1998/Math/MathML"> b b </math>b),让模型预测的分数尽可能接近真实分数。
传统方法的问题
-
全量梯度下降:每次计算都用全部 1000 个数据,计算量太大,像每次下山都要绕山走一圈才决定方向,太慢!
- 全量梯度下降的计算过程,可参考【深度学习】梯度计算
-
随机梯度下降(SGD):每次只用 1 个数据,比如只看学生 A 的数据调整参数,但噪声太大,像闭着眼睛乱跳,可能跳错方向。
使用 小批量随机梯度下降
- 折中方案 :每次随机选一小批数据(比如 32 个学生),计算他们的平均误差,再调整参数。
- 优点:比全量数据快,比单个数据更稳定,就像每次看一小片区域的地形再决定下山方向。
计算步骤
(1)初始化参数
- 随机给模型参数(比如权重 (w_1, w_2) 和偏置 (b))赋初始值,比如 (w_1=0.5, w_2=0.3, b=10)。
(2)随机划分小批量(Mini-batch)
- 把训练数据(比如 1 万条)随机打乱,分成多个小批量(比如每批 32 条)。
- 举例:1 万条数据 → 划分成 313 个小批量(每批 32 条,最后一批可能不足 32 条)。
(3)迭代更新参数
对每个小批量重复以下步骤:
- 计算梯度 :用当前小批量的数据,计算损失函数对每个参数的梯度(即"下山方向")。
- 梯度公式 (以线性回归为例):
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> 梯度 = 1 批量大小 ∑ i ∈ 小批量 ( 预测值 − 真实值 ) × 输入特征 \text{梯度} = \frac{1}{\text{批量大小}} \sum_{i \in \text{小批量}} (\text{预测值} - \text{真实值}) \times \text{输入特征} </math>梯度=批量大小1i∈小批量∑(预测值−真实值)×输入特征
- 梯度公式 (以线性回归为例):
- 更新参数 :
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> w ← w − 学习率 × 梯度 w \leftarrow w - \text{学习率} \times \text{梯度} </math>w←w−学习率×梯度
(4)重复直到收敛
- 不断重复步骤(2)-(3),直到损失函数不再明显下降,或达到预设的迭代次数。
关键概念解释
学习率(Learning Rate)
- 比喻 :就像下山时的步长。
- 步子太大(学习率过高):可能直接跨过最低点,甚至冲上对面山坡。
- 步子太小(学习率过低):下山太慢,浪费时间。
- 调参目标:找到一个"刚刚好"的步长。
批量大小(Batch Size)
- 比喻 :就像每次下山前观察的区域大小。
- 区域太大(Batch Size=1000):计算量大,但方向准确。
- 区域太小(Batch Size=1):计算快,但方向可能不准。
- 折中选择:通常选 32、64、128 等较小的批量。
实现代码
python
import torch
import numpy as np
from torch.utils.data import DataLoader, TensorDataset
# =============================== 1. 生成模拟数据(n 个样本) ===============================
def generate_data(n_samples):
np.random.seed(42) # 固定随机种子保证结果可复现
# 房屋面积(40-150 平米),均匀分布生成
x1 = np.random.uniform(40, 150, n_samples).astype(np.float32)
# 房间数(1-5 个),整数均匀分布生成后转为浮点数
x2 = np.random.randint(1, 6, n_samples).astype(np.float32)
# 真实房价:基础价50万 + 面积*1.2万 + 房间数*30万 + 高斯噪声(均值0,标准差10)
y = (50 + x1 * 1.2 + x2 * 30 + np.random.normal(0, 10, n_samples)).astype(np.float32)
return x1, x2, y
n_samples = 1000 # 总样本数
x1, x2, y_true = generate_data(n_samples)
# 将NumPy数组转换为PyTorch张量
x1_tensor = torch.tensor(x1) # 输入特征1(面积)
x2_tensor = torch.tensor(x2) # 输入特征2(房间数)
y_tensor = torch.tensor(y_true) # 真实房价标签
# 输出x1_tensor, x2_tensor, y_tensor
print("x1_tensor:", x1_tensor)
print("=" * 100)
print("x2_tensor:", x2_tensor)
print("=" * 100)
print("y_tensor:", y_tensor)
print("=" * 100)
# 创建数据集和DataLoader(用于小批量加载)
batch_size = 32 # 每个小批量的样本数
dataset = TensorDataset(x1_tensor, x2_tensor, y_tensor) # 将输入和标签打包为数据集
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True) # 支持随机打乱和批量加载
# =============================== 2. 初始化参数和优化器 ===============================
# 初始化模型参数(权重和偏置),并启用梯度跟踪
w1 = torch.tensor(0.0, requires_grad=True) # 面积权重
w2 = torch.tensor(0.0, requires_grad=True) # 房间数权重
b = torch.tensor(0.0, requires_grad=True) # 偏置项
params = [w1, w2, b] # 所有参数的列表
# 定义学习率和训练轮次
learning_rate = 0.0001 # 较小的学习率防止梯度更新过大
n_epochs = 100 # 整个数据集迭代的次数
# 使用随机梯度下降优化器(SGD)
optimizer = torch.optim.SGD(params, lr=learning_rate)
# =============================== 3. 训练循环(小批量梯度下降) ===============================
loss_history = [] # 记录每轮的平均损失值
for epoch in range(n_epochs):
epoch_loss = 0.0 # 累计当前轮次的总损失
for batch_idx, (x1_batch, x2_batch, y_batch) in enumerate(dataloader):
# 前向传播:计算当前批量的预测值
y_pred = w1 * x1_batch + w2 * x2_batch + b
# 计算均方误差损失(MSE)
loss = torch.mean((y_pred - y_batch) ** 2)
epoch_loss += loss.item() # 累加损失值(转换为Python数值)
# 反向传播与参数更新
optimizer.zero_grad() # 清空之前的梯度
loss.backward() # 计算当前损失的梯度
optimizer.step() # 根据梯度更新参数
# 计算当前轮次的平均损失并记录
avg_loss = epoch_loss / len(dataloader) # 总损失除以批次数
loss_history.append(avg_loss)
print(f"Epoch [{epoch+1}/{n_epochs}], Loss: {avg_loss:.2f}")
# =============================== 4. 结果分析 ===============================
print("\n训练结果:")
# 输出训练后的参数值(理想值应为w1≈1.2, w2≈30, b≈50)
print(f"最终参数: w1={w1.item():.2f}, w2={w2.item():.2f}, b={b.item():.2f}")