D2L-4-Multilayer Perceptrons

本文最后更新于:1 年前

本章有很好的实践!!!最后一个大节,有精力请务必复现!!!

最简单的深度网络称为多层感知机。多层感知机由多层神经元组成, 每一层与它的上一层相连,从中接收输入; 同时每一层也与它的下一层相连,影响当前层的神经元。 当我们训练容量较大的模型时,我们面临着过拟合的风险。 因此,本章将从基本的概念介绍开始讲起,包括过拟合欠拟合和模型选择。 为了解决这些问题,本章将介绍权重衰减暂退法等正则化技术。 我们还将讨论数值稳定性和参数初始化相关的问题, 这些问题是成功训练深度网络的关键。

多层感知机

隐藏层

softmax回归的模型架构。 该模型通过单个仿射变换将我们的输入直接映射到输出,然后进行softmax操作。 如果我们的标签通过仿射变换后确实与我们的输入数据相关,那么这种方法确实足够了。 但是,仿射变换中的线性是一个很强的假设。

线性模型可能会出错

  • 线性意味着单调假设: 任何特征的增大都会导致模型输出的增大(如果对应的权重为正), 或者导致模型输出的减小(如果对应的权重为负)。然而我们可以很容易找出违反单调性的例子。我们可以采用一些方法“缓解”这些问题:一种方法是对我们的数据进行预处理, 使线性变得更合理,如使用输入的对数作为我们的特征。也可以通过一些巧妙的预处理来解决问题。 例如,我们可以使用与中间值的距离作为特征。

我们的数据可能会有一种表示,这种表示会考虑到我们在特征之间的相关交互作用。 在此表示的基础上建立一个线性模型可能会是合适的, 但我们不知道如何手动计算这么一种表示。 对于深度神经网络,我们使用观测数据来联合学习隐藏层表示和应用于该表示的线性预测器。

在网络中加入隐藏层

  • 我们可以通过在网络中加入一个或多个隐藏层来克服线性模型的限制, 使其能处理更普遍的函数关系类型。 要做到这一点,最简单的方法是将许多全连接层堆叠在一起。 每一层都输出到上面的层,直到生成最后的输出。 我们可以把前$L-1$层看作表示,把最后一层看作线性预测器。 这种架构通常称为多层感知机(multilayer perceptron),通常缩写为MLP

../_images/mlp.svg

一个单隐藏层的多层感知机,具有5个隐藏单元。这个多层感知机有4个输入,3个输出,其隐藏层包含5个隐藏单元。 输入层不涉及任何计算,因此使用此网络产生输出只需要实现隐藏层和输出层的计算。 因此,这个多层感知机中的层数为2。 注意,这两个层都是全连接的。 每个输入都会影响隐藏层中的每个神经元, 而隐藏层中的每个神经元又会影响输出层中的每个神经元。

具有全连接层的多层感知机的参数开销可能会高得令人望而却步。 即使在不改变输入或输出大小的情况下, 可能在参数节约和模型有效性之间进行权衡。

从线性到非线性

  • 我们通过矩阵$\mathbf{X} \in \mathbb{R}^{n \times d}$来表示$n$个样本的小批量, 其中每个样本具有$d$个输入特征。 对于具有$h$个隐藏单元的单隐藏层多层感知机, 用$\mathbf{H} \in \mathbb{R}^{n \times h}$表示隐藏层的输出, 称为隐藏表示(hidden representations)。 在数学或代码中,$\mathbf{H}$也被称为隐藏层变量(hidden-layer variable) 或隐藏变量(hidden variable)。 因为隐藏层和输出层都是全连接的, 所以我们有隐藏层权重$\mathbf{W}^{(1)} \in \mathbb{R}^{d \times h}$和隐藏层偏置$\mathbf{b}^{(1)} \in \mathbb{R}^{1 \times h}$,以及输出层权重$\mathbf{W}^{(2)} \in \mathbb{R}^{h \times q}$和输出层偏置$\mathbf{b}^{(2)} \in \mathbb{R}^{1 \times q}$。 形式上,我们按如下方式计算单隐藏层多层感知机的输出$\mathbf{O} \in \mathbb{R}^{n \times q}$:

$$
\begin{split}\begin{aligned}
\mathbf{H} & = \mathbf{X} \mathbf{W}^{(1)} + \mathbf{b}^{(1)}, \
\mathbf{O} & = \mathbf{H}\mathbf{W}^{(2)} + \mathbf{b}^{(2)}.
\end{aligned}\end{split}
$$

但是上面这种做法,简单的拼了两层,没有任何好处。仿射函数的仿射函数本身就是仿射函数, 但是我们之前的线性模型已经能够表示任何仿射函数。即对于任意权重值, 我们只需合并隐藏层,便可产生具有参数$\mathbf{W} = \mathbf{W}^{(1)}\mathbf{W}^{(2)}$的单层模型

$$
\mathbf{O} = (\mathbf{X} \mathbf{W}^{(1)} + \mathbf{b}^{(1)})\mathbf{W}^{(2)} + \mathbf{b}^{(2)} = \mathbf{X} \mathbf{W}^{(1)}\mathbf{W}^{(2)} + \mathbf{b}^{(1)} \mathbf{W}^{(2)} + \mathbf{b}^{(2)} = \mathbf{X} \mathbf{W} + \mathbf{b}.
$$

  • 为了发挥多层架构的潜力, 我们还需要一个额外的关键要素: 在仿射变换之后对每个隐藏单元应用非线性的激活函数(activation function)$\sigma$。 激活函数的输出(例如,$\sigma(\cdot)$)被称为活性值(activations)。 一般来说,有了激活函数,就不可能再将我们的多层感知机退化成线性模型:

$$
\begin{split}\begin{aligned}
\mathbf{H} & = \sigma(\mathbf{X} \mathbf{W}^{(1)} + \mathbf{b}^{(1)}), \
\mathbf{O} & = \mathbf{H}\mathbf{W}^{(2)} + \mathbf{b}^{(2)}.\
\end{aligned}\end{split}
$$

为了构建更通用的多层感知机, 我们可以继续堆叠这样的隐藏层, 例如: $\mathbf{H}^{(1)} = \sigma_1(\mathbf{X} \mathbf{W}^{(1)} + \mathbf{b}^{(1)})$

通用近似定理

  • 多层感知机可以通过隐藏神经元,捕捉到输入之间复杂的相互作用, 这些神经元依赖于每个输入的值。 我们可以很容易地设计隐藏节点来执行任意计算。 例如,在一对输入上进行基本逻辑操作,多层感知机是通用近似器。 即使是网络只有一个隐藏层,给定足够的神经元和正确的权重, 我们可以对任意函数建模,尽管实际中学习该函数是很困难的。 神经网络有点像C语言。 C语言和任何其他现代编程语言一样,能够表达任何可计算的程序。 但实际上,想出一个符合规范的程序才是最困难的部分。
  • 虽然一个单隐层网络能学习任何函数, 但并不意味着我们应该尝试使用单隐藏层网络来解决所有问题。 事实上,通过使用更深(而不是更广)的网络,我们可以更容易地逼近许多函数。 我们将在后面的章节中进行更细致的讨论。

激活函数

激活函数(activation function)通过计算加权和并加上偏置来确定神经元是否应该被激活, 它们将输入信号转换为输出的可微运算。 大多数激活函数都是非线性的。 由于激活函数是深度学习的基础,下面简要介绍一些常见的激活函数。

1
2
3
%matplotlib inline
import torch
from d2l import torch as d2l

ReLU函数

  • 最受欢迎的激活函数是修正线性单元(Rectified linear unit,ReLU), 因为它实现简单,同时在各种预测任务中表现良好。 ReLU提供了一种非常简单的非线性变换。 给定元素$x$,ReLU函数被定义为该元素与0的最大值:

$$
\operatorname{ReLU}(x) = \max(x, 0).
$$

通俗地说,ReLU函数通过将相应的活性值设为0,仅保留正元素并丢弃所有负元素。 为了直观感受一下,我们可以画出函数的曲线图。 正如从图中所看到,激活函数是分段线性的。

1
2
3
x = torch.arange(-8.0, 8.0, 0.1, requires_grad=True)
y = torch.relu(x)
d2l.plot(x.detach(), y.detach(), 'x', 'relu(x)', figsize=(5, 2.5))

../_images/output_mlp_76f463_21_0.svg

  • 当输入为负时,ReLU函数的导数为0,而当输入为正时,ReLU函数的导数为1。 注意,当输入值精确等于0时,ReLU函数不可导。 在此时,我们默认使用左侧的导数,即当输入为0时导数为0。 我们可以忽略这种情况,因为输入可能永远都不会是0。 这里引用一句古老的谚语,“如果微妙的边界条件很重要,我们很可能是在研究数学而非工程”, 这个观点正好适用于这里。 下面我们绘制ReLU函数的导数。
1
2
y.backward(torch.ones_like(x), retain_graph=True)
d2l.plot(x.detach(), x.grad, 'x', 'grad of relu', figsize=(5, 2.5))

../_images/output_mlp_76f463_36_0.svg

  • 使用ReLU的原因是,它求导表现得特别好:要么让参数消失,要么让参数通过。 这使得优化表现得更好,并且ReLU减轻了困扰以往神经网络的梯度消失问题。注意,ReLU函数有许多变体,包括参数化ReLU(Parameterized ReLU,pReLU) 函数 (He et al., 2015)。 该变体为ReLU添加了一个线性项,因此即使参数是负的,某些信息仍然可以通过:$\operatorname{pReLU}(x) = \max(0, x) + \alpha \min(0, x).$

sigmoid函数

  • 对于一个定义域在$\mathbb{R}$中的输入, sigmoid函数将输入变换为区间(0, 1)上的输出。 因此,sigmoid通常称为挤压函数(squashing function): 它将范围(-inf, inf)中的任意输入压缩到区间(0, 1)中的某个值:

$$
\operatorname{sigmoid}(x) = \frac{1}{1 + \exp(-x)}.
$$

  • sigmoid函数是一个自然的选择,因为它是一个平滑的、可微的阈值单元近似。 当我们想要将输出视作二元分类问题的概率时, sigmoid仍然被广泛用作输出单元上的激活函数 (sigmoid可以视为softmax的特例)。 然而,sigmoid在隐藏层中已经较少使用, 它在大部分时候被更简单、更容易训练的ReLU所取代。

../_images/output_mlp_76f463_51_0.svg

注意,当输入接近0时,sigmoid函数接近线性变换。

  • sigmoid函数的导数为下面的公式:

$$
\frac{d}{dx} \operatorname{sigmoid}(x) = \frac{\exp(-x)}{(1 + \exp(-x))^2} = \operatorname{sigmoid}(x)\left(1-\operatorname{sigmoid}(x)\right).
$$

注意,当输入为0时,sigmoid函数的导数达到最大值0.25; 而输入在任一方向上越远离0点时,导数越接近0。

1
2
3
4
# 清除以前的梯度
x.grad.data.zero_()
y.backward(torch.ones_like(x),retain_graph=True)
d2l.plot(x.detach(), x.grad, 'x', 'grad of sigmoid', figsize=(5, 2.5))

../_images/output_mlp_76f463_66_0.svg

tanh函数

  • 与sigmoid函数类似, tanh(双曲正切)函数也能将其输入压缩转换到区间(-1, 1)上。 tanh函数的公式如下:

$$
\operatorname{tanh}(x) = \frac{1 - \exp(-2x)}{1 + \exp(-2x)}.
$$

注意,当输入在0附近时,tanh函数接近线性变换。 函数的形状类似于sigmoid函数, 不同的是tanh函数关于坐标系原点中心对称。

1
2
y = torch.tanh(x)
d2l.plot(x.detach(), y.detach(), 'x', 'tanh(x)', figsize=(5, 2.5))

../_images/output_mlp_76f463_81_0.svg

  • tanh函数的导数是:

$$
\frac{d}{dx} \operatorname{tanh}(x) = 1 - \operatorname{tanh}^2(x).
$$

  • tanh函数的导数图像如下所示。 当输入接近0时,tanh函数的导数接近最大值1。 与我们在sigmoid函数图像中看到的类似, 输入在任一方向上越远离0点,导数越接近0。
1
2
3
4
# 清除以前的梯度
x.grad.data.zero_()
y.backward(torch.ones_like(x),retain_graph=True)
d2l.plot(x.detach(), x.grad, 'x', 'grad of tanh', figsize=(5, 2.5))

../_images/output_mlp_76f463_96_0.svg

多层感知机的从零开始实现

