【PyTorch系列】实现简单线性回归

360影视 2025-01-12 00:09 2

摘要:本系列是对《动手学深度学习》2.0.0书中代码的复现,这本书由阿斯顿·张,李沐等编写,其介绍了深度学习领域的一些基本的背景知识,概念逻辑,使用PyTorch框架实现了一些基本的神经网络结构。

Tips

前言

1.本系列是对《动手学深度学习》2.0.0书中代码的复现,这本书由阿斯顿·张,李沐等编写,其介绍了深度学习领域的一些基本的背景知识,概念逻辑,使用PyTorch框架实现了一些基本的神经网络结构。

1.线性回归基本概念

1.1 回归

回归(regression)是能为一个或者多个自变量与因变量之间关系建模的一类方法,回归常用来表示输入与输出之间的关系。

线性回归(linear regression)在回归的各种标准工具中最简单并且最流行。线性回归基于几个基本的假设:首先,假设自变量x与因变量y之间的关系是线性的,也即y中的所有元素可以表示为x中所有元素的加权和,这里通常允许包含一些噪声数据;其次,设置的噪声一般比较正常,如遵循正态分布等。

1.2 损失函数

在开始考虑如何用模型拟合(fit)数据之前,需要确定一个拟合程度的度量。损失函数(loss function)能够量化目标的实际值与预测值之间的差距。通常会选择非负数作为损失,且数值越小表示损失越小,完美预测时的损失为0。回归问题中最常用的损失函数是平方误差函数,其定义为预测值与真实值之间误差的平方的一半。

回归问题中最常用的损失函数是平方误差函数,其定义为预测值与真实值之间误差的平方取平均。

1.3 解析解

线性回归刚好是一个很简单的优化问题。与在本书中所讲到的其他大部分模型不同,线性回归的解可以用一个公式简单地表达出来,这类解叫作解析解(analytical solution)。像线性回归这样的简单问题存在解析解,但并不是所有的问题都存在解析解。解析解可以进行很好的数学分析,但解析解对问题的限制很严格,导致它无法广泛应用在深度学习里。

1.4 随机梯度下降

即使在无法得到解析解的情况下,仍然可以有效地训练模型。在许多任务上,那些难以优化的模型效果要更好。因此,弄清楚如何训练这些难以优化的模型是非常重要的。

本书中主要使用的是一种名为梯度下降(gradient descent)的方法,这种方法几乎可以优化所有深度学习模型。它通过不断地在损失函数递减的方向上更新参数来降低误差。

梯度下降最简单的用法是计算损失函数(数据集中所有样本的损失均值)关于模型参数的导数(也即梯度)。但实际中的执行可能会非常慢:因为在每一次更新参数之前,必须遍历整个数据集。因此,通常会在每次需要计算更新的时候随机抽取一小批样本,这种变体叫做小批量随机梯度下降(minibatch stochastic gradient descent)。

2.线性回归实现

2.1 生成数据集

在synthetic_data函数中,首先生成了一个形状为(num_examples,len(w))的张量X,元素服从标准正态分布(均值为0,标准差为1)。在执行了矩阵的乘法之后,对y加上了一个符合正态分布的随机噪声数据。这个噪声数据可以视为模型预测和打标签时的潜在观测误差,为简化问题,这里将其标准差设置为0.01。整套流程下来,生成一个包含1000个样本的数据集,每个样本包含从标准正态分布中采样的2个特征,这里需要补充一下,在torch中对张量的乘法包含许多函数,其中torch.mv表示的是将一个二维的张量(矩阵)与一个一维的向量相乘,torch.mm表示的是将两个二维的张量(矩阵)相乘,torch.matmul表示的是将两个张量相乘,可以广泛应用于张量的乘法操作。最终输出中的第一张图是第二个特征散点图,第二张图是第一个特征散点图,因为第二个特征的权重更大,所以其关系更明显。%matplotlib inline import randomimport torchfrom d2l import torch as d2ldef 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 # 设置biasfeatures,labels=synthetic_data(true_w,true_b,1000)print('features:',features[0],'\nlabel:',labels[0])d2l.set_figsized2l.plt.scatter(features[:,1].detach.numpy,labels.detach.numpy,1)d2l.plt.scatter(features[:,0].detach.numpy,labels.detach.numpy,1)

输出如下:

features: tensor([-0.8129, 0.5262]) label: tensor([0.7790])

2.2 读取数据集

实际在进行模型训练的时候,需要对所有的数据集进行遍历,每次都是抽取小批量的样本大小。此时即有必要定义一个函数,用于每次从打乱的数据集中抽取小批量数据样本。上面使用data_iter迭代器实现从大量样本数据中每次随机抽取少量样本数据。

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=10for X,y in data_iter(batch_size,features,labels): print(X,'\n',y) # 每次打印10个数据样本(X,y) break

