动手学深度学习(二)线性神经网络
回归任务是指对变量进行预测的任务。
目录
回归任务是指对连续变量进行预测的任务。
一、线性回归
线性回归模型是一种常用的统计学习方法,用于分析自变量与因变量之间的关系。它通过建立一个关于自变量和因变量的线性方程,来对未知数据进行预测。
1.1 线性模型
举个例子,房价预测模型:
- 假设1︰影响房价的关键因素是卧室个数,卫生间个数和居住面积,记为x1,x2,x3。
- 假设2:成交价是关键因素的加权和,
权重和偏差
的实际值在后面决定。
- 给定n维输入,
,向量x对应于单个数据样本的特征。
- 线性模型有一个n维权重和一个标量偏差,
,
。权重
决定了每个特征对预测值的影响。偏置
是指当所有的特征都取0时,预测值应为多少。
- 输出是输入的加权和,
。我们常用
表示预测值。
则,该房价预测模型为:,这是一个线性预测模型。给定一个数据集(如x),我们的目标就是寻找模型的权重
和偏置
,使得根据模型做出的预测大体符合数据中真实价格
。也是就说最佳的权重
和偏置
有能力使得预测值
逼近真实值
,找到最佳的权重
和偏置
这是我们的最终目的。
1.2 损失函数(衡量预估质量)
用于比较真实值和预估值的差异,即以特定规则计算真实值和预估值的差值,例如房屋售价和估价。
假设是真实值,
是预测值,平方差损失为
,我们以该函数作为损失函数。
设训练集有n个样本,则这n个样本的损失均值为
Q:那么损失函数,对我们找到最优的权重
和偏置
有什么帮助呢?
我们可以看到,最佳的预测值与真实值之间的损失值一定是尽可能小的,因此我们只要求得最小的损失值,那么得到这个损失值的权重
和偏置
一定是最优的。
Q:怎么求得最小的损失值呢?
如,平方差损失函数是一个凹函数,那么求解最小的损失值,我们只需要将该函数关于
的偏导数设为0,求导即可。求解得到的
就是最优的权重
。预测出的预估值
也就最接近真实值。这类解称为解析解。
二、基础优化算法(梯度下降算法)
在绝大多数的情况下,损失函数是很复杂的(比如逻辑回归),根本无法得到参数估计值的表达式,也就无从获取没有显示解(解析解)。
此需要一种对大多数函数都适用的方法,这就引出了“梯度下降算法”,这种方法几乎可以优化所有深度学习模型。它通过不断地在损失函数递减的方向上更新参数来降低误差(原理)。

2.1 梯度下降公式
首先,我们需要确定初始化模型的参数,接下来重复迭代更新参数t=1、2、3、....、n,更新权重的公式为:

其中,为上一次更新权重的结果,
为学习率(这是一个超参数,决定了每次参数更新的步长),
为损失函数递增的方向(注意公式中为负)。
2.2 选择学习率
梯度下降的过程宛如一个人在走下山路,一步一步地接近谷底,学习率相当于这个人的步长。

学习率的选取不易过大,也不宜过小。学习率选取过大会使得权重更新的过程一直在震荡,而不是真正的在下降。学习率选取过小,会使得权重更新的过程十分缓慢,影响效率。
2.3 小批量随机梯度下降
一个神经网络模型的训练可能需要几分钟至数个小时,我们可以采用小批量随机梯度下降的方式来加快这一过程。
在整个训练集上计算梯度太昂贵了,因此可以随机采用 个样本
来求取整个训练集的近似损失(原理)。求近似损失公式为:

其中, 是批量大小,另一个重要的超参数。
Q:如何选择批量大小?
选择批量大小不能太小,也不能太大。批量大小选择过小,则每次计算量太小,不适合并行来最大利用计算资源。批量大小选择过大,内存消耗增加浪费计算,例如如果所有样本都是相同的。
三、线性回归的从零开始实现(代码实现)
3.1 导包
import random # 用于随机梯度下降
import torch
from d2l.torch import d2l # 这是动手学深度学习这本书中,李沐大神封装的一些方法
3.2 生成数据集
首先,我们根据带有噪声的线性模型构造一个人造数据集,我们的目的是通过这个数据集来还原线性模型中正确的参数。
我们使用线性模型参数 ,
和噪声项
生成数据集及其标签。
![]()
# 生成数据集
def synthetic_data(w, b, num_examples):
"""生成 y=Xw + b + 噪声"""
X = torch.normal(0, 1, (num_examples, len(w))) # 正态分布(均值为0,标准差为1)
y = torch.matmul(X, w) + b # 矩阵相乘
y += torch.normal(0, 0.01, y.shape) # 加入噪声项
# 得到的y为行向量的形式,为了使其变为一列的形式需要进行reshape
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)
补充:
torch.normal(mean,std,size,out)是PyTorch中用于生成服从正态分布的随机数的函数。
- mean:正态分布的均值。可以是一个数值或一个张量。
- std:正态分布的标准差。可以是一个数值或一个张量。
- size:输出随机数的形状。可以是一个整数,用于生成一个大小为(size,)的一维张量;也可以是一个元组,用于生成相应形状的多维张量。
- out:可选参数,用于指定一个输出张量。
绘图查看生成的数据集:
d2l.set_figsize()
d2l.plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1);
d2l.plt.show()