我们尝试自己实现一个多层感知机。 为了与之前softmax回归获得的结果进行比较, 我们将继续使用Fashion-MNIST图像分类数据集 。

1
2
3
4
5
6
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)

初始化模型参数

1
2
3
4
5
6
7
8
9
10
num_inputs, num_outputs, num_hiddens = 784, 10, 256

W1 = nn.Parameter(torch.randn(
num_inputs, num_hiddens, requires_grad=True) * 0.01)
b1 = nn.Parameter(torch.zeros(num_hiddens, requires_grad=True))
W2 = nn.Parameter(torch.randn(
num_hiddens, num_outputs, requires_grad=True) * 0.01)
b2 = nn.Parameter(torch.zeros(num_outputs, requires_grad=True))

params = [W1, b1, W2, b2]

我们用几个张量来表示我们的参数。 注意,对于每一层我们都要记录一个权重矩阵和一个偏置向量。 跟以前一样,我们要为损失关于这些参数的梯度分配内存。

激活函数

1
2
3
def relu(X):
a = torch.zeros_like(X)
return torch.max(X, a)

有内置的ReLU激活函数,但这里我们手写一个捏!

模型

1
2
3
4
def net(X):
X = X.reshape((-1, num_inputs))
H = relu(X@W1 + b1) # 这里“@”代表矩阵乘法
return (H@W2 + b2)

损失函数

  • 我们已经从零实现过softmax函数,在这里我们直接使用高级API中的内置函数来计算softmax和交叉熵损失。 回想一下我们之前在 3.7.2节中 对这些复杂问题的讨论。 我们鼓励感兴趣的读者查看损失函数的源代码,以加深对实现细节的了解。
1
loss = nn.CrossEntropyLoss(reduction='none')

CrossEntropyLoss自带了softmax捏!

训练&预测

  • 多层感知机的训练过程与softmax回归的训练过程完全相同。 可以直接调用d2l包的train_ch3函数。将迭代周期数设置为10,并将学习率设置为0.1.
1
2
3
num_epochs, lr = 10, 0.1
updater = torch.optim.SGD(params, lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, updater)

../_images/output_mlp-scratch_106d07_81_0.svg

1
d2l.predict_ch3(net, test_iter)

多层感知机的简洁实现

1
2
3
import torch
from torch import nn
from d2l import torch as d2l

模型

  • 唯一的区别是我们添加了2个全连接层(之前我们只添加了1个全连接层)。 第一层是隐藏层,它包含256个隐藏单元,并使用了ReLU激活函数。 第二层是输出层。
1
2
3
4
5
6
7
8
9
10
net = nn.Sequential(nn.Flatten(),
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 10))

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

net.apply(init_weights);
  • 训练过程的实现与我们实现softmax回归时完全相同, 这种模块化设计使我们能够将与模型架构有关的内容独立出来。
1
2
3
4
5
6
batch_size, lr, num_epochs = 256, 0.1, 10
loss = nn.CrossEntropyLoss(reduction='none')
trainer = torch.optim.SGD(net.parameters(), lr=lr)

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

模型选择、欠拟合和过拟合

  • 作为机器学习科学家,我们的目标是发现模式(pattern)。这些模式捕捉到了我们训练集潜在总体的规律。 如果成功做到了这点,即使是对以前从未遇到过的个体, 模型也可以成功地评估风险。 如何发现可以泛化的模式是机器学习的根本问题。
  • 困难在于,当我们训练模型时,我们只能访问数据中的小部分样本。 当我们使用有限的样本时,可能会遇到这样的问题: 当收集到更多的数据时,会发现之前找到的明显关系并不成立。
  • 将模型在训练数据上拟合的比在潜在分布中更接近的现象称为过拟合(overfitting), 用于对抗过拟合的技术称为正则化(regularization)。 在前面的章节中,有些读者可能在用Fashion-MNIST数据集做实验时已经观察到了这种过拟合现象。 在实验中调整模型架构或超参数时会发现: 如果有足够多的神经元、层数和训练迭代周期, 模型最终可以在训练集上达到完美的精度,此时测试集的准确性却下降了。

训练误差和泛化误差

  • 训练误差(training error)是指, 模型在训练数据集上计算得到的误差。 泛化误差(generalization error)是指, 模型应用在同样从原始样本的分布中抽取的无限多数据样本时,模型误差的期望。

我们永远不能准确地计算出泛化误差。 这是因为无限多的数据样本是一个虚构的对象。 在实际中,我们只能通过将模型应用于一个独立的测试集来估计泛化误差, 该测试集由随机选取的、未曾在训练集中出现的数据样本构成。

统计学习理论

由于泛化是机器学习中的基本问题, 许多数学家和理论家毕生致力于研究描述这一现象的形式理论。 在同名定理(eponymous theorem)中, 格里文科和坎特利推导出了训练误差收敛到泛化误差的速率。 在一系列开创性的论文中, Vapnik和Chervonenkis 将这一理论扩展到更一般种类的函数。 这项工作为统计学习理论奠定了基础。

  • 在我们目前已探讨、并将在之后继续探讨的监督学习情景中, 我们假设训练数据和测试数据都是从相同的分布中独立提取的。 这通常被称为独立同分布假设(i.i.d. assumption), 这意味着对数据进行采样的过程没有进行“记忆”。 换句话说,抽取的第2个样本和第3个样本的相关性, 并不比抽取的第2个样本和第200万个样本的相关性更强。

  • 假设是存在漏洞的,即很容易找出假设失效的情况。 有时候我们即使轻微违背独立同分布假设,模型仍将继续运行得非常好。 比如,我们有许多有用的工具已经应用于现实,如人脸识别、语音识别和语言翻译。 毕竟,几乎所有现实的应用都至少涉及到一些违背独立同分布假设的情况。有些违背独立同分布假设的行为肯定会带来麻烦。 比如,我们试图只用来自大学生的人脸数据来训练一个人脸识别系统, 然后想要用它来监测疗养院中的老人。 这不太可能有效,因为大学生看起来往往与老年人有很大的不同。

  • 当我们训练模型时,我们试图找到一个能够尽可能拟合训练数据的函数。 但是如果它执行地“太好了”,而不能对看不见的数据做到很好泛化,就会导致过拟合。 这种情况正是我们想要避免或控制的。 深度学习中有许多启发式的技术旨在防止过拟合。

模型复杂性

  • 当我们有简单的模型和大量的数据时,我们期望泛化误差与训练误差相近。 当我们有更复杂的模型和更少的样本时,我们预计训练误差会下降,但泛化误差会增大。 模型复杂性由什么构成是一个复杂的问题。 一个模型是否能很好地泛化取决于很多因素。 例如,具有更多参数的模型可能被认为更复杂, 参数有更大取值范围的模型可能更为复杂。 通常对于神经网络,我们认为需要更多训练迭代的模型比较复杂, 而需要早停(early stopping)的模型(即较少训练迭代周期)就不那么复杂。
  • 我们很难比较本质上不同大类的模型之间(例如,决策树与神经网络)的复杂性。 就目前而言,一条简单的经验法则相当有用: 统计学家认为,能够轻松解释任意事实的模型是复杂的, 而表达能力有限但仍能很好地解释数据的模型可能更有现实用途。 本节为了给出一些直观的印象,我们将重点介绍几个倾向于影响模型泛化的因素。
    1. 可调整参数的数量。当可调整参数的数量(有时称为自由度)很大时,模型往往更容易过拟合。
    2. 参数采用的值。当权重的取值范围较大时,模型可能更容易过拟合。
    3. 训练样本的数量。即使模型很简单,也很容易过拟合只包含一两个样本的数据集。而过拟合一个有数百万个样本的数据集则需要一个极其灵活的模型。

模型选择

  • 有时,需要进行比较的模型在本质上是完全不同的(比如,决策树与线性模型)。 又有时,我们需要比较不同的超参数设置下的同一类模型。训练多层感知机模型时,我们可能希望比较具有 不同数量的隐藏层、不同数量的隐藏单元以及不同的激活函数组合的模型。 为了确定候选模型中的最佳模型,我们通常会使用验证集。

验证集

  • 在我们确定所有的超参数之前,我们不希望用到测试集。 如果我们在模型选择过程中使用测试数据,可能会有过拟合测试数据的风险,那就麻烦大了。 如果我们过拟合了训练数据,还可以在测试数据上的评估来判断过拟合。 但是如果我们过拟合了测试数据,我们又该怎么知道呢?
  • 我们决不能依靠测试数据进行模型选择。 我们也不能仅仅依靠训练数据来选择模型,因为我们无法估计训练数据的泛化误差。
  • 虽然理想情况下我们只会使用测试数据一次, 以评估最好的模型或比较一些模型效果,但现实是测试数据很少在使用一次后被丢弃。 我们很少能有充足的数据来对每一轮实验采用全新测试集。
  • 解决此问题的常见做法是将我们的数据分成三份, 除了训练和测试数据集之外,还增加一个验证数据集(validation dataset), 也叫验证集(validation set)。 但现实是验证数据和测试数据之间的边界模糊得令人担忧。 除非另有明确说明,否则在这本书的实验中, 我们实际上是在使用应该被正确地称为训练数据和验证数据的数据集, 并没有真正的测试数据集。 因此,书中每次实验报告的准确度都是验证集准确度,而不是测试集准确度。

$K$折交叉验证

  • 当训练数据稀缺时,我们甚至可能无法提供足够的数据来构成一个合适的验证集。 这个问题的一个流行的解决方案是采用�折交叉验证。 这里,原始训练数据被分成$K$个不重叠的子集。 然后执行$K-1$次模型训练和验证,每次在$K-1$个子集上进行训练, 并在剩余的一个子集(在该轮中没有用于训练的子集)上进行验证。 最后,通过对$K$次实验的结果取平均来估计训练和验证误差。

欠拟合还是过拟合?

  • 当我们比较训练和验证误差时,我们要注意两种常见的情况。
    • 我们要注意这样的情况:训练误差和验证误差都很严重, 但它们之间仅有一点差距。 如果模型不能降低训练误差,这可能意味着模型过于简单(即表达能力不足), 无法捕获试图学习的模式。 此外,由于我们的训练和验证误差之间的泛化误差很小, 我们有理由相信可以用一个更复杂的模型降低训练误差。 这种现象被称为欠拟合(underfitting)。
    • 当我们的训练误差明显低于验证误差时要小心, 这表明严重的过拟合(overfitting)。 注意,过拟合并不总是一件坏事。 特别是在深度学习领域,众所周知, 最好的预测模型在训练数据上的表现往往比在保留(验证)数据上好得多。 最终,我们通常更关心验证误差,而不是训练误差和验证误差之间的差距。

模型复杂性

  • 为了说明一些关于过拟合和模型复杂性的经典直觉, 我们给出一个多项式的例子。 给定由单个特征$x$和对应实数标$y$组成的训练数据, 我们试图找到下面的$d$阶多项式来估计标签$y$。

$$
\hat{y}= \sum_{i=0}^d x^i w_i
$$

这只是一个线性回归问题,我们的特征是$x_{i}$的幂给出的, 模型的权重是$w_{i}$给出的,偏置是$w_{0}$给出的 (因为对于所有的$x$都有$x^{0} = 1$)。 由于这只是一个线性回归问题,我们可以使用平方误差作为我们的损失函数。

  • 高阶多项式函数比低阶多项式函数复杂得多。 高阶多项式的参数较多,模型函数的选择范围较广。 因此在固定训练数据集的情况下, 高阶多项式函数相对于低阶多项式的训练误差应该始终更低(最坏也是相等)。 事实上,当数据样本包含了$x$的不同值时, 函数阶数等于数据样本数量的多项式函数可以完美拟合训练集。

../_images/capacity-vs-error.svg

数据集大小

  • 训练数据集中的样本越少,我们就越有可能(且更严重地)过拟合。 随着训练数据量的增加,泛化误差通常会减小。 此外,一般来说,更多的数据不会有什么坏处。 对于固定的任务和数据分布,模型复杂性和数据集大小之间通常存在关系。 给出更多的数据,我们可能会尝试拟合一个更复杂的模型。 能够拟合更复杂的模型可能是有益的。 如果没有足够的数据,简单的模型可能更有用。 对于许多任务,深度学习只有在有数千个训练样本时才优于线性模型。 从一定程度上来说,深度学习目前的生机要归功于 廉价存储、互联设备以及数字化经济带来的海量数据集。

多项式回归

1
2
3
4
5
import math
import numpy as np
import torch
from torch import nn
from d2l import torch as d2l

