李沐深度学习-线性回归softmax

线性回归假设输出与各个输入之间是线性关系。模型输出 y^ 是线性回归对真实价格 y 的预测或估计。我们通常允许它们之间有一定误差。

通过数据来寻找特定的模型参数值,使模型在数据上的误差尽可能小。这个过程叫作模型训练(model training)。

1) 训练数据

我们通常收集一系列的真实数据,例如多栋房屋的真实售出价格和它们对应的面积和房龄。我们希望在这个数据上面寻找模型参数来使模型的预测价格与真实价格的误差最小。在机器学习术语里,该数据集被称为训练数据集(training data set)或训练集(training set),一栋房屋被称为一个样本(sample),其真实售出价格叫作标签(label),用来预测标签的两个因素叫作特征(feature)。特征用来表征样本的特点。

(2) 损失函数

在模型训练中,我们需要衡量价格预测值与真实值之间的误差。通常我们会选取一个非负数作为误差,且数值越小表示误差越小。

一个常用的选择是平方函数。它在评估索引为 i 的样本误差的表达式为

ℓ(i)(w1,w2,b)=12(y^(i)−y(i))2

其中常数 1/2 使对平方项求导后的常数系数为1。在机器学习里,将衡量误差的函数称为损失函数(loss function)。这里使用的平方误差函数也称为平方损失(square loss)。

(3) 优化算法

通过优化算法有限次迭代模型参数来尽可能降低损失函数的值。这类解叫作数值解(numerical solution)。

在求数值解的优化算法中,小批量随机梯度下降(mini-batch stochastic gradient descent)在深度学习中被广泛使用。它的算法很简单:先选取一组模型参数的初始值,如随机选取。给模型一个“起点”;接下来对参数进行多次迭代,使每次迭代都可能降低损失函数的值。在每次迭代中,先随机均匀采样一个由固定数目训练数据样本所组成的小批量(mini-batch),然后求小批量中数据样本的平均损失有关模型参数的导数(梯度),最后用此结果与预先设定的一个正数的乘积即学习率相乘的结果作为模型参数在本次迭代的更新量。

需要强调的是,这里的批量大小和学习率的值是人为设定的,并不是通过模型训练学出的,因此叫作超参数(hyperparameter)。我们通常所说的“调参”指的正是调节超参数,例如通过反复试错来找到超参数合适的值。

线性回归是一个单层神经网络。神经网络图隐去了模型参数权重和偏差

输入分别为x1 和x2,因此输入层的输入个数为 2。输入个数也叫特征数或特征向量维度。网络的输出o输出层的输出个数为 1。需要注意的是,我们直接将图3-1 中神经网络的输出o作为线性回归的输出,即

。由于输入层并不涉及计算,按照惯例,图3-1 所示的神经网络的层数为 1。所以,线性回归是一个单层神经网络。输出层中负责计算o的单元又叫神经元。在线性回归中,o的计算依赖于x1和x2。也就是说,输出层中的神经元和输入层中各个输入完全连接。因此,这里的输出层又叫全连接层(fully-connected layer)或稠密层 ( dense layer)。

2.矢量计算表达式

在模型训练或预测时,我们常常会同时处理多个数据样本并用到矢量计算。在介绍线性回归的矢量计算表达式之前,让我们先考虑对两个向量相加的两种方法。

下面先定义两个 1 000 维的向量。

  1. In[1]:from mxnet import nd
  2. from time import time
  3. a = nd.ones(shape=1000)
  4. b = nd.ones(shape=1000)

向量相加的一种方法是,将这两个向量按元素逐一做标量加法。

  1. In[2]: start = time()
  2. c = nd.zeros(shape=1000)
  3. for i in range(1000):
  4. c[i]= a[i]+ b[i]
  5. time()- start
  6. Out[2]:0.16967248916625977

向量相加的另一种方法是,将这两个向量直接做矢量加法

  1. In[3]: start = time()
  2. d = a + b
  3. time()- start
  4. Out[3]:0.00031185150146484375

结果很明显,后者比前者更省时。因此,我们应该尽可能采用矢量计算,以提升计算效率。

让我们再次回到本节的房价预测问题。如果我们对训练数据集里的3个房屋样本(索引分别为1、2和3)逐一预测价格,将得到

现在,我们将上面3个等式转化成矢量计算。设

对 3 个房屋样本预测价格的矢量计算表达式为

,其中的加法运算使用了广播机制。例如:

  1. In[4]: a = nd.ones(shape=3)
  2. b =10
  3. a + b

其中梯度是损失有关3个为标量的模型参数的偏导数组成的向量:

小结
    • 和大多数深度学习模型一样,对于线性回归这样一种单层神经网络,它的基本要素包括模型、训练数据、损失函数和优化算法。
    • 既可以用神经网络图表示线性回归,又可以用矢量计算表示该模型。
    • 应该尽可能采用矢量计算,以提升计算效率。

线性回归的从零开始实现

只利用NDArrayautograd来实现一个线性回归的训练。

  1. In[1]:%matplotlib inline
  2. fromIPythonimport display
  3. from matplotlib import pyplot as plt
  4. from mxnet import autograd, nd
  5. importrandom

其中噪声项ϵ服从均值为 0、标准差为 0.01 的正态分布。噪声代表了数据集中无意义的干扰。下面,让我们生成数据集。

  1. In[2]: num_inputs =2
  2. num_examples =1000
  3. true_w =[2,-3.4]
  4. true_b =4.2
  5. features = nd.random.normal(scale=1, shape=(num_examples, num_inputs))
  6. labels = true_w[0]* features[:,0]+ true_w[1]* features[:,1]+ true_b
  7. labels += nd.random.normal(scale=0.01, shape=labels.shape)

注意,features的每一行是一个长度为 2 的向量,而labels的每一行是一个长度为1的向量(标量)。

  1. In[3]: features[0], labels[0]
  2. Out[3]:(
  3. [2.21220640.7740038]
  4. <NDArray2@cpu(0)>,
  5. [6.000587]
  6. <NDArray1@cpu(0)>)

通过生成第二个特征features[:, 1]和标签labels散点图,可以更直观地观察两者间的线性关系。

  1. In[4]:def use_svg_display():
  2. # 用矢量图显示
  3. display.set_matplotlib_formats('svg')
  4. def set_f igsize(f igsize=(3.5,2.5)):
  5. use_svg_display()
  6. # 设置图的尺寸
  7. plt.rcParams['f igure.f igsize']= f igsize
  8. set_f igsize()
  9. plt.scatter(features[:,1].asnumpy(), labels.asnumpy(),1);# 加分号只显示图

我们将上面的plt作图函数以及use_svg_display函数和set_f igsize函数定义在d2lzh包里。以后在作图时,我们将直接调用d2lzh.plt。由于pltd2lzh包中是一个全局变量,我们在作图前只需要调用d2lzh.set_figsize()即可打印矢量图并设置图的尺寸。

3.2.2 读取数据集

在训练模型的时候,我们需要遍历数据集并不断读取小批量数据样本。这里我们定义一个函数:它每次返回batch_size(批量大小)个随机样本的特征和标签。

  1. In[5]:# 本函数已保存在d2lzh包中方便以后使用
  2. def data_iter(batch_size, features, labels):
  3. num_examples = len(features)
  4. indices = list(range(num_examples))
  5. random.shuff le(indices)# 样本的读取顺序是随机的
  6. for i in range(0, num_examples, batch_size):
  7. j = nd.array(indices[i: min(i + batch_size, num_examples)])
  8. yield features.take(j), labels.take(j)# take函数根据索引返回对应元素

3.3 线性回归的简洁实现

3.3.3 定义模型

在构造模型时,我们在该容器中依次添加层。当给定输入数据时,容器中的每一层将依次计算并将输出作为下一层的输入。

  1. In[4]:from mxnet.gluon importnn
  2. net = nn.Sequential()
class LinearNet(nn.Module):
    def __init__(self, n_feature):
        super(LinearNet, self).__init__()
        self.linear = nn.Linear(n_feature, 1)
    # forward 定义前向传播
    def forward(self, x):
        y = self.linear(x)
        return y
    
net = LinearNet(num_inputs)
print(net) # 使用print可以打印出网络的结构

输出:

LinearNet(
  (linear): Linear(in_features=2, out_features=1, bias=True)
)

net = nn.Sequential()
net.add_module('linear', nn.Linear(num_inputs, 1))
# net.add_module ......


print(net)
print(net[0])

输出:

Sequential(
  (linear): Linear(in_features=2, out_features=1, bias=True)
)
Linear(in_features=2, out_features=1, bias=True)

可以通过net.parameters()来查看模型所有的可学习参数,此函数将返回一个生成器。

for param in net.parameters():
    print(param)

输出:

Parameter containing:
tensor([[-0.0277,  0.2771]], requires_grad=True)
Parameter containing:
tensor([0.3395], requires_grad=True)

回顾图3.1中线性回归在神经网络图中的表示。作为一个单层神经网络,线性回归输出层中的神经元和输入层中各个输入完全连接。因此,线性回归的输出层又叫全连接层。

注意:torch.nn仅支持输入一个batch的样本不支持单个样本输入,如果只有单个样本,可使用input.unsqueeze(0)来添加一维。

