d2l 学习笔记:简易线性模型实现
1. 线性模型训练流程
D2L 线性回归从零实现展示了一个机器学习模型训练的最小闭环:生成数据、定义模型、定义损失函数、读取小批量数据、计算梯度、更新参数,并在循环中逐步逼近真实规律。
数据生成
-> 定义模型
-> 定义损失
-> 读取 batch
-> 前向传播
-> 反向传播
-> 参数更新
-> 循环训练
对应到代码组件:
| 组件 | 作用 |
|---|---|
synthetic_data() | 构造带噪声的线性回归训练数据。 |
linreg() | 定义线性回归模型。 |
squared_loss() | 定义平方损失函数。 |
data_iter() | 以小批量方式读取数据。 |
backward() | 自动计算梯度。 |
sgd() | 按梯度更新参数。 |
| training loop | 多轮迭代训练模型。 |
2. 生成训练数据
2.1. 数据生成代码
def synthetic_data(w, b, num_examples):
X = torch.normal(0, 1, (num_examples, len(w)))
y = torch.matmul(X, w) + b
y += torch.normal(0, 0.01, y.shape)
return X, y.reshape((-1, 1))
这段代码用于模拟现实世界中的监督学习数据。输入特征 根据正态分布随机生成,标签 按线性关系生成,并加入少量噪声。
2.2. 真实规律
数据生成时假设真实规律为:
示例真实参数:
true_w = torch.tensor([2, -3.4])
true_b = 4.2
对应数学形式为:
其中 表示噪声,用于模拟真实数据中的随机扰动。
2.3. 输入特征矩阵
X = torch.normal(0, 1, (1000, 2))
该语句生成 1000 个样本,每个样本有 2 个特征:
矩阵形式可以表示为:
2.4. 标签生成
y = torch.matmul(X, w) + b
数学形式为:
为了让数据更接近真实场景,代码会追加噪声:
y += torch.normal(0, 0.01, y.shape)
最终标签为:
3. 定义线性回归模型
3.1. 模型代码
def linreg(X, w, b):
return torch.matmul(X, w) + b
线性回归模型的数学形式为:
其中:
| 符号 | 含义 |
|---|---|
| 输入特征矩阵。 | |
| 模型权重参数。 | |
| 模型偏置参数。 | |
| 模型预测值。 |
3.2. 训练参数与真实参数
数据生成时使用的 true_w、true_b 是真实规律;训练时模型并不知道真实参数,只能从随机初始化开始,通过数据逐步学习。
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
训练目标是让模型参数 、 逐步接近真实参数。
4. 定义损失函数
4.1. 平方损失代码
def squared_loss(y_hat, y):
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
平方损失的数学形式为:
加入 是为了求导后抵消平方项带来的系数 2,使梯度形式更简洁。
4.2. 平方损失的作用
损失函数衡量预测值与真实值之间的差距。
| 情况 | 含义 |
|---|---|
| 损失较小 | 模型预测接近真实标签。 |
| 损失较大 | 模型预测偏离真实标签。 |
平方损失适合线性回归这类连续值预测任务,因为它能惩罚较大的预测误差。
5. 小批量数据读取
5.1. data_iter 代码
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
random.shuffle(indices)
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(
indices[i:min(i + batch_size, num_examples)]
)
yield features[batch_indices], labels[batch_indices]
data_iter() 的作用是把完整训练集拆成多个小批量,每次返回一部分样本用于训练。
5.2. 为什么使用小批量
| 训练方式 | 特点 |
|---|---|
| 全量梯度下降 | 每次使用全部数据,梯度稳定但计算成本高。 |
| 随机梯度下降 | 每次使用一个样本,更新频繁但噪声较大。 |
| 小批量随机梯度下降 | 每次使用一批样本,在稳定性和效率之间折中。 |
小批量训练是深度学习中最常见的训练方式。
6. 自动求梯度
6.1. 前向与损失计算
l = loss(net(X, w, b), y)
这一行先计算预测值 ,再计算预测值与真实标签之间的损失。
6.2. 反向传播
l.sum().backward()
backward() 会根据计算图自动计算损失对参数的梯度:
梯度表示如果想让损失下降,参数应该沿哪个方向调整。
7. 使用 SGD 更新参数
7.1. SGD 代码
def sgd(params, lr, batch_size):
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()
7.2. 参数更新公式
随机梯度下降的参数更新公式为:
其中 是学习率。
7.3. 清空梯度
param.grad.zero_()
PyTorch 默认会累积梯度,因此每次更新参数后需要清空梯度,避免下一轮训练使用旧梯度。
8. 完整训练代码
lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y)
l.sum().backward()
sgd([w, b], lr, batch_size)
with torch.no_grad():
train_l = loss(net(features, w, b), labels)
print(f"epoch {epoch + 1}, loss {float(train_l.mean()):f}")
训练循环中,每个 epoch 会遍历所有小批量数据。每个小批量都会经历前向传播、损失计算、反向传播和参数更新。
9. 理论与代码对应关系
| 机器学习概念 | 代码实现 | 说明 |
|---|---|---|
| 数据集 | features、labels | 用于训练模型的输入和标签。 |
| 模型 | linreg() | 将输入映射为预测值。 |
| 参数 | w、b | 训练过程中被优化的变量。 |
| 损失函数 | squared_loss() | 衡量预测误差。 |
| 梯度 | backward() | 计算参数对损失的影响方向。 |
| 优化器 | sgd() | 根据梯度更新参数。 |
| 训练循环 | for epoch in range(num_epochs) | 重复优化过程。 |
10. 关键理解
10.1. 模型是什么
模型是一个带参数的函数。在线性回归中,模型形式是:
10.2. 训练是什么
训练是不断调整参数 和 ,让模型预测值 更接近真实值 的过程。
10.3. 如何评价参数
通过损失函数评价参数好坏:
损失越小,说明当前参数对应的模型预测效果越好。
10.4. 如何寻找参数
通过梯度下降寻找参数:
其中 表示所有待训练参数。
11. 训练模板
线性回归从零实现形成了一个通用机器学习训练模板:
准备数据
-> 定义模型
-> 定义损失函数
-> 定义优化方法
-> 小批量读取数据
-> 前向传播
-> 计算损失
-> 反向传播
-> 更新参数
-> 评估训练效果
这个模板不仅适用于线性回归,也适用于后续的 Softmax 回归、多层感知机、卷积神经网络和 Transformer 等模型。复杂模型的差异主要体现在模型结构、损失函数和优化策略上。
关联文档
- d2l 学习笔记:引言:D2L 学习入口。
- d2l 学习笔记:线性回归的损失函数:线性回归损失函数与优化目标。
- d2l 学习笔记:深度学习模型训练过程的数学原理:训练流程的数学抽象。
- d2l 学习笔记:softmax 回归:从线性回归扩展到多分类任务。