生成数据集

$$
y = 5 + 1.2x - 3.4\frac{x^2}{2!} + 5.6 \frac{x^3}{3!} + \epsilon \text{ where }
\epsilon \sim \mathcal{N}(0, 0.1^2).
$$

  • 噪声项$\epsilon$服从均值为0且标准差为0.1的正态分布。 在优化的过程中,我们通常希望避免非常大的梯度值或损失值。 这就是我们将特征从$x^i$调整为$\frac{x^i}{i!}$的原因, 这样可以避免很大的$i$带来的特别大的指数值。 我们将为训练集和测试集各生成100个样本。
1
2
3
4
5
6
7
8
9
10
11
12
13
max_degree = 20  # 多项式的最大阶数
n_train, n_test = 100, 100 # 训练和测试数据集大小
true_w = np.zeros(max_degree) # 分配大量的空间
true_w[0:4] = np.array([5, 1.2, -3.4, 5.6])

features = np.random.normal(size=(n_train + n_test, 1))
np.random.shuffle(features)
poly_features = np.power(features, np.arange(max_degree).reshape(1, -1))
for i in range(max_degree):
poly_features[:, i] /= math.gamma(i + 1) # gamma(n)=(n-1)!
# labels的维度:(n_train+n_test,)
labels = np.dot(poly_features, true_w)
labels += np.random.normal(scale=0.1, size=labels.shape)
  • 同样,存储在poly_features中的单项式由gamma函数重新缩放, 其中$\Gamma(n)=(n-1)!$。 从生成的数据集中查看一下前2个样本, 第一个值是与偏置相对应的常量特征。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# NumPy ndarray转换为tensor
true_w, features, poly_features, labels = [torch.tensor(x, dtype=
torch.float32) for x in [true_w, features, poly_features, labels]]

features[:2], poly_features[:2, :], labels[:2]

# result
(tensor([[-0.7408],
[ 0.9021]]),
tensor([[ 1.0000e+00, -7.4078e-01, 2.7438e-01, -6.7751e-02, 1.2547e-02,
-1.8589e-03, 2.2951e-04, -2.4288e-05, 2.2490e-06, -1.8511e-07,
1.3713e-08, -9.2346e-10, 5.7007e-11, -3.2484e-12, 1.7188e-13,
-8.4884e-15, 3.9300e-16, -1.7125e-17, 7.0478e-19, -2.7478e-20],
[ 1.0000e+00, 9.0208e-01, 4.0687e-01, 1.2234e-01, 2.7591e-02,
4.9777e-03, 7.4838e-04, 9.6443e-05, 1.0875e-05, 1.0900e-06,
9.8325e-08, 8.0633e-09, 6.0614e-10, 4.2061e-11, 2.7101e-12,
1.6298e-13, 9.1889e-15, 4.8759e-16, 2.4436e-17, 1.1602e-18]]),
tensor([2.9074, 5.2200]))

对模型进行训练和测试

  • 首先让我们实现一个函数来评估模型在给定数据集上的损失。
1
2
3
4
5
6
7
8
9
def evaluate_loss(net, data_iter, loss):  #@save
"""评估给定数据集上模型的损失"""
metric = d2l.Accumulator(2) # 损失的总和,样本数量
for X, y in data_iter:
out = net(X)
y = y.reshape(out.shape)
l = loss(out, y)
metric.add(l.sum(), l.numel())
return metric[0] / metric[1]
  • 现在定义训练函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def train(train_features, test_features, train_labels, test_labels,
num_epochs=400):
loss = nn.MSELoss(reduction='none')
input_shape = train_features.shape[-1]
# 不设置偏置,因为我们已经在多项式中实现了它
net = nn.Sequential(nn.Linear(input_shape, 1, bias=False))
batch_size = min(10, train_labels.shape[0])
train_iter = d2l.load_array((train_features, train_labels.reshape(-1,1)),
batch_size)
test_iter = d2l.load_array((test_features, test_labels.reshape(-1,1)),
batch_size, is_train=False)
trainer = torch.optim.SGD(net.parameters(), lr=0.01)
animator = d2l.Animator(xlabel='epoch', ylabel='loss', yscale='log',
xlim=[1, num_epochs], ylim=[1e-3, 1e2],
legend=['train', 'test'])
for epoch in range(num_epochs):
d2l.train_epoch_ch3(net, train_iter, loss, trainer)
if epoch == 0 or (epoch + 1) % 20 == 0:
animator.add(epoch + 1, (evaluate_loss(net, train_iter, loss),
evaluate_loss(net, test_iter, loss)))
print('weight:', net[0].weight.data.numpy())

三阶多项式函数拟合(正常)

1
2
3
4
5
6
# 从多项式特征中选择前4个维度,即1,x,x^2/2!,x^3/3!
train(poly_features[:n_train, :4], poly_features[n_train:, :4],
labels[:n_train], labels[n_train:])

# result
weight: [[ 5.000446 1.1925726 -3.3994446 5.613429 ]]

../_images/output_underfit-overfit_ec26bd_65_1.svg

线性函数拟合(欠拟合)

1
2
3
4
5
6
# 从多项式特征中选择前2个维度,即1和x
train(poly_features[:n_train, :2], poly_features[n_train:, :2],
labels[:n_train], labels[n_train:])

# result
weight: [[3.3861537 3.623168 ]]

../_images/output_underfit-overfit_ec26bd_80_1.svg

高阶多项式函数拟合(过拟合)

1
2
3
4
5
6
7
8
9
# 从多项式特征中选取所有维度
train(poly_features[:n_train, :], poly_features[n_train:, :],
labels[:n_train], labels[n_train:], num_epochs=1500)

# result
weight: [[ 4.9922 1.305686 -3.352961 5.116522 -0.11140402 1.3026645
0.12679572 0.16638486 0.05130938 -0.02277358 0.00806421 -0.0516805
-0.02426281 -0.01502219 -0.04941352 0.06389863 -0.04761845 -0.04380166
-0.05188227 0.05655775]]

../_images/output_underfit-overfit_ec26bd_95_1.svg

权重衰减

  • 我们总是可以通过去收集更多的训练数据来缓解过拟合。 但这可能成本很高,耗时颇多,或者完全超出我们的控制,因而在短期内不可能做到。 假设我们已经拥有尽可能多的高质量数据,我们便可以将重点放在正则化技术上。
  • 我们可以通过调整拟合多项式的阶数来限制模型的容量。 实际上,限制特征的数量是缓解过拟合的一种常用技术。 然而,简单地丢弃特征对这项工作来说可能过于生硬。 我们继续思考多项式回归的例子,考虑高维输入可能发生的情况。 多项式对多变量数据的自然扩展称为单项式(monomials), 也可以说是变量幂的乘积。 单项式的阶数是幂的和。 例如,$x_1^2 x_2$和$x_3 x_5^2$都是3次单项式。
  • 随着阶数$d$的增长,带有阶数$d$的项数迅速增加。 给定$k$个变量,阶数为$d$的项的个数为${k - 1 + d} \choose {k - 1}$,即为$C^{k-1}_{k-1+d} = \frac{(k-1+d)!}{(d)!(k-1)!}$。 因此即使是阶数上的微小变化,比如从2到3,也会显著增加我们模型的复杂性。 仅仅通过简单的限制特征数量(在多项式回归中体现为限制阶数),可能仍然使模型在过简单和过复杂中徘徊, 我们需要一个更细粒度的工具来调整函数的复杂性,使其达到一个合适的平衡位置。 ## 范数与权重衰减
  • 在训练参数化机器学习模型时, 权重衰减(weight decay)是最广泛使用的正则化的技术之一, 它通常也被称为$L_2$正则化。 这项技术通过函数与零的距离来衡量函数的复杂度, 因为在所有函数$f$中,函数$f=0$(所有输入都得到值0) 在某种意义上是最简单的。但是我们应该如何精确地测量一个函数和零之间的距离呢?一种简单的方法是通过线性函数$f(\mathbf{x}) = \mathbf{w}^\top \mathbf{x}$中的权重向量的某个范数来度量其复杂性, 例如$| \mathbf{w} |^2$。 要保证权重向量比较小, 最常用方法是将其范数作为惩罚项加到最小化损失的问题中。将原来的训练目标最小化训练标签上的预测损失, 调整为最小化预测损失和惩罚项之和。 现在,如果我们的权重向量增长的太大, 我们的学习算法可能会更集中于最小化权重范数$| \mathbf{w} |^2$。线性回归例子中, 我们的损失由下式给出:

$$
L(\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.
$$

为了惩罚权重向量的大小, 我们必须以某种方式在损失函数中添加$| \mathbf{w} |^2$, 但是模型应该如何平衡这个新的额外惩罚的损失? 实际上,我们通过正则化常数$\lambda$来描述这种权衡, 这是一个非负超参数,我们使用验证数据拟合:

$$
L(\mathbf{w}, b) + \frac{\lambda}{2} |\mathbf{w}|^2,
$$

  • $\lambda = 0$我们恢复了原来的损失函数。 对于$\lambda>0$,我们限制$| \mathbf{w} |$的大小。 这里我们仍然除以2:当我们取一个二次函数的导数时, 2和1/2会抵消,以确保更新表达式看起来既漂亮又简单。

为什么在这里我们使用平方范数而不是标准范数(即欧几里得距离)? 我们这样做是为了便于计算。 通过平方$L_2$范数,我们去掉平方根,留下权重向量每个分量的平方和。 这使得惩罚的导数很容易计算:导数的和等于和的导数。

  • why $L_2$ not $L_1$?这个选择在整个统计领域中都是有效的和受欢迎的。 $L_2$正则化线性模型构成经典的岭回归(ridge regression)算法, $L_1$正则化线性回归是统计学中类似的基本模型, 通常被称为套索回归(lasso regression)。 $L_2$范数的一个原因是它对权重向量的大分量施加了巨大的惩罚。 这使得我们的学习算法偏向于在大量特征上均匀分布权重的模型。 在实践中,这可能使它们对单个变量中的观测误差更为稳定。 相比之下,$L_1$惩罚会导致模型将权重集中在一小部分特征上, 而将其他权重清除为零。 这称为特征选择(feature selection),这可能是其他场景下需要的。
  • $L_2$正则化回归的小批量随机梯度下降更新如下式:

$$
\begin{aligned}
\mathbf{w} & \leftarrow \left(1- \eta\lambda \right) \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).
\end{aligned}
$$

  • 根据之前章节所讲的,我们根据估计值与观测值之间的差异来更新$w$。 然而,我们同时也在试图$w$的大小缩小到零。 这就是为什么这种方法有时被称为权重衰减。 我们仅考虑惩罚项,优化算法在训练的每一步衰减权重。 与特征选择相比,权重衰减为我们提供了一种连续的机制来调整函数的复杂度。 较小的$\lambda$值对应较少约束的$\mathbf{w}$, 而较大的$\lambda$值对$\mathbf{w}$的约束更大。
  • 是否对相应的偏置$b^2$进行惩罚在不同的实践中会有所不同, 在神经网络的不同层中也会有所不同。 通常,网络输出层的偏置项不会被正则化。

正则化

  • 正则化是一种用于减少模型过拟合的技术。过拟合指的是模型在训练数据上表现很好,但在未见过的测试数据上表现较差的情况。正则化的目标是通过约束模型的复杂度来平衡模型的拟合能力和泛化能力。它通过在损失函数中添加一个正则化项,来惩罚模型的复杂度,从而限制参数的取值范围,防止模型过度拟合训练数据。
  • 常见的正则化方法有:
    • L1 正则化(L1 Regularization):将模型参数的绝对值之和作为正则化项,可以使得某些参数变为0,实现特征选择的效果。
    • L2 正则化(L2 Regularization):将模型参数的平方和作为正则化项,可以使得参数的取值尽可能小,降低模型复杂度。
    • Elastic Net 正则化:L1 正则化和 L2 正则化的组合形式,综合了两者的优点。
    • Dropout 正则化:在训练过程中,随机地将一部分神经元的输出置为0,可以增强模型的鲁棒性和泛化能力。

正则化通过控制模型的复杂度,可以减少模型对训练数据中的噪声的过度拟合,提高模型在未知数据上的表现。它是解决过拟合问题的一种有力手段,在机器学习中广泛应用。

高维线性回归

1
2
3
4
%matplotlib inline
import torch
from torch import nn
from d2l import torch as d2l
  • 首先,我们像以前一样生成一些数据,生成公式如下:

$$
y = 0.05 + \sum_{i = 1}^d 0.01 x_i + \epsilon \text{ where }
\epsilon \sim \mathcal{N}(0, 0.01^2).
$$

  • 我们选择标签是关于输入的线性函数。 标签同时被均值为0,标准差为0.01高斯噪声破坏。 为了使过拟合的效果更加明显,我们可以将问题的维数增加到d=200, 并使用一个只包含20个样本的小训练集。
1
2
3
4
5
6
n_train, n_test, num_inputs, batch_size = 20, 100, 200, 5
true_w, true_b = torch.ones((num_inputs, 1)) * 0.01, 0.05
train_data = d2l.synthetic_data(true_w, true_b, n_train)
train_iter = d2l.load_array(train_data, batch_size)
test_data = d2l.synthetic_data(true_w, true_b, n_test)
test_iter = d2l.load_array(test_data, batch_size, is_train=False)

从零开始实现

只需将$L_2$的平方惩罚添加到原始目标函数中。

初始化模型参数

1
2
3
4
def init_params():
w = torch.normal(0, 1, size=(num_inputs, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
return [w, b]

定义$L_2$范数惩罚

实现这一惩罚最方便的方法是对所有项求平方后并将它们求和。

1
2
def l2_penalty(w):
return torch.sum(w.pow(2)) / 2

定义训练代码实现

  • 下面的代码将模型拟合训练数据集,并在测试数据集上进行评估。 线性网络和平方损失没有变化, 所以我们通过d2l.linreg和d2l.squared_loss导入它们。 唯一的变化是损失现在包括了惩罚项。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def train(lambd):
w, b = init_params()
net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss
num_epochs, lr = 100, 0.003
animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log',
xlim=[5, num_epochs], legend=['train', 'test'])
for epoch in range(num_epochs):
for X, y in train_iter:
# 增加了L2范数惩罚项,
# 广播机制使l2_penalty(w)成为一个长度为batch_size的向量
l = loss(net(X), y) + lambd * l2_penalty(w)
l.sum().backward()
d2l.sgd([w, b], lr, batch_size)
if (epoch + 1) % 5 == 0:
animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss),
d2l.evaluate_loss(net, test_iter, loss)))
print('w的L2范数是:', torch.norm(w).item())

忽略正则化直接训练

1
2
3
4
train(lambd=0)

# result
w的L2范数是: 13.97721004486084

../_images/output_weight-decay_ec9cc0_81_1.svg

使用权重衰减

下面,我们使用权重衰减来运行代码。 注意,在这里训练误差增大,但测试误差减小。 这正是我们期望从正则化中得到的效果。

1
2
3
4
train(lambd=3)

# result
w的L2范数是: 0.3624069094657898

../_images/output_weight-decay_ec9cc0_96_1.svg

简洁实现

  • 由于权重衰减在神经网络优化中很常用, 深度学习框架为了便于我们使用权重衰减, 将权重衰减集成到优化算法中,以便与任何损失函数结合使用。 此外,这种集成还有计算上的好处, 允许在不增加任何额外的计算开销的情况下向算法中添加权重衰减。 由于更新的权重衰减部分仅依赖于每个参数的当前值, 因此优化器必须至少接触每个参数一次。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def train_concise(wd):
net = nn.Sequential(nn.Linear(num_inputs, 1))
for param in net.parameters():
param.data.normal_()
loss = nn.MSELoss(reduction='none')
num_epochs, lr = 100, 0.003
# 偏置参数没有衰减
trainer = torch.optim.SGD([
{"params":net[0].weight,'weight_decay': wd},
{"params":net[0].bias}], lr=lr)
animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log',
xlim=[5, num_epochs], legend=['train', 'test'])
for epoch in range(num_epochs):
for X, y in train_iter:
trainer.zero_grad()
l = loss(net(X), y)
l.mean().backward()
trainer.step()
if (epoch + 1) % 5 == 0:
animator.add(epoch + 1,
(d2l.evaluate_loss(net, train_iter, loss),
d2l.evaluate_loss(net, test_iter, loss)))
print('w的L2范数:', net[0].weight.norm().item())
  1. 首先,定义了一个含有一个线性层的序列网络(net),其中 num_inputs 是输入特征的数量。这个线性层将输入特征映射到一个单独的输出。
  2. 使用正态分布随机初始化网络中的参数(权重和偏置)。
  3. 定义损失函数为均方误差(MSE)损失函数,并设置reduction='none',表示不进行求和或平均。
  4. 定义了训练轮数num_epochs和学习率lr
  5. 创建了一个优化器(trainer)来更新网络参数。这里使用了随机梯度下降(SGD)优化器,并通过weight_decay参数设置权重衰减。
  6. 创建了一个可视化动画(animator),用于显示每个训练周期的训练损失和测试损失。
  7. 在每个训练周期内,遍历训练数据集(train_iter)并进行训练。
  8. 在每个训练周期结束后,根据需要计算训练集和测试集上的损失,并将结果添加到动画中进行可视化。
  9. 打印输出了网络权重参数w的L2范数,即权重矩阵的所有元素平方和的开根号。
  • 这些图看起来和我们从零开始实现权重衰减时的图相同。 然而,它们运行得更快,更容易实现。 对于更复杂的问题,这一好处将变得更加明显。
1
2
3
4
train_concise(0)
# w的L2范数: 14.670721054077148
train_concise(3)
# w的L2范数: 0.3454631567001343

image-20230731164314132

../_images/output_weight-decay_ec9cc0_131_1.svg

到目前为止,我们只接触到一个简单线性函数的概念。 此外,由什么构成一个简单的非线性函数可能是一个更复杂的问题。 例如,再生核希尔伯特空间(RKHS) 允许在非线性环境中应用为线性函数引入的工具。 不幸的是,基于RKHS的算法往往难以应用到大型、高维的数据。 在这本书中,我们将默认使用简单的启发式方法,即在深层网络的所有层上应用权重衰减。

暂退法(Dropout)

我们介绍了通过惩罚权重的$L_2$范数来正则化统计模型的经典方法。 在概率角度看,我们可以通过以下论证来证明这一技术的合理性: 我们已经假设了一个先验,即权重的值取自均值为0的高斯分布。 更直观的是,我们希望模型深度挖掘特征,即将其权重分散到许多特征中, 而不是过于依赖少数潜在的虚假关联。

重新审视过拟合

  • 当面对更多的特征而样本不足时,线性模型往往会过拟合。 相反,当给出更多样本而不是特征,通常线性模型不会过拟合。 不幸的是,线性模型泛化的可靠性是有代价的。 简单地说,线性模型没有考虑到特征之间的交互作用。 对于每个特征,线性模型必须指定正的或负的权重,而忽略其他特征。
  • 泛化性和灵活性之间的这种基本权衡被描述为偏差-方差权衡(bias-variance tradeoff)。 线性模型有很高的偏差:它们只能表示一小类函数。 然而,这些模型的方差很低:它们在不同的随机数据样本上可以得出相似的结果。
  • 深度神经网络位于偏差-方差谱的另一端。 与线性模型不同,神经网络并不局限于单独查看每个特征,而是学习特征之间的交互。 例如,神经网络可能推断“尼日利亚”和“西联汇款”一起出现在电子邮件中表示垃圾邮件, 但单独出现则不表示垃圾邮件。
  • 即使我们有比特征多得多的样本,深度神经网络也有可能过拟合。 2017年,一组研究人员通过在随机标记的图像上训练深度网络。 这展示了神经网络的极大灵活性,因为人类很难将输入和随机标记的输出联系起来, 但通过随机梯度下降优化的神经网络可以完美地标记训练集中的每一幅图像。 想一想这意味着什么? 假设标签是随机均匀分配的,并且有10个类别,那么分类器在测试数据上很难取得高于10%的精度, 那么这里的泛化差距就高达90%,如此严重的过拟合。
  • 深度网络的泛化性质令人费解,而这种泛化性质的数学基础仍然是悬而未决的研究问题。 我们鼓励喜好研究理论的读者更深入地研究这个主题。 本节,我们将着重对实际工具的探究,这些工具倾向于改进深层网络的泛化性。

扰动的稳健性

  • 我们期待“好”的预测模型能在未知的数据上有很好的表现: 经典泛化理论认为,为了缩小训练和测试性能之间的差距,应该以简单的模型为目标。 正如我们讨论权重衰减时看到的那样, 参数的范数也代表了一种有用的简单性度量。简单性的另一个角度是平滑性,即函数不应该对其输入的微小变化敏感。 例如,当我们对图像进行分类时,我们预计向像素添加一些随机噪声应该是基本无影响的。
  • 暂退法在前向传播过程中,计算每一内部层的同时注入噪声,这已经成为训练神经网络的常用技术。 这种方法之所以被称为暂退法,因为我们从表面上看是在训练过程中丢弃(drop out)一些神经元。 在整个训练过程的每一次迭代中,标准暂退法包括在计算下一层之前将当前层中的一些节点置零。关键的挑战就是如何注入这种噪声。 一种想法是以一种无偏向(unbiased)的方式注入噪声。 这样在固定住其他层时,每一层的期望值等于没有噪音时的值。
  • 在标准暂退法正则化中,通过按保留(未丢弃)的节点的分数进行规范化来消除每一层的偏差。 换言之,每个中间活性值$h$以暂退概率$p$由随机变量$h’$替换,如下所示:

$$
\begin{split}\begin{aligned}
h’ =
\begin{cases}
0 & \text{ 概率为 } p \
\frac{h}{1-p} & \text{ 其他情况}
\end{cases}
\end{aligned}\end{split}
$$

根据此模型的设计,其期望值保持不变,即$E[h’] = h$。

实践中的暂退法

  • 当我们将暂退法应用到隐藏层,以$p$的概率将隐藏单元置为零时, 结果可以看作一个只包含原始神经元子集的网络。并且它们各自的梯度在执行反向传播时也会消失。 输出的计算不再依赖于$h_2$或$h_5$,并且它们各自的梯度在执行反向传播时也会消失。这样,输出层的计算不能过度依赖于$h_1, \ldots, h_5$的任何一个元素。

../_images/dropout2.svg

通常,我们在测试时不用暂退法。 给定一个训练好的模型和一个新的样本,我们不会丢弃任何节点,因此不需要标准化。 然而也有一些例外:一些研究人员在测试时使用暂退法, 用于估计神经网络预测的“不确定性”: 如果通过许多不同的暂退法遮盖后得到的预测结果都是一致的,那么我们可以说网络发挥更稳定。

从零开始实现

要实现单层的暂退法函数, 我们从均匀分布$U[0, 1]$中抽取样本,样本数与这层神经网络的维度一致。 然后我们保留那些对应样本大于$p$的节点,把剩下的丢弃。在下面的代码中,我们实现 dropout_layer 函数, 该函数以dropout的概率丢弃张量输入X中的元素, 如上所述重新缩放剩余部分:将剩余部分除以1.0-dropout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import torch
from torch import nn
from d2l import torch as d2l


def dropout_layer(X, dropout):
assert 0 <= dropout <= 1
# 在本情况中,所有元素都被丢弃
if dropout == 1:
return torch.zeros_like(X)
# 在本情况中,所有元素都被保留
if dropout == 0:
return X
mask = (torch.rand(X.shape) > dropout).float()
return mask * X / (1.0 - dropout)

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
X= torch.arange(16, dtype = torch.float32).reshape((2, 8))
print(X)
print(dropout_layer(X, 0.))
print(dropout_layer(X, 0.5))
print(dropout_layer(X, 1.))

# result
tensor([[ 0., 1., 2., 3., 4., 5., 6., 7.],
[ 8., 9., 10., 11., 12., 13., 14., 15.]])
tensor([[ 0., 1., 2., 3., 4., 5., 6., 7.],
[ 8., 9., 10., 11., 12., 13., 14., 15.]])
tensor([[ 0., 2., 0., 6., 8., 10., 0., 0.],
[16., 0., 0., 22., 0., 26., 0., 0.]])
tensor([[0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0.]])

定义模型参数

我们定义具有两个隐藏层的多层感知机,每个隐藏层包含256个单元。

1
num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256

定义模型

我们可以将暂退法应用于每个隐藏层的输出(在激活函数之后), 并且可以为每一层分别设置暂退概率: 常见的技巧是在靠近输入层的地方设置较低的暂退概率。 下面的模型将第一个和第二个隐藏层的暂退概率分别设置为0.2和0.5, 并且暂退法只在训练期间有效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
dropout1, dropout2 = 0.2, 0.5

