深度学习基础之线性回归
第三章 线性回归
- 解决线性回归问题的两种方法:使用公式(显式解)和使用梯度下降
- 评估线性回归模型的指标:均方根误差(MSE_LOSS)
- 使用Pytorch实现梯度下降法的线性回归模型
1、梯度下降
- 梯度下降是一种非常通用的优化算法,能够为大范围的问题找到最优解。梯度下降的整体思路是通过多次迭代,每次迭代都试图对损失函数进行优化,从而找到最小损失函数值对应的参数值。
- 梯度下降的一般流程:
- 随机初始化模型参数,通常是一个接近0的随机数,即初始化权重 w_0 和偏差 b_0
- 重复下面步骤直到停止条件达成
- 计算损失函数关于模型参数的梯度,即计算 w_t=w_{t-1}-\eta \frac{\partial l}{\partial w_{t-1}}
- 使用学习率乘以梯度更新模型参数,沿着梯度下降的方向增加损失函数值
- 超参数定义:人为定义的参数,不会通过训练得到,需要手动设置,如学习率、迭代次数、批量大小等
- 本章使用的超参数:学习率、迭代次数、批量大小
- 梯度下降的学习率:学习率过大,会导致损失函数值不断增大,甚至发散;学习率过小,会导致损失函数值收敛缓慢
- 梯度下降的批量大小:批量大小过大,会导致内存溢出;批量大小过小,会导致损失函数值收敛缓慢
- 梯度下降的迭代次数:迭代次数过多,会导致过拟合;迭代次数过少,会导致欠拟合
2、小批量随机梯度下降
- 梯度下降的一个重要变体是小批量随机梯度下降,它每次迭代都随机采样一个由固定数量训练样本所组成的小批量,然后求小批量中数据样本的平均损失有关模型参数的导数,最后使用此结果来更新模型参数。
- 随机采样 b 个样本 i_1,i_2,...,i_b 计算损失:l(w,b)=\frac{1}{b}\sum_{j=1}^b l(w,i_j)
- b 是小批量样本的大小,通常取2的幂次方,如32、64、128等,即为批量大小(batch_size)
3、线性回归的从零开始实现
1、导入所需的包或模块
%matplotlib inline
import random
import torch
from d2l import torch as d2l
2、根据带有噪声的线性模型生成数据集,使用线性模型真实权重 w=[2,-3.4]^T 和偏差 b=4.2,噪声项 \epsilon 服从均值为0、标准差为0.01的正态分布生成数据集及其标签:y=Xw+b+\epsilon
def synthetic_data(w, b, num_examples):
X = torch.normal(0, 1, (num_examples, len(w))) # 均值为0,标准差为1,形状为num_examples*len(w)
y = torch.matmul(X, w) + b # Xw+b
y += torch.normal(0, 0.01, y.shape) # 均值为0,标准差为0.01,形状为y.shape,增加噪声
return X, y.reshape((-1, 1)) # 把X和y做成列向量返回
true_w = torch.tensor([2, -3.4]) # w的真实值
true_b = 4.2 # b的真实值
features, labels = synthetic_data(true_w, true_b, 1000)
features中的每一行对应一个数据样本,features中的每一列对应一个特征;labels亦是如此
print('features:', features[0],'\nlabel:', labels[0])
features: tensor([0.7274, 0.2557])
label: tensor([4.7834])
d2l.set_figsize()
d2l.plt.scatter(features[:, (1)].detach().numpy(), labels.detach().numpy(), 1); # 画出features[:, (1)]和labels的散点图
3、定义data_iter
函数,该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size
的小批量样本,每个小批量包含一组特征和标签
yield
关键字:在for循环中,每次循环都会执行yield
语句,将yield
语句的参数作为返回值返回,同时暂停执行,直到下一次调用或迭代终止
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_size个样本
batch_indices = torch.tensor(indices[i:min(i + batch_size, num_examples)]) # 取出batch_size个样本的索引
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
tensor([[-1.4941, -0.7959],
[-0.8461, -1.6286],
[-0.1836, -0.1776],
[-0.3241, 0.1180],
[ 1.8991, 0.7575],
[-0.3994, 0.1566],
[-0.1014, -0.7125],
[ 0.4910, -0.7328],
[-0.2212, 0.2266],
[ 0.3280, 2.2837]])
tensor([[ 3.9066],
[ 8.0464],
[ 4.4511],
[ 3.1502],
[ 5.4203],
[ 2.8712],
[ 6.4279],
[ 7.6542],
[ 2.9926],
[-2.9197]])
4、线性模型定义
- 初始化模型参数,将权重初始化为均值为0、标准差为0.01的正态分布随机数,偏差初始化为0
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True) # w的初始值
b = torch.zeros(1, requires_grad=True) # b的初始值
- 定义模型,输入为特征矩阵X,输出为预测值y_hat
def linreg(X, w, b):
return torch.matmul(X, w) + b # y_hat = Xw+b
- 定义损失函数:均方损失
def squared_loss(y_hat, y):
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2 # (y_hat-y)^2
- 定义优化算法,使用小批量随机梯度下降算法
def sgd(params, lr, batch_size):
with torch.no_grad(): # 不计算梯度
for param in params: # 遍历参数
param -= lr * param.grad / batch_size # 更新参数
param.grad.zero_() # 梯度清零
5、模型训练:下方两个超参数学习率lr
和迭代数num_epochs
可调
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): # 遍历数据集
# loss得到的是一个batch_size个样本的损失,sum得到的是样本的损失和,以此计算关于w和b的梯度
l = loss(net(X, w, b), y) # 计算损失
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}') # 打印训练损失
epoch 1, loss 0.033516
epoch 2, loss 0.000116
epoch 3, loss 0.000051
6、比较真实的参数和通过训练学到的参数,以此评估训练得到的模型的效果
print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')
w的估计误差: tensor([ 0.0007, -0.0006], grad_fn=<SubBackward0>)
b的估计误差: tensor([-0.0002], grad_fn=<RsubBackward1>)
4、线性回归的简洁实现
1、导入所需的包或模块,并且给出真实的权重和偏差,生成带有噪声的数据集
import torch
import numpy as np
from d2l import torch as d2l
from torch.utils import data
true_w = torch.tensor([2, -3.4]) # w的真实值
true_b = 4.2 # b的真实值
features, labels = synthetic_data(true_w, true_b, 1000) # 生成数据集
2、利用框架中的API函数来读取数据集
def load_array(data_arrays, batch_size, is_train=True): # 读取数据集
dataset = data.TensorDataset(*data_arrays) # 读取数据集
return data.DataLoader(dataset, batch_size, shuffle=is_train) # 返回数据集
batch_size = 10 # 批量大小
data_iter = load_array((features, labels), batch_size) # 读取数据集
next(iter(data_iter)) # 读取第一个小批量样本
[tensor([[ 1.1189, -0.2509],
[ 1.5438, -0.2172],
[-1.3757, -1.0243],
[-1.2592, 1.7337],
[-1.8912, -1.2692],
[-0.3273, -0.4090],
[-0.7802, 0.4697],
[ 0.9599, 1.4042],
[-0.1536, -0.8057],
[ 0.9245, 0.4708]]),
tensor([[ 7.2854],
[ 8.0311],
[ 4.9229],
[-4.2319],
[ 4.7270],
[ 4.9278],
[ 1.0459],
[ 1.3580],
[ 6.6371],
[ 4.4355]])]
3、定义模型
- 使用框架中的
nn.Linear
类定义线性回归模型,该类已包含参数和偏差
# nn是神经网络(Nueral Network)的缩写
from torch import nn
net = nn.Sequential(nn.Linear(2, 1)) # 线性回归模型
- 初始化模型参数,将权重初始化为均值为0、标准差为0.01的正态分布随机数,偏差初始化为0
net[0].weight.data.normal_(0, 0.01) # 初始化权重
net[0].bias.data.fill_(0) # 初始化偏差
tensor([0.])
- 计算均方损失,使用框架中的
nn.MSELoss
类
loss = nn.MSELoss() # 损失函数
- 实例化优化器,使用框架中的
optim.SGD
类
trainer = torch.optim.SGD(net.parameters(), lr=0.03) # 优化器,net.parameters()返回需要更新的参数w和b
4、训练模型
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}') # 打印训练损失
epoch 1, loss 0.000234
epoch 2, loss 0.000106
epoch 3, loss 0.000106
5、小结
- 线性回归模型是一个较小的神经网络,但是麻雀虽小,五脏俱全,它是一个完整的神经网络
- 线性回归模型包括了数据的读取、模型的定义、模型参数的初始化、损失函数、模型的训练和模型的预测
- 线性回归以及其他模型都是比较模板化的,线性回归模型是其中基本且简单的模型
- 比较4、5可以得出,使用框架的实现比从零开始的实现更加简洁,而且运行效率更高
评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果