【动手学深度学习】--01.线性回归

线性回归

学习视频:线性回归 + 基础优化算法【动手学深度学习v2】

1.原理

1.1线性模型

img

线性回归可以看成是一个单层神经网络

image-20230713230202391

1.2衡量预估质量——平方损失

img

使用平方损失来衡量预测值和真实值的差异

1.3训练数据

img

1.4参数学习

img

1.5优化算法

虽然线性回归有解析解,但是一般的模型都没有解析解,因此在此介绍更为普适的求解最优解的方法。当一个模型没有显示解的时候,可以使用梯度下降法来逼近最优解

梯度下降

img

小批量梯度下降

img

  • 批量不能太小,若计算量太小,不适合并行来最大利用计算资源

  • 批量不能太大,否则内存消耗增加,浪费计算

总结:

  • 梯度下降通过不断沿着反梯度方向更新参数求解

  • 小批量随机梯度下降是深度学习默认的求解算法

  • 两个重要的超参数是批量大小和学习率

2.从零开始实现线性回归

2.1生成数据集

  • 根据带有噪声的线性模型构造一个人造数据集

image-20230713220007922

torch.normal(means, std, size,require_grade)

means (Tensor) – 均值
std (Tensor) – 标准差
size (Tensor) – 可选的输出张量
require_grade :表示是否求梯度值

torch.matmul(input,other)

计算两个张量input和other的矩阵乘积
matmul函数没有强制规定维度和大小,可以用利用广播机制进行不同维度的相乘操作

import matplotlib.pyplot as plt
import random
import torch

def synthetic_data(w,b,num_examples):
    '''生成y = Xw + b + 噪声'''
    #X中的元素是均值为0,方差为1的随机值,是一个n*len(w)的二维张量
    X = torch.normal(0,1,(num_examples,len(w))) 
    #y是一个一维的张量
    y = torch.matmul(X,w) + b
    ##增加随机噪音,噪音是均值为0,方差为0.01的随机数
    y += torch.normal(0,0.01,y.shape)
    #y.reshape((-1, 1)) 的意思是将 y 数组重新排列成一个列向量。其中 -1 表示自动计算行数,1 表示列数为 1
    return X,y.reshape((-1,1))

X是一个n*len(w)的二维张量,而w是一个一维张量,根据向量相乘的规则,会得到一个n行一列的一维张量y

定义好w和b的值,利用上面的函数生成1000个数量的数据集

true_w = torch.tensor([2,-3.4])
true_b = 4.2
features,labels = synthetic_data(true_w,true_b,10000)

可以通过散点图的方式,看到两个特征关于y的线性关系,这里为了能使用matplotlib绘图,在pytorch中需要把张量通过detach函数,将tensor从计算图剥离,然后才能使用numpy函数转化为矩阵传入plt,它作为叶子节点仍指向之前tensor

torch.detach()函数

返回一个新的tensor,从当前计算图中分离下来。但是仍指向原变量的存放位置,不同之处只是requirse_grad为false.得到的这个tensor永远不需要计算器梯度,不具有grad.

即使之后重新将它的requires_grad置为true,它也不会具有梯度grad.这样我们就会继续使用这个新的tensor进行计算,后面当我们进行反向传播时,到该调用detach()的tensor就会停止,不能再继续向前进行传播.

plt.scatter(features[:, 0].detach().numpy(), labels.detach().numpy(), 1)
plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1)

image-20230713223013081

image-20230713222127531

2.2批量读取数据集——Mini-batch思想

在下面的代码中,我们定义一个data_iter函数, 该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size的小批量。 每个小批量包含一组特征和标签。

def data_iter(batch_size, features, labels):
    #返回X数据量,即样本数量
    num_examples = len(features)
    #对正常数据的迭代器生成列表
    indices = list(range(num_examples))
    # 这些样本是随机读取的,没有特定的顺序,打乱顺序
    random.shuffle(indices)
    #从0到样本总数量进行迭代,步长为batch_size
    for i in range(0, num_examples, batch_size):
        #这里巧妙的min可以在最终组数据不够的时候返回的是最后一个节点的下标
        batch_indices = torch.tensor(
            indices[i: min(i + batch_size, num_examples)])
        #yield类似return 返回一个值,并且记住这个返回的位置,下次迭代从这个位置开始
        yield features[batch_indices], labels[batch_indices]