class Net(nn.Module):
def __init__(self, num_inputs, num_outputs, num_hiddens1, num_hiddens2,
is_training = True):
super(Net, self).__init__()
self.num_inputs = num_inputs
self.training = is_training
self.lin1 = nn.Linear(num_inputs, num_hiddens1)
self.lin2 = nn.Linear(num_hiddens1, num_hiddens2)
self.lin3 = nn.Linear(num_hiddens2, num_outputs)
self.relu = nn.ReLU()

def forward(self, X):
H1 = self.relu(self.lin1(X.reshape((-1, self.num_inputs))))
# 只有在训练模型时才使用dropout
if self.training == True:
# 在第一个全连接层之后添加一个dropout层
H1 = dropout_layer(H1, dropout1)
H2 = self.relu(self.lin2(H1))
if self.training == True:
# 在第二个全连接层之后添加一个dropout层
H2 = dropout_layer(H2, dropout2)
out = self.lin3(H2)
return out


net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)
  1. 首先定义了一个名为 Net 的类,继承自 nn.Module
  2. __init__ 方法中,初始化了神经网络的各个层和参数。num_inputs 是输入特征的数量,num_outputs 是输出的数量,num_hiddens1num_hiddens2 分别是第一个和第二个隐藏层的神经元数量。
  3. 添加了一个名为 dropout1 的 dropout 层,并指定 dropout 概率为 0.2。这意味着在训练过程中,以概率 0.2 将第一个隐藏层的输出设置为 0,以减少过拟合。
  4. 添加了一个名为 dropout2 的 dropout 层,并指定 dropout 概率为 0.5。同样,在训练过程中,以概率 0.5 将第二个隐藏层的输出设置为 0,进一步减少过拟合。
  5. 定义了 forward 方法来执行前向传播。首先将输入 X 通过第一个线性层 lin1 并经过 ReLU 激活函数 relu 得到 H1。然后,如果模型处于训练状态,将 H1 传入第一个 dropout 层进行处理,dropout 按照给定的概率将一部分神经元的输出置为 0。接着,将 H1 经过第二个线性层 lin2 和 ReLU 激活函数得到 H2,并同样使用 dropout 层处理。最后,将 H2 通过最后一个线性层 lin3 获得模型的输出 out
  6. 创建了一个名为 net 的神经网络实例,使用上述定义的参数来初始化。
  • 这段代码中通过在不同隐藏层之后添加 dropout 层,在训练过程中随机地将一些神经元的输出置为零,以减少模型过拟合的风险。dropout1 和 dropout2 分别是第一个和第二个隐藏层之后的 dropout 概率。

训练和测试

