李沐《动手学深度学习》线性神经网络 线性回归

发布时间:2024年01月14日

系列文章

李沐《动手学深度学习》预备知识 张量操作及数据处理
李沐《动手学深度学习》预备知识 线性代数及微积分


教材:李沐《动手学深度学习》

一、线性回归

(一)线性回归的基本元素

  1. 线性回归基于的假设
    • 假设自变量和因变量之间的关系是线性的,这里通常允许包含观测值的一些噪声;
    • 假设任何噪声都比较正常,如噪声遵循正态分布。
  2. 线性回归的矩阵向量表示:(这个过程中的求和将使用广播机制 )
    y ^ = X w + b \hat{y}=Xw+b y^?=Xw+b
  3. 线性回归的损失函数:(回归问题中最常用的损失函数是平方误差函数)
    平方误差的定义为:
    l ( i ) ( w , b ) = 1 2 ( y ^ ( i ) ? y ( i ) ) 2 l^{(i)}(w,b)=\frac{1}{2}(\hat{y}^{(i)}-y^{(i)})^2 l(i)(w,b)=21?(y^?(i)?y(i))2
    训练集n个样本上的损失均值:
    L ( w , b ) = 1 n ∑ i = 1 n l ( i ) ( w , b ) = 1 n ∑ i = 1 n 1 2 ( w T x ( i ) + b ? y ( i ) ) 2 L(w,b)=\frac{1}{n}\sum_{i=1}^nl^{(i)}(w,b)=\frac{1}{n}\sum_{i=1}^n\frac{1}{2}(w^Tx^{(i)}+b-y^{(i)})^2 L(w,b)=n1?i=1n?l(i)(w,b)=n1?i=1n?21?(wTx(i)+b?y(i))2
    训练模型时,希望找到一组参数最小化所有训练样本的损失,即:
    w ? , b ? = argmin ? w , b ? L ( w , b ) w^*,b^*={ \underset {w,b} { \operatorname {argmin} } \,L(w,b)} w?,b?=w,bargmin?L(w,b)
  4. 解析解
    线性回归的解可以用一个公式简单的表达出来,这类解成为解析解。不是所有的问题都存在解析解。
    w ? = ( X T X ) ? 1 X T y w^*=(X^TX)^{-1}X^Ty w?=(XTX)?1XTy
  5. 正态分布与平方损失
    在高斯噪声的假设下,最小化均方误差等价于对线性模型的极大似然估计。

(二)随机梯度下降

  1. 随机梯度下降方法几乎可以优化所有深度学习模型,它通过不断地在损失函数递减的方向上更新参数来降低误差;
  2. 梯度下降的用法是计算损失函数关于模型参数的导数;
  3. 由于每次更新参数之前都必须遍历整个数据集,梯度下降法的执行可能会非常慢。因此我们通常会采用每次需要计算更新时随机抽取一小批样本的方法,即小批量随机梯度下降法。

随机梯度下降的公式表示:
( w , b ) ← ( w , b ) ? η ∣ B ∣ ∑ i ∈ B ? ( w , b ) l ( i ) ( w , b ) (w,b)\leftarrow(w,b)-\frac{\eta}{|B|}\sum_{i\in B}\partial_{(w,b)}l^{(i)}(w,b) (w,b)(w,b)?Bη?iB??(w,b)?l(i)(w,b)

在每次迭代中,先随机抽样一个小批量 B B B(由固定数量的训练样本组成),然后计算小批量的平均损失的导数(梯度),最后将梯度乘以一个预定的正数 η \eta η(学习率),并从当前参数的值中减掉。

批量大小和学习率的值通常是手动预先指定,而不是通过模型训练得到的。 这些可以调整但不在训练过程中更新的参数称为超参数调参(hyperparameter tuning)是选择超参数的过程。 超参数通常是我们根据训练迭代结果来调整的, 而训练迭代结果是在独立的验证数据集(validation dataset)上评估得到的。

(三)矢量化加速(实例化说明)

在训练模型时,会利用线性代数库对计算进行矢量化,从而实现整个小批量样本的同时处理。
对比矢量化和for循环两种计算方法,会发现矢量化方法的计算时间短很多,代码实现如下:

相关库的准备:

%matplotlib inline
import math
import time
import numpy as np
import torch
from d2l import torch as d2l

d2l的安装

定义一个计时器:

class Timer:  #@save
    """记录多次运行时间"""
    def __init__(self):
        self.times = []
        self.start()

    def start(self):
        """启动计时器"""
        self.tik = time.time()

    def stop(self):
        """停止计时器并将时间记录在列表中"""
        self.times.append(time.time() - self.tik)
        return self.times[-1]

    def avg(self):
        """返回平均时间"""
        return sum(self.times) / len(self.times)

    def sum(self):
        """返回时间总和"""
        return sum(self.times)

    def cumsum(self):
        """返回累计时间"""
        return np.array(self.times).cumsum().tolist()

数据准备:

n=10000
a=torch.ones([n])
b=torch.ones([n])

用python for循环进行计算:
该方法花费的时间为’0.16749 sec’

c=torch.zeros(n)
timer=Timer()
for i in range(n):
    c[i]=a[i]+b[i]
f'{timer.stop():.5f} sec'

用矢量化进行计算:
该方法花费的时间为’0.00042 sec’

timer.start()
d=a+b
f'{timer.stop():.5f} sec'

结果很明显,第二种方法比第一种方法快得多。 矢量化代码通常会带来数量级的加速。 另外,可以将更多的数学运算放到库中,而无须自己编写那么多的计算,从而减少了出错的可能性。