3.3.4 初始化模型参数

在使用net前,我们需要初始化模型参数,如线性回归模型中的权重和偏差。PyTorch在init模块中提供了多种参数初始化方法。这里的initinitializer的缩写形式。我们通过init.normal_将权重参数每个元素初始化为随机采样于均值为0、标准差为0.01的正态分布。偏差会初始化为零。

from torch.nn import init

init.normal_(net[0].weight, mean=0, std=0.01)
init.constant_(net[0].bias, val=0)  # 也可以直接修改bias的data: net[0].bias.data.fill_(0)

注:如果这里的net是用3.3.3节一开始的代码自定义的,那么上面代码会报错,net[0].weight应改为net.linear.weightbias亦然。因为net[0]这样根据下标访问子模块的写法只有当net是个ModuleList或者Sequential实例时才可以,详见4.1节。

3.3.5 定义损失函数

PyTorch在nn模块中提供了各种损失函数,这些损失函数可看作是一种特殊的层,PyTorch也将这些损失函数实现为nn.Module的子类。我们现在使用它提供的均方误差损失作为模型的损失函数。

loss = nn.MSELoss()

3.3.6 定义优化算法

同样,我们也无须自己实现小批量随机梯度下降算法。torch.optim模块提供了很多常用的优化算法比如SGD、Adam和RMSProp等。下面我们创建一个用于优化net所有参数的优化器实例,并指定学习率为0.03的小批量随机梯度下降(SGD)为优化算法。

import torch.optim as optim

optimizer = optim.SGD(net.parameters(), lr=0.03)
print(optimizer)

输出:

SGD (
Parameter Group 0
    dampening: 0
    lr: 0.03
    momentum: 0
    nesterov: False
    weight_decay: 0
)

我们还可以为不同子网络设置不同的学习率,这在finetune时经常用到。例:

optimizer =optim.SGD([
                # 如果对某个参数不指定学习率,就使用最外层的默认学习率
                {'params': net.subnet1.parameters()}, # lr=0.03
                {'params': net.subnet2.parameters(), 'lr': 0.01}
            ], lr=0.03)
# 调整学习率
for param_group in optimizer.param_groups:
    param_group['lr'] *= 0.1 # 学习率为之前的0.1倍

3.3.7 训练模型

在使用Gluon训练模型时,我们通过调用optim实例的step函数来迭代模型参数。按照小批量随机梯度下降的定义,我们在step函数中指明批量大小,从而对批量中样本梯度求平均。

num_epochs = 3
for epoch in range(1, num_epochs + 1):
    for X, y in data_iter:
        output = net(X)
        l = loss(output, y.view(-1, 1))
        optimizer.zero_grad() # 梯度清零,等价于net.zero_grad()
        l.backward()
        optimizer.step()
    print('epoch %d, loss: %f' % (epoch, l.item()))

输出:

epoch 1, loss: 0.000457
epoch 2, loss: 0.000081
epoch 3, loss: 0.000198

下面我们分别比较学到的模型参数和真实的模型参数。我们从net获得需要的层,并访问其权重(weight)和偏差(bias)。学到的参数和真实的参数很接近。

dense = net[0]
print(true_w, dense.weight)
print(true_b, dense.bias)

softmax回归

和线性回归不同,softmax回归的输出单元从一个变成了多个,且引入了softmax运算使输出更适合离散值的预测和训练。

分类问题

图像分类问题,其输入图像的高和宽均为2像素,且色彩为灰度。这样每个像素值都可以用一个标量表示。我们将图像中的4像素分别记为$x_1, x_2, x_3, x_4$。假设训练数据集中图像的真实标签为狗、猫或鸡(假设可以用4像素表示出这3种动物),这些标签分别对应离散值$y_1, y_2, y_3$。

我们通常使用离散的数值来表示类别,例如$y_1=1, y_2=2, y_3=3$。如此,一张图像的标签为1、2和3这3个数值中的一个。虽然我们仍然可以使用回归模型来进行建模,并将预测值就近定点化到1、2和3这3个离散值之一,但这种连续值到离散值的转化通常会影响到分类质量。因此我们一般使用更加适合离散值输出的模型来解决分类问题。

softmax回归模型

softmax回归跟线性回归一样将输入特征与权重做线性叠加。与线性回归的一个主要不同在于,softmax回归的输出值个数等于标签里的类别数。因为一共有4种特征和3种输出动物类别,所以权重包含12个标量(带下标的$w$)、偏差包含3个标量(带下标的$b$),且对每个输入计算$o_1, o_2, o_3$这3个输出

用神经网络图描绘了上面的计算。softmax回归同线性回归一样,也是一个单层神经网络。由于每个输出$o_1, o_2, o_3$的计算都要依赖于所有的输入$x_1, x_2, x_3, x_4$,softmax回归的输出层也是一个全连接层。

交叉熵损失函数

使用更适合衡量两个概率分布差异的测量函数。其中,交叉熵(cross entropy)是一个常用的衡量方法

遇到一个样本有多个标签时,例如图像里含有不止一个物体时,我们并不能做这一步简化。但即便对于这种情况,交叉熵同样只关心对图像中出现的物体类别的预测概率。

在训练好softmax回归模型后,给定任一样本特征,就可以预测每个输出类别的概率。通常,我们把预测概率最大的类别作为输出类别。如果它与真实类别(标签)一致,说明这次预测是正确的。

使用准确率(accuracy)来评价模型的表现。它等于正确预测数量与总预测数量之比。

当y=1时:L与预测输出的关系如图所示

  • 小结

  • softmax回归适用于分类问题。它使用softmax运算输出类别的概率分布。
  • softmax回归是一个单层神经网络,输出个数等于分类问题中的类别个数。
  • 交叉熵适合衡量两个概率分布的差异。

图像分类数据集(Fashion-MNIST)

在介绍softmax回归的实现前我们先引入一个多类图像分类数据集。以方便我们观察比较算法之间在模型精度和计算效率上的区别。

torchvision主要由以下几部分构成:

  1. torchvision.datasets: 一些加载数据的函数及常用的数据集接口;
  2. torchvision.models: 包含常用的模型结构(含预训练模型),例如AlexNet、VGG、ResNet等;
  3. torchvision.transforms: 常用的图片变换,例如裁剪、旋转等;
  4. torchvision.utils: 其他的一些有用的方法。

小结

  • Fashion-MNIST是一个10类服饰分类数据集,之后章节里将使用它来检验不同算法的表现。
  • 我们将高和宽分别为$h$和$w$像素的图像的形状记为$h \times w$或(h,w)
import torch
from torch import nn
from torch.nn import init
import numpy as np
import sys
sys.path.append("..") 
import d2lzh_pytorch as d2l

3.7.1 获取和读取数据

batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

3.7.2 定义和初始化模型

softmax回归的输出层是一个全连接层,所以我们用一个线性模块就可以了。因为前面我们数据返回的每个batch样本x的形状为(batch_size, 1, 28, 28), 所以我们要先用view()x的形状转换成(batch_size, 784)才送入全连接层。

num_inputs = 784
num_outputs = 10

class LinearNet(nn.Module):
    def __init__(self, num_inputs, num_outputs):
        super(LinearNet, self).__init__()
        self.linear = nn.Linear(num_inputs, num_outputs)
    def forward(self, x): # x shape: (batch, 1, 28, 28)
        y = self.linear(x.view(x.shape[0], -1))
        return y
    
net = LinearNet(num_inputs, num_outputs)

我们将对x的形状转换的这个功能自定义一个FlattenLayer并记录在d2lzh_pytorch中方便后面使用。

class FlattenLayer(nn.Module):
    def __init__(self):
        super(FlattenLayer, self).__init__()
    def forward(self, x): # x shape: (batch, *, *, ...)
        return x.view(x.shape[0], -1)

这样我们就可以更方便地定义我们的模型:

from collections import OrderedDict

net = nn.Sequential(
    # FlattenLayer(),
    # nn.Linear(num_inputs, num_outputs)
    OrderedDict([
        ('flatten', FlattenLayer()),
        ('linear', nn.Linear(num_inputs, num_outputs))
    ])
)

然后,我们使用均值为0、标准差为0.01的正态分布随机初始化模型的权重参数。

init.normal_(net.linear.weight, mean=0, std=0.01)
init.constant_(net.linear.bias, val=0) 

3.7.3 softmax和交叉熵损失函数

loss = nn.CrossEntropyLoss()

3.7.4 定义优化算法

我们使用学习率为0.1的小批量随机梯度下降作为优化算法。

optimizer = torch.optim.SGD(net.parameters(), lr=0.1)

3.7.5 训练模型

num_epochs = 5
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, optimizer)

输出:

epoch 1, loss 0.0031, train acc 0.745, test acc 0.790
epoch 2, loss 0.0022, train acc 0.812, test acc 0.807
epoch 3, loss 0.0021, train acc 0.825, test acc 0.806
epoch 4, loss 0.0020, train acc 0.832, test acc 0.810
epoch 5, loss 0.0019, train acc 0.838, test acc 0.823

小结

  • PyTorch提供的函数往往具有更好的数值稳定性。
  • 可以使用PyTorch更简洁地实现softmax回归。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值