上一篇博客主要推导了线性回归的概念,这篇博客从 0 开始实现线性回归。这里的例子来自于李沐老师的动手学机器学习。
线性回归的从零开始实现 #
在开始之前我们首先需要导入一些依赖的库
%matplotlib inline
import random
import torch
from IPython import display
from matplotlib import pyplot as plt
from matplotlib_inline import backend_inline
根据带有噪声的线性模型构造一个人造数据集。 我们使用线性模型参数𝐰=[2,−3.4]⊤、𝑏=4.2 和噪声项ϵ生成数据集及其标签:
def synthetic_data(w, b, num_examples):
"""生成 y = Xw + b + 噪声。"""
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))
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
然后使用下面的代码块就可以把随机生成的数据显示出来
def set_figsize(figsize=(3.5, 2.5)):
backend_inline.set_matplotlib_formats('svg')
plt.rcParams['figure.figsize'] = figsize
set_figsize()
plt.scatter(features[:, (1)].detach().numpy(),
labels.detach().numpy(), 1);
plt.show()
显示的效果如下图,
定义一个data_iter
函数, 该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size
的小批量。
下面代码中使用了 shuffle 函数把所有的样本数据进行随机打乱,这里的打乱是按照行去做打乱,然后便利这些样本,每次从中取出一小批的数据来做训练。 yield 类似于一个迭代器,会不停地从特征和标签中迭代数据出来。
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]
batch_size = 10
到目前为止数据就已经准备好了,然后就可以开始定义模型。因为是反向传播,所以我们需要事先给定一个梯度,损失函数就采用最小二乘法的均方误差。同样因为梯度的方向指向增长最快的方向,所以我们需要按照梯度的反方向来进行梯度下降,从而保证损失函数最小。
在 sgd 函数中需要知道的是梯度就保存在 grad 中,并且每次执行完都要把梯度设为 0,这样才能保证每次训练都不会产生相关。batch_size 就是最小二乘法中的 n
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
def linreg(X, w, b):
"""线性回归模型。"""
return torch.matmul(X, w) + b
def squared_loss(y_hat, y):
"""均方损失。"""
return (y_hat - y.reshape(y_hat.shape))**2 / 2
def sgd(params, lr, batch_size):
"""小批量随机梯度下降。"""
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()
在训练开始之前我们还需要指定一些超参数。首先是学习率,因为要沿着梯度的反方向进行训练,但是每次沿着这个方向走多远需要事先由我们认为指定,这个就是学习率 lr;然后是训练的轮数 epoch,这里训练 3 轮。
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}')
训练的结果如下,
线性回归的简洁实现 #
下面主要根据 pytorch 中已有的库来简化线性回归的实现。首先调用框架中现有的API来读取数据,只需要导入 data 的库
from torch.utils import data
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 = 10
data_iter = load_array((features, labels), batch_size)
next(iter(data_iter))
执行结果如下,
然后模型定义基本也都有现成的实现,我们可以直接调用接口。这里 Linear 是调用了神经网络里的一个全连接层,它的输入纬度是 2,输出维度是 1.所以也可以认为线性回归是一个层数为 1 的神经网络。然后把它放到 Sequential 的容器里,
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))
#初始化模型参数
net[0].weight.data.normal_(0, 0.01)
loss = nn.MSELoss()
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
net[0].bias.data.fill_(0)
最后训练过程和之前类似
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()
trainer.step()
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')
w = net[0].weight.data
print('w的估计误差:', true_w - w.reshape(true_w.shape))
b = net[0].bias.data
print('b的估计误差:', true_b - b)
我们比较拟合出的参数和真实的结果,发现误差也很接近,如下图。
Last modified on 2025-07-12