线性回归

1.线性回归的从零开始实现

1.1 构造数据集

以参数w=[2,−3.4]⊤、b=4.2和噪声项ϵ生成数据集。

标签:y=Xw+b+ϵ

torch.normal():生成一个服从正态分布的随机张量。

torch.matmul:计算两个张量矩阵相乘(可以用利用广播机制进行不同维度的相乘操作)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def synthetic_data(w, b, num_examples):  
"""生成 y = Xw + b + 噪声。"""
X = torch.normal(0, 1, (num_examples, len(w)))
#torch.normal()生成一个服从正态分布(均值为0,标准差为1)的随机张量X,大小为(num_examples, len(w))
#其中num_examples是样本数量,len(w)是特征数量。
y = torch.matmul(X, w) + b
#函数通过矩阵乘法计算目标变量y。y是由X和w之间的线性关系构造的,并且加上了偏差项b。
y += torch.normal(0, 0.01, y.shape)
#函数为目标变量y添加了一个服从正态分布(均值为0,标准差为0.01)的随机噪声,以模拟真实数据中的噪声。
return X, y.reshape((-1, 1))

true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)

1.2 观察特征的相关性

将features张量中第二列的数据作为x轴,labels张量的数据作为y轴,绘制出散点图,散点图的每个点的大小为1。

1
2
d2l.plt.scatter(features[:,1].detach().numpy(),
labels.detach().numpy(), 1);#detach()函数用于从计算图中分离出这部分数据,以防止在绘制图形时产生梯度计算

img

1.3 构造一个PyTorch数据迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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_size个步长进行迭代,直到遍历完所有样本。
batch_indices = torch.tensor(indices[i:min(i +
batch_size, num_examples)])
#从打乱后的索引列表中选择一个批次的索引。
#注意,最后一个批次可能不足batch_size,因此使用min(i + batch_size, num_examples)来确保不超出数据集的边界。
yield features[batch_indices], labels[batch_indices]

batch_size = 10
for X, y in data_iter(batch_size, features, labels):
print(X, '\n', y)
break

out:
tensor([[ 0.9374, -0.1930],
[ 0.1325, -1.0253],
[ 0.9573, 0.4727],
[-0.3795, 0.4407],
[-0.4063, 1.0375],
[ 0.7656, -1.9743],
[ 1.4891, 1.4386],
[-0.9692, -0.4879],
[ 2.5848, 1.3103],
[-0.5822, 0.5608]])
tensor([[ 6.7309],
[ 7.9584],
[ 4.4986],
[ 1.9576],
[-0.1413],
[12.4321],
[ 2.2723],
[ 3.9170],
[ 4.9298],
[ 1.1197]])

1.4 初始化模型参数

1
2
3
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
#requires_grad=True:表示这个张量是可训练的,当执行反向传播时,PyTorch会自动计算关于w的梯度,并在优化算法中更新这个张量。

1.5 定义模型

1
2
3
def linreg(X, w, b):  
"""线性回归模型。"""
return torch.matmul(X, w) + b

1.6 定义损失函数

1
2
3
def squared_loss(y_hat, y):  #y_hat:预测值
"""均方损失。"""
return (y_hat - y.reshape(y_hat.shape))**2 / 2

1.7 定义优化算法

1
2
3
4
5
6
def sgd(params, lr, batch_size):  #params(模型的可训练参数)、lr(学习率)和batch_size(批大小)
"""小批量随机梯度下降。"""
with torch.no_grad():#上下文管理器,用于包裹在其内部的操作,告诉PyTorch在这个上下文中不要计算梯度
for param in params:
param -= lr * param.grad / batch_size#梯度乘以学习率,再除以批大小来更新参数
param.grad.zero_()#将参数的梯度清零,在PyTorch中,梯度是累积的,需要手动将梯度置零

1.8 训练过程

1
2
3
4
5
6
7
8
9
10
11
12
13
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()# l是向量,用sum转换成标量,再对损失进行反向传播,计算模型参数 w 和 b 的梯度。
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}')

1.9 评估训练效果

1
2
print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')

2.线性回归的简洁实现

2.1 构造数据集

1
2
3
4
5
6
7
8
import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l

true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000)

2.2 构造一个PyTorch数据迭代器

1
2
3
4
5
6
7
8
9
10
def load_array(data_arrays, batch_size, is_train=True):  
"""构造一个PyTorch数据迭代器。"""
dataset = data.TensorDataset(*data_arrays)#用于将特征数据和标签数据打包成一个数据集对象
return data.DataLoader(dataset, batch_size, shuffle=is_train)#从数据集中按照批大小 batch_size 加载数据
#shuffle=is_train 表示在训练模式下(is_train=True)将数据打乱顺序

batch_size = 10
data_iter = load_array((features, labels), batch_size)

next(iter(data_iter))

2.3 构建模型

nn.Sequential:PyTorch中的一个容器类,它按照顺序组合各种神经网络的层。

nn.Linear(2, 1):nn.Linear是一个全连接层(线性层),它接收两个参数:输入特征的数量(输入维度)和输出特征的数量(输出维度)。因此该层将实现一个将输入维度为2的数据映射为输出维度为1的线性变换(线性回归模型)。

1
2
3
4
5
6
7
8
9
from torch import nn

net = nn.Sequential(nn.Linear(2, 1))
#初始化模型参数
net[0].weight.data.normal_(0, 0.01)#net[0]表示模型中的第一个层
net[0].bias.data.fill_(0)

loss = nn.MSELoss()#定义损失函数
trainer = torch.optim.SGD(net.parameters(), lr=0.03)#实例化SGD实例

2.4 训练过程

1
2
3
4
5
6
7
8
9
num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter:
l = loss(net(X), y)
trainer.zero_grad()
l.backward() #pytorch自动对l求sum,无需再求sum
trainer.step()
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')