R 深度学习入门指南(二)

原文:Introduction to Deep Learning Using R

协议:CC BY-NC-SA 4.0

五、卷积神经网络

类似于第四章中关于多层感知器问题的概念,卷积神经网络(CNN)也有多层,用于计算给定数据集的输出。这个模型的发展可以追溯到 20 世纪 50 年代,当时研究人员 Hubel 和 Wiesel 对动物视觉皮层进行了建模。在 1968 年的一篇论文中,他们详细讨论了他们的发现,这些发现在他们研究的猴子和猫的大脑中识别出了简单细胞和复杂细胞。他们观察到,相对于观察到的直边,简单细胞具有最大化的输出。相反,观察到复杂细胞中的感受野相当大,并且它们的输出相对不受上述感受野内边缘位置的影响。除了图像识别之外,CNN 最初获得并仍然保持其恶名,CNN 还有相当多的其他应用,例如在自然语言处理和强化学习领域。

CNN 的结构和性质

广义地说,CNN 是多层神经网络模型。与 Hubel 和 Weisel 描述的动物视觉皮层的结构一致,该模型可以被视觉地解释,如图 5-1 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5-1。

Broad visual display of a CNN

每个区块代表 CNN 的一个不同层,我将在本章后面详细解释。从左到右是输入层、隐藏层(卷积层、汇集层和分离层)和全连接层。在最后一层之后,模型输出一个分类。现在考虑图 5-2 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5-2。

CNN architecture diagram

完全连接的层加强了神经元和相邻层之间的局部连接,如图 5-2 所示。因此,隐藏层的输入是来自该隐藏层之前的层的神经元的子集。这确保了学习的子集神经元产生最佳可能的响应。此外,单元在特征/激活图中共享相同的权重和偏差,使得给定层中的所有神经元都在分析/检测完全相同的特征。

至于 CNN 上下文中的特征,我指的是图像中不同的部分。这就是我们的过滤器与它正在分析的图像部分进行比较的内容,因此它可以确定正在扫描的图像部分与正在分析的特征的相似程度。假设我们有足够的训练数据和足够多的图像类别,这些特征足够明显,有助于区分不同的类别。

想象我们正在看两幅图像,具体来说是 X 和 O,如图 5-3 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5-3。

O and X example photo

如果我们把 X 和 O 都想象成不同的图像,那么对于人眼来说,我们可以确定它们是明显不同的字母。它们的区别因素包括 O 的中心是空的,而 X 的中心有两条相交的线。图 5-4 和 5-5 显示了这些数值的特征图示例。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5-5。

Feature map of “O”

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5-4。

Feature map of “X”

这些值通常表示为矩阵中的一个条目,黑色和白色分别为–1 和 1。当处理彩色图像时,每个像素通常被表示为矩阵中的一个条目,对于黑色和白色分别具有值 1 或 256。但是,根据所使用的语言,零索引可能会影响 RGB 值的表示,从而使边界向后移动 1。

CNN 架构的组件

本节涵盖 CNN 架构的组件。

卷积层

这一层是任何给定 CNN 中大多数计算发生的地方,因此是图像通过输入后的第一层。在卷积层中,我们用滤波器扫描图像的一部分。就高度和宽度而言,每个过滤器都不是特别大,但是它们都延伸通过该层的整个长度。

例如,假设我们试图将一幅图像分类为 1 或 0,而实际上这幅图像是 1。想象一下,图像有一个黑色背景,但数字是用白色像素勾勒出来的。图 5-6 显示了该图像的一个示例。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5-6。

Example image of “1”

计算机会将值为 1 的白色像素和值为-1 的黑色像素区分开来。当我们通过卷积层输入该图像时,该模型提取图像的独特特征,这些特征通常是最终定义特定图像的颜色、形状和边缘。一旦我们有了给定训练图像的特征,我们就对输入的图像进行所谓的过滤。过滤是获取图像特征的过程,在这种情况下,我们可以将其想象为一个 3 x 3 像素的正方形,并将其与输入图像的一部分匹配,该部分也是一个 3 x 3 像素的正方形。在图 5-7 中,我们可以看到过滤的过程是什么样子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5-7。

Example of a filter

然后,我们将特征的像素数量乘以图像块的相应像素数量。在示例中,我们应该为每个操作获得 1 或–1 的输出。直观上,当像素完全匹配时,它们应该输出为 1,当不匹配时,它们应该输出为–1。最后,我们取像素乘积的平均值。如果图像完全匹配,则平均值应为 1。如果没有,那就比 1 低了相当大的度数。在这种情况下,想象图像补丁和选择的特征根本不匹配。因此,当我们取平均值时,它应该输出到-1。我们将该产品放在我们正在分析的图像片位置的中心,在所谓的特征图上具有给定的特征。这最终将是卷积层的输出,并将用于下一层。卷积层将通过不同的迭代产生多个特征图。在每个可能的位置将特征与给定的图像块进行匹配的过程称为图像卷积。我们将给定 CNN 的特征/激活图表示为

)

其中 w k 是权重,b k 是偏差,x 是特定像素的值,tanh 是数据中的非线性。下标 I、j 指的是代表特征/激活图的矩阵的条目。权重 w k 最终将特征图中的像素连接到前一层。最终,卷积图层是由前面描述的操作生成的特征地图的堆叠。然后,我们将特征地图放入池层。我们计算输出体积的空间大小为

)

其中 W =输入音量大小,F =卷积层中感受野的大小,P =零填充量,S =步幅。

汇集层

在连续的卷积层之间,通常放置一个所谓的池层。简而言之,汇集层采用卷积层中生成的特征地图,并将它们“汇集”到一个图像中。汇集层有效地执行维度缩减,因此优先强调空间表示,从而降低模型的复杂性。这可以与决策树中的修剪过程相比较,同样有助于防止给定模型的过度拟合。在原型 CNN 模型中,池层有一个 2 x 2 的过滤器,步幅为 2,输入中的每个深度切片都被向下采样,因此我们相对于高度和宽度移动了 2 个像素。池层中的这些操作有助于丢弃 75%的功能/激活映射。这一层使用最大值运算,在前面提到的例子中,最大值超过 4 个数字,或任何给定特征/激活图中的 4 个像素。

与之前描述的示例保持一致,假设使用 2 x 2 过滤器,我们正在查看 9 x 9 特征地图,其中我们使用以下分数分析左上角:

| .88 | Zero | | Zero | .95 |

使用最大值运算时,我们会选择 0.95,因为这是 2 x 2 窗口内的最大值。因为我们的步幅为 2,所以我们向右移动了 2 个像素,这意味着我们看到的是一个 2 x 2 的图像切片,其中该切片的左上角应该是特征图的第三列,直到我们得到一个最大合并图像,这大大减少了模型的不必要的复杂性。作为该层中使用的最大值运算的直接结果,在分析图像时,我们不需要像前一层那样精确,因此这有助于创建一个更健壮的模型,可以更容易地对输入进行分类。我这样说的具体意思是,连接每一层的权重值可以更一般化到它们所接触到的所有训练数据,而不是以 CNN 在样本外表现不佳的方式过度拟合。

决定输出空间大小的函数由

)

给出

其中

)

校正线性单位(ReLU)图层

整流器是激活功能的另一个术语。通常,我们将以下函数应用于该层的输入

)

其中 x =神经元的输入。

当应用于特征图时,我们可以想象特征图中的任何负值现在都为零。具体来说,这有助于勾勒出更接近最相关图像的特征地图。我们对所有的特征地图都这样做,然后得到图像的“堆栈”。

全连接(FC)层

这一层中的任何神经元都连接到前一层中的所有激活图。这一层通常位于用户确定数量的卷积层、池层和 ReLU 层之后。由于先前操作中指定的图像缩小,输入到该层的图像将明显小于原始输入。在这一层,我们扫描简化的图像,这些图像应该对应于每个特征图,并将这里给出的每个值转换为值列表。这个列表对应于我们放入的 k 个图像中的一个。根据本章开头的例子,我们最初输入 1。在移动通过所有层之后,我们取对应于这个图像的分数的平均值,然后这是图像为 1 或 0 的概率。应当注意,该层与卷积层之间的唯一区别在于,卷积层仅连接到输入中的局部区域,并且卷积层体积中的许多神经元共享参数。考虑到这一点,在构建给定架构时,我们还可以在 FC 层和卷积层之间进行转换。

损失层

在这一层,我们将预测的标签与图像的实际标签进行比较。当试图从 k 个可能的特征级别中对对象进行分类时,我们将使用 softmax 损失分类器。为了对特定图像的标签进行回归,使用欧几里德函数也是常见的。它们的功能如下:

  1. Softmax loss function :

    )

  2. Euclidean loss function :

    )

  3. Softmax normalization :

    )

当使用反向传播算法时,我们制作一个混淆矩阵来比较 1 或 0,其中我们将答案的标签减去分配的概率。按照我们一直使用的例子,假设 1 = 1,0 = 0,但当我们输入 1 时,我们只收到 0 的概率为 0 . 85,1 的概率为 0 . 45。因此,我们会有–. 60 的累积误差。然后,我们通过在完全连接的层中显示的权重,使用如前所述的梯度方法,以指定的学习速率来调整每个特征映射像素。我们将权重初始化为 0,并在达到损失容限或达到最大迭代阈值的点停止 CNN。必须考虑前面章节中描述的关于收敛到最优解的相同考虑。

调谐参数

发送到输入层的图像应该多次被 2 整除。常见的图像尺寸为 32 x 32、64 x 62 等。卷积层的过滤器尺寸应该最多为 3 x 3 或 5 x 5,并且应该以不改变输入的空间尺寸的方式执行零填充。对于池层,它们的尺寸应该是 2 x 2,通常步长为 2。使用这些参数,75%的激活将被丢弃。大于 3 的池层会导致分类过程中的过多损失。当描述神经元及其排列时,超参数与这一对话最相关。具体来说,我将提到步幅、深度和零填充。在 CNN 最重要的参数中,步幅是一个固定参数,它决定了滑过过滤器的像素数量。例如,如果步幅为 2,则每次有 2 个像素滑过过滤器。通常,步幅不大于 2,也不小于 1。零填充是输入体积边界周围零的大小。通过控制零填充,我们可以从一层到另一层更仔细地控制激活图和其他输出的大小。最后,深度是指我们为给定实验选择的滤波器数量,每个滤波器都是最终在卷积层中搜索每个图像的内容。

著名的 CNN 架构

  • LeNet:由著名的深度学习研究人员 Yann LeCun 在 20 世纪 90 年代开发,LeNet 是一个相对简单的架构,从各方面考虑。该模型最初的目的是对数字进行分类,读取邮政编码,并执行一般的简单图像分类。这被认为类似于任何开发者首先用给定语言编写的“Hello World”程序,因为它被认为是第一个成功应用于实际任务的 CNN 程序。如图 5-8 所示,涉及的层次如下:

    • input, conv layer, ReLU, pooling layer, conv layer, ReLU, pooling layer, fully connected, ReLu, fully connected, and softmax classifier.

      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

      图 5-8。

      Visualization of LeNet

  • GoogLeNet (Inception):这种架构在 2014 年赢得了 ImageNet 大规模视觉识别挑战(ILSVRC)比赛,以此向 Yann LeCun 的 LeNet 致敬。它是由克里斯蒂安·塞格迪、、杨青·贾、皮埃尔·塞尔马内、斯科特·里德、德拉戈米尔·安盖洛夫、杜米特鲁·埃赫兰、钦文·万霍克和安德鲁·拉宾诺维奇开发的。GoogLeNet 的名字来源于这样一个事实,即该架构的相当多的开发人员在 Google Inc .工作。在他们的论文“用卷积加深”中,他们描述了一种允许“在保持计算预算约束的同时增加网络的深度和宽度”的架构。如图 5-9 所示,建议的结构如下:初始架构的重点是通过前面描述的层中的方向,CNN 模型允许“增加每个阶段的单元数量”,而不会使模型变得太复杂。总的来说,该模型旨在处理各种规模的视觉信息,然后将计算汇总到下一阶段,以便同时分析更高层次的抽象。

    • Input, conv. layer, max pool, conv layer, pooling layer (with max function), inception (2 layers), max pool, inception, inception (5 layers), max pool, inception (2 layers), average pooling layer, dropout, ReLU, softmax classifier.

      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

      图 5-9。

      GoogLeNet architectureThe focus of the inception architecture is that through the orientation in the layers as described earlier, the CNN model allows for “increasing the number of units at each stage” without doing so to the point where the model becomes too complex. Overall, the model seeks to process visual information at various scales and then aggregate the calculations to the next stage so higher levels of abstraction are analyzed simultaneously.

  • AlexNet: Developed by Alex Krizhevsky, Ilya Sutskever, and Geoffrey Hinton , this won the ILSVRC in 2012. Similar in architecture to LeNet, AlexNet uses “non-saturating neurons” and efficiently implements the GPU for the convolution layers. The neurons in fully connected layers are connected to all neurons in the previous layer, response-normalization layers follow the first and second convolutional layers, and the kernel of layers two, four, and five are connected only to the kernel maps in the previous layer, which would be on the same GPU. The architecture is as follows (and shown in Figure 5-10):

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 5-10。

    AlexNet architecture

    • 卷积(5 层),全连接(3 层),[输出为 1000 路 softmax 分类器]
  • VGGNet:它在 ILSVRC 2014 竞赛中获得了 AlexNet 的第二名。VGGNet 是由牛津大学的卡伦·西蒙扬和安德鲁·齐泽曼开发的。感受野是 3×3,有 1×1 个过滤器,步幅是 2,最大集合大小是 2×2。该架构是这样的,输入通过几个卷积层馈送到三个完全连接的层(第一层和第二层具有 4096 个通道,最后一层是执行 1000 路分类的 softmax 层)。

  • ResNet:2015 年 ILSVRC 的第一名得主,ResNet 拥有 152 层——远远超过了前面提到的网络的数量。它是由微软研究院的何开民、、任和开发的。这种架构的目的是形成一个学习参考层输入的剩余函数的网络,而不是学习未参考函数的网络。最终结果是,网络更容易学习,更容易优化,并通过增加深度来提高精度,而不是通过增加深度来降低精度。

正规化

当多层感知器有不止一层时,它们被认为具有逼近给定目标的能力,这将导致过度拟合。为了防止过度拟合,通常建议对输入数据进行正则化,然而,在 CNN 的情况下,这是一个稍微不同的过程,我们可以使用:1) DropOut,这是从人脑中观察到的现象的灵感中获得的。这是一个给定的隐藏层不被通过的概率,这个概率我们设置为一个超参数。2)随机池,其中随机选择激活。可以说,随机池不需要超参数,可以作为一种启发式方法,与其他正则化技术一起使用。3) DropConnect,它是 dropout 的推广,其中每个连接都可以以 1–p 的概率被丢弃。该层中的每个单元都从前一层中的随机单元输入数据,这些数据在每次迭代时都会发生变化。这有助于确保重量不会过重。4)权重衰减,其功能类似于 L1/L2 正则化,其中我们严重惩罚大的权重向量。

在这些方法中,CNN 对使用 DropOut 有相当大的热情,因为它被证明是一种有效和强大的技术。除了防止过度拟合之外,已经观察到丢弃提高了具有大量参数的网络的计算效率,因为这种形式的正则化实际上导致网络在给定迭代期间变得更小。在所有这些迭代之后,较小网络的性能可以被平均为完整网络的性能的一般预测。第二,可以观察到,丢弃层在网络中引入了随机化的性能,这使得数据内的噪声被平均,使得其对数据内的信号的屏蔽被减小。

使用 L1 正则化也并不少见,但要注意的是,这种情况下的权重向量通常会缩小到 0,有时缩小到足够小,这样我们就可以得到稀疏填充的权重矩阵。这种类型的正则化的负面影响是,由于层之间的“死”连接,包含重要信息的某些层的输入可能变得完全不被注意。相比之下,当你特别想要非常明确的特征选择时,L1 正则化可能会产生明显更好的性能。

传统上,L2 正则化被视为在 CNN 中执行正则化的标准方法,因为它倾向于惩罚异常大的权重,而支持那些相对于整个矩阵而言比例一般较低的权重。与 L1 正则化相比,您会得到一个更加丰富的权重矩阵,这将导致网络从一个给定的层向下一层提供更多的数据。因此,特征选择将比使用 L1 正则化时更不明显。

你应该知道的最后一种正则化是对 L1 或 L2 正则化的补充,通过对给定权重的标准大小施加限制。因此,这将允许参数更新具有硬限制,并因此限制给定网络能够产生的可能解决方案的数量。这将有助于通过限制可能的解决方案来更快地训练网络,并且在最佳解决方案中防止参数在不正确的方向上更新太多。

摘要

我们已经充分讨论了 CNN 的概念,并浏览了目前最新的所有架构。有关 CNN 的应用示例,请参见第十一章,特别是关于图像数据的预处理,这是构建图像识别软件的一个非常重要的步骤。接下来,我们将讨论循环神经网络(RNs)以及在基于时间序列的数据中检测模式的复杂性。

六、循环神经网络

循环神经网络(RNNs)是被创建来处理模式识别范围内的问题的模型,并且基本上建立在关于前馈 MLPs 的相同概念上。不同之处在于,虽然 MLP 根据定义具有多层,但 rnn 没有,而是具有将输入转换为输出的定向循环。本章开始时,我将介绍几个 RNN 模型,并以 RNNs 的实际应用结束。

完全递归网络

假设我们有一个输入 x,我们将其输入到 RNN 模型中,其中我们将状态定义为 h,输入乘以权重矩阵 w。到目前为止,一切都与之前描述的神经网络模型相同,但如前所述,rnn 随着时间的推移对输入执行相同的任务。正因为如此,为了计算一个神经网络的当前状态,我们推导出如下等式:

)

)

这里的关键特征是,当神经网络执行这些操作时,它会“展开”成多个新状态,每个新状态都依赖于先前的状态。因为这些网络对每个输入都执行相同的任务,除了模型的功能依赖性之外,rnn 通常被称为具有记忆。图 6-1 显示了一个 RNN。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-1。

Architecture of recurrent neural network

这种形式的 RNN 是在 20 世纪 80 年代开发的。与其他神经网络类似,多层神经元通过权重连接,每个权重通过反向传播方法改变。我们基于评估统计来改变我们的权重,在这种情况下,评估统计是给定时间步长激活单元的加权和。总误差是所有时间步长上所有这些单独加权和的总和。在某些时间步骤中,某些输出单元可能会有教师驱动的目标激活。例如,如果输入序列是对应于说出的数字的语音信号,则在序列末尾的最终目标输出可以是对该数字进行分类的标签。对于每个序列,其误差是由网络计算的所有目标信号与相应激活的偏差之和。对于大量序列的训练集,总误差是所有单个序列的误差之和。

通过时间反向传播训练 RNNs(BPPT)

Sepp Hochreiter 和 Jurgen Schmidhuber 等人被认为是开发深度学习训练方法的最伟大的先驱之一。标准方法被称为时间反向传播(BPTT)。BPTT 与常规反向传播大致相同,只是它是为了处理 rnn 的一个特定问题而创建的,即我们正在通过不同的时间步骤进化一个模型。对于每个训练时期,我们首先在相当小的序列上进行训练,然后逐渐增加上述训练序列的长度。直观上,这通常被设想为对长度为 1,2 到 N 的序列进行训练,其中 N 是该序列的最大可能长度。这里有一个方程更简洁地描述了这种现象

)

其中 t =时间步长,h = t 处隐藏节点的索引,j = t = 1 步处的隐藏节点,δ =误差。

具体来说,我们可以这样看待这些现象:我们用等式

)

将 W 定义为输出层的权重矩阵

其中 e o =来自输出层的错误:

)

我们现在有了 k 个序列,通过它们我们将网络展开成一个常规的前馈网络,我们一直观察到现在。然而,RNN 模型中的循环层同时接受前一层和后一层的输入。为了抵消反向传播误差时由于同时输入而发生的权重变化,我们对每一层接收到的更新进行平均。

Elman 神经网络

RNN 建筑公司还收到了杰弗里·埃尔曼的额外捐款,他创建了以他的名字命名的埃尔曼网络模型。Elman 构建的体系结构主要是用于语言处理算法,但它们也可以用于输入数据是顺序的或基于时间序列的任何问题。图 6-2 显示了 Elman 网络的基本结构。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-2。