因为带有噪声原本的线性模型,变成了离散的点,但依然可以看出它是围绕着线性模型生成的。
3.3 读取数据集
定义一个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]
补充:
range(start, stop, step),三个参数分别为起始,终止,步长。
random.shuffle(),调用后会直接打乱原来的列表。
3.4 定义初始化模型参数
# 初始化模型参数
w = torch.normal(0,0.01,(2,1),requires_grad=True)
b = torch.zeros(1,requires_grad=True)
3.5 定义模型
def linear(X,w,b):
"""定义模型"""
return torch.matmul(X,w)+b
3.6 定义损失函数
采用平方损失函数。
def squared_loss(y_hat,y):
# reshape为同一形状,再求平方差损失
return (y_hat-y.reshape(y_hat.shape))**2/2
3.7 定义优化算法
小批量随机梯度下降。
# 小批量随机梯度下降
def sgd(params, lr, batch_size):
with torch.no_grad(): # 以下不会生成计算图
for param in params: # 参数b和w
param -= lr*param.grad/batch_size
# 梯度归零
param.grad.zero_()
补充:
在每一步中使用从数据集中随机抽取的一个小批量,然后根据参数计算损失的梯度。因为我们定义的损失函数是一个批量的总和,所以我们使用
batch_size归一化步长,这样使得步长大小不会取决于我们对批量大小的选择。
3.7 训练过程
(1)初始化参数。
(2)重复以下训练,直到轮次结束:
- 计算梯度
- 更新参数
lr = 0.03 # 学习率
num_epochs = 3 # 迭代周期
batch_size = 10 # 批量大小
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中的所有元素被加到一起,backward的对象只能是标量
# 并以此计算关于[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}')
补充:
backward()函数是反向求导,使用链式法则求导。如:l.sum().backward(),是指根据计算图,从标量l.sum()开始向后反向传播梯度(即,通过链式法则,从后往前计算每个参与得到
的tensor的梯度),并将得到的每个梯度存入对应tensor的grad元素中。
深度学习框架通过自动计算导数,即自动微分(automatic differentiation))来加快求导。实践中,根据设计好的模型,系统会构建一个计算图(computational graph),来跟踪计算是哪些数据通过哪些操作组合起来产生输出。自动微分使系统能够随后反向传播梯度。这里,反向传播(backpropagate) 意味着跟踪整个计算图,填充关于每个参数的偏导数。
四、线性回归的简洁实现
所谓的简洁实现是指使用pytorch的nn模组中到的一些数据预处理方法,来简洁地实现线性回归模型以及生成数据集。
4.1 导包和生成数据集
import numpy as np
import torch
from torch.utils import data
from torch import nn # nn是神经网络的缩写
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) # 直接生成
4.2 读取数据集
调用torch.utils框架中现有的API来读取数据。
# 读取数据集
def load_array(data_arrays, batch_size, is_train=True):
"""构造一个PyTorch数据迭代器"""
dataset = data.TensorDataset(*data_arrays) # 创建dataset对象
return data.DataLoader(dataset, batch_size, shuffle=is_train) # 数据加载器,每一次随机返回batch_size个样本
batch_size = 10
data_iter = load_array((features, labels), batch_size)
我们读取并打印第一个小批量的样本查看。
print(next(iter(data_iter))) # iter构造迭代器,next函数从迭代器中读取第一项

补充:
data.TensorDataset(data_tensor, target_tensor),可以将给定的 tensor 数据(样本和标签),将它们包装成 dataset 。这些 tensor 数据的形状可以不尽相同,但第一个维度必须具有相同大小。
- data_tensor (Tensor) – 样本数据
- target_tensor (Tensor) – 样本目标(标签)
data.DataLoader(dataset, batch_size=1, shuffle=False, sampler=None,num_workers=0, collate_fn=<function default_collate>, pin_memory=False, drop_last=False),数据加载器,可以结合数据集和取样器,并在数据集上提供单线程或多线程迭代器。实际上,DataLoader是按指定规则在指定的数据集上生成了批量大小固定的可供迭代的数据。
- dataset (Dataset) – 加载数据的数据集。
- batch_size (int, optional) – 每个batch加载多少个样本(默认: 1)。
- shuffle (bool, optional) – 设置为
True时,会在每个epoch重新打乱数据(默认: False).- sampler (Sampler, optional) – 定义从数据集中提取样本的策略。如果指定,则忽略
shuffle参数。- num_workers (int, optional) – 用多少个子进程加载数据。0表示数据将在主进程中加载(默认: 0)
- pin_memory (bool, optional) – 内存寄存,默认为False。在数据返回前,是否将数据复制到CUDA内存中。
- drop_last (bool, optional) – 如果数据集大小不能被batch size整除,则设置为True后可删除最后一个不完整的batch。如果设为False并且数据集的大小不能被batch size整除,则最后一个batch将更小。(默认: False)
4.3 定义模型
使用nn框架的预定义好的层(线性层或全连接层)。
net = nn.Sequential(nn.Linear(2, 1)) # 输入大小为2、输出大小为1
第一个参数指定输入特征的形状大小,即2。
第二个参数指定输出特征形状,输出特征形状为单个标量,因此为1。
4.4 初始化模型参数
# 初始化模型参数
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
net[0]是指网络的第一层。weight.data是指权重参数值,采用normal_()从均值为0,方差为1的正态分布区间随机选取。bias.data是偏置参数值,采用fill_()填充为0。
4.5 定义损失函数
计算均方误差使用的是MSELoss类,也称为平方L2范数。默认情况下,它返回所有样本损失的平均值。
# 定义损失函数
loss = nn.MSELoss()
4.6 定义优化算法
小批量随机梯度下降算法,PyTorch在optim模块中实现了该算法的许多不同版本,其中之一,如torch.optim.SGD()。
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
第一个参数是模型中所有的待优化的参数,第二个参数是学习率。
4.7 训练
# 训练
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)
更多推荐

所有评论(0)