(四)从线性回归到神经网络

我们可以将线性回归模型视为仅由单个人工神经元组成的神经网络,或称为单层神经网络:
在这里插入图片描述

  • 输入为 x 1 , … , x d x_1,\ldots,x_d x1?,,xd?, 因此输入层中的输入数为d;
  • 输出为 o 1 o_1 o1?,因此输出层中的输出数是1;
  • 由于模型重点在发生计算的地方,所以通常我们在计算层数时不考虑输入层。 也就是说, 图中神经网络的层数为1。

对于线性回归,每个输入都与每个输出(在本例中只有一个输出)相连, 我们将这种变换( 图中的输出层) 称为全连接层(fully-connected layer)。

二、线性回归的从零开始实现

(一)生成数据集

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

  • 包含1000个样本
  • 线性模型参数: w = [ 2 , ? 3.4 ] T w=[2,-3.4]^T w=[2,?3.4]T b = 4.2 b=4.2 b=4.2
  • 噪声项服从均值为0,标准差为0.01的正态分布
def synthetic_data(w,b,num_examples):
    X=torch.normal(0,1,(num_examples,len(w)))
    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)

(二)读取数据集

定义一个函数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]

通常,我们利用GPU并行运算的优势,处理合理大小的“小批量”。 每个样本都可以并行地进行模型计算,且每个样本损失函数的梯度也可以被并行计算。 GPU可以在处理几百个样本时,所花费的时间不比处理一个样本时多太多。

(三)初始化模型参数

从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重, 并将偏置初始化为0:

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

(五)定义损失函数

使用 平方损失函数。 在实现中,我们需要将真实值y的形状转换为和预测值y_hat的形状相同。

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
            param.grad.zero_()

(七)训练

在每次迭代中,我们读取一小批量训练样本,并通过我们的模型来获得一组预测。 计算完损失后,我们开始反向传播,存储每个参数的梯度。 最后,我们调用优化算法sgd来更新模型参数。

在每个迭代周期(epoch)中,使用data_iter函数遍历整个数据集, 并将训练数据集中所有样本都使用一次(假设样本数能够被批量大小整除)。 这里的迭代周期个数num_epochs和学习率lr都是超参数,分别设为3和0.03。 设置超参数很棘手,需要通过反复试验进行调整。

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)
        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}')

三、线性回归的简洁实现

(一)生成数据集

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

  • 包含1000个样本
  • 线性模型参数: w = [ 2 , ? 3.4 ] T w=[2,-3.4]^T w=[2,?3.4]T b = 4.2 b=4.2 b=4.2
  • 噪声项服从均值为0,标准差为0.01的正态分布
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)

(二)读取数据集

调用框架中现有的API来读取数据:

  • 将features和labels作为API的参数传递
  • 通过数据迭代器指定batch_size
  • 布尔值is_train表示是否希望数据迭代器对象在每个迭代周期内打乱数据。
def load_array(data_arrays,batch_size,is_train=True):
    #构造一个数据迭代器
    # TensorDataset 将输入的特征数据和标签数据打包成一个数据集
    dataset=data.TensorDataset(*data_arrays)
    # DataLoader 用于批量加载数据,可以选择是否在每个 epoch 时随机打乱数据
    return data.DataLoader(dataset,batch_size,shuffle=is_train)


batch_size=10
data_iter=load_array((features,labels),batch_size)

(三)定义模型

对于标准深度学习模型,我们可以使用框架的预定义好的层。这使我们只需关注使用哪些层来构造模型,而不必关注层的实现细节。

torch.nn.Sequential 是一个容器模块,它按顺序包含了其他模块(layers)。这个容器允许将一系列的神经网络层按照顺序组合在一起,形成一个更大的网络模型。Sequential 类提供了一种简单的方式来构建和组织神经网络模型,尤其适用于顺序堆叠的层结构。

将两个参数传递到nn.Linear中。 第一个指定输入特征形状,即2,第二个指定输出特征形状,输出特征形状为单个标量,因此为1。

from torch import nn
net=nn.Sequential(nn.Linear(2,1))

(四)初始化模型参数

在使用net之前,我们需要初始化模型参数。 如在线性回归模型中的权重和偏置。 深度学习框架通常有预定义的方法来初始化参数。 在这里,我们指定每个权重参数应该从均值为0、标准差为0.01的正态分布中随机采样, 偏置参数将初始化为零。

通过net[0]选择网络中的第一个图层, 然后使用weight.data和bias.data方法访问参数。 我们还可以使用替换方法normal_和fill_来重写参数值。

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

(五)定义损失函数

使用 平方损失函数。 在实现中,我们需要将真实值y的形状转换为和预测值y_hat的形状相同。

loss=nn.MSELoss()

(六)定义优化算法

使用小批量随机梯度下降法进行优化。

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

(七)训练

在每次迭代中,我们读取一小批量训练样本,并通过我们的模型来获得一组预测。 计算完损失后,我们开始反向传播,存储每个参数的梯度。 最后,我们调用优化算法sgd来更新模型参数。

在每个迭代周期(epoch)中,使用data_iter函数遍历整个数据集, 并将训练数据集中所有样本都使用一次(假设样本数能够被批量大小整除)。 这里的迭代周期个数num_epochs和学习率lr都是超参数,分别设为3和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}')
文章来源:https://blog.csdn.net/weixin_47748259/article/details/135477888
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。