输出如下:

tensor([[ 1.0702, 0.4594], ... [-1.1399, 0.6513]]) tensor([[ 4.7894], ... [-0.2808]])

2.3 初始化模型参数

这一步来到了创建预测模型的第一步中,也即首先需要设置各种参数,包括权重参数w与偏置数b。

w=torch.normal(0,0.01,size=(2,1),requires_grad=True) # 生成一个形状为(2,1)的张量,然后设置requires_grad=True,表示在进行反向传播的时候,需要对这个参数进行求导b=torch.zeros(1,requires_grad=True) # 这里生成的是一个形状为(1,)的张量,表示的是一个标量,同样也是初始化为0,同时也需要设置requires_grad=True

2.4 定义模型

""""采用线性回归模型来计算输出""" return torch.matmul(X,w)+b # 使用广播机制计算张量与标量之间的加和

2.5 定义损失函数

def squared_loss(y_hat,y): """使用均方误差作为损失函数输出""" return (y_hat-y.reshape(y_hat.shape))**2/2

2.6 定义优化算法

在每一步中,使用从数据集中随机抽取的一个小批量,然后根据参数计算损失的梯度。接下来,朝着减少损失的方向更新参数。下面的函数实现小批量随机梯度下降更新。该函数接受模型参数集合、学习速率和批量大小作为输入。每一步更新的大小由学习速率lr决定。因为计算的损失是一个批量样本的总和,所以用批量大小(batch_size)来规范化步长,这样步长大小就不会取决于对批量大小的选择。

def sgd(params,lr,batch_size): """小批量随机梯度下降算法""" with torch.no_grad: # 禁用梯度计算 for param in params: print(param.grad) param-=lr*param.grad/batch_size # 规范步长 param.grad.zero_ # 梯度清零,避免累加

2.7 训练模型

在每次迭代中,首先读取一小批量训练样本,通过模型来获得一组预测。计算完损失后,开始进行反向传播,存储每个参数的梯度。之后,通过调用优化算法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) # 计算损失函数 l.sum.backward sgd([w,b],lr,batch_size) # 根据梯度更新模型参数w与b with torch.no_grad: train_l=loss(net(features,w,b),labels) print(f'epoch {epoch+1},loss {float(train_l.mean):f}')

输出如下:

tensor([[ 8.9830], [34.8158]])tensor([-27.0298])...tensor([[0.0377], [0.0110]])tensor([-0.0273])epoch 3,loss 0.000052

3.线性回归简洁实现

之所以称为线性回归的简洁实现,主要是相较于原来的单纯使用张量与自动微分构建模型,在这里还使用的是一些现有的创建数据、读取数据以及处理数据等的API接口。

在PyTorch中,data模块提供了数据处理工具,nn模块定义了大量的神经网络层和常见损失函数。具体的逻辑同第三部分的逻辑一样,其代码显示如下:

# 1.生成数据集import numpy as npimport torchfrom torch.utils import datafrom d2l import torch as d2ltrue_w = torch.tensor([2, -3.4]) # 生成权重张量true_b = 4.2 # 生成偏置features, labels = d2l.synthetic_data(true_w, true_b, 1000) # 生成1000个样本,内置函数是通过使用一种线性的方式生成的# 2.读取数据集def load_array(data_arrays, batch_size, is_train=True): dataset = data.TensorDataset(*data_arrays) # 转换为TensorDataset对象 return data.DataLoader(dataset, batch_size, shuffle=is_train) # 返回一个DataLoader对象,用于批量加载数据batch_size = 10data_iter = load_array((features, labels), batch_size)# 调用load_array函数,读取数据集,并返回一个DataLoader对象next(iter(data_iter)) # 创建一个迭代器对象# 3.定义模型from torch import nnnet=nn.Sequential(nn.Linear(2, 1)) # Sequential是一个容器,它按照顺序执行其中的模块,这里的Sequential只包含一个线性层# 4.初始化模型参数net[0].weight.data.uniform_(-0.1, 0.1) # 将输入的第一层的参数初始化在-0.1与0.1之间均匀分布的随机数net[0].bias.data.fill_(0) # 将数据张量的值全部都初始化为0# 5.定义损失函数loss=nn.MSELoss# 6.定义优化算法trianer=torch.optim.SGD(net.parameters,lr=0.03)# 7.进行模型训练num_epochs=3for epoch in range(num_epochs): for X,y in data_iter: l=loss(net(X),y) # 计算损失 trianer.zero_grad # 梯度清零 l.backward # 计算梯度 trianer.step # 更新参数 l=loss(net(features),labels) print(f'epoch {epoch + 1}, loss {l:f}')w=net[0].weight.dataprint('w的估计误差:',true_w-w.reshape(true_w.shape)) b=net[0].bias.dataprint('b的估计误差:',true_b-b)

来源:炫明教育

相关推荐