原理
线性回归是一种用于建立变量之间线性关系的统计模型。其原理基于以下假设:
-
线性关系:线性回归假设自变量(输入变量)和因变量(输出变量)之间存在一个线性关系。
-
独立性:自变量与误差项之间应该是独立的,即自变量的取值不受误差项的影响。
-
同方差性:对于所有自变量的取值,误差项的方差应该是恒定的。
-
无多重共线性:自变量之间应该是线性独立的,不存在严格的线性依赖关系。
基于这些假设,线性回归的目标是找到最佳拟合直线,以最小化观测数据与模型之间的误差。常用的拟合准则是最小二乘法,即最小化观测数据的残差平方和。
一元线性回归
具体实现步骤文字说明
-
收集数据:获取包含自变量和因变量的训练数据样本。
-
特征选择:根据实际问题选择合适的自变量作为模型输入。
-
模型建立:建立线性回归模型,假设因变量与自变量之间存在线性关系。
-
参数估计:使用最小二乘法或其他优化算法,估计模型参数,即拟合直线的斜率和截距。
-
模型评估:通过评估拟合程度和统计指标(如均方误差、决定系数等),判断模型的好坏。
-
预测与应用:利用训练好的模型进行预测,对新的自变量进行因变量的估计或预测。
具体实现
%matplotlib inline
import random
import torch
from d2l import torch as d2l
生成数据集,torch.normal函数,该函数返回从单独的正态分布中提取的随机数的张量,该正态分布的均值是mean,也就是噪声。标准差是std。torch.matmul函数,是tensor的乘法,输入可以是高维的。当输入都是二维时,就是普通的矩阵乘法,和tensor.mm函数用法相同。synthetic_data函数是d2l的封装函数,具体作用是根据相关参数,生成随机数据集。
def synthetic_data(w, b, num_examples): #@save
"""生成 y = Xw + b + 噪声。"""
X = torch.normal(0, 1, (num_examples, len(w))) #最后一个参数指定X维度,len(w)特征个数
y = torch.matmul(X, w) + b
y += torch.normal(0, 0.01, y.shape)
return X, y.reshape((-1, 1))
features中的每一行都包含一个二维数据样本,labels中的每一行都包含一维标签值(一个标量)
print('features:', features[0],'\nlabel:', labels[0])
d21.set figsize函数是Deep Learning框架中的一个函数,用于设置Matplotlib图像的大小。该函数可以接受两个参数,分别为宽度和高度,单位为英寸。使用该函数可以方便地调整图形的大小,以适应不同的显示设备和打印需求。plt.scatter( ),它用于散点图的绘制。d2l.plt.scatter 中的参数其实就是matplotlib中的scatter。
d2l.set_figsize()
d2l.plt.scatter(features[:, (1)].detach().numpy(), labels.detach().numpy(), 1);
定义一个data iter函数,该函数接收批量大小特征矩阵和标签向量作为输入,生成大小为batch_size的小批量]。每个小批量包含一组特征和标签。random.shuffle()用于将一个列表中的元素打乱顺序,这个方法不会生成新的列表,只是将原列表的次序打乱。
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相等。
batch_size = 10
for X, y in data_iter(batch_size, features, labels):
print(X, '\n', y)
break
初始化模型参数,初始值为0.后续就是逐步更新参数,使得其更加符合我们的模型。
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
#w = torch.zeros((2,1) ,requires_grad=True)#权重变为0
b = torch.zeros(1, requires_grad=True)
定义模型,主要是将模型的输入和参数同模型的输出结合起来,注意存在偏置b
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
定义优化算法
在pytorch中,tensor有一个requires_grad参数,如果设置为True,则反向传播时,该tensor就会自动求导。tensor的requires_grad的属性默认为False,若一个节点(叶子变量:自己创建的tensor)requires_grad被设置为True,那么所有依赖它的节点requires_grad都为True(即使其他相依赖的tensor的requires_grad = False)。当requires_grad设置为False时,反向传播时就不会自动求导了,因此大大节约了显存或者说内存。
torch.no_grad的作用:在该模块下,所有计算得出的tensor的requires_grad都自动设置为False。即使一个tensor(命名为x)的requires_grad = True,在with torch.no_grad计算,由x得到的新tensor(命名为w-标量)requires_grad也为False,且grad_fn也为None,即不会对w求导。
param.grad其实是batch中所有样本的grad总和,所以这个时候除以batch_size就是相当于取一个平均值,这样就算下一次传入的batch_size改变了,最后也不会影响得到的平均数。
def sgd(params, lr, batch_size): #@save
"""小批量随机梯度下降。"""
with torch.no_grad(): #当前计算不需要反向传播
for param in params:
param -= lr * param.grad / batch_size #手动实现参数更新
param.grad.zero_()
训练
数据准备:准备训练数据集,包括输入特征和对应的输出值。通常将输入特征表示为一个矩阵X,每一行表示一个样本的特征值,输出值表示为一个向量y。
参数初始化:初始化模型参数,包括权重w和偏置b。
定义模型:定义线性回归模型,使用参数w和b构建模型的预测函数,一般为线性函数 y = Xw + b。
定义损失函数:定义损失函数,衡量模型预测值与真实值之间的差距。线性回归通常使用均方误差(MSE)作为损失函数,即损失函数为 L = (1/2m) * sum((y_pred - y)^2),其中m为样本数。
定义优化算法:定义优化算法,用于更新模型参数以最小化损失函数。常用的优化算法是梯度下降法,通过计算损失函数对参数的梯度来更新参数。梯度下降法的更新公式为 w = w - learning_rate * gradient_w,b = b - learning_rate * gradient_b,其中learning_rate为学习率,gradient_w和gradient_b分别为损失函数对w和b的梯度。
训练模型:使用训练数据集进行模型训练。迭代训练过程中,根据当前模型参数计算预测值,并计算损失函数。然后根据损失函数的梯度更新模型参数,重复迭代直到收敛或达到指定的迭代次数。
lr = 0.2 #0.1效果也不错
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}')
参数比较
print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')
实验小结
PyTorch提供的函数往往具有更好的数值稳定性,要善于使用pytorch提供的函数,更简洁地实现softmax回归等方法。
从零开始实现了线性回归模型。首先,我们使用了一个简单的数据集来训练模型,并使用梯度下降算法来更新模型的参数。我们还实现了模型的预测函数和评估函数,以便对模型的性能进行评估。通过实验,我们发现,线性回归模型可以很好地拟合线性关系的数据。当我们使用更多的训练样本和更小的学习率时,模型的拟合效果更好。然而,当训练样本较少或学习率过大时,模型的拟合效果会变差。
通过本次实验,使我对线性回归模型有了更深入的理解。