Illustration of Elman network

Elman 在该体系结构中包括一层上下文单元,其区别在于它们的功能高度关注先前的内部状态。Elman 网络的关键区别之一是隐藏层的输出也馈入前一层中的上下文单元,但是连接上下文单元和隐藏层的权重具有恒定值 1,使得关系是线性的。此后,输入层和上下文层同时激活隐藏层,于是隐藏层在执行更新步骤的同时也输出一个值。在下一个时期期间,先前描述的训练序列以相同的方式发生,除了这里我们观察到具有上下文单元的层现在采用来自前一时期的隐藏层的值。上下文单元的这一特征通俗地被描述为具有记忆的网络。训练这个神经网络需要许多步骤,步骤的数量最终取决于所选字符串的长度。

神经历史压缩器

消失梯度特指网络早期层中的梯度变得极小。这是由于我们使用的激活函数,通常是 tanh 或 sigmoid。因为这些激活函数将输入“挤压”到相对较小的范围内,使得插值结果更容易,这使得导出梯度变得非常困难。在多个堆叠层之后重复这个挤压输入的过程,当我们反向传播到第一层时,我们的梯度已经“消失”了消失梯度的问题通过神经历史压缩器的创建得到了部分解决,这是一种早期的生成模型,作为循环神经网络的无监督“堆栈”来实现。输入级学习从以前的输入历史预测它的下一个输入。在下一个更高级别的 RNN 中,输入仅包括堆栈中 rnn 子集的不可预测输入,这确保了内部状态很少被重新计算。因此,每个高级 RNN 学习下面 RNN 中信息的压缩表示。通过设计,我们可以从最高级别的序列表示中精确地重构输入序列。当我们使用具有相当可预测性的序列数据时,监督学习可以用于通过最高级别的 RNN 对更深层次的序列进行分类。

长短期记忆(LSTM)

LSTM 是一个越来越受欢迎的模型,它的优势在于处理数据噪音中信号之间未知大小的间隙。LSTMs 是由 Sepp Hochreiter 和 Jurgen Schmidhuber 在 20 世纪 90 年代末开发的,它是通用的,当存在足够多的网络单元时,计算机可以计算的任何东西都可以用 LSTMs 复制,假设我们有一个正确校准的权重矩阵。图 6-3 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-3。

Visualization of long short-term memory network

LSTMs 的应用范围部分解释了它们受欢迎的原因,因为它们经常用于机器人控制、时间序列预测、语音识别和其他任务的领域。与我们在其他 RNN 体系结构中常见的单元不同,LSTM 网络包含块。LSTMs 的另一个关键区别因素是能够长时间“记住”一个给定值,并且模型中的门决定输入序列的几个属性。门的考虑因素包括输入重要性、何时应该保留内存或进行“垃圾收集”并删除数据,以及输出值时间。LSTM 模块的典型实现如图 6-3 所示。标准 LSTM 中的 sigmoid 单位包含等式

)

其中 s 是挤压函数(在许多情况下,通常是逻辑函数或任何激活函数,如先前模型中所述)。查看图 6-3 ,最左边的 sigmoid 单元将输入馈送到 LSTM 块的“存储器”从这一点开始,图中的其他单元就像门一样,允许或拒绝对 LSTM 存储器的访问。标题为 I 的单元,我们将其表示为图中的输入门,将阻止所有非常小(接近于零)的值进入存储器。图底部的单元“遗忘”门“遗忘”它所记忆的任何值,并将其从内存中丢弃。该图右上角的单元是“输出门”,它决定是否应该输出存储在 LSTM 存储器中的值。有时,我们观察到用以下符号表示的单位:P 或σ。具有求和符号的单元被反馈到 LSTM 块中,以便于在没有值衰减的许多时间步长上记忆相同的值。典型地,该值也被输入到三个门单元,以改善它们各自的决策过程。在 LSTMs 中使用的矩阵的 Haramard 乘积或 entrywise 乘积由以下索引符号给出:

)

传统 LSTM

上面,我们有一个 LSTM 的层,我们的数据通过它传递

)

)

)

)

)

其中 x =输入向量,h t =输出向量,c t =细胞状态,(W,U,b) =参数矩阵和向量,(f t ,i t ,ot=记忆信息,获取信息,输出,分别为,sg = sigmoid 函数,sc =原始双曲正切,sh =原始双曲正切。

培训 LSTMs

BPPT 用于 LSTM,但是由于 LSTM 的特殊特征,我们也可以像传统一样通过 BP 使用梯度下降。LSTMs 中的消失梯度由误差转盘专门处理。LSTMs“记住”它们的反向传播误差,然后反馈给每个权重。因此,规则的反向传播对于训练 LSTM 块在很长的持续时间内记住值是有效的。

RNNs 内的结构阻尼

如果我们使用共轭梯度法,它偏离原始 x 太远,曲率估计变得不准确,我们可能会观察到无法收敛到全局最优。根据 Martens 和 Sutskever 的建议,当使用共轭梯度法时,建议使用结构阻尼。用这种方法,我们惩罚与 x 的大偏差,其中公式由

)

给出

其中| |δx | |2是偏差的大小。λ,类似于岭回归,用作调整参数。

调谐参数是自适应的,通过类似于第三章所述的 Levenburg-Marq 算法的过程进行选择。建议我们找到一个减速比,由下面的等式给出:

)

调整参数更新算法

权重在每个时间步长更新,因此增加矩阵中的值会导致输出的剧烈变化:

)

)

)

)

)

)

RNN 的实际例子:模式检测

让我们以尝试预测基于时序的序列数据为例。在这种情况下,我们将尝试预测一年中不同时间的牛奶产量(图 6-4 和 6-5 )。让我们从检查我们的数据开始,以获得对它的理解:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-5。

Visualization of milk data via histogram

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-4。

Visualization of sequence

#Clear the workspace
rm(list = ls())
#Load the necessary packages
require(rnn)

#Function to be used later
#Creating Training and Test Data Set
dataset <- function(data){
  x <- y <- c()
  for (i in 1:(nrow(data)-2)){
    x <- append(x, data[i, 2])
    y <- append(y, data[i+1, 2])
  }
  #Creating New DataFrame
  output <- cbind(x,y)
  return(output[1:nrow(output)-1,])
}

当处理时间序列数据时,我们将不得不执行大量的数据转换。特别是,我们必须创建与给定数据略有不同的 X 和 Y 变量。从dataset()函数中,我们创建了一个新的 X 变量,它是从原来的 Y 变量开始的时间步长 t。我们创建一个新的 Y 变量,它是从原来的 Y 变量 t + 1 而来的。然后,我们将数据截断一行,这样就可以删除缺失的观察值。接下来,让我们加载并可视化数据(如图 6-4 和 6-5 ):

#Monthly Milk Production: Pounds Per Cow
data <- read.table("/Users/tawehbeysolow/Downloads/monthly-milk-production-pounds-p.csv", header = TRUE, sep = ",")
#Plotting Sequence
plot(data[,2], main = "Monthly Milk Production in Pounds", xlab = "Month", ylab = "Pounds",
     lwd = 1.5, col = "cadetblue", type = "l")
#Ploting Histogram
hist(data[,2], main = "Histogram of Monthly Milk Production in Pounds", xlab = "Pounds", col = "red")

如您所见,尽管值的范围看起来很广,但我们的数据在值的频率方面有严重的右偏。

现在您已经直观地理解了我们的数据,让我们继续准备要输入到 RNN 中的数据:

#Creating Test and Training Sets
newData <- dataset(data = data)

#Creating Test and Train Data
rows <- sample(1:120, 120)
trainingData <- scale(newData[rows, ])
testData <- scale(newData[-rows, ])

我建议所有用户在将数据输入到 RNN 之前使用最大-最小缩放,因为这有助于减少给定神经网络的误差。与标准归一化类似,最大-最小缩放会显著缩小输入数据集的范围,但它是通过对 0 到 1 之间的观察值进行分类来实现的,而不是通过返回数据偏离平均值的标准偏差来实现的。完成这一步后,我们可以输入数据。用户可以随意试验这些参数,但是我已经训练了网络以获得良好的性能。

现在让我们评估我们的培训和测试结果(如图 6-6 和 6-7 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-7。

Test set performance

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-6。

Training data performance

#Max-Min Scaling
x <- trainingData[,1]
y <- trainingData[,2]

train_x <- (x - min(x))/(max(x)-min(x))
train_y <- (y - min(y))/(max(y)-min(y))

#RNN Model
RNN <- trainr(Y = as.matrix(train_x),X = as.matrix(train_y),
learningrate = 0.04, momentum = 0.1,
network_type = "rnn", numepochs = 700, hidden_dim = 3)

y_h <- predictr(RNN, as.matrix(train_x))
#Comparing Plots of Predicted Curve vs Actual Curve: Training Data
plot(train_y, col = "blue", type = "l", main = "Actual vs Predicted Curve", lwd = 2)
lines(y_h, type = "l", col = "red", lwd = 1)
cat("Train MSE: ", mse(y_h, train_y))

#Test Data
testData <- scale(newData[-rows, ])
x <- testData[,1]
y <- testData[,2]
test_x <- (x - min(x))/(max(x)-min(x))
test_y <- (y - min(y))/(max(y)-min(y))
y_h2 <- predictr(RNN, as.matrix(x))

#Comparing Plots of Predicted Curve vs Actual Curve: Test Data
plot(test_y, col = "blue", type = "l", main = "Actual vs Predicted Curve", lwd = 2)
lines(y_h3, type = "l", col = "red", lwd = 1)
cat("Test MSE: ", mse(y_h2, test_y))

训练集和测试集的 MSEs 分别为 0.01268307 和 0.06666131。虽然训练集的 MSE 较低,但这可能只是因为训练集明显大于测试集。通过直观地比较各个图中的曲线,我们可以看到测试性能不如训练集准确。如您所见,训练集和测试集中的实际曲线显示出比 RNN 完全捕获的更高的方差。如果你在读电子书,实际曲线是蓝色的,预测曲线是红色的。

摘要

本章有效地涵盖了最常提到的 RNN 例子。它还引导读者解决时间序列数据问题。第七章讲述了深度学习的一些最新发展,并探索了我们如何利用这些见解来解决更困难的问题。

七、自编码器、受限玻尔兹曼机和深度信念网络

本章涵盖了一些更新、更先进的深度学习模型,这些模型在该领域越来越受欢迎。它旨在帮助您了解数据科学领域的一些最新发展。要了解这些模型在实际环境中是如何应用的,请参见第 10 和 11 章,我们将在实际例子中使用这些模型。

自编码器

在讨论受限玻尔兹曼机(RBM)之前,我想解决一组相关的算法。自编码器被称为特征提取器,因为它们能够学习数据的编码/表示。输入到 RBM 的数据将与我们输入到任何机器学习算法的数据相同,但为了简单起见,我们可以将其想象为一个 M x N 矩阵,其中每一列是一个唯一的特征,每一行是 N 个特征的唯一观察。它是一种无监督的学习方法,使用反向传播找到一种方法来重建自己的输入。由 Geoffrey Hinton 和其他研究人员开发的自编码器解决了如何执行反向传播的问题,而无需明确告诉自编码器要学习什么。

自编码器由两部分组成:编码器和解码器。让我们看一个简单的例子,我们称之为 n/p/n 自编码器架构。该架构由)表示,其中以下内容为真:

  1. )正集。
  2. n 和 p 是正整数,其中)
  3. )是一个函数,其中)
  4. )是一个函数,其中)
  5. )当目标出现时,)
  6. δ是 L p 范数或一些其他损失/相异函数。

对于任意的)和),自编码器将输入 x 转换为输出向量:

)

概括地说,我们试图通过使用自编码器来解决的问题最终是一个优化问题——在这种情况下,它是最小化损失/相异度函数。我们把这个问题定义为:

)

当目标出现时:

)

线性自编码器与主成分分析(PCA)

对于这个例子,让我们看看主成分分析(PCA)和线性自编码器之间的相似之处。主成分分析的主要目的是找到原始数据集的线性变换,这些变换包含了。当将这种分析转换到原始数据集时,我们使用它来实现降维。第八章更详细地讨论了 PCA,但是我将在这里解释它与线性自编码器的关系。简单地说,PCA 是一种正交线性变换,其中我们寻求最大化每个主成分内的方差,以满足每个主成分彼此不相关的约束。让我们把 y 定义为:

)

其中)和是数据集,)和是正交协方差矩阵。与 PCA 的情况一样,每个主成分应该按照方差递减的顺序列出。我们定义最大方差的方向如下:

)

根据定义,这是一个约束优化问题,可通过使用拉格朗日乘数来解决。因此,我们可以把这个问题改造成

)

)

)

其中)

单层自编码器将产生与 PCA 几乎完全相同的特征向量。也就是说,PCA 在推导过程中假设了一个线性系统,而自编码器则不然。在我们在自编码器中强制线性的情况下,将得到类似的答案。

要查看自编码器的应用,请参见第十一章,其中我们专门使用这些模型进行异常检测,并提高标准机器学习模型的模型性能。

受限玻尔兹曼机

在 20 世纪 80 年代,Geoffrey Hinton、David Ackley 和 Terrence Sejnowski 开发了这种算法,它可以被描述为一种随机神经网络。当时,它代表了深度学习科学的突破,因为它是第一批能够学习数据的内部表示并有能力解决困难的组合学问题的模型之一。标准的受限玻尔兹曼机具有二进制值的隐藏和可见单元,由权重矩阵 W 和偏置权重组成,权重矩阵 W 与给定的一组隐藏单元和可见单元之间的连接相关联。隐藏、可见和偏置单元可以被认为是类似于出现在多层感知器模型中的那些相同的单元。给定这些,一个组态的能量表述如下:

)

这个能量函数类似于 Hopfield 网络的输出神经元(见图 7-1 ),是一种特殊类型的 RNN。由约翰·霍普菲尔德在 20 世纪 80 年代创建,与其他 RNN 模型一样,输入通常是我们怀疑具有某种潜在模式的数据(例如时间序列)。计算所有输入的加权和,然后将其输入线性分类器,如逻辑函数。我们将输出定义为:

)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 7-1。

Visualization of a Hopfield network

将数据输入模型后,网络中的所有节点都会收到特定的值。然后,使用异步或同步更新对网络进行多次迭代。达到停止标准后,将显示神经元内的值。Hopfield 网络的主要动机是发现存储在权重矩阵中的模式。

当回过头来参考 RBM 模型时,构成数据基础的概率分布被定义为

)

)

其中) =指数函数,上标为前述能量函数的负值。

RBMs 和二部图具有相似的性质。这样,给定来自可见单元的激活,来自隐藏单元的激活是相互独立的,使得

)

而个体激活概率分别是

)

)

其中 a =激活单位。

RBM 的可见单元的值可以从多项式分布中导出,而隐藏单元的值可以从伯努利分布中导出。在我们为可见单元使用 softmax 函数的实例中,我们有以下函数:

)

传统上,通过反向传播使用梯度下降来优化 RBM 内部的权重,直到我们收敛到最优解。RBM 最流行的用例之一是填充数据集中缺失的值,特别是在协同过滤的情况下。第十一章看一个执行协同过滤的简单例子。如果你有兴趣阅读关于使用 RBMs 执行这一操作的文章,可以搜索 Salakhutdinov 等人关于使用 RBMs 进行协同过滤的论文( http://www.machinelearning.org/proceedings/icml2007/papers/407.pdf )。

关于 RBMs 的实现,有几个包您可以随意探索,比如 deepnet、darch 和其他在线实现。如果您觉得足够先进,您也可以寻求创建自己的实现。与此同时,你应该检查深度学习框架的更新,看看他们是否/何时添加 RBM 实现。

对比发散学习

由 Hinton 开发的对比发散(CD)学习是训练受限玻尔兹曼机的标准方法。它基于使用 Gibbs 采样的思想,运行 k 个步骤,用训练集的训练样本初始化,并在 k 个步骤后产生样本。作为无向图模型的训练方法,它有更广泛的应用,但它最流行的用例是 RBM 的训练。我将从定义对数似然的梯度开始讨论:

)

)

)

)

直观上,我们将对数似然定义为某个参数具有某个值的概率。上面,我们将 sig()函数定义为 signum 函数,它返回输入的符号。

我们用下面的等式定义训练模式 v 的对数似然的梯度:

)

)

)

训练集)上的梯度的平均值被给定为

)

)

)

其中

)

)

)

现在,回到我们最初的讨论,我们近似训练模式 v 的对数似然的梯度如下:

)

每一个参数的导数都是根据 p(v)上期望值的近似值计算出来的。在批量学习中,我们计算整个训练集的梯度。然而,在某些情况下,对训练数据集的子集运行这种近似在计算上更有效,我们称之为小批量。如果我们在执行这种近似时评估训练集的单个元素,这就是所谓的在线学习。在 RBMs 中,我们将重建误差称为实际输入和预测输入之间的差异,该差异从训练开始向前移动时急剧下降。建议使用这一指标,但要谨慎行事。CD 学习近似地优化了训练数据和由 RBM 和吉布斯链的混合率产生的数据之间的 KL 散度。也就是说,如果混合率也很小,重建误差通常可能看起来很小。随着 RBM 内权重的增加,我们通常会观察到混合速率反向移动。但是,较低的混合率并不总是意味着一个模型优于混合率较高的模型。

与其他深度学习模型类似,RBM 权重通常使用从正态分布随机采样的值或其他无穷小的值来初始化。关于学习率,必须考虑梯度方法的相同因素,特别是注意不要选择太大或太小的学习率。也就是说,自适应学习速率可能会导致问题,因为它会给出由于较低的重建误差而导致模型正在改善的外观,然而,如前所述,情况可能并不总是如此。建议每次权重更新一般约为当前权重的)倍。初始隐藏偏差和权重通常通过从正态分布中随机选择它们来初始化,这是其他神经网络模型的标准操作程序。

成果管理制内的势头

为了提高 RBM 的学习速度,动量法是一种推荐的方法。想象一个梯度图,如图 7-2 所示。如果我们可以想象一个圆上的一个点所代表的误差,那么当这个点越来越接近最小值时,它会获得“动量”,但如果它试图越过这个点,沿着对面的球体向上移动,它就会失去动量。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 7-2。

Gradient plot

与传统的梯度下降公式不同,动量法逐渐影响参数更新的速度。我们把动量定义为一个给定时期后仍然存在的速度的百分比;我们假设一个参数的速度随时间衰减。实际上,动量方法使得参数的更新朝着不是最陡下降的方向移动,除了不太复杂之外,与典型的梯度方法一样。使用动量法时,建议将动量参数α设置为. 5。当进一步减小重建误差变得更加困难时,动量应该增加到 0.9。如果我们注意到重建误差的不稳定性——通常表现为偶尔的增量增加——我们会将学习率降低 2 倍,直到这种现象消失。我们将更新参数的动量法定义如下:

)

重量衰减

权重衰减可视为一种正则化形式,类似于岭回归和/或 LASSO 中的参数正则化。在 RMBs 中,我们通常使用欧几里德范数,我们将其表示为权重的成本。通常,从业者取罚项的导数,并乘以学习率。这防止了学习率改变我们试图优化的目标函数。权重衰减有助于减少过度拟合,以这种方式实现的解决方案不会有异常大的权重或权重总是打开或关闭的单元。它还提高了混合率,参考我们执行的吉布斯采样,使 CD 学习更准确。Geoffrey Hinton 建议最初使用 0.0001 的权重成本。

稀少

一般来说,一个好的模型是具有隐藏单元的模型,这些隐藏单元只在部分时间是活动的。原因是,与活动单元密集的模型相比,活动单元稀疏的模型更容易解释。我们可以通过使用正则化指定一个单元活动的概率来实现稀疏性。这个概率用 q 表示,用

)

估计

其中 q current =隐藏单元的平均激活概率

要使用的自然惩罚度量是期望分布和实际分布之间的交叉熵:

)

正如 Hinton 所建议的,我们寻求低至 0.1 9 和高至 0.01 的稀疏目标。我们将衰减率表示为λ,它指的是估计的稀疏度值。这应该不高于 0.99,但高于 0.9。如果我们计算的概率是围绕目标值聚集的,我们应该减少稀疏性成本,对这种建模的一般建议是在收集随机样本时收集平均活动的直方图。

