多层感知机 (非线性神经网络) (PyTorch)
隐藏层
回想一下softmax
回归的模型架构。该模型通过单个仿射变换将我们的输入直接映射到输出,然后进行softmax
操作。如果我们的标签通过仿射变换后确实与我们的输入数据相关,那么这种方法确实足够了。但是,仿射变换中的线性是一个很强的假设。例如,线性意味着单调假设:任何特征的增大都会导致模型输出的增大(如果对应的权重为正),或者导致模型输出的减小(如果对应的权重为负)。例如,如果我们试图预测一个人是否会偿还贷款。我们可以认为,在其它条件不变的情况下,收入较高的申请人比收入较低的申请人更有可能偿还贷款。但是,虽然收入与还款概率存在单调性,但是它们不是线性相关的。收入从0增加到5万,可能比从100万增加到105万带来更大的还款可能性。处理这一问题的一种方法是对我们的数据进行预处理,使线性变得更合理,如使用收入的对数作为我们的特征。然而我们可以很容易找出违反单调性的例子。
例如,我们想要根据体温预测死亡率。对于体温高于37摄氏度的人来说,温度越高风险越大。然而对于体温低于37摄氏度的人来说,温度越高风险就越低。在这种情况下,我们也可以通过一些巧妙的预处理来解决问题。例如,我们可以使用与37摄氏度的距离作为特征。但是,如何对猫和狗的图像进行分类呢?增加位置(13,17)
处像素的强度是否总是增加(或降低)图像描绘狗的似然?对线性模型的依赖对应于一个隐含的假设,即区分猫和狗的唯一要求是评估单个像素的强度。在一个倒置图像后依然保留类别的世界里,这种方法注定会失败。与我们前面的例子相比,这里的线性很荒谬,而且我们难以通过简单的预处理来解决这个问题。这是因为任何像素的重要性都以复杂的方式取决于该像素的上下文(周围像素的值)。我们的数据可能会有一种表示,这种表示会考虑到我们在特征之间的相关交互作用。在此表示的基础上建立一个线性模型可能会是合适的,但我们不知道如何手动计算这么一种表示。对于深度神经网络,我们使用观测数据来联合学习隐藏层表示和应用于该表示的线性预测器。
网络架构
我们可以通过在网络中加入一个或多个隐藏层来克服线性模型的限制,使其能够处理更普遍的函数关系类型。要做到这一点,最简单的方法是将许多全连接层堆叠在一起,每一层都输出到上面的层,知道生成最后的输出。我们可以把钱multilayer perceptron
),通常缩写为MLP
。下面,我们以图的方式描述了多层感知机:
这个多层感知机有4个输入,3个输出,其隐藏层包含5个隐藏单元。输入层不涉及任何计算,因此使用此网络产生输出只需要实现隐藏层和输出层的计算。因此,这个多层感知机中的层数为2.注意,这两个层都是全连接的。每个输入都会影响隐藏层中的每个神经元,而隐藏层中的每个神经元又会影响输出层中的每个神经元。具有全连接层的多层感知机的参数开销可能会高的令人望而却步。即使不改变输入或输出大小的情况下,可能在参数节约和模型有效性之间进行平衡。
同之前一样,我们通过矩阵hidden representations
)。在数学或代码中,hidden-layer variable
)因为隐藏层和输出层都是全连接的,所以我们有隐藏层权重
注意在添加隐藏层之后,模型现在需要跟踪和更新额外的参数。可我们能从中得到什么好处呢?在上面定义的模型里,我们没有好处!原因很简单:上面的隐藏单元由输入的仿射函数给出,而输出(softmax
操作前)只是隐藏单元的仿射函数。仿射函数的仿射函数本身就是仿射函数,但是我们之前的线性模型已经能够表示任何仿射函数。我们可以证明这一等价性,即对于任意权重值,我们只需合并隐藏层,便可产生具有参数
为了f发挥多层架构的潜力,我们还需要一个额外的关键要素:在仿射变换之后对每个隐藏单元应用非线性激活函数(activation function
)activations
)。一般来说,有了激活函数,就不可能再将我们的多层感知机退化成线性模型:
由于softmax
符号来表示按行操作。但是应用于隐藏层的激活函数通常不仅按行操作,也按元素操作。这意味着,在计算每一层的线性部分之后,我们可以计算每个活性值,而不需要查看其他隐藏单元所取的值。对于大多数激活函数都是这样。为了构建更通用的多层感知机,我们可以继续堆叠这样的隐藏层,例如:
多层感知机可以通过隐藏神经元,捕捉到输入之间复杂的相互作用,这些神经元依赖于每个输入的值。我们可以很容易地设计隐藏节点来执行任意计算。例如,在一对输入上进行基本逻辑操作,多层感知机是通用近似器。即使是网络只有一个隐藏层,给定足够的神经元和正确的权重,我们可以对任意函数建模,尽管实际中学习该函数是很困难的。神经网络有点像C
语言。C
语言和任何其他现代编程语言一样,能够表达任何可计算的程序。但实际上,想出一个符合规范的程序才是最困难的部分。而且,虽然一个单隐层网络能学习任何函数,但并不意味着我们应该尝试使用单隐藏层网络来解决所有问题。事实上,通过使用更深(而不是更广)的网络,我们可以更容易地逼近许多函数。
激活函数
激活函数(activation function
)通过计算加权和并加上偏置来确定神经元是否应该被激活,它们将输入信号转换为输出的可微运算。大多数激活函数都是非线性的。由于激活函数是深度学习的基础,下面简要介绍一些常见的激活函数。
ReLU函数
最受欢迎的激活函数是修正线性单元(Rectified linear unit,ReLU
),因为它实现简单,同时在各种预测任务中表现良好。ReLU
提供了一种非常简单的非线性变换。给定元素ReLU
函数被定义为该元素与
通俗地说,ReLU
函数通过将相应的活性值设为0
,仅保留正元素并丢弃所有负元素。为了直观感受一下,我们可以画出函数的曲线图。正如从图中所看到,激活函数是分段线性的。
1 | x = torch.arange(-8.0, 8.0, 0.1, requires_grad=True) |
当输入为负时,ReLU
函数的导数为0
,而当输入为正时,ReLU
函数的导数为1
。 注意,当输入值精确等于0
时,ReLU
函数不可导。在此时,我们默认使用左侧的导数,即当输入为0
时导数为0
。我们可以忽略这种情况,因为输入可能永远都不会是0
。这里引用一句古老的谚语,“如果微妙的边界条件很重要,我们很可能是在研究数学而非工程”,这个观点正好适用于这里。下面我们绘制ReLU
函数的导数。
1 | y.backward(torch.ones_like(x), retain_graph=True) |
使用ReLU
的原因是,它求导表现得特别好:要么让参数消失,要么让参数通过。这使得优化表现得更好,并且ReLU
减轻了困扰以往神经网络的梯度消失问题。注意,ReLU
函数有许多变体,包括参数化ReLU(Parameterized ReLU,pReLU)
函数,该变体为ReLU添加了一个线性项,因此即使参数是负的,某些信息仍然可以通过:
sigmoid函数
对于一个定义域在sigmoid
函数将输入变换为区间(0, 1)上的输出。因此,sigmoid通常称为挤压函数(squashing function
):它将范围(-inf, inf
)中的任意输入压缩到区间(0, 1
)中的某个值:
在最早的神经网络中,科学家们感兴趣的是对“激发”或“不激发”的生物神经元进行建模。因此,这一领域的先驱可以一直追溯到人工神经元的发明者麦卡洛克和皮茨,他们专注于阈值单元。阈值单元在其输入低于某个阈值时取值0
,当输入超过阈值时取值1
。当人们逐渐关注到到基于梯度的学习时,sigmoid
函数是一个自然的选择,因为它是一个平滑的、可微的阈值单元近似。当我们想要将输出视作二元分类问题的概率时,sigmoid
仍然被广泛用作输出单元上的激活函数(sigmoid
可以视为softmax
的特例)。然而,sigmoid
在隐藏层中已经较少使用,它在大部分时候被更简单、更容易训练的ReLU
所取代。下面,我们绘制sigmoid
函数。注意,当输入接近0时,sigmoid
函数接近线性变换。
1 | y = torch.sigmoid(x) |
sigmoid
函数的导数为下面的公式:
sigmoid
函数的导数图像如下所示。注意,当输入为0时,sigmoid
函数的导数达到最大值0.25
;而输入在任一方向上越远离0
点时,导数越接近0
。
1 | # 清除以前的梯度 |
tanh函数
与sigmoid
函数类似,tanh
(双曲正切)函数也能将其输入压缩转换到区间(-1, 1
)上。tanh
函数的公式如下:
下面我们绘制tanh
函数。注意,当输入在0
附近时,tanh
函数接近线性变换。函数的形状类似于sigmoid
函数,不同的是tanh
函数关于坐标系原点中心对称。
1 | y = torch.tanh(x) |
tanh
函数的导数是:
tanh
函数的导数图像如下所示。当输入接近0
时,tanh
函数的导数接近最大值1
。与我们在sigmoid
函数图像中看到的类似,输入在任一方向上越远离0
点,导数越接近0
。
1 | # 清除以前的梯度 |
总结
多层感知机在输出层和输入层之间增加一个或多个全连接隐藏层,并通过激活函数转换隐藏层的输出。常用的激活函数包括ReLU
函数、sigmoid
函数和tanh
函数。
模型选择、欠拟合和过拟合
我们的目标是发现模式(pattern
)。但是,我们如何才能确定模型是真正发现了一种泛化模式,而不是简单的记住了数据呢?例如,我们想要在患者的基因数据与痴呆状态之间寻找模式,其中标签是从集合{痴呆,轻度认知障碍,健康}中提取的。因为基因可以唯一确定每个个体(不考虑双胞胎),所以在这个任务中是有可能记住这个数据集。我们不想让模型只会做这样的事情:“那是鲍勃!我记得他!他有痴呆症!”。原因很简单:当我们将来部署该模型时,模型需要判断从未见过的患者。只有当模型真正发现了一种泛化模式时,才会作出有效的预测。
更正式地说,我们的目标是发现某些模式,这些模式捕捉到了我们训练集潜在总体的规律。如果成功做到了这点,即使是对以前从未遇到过的个体,模型也可以成功地评估风险。如何发现可以泛化的模式是机器学习的根本问题。困难在于,当我们训练模型时,我们只能访问数据中的小部分样本。最大的公开图像数据集包含大约一百万张图像。而在大部分时候,我们只能从数千或数万个数据样本中学习。在大型医院系统中,我们可能会访问数十万份医疗记录。当我们使用有限的样本时,可能会遇到这样的问题:当收集到更多的数据时,会发现之前找到的明显关系并不成立。将模型在训练数据上拟合的比在潜在分布中更接近的现象称为过拟合(overfitting
),用于对抗过拟合的技术称为正则化(regularization
)。
训练误差和泛化误差
为了进一步讨论这一现象,我们需要了解训练误差和泛化误差。训练误差(training error
)是指,模型在训练数据集上计算得到的误差。泛化误差(generalization error
)是指,模型应用在同样从原始样本的分布中抽取的无限多数据样本时,模型误差的期望。问题是,我们永远不能准确地计算出泛化误差。这是因为无限多的数据样本是一个虚构的对象。在实际中,我们只能通过将模型应用于一个独立的测试集来估计泛化误差,该测试集由随机选取的、未曾在训练集中出现的数据样本构成。
下面的三个思维实验将有助于更好地说明这种情况。假设一个大学生正在努力准备期末考试。一个勤奋的学生会努力做好练习,并利用往年的考试题目来测试自己的能力。尽管如此,在过去的考试题目上取得好成绩并不能保证他会在真正考试时发挥出色。例如,学生可能试图通过死记硬背考题的答案来做准备。他甚至可以完全记住过去考试的答案。另一名学生可能会通过试图理解给出某些答案的原因来做准备。在大多数情况下,后者会考得更好。
类似地,考虑一个简单地使用查表法来回答问题的模型。如果允许的输入集合是离散的并且相当小,那么也许在查看许多训练样本后,该方法将执行得很好。但当这个模型面对从未见过的例子时,它表现的可能比随机猜测好不到哪去。这是因为输入空间太大了,远远不可能记住每一个可能的输入所对应的答案。例如,考虑
最后,考虑对掷硬币的结果(类别0:正面,类别1:反面)进行分类的问题。假设硬币是公平的,无论我们想出什么算法,泛化误差始终是0,1,1,1,0,1
}。我们的算法不需要额外的特征,将倾向于总是预测多数类,从我们有限的样本来看,它似乎是1占主流。在这种情况下,总是预测类1的模型将产生
由于泛化是机器学习中的基本问题,许多数学家和理论家毕生致力于研究描述这一现象的形式理论。在同名定理(eponymous theorem
)中,格里文科和坎特利推导出了训练误差收敛到泛化误差的速率。在一系列开创性的论文中,Vapnik
和Chervonenkis
将这一理论扩展到更一般种类的函数。这项工作为统计学习理论奠定了基础。在我们目前已探讨、并将在之后继续探讨的监督学习情景中,我们假设训练数据和测试数据都是从相同的分布中独立提取的。这通常被称为独立同分布假设,这意味着对数据进行采样的过程没有进行“记忆”。换句话说,抽取的第2
个样本和第3
个样本的相关性,并不比抽取的第2
个样本和第200
万个样本的相关性更强。
有时候我们即使轻微违背独立同分布假设,模型仍将继续运行得非常好。比如,我们有许多有用的工具已经应用于现实,如人脸识别、语音识别和语言翻译。毕竟,几乎所有现实的应用都至少涉及到一些违背独立同分布假设的情况。有些违背独立同分布假设的行为肯定会带来麻烦。比如,我们试图只用来自大学生的人脸数据来训练一个人脸识别系统,然后想要用它来监测疗养院中的老人。这不太可能有效,因为大学生看起来往往与老年人有很大的不同。目前,即使认为独立同分布假设是理所当然的,理解泛化性也是一个困难的问题。此外,能够解释深层神经网络泛化性能的理论基础,也仍在继续困扰着学习理论领域最伟大的学者们。当我们训练模型时,我们试图找到一个能够尽可能拟合训练数据的函数。但是如果它执行地“太好了”,而不能对看不见的数据做到很好泛化,就会导致过拟合。这种情况正是我们想要避免或控制的。深度学习中有许多启发式的技术旨在防止过拟合。
当我们有简单的模型和大量的数据时,我们期望泛化误差与训练误差相近。当我们有更复杂的模型和更少的样本时,我们预计训练误差会下降,但泛化误差会增大。模型复杂性由什么构成是一个复杂的问题。一个模型是否能很好地泛化取决于很多因素。例如,具有更多参数的模型可能被认为更复杂,参数有更大取值范围的模型可能更为复杂。通常对于神经网络,我们认为需要更多训练迭代的模型比较复杂,而需要早停(early stopping
)的模型(即较少训练迭代周期)就不那么复杂。
我们很难比较本质上不同大类的模型之间(例如,决策树与神经网络)的复杂性。就目前而言,一条简单的经验法则相当有用:统计学家认为,能够轻松解释任意事实的模型是复杂的,而表达能力有限但仍能很好地解释数据的模型可能更有现实用途。在哲学上,这与波普尔的科学理论的可证伪性标准密切相关:如果一个理论能拟合数据,且有具体的测试可以用来证明它是错误的,那么它就是好的。这一点很重要,因为所有的统计估计都是事后归纳。也就是说,我们在观察事实之后进行估计,因此容易受到相关谬误的影响。目前,我们将把哲学放在一边,坚持更切实的问题。
影响模型泛化的因素:
- 可调整参数的数量。当可调整参数的数量(有时称为自由度)很大时,模型往往更容易过拟合。
- 参数采用的值。当权重的取值范围较大时,模型可能更容易过拟合。
- 训练样本的数量。即使模型很简单,也很容易过拟合只包含一两个样本的数据集。而过拟合一个有数百万个样本的数据集则需要一个极其灵活的模型。
模型选择
在机器学习中,我们通常在评估几个候选模型后选择最终的模型。这个过程叫做模型选择。有时,需要进行比较的模型在本质上是完全不同的(比如,决策树与线性模型)。又有时,我们需要比较不同的超参数设置下的同一类模型。例如,训练多层感知机模型时,我们可能希望比较具有 不同数量的隐藏层、不同数量的隐藏单元以及不同的激活函数组合的模型。为了确定候选模型中的最佳模型,我们通常会使用验证集。
原则上,在我们确定所有的超参数之前,我们不希望用到测试集。如果我们在模型选择过程中使用测试数据,可能会有过拟合测试数据的风险,那就麻烦大了。如果我们过拟合了训练数据,还可以在测试数据上的评估来判断过拟合。但是如果我们过拟合了测试数据,我们又该怎么知道呢?因此,我们决不能依靠测试数据进行模型选择。然而,我们也不能仅仅依靠训练数据来选择模型,因为我们无法估计训练数据的泛化误差。在实际应用中,情况变得更加复杂。虽然理想情况下我们只会使用测试数据一次,以评估最好的模型或比较一些模型效果,但现实是测试数据很少在使用一次后被丢弃。我们很少能有充足的数据来对每一轮实验采用全新测试集。解决此问题的常见做法是将我们的数据分成三份,除了训练和测试数据集之外,还增加一个验证数据集(validation dataset
),也叫验证集(validation set
)。
当训练数据稀缺时,我们甚至可能无法提供足够的数据来构成一个合适的验证集。这个问题的一个流行的解决方案是采用
欠拟合还是过拟合?
当我们比较训练和验证误差时,我们要注意两种常见的情况。首先,我们要注意这样的情况:训练误差和验证误差都很严重,但它们之间仅有一点差距。如果模型不能降低训练误差,这可能意味着模型过于简单(即表达能力不足),无法捕获试图学习的模式。此外,由于我们的训练和验证误差之间的泛化误差很小,我们有理由相信可以用一个更复杂的模型降低训练误差。这种现象被称为欠拟合(underfitting
)。另一方面,当我们的训练误差明显低于验证误差时要小心,这表明严重的过拟合(overfitting
)。注意,过拟合并不总是一件坏事。特别是在深度学习领域,众所周知,最好的预测模型在训练数据上的表现往往比在保留(验证)数据上好得多。最终,我们通常更关心验证误差,而不是训练误差和验证误差之间的差距。
为了说明一些关于过拟合和模型复杂性的经典直觉,我们给出一个多项式的例子。给定由单个特征
这只是一个线性回归问题,我们的特征是
高阶多项式函数比低阶多项式函数复杂得多。高阶多项式的参数较多,模型函数的选择范围较广。因此在固定训练数据集的情况下,高阶多项式函数相对于低阶多项式的训练误差应该始终更低(最坏也是相等)。事实上,当数据样本包含了
另一个重要因素是数据集的大小。训练数据集中的样本越少,我们就越有可能(且更严重地)过拟合。随着训练数据量的增加,泛化误差通常会减小。此外,一般来说,更多的数据不会有什么坏处。对于固定的任务和数据分布,模型复杂性和数据集大小之间通常存在关系。给出更多的数据,我们可能会尝试拟合一个更复杂的模型。能够拟合更复杂的模型可能是有益的。如果没有足够的数据,简单的模型可能更有用。对于许多任务,深度学习只有在有数千个训练样本时才优于线性模型。从一定程度上来说,深度学习目前的生机要归功于廉价存储、互联设备以及数字化经济带来的海量数据集。
多项式回归
生成数据集
给定
噪声项0
且标准差为0.1
正态分布。在优化的过程中,我们通常希望避免非常大的梯度值或损失值。这就是我们将特征从100
个样本。
1 | import math |
对模型进行训练和测试
首先让我们实现一个函数来评估模型在给定数据集上的损失。
1 | def evaluate_loss(net, data_iter, loss): #@save |
三阶多项式函数拟合(正常)
我们将首先使用三阶多项式函数,它与数据生成函数的阶数相同。结果表明,该模型能有效降低训练损失和测试损失。学习到的模型参数也接近真实值
1 | # 从多项式特征中选择前4个维度,即1,x,x^2/2!,x^3/3! |
线性函数拟合(欠拟合)
让我们再看看线性函数拟合,减少该模型的训练损失相对困难。在最后一个迭代周期完成后,训练损失仍然很高。当用来拟合非线性模式(如这里的三阶多项式函数)时,线性模型容易欠拟合。
1 | # 从多项式特征中选择前2个维度,即1和x |
高阶多项式函数拟合(过拟合)
现在,让我们尝试使用一个阶数过高的多项式来训练模型。在这种情况下,没有足够的数据用于学到高阶系数应该具有接近于零的值。因此,这个过于复杂的模型会轻易受到训练数据中噪声的影响。虽然训练损失可以有效地降低,但测试损失仍然很高。结果表明,复杂模型对数据造成了过拟合。
1 | # 从多项式特征中选取所有维度 |
总结
欠拟合是指模型无法继续减少训练误差。过拟合是指训练误差远小于验证误差。由于不能基于训练误差来估计泛化误差,因此简单地最小化训练误差并不一定意味着泛化误差的减小。机器学习模型需要注意防止过拟合,即防止泛化误差过大。验证集可以用于模型选择,但不能过于随意地使用它。我们应该选择一个复杂度适当的模型,避免使用数量不足的训练样本。
权重衰减
回想一下,在多项式回归的例子中,我们可以通过调整拟合多项式的阶数来限制模型的容量。实际上,限制特征的数量是缓解过拟合的一种常用技术。然而,简单的丢弃特征对这项工作来说可能过于生硬。我们继续思考多项式回归的例子,考虑高维输入可能发生的情况。多项式对多变量数据的自然扩展称单项式(monomials
)。也可以说变量幂的乘积,单项式的阶数是幂的和。例如,weight decay
)是最广泛使用的正则化技术之一,它通常也被称为
一种简单的方法是通过线性函数
回想一下,
对于
此外,为什么我们首先使用ridge regression
),lasso regression
)。使用feature selection
),这可能是其他场景下需要的。
我们根据估计值与观测值之间的差异来更新
暂退法(Dropout)
我们介绍了通过惩罚权重的
重新审视过拟合
当面对更多的特征而样本不足时,线性模型往往会过拟合。相反,当给出更多样本而不是特征,通常线性模型不会过拟合。不幸的是,线性模型泛化的可靠性是有代价的。简单地说,线性模型没有考虑到特征之间的交互作用。对于每个特征,线性模型必须指定正的或负的权重,而忽略其他特征。泛化性和灵活性之间的这种基本权衡被描述为偏差-方差权衡(bias-variance tradeoff
)。线性模型有很高的偏差:它们只能表示一小类函数。然而,这些模型的方差很低:它们在不同的随机数据样本上可以得出相似的结果。深度神经网络位于偏差-方差谱的另一端。与线性模型不同,神经网络并不局限于单独查看每个特征,而是学习特征之间的交互。即使我们有比特征多得多的样本,深度神经网络也有可能过拟合。2017
年,一组研究人员通过在随机标记的图像上训练深度网络。这展示了神经网络的极大灵活性,因为人类很难将输入和随机标记的输出联系起来,但通过随机梯度下降优化的神经网络可以完美地标记训练集中的每一幅图像。想一想这意味着什么?假设标签是随机均匀分配的,并且有10
个类别,那么分类器在测试数据上很难取得高于10%
的精度,那么这里的泛化差距就高达90%
,如此严重的过拟合。
扰动的稳健性
在探究泛化性之前,我们先来定义一下什么是一个“好”的预测模型? 我们期待“好”的预测模型能在未知的数据上有很好的表现:经典泛化理论认为,为了缩小训练和测试性能之间的差距,应该以简单的模型为目标。简单性以较小维度的形式展现,简单性的另一个角度是平滑性,即函数不应该对其输入的微小变化敏感。例如,当我们对图像进行分类时,我们预计向像素添加一些随机噪声应该是基本无影响的。 1995
年,克里斯托弗·毕晓普证明了 具有输入噪声的训练等价于Tikhonov
正则化 (Bishop, 1995
)。这项工作用数学证实了“要求函数光滑”和“要求函数对输入的随机噪声具有适应性”之间的联系。然后在2014
年,斯里瓦斯塔瓦等人 (Srivastava et al., 2014
) 就如何将毕晓普的想法应用于网络的内部层提出了一个想法:在训练过程中,他们建议在计算后续层之前向网络的每一层注入噪声。因为当训练一个有多层的深层网络时,注入噪声只会在输入-输出映射上增强平滑性。
这个想法被称为暂退法(dropout
)。暂退法在前向传播过程中,计算每一内部层的同时注入噪声,这已经成为训练神经网络的常用技术。这种方法之所以被称为暂退法,因为我们从表面上看是在训练过程中丢弃(drop out
)一些神经元。在整个训练过程的每一次迭代中,标准暂退法包括在计算下一层之前将当前层中的一些节点置零。需要说明的是,暂退法的原始论文提到了一个关于有性繁殖的类比:神经网络过拟合与每一层都依赖于前一层激活值相关,称这种情况为“共适应性”。作者认为,暂退法会破坏共适应性,就像有性生殖会破坏共适应的基因一样。那么关键的挑战就是如何注入这种噪声。一种想法是以一种无偏向(unbiased
)的方式注入噪声。这样在固定住其他层时,每一层的期望值等于没有噪音时的值。在毕晓普的工作中,他将高斯噪声添加到线性模型的输入中。在每次训练迭代中,他将从均值为零的分布
根据此模型的设计,其期望值保持不变,即1
个隐藏层和5
个隐藏单元的多层感知机。当我们将暂退法应用到隐藏层,以
通常,我们在测试时不用暂退法。给定一个训练好的模型和一个新的样本,我们不会丢弃任何节点,因此不需要标准化。然而也有一些例外:一些研究人员在测试时使用暂退法,用于估计神经网络预测的“不确定性”:如果通过许多不同的暂退法遮盖后得到的预测结果都是一致的,那么我们可以说网络发挥更稳定。
前向传播、反向传播和计算图
我们已经学习了如何用小批量随机梯度下降训练模型。然而当实现该算法时,我们只考虑了通过前向传播(forward propagation
)所涉及的计算。在计算梯度时,我们只调用了深度学习框架提供的反向传播函数,而不知其所以然。梯度的自动计算(自动微分)大大简化了深度学习算法的实现。在自动微分之前,即使是对复杂模型的微小调整也需要手工重新计算复杂的导数,学术论文也不得不分配大量页面来推导更新规则。
前向传播
前向传播(forward propagation
)指的是:按顺序(从输入层到输出层)计算和存储神经网络中每层的结果。我们将一步步研究单隐藏层神经网络的机制,为了简单起见,我们假设输入样本是
其中
隐藏变量
假设损失函数为
根据
其中矩阵的Frobenius
范数是将矩阵展平为向量后应用的
下面,我们将objective function
)。
前向传播计算图
绘制计算图有助于我们可视化计算中操作符和变量的依赖关系。下图是上述简单网络相对应的计算图,其中正方形表示变量,圆圈表示操作符。左下角表示输入,右上角表示输出。注意显示数据流的箭头方向主要是向右和向上的。
反向传播
反向传播(backward propagation
)指的是计算神经网络参数梯度的方法。简言之,该方法根据微积分中的链式规则,按相反的顺序从输出层到输入层遍历网络。该算法存储了计算某些参数梯度时所需的任何中间变量(偏导数)。假设我们有函数
在这里,我们使用
接下来,我们根据链式法则计算目标函数关于输出层变量
接下来,我们计算正则化项相对于两个参数的梯度:
现在我们可以计算最接近输出层的模型参数的梯度
为了获得关于
由于激活函数
最后,我们可以得到最接近输入层的模型参数的梯度
训练神经网络
在训练神经网络时,前向传播和反向传播相互依赖。对于前向传播,我们沿着依赖的方向遍历计算图并计算其路径上的所有变量。然后将这些用于反向传播,其中计算顺序与计算图的相反。在训练神经网络时,在初始化模型参数后,我们交替使用前向传播和反向传播,利用反向传播给出的梯度来更新模型参数。注意,反向传播重复利用前向传播中存储的中间值,以避免重复计算。带来的影响之一是我们需要保留中间值,直到反向传播完成。这也是训练比单纯的预测需要更多的内存(显存)的原因之一。此外,这些中间值的大小与网络层的数量和批量的大小大致成正比。因此,使用更大的批量来训练更深层次的网络更容易导致内存不足(out of memory
)错误。
总结
前向传播在神经网络定义的计算图中按顺序计算和存储中间变量,它的顺序是从输入层到输出层。反向传播按相反的顺序(从输出层到输入层)计算和存储神经网络的中间变量和参数的梯度。在训练深度学习模型时,前向传播和反向传播是相互依赖的。训练比预测需要更多的内存。
数值稳定性和模型初始化
我们实现的每个模型都是根据某个预先指定的分布来初始化模型的参数。有人会认为初始化方案是理所当然的,忽略了如何做出这些选择的细节。甚至有人可能会觉得,初始化方案的选择并不是特别重要。相反,初始化方案的选择在神经网络学习中起着举足轻重的作用,它对保持数值稳定性至关重要。此外,这些初始化方案的选择可以与非线性激活函数的选择有效的结合在一起。我们选择哪个函数以及如何初始化参数可以决定优化算法收敛的速度有多快。糟糕的选择可能会导致我们在训练时遇到梯度爆炸或梯度消失。
梯度消失和梯度爆炸
考虑一个具有
如果所有隐藏变量和输入都是向量,我们可以将
换言之,该梯度是
不稳定梯度带来的风险不止在于数值表示;不稳定梯度也威胁到我们优化算法的稳定性。我们可能面临一些问题。要么是梯度爆炸(gradient exploding
)问题:参数更新过大,破坏了模型的稳定收敛;要么是梯度消失(gradient vanishing
)问题:参数更新过小,在每次更新时几乎不会移动,导致模型无法学习。
梯度消失
曾经sigmoid
函数sigmoid
函数为什么会导致梯度消失。
1 | import torch |
正如上图,当sigmoid
函数的输入很大或是很小时,它的梯度都会消失。此外,当反向传播通过许多层时,除非我们在刚刚好的地方,这些地方sigmoid
函数的输入接近于零,否则整个乘积的梯度可能会消失。当我们的网络有很多层时,除非我们很小心,否则在某一层可能会切断梯度。事实上,这个问题曾经困扰着深度网络的训练。因此,更稳定的ReLU
系列函数已经成为从业者的默认选择(虽然在神经科学的角度看起来不太合理)。
梯度爆炸
相反,梯度爆炸可能同样令人烦恼。为了更好地说明这一点,我们生成100个高斯随机矩阵,并将它们与某个初始矩阵相乘。对于我们选择的尺度(方差
1 | M = torch.normal(0, 1, size=(4,4)) |
打破对称性
神经网络设计中的另一个问题是其参数化所固有的对称性。假设我们有一个简单的多层感知机,它有一个隐藏层和两个隐藏单元。在这种情况下,我们可以对第一层的权重
,
参数初始化
解决(或至少减轻)上述问题的一种方法是进行参数初始化,优化期间的注意和适当的正则化也可以进一步提高稳定性。我们使用正态分布来初始化权重值。如果我们不指定初始化方法,框架将使用默认的随机初始化方法,对于中等难度的问题,这种方法通常很有效。让我们看看某些没有非线性的全连接层输出(例如,隐藏变量)
权重
保持方差不变的一种方法是设置
这就是现在标准且实用的Xavier
初始化的基础,它以其提出者(Glorot and Bengio, 2010
)第一作者的名字命名。通常,Xavier
初始化从均值为零,方差
尽管在上述数学推理中,“不存在非线性”的假设在神经网络中很容易被违反,但Xavier
初始化方法在实践中被证明是有效的。
总结
梯度消失和梯度爆炸是深度网络中常见的问题。在参数初始化时需要非常小心,以确保梯度和参数可以得到很好的控制。需要用启发式的初始化方法来确保初始梯度既不太大也不太小。ReLU
激活函数缓解了梯度消失问题,这样可以加速收敛。随机初始化是保证在进行优化前打破对称性的关键。Xavier
初始化表明,对于每一层,输出的方差不受输入数量的影响,任何梯度的方差不受输出数量的影响。
环境和分布偏移
分布偏移的类型
首先,我们可能考虑数据发生变化的各种方式,以及为挽救模型性能可能采取的措施。在一个经典情景中,假设训练数据是从某个分布
协变量偏移
在不同分布偏移中,协变量偏移可能是最为广泛研究的。这里我们假设:虽然输入的分布可能随时间而改变,但标签函数(即条件分布covariate shift
),因为这个问题是由于协变量(特征)分布的变化而产生的。虽然有时我们可以在不引用因果关系的情况下对分布偏移进行推断,但在我们认为
标签偏移
标签偏移(label shift
)描述了与协变量偏移相反的问题。这里我们假设标签边缘概率
概念偏移
我们也可能会遇到概念偏移(concept shift
):当标签的定义发生变化时,就会出现这种问题。这听起来很奇怪——一只猫就是一只猫,不是吗?然而,其他类别会随着不同时间的用法而发生变化。精神疾病的诊断标准、所谓的时髦、以及工作头衔等等,都是概念偏移的日常映射。如果我们要建立一个机器翻译系统,
分布偏移纠正
在许多情况下训练和测试分布
经验风险与实际风险
首先我们反思一下在模型训练期间到底发生了什么?训练数据
其中empirical risk
)是为了近似真实风险(true risk
),整个训练数据上的平均损失,即从其真实分布
然而在实践中,我们通常无法获得总体数据。因此,经验风险是一种实用的机器学习策略,希望能近似最小化真实风险。
协变量偏移纠正
假设对于带标签的数据
换句话说,我们需要根据数据来自正确分布与来自错误分布的概率之比,来重新衡量每个数据样本的权重:
将权重
由于不知道这个比率,我们需要估计它。有许多方法都可以用,包括一些花哨的算子理论方法,试图直接使用最小范数或最大熵原理重新校准期望算子。对于任意一种这样的方法,我们都需要从两个分布中抽取样本:“真实”的分布
在这种情况下,有一种非常有效的方法可以得到几乎与原始方法一样好的结果:对数几率回归(logistic regression
)。这是用于二元分类的softmax回归的一个特例。综上所述,我们学习了一个分类器来区分从
和
抽取的数据为1
,从-1
。然后,混合数据集中的概率由下式给出:
因此,如果我们使用对数几率回归方法,其中
因此,我们需要解决两个问题:第一个问题是关于区分来自两个分布的数据;第二个问题是加权经验风险的最小化问题。在这个问题中,我们将对其中的项加权
现在,我们来看一下完整的协变量偏移纠正算法。假设我们有一个训练集
- 生成一个二元分类训练集:
。 - 用对数几率回归训练二元分类器得到函数
。 - 使用
或更好的 ( 为常量)对训练数据进行加权。 - 使用权重
进行 的训练。
请注意,上述算法依赖于一个重要的假设:需要目标分布(例如,测试分布)中的每个数据样本在训练时出现的概率非零。如果我们找到
标签偏移纠正
假设我们处理的是
这里,重要性权重将对应于标签似然比率:
标签偏移的一个好处是,如果我们在源分布上有一个相当好的模型,那么我们可以得到对这些权重的一致估计,而不需要处理周边的其他维度。 在深度学习中,输入往往是高维对象(如图像),而标签通常是低维(如类别)。
为了估计目标标签分布,我们首先采用性能相当好的现成的分类器(通常基于训练数据进行训练),并使用验证集(也来自训练分布)计算其混淆矩阵。混淆矩阵
是验证集中,真实标签为
现在,我们不能直接计算目标数据上的混淆矩阵,因为我们无法看到真实环境下的样本的标签,除非我们再搭建一个复杂的实时标注流程。然而,我们所能做的是将所有模型在测试时的预测取平均数,得到平均模型输出
因为作为一个估计,
因为我们观测源数据上的标签,所以很容易估计分布
概念偏移纠正
概念偏移很难用原则性的方式解决。例如,在一个问题突然从“区分猫和狗”偏移为“区分白色和黑色动物”的情况下,除了从零开始收集新标签和训练,别无妙方。幸运的是,在实践中这种极端的偏移是罕见的。相反,通常情况下,概念的变化总是缓慢的。比如下面是一些例子:
- 在计算广告中,新产品推出后,旧产品变得不那么受欢迎了。这意味着广告的分布和受欢迎程度是逐渐变化的,任何点击率预测器都需要随之逐渐变化;
- 由于环境的磨损,交通摄像头的镜头会逐渐退化,影响摄像头的图像质量;
- 新闻内容逐渐变化(即新新闻的出现)。
在这种情况下,我们可以使用与训练网络相同的方法,使其适应数据的变化。换言之,我们使用新数据更新现有的网络权重,而不是从头开始训练。
学习问题分类
批量学习
在批量学习(batch learning
)中,我们可以访问一组训练特征和标签
在线学习
除了“批量”地学习,我们还可以单个“在线”学习数据online learning
)中,我们有以下的循环。在这个循环中,给定新的观测结果,我们会不断地改进我们的模型。
老虎机
老虎机(bandits
)是上述问题的一个特例。虽然在大多数学习问题中,我们有一个连续参数化的函数
控制
在很多情况下,环境会记住我们所做的事。不一定是以一种对抗的方式,但它会记住,而且它的反应将取决于之前发生的事情。例如,咖啡锅炉控制器将根据之前是否加热锅炉来观测到不同的温度。在这种情况下,PID
(比例—积分—微分)控制器算法是一个流行的选择。同样,一个用户在新闻网站上的行为将取决于之前向她展示的内容(例如,大多数新闻她只阅读一次)。许多这样的算法形成了一个环境模型,在这个模型中,他们的行为使得他们的决策看起来不那么随机。近年来,控制理论(如PID
的变体)也被用于自动调整超参数,以获得更好的解构和重建质量,提高生成文本的多样性和生成图像的重建质量
强化学习
强化学习(reinforcement learning
)强调如何基于环境而行动,以取得最大化的预期利益。国际象棋、围棋、西洋双陆棋或星际争霸都是强化学习的应用实例。再比如,为自动驾驶汽车制造一个控制器,或者以其他方式对自动驾驶汽车的驾驶方式做出反应(例如,试图避开某物体,试图造成事故,或者试图与其合作)。
环境的考虑
上述不同情况之间的一个关键区别是:在静止环境中可能一直有效的相同策略,在环境能够改变的情况下可能不会始终有效。例如,一个交易者发现的套利机会很可能在他开始利用它时就消失了。环境变化的速度和方式在很大程度上决定了我们可以采用的算法类型。例如,如果我们知道事情只会缓慢地变化,就可以迫使任何估计也只能缓慢地发生改变。如果我们知道环境可能会瞬间发生变化,但这种变化非常罕见,我们就可以在使用算法时考虑到这一点。当一个数据科学家试图解决的问题会随着时间的推移而发生变化时,这些类型的知识至关重要。
总结
在许多情况下,训练集和测试集并不来自同一个分布。这就是所谓的分布偏移。真实风险是从真实分布中抽取的所有数据的总体损失的预期。然而,这个数据通常是无法获得的。经验风险是训练数据的平均损失,用于近似真实风险。在实践中,我们进行经验风险最小化。在相应的假设条件下,可以在测试时检测并纠正协变量偏移和标签偏移。在测试时,不考虑这种偏移可能会成为问题。在某些情况下,环境可能会记住自动操作并以令人惊讶的方式做出响应。在构建模型时,我们必须考虑到这种可能性,并继续监控实时系统,并对我们的模型和环境以意想不到的方式纠缠在一起的可能性持开放态度。