《动手学深度学习》3.2 线性回归从零开始实现

1前言

从零开始学习ai文章系列计划是个人在《动手学深度学习》和《磨菇书》两本书的学习中的个人笔记,文章也会以课本中的章节分开,即每个章节一片笔记。我会尽量的把主要内容以及遇到的难点进行记录与解决,如果哪里有错误的欢迎指正。或者不清晰的可以直接查看原文部分。

《动手学深度学习》原文(课本):https://tangshusen.me/Dive-into-DL-PyTorch/#/

《动手学深度学习》代码:https://github.com/ShusenTang/Dive-into-DL-PyTorch

(由于有时候公式太多,可能会直接贴图片)

在了解了线性回归的背景知识之后,现在我们可以动手实现它了。尽管强大的深度学习框架可以减少大量重复性工作,但若过于依赖它提供的便利,会导致我们很难深入理解深度学习是如何工作的。因此,本节将介绍如何只利用Tensorautograd来实现一个线性回归的训练。

首先,导入本节中实验所需的包或模块,其中的matplotlib包可用于作图,且设置成嵌入显示。

1
2
3
4
5
import torch
from IPython import display
from matplotlib import pyplot as plt
import numpy as np
import random

2 构造一个简单的人工训练数据集

设训练数据集样本数为1000,输入个数(特征数)为2。给定随机生成的批量样本特征X,使用线性回归模型真实权重 \(w=[2,−3.4]^⊤\) 和偏差 b=4.2,以及一个随机噪声项 ϵ 来生成标签 \[ y=Xw+b+ϵ \] torch.randn 是 PyTorch 中用于生成服从标准正态分布(均值为 0,标准差为 1)的随机张量的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
num_inputs = 2
num_examples = 1000
true_w = [2, -3.4]
true_b = 4.2

# features是一个行数为num_examples,列数为num_inputs的张量,其每个内容都是从正态分布弄出来的随机数
features = torch.randn(num_examples, num_inputs, dtype=torch.float32)

# features中每行进行=> 第一列与第一个w相乘 + 第二列与第二个w相乘 + b
# 生成与features同行数的张量labels
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b

print(len(labels))
print(type(labels))

# labels中每个数也加上噪音
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()),
dtype=torch.float32)

3.读取数据

上图中,j 通过torch.longTensor,获取了一个由标量组成的列表张量,如 [ 1, 3, 4, 67, 22] 。

index_select(dim, index) 用于在指定维度 dim 上,根据索引 index 选择张量的特定元素。

features.index_select(0, j):从 features 按行选择索引 j 对应的样本

函数执行后就直接从fetures与labels中返回对应大小行数的的tensor了

4. 初始化模型参数

将权重初始化成均值为0、标准差为0.01的正态随机数,偏差则初始化成0。之后的模型训练中,需要对这些参数求梯度来迭代参数的值,因此我们要让它们的requires_grad=True。

(可以看出这里是w是列向量)

1
2
3
4
5
w = torch.tensor(np.random.normal(0, 0.01, (num_inputs, 1)), dtype=torch.float32)
b = torch.zeros(1, dtype=torch.float32)

w.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)

## 5.定义模型

我们使用mm函数做矩阵乘法。

1
2
def linreg(X, w, b):
return torch.mm(X, w) + b

6.定义损失函数

使用上一节描述的平方损失来定义线性回归的损失函数。在实现中,我们需要把真实值y变形成预测值y_hat的形状。以下函数返回的结果也将和y_hat的形状相同。

这里的 y.view(y_hat.size()) 主要作用是让 y 的形状与 y_hat 保持一致,以便执行后续的减法运算 y_hat - y

y 的原始形状: torch.Size([3])

y 经过 view 变成: torch.Size([3, 1])

1
2
3
def squared_loss(y_hat, y):
# 注意这里返回的是向量, 另外, pytorch里的MSELoss并没有除以 2
return (y_hat - y.view(y_hat.size())) ** 2 / 2

7.定义优化算法

以下的sgd函数实现了上一节中介绍的小批量随机梯度下降算法。它通过不断迭代模型参数来优化损失函数。这里自动求梯度模块计算得来的梯度是一个批量样本的梯度和。我们将它除以批量大小来得到平均值。

image-20250304100914564

param.grad 和 param 的形状是完全相同的,这样可以逐元素计算梯度,并对 param 中的每个数值进行更新。

1
2
3
def sgd(params, lr, batch_size):
for param in params:
param.data -= lr * param.grad / batch_size # 注意这里更改param时用的param.data

8.训练模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
lr = 0.03
num_epochs = 3

for epoch in range(num_epochs): # 训练模型一共需要num_epochs个迭代周期
# 在每一个迭代周期中,会遍历训练数据所有样本一次(假设样本数能够被批量大小整除)。X和y分别是小批量样本的特征和标签
for X, y in data_iter(batch_size, features, labels):
l = squared_loss(linreg(X, w, b), y).sum() # l是有关小批量linreg(X, w, b)和y的损失

l.backward() # 小批量的损失对模型参数求梯度
sgd([w, b], lr, batch_size) # 使用小批量随机梯度下降迭代模型参数

# 不要忘了梯度清零
w.grad.data.zero_()
b.grad.data.zero_()

train_l = squared_loss(linreg(features, w, b), labels)

print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))

训练完成后,我们可以比较学到的参数和用来生成训练集的真实参数。它们应该很接近。

1
2
print(true_w, '\n', w)
print(true_b, '\n', b)