隐藏单元的数量和类型

通常,主要的考虑是我们寻求避免过度拟合。因此,我们通常会尝试使用更少的隐藏单元,而不是更多。特别是,如果观察到的数据非常相似,我们也应该尝试使用更少而不是更多的隐藏单元。然而,如果我们试图实现的稀疏目标恰好落在一个非常小的范围内(或者本身非常小),那么使用比正常情况更多的隐藏单元是合理的。至于单位的类型,我们可以使用高斯可见(和/或隐藏)的,除了 sigmoid 和 softmax 之外的单位分别用下面表示:

)

)

)

)

深度信念网络

我要介绍的最后一个模型是深度信念网络(DBN),如图 7-3 所示,这是 Geoffrey Hinton 的另一项创新。为了制造 DBN,我们将受限的玻尔兹曼机堆叠在一起,一次训练一层。通常,我们将 DBNs 用于无监督学习问题。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 7-3。

Visualization of a deep belief network

在 2006 年的一篇论文中,多伦多大学的研究员 Geoffrey Hinton 和 Simon Osindero 描述了一种用于快速学习的算法。具有许多隐藏层的训练网络所带来的困难启发了混合模型的创建。与训练问题相关,该模型的主要吸引力在于,通过设计,存在互补的先验,这允许我们容易地从条件概率分布中提取。这是通过从网络深层的随机配置开始实现的。然后,我们遍历网络的每一层,其中给定层的状态由伯努利试验确定。伯努利函数的参数是从初始“自上而下”传递中的前一层接收的输入中导出的。

快速学习算法(Hinton 和 Osindero 2006)

通过在给定的层中取一个随机状态并对其执行 Gibbs 抽样,从 RBM 中生成数据。简而言之,Gibbs 抽样是一种蒙特卡罗方法,在这种方法中,我们试图根据用户指定的概率分布获得一个序列,但该算法试图近似这个序列。通常,分布是多元的。所选层内的所有单元以并行方式更新,并且重复这一过程,直到我们决定从平衡分布中进行采样。在图 7-4 中,我们可以看到 RBM 的可见层和隐藏层。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 7-4。

Visualization of restricted Boltzmann machine

每个权重使用一个可见单元 I 和一个隐藏单元 j。当数据向量被“钳制”在可见单元上时,隐藏单元从它们的条件分布中被采样,这是阶乘。对数概率的梯度由下面给出:

)

当我们最小化 KL 散度时,我们实际上最大化了对数概率。如果你想学习复杂的模型,把单一的模型分解成更小、更简单的模型。过了这一点,就可以依次学习这些模型了。如第三章所述,这种顺序学习的一个例子是梯度推进。基于更高层导出 W 0 的互补先验的假设,学习 W 0 的合理近似。在实践中,我们可以通过假设所有权重矩阵必须彼此相等来实现这个结果。当解决这个约束优化问题时,学习变得比以前容易得多,并且问题本身被简化为学习一个 RBM,于是通过最小化对比差异来获得好的近似解。

算法步骤

  1. 假设所有权重矩阵都是并列的,学习 W 0
  2. 使用 W 0 T 来推断第一个隐藏层中变量状态的阶乘近似后验分布。
  3. 学习一个关于 W 0 T 生成的数据的高层抽象的 RBM 模型。
  4. 重复直到收敛到最优解。

如果模型中较高层次的权重矩阵发生变化,我们肯定会看到模型的改进。如果)是数据的真实后验值,则给定的界限变成等式。Hinton 特别提出了一种贪婪的学习方法,如 Neal 和 Hinton (1998)所述。给定构型 v 0 ,h 0 的能量定义为

)

)

同一个界

其中 h 0 =初始隐藏层单元的二进制配置,p(h 0 ) =当前模型的先验 h 0 ,以及) =初始隐藏层的二进制配置的概率分布。

摘要

这就结束了对自编码器、RBM 和 dbn 的讨论。这也结束了所有关于深度学习模型的章节。既然我们已经讨论了这些模型,现在是时候将我们的注意力转向实验设计和特征选择技术,以帮助您提高机器学习模型的准确性。

八、实验设计和启发式

在回顾了所有与你将遇到的问题解决相关的机器学习和深度学习模型之后,终于到了谈论构建你的研究的有用方法的时候了,包括正式和非正式的方法。

除了知道如何正确评估开发的解决方案,你还应该熟悉与实验设计领域相关的概念。罗纳德·费雪是 20 世纪杰出的英国统计学家,也是统计学领域最有影响力的人物之一。他的技术在进行实验时经常被引用,即使你没有明确地使用它们,回顾一下也是有用的。

方差分析(ANOVA)

方差分析是一组用于研究数据中各组观察值之间差异的方法。z 和 t 检验的扩展,类似于回归,我们观察响应和解释变量之间的相互作用。我们假设数据中的观察值是独立且同分布的(IID)正态随机变量,残差是正态分布的,方差是齐次的。在多个方差分析模型中,下面是本节其余部分讨论的模型。

单向方差分析

用于比较三个或更多样本空间的平均值。具体来说,它用于由具有两个或更多级别的一个变量/因子执行分类的情况。

双向(多向)方差分析

这类似于单因素方差分析,除了这种模型可用于有两个或更多解释变量的情况。

混合设计方差分析

与之前描述的模型相比,混合设计方差分析的区别在于其中一个因素变量是跨受试者分析的,而另一个因素是受试者内的变量。

多元方差分析

这种方法类似于单因素方差分析和双因素方差分析,只是它特别用于分析多变量样本均值,或者在给定数据集中有两个或更多解释变量时。

讨论了各种方差分析模型后,下一节将讨论评估结果的方法:F 统计量。

f 统计量和 f 分布

以罗纳德·费雪命名的 F 统计量是两个统计方差的比值。f 统计基于 f 分布,一种连续的概率分布(见图 8-1 )。我们把这种分布称为 f 检验的给定检验统计量的零分布。让我们假设我们有变量 A 和 B,使得它们都具有分别具有 n 和 d 自由度的卡方分布,使得

)

)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-1。

PDF for F-distribution

比方说,我们正在考虑单向方差分析,并假设一组人群的平均值相等且呈正态分布。我们将 F 统计量定义为

)

其中 k 是自由度,n 是 n 个响应变量的数量。零假设表示仅使用 x 截距创建的模型和由用户创建的模型产生不可区分的结果(在给定的置信区间内)。另一个假设是,读者创建的模型明显优于仅具有 x 截距的模型。就像测试任何其他统计显著性的度量一样,这是基于我们想要设置的阈值来确定的。(90%的置信度,95%的置信度,以此类推)。

现在,让我们用一个玩具例子来应用和解释我们刚刚提到的概念。对于本例,我们将使用虹膜数据集:

#Loading Data
data("iris")

#Simple ANOVA
#Toy Example Using Iris Data as Y
y <- iris[, 1]
x <- seq(1, length(y), 1)
plot(y)

该数据集将用于在以下实验中创建响应和/或解释变量。在第一个玩具示例中,我们采用 iris 数据集的第一列(代表每次观察的萼片长度)并将其作为解释变量。然而,在我们进行单因素方差分析之前,让我们验证将数据拟合到线性模型所需的假设。我们将从目视检查我们的数据开始,如图 8-2 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-2。

Visualization of data

我们立即注意到,数据在方向上是相当线性的,具有正斜率。这是一个很好的第一个指标,但我们应该更深入地挖掘,以确保我们的其他假设得到满足。在这种情况下,我们将专注于绘制拟合模型的残差。所谓残差,我指的是实际值减去模型预测值的余数。在处理线性模型时,您应该大量使用残差分析,但一般情况下也是如此,因为残差分析提供了对特定模型工作情况以及数据方向的直观了解。在图 8-3 中,我们看到通过拟合 x 和 y 的线性模型创建的以下曲线图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-3。

Residual plot

plot(glm(y∼x))

注意图 8-3 中显示的四个中右上角的图形。这是一个分位数图,它有效地显示了残差分布的正常程度。当仔细检查该图时,我们可以看到相当数量的数据位于虚线 45 度角线上,这是数据中正常的标记。然而——通常情况下——我们注意到尾部倾向于稍微高于这条线。值得注意的是,我们认为是正态分布的数据几乎总是表现出相似的模式。在现实世界中,当我们拥有足够多的数据时,大多数数据往往接近正态分布,但它不太可能完全呈正态分布。因此,我们在这里接受数据是正态分布的,并继续验证其余的假设。当数据呈正态分布时,它可以符合线性模型,因此我们可以合理地估计 x 变量范围内的值。

因为我们还要求误差呈现恒定方差,所以让我们将注意力转向左上角的图。请注意,此图中的 x 轴表示回归输出的值,而 y 轴详细说明了残差的值。穿过图中心的水平线表示拟合值等于实际值的区域,或者观测值的残差为零的区域。具体参考我们的数据时,我们可以看到,一般来说,从图的左侧到右侧,所绘制的残差的形状似乎是一致的。因此,我们可以说残差实际上表现出恒定的方差。如果不是,我们会注意到散点图的形状中会有明显的图案,从图的左侧到右侧会变得更夸张或不那么夸张。

最重要的是,注意图右下方的情节。它提出了一个重要的概念,有助于理解某些数据点如何改变回归模型的拟合线。杠杆被描述为特定观察值与数据集其余部分之间的相对差异。通过将索引放置在数据点附近,在 R 中表示特别具有高杠杆作用的观察值。我们用

)

来定义杠杆

其中 n =观察次数,XI= X 的第 I 次观察,)= X 内所有观察的平均值,i = 1,2,…,n

与杠杆高度相关的是库克距离(Cook’s distance)的概念,它直接估计一个特定的观察对这个回归模型的影响。我们把库克的距离定义为

)

其中 e i 2 =给定观测值的残差平方,s 2 =模型的均方误差,p =模型中参数的个数,HI= H 矩阵的第 I 条对角线其中) i = 1,2,…,n,n =观测值个数。

通常,如果观察值的 Cook 距离值大于 1 或距离值大于 4/n,我们认为该观察值特别有影响。使用哪个阈值最终取决于您,但很明显,这将取决于具体情况,值得在实验的基础上检查是否提供了更多或更少异常值的数据集,以及这将如何影响您的最终目标。例如,如果实验的目的是异常检测,那么降低阈值使得数据集中更多的噪声被限定为信号可能是愚蠢的。当回头参考我们的特定图时,我们可以看到相当数量的数据点被标记为有影响。在我们选择型号时,我们将牢记这一点。

当评估数据集中的所有图时,我们可以自信地说,尽管存在异常值,并且我们的假设不完全符合,但 OLS 回归的稳健性允许克服这些轻微的偏差。因此,选择 OLS 回归作为这项任务的模型是合理的,因此方差分析将产生具有统计意义的结果。执行代码时,我们观察到以下情况:

simpleAOV <- aov(y ∼ x)
summary(simpleAOV)

             Df Sum Sq Mean Sq F value Pr(>F)
x             1  52.48   52.48   156.3 <2e-16 ***
Residuals   148  49.69    0.34

正如当我们在 glm 对象上使用summary()函数时,我们得到了它的统计显著性的度量。然而,我们得到的不是 Z 分数,而是本例之前提出的概念的 F 分数及其相对 p 分数。在这种情况下,我们可以以大于 99%的显著性说,我们拒绝了零假设的结果。因此,该模型比仅截距模型更适合,因此我们对其结果更有信心。然而,假设我们想要比较多个合适的模型。因此,让我们来看看当我们包含不止一个变量时会发生什么,但是也要研究这两个变量之间的相互作用。

正如我们在下面的代码中看到的,我们在这个模型中使用第二列和第三列作为解释变量。在拟合我们的模型时,我们将两个解释变量相乘。执行代码时,我们会观察到以下结果:

#Mixed Design Anova
x1 <- iris[,2]
x2 <- iris[,3]
mixedAOV <- aov(y ∼ x1*x2)
summary(mixedAOV)

             Df Sum Sq Mean Sq F value   Pr(>F)
x1            1   1.41    1.41    12.9 0.000447 ***
x2            1  84.43   84.43   771.4  < 2e-16 ***
x1:x2         1   0.35    0.35     3.2 0.075712 .
Residuals   146  15.98    0.11

我们的残差明显更小,并且所有变量在至少 90%的置信区间内具有统计显著性。让我们执行下面的代码,直观地比较图 8-4 中的两个模型:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-4。

Mixed design ANOVA plot

par(mfrow = c(2,2))
plot(glm(y ∼ x1*x2))
dev.off()

我们可以看到,我们需要满足的所有假设都做得非常好。实际上,所有的残差都是正态分布的,如正态 Q-Q 图所示,残差呈现恒定的方差,相当小的一部分具有杠杆作用。因此,当在我们定义的两个模型之间进行选择时,与第一个相比,我们选择第二个是合理的。

这是一个简单的例子,说明我们如何在模型选择过程中使用方差分析。在第 10 和 11 章中,你将学会有效地执行这些关于比较深度学习和机器学习算法的相同分析。

现在让我们在费希尔原理的指导下,更详细地讨论如何组织我们的实验。

费希尔原则

有史以来最杰出的统计学家之一罗纳德·费雪解释了实验设计的原则。以下是对他的原则的描述,以及关于如何实现这些原则的一般建议:

  1. 实验陈述:你应该明确陈述激发实验的场景,非常明确地给出实验中将要发生的步骤的概要。一般认为,导言应该包括对主题的高层次概述,每一部分都应该更详细地描述不同的组成部分,从实验开始到结束有逻辑地进行。
  2. 解释及其推理基础:从一开始,给出你可能期望的合理结果是合理的。你应该陈述你认为必须考虑的结果,但要意识到为你的下属提供一个无止境的结果列表可能不会很有帮助。此外,当讨论所有可能的结果时,要为阅读你的研究的人提供可操作的见解。不言而喻,确实能给出可操作见解的研究,会给误用留下更多空间。
  3. 显著性测试:在评估机器学习和深度学习解决方案的背景下,一个简单的建议是引导用于评估给定模型的测试统计。有理由假设,如果你在足够长的时间内得到足够多的测试统计数据,那么数据将呈正态分布。从这一点出发,可以进行 Z 检验,以确定模型的合理统计置信度。
  4. 零假设:这种假设应该声明显示的结果没有显著性,并且测试人群之间的任何偏差都是由于一些无关的误差,例如不适当的采样或与适当的实验实践的偏差。这必须是所有统计测试的一个组成部分。
  5. 随机化:测试有效性的物理基础:当进行测试时,所得到的结果应该以一种结果没有偏差的方式进行。在某些情况下,这可能需要对数据进行随机观察,以消除实验建模中可能导致某些结果的任何固有偏差。
  6. 统计复制:从测试中得出的结果应该而且必须是可复制的。鉴于数据集和我们预期观察这种情况的环境的固有限制,得出的不合理的结果不如可复制的结果有价值。
  7. 分组:将不同的实验组划分开来,从而减少或完全防止不同的变异和偏差影响实验结果的过程。

普拉克特-伯尔曼设计公司

Plackett-Burman 设计是由 Robin Plackett 和 J. P. Burman 在 20 世纪 40 年代创建的,是一种寻找解释变量的可量化相关性的方法,在这种情况下我们称之为因子,其中每个因子有 L 个水平。总体目标是使用有限数量的实验来最小化相关性估计的方差。为了实现这个目标,选择一个实验设计,使得任何给定因素对的每个组合在每个实验“运行”中出现相同的次数

Plackett-Burman 设计需要少量实验,特别是 4 到 36 的倍数,并且该设计有 N 个样本,可以研究多达 k 个参数,其中 k = N–1。在 L = 2 的情况下,使用每个元素为–1 或 1 的正交矩阵。这个矩阵也称为哈达玛矩阵。这种方法对于识别不同因素对响应变量的主要影响是有用的,这样我们就可以消除那些似乎影响很小或没有影响的因素。Plackett 和 Burman 自己给出了 L 等于 3、4、5 和 7 的具体设计。

请看图 8-5 中的矩阵,它直观地描述了 Plackett-Burman 设计。当执行实验设计(DOE)时,您必须将适当的行作为设计表的第一行。在这种情况下,我们从+、-、+、-、+、+开始。这是每行中出现的序列的排列,代表一种治疗组合。您可以将治疗组合视为一个功能集的独特组合。然后,通过将前一行中的序列向右移动一列来创建第二行。对剩余的每一行重复这一过程。最后一行显示所有负元素。不过,重要的是要认识到,Plackett-Burman 设计不能描述对一个给定因素的影响是否会导致另一个因素的影响,同样,它也不能知道给定足够小的设计的影响本身。这种设计被认为是数据分析的准备步骤,除了随后采取的其他步骤之外,还建议将替代的准备步骤与其并列。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-5。

Plackett-Burman matrix

填空白

这些方法不需要离散的参数,样本大小的选择与参数总数无关。对于读者想要创建响应面的情况,推荐使用这些方法,但是应该注意,很难分别确定给定或一组参数的主要影响和相互作用。

全因子

全析因是实验设计中最流行的方法之一,其中 N = 2^K,k 等于因子的个数。举个例子,让我们有 k 个因子,其中 L = 2。在这个模型中,在实验发生之前,我们不区分干扰因素和主要因素。假设 L = 2,我们将它们表示为高电平“h”或低电平“L”。高级因子的值为 1,低级因子的值为–1。我们将变量的交互作用确定为单个因素的乘积。从任何给定因子约束的可能实验来看,一次改变一个因子的样本仍然是样本空间的一部分。这考虑了每个因素对响应变量的影响。现在,让我们将 M 定义为变量 x 的主要交集。这是高水平样本的平均响应变量与低水平样本的平均响应之间的差异。如果我们有三个因子,每个因子有两个级别,那么 X_1 的 M 将被定义如下:

)

如果我们想了解两个或更多因素之间的相互作用,等式将是相同的,除了变量的相互作用将由变量的乘积来表示,而不是一个因素在给定状态下拥有的单个值。主效应和交叉效应统计提供了一种有效的方法来确定单个因素或因素组合对响应变量的影响程度。全因子设计不会以任何这样的方式使数据复杂化,并且提供了一种检查可变效应的透明方法。如果有两个以上的级别,则必须进行调整,以获得所有级别对给定响应变量的平均影响,其中分母为 N,因此

)

Halton、Faure 和 Sobol 序列

在空间填充技术的保护伞下,其中许多是由伪随机数发生器激发的。伪随机数是通过随机性测试的序列生成集。我们将伪随机数发生器表示为以下函数:

![KaTeX parse error: Invalid delimiter '0' after '\left' at position 13: \phi :\left0̲,1\right)\to \l…我们必须选择一个能给出γ k 均匀分布的ϕ值。实现这一点的一种流行方法是范德科尔普序列,其中我们有一个基数 b,≥ 2 和连续的整数 n 以它们的 b-adic 展开形式表示,使得下面是真实的)

![KaTeX parse error: Invalid delimiter '0' after '\left' at position 37: …{N}}_0\to \left0̲,1\right), )

其中 a 表示展开系数。

Halton 序列分别在第一、第二和第三维中使用基数为 2、基数为 3 和基数为 5 的 Van der Corput 序列。这种模式继续下去,在每一个连续的维度中,质数被用作基数。也就是说,多维聚类导致维度之间的高度相关性,实际上违背了实验设计本身的目的。为了解决这个问题,Faure 和 Sobol 序列对所有维度只使用一个基,对每个维度使用不同的向量元素排列。

A/B 测试

在设计应用程序、网站和/或仪表板应用程序时,确定某些功能的变化对产品的影响是很有用的。例如,我们可以想象,一个工程师正试图用某种统计确定性来确定一个新特性的实现是否对获得新用户有影响。在这种情况下,建议人们使用 A/B 测试。广义的 A/B 检验是指用来比较两个数据集的统计假设检验方法,一个是对照组,一个是测试组,分别是 A 和 B。我们也可以修改测试,这样我们可以测试一个和多个额外的控制测试。