使用如下代码进行测试,得到批量读取的效果

batch_size = 10

for X, y in data_iter(batch_size, features, labels):
    print(X, '\n', y)
    break

image-20230713223528821

2.3初始化模型参数

在下面的代码中,我们通过从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重, 并将偏置初始化为0

w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)

2.4定义模型

要计算线性模型的输出, 我们只需计算输入特征X和模型权重w的矩阵-向量乘法后加上偏置b

def linreg(X, w, b):  #@save
    """线性回归模型"""
    return torch.matmul(X, w) + b

2.5定义损失函数

def squared_loss(y_hat, y):  #@save
    """均方损失"""
    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:
            param -= lr * param.grad / batch_size
            param.grad.zero_()

2.7训练模型

整个训练过程主要分为:

  • 参数设置
  • 迭代轮次
  • 批量处理
  • 正向传播 (计算损失)
  • 反向传播
  • 更新参数
  • 梯度清零
  • 梯度更新

image-20230713224148249

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)  # X和y的小批量损失
        # 因为l形状是(batch_size,1),而不是一个标量。l中的所有元素被加到一起,
        # 并以此计算关于[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}')

3.借助深度学习框架实现线性回归模型

3.1生成数据集

#使用深度学习框架实现
import numpy as np
import torch
from torch.utils import data

true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)

3.2读取数据集

具体参考python之TensorDataset和DataLoader

torch,utils.data.TensorDataset(data_tensor,target_tensor)*

TensorDataset 可以用来对 tensor 进行打包,就好像 python 中的 zip 功能。
该类通过每一个 tensor 的第一个维度进行索引。
因此,该类中的 tensor 第一维度必须相等.
另外:TensorDataset 中的参数必须是 tensor

torch.utils.data.DataLoader(dataset,batch_size,shuffle)*

dataset:加载数据的数据集
batch_size:每批加载多少个样本
shuffle:表示是否希望数据迭代器对象在每个迭代周期内打乱数据

def load_array(data_arrays, batch_size, is_train=True): 
    """构造一个PyTorch数据迭代器"""
    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)

3.3定义模型

在PyTorch中,全连接层在Linear类中定义。 值得注意的是,我们将两个参数传递到nn.Linear中。 第一个指定输入特征形状,即2,第二个指定输出特征形状,输出特征形状为单个标量,因此为1。

# nn是神经网络的缩写
from torch import nn

net = nn.Sequential(nn.Linear(2, 1))

我们这里使用到了Sequential函数,它的作用类似于封装一个我们自定义的模型,可以将我们写好的网络一层一层连接实例化 torch.nn.Sequential()讲解

3.4初始化模型参数

normal和fill加下划线都是将原本data的值进行填充替换的作用,们通过net[0]选择网络中的第一个图层, 然后使用weight.databias.data方法访问参数

net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)

3.5定义损失函数

计算均方误差使用的是MSELoss类,也称为平方L2范数

loss = nn.MSELoss() 

3.6定义优化算法

小批量随机梯度下降算法是一种优化神经网络的标准工具, PyTorch在optim模块中实现了该算法的许多变种。 当我们实例化一个SGD实例时,我们要指定优化的参数 (可通过net.parameters()从我们的模型中获得)以及优化算法所需的超参数字典。 小批量随机梯度下降只需要设置lr值,这里设置为0.03。

trainer = torch.optim.SGD(net.parameters(), lr=0.03)

3.7训练

对于每一个小批量,我们会进行以下步骤:

  • 通过调用net(X)生成预测并计算损失l(前向传播)。
  • 通过进行反向传播来计算梯度。
  • 通过调用优化器来更新模型参数。
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)
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值