动手学深度学习——线性回归(原理解释+代码详解)

1、线性回归

回归是能够为一个或多个自变量与因变量之间关系建模的一类方法。

当函数为未知参数的线性函数时,称为线性回归模型。

机器学习领域中大多数任务通常都与预测有关,当我们预测一个数值时,就会涉及到回归问题。常见的例子有:预测价格,预测需求等。

2、线性回归模型

假设自变量x与因变量y满足线性关系,数学中一般的线性方程为:y=ax+b
我们将y可以表示为x中元素的加权和,并且允许包含观测值的一些噪声,可以将式子设为:
在这里插入图片描述

在机器学习中,线性回归模型建立的步骤一般是:
先假设一个线性模型——>计算该模型预测值与实际值的差距loss(损失函数)——>通过优化算法(如随机梯度下降)来更新参数如w,b来降低误差

2.1 线性模型

一般我们将线性假设可以表示为特征的加权和:

x表示元素的特征。
w称为权重,权重决定了每个特征对我们预测值的影响。
b称为偏置或者偏移量,偏移量指当所有特征值为0时,预测值应该为多少。(对于偏置的加入可以帮助模型有更好的泛化能力,即该模型在未见过的数据也有较好的表现)

如果是房屋的价格可以表示为:

2.2 损失函数

对于我们假设的线性模型,预测值可能离实际值还有差距,为了表示这一差距,我们引入损失函数这一概念。

损失函数能够量化目标的实际值与预测值之间的差距,通常选择非负数作为损失,且数值越小表示损失越小。

2.2.1 平方差损失函数

回归问题中最常用的损失函数是平方误差函数。在这里插入图片描述
常数1/2便于我们在对损失函数(平方项)求导时,能够将系数化为1

2.2.2 整个数据集上的损失函数

为了度量模型能够在整个数据集上的质量,我们需计算训练集n个样本的损失均值。
即上式求和再除以n(含参数w,b的为展开式)
在这里插入图片描述
我们的目标则是为了寻得一组参数w*,b*,使最小化训练样本的总损失
在这里插入图片描述

2.3 随机梯度下降

如上文所说,我们为了使总损失最小,我们需要对参数w和b进行更新。
而更新方法则是使用到一种叫做梯度下降的方法,它不断在损失函数递减的方向上更新参数。

梯度下降的简单用法是计算损失函数关于模型参数的导数(可以称为梯度),损失函数为数据集中所有样本的损失均值。

实际过程中由于数据集较大,每次更新参数会遍历整个数据集,执行会非常缓慢。我们通常会在每次更新的时候抽取一小批量样本,这种变体叫做小批量随机梯度下降。

每次迭代中,该过程为:
1、首先随机抽取一个小批量β,它是由固定数量的训练样本组成
2、计算小批量的平均损失关于模型参数的导数
3、将梯度乘以一个预先确定的正数η,并从当前参数的值中减掉
在这里插入图片描述
其中∂表示偏导数,η表示学习率,批量大小和学习率的值通常是手动预先设定的,这些可以调整但不在训练过程中更新的参数称为超参数。

2.4 用模型进行预测

给定“已学习”的线性回归模型在这里插入图片描述
我们现在可以通过房屋面积x1和房龄x2来估计一个(未包含在训练数据中的)新房屋价格。
给定特征估计目标的过程通常称为预测或推断。

3、线性回归的简单实现

了解到关键思路后,我们可以通过代码来实现线性回归。
下面的代码有些是调用或者定义的函数,刚开始学习的时候不必纠结代码,主要了解代码的整体实现过程。

步骤函数
生成数据集在这里插入图片描述
读取数据集在这里插入图片描述
初始化模型参数在这里插入图片描述
定义模型在这里插入图片描述
定义损失函数在这里插入图片描述
定义优化算法在这里插入图片描述
训练在这里插入图片描述

3.1 生成数据集

生成一个包含1000个样本的数据集,每个样本包含从标准正态分布中采用的2个特征。

线性模型参数
在这里插入图片描述
生成的数据集及标签:
在这里插入图片描述
噪声项ε可以视为模型预测和标签时的潜在观测误差,它服从均值为0的正态分布,标准差为0.01。

#matplotlib包用于作图,且设置成嵌入显示
%matplotlib inline
import random
import torch
from d2l import torch as d2l
def synthetic_data(w, b, num_examples):  #@save
    """生成y=Xw+b+噪声"""
    # normal:返回一个张量,包含从指定均值means和标准差std的离散正态分布抽取的一组随机数
    X = torch.normal(0, 1, (num_examples, len(w))) #均值为0,方差为1

	# matmul:矩阵乘法
    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)