A/B 测试的动机很简单,因为不同产品的开发,不管它们是否具有机器学习或深度学习能力,都允许我们用统计上的信心来确定我们是否从最初的迭代到下一次迭代做出了改进。也就是说,我们可以使用这些过程作为一系列实验,从一代软件迭代到下一代软件,以观察效率的提高。通常,beta-二项式层次模型是最流行的方法之一,通过它我们可以 A/B 测试一个控制组而不是多个测试组。因此,我们将回顾这一模式。然而,首先让我们回顾一个简单的双样本 A/B 测试。

简单双样本 A/B 检验

假设我们在这里将一个控制组与一个测试组进行比较,并且我们试图了解我们的新网站是否会因为功能变化而产生更多的点击。我们将坚定地表明,虽然这个测试对两个例子是稳定的,但您应该避免对两个以上的样本使用这个测试。假设我们有两个数据集代表不同网站的不同属性,我们希望在 95%的置信度下进行测试。为此,我们将使用 t 检验。现在,我们还假设在执行 t 检验后,我们观察到均值的差异显著不同,并且 x2 与之前的模型相比显著提高。现在让我们假设我们一直在制作不同版本的网页,并不断尝试使用这种模式。经过九次不同的测试,x2 仍然被证明是最优秀的车型。但是当我们运行 x2 时,我们实际上看不到 x2 对其他网站的点击有任何改善。双样本 A/B 测试的常见问题是由于假阳性。

接下来,我将通过二项式分布展示 10 个个体假设检验显示正确结果的概率。设事件 A = x2 假设在 90%置信区间优于其他 9 个同行,B = x2 在 95%置信区间优于其他 9 个同行,C = x2 在 99%置信区间优于其他 9 个同行:

)

简单地说,在事件 A、B 和 C 下,我们可以预期我们的实验产生 6.5、4.013 和 0.95 的假阳性。虽然 99%的置信区间在这个例子中表现最好,但是我们可以看到在其他置信区间下为什么这个方法会成为一个问题。因此,为了测试多个组,建议我们使用 beta-二项式分布。

用于 A/B 测试的 beta-二项式分层模型

贝叶斯统计是关于概率概念的一个学派。这里,它成为该模型的理论基础,并且还可以用于提供关于分布的改进的分级模型。在贝叶斯统计中,我们经常提到先验分布和后验分布。先验分布是指某个参数(我们已经获得的数据)的概率分布,而后验分布是指某个参数(我们想要获得的数据)的概率分布。先验分布和后验分布形成共轭分布。为了便于分析,我们通常寻求使用同一家族内的分布来表示先验分布和后验分布,这就是为什么在这个层次模型中我们使用贝塔分布和二项式分布。

贝塔分布是区间[0,1]内的概率分布,参数α和β最终控制分布的形状。通常,我们使用贝塔分布来统计建模随机变量。如前所述,与贝塔分布属于同一家族的是二项分布。这通常用于模拟以独立二元结果为特征的概率分布,例如掷硬币。我们将贝塔分布和二项式分布(见图 8-6 和 8-7 )的概率密度函数分别定义为

)

)

其中 n =成功的次数,k =试验的总次数,p =成功的概率。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-7。

Binomial distribution

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-6。

Beta distribution

然后,我们将 beta 分布和先验分布的后验期望值建模为二项式,然后我们比较先验分布和后验分布之间的均值差异,以比较网站性能。

特征/变量选择技术

现在我们已经讨论了几个实验设计模型,让我们来谈谈在你对给定数据集中的因素有了更多的了解之后,你想要采取的步骤。变量选择似乎与实验设计直接相关,但本节将讨论用于降低维度的更具体的算法,以及用于分析变量及其与响应变量的相互作用的探索性较少的方法。这一点很重要,原因有很多,但在部署机器学习算法时,这往往是优化它们的一个主要因素。特征选择是一个比参数调整简单得多的过程,尤其是在深度学习模型中。因此,这是一种快速创建模型的方法,可以更快地训练并产生更准确的输出。与前面描述的许多技术一样,必须小心谨慎,因为过多的特征选择会导致创建过度拟合的模型。

向后和向前选择

向后选择是最简单的变量选择方法之一,在使用简单或多元线性回归时尤其常见。首先,你应该获取包含所有解释变量的数据集,并根据响应变量对它们进行回归。在这一步之后,选择适合于给定情况的统计显著性水平(85%、90%、95%等等)。一次一个变量,我们从数据集中删除具有最低统计显著性的变量(例如在使用 glm()模型时由 summary()函数产生的统计显著性)。我们回归原始数据集的新子集,并继续下去,直到数据集中的所有变量都具有统计显著性。在正向选择中,过程与先前的方法相同,除了区别在于您从没有变量的模型开始,添加变量,并检查它们的统计显著性。如果它们处于或高于阈值,则应该添加它们。如果没有,就应该删除。使用这些方法时要记住的注意事项是显著减少统计噪声,但要避免模型过度拟合测试数据,特别是当样本外预测是所构建模型的最终目标时。您还应该注意不要删除过多的变量,以免降低性能。

对于深度学习,一些模型中嵌入了特征选择。具体来说,CNN 中的某些层可以说是为了消除噪声而存在的,以便留下的数据富含信息。具体来说,可以认为池层就是这样做的。通过减少输入大小,我们减轻了从输入到输出的计算负荷,同时还帮助算法更准确地调整这些层之间的权重,并最终对图像进行分类。

除了使用 P 值,您还可以选择其他统计标准来确定保留/删除哪些变量。其中最常见的是赤池信息准则(AIC)和贝叶斯信息准则(BIC):

)

)

)

其中 L 是模型的最大似然函数,)是参数,k 是参数的数量。

AIC 和 BIC 关系非常密切。AIC 基于信息论领域,目标是选择一个具有最小 AIC 值的模型。根据函数的定义,对数似然值越大,AIC 值越小。从今以后,更接近数据拟合的模型将最终具有较低的 AIC 值。BIC 最终是由贝叶斯统计推动的,与 AIC 相似。BIC 分数专门用于评估模型在训练集上的性能,其中我们选择产生最小 BIC 的模型。特别是,BIC 惩罚有更多而不是更少参数的模型。正因为如此,BIC 天生喜欢不会过度适应数据集的模型,因此制定了一个标准,鼓励你选择一个能概括你正在分析的数据的模型。请注意,BIC 无法处理复杂的模型集合,仅在 n 远大于 k 的情况下才有效。考虑到 AIC,计算的 AIC 值必须跨相同的数据。具体来说,它不是一个客观的衡量标准,如决定系数。

主成分分析

主成分分析(PCA)是最常用的变量选择技术之一,可以专门用于数值数据。前面在几个例子中提到,PCA 是一种用于降低数据集维数的统计方法。简而言之,我们将数据转换为新的变量,称为主成分,并消除主成分,这些主成分解释了数据集中可忽略的方差。这种技术的好处是,我们保留了数据集的方差,同时能够比转换之前更容易地执行可视化和探索性分析。

我们的目标是从 x 向量中找到随机变量的线性函数,从α向量中找到方差最大的常数向量。这个线性函数产生我们的主分量。尽管如此,每个主成分必须按照方差递减的顺序排列,并且每个主成分必须彼此不相关。我们的目标如下:

)

我们寻求使用约束优化,因为如果没有约束,a k 的值可能无限大。因此,我们将选择以下规范化约束,其中)

拉格朗日乘子法是一种对可微函数进行约束优化的工具。特别是,它有助于在给定的约束条件下找到相应函数的局部最大值和最小值。在实验范围内,拉格朗日乘数应用如下

)

)

)

)

方程的最后一步产生特征向量αk及其相应的特征值λk。我们的目标是最大化λ k ,并且特征向量以降序定义。如果λ 1 是最大的特征向量,那么第一主成分定义为)一般来说,我们定义给定的特征向量为 x 的第 k 个主成分,并且给定特征向量的方差由其对应的特征值表示。我现在将演示当 k = 1 和 k > 2 时的这个过程。第二主成分在与第一主成分不相关的情况下最大化方差,非相关约束如下:

)

)

)

)

)

)

)

这个过程可以重复到 k = p,产生 p 个随机变量中每一个的主分量。但是,与 PCA 相关的限制是多方面的,对于问题类型必须加以考虑。首先,PCA 假设特征之间存在线性相关性。显然,在实际环境中不一定总是如此,因此使得 PCA 得出的结果值得怀疑。其次,PCA 只能用于数字数据集,数字编码分类数据的下降(在本章后面讨论)会增加隐含偏差,使这种技术的结果变得无用。此外,PCA 明确假设方差是分析数据集时最重要的统计量。尽管方差通常是一个重要的统计量,但在一些问题案例中,它不一定是。

PCA 如何应用于深度学习的一个例子是通过 PCA 白化的过程。当我们提到白化时,我们指的是使输入数据不那么同质的过程,以努力使数据从一个观察到另一个观察时不那么同质。在 CNN 的例子中,这对于图像分类非常有用。具体而言,在图像数据中,彼此相邻的许多像素在大区域内通常具有相似的值,如果不是相同的话。

这方面的一个例子是查看 MNIST 数据集,看看图像的哪些部分是黑色的,哪些是白色的。相反,PCA 白化产生矩阵的特征分解,从而消除了这种同质性。因此,每个个体的特征与其原始形式相比明显不太相似,但是数据内的方差被保留,这是在矩阵上执行特征分解时的好处。

特征分析

因素是不可观察的变量,彼此高度相关,并影响给定的解释变量。与 PCA 的最终目的降维不同,因子分析寻求定位自变量。此外,我们想确定这些因素对表面属性有什么影响。它建立在这样的假设上,即观察到的变量可以减少到一个表现出相似方差的子集。在因子分析中,我们要求数据必须是正态分布的,并且数据集中实际上没有异常值。我们还应该寻求分析大量的观察数据,虽然相关性不是近似线性的以避免多重共线性,但在整个数据集上必须是中等至高的。典型的因子分析模型由

)

给出

其中 e j =给定解释变量的唯一和特定因素,j =因素负荷,X j =解释变量,m =潜在因素

因素负荷可以被认为是权重,它们表示相对于单个变量,它们对给定因素的影响程度。表面属性被表示为单独的解释变量。典型地,因子分析模型将产生因子,使得各个变量之间没有相关性,因此我们有独立变量,类似于主成分。应该注意的是,因素不是被创建的,而是基于表面属性之间的相关性而被揭示的。看不见的因素可以是无形的,但却是可以想象的。例如,我们可以想象在一个给定的实验中,与一个人相比,一个人的阅读或写作能力。就我们如何衡量它们而言,这些属性是不客观的,但当评估一个标准化的考试时,例如,阅读和写作部分,显然会影响一个人的分数。

因子分析的局限性

因子分析可以找到一种方法来获得从随机数生成的数据中的模式。因此,人们应该记住,如果可以在随机数据中找到结构,那么他们在结构化数据中观察到的模式也可能是错误的。此外,在数据中发现的结构最终是输入到因子分析中的变量/数据集的衍生物。简而言之,数据集中没有显而易见的客观模式,最终数据集/变量的重组会导致因子分析产生的结果出现显著差异。因此,一个人如何解释因素分析的结果最终比它看起来更主观。也就是说,建议将因子分析与统计方法一起使用,并且/或者对数据进行结构化,使其符合所处理问题领域内已知为真的假设。

处理分类数据

在您可能遇到的所有困难中,最大的挑战之一是处理和分析分类数据,或数值数据。通常,我们经常遇到分类数据作为一个因素变量具有不同的水平。本节讨论将会遇到的一些常见问题以及可能的解决方案,同时要记住一些注意事项。

编码因子级别

例如,假设我们有一个数据集,其中我们正在分析一个变量,即给定街区的所有街道。这是一个特别有趣的例子,因为街道可能都是名称(如“枫树街”、“云杉街”、“红杉街”等等),也可能都是数字(第一街、第二街、第三街等等)。如果街道是名字,我们可以采取这种方法,用数字对街道进行编码。这是给每个变量一个唯一标识符的简单方法,但是它有局限性。机器学习算法将把级别解释为值的指示,而不是唯一的标识符,这实质上没有给出关于观察的“质量”的描述性数据。具体来说,如果我们将“枫树街”标记为 1,将“云杉街”标记为 2,当没有证据确定这一点时,许多算法可能会将云杉街解释为比枫树街更重要。当考虑数字的情况时,同样的问题也存在,但它只是隐含的,而不是由标签编码引起的。这种技术的另一个局限性是,如果编码变量与其他变量高度相关,多重共线性可能会被引入数据集,否则它将不会存在。

分类标签问题:级别太多

为了与使用街道名称的示例保持一致,我们可以想象在许多城市中,这将导致我们拥有数百甚至数千条单独的街道。尽管有变化的变量比完全没有变化的变量产生更好的结果,但这也会在执行模型评估时造成困难。因此,在这些情况下,对变量进行编码并使用分类/回归树或随机森林模型可能是一个好主意。此外,一个建议的方法是对变量进行编码,并使用 K-means 聚类来获得聚类数,然后我们用该变量替换级别。尽管在许多方面,这仍然会使我们之前讨论的编码变量的偏差融入到聚类观察中,但这仍然是一种有效降低级别的方法,应该在必要时进行探索。

典型相关分析

与 PCA 密切相关的是典型相关分析(CCA ),这是一种寻找两个变量的线性组合的方法,使得它们彼此具有最大可能的协方差。通常,这是一种数据预处理技术,适用于使用多元线性回归的相同情况,但特别是当有两组多元数据集时,我们希望检查以下各项之间的关系:

给定两个矢量)和方向)

)

包装器、过滤器和嵌入式(WFE)算法

当评估一些更先进的变量选择技术时,我们采用 WFE 算法。通过在数据上运行每个可能的特征子集并评估模型性能来区分包装器算法,从而选择对于给定模型表现最佳的子集。嵌入式算法被明确地写入模型的过程中(使用 LASSO 的 L1 正则化)。过滤方法试图通过查看数据本身来评估特征的优点,而不是仅通过方法来评估其性能。

救济算法

由 Aha、Kibler 和 Albert 在 1991 年设计的 relief 算法是一种基于特征的权重算法,其灵感来自基于实例的学习。每个特征被分配一个表示其与目标的相关性的权重。该算法是随机化的,并且相关性值的更新取决于所选实例和两个最近实例之间的差异。

算法
  1. 给定) T =迭代次数,s =核宽度,q =停止准则。
  2. 对于 t = 1 : T
    1. 计算成对距离 w.r.t. )
    2. 计算 P m ,P h ,P o
    3. 更新权重。
    4. 如果)。

其他本地搜索方法

如果不是直接相关的话,本文后面部分提到的许多算法将从优化的这个子领域中得到启发,这个子领域通常用于计算密集型优化问题。我们认为所有可能的解决方案都在我们称为特征空间或搜索空间的集合中。目标是满足我们寻求解决的优化问题的全局最优。局部搜索算法从特征空间中的随机元素开始,并在每次迭代中基于从当前邻域产生的信息选择新的解决方案。在这个阶段之后,算法将移动到最近的邻域中的给定邻域,但是根据问题,搜索算法可以选择不止一个邻域。

爬山搜索方法

在 20 世纪 80 年代和 90 年代机器学习发展之前,爬山往往是更受欢迎的搜索方法之一。爬山形成了本章中描述的许多新的搜索方法的动机,并且对于参数调整仍然是一种有用的技术。与其他搜索方法一样,爬山法寻求在当前点的局部范围内优化目标函数。爬山法最适用于有一个最大值或一个最小值的函数,这样算法就可以相对容易地找到问题的解。然而,对于具有大量局部极小值的函数,它面临许多问题。为了解决这个问题,许多不同的启发式算法和方法,如随机重启以避免局部极小值和搜索轨迹的随机邻域选择,都被添加到基本的爬山算法中。

遗传算法

遗传算法被认为是人工智能领域的直接产物,因为它们直接模拟了进化过程。在该算法中,总特征空间的几个子集“进化”,使得下一个子集在统计上优于上一次迭代。当一个更好的子集不能被创建时,进化过程停止,并且最好的子集被选择作为答案。与其他算法相比,该算法的优势在于,遗传算法可以在多次迭代中积累关于给定特征空间的信息,该过程本质上是并行的,因此陷入局部最小值的可能性较小,并且该算法本身相对容易理解。遗传算法的局限性之一是,如果存在大量的局部最优解,遗传算法并不总是收敛于全局最优解。此外,该算法可能不是部署的最佳选择,因为它难以扩展,因为特征空间大小随着可能子集的数量呈指数增长。

算法
  • 选择一组初始随机解决方案供选择。
  • 基于一些统计标准评估解决方案,例如 MSE。
  • 选择最佳人选。
  • 通过“变异”先前选择的解决方案来产生新的个体。
  • 评估新解决方案的适用性。
  • 当达到某个标准时停止,如损失容限。

模拟退火

在我们将要讨论的启发式技术中,SA 是少数被评估的概率模型之一。SA 的设计灵感来自于冶金学中的退火,它模拟了缓慢冷却的效果,即缓慢降低接受更差解决方案的可能性。我们将每个解视为一个状态,算法可以搜索的邻域逐渐变小。在特征空间已经被完全搜索之后,或者已经达到另一个停止标准之后,该算法收敛于一个解。一

算法
  • T =温度=热,冻结=停止标准。
  • 而(温度!=冻结),移动到特征空间中的随机点并计算能量。
  • 当系统在电流 t 下处于热平衡时,If 能量< 0 or loss tolerance, accept new state with probability )。
  • If (E 在最后几次迭代中递减),),否则 T =冻结。

SA 的最大困难是所需的参数调整量,随着特征量(和相应的特征空间)的增加,这变得很耗时。此外,对于这些参数中的任何一个都没有通用的基线或经验法则,这进一步增加了这种技术处理大量变化的数据集的难度。它应该更像是一种研究技术,而不是在算法中使用的技术。

蚁群优化算法

蚁群算法是 20 世纪 90 年代首次提出的一套优化算法。对组合数学问题最有用,ACO 已经被用于诸如车辆路径、计算机视觉、特征子集选择、定量金融和其他领域的任务。直觉是基于成群蚂蚁的活动,最终目标通常是从特征空间中找到给定随机选项集的最佳选项。在这种情况下,我们可以将一个蚁群想象成一个由边连接起来的图,其中每个节点代表数据集中 k 个特征中的一个。蚂蚁沿着边缘行进,“释放信息素”以吸引更多的蚂蚁进行后续迭代。根据设计,信息素会随着时间的推移而衰减,但是从 x 点到 y 点沿着尽可能短的边行进的蚂蚁会沿着给定的路径储存更多的信息素。因为蚂蚁被吸引到有更多信息素的路径上,这是寻找最优解的方法。每个“蚂蚁”从一个给定的状态移动,其概率由

)

给出

其中𝓇 ) =沉积在给定路径上的信息素,η =从 x : y 到所有路径距离之和的比例,β =调谐参数,jIk=未被访问的邻居节点

用信息素更新为

)

其中τ xy =沉积的环己烯酮,ρ =环己烯酮的蒸发速率。

我们将δτxy表示为由

)

给出的单个蚂蚁在给定路径上投放的信息素数量

其中 Q =某个常数,L k =用户定义的损失函数。

虽然 ACO 问题在没有大量特征的情况下是成功的,并且它通常比模拟退火和遗传算法执行得更好,但是随着更多节点的增加,问题变得更加难以解决。除此之外,虽然收敛是有保证的,但是不确定收敛实际何时发生。

算法
  • 通过创建完整的解决方案空间进行初始化。
  • 当没有达到停止标准时,将每个蚂蚁定位在给定的开始节点。
  • 对于每个蚂蚁,通过状态转换规则选择下一个节点。
  • 应用信息素更新,直到每只蚂蚁都达到给定的解决方案。
  • 根据选择标准评估每个解决方案。
  • 更新最佳解决方案,并在此路径上应用信息素更新。
  • 重复直到收敛到全局最优。

可变邻域搜索(VNS)

VNS 是一个特征子集选择算法家族,旨在处理组合学的挑战,并因此提供有保证的收敛性。发展于 20 世纪 90 年代末,VNS 的灵感来自于寻找离散和连续优化问题的解决方案的愿望(线性和非线性规划问题就是一个例子)。VNS 的假设是,关于给定邻域的局部最小值理论上可能不是另一个邻域中的局部最小值,局部最小值在一个或多个邻域之间彼此相对接近,全局最小值是解空间内所有邻域的局部最小值。在关于局部搜索方法的可用于 VNS 的算法中,有一些相关的扩展对于给定的任务更加具体。对于基于特征的选择,我们将研究基于过滤器的 VNS 算法。

