Skip to content

第三章 线性神经网络

在介绍深度神经网络之前,我们需要了解神经网络训练的基础知识。本章我们将介绍神经网络的整个训练过程,包括:

  • 定义简单的神经网络架构
  • 数据处理
  • 指定损失函数
  • 如何训练模型

为了更容易学习,我们将从经典算法 —— 线性神经网络 开始,介绍神经网络的基础知识。经典统计学习 技术中的线性回归和 softmax 回归可以视为线性神经网络,这些知识将为本书其他部分中更复杂的技术奠定基础。

3.1 线性回归

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

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

  • 预测价格:房屋、股票等
  • 预测住院时间:针对住院病人等
  • 预测需求:零售销量等

但不是所有的预测都是回归问题。在后面的章节中,我们将介绍 分类问题。分类问题的目标是预测数据属于 一组类别中的哪一个

3.1.1 线性回归的基本元素

线性回归 linear regression 可以追溯到 19 世纪初,它在回归的各种标准工具中最简单而且最流行。线性回归基于几个简单的假设:

  • 首先,假设自变量 x 和因变量 y 之间的关系是线性的,即 y 可以表示为 x 中元素的加权和,这里通常允许包含观测值的一些噪声
  • 其次,我们假设任何噪声都比较正常,如噪声遵循正态分布

为了解释线性回归,我们举一个实际的例子:我们希望根据房屋的面积(平方英尺)和房龄(年)来估算房屋价格(美元)。为了开发一个能预测房价的模型,我们需要收集一个真实的数据集。这个数据集包括了房屋的销售价格、面积和房龄。在机器学习的术语中:

  • 训练数据集 training data set:收集到的数据集
    • 训练集 training set
  • 样本 sample:每行数据,比如一次房屋交易相对应的数据
    • 数据点 data point
    • 数据样本 data instance
  • 标签 label:试图预测的目标,比如房屋的价格
    • 目标 target
  • 特征 feature:预测所依据的自变量:比如面积和房龄
    • 协变量 covariate

通常,我们使用一个固定的字母 n 来表示数据集中的样本个数。对于索引为 i 的样本,也就是第 i 个样本,它的输入和输出分别是自变量和因变量,即:

x(i)=[x1(i),x2(i)],y(i)

例如其中的 x1 代表自变量中的面积,x2 代表自变量中的房龄。

3.1.1.1 线性模型

线性假设是指 目标/标签可以表示为特征的加权和,也就是房屋价格可以表示为面积和房龄的线性加权组合。如下面的式子:

price=wareaarea+wageage+b.

其中:

  • warea,wage 被称为 权重 weight,它决定了每个特征对预测值的影响
  • b 被称为 偏置 bias,也可以称为偏移量 offset 或者截距 intercept。偏置的几何解释是指当所有特征都取值为 0 时,预测值应该为多少
  • 即使现实中不会有任何房子的面积是 0 或房龄正好是 0 年,我们仍然需要偏置项。如果没有偏置项,我们模型的表达能力将受到限制

从数学意义上来讲,线性假设方程是输入特征的一个 仿射变换 affine transformation,仿射变换的特点是通过加权和对特征进行 线性变换 linear transformation,同时通过偏置项进行 平移 translation

对于给定的数据集,每一项数据都有三个数,分别是房价、面积和房龄,我们预先假设它们之间呈线性关系。因此我们的目标是寻找模型的权重 w 和偏置 b,使得根据模型做出的 预测价格与数据里的真实价格最接近。输出的预测值由输入特征通过线性模型的仿射变换决定,仿射变换由所选权重和偏置确定。

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

y^=w1x1+...+wdxd+b.

因此所有的特征向量都在 d 维欧式空间中,即 xRd,并且所有权重也都放在其中,即 wRd,因此我们可以进一步用 多维向量的点积 将上式写为:

y^=wx+b.

上式是对于单个数据样本的特征 x 和预测标签 y^ 成立的。而我们有 n 个样本,因此可以用 XRn×d 表示所有样本。其中,X每一行是一个样本,每一列是一种特征

对于 特征集合 X,预测值也可以构成一个 n 维向量 y^Rn,它可以通过 矩阵-向量乘法 表示为:

y^=Xw+b

这个过程中的求和将使用 广播机制

注意,我们已知的是数据集,也就是训练数据的特征 X 和对应数据的已知标签 y。而线性回归是要通过它们找到一组权重 w 和偏置 b,要求:当给定一组从 X 同分布 中采样得到的新样本特征时,这组权重向量和偏置能够使得新样本 预测标签和实际标签的误差尽可能小