注意,features中的每一行都包含一个二维数据样本,labels中的每一行都包含一维标签值(一个标量)。
在这里插入图片描述

3.2 读取数据集

训练模型要对数据集进行遍历,每次抽取一小批量,并使用它们来更新模型。

data_iter函数,该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size的小批量。每个小批量包含一组特征和标签。
在这里插入图片描述

random.shuffle()——打乱顺序
indices[i: min(i + batch_size, num_examples)])——按batch_size进行切片,得到小批量

# features:特征矩阵,labels:标签向量
def data_iter(batch_size, features, labels):
	# len():求矩阵时长度相当于输出行的数目
    num_examples = len(features)
    # 生成标号,列表形式储存
    indices = list(range(num_examples))
    # 这些样本是随机读取的,没有特定的顺序
    random.shuffle(indices)

	# 构造随机样本,打乱样本,等间隔访问,达到随机
	# 从0开始,到num_exampls结束,每次间隔batc_size
    for i in range(0, num_examples, batch_size):
        batch_indices = torch.tensor(
            indices[i: min(i + batch_size, num_examples)])
        # yield:返回一个值,并且记住这个返回的位置,下次迭代从这个位置开始
        yield features[batch_indices], labels[batch_indices]

3.3 初始化模型参数

我们通过从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重, 并将偏置初始化为0。
初始化参数后,我们的任务是更新这些参数,直到这些参数足够拟合我们的数据。

每次更新都需要计算损失函数关于模型参数的梯度。 有了这个梯度,我们就可以向减小损失的方向更新每个参数。

torch.normal(means, std, out=None)均值,标准差,可选的输出张量

# 此时size定义两行一列
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)

#  requires_grad用于说明当前向量是否需要在计算中保留对应的梯度信息
b = torch.zeros(1, requires_grad=True)

3.4 定义模型

线性回归模型:y=wx+b

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

3.5 定义损失函数

采取平方损失函数
在这里插入图片描述
y.reshape(y_hat.shape)将y的形状统一为y_hat

# y_hat是预测值, y是真实值
def squared_loss(y_hat, y):  #@save
    """均方损失"""
    # 返回平方误差
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2

3.6 定义优化算法

小批量随机梯度下降:在每一步中,使用从数据集中抽取的一个小批量,然后根据参数计算损失的梯度。接下来,朝着减少损失的方向更新我们的参数。

每一步更新的大小由学习速率lr决定这里是引用

with torch.no_grad()
强制之后的内容不计算梯度。
在使用pytorch时,并不是所有的操作都需要进行计算图的生成(计算过程的构建,以便梯度反向传播等操作)。而对于tensor的计算操作,默认是要进行计算图的构建的。


def sgd(params, lr, batch_size):  #@save
    """小批量随机梯度下降"""
    with torch.no_grad():
        for param in params:
        	# param.grad是求梯度
            param -= lr * param.grad / batch_size
            
            # 因为pytorch不会自动将梯度设置为0,设置为零后下次计算就不会与上次相关了
            param.grad.zero_()

3.7 训练

训练过程可以概括为:

  1. 在每次迭代中,读取一小批量训练样本,并通过模型来获得一组预测。
  2. 计算完损失后,开始反向传播,存储每个参数的梯度。
  3. 调用优化算法sgd来更新模型参数。
    这里是引用
lr = 0.03 #学习率(超参数)
num_epochs = 3 #把整个数据扫3遍
net = linreg #模型生成函数
loss = squared_loss #均方损失函数

for epoch in range(num_epochs):
    for X, y in data_iter(batch_size, features, labels):
    	# 'X'和'y'的小批量损失
    	# 因为'l'形状是('batch_size',1),而不是一个标量。l中的所有元素被加到一起,
        # 并以此计算关于[w,b]的梯度
        l = loss(net(X, w, b), y)  # X和y的小批量损失
 
 		# 求和之后算梯度,这里backward()会对w和b进行求导
        l.sum().backward()
        sgd([w, b], lr, batch_size)  # 使用参数的梯度更新参数

	# net(features, w, b):预测值
    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}')

输出:损失与估计误差
在这里插入图片描述
在这里插入图片描述

4、线性回归的简洁实现

