Linear Regression 2 | 线性回归 2

Albert Wang / 2025-07-12 / 400 Words/has been Read   Times


上一篇博客主要推导了线性回归的概念,这篇博客从 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() 

显示的效果如下图,

image-20240309030246676

定义一个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}')

训练的结果如下,

image-20240309030246676

线性回归的简洁实现 #

下面主要根据 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))

执行结果如下,

image-20240309030246676

然后模型定义基本也都有现成的实现,我们可以直接调用接口。这里 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)

我们比较拟合出的参数和真实的结果,发现误差也很接近,如下图。

image-20240309030246676

Last modified on 2025-07-12