动手学深度学习 Task03:线性神经网络

01.线性回归

回归(regression)是能为一个或多个自变量与因变量之间关系建模的一类方法。在自然科学和社会科学领域,回归经常用来表示输入和输出之间的关系。

在机器学习领域中的大多数任务通常都与预测(prediction)有关。当我们想预测一个数值时,就会涉及到回归问题。常见的例子包括:预测价格(房屋、股票等)、预测住院时间(针对住院病人等)、预测需求(零售销量等)。但不是所有的预测都是回归问题。在后面的章节中,我们将介绍分类问题。分类问题的目标是预测数据属于一组类别中的哪一个。

线性模型

线性假设是指目标(房屋价格)可以表示为特征(面积和房龄)的加权和,如下面的式子:

p r i c e = w a r e a ⋅ a r e a + w a g e ⋅ a g e + b . \mathrm{price} = w_{\mathrm{area}} \cdot \mathrm{area} + w_{\mathrm{age}} \cdot \mathrm{age} + b. price=wareaarea+wageage+b.

w a r e a w_{\mathrm{area}} warea w a g e w_{\mathrm{age}} wage称为权重(weight),权重决定了每个特征对我们预测值的影响。 b b b称为偏置(bias)、偏移量(offset)或截距(intercept)。偏置是指当所有特征都取值为0时,预测值应该为多少。即使现实中不会有任何房子的面积是0或房龄正好是0年,我们仍然需要偏置项。如果没有偏置项,我们模型的表达能力将受到限制。严格来说, 是输入特征的一个仿射变换(affine transformation)。仿射变换的特点是通过加权和对特征进行线性变换(linear transformation),并通过偏置项来进行平移(translation)。

给定一个数据集,我们的目标是寻找模型的权重 w \mathbf{w} w和偏置 b b b,使得根据模型做出的预测大体符合数据里的真实价格。输出的预测值由输入特征通过线性模型的仿射变换决定,仿射变换由所选权重和偏置确定。

而在机器学习领域,我们通常使用的是高维数据集,建模时采用线性代数表示法会比较方便。
当我们的输入包含 d d d个特征时,我们将预测结果 y ^ \hat{y} y^(通常使用“尖角”符号表示 y y y的估计值)表示为:

y ^ = w 1 x 1 + . . . + w d x d + b . \hat{y} = w_1 x_1 + ... + w_d x_d + b. y^=w1x1+...+wdxd+b.

将所有特征放到向量 x ∈ R d \mathbf{x} \in \mathbb{R}^d xRd中,并将所有权重放到向量 w ∈ R d \mathbf{w} \in \mathbb{R}^d wRd中,我们可以用点积形式来简洁地表达模型:

y ^ = w ⊤ x + b . \hat{y} = \mathbf{w}^\top \mathbf{x} + b. y^=wx+b.

向量 x \mathbf{x} x对应于单个数据样本的特征。用符号表示的矩阵 X ∈ R n × d \mathbf{X} \in \mathbb{R}^{n \times d} XRn×d可以很方便地引用我们整个数据集的 n n n个样本。其中, X \mathbf{X} X的每一行是一个样本,每一列是一种特征。

对于特征集合 X \mathbf{X} X,预测值 y ^ ∈ R n \hat{\mathbf{y}} \in \mathbb{R}^n y^Rn可以通过矩阵-向量乘法表示为:

y ^ = X w + b {\hat{\mathbf{y}}} = \mathbf{X} \mathbf{w} + b y^=Xw+b

这个过程中的求和将使用广播机制。给定训练数据特征 X \mathbf{X} X和对应的已知标签 y \mathbf{y} y,线性回归的目标是找到一组权重向量 w \mathbf{w} w和偏置 b b b:当给定从 X \mathbf{X} X的同分布中取样的新样本特征时,这组权重向量和偏置能够使得新样本预测标签的误差尽可能小。

虽然我们相信给定 x \mathbf{x} x预测 y y y的最佳模型会是线性的,但我们很难找到一个有 n n n个样本的真实数据集,其中对于所有的 1 ≤ i ≤ n 1 \leq i \leq n 1in y ( i ) y^{(i)} y(i)完全等于 w ⊤ x ( i ) + b \mathbf{w}^\top \mathbf{x}^{(i)}+b wx(i)+b。无论我们使用什么手段来观察特征 X \mathbf{X} X和标签 y \mathbf{y} y,都可能会出现少量的观测误差。因此,即使确信特征与标签的潜在关系是线性的,我们也会加入一个噪声项来考虑观测误差带来的影响。