这里采用深度学习框架来简洁实现线性回归模型,我们不必单独分配参数、不必定义我们的损失函数,也不必手动实现小批量随机梯度下降。,可以直接调用API来进行训练。

API(Application Programming Interface)应用程序接口。

4.1 生成数据集

d2l包:由李沐老师等人开发的《动手学深度学习》配套的包,提供一些数学运算工具。
synthetic_data():合成数据函数。

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)

next(iter(data_iter))使用iter构造Python迭代器,并使用next从迭代器中获取第一项。

4.2 读取数据集

TensorDataset 将张量的第一个维度视为数据集大小的维度,数据集在传入 DataLoader 后,该维度也是 batch_size 所在的维度。
shuffle表示对数据打乱顺序。

def load_array(data_arrays, batch_size, is_train=True):  #@save
    """构造一个PyTorch数据迭代器"""
    # 将传入的特征和标签作为list传到TensorDataset,里面得到一个pytorch的数据集
    dataset = data.TensorDataset(*data_arrays)
    # 调用Dataloader每次从dataset里面挑选batch_size个样本出来
    return data.DataLoader(dataset, batch_size, shuffle=is_train)

batch_size = 10
# 将特征和标签传入load_array
data_iter = load_array((features, labels), batch_size)

4.3 定义模型

对于标准深度学习模型,我们可以使用框架的预定义好的层。
首先定义一个模型变量net,它是一个Sequential类的实例。 Sequential类将多个层串联在一起。 当给定输入数据时,Sequential实例将数据传入到第一层, 然后将第一层的输出作为第二层的输入,以此类推。

Sequential类可以看成一个容器包装各层,当一个模型较简单的时候,我们可以使用torch.nn.Sequential类来实现简单的顺序连接模型。

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

# 第一个指定输入特征形状为2,第二个指定输出特征形状为单个标量为1
net = nn.Sequential(nn.Linear(2, 1))

4.4 初始化模型参数

在使用net之前,我们需要初始化模型参数。在这里,我们指定每个权重参数应该从均值为0、标准差为0.01的正态分布中随机采样, 偏置参数将初始化为零。

# net[0] 表示第0层,.weight访问w,data就是w的值,下划线的意思是,使用正态分布,替换掉权重data的值
net[0].weight.data.normal_(0, 0.01)
# 使用填充0,data是偏置b,替换掉偏差data的值
net[0].bias.data.fill_(0)

4.5 定义损失函数

计算均方误差使用的是MSELoss类,也称为平方L2范数。 默认情况下,它返回所有样本损失的平均值。

loss = nn.MSELoss()

4.6 定义优化算法

这里使用小批量随机梯度下降算法,该算法在PyTorch在optim模块中。
当我们实例化一个SGD实例时,我们要指定优化的参数 (可通过net.parameters()从我们的模型中获得)以及优化算法所需的超参数字典。 小批量随机梯度下降只需要设置lr值,这里设置为0.03。

net.parameters()是一个生成器,用于生成模型中的参数。其中,第一个参数是生成器的第一个元素,第二个参数是该元素的具体参数值。该函数用循环获取所有参数,并将其作为参数传递。

# net.parameters():net里面的所有参数,包括w,b,然后指定学习率lr
trainer = torch.optim.SGD(net.parameters(), lr=0.03)

4.7 训练

在每个迭代周期里,我们将完整遍历一次数据集(train_data), 不停地从中获取一个小批量的输入和相应的标签。 对于每一个小批量,我们会进行以下步骤:

  1. 通过调用net(X)生成预测并计算损失l(前向传播)。
  2. 通过进行反向传播来计算梯度。
  3. 通过调用优化器来更新模型参数。
num_epochs = 3 #迭代3次
for epoch in range(num_epochs):
    for X, y in data_iter:
        l = loss(net(X) ,y) #因为net自带参数,所以和之前不同的是不需要再传进去w和b
        trainer.zero_grad() #梯度清零
        l.backward()  #计算梯度
        trainer.step() #调用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)

输出:损失和w、b的估计误差
在这里插入图片描述
在这里插入图片描述

4.8 小结

  • 可以使用PyTorch的高级API更简洁地实现模型。
  • 在PyTorch中,data模块提供了数据处理工具,nn模块定义了大量的神经网络层和常见损失函数。

参考资料:
[1]动手学深度学习:http://zh-v2.d2l.ai/index.html
[2]跟李沐学AI:https://space.bilibili.com/1567748478

  • 8
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值