算法
  • 找到一个初步的解决方案。
  • 为 k = 1,…,j 选择邻域集合 N k ,其中 j = #个邻域和一个停止准则。
  • 设 k = 1,从)的第 k 邻域生成一个随机点 S’。
  • 如果基于目标函数,应用搜索方法使停止标准更接近达到。
  • 如果此解决方案优于以前的解决方案,请将解决方案更新为当前解决方案。否则,设置 k = k + 1,保留当前解。
  • 继续进行,直到收敛到全局最优或达到停止标准。

通常,我们选择信息商或线性相关性作为这些算法中的评估函数,但这最终是一个可以改变的参数。如果你觉得更先进,请随意实现自己的深度学习和/或机器学习算法,而不是传统的梯度下降,你可以使用上述搜索方法之一进行参数优化。尽管这可能很困难,但它将为您提供一个熟悉特定算法的绝佳练习,同时还能帮助您理解给定算法中的特定操作如何影响性能。这就把我们带到了一个关于改进现有机器学习算法的类似主题:反应式搜索优化。

反应式搜索优化

RSO 是优化领域中相对较新的创新。它产生了有趣的影响,值得更高级的读者一提。RSO 的目的是为那些打算创建机器学习平台和工具的人提供特别的帮助,这些平台和工具是为那些不像典型的机器学习工程师那样技术娴熟的用户设计的。智能优化指的是 RSO 内部更具体的研究领域,但仍然是相关的。在这个范例中,我们评估不同学习方案的有效性。大致有三种,我们称之为线上、线下以及两者不同比例的结合。这是在不同环境中实现算法的思想,使得它们具有不同的搜索历史,这最终影响当前会话中的时期的动作。

被动禁令

基于禁止的技术和智能方案,与基本的启发式搜索如局部搜索相反,是禁忌搜索的智力动机。禁忌搜索方法主要是在 20 世纪 80 年代获得了最初的牵引力,鉴于它所占据的肥沃土壤,它已被证明是一个大的研究领域。与局部搜索方法相比,禁忌搜索(TS)特别值得注意,因为它使用了从数据集中收集的先验信息,以及它如何影响新迭代的结果。假设我们有一个由长度为)的二进制字符串组成的可行搜索空间,X 是当前配置,N(X)是前一个邻域。以下等式与禁忌搜索相关,即基于禁止的

)

)

其中允许功能选择)的子集,使得它依赖于整个搜索轨迹)。

禁忌搜索算法有多种分类方式,但我将阐述的最初区别因素是 TS 算法中确定性系统与随机系统。禁忌搜索的最基本形式被称为严格禁忌搜索。在该算法中,我们观察到 N(X)具有以下值:

)

当引入一个禁止参数 T 时,它决定了一个移动在执行其逆移动后将保持禁止多长时间,我们可以得到两个不同于严格禁忌搜索的算法。当且仅当通过将方向应用于搜索而从当前点获得邻居,使得其逆在最后 T 次迭代期间没有被使用,例如

)

时,才允许邻居

其中 LastUsed()是 move μ的最后一次使用时间。如果 T 随着迭代计数器而变化,生成搜索轨迹的一般动态系统包括 T 的附加演化方程,使得

)

)

对于作用于二进制字符串的基本移动,)。

对于随机模型,我们可以用概率生成-接受规则代替禁止规则,大概率表示允许的移动,小概率表示禁止的移动。随机性可以增加 TS 算法的鲁棒性。随机性会限制或消除记忆诱发活动的益处,这是禁忌搜索的主要吸引力。鲁棒禁忌搜索的特点是禁止参数在搜索过程中在上限和下限之间随机变化。在固定禁忌搜索中,可以通过随机打破束缚来增加随机性,或者通过一个以上的最佳邻居()函数的候选来获得成本函数的降低。当在反应式禁忌搜索中实现随机性时,观察到相同的效果。

固定禁忌搜索

让我们假设我们有一个搜索空间 X,使得)具有一个成本函数),其中 b 是一个 3 比特的字符串。可行点将是如图 8-8 所示的三维立方体的边缘。给定点的邻域是与边相连的点集。点 X⁰ = [0,0,0]与 f(X⁰) = 0 是一个局部极小值,因为其他移动产生了更高的成本。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-8。

A feature space with error function, E, and f value = [x,y,z], using tabu search

我们将定义两个参数,用于测试给定禁忌搜索时期的效率,表示为汉明距离和最小重复间隔。汉明距离描述了沿着搜索轨迹的起始点和最成功点之间的距离,而最小重复间隔描述了沿着给定搜索轨迹访问类似移动的次数。这些参数的方程式如下:

)

)

向前看,我们应该注意避免搜索轨迹的吸引子,这里我们把吸引子定义为由确定性局部搜索产生的局部极小值。如果代价函数是下界的,并且从任意点开始,它将终止于局部极小点。我们还定义了所谓的吸引盆地。吸引盆由所有点组成,使得从它们开始的确定性局部搜索轨迹终止于特定的局部极小点。确定性搜索轨迹经常遭受偏向于吸引盆地的痛苦,并且因此可能产生不是全局极小值的结果。为了解决这个问题,给定的搜索点保持接近在搜索轨迹开始时发现的局部极小值。在这之后,搜索轨迹可以搜索关于降低成本函数的更好的吸引流域。一如既往,我们必须意识到存在一些局限性。使用禁忌搜索时,最常遇到的困难是确定适当的禁止参数,并使该技术足够健壮,从而不需要从一个上下文到另一个上下文进行繁琐的调整。这就把我们带到了反应式禁忌搜索,它已经被提出作为解决这些问题的一种方法。

反应式禁忌搜索

反应式禁忌搜索(RTS)具有一个禁止参数,该参数通过搜索轨迹内的反应机制来确定。我们一开始用值 1 初始化它,但是我们给它的变化增加了一个确定性的方面。如果有证据表明搜索轨迹需要多样化,T 增加。一旦这个证据不明显,T 就会降低。当我们沿着搜索轨迹重复访问先前的点时,就获得了搜索路径多样化的充分证据,因为它们存储在算法的“存储器”中。此外,为了避免算法非常严格地陷入吸引域的情况,RTS 有一个逃逸机制。这是在给定周期内重复了太多搜索轨迹配置时启动的,其特征是当前搜索路径的随机重新配置。

目标函数 f 最终是搜索轨迹方向的信息来源。因此,下面的算法直接属于这个范例。

WalkSAT 算法

WalkSAT 算法可以理解为 GSAT 算法的一个更一般化的版本,它是一种局部搜索算法。在该算法中,对于给定的迭代次数,允许有固定数量的机会来找到解决方案。在给定的迭代过程中,算法在两个标准之间选择一个变量。此后,变量被放入翻转函数)。WalkSAT 通过比 GSAT 更少的计算获得能量,因为它在给定时间考虑的参数更少。除此之外,通过确定变量选择的子句的乘积,它因此有机会解决可能阻止收敛到全局最优的问题变量。子句加权也可以合并到 WalkSAT 算法中,这为参数调整和产生的反馈循环提供了新的可能性。下面的算法建议将权重作为一种鼓励优先解决较难子句的方法。几个结构之后,困难的分句被认为是这样的。

k-最近邻(KNN)

KNN 被认为是基于实例的学习,其特征在于函数的局部近似和分类后发生的所有计算。它也可以用于回归,但通常被描述为一种搜索方法。它的主要优点是相对容易理解,并且在数据模式不规则的情况下是有效的。在分类的情况下,这些模型被认为是基于记忆的,其中我们定义了我们想要考虑的 k 个相邻点。我们对标准化数据使用欧几里德范数来确定给定点与其 k 个邻居之间的距离。这个方程给出为

)

其中 I = 1,2,…,N,N =观察总数,x i =第 I 次观察,y =我们要分类的特定点。

随着 K 的增加,通常我们会注意到类之间的定义变得不那么严格,导致通常更健壮的模型。就涉及特征选择而言,KNN 可以用作数据预处理技术,通常与其他搜索技术一起用于更精细的特征选择。Tahir、Bouridane 和 Kurugollu 在 2007 年的一篇论文中给出了一个例子,他们使用禁忌搜索和 KNNs 的变体创建了一个混合算法。该算法执行特征加权和选择,产生更准确的分类结果。流水线发生使得特征通过禁忌搜索被选择和加权,并通过 KNN 被分类。如果我们不使用禁忌搜索来执行特征选择,或者根本不执行特征选择,那么更多的噪声将被结合到 KNN 算法的决策过程中。通常情况下,在这里执行特征选择有助于算法在对每个观察值进行分类时做出更精确的选择。

摘要

到目前为止,这一章是对所讨论的所有粒度细节的一种元启发。首先,实验设计、特性选择和 A/B 测试对任何数据科学家的职业都至关重要。正确构建实验的能力至关重要,通过这些实验,您可以进行建模,通过修改输入来提高它们的性能,然后定量验证模型的结果。第九章讨论了为那些对创建个人或专业使用的构建感兴趣的人提供的硬件解决方案。

九、硬件和软件建议

要在专业环境中应用本书中探索的技术,硬件升级可能会成为一个考虑因素。在某些情况下,甚至有必要从头开始建造一台计算机。很少有现成的版本,即使有也要花费惊人的金钱。考虑到这一点,本章旨在为读者提供他们最应该注意的硬件组件的基本概述,并提供关于购买硬件的一般建议。

用标准硬件处理数据

在相对“普通”的机器上操作时,您可能会面临许多困难。当使用大型数据集处理机器学习和深度学习问题时,通常建议您对数据子集运行大多数操作,并以迭代次数乘以子集大小等于原始数据集大小的方式进行训练。虽然这只是提供了一个近似的性能,但是它可能能够运行您的解决方案,而不会由于 RAM 不足而导致解释器崩溃。

也强烈建议资金充足的个人使用亚马逊网络服务(AWS)。从专业角度来说,亚马逊是云服务的首选解决方案,甚至可能让你获得许多雇主渴望拥有的宝贵技能。简而言之,你可以付费在云环境中运行你需要的所有硬件的实例。尽管出于部署目的,这样做可能成本极高,但对于概念证明或研究,使用像 Amazon AWS 这样的云服务可能是解决深度学习问题的一种经济高效且简单的解决方案。但是,如果你需要实现解决方案,作为为业务或服务部署算法的一部分,请继续阅读——本章给出的建议是一个很好的起点。

固态硬盘和硬盘(HDD)

硬盘(HDD)是一种用于保存信息的存储设备,即使机器不在线。硬盘的主要特征是它可以存储的数据量和它提供的性能。正如我在本书前面提到的,自 2000 年代中期以来,存储的价格大幅下降,促进了对机器学习和深度学习科学的兴趣复苏。这一发展使得存储和收集大量的训练数据和/或训练模型成为可能,您可以在以后更新这些数据和/或模型或将其用于相关任务。用户应该熟悉他们最想处理的案例。

图形处理单元

在区分能够提供高性能深度学习的机器和不是专门用于深度学习的机器方面,GPU 是最常被引用的硬件之一)。对于深度学习,GPU 加速了计算的处理,是深度学习构建的一个组成部分。与中央处理器(CPU)计算相比,GPU 的性能明显优于 CPU,并且是大部分计算发生的地方。您可以构建一个具有多个 GPU 的单元,但在这样做时要意识到有效利用计算能力的挑战。如果您不熟悉并行计算,学习和正确实现这样的构建可能会非常耗时,并且会花费未知的时间,更不用说在有效部署算法/解决方案之前设计和调试软件所需的时间了。

有不同语言的软件包可以并行化您的代码并提高性能。在 R 中,我建议您考虑并行包,特别是对于在大量数据上执行相同的任务。不是将整个数据集输入到算法中,而是将其分解,使得相同的任务与数据集的块并行执行,从而使其更高效。在适用的情况下,您还应该实现lapply函数。这个函数接受一个参数并将其传递给一个函数,使得执行复杂的操作比使用嵌套循环在计算上更有效。

我对 GPU 的建议(截至 2017 年初)集中在以下 Nvidia 型号上:

  • 泰坦 x 号
  • GTX 680
  • GTX 980

截至 2017 年初,英伟达是少数几家专注于开发专门用于深度学习的 GPU 的公司之一。(注意,AMD 正在与谷歌合作创建深度学习硬件,将于 2017 年某个时候发布。)虽然这对普通从业者来说可能不太划算,但对于专业人士或有足够预算的人来说,我建议您在 AMD 的 FirePro S9300 x2 GPU 发布时查看其规格和性能评论。

选择 GPU 取决于您想要解决的问题类型以及您预计在此过程中消耗的内存量。使用 CNN 的人应该会消耗大量内存,尤其是在训练给定模型的过程中。深度学习的图像和其他数据的物理存储是另一个需要记住的考虑因素。尽管固态存储和虚拟存储的价格都大幅下降,但您应该留出时间来正确估计所需的存储。

中央处理器

除了执行非常基本的算术、逻辑和输入/输出功能之外,CPU 还指示计算机应该进行什么操作以及这些操作应该在哪里进行。CPU 还与 GPU 密切合作,以启动函数调用并启动向 GPU 的计算传输。对于深度学习特定的工作,CPU 核心的数量以及 CPU 缓存大小都很重要。大多数深度学习库依赖于使用单个 CPU 线程,通常每个 GPU 使用一个线程就可以很好地执行。然而,每个 GPU 使用更多线程可能会带来更好的性能——将这一事实与您打算执行的任务联系起来。对于图像分类任务,比如经典的 MNIST 数字分类任务,我发现如果我在使用本地机器时遇到困难,使用 AWS 的 g2.2xlarge 实例就足够了——它提供了 1 个 GPU,15 GB 的 RAM 和 60 GB 的 SSD 存储。

关于 CPU 缓存大小,有几种不同速度的缓存类型。L1 和 L2 往往很快,L3 和 L4 很慢。CPU 缓存的目的是通过匹配密钥对值来帮助加速计算。在实际环境中遇到的大多数数据集都太大,无法放入 CPU 缓存中,因此对于每个小批量,将从给定计算机上的 RAM 中读入新数据。在深度学习的情况下,大部分计算发生在 GPU 中,所以你不必担心购买能够处理这种负载的 CPU。但是,由于 CPU 缓存未命中,您可能会经常看到机器性能不佳,并且您有延迟问题。这导致了缓存未命中的核心考虑因素:RAM 以及在机器学习和深度学习中经常需要更多 RAM。

随机存取存储器

ram 存储经常使用的程序指令,使得程序的速度增加,因为它存储将被读取或写入的数据,而不管其在 RAM 中的位置。至于你需要的内存大小,它应该与你正在使用的 GPU 的大小相当。使用小于 GPU 大小的 RAM 可能会导致延迟问题,这可能会导致问题,特别是在训练不同的网络(如 CNN)时。使用更多而不是更少的 RAM 可以让您更容易地执行预处理和特征工程。说“买尽可能多的内存”很容易,但当然这并不总是可能的。然而,你应该考虑在这方面投入大量的可用资金。

母板

主板是主要的电路板,除了个人电脑之外,在各种产品中都可以找到。它的主要目的是促进计算机内各种组件之间的通信,并保持这些组件之间的连接器。确保主板有足够的 PCIe 端口来支持将安装在给定计算机中的 GPU 数量,并支持所有其他选择的硬件组件。

电源装置(PSU)

电源单元将交流电转换为稳定的直流电,以便计算机内的组件可以使用。关于用于深度学习的 PSU,请注意,如果您使用多个 GPU,请购买一个可以服务于您使用的 GPU 数量的 PSU。深度学习通常需要密集的训练,运行这些实例的成本应该最小化。给定深度学习机器所需的瓦特数可以通过将 GPU 和 CPU 的瓦特数相加,同时为计算机内的其他组件和功耗差异添加大约 200 瓦特来估算。

优化机器学习软件

本章的主要目的是让读者找到他们在改进机器方面的关注点。最终目标是提高被测试和部署的软件的性能,但是其中一部分涉及到直接优化软件。为此,在所有其他步骤之前,我建议您尝试改进您正在使用的算法,或者在实现给定解决方案时找到一个更好的算法。算法的最佳选择和找到所述算法的最佳实现可能相当耗时。这可能需要通读大量的文档,深入查看各种函数的代码,还可能需要做实验。虽然这本书是为那些在 R 方面相对有经验的人和刚接触深度学习/机器学习的人准备的,但在阅读完这篇文章后,你应该有足够的信心开始创建自己的各种机器学习算法的实现。虽然很费时间,但这样做可以让你学到很多关于不同算法及其实现的效率。

目前一个常见的争论围绕着使用哪种语言。r 是一种非常容易理解的语言,非常适合用于概念验证,特别是因为它的语法允许快速编写和测试代码。然而,当试图为任何需要实时应用的东西部署算法时,尤其是当试图将软件嵌入其他应用时,往往会被证明是麻烦的。如果你打算在一个专业的环境中工作,在设计任何事情的最终解决方案时,请记住这一点。通常,那些希望提高速度的人经常用 C++来写。当然,这本书没有涵盖 C++,也没有涉及 C++中的任何包,但是读者应该探索 C++中用于机器学习和深度学习的无数库。

摘要

本章应该让读者对他们在为机器学习进行专门构建时,或者在试图修改他们现有的硬件以更好地服务于他们的深度学习需求时,应该关注的一些最常见的问题有一个基本的了解。

第十章深入探讨了更多使用机器学习和深度学习解决方案的实际例子。

十、机器学习示例问题

在这一章中,我们将开始把目前讨论的技术应用到你可能面临的实际问题中。所提供的数据集将从随机数据中生成,或将来自 https://github.com/TawehBeysolowII/AnIntroductionToDeepLearning 。请注意,您还可以参考前面章节中给出的示例中提供的所有代码和数据集的 URL。

在这一章中,我们将专门研究机器学习问题。虽然我不能涵盖所有可能的领域和问题类型,但是这里的例子将重点解决用户可能遇到的常见场景。

我鼓励您将这些最后的示例章节视为如何从数据集(原始或处理过的)到解决方案的教程。虽然这些例子是可行的解决方案,但最重要的方面是应用我们已经讨论过的实验设计、特性选择和模型评估方法来有效地解决问题。

问题 1:资产价格预测

量化金融是一个不断将数据科学和机器学习技术融入其方法的领域,特别是在自动化交易和市场研究的过程中。虽然数量金融学本身是一个具有丰富多样性和自身技术的领域,但我们可以应用许多宽泛的分析和数学概念。对于这个例子,我们将使用 quantmod 包来下载金融数据,我将带您了解如何预测资产价格。我还将简要解释如何创建交易策略——特别是统计套利策略。和往常一样,强烈建议在应用这些技术之前对这些结果进行回溯测试。本章的目的是提供对机器学习的学术理解——它不打算作为量化投资组合管理的教程!

让我们假设你是一家资产管理公司的定量分析师,你的任务是合理预测一项标准普尔 500 资产的回报。你的董事总经理认为,还有十只股票有助于模拟这种特殊资产的表现,你应该在分析中使用它们。除了建议使用机器学习方法来解决这个问题之外,导演没有给出具体的使用方法。

让我们从定义问题开始。

问题类型:监督学习—回归

我们试图预测离散或连续值的任何问题都被称为回归问题。因为我们已经有了答案,并且我们正在尝试将我们提出的答案与实际答案进行比较,这是一个监督学习问题。具体来说,我们将尝试根据其他资产 x 的回报来预测一项资产 y 的回报。让我们开始构建管道来解决这个问题。

通常,使用雅虎!或者建议使用 Google Finance API 来完成这些任务。对于那些特别关注机器学习在定量金融中的应用的人,请注意雅虎!《金融》杂志的数据有生存偏见,也就是说,任何已经倒闭的公司都不能再访问他们的数据。因此,因任何原因被除名的公司不再存储在数据库中。这就产生了一个问题,因为使用这些数据的所有策略都不会反映出最糟糕的情况,例如,有人在金融危机期间交易了雷曼兄弟(Lehman Brothers)的贝尔斯登(Bear Sterns)等证券。但是,可以找到保存破产或不再上市的公司数据的数据库(Norgate Data 就是一个例子)。

