跟着李沐老师学习深度学习(二)
线性回归 + 基础优化算法
课程视频:https://space.bilibili.com/1567748478/channel/seriesdetail?sid=358497
课程教材:https://zh.d2l.ai/
线性回归
了解线性回归前的例子
- 在美国买房
看中一个房子,进行参观了解 --> 估计一个价格(出价) —— 预测问题
引出线性回归—简化模型
- 假设 1:影响房价的关键因素是卧室个数,卫生间个数和居住面积,记为 x1,x2,x3
- 假设2:成交价是关键因素的加权和:y=WX+Wx+Wx+b
权重和偏差的实际值在后面决定
线性模型
- 给定n维输入:
- 线性模型有一个n维权重和一个标量偏差
- 输出是输入的加权和:
在这里插入图片描述
- 向量版本:
- 线性模型可以看作是单层神经网络:
- 神经网络来源于神经科学
主要思想:使用人工的神经网络来模拟人的大脑;
衡量预估质量
-
比较真实值和预估值,例如房屋售价和估价;以下式子叫做平方损失。
-
训练数据
- 收集一些数据点来决定参数值(权重和偏差),例如过去6个月卖的房子(称为训练数据)
- 假设有n个样本,记:
-
参数学习
- 训练损失
- 最小化损失来学习参数
- 训练损失
-
显示解
-
将偏差加入权重 (输入加入一列全1;权重加入偏差)
-
损失是凸函数,所以最优解满足:
(唯一具有最优解的模型)
-
总结
- 线性回归是对n维输入的加权,外加偏差
- 使用平方损失来衡量预测值和真实值的差异
- 线性回归有显示解(最简单的模型)
- 线性回归可以看做是单层神经网络
基础优化方法
梯度下降
(当模型中没有显示解时,使用优化方法)
-
挑选一个初始值w0;重复迭代参数t = 1, 2, 3
- 沿梯度方向将增加损失函数值
- 学习率:步长的超参数(需要认为指定)
选择学习率
不能太小、也不能太大;
分类:小批量随机梯度下降(常用)
类型 | 定义 | 优点 | 缺点 |
---|---|---|---|
批量梯度下降 (Batch Gradient Descent) | 每次迭代时,计算整个训练数据集的梯度。 | 收敛性较好,最终能找到全局最小值(对于凸优化问题)。 | 计算成本高,尤其是在数据量很大的时候,因为每次都需要用整个数据集来计算梯度。 |
随机梯度下降 (Stochastic Gradient Descent, SGD) | 每次迭代时,只用一个样本来计算梯度并更新参数。 | 计算速度更快,因为每次只使用一个样本;适合大规模数据集。 | 每次更新的方向波动较大,可能导致收敛速度慢或者最终收敛到局部最小值。 |
小批量梯度下降 (Mini - batch Gradient Descent) | 每次迭代时,用一个小批量的数据(例如32或64个样本)来计算梯度并更新参数。 | 比批量梯度下降速度快,但又能比随机梯度下降更稳定,适用于深度学习中。 | 需要根据批量大小进行调整,但比批量和随机梯度下降的结合要灵活。 |
-
在整个训练集上算梯度太贵
- 一个深度神经网络模型可能需要数分钟至数小时
-
可以随机采样b个样本i1,i2,…,ib 来近似损失
- b是批量大小,另一个重要的超参数。
-
对于批量大小的选择:
- 不能太小:每次计算量太小,不适合并行来最大利用计算资源。
- 不能太大:内存消耗增加浪费计算,例如如果所有样本都是相同的
总结
- 梯度下降通过不断沿着反梯度方向更新参数求解
- 小批量随机梯度下降是深度学习默认的求解算法(稳定、简单)
- 两个重要的超参数是批量大小和学习率
代码实现 – 从零实现线性回归
# 1 线性回归的从零开始实现
# 从零开始实现整个方法:包括数据流水线、模型、损失函数和小批量随机梯度下降优化器
%matplotlib inline
import random
import torch
from d2l import torch as d2l
# 根据带有的噪声的线性模型构造一个人造的数据集。我们使用线性模型参数w = [2, -3.4],b = 4.2 还有噪声项及其标签
def synthetic_data(w, b, num_examples):
"""生成 y = Xw + b + 噪声。"""
# 均值为0,方差为1的随机数,大小:有n个样本,共len(w)列
X = torch.normal(0, 1, (num_examples, len(w)))
# y = Xw + b
y = torch.matmul(X, w) + b
# 加入随机噪音:均值为0,方差为0.01的随机数
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
# 调用synthetic_data函数,生成包含 1000 个样本的特征矩阵features和目标值向量labels。
features, labels = synthetic_data(true_w, true_b, 1000)
# 通过以上代码可以得到我们的训练样本
# features 中的每一行都包含一个二维数据样本,labels中的每一行都包含一维标签值(一个标量)
print('features', features[0], '\nlabel:', labels[0])
d2l.set_figsize()
# scatter函数绘制散点图,将特征矩阵features的第二列(索引为 1)作为横坐标,目标值向量labels作为纵坐标进行可视化展示。detach().numpy()用于将 PyTorch 张量转换为 NumPy 数组以便绘图。
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)
# 创建一个包含从0到num_examples - 1的整数列表,用于表示样本的索引
indices = list(range(num_examples))
# 这些样本是随机读取的,没有特定的顺序;(随机打乱样本的索引顺序)
random.shuffle(indices)
# 使用for循环遍历样本索引,步长为batch_size
for i in range(0, num_examples, batch_size):
# 从打乱后的索引列表中取出当前小批量的索引
# min(i + batch_size, num_examples) 确保最后一个小批量的样本数量不会超过剩余样本数量
batch_indices = torch.tensor(indices[i:min(i + batch_size, num_examples)])
# 使用生成器的yield 关键之返回当前小批量的特征和标签
# 生成器可以逐个返回值,而不是一次性返回所有值,节省内存
yield features[batch_indices], labels[batch_indices]
# 设置批量大小为10
batch_size = 10
# 使用for循环从data_iter生成器中获取小批量数据
for X, y in data_iter(batch_size, features, labels):
# 打印当前小批量的特征和标签
print(X, '\n', y)
break
# 定义初始化模型参数
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
# 定义模型
def linreg(X, w, b):
"""线性回归模型"""
return torch.matmul(X, w) + b
# 定义损失函数
def squared_loss(y_hat, y):
"""均方损失"""
return (y_hat - y.reshape(y_hat.shape))**2 / 2
# 定义优化算法
def sgd(params, lr, batch_size):
"""小批量随机梯度下降"""
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
# 将梯度设为0;不影响下一次的计算
param.grad.zero_()
# 训练过程
# 学习率
lr = 0.001
# 迭代次数
num_epochs = 10
# 模型
net = linreg
# 损失函数
loss = squared_loss
for epoch in range(num_epochs):
# 每次拿出一个批量大小为batch_size的 X和 y
for X, y in data_iter(batch_size, features, labels):
# 拿预测值net(X, w, b) 和真实值y作损失计算
l = loss(net(X, w, b), y) # x 和 y 的小批量损失
# 因为l 的形状是(batch_size, 1),而不是一个标量。
# 并以此计算关于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}')
代码实现 – 简洁实现
# 线性回归的简洁实现
# 使用深度学习框架进行简洁实现线性回归模型 生成训练数据
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)
# 当 is_train 为 True 时,表示处于训练阶段,数据会被随机打乱;当为 False 时,表示处于验证或测试阶段,数据不会被打乱。
def load_array(data_arrays, batch_size, is_train=True):
"""构造一个pytorch数据迭代器"""
# TensorDataset用于将多个张量组合成一个数据集
dataset = data.TensorDataset(*data_arrays)
# DataLoader 会根据这些参数将数据集划分为多个批次,并在需要时提供迭代访问这些批次的功能。
# 当 shuffle 为 True 时,每次迭代时数据会被随机打乱,有助于提高模型训练的泛化能力。
return data.DataLoader(dataset, batch_size, shuffle=is_train)
batch_size = 10
data_iter = load_array((features, labels), batch_size)
next(iter(data_iter))
# 接下来是模型的定义
# 使用框架的预定义好的层
# nn 是神经网络的缩写
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))
# 初始化模型参数
# net[0]代表:nn.Linear(2, 1);
# normal_(0, 0.01):将张量中的每个元素用从指定正态分布中采样得到的值进行替换; 0 是正态分布的均值,0.01 是正态分布的标准差
net[0].weight.data.normal_(0, 0.01)
# fill_(): 将张量中的每个元素都填充为指定的值,这里指定的值是 0;
net[0].bias.data.fill_(0)
# 计算均方误差使用的是MSELoss类,也称为平方范数(L2)
loss = nn.MSELoss()
# 实例化SGD 实例
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
# 训练过程:
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)