回归是能为一个或多个自变量与因变量之间的关系进行建模的方法,在机器学习领域中,想要预测一个数值时,就会涉及到回归问题,比如预测房屋、股票价格,预测销售量、需求量等。
为了解释线性回归,在房屋估价问题中,希望用房屋的面积和房屋的年龄来估算房屋的价格。为了实现这样的预测房价模型,首先需要收集一个真实的数据集,该数据集包含了房屋的价格,面积和年龄,也叫作训练集。预测的目标(房屋的价格)称为标签,预测所依据的自变量(面积、年龄)称为特征。
我们使用来表示数据集中的样本数,对索引为的样本,输入两个特征表示为:
对应的标签是 。
在预测房屋价格模型中,最终的预测价格可以表示成特征的加权和:
其中是权重,是偏置,如果没有偏置,该模型的表达能力会受到限制,我们的目标是通过给定的数据集,寻找模型的权重 和偏置 ,当输入包含 个特征时,预测结果用 表示:
将所有特征放到向量 中,所有权重放到向量 中,可以用点积来表示模型:
对于所有的特征集合 ,预测值 可以通过矩阵-向量乘法表示:
给定训练数据特征 和对应的已知标签 ,线性回归的目标是找到一组权重向量 和偏置 ,当从 中取新的样本特征时,这组权重向量和偏置能够使得新样本预测标签的误差尽可能小。
损失函数是用来量化目标的实际值与预测值之间的差距,在回归问题中常用的损失函数是平方误差函数,当样本 的预测值是 ,其相应的真实标签是为 时,平方误差为:
由于平方误差函数中的二次方项,估计值和观测值之间较大的差异将导致更大的损失,我们需计算在训练集 个样本上的损失均值:
当无法对损失函数直接进行求解时,我们仍可以有效的训练模型。使用梯度下降方法,其中最简单的用法是计算损失函数关于模型参数的导数,但是这样更新会计算数据集中所有样本的损失均值,执行可能很慢,因此在每次需要计算更新的时候随机抽取一小批样本,这种方法叫做小批量随机梯度下降。
在每次迭代中,先随机抽样一个小批量 ,它包含固定数量的训练样本,然后计算小批量的平均损失关于模型参数的导数:
代表每个小批量中的样本数,表示学习率,在训练了预先确定的若干迭代次数后,得到的估计值也不会使损失函数真正地达到最小值。 因为算法会使得损失向最小值缓慢收敛,但却不能在有限的步数内非常精确地达到最小值。
导入所需要的包,这里使用的Pytorch框架。
%matplotlib inline
import random
import torch
from d2l import torch as d2l
我们将根据带有噪声的线性模型构造一个人造数据集。 我们的任务是使用这个有限样本的数据集来恢复这个模型的参数。我们生成一个包含1000个样本的数据集,每个样本包含两个特征,合成数据集是。
使用线性模型参数 , 和噪声项 生成数据集和标签:
噪声项视为模型预测和标签时的潜在观测误差。
def synthetic_data(w, b, num_examples): #@save
"""生成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)
在features中每一行包含一个二维数据样本,它拥有两个特征,而labels中每一行包含一维标签
print('features:', features[0],'\nlabel:', labels[0])
features: tensor([-0.1413, 0.9253])
label: tensor([0.7524])
通过生成第二个特征 feature 和 labels 的散点图,可以看出两者线性关系
d2l.set_figsize()
d2l.plt.scatter(features[:, (1)].detach().numpy(), labels.detach().numpy(), 1);
训练模型时要对数据集进行遍历,每次抽取一小批量样本,并使用它们来更新我们的模型。定义一个函数, 该函数能打乱数据集中的样本并以小批量方式获取数据。
在下面的代码中,我们定义一个data_iter函数, 该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size的小批量。 每个小批量包含一组特征和标签。
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
for X, y in data_iter(batch_size, features, labels):
print(X, '\n', y)
break
tensor([[-0.0929, 0.3136],
[-0.4081, 0.5990],
[ 1.2006, -0.8625],
[ 2.8351, 1.2113],
[ 0.4811, 1.6206],
[-1.5946, 0.7590],
[-0.7296, 2.0734],
[ 1.4357, -0.4068],
[-1.1405, -0.0359],
[ 0.6749, 0.9677]])
tensor([[ 2.9562],
[ 1.3347],
[ 9.5308],
[ 5.7467],
[-0.3549],
[-1.5650],
[-4.3218],
[ 8.4510],
[ 2.0353],
[ 2.2612]])
我们通过从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重, 并将偏置初始化为0。
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
初始化参数之后,我们需要更新这些参数,直到这些参数足够拟合我们的数据。 每次更新都需要计算损失函数关于模型参数的梯度。 有了这个梯度,我们就可以向减小损失的方向更新每个参数。
def linreg(X, w, b): #@save
"""线性回归模型"""
return torch.matmul(X, w) + b
在实现中,我们需要将真实值 y 的形状转换为和预测值 y_hat 的形状相同。
def squared_loss(y_hat, y): #@save
"""均方损失"""
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
在每一步中,使用从数据集中随机抽取的一个小批量,然后根据参数计算损失的梯度。 接下来,朝着减少损失的方向更新我们的参数。因为我们计算的损失是一个批量样本的总和,所以我们用批量大小来规范化步长。
def sgd(params, lr, batch_size): #@save
"""小批量随机梯度下降"""
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()
在每次迭代中,我们读取一小批量训练样本,并通过我们的模型来获得一组预测。 计算完损失后,我们开始反向传播,存储每个参数的梯度。 最后,我们调用优化算法sgd来更新模型参数。
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) # X和y的小批量损失
# 因为l形状是(batch_size,1),而不是一个标量。l中的所有元素被加到一起,
# 并以此计算关于[w,b]的梯度
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 1, loss 0.026352
epoch 2, loss 0.000093
epoch 3, loss 0.000054
w的估计误差: tensor([ 0.0002, -0.0001], grad_fn=)
b的估计误差: tensor([0.0006], grad_fn=)