我们将从使用 Google Finance API 加载数据开始,但是将使用 quantmod 包来完成。对于任何需要访问股票数据的工作,都推荐使用这个软件包,比如获取各种金融工具的每日、每月或每季度的价格,以及从上市公司获取财务报表数据。

让我们开始浏览代码:

#Clear the workspace (1)
rm(list = ls())

#Upload the necessary packages (2)
require(quantmod)
require(MASS)
require(LiblineaR)
require(rpart)
require(mlbench)
require(caret)
require(lmridge)
require(e1071)
require(Metrics)
require(h2o)
require(class)
#Please access github to see the rest of the required packages!

#Summary Statistics Function
#We will use this later to evaluate our model performance (3)
summaryStatistics <- function(array){
  Mean <- mean(array)
  Std <- sd(array)
  Min <- min(array)
  Max <- max(array)
  Range <- Max - Min
  output <- data.frame("Mean" = Mean, "Std Dev" = Std, "Min" =  Min,"Max" = Max, "Range" = Range)
  return(output)
}

在前面的代码中,和使用 R 时一样,在进行新的实验时,清空工作空间(1)很重要。然后我们加载所需的包(2)。定义的下一个函数给出了我们正在分析的数组的汇总统计数据(3)。在本例中,我们将只关注 MSE。这是为了提供一个如何评估机器学习模型的简单示例。

我经常采用两种方法:

  • 在默认模式下评估几个模型,然后对最佳模型执行参数调整。
  • 一次调整一个参数,然后对调整后的模型进行评估。

在这里,我将执行后者,尽管出于简单和解释的目的,没有那么深入。

实验描述

我们将创建一个通用管道来解决这个问题,描述如下:

数据摄取→特征选择→模型训练和评估→模型选择

具体来说,在这个问题中,我们将尝试根据我们怀疑准确描述这些回报的股票(市场指数和其他股票的混合)的回报来预测福特公司(股票代码为 F)的回报。我们的股票投资组合的选择本身就是一项研究,但在这种情况下,我们选择了与汽车市场相关的股票(宏观指标和与能源行业相关的指标)。这里的假设是,跟踪福特表现的股票很可能是同一行业的公司,或者是以某种方式服务于汽车市场的相关行业的公司,或者是对整体经济有更大影响的公司。

请注意,除了正确理解如何创建机器学习模型所需的数学知识之外,还有必要为这些模型提供有用的数据。如果我们使用与正在解决的问题完全不相关的特征,我们将很难从拟合的模型中得到任何有用的结果。因此,在我们对算法进行任何微调之前,我们为创建数据集而做出的这些假设将有助于产生更好的结果:

#Loading Data From Yahoo Finance (4)
stocks <- c("F", "SPY", "DJIA", "HAL", "MSFT", "SWN", "SJM", "SLG", "STJ")
stockData  <- list()
for(i in stocks){
  stockData[[i]] <- getSymbols(i, src = 'google', auto.assign = FALSE, from = "2013-01-01", to = "2017-01-01")
}

#Creating Matrix of close prices
df  <- matrix(nrow = nrow(stockData[[1]]), ncol = length(stockData))
for (i in 1:length(stockData)){
  df[,i]  <- stockData[[i]][,4]
}
#Calculating Returns
return_df  <- matrix(nrow = nrow(df), ncol = ncol(df))
for (j in 1:ncol(return_df)){
  for(i in 1:nrow(return_df) - 1){
    return_df[i,j]  <- (df[i+1, j]/df[i,j]) - 1
  }
}

在前面的代码中,我们从 Yahoo!金融(4)。除非在初次下载后保存了这些数据,否则您应该有一个活动的互联网连接,否则这部分代码将无法正常执行。在计算给定股票的回报时,您可以将回报视为衍生产品,但基于回报的价格的更简单公式如下:

)

(一)

其中 x =股票 x,y =股票 y,t =时间段(1,2,… n),

n =观察次数,)= t 期间股票 x 的价格

为了这个实验的目的,同样在量化金融的许多这样的情况下,我们根据调整后的收盘价计算回报(等式 A)。我们将这些调整后的收盘价称为收盘价,因为它们反映了由于股息、股票分割或其他与股票表现或市场条件无关的财务调整而导致的基础股票价格随时间的任何变化。在这里,我们将查看每日收益。时间频率的选择完全由用户决定,并取决于被评估的策略。一般来说,高频交易在一天内发生多次,低频交易发生的增量明显长于一天。

我们组织数据,使得每一列代表给定股票的收益,每一行代表给定一天的收益。图 10-1 显示了数据集的头部。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-1。

Head of stock return data set

股票回报通常与机器学习算法配合得很好,因为它们都以类似的方式进行缩放,并表示与给定股票内的所有观察结果以及可用于分析的股票范围相关的度量。

特征选择

在处理时间序列数据时,我们经常会遇到多重共线性。因此,PCA 是用于特征选择的公平方法。我们这样做是因为除了变量之间的线性相关性很高这一事实之外,可能还有不需要评估的特征,因此不需要评估噪声。因此,根据特征贡献的方差来评价特征是合理的。下面显示了执行 PCA 的代码:

#Feature Selection
#Removing last row since it is an NA VALUE
return_df  <- return_df[-nrow(return_df), ]
#Making DataFrame with all values except label IE all columns except for Ford since we are trying to predict this
#Determing Which Variables Are Unnecessary
pca_df  <- return_df[, -1]
pca  <- prcomp(scale(pca_df))
cor(return_df[, -1])
summary(pca)

当执行前面的代码时,我们会收到如图 10-2 和 10-3 所示的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-3。

Summary of principal components analysis (PCA) on data set

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-2。

Correlation matrix for entire data set

在图 10-3 的第 2 行,你可以看到每个主成分对数据集贡献的方差的比例。为了清楚起见,必须声明主成分不代表数据集中的特征。也就是说,我们可以认为主成分 1 是特征 1 到 8 的组合,PC 2 是特征 2 到 8 的组合,等等。一般的经验法则是将对总方差贡献 1%或更少的主成分视为无关紧要的主成分。当将其转化为数据集时,我们将删除数据集中的特征 8。这种相同的分析模式应该进行外推,但只有当观察到特征之间的线性相关性时。回到图 10-1 ,你可以看到这些特征之间通常存在中度到强烈的线性相关性,这表明主成分分析确实是特征的一个合适选择。

模型评估

既然我们已经预处理了数据,让我们考虑我们的算法选择。在本例中,我们将评估几个不同的选择,并评估所有选择的 MSE。选择的型号数量完全由您决定,但是对于这个实际的例子,我将选择两个。此外,如果您选择评估 MSE 之外的统计数据,例如 R 的平方,则评估这些与实验目标相关的度量是合理的。也就是说,MSE 应该是回归模型中最小化的主要目标,也应该是所有其他评估方法中的主要关注点。

里脊回归

让我们选择第一个模型:岭回归。这里,我们将根据调整参数的值来评估 MSE。在下面的代码中,我们从从正态分布(5)中随机采样值开始。这些值将用于选择调整参数的大小,我们用 k 表示。这背后的直觉是,我们将从最低到最高对这些值进行排序,然后随着调整参数的增加,通过可视化误差来比较岭回归模型的 MSEs 性能:

#Ridge Regression
k <- sort(rnorm(100))(5)

在下面的代码中,我们开始交叉验证我们的结果,以便我们评估模型性能的一般性,而不是在完全相同的数据集上测试我们的算法(6)。我们选择使用大小相等的训练和测试集,将数据分成两半:

mse_ridge <- c()
for (j in 1:length(k)){ (6)
    valid_rows <- sample(1:(nrow(return_df)/2))
    valid_set <- new_returns[valid_rows, -1]
    valid_y <- new_returns[valid_rows, 1]
#Ridge Regression (7)
    ridgeReg <- lmridge(valid_y ∼ valid_set[,1] + valid_set[,2] + valid_set[,3] + valid_set[,4]
                             + valid_set[,5] + valid_set[,6], data = as.data.frame(valid_set), type = type,  K = k[j])
    mse_ridge <- append(rstats1.lmridge(ridgeReg)$mse, mse_ridge)
}

然后,我们使用lmridge()函数将数据拟合到岭回归模型,然后将 MSE 附加到名为mse_ridge (7)的向量。

当执行以下代码时,我们看到如图 10-4 所示的结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-4。

MSE over tuning parameter size

#Plots of MSE and R2 as Tuning Parameter Grows
plot(k, mse_ridge, main = "MSE over Tuning Parameter Size", xlab = "K", ylab = "MSE", type = "l",
     col = "cadetblue")

当查看该图时,我们看到当我们的调整参数 K 最接近所显示范围的上限和下限时,该模型表现最佳。具体来说,我们将选择创建一个调整参数值为 1 的拟合模型,因为这个 K 值会产生较低的 MSE。评估模型时,在访谈、实验和个人评估中,使用图来查看模型在某些参数值变化时的性能是很重要的。这对你和其他使用/评估你的代码的人都很有用。这将有助于引导人们了解你的思维过程,而情节往往比看终端代码的数字输出更有吸引力。

在我们对验证集之外的数据测试我们的拟合模型之前,让我们看看如何调整另一个算法:支持向量回归(SVR)。

支持向量回归机

这里要调整的主要参数是kernel函数,它决定了超平面的形状,因此也决定了回归线的形状。当我们执行下面的代码时,我们得到如图 10-5 所示的图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-5。

SVR MSE with respect to kernel selection

#Kernel Selection
svr_mse <- c()
k <- c("linear", "polynomial", "sigmoid")
for (i in 1:length(k)){
  valid_rows <- sample(1:(nrow(return_df)/2))
  valid_set <- new_returns[valid_rows, -1]
  valid_y <- new_returns[valid_rows, 1]

  SVR <- svm(valid_y ∼ valid_set[,1] + valid_set[,2] + valid_set[,3] + valid_set[,4]
             + valid_set[,5] + valid_set[,6], kernel = k[i])
  svr_y <- predict(SVR, data = valid_set)
  svr_mse <- append(mse(valid_y, svr_y), svr_mse)
}

#Plots of MSE and R2 as Tuning Parameter Grows
plot(svr_mse, main = "MSE over Tuning Parameter Size", xlab = "K", ylab = "MSE", type = "l",
       col = "cadetblue")

在评估输出时,我们注意到图 10-5 中的以下 MSE 值。多项式核产生最小的 MSE,因此是我们的选择。现在,我们已经训练了两个模型,我们将使用调整后的模型对样本外进行预测。在实际设置中,您可能需要安装两个以上的模型并评估性能。因为这个过程是详尽的,为了便于解释,我将这个例子浓缩为比较两个模型。无论如何,让我们来看看我们调整后的模型的性能:

#Predicting out of Sample with Tuned Models
#Tuned Ridge Regression
ridgeReg <- lmridge(valid_y ∼ valid_set[,1] + valid_set[,2] + valid_set[,3] + valid_set[,4]
                    + valid_set[,5] + valid_set[,6], data = as.data.frame(valid_set), type = type,  K = 1)

y_h <- predict(ridgeReg, as.data.frame(new_returns[-valid_rows, -1]))
mse_ridge <- mse(new_returns[-valid_rows, 1], y_h)

#Tuned Support Vector Regression
svr <-   SVR <- svm(valid_y ∼ valid_set[,1] + valid_set[,2] + valid_set[,3] + valid_set[,4]
                    + valid_set[,5] + valid_set[,6], kernel = "polynomial")
svr_y <- predict(svr, data = new_returns[-valid_rows, -1])
svr_mse <- mse(new_returns[-valid_rows, 1], svr_y)

#Tail of Predicted Value DataFrames
svr_pred <- cbind(new_returns[-valid_rows, 1], svr_y)
colnames(svr_pred) <- c("Actual", "Predicted")
tail(svr_pred)
ridge_pred <- cbind(new_returns[-valid_rows, 1], y_h)
colnames(ridge_pred) <- c("Actual", "Predicted")
tail(ridge_pred)

前面的代码使用了我们训练的回归模型,只是我们根据产生最低 MSE 的值来设置参数值。尽管我们将模型与训练数据相匹配,但我们是在测试数据上进行预测。这表现在我们使用所有我们没有训练模型的观察值从返回数据帧中进行索引。当对测试数据集进行预测时,图 10-6 和 10-7 显示了每个算法的实际股票值与预测股票值的对比。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-7。

Tail of actual versus predicted data frame (ridge regression)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-6。

Tail of actual versus predicted data frame (SVR)

当评估这些算法的 MSE 时,我们得到以下结果:

  • 支持向量回归的 MSE:0.0002967161
  • 岭回归的 MSE:0.002632815

基于这些结果,有理由说我们应该选择岭回归而不是基于更好的 MSE 的 SVR。在评估一个解决方案时,除了完全不同的算法之外,您应该可以自由地研究给出的示例,并使用不同的特性选择算法。这一节的目的,同样是提供我通常如何处理这些问题的见解,以便您可以开始开发自己的方法。尽管模型选择和调优有一些通用的指导原则,但是每个人都可以按照自己的方式自由地执行。

现在让我们来看一个分类问题。

问题 2:速配

在快速约会中,参与者会见许多人,每个人几分钟,然后决定他们希望再次见到谁。我们将使用的数据集包含了对研究生和专业学生进行的快速约会实验的信息。实验中的每个人与 10-20 个随机选择的异性(只有异性配对)见面,每个人四分钟。每次快速约会后,每个参与者都要填写一份关于对方的问卷。我们的目标是建立一个模型来预测哪对约会者希望再次见面(即有第二次约会)。

问题类型:分类

我们试图确定二元或有限多项式结果的任何问题都可以被认为是分类问题。在这种情况下,这将是一个监督问题,因为我们事先知道数据的标签,但我们需要通过特定于该数据集的确定性规则来计算它们。第二次约会只有在特定配对中的两个人都决定他们想再次见到对方的情况下才会计划。因此,我们将在数据集的预处理阶段创建该列:

#Upload Necessary Packages
require(ggplot2)
require(lattice)
require(nnet)
require(pROC)
require(ROCR)

#Clear the workspace
rm(list = ls())

#Upload the necessary data
data  <- read.csv("/Users/tawehbeysolow/Desktop/projectportfolio/SpeedDating.csv", header = TRUE, stringsAsFactors = TRUE)

#Creating response label
second_date  <- matrix(nrow = nrow(data), ncol = 1)

for (i in 1:nrow(data)){
  if (data[i,1] + data[i,2] == 2){
    second_date[i]  <- 1
  } else {
    second_date[i]  <- 0
  }
}

和往常一样,我们通过加载必要的包和清理工作区来开始实验。然后,我们加载数据并创建一个名为 second_date 的响应标签。

现在我们已经完成了一些初步的预处理,让我们来描述和探索我们的数据集。该数据集中的特征如下,从第一列到最后一列:

  • Second_Date:二进制数据集的响应变量 y。1 =是(您想再次看到日期),0 =否(您不想再次看到日期)。
  • 决定:由性别隔离的个人做出的决定,关于他们是否愿意进行第二次约会。1 =是(您想再次看到日期),0 =否(您不想再次看到日期)。
  • 喜欢:总的来说,你有多喜欢这个人?(1 =完全不喜欢,10 =喜欢很多)。
  • PartnerYes:你认为这个人会答应你的可能性有多大?(1 =不太可能,10 =极有可能)。
  • 年龄:年龄。
  • 种族:白种人、亚洲人、黑人、拉丁美洲人或其他人种。
  • 有吸引力:用 1-10 的标准给伴侣的吸引力打分(1 =糟糕,10 =很棒)。
  • 真诚:给合作伙伴的销售诚意评分,1-10(1 =糟糕,10 =很好)。
  • 有趣:用 1-10 分的标准评价伴侣的有趣程度(1 =糟糕,10 =很棒)。
  • 雄心勃勃:用 1-10 的标准给伴侣的雄心壮志打分(1 =糟糕,10 =伟大)。
  • 共同兴趣:用 1-10 分(1 =很糟糕,10 =很棒)来评价你和伴侣共同兴趣/爱好的程度。

预处理:数据清理和插补

注意,在这个数据集中有 NA 观察值。如前所述,我们有多种工具来处理这个问题,但重要的是我们要从算法上找到处理这个问题的方法。我们将在执行任何特性转换之前解决这个问题。以下代码显示了我们处理 NA 数据的过程:

#Cleaning Data
#Finding NA Observations
lappend <- function (List, ...){
  List <- c(List, list(...))
  return(List)
}
na_index <- list()
for (i in 1:ncol(data)){
  na_index <- lappend(na_index, which(is.na(data[,i])))
}

首先,我们创建一个函数,让我们将向量附加到一个列表中,这样,对于每一列,我们都有一个指示 NA 观察位置的行向量。给定数据集的性质,在给定该列/特征中的数据的情况下,使用最合理的方法估算值是合乎逻辑的。请注意,Second_Date、DecisionM、DecisionF、RaceM 和 RaceF 列没有任何缺失数据。我们将处理确实存在缺失数据的特征。

我们将使用第三章中描述的期望最大化(EM)算法进行数据插补。这在amelia包中给出,可以从 R 端子安装。不过,在此之前,我们必须稍微准备一下我们的数据:

#Imputing NA Values where they are missing using EM Algorithm
#Step 1: Label Encoding Factor Variables to prepare for input to EM Algorithm
data$RaceM <- as.numeric(data$RaceM)
data$RaceF <- as.numeric(data$RaceF)

#Step 2: Inputting data to EM Algorithm
data <-  amelia(x = data, m = 1,  boot.type = "none")$imputations$imp1

#Proof of EM Imputation
na_index <- list()
for (i in 1:ncol(data)){
  na_index <- lappend(na_index, which(is.na(data[,i])))
}
na_index <- matrix(na_index, ncol = length(na_index), nrow = 1)
print(na_index)

 #Scaling Age Features using Gaussian Normalization
data$AgeM <- scale(data$AgeM)
data$AgeF <- scale(data$AgeF)

EM 算法不能处理因子(分类变量)。这意味着我们必须在将这些因素输入算法之前对它们进行数字编码。在这之后,我们执行amelia函数,它执行我们想要的。接下来,我们通过索引任何 NA 值,然后打印此输出,提供该数据集中不再有 NA 数据的证据,产生如图 10-8 所示的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-8。

Displaying counts of NA values in cleaned data set

我们已经成功地移除了所有 NA 观测值,并将在继续进行特性选择之前执行最后一点预处理。让我们看看男性和女性的年龄分布。我们对此进行如下编码,并接收后续结果:

#Scaling Age Features using Gaussian Normalization
summaryStatistics(data$AgeM)

Mean  Std.Dev Min Max Range
1 26.60727 3.509664  18  42    24

summaryStatistics(data$AgeF)

Mean  Std.Dev Min Max Range
1 26.24317 3.977411  19  55    36

#Making Histograms of Data
hist(data$AgeM, main = "Distribution of Age in Males", xlab = "Age", ylab = "Frequency", col = "darkorange3")
hist(data$AgeF, main = "Distribution of Age in Females", xlab = "Age", ylab = "Frequency", col = "firebrick1")
data$AgeM <- scale(data$AgeM)
data$AgeF <- scale(data$AgeF)

当使用hist()函数可视化数据的分布时,代码产生如图 10-9 和 10-10 所示的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-10。

Histogram of female ages

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-9。

Histogram of male ages

女性和男性年龄的分布呈正偏态,这意味着平均值小于中位数。然而,请注意,与男性年龄相比,女性年龄的差异明显较小。虽然这也可能是我们想要保留的观点,但是在探索数据集和解释信息显示的内容时,您应该了解显示图表的重要性。对于不太懂技术的人来说,这可能是展示信息的最有吸引力的方式之一。对于那些经常做报告的人来说,有效地利用情节是必须的。最后,我们通过对年龄变量执行高斯归一化来结束我们的数据清理和预处理,以便它们的输入不会影响我们的分类模型的准确性,因为它们与不是数字标签的每个其他变量处于不同的范围。

既然已经完成了所有必要的预处理,我们就可以着手特征选择的任务了。

特征选择

