动手学深度学习——线性回归(原理解释+代码详解)
线性回归模型:y=wx+bdef linreg(X , w , b) : #@save """线性回归模型""" return torch . matmul(X , w) + b采取平方损失函数将y的形状统一为y_hat# y_hat是预测值, y是真实值 def squared_loss(y_hat , y) : #@save """均方损失""" # 返回平方误差 return(y_hat -
目录
1、线性回归
回归是能够为一个或多个自变量与因变量之间关系建模的一类方法。
当函数为未知参数的线性函数时,称为线性回归模型。
机器学习领域中大多数任务通常都与预测有关,当我们预测一个数值时,就会涉及到回归问题。常见的例子有:预测价格,预测需求等。
2、线性回归模型
假设自变量x与因变量y满足线性关系,数学中一般的线性方程为:y=ax+b
我们将y可以表示为x中元素的加权和,并且允许包含观测值的一些噪声,可以将式子设为:
在机器学习中,线性回归模型建立的步骤一般是:
先假设一个线性模型——>计算该模型预测值与实际值的差距loss(损失函数)——>通过优化算法(如随机梯度下降)来更新参数如w,b来降低误差
2.1 线性模型
一般我们将线性假设可以表示为特征的加权和:
x表示元素的特征。
w称为权重,权重决定了每个特征对我们预测值的影响。
b称为偏置或者偏移量,偏移量指当所有特征值为0时,预测值应该为多少。(对于偏置的加入可以帮助模型有更好的泛化能力,即该模型在未见过的数据也有较好的表现)
如果是房屋的价格可以表示为:
2.2 损失函数
对于我们假设的线性模型,预测值可能离实际值还有差距,为了表示这一差距,我们引入损失函数这一概念。
损失函数能够量化目标的实际值与预测值之间的差距,通常选择非负数作为损失,且数值越小表示损失越小。
2.2.1 平方差损失函数
回归问题中最常用的损失函数是平方误差函数。
常数1/2便于我们在对损失函数(平方项)求导时,能够将系数化为1
2.2.2 整个数据集上的损失函数
为了度量模型能够在整个数据集上的质量,我们需计算训练集n个样本的损失均值。
即上式求和再除以n(含参数w,b的为展开式)
我们的目标则是为了寻得一组参数w*,b*,使最小化训练样本的总损失
2.3 随机梯度下降
如上文所说,我们为了使总损失最小,我们需要对参数w和b进行更新。
而更新方法则是使用到一种叫做梯度下降的方法,它不断在损失函数递减的方向上更新参数。
梯度下降的简单用法是计算损失函数关于模型参数的导数(可以称为梯度),损失函数为数据集中所有样本的损失均值。
实际过程中由于数据集较大,每次更新参数会遍历整个数据集,执行会非常缓慢。我们通常会在每次更新的时候抽取一小批量样本,这种变体叫做小批量随机梯度下降。
每次迭代中,该过程为:
1、首先随机抽取一个小批量β,它是由固定数量的训练样本组成
2、计算小批量的平均损失关于模型参数的导数
3、将梯度乘以一个预先确定的正数η,并从当前参数的值中减掉
其中∂表示偏导数,η表示学习率,批量大小和学习率的值通常是手动预先设定的,这些可以调整但不在训练过程中更新的参数称为超参数。
2.4 用模型进行预测
给定“已学习”的线性回归模型
我们现在可以通过房屋面积x1和房龄x2来估计一个(未包含在训练数据中的)新房屋价格。
给定特征估计目标的过程通常称为预测或推断。
3、线性回归的简单实现
了解到关键思路后,我们可以通过代码来实现线性回归。
下面的代码有些是调用或者定义的函数,刚开始学习的时候不必纠结代码,主要了解代码的整体实现过程。
步骤 | 函数 |
---|---|
生成数据集 | |
读取数据集 | |
初始化模型参数 | |
定义模型 | |
定义损失函数 | |
定义优化算法 | |
训练 |
3.1 生成数据集
生成一个包含1000个样本的数据集,每个样本包含从标准正态分布中采用的2个特征。
线性模型参数
生成的数据集及标签:
噪声项ε可以视为模型预测和标签时的潜在观测误差,它服从均值为0的正态分布,标准差为0.01。
#matplotlib包用于作图,且设置成嵌入显示
%matplotlib inline
import random
import torch
from d2l import torch as d2l
def synthetic_data(w, b, num_examples): #@save
"""生成y=Xw+b+噪声"""
# normal:返回一个张量,包含从指定均值means和标准差std的离散正态分布抽取的一组随机数
X = torch.normal(0, 1, (num_examples, len(w))) #均值为0,方差为1
# matmul:矩阵乘法
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中的每一行都包含一维标签值(一个标量)。
3.2 读取数据集
训练模型要对数据集进行遍历,每次抽取一小批量,并使用它们来更新模型。
data_iter函数,该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size的小批量。每个小批量包含一组特征和标签。
random.shuffle()
——打乱顺序
indices[i: min(i + batch_size, num_examples)])
——按batch_size进行切片,得到小批量
# features:特征矩阵,labels:标签向量
def data_iter(batch_size, features, labels):
# len():求矩阵时长度相当于输出行的数目
num_examples = len(features)
# 生成标号,列表形式储存
indices = list(range(num_examples))
# 这些样本是随机读取的,没有特定的顺序
random.shuffle(indices)
# 构造随机样本,打乱样本,等间隔访问,达到随机
# 从0开始,到num_exampls结束,每次间隔batc_size
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(
indices[i: min(i + batch_size, num_examples)])
# yield:返回一个值,并且记住这个返回的位置,下次迭代从这个位置开始
yield features[batch_indices], labels[batch_indices]
3.3 初始化模型参数
我们通过从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重, 并将偏置初始化为0。
初始化参数后,我们的任务是更新这些参数,直到这些参数足够拟合我们的数据。
每次更新都需要计算损失函数关于模型参数的梯度。 有了这个梯度,我们就可以向减小损失的方向更新每个参数。
torch.normal(means, std, out=None)
均值,标准差,可选的输出张量
# 此时size定义两行一列
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
# requires_grad用于说明当前向量是否需要在计算中保留对应的梯度信息
b = torch.zeros(1, requires_grad=True)
3.4 定义模型
线性回归模型:y=wx+b
def linreg(X, w, b): #@save
"""线性回归模型"""
return torch.matmul(X, w) + b
3.5 定义损失函数
采取平方损失函数
y.reshape(y_hat.shape)
将y的形状统一为y_hat
# y_hat是预测值, y是真实值
def squared_loss(y_hat, y): #@save
"""均方损失"""
# 返回平方误差
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
3.6 定义优化算法
小批量随机梯度下降:在每一步中,使用从数据集中抽取的一个小批量,然后根据参数计算损失的梯度。接下来,朝着减少损失的方向更新我们的参数。
每一步更新的大小由学习速率lr决定
with torch.no_grad()
强制之后的内容不计算梯度。
在使用pytorch时,并不是所有的操作都需要进行计算图的生成(计算过程的构建,以便梯度反向传播等操作)。而对于tensor的计算操作,默认是要进行计算图的构建的。
def sgd(params, lr, batch_size): #@save
"""小批量随机梯度下降"""
with torch.no_grad():
for param in params:
# param.grad是求梯度
param -= lr * param.grad / batch_size
# 因为pytorch不会自动将梯度设置为0,设置为零后下次计算就不会与上次相关了
param.grad.zero_()
3.7 训练
训练过程可以概括为:
- 在每次迭代中,读取一小批量训练样本,并通过模型来获得一组预测。
- 计算完损失后,开始反向传播,存储每个参数的梯度。
- 调用优化算法sgd来更新模型参数。
lr = 0.03 #学习率(超参数)
num_epochs = 3 #把整个数据扫3遍
net = linreg #模型生成函数
loss = squared_loss #均方损失函数
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
# 'X'和'y'的小批量损失
# 因为'l'形状是('batch_size',1),而不是一个标量。l中的所有元素被加到一起,
# 并以此计算关于[w,b]的梯度
l = loss(net(X, w, b), y) # X和y的小批量损失
# 求和之后算梯度,这里backward()会对w和b进行求导
l.sum().backward()
sgd([w, b], lr, batch_size) # 使用参数的梯度更新参数
# net(features, 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}')
print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')
输出:损失与估计误差
4、线性回归的简洁实现
这里采用深度学习框架来简洁实现线性回归模型,我们不必单独分配参数、不必定义我们的损失函数,也不必手动实现小批量随机梯度下降。,可以直接调用API来进行训练。
API(Application Programming Interface)应用程序接口。
4.1 生成数据集
d2l
包:由李沐老师等人开发的《动手学深度学习》配套的包,提供一些数学运算工具。
synthetic_data()
:合成数据函数。
import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l
true_w = torch.tensor([2, -3.4])
true_b = 4.2
# 合成数据
features, labels = d2l.synthetic_data(true_w, true_b, 1000)
next(iter(data_iter))
使用iter构造Python迭代器,并使用next从迭代器中获取第一项。
4.2 读取数据集
TensorDataset
将张量的第一个维度视为数据集大小的维度,数据集在传入DataLoader
后,该维度也是batch_size
所在的维度。
shuffle
表示对数据打乱顺序。
def load_array(data_arrays, batch_size, is_train=True): #@save
"""构造一个PyTorch数据迭代器"""
# 将传入的特征和标签作为list传到TensorDataset,里面得到一个pytorch的数据集
dataset = data.TensorDataset(*data_arrays)
# 调用Dataloader每次从dataset里面挑选batch_size个样本出来
return data.DataLoader(dataset, batch_size, shuffle=is_train)
batch_size = 10
# 将特征和标签传入load_array
data_iter = load_array((features, labels), batch_size)
4.3 定义模型
对于标准深度学习模型,我们可以使用框架的预定义好的层。
首先定义一个模型变量net,它是一个Sequential类的实例。 Sequential类将多个层串联在一起。 当给定输入数据时,Sequential实例将数据传入到第一层, 然后将第一层的输出作为第二层的输入,以此类推。
Sequential类
可以看成一个容器包装各层,当一个模型较简单的时候,我们可以使用torch.nn.Sequential类来实现简单的顺序连接模型。
# nn是神经网络的缩写
from torch import nn
# 第一个指定输入特征形状为2,第二个指定输出特征形状为单个标量为1
net = nn.Sequential(nn.Linear(2, 1))
4.4 初始化模型参数
在使用net之前,我们需要初始化模型参数。在这里,我们指定每个权重参数应该从均值为0、标准差为0.01的正态分布中随机采样, 偏置参数将初始化为零。
# net[0] 表示第0层,.weight访问w,data就是w的值,下划线的意思是,使用正态分布,替换掉权重data的值
net[0].weight.data.normal_(0, 0.01)
# 使用填充0,data是偏置b,替换掉偏差data的值
net[0].bias.data.fill_(0)
4.5 定义损失函数
计算均方误差使用的是MSELoss类,也称为平方L2范数。 默认情况下,它返回所有样本损失的平均值。
loss = nn.MSELoss()
4.6 定义优化算法
这里使用小批量随机梯度下降算法,该算法在PyTorch在optim模块中。
当我们实例化一个SGD实例时,我们要指定优化的参数 (可通过net.parameters()从我们的模型中获得)以及优化算法所需的超参数字典。 小批量随机梯度下降只需要设置lr值,这里设置为0.03。
net.parameters()
是一个生成器,用于生成模型中的参数。其中,第一个参数是生成器的第一个元素,第二个参数是该元素的具体参数值。该函数用循环获取所有参数,并将其作为参数传递。
# net.parameters():net里面的所有参数,包括w,b,然后指定学习率lr
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
4.7 训练
在每个迭代周期里,我们将完整遍历一次数据集(train_data), 不停地从中获取一个小批量的输入和相应的标签。 对于每一个小批量,我们会进行以下步骤:
- 通过调用net(X)生成预测并计算损失l(前向传播)。
- 通过进行反向传播来计算梯度。
- 通过调用优化器来更新模型参数。
num_epochs = 3 #迭代3次
for epoch in range(num_epochs):
for X, y in data_iter:
l = loss(net(X) ,y) #因为net自带参数,所以和之前不同的是不需要再传进去w和b
trainer.zero_grad() #梯度清零
l.backward() #计算梯度
trainer.step() #调用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)
输出:损失和w、b的估计误差
4.8 小结
- 可以使用PyTorch的高级API更简洁地实现模型。
- 在PyTorch中,data模块提供了数据处理工具,nn模块定义了大量的神经网络层和常见损失函数。
参考资料:
[1]动手学深度学习:http://zh-v2.d2l.ai/index.html
[2]跟李沐学AI:https://space.bilibili.com/1567748478
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)