接下来我们探究一下:假如特征和标签之间的关系真是线性的,那我们是否能够找到最合适的权重和偏置呢?答案是否定的,因为我们很难找到一个有 n 个样本的真实数据集,观测是存在 误差 的。或者说特征和标签之间的关系本身并非线性,只是非常接近线性。

对于以上这两种情况,我们都通过引入一个噪声项来解决,考虑误差带来的影响。在开始寻找最好的 模型参数 model parameters wb 之前,我们还需要两个东西:

  • 一种模型质量的度量方式
  • 一种能够 更新模型 以提高模型预测质量的方法

3.1.1.2 损失函数

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

l(i)(w,b)=12(y^(i)y(i))2.

常数 1/2 不会带来本质的差别,但这样在形式上稍微简单一些(对损失函数求导后系数为 1)。由于训练数据集是已知的,所以经验误差只是 关于模型参数的函数。为了进一步说明,来看下面的例子: 我们为一维情况下的回归问题绘制图像,如图所示:

../_images/fit-linreg.svg

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

L(w,b)=1ni=1nl(i)(w,b)=1ni=1n12(wx(i)+by(i))2.

该式子中,x(i),y(i) 是已知量,w,b 是未知量。在训练模型时,我们希望寻找一组参数 w,b,使得这组参数能 最小化在所有训练样本上的总损失。如下式:

w,b=argminw,b L(w,b).

3.1.1.3 解析解

线性回归刚好是一个很简单的优化问题。与我们将在本书中所讲到的其他大部分模型不同,线性回归的解可以用一个公式简单地表达出来,这类解叫作 解析解 analytical solution

首先,

3.1.1.4 随机梯度下降

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

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

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

3.1.2 矢量化加速

在训练我们的模型时,我们经常希望能够 同时处理整个小批量的样本。为了实现这一点,需要我们对计算进行矢量化,从而利用线性代数库,而不是在 Python 中编写开销高昂的 for 循环:

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

为了说明矢量化为什么如此重要,我们考虑对向量相加的两种方法。我们实例化两个全为 1 的 10000 维向量。在一种方法中,我们将使用 Python 的 for 循环遍历向量;在另一种方法中,我们将依赖对运算符 + 的调用。

python
n = 10000
a = torch.ones([n])  # for
b = torch.ones([n])  # +

由于在本书中我们将频繁地进行运行时间的基准测试,所以我们定义一个计时器:

python
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()

现在我们可以对工作负载进行基准测试。

首先,我们使用 for

3.1.3 正态分布与平方损失

不知道你们会不会有个疑问,为什么损失函数就是平方损失函数呢?接下来,我们通过对噪声分布的假设来解读平方损失目标函数。

正态分布

3.2 线性回归的从零开始实现

在了解线性回归的关键思想之后,我们可以开始通过代码来动手实现线性回归了。在这一节中,我们将从零开始实现整个方法,包括:

  • 数据流水线
  • 模型
  • 损失函数
  • 小批量随机梯度下降优化器

虽然现代的深度学习框架几乎可以自动化地进行所有这些工作,但从零开始实现可以确保我们真正知道自己在做什么。同时,了解更细致的工作原理将方便我们 自定义模型、自定义层或自定义损失函数。在这一节中,我们将只使用 张量和自动求导。在之后的章节中,我们会充分利用深度学习框架的优势,介绍更简洁的实现方式。

python
%matplotlib inline
import random
import torch
from d2l import torch as d2l

3.2.1 生成数据集

为了简单起见,我们将根据带有噪声的线性模型构造一个人造数据集。我们的任务是使用这个有限样本的数据集来恢复这个模型的参数。我们将使用低维数据,这样可以很容易地将其可视化。在下面的代码中,我们生成一个包含 1000 个样本的数据集, 每个样本包含从标准正态分布中采样的 2 个特征。我们的合成数据集是一个矩阵

我们认为:

python
def synthetic_data(w, b, num_examples):  #@save
    """生成 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)

3.2.2 读取数据集

回想一下,训练模型时要对数据集进行遍历,每次抽取一小批量样本,并使用它们来更新我们的模型。由于这个过程是训练机器学习算法的基础,所以有必要定义一个函数,该函数能 打乱数据集中的样本 并以小批量方式获取数据。

python
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]

Released under the MIT License.