这个数据集没有异常大量的观察值,但 27 个单独的特征可能会造成过度杀伤,并将不必要地削弱我们的机器学习算法的预测能力。因此,我们消除不必要的特征是合理的,尽管我们应该注意这个过程不一定像看起来那样简单。

当查看相关矩阵时(该矩阵太大,无法在此显示),我们注意到通常存在弱到中等的线性相关性。我们可能无法从任何严重依赖线性假设的模型中获得有效的结果。当把它与特征选择联系起来时,我们同样不可能从使用 PCA 中得到好的结果。因此,我选择使用随机森林来表示特征的重要性,基于它们对观察分类的影响程度:

#Feature Selection
corr <- cor(data)

#Converting all Columns to Numeric prior to Input
for (i in 1:ncol(data)){
  data[,i] <- as.integer(data[,i])
}

#Random Forest Feature Selection Based on Importance of Classification
data$second_date <- as.factor(data$second_date)
featImport <- random.forest.importance(second_date ∼., data = data, importance.type = 1)
columns <- cutoff.k.percent(featImport, 0.4)
print(columns)

执行上述代码时,以下各列高于为重要性设置的阈值 0.4:

[1] "DecisionF"  "DecisionM"          "AttractiveM"    "FunF"    "LikeM"
[6] "LikeF"      "SharedInterestsF"   "AttractiveF"    "PartnerYesM"

这些将是我们训练集中使用的特性,现在我们可以继续进行模型训练和评估。

模型训练和评估

既然我们已经有了一个充分简化和转换的数据集,那么是时候开始模型选择的过程了。因为决定分类的函数不是线性的,所以我们应该考虑可以处理这种类型数据的函数。在下一个问题中,我们将使用以下算法组合:

  • 逻辑回归
  • 贝叶斯分类器
  • k-最近邻

我们将单独调整每个算法的参数,评估训练集的性能,然后预测样本外。一旦我们对所有的算法都这样做了,我们将并排评估结果,然后选择最佳的算法。

方法 1:逻辑回归

有人建议,在评估投资组合分类算法时,你应该总是从逻辑回归开始。原因不在于期望这是最好的算法,而更多的是从这样一个观点来看,这形成了一个基线评估,从这个评估中你可以比较不同的分类算法。在本实验中,我们将评估模型在 AUC 分数方面的表现,AUC 分数是(ROC)曲线下的面积:

#Method 1: Logistic Regression
lambda <- seq(.01, 1, .01)
AUC <- c()
for (i in 1:length(lambda)){
  rows <- sample(1:nrow(processedData), nrow(processedData)/2)
  logReg <- glm(as.factor(second_date[rows])., data = processedData[rows, ], family = binomial(link = "logit"), method = "glm.fit")
  y_h <- ifelse(logReg$fitted.values >= lambda[i], 1, 0)
  AUC <- append(roc(y_h, as.numeric(second_date[-rows]))$auc, AUC)
}

我们从改变阈值开始,该阈值决定了我们是基于 lambda 参数将观察分类为 1 还是 0。我们迭代该算法,并将基于该参数的 AUC 分数附加到 AUC 向量。在这个迭代循环之后,我们应该使用一个图来直观地评估性能。当绘制λ值的 AUC 得分向量时,我们编写以下代码,并观察图 10-11 中所示的输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-11。

AUC over lambda value

#Summary Statistics and Various Plots
plot(lambda[-1], AUC, main = "AUC over Lambda Value \n(Logistic Regression)",
     xlab = "Lambda", ylab = "AUC", type = "l", col = "cadetblue")

我们看到,当λ值为 0.15 时,AUC 得分最高,因此我们将使用该λ值。这是我建议你如何调整机器学习算法参数的一个例子。每个参数都应该单独调整,以便实现给定的目标,无论是最小化 MSE 还是最大化 AUC。在逻辑回归中,对数优势阈值实际上是我们需要调整的唯一参数。我们可以在测试集上通过多次迭代来查看优化模型的性能:

#Tuned Model
AUC <- c()
for (i in 1:length(lambda)){
  rows <- sample(1:nrow(processedData), nrow(processedData)/2)
  logReg <- glm(as.factor(second_date[rows])., data = processedData[rows, ], family = binomial(link = "logit"), method = "glm.fit")
  y_h <- ifelse(logReg$fitted.values >= lambda[which(AUC == max(AUC))], 1, 0)
  AUC <- append(roc(y_h, as.numeric(second_date[-rows]))$auc, AUC)

}

#Summary Statistics and Various Plots
plot(AUC, main = "AUC over 100 Iterations \n(Naive Bayes Classifier)",
     xlab = "Iterations", ylab = "AUC", type = "l", col = "cadetblue")
hist(AUC, main = "Histogram for AUC \n(Naive Bayes Classifier)",
     xlab = "AUC Value", ylab = "Frequency", col = "firebrick3")

通过收集 AUC,我们遵循与调整机器学习算法时相同的直觉。逻辑回归的本质是在每次迭代时拟合模型,而不是像某些算法那样选择最佳回归解。当相对于一段时间内的迭代绘制 AUC 向量并绘制 AUC 向量的直方图时,我们观察到图 10-12 和 10-13 中所示的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-13。

Logistic regression AUC histogram over 100 iterations

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-12。

Logistic regression AUC over 100 iterations

在数值上,我们可以用下面的函数来总结这个向量:

summaryStatistics(AUC)

Mean           Std.Dev       Min         Max         Range
1 0.5063276    0.04964798    0.3920711   0.6297832   0.2377121

我们将在前进的道路上牢记这些价值观。当按原样分析它们时,逻辑回归是一个不充分的分类器。通常,我们希望 AUC 分数至少为 0.70,因为 0.50 的分数表明模型只有 50%的正确率。小于 0 . 50 不是最佳的,这意味着我们应该认为这个分类器是不够的。

方法 3:K-最近邻(KNN)

这是一个相当简单的分类算法,在第三章中有详细描述。选择这种算法相对于另一种概率算法的目的是创建一个多样化的算法组合,以便我们可以推断出哪种类型的算法最适合这项任务。作为读者的注意事项,class 包中的 K-NN 算法从测试数据中产生分类。若要仅根据训练数据训练算法,请使用分配给“train”参数的相同数据:

#Method 3: K-Nearest Neighbor
#Tuning K Parameter (Number of Neighbors)
K <- seq(1, 40, 1)
AUC <- c()
for (i in 1:length(K)){
  rows <- sample(1:nrow(processedData), nrow(processedData)/2)
  y_h <- knn(train = processedData[rows, ], test = processedData[rows,], cl = second_date[rows], k = K[i], use.all = TRUE)
  AUC <- append(roc(y_h, as.numeric(second_date[rows]))$auc, AUC)
}

#Summary Statistics and Various Plots
plot(AUC, main = "AUC over K Value \n(K Nearest Neighbor)",  xlab = "K", ylab = "AUC", type = "l", col = "cadetblue")

当查看 AUC 与 K 值的关系图时,我们可以看到图 10-14 所示的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-14。

KNN classifier AUC over 100 iterations

对于所有值,训练阶段的 AUC 分数通常令人印象深刻,但是选择比大 K 值低的 K 值以防止过度拟合是合理的。因此,我们将选择 K 为 3。让我们用我们调优的模型在测试集上观察 AUC 分数,如图 10-15 和 10-16 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-16。

KNN AUC over 100 iterations on test set histogram

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-15。

KNN AUC over 100 iterations on test set

在数字上,我们评估 AUC 向量如下:

summaryStatistics(AUC)

Mean          Std.Dev       Min         Max        Range
1 0.445006    0.01126862    0.4257075   0.4663915  0.04068396

最后,我们预测出样本并观察到以下结果:

#Predicting out of Sample
y_h <- knn(train = processedData[rows, ], test = processedData[-rows, ], cl = second_date[-rows])
roc(y_h, as.numeric(second_date[-rows]))$auc

曲线下面积:0.4638

除了测试集的性能客观上很差之外,我们还看到了从训练集到测试集的明显下降。

方法 2:贝叶斯分类器

我怀疑第二次约会的发生可以用贝叶斯估计器来建模,所以我们将开始的第一个模型是贝叶斯分类器。在下面的代码中,我们首先对数据集执行双重交叉验证,以便评估训练集的性能。在这个特定的模型中,只需要进行很少的调整,所以我们只需观察模型在 100 次迭代后的性能:

#Method 1: Bayesian Classifier
AUC <- c()
for (i in 1:100){
  rows <- sample(1:nrow(processedData), 92)
  bayesClass <- naiveBayes(y = as.factor(second_date[rows]), x = processedData[rows, ], data = processedData)
  y_h <- predict(bayesClass, processedData[rows, ], type = c("class"))
  AUC <- append(roc(y_h, as.numeric(second_date[rows]))$auc, AUC)
}

#Summary Statistics and Various Plots
plot(AUC, main = "AUC over 100 Iterations \n(Naive Bayes Classifier)",
     xlab = "Iterations", ylab = "AUC", type = "l", col = "cadetblue")

hist(AUC, main = "Histogram for AUC \n(Naive Bayes Classifier)",
     xlab = "AUC Value", ylab = "Frequency", col = "cadetblue")

summaryStatistics(AUC)

当执行代码时,我们将 AUC 分数附加到向量 AUC,如前面循环 100 次迭代的代码所示。该矢量的线图和直方图如图 10-17 和 10-18 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-18。

Bayes classifier AUC histogram over 100 iterations

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-17。

Bayes classifier AUC performance over 100 iterations

我们观察到 AUC 分数在其分布中具有轻微的右偏,并且大多数 AUC 分数分布在彼此相对紧密的范围内。当查看原始数值数据时,我们观察到以下情况:

Mean          Std.Dev       Min         Max         Range
1 0.8251087   0.03142345    0.7567568   0.9027778   0.146021

对于我们选择的模型而言,这些 AUC 分数超出了一般可接受的范围,尽管我们仍应评估样本外模型的性能,以确定该过程的稳定性:

#Predicting out of Sample
y_h <- predict(bayesClass, processedData[-rows, ], type = c("class"))
roc(y_h, as.numeric(second_date[-rows]))$auc

执行以下代码后,我们观察到以下 AUC 得分:曲线下面积:0.8219。这在从训练集产生的数据的分布中是可接受的,该 AUC 分数趋向于数据的平均值。

在评估所选的解决方案时,我强烈建议选择贝叶斯分类器,因为它从训练到测试集都很稳定,并且 AUC 得分高于所有其他方法。在实际设置中,我们会使用样本数据的预测来帮助影响我们的决策过程。在专业背景下,这可能包括根据不同用户的交友档案向他们进行有针对性的营销或推荐。

摘要

现在你已经对我如何推荐应用我在前面章节中解释的概念有了一个简要而全面的了解。你还应该注意到,虽然我已经成功地使用这种通用过程/方法实现了机器学习算法,但这并不是训练/调整机器学习模型的唯一方式。尽管如此,我还是非常强调度量标准的使用,以及在调优不同参数时根据这些度量标准绘制模型的性能。第十一章将看如何实现和使用各种深度学习模型的使用示例。

十一、深度学习和其他示例问题

既然我已经充分地介绍了如何使用和应用机器学习概念,我们应该最终使用 r 来应用和编码深度学习模型。这似乎是一项艰巨的任务,但不要被吓倒。如果您已经能够成功地编写本书中的所有代码,那么就只需要适应新的软件包了。我们将讨论各种深度学习示例,但将从处理更简单的模型开始,然后最终转向更复杂的模型。这些练习有两个目的:

  • 展示如何构建这些模型或者从不同的包中访问它们
  • 举例说明它们在实际概念中的应用

自编码器

该书深度学习章节中描述的许多其他模型在谈到如何使用它们时都相对简单,但我发现自编码器的使用不会自动变得清晰。因此,我想探索一个用例,在这个用例中,自编码器的使用在实际环境中变得非常清晰。让我们考虑这样一种情况,我们希望使用自编码器来提高第十章中分类算法的性能。具体来说,我指的是我们走过的分类问题,其中我们试图根据几个特征来确定一对个体是否会进行第二次约会。让我们从贝叶斯分类器开始:

#Bayes Classifier
#Bayes Classifier
AUC <- c()
for (i in 1:100){
  rows <- sample(1:nrow(processedData), 92)
  bayesClass <- naiveBayes(y = as.factor(second_date[rows]), x = processedData[rows, ], data = processedData)
  y_h <- predict(bayesClass, processedData[rows, ], type = c("class"))
  AUC <- append(roc(y_h, as.numeric(second_date[rows]))$auc, AUC)
}

summaryStatistics(AUC)
curve <- roc(y_h, as.numeric(second_date[rows]))
plot(curve, main = "Bayesian Classifier ROC")

当执行前面的代码时,会产生如图 11-1 所示的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-1。

ROC plot for Bayesian classifier

当收集样本统计数据时,我们观察该模型的 AUC 分数:

Mean       Std.Dev     Min        Max    Range
0.8210827  0.02375922  0.7571429  0.875  0.1178571

这些都是客观上的好成绩。然而,出于这个例子的目的,我们将使用一个自编码器来帮助进一步提高这个模型的性能。这就是我介绍 h2o 的地方。h2o 为 R(以及其他语言)提供了一个深度学习框架,您会发现它对实现许多模型非常有用。我鼓励你搜索文档,因为深度学习模型的一些实现很难找到(更不用说找到健壮的实现了)。让我们初始化 h2o 并使用自编码器:

#Autoencoder
h2o.init()
training_data <- as.h2o(processedData, destination_frame = "train_data")
autoencoder <- h2o.deeplearning(x = colnames(processedData),
 training_frame = training_data, autoencoder = TRUE, activation = "Tanh",
 hidden = c(6,5,6), epochs = 10)
autoencoder

h2o 类似于 TensorFlow,每个会话都必须初始化。初始化之后,无论什么数据通过所用的模型,都必须转换成 h2o 友好的格式。我们对训练数据进行转换。我们的 autoencoder 有三个隐藏层,每个层在给定的层中分别有六个、五个和六个神经元(由h2o.deeplearning()函数中的“hidden”参数表示)。我们用 tanh 作为我们的激活函数。执行以下代码后,我们会看到如图 11-2 所示的内容。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-2。

Summary of autoencoder function

注意 MSE 值。因为我们试图重新创建一个函数的输入,这就变成了一个回归任务。因此,我们使用传统的回归统计(MSE 和 RSME)来评估该算法的有效性。让我们仔细看看此处得出的 MSE,并根据保存训练数据的数据帧的索引来查看 MSE:

#Reconstruct Original Data Set
syntheticData <- h2o.anomaly(autoencoder, training_data, per_feature = FALSE)
errorRate <- as.data.frame(syntheticData)

#Plotting Error Rate of Feature Reconstruction
plot(sort(errorRate$Reconstruction.MSE), main = "Reconstruction Error Rate")

h2o.anomaly()函数使用自编码器来检测异常,我们在统计上将异常定义为在重建过程中 MSE 明显高于其他观测值的观测值。当执行前面的代码时,我们得到图 11-3 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-3。

Plot of reconstruction error

我们可以看到,从指数水平 225 到训练数据结束,MSE 稳定增加,但也急剧增加。我们可以合理地说,离群值通常是这些最后的输入。考虑到这一点,我们将使用由 MSE 确定的阈值来将离群值从非离群值中分离到它们各自的子集。我们试图通过将我们的模型拟合到这些子集来训练我们的贝叶斯分类器,并观察模型的性能相对于 AUC 分数如何提高(或不提高):

#Removing Anomolies from Data
train_data <- processedData[errorRate$Reconstruction.MSE < 0.01, ]

#Bayes Classifier
AUC <- c()
for (i in 1:100){
  rows <- sample(1:nrow(processedData), 92)
  bayesClass1 <- naiveBayes(y = as.factor(second_date[rows]), x = processedData[rows, ], data = processedData)
  y_h <- predict(bayesClass1, processedData[rows, ], type = c("class"))
  AUC <- append(roc(y_h, as.numeric(second_date[rows]))$auc, AUC)
}

#Summary Statistics
summaryStatistics(AUC)

我们遵循第十章中关于模型训练的相同一般步骤,收集 100 次试验的 AUC 统计样本。这里唯一的区别是,我们使用了低于 MSE 阈值的指数值的数据子集。查看汇总统计数据时,我们观察到以下情况:

Mean       Std.Dev     Min   Max        Range
0.8274664  0.03076285  0.75  0.9117647  0.1617647

当将我们的结果分布与原始模型进行比较时,我们观察到一个稍高的平均值,一个较高的最大值。然而,我们也观察到一个较低的最小值。因此,我们的结果的范围和标准偏差增加。让我们评估一下我们只看异常情况时的结果:

##########################################################################
#Using only Anomalies in Data Set
train_data <- processedData[errorRate$Reconstruction.MSE >= 0.01, ]

#Bayes Classifier
AUC <- c()
for (i in 1:100){
  rows <- sample(1:nrow(processedData), 92)
  bayesClass2 <- naiveBayes(y = as.factor(second_date[rows]), x = processedData[rows, ], data = processedData)
  y_h <- predict(bayesClass2, processedData[rows, ], type = c("class"))
  AUC <- append(roc(y_h, as.numeric(second_date[rows]))$auc, AUC)
}

#Summary Statistics
summaryStatistics(AUC)

执行上述代码时,我们会看到以下结果:

Mean       Std.Dev     Min        Max        Range
0.8323727  0.03168166  0.7692308  0.9107143  0.1414835

在这里,我们观察到,这种分布包含最高的平均值和最小值,与范围和标准偏差的中等结果。当在两个数据集之间进行选择时,我会主张在这种情况下使用第二个子集,因为平均而言 AUC 得分表现更好,并且考虑到至少我们仍然可以期待更高的得分。

这种技术的重要性在于,它是一种有效的方法,通过这种方法,您可以在数据子集上拟合高级模型。如果您发现您的数据集比您想要的要小,这将非常方便。尽管使用了适当的交叉验证技术、数据预处理技术和参数调整技术,但有时您会发现自己在尝试调整一个性能稍微不令人满意的模型时遇到困难。在由于缺乏数据而导致这种情况的情况下,在试图获取更多数据之前,我会首先尝试使用这种技术。至于我们实验的最后一步,让我们使用拟合的模型,看看它们在样本外的表现如何:

#Fitted Models and Out of Sample Performance
AUC1 <- AUC2 <- c()

for (i in 1:100){
  rows <- sample(1:nrow(processedData), 92)
  y_h1 <- predict(bayesClass1, processedData[-rows,], type = c("class"))
  y_h2 <- predict(bayesClass2, processedData[-rows,], type = c("class"))
  AUC1 <- append(roc(y_h1, as.numeric(second_date[-rows]))$auc, AUC1)
  AUC2 <- append(roc(y_h2, as.numeric(second_date[-rows]))$auc, AUC2)
}
summaryStatistics(AUC1)
summaryStatistics(AUC2)

当执行前面的代码时,我们看到模型与没有异常和只有异常的子集相匹配的结果,分别如图 11-4 和 11-5 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-5。

ROC curve for Bayes model w/o anomalies (AUC: 0.8188)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-4。

ROC curve for Bayes model without anomalies (AUC : 0.7821)

Mean       Std.Dev     Min        Max        Range
0.7890102  0.01468805  0.75       0.8194444  0.06944444
Mean       Std.Dev     Min        Max        Range
0.8303613  0.01506222  0.7957983  0.8688836  0.07308532

当回顾我们实验的结果时,已经变得非常清楚的是,仅拟合异常的第二个模型比拟合没有异常的观察的模型产生明显更好的模型。但是,在我们完全确信应该使用第二个模型之前,让我们使用来自这两个模型的数据快速执行一个双边假设检验。

因为我们对结果进行了 100 次采样,所以我们可以安全地使用 Z 检验。因此,我们设置 Z 测试参数,如以下代码所示:

#Two Sided Hypothesis Test
require(BSDA)

z.test(x = AUC1, y = AUC2, alternative = "two.sided", mu = mean(AUC2) - mean(AUC1),
                 conf.level = 0.99, sigma.x = sd(AUC1), sigma.y = sd(AUC2))

当执行前面的函数时,它产生如图 11-6 所示的输出。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-6。

Two-sided hypothesis test results