1
2
3
4
5
num_epochs, lr, batch_size = 10, 0.5, 256
loss = nn.CrossEntropyLoss(reduction='none')
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
trainer = torch.optim.SGD(net.parameters(), lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

../_images/output_dropout_1110bf_66_0.svg

简洁实现

  • 对于深度学习框架的高级API,我们只需在每个全连接层之后添加一个Dropout层, 将暂退概率作为唯一的参数传递给它的构造函数。 在训练时,Dropout层将根据指定的暂退概率随机丢弃上一层的输出(相当于下一层的输入)。 在测试时,Dropout层仅传递数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
net = nn.Sequential(nn.Flatten(),
nn.Linear(784, 256),
nn.ReLU(),
# 在第一个全连接层之后添加一个dropout层
nn.Dropout(dropout1),
nn.Linear(256, 256),
nn.ReLU(),
# 在第二个全连接层之后添加一个dropout层
nn.Dropout(dropout2),
nn.Linear(256, 10))

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

net.apply(init_weights);

接下来,我们对模型进行训练和测试。

1
2
trainer = torch.optim.SGD(net.parameters(), lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

../_images/output_dropout_1110bf_96_0.svg

前向传播、反向传播和计算图

梯度的自动计算(自动微分)大大简化了深度学习算法的实现。 在自动微分之前,即使是对复杂模型的微小调整也需要手工重新计算复杂的导数, 学术论文也不得不分配大量页面来推导更新规则。 本节将通过一些基本的数学和计算图, 深入探讨反向传播的细节。 首先,我们将重点放在带权重衰减($L_2$正则化)的单隐藏层多层感知机上。

前向传播

前向传播(forward propagation或forward pass) 指的是:按顺序(从输入层到输出层)计算和存储神经网络中每层的结果。

  • 简单起见,我们假设输入样本是$\mathbf{x}\in \mathbb{R}^d$, 并且我们的隐藏层不包括偏置项。 这里的中间变量是:

$$
\mathbf{z}= \mathbf{W}^{(1)} \mathbf{x},
$$

$\mathbf{W}^{(1)} \in \mathbb{R}^{h \times d}$是隐藏层的权重参数。 将中间变量$\mathbf{z}\in \mathbb{R}^h$通过激活函数$\phi$后, 我们得到长度为$h$的隐藏激活向量:

$$
\mathbf{h}= \phi (\mathbf{z}).
$$

隐藏变量$h$也是一个中间变量。 假设输出层的参数只有权重$\mathbf{W}^{(2)} \in \mathbb{R}^{q \times h}$, 我们可以得到输出层变量,它是一个长度为$q$的向量:

$$
\mathbf{o}= \mathbf{W}^{(2)} \mathbf{h}.
$$

假设损失函数为$l$,样本标签为$y$,我们可以计算单个数据样本的损失项,

$$
L = l(\mathbf{o}, y).
$$

根据$L_2$正则化的定义,给定超参数$\lambda$,正则化项为

$$
s = \frac{\lambda}{2} \left(|\mathbf{W}^{(1)}|_F^2 + |\mathbf{W}^{(2)}|_F^2\right),
$$

其中矩阵的Frobenius范数是将矩阵展平为向量后应用的$L_2$范数。 最后,模型在给定数据样本上的正则化损失为:

$$
J = L + s.
$$

在下面的讨论中,我们将$J$称为目标函数(objective function)。

前向传播计算图

  • 绘制计算图有助于我们可视化计算中操作符和变量的依赖关系。下图是与上述简单网络相对应的计算图, 其中正方形表示变量,圆圈表示操作符。 左下角表示输入,右上角表示输出。 注意显示数据流的箭头方向主要是向右和向上的。

../_images/forward.svg

反向传播

  • 反向传播(backward propagation或backpropagation)指的是计算神经网络参数梯度的方法。 简言之,该方法根据微积分中的链式规则,按相反的顺序从输出层到输入层遍历网络。 该算法存储了计算某些参数梯度时所需的任何中间变量(偏导数)。 假设我们有函数$\mathsf{Y}=f(\mathsf{X})$和$\mathsf{Z}=g(\mathsf{Y})$, 其中输入和输出$\mathsf{X}, \mathsf{Y}, \mathsf{Z}$是任意形状的张量。 利用链式法则,我们可以计算$\mathsf{Z}$关于$\mathsf{X}$的导数:

$$
\frac{\partial \mathsf{Z}}{\partial \mathsf{X}} = \text{prod}\left(\frac{\partial \mathsf{Z}}{\partial \mathsf{Y}}, \frac{\partial \mathsf{Y}}{\partial \mathsf{X}}\right).
$$

我们使用$prod$运算符在执行必要的操作(如换位和交换输入位置)后将其参数相乘。 对于向量,这很简单,它只是矩阵-矩阵乘法。 对于高维张量,我们使用适当的对应项。 运算符$prod$指代了所有的这些符号。单隐藏层简单网络的参数是$\mathbf{W}^{(1)}$和$\mathbf{W}^{(2)}$。 反向传播的目的是计算梯度$\partial J/\partial \mathbf{W}^{(1)}$和 $\partial J/\partial \mathbf{W}^{(2)}$。 为此,我们应用链式法则,依次计算每个中间变量和参数的梯度。 计算的顺序与前向传播中执行的顺序相反,因为我们需要从计算图的结果开始,并朝着参数的方向努力。

  • 第一步是计算目标函数$J=L+s$相对于损失项$L$和正则项$s$的梯度。

$$
\frac{\partial J}{\partial L} = 1 ; \text{and} ; \frac{\partial J}{\partial s} = 1.
$$

  • 接下来,我们根据链式法则计算目标函数关于输出层变量$o$的梯度。

$$
\frac{\partial J}{\partial \mathbf{o}}
= \text{prod}\left(\frac{\partial J}{\partial L}, \frac{\partial L}{\partial \mathbf{o}}\right)
= \frac{\partial L}{\partial \mathbf{o}}
\in \mathbb{R}^q.
$$

  • 接下来,我们计算正则化项相对于两个参数的梯度:

$$
\frac{\partial s}{\partial \mathbf{W}^{(1)}} = \lambda \mathbf{W}^{(1)}
; \text{and} ;
\frac{\partial s}{\partial \mathbf{W}^{(2)}} = \lambda \mathbf{W}^{(2)}.
$$

  • 现在我们可以计算最接近输出层的模型参数的梯度$\partial J/\partial \mathbf{W}^{(2)} \in \mathbb{R}^{q \times h}$。 使用链式法则得出:

$$
\frac{\partial J}{\partial \mathbf{W}^{(2)}}= \text{prod}\left(\frac{\partial J}{\partial \mathbf{o}}, \frac{\partial \mathbf{o}}{\partial \mathbf{W}^{(2)}}\right) + \text{prod}\left(\frac{\partial J}{\partial s}, \frac{\partial s}{\partial \mathbf{W}^{(2)}}\right)= \frac{\partial J}{\partial \mathbf{o}} \mathbf{h}^\top + \lambda \mathbf{W}^{(2)}.
$$

  • 为了获得关于$\mathbf{W}^{(1)}$的梯度,我们需要继续沿着输出层到隐藏层反向传播。 关于隐藏层输出的梯度$\partial J/\partial \mathbf{h} \in \mathbb{R}^h$由下式给出:

$$
\frac{\partial J}{\partial \mathbf{h}}
= \text{prod}\left(\frac{\partial J}{\partial \mathbf{o}}, \frac{\partial \mathbf{o}}{\partial \mathbf{h}}\right)
= {\mathbf{W}^{(2)}}^\top \frac{\partial J}{\partial \mathbf{o}}.
$$

  • 由于激活函数$\phi$是按元素计算的, 计算中间变量$\mathbf{z}$的梯度$\partial J/\partial \mathbf{z} \in \mathbb{R}^h$需要使用按元素乘法运算符,我们用$\odot$表示:

$$
\frac{\partial J}{\partial \mathbf{z}}
= \text{prod}\left(\frac{\partial J}{\partial \mathbf{h}}, \frac{\partial \mathbf{h}}{\partial \mathbf{z}}\right)
= \frac{\partial J}{\partial \mathbf{h}} \odot \phi’\left(\mathbf{z}\right).
$$

  • 最后,我们可以得到最接近输入层的模型参数的梯度$\partial J/\partial \mathbf{W}^{(1)} \in \mathbb{R}^{h \times d}$。 根据链式法则,我们得到:

$$
\frac{\partial J}{\partial \mathbf{W}^{(1)}}
= \text{prod}\left(\frac{\partial J}{\partial \mathbf{z}}, \frac{\partial \mathbf{z}}{\partial \mathbf{W}^{(1)}}\right) + \text{prod}\left(\frac{\partial J}{\partial s}, \frac{\partial s}{\partial \mathbf{W}^{(1)}}\right)
= \frac{\partial J}{\partial \mathbf{z}} \mathbf{x}^\top + \lambda \mathbf{W}^{(1)}.
$$

训练神经网络

  • 在训练神经网络时,前向传播和反向传播相互依赖。 对于前向传播,我们沿着依赖的方向遍历计算图并计算其路径上的所有变量。 然后将这些用于反向传播,其中计算顺序与计算图的相反。
  • 一方面,在前向传播期间计算正则项取决于模型参数$\mathbf{W}^{(1)}$和$\mathbf{W}^{(2)}$的当前值。它们是由优化算法根据最近迭代的反向传播给出的。另一方面,反向传播期间参数的梯度计算, 取决于由前向传播给出的隐藏变量$h$的当前值。
  • 因此,在训练神经网络时,在初始化模型参数后, 我们交替使用前向传播和反向传播,利用反向传播给出的梯度来更新模型参数。 注意,反向传播重复利用前向传播中存储的中间值,以避免重复计算。 带来的影响之一是我们需要保留中间值,直到反向传播完成。 这也是训练比单纯的预测需要更多的内存(显存)的原因之一。 此外,这些中间值的大小与网络层的数量和批量的大小大致成正比。 因此,使用更大的批量来训练更深层次的网络更容易导致内存不足(out of memory)错误。

数值稳定性和模型初始化

我们实现的每个模型都是根据某个预先指定的分布来初始化模型的参数。 有人会认为初始化方案是理所当然的,忽略了如何做出这些选择的细节。甚至有人可能会觉得,初始化方案的选择并不是特别重要。 相反,初始化方案的选择在神经网络学习中起着举足轻重的作用, 它对保持数值稳定性至关重要。 此外,这些初始化方案的选择可以与非线性激活函数的选择有趣的结合在一起。 我们选择哪个函数以及如何初始化参数可以决定优化算法收敛的速度有多快。 糟糕选择可能会导致我们在训练时遇到梯度爆炸或梯度消失。 本节将更详细地探讨这些主题,并讨论一些有用的启发式方法。 这些启发式方法在整个深度学习生涯中都很有用。

梯度消失和梯度爆炸

  • 考虑一个具有$L$层、输入$x$和输出$o$的深层网络。 每一层$l$由变换$f_l$定义, 该变换的参数为权重$\mathbf{W}^{(l)}$, 其隐藏变量是$\mathbf{h}^{(l)}$(令 $\mathbf{h}^{(0)} = \mathbf{x}$)。 我们的网络可以表示为:

$$
\mathbf{h}^{(l)} = f_l (\mathbf{h}^{(l-1)}) \text{ 因此 } \mathbf{o} = f_L \circ \ldots \circ f_1(\mathbf{x}).
$$

  • 如果所有隐藏变量和输入都是向量, 我们可以将$o$关于任何一组参数$\mathbf{W}^{(l)}$的梯度写为下式:

$$
\partial_{\mathbf{W}^{(l)}} \mathbf{o} = \underbrace{\partial_{\mathbf{h}^{(L-1)}} \mathbf{h}^{(L)}}{ \mathbf{M}^{(L)} \stackrel{\mathrm{def}}{=}} \cdot \ldots \cdot \underbrace{\partial{\mathbf{h}^{(l)}} \mathbf{h}^{(l+1)}}{ \mathbf{M}^{(l+1)} \stackrel{\mathrm{def}}{=}} \underbrace{\partial{\mathbf{W}^{(l)}} \mathbf{h}^{(l)}}_{ \mathbf{v}^{(l)} \stackrel{\mathrm{def}}{=}}.
$$

  • 换言之,该梯度是$L-l$个矩阵$\mathbf{M}^{(L)} \cdot \ldots \cdot \mathbf{M}^{(l+1)}$与梯度向量$\mathbf{v}^{(l)}$的乘积。 因此,我们容易受到数值下溢问题的影响. 当将太多的概率乘在一起时,这些问题经常会出现。 在处理概率时,一个常见的技巧是切换到对数空间, 即将数值表示的压力从尾数转移到指数。 不幸的是,上面的问题更为严重: 最初,矩阵$\mathbf{M}^{(l)}$可能具有各种各样的特征值。 他们可能很小,也可能很大; 他们的乘积可能非常大,也可能非常小。

不稳定梯度带来的风险不止在于数值表示; 不稳定梯度也威胁到我们优化算法的稳定性。 我们可能面临一些问题。 要么是梯度爆炸(gradient exploding)问题: 参数更新过大,破坏了模型的稳定收敛; 要么是梯度消失(gradient vanishing)问题: 参数更新过小,在每次更新时几乎不会移动,导致模型无法学习。

梯度消失

  • 曾经sigmoid函数$1/(1 + \exp(-x))$很流行, 因为它类似于阈值函数。神经元要么完全激活要么完全不激活(就像生物神经元)的想法很有吸引力。 然而,它却是导致梯度消失问题的一个常见的原因, 让我们仔细看看sigmoid函数为什么会导致梯度消失。
1
2
3
4
5
6
7
8
9
10
%matplotlib inline
import torch
from d2l import torch as d2l

x = torch.arange(-8.0, 8.0, 0.1, requires_grad=True)
y = torch.sigmoid(x)
y.backward(torch.ones_like(x))

d2l.plot(x.detach().numpy(), [y.detach().numpy(), x.grad.numpy()],
legend=['sigmoid', 'gradient'], figsize=(4.5, 2.5))

../_images/output_numerical-stability-and-init_e60514_6_0.svg

当sigmoid函数的输入很大或是很小时,它的梯度都会消失。 此外,当反向传播通过许多层时,除非我们在刚刚好的地方, 这些地方sigmoid函数的输入接近于零,否则整个乘积的梯度可能会消失。 当我们的网络有很多层时,除非我们很小心,否则在某一层可能会切断梯度。 事实上,这个问题曾经困扰着深度网络的训练。 因此,更稳定的ReLU系列函数已经成为从业者的默认选择(虽然在神经科学的角度看起来不太合理)。

  • 梯度值变成0
    • 对16位浮点数尤为严重
  • 训练没有进展
    • 不管如何选择学习率, 对于底部层尤为严重
  • 仅仅顶部层训练的较好, 无法让神经网络更深

梯度爆炸

  • 我们生成100个高斯随机矩阵,并将它们与某个初始矩阵相乘。 对于我们选择的尺度(方差$\sigma^2=1$),矩阵乘积发生爆炸。 当这种情况是由于深度网络的初始化所导致时,我们没有机会让梯度下降优化器收敛。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
M = torch.normal(0, 1, size=(4,4))
print('一个矩阵 \n',M)
for i in range(100):
M = torch.mm(M,torch.normal(0, 1, size=(4, 4)))

print('乘以100个矩阵后\n', M)

# result
一个矩阵
tensor([[ 1.1677, 0.3804, -0.7343, -1.8798],
[ 0.5325, -2.4282, 0.2886, 0.0961],
[-0.6474, -1.2622, -0.0183, -0.0958],
[-0.9811, 0.9840, -1.4430, 0.1905]])
乘以100个矩阵后
tensor([[-2.0338e+22, -1.2461e+21, 1.6642e+22, 1.4078e+21],
[ 1.4095e+22, 8.6345e+20, -1.1534e+22, -9.7620e+20],
[ 3.1960e+22, 1.9580e+21, -2.6153e+22, -2.2129e+21],
[ 9.7268e+21, 5.9587e+20, -7.9593e+21, -6.7356e+20]])

递推会随着层数的增加被放大到很大 or 缩小到很小

  • 值超出值域(infinity)
    • 对于16位浮点数尤其严重(区间:6e-5 - 6e4)
  • 对学习率敏感
    • 如果学习率太大->大参数值->更大的梯度
    • 如果学习率太小->训练无进展
    • 我们可能需要在训练过程不断调整学习率

打破对称性

  • 神经网络设计中的另一个问题是其参数化所固有的对称性。 假设我们有一个简单的多层感知机,它有一个隐藏层和两个隐藏单元。 在这种情况下,我们可以对第一层的权重$\mathbf{W}^{(1)}$,进行重排列, 并且同样对输出层的权重进行重排列,可以获得相同的函数。 第一个隐藏单元与第二个隐藏单元没有什么特别的区别。 换句话说,我们在每一层的隐藏单元之间具有排列对称性。
  • 假设输出层将上述两个隐藏单元的多层感知机转换为仅一个输出单元。 想象一下,如果我们将隐藏层的所有参数初始化为$\mathbf{W}^{(1)} = c$, $c$为常量,会发生什么? 在这种情况下,在前向传播期间,两个隐藏单元采用相同的输入和参数, 产生相同的激活,该激活被送到输出单元。 在反向传播期间,根据参数$W^{(1)}$对输出单元进行微分, 得到一个梯度,其元素都取相同的值。 因此,在基于梯度的迭代(例如,小批量随机梯度下降)之后,$W^{(1)}$的所有元素仍然采用相同的值。 这样的迭代永远不会打破对称性,我们可能永远也无法实现网络的表达能力。 隐藏层的行为就好像只有一个单元。 请注意,虽然小批量随机梯度下降不会打破这种对称性,但暂退法正则化可以。

多个神经元效果一样,那就是没啥意义嘛,学不到新东西。

参数初始化

  • 解决(或至少减轻)上述问题的一种方法是进行参数初始化, 优化期间的注意和适当的正则化也可以进一步提高稳定性。限制梯度的取值范围!!!例如我们可能希望模型不同层之间的参数都类似相同,这样整个模型整体的效果会好一点捏!
  • 目标:让参数值在合理的范围内
    • 例如: $[1e-6, 1e3]$
    • 乘法变加法: ResNet, LSTM
    • 归一化: 梯度归一化,梯度剪裁
    • 合理的权重初始和激活函数

默认初始化

  • 我们使用正态分布来初始化权重值。如果我们不指定初始化方法, 框架将使用默认的随机初始化方法,对于中等难度的问题,这种方法通常很有效。
  • 比较好的处理方法:
    • 让每层的方式是一个常数
      • 每层的输出和梯度都看作随机变量
      • 让它们的方差和均值都保持一致
  • 训练开始的时候更容易数值不稳定:
    • 远离最优解的地方,损失函数表面可能很复杂
    • 最优解附近表面比较平
  • 使用$N(0, 0.01)$来初始化可能对小网络没问题,但不能保证深度神经网络。

Xavier初始化

  • 让我们看看某些没有非线性的全连接层输出(例如,隐藏变量)$o_{i}$的尺度分布。 对于该层$n_\mathrm{in}$输入$x_j$及其相关权重$w_{ij}$,输出由下式给出:

$$
o_{i} = \sum_{j=1}^{n_\mathrm{in}} w_{ij} x_j.
$$

  • 权重$w_{ij}$都是从同一分布中独立抽取的。 此外,让我们假设该分布具有零均值和方差$\sigma^2$。 请注意,这并不意味着分布必须是高斯的,只是均值和方差需要存在。 现在,让我们假设层$x_j$的输入也具有零均值和方差$\gamma^2$, 并且它们独立于$w_{ij}$并且彼此独立。 在这种情况下,我们可以按如下方式计算$o_i$的平均值和方差:

$$
\begin{split}\begin{aligned}
E[o_i] & = \sum_{j=1}^{n_\mathrm{in}} E[w_{ij} x_j] \&= \sum_{j=1}^{n_\mathrm{in}} E[w_{ij}] E[x_j] \&= 0, \
\mathrm{Var}[o_i] & = E[o_i^2] - (E[o_i])^2 \
& = \sum_{j=1}^{n_\mathrm{in}} E[w^2_{ij} x^2_j] - 0 \
& = \sum_{j=1}^{n_\mathrm{in}} E[w^2_{ij}] E[x^2_j] \
& = n_\mathrm{in} \sigma^2 \gamma^2.
\end{aligned}\end{split}
$$

  • 保持方差不变的一种方法是设置$n_\mathrm{in} \sigma^2 = 1$。 现在考虑反向传播过程,我们面临着类似的问题,尽管梯度是从更靠近输出的层传播的。 使用与前向传播相同的推断,我们可以看到,除非$n_\mathrm{out} \sigma^2 = 1$, 否则梯度的方差可能会增大,其中$n_\mathrm{out}$是该层的输出的数量。 这使得我们进退两难:我们不可能同时满足这两个条件。 相反,我们只需满足:

$$
\begin{aligned}
\frac{1}{2} (n_\mathrm{in} + n_\mathrm{out}) \sigma^2 = 1 \text{ 或等价于 }
\sigma = \sqrt{\frac{2}{n_\mathrm{in} + n_\mathrm{out}}}.
\end{aligned}
$$

  • 这就是现在标准且实用的Xavier初始化的基础, 通常,Xavier初始化从均值为零,方差$\sigma^2 = \frac{2}{n_\mathrm{in} + n_\mathrm{out}}$的高斯分布中采样权重。 我们也可以将其改为选择从均匀分布中抽取权重时的方差。 注意均匀分布$U(-a, a)$的方差为$\frac{a^2}{3}$。 将$\frac{a^2}{3}$代入到$\sigma^2$的条件中,将得到初始化值域:

$$
U\left(-\sqrt{\frac{6}{n_\mathrm{in} + n_\mathrm{out}}}, \sqrt{\frac{6}{n_\mathrm{in} + n_\mathrm{out}}}\right).
$$

初始化的时候的方差,是根据输入输出的维度来定义的。常用!可以适配层数,不同搭配的神经网络模型!

尽管在上述数学推理中,“不存在非线性”的假设在神经网络中很容易被违反, 但Xavier初始化方法在实践中被证明是有效的。

环境和分布偏移

许多失败的机器学习部署(即实际应用)都可以追究到这种方式。 有时,根据测试集的精度衡量,模型表现得非常出色。 但是当数据分布突然改变时,模型在部署中会出现灾难性的失败。 更隐蔽的是,有时模型的部署本身就是扰乱数据分布的催化剂。 这种情况可能会带来灾难性的后果。

分布偏移的类型

  • 在一个经典的情景中,假设训练数据是从某个分布$p_S(\mathbf{x},y)$中采样的, 但是测试数据将包含从不同分布$p_T(\mathbf{x},y)$中抽取的未标记样本。 一个清醒的现实是:如果没有任何关于$p_S$和$p_T$之间相互关系的假设, 学习到一个分类器是不可能的。

  • 幸运的是,在对未来我们的数据可能发生变化的一些限制性假设下, 有些算法可以检测这种偏移,甚至可以动态调整,提高原始分类器的精度。

协变量偏移

  • 在不同分布偏移中,协变量偏移可能是最为广泛研究的。 这里我们假设:虽然输入的分布可能随时间而改变, 但标签函数(即条件分布$P(y \mid \mathbf{x})$)没有改变。 统计学家称之为协变量偏移(covariate shift), 因为这个问题是由于协变量(特征)分布的变化而产生的。 虽然有时我们可以在不引用因果关系的情况下对分布偏移进行推断, 但在我们认为$\mathbf{x}$导致$y$的情况下,协变量偏移是一种自然假设。

标签偏移

  • 标签偏移(label shift)描述了与协变量偏移相反的问题。 这里我们假设标签边缘概率$P(y)$可以改变, 但是类别条件分布$P(\mathbf{x} \mid y)$在不同的领域之间保持不变。 当我们认为$y$导致$\mathbf{x}$时,标签偏移是一个合理的假设。 例如,预测患者的疾病,我们可能根据症状来判断, 即使疾病的相对流行率随着时间的推移而变化。 标签偏移在这里是恰当的假设,因为疾病会引起症状。 在另一些情况下,标签偏移和协变量偏移假设可以同时成立。 例如,当标签是确定的,即使$y$导致$\mathbf{x}$,协变量偏移假设也会得到满足。 有趣的是,在这些情况下,使用基于标签偏移假设的方法通常是有利的。 这是因为这些方法倾向于包含看起来像标签(通常是低维)的对象, 而不是像输入(通常是高维的)对象。

概念偏移

  • 我们也可能会遇到概念偏移(concept shift): 当标签的定义发生变化时,就会出现这种问题。 这听起来很奇怪——一只猫就是一只猫,不是吗? 然而,其他类别会随着不同时间的用法而发生变化。 精神疾病的诊断标准、所谓的时髦、以及工作头衔等等,都是概念偏移的日常映射。
  • 如果我们要建立一个机器翻译系统, $P(y \mid \mathbf{x})$的分布可能会因我们的位置不同而得到不同的翻译。 这个问题可能很难被发现。 所以,我们最好可以利用在时间或空间上逐渐发生偏移的知识。

分布偏移示例

  • 在深入研究形式体系和算法之前,我们可以讨论一些协变量偏移或概念偏移可能并不明显的具体情况。
  • 例子有:
    • 医学诊断(收集训练数据的分布和在实际中遇到的数据分布可能有很大的不同,用近乎完美的精度来区分健康和患病人群确实很容易。 然而,这可能是因为受试者在年龄、激素水平、体力活动、 饮食、饮酒以及其他许多与疾病无关的因素上存在差异。 这对检测疾病的分类器可能并不适用。 这些抽样可能会遇到极端的协变量偏移。 )
    • 自动驾驶汽车( 由于真实的注释数据获取成本很高,他们想出了一个“聪明”的想法: 将游戏渲染引擎中的合成数据用作额外的训练数据。 这对从渲染引擎中抽取的“测试数据”非常有效,但应用在一辆真正的汽车里真是一场灾难。)

非平稳分布

  • 当分布变化缓慢并且模型没有得到充分更新时,就会出现更微妙的情况: 非平稳分布(nonstationary distribution)。 以下是一些典型例子:
    • 训练一个计算广告模型,但却没有经常更新(例如,一个2009年训练的模型不知道一个叫iPad的不知名新设备刚刚上市);
    • 建立一个垃圾邮件过滤器,它能很好地检测到所有垃圾邮件。但是,垃圾邮件发送者们变得聪明起来,制造出新的信息,看起来不像我们以前见过的任何垃圾邮件;
    • 建立一个产品推荐系统,它在整个冬天都有效,但圣诞节过后很久还会继续推荐圣诞帽。

更多轶事

  • 建立一个人脸检测器,它在所有基准测试中都能很好地工作,但是它在测试数据上失败了:有问题的例子是人脸充满了整个图像的特写镜头(训练集中没有这样的数据)。
  • 为美国市场建立了一个网络搜索引擎,并希望将其部署到英国。
  • 通过在一个大的数据集来训练图像分类器,其中每一个大类的数量在数据集近乎是平均的,比如1000个类别,每个类别由1000个图像表示。但是将该系统部署到真实世界中,照片的实际标签分布显然是不均匀的。

分布偏移纠正

  • 正如我们所讨论的,在许多情况下训练和测试分布$P(\mathbf{x}, y)$是不同的。 在一些情况下,我们很幸运,不管协变量、标签或概念如何发生偏移,模型都能正常工作。 在另一些情况下,我们可以通过运用策略来应对这种偏移,从而做得更好。 本节的其余部分将着重于应对这种偏移的技术细节。

经验风险与实际风险

  • 首先我们反思一下在模型训练期间到底发生了什么?训练数据${(\mathbf{x}_1, y_1), \ldots, (\mathbf{x}_n, y_n)}$的特征和相关的标签经过迭代,在每一个小批量之后更新模型$f$的参数。 为了简单起见,我们不考虑正则化,因此极大地降低了训练损失:

$$
\mathop{\mathrm{minimize}}f \frac{1}{n} \sum{i=1}^n l(f(\mathbf{x}_i), y_i),
$$

  • 其中$l$是损失函数,用来度量: 给定标签$y_i$,预测$f(\mathbf{x}_i)$的“糟糕程度”。 统计学家称这一项为经验风险。 经验风险(empirical risk)是为了近似 真实风险(true risk), 整个训练数据上的平均损失,即从其真实分布$p(\mathbf{x},y)$中抽取的所有数据的总体损失的期望值:

$$
E_{p(\mathbf{x}, y)} [l(f(\mathbf{x}), y)] = \int\int l(f(\mathbf{x}), y) p(\mathbf{x}, y) ;d\mathbf{x}dy.
$$

  • 然而在实践中,我们通常无法获得总体数据。 因此,经验风险最小化即在中最小化经验风险, 是一种实用的机器学习策略,希望能近似最小化真实风险。

协变量偏移纠正

  • 假设对于带标签的数据$(\mathbf{x}_i, y_i)$, 我们要评估$P(y \mid \mathbf{x})$。 然而观测值$\mathbf{x}_i$是从某些源分布$q(\mathbf{x})$中得出的, 而不是从目标分布$p(\mathbf{x})$中得出的。 幸运的是,依赖性假设意味着条件分布保持不变,即: $p(y \mid \mathbf{x}) = q(y \mid \mathbf{x})$。 如果源分布$q(\mathbf{x})$是“错误的”, 我们可以通过在真实风险的计算中,使用以下简单的恒等式来进行纠正:

$$
\begin{aligned}
\int\int l(f(\mathbf{x}), y) p(y \mid \mathbf{x})p(\mathbf{x}) ;d\mathbf{x}dy =
\int\int l(f(\mathbf{x}), y) q(y \mid \mathbf{x})q(\mathbf{x})\frac{p(\mathbf{x})}{q(\mathbf{x})} ;d\mathbf{x}dy.
\end{aligned}
$$

  • 换句话说,我们需要根据数据来自正确分布与来自错误分布的概率之比, 来重新衡量每个数据样本的权重:

$$
\beta_i \stackrel{\mathrm{def}}{=} \frac{p(\mathbf{x}_i)}{q(\mathbf{x}_i)}.
$$

  • 将权重$\beta_i$代入到每个数据样本$\mathop{\mathrm{minimize}}f \frac{1}{n} \sum{i=1}^n \beta_i l(f(\mathbf{x}_i), y_i).$中, 我们可以使用”加权经验风险最小化“来训练模型:

$$
\mathop{\mathrm{minimize}}f \frac{1}{n} \sum{i=1}^n \beta_i l(f(\mathbf{x}_i), y_i).
$$

由于不知道这个比率,我们需要估计它。 有许多方法都可以用,包括一些花哨的算子理论方法, 试图直接使用最小范数或最大熵原理重新校准期望算子。 对于任意一种这样的方法,我们都需要从两个分布中抽取样本: “真实”的分布$p$,通过访问测试数据获取; 训练集$q$,通过人工合成的很容易获得。 请注意,我们只需要特征$\mathbf{x} \sim p(\mathbf{x})$, 不需要访问标签$y \sim p(y)$。

  • 在这种情况下,有一种非常有效的方法可以得到几乎与原始方法一样好的结果: 对数几率回归(logistic regression)。 这是用于二元分类的softmax回归(见 3.4节)的一个特例。 综上所述,我们学习了一个分类器来区分从$p(\mathbf{x})$抽取的数据 和从$q(\mathbf{x})$抽取的数据。 如果无法区分这两个分布,则意味着相关的样本可能来自这两个分布中的任何一个。 另一方面,任何可以很好区分的样本都应该相应地显著增加或减少权重。为了简单起见,假设我们分别从$p(\mathbf{x})$和$q(\mathbf{x})$两个分布中抽取相同数量的样本。 现在用$z$标签表示:从$p$抽取的数据为1,从$q$抽取的数据为−1。 然后,混合数据集中的概率由下式给出:

$$
P(z=1 \mid \mathbf{x}) = \frac{p(\mathbf{x})}{p(\mathbf{x})+q(\mathbf{x})} \text{ and hence } \frac{P(z=1 \mid \mathbf{x})}{P(z=-1 \mid \mathbf{x})} = \frac{p(\mathbf{x})}{q(\mathbf{x})}.
$$

  • 因此,如果我们使用对数几率回归方法,其中$P(z=1 \mid \mathbf{x})=\frac{1}{1+\exp(-h(\mathbf{x}))}$($h$是一个参数化函数),则很自然有:

$$
\beta_i = \frac{1/(1 + \exp(-h(\mathbf{x}_i)))}{\exp(-h(\mathbf{x}_i))/(1 + \exp(-h(\mathbf{x}_i)))} = \exp(h(\mathbf{x}_i)).
$$

因此,我们需要解决两个问题: 第一个问题是关于区分来自两个分布的数据; 第二个问题是关于 (4.9.5) 中的加权经验风险的最小化问题。

  • 现在,我们来看一下完整的协变量偏移纠正算法。 假设我们有一个训练集${(\mathbf{x}_1, y_1), \ldots, (\mathbf{x}_n, y_n)}$和一个未标记的测试集${\mathbf{u}_1, \ldots, \mathbf{u}_m}$。 对于协变量偏移,我们假设$1 \leq i \leq n$的$\mathbf{x}_i$来自某个源分布, $\mathbf{u}_i$来自目标分布。 以下是纠正协变量偏移的典型算法:
    1. 生成一个二元分类训练集:${(\mathbf{x}_1, -1), \ldots, (\mathbf{x}_n, -1), (\mathbf{u}_1, 1), \ldots, (\mathbf{u}_m, 1)}$。
    2. 用对数几率回归训练二元分类器得到函数$h$。
    3. 使用$\beta_i = \exp(h(\mathbf{x}_i))$或更好的$\beta_i = \min(\exp(h(\mathbf{x}_i)), c)$($c$为常量)对训练数据进行加权。
    4. 使用权重$\beta_i$进行 (4.9.5) 中${(\mathbf{x}_1, y_1), \ldots, (\mathbf{x}_n, y_n)}$的训练。

请注意,上述算法依赖于一个重要的假设: 需要目标分布(例如,测试分布)中的每个数据样本在训练时出现的概率非零。 如果我们找到$p(\mathbf{x}) > 0$但$q(\mathbf{x}) = 0$的点, 那么相应的重要性权重会是无穷大。

本质就是对于数据进行回归,找出数据权重的分界模型,然后用$\beta_{i}$算出加权规则,再进行训练。

标签偏移纠正

  • 假设我们处理的是$k$个类别的分类任务。$p$和$q$中分别是源分布(例如训练时的分布)和目标分布(例如测试时的分布)。 假设标签的分布随时间变化:$q(y) \neq p(y)$, 但类别条件分布保持不变:$q(\mathbf{x} \mid y)=p(\mathbf{x} \mid y)$。 如果源分布$q(y)$是“错误的”, 我们可以根据 (4.9.2)中定义的真实风险中的恒等式进行更正:

$$
\begin{aligned}
\int\int l(f(\mathbf{x}), y) p(\mathbf{x} \mid y)p(y) ;d\mathbf{x}dy =
\int\int l(f(\mathbf{x}), y) q(\mathbf{x} \mid y)q(y)\frac{p(y)}{q(y)} ;d\mathbf{x}dy.
\end{aligned}
$$

  • 这里,重要性权重将对应于标签似然比率

$$
\beta_i \stackrel{\mathrm{def}}{=} \frac{p(y_i)}{q(y_i)}.
$$

  • 标签偏移的一个好处是,如果我们在源分布上有一个相当好的模型, 那么我们可以得到对这些权重的一致估计,而不需要处理周边的其他维度。 在深度学习中,输入往往是高维对象(如图像),而标签通常是低维(如类别)。为了估计目标标签分布,我们首先采用性能相当好的现成的分类器(通常基于训练数据进行训练), 并使用验证集(也来自训练分布)计算其混淆矩阵。 混淆矩阵$\mathbf{C}$是一个$k \times k$矩阵, 其中每列对应于标签类别,每行对应于模型的预测类别。 每个单元格的值$c_{ij}$是验证集中,真实标签为$j$, 而我们的模型预测为$i$的样本数量所占的比例。
  • 我们不能直接计算目标数据上的混淆矩阵, 因为我们无法看到真实环境下的样本的标签, 除非我们再搭建一个复杂的实时标注流程。 然而,我们所能做的是将所有模型在测试时的预测取平均数, 得到平均模型输出$\mu(\hat{\mathbf{y}}) \in \mathbb{R}^k$, 其中第$i$个元素$\mu(\hat{y}_i)$是我们模型预测测试集中$i$的总预测分数。结果表明,如果我们的分类器一开始就相当准确, 并且目标数据只包含我们以前见过的类别, 以及如果标签偏移假设成立(这里最强的假设), 我们就可以通过求解一个简单的线性系统来估计测试集的标签分布

$$
\mathbf{C} p(\mathbf{y}) = \mu(\hat{\mathbf{y}}),
$$

  • 因为作为一个估计,$\sum_{j=1}^k c_{ij} p(y_j) = \mu(\hat{y}i)$对所有$1 \leq i \leq k$成立, 其中$p(y_j)$是$k$维标签分布向量$p(\mathbf{y})$的第$j^\mathrm{th}$元素。 如果我们的分类器一开始就足够精确,那么混淆矩阵$\mathbf{C}$将是可逆的, 进而我们可以得到一个解$p(\mathbf{y}) = \mathbf{C}^{-1} \mu(\hat{\mathbf{y}})$。因为我们观测源数据上的标签,所以很容易估计分布$q(y)$。 那么对于标签为$y{i}$的任何训练样本$i$, 我们可以使用我们估计的$p(y_i)/q(y_i)$比率来计算权重$\beta_i$, 并将其代入上面的式子中。

概念偏移纠正

概念偏移很难用原则性的方式解决。 例如,在一个问题突然从“区分猫和狗”偏移为“区分白色和黑色动物”的情况下, 除了从零开始收集新标签和训练,别无妙方。 幸运的是,在实践中这种极端的偏移是罕见的。 相反,通常情况下,概念的变化总是缓慢的。 比如下面是一些例子:

  • 在计算广告中,新产品推出后,旧产品变得不那么受欢迎了。这意味着广告的分布和受欢迎程度是逐渐变化的,任何点击率预测器都需要随之逐渐变化;
  • 由于环境的磨损,交通摄像头的镜头会逐渐退化,影响摄像头的图像质量;
  • 新闻内容逐渐变化(即新新闻的出现)。

在这种情况下,我们可以使用与训练网络相同的方法,使其适应数据的变化。 换言之,我们使用新数据更新现有的网络权重,而不是从头开始训练。

学习问题的分类法

有了如何处理分布变化的知识,我们现在可以考虑机器学习问题形式化的其他方面。

批量学习

  • 批量学习(batch learning)中,我们可以访问一组训练特征和标签${(\mathbf{x}_1, y_1), \ldots, (\mathbf{x}_n, y_n)}$, 我们使用这些特性和标签训练$f(\mathbf{x})$。 然后,我们部署此模型来对来自同一分布的新数据$(\mathbf{x}, y)$进行评分。 例如,我们可以根据猫和狗的大量图片训练猫检测器。 一旦我们训练了它,我们就把它作为智能猫门计算视觉系统的一部分,来控制只允许猫进入。 然后这个系统会被安装在客户家中,基本再也不会更新。

在线学习

  • 除了“批量”地学习,我们还可以单个“在线”学习数据$(\mathbf{x}_i, y_i)$。 更具体地说,我们首先观测到$\mathbf{x}_i$, 然后我们得出一个估计值$f(\mathbf{x}_i)$, 只有当我们做到这一点后,我们才观测到$y_i$。 然后根据我们的决定,我们会得到奖励或损失。 许多实际问题都属于这一类。 例如,我们需要预测明天的股票价格, 这样我们就可以根据这个预测进行交易。 在一天结束时,我们会评估我们的预测是否盈利。 换句话说,在在线学习(online learning)中,我们有以下的循环。 在这个循环中,给定新的观测结果,我们会不断地改进我们的模型。

$$
\mathrm{model} ~ f_t \longrightarrow
\mathrm{data} ~ \mathbf{x}_t \longrightarrow
\mathrm{estimate} ~ f_t(\mathbf{x}_t) \longrightarrow
\mathrm{observation} ~ y_t \longrightarrow
\mathrm{loss} ~ l(y_t, f_t(\mathbf{x}t)) \longrightarrow
\mathrm{model} ~ f
{t+1}
$$

老虎机

  • 老虎机(bandits)是上述问题的一个特例。 虽然在大多数学习问题中,我们有一个连续参数化的函数$f$(例如,一个深度网络)。 但在一个老虎机问题中,我们只有有限数量的手臂可以拉动。 也就是说,我们可以采取的行动是有限的。 对于这个更简单的问题,可以获得更强的最优性理论保证,这并不令人惊讶。 我们之所以列出它,主要是因为这个问题经常被视为一个单独的学习问题的情景。

控制

  • 在很多情况下,环境会记住我们所做的事。 不一定是以一种对抗的方式,但它会记住,而且它的反应将取决于之前发生的事情。 例如,咖啡锅炉控制器将根据之前是否加热锅炉来观测到不同的温度。 在这种情况下,PID(比例—积分—微分)控制器算法是一个流行的选择。 同样,一个用户在新闻网站上的行为将取决于之前向她展示的内容(例如,大多数新闻她只阅读一次)。 许多这样的算法形成了一个环境模型,在这个模型中,他们的行为使得他们的决策看起来不那么随机。 近年来,控制理论(如PID的变体)也被用于自动调整超参数, 以获得更好的解构和重建质量,提高生成文本的多样性和生成图像的重建质量 (Shao et al., 2020)。

强化学习

  • 强化学习(reinforcement learning)强调如何基于环境而行动,以取得最大化的预期利益。 国际象棋、围棋、西洋双陆棋或星际争霸都是强化学习的应用实例。 再比如,为自动驾驶汽车制造一个控制器,或者以其他方式对自动驾驶汽车的驾驶方式做出反应 (例如,试图避开某物体,试图造成事故,或者试图与其合作)。

考虑到环境

  • 上述不同情况之间的一个关键区别是: 在静止环境中可能一直有效的相同策略, 在环境能够改变的情况下可能不会始终有效。 例如,一个交易者发现的套利机会很可能在他开始利用它时就消失了。 环境变化的速度和方式在很大程度上决定了我们可以采用的算法类型。 例如,如果我们知道事情只会缓慢地变化, 就可以迫使任何估计也只能缓慢地发生改变。 如果我们知道环境可能会瞬间发生变化,但这种变化非常罕见, 我们就可以在使用算法时考虑到这一点。 当一个数据科学家试图解决的问题会随着时间的推移而发生变化时, 这些类型的知识至关重要。

机器学习中的公平、责任和透明度

  • 当我们部署机器学习系统时, 不仅仅是在优化一个预测模型, 而通常是在提供一个会被用来(部分或完全)进行自动化决策的工具。从考虑预测到决策的飞跃不仅提出了新的技术问题, 而且还提出了一系列必须仔细考虑的伦理问题。 如果我们正在部署一个医疗诊断系统,我们需要知道它可能适用于哪些人群,哪些人群可能无效。 忽视对一个亚群体的幸福的可预见风险可能会导致我们执行劣质的护理水平。 此外,一旦我们规划整个决策系统,我们必须退后一步,重新考虑如何评估我们的技术。 在这个视野变化所导致的结果中,我们会发现精度很少成为合适的衡量标准。 例如,当我们将预测转化为行动时,我们通常会考虑到各种方式犯错的潜在成本敏感性。 举个例子:将图像错误地分到某一类别可能被视为种族歧视,而错误地分到另一个类别是无害的, 那么我们可能需要相应地调整我们的阈值,在设计决策方式时考虑到这些社会价值。 我们还需要注意预测系统如何导致反馈循环。 例如,考虑预测性警务系统,它将巡逻人员分配到预测犯罪率较高的地区。 很容易看出一种令人担忧的模式是如何出现的:
  1. 犯罪率高的社区会得到更多的巡逻;
  2. 因此,在这些社区中会发现更多的犯罪行为,输入可用于未来迭代的训练数据;
  3. 面对更多的积极因素,该模型预测这些社区还会有更多的犯罪;
  4. 下一次迭代中,更新后的模型会更加倾向于针对同一个地区,这会导致更多的犯罪行为被发现等等。
  • 在建模纠正过程中,模型的预测与训练数据耦合的各种机制都没有得到解释, 研究人员称之为“失控反馈循环”的现象。 此外,我们首先要注意我们是否解决了正确的问题。 比如,预测算法现在在信息传播中起着巨大的中介作用, 个人看到的新闻应该由他们喜欢的Facebook页面决定吗? 这些只是在机器学习职业生涯中可能遇到的令人感到“压力山大”的道德困境中的一小部分。

References

  1. https://zh.d2l.ai/chapter_multilayer-perceptrons/index.html
  2. He et al., 2015
  3. https://developer.aliyun.com/article/971730#:~:text=L1%E8%8C%83%E6%95%B0%EF%BC%88L1%20norm,%E5%85%83%E7%B4%A0%E7%BB%9D%E5%AF%B9%E5%80%BC%E4%B9%8B%E5%92%8C%E3%80%82
  4. https://en.wikipedia.org/wiki/Reproducing_kernel_Hilbert_space
  5. 再生核希尔伯特空间(RKHS)
  6. https://zhuanlan.zhihu.com/p/476067423
  7. https://www.bilibili.com/video/BV1Y5411c7aY/?spm_id_from=333.999.0.0&vd_source=ff957cd8fbaeb55d52afc75fbcc87dfd
  8. https://space.bilibili.com/1567748478/channel/seriesdetail?sid=358497
  9. https://www.bilibili.com/video/BV1u64y1i75a/?p=2&spm_id_from=pageDriver&vd_source=ff957cd8fbaeb55d52afc75fbcc87dfd
  10. https://baike.baidu.com/item/%E6%B7%B7%E6%B7%86%E7%9F%A9%E9%98%B5/10087822
  11. https://www.bilibili.com/video/BV1NK4y1P7Tu?p=3&vd_source=ff957cd8fbaeb55d52afc75fbcc87dfd
  12. https://www.bilibili.com/video/BV1rh411m7Hb/?spm_id_from=333.788.recommend_more_video.3&vd_source=ff957cd8fbaeb55d52afc75fbcc87dfd
  13. https://www.bilibili.com/video/BV15Q4y1o7vc/?spm_id_from=333.999.0.0&vd_source=ff957cd8fbaeb55d52afc75fbcc87dfd
  14. https://github.com/mli/paper-reading
  15. https://www.jianshu.com/p/42e93acacc52

D2L-4-Multilayer Perceptrons
https://alexanderliu-creator.github.io/2023/07/30/d2l-4-multilayer-perceptrons/
作者
Alexander Liu
发布于
2023年7月30日
许可协议