在开始寻找最好的模型参数(model parameters) w \mathbf{w} w b b b之前,我们还需要两个东西:
(1)一种模型质量的度量方式;
(2)一种能够更新模型以提高模型预测质量的方法。

损失函数

在我们开始考虑如何用模型拟合(fit)数据之前,我们需要确定一个拟合程度的度量。损失函数(loss function)能够量化目标的实际值与预测值之间的差距。通常我们会选择非负数作为损失,且数值越小表示损失越小,完美预测时的损失为0。回归问题中最常用的损失函数是平方误差函数。当样本 i i i的预测值为 y ^ ( i ) \hat{y}^{(i)} y^(i),其相应的真实标签为 y ( i ) y^{(i)} y(i)时,平方误差可以定义为以下公式:

l ( i ) ( w , b ) = 1 2 ( y ^ ( i ) − y ( i ) ) 2 . l^{(i)}(\mathbf{w}, b) = \frac{1}{2} \left(\hat{y}^{(i)} - y^{(i)}\right)^2. l(i)(w,b)=21(y^(i)y(i))2.

常数 1 2 \frac{1}{2} 21不会带来本质的差别,但这样在形式上稍微简单一些(因为当我们对损失函数求导后常数系数为1)。由于训练数据集并不受我们控制,所以经验误差只是关于模型参数的函数。

由于平方误差函数中的二次方项,估计值 y ^ ( i ) \hat{y}^{(i)} y^(i)和观测值 y ( i ) y^{(i)} y(i)之间较大的差异将导致更大的损失。为了度量模型在整个数据集上的质量,我们需计算在训练集 n n n个样本上的损失均值(也等价于求和)。

L ( w , b ) = 1 n ∑ i = 1 n l ( i ) ( w , b ) = 1 n ∑ i = 1 n 1 2 ( w ⊤ x ( i ) + b − y ( i ) ) 2 . L(\mathbf{w}, b) =\frac{1}{n}\sum_{i=1}^n l^{(i)}(\mathbf{w}, b) =\frac{1}{n} \sum_{i=1}^n \frac{1}{2}\left(\mathbf{w}^\top \mathbf{x}^{(i)} + b - y^{(i)}\right)^2. L(w,b)=n1i=1nl(i)(w,b)=n1i=1n21(wx(i)+by(i))2.

在训练模型时,我们希望寻找一组参数( w ∗ , b ∗ \mathbf{w}^*, b^* w,b),这组参数能最小化在所有训练样本上的总损失。如下式:

w ∗ , b ∗ = argmin ⁡ w , b   L ( w , b ) . \mathbf{w}^*, b^* = \operatorname*{argmin}_{\mathbf{w}, b}\ L(\mathbf{w}, b). w,b=w,bargmin L(w,b).

随机梯度下降

即使在我们无法得到解析解的情况下,我们仍然可以有效地训练模型。在许多任务上,那些难以优化的模型效果要更好。因此,弄清楚如何训练这些难以优化的模型是非常重要的。

本书中我们用到一种名为梯度下降(gradient descent)的方法,这种方法几乎可以优化所有深度学习模型。它通过不断地在损失函数递减的方向上更新参数来降低误差。

梯度下降最简单的用法是计算损失函数(数据集中所有样本的损失均值)关于模型参数的导数(在这里也可以称为梯度)。但实际中的执行可能会非常慢:因为在每一次更新参数之前,我们必须遍历整个数据集。因此,我们通常会在每次需要计算更新的时候随机抽取一小批样本,这种变体叫做小批量随机梯度下降(minibatch stochastic gradient descent)。

在每次迭代中,我们首先随机抽样一个小批量 B \mathcal{B} B,它是由固定数量的训练样本组成的。然后,我们计算小批量的平均损失关于模型参数的导数(也可以称为梯度)。最后,我们将梯度乘以一个预先确定的正数 η \eta η,并从当前参数的值中减掉。

我们用下面的数学公式来表示这一更新过程( ∂ \partial 表示偏导数):

( w , b ) ← ( w , b ) − η ∣ B ∣ ∑ i ∈ B ∂ ( w , b ) l ( i ) ( w , b ) . (\mathbf{w},b) \leftarrow (\mathbf{w},b) - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_{(\mathbf{w},b)} l^{(i)}(\mathbf{w},b). (w,b)(w,b)BηiB(w,b)l(i)(w,b).

总结一下,算法的步骤如下:
(1)初始化模型参数的值,如随机初始化;
(2)从数据集中随机抽取小批量样本且在负梯度的方向上更新参数,并不断迭代这一步骤。对于平方损失和仿射变换,我们可以明确地写成如下形式:

w ← w − η ∣ B ∣ ∑ i ∈ B ∂ w l ( i ) ( w , b ) = w − η ∣ B ∣ ∑ i ∈ B x ( i ) ( w ⊤ x ( i ) + b − y ( i ) ) , b ← b − η ∣ B ∣ ∑ i ∈ B ∂ b l ( i ) ( w , b ) = b − η ∣ B ∣ ∑ i ∈ B ( w ⊤ x ( i ) + b − y ( i ) ) . \begin{aligned} \mathbf{w} &\leftarrow \mathbf{w} - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_{\mathbf{w}} l^{(i)}(\mathbf{w}, b) = \mathbf{w} - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \mathbf{x}^{(i)} \left(\mathbf{w}^\top \mathbf{x}^{(i)} + b - y^{(i)}\right),\\ b &\leftarrow b - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_b l^{(i)}(\mathbf{w}, b) = b - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \left(\mathbf{w}^\top \mathbf{x}^{(i)} + b - y^{(i)}\right). \end{aligned} wbwBηiBwl(i)(w,b)=wBηiBx(i)(wx(i)+by(i)),bBηiBbl(i)(w,b)=bBηiB(wx(i)+by(i)).

公式中的 w \mathbf{w} w x \mathbf{x} x都是向量。在这里,更优雅的向量表示法比系数表示法(如 w 1 , w 2 , … , w d w_1, w_2, \ldots, w_d w1,w2,,wd)更具可读性。 ∣ B ∣ |\mathcal{B}| B表示每个小批量中的样本数,这也称为批量大小(batch size)。 η \eta η表示学习率(learning rate)。批量大小和学习率的值通常是手动预先指定,而不是通过模型训练得到的。这些可以调整但不在训练过程中更新的参数称为超参数(hyperparameter)。调参(hyperparameter tuning)是选择超参数的过程。超参数通常是我们根据训练迭代结果来调整的,而训练迭代结果是在独立的验证数据集(validation dataset)上评估得到的。

在训练了预先确定的若干迭代次数后(或者直到满足某些其他停止条件后),我们记录下模型参数的估计值,表示为 w ^ , b ^ \hat{\mathbf{w}}, \hat{b} w^,b^。但是,即使我们的函数确实是线性的且无噪声,这些估计值也不会使损失函数真正地达到最小值。因为算法会使得损失向最小值缓慢收敛,但却不能在有限的步数内非常精确地达到最小值。

线性回归恰好是一个在整个域中只有一个最小值的学习问题。但是对像深度神经网络这样复杂的模型来说,损失平面上通常包含多个最小值。深度学习实践者很少会去花费大力气寻找这样一组参数,使得在训练集上的损失达到最小。事实上,更难做到的是找到一组参数,这组参数能够在我们从未见过的数据上实现较低的损失,这一挑战被称为泛化(generalization)。

用模型进行预测

给定“已学习”的线性回归模型 w ^ ⊤ x + b ^ \hat{\mathbf{w}}^\top \mathbf{x} + \hat{b} w^x+b^,现在我们可以通过房屋面积 x 1 x_1 x1和房龄 x 2 x_2 x2来估计一个(未包含在训练数据中的)新房屋价格。给定特征估计目标的过程通常称为预测(prediction)或推断(inference)。

02.线性回归的实现

通过使用深度学习框架来简洁地实现 线性回归模型 生成数据集

def synthetic_data(w, b, num_examples):  
    """生成 y = Xw + b + 噪声。"""
    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)

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

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)

next(iter(data_iter))

输出

[tensor([[-1.0640,  2.5572],
         [-0.1636,  0.6169],
         [ 0.3901, -0.0894],
         [-0.9377,  1.0138],
         [-0.7310, -1.2113],
         [ 1.7773,  0.3604],
         [-1.1299, -2.0182],
         [ 0.6764, -0.9419],
         [-1.0216,  1.7837],
         [-0.7049,  0.3120]]),
 tensor([[-6.6252],
         [ 1.7683],
         [ 5.2709],
         [-1.1358],
         [ 6.8711],
         [ 6.5422],
         [ 8.8020],
         [ 8.7636],
         [-3.9218],
         [ 1.7232]])]

使用框架的预定义好的层

from torch import nn

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

初始化模型参数

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

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

loss = nn.MSELoss()

实例化 SGD 实例

trainer = torch.optim.SGD(net.parameters(), lr=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}')

输出

epoch 1, loss 0.000207
epoch 2, loss 0.000107
epoch 3, loss 0.000107

比较生成数据集的真实参数和通过有限数据训练获得的模型参数

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的估计误差: tensor([-0.0004,  0.0002])
b的估计误差: tensor([-0.0004])

02.softmax回归

分类问题

我们从一个图像分类问题开始。假设每次输入是一个 2 × 2 2\times2 2×2的灰度图像。我们可以用一个标量表示每个像素值,每个图像对应四个特征 x 1 , x 2 , x 3 , x 4 x_1, x_2, x_3, x_4 x1,x2,x3,x4。此外,假设每个图像属于类别“猫”“鸡”和“狗”中的一个。

接下来,我们要选择如何表示标签。我们有两个明显的选择:最直接的想法是选择 y ∈ { 1 , 2 , 3 } y \in \{1, 2, 3\} y{1,2,3},其中整数分别代表 { 狗 , 猫 , 鸡 } \{\text{狗}, \text{猫}, \text{鸡}\} {,,}。这是在计算机上存储此类信息的有效方法。如果类别间有一些自然顺序,比如说我们试图预测 { 婴儿 , 儿童 , 青少年 , 青年人 , 中年人 , 老年人 } \{\text{婴儿}, \text{儿童}, \text{青少年}, \text{青年人}, \text{中年人}, \text{老年人}\} {婴儿,儿童,青少年,青年人,中年人,老年人},那么将这个问题转变为回归问题,并且保留这种格式是有意义的。

但是一般的分类问题并不与类别之间的自然顺序有关。幸运的是,统计学家很早以前就发明一种表示分类数据的简单方法:独热编码(one-hot encoding)。独热编码是一个向量,它的分量和类别一样多。类别对应的分量设置为1,其他所有分量设置为0。在我们的例子中,标签 y y y将是一个三维向量,其中 ( 1 , 0 , 0 ) (1, 0, 0) (1,0,0)对应于“猫”、 ( 0 , 1 , 0 ) (0, 1, 0) (0,1,0)对应于“鸡”、 ( 0 , 0 , 1 ) (0, 0, 1) (0,0,1)对应于“狗”:

y ∈ { ( 1 , 0 , 0 ) , ( 0 , 1 , 0 ) , ( 0 , 0 , 1 ) } . y \in \{(1, 0, 0), (0, 1, 0), (0, 0, 1)\}. y{(1,0,0),(0,1,0),(0,0,1)}.

网络架构

为了估计所有可能类别的条件概率,我们需要一个有多个输出的模型,每个类别对应一个输出。为了解决线性模型的分类问题,我们需要和输出一样多的仿射函数(affine function)。每个输出对应于它自己的仿射函数。在我们的例子中,由于我们有4个特征和3个可能的输出类别,我们将需要12个标量来表示权重(带下标的 w w w),3个标量来表示偏置(带下标的 b b b)。下面我们为每个输入计算三个未规范化的预测(logit): o 1 o_1 o1 o 2 o_2 o2 o 3 o_3 o3

o 1 = x 1 w 11 + x 2 w 12 + x 3 w 13 + x 4 w 14 + b 1 , o 2 = x 1 w 21 + x 2 w 22 + x 3 w 23 + x 4 w 24 + b 2 , o 3 = x 1 w 31 + x 2 w 32 + x 3 w 33 + x 4 w 34 + b 3 . \begin{aligned} o_1 &= x_1 w_{11} + x_2 w_{12} + x_3 w_{13} + x_4 w_{14} + b_1,\\ o_2 &= x_1 w_{21} + x_2 w_{22} + x_3 w_{23} + x_4 w_{24} + b_2,\\ o_3 &= x_1 w_{31} + x_2 w_{32} + x_3 w_{33} + x_4 w_{34} + b_3. \end{aligned} o1o2o3=x1w11+x2w12+x3w13+x4w14+b1,=x1w21+x2w22+x3w23+x4w24+b2,=x1w31+x2w32+x3w33+x4w34+b3.

我们可以用神经网络图来描述这个计算过程。与线性回归一样,softmax回归也是一个单层神经网络。由于计算每个输出 o 1 o_1 o1 o 2 o_2 o2 o 3 o_3 o3取决于所有输入 x 1 x_1 x1 x 2 x_2 x2 x 3 x_3 x3 x 4 x_4 x4,所以softmax回归的输出层也是全连接层。

在这里插入图片描述

为了更简洁地表达模型,我们仍然使用线性代数符号。通过向量形式表达为 o = W x + b \mathbf{o} = \mathbf{W} \mathbf{x} + \mathbf{b} o=Wx+b,这是一种更适合数学和编写代码的形式。由此,我们已经将所有权重放到一个 3 × 4 3 \times 4 3×4矩阵中。对于给定数据样本的特征 x \mathbf{x} x,我们的输出是由权重与输入特征进行矩阵-向量乘法再加上偏置 b \mathbf{b} b得到的。

全连接层的参数开销

正如我们将在后续章节中看到的,在深度学习中,全连接层无处不在。然而,顾名思义,全连接层是“完全”连接的,可能有很多可学习的参数。具体来说,对于任何具有 d d d个输入和 q q q个输出的全连接层,参数开销为 O ( d q ) \mathcal{O}(dq) O(dq),这个数字在实践中可能高得令人望而却步。幸运的是,将 d d d个输入转换为 q q q个输出的成本可以减少到 O ( d q n ) \mathcal{O}(\frac{dq}{n}) O(ndq),其中超参数 n n n可以由我们灵活指定,以在实际应用中平衡参数节约和模型有效性

softmax运算

现在我们将优化参数以最大化观测数据的概率。为了得到预测结果,我们将设置一个阈值,如选择具有最大概率的标签。

我们希望模型的输出 y ^ j \hat{y}_j y^j可以视为属于类 j j j的概率,然后选择具有最大输出值的类别 argmax ⁡ j y j \operatorname*{argmax}_j y_j argmaxjyj作为我们的预测。例如,如果 y ^ 1 \hat{y}_1 y^1 y ^ 2 \hat{y}_2 y^2 y ^ 3 \hat{y}_3 y^3分别为0.1、0.8和0.1,那么我们预测的类别是2,在我们的例子中代表“鸡”。

然而我们能否将未规范化的预测 o o o直接视作我们感兴趣的输出呢?答案是否定的。因为将线性层的输出直接视为概率时存在一些问题:一方面,我们没有限制这些输出数字的总和为1。另一方面,根据输入的不同,它们可以为负值。这些违反了概率基本公理。

要将输出视为概率,我们必须保证在任何数据上的输出都是非负的且总和为1。此外,我们需要一个训练的目标函数,来激励模型精准地估计概率。例如,在分类器输出0.5的所有样本中,我们希望这些样本是刚好有一半实际上属于预测的类别。这个属性叫做校准(calibration)。

社会科学家邓肯·卢斯于1959年在选择模型(choice model)的理论基础上发明的softmax函数正是这样做的:softmax函数能够将未规范化的预测变换为非负数并且总和为1,同时让模型保持可导的性质。为了完成这一目标,我们首先对每个未规范化的预测求幂,这样可以确保输出非负。为了确保最终输出的概率值总和为1,我们再让每个求幂后的结果除以它们的总和。如下式:

y ^ = s o f t m a x ( o ) 其中 y ^ j = exp ⁡ ( o j ) ∑ k exp ⁡ ( o k ) \hat{\mathbf{y}} = \mathrm{softmax}(\mathbf{o})\quad \text{其中}\quad \hat{y}_j = \frac{\exp(o_j)}{\sum_k \exp(o_k)} y^=softmax(o)其中y^j=kexp(ok)exp(oj)

这里,对于所有的 j j j总有 0 ≤ y ^ j ≤ 1 0 \leq \hat{y}_j \leq 1 0y^j1。因此, y ^ \hat{\mathbf{y}} y^可以视为一个正确的概率分布。softmax运算不会改变未规范化的预测 o \mathbf{o} o之间的大小次序,只会确定分配给每个类别的概率。因此,在预测过程中,我们仍然可以用下式来选择最有可能的类别。

argmax ⁡ j y ^ j = argmax ⁡ j o j . \operatorname*{argmax}_j \hat y_j = \operatorname*{argmax}_j o_j. jargmaxy^j=jargmaxoj.

尽管softmax是一个非线性函数,但softmax回归的输出仍然由输入特征的仿射变换决定。因此,softmax回归是一个线性模型(linear model)。

softmax回归的实现

通过深度学习框架的高级API能够使实现 softmax 回归变得更加容易

import torch
from torch import nn
from d2l import torch as d2l

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

Softmax 回归的输出层是一个全连接层

net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))

def init_weights(m):
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, std=0.01)

net.apply(init_weights);

在交叉熵损失函数中传递未归一化的预测,并同时计算softmax及其对数

loss = nn.CrossEntropyLoss()

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

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

调用 之前 定义的训练函数来训练模型

num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值