从统计学上看,在 99%的置信区间内,我们已经确定两个模型的结果在统计学上彼此不同,因此我们可以放心地选择拟合的第二个贝叶斯模型,因为我们知道它是最优模型。

卷积神经网络

当我在第五章中讨论 CNN 时,我通过讨论 MNIST 数字识别用例展示了这个模型的威力。尽管这一度是 CNN 的主要用途,但现在它们正被用于越来越困难和复杂的任务。现在我想探索一个用例,在这个用例中,我们试图区分比手写数字复杂得多的不同对象。在本教程中,我们将使用加州理工学院 101 数据集,它包含 101 个对象类别,每个类别中有 60 到 800 张图像。我们将从每个类别中选取不同的图片,这样我们就可以得到不同的图片,而不会选取完全不同的图片。我们将在吉他和笔记本电脑的图像中进行选择。这些照片的样本如图 11-7 和 11-8 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-8。

Photo of laptop

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-7。

Photo of guitar

这些图像是技术的产物,但它们彼此之间有着明显的不同,我们希望人类能够区分它们。现在让我们讨论我们应该如何为 CNN 准备数据。

预处理

处理图像文件需要一种特殊类型的预处理,我们还没有详细讨论过,主要是因为图像识别和计算机视觉是计算机科学的一个非常特殊的子领域。明智的做法是寻找其他文本来建立你对计算机视觉的理解,但是这篇文章将会给你一个基本的概述。我们正在处理彩色图像,每个图像都有 x,y,z 维度,其中 x 和 y 是每张照片特有的,但是 z 总是 3。就计算机理解的图像文件而言,它们是相互堆叠的三层矩阵,每个像素是矩阵中的一个单独的条目。对于这个任务,我推荐你使用 EBImage 包,这样你就可以灰度化和调整图像大小。为了帮助神经网络的训练时间,我们将调整图像的大小,使它们更小,因此神经网络接受的数据更少。但是让我们一步一步地完成我们的预处理:

#Loading required packages
require(mxnet)
require(EBImage)
require(jpeg)
require(pROC)

#Downloading the strings of the image files in each directory
guitar_photos <- list.files("/file/path/to/image")
laptop_photos <- list.files("/file/path/to/image")

加州理工学院图书馆被组织成多个层次的目录,所以当试图以自动化的方式访问这些图像时要小心。每个类别的所有目录都具有相同的文件名格式:图像文件表示为 image_000,X,其中 X 是目录中图像的编号。但是每个目录都有不同数量的文件,所以我们应该使用list.files()函数来收集目录中所有图像文件的名称。我们在下面的代码中使用它们。使用list.files()功能时吉他照片目录的内容以截断的形式显示在图 11-9 中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-9。

List of files from image directory

现在我们有了各个文件的名称,我们可以使用以下过程将它们加载到img_data数据框中:

#Creating Empty Data Frame
img_data <- data.frame()

#Turning Photos into Bitmaps
#Guitar Bitmaps
for (i in 1:length(bass_photos)){
  img <- readJPEG(paste("/path/to/image/directory/", guitar_photos[i], sep = ""))

我们在这里使用paste函数将目录和带有字符串的图像结合起来,这样它就可以引导我们找到数据。使用 jpeg 包中的readJPEG()函数,我们可以将图像读入位图,就像前面描述的矩阵堆栈一样。每个维度代表构成每张彩色照片的三种颜色(红色、蓝色和绿色)。但是为了降低我们正在处理的图像的复杂性,我们将把这些图像转换成灰度(黑白)。当处理黑白图像时,我们给像素值分配一个 0 到 1 之间的数字,0 代表黑色,1 代表白色。中间的颜色决定了特定颜色向光谱任一侧的强度:

#Reshape to 64x64 pixel size and grayscale image
img <- Image(img, dim = c(64, 64), color = "grayscale")

#Resizing Image to 28x28 Pixel Size
img <- resize(img, w = 28, h = 28)
img <- img@.Data

我们使用 EBImage 中提供的resize()函数对各种图像进行整形和调整大小。如果你对查看图像灰度化后的样子感兴趣,可以随意使用display()Image()函数。调整图像大小后,我们将位图转换成矢量,以便更好地存储。最后,在创建和训练模型时,我们必须向数据向量添加一个标签。这在计算我们模型的准确性时会很有用。具体来说,吉他将被标记为 1,笔记本电脑将被标记为 2:

  #Transforming to vector
  img <- as.vector(t(img))

  #Adding Label
  label <- 1

  img <- c(label, img)

  #Appending to List
 img_data <- rbind(img_data, img)

}

我们对笔记本电脑图像重复这一过程。如果您想使用这种预处理和模型评估的结构,请随意,或者尝试其他预处理方法。在创建 CNN 模型之前,我们必须确保模型的输入格式是正确的。MXNet 和许多神经网络模型都有您应该熟悉的特定格式。第一步是创建一个训练和测试集。在本例中,我们将拆分数据集,这样我们可以针对 75%的数据进行训练,针对剩余的 25%进行测试。我们现在将转换数据,使其成为一个矩阵,其中每一行都是不同的图像观察,标签作为第一列条目,位图值作为连续的列条目。然后,我们将从 X 矩阵中剥离标签,并将其用作 y 向量相应观察顺序中的值。然后,我们使用sample()函数执行交叉验证:

#Transforming data into matrix for input into CNN
training_set <- data.matrix(img_data)

#Cross Validating Results
rows <- sample(1:nrow(training_set), nrow(training_set)*.75)

#Training Set
x_train <- t(training_set[rows, -1])
y_train <- training_set[rows, 1]
dim(x_train) <- c(28,28, 1, ncol(x_train))

在前面的代码中,指出一个明显的细节是很重要的,如果忽略这个细节,您将无法执行代码。MXNet CNN 模型只取一个 4 维的 X 矩阵。请务必记住这一点,否则您将浪费时间来调试这个问题!我们还相应地改变了测试集的维度:

#Test Set
x_test <- t(training_set[-rows, -1])
y_test <- training_set[-rows, 1];
dim(x_test) <- c(28,28, 1, ncol(x_test))

既然我们已经完成了数据的预处理,我们终于可以开始构建和训练我们的模型了。

模型构建和培训

CNN 模型的构建方式是数据通过每一层,但实际输入到FeedForward()函数的唯一一层是最后一层。因此,我们在这里激活模型之前构建它。有些包可能更专有,需要更少的架构,但 MXNet 允许很大程度的定制,如果你想构建不同的 ConvNet 结构,这将是有用的,如第五章中详述的那些。如果你想提高这里的结果,这可能是一个很好的利用你的时间。

让我们来看看建筑。这里我们将使用通用的 LeNet 架构,这是图像识别任务的标准。因此,我们以同样的方式组织这些层:

data <- mx.symbol.Variable('data')

#Layer 1
convolution_l1 <- mx.symbol.Convolution(data = data, kernel = c(5,5), num_filter = 20)
tanh_l1 <- mx.symbol.Activation(data = convolution_l1, act_type = "tanh")
pooling_l1 <- mx.symbol.Pooling(data = tanh_l1, pool_type = "max", kernel = c(2,2), stride = c(2,2))

#Layer 2
convolution_l2 <- mx.symbol.Convolution(data = pooling_l1, kernel = c(5,5), num_filter = 20)
tanh_l2 <- mx.symbol.Activation(data = convolution_l2, act_type = "tanh")
pooling_l2 <- mx.symbol.Pooling(data = tanh_l2, pool_type = "max", kernel = c(2,2), stride = c(2,2))

我们首先创建一个虚拟的data变量,该变量将用于以对 ConvNet 友好的文件格式传递 x 矩阵值。data经过每一层,如第五章所述,模型从数据的较低抽象到较高抽象进行构建,以做出决定。在这里,我们将使用一般建议的步幅 2,20 个过滤器在第一 Conv 层,50 个过滤器在第二 Conv 层。作为激活函数,我们使用 tanh。该激活函数将在整个模型中保持不变,但输出函数除外:

#Fully Connected 1
fl <- mx.symbol.Flatten(data = pooling_l2)
full_conn1 <- mx.symbol.FullyConnected(data = fl, num_hidden = 500)
tanh_l3 <- mx.symbol.Activation(data = full_conn1, act_type = "tanh")

#Fully Connected 2
full_conn2 <- mx.symbol.FullyConnected(data = tanh_l3, num_hidden = 40)

#Softmax Classification Layer
CNN <- mx.symbol.SoftmaxOutput(data = full_conn2)

数据继续传递到完全连接的层。在完全连接的层中分别有 500 和 40 个隐藏神经元。最后,数据到达最后一层,在这里我们有一个 softmax 分类器来确定观察值的类别。

但是,在我们做出任何预测之前,我们必须使用上一节中建议的方法来训练我们的参数。如果可能,特别是在神经网络的情况下,强烈建议对支持这些功能的包使用本地搜索方法。具体来说,h2o 支持网格搜索功能来调整参数。虽然我们在这里使用 MXNet,但是让读者了解提供这些功能的包是很有用的。

让我们从训练参数开始:

#Learning Rate Parameter
AUC <- c()
learn_rate <- c(0.01, 0.02, 0.03, 0.04)
CPU <- mx.cpu()

for (i in 1:length(learn_rate)){
  cnn_model <- mx.model.FeedForward.create(CNN, X = x_train, y = y_train, ctx = CPU, num.round = 50, array.batch.size = 40,
learning.rate = learn_rate[i],
momentum = 0.9, eval.metric = mx.metric.accuracy,
epoch.end.callback = mx.callback.log.train.metric(100),
 optimizer = "sgd")
#Code redated partially, please check github!

与其他神经网络模型类似,学习率参数决定了更新连接各层的权重时梯度的大小。我们给出了一个数组,并根据图 11-10 中的调整参数绘制了 AUC。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-10。

AUC score over learning rate

我们可以清楚地看到,这里 0.04 的学习率是最优的,因为它产生最高的 AUC 分数。

现在让我们训练动量参数:

AUC1 <- c()
mom <- c(0.5, 0.9, 1.5)
for (i in 1:length(mom)){
cnn_model <- mx.model.FeedForward.create(CNN, X = x_train, y = y_train, ctx = CPU, num.round = 50, array.batch.size = 40, learning.rate = 0.04,
momentum = mom[i], eval.metric = mx.metric.accuracy,
epoch.end.callback = mx.callback.log.train.metric(100), optimizer = "sgd")
#Code redacted partially, please check github!

当我们执行前面的代码时,我们会收到如图 11-11 所示的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-11。

AUC over momentum value

当评估不同参数的结果时,我们将动量值设置为 0.9。现在我们已经调优了这两个参数,我们可以在最后一部分开始训练调优的模型,并在测试和训练集上评估它的性能:

#Fitted Model Training
cnn_model <- mx.model.FeedForward.create(CNN, X = x_train, y = y_train, ctx = CPU, num.round = 150, array.batch.size = 40,
learning.rate = 0.04, momentum = 0.9, eval.metric = mx.metric.accuracy,
initializer = mx.init.normal(0.01) , optimizer = "sgd")

#Calculating Training Set Accuracy
y_h <- predict(cnn_model, x_train)
Labels <- max.col(t(y_h)) - 1
roc(as.factor(y_train), as.numeric(Labels))
curve <- roc(as.factor(y_train), as.numeric(Labels))
#Code partially redacted, please check github!

在执行代码之前,我想指出一个细节。这里,我们没有启用 GPU 训练。如果您想减少训练时间并提高计算性能,请查看 MXNet 文档中启用此功能的必要步骤。在这个例子中,我们将使用 CPU 培训。您还应该意识到,增加num.round参数的诱惑通常会很强烈,因为这将直接影响模型对训练集数据的准确性。请注意,将该参数设置得太高会导致过度拟合,特别是对于我们在本例中使用的数据集。当执行上述代码时,用户应该看到终端以如下格式打印出训练精度:

[184] Train-accuracy=0.708333333333333
[185] Train-accuracy=0.708333333333333
[186] Train-accuracy=0.708333333333333
[187] Train-accuracy=0.708333333333333
[188] Train-accuracy=0.708333333333333

单词Train-accuracy左侧的数字代表当前迭代,该迭代将运行到num.round参数中指示的数字。这里使用的accuracy参数相当于 AUC 分数,由mx.metric.accuracy对象给出。像往常一样,学习率很难近似,但我们可以通过使用随机梯度下降优化器调整神经网络中的权重来减轻准确性的损失。执行代码时,我们得到图 11-12 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-12。

ROC plot for CNN over training data

该 ROC 图的 AUC 为 0.7706。当评估测试数据的性能时,产生图 11-13 和结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-13。

ROC plot for CNN over test data

当对照测试数据进行预测时,模型的 AUC 为 0.7063。粗略地说,这里的性能相当相似,尽管正如我们所料,我们确实注意到从训练集到测试集的性能有所下降。也就是说,即使在这种情况下,也不太可能有任何过度拟合的迹象。然而,如果您想将这样的东西投入生产,您可能仍然倾向于改进这些模型的性能。理想情况下,在对图像进行分类时,我们希望模型的准确率至少达到 90%。虽然这里的图像分类情况是相当良性的,但是存在不正确的分类会在每次观察中损失大量金钱或者导致不正确的诊断从而导致患者接受不适当的护理的情况。考虑到这一点,你将如何从这一点着手?

最合理的下一步是为这个模型的训练阶段获取更多的数据。这通常是我们认为建立足够的卷积神经网络的最大挑战:获得足够的训练数据。对于许多不同的商业产品,合法地获取这些数据可能是一项非常艰巨的任务,在最坏的情况下,需要团队自己在现实世界中获取数据。在为特定任务创建 CNN 时,读者应该注意这一点,因为有时任务的可行性纯粹是数据可访问性的问题。在这种情况下,我们使用的数据集大约只有总共 170 张照片,其中 75%以上是我们训练的。

另一个你可能想注意的建议是使用另一个网络架构,或者如果你有足够的野心,尝试创建自己的网络架构。然而,创建自己的网络架构可能是一项极其艰巨的任务。另一个可能的探索途径是创建几个卷积神经网络。从这些模型中,我们可以创建一个数据集,其中每个特征都是给定 CNN 的输出。这个数据集然后可以被输入到传统的机器学习模型中。但是,您应该意识到,这些方法本身可能需要对前面概述的方法进行重大调整。

协同过滤

对于我们的最后一个例子,我们将简要地处理推荐系统的问题,就像在前面的章节中简要地处理的那样。推荐系统是不断发展的,但是由于数据科学在其中的应用,提出这个概念是有用的。在这里,您将了解插补的实际应用,以及数据科学的一些软技能,如数据转换,这些都已简要介绍过,但从未介绍过。

推荐系统是 Amazon.com 等电子商务网站特有的,但也存在于网飞等基于内容的网站。动机相当简单,因为向客户推荐他们合理喜欢的产品是合理的。然而,这样做的任务比看起来更困难。大多数用户不会使用某个公司提供的所有产品。即使他们这样做了,也不意味着他们会对他们使用的每一种产品进行评级。这就给我们留下了一个矩阵中数值稀少的问题。然而,我们已经回顾了处理这个问题的技术,并将继续检查我们的数据集。

对于这个实验,我们将使用第三个 Jester 数据集( http://goldberg.berkeley.edu/jester-data/ )。特征都代表个人笑话,行代表用户。矩阵中的每个条目都是一个笑话的等级,其中下限是–10,上限是 10。然而,每当没有一个笑话的条目时,就用 99 来表示。当检查数据集的头部时,我们看到如图 11-14 所示的矩阵。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-14。

Snapshot of the Jester data set

这里的目标是根据笑话本身的相似性来衡量不同用户口味的相似性。为此,我们将计算列向量之间的余弦相似度。简而言之,在讨论结合矩阵分解和 RBMs 来估算缺失值之前,让我们先讨论余弦相似性的概念。当处理试图比较向量的问题时,余弦相似性是一个经常被引用的概念。直观上,我们将余弦相似度定义为两个非零向量不同的程度。数学上,我们用下面的等式

)

定义余弦相似度,其中 A,B =两个不同的向量。

与相关系数类似,余弦相似值的范围从–1 到 1。余弦相似度为 1 表示值完全相同,而–1 表示值完全相反。零值表示向量之间完全没有关系。考虑到这一点,我们将比较某些音乐的消费模式,这样我们就可以比较哪些项目彼此最相似,因此应该推荐给其他人。

然而,对于那些密切关注的人来说,余弦相似性与两个非零向量一起使用,这意味着我们必须为我们的数据集生成缺失值。已经讨论了许多插补技术,但 Geoffrey Hinton 认为在这种情况下有用的一种技术是矩阵分解。具体来说,我建议你用奇异值分解(SVD)。

本书其他地方讨论的 SVD 和 PCA 是高度相关的技术。它们都是执行矩阵特征分解,但是 SVDs 应用不同于 PCA 的应用。特别地,奇异值分解可以用来逼近缺失值。因此,让我们使用impute.svd()函数估算我们的值:

require(lsa)
require(bcv)
require(gdata)
require(Matrix)

#Upload the data set
#Please be patient this may take a handful of seconds to load.
data <- read.xls("/path/to/data/.xls", sheet = 1)
colnames(data) <- seq(1, ncol(data), 1)

#Converting 99s to NA Values (1)
data[data == 99] <- NA

#Converting 99s to Mean Column Values (2)
for (i in 1:ncol(data)){
  data[is.na(data[,i]), i] <- mean(data[,i], na.rm = TRUE)
}

我们首先将 99(1)转换为 NA 值,然后将 NA 值更改为列 means (2)。在这一点上,我们可以前进,估算的价值:

#Imputing Data via SVD
newData <- impute.svd(data, k = qr(data)$rank, tol = 1e-4, maxiter = 200)
print(newData$rss)
head(data[, 2:10])

请注意,impute.svd()函数要求您估算缺失值的任一列平均值,或者如果一整列的观察值缺失,则使其为 0。如果你不遵循这些指示,你会收到不正确的结果。当执行前面的代码时,我们产生如图 11-15 所示的输出。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-15。

Head of imputed data set

当执行 SVD 时,我们还计算了关于非缺失值和这些非缺失值的预测的平方和为 4.398197e-20。想要挑战自我的读者可以不使用 SVD 估算值,而是使用 RBM。但是,请注意,这项任务的计算量非常大,并且针对这项任务修改 RBM 并不容易。寻找 Geoffrey Hinton 给出的关于这个主题的高层次概述( http://www.machinelearning.org/proceedings/icml2007/papers/407.pdf )。

我们现在可以计算列之间的余弦距离:

itemData <- matrix(NA, nrow = ncol(data), ncol = 11,
                   dimnames=list(colnames(data)))
#Getting Cosine Distances
for (i in 1:nrow(itemData)){
  for (j in 1:ncol(itemData)){
    itemData[i,j] <- cosine(data[,i], data[,j])
  }
}

当执行前面的代码时,我们产生如图 11-16 所示的数据集。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-16。

Head of the cosine distance data set

从这个数据集,我们现在可以执行最终的数据转换,这样每行代表一个特定的笑话,每列代表从左到右降序排列的最相似的笑话。我们首先通过实例化一个具有适当维度(1)的空矩阵来做到这一点。实例化该矩阵后,我们可以通过对余弦值进行排序并获取包含前 11 个值的索引来填充数据——我们获取前 11 个值是因为数字 1 值本身总是相同的项目:

#Creating Matrix for ranking similarities (1)
similarMat <- matrix(NA, nrow = ncol(itemData), ncol = 11)

#Sorting Data Within Item Data Matrix (2)
for(i in 1:ncol(itemData)) {
  rows <- order(itemData[,i], decreasing = TRUE)
  similarMat[i,] <- (t(head(n=11, rownames(data[rows ,][i]))))
}

#Printing Result
similarMat

当执行前面的代码时,我们得到我们的最终答案,如图 11-17 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-17。

Top 10 recommendations for 11 separate jokes

我们将结果解释为对 11 个不同的笑话产生了前 10 个推荐。您可以在一个平台中实现这一点,这样,在一个网页上,用户可以收到不同页面、产品或类似实体的推荐。

摘要

我们现在已经到了本章的结尾,以及我们对深度学习和机器学习技术的全面回顾。第十二章提供了所有数据科学家在他们的研究或专业努力中前进时应该知道的简要建议。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值