每个程序员应该知道的 50 个算法(三)

原文:annas-archive.org/md5/a52efc405cb495a26c2bdcb2c25f51df

译者:飞龙

协议:CC BY-NC-SA 4.0

第八章:神经网络算法

幽默没有算法。

—罗伯特·曼科夫

神经网络已经成为研究的课题超过七十年,但由于计算能力的限制和数字化数据的匮乏,它们的应用受到了制约。如今,由于我们日益增长的解决复杂挑战的需求、数据生产的爆炸性增长以及如云计算等技术的进步,环境发生了显著变化,赋予了我们强大的计算能力。这些改进为我们提供了开发和应用这些复杂算法的潜力,以解决曾经被认为不切实际的复杂问题。事实上,这是一个迅速发展的研究领域,是机器人技术、边缘计算、自然语言处理和自动驾驶汽车等前沿技术领域大多数重大进展的源泉。

本章首先介绍典型神经网络的主要概念和组成部分。接下来,介绍神经网络的不同类型,并解释这些神经网络中使用的各种激活函数。然后,详细讨论反向传播算法,这是训练神经网络中最广泛使用的算法。接下来,解释转移学习技术,它可以极大简化并部分自动化模型的训练。最后,通过一个现实世界的应用示例,说明如何使用深度学习来标记欺诈性文档。

本章讨论的主要概念如下:

  • 理解神经网络

  • 神经网络的演变

  • 训练神经网络

  • 工具和框架

  • 转移学习

  • 案例研究:使用深度学习进行欺诈检测

让我们从神经网络的基础开始。

神经网络的演变

神经网络在其最基本的层面上,由被称为神经元的独立单元组成。这些神经元是神经网络的基石,每个神经元执行各自特定的任务。当这些独立的神经元组织成结构化的层时,神经网络的真正力量得以展现,从而促进复杂的处理过程。每个神经网络都由这些层的错综复杂的网络组成,层与层之间通过连接形成互联网络。

信息或信号在通过这些层时被一步一步地处理。每一层都会修改信号,最终影响整体输出。具体来说,初始层接收输入信号,对其进行处理后将其传递到下一层。随后的层进一步处理接收到的信号并继续传递。这一传递过程一直持续,直到信号到达最终层,生成所需的输出。

正是这些隐藏层或中间层赋予了神经网络进行深度学习的能力。这些层通过逐步将原始输入数据转换为更有用的形式,创建了抽象表示的层次结构。这有助于从原始数据中提取更高层次的特征。

这种深度学习能力具有广泛的实际应用,从使亚马逊的 Alexa 能够理解语音命令,到支持谷歌的图像和整理谷歌照片。

历史背景

受到人类大脑中神经元工作的启发,Frank Rosenblatt 在 1957 年提出了神经网络的概念。要完全理解其结构,简要查看人类大脑神经元的分层结构是很有帮助的。(参考图 8.1,了解人类大脑中神经元如何相互连接。)

在人脑中,树突充当传感器,检测信号。树突是神经元的组成部分,作为主要感觉器官。它们负责检测传入的信号。然后信号传递给轴突,这是神经细胞的一种长而细的突出部分。轴突的功能是将这个信号传输到肌肉、腺体和其他神经元。如下图所示,信号通过称为突触的相互连接组织传递,然后传递给其他神经元。请注意,通过这种有机管道,信号一直传播,直到达到目标肌肉或腺体,引起所需的动作。信号通常需要七到八毫秒才能通过神经元链传播并到达目标。

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_01.png

图 8.1:人脑中连接在一起的神经元

受到这种自然信号处理建筑杰作的启发,Frank Rosenblatt 设计了一种技术,使得可以按层处理数字信息以解决复杂的数学问题。他最初设计的神经网络尝试非常简单,看起来像一个线性回归模型。这种简单的神经网络没有任何隐藏层,被命名为感知器。这种没有任何层的简单神经网络,感知器,成为了神经网络的基本单元。实质上,感知器是生物神经元的数学模拟,因此是更复杂神经网络的基本构建块。

现在,让我们深入了解人工智能AI)演化历史的简明史账。

AI 寒冬与 AI 春天的曙光

对感知器这一突破性概念的最初热情在其重大局限性被发现后迅速消退。1969 年,马文·明斯基和西摩·帕珀特进行了深入研究,揭示了感知器在学习能力上的局限性。他们发现,感知器无法学习和处理复杂的逻辑函数,甚至在处理像异或(XOR)这样的简单逻辑函数时也存在困难。

这一发现引发了对机器学习ML)和神经网络兴趣的显著下降,开启了一个通常被称为“人工智能寒冬”的时代。这一时期,全球研究界普遍对人工智能的潜力表示怀疑,认为其不足以解决复杂问题。

回顾起来,“人工智能寒冬”在某种程度上是当时硬件能力受限的结果。那时的硬件要么缺乏必要的计算能力,要么过于昂贵,这极大地阻碍了人工智能的进展。这一限制阻碍了人工智能的应用和发展,导致人们普遍对其潜力感到失望。

到了 1990 年代末,关于人工智能及其潜力的看法发生了巨大变化。推动这一变化的催化剂是分布式计算的发展,它提供了易于获取和负担得起的基础设施。看到人工智能的潜力,当时新崛起的 IT 巨头(如谷歌)将人工智能作为其研发的重点。这种对人工智能的重新兴趣导致了所谓“人工智能寒冬”的解冻。这一解冻重新激发了对人工智能的研究,最终使得当前时代成为一个可以称之为人工智能春天的时代,大家对人工智能和神经网络充满兴趣。此外,数字化数据当时尚未普及。

理解神经网络

首先,让我们从神经网络的核心——感知器开始。你可以把一个单独的感知器看作是最简单的神经网络,它是现代复杂多层架构的基本构建模块。让我们从理解感知器的工作原理开始。

理解感知器

一个单一的感知器有多个输入和一个输出,该输出由激活函数控制或激活。如下图图 8.2所示:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_02.png

图 8.2:一个简单的感知器

图 8.2中显示的感知机有三个输入特征;x[1],x[2],和x[3]。我们还加入了一个常数信号,称为偏置。偏置在我们的神经网络模型中起着关键作用,因为它允许在拟合数据时具有灵活性。它的作用类似于线性方程中添加的截距——作为激活函数的一种“偏移”——从而使我们在输入为零时能够更好地拟合数据。输入特征和偏置与权重相乘并求和,得到加权和 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_001.png。这个加权和会传递给激活函数,产生输出 y。能够使用多种激活函数来制定特征与标签之间的复杂关系是神经网络的一个优势。通过超参数可以选择多种激活函数。一些常见的例子包括 sigmoid 函数,它将值压缩到 0 到 1 之间,是二分类问题的好选择;tanh 函数,它将值缩放到 -1 到 1 之间,提供零中心的输出;以及修正线性单元ReLU)函数,它将向量中的所有负值设为零,有效地去除任何负面影响,并且在卷积神经网络中常常被使用。接下来,本章将详细讨论这些激活函数。

现在让我们来探讨一下神经网络背后的直觉。

理解神经网络背后的直觉

在上一章中,我们讨论了一些传统的机器学习算法。这些传统算法在许多重要的应用场景中表现优异,但它们也有一定的局限性。当训练数据集中的潜在模式开始变得非线性和多维时,传统机器学习算法的能力已经无法准确捕捉特征与标签之间复杂的关系。这些不完备的、相对简化的数学公式化表示复杂模式,导致在这些用例中的训练模型表现不佳。

在现实世界的场景中,我们经常遇到特征与标签之间的关系不是线性或简单的,而是呈现出复杂的模式。这正是神经网络的优势所在,它为我们提供了一个强大的工具,用于建模这些复杂性。

神经网络在处理高维数据或特征与结果之间的关系是非线性的情况下特别有效。例如,它们在图像和语音识别等应用中表现出色,其中输入数据(像素或声波)具有复杂的层级结构。传统的机器学习算法可能在这些情况下表现不佳,因为特征之间关系的高度复杂性和非线性。

虽然神经网络是非常强大的工具,但我们必须承认它们并非没有局限性。这些限制将在本章后面详细探讨,对于神经网络在解决现实问题中的有效应用,了解这些限制至关重要。

现在,让我们举例说明使用更简单的机器学习算法,如线性回归时常见的模式及其相关挑战。假设我们正在尝试根据“受教育年限”预测数据科学家的工资。我们从两个不同的组织收集了两个数据集。

首先,让我们介绍数据集 1,如*图 8.3(a)*所示。它描述了特征(受教育年限)与标签(工资)之间的相对简单的关系,看起来是线性的。然而,即使是这个简单的模式,在我们尝试用线性算法进行数学建模时,也会遇到一些挑战:

  • 我们知道,工资不能为负数,这意味着无论受教育年限如何,工资(y)都不应小于零。

  • 至少有一位刚毕业的初级数据科学家,可能只用了“x[1]”年的教育时间,但目前工资为零,可能是实习生。因此,在“x”的取值范围从零到“x[1]”时,工资“y”保持为零,如*图 8.3(a)*所示。

有趣的是,我们可以利用神经网络中可用的修正线性激活函数来捕捉特征与标签之间这种复杂的关系,这是我们后面将探讨的一个概念。

接下来,我们来看数据集 2,如*图 8.3(b)*所示。这个数据集表示特征与标签之间的非线性关系。其工作原理如下:

  1. 当“x” (受教育年限)从零变化到“x[1]”时,工资“y”保持为零。

  2. 当“x”接近“x[2]”时,工资急剧增加。

  3. 但一旦“y”超过“x[2]”,工资将达到平稳状态并趋于平坦。

正如我们将在本书后面看到的,我们可以在神经网络框架内使用 Sigmoid 激活函数来建模此类关系。理解这些模式并知道何时应用合适的工具,是有效利用神经网络强大功能的关键:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_03.png

图 8.3:工资与受教育年限

(a) 数据集 1:线性模式 (b) 数据集 2:非线性模式

理解分层深度学习架构

对于更复杂的问题,研究人员开发了一种多层神经网络,称为多层感知器。一个多层神经网络有几个不同的层,如下图所示。这些层如下:

  • 输入层:第一层是输入层。在输入层,特征值作为输入被馈送到网络中。

  • 隐藏层:输入层后面跟随一个或多个隐藏层。每个隐藏层都是类似激活函数的数组。

  • 输出层:最后一层称为输出层。

一个简单的神经网络会有一个隐藏层。一个深度神经网络是一个有两个或更多隐藏层的神经网络。见图 8.4

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_04.png

图 8.4:简单神经网络与深度神经网络

接下来,让我们尝试理解隐藏层的功能。

培养对隐藏层的直觉

在神经网络中,隐藏层在解释输入数据中起着关键作用。隐藏层在神经网络中以层级结构有序组织,每一层对其输入数据执行独特的非线性转换。这种设计允许从输入中提取逐渐更抽象、更细致的特征。

以卷积神经网络为例,卷积神经网络是专为图像处理任务设计的神经网络子类型。在这个背景下,较低的隐藏层专注于辨别图像中的简单局部特征,如边缘和角落。这些特征虽然是基础的,但单独来看并没有太大意义。

随着我们深入到隐藏层,这些层开始连接起各个点。可以说,它们将较低层检测到的基本模式整合成更复杂、更有意义的结构。结果,本来杂乱无章的边缘和角落,转变为可识别的形状和模式,从而赋予网络一定的“视觉”。

这个逐步转换的过程将未经处理的像素值转化为精细的特征和模式映射,从而实现诸如指纹识别等高级应用。在这里,网络能够识别指纹中脊线和谷线的独特排列,将这些原始的视觉数据转换为独特的身份标识。因此,隐藏层将原始数据转换并提炼成有价值的洞察。

应该使用多少个隐藏层?

请注意,隐藏层的最佳数量会根据问题的不同而有所变化。对于某些问题,应该使用单层神经网络。这些问题通常表现出简单的模式,可以通过简洁的网络设计轻松捕捉和表达。对于其他问题,我们应增加多个层以获得最佳性能。例如,如果你正在处理一个复杂的问题,如图像识别或自然语言处理,可能需要一个具有多个隐藏层和每层更多节点的神经网络。

数据的潜在模式的复杂性将很大程度上影响你的网络设计。例如,对于简单问题使用过于复杂的神经网络可能导致过拟合,使得你的模型过度拟合训练数据,并且在新的、未见过的数据上表现不佳。另一方面,过于简单的模型可能会导致欠拟合,即模型未能捕捉数据中的关键模式。

此外,激活函数的选择也起着关键作用。例如,如果你的输出需要是二元的(如是/否问题),则可以使用 sigmoid 函数。对于多分类问题,softmax 函数可能更为合适。

最终,选择神经网络架构的过程需要仔细分析你的问题,并进行实验和微调。在这个过程中,开发一个基准实验模型可能会很有帮助,这样你可以通过迭代调整和优化网络设计,以达到最佳性能。

接下来,我们来看看神经网络的数学基础。

神经网络的数学基础

理解神经网络的数学基础是发挥其能力的关键。虽然它们看起来复杂,但其原理基于熟悉的数学概念,如线性代数、微积分和概率论。神经网络的魅力在于其从数据中学习并随着时间推移不断改进的能力,这些特性源于它们的数学结构:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_05.png

图 8.5:多层感知机

图 8.5 显示了一个四层神经网络。在这个神经网络中,一个重要的要点是,神经元是该网络的基本单元,并且每一层的神经元都与下一层的所有神经元相连接。对于复杂的网络,这些连接的数量会急剧增加,我们将探索在不牺牲太多质量的情况下减少这些连接的不同方法。

首先,让我们尝试表述我们要解决的问题。

输入是一个特征向量 x,其维度为 n

我们希望神经网络能够预测值。预测值用 ý 表示。

从数学角度看,我们想要确定,在给定特定输入的情况下,交易是欺诈的概率。换句话说,给定 x 的特定值,y = 1 的概率是多少?从数学角度看,我们可以表示为:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_002.png

请注意,x 是一个 n[x] 维的向量,其中 n[x] 是输入变量的数量。

图 8.6所示,神经网络有四层。输入层和输出层之间的层称为隐藏层。第一层隐藏层中的神经元数量用https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_003.png表示。各个节点之间的连接由被称为权重的参数乘以。训练神经网络的过程本质上是围绕着确定与网络中各个神经元连接相关的权重的最优值。通过调整这些权重,网络可以调整其计算并随着时间的推移提高性能。

让我们看看如何训练一个神经网络。

训练神经网络

使用给定数据集构建神经网络的过程称为训练神经网络。让我们深入了解典型神经网络的结构。当我们谈论训练神经网络时,我们是在谈论为权重计算最佳值。训练是通过使用一组以训练数据形式呈现的示例进行的。训练数据中的示例为不同输入值组合的输出提供了预期值。神经网络的训练过程与传统模型的训练方式不同(这在第七章传统监督学习算法中有讨论)。

理解神经网络的结构

让我们看看神经网络由哪些部分组成:

  • :层是神经网络的核心构建块。每一层是一个数据处理模块,充当过滤器。它接收一个或多个输入,以某种方式处理这些输入,然后生成一个或多个输出。每次数据通过一层时,它都会经历一个处理阶段,并展示与我们试图回答的业务问题相关的模式。

  • 损失函数:损失函数提供了在学习过程的各个迭代中使用的反馈信号。损失函数为单个示例提供偏差。

  • 成本函数:成本函数是完整示例集上的损失函数。

  • 优化器:优化器决定如何解释损失函数提供的反馈信号。

  • 输入数据:输入数据是用于训练神经网络的数据。它指定了目标变量。

  • 权重:权重通过训练网络来计算。权重大致对应于每个输入的重要性。例如,如果某个输入比其他输入更重要,那么在训练后,它将被赋予更大的权重值,作为乘数。即使这个重要输入的信号较弱,它也会因为较大的权重值(作为乘数的作用)而增强。因此,权重最终根据输入的重要性调整每个输入的影响。

  • 激活函数:这些值会被不同的权重乘以,然后汇总。它们如何被汇总以及如何解读其值,将由所选择的激活函数的类型决定。

现在,让我们来看看神经网络训练中一个非常重要的方面。

在训练神经网络时,我们会逐一处理每一个样本。对于每一个样本,我们使用正在训练的模型生成输出。术语“正在训练”指的是模型的学习状态,此时模型仍在调整并从数据中学习,尚未达到最佳性能。在这个阶段,模型参数,如权重,持续更新和调整,以提高其预测性能。我们计算期望输出与预测输出之间的差异。对于每个单独的样本,这个差异被称为损失。所有样本的损失加起来,就是代价。随着训练的进行,我们的目标是找到合适的权重值,以使得损失值最小化。在整个训练过程中,我们会不断调整权重值,直到找到一组能使总体代价最小的权重值。一旦我们达到最小代价,就标志着模型已经训练完成。

定义梯度下降

训练神经网络的核心目标是确定权重的正确值,这些权重像“旋钮”或“调节器”一样,通过调整它们来最小化模型预测与实际值之间的差异。

当训练开始时,我们使用随机或默认值初始化这些权重。然后,我们使用优化算法逐步调整它们,常用的优化算法是“梯度下降”,以逐步改进模型的预测结果。

让我们深入了解梯度下降算法。梯度下降的旅程从我们设置的初始随机权重值开始。

从这个起点开始,我们迭代并在每一步调整这些权重,使得我们更接近最小代价。

为了更清楚地说明这一点,假设我们的数据特征是输入向量X。目标变量的真实值是Y,而我们模型预测的值是Y。我们衡量实际值与预测值之间的差异或偏差,这个差异就是我们的损失。

然后,我们更新权重,考虑到两个关键因素:移动的方向和步伐的大小,也就是学习率。

“方向”告诉我们该朝哪个方向移动,以找到损失函数的最小值。可以把它想象成下坡——我们希望沿着坡度最陡的地方“下坡”,这样可以最快到达底部(即最小损失)。

“学习率”决定了我们在选择的方向上的步长大小。就像决定是走下山坡还是跑下去——较大的学习率意味着更大的步伐(像奔跑),而较小的学习率意味着较小的步伐(像走路)。

这个迭代过程的目标是达到一个我们无法继续“下坡”的点,意味着我们已经找到了最小的成本,表示我们的权重已经最优,模型也已经很好地训练完成。

这个简单的迭代过程在下图中展示:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_06.png

图 8.6:梯度下降算法,寻找最小值

图示展示了通过调整权重,梯度下降如何尝试找到最小的成本。学习率和选择的方向将决定图表中下一个要探索的点。

选择正确的学习率非常重要。如果学习率太小,问题可能需要很长时间才能收敛。如果学习率过高,问题将无法收敛。在前面的图示中,表示当前解的点会在图表的两条对立线之间不断摆动。

现在,让我们来看看如何最小化梯度。仅考虑两个变量,xyxy 的梯度计算如下:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_004.png

为了最小化梯度,可以使用以下方法:

def adjust_position(gradient):
    while gradient != 0:
        if gradient < 0:
            print("Move right")
            # here would be your logic to move right
        elif gradient > 0:
            print("Move left")
            # here would be your logic to move left 

该算法还可以用于寻找神经网络权重的最优或近似最优值。

请注意,梯度下降的计算是从网络的后端开始进行的。我们首先计算最终层的梯度,然后是倒数第二层的梯度,再然后是之前的层,一直到达第一层。这就是所谓的反向传播,它是由 Hinton、Williams 和 Rumelhart 于 1985 年提出的。

接下来,让我们深入探讨激活函数。

激活函数

激活函数制定了如何处理特定神经元的输入以生成输出的方式。

图 8.7所示,神经网络中的每个神经元都有一个激活函数,决定了如何处理输入数据:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_07.png

图 8.7:激活函数

在前面的图示中,我们可以看到由激活函数生成的结果被传递到输出端。激活函数设定了如何解释输入值以生成输出的标准。

对于完全相同的输入值,不同的激活函数将产生不同的输出。理解如何选择正确的激活函数在使用神经网络解决问题时非常重要。

现在,让我们逐一看看这些激活函数。

步骤函数

最简单的激活函数是阈值函数。阈值函数的输出是二值的:0 或 1。如果任何输入大于 1,它将生成 1 作为输出。这可以通过 图 8.8 来解释:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_08.png

图 8.8:阶跃函数

尽管其简单性,阈值激活函数在我们需要输出之间清晰划分时起着重要作用。使用此函数,只要输入的加权和中有任何非零值,输出 (y) 就会变为 1。然而,它的简单性也带来了缺点——该函数极其敏感,可能会因为输入中的微弱信号或噪声而被错误触发。

例如,考虑一种情况,其中神经网络使用此函数将电子邮件分类为“垃圾邮件”或“非垃圾邮件”。在这里,输出 1 可能表示“垃圾邮件”,而 0 可能表示“非垃圾邮件”。某个特征(如某些关键垃圾邮件词汇)的最轻微出现可能会触发该函数将电子邮件分类为“垃圾邮件”。因此,尽管它在某些应用中是一个有价值的工具,但在输入数据中噪声或轻微变化常见的情况下,应该考虑其过度敏感性的潜力。接下来,让我们深入了解 Sigmoid 函数。

Sigmoid 函数

Sigmoid 函数可以视为阈值函数的一种改进。在这里,我们可以控制激活函数的敏感性:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_09.png

图 8.9:Sigmoid 激活函数

Sigmoid 函数 y 定义如下,并在 图 8.9 中显示:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_005.png

可以通过以下方式在 Python 中实现:

def sigmoidFunction(z):
      return 1/ (1+np.exp(-z)) 

上面的代码使用 Python 演示了 Sigmoid 函数。这里,np.exp(-z) 是对 -z 应用的指数运算,结果加上 1 构成方程的分母,从而得到一个介于 0 和 1 之间的值。

通过 Sigmoid 函数降低激活函数的敏感性,使其不易受到输入中的突变或“故障”影响。然而,值得注意的是,输出仍然是二值的,意味着它只能是 0 或 1。

Sigmoid 函数广泛应用于二分类问题,其中输出预期为 0 或 1。例如,如果你正在开发一个模型来预测电子邮件是否是垃圾邮件(1)或非垃圾邮件(0),则 Sigmoid 激活函数将是一个合适的选择。

现在,让我们深入了解 ReLU 激活函数。

ReLU

本章介绍的前两个激活函数的输出是二进制的。这意味着它们会将一组输入变量转换为二进制输出。ReLU 是一种激活函数,它将一组输入变量作为输入,并将其转换为单一的连续输出。在神经网络中,ReLU 是最流行的激活函数,通常用于隐藏层,我们不希望将连续变量转换为类别变量。

以下图表总结了 ReLU 激活函数:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_10.png

图 8.10:ReLU

请注意,当x ≤ 0 时,这意味着y = 0。这意味着输入中任何为零或小于零的信号都会被转换为零输出:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_006.png

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_007.png

一旦x变为大于零,它就是x

ReLU 函数是神经网络中最常用的激活函数之一。它可以在 Python 中按如下方式实现:

def relu(x):
    if x < 0:
        return 0
    else:
        return x 

现在让我们看看基于 ReLU 的 Leaky ReLU。

Leaky ReLU

在 ReLU 中,x的负值会导致y的值为零。这意味着在过程中丢失了一些信息,这使得训练周期特别在训练初期变得更长。Leaky ReLU 激活函数解决了这个问题。以下内容适用于 Leaky ReLU:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_008.png

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_007.png

如下图所示:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_11.png

图 8.11:Leaky ReLU

这里,https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_07_033.png是一个值小于 1 的参数。

它可以在 Python 中按如下方式实现:

def leaky_relu(x, beta=0.01):
    if x < 0:
        return beta * x    
    else:        
        return x 

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_011.png赋值有多种策略:

双曲正切(tanh)

双曲正切函数,或称为 tanh,与 sigmoid 函数密切相关,其主要区别在于:它可以输出负值,从而提供一个更广泛的输出范围,介于 -11 之间。这在我们想要建模包含正负影响的现象时非常有用。图 8.12 展示了这一点:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_12.png

图 8.12:双曲正切

y 函数如下:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_016.png

它可以通过以下 Python 代码实现:

import numpy as np
def tanh(x): 
    numerator = 1 - np.exp(-2 * x) 
    denominator = 1 + np.exp(-2 * x) 
    return numerator / denominator 

在这段 Python 代码中,我们使用了 numpy 库,简称 np,来处理数学运算。tanh 函数,像 sigmoid 一样,是神经网络中的一种激活函数,用于为模型引入非线性。它通常在神经网络的隐藏层中优于 sigmoid,因为它通过将输出的均值设置为 0 来使数据居中,从而使得下一个层的学习更加容易。然而,选择 tanhsigmoid 或其他激活函数,主要取决于你正在处理的模型的具体需求和复杂性。

接下来,让我们深入探讨一下 softmax 函数。

Softmax

有时,我们需要激活函数的输出有多个层级。Softmax 就是一种激活函数,它为我们提供了超过两个层级的输出。它最适合用于多分类问题。假设我们有 n 个类别。我们有输入值,这些输入值将类别映射如下:

x = {x((1))*,x*((2)),…x^((n))}

Softmax 操作基于概率理论。对于二分类器,最后一层的激活函数将是 sigmoid,而对于多分类器,则使用 softmax。举个例子,假设我们要对一张水果图片进行分类,类别为 apple(苹果)、banana(香蕉)、cherry(樱桃)和 date(枣)。Softmax 函数会计算这张图片属于每个类别的概率。概率最高的类别会被认为是预测结果。

为了在 Python 代码和公式中解释这一点,让我们来看以下内容:

import numpy as np
def softmax(x): 
    return np.exp(x) / np.sum(np.exp(x), axis=0) 
numpy library (np) to perform the mathematical operations. The softmax function takes an array of x as input, applies the exponential function to each element, and normalizes the results so that they sum up to 1, which is the total probability across all classes.

现在,让我们看一下与神经网络相关的各种工具和框架。

工具和框架

本节将深入探讨一些专门为便于实现神经网络而开发的工具和框架。每个框架都有其独特的优点和可能的局限性。

在众多可用的选项中,我们选择重点介绍 Keras,它是一个高级神经网络 API,可以在 TensorFlow 之上运行。你可能会问,为什么是 Keras 和 TensorFlow?这两个结合起来提供了多个显著的优势,成为了业内实践者的热门选择。

首先,Keras 因其用户友好且模块化的特点,简化了构建和设计神经网络模型的过程,既适合初学者也适合有经验的用户。其次,它与 TensorFlow 的兼容性——TensorFlow 是一个强大的端到端开源机器学习平台——确保了其健壮性和多功能性。TensorFlow 提供的高计算性能是其另一个宝贵资产。两者结合,形成了一个动态组合,在可用性和功能性之间取得了平衡,使其成为神经网络模型开发和部署的绝佳选择。

在接下来的章节中,我们将探讨如何使用具有 TensorFlow 后端的 Keras 来构建神经网络。

Keras

Keras (www.tensorflow.org/guide/keras) 是一个非常流行且易于使用的神经网络库,使用 Python 编写。它的编写目标是易用性,并提供了实现深度学习的最快方式。Keras 仅提供高级模块,被视为模型级别的工具。

现在,让我们看看 Keras 的各种后端引擎。

Keras 的后端引擎

Keras 需要一个较低级别的深度学习库来执行张量级别的操作。这个基础层被称为“后端引擎”。

简单来说,张量级别的操作涉及对多维数据数组(即张量)进行计算和转换,张量是神经网络中使用的主要数据结构。这个较低级别的深度学习库被称为后端引擎。Keras 的后端引擎可能包括以下几种:

这种模块化深度学习技术栈的格式如下面的图所示:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_13.png

图 8.13:Keras 架构

这种模块化的深度学习架构的优势在于,Keras 的后端可以在不重写任何代码的情况下进行更改。例如,如果我们发现 TensorFlow 在某个特定任务上比 Theano 更好,我们可以简单地将后端更改为 TensorFlow,而无需重写代码。

接下来,让我们深入了解深度学习栈的低级层次。

深度学习栈的低级层次

我们刚才提到的三种后端引擎都可以在 CPU 和 GPU 上运行,使用堆栈的低级层。对于 CPU,使用一个低级的张量操作库Eigen。对于 GPU,TensorFlow 使用 NVIDIA 的CUDA 深度神经网络cuDNN)库。值得解释的是,为什么在机器学习中通常更偏爱 GPU。

虽然 CPU 具有多功能性和强大性能,但 GPU 是专门设计用来同时处理多个操作的,这在处理大量数据时尤其有利,而这在机器学习任务中非常常见。GPU 的这一特性,加上更高的内存带宽,可以显著加快机器学习计算,因此它们成为这些任务的流行选择。

接下来,让我们解释一下超参数。

定义超参数

第六章无监督机器学习算法》中讨论的那样,超参数是一个在学习过程开始之前选择的参数值。我们通常从常识性的值开始,然后尝试优化它们。对于神经网络,重要的超参数包括:

  • 激活函数

  • 学习率

  • 隐藏层的数量

  • 每个隐藏层中的神经元数量

让我们看看如何使用 Keras 定义一个模型。

定义一个 Keras 模型

定义完整 Keras 模型涉及三个步骤:

  1. 定义层

  2. 定义学习过程

  3. 测试模型

我们可以使用Keras有两种方式来构建模型:

  • 函数式 API:这允许我们为无环图的层架构模型。可以使用函数式 API 创建更复杂的模型。

  • 顺序 API:这允许我们为线性堆叠的层架构模型。它适用于相对简单的模型,是构建模型时的常用选择。

首先,我们来看一下使用顺序方式定义 Keras 模型:

  1. 让我们从导入tensorflow库开始:

    import tensorflow as tf 
    
  2. 然后,从 Keras 的 datasets 加载 MNIST 数据集:

    mnist = tf.keras.datasets.mnist 
    
  3. 接下来,将数据集拆分为训练集和测试集:

    (train_images, train_labels), (test_images, test_labels) = mnist.load_data() 
    
  4. 我们将像素值从255的比例归一化到1的比例:

    train_images, test_images = train_images / 255.0,                             test_images / 255.0 
    
  5. 接下来,我们定义模型的结构:

    model = tf.keras.models.Sequential([
        tf.keras.layers.Flatten(input_shape=(28, 28)),
        tf.keras.layers.Dense(128, activation='relu'),
        tf.keras.layers.Dropout(0.15),
        tf.keras.layers.Dense(128, activation='relu'),
        tf.keras.layers.Dropout(0.15),
        tf.keras.layers.Dense(10, activation='softmax'),
    ]) 
    

该脚本正在训练一个模型来分类MNIST数据集中的图像,该数据集包含 70,000 张由高中生和美国人口普查局员工手写的数字小图像。

模型使用 Keras 中的Sequential方法定义,表示我们的模型是一个线性堆叠的层:

  1. 第一层是一个Flatten层,它将图像的格式从二维数组转换为一维数组。

  2. 下一层是一个Dense层,它是一个全连接神经层,包含 128 个节点(或神经元)。此处使用relu(ReLU)激活函数。

  3. Dropout层在每次训练时随机将输入单元设置为0,其频率由每步的率控制,帮助防止过拟合。

  4. 另一个Dense层被包含在内,类似于前一个层,它也使用relu激活函数。

  5. 我们再次应用一个Dropout层,使用与之前相同的比率。

  6. 最后一层是一个 10 节点的 softmax 层——它返回一个包含 10 个概率分数的数组,总和为1。每个节点包含一个分数,表示当前图像属于 10 个数字类别中的某一类别的概率。

请注意,在这里,我们创建了三层——前两层使用relu激活函数,第三层使用softmax作为激活函数。

现在,让我们来看一下使用 Functional API 定义 Keras 模型的方式:

  1. 首先,我们导入tensorflow库:

    # Ensure TensorFlow 2.x is being used
    %tensorflow_version 2.x
    import tensorflow as tf
    from tensorflow.keras.datasets import mnist 
    
  2. 为了使用MNIST数据集,我们首先将其加载到内存中。该数据集已经方便地分为训练集和测试集,包含了图像和相应的标签:

    # Load MNIST dataset
    (train_images, train_labels), (test_images, test_labels) = mnist.load_data()
    # Normalize the pixel values to be between 0 and 1
    train_images, test_images = train_images / 255.0, test_images / 255.0 
    
  3. MNIST数据集中的图像大小为28x28像素。在使用 TensorFlow 设置神经网络模型时,需要指定输入数据的形状。在这里,我们为模型建立了输入张量:

    inputs = tf.keras.Input(shape=(28,28)) 
    
  4. 接下来,Flatten层是一个简单的数据预处理步骤。它通过“拉平”输入,将二维的128x128像素图像转换为一维数组。这样可以为后续的Dense层做准备:

    x = tf.keras.layers.Flatten()(inputs) 
    
  5. 接下来是第一个Dense层,也称为全连接层,其中每个输入节点(或神经元)都与每个输出节点相连接。该层有 512 个输出节点,并使用relu激活函数。ReLU 是一个流行的激活函数,它在输入为正时直接输出输入值;否则输出零:

    x = tf.keras.layers.Dense(512, activation='relu', name='d1')(x) 
    
  6. Dropout层会在每次训练更新时随机将输入节点的一部分(在本例中为 0.2 或 20%)设置为 0,这有助于防止过拟合:

    x = tf.keras.layers.Dropout(0.2)(x) 
    
  7. 最后是输出层。它是另一个Dense层,包含 10 个输出节点(假设是 10 个类别)。应用softmax激活函数,该函数输出一个概率分布,覆盖 10 个类别,这意味着它将输出 10 个值,总和为 1。每个值表示模型对输入图像对应某一特定类别的置信度:

    predictions = tf.keras.layers.Dense(10, activation=tf.nn.softmax, name='d2')(x)
    model = tf.keras.Model(inputs=inputs, outputs=predictions) 
    

请注意,我们可以使用 Sequential 和 Functional 两种 API 定义相同的神经网络。从性能角度来看,选择哪种方式定义模型并没有区别。

让我们将数值型的train_labelstest_labels转换为 one-hot 编码向量。在下面的代码中,每个标签都会变成一个大小为 10 的二进制数组,其中相应数字的索引位置为 1,其余位置为 0:

# One-hot encode the labels
train_labels_one_hot = tf.keras.utils.to_categorical(train_labels, 10)
test_labels_one_hot = tf.keras.utils.to_categorical(test_labels, 10) 

我们现在应该定义学习过程。

在这一步,我们定义三项内容:

  • 优化器

  • loss函数

  • 将量化模型质量的度量标准:

optimizer = tf.keras.optimizers.RMSprop()
loss = 'categorical_crossentropy'
metrics = ['accuracy']
model.compile(optimizer=optimizer, loss=loss, metrics=metrics) 

请注意,我们使用model.compile函数来定义优化器、损失函数和度量标准。

现在我们将训练模型。

一旦架构定义完成,就可以开始训练模型:

history = model.fit(train_images, train_labels_one_hot, epochs=10, validation_data=(test_images, test_labels_one_hot)) 

请注意,像batch_sizeepochs这样的参数是可配置的参数,因此它们是超参数。

接下来,让我们深入探讨如何选择顺序模型或功能模型。

选择顺序模型或功能模型

在决定使用顺序模型还是功能模型来构建神经网络时,网络架构的性质将指导你的选择。顺序模型适用于简单的线性层堆叠。它实现起来简单直接,是初学者或处理简单任务的理想选择。然而,这种模型有一个关键的限制:每一层只能连接到一个输入张量和一个输出张量。

如果你的网络架构更加复杂,例如在任何阶段(输入层、输出层或隐藏层)有多个输入或输出,那么顺序模型就不再适用。对于这种复杂的架构,功能模型更为合适。该模型提供了更高的灵活性,允许在任何层具有多个输入和输出,从而支持更复杂的网络结构。现在让我们更深入地理解 TensorFlow。

了解 TensorFlow

TensorFlow 是最流行的神经网络工作库之一。在前面的部分中,我们看到它如何作为 Keras 的后台引擎使用。它是一个开源的高性能库,实际上可以用于任何数值计算。

如果我们看看堆栈,我们可以看到,我们可以使用像 Python 或 C++这样的高级语言编写 TensorFlow 代码,这些代码会被 TensorFlow 分布式执行引擎解释执行。这使得它对开发者来说非常有用并且广受欢迎。

TensorFlow 通过使用有向图DG)来体现你的计算。在这个图中,节点是数学运算,连接这些节点的边代表这些运算的输入和输出。此外,这些边还表示数据数组。

除了作为 Keras 的后台引擎,TensorFlow 还广泛应用于各种场景中。它可以帮助开发复杂的机器学习模型、处理大规模数据集,甚至在不同平台上部署 AI 应用。无论你是在创建推荐系统、图像分类模型,还是自然语言处理工具,TensorFlow 都能有效地满足这些任务以及更多需求。

介绍 TensorFlow 的基本概念

让我们简要了解一下 TensorFlow 中的概念,比如标量、向量和矩阵。我们知道,像三或五这样的简单数字,在传统数学中被称为标量。此外,在物理学中,向量是具有大小和方向的量。在 TensorFlow 中,我们用向量表示一维数组。扩展这一概念,二维数组即为矩阵。对于三维数组,我们使用3D 张量这一术语。我们使用来表示数据结构的维度。因此,标量秩 0的数据结构,向量秩 1的数据结构,矩阵秩 2的数据结构。这些多维结构被称为张量,并在以下图表中展示:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_14.png

图 8.14:多维结构或张量

如我们在前面的图表中看到的,秩定义了张量的维度。

现在,让我们来看另一个参数,shapeshape是一个整数元组,指定每个维度中数组的长度。

以下图表解释了shape的概念:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_15.png

图 8.15:形状的概念

使用shape和秩,我们可以指定张量的详细信息。

理解张量数学

现在让我们看一下使用张量进行的不同数学运算:

  • 让我们定义两个标量,并尝试使用 TensorFlow 进行加法和乘法运算:

    print("Define constant tensors")
    a = tf.constant(2)
    print("a = %i" % a)
    b = tf.constant(3)
    print("b = %i" % b) 
    
    Define constant tensors
    a = 2
    b = 3 
    
  • 我们可以对其进行加法和乘法运算并展示结果:

    print("Running operations, without tf.Session")
    c = a + b
    print("a + b = %i" % c)
    d = a * b
    print("a * b = %i" % d) 
    
    Running operations, without tf.Session
    a + b = 5
    a * b = 6 
    
  • 我们还可以通过将两个张量相加来创建一个新的标量张量:

    c = a + b
    print("a + b = %s" % c) 
    
    a + b = tf.Tensor(5, shape=(), dtype=int32) 
    
  • 我们还可以执行复杂的张量运算:

    d = a*b
    print("a * b = %s" % d) 
    
    a * b = tf.Tensor(6, shape=(), dtype=int32) 
    

理解神经网络的类型

神经网络可以根据神经元的互联方式进行不同的设计。在密集型或全连接的神经网络中,给定层中的每个神经元都与下一层的每个神经元相连接。这意味着来自前一层的每个输入都会传递到下一层的每个神经元,从而最大化信息流动。

然而,神经网络并不总是完全连接的。有些网络可能基于其解决问题的需求,具有特定的连接模式。例如,在用于图像处理的卷积神经网络中,某一层的每个神经元可能只与上一层中一小块区域的神经元相连接。这与人类视觉皮层中神经元的组织方式相似,并帮助网络高效处理视觉信息。

记住,神经网络的具体架构——神经元如何互联——对其功能和性能有着极大的影响。

卷积神经网络

卷积神经网络CNNs)通常用于分析多媒体数据。为了深入了解 CNN 如何分析基于图像的数据,我们需要掌握以下过程:

  • 卷积

  • 池化

让我们逐一探索它们。

卷积

卷积过程通过使用一个称为滤波器(也叫卷积核)的小图像处理特定图像,强调图像中的某些模式。例如,如果我们想找到图像中物体的边缘,我们可以将图像与特定的滤波器卷积,从而得到边缘。边缘检测有助于物体检测、物体分类等应用。因此,卷积过程就是在图像中寻找特征和特性。

查找模式的方法是基于找到可以在不同数据上重用的模式。这些可重用的模式被称为滤波器或卷积核。

池化

为了进行机器学习,处理多媒体数据的重要部分是下采样。下采样是减少数据分辨率的过程,即降低数据的复杂性或维度。池化提供了两个主要优点:

  • 通过降低数据的复杂性,我们可以显著减少模型的训练时间,提高计算效率。

  • 池化抽象并聚合了多媒体数据中的不必要细节,使其更加通用。反过来,这增强了模型表示相似问题的能力。

下采样过程如下:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_16.png

图 8.16:下采样

在下采样过程中,我们本质上是将一组像素压缩成一个代表性像素。例如,我们可以将一个 2x2 像素块压缩成一个像素,从而将原始数据的分辨率减少四倍。

新像素的代表值可以通过多种方式选择。其中一种方法是“最大池化”,在这种方法中,我们从原始像素块中选择最大值来表示新的单一像素。

另一方面,如果我们选择取像素块值的平均值,这个过程将被称为“平均池化”。

最大池化与平均池化的选择通常取决于具体任务。最大池化在我们希望保留图像中最显著特征时特别有用,因为它保留了一个块中的最大像素值,从而捕捉到该部分中最突出或最显眼的特征。

相比之下,平均池化在我们希望保留整体背景并减少噪声时通常更有用,因为它考虑了块内的所有值并计算它们的平均值,从而创建一个更平衡的表示,可能对像素值中的细微变化或噪声不太敏感。

生成对抗网络

生成对抗网络,通常称为 GAN,是一种能够生成合成数据的神经网络类别。它由 Ian Goodfellow 及其团队于 2014 年首次提出,因其创新的方法而受到赞誉,能够创建与原始训练样本相似的新数据。

生成对抗网络(GAN)一个显著的应用是能够生成现实中不存在的人的逼真图像,展示了它们在细节生成上的非凡能力。然而,更为关键的应用在于它们能够生成合成数据,从而扩充现有的训练数据集,在数据可用性有限的情况下,这种应用非常有价值。

尽管生成对抗网络(GAN)具有潜力,但它们并非没有局限性。GAN 的训练过程可能相当具有挑战性,常常导致一些问题,如模式崩溃,生成器开始产生有限种类的样本。此外,生成数据的质量在很大程度上取决于输入数据的质量和多样性。数据不具代表性或有偏差时,可能导致合成数据的效果不佳,甚至可能偏向某些特定方向。

在接下来的章节中,我们将探讨什么是迁移学习。

使用迁移学习

多年来,无数组织、研究机构和开源社区的贡献者们精心构建了适用于一般用途的复杂模型。这些模型通常通过大量数据进行训练,经过多年的努力优化,适用于各种应用场景,如:

  • 检测视频或图像中的物体

  • 转录音频

  • 分析文本情感

在启动新的机器学习模型训练时,不妨考虑一个问题:与其从零开始,是否可以修改已有的预训练模型来满足我们的需求。简而言之,我们能否利用现有模型的学习成果,定制一个适应我们特定需求的模型?这种方法被称为迁移学习,具有以下几项优势:

  • 它为我们的模型训练提供了一个良好的起点。

  • 它通过利用一个经过预验证和可靠的模型,可能提升我们模型的质量。

  • 在我们的问题缺乏足够数据的情况下,使用预训练模型进行迁移学习可以提供极大的帮助。

考虑以下实际例子,在这些场景下,迁移学习将大有裨益:

  • 在训练机器人时,可以先通过一个模拟游戏来训练神经网络模型。在这个受控环境中,我们可以创建一些在现实世界中难以复制的稀有事件。训练完成后,可以应用迁移学习来使模型适应现实世界的场景。

  • 假设我们旨在构建一个模型,用于区分视频流中的苹果和 Windows 笔记本电脑。现有的开源物体检测模型以其在视频流中对不同物体分类的高准确率而著称,这些模型可以作为理想的起点。通过迁移学习,我们可以首先利用这些模型识别物体为笔记本电脑。接着,我们可以进一步优化我们的模型,区分苹果和 Windows 笔记本电脑。

在下一节中,我们将实现本章讨论的原则,创建一个用于分类欺诈文档的神经网络。

作为一个视觉示例,考虑一个预训练的模型作为一棵成熟的大树,树上有许多枝条(层)。一些枝条上已经挂满了果实(训练好以识别特征)。在应用迁移学习时,我们“冻结”这些结实的枝条,保留它们已建立的学习成果。然后,我们允许新的枝条生长并结出果实,这类似于训练额外的层来理解我们的特定特征。冻结某些层并训练其他层的过程概括了迁移学习的本质。

案例研究 – 使用深度学习进行欺诈检测

使用机器学习技术识别欺诈文档是一个活跃且具有挑战性的研究领域。研究人员正在探索神经网络的模式识别能力在多大程度上可以用于此目的。与手动属性提取器不同,可以使用原始像素来构建多种深度学习架构结构。

方法论

本节介绍的技术使用了一种称为Siamese 神经网络的神经网络架构,该架构具有两个共享相同架构和参数的分支。

使用 Siamese 神经网络标记欺诈文档的示意图如下所示:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_08_17.png

图 8.17:Siamese 神经网络

当需要验证某一文档的真实性时,我们首先根据其布局和类型对文档进行分类,然后将其与预期的模板和模式进行比较。如果偏离超过某个阈值,则标记为伪造文档;否则,认为它是一个真实文档。对于关键的使用场景,我们可以为边界情况添加人工处理过程,在这些情况下,算法明确将文档分类为真实或伪造。

为了将文档与预期模板进行比较,我们在我们的 Siamese 架构中使用两个相同的 CNN。CNN 具有学习最佳平移不变局部特征检测器的优势,并且能够构建对输入图像几何畸变具有鲁棒性的表示。这非常适合我们的问题,因为我们的目标是通过单个网络传递真实文档和测试文档,然后比较它们的输出以确定相似性。为了实现这一目标,我们执行以下步骤。

假设我们要测试一个文档。对于每种文档类别,我们执行以下步骤:

  1. 获取存储的真实文档图像。我们称其为真实文档。测试文档应该与真实文档相似。

  2. 真实文档通过神经网络层传递,创建一个特征向量,这是该文档模式的数学表示。我们称其为特征向量 1,如前图所示。

  3. 需要测试的文档称为 测试文档。我们将该文档传递通过一个与用于创建真实文档特征向量的神经网络类似的神经网络。测试文档的特征向量称为 特征向量 2

  4. 我们使用 特征向量 1特征向量 2 之间的欧氏距离来计算真实文档与测试文档之间的相似度得分。这个相似度得分被称为 相似度度量MOS)。MOS 是一个介于 0 和 1 之间的数字。数字越大,表示文档之间的距离越小,文档相似的可能性越大。

  5. 如果神经网络计算出的相似度得分低于预定义的阈值,我们将标记该文档为欺诈文档。

让我们看看如何使用 Python 实现双胞胎神经网络。

为了说明如何使用 Python 实现双胞胎神经网络,我们将把这个过程拆解成更简单、易管理的块。这种方法将帮助我们遵循 PEP8 风格指南,保持代码的可读性和可维护性:

  1. 首先,让我们导入所需的 Python 包:

    import random
    import numpy as np
    import tensorflow as tf 
    
  2. 接下来,我们定义将处理双胞胎网络每个分支的网络模型。注意,我们已将丢弃率设置为 0.15,以减少过拟合:

    def createTemplate():
        return tf.keras.models.Sequential([
            tf.keras.layers.Flatten(),
            tf.keras.layers.Dense(128, activation='relu'),
            tf.keras.layers.Dropout(0.15),
            tf.keras.layers.Dense(128, activation='relu'),
            tf.keras.layers.Dropout(0.15),
            tf.keras.layers.Dense(64, activation='relu'), 
        ]) 
    
  3. 对于我们的双胞胎网络,我们将使用 MNIST 图像。这些图像非常适合测试我们的双胞胎网络的有效性。我们准备数据,使得每个样本将包含两张图像和一个二元相似度标志,指示它们是否属于同一类别:

    def prepareData(inputs: np.ndarray, labels: np.ndarray):
        classesNumbers = 10
        digitalIdx = [np.where(labels == i)[0] for i in range(classesNumbers)] 
    
  4. prepareData 函数中,我们确保所有数字的样本数量相等。我们首先使用 np.where 函数创建一个索引,表示每个数字在数据集中出现的位置。

    然后,我们准备图像对并分配标签:

     pairs = list()
        labels = list()
        n = min([len(digitalIdx[d]) for d in range(classesNumbers)]) - 1
        for d in range(classesNumbers):
            for i in range(n):
                z1, z2 = digitalIdx[d][i], digitalIdx[d][i + 1]
                pairs += [[inputs[z1], inputs[z2]]]
                inc = random.randrange(1, classesNumbers)
                dn = (d + inc) % classesNumbers
                z1, z2 = digitalIdx[d][i], digitalIdx[dn][i]
                pairs += [[inputs[z1], inputs[z2]]]
                labels += [1, 0] 
        return np.array(pairs), np.array(labels, dtype=np.float32) 
    
  5. 随后,我们将准备训练和测试数据集:

    input_a = tf.keras.layers.Input(shape=input_shape)
    encoder1 = base_network(input_a)
    input_b = tf.keras.layers.Input(shape=input_shape)
    encoder2 = base_network(input_b) 
    
  6. 最后,我们将实现 MOS,它量化了我们想要比较的两个文档之间的距离:

    distance = tf.keras.layers.Lambda( 
        lambda embeddings: tf.keras.backend.abs(
            embeddings[0] - embeddings[1]
        )
    ) ([encoder1, encoder2])
    measureOfSimilarity = tf.keras.layers.Dense(1, activation='sigmoid') (distance) 
    

现在,让我们训练模型。我们将使用 10 个 epoch 来训练该模型:

# Build the model
model = tf.keras.models.Model([input_a, input_b], measureOfSimilarity)
# Train
model.compile(loss='binary_crossentropy',optimizer=tf.keras.optimizers.Adam(),metrics=['accuracy'])
model.fit([train_pairs[:, 0], train_pairs[:, 1]], tr_labels, 
          batch_size=128,epochs=10,validation_data=([test_pairs[:, 0], test_pairs[:, 1]], test_labels)) 
Epoch 1/10
847/847 [==============================] - 6s 7ms/step - loss: 0.3459 - accuracy: 0.8500 - val_loss: 0.2652 - val_accuracy: 0.9105
Epoch 2/10
847/847 [==============================] - 6s 7ms/step - loss: 0.1773 - accuracy: 0.9337 - val_loss: 0.1685 - val_accuracy: 0.9508
Epoch 3/10
847/847 [==============================] - 6s 7ms/step - loss: 0.1215 - accuracy: 0.9563 - val_loss: 0.1301 - val_accuracy: 0.9610
Epoch 4/10
847/847 [==============================] - 6s 7ms/step - loss: 0.0956 - accuracy: 0.9665 - val_loss: 0.1087 - val_accuracy: 0.9685
Epoch 5/10
847/847 [==============================] - 6s 7ms/step - loss: 0.0790 - accuracy: 0.9724 - val_loss: 0.1104 - val_accuracy: 0.9669
Epoch 6/10
847/847 [==============================] - 6s 7ms/step - loss: 0.0649 - accuracy: 0.9770 - val_loss: 0.0949 - val_accuracy: 0.9715
Epoch 7/10
847/847 [==============================] - 6s 7ms/step - loss: 0.0568 - accuracy: 0.9803 - val_loss: 0.0895 - val_accuracy: 0.9722
Epoch 8/10
847/847 [==============================] - 6s 7ms/step - loss: 0.0513 - accuracy: 0.9823 - val_loss: 0.0807 - val_accuracy: 0.9770
Epoch 9/10
847/847 [==============================] - 6s 7ms/step - loss: 0.0439 - accuracy: 0.9847 - val_loss: 0.0916 - val_accuracy: 0.9737
Epoch 10/10
847/847 [==============================] - 6s 7ms/step - loss: 0.0417 - accuracy: 0.9853 - val_loss: 0.0835 - val_accuracy: 0.9749
<tensorflow.python.keras.callbacks.History at 0x7ff1218297b8> 

注意,使用 10 个 epoch 我们达到了 97.49% 的准确率。增加 epoch 数量将进一步提高准确度。

摘要

在本章中,我们探讨了神经网络的演变,研究了不同类型、关键组件如激活函数,以及重要的梯度下降算法。我们还提到了迁移学习的概念及其在识别欺诈文档中的实际应用。

随着我们进入下一章,我们将深入探讨自然语言处理,探索诸如词嵌入和递归网络等领域。我们还将学习如何实现情感分析。神经网络的迷人世界仍在展开。

在 Discord 上了解更多

要加入本书的 Discord 社区——在这里你可以分享反馈,向作者提问,并了解新版本的发布——请扫描下方二维码:

packt.link/WHLel

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/QR_Code1955211820597889031.png

第九章:自然语言处理的算法

语言是思维最重要的工具。

—马文·明斯基

本章介绍了自然语言处理NLP)的算法。首先介绍了 NLP 的基础知识。然后介绍了为 NLP 任务准备数据。接下来,解释了文本数据向量化的概念。然后,我们讨论了词嵌入。最后,展示了一个详细的用例。

本章由以下几部分组成:

  • 介绍 NLP

  • 基于词袋模型BoW-based)的 NLP

  • 词嵌入介绍

  • 案例研究:餐厅评论情感分析

到本章结束时,你将理解用于自然语言处理(NLP)的基本技术。你还应该理解 NLP 如何用于解决一些有趣的现实世界问题。

让我们从基本概念开始。

介绍 NLP

NLP 是机器学习算法的一个分支,处理计算机与人类语言之间的互动。它涉及分析、处理和理解人类语言,以使计算机能够理解并回应人类的沟通。NLP 是一个综合性的学科,涉及使用计算机语言学算法以及人机交互技术和方法来处理复杂的非结构化数据。

NLP 通过处理人类语言并将其分解为基本部分,如单词、短语和句子,来工作。目标是使计算机理解文本的含义并做出适当回应。NLP 算法利用各种技术,如统计模型、机器学习和深度学习,来分析和处理大量的自然语言数据。对于复杂问题,我们可能需要使用多种技术的组合来找到有效的解决方案。

NLP 的一个重大挑战是处理人类语言的复杂性和歧义性。语言种类繁多,具有复杂的语法结构和习惯用语。此外,单词和短语的含义可能根据使用的上下文而有所不同。NLP 算法必须能够处理这些复杂性,以实现有效的语言处理。

让我们从一些在讨论 NLP 时使用的术语开始。

理解 NLP 术语

NLP 是一个广泛的研究领域。在这一部分,我们将探讨一些与 NLP 相关的基本术语:

  • 语料库:语料库是一个大型且结构化的文本或语音数据集合,作为 NLP 算法的资源。它可以由各种类型的文本数据组成,如书面文本、口语语言、转录对话和社交媒体帖子。语料库通过有意地从各种在线和离线来源收集和组织数据来创建,包括互联网。虽然互联网是获取数据的丰富来源,但决定将哪些数据包含在语料库中,需要根据特定研究或分析的目标进行有目的的选择和对齐。

    语料库(corpora)是语料(corpus)的复数形式,可以进行注释,意味着它们可能包含关于文本的额外细节,例如词性标签和命名实体。这些注释语料库提供了特定的信息,能够增强 NLP 算法的训练和评估,使它们在该领域成为极具价值的资源。

  • 标准化:这个过程涉及将文本转换为标准形式,例如将所有字符转换为小写字母或去除标点符号,使其更容易进行分析。

  • 分词:分词将文本拆分成更小的部分,称为词元,通常是单词或子词,从而实现更结构化的分析。

  • 命名实体识别NER):NER 用于识别和分类文本中的命名实体,例如人名、地点、组织等。

  • 停用词:这些是常用词,例如 andtheis,它们在文本处理过程中通常会被过滤掉,因为它们可能不会提供显著的意义。

  • 词干提取和词形还原:词干提取是将单词还原为其词根形式,而词形还原是将单词转换为其基本或词典形式。这两种技术有助于分析单词的核心含义。

接下来,让我们研究 NLP 中使用的不同文本预处理技术:

  • 词嵌入:这是一种将单词转换为数值形式的方法,其中每个单词都表示为一个向量,位于一个可能具有多个维度的空间中。在这个背景下,“高维向量”指的是一个数字数组,其中维度数量或单独的成分是相当大的——通常在数百甚至数千维之间。使用高维向量的思想是为了捕捉单词之间复杂的关系,使得具有相似含义的单词在这个多维空间中更接近。向量维度越多,它能够捕捉的关系就越细致。因此,在词嵌入中,语义相关的单词会在这个高维空间中彼此更接近,从而使得算法能够更容易地理解和处理语言,反映出人类的理解方式。

  • 语言建模:语言建模是开发统计模型的过程,这些模型可以根据给定文本语料库中发现的模式和结构,预测或生成单词或字符的序列。

  • 机器翻译:使用自然语言处理(NLP)技术和模型自动将文本从一种语言翻译成另一种语言的过程。

  • 情感分析:通过分析文本中使用的单词、短语及其上下文来确定一段文本中表达的态度或情感的过程。

NLP 中的文本预处理

文本预处理是 NLP 中的一个关键阶段,在这一阶段,原始文本数据会经过转换,变得适合机器学习算法。这个转化过程涉及将无序且通常杂乱无章的文本转化为所谓的“结构化格式”。结构化格式意味着数据被组织成更加系统和可预测的模式,通常涉及分词、词干提取和删除不需要的字符等技术。这些步骤有助于清理文本,减少无关信息或“噪音”,并以一种更便于机器学习模型理解的方式整理数据。通过这种方法,原始文本中的不一致性和不规则性得以转化,形成一种能够提高后续 NLP 任务准确性、性能和效率的形式。在本节中,我们将探索用于文本预处理的各种技术,以实现这种结构化格式。

分词

提醒一下,分词是将文本分解成更小单位(即令牌)的关键过程。这些令牌可以是单个词语,甚至是子词。在 NLP 中,分词通常被视为准备文本数据进行进一步分析的第一步。分词之所以如此重要,源于语言本身的特性,理解和处理文本需要将其分解为可管理的部分。通过将连续的文本流转化为单独的令牌,我们创造了一种结构化格式,类似于人类自然阅读和理解语言的方式。这种结构化使得机器学习模型能够以清晰且系统化的方式分析文本,从而识别数据中的模式和关系。随着我们深入研究 NLP 技术,这种令牌化的格式成为许多其他预处理和分析步骤的基础。

is tokenizing the given text using the Natural Language Toolkit (nltk) library in Python. The nltk is a widely used library in Python, specifically designed for working with human language data. It provides easy-to-use interfaces and tools for tasks such as classification, tokenization, stemming, tagging, parsing, and more, making it a valuable asset for NLP. For those who wish to leverage these capabilities in their Python projects, the nltk library can be downloaded and installed directly from the Python Package Index (PyPI) by using the command pip install nltk. By incorporating the nltk library into your code, you can access a rich set of functions and resources that streamline the development and execution of various NLP tasks, making it a popular choice among researchers, educators, and developers in the field of computational linguistics. Let us start by importing relevant functions and using them:
from nltk.tokenize import word_tokenize
corpus = 'This is a book about algorithms.'
tokens = word_tokenize(corpus)
print(tokens) 

输出将是如下所示的列表:

['This', 'is', 'a', 'book', 'about', 'algorithms', '.'] 

在这个示例中,每个令牌都是一个单词。最终令牌的粒度将根据目标而有所不同——例如,每个令牌可以是一个单词、一句话或一段话。

要基于句子对文本进行分词,可以使用nltk.tokenize模块中的sent_tokenize函数:

from nltk.tokenize import sent_tokenize
corpus = 'This is a book about algorithms. It covers various topics in depth.' 

在这个例子中,corpus变量包含了两个句子。sent_tokenize函数将语料库作为输入,并返回一个句子的列表。当你运行修改后的代码时,将得到以下输出:

sentences = sent_tokenize(corpus)
print(sentences) 
['This is a book about algorithms.', 'It covers various topics in depth.'] 

有时我们可能需要将较大的文本拆分为按段落划分的块。nltk可以帮助完成这项任务。这项功能在文档摘要等应用中尤其有用,因为在这些应用中,理解段落级别的结构可能至关重要。将文本按段落进行分词看似简单,但根据文本的结构和格式,可能会变得复杂。一个简单的方法是通过两个换行符来拆分文本,这通常用于纯文本文档中的段落分隔。

这里是一个基本示例:

def tokenize_paragraphs(text):
    # Split by two newline characters
    paragraphs = text.split('\n\n') 
    return [p.strip() for p in paragraphs if p] 

接下来,让我们看看如何清理数据。

清理数据

清理数据是 NLP 中的一个关键步骤,因为原始文本数据通常包含噪音和无关信息,这些信息可能会妨碍 NLP 模型的性能。清理数据的目标是对文本数据进行预处理,去除噪音和无关信息,并将其转换为适合使用 NLP 技术分析的格式。请注意,数据清理是在数据被分词之后进行的。原因在于,清理可能涉及到依赖于分词揭示的结构的操作。例如,删除特定单词或修改单词形式可能在文本被分词为独立的词汇后更为准确。

让我们研究一些用于清理数据并为机器学习任务做准备的技术:

大小写转换

大小写转换是自然语言处理(NLP)中的一种技术,它将文本从一种大小写格式转换为另一种格式,例如从大写转换为小写,或者从标题式大小写转换为大写。

例如,标题式大小写的“Natural Language Processing”可以转换为小写,即“natural language processing”。

这一简单而有效的步骤有助于标准化文本,从而简化其在各种 NLP 算法中的处理。通过确保文本处于统一的大小写格式,有助于消除由于大小写变化可能产生的不一致性。

标点符号移除

在 NLP 中,标点符号移除是指在分析之前,从原始文本数据中删除标点符号的过程。标点符号是如句号(.)、逗号(,)、问号(?)和感叹号(!)等符号,它们在书面语言中用于表示停顿、强调或语调。虽然它们在书面语言中至关重要,但它们会为原始文本数据增加噪音和复杂性,进而妨碍 NLP 模型的性能。

合理的疑虑是,删除标点符号可能会影响句子的意义。请考虑以下示例:

"她是只猫。"

"她是只猫??"

没有标点符号时,两行文本都变成了“她是只猫”,可能失去了问号所传达的独特强调。

然而,值得注意的是,在许多 NLP 任务中,如主题分类或情感分析,标点符号可能不会显著影响整体理解。此外,模型可以依赖于文本结构、内容或上下文中的其他线索来推导含义。在标点符号细微差别至关重要的情况下,可能需要使用专门的模型和预处理技术来保留所需的信息。

处理数字在 NLP 中的应用

文本数据中的数字可能给 NLP 带来挑战。下面是两种处理文本中数字的主要策略,既考虑了传统的去除方法,也考虑了标准化的替代选项。

在某些自然语言处理(NLP)任务中,数字可能被视为噪声,特别是当关注点集中在像词频或情感分析等方面时。这就是为什么一些分析师可能选择去除数字的原因:

  • 缺乏相关性:在某些文本分析情境中,数字字符可能不携带重要的含义。

  • 扭曲词频统计:数字可能会扭曲词频统计,尤其是在像主题建模这样的模型中。

  • 减少复杂性:去除数字可以简化文本数据,可能提升 NLP 模型的性能。

然而,一种替代方法是将所有数字转换为标准表示,而不是将其丢弃。这种方法承认数字可以携带重要信息,并确保其在一致格式中保留其值。在数字数据对文本含义起着至关重要作用的语境中,这种方法特别有用。

决定是否去除或保留数字需要理解所解决的问题。一个算法可能需要定制,以根据文本的上下文和特定的 NLP 任务来区分数字是否重要。分析数字在文本领域中的作用以及分析的目标,可以引导这一决策过程。

在 NLP 中处理数字并不是一成不变的方法。是否去除、标准化或仔细分析数字,取决于任务的独特要求。理解这些选项及其影响,有助于做出符合文本分析目标的明智决策。

去除空格

在 NLP 中,空格去除指的是去除不必要的空格字符,如多个空格和制表符字符。在文本数据的语境中,空格不仅是单词之间的空白,还包括其他“看不见”的字符,这些字符在文本中创建了间距。在 NLP 中,空格去除指的是去除这些不必要的空格字符。去除不必要的空格可以减少文本数据的大小,并使其更易于处理和分析。

下面是一个简单的例子来说明空格去除:

  • 输入文本:"The quick brown fox \tjumps over the lazy dog."

  • 处理过的文本:"The quick brown fox jumps over the lazy dog."

在上述示例中,去除了额外的空格和一个制表符字符(由 \t 表示),从而创建了更干净且更标准化的文本字符串。

停用词去除

停用词去除是指从文本语料库中删除常见词汇,称为停用词。停用词是在语言中频繁出现,但不具有重要意义或不有助于理解文本的词汇。英语中的停用词包括 the, 和*, is, in* 和 for。停用词去除有助于减少数据的维度,并提高算法的效率。通过去除那些对分析没有重要贡献的词汇,可以将计算资源集中在真正重要的词汇上,从而提高各种自然语言处理算法的效率。

请注意,停用词去除不仅仅是减少文本大小;它是为了专注于分析中真正重要的词汇。虽然停用词在语言结构中扮演着重要角色,但在自然语言处理中的去除,可以提升分析的效率和重点,特别是在情感分析等任务中,主要关心的是理解潜在的情感或观点。

词干提取和词形还原

在文本数据中,大多数单词可能以略微不同的形式出现。将每个单词简化为它的原型或词干,这一过程称为词干提取。它用于根据单词的相似含义将单词分组,以减少需要分析的单词总数。本质上,词干提取减少了问题的整体条件性。英语中最常用的词干提取算法是 Porter 算法。

例如,让我们看一些例子:

  • 示例 1:{use, used, using, uses} => use

  • 示例 2:{easily, easier, easiest} => easi

需要注意的是,词干提取有时会导致拼写错误,如示例 2 中生成的 easi

词干提取是一种简单且快速的处理过程,但它可能并不总是产生正确的结果。在需要正确拼写的情况下,词形还原是一种更合适的方法。词形还原考虑上下文并将单词还原为其基本形式。单词的基本形式,也称为词根,是其最简单且最具意义的版本。它代表了单词在字典中的形式,去除了任何词尾变化,形成一个正确的英语单词,从而产生更准确、更有意义的词根。

引导算法识别相似性是一个精确且深思熟虑的任务。与人类不同,算法需要明确的规则和标准来建立连接,这些连接对我们来说可能看起来是显而易见的。理解这一差异并知道如何提供必要的引导,是开发和调整算法在各种应用中的重要技能。

使用 Python 清理数据

让我们看一下如何使用 Python 清理文本。

首先,我们需要导入必要的库:

import string
import re
import nltk
from nltk.corpus import stopwords
from nltk.stem import PorterStemmer
# Make sure to download the NLTK resources
nltk.download('punkt')
nltk.download('stopwords') 

接下来,这是执行文本清理的主函数:

def clean_text(text):
    """
    Cleans input text by converting case, removing punctuation, numbers,
    white spaces, stop words and stemming
    """
    # Convert to lowercase
    text = text.lower()

    # Remove punctuation
    text = text.translate(str.maketrans('', '', string.punctuation))

    # Remove numbers
    text = re.sub(r'\d+', '', text)

    # Remove white spaces
    text = text.strip()

    # Remove stop words
    stop_words = set(stopwords.words('english'))
    tokens = nltk.word_tokenize(text)
    filtered_text = [word for word in tokens if word not in stop_words]
    text = ' '.join(filtered_text)

    # Stemming
    ps = PorterStemmer()
    tokens = nltk.word_tokenize(text)
    stemmed_text = [ps.stem(word) for word in tokens]
    text = ' '.join(stemmed_text)

    return text 

让我们测试一下clean_text()函数:

corpus="7- Today, Ottawa is becoming cold again "
clean_text(corpus) 

结果将是:

today ottawa becom cold 

注意输出中的单词becom。由于我们使用了词干提取技术,输出中的并非所有单词都是正确的英语单词。

所有前面的处理步骤通常是必需的;实际的处理步骤取决于我们要解决的问题。它们会因使用场景而异——例如,如果文本中的数字表示一些在我们尝试解决的问题背景下可能有意义的内容,那么在标准化阶段我们可能不需要去除这些数字。

一旦数据被清理,我们需要将结果存储在一个专门为此目的设计的数据结构中。这个数据结构被称为术语文档矩阵TDM),接下来会详细解释。

理解术语文档矩阵

TDM(术语文档矩阵)是自然语言处理(NLP)中使用的数学结构。它是一个表格,用于统计文档集合中术语(单词)的频率。每一行表示一个独特的术语,每一列表示一个特定的文档。它是文本分析中的一个重要工具,可以让你看到每个单词在不同文本中出现的频率。

对于包含单词catdog的文档:

  • 文档 1:cat cat dog

  • 文档 2:dog dog cat

Document 1Document 2
cat21
dog12

这种矩阵结构允许高效地存储、组织和分析大型文本数据集。在 Python 中,可以使用sklearn库中的CountVectorizer模块来创建一个 TDM,方法如下:

from sklearn.feature_extraction.text import CountVectorizer
# Define a list of documents
documents = ["Machine Learning is useful", "Machine Learning is fun", "Machine Learning is AI"]
# Create an instance of CountVectorizer
vectorizer = CountVectorizer()
# Fit and transform the documents into a TDM
tdm = vectorizer.fit_transform(documents)
# Print the TDM
print(tdm.toarray()) 

输出如下:

[[0 0 1 1 1 1]
 [0 1 1 1 1 0]
 [1 0 1 1 1 0]] 

请注意,每个文档对应一行,每个不同的单词对应一列。这里有三个文档和六个不同的单词,结果是一个 3x6 的矩阵。

在这个矩阵中,数字表示每个单词(列)在对应文档(行)中出现的频率。例如,如果第一行第一列的数字是 1,这意味着第一个单词在第一个文档中出现了一次。

默认情况下,TDM 使用每个术语的频率,这是量化每个单词在每个文档中的重要性的一种简单方法。更精细的量化方法是使用 TF-IDF,这将在下一节中解释。

使用 TF-IDF

词频-逆文档频率TF-IDF)是一种用于计算单词在文档中重要性的方法。它考虑了两个主要成分来确定每个术语的权重:词频TF)和逆文档频率IDF)。TF 关注一个词在特定文档中出现的频率,而 IDF 则检查这个词在整个文档集合(即语料库)中的稀有程度。在 TF-IDF 的上下文中,语料库指的是你正在分析的所有文档。如果我们正在处理一组书评,举例来说,语料库将包括所有的书评:

  • TF:TF 衡量一个术语在文档中出现的次数。它的计算方法是术语在文档中出现的次数与文档中术语总数的比值。术语出现得越频繁,TF 值就越高。

  • IDF:IDF 衡量一个术语在整个文档集合中的重要性。它的计算方法是语料库中总文档数与包含该术语的文档数之比的对数。术语在语料库中越稀有,它的 IDF 值就越高。

要使用 Python 计算 TF-IDF,请执行以下操作:

from sklearn.feature_extraction.text import TfidfVectorizer
# Define a list of documents
documents = ["Machine Learning enables learning", "Machine Learning is fun", "Machine Learning is useful"]
# Create an instance of TfidfVectorizer
vectorizer = TfidfVectorizer()
# Fit and transform the documents into a TF-IDF matrix
tfidf_matrix = vectorizer.fit_transform(documents)
# Get the feature names
feature_names = vectorizer.get_feature_names_out()
# Loop over the feature names and print the TF-IDF score for each term
for i, term in enumerate(feature_names):
    tfidf = tfidf_matrix[:, i].toarray().flatten()
    print(f"{term}: {tfidf}") 

这将输出:

enables:   [0.60366655 0\.         0\.        ]
fun:       [0\.         0.66283998 0\.        ]
is:        [0\.         0.50410689 0.50410689]
learning:  [0.71307037 0.39148397 0.39148397]
machine:   [0.35653519 0.39148397 0.39148397]
useful:    [0\.         0\.         0.66283998] 

输出中的每一列对应一个文档,行表示各文档中术语的 TF-IDF 值。例如,术语kids只有在第二个文档中才有非零的 TF-IDF 值,这与我们的预期一致。

结果总结与讨论

TF-IDF 方法提供了一种有价值的方式来衡量术语在单个文档内以及在整个语料库中的重要性。计算得到的 TF-IDF 值揭示了每个术语在每个文档中的相关性,同时考虑了它们在给定文档中的频率以及在整个语料库中的稀有性。在提供的示例中,不同术语的 TF-IDF 值变化表明该模型能够区分那些在特定文档中独有的词汇和那些使用频率较高的词汇。这种能力可以在多个应用中加以利用,如文本分类、信息检索和特征选择,提升对文本数据的理解和处理。

词嵌入简介

NLP 的一项重大进展是我们能够创建单词的有意义的数字表示形式,采用稠密向量的形式。这种技术称为词嵌入。那么,稠密向量到底是什么呢?假设你有一个单词 apple(苹果)。在词嵌入中,apple 可能被表示为一系列数字,例如 [0.5, 0.8, 0.2],其中每个数字都是连续的、多维空间中的一个坐标。“稠密”意味着这些数字大多数或全部都不为零,不像稀疏向量那样许多元素为零。简单来说,词嵌入将文本中的每个单词转化为一个独特的、多维的空间点。这样,含义相似的单词将最终在这个空间中彼此接近,从而使算法能够理解单词之间的关系。Yoshua Bengio 在他的论文 A Neural Probabilistic Language Model 中首次提出了这个术语。NLP 问题中的每个单词可以被视为一个类别对象。

在词嵌入中,尝试建立每个单词的邻域,并利用它来量化单词的意义和重要性。一个单词的邻域是指围绕特定单词的一组单词。

为了真正理解词嵌入的概念,我们来看一个涉及四个常见水果词汇的具体例子:apple(苹果)、banana(香蕉)、orange(橙子)和pear(梨)。这里的目标是将这些单词表示为稠密向量,这些向量是数字数组,其中每个数字捕捉单词的特定特征或特性。

为什么要以这种方式表示单词呢?在自然语言处理(NLP)中,将单词转换为稠密向量可以使算法量化不同单词之间的关系。本质上,我们是在将抽象的语言转化为可以用数学方法衡量的内容。

考虑我们水果单词的甜度、酸度和多汁度特征。我们可以对每个水果的这些特征进行从 0 到 1 的评分,0 表示该特征完全缺失,1 表示该特征非常明显。评分可能是这样的:

"apple": [0.5, 0.8, 0.2] – moderately sweet, quite acidic, not very juicy
"banana": [0.2, 0.3, 0.1]not very sweet, moderately acidic, not juicy
"orange": [0.9, 0.6, 0.9] – very sweet, somewhat acidic, very juicy
"pear": [0.4, 0.1, 0.7] – moderately sweet, barely acidic, quite juicy 

这些数字是主观的,可以通过味觉测试、专家意见或其他方法得出,但它们的作用是将单词转化为算法可以理解并使用的格式。

通过可视化,你可以想象一个三维空间,其中每个坐标轴代表一个特征(甜度、酸度或多汁度),每个水果的向量将其放置在这个空间中的特定位置。具有相似口味的单词(水果)会在这个空间中彼此更接近。

那么,为什么选择长度为 3 的稠密向量呢?这是基于我们选择的特征来表示的。在其他应用中,向量的长度可能不同,取决于你希望捕捉的特征数量。

这个例子展示了词嵌入是如何将一个单词转化为一个持有实际意义的数字向量的。这是让机器“理解”并处理人类语言的关键步骤。

使用 Word2Vec 实现词嵌入

Word2Vec 是一种用于获取单词向量表示的突出方法,通常称为单词嵌入。该算法并不是“生成单词”,而是创建代表每个单词语义的数值向量。

Word2Vec 的基本思想是利用神经网络来预测给定文本语料库中每个单词的上下文。神经网络通过输入单词及其周围的上下文单词进行训练,网络学习输出给定输入单词的上下文单词的概率分布。神经网络的权重随后被用作单词嵌入,这些嵌入可以用于各种自然语言处理任务:

import gensim
# Define a text corpus
corpus = [['apple', 'banana', 'orange', 'pear'],
          ['car', 'bus', 'train', 'plane'],
          ['dog', 'cat', 'fox', 'fish']]
# Train a word2vec model on the corpus
model = gensim.models.Word2Vec(corpus, window=5, min_count=1, workers=4) 

让我们分解一下Word2Vec()函数的重要参数:

  • sentences:这是模型的输入数据。它应该是一个句子的集合,每个句子是一个单词列表。实际上,它是一个单词列表的列表,代表了你的整个文本语料库。

  • size:定义了单词嵌入的维度。换句话说,它设置了表示单词的向量中的特征或数值的数量。一个典型的值可能是100300,具体取决于词汇的复杂性。

  • window:该参数设置目标单词与句子中用于预测的上下文单词之间的最大距离。例如,如果将窗口大小设置为5,算法将在训练过程中考虑目标单词前后五个立即相邻的单词。

  • min_count:通过设置此参数,可以排除在语料库中出现频率较低的单词。例如,如果将min_count设置为2,那么在所有句子中出现次数少于两次的单词将在训练过程中被忽略。

  • workers:指的是训练过程中使用的处理线程数量。增加该值可以通过启用并行处理来加速在多核机器上的训练。

一旦 Word2Vec 模型训练完成,使用它的强大方法之一是测量嵌入空间中单词之间的相似性或“距离”。这个相似性得分可以让我们洞察模型如何看待不同单词之间的关系。现在,让我们通过查看cartrain之间的距离来检查模型:

print(model.wv.similarity('car', 'train')) 
-0.057745814 

现在让我们来看一下carapple的相似度:

print(model.wv.similarity('car', 'apple')) 
0.11117952 

因此,输出给我们的是基于模型学习到的单词嵌入之间的相似性得分。

解释相似性得分

以下细节有助于解释相似性得分:

  • 非常相似:接近 1 的得分表示强烈的相似性。具有此得分的单词通常共享上下文或语义意义。

  • 适度相似:接近 0.5 的得分表示某种程度的相似性,可能是由于共享的属性或主题。

  • 相似度弱或没有相似性:接近 0 或负数的得分表示意义之间几乎没有相似性,甚至存在对比。

因此,这些相似度分数提供了关于单词关系的定量见解。通过理解这些分数,你可以更好地分析文本语料库的语义结构,并将其用于各种 NLP 任务。

Word2Vec 提供了一种强大且高效的方式来表示文本数据,能够捕捉单词之间的语义关系、减少维度并提高下游 NLP 任务的准确性。让我们来看看 Word2Vec 的优缺点。

Word2Vec 的优缺点

以下是使用 Word2Vec 的优点:

  • 捕捉语义关系:Word2Vec 的嵌入在向量空间中的位置使得语义相关的单词靠得很近。通过这种空间安排,捕捉了语法和语义关系,如同义词、类比等,从而在信息检索和语义分析等任务中取得更好的表现。

  • 降维:传统的单热编码(one-hot encoding)会创建一个稀疏且高维的空间,尤其是当词汇表很大时。Word2Vec 将其压缩为一个更加密集且低维的连续向量空间(通常为 100 到 300 维)。这种压缩表示保留了重要的语言模式,同时在计算上更高效。

  • 处理词汇外单词:Word2Vec 可以通过利用上下文词来推断未出现在训练语料中的单词的嵌入。这个特性有助于更好地泛化到未见过或新的文本数据,增强了模型的鲁棒性。

现在让我们来看看使用 Word2Vec 的一些缺点:

  • 训练复杂性:Word2Vec 模型的训练可能需要大量计算资源,特别是在拥有庞大词汇表和高维向量时。它们需要大量的计算资源,并可能需要优化技术,如负采样或层次化软最大(hierarchical softmax),以实现高效扩展。

  • 缺乏可解释性:Word2Vec 嵌入的连续性和密集性使得它们难以被人类理解。与精心设计的语言特征不同,Word2Vec 中的维度不对应直观的特征,这使得理解捕获了单词的哪些具体方面变得困难。

  • 对文本预处理敏感:Word2Vec 嵌入的质量和效果可能会根据应用于文本数据的预处理步骤而显著变化。诸如分词、词干提取、词形还原或去除停用词等因素必须谨慎考虑。预处理的选择可能会影响向量空间中的空间关系,从而可能影响模型在下游任务中的表现。

接下来,我们来看一个关于餐厅评论的案例研究,结合了本章介绍的所有概念。

案例研究:餐厅评论情感分析

我们将使用 Yelp 评论数据集,该数据集包含标记为正面(5 星)或负面(1 星)的评论。我们将训练一个可以将餐厅评论分类为负面或正面的模型。

让我们通过以下步骤实现这个处理管道。

导入所需的库并加载数据集

首先,我们导入所需的包:

import numpy as np
import pandas as pd
import re
from nltk.stem import PorterStemmer
from nltk.corpus import stopwords 

然后我们从一个.csv文件导入数据集:

url = 'https://storage.googleapis.com/neurals/data/2023/Restaurant_Reviews.tsv'
dataset = pd.read_csv(url, delimiter='\t', quoting=3)
dataset.head() 
 Review     Liked
0                           Wow... Loved this place.        1
1                                 Crust is not good.        0
2          Not tasty and the texture was just nasty.        0
3     Stopped by during the late May bank holiday of...     1
4      The selection on the menu was great and so wer...    1 

构建一个干净的语料库:文本数据预处理

接下来,我们通过对数据集中的每条评论进行词干提取和停用词去除等文本预处理来清洗数据:

def clean_text(text):
    text = re.sub('[^a-zA-Z]', ' ', text)
    text = text.lower()
    text = text.split()
    ps = PorterStemmer()
    text = [
        ps.stem(word) for word in text 
        if not word in set(stopwords.words('english'))]
    text = ' '.join(text)
    return text
corpus = [clean_text(review) for review in dataset['Review']] 

代码遍历数据集中的每一条评论(在这种情况下是'Review'列),并应用clean_text函数对每条评论进行预处理和清洗。代码创建了一个名为corpus的新列表。结果是一个存储在corpus变量中的已清洗和预处理过的评论列表。

将文本数据转换为数值特征

现在让我们定义特征(由y表示)和标签(由X表示)。记住,特征是描述数据特征的自变量或属性,作为预测的输入。

标签是模型被训练来预测的因变量或目标值,表示与特征对应的结果:

vectorizer = CountVectorizer(max_features=1500)
X = vectorizer.fit_transform(corpus).toarray()
y = dataset.iloc[:, 1].values 

让我们将数据分为测试数据和训练数据:

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=0) 

为了训练模型,我们使用了在第七章中学习的朴素贝叶斯算法:

classifier = GaussianNB()
classifier.fit(X_train, y_train) 

让我们预测测试集的结果:

y_pred = classifier.predict(X_test) 

接下来,我们打印混淆矩阵。记住,混淆矩阵是一个帮助可视化分类模型表现的表格:

cm = confusion_matrix(y_test, y_pred)
print(cm) 
[[55 42]
 [12 91]] 

通过查看混淆矩阵,我们可以估算误分类情况。

分析结果

混淆矩阵让我们窥见了模型所做的误分类。在这个背景下,有:

  • 55 个真正的正例(正确预测的正面评论)

  • 42 个假正例(错误预测为正面的评论)

  • 12 个假负例(错误预测为负面的评论)

  • 91 个真正的负例(正确预测的负面评论)

55 个真正的正例和 91 个真正的负例表明我们的模型具有合理的能力,能够区分正面和负面评论。然而,42 个假正例和 12 个假负例突显了潜在的改进空间。

在餐厅评论的背景下,理解这些数字有助于商家和顾客评估整体情感。高比例的真正正例和真正负例表明模型能够被信任,提供准确的情感概述。这些信息对于想要提升服务的餐厅或寻求真实评论的潜在顾客来说,可能非常宝贵。另一方面,假正例和假负例的存在则表明模型可能需要调整,以避免误分类并提供更准确的洞察。

自然语言处理(NLP)的应用

自然语言处理(NLP)技术的持续进步彻底改变了我们与计算机和其他数字设备的互动方式。近年来,它在多个任务上取得了显著进展,并取得了令人印象深刻的成就,包括:

  • 主题识别:在文本库中发现主题,并根据发现的主题对库中的文档进行分类。

  • 情感分析:根据文本中的正面或负面情感对其进行分类。

  • 机器翻译:在不同语言之间进行翻译。

  • 语音转文本:将口语转换为文本。

  • 问答:这是通过使用可用信息来理解和回应查询的过程。它涉及智能地解读问题,并根据现有知识或数据提供相关的答案。

  • 实体识别:从文本中识别实体(如人、地点或事物)。

  • 假新闻检测:根据内容标记假新闻。

总结

本章讨论了与 NLP 相关的基本术语,如语料库、词向量、语言建模、机器翻译和情感分析。此外,本章还介绍了 NLP 中至关重要的各种文本预处理技术,包括分词,它将文本分解成称为标记的小单位,以及其他技术,如词干提取和去除停用词。

本章还讨论了词向量,并展示了一个餐厅评论情感分析的案例。现在,读者应当对 NLP 中使用的基本技术及其在现实世界问题中的潜在应用有了更好的理解。

在下一章,我们将讨论如何训练处理顺序数据的神经网络。我们还将探讨如何利用深度学习进一步改善自然语言处理(NLP)技术和本章讨论的方法论。

了解更多信息,请访问 Discord

要加入本书的 Discord 社区 —— 你可以在这里分享反馈、向作者提问并了解新版本 —— 请扫描下面的二维码:

packt.link/WHLel

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/QR_Code1955211820597889031.png

第十章:理解顺序模型

一个序列的工作方式是一个集合无法做到的。

—George Murray

本章介绍了机器学习模型中的一个重要类别——顺序模型。此类模型的一个定义特征是,处理层以这样的方式排列:一层的输出是另一层的输入。这种架构使它们非常适合处理顺序数据。顺序数据是由有序元素组成的数据类型,例如文档中的一句话或股市价格的时间序列。

在本章中,我们将从理解顺序数据的特征开始。然后,我们将介绍 RNN 的工作原理以及如何使用它们处理顺序数据。接下来,我们将学习如何通过 GRU 解决 RNN 的局限性而不牺牲准确性。然后,我们将讨论 LSTM 的架构。最后,我们将比较不同的顺序建模架构,并推荐在何时使用哪种架构。

在本章中,我们将讨论以下概念:

  • 理解顺序数据

  • RNN 如何处理顺序数据

  • 通过 GRU 解决 RNN 的局限性

  • 理解 LSTM

让我们首先了解顺序数据的特征。

理解顺序数据

顺序数据是一种特殊的数据结构,其中元素的顺序至关重要,每个元素与其前面的元素之间存在关联依赖性。这种“顺序行为”非常独特,因为它不仅在单独的元素中传递信息,还在它们发生的模式或顺序中传递信息。在顺序数据中,当前的观察不仅受到外部因素的影响,还受到序列中之前观察值的影响。这种依赖性构成了顺序数据的核心特征。

理解不同类型的顺序数据对于理解其广泛应用至关重要。以下是主要的分类:

  • 时间序列数据:这是按时间顺序索引或列出的数据点序列。任何时间点的值都依赖于过去的值。时间序列数据广泛应用于经济学、金融和医疗等各个领域。

  • 文本数据:文本数据本质上也是顺序的,其中单词、句子或段落的顺序可以传递意义。自然语言处理NLP)利用这一顺序特性来分析和解释人类语言。

  • 时空数据:这类数据捕捉了空间和时间之间的关系,例如特定地理区域内的天气模式或交通流量随时间变化的情况。

下面是这些类型的顺序数据在现实世界场景中的表现方式:

  • 时间序列数据:这种数据类型通过金融市场趋势得到清晰体现,股票价格随着持续的市场动态不断变化。类似地,社会学研究可以分析出生率,反映出受经济条件和社会政策等因素影响的年度变化。

  • 文本数据:文本的顺序性在文学和新闻作品中至关重要。在小说、新闻文章或论文中,单词、句子和段落的特定排列构建了叙事和论证,赋予文本超越单词本身的意义。

  • 时空数据:这种数据类型在城市发展和环境研究中至关重要。例如,可以跟踪不同地区的房价随时间的变化,以识别经济趋势,而气象研究可以监测特定地理位置的天气变化,预测模式和自然事件。

这些现实世界的例子展示了不同类型数据中固有的顺序行为如何被利用以提供见解并推动各个领域的决策。

在深度学习中,处理序列数据需要像序列模型这样的专门神经网络架构。这些模型旨在捕获和利用序列数据元素之间固有的时间依赖关系。通过识别这些依赖关系,序列模型为创建更为细致和有效的机器学习模型提供了坚实的框架。

总之,序列数据是一种在各个领域中应用广泛的丰富而复杂的数据类型。认识其顺序性、理解其类型,并利用专业模型,使数据科学家能够深入洞察并构建更强大的预测工具。在我们研究技术细节之前,让我们先来看一看序列建模技术的历史。

让我们来研究不同类型的序列模型。

序列模型的类型

通过检查它们处理的数据类型(如文本信息、数值数据或基于时间的模式),以及这些数据从过程开始到结束如何演变或转换,将序列模型分类为各种类别。通过深入了解这些特征,我们可以确定三种主要类型的序列模型。

一对多

在一对多序列模型中,单一事件或输入可以启动整个序列的生成。这个独特的特性为广泛的应用领域打开了大门,但也带来了训练和实施的复杂性。一对多序列模型提供了令人兴奋的机会,但也伴随着训练和执行中的固有复杂性。随着生成性 AI 的不断进步,这些模型很可能在塑造各个领域的创造性和定制化解决方案中发挥关键作用。

发挥其潜力的关键在于理解其能力,并识别训练和实施中的复杂性。一对多序列模型如图 10.1所示:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_01.png

图 10.1:一对多序列模型

让我们深入探讨一对多模型的特性、能力和挑战:

  • 广泛的应用范围:将单一输入转化为有意义的序列的能力使得一对多模型既多才多艺又强大。它们可以用于写诗、创作艺术作品如绘画和图画,甚至为求职申请编写个性化的求职信。

  • 生成性 AI 的一部分:这些模型属于生成性 AI 的范畴,这是一个新兴领域,旨在创建既连贯又符合上下文的新内容。这使得它们能够执行上述提到的各种任务。

  • 强化训练过程:与其他序列模型相比,训练一对多模型通常更加耗时且计算开销更大。原因在于将单一输入转化为多种潜在输出的复杂性。模型不仅需要学习输入与输出之间的关系,还需要掌握生成序列中固有的复杂模式和结构。

请注意,与一对一模型(一个输入对应一个输出)或多对多模型(一个输入序列对应一个输出序列)不同,一对多范式必须从单一的起点中推导出丰富且结构化的序列。这需要对底层模式有更深入的理解,并且通常需要更复杂的训练算法。

一对多方法并非没有挑战。确保生成的序列保持一致性、相关性和创造性需要精心设计和细致调优。它通常需要更大规模的数据集,并依赖于特定领域的专家知识来指导模型的训练。

多对一

多对一序列模型是数据分析中的专业工具,它们将一系列输入转化为一个单一输出。将多个输入合成为一个简明输出的过程构成了多对一模型的核心,使其能够提炼数据的本质特征。

这些模型有广泛的应用,例如情感分析,在这种应用中,像评论或帖子这样的词语序列被分析,以确定整体情感,例如正面、负面或中立。多对一的序列模型如图 10.2所示:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_02.png

图 10.2:多对一序列模型

多对一模型的训练过程是其功能中复杂但至关重要的一部分。它使得这些模型与一对多模型有所不同,后者的重点是从单一输入创建一个序列。相比之下,多对一模型必须高效地压缩信息,因此需要仔细选择算法并精确调节参数。

训练多对一模型的过程包括教会它识别输入序列的关键特征,并准确地在输出中表示这些特征。这需要舍弃不相关的信息,这项任务需要精细的平衡。训练过程通常还需要针对输入数据的特定性质进行专业的预处理和特征工程。

正如前一小节所讨论的,训练多对一模型可能比其他类型的模型更具挑战性,因为它需要更深入地理解数据中的潜在关系。在训练过程中持续监控模型的表现,以及系统地选择数据和超参数,对于模型的成功至关重要。

多对一模型因其能够将复杂数据简化为易于理解的洞察力而值得注意,广泛应用于各行各业的任务,如摘要、分类和预测。尽管它们的设计和训练可能非常复杂,但它们解读序列数据的独特能力为解决复杂数据分析挑战提供了创新的解决方案。

因此,多对一序列模型是当代数据分析中至关重要的工具,理解其特定的训练过程对于充分利用其能力至关重要。训练过程的特点是精确的算法选择、参数调优和领域专业知识,这使得这些模型与众不同。随着该领域的发展,多对一模型将继续为数据解读和应用做出宝贵贡献。

多对多

这是一种序列模型,它将序列数据作为输入,经过某种方式处理后,生成序列数据作为输出。多对多模型的一个例子是机器翻译,其中一种语言的词序列被翻译成另一种语言中的对应序列。例如,英语文本翻译成法语就是一个典型的例子。虽然有许多机器翻译模型属于这一类别,但一种突出的做法是使用**序列到序列(Seq2Seq)**模型,特别是与 LSTM 网络配合使用。使用 LSTM 的 Seq2Seq 模型已经成为处理英语到法语翻译等任务的标准方法,并已在多个 NLP 框架和工具中实现。多对多序列模型如图 10.3所示:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_03.png

图 10.3:多对多序列模型

多年来,已经开发出许多算法,用于处理和训练使用序列数据的机器学习模型。我们先从学习如何使用三维数据结构表示序列数据开始。

序列模型的数据表示

时间步(Timesteps)为数据增添了深度,使其成为一个三维结构。在序列数据的上下文中,这一维度的每个“单元”或实例被称为“时间步”。需要记住的是:虽然这一维度被称为“时间步”,但这一维度中的每个数据点都是一个“时间步”。图 10.4 展示了用于训练 RNN 的三维数据结构,强调了时间步的添加:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_04.png

图 10.4:RNN 训练中使用的三维数据结构

鉴于时间步的概念是我们探索中的新内容,特引入了一种特殊的符号表示法来有效地表示它。该符号是在尖括号中括住时间步,并与相关变量配对。例如,使用此符号表示法,https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_001.pnghttps://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_002.png分别表示变量stock_price在时间步t1t2的值。

将数据划分为批次的选择,本质上是决定“长度”,既可以是有意的设计决策,也可能受到外部工具和库的影响。通常,机器学习框架提供了自动批处理数据的工具,但选择最佳的批次大小可能需要结合实验和领域知识。

让我们从 RNN 开始讨论序列建模技术。

介绍 RNN

RNN(循环神经网络)是一种专门为处理序列数据而设计的神经网络。以下是其关键特性的解析。

“递归”一词源于 RNN 所具备的独特反馈回路。与传统的神经网络不同,传统神经网络本质上是无状态的,并且仅根据当前输入生成输出,而 RNN 则将一个“状态”从序列中的一步传递到下一步。

当我们谈论 RNN 中的“运行”时,我们指的是序列中某个元素的单次传递或处理。因此,随着 RNN 处理每个元素或每次“运行”,它会保留一些来自前一步的信息。

RNN 的魔力在于它们能够保持对先前运行或步骤的记忆。它们通过结合一个额外的输入——即来自前一步的状态或记忆——来实现这一点。这种机制使 RNN 能够识别和学习序列中元素之间的依赖关系,例如句子中连续单词之间的关系。

让我们详细研究 RNN 的架构。

理解 RNN 的架构

首先,让我们定义一些变量:

理解记忆单元和隐藏状态

RNNs之所以突出,是因为它们天生具有记住并维持上下文的能力,随着时间步的推进保持这种能力。某个时间步* t 的状态通过 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_006.png表示,其中h表示隐藏状态。这是截至某一特定时间步所学到的信息的总结。如图 10.5*所示,RNN 通过在每个时间步更新其隐藏状态不断学习。RNN 在每个时间步使用这个隐藏状态来保持上下文。从本质上讲,“上下文”是指 RNN 从之前的时间步中保留的集体信息或知识。它使 RNN 能够在每个时间步记住状态,并将这些信息传递到下一个时间步,随着序列的推进。这个隐藏状态使得 RNN 是有状态的:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_05.png

图 10.5:RNN 中的隐藏状态

例如,如果我们使用 RNN 将一句话从英语翻译成法语,那么每个输入就是需要定义为序列数据的句子。为了准确翻译,RNN 不能单独翻译每个单词。它需要捕捉到已经翻译的单词的上下文,从而使 RNN 能够正确翻译整个句子。这是通过在每个时间步计算并存储隐藏状态来实现的,并将其传递给后续时间步。

RNN 通过记住状态并打算将其用于未来时间步的策略带来了新的研究问题,需要解决。例如,记住什么以及忘记什么。而且,也许最棘手的问题是,何时忘记。RNN 的变种,如 GRU 和 LSTM,尝试以不同的方式回答这些问题。

理解输入变量的特性

让我们更深入地理解输入变量https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_007.png及其在处理 RNN 时的编码方法。RNN 的一个关键应用领域是在 NLP 中。在这里,我们处理的序列数据是句子。可以把每个句子看作是一个单词的序列,因此一个句子可以被描述为:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_008.png

在这个表示中,https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_009.png表示句子中的一个单独单词。为了避免混淆:每个https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_010.png并不是整个句子,而是其中的一个单独单词。

每个单词https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_011.png都使用一个独热编码向量进行编码。该向量的长度由|V|定义,其中:

  • V 表示我们的词汇集合,它是一个包含不同单词的集合。

  • |V|量化了 V 中条目的总数。

在广泛应用的背景下,可以将 V 视为包含标准英语词典中所有单词的集合,通常包含大约 150,000 个单词。然而,对于特定的 NLP 任务,只需要这个庞大词汇表的一部分。

注意:区分 V 和|V|非常重要。V 代表词汇表本身,而|V|表示该词汇表的大小。

当提到“词典”时,我们指的是标准英语词典的一般概念。然而,也有更为详尽的语料库可用,如 Common Crawl,其中包含的词集可以达到数千万个单词。

对于许多应用程序来说,这个词汇表的子集就足够了。形式化地说,

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_012.png

为了理解 RNN 的工作原理,让我们先看看第一个时间步t1

在第一个时间步训练 RNN

RNN 通过一次分析一个时间步的序列来操作。让我们深入了解这一过程的初始阶段。在时间步t1,网络接收表示为https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_013.png的输入。基于这个输入,RNN 做出初步预测,我们将其表示为https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_014.png。在每个时间步tt,RNN 利用来自前一个时间步的隐藏状态https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_015.png,以提供上下文信息。

然而,在t1时刻,由于我们刚刚开始,因此没有前一个隐藏状态可以引用。因此,隐藏状态https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_016.png初始化为零。

激活函数的作用

参考图 10.6,你会注意到一个标记为A的元素。它代表激活函数,这是神经网络中的关键组件。本质上,激活函数决定了多少信号传递到下一层。在这个时间步,激活函数接收输入https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_017.png和前一个隐藏状态https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_018.png

第八章所述,神经网络中的激活函数是一个数学方程式,根据输入决定神经元的输出。它的主要作用是将非线性引入网络,从而使其能够通过误差进行学习和调整,这对于学习复杂的模式至关重要。

许多神经网络中常用的激活函数是“tanh”。那么,这种偏好的背后有什么原因呢?

神经网络的世界并非没有挑战,其中一个难题就是梯度消失问题。简单来说,当我们持续训练模型时,梯度值——指导我们调整权重的数值——有时会变得非常小。这种下降意味着我们对网络权重的调整几乎可以忽略不计。这些微小的调整导致学习过程变得极其缓慢,有时甚至会停滞不前。这时,“tanh”函数就发挥了作用。之所以选择它,是因为它在对抗梯度消失问题时充当了缓冲作用,推动训练过程朝着一致性和效率的方向发展:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_06.png

图 10.6:RNN 在时间步 t1 的训练

当我们聚焦于激活函数的结果时,我们得到了隐藏状态的值,https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_019.png。在数学上,这种关系可以表示为:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_020.png

这个隐藏状态不仅仅是一个过渡阶段。当我们进入下一个时间步,t2时,它依然保持重要的价值。可以把它想象成接力赛选手传递接力棒,或者在这个例子中,是从一个时间步到下一个时间步的上下文传递,确保序列的连续性。

第二个激活函数(在图 10.7中由B表示)用于生成时间步t1的预测输出 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_021.png。选择这个激活函数将取决于输出变量的类型。例如,如果 RNN 用于预测股市价格,可以采用 ReLU 函数,因为输出变量是连续的。另一方面,如果我们对一堆帖子进行情感分析,可能会使用 sigmoid 激活函数。在图 10.7中,假设它是一个多类输出变量,我们使用的是 softmax 激活函数。请记住,多类输出变量指的是输出或预测可以落入多个不同类别中的情况。在机器学习中,这种情况通常出现在分类问题中,目标是将输入归类为几个预定义类别之一。例如,如果我们将物体分类为汽车、自行车或公交车,则输出变量有多个类别,因此称为“多类”。在数学上,我们可以将其表示为:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_022.png

方程 10.1方程 10.2可以明显看出,训练 RNN 的目标是找到三组权重矩阵(W[hx]、W[hh]和W[yh])和两组偏置(b[h]和b[y])的最优值。随着训练的进展,显而易见,这些权重和偏置在所有时间步中保持一致。

训练整个序列的 RNN

之前,我们为第一个时间步t1推导了隐藏状态的数学公式。现在,我们通过多个时间步来研究 RNN 的工作原理,以训练完整的序列,如图 10.7所示:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_07.png

图 10.7:RNN 中的顺序处理

信息:在图 10.7中,可以观察到隐藏状态从左到右传播,并通过箭头A将上下文信息向前传递。RNN 及其变体能够创建这种“信息高速公路”并在时间上传播,是 RNN 的定义特征。

我们为时间步t1计算了方程 10.1。对于任何时间步 t,我们可以将方程 10.1推广为:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_023.png

对于 NLP 应用,https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_024.png被编码为一个独热向量。在这种情况下,https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_025.png的维度将等于|V|,其中 V 是表示词汇表的向量。隐藏变量https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_026.png将是原始输入https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_025.png的低维表示。通过将输入变量https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_028.png的维度降低多个倍数,我们希望隐藏层仅捕捉输入变量https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_025.png的重要信息。https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_006.png的维度由D[h]表示。

对于https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_006.png的维度比https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_032.png低 500 倍,这并不罕见。

所以,通常情况下:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_033.png

由于https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_034.png的维度较低,权重矩阵W[hh]相对较小,类似于https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_035.png。另一方面,W[hx]的宽度将与https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_036.png一样宽。

合并权重矩阵

方程 10.3中,W[hh]和W[hx]都用于计算https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_037.png。为了简化分析,有助于将W[hh]和W[hx]合并为一个权重参数矩阵,https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_038.png。这种简化表示对于后续章节中讨论的更复杂的 RNN 变体非常有用。

为了创建一个合并的权重矩阵W[h],我们只需将W[hh]和W[hx]水平拼接,形成一个合并的权重矩阵W[h]:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_039.png

由于我们只是进行水平拼接,W[h]的维度将有相同数量的行和总列数,即:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_040.png

方程 10.3中使用W[h]:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_041.png

其中 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_042.png 表示将两个向量垂直堆叠在一起。

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_043.png

其中 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_044.pnghttps://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_045.png 是相应的转置向量。

让我们看一个具体的例子。

假设我们正在为自然语言处理(NLP)应用使用 RNN。词汇表的大小是 50,000 个单词。这意味着每个输入 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_025.png 将被编码为一个维度为 50,000 的热编码向量。假设 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_047.png 的维度为 50。它将是 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_025.png 的低维表示。

现在,应该显而易见,W[hh] 的维度将是 (50https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_049.png50)。W[hx] 的维度将是 (50https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_049.png50,000)。

回到上面的例子,W[h] 的维度将是 (50x50,000+50) = 50https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_051.png50,050,即:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_052.png

计算每个时间步的输出

在我们的模型中,给定时间步(如 t1)生成的输出由 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_053.png 表示。由于我们在模型中使用了 softmax 函数进行归一化,因此任何时间步 tt 的输出可以通过以下方程式进行概括:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_054.png

理解在每个时间步如何计算输出,为随后的训练阶段奠定基础,在这一阶段,我们需要评估模型的表现。

现在我们已经掌握了如何在每个时间步生成输出,接下来需要确定这些预测输出与实际目标值之间的差异。这种差异被称为“损失”,它为我们提供了模型误差的衡量标准。在接下来的部分,我们将深入探讨计算 RNN 损失的方法,帮助我们评估模型的准确性,并对权重和偏差进行必要的调整。这个过程对于训练模型使其做出更准确的预测至关重要,从而提高整体性能。

计算 RNN 损失

如前所述,训练 RNN 的目标是找到三组权重(W[hx]、W[hh] 和 W[yh])以及两组偏差(b[h] 和 b[y])的正确值。最初,在时间步 t1,这些值会随机初始化。

随着训练过程的推进,这些值会随着梯度下降算法的应用而发生变化。我们需要在 RNN 的前向传播过程中计算每个时间步的损失。让我们分解计算损失的过程:

  1. 计算单个时间步的损失

    在时间步 t1,预测输出为 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_055.png。期望输出为 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_056.png。实际使用的损失函数将取决于我们训练的模型类型。例如,如果我们正在训练分类器,那么在时间步 t1 的损失将是:

    https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_057.png

  2. 完整序列的聚合损失

    对于由多个时间步组成的完整序列,我们将计算每个时间步的单独损失, {t[1],t[2],…t[T]}。一个包含 T 个时间步的序列的损失,将是每个时间步损失的汇总,按照以下公式计算:

    https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_058.png

  3. 计算一批次中多个序列的损失

    如果一个批次中有多个序列,首先会为每个单独的序列计算损失。然后,我们计算一个批次中所有序列的总体损失,并将其用于反向传播。

    通过以这种结构化的方式计算损失,我们引导模型调整其权重和偏差,以更好地与期望的输出对齐。这个迭代过程,在多个批次和时期中反复进行,使得模型能够从数据中学习并做出更准确的预测。

时间反向传播

第八章所述,反向传播用于神经网络中,从训练数据集的示例中逐步学习。RNN 在训练数据中增加了另一个维度,即时间步。时间反向传播BPTT)旨在处理序列数据,因为训练过程是通过时间步进行的。

当前馈过程计算完一个批次的最后一个时间步的损失时,触发反向传播。然后我们应用这个导数来调整 RNN 模型的权重和偏差。RNN 有三组权重,W[hh],W[hx] 和 W[hy],以及两组偏差(b[h] 和 b[y])。一旦调整了权重和偏差,我们将继续进行梯度下降来训练模型。

本节的名称 时间反向传播 并不暗示任何让我们回到中世纪时代的时间机器。它的来源在于一旦通过前馈计算了成本,必须通过每个时间步倒推并更新权重和偏差。

反向传播过程对于调优模型的参数至关重要,但一旦模型训练完成,接下来该做什么呢?在我们使用反向传播来最小化损失之后,模型已经准备好进行预测。在接下来的部分,我们将探讨如何使用训练好的 RNN 模型对新数据进行预测。我们会发现,使用 RNN 进行预测与使用全连接神经网络的过程类似,输入数据经过训练好的 RNN 处理后,生成预测结果。这种从训练到预测的转变,形成了理解 RNN 如何应用于现实问题的自然进展。

使用 RNN 进行预测

一旦模型训练完成,使用 RNN 进行预测与使用全连接神经网络进行预测类似。将输入数据作为输入传递给训练好的 RNN 模型,得到预测结果。其工作原理如下:

  1. 输入准备:与标准神经网络一样,首先要准备输入数据。对于 RNN 而言,这些输入数据通常是序列化的,代表过程或系列中的时间步。

  2. 模型利用:接着将输入数据输入到已训练的 RNN 模型中。模型在训练阶段优化的学习权重和偏置将用于通过网络的每一层处理输入数据。在 RNN 中,这包括通过处理数据的循环连接来处理数据的序列特性。

  3. 激活函数:与其他神经网络一样,RNN 中的激活函数会在数据通过各层时对其进行转换。根据 RNN 的具体设计,可能在不同阶段使用不同的激活函数。

  4. 生成预测:倒数第二步是生成预测。RNN 的输出会通过最后一层进行处理,通常在分类任务中使用 softmax 激活函数,生成每个输入序列的最终预测结果。

  5. 解释:预测结果将根据具体任务进行解释。这可能是分类一段文本序列、预测时间序列中的下一个值,或者任何依赖于序列数据的其他任务。

因此,RNN 的预测过程与全连接神经网络相似,主要区别在于对序列数据的处理。RNN 捕捉数据中时间关系的能力使其能够提供其他神经网络架构难以处理的独特洞察和预测。

基本 RNN 的局限性

在本章的前面,我们介绍了基本 RNN。有时我们将基本 RNN 称为“纯粹的香草”RNN。这个术语指的是它们的基本、朴素结构。虽然它们作为递归神经网络的一个良好入门,但这些基本 RNN 确实有显著的局限性:

  1. 梯度消失问题:这个问题使得 RNN 很难学习和保持数据中的长期依赖关系。

  2. 无法预见序列中的未来:传统的 RNN 从头到尾处理序列,这限制了它们理解序列中未来上下文的能力。

让我们逐一探讨这些问题。

梯度消失问题

RNN 逐步处理输入数据,一次一个时间步。这意味着随着输入序列变长,RNN 很难捕捉长期依赖关系。长期依赖关系指的是序列中相距较远的元素之间的关系。想象一下分析一段长篇文本,比如一部小说。如果一个角色在第一章的行为影响了最后一章的事件,那就是一个长期依赖关系。文本开头的信息必须“记住”直到结尾,才能充分理解。

RNN 通常在处理这样的长程依赖时会遇到困难。RNN 的隐藏状态机制旨在保留来自前一个时间步的信息,但它过于简单,无法捕捉这些复杂的关系。随着相关元素之间距离的增加,RNN 可能会丧失对连接的跟踪。它没有智能来判断何时保存记忆、何时忘记信息。

对于许多顺序数据的应用场景,只有最新的信息是重要的。例如,考虑一个预测文本应用,它试图通过建议下一个要输入的单词来协助一个人写邮件。

如我们所知,这种功能现在在现代文字处理软件中是标准配置。如果用户正在输入:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_08.png

图 10.8:预测文本示例

预测文本应用可以轻松地建议下一个单词“hard”。它不需要带入前一句话的上下文来预测下一个单词。对于这种不需要长时记忆的应用,RNN 是最佳选择。RNN 在不牺牲准确性的情况下,不会使架构过于复杂。

但对于其他应用,保留长时依赖关系是重要的。RNN 在管理长时依赖关系时遇到困难。让我们来看一个例子:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_09.png

图 10.9:带有长时依赖的预测文本示例

当我们从左到右阅读这句话时,可以观察到“was”(稍后在句子中使用)指的是“man”。原始形式的 RNN 在多时间步长中会很难保持隐藏状态的传递。原因在于,在 RNN 中,隐藏状态是针对每个时间步计算的,并且被传递到下一个时间步。

由于此操作的递归特性,我们总是担心在不同时间步从一个元素到另一个元素的过程中,信号会提前衰减。RNN 的这种行为被称为梯度消失问题。为了应对梯度消失问题,我们通常选择 tanh 作为激活函数。由于 tanh 的二阶导数衰减到零的速度非常慢,选择 tanh 有助于在一定程度上管理梯度消失问题。但是我们需要更复杂的架构,如 GRU 和 LSTM,以更好地管理梯度消失问题,下一节将详细讨论这一点。

无法在序列中向前看

RNN 可以根据信息流动的方向分类。主要有两种类型:单向 RNN 和双向 RNN。

  • 单向 RNN:这些网络以单一方向处理输入数据,通常是从序列的开始到结束。它们将上下文信息逐步传递,随着序列元素的迭代(如句子中的单词),逐步建立理解。其局限性在于:单向 RNN 无法在序列中“向前看”。

    它们只能访问到迄今为止所看到的信息,这意味着它们无法结合未来的元素来构建更准确或更细致的上下文。想象一下,逐字阅读一篇复杂的句子,而无法提前预览即将出现的内容。你可能会错过一些细微之处或误解整体意义。

  • 双向 RNN:相反,双向 RNN 同时处理序列的两个方向。它们结合了过去和未来元素的信息,使得对上下文有更丰富的理解。

让我们考虑以下两句话:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_10.png

图 10.10:RNN 必须在句子中提前查看的示例

这两句话都使用了“cricket”这个词。如果上下文仅从左到右构建,就像单向 RNN 那样,我们就无法正确地理解“cricket”,因为其相关信息将在未来的时间步中出现。为了解决这个问题,我们将研究双向 RNN,它们在第十一章中有详细讨论。

现在让我们研究 GRU 及其详细的工作原理和架构。

GRU

GRU 代表了基本 RNN 结构的一种演变,特别设计用来解决传统 RNN 遇到的一些挑战,例如梯度消失问题。GRU 的架构如图 10.8所示:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_11.png

图 10.11:GRU

让我们从讨论第一种激活函数开始,该函数标注为A。在每个时间步 t,GRU 首先使用 tanh 激活函数计算隐藏状态,并利用 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_059.pnghttps://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_060.png 作为输入。这个计算与上一节中介绍的原始 RNN 中隐藏状态的确定方法没有什么不同。但是有一个重要的区别。输出是一个候选隐藏状态,它是通过公式 10.6计算得出的:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_061.png

其中 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_062.png 是隐藏层的候选值。

现在,GRU 不会立即使用候选隐藏状态,而是花时间决定是否使用它。可以想象成某人做决定之前停下来思考。这一停顿思考的过程就是我们所说的门控机制。它检查信息,然后选择接下来要记住的细节和要遗忘的部分。它有点像过滤掉噪音,集中注意力在重要的东西上。通过将旧信息(来自之前的隐藏状态)和新草案(候选状态)结合起来,GRU 能够更好地跟随长篇故事或序列,而不会迷失方向。通过引入候选隐藏状态,GRU 增加了额外的灵活性。它们可以谨慎地决定将候选状态的哪一部分纳入。这个区别使得 GRU 能够巧妙地应对诸如梯度消失之类的挑战,而传统 RNN 通常缺乏这种能力。简单来说,经典的 RNN 可能难以记住长篇故事,而 GRU 凭借其独特的特点,更像是优秀的听众和记忆者。

LSTM 是在 1997 年提出的,而 GRU 则是在 2014 年提出的。大多数关于这一主题的书籍倾向于按时间顺序呈现,首先介绍 LSTM。我选择按复杂度顺序呈现这些算法。由于 GRU 的提出动机是简化 LSTM,因此从学习较简单的算法开始可能会更有帮助。

引入更新门

在标准的 RNN 中,每个时间步的隐藏值都会被计算并自动成为记忆单元的新状态。相比之下,GRU 引入了一个更细致的方法。GRU 模型通过允许控制何时更新记忆单元的状态,为这个过程带来了更多的灵活性。这个增加的灵活性是通过一个叫做“更新门”的机制实现的,有时也被称为“重置门”。

更新门的作用是评估候选隐藏状态中的信息,https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_063.png,是否足够重要以更新记忆单元的隐藏状态,或者记忆单元是否应该保留之前时间步的旧隐藏值。

从数学角度来看,这个决策过程帮助模型更加有选择性地管理信息,决定是整合新的见解,还是继续依赖之前获得的知识。如果模型认为候选隐藏状态的信息不足以改变记忆单元当前的状态,那么就会保留之前的隐藏值。相反,如果新的信息被认为相关,它就会覆盖记忆单元的状态,从而在处理序列时调整模型的内部表示。

这种独特的门控机制使得 GRU 区别于传统的 RNN,并且使得它能在处理具有复杂时间关系的序列数据时,进行更有效的学习。

实现更新门

我们在记忆单元中如何更新状态所加入的智能是 GRU 的定义特征。很快我们将决定是否应该用候选隐藏状态更新当前的隐藏状态。为了做出这个决定,我们使用图 10.11中显示的第二个激活函数,标注为B。这个激活函数实现了更新门。

它作为一个 sigmoid 层实现,该层以当前输入和先前的隐藏状态为输入。sigmoid 层的输出是一个介于 0 和 1 之间的值,由变量https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_064.png表示。更新门的输出是变量https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_065.png,它由以下 sigmoid 函数控制:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_066.png

由于https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_067.png是 sigmoid 函数的输出,它接近于 1 或 0,这决定了更新门是否开启。如果更新门开启,https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_068.png将被选为新的隐藏状态。在训练过程中,GRU 将学习何时开启门,何时关闭门。

更新隐藏单元

对于某个时间步,下一隐藏状态是通过以下方程的计算得出的:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_069.png

方程 10.8 包含两个项,标注为12。作为 sigmoid 函数的输出,https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_070.png可以是 0 或 1。这意味着:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_071.png

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_072.png

换句话说,如果门是开启的,更新https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_073.png的值。否则,只需保留旧状态。

现在让我们来看看如何在多个时间步上运行 GRU。

在多个时间步上运行 GRU

当在多个时间步上部署 GRU 时,我们可以像图 10.12所示那样可视化这个过程。就像我们在前一部分讨论的基础 RNN 一样,GRU 创建了可以看作“信息高速公路”的东西。这条路径有效地将上下文从序列的开始传递到结束,在图 10.12中可视化为https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_074.png,并标注为A

GRU 与传统 RNN 的区别在于它关于信息如何在这条高速公路上传输的决策过程。与每个时间步盲目地传递信息不同,GRU 会暂停并评估其相关性。

让我们通过一个基本的例子来说明。假设你正在阅读一本书,每个句子都是一条信息。然而,与你记住每个句子的每个细节不同,你的大脑(像一个 GRU)会选择性地回忆起那些最有影响力或最有情感的句子。这种选择性记忆类似于 GRU 中更新门的工作方式。

更新门在这里发挥着至关重要的作用。它是一个机制,决定哪些先前的信息,或先前的“隐藏状态”,应该保留或丢弃。本质上,更新门帮助网络聚焦并保留最相关的细节,确保传递的上下文保持尽可能相关。

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_12.png

图 10.12:RNN 中的顺序处理

引入 LSTM

RNN 广泛应用于序列建模任务,但它们在捕捉数据中的长期依赖性方面存在局限性。为了克服这些局限性,开发了 RNN 的高级版本——LSTM。与简单的 RNN 不同,LSTM 具有更复杂的机制来管理上下文,使其能够更好地捕捉序列中的模式。

在上一节中,我们讨论了 GRU,其中隐藏状态 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_006.png 用于将上下文从一个时间步传递到下一个时间步。LSTM 拥有更为复杂的机制来管理上下文。它有两个变量来携带上下文信息:细胞状态和隐藏状态。它们的解释如下:

  1. 细胞状态(表示为 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_076.png):它负责维护输入数据的长期依赖性。它从一个时间步传递到下一个时间步,用于在更长时间内保持信息。正如我们在本节稍后将学到的,细胞状态的内容是由遗忘门和更新门精确决定的。它可以被视为 LSTM 的“持久层”或“记忆”,因为它在较长时间内保持信息。

  2. 隐藏状态(表示为 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_077.png):该上下文关注于当前时间步,它可能对长期依赖性重要,也可能不重要。它是 LSTM 单元在特定时间步的输出,并作为输入传递到下一个时间步。如图 10.23所示,隐藏状态 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_078.png 用于生成时间步 t 的输出 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_079.png

现在让我们更详细地研究这些机制,从当前细胞状态如何更新开始。

引入遗忘门

LSTM 网络中的遗忘门负责确定从先前状态中丢弃哪些信息,保留哪些信息。它在图 10.3中标注为A。它实现为一个 sigmoid 层,输入为当前输入和先前的隐藏状态。sigmoid 层的输出是一个在 0 到 1 之间的值向量,每个值对应于 LSTM 记忆中一个单元的状态。

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_080.png

由于它是一个 sigmoid 函数,这意味着 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_081.png 可以接近 0 或接近 1。

如果 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_082.png 为 1,则意味着应使用来自前一状态 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_083.png 的值来计算 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_084.png。如果 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_085.png 为 0,则意味着应忘记来自前一状态 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_086.png 的值。

信息:通常,二进制变量在其逻辑为 1 时被认为是激活的。当 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_087.png = 0 时,“遗忘门” 忘记前一状态的行为可能显得不直观,但这是原始论文中提出的逻辑,研究人员为了保持一致性遵循了这一点。

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_13.png

图 10.13:LSTM 架构

候选单元状态

在 LSTM 中,在每个时间步,计算出一个候选单元状态,https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_088.png,它在图 10.13中标注为 Y,并作为提议的新状态用于记忆单元。它通过当前输入 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_025.png 和前一隐藏状态 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_090.png 来计算,公式如下:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_091.png

更新门

更新门也叫做输入门。LSTM 网络中的更新门是一种机制,它允许网络有选择地将新信息融入当前状态,使得记忆能够集中关注最相关的信息。它在图 10.13中标注为 B

它负责判断候选单元状态 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_088.png 是否应添加到 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_093.png 中。它作为一个 sigmoid 层实现,输入为当前输入 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_025.png 和前一时刻的隐藏状态:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_095.png

sigmoid 层的输出,https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_096.png,是一个值介于 0 和 1 之间的向量,每个值对应 LSTM 记忆中的一个单元。值为 0 表示计算出的 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_097.png 应该被忽略,而值为 1 表示 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_098.png 足够重要,应该被纳入 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_099.png 中。作为一个 sigmoid 函数,它的值可以介于 0 和 1 之间,表示来自 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_097.png 的部分信息应该被融入 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_093.png,但不是全部。

更新门允许 LSTM 有选择地将新信息融入当前状态,防止记忆被无关数据淹没。通过控制新信息加入记忆状态的量,更新门帮助 LSTM 保持在保留前一状态和融入新信息之间的平衡。

计算记忆状态

与 GRU 相比,LSTM 的主要区别在于,LSTM 不仅有一个更新门(如 GRU 中的那样),还为隐藏状态管理提供了独立的更新和遗忘门。每个门决定了各种状态的正确混合,以最优地计算长时记忆 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_093.png、当前单元状态和当前隐藏状态 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_078.png。记忆状态通过以下方式计算:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_104.png

方程 10.12 由标注为 12 的两个项组成。作为 sigmoid 函数的输出,https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_105.pnghttps://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_106.png 的值可以是 0 或 1。意味着:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_071.png

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_072.png

换句话说,如果门是开启的,则更新 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_037.png 的值。否则,保留旧状态。

因此,GRU 中的更新门是一种机制,允许网络有选择地丢弃之前隐藏状态中的信息,以便隐藏状态可以专注于最相关的信息。如图 10.13所示,展示了状态如何从左向右传递。

输出门

LSTM 网络中的输出门在图 10.13中标注为 C。它负责确定当前记忆状态中的哪些信息应作为 LSTM 的输出传递。它作为一个 sigmoid 层实现,输入为当前输入和前一个隐藏状态。sigmoid 层的输出是一个值在 0 和 1 之间的向量,其中每个值对应于 LSTM 内存中的一个单独的单元。

由于它是一个 sigmoid 函数,这意味着 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_110.png 可以接近 0 或 1。

如果 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_111.png 为 1,则表示应该使用之前状态的值 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_099.png 来进行计算。若 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_081.png 为 0,则表示应该忘记之前状态的值 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_099.png

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_116.png

值为 0 表示对应的单元不应对输出作出贡献,而值为 1 则表示该单元应完全贡献于输出。介于 0 和 1 之间的值表示该单元应部分贡献其值给输出。

在 LSTM 中,经过输出门处理后,当前状态会通过一个 tanh 函数。该函数调整值,使其落在 -1 到 1 的范围内。为什么需要这种缩放?tanh 函数确保 LSTM 的输出保持归一化,并防止值变得过大,这在训练过程中可能会导致梯度爆炸等问题。

经缩放后,输出门的结果与此归一化状态相乘。这个组合值表示 LSTM 在特定时间步的最终输出。

为了提供一个简单的类比:想象调整音乐的音量,使其既不太大也不太小,而是刚好适合你的环境。tanh函数的作用类似,确保输出是优化的,适合进一步处理。

输出门非常重要,因为它允许 LSTM 从当前的记忆状态中选择性地传递相关信息作为输出。它还帮助防止无关信息被作为输出传递。

该输出门生成变量 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_117.png,决定细胞状态对隐藏状态的贡献是否输出:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_118.png

在 LSTM 中,https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_119.png 被用作输入到门中,而 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_093.png 是隐藏状态。

总结来说,LSTM 网络中的输出门是一种机制,它允许网络从当前记忆状态中选择性地传递相关信息作为输出,这样 LSTM 就可以根据其存储的相关信息生成适当的输出。

将所有内容汇总

让我们深入了解 LSTM 在多个时间步中的工作原理,如图 10.14中的A所示。

就像 GRU 一样,LSTM 创建了一条通道——通常称为“信息高速公路”——帮助将上下文传递到后续的时间步。这个过程在图 10.14中有所展示。LSTM 的魅力在于它能够利用长期记忆来传递这些上下文。

当我们从一个时间步推进到下一个时间步时,LSTM 会学习哪些信息应该保留在其长期记忆中,表示为 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_076.png。在每个时间步的开始,https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_093.png 与“忘记门”进行交互,允许一些信息被丢弃。接着,它遇到“更新门”,新的数据被注入其中。这使得 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_093.png 能够在时间步之间转换,按照两个门的指示,不断地获得和舍弃信息。

现在,事情变得复杂了。在每个时间步结束时,长期记忆的副本 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_093.png 通过 tanh 函数进行转换。处理后的数据经过输出门筛选,最终得到我们称之为短期记忆的结果 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_077.png。这个短期记忆有双重作用:它决定了特定时间步的输出,并为随后的时间步奠定基础,如图 10.14所示:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_14.png

图 10.14:带有多个时间步的 LSTM

现在让我们看看如何编写 RNN 的代码。

编写顺序模型

在我们的 LSTM 探索中,我们将深入研究使用著名的 IMDb 电影评论数据集进行情感分析。在这里,每条评论都被标记为一个情感,正面或负面,使用二进制值进行编码(True表示正面,False表示负面)。我们的目标是创建一个二分类器,能够仅根据评论的文本内容预测这些情感。

总体而言,该数据集包含 50,000 条电影评论。为了我们的目的,我们将其平分:25,000 条用于训练模型,剩下的 25,000 条用于评估模型的性能。

对于那些希望深入了解数据集的人,更多信息可以在斯坦福的 IMDB 数据集中找到。

加载数据集

首先,我们需要加载数据集。我们将通过keras.datasets导入该数据集。通过keras.datasets导入的优势在于,它已经被处理成可以用于机器学习的格式。例如,评论已被单独编码为单词索引的列表。特定单词的总体频率被选为索引。因此,如果一个单词的索引是“7”,这意味着它是第 7 个最常见的单词。使用预处理好的数据让我们能够专注于 RNN 算法,而不是数据准备:

import tensorflow as tf
from tensorflow.keras.datasets import imdb
vocab_size = 50000
(X_train,Y_train),(X_test,Y_test) = tf.keras.datasets.imdb.load_data(num_words= vocab_size) 

请注意,参数num_words=50000用于仅选择前 50000 个词汇。由于单词的频率被用作索引,这意味着所有索引小于 50000 的单词都会被过滤掉:

"I watched the movie in a cinema and I really like it" 
[13, 296, 4, 20, 11, 6, 4435, 5, 13, 66, 447,12] 

在处理长度不等的序列时,通常需要确保它们都具有相同的长度。这一点在将序列输入神经网络时尤为重要,因为神经网络通常期望输入的大小一致。为此,我们使用填充——在序列的开头或末尾添加零,直到它们达到指定的长度。

下面是如何使用 TensorFlow 实现这一点:

# Pad the sequences
max_review_length = 500
x_train = tf.keras.preprocessing.sequence.pad_sequences(x_train, maxlen=max_review_length)
x_test = tf.keras.preprocessing.sequence.pad_sequences(x_test, maxlen=max_review_length) 

索引非常适合算法处理。为了便于人类阅读,我们可以将这些索引转换回单词:

word_index = tf.keras.datasets.imdb.get_word_index()
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
def decode_review(padded_sequence):
    return " ".join([reverse_word_index.get(i - 3, "?") for i in padded_sequence]) 

请注意,单词索引从 3 开始,而不是从 0 或 1. 这是因为前三个索引是保留的。

接下来,让我们看看如何准备数据。

准备数据

在我们的例子中,我们考虑的词汇量为 50,000 个单词。这意味着输入序列中的每个单词https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_10_025.png都将使用一个 one-hot 向量表示,其中每个向量的维度为 50,000. One-hot 向量是一个二进制向量,除了与单词对应的索引位置为 1 外,其他位置都是 0。下面是我们如何在 TensorFlow 中加载 IMDb 数据集,并指定词汇大小:

vocab_size = 50000
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.imdb.load_data(num_words=vocab_size) 

请注意,由于vocab_size被设置为50,000,因此数据将加载前50,000个最常出现的单词,其余单词将被丢弃或替换为一个特殊的符号(通常用<UNK>表示“未知”)。这确保了我们的输入数据是可管理的,并且只包含对模型最相关的信息。变量x_trainx_test分别包含训练和测试输入数据,而y_trainy_test则包含相应的标签。

创建模型

我们首先定义一个空的堆栈。我们将使用这个堆栈逐层构建我们的网络:

model = tf.keras.models.Sequential() 

接下来,我们将向模型中添加一个Embedding层。如果你还记得我们在第九章中讨论的词嵌入,我们使用它们来表示词汇在连续向量空间中的位置。Embedding层也起到类似的作用,但它是在神经网络中进行的。它提供了一种将词汇表中的每个单词映射到连续向量的方式。彼此接近的词在这个向量空间中可能会共享上下文或意义。

让我们定义Embedding层,考虑到我们之前选择的词汇表大小,并将每个词映射到一个 50 维的向量,对应于!的维度:

model.add(
    tf.keras.layers.Embedding(
        input_dim = vocab_size, 
        output_dim = 50, 
        input_length = review_length 
    )
) 

Dropout层可以防止过拟合,并通过在学习阶段随机禁用神经元,迫使模型学习同一数据的多种表示。让我们随机禁用 25%的神经元以应对过拟合:

model.add(
    tf.keras.layers.Dropout(
        rate=0.25
    )
) 

接下来,我们将添加一个 LSTM 层,它是 RNN 的一种专门形式。虽然基础 RNN 在学习长期依赖关系时存在问题,LSTM 旨在记住这些依赖关系,使其适用于我们的任务。这个 LSTM 层将分析评论中词语的顺序及其嵌入,利用这些信息来确定给定评论的情感。我们将在这一层中使用 32 个单元:

model.add(
    tf.keras.layers.LSTM(
        units=32 
    )
) 

添加第二个Dropout层,随机丢弃 25%的神经元,以减少过拟合:

model.add(
    tf.keras.layers.Dropout(
        rate=0.25
    )
) 

所有 LSTM 单元都连接到Dense层中的一个节点。一个 sigmoid 激活函数决定了这个节点的输出——一个介于 0 和 1 之间的值。接近 0 表示负面评论,接近 1 表示正面评论:

model.add(
    tf.keras.layers.Dense(
        units=1, 
        activation='sigmoid' 
    )
) 

现在,让我们编译模型。我们将使用binary_crossentropy作为损失函数,Adam作为优化器:

model.compile(
    loss=tf.keras.losses.binary_crossentropy, 
    optimizer=tf.keras.optimizers.Adam(), 
    metrics=['accuracy']) 

显示模型结构的摘要:

model.summary() 
__________________________________________________________________________
Layer (type)               Output Shape               Param #
=========================================================================
embedding (Embedding)      (None, 500, 32)            320000
dropout (Dropout)          (None, 500, 32)            0
lstm (LSTM)                (None, 32)                 8320
dropout_1 (Dropout)        (None, 32)                 0
dense (Dense)              (None, 1)                  33
=========================================================================
Total params: 328,353
Trainable params: 328,353
Non—trainable params: 0 

训练模型

我们现在将基于训练数据训练 LSTM 模型。训练模型涉及几个关键组件,每个组件如下所述:

  • 训练数据:这些是模型将从中学习的特征(评论)和标签(正面或负面情感)。

  • 批次大小:这决定了每次更新模型参数时将使用的样本数量。较大的批次大小可能需要更多的内存。

  • Epochs:一个 epoch 是对整个训练数据集的一次完整迭代。epoch 越多,学习算法就会越多次地遍历整个训练数据集。

  • Validation Split:这部分训练数据将被用于验证,不用于训练。它帮助我们评估模型的表现。

  • Verbose:这个参数控制模型在训练过程中会输出多少内容。值为 1 时,进度条会显示出来:

    history = model.fit(
        x_train, y_train,    # Training data
        batch_size=256,      
        epochs=3,            
        validation_split=0.2,
        verbose=1            
    ) 
    
    Epoch 1/3
    79/79 [==============================] - 75s 924ms/step - loss: 0.5757 - accuracy: 0.7060 - val_loss: 0.4365 - val_accuracy: 0.8222
    Epoch 2/3
    79/79 [==============================] - 79s 1s/step - loss: 0.2958 - accuracy: 0.8900 - val_loss: 0.3040 - val_accuracy: 0.8812
    Epoch 3/3
    79/79 [==============================] - 73s 928ms/step - loss: 0.1739 - accuracy: 0.9437 - val_loss: 0.2768 - val_accuracy: 0.8884 
    

查看一些错误的预测

让我们看一下其中一些错误分类的评论:

predicted_probs = model.predict(x_test)
predicted_classes_reshaped = (predicted_probs > 0.5).astype("int32").reshape(-1)
incorrect = np.nonzero(predicted_classes_reshaped != y_test)[0] 

我们选择了前 20 条被错误分类的评论:

class_names = ["Negative", "Positive"]
for j, incorrect_index in enumerate(incorrect[0:20]):
    predicted = class_names[predicted_classes_reshaped[incorrect_index]]
    actual = class_names[y_test[incorrect_index]]
    human_readable_review = decode_review(x_test[incorrect_index])
    print(f"Incorrectly classified Test Review [{j+1}]")
    print(f"Test Review #{incorrect_index}: Predicted [{predicted}] Actual [{actual}]")
    print(f"Test Review Text: {human_readable_review.replace('<PAD> ', '')}\n") 

总结

本章解释了顺序模型的基础概念,旨在让你对这些技术及其方法有一个基本的了解。在这一章中,我们介绍了适合处理顺序数据的 RNN。GRU 是一种 RNN 类型,由 Cho 等人于 2014 年提出,作为 LSTM 网络的简化替代方案。

和 LSTM 一样,GRU 被设计用来学习顺序数据中的长期依赖关系,但它们采用了不同的方法。GRU 使用单一的门控机制来控制信息在隐藏状态中流入和流出的过程,而 LSTM 使用三个门控机制。这使得 GRU 更容易训练,参数更少,使用起来更高效。

下一章介绍了一些与顺序模型相关的高级技术。

了解更多内容,请访问 Discord

要加入这本书的 Discord 社区,在这里你可以分享反馈、向作者提问并了解新版本,请扫描下面的二维码:

packt.link/WHLel

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/QR_Code1955211820597889031.png

第十一章:高级顺序建模算法

算法是一个指令序列,按照该序列执行可以解决一个问题。

—未知

在上一章中,我们探讨了顺序模型的核心原理。它提供了这些技术和方法的入门概述。上一章讨论的顺序建模算法有两个基本限制。首先,输出序列必须与输入序列有相同数量的元素。其次,这些算法一次只能处理输入序列中的一个元素。如果输入序列是一句句子,那么到目前为止讨论的顺序算法只能一次“关注”或处理一个单词。为了更好地模拟人脑的处理能力,我们需要的不仅仅是这些。我们需要复杂的顺序模型,能够处理长度与输入不同的输出,并且能够同时关注句子中的多个单词,从而打破这一信息瓶颈。

本章将更深入地探讨顺序模型的高级内容,了解如何创建复杂的配置。我们将从分解关键元素开始,例如自编码器和序列到序列Seq2Seq)模型。接下来,我们将介绍注意力机制和变换器,它们在大语言模型LLMs)的发展中起着至关重要的作用,随后我们将对其进行学习。

到本章结束时,您将全面了解这些高级结构及其在机器学习领域的重要性。我们还将提供这些模型的实际应用的深入见解。

本章涵盖以下主题:

  • 自编码器介绍

  • Seq2Seq 模型

  • 注意力机制

  • 变换器

  • 大语言模型

  • 深度和广度架构

首先,让我们概述一下高级顺序模型。

高级顺序建模技术的演变

第十章,《理解顺序模型》中,我们讨论了顺序模型的基础知识。尽管它们有许多应用场景,但在理解和生成复杂的人类语言细节方面仍然面临挑战。

我们将从讨论自编码器开始。自编码器是在 2010 年代初期提出的,为数据表示提供了一种新颖的方式。它们在自然语言处理NLP)中标志着一个重要的演变,彻底改变了我们对数据编码和解码的思考方式。但 NLP 的进展并未止步于此。到 2010 年代中期,Seq2Seq模型开始出现,带来了用于任务(如语言翻译)的一些创新方法。这些模型能够巧妙地将一种序列形式转化为另一种,开启了先进序列处理的新时代。

然而,随着数据复杂度的增加,NLP 社区感受到了对更精密工具的需求。这促成了 2015 年注意力机制的发布。这个优雅的解决方案赋予了模型选择性地关注输入数据特定部分的能力,使其能更高效地处理更长的序列。实质上,它允许模型对不同数据段的重要性进行加权,从而放大相关信息,减小不相关信息。

在此基础上,2017 年迎来了变换器架构的出现。充分利用注意力机制的能力,变换器在 NLP 中树立了新的标杆。

这些进展最终促成了大语言模型LLMs)的发展。经过海量且多样化文本数据的训练,LLMs 能够理解并生成细腻的人类语言表达。它们无与伦比的能力在广泛应用中得到了体现,从医疗诊断到金融中的算法交易。

在接下来的部分中,我们将深入探讨自动编码器的复杂性——从它们的早期起源到今天在先进序列模型中的核心作用。准备好深入了解这些变革性工具的机制、应用和演变吧。

探索自动编码器

自动编码器在神经网络架构的领域中占据了独特的地位,在高级序列模型的叙事中扮演着关键角色。基本上,自动编码器旨在创建一个输出与输入相似的网络,意味着将输入数据压缩成更简洁、低维的潜在表示。

自动编码器结构可以被概念化为一个双阶段过程:编码阶段和解码阶段。

考虑以下图示:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_01.png

图 11.1:自动编码器架构

在这个图示中,我们做出以下假设:

  • x 对应于输入数据

  • h 是我们数据的压缩形式

  • r 表示输出,即 x 的重建或近似值

我们可以看到,这两个阶段分别由 fg 表示。让我们更详细地看一下它们:

  • 编码f):用数学形式表示为 h = f(x)。在此阶段,输入数据* x *被转化为一个简化的、隐藏的表示,称为 h

  • 解码g):在此阶段,用 r = g(h) 表示,紧凑的 h 被展开,旨在重建最初的输入。

在训练自动编码器时,目标是完善 h,确保它能 encapsulate 输入数据的本质。在实现高质量的 h 时,我们确保重建的输出 r 能尽量少的损失地再现原始的 x。目标不仅是重建,还要训练出一个精简且高效的 h,以完成这个重建任务。

编写自动编码器

美国国家标准与技术研究所MNIST)数据集是一个著名的手写数字数据库,包含 28x28 像素的灰度图像,表示从 0 到 9 的数字。它已广泛用作机器学习算法的基准。更多信息以及数据集的访问可以通过官方 MNIST 网站获得。对于有兴趣访问数据集的人,它可以在 Yann LeCun 主办的官方 MNIST 存储库中找到:yann.lecun.com/exdb/mnist/。请注意,下载数据集可能需要创建账户。

在本节中,我们将使用自编码器重建这些手写数字。自编码器的独特之处在于其训练机制:输入目标输出是相同的图像。我们来详细解析一下。

首先是训练阶段,期间将进行以下步骤:

  1. MNIST 图像被提供给自编码器。

  2. 编码器部分将这些图像压缩成浓缩的潜在表示。

  3. 解码器部分随后会尝试从这个表示中恢复原始图像。通过反复迭代这个过程,自编码器掌握了压缩和重构的细节,捕捉到手写数字的核心模式。

第二步是重构阶段

  1. 训练好的模型在接收到新的手写数字图像时,自编码器会首先将其编码成内部表示。

  2. 然后,解码这个表示将得到一个重构的图像,如果训练成功,它应该与原始图像非常相似。

在 MNIST 数据集上有效训练后的自编码器,成为一个强大的工具,用来处理和重构手写数字图像。

环境设置

在深入代码之前,必须导入必要的库。TensorFlow 将是我们的主要工具,但在数据处理方面,像 NumPy 这样的库可能至关重要:

import tensorflow as tf 

数据准备

接下来,我们将把数据集划分为训练集和测试集,然后对它们进行归一化处理:

# Load dataset
(x_train, _), (x_test, _) = tf.keras.datasets.mnist.load_data()
# Normalize data to range [0, 1]
x_train, x_test = x_train / 255.0, x_test / 255.0 

请注意,255.0的除法操作是为了对灰度图像数据进行归一化,这是优化学习过程的一个步骤。

模型架构

设计自编码器涉及到关于层次、尺寸和激活函数的决策。在这里,模型是通过 TensorFlow 的SequentialDense类定义的:

model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=(28, 28)),
    tf.keras.layers.Dense(32, activation='relu'),
    tf.keras.layers.Dense(784, activation='sigmoid'),
    tf.keras.layers.Reshape((28, 28))
]) 

将 28x28 的图像展平后,我们得到一个包含 784 个元素的一维数组,因此输入的形状为该数组的形状。

编译

模型定义完成后,将使用指定的损失函数和优化器进行编译。由于我们的灰度图像是二值性质,因此选择了二元交叉熵作为损失函数:

model.compile(loss='binary_crossentropy', optimizer='adam') 

训练

训练阶段通过fit方法启动。在这里,模型学习 MNIST 手写数字的细节:

model.fit(x_train, x_train, epochs=10, batch_size=128,
          validation_data=(x_test, x_test)) 

预测

使用训练好的模型,可以执行以下预测操作(包括编码和解码):

encoded_data = model.predict(x_test)
decoded_data = model.predict(encoded_data) 

可视化

现在让我们直观地比较原始图像与其重建后的对比图像。以下脚本展示了一个可视化过程,显示了两排图像:

n = 10  # number of images to display
plt.figure(figsize=(20, 4))
for i in range(n):
    # Original images
    ax = plt.subplot(2, n, i + 1)
    plt.imshow(x_test[i].reshape(28, 28) , cmap='gray')
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
    # Reconstructed images
    ax = plt.subplot(2, n, i + 1 + n)
    plt.imshow(decoded_data[i].reshape(28, 28) , cmap='gray')
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
plt.show() 

以下截图显示了输出的重建图像:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_02.png

图 11.2:原始测试图像(上排)和自编码器重建后的图像(下排)

最上排展示了原始测试图像,而下排展示了通过自编码器重建后的图像。通过这种并排比较,我们可以辨别出模型在保持输入的内在特征方面的有效性。

现在让我们讨论 Seq2Seq 模型。

理解 Seq2Seq 模型

在我们探索自编码器之后,另一个在高级序列模型领域中的突破性架构是Seq2Seq模型。Seq2Seq 模型在许多最先进的自然语言处理任务中占据重要地位,展现了一种独特的能力:将输入序列转换为可能在长度上不同的输出序列。这种灵活性使其在诸如机器翻译等挑战中表现出色,因为源语言和目标语言的句子长度可以自然地不同。

请参阅图 11.3,它展示了 Seq2Seq 模型的核心组成部分:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_03.png

图 11.3:Seq2Seq 模型架构示意图

总的来说,有三个主要元素:

  • 编码器:处理输入序列

  • 思维向量:编码器和解码器之间的桥梁

  • 解码器:生成输出序列

让我们一个一个地探索它们。

编码器

编码器在图 11.3中显示为https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_09.png。正如我们所观察到的,它是一个输入循环神经网络RNN),用于处理输入序列。此处的输入句子是一个三词句子:Is Ottawa cold? 它可以表示为:

X = {x^(<1>), x^(<2>),… …., x^()}

编码器遍历该序列,直到遇到句子结束(<EOS>)标记,表示输入的结束。它将位于时间步L1

思维向量

在编码阶段,RNN 更新其隐藏状态,表示为 h^()。序列结束时捕获的最终隐藏状态 h^()会传递给解码器。这个最终状态被称为思维向量,由 Geoffrey Hinton 于 2015 年提出。这个紧凑的表示捕捉了输入序列的精髓。思维向量在图 11.3中显示为https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_10.png

解码器或写入器

在编码过程完成后,<GO>符号表示解码器开始工作。使用编码器的最后一个隐藏状态 h^()作为其初始输入,解码器作为输出 RNN,开始构建输出序列 Y = {y^(<1>), y^(<2>),… …., y^()}。在图 11.3的背景下,这个输出序列转化为句子:是的它是

Seq2Seq 中的特殊符号

虽然<EOS><GO>是 Seq2Seq 范式中的重要符号,但还有一些其他值得注意的符号:

  • <UNK>:代表未知,这个符号用来替换不常见的单词,确保词汇表保持可管理。

  • <PAD>:用于填充较短的序列,这个符号在训练过程中标准化序列长度,从而提升模型的效率。

Seq2Seq 模型的一个显著特点是能够处理可变序列长度,这意味着输入和输出序列在大小上本质上可以有所不同。这种灵活性,再加上其顺序特性,使得 Seq2Seq 成为高级建模领域中的一个重要架构,架起了从自编码器到更复杂、更精细的序列处理系统的桥梁。

在经历了自编码器的基础领域并深入研究了 Seq2Seq 模型之后,我们现在需要理解编码器-解码器框架的局限性。

信息瓶颈难题

正如我们所学到的,传统 Seq2Seq 模型的核心是思维向量 h^()。这是来自编码器的最后一个隐藏状态,作为连接编码器和解码器的桥梁。这个向量负责封装整个输入序列,X。该机制的简单性既是其优势也是其弱点。当序列变得更长时,这种弱点尤为突出;将大量信息压缩成固定大小的表示变得越来越困难。这被称为信息瓶颈。无论输入的丰富性或复杂性如何,固定长度的记忆限制意味着从编码器传递到解码器的信息量是有限的。

要了解这个问题是如何被解决的,我们需要将焦点从 Seq2Seq 模型转移到注意力机制上。

理解注意力机制

在传统 Seq2Seq 模型中的固定长度记忆带来的挑战之后,2014 年标志着一次革命性的进步。Dzmitry Bahdanau、KyungHyun Cho 和 Yoshua Bengio 提出了一种变革性解决方案:注意力机制。与早期模型试图(常常徒劳地)将整个序列压缩到有限的内存空间不同,注意力机制使得模型能够专注于输入序列中具体且相关的部分。可以把它想象成在每个解码步骤中仅对最关键数据进行放大。

神经网络中的注意力是什么?

正如俗话所说,注意力集中之处,就是焦点所在。在自然语言处理(NLP)领域,尤其是在大语言模型(LLM)的训练中,注意力受到了极大的关注。传统上,神经网络按固定顺序处理输入数据,可能会错过上下文的重要性。而注意力机制的引入,则是为了解决这个问题——它能够衡量不同输入数据的重要性,更加关注相关内容。

基本概念

就像人类会将更多注意力放在图像或文本的显著部分一样,注意力机制使神经模型能够专注于输入数据中更相关的部分。它有效地指引模型下一步“看”哪里。

示例

受到我最近一次前往埃及的旅行启发,这次旅行仿佛是一次穿越时空的旅行,让我们思考古埃及的表现力和象征性语言:象形文字。

象形文字不仅仅是符号;它们是艺术与语言的复杂融合,代表着多重含义。这个系统,通过其丰富的符号阵列,展示了神经网络注意力机制的基础原则。

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_04.png

图 11.4:吉萨的著名金字塔——胡夫金字塔和哈夫拉金字塔,旁边是古老埃及象形文字的铭文,“象形文字”(照片由作者拍摄)

举例来说,一位埃及抄写员希望传达关于尼罗河边即将举行的盛大节日的消息。在成千上万的象形文字中:

然而,为了传达节日的宏伟和重要性,并非所有符号的权重都相同。抄写员必须强调或重复特定的象形文字,以引起人们对信息最关键部分的注意。

这种选择性强调与神经网络的注意力机制相似。

注意力机制的三个关键方面

在神经网络,尤其是 NLP 任务中,注意力机制在过滤和聚焦相关信息方面起着至关重要的作用。在这里,我们将注意力的主要方面提炼为三个关键组成部分:上下文相关性、符号效率和优先关注:

  • 上下文相关性

    • 概述:本质上,注意力旨在将更多的重视分配给那些被认为与当前任务更相关的输入数据部分。

    • 深入探讨:以一个简单的输入例如*“宏伟的尼罗河节”*为例。在这种情况下,注意力机制可能会给“尼罗河”和“宏伟”这两个词赋予更高的权重。这并非因为它们的普遍意义,而是由于它们在任务中的特定重要性。注意力机制并非将每个词或输入都视为同等重要,而是根据上下文区分并调整模型的聚焦点。

    • 实际应用:可以把这看作是一束聚光灯。就像聚光灯在舞台上照亮特定演员在关键时刻的表现,而其他演员则被暗淡处理,注意力也会把光照在更具上下文价值的输入数据上。

  • 符号效率

    • 概述:注意力机制能够将大量信息浓缩成易于理解的关键片段。

    • 深入探讨:象形文字可以通过单一符号承载复杂的叙事或思想。类比地,注意力机制通过分配不同的权重,能够判断数据的哪些部分包含最多的信息,并应优先处理这些部分。

    • 实际应用:考虑将一篇大文档压缩成简洁的摘要。摘要仅保留最关键的信息,这类似于注意力机制从更大的输入中提取并优先处理最相关的数据。

  • 优先聚焦

    • 概述:注意力机制不会均匀分配它们的聚焦点。它们的设计是根据输入数据与任务的相关性来优先考虑某些片段。

    • 深入探讨:从象形文字的例子中汲取灵感,就像古埃及的抄写员在表达生命或庆祝的概念时会强调“安卡”符号一样,注意力机制也会根据任务的相关性调整对输入特定部分的聚焦(或权重)。

    • 实际应用:这就像阅读一篇研究论文。虽然整个文档都有价值,但人们可能更关注摘要、结论或与自己当前研究需求相关的特定数据点。

因此,神经网络中的注意力机制模仿了人类在处理信息时自然使用的选择性聚焦方式。通过理解注意力如何优先处理数据的细微差别,我们可以更好地设计和解读神经模型。

更深入探讨注意力机制

注意力机制可以被看作是一种进化的沟通方式,就像古代的象形文字一样。传统上,编码器试图将整个输入序列浓缩成一个概括性的隐藏状态。这就像古埃及的抄写员试图通过一个单一的象形文字来表达整个事件。虽然这在理论上是可能的,但它很具挑战性,并且可能无法完整捕捉事件的全部精髓。

现在,采用增强的编码器-解码器方法,我们可以为每个步骤生成一个隐藏状态,从而为解码器提供更丰富的数据。但是,如果同时引用每一个象形符号(或状态),那将会是混乱的,类似于一位抄写员用所有可用的符号来描述尼罗河旁的一个事件。这就是注意力机制的作用所在。

注意力机制使解码器能够进行优先级排序。就像一位抄写员可能专注于“Ankh”符号来象征生命和活力,或者“Was”权杖代表权力,甚至描绘尼罗河本身来指示位置一样,解码器会为每个编码器状态分配不同的权重。它决定序列中的哪些部分(或哪些象形符号)值得更多的关注。以我们的翻译示例为例,当将“Transformers are great!”翻译为“Transformatoren sind grossartig!”时,机制会强调“great”与“grossartig”的对齐,确保核心情感不变。

这种选择性聚焦,无论是在神经网络的注意力机制中,还是在象形文字叙事中,都能确保传达信息的准确性和清晰度。

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_05.png

图 11.5:采用增强注意力机制的编码器-解码器结构的 RNN

注意力机制的挑战

尽管将注意力机制与 RNN 结合可以带来显著的改进,但这并不是万能的解决方案。其中一个重大障碍是计算成本。将多个隐藏状态从编码器传输到解码器的过程需要大量的计算能力。

然而,正如所有技术进步一样,解决方案不断涌现。其中一个进展是自注意力的引入,这是变压器架构的基石。这种创新的变体优化了注意力过程,使其更加高效和可扩展。

深入探讨自注意力机制

再次考虑古老的象形文字艺术,其中符号的选择是有意为之,用来传达复杂的消息。自注意力的运作方式类似,确定序列中哪些部分是关键并应当被强调。

图 11.6展示了在顺序模型中整合自注意力的美妙之处。想象底层通过双向 RNN 运作,像是金字塔的基石。它们生成我们所称的上下文向量c2),类似于象形文字对事件的总结。

序列中的每一步或每个单词都有其权重,用α表示。这些权重与上下文向量相互作用,强调某些元素的重要性,而不是其他元素。

假设一个场景,其中输入X[k]表示一个独立的句子,记作k,该句子的长度为L1。这可以用数学方式表示为:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_002.png

在这里,每个元素,https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_003.png,代表句子k中的一个单词或符号:上标表示它在句子中的特定位置或时间步。

注意力权重

在自注意力机制中,注意力权重起着至关重要的作用,像一个指向重要单词的指南针。它们在生成上下文向量时,给每个单词分配一个“重要性分数”。

为了让这更有意义,考虑我们之前的翻译示例:“Transformers are great!” 翻译为 “Transformatoren sind grossartig!”。当聚焦于“Transformers”时,注意力权重可能是这样分布的:

  • α[2,1]:衡量“Transformers”与句子开头之间的关系。此处的高值表示“Transformers”在其上下文中显著依赖句子开头。

  • α[2,2]:反映了“Transformers”对其内在意义的强调程度。

  • α[2,3] 和 α[2,4]:衡量“Transformers”在上下文中对“are”和“great!”这两个词的依赖程度。高分值表示“Transformers”深受这些周围词汇的影响。

在训练过程中,这些注意力权重会不断地被调整和精细化。这种持续的优化确保我们的模型理解句子中单词之间错综复杂的关系,捕捉到显性和隐性之间的联系。

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_06.png

图 11.6:在序列模型中整合自注意力机制

在深入探讨自注意力机制之前,理解组成图 11.6中的关键部分是至关重要的。

编码器:双向 RNN

在上一章中,我们研究了单向 RNN 及其变种的主要架构组件。双向 RNN的发明旨在解决这一需求(Schuster 和 Paliwal,1997)。我们还发现了单向 RNN 的一个缺陷,即它们只能向一个方向传递上下文。

对于一个输入序列,假设是 X,双向 RNN 首先从头到尾读取它,然后再从尾到头读取。这种双重方法帮助捕捉基于前后元素的信息。对于每个时间步,我们得到两个隐藏状态:一个是 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_004.png(正向)方向的,另一个是 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_005.png(反向)方向的。这些状态被合并成该时间步的单一状态,表示为:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_006.png

举个例子,如果 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_007.pnghttps://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_008.png 是 64 维向量,那么结果的 h^() 将是 128 维的。这个合并后的状态是来自两个方向的序列上下文的详细表示。

思维向量

思维向量,这里用 C[k] 表示,是输入 X[k] 的一种表征。如我们所学,它的创建旨在捕捉 X[k] 中每个元素的顺序模式、上下文和状态。

在我们前面的图示中,它被定义为:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_009.png

其中 https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_010.png 是在训练过程中针对时间步 t 精炼后的注意力权重。

使用求和符号,它可以表示为:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_011.png

解码器:常规的 RNN

图 11.5 显示了通过思维向量连接的解码器与编码器。

某个句子 k 的解码器输出表示为:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_012.png

请注意,输出的长度是 L2,与输入序列的长度 L1 不同。

训练与推理

在某个输入序列 k 的训练数据中,我们有一个表示地面真实的期望输出向量,这个向量用 Y[k]表示。它是:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_013.png

在每个时间步,解码器的 RNN 接收三个输入:

然而,在推理过程中,由于没有先前的真实值可用,解码器的 RNN 会使用先前的输出词,https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_016.png,代替。

现在我们已经了解了自注意力如何解决注意力机制面临的挑战以及它涉及的基本操作,我们可以将注意力转向序列建模中的下一个重大进展:变换器(transformers)。

变换器:自注意力之后神经网络的演进

我们对自注意力的探索揭示了它强大的能力,可以重新解释序列数据,基于与其他词的关系为每个词提供上下文理解。这个原则为神经网络设计的进化跃进奠定了基础:变换器架构。

变换器架构由谷歌大脑团队在他们 2017 年的论文《Attention is All You Need》(arxiv.org/abs/1706.03762)中提出,基于自注意力的本质。变换器问世之前,RNN 是首选。可以将 RNN 看作是勤奋的图书管理员,逐字地读取英语句子并将其翻译成德语,确保上下文从一个词传递到下一个词。它们在处理短文本时非常可靠,但当句子变得过长时,可能会发生错位,丢失早期词汇的本意。

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_07.png

图 11.7:原始变换器的编码器-解码器架构

变换器是处理序列数据的一种全新方法。与线性逐字的进程不同,变换器借助先进的注意力机制,可以一眼理解整个序列。这就像瞬间抓住整段文字的情感,而不是逐字拼凑。这种全局视角确保了更丰富、全面的理解,庆祝了词与词之间微妙的相互作用。

自注意力是变换器高效性的核心。虽然我们在前面已经提到过这一点,但值得注意的是它在这里的重要性。网络的每一层通过自注意力机制可以与输入数据的其他部分产生共鸣。如图 11.7所示,变换器架构在其编码器和解码器部分都使用自注意力机制,然后这些部分再传递给神经网络(也称为前馈神经网络(FFNNs))。除了更易于训练外,这一架构也促进了 NLP 领域的许多最新突破。

为了说明这一点,考虑*《古埃及:埃及历史的迷人概述》*,作者比利·韦尔曼。在书中,像拉美西斯和克娄巴特拉这样的早期法老与金字塔建设之间的关系既庞大又复杂。传统模型可能在面对如此庞大的内容时会遇到困难。

为什么变换器如此出色

变换器架构凭借其自注意力机制,成为一个有前景的解决方案。当遇到像“金字塔”这样的术语时,模型可以通过自注意力机制评估它与“拉美西斯”或“克娄巴特拉”等术语的相关性,而不管它们的位置如何。这种对不同输入部分进行关注的能力,展示了变换器在现代 NLP 中的重要性。

一个 Python 代码解析

下面是自注意力机制实现的简化版本:

import numpy as np
def self_attention(Q, K, V):
    """
    Q: Query matrix
    K: Key matrix
    V: Value matrix
    """

    # Calculate the attention weights
    attention_weights = np.matmul(Q, K.T)

    # Apply the softmax to get probabilities
    attention_probs = np.exp(attention_weights) / np.sum(np.exp(attention_weights), axis=1, keepdims=True)

    # Multiply the probabilities with the value matrix to get the output
    output = np.matmul(attention_probs, V)

    return output
# Example
Q = np.array([[1, 0, 1], [0, 2, 0], [1, 1, 0]])  # Example Query
K = np.array([[1, 0, 1], [0, 2, 0], [1, 1, 0]])  # Key matrix
V = np.array([[0, 2, 0], [1, 0, 1], [0, 1, 2]])  # Value matrix
output = self_attention(Q, K, V)
print(output) 

输出:

[[0.09003057 1.57521038 0.57948752]
 [0.86681333 0.14906291 1.10143419]
 [0.4223188  0.73304361 1.26695639]] 

这段代码是一个基本表示,真实的变换器模型采用了更优化且更详细的方法,尤其是在扩展到更大序列时。但本质上是动态加权序列中的不同单词,允许模型引入上下文理解。

理解输出

  • 第一行,[0.09003057 1.57521038 0.57948752],对应查询中第一个单词的 V 矩阵加权组合(在这种情况下,表示为 Q 矩阵的第一行)。这意味着当我们的模型遇到由这个查询表示的单词时,它会将 9%的关注放在第一个单词,57.5%的关注放在第二个单词,57.9%的关注放在第三个单词,从 V 矩阵中提取上下文理解。

  • 第二行,[0.86681333 0.14906291 1.10143419],是查询中第二个单词的注意力结果。它对 V 矩阵中第一个单词、第二个单词和第三个单词的关注度分别为 86.6%、14.9%和 110.1%。

  • 第三行,[0.4223188 0.73304361 1.26695639],对应查询中的第三个单词。它对 V 矩阵中各个单词的注意力权重分别为 42.2%、73.3%和 126.7%。

在回顾了变换器、它们在序列建模中的地位、它们的代码及其输出之后,我们可以考虑 NLP 领域的下一个重大进展。接下来,我们来看一下 LLMs。

LLMs

LLMs是 NLP 领域中继变换器(transformers)之后的下一个进化步骤。它们不仅仅是增强版的旧模型;它们代表了一个飞跃。这些模型可以处理大量文本数据,并执行曾经被认为只有人类大脑才能完成的任务。

简而言之,LLMs 可以生成文本、回答问题,甚至编写代码。想象一下和软件聊天,它像人类一样回复,捕捉微妙的暗示并记得之前对话的内容。这正是 LLMs 所能提供的。

语言模型LMs)一直是自然语言处理(NLP)的支柱,帮助完成从机器翻译到更现代的文本分类等任务。早期的 LMs 依赖于 RNNs 和长短期记忆LSTM)结构,而今天 NLP 的成就主要得益于深度学习技术,特别是 transformer 模型。

LLMs 的标志性特征?它们能够阅读并从大量文本中学习。从零开始训练一个 LLM 是一项艰巨的任务,需要强大的计算机和大量的时间。根据模型的大小和训练数据的量——例如来自像维基百科或 Common Crawl 数据集这样的庞大来源——训练一个 LLM 可能需要几周甚至几个月的时间。

处理长序列是 LLMs 面临的已知挑战。早期基于 RNNs 和 LSTMs 的模型在处理长序列时常常会丢失重要细节,这影响了它们的性能。这时我们开始看到注意力的作用。注意力机制就像一盏手电筒,照亮长输入中的重要部分。例如,在一篇关于汽车进展的文章中,注意力确保模型能够识别并聚焦于主要突破,无论这些突破出现在文本的哪里。

理解 LLMs 中的注意力机制

注意力机制已经成为神经网络领域的基础,尤其在 LLMs 中表现得尤为突出。训练这些庞大的模型,包含数百万甚至数十亿个参数,并非易事。从本质上讲,注意力机制就像高光笔,强调关键细节。例如,在处理一篇关于 NLP 发展的长篇文章时,LLMs 能够理解整体主题,但注意力确保它们不会错过重要的里程碑。transformer 模型利用这一注意力特性,帮助 LLMs 处理庞大的文本片段,并确保上下文的一致性。

对于 LLMs 来说,上下文至关重要。例如,如果一个 LLM 编写了一个以猫为开头的故事,注意力机制确保随着故事的发展,上下文保持一致。因此,故事不会引入像“狗吠声”这样的无关声音,而是自然地倾向于“猫叫声”或“喵喵声”。

训练一个 LLM 就像是连续运行超级计算机数月,仅仅为了处理大量的文本数据。而且,当初始训练完成时,这只是个开始。可以把它想象成拥有一辆高端汽车——你需要定期进行维护。同样,LLMs 也需要基于新数据进行频繁的更新和调整。

即使训练完一个 LLM,工作也远未结束。为了保持这些模型的有效性,它们需要不断学习。想象一下,教某人英语语法规则,然后再加入俚语或习语——他们需要适应这些不规则用法,才能全面理解。

强调一个历史性转折点,2017 年至 2018 年间,LLM(大规模语言模型)领域发生了显著变化。包括 OpenAI 在内的公司开始利用无监督预训练,为情感分析等任务的更简化模型铺平了道路。

探索 NLP 的强大引擎:GPT 和 BERT

通用语言模型微调ULMFiT)为自然语言处理(NLP)开启了新时代。这种方法开创了预训练 LSTM 模型的再利用,并将其适应于多种 NLP 任务,从而节省了计算资源和时间。让我们来分解一下它的过程:

  1. 预训练:这类似于教一个孩子语言的基础。通过使用像 Wikipedia 这样的广泛数据集,模型学习语言的基本结构和语法。可以把它看作是为学生提供通识教材。

  2. 领域适应:模型然后深入到特定领域或类型。如果第一步是学习语法,那么这一步就像是将模型引入不同的文学类型——从惊悚小说到科学期刊。它仍然在预测词汇,但现在是在特定的上下文中进行。

  3. 微调:最后,模型被精细调整,以应对特定任务,例如检测给定文本中的情绪或情感。这相当于训练学生写作论文或深入分析文本。

2018 年 LLM 先驱:GPT 和 BERT

2018 年见证了两款突出模型的崛起:GPT 和 BERT。让我们更详细地了解它们。

生成预训练变换器(GPT)

受 ULMFiT 启发,GPT 是一个依赖于变换器架构解码器部分的模型。想象人类文学的浩瀚。如果传统模型是通过固定的一组书籍来训练,那么 GPT 就像是给学者提供了整个图书馆的访问权限,包括 BookCorpus——一个包含多样化未出版书籍的丰富数据集。这使得 GPT 可以从小说到历史等多种类型中汲取见解。

这里有一个类比:传统模型可能知道莎士比亚剧本的情节。GPT 通过广泛的学习,不仅理解情节,还能掌握文化背景、人物细微之处以及莎士比亚写作风格随时间的演变。

它对解码器的专注使 GPT 成为生成既相关又连贯文本的大师,就像一位经验丰富的作者在起草小说。

BERT(双向编码器表示从变换器)

BERT 通过其“掩蔽语言模型”技术彻底改变了传统的语言建模。与仅预测句子中下一个单词的模型不同,BERT 会填补故意空缺或“掩蔽”的词,从而增强了其对上下文的理解。

让我们来看看这个变化的背景。在像“她去巴黎参观 ___”这样的句子中,传统模型可能会预测符合“the”之后的词,比如“博物馆”。而 BERT 在遇到“她去巴黎参观 masked”时,会试图推断出“masked”应该被“埃菲尔铁塔”替代,理解到巴黎之行的更广泛背景。

BERT 的方法提供了更全面的语言理解,基于前后文捕捉单词的本质,提升了其语言理解能力。

训练大语言模型(LLM)成功的关键在于结合“深度”和“广度”学习架构。可以把“深度”部分看作是专注于某一领域的专家,而“广度”方法则像是通才,了解各个领域的基础知识。

使用深度和广度模型来创建强大的大语言模型(LLMs)

大语言模型的设计非常精细,旨在在一个相对具体的任务上表现出色:预测序列中的下一个词汇。起初看似简单,但为了高精度地完成这一任务,模型往往借鉴了人类学习中的某些方面。

人脑,作为自然界的奇迹,通过识别和抽象周围环境中的常见模式来处理信息。在此基础理解的基础上,人类还通过记忆那些不符合常规模式的特定实例或例外来增强他们的知识。可以将其理解为:首先了解一条规则,然后学习该规则的例外情况。

为了让机器具备这种双层次学习方法,我们需要深思熟虑的机器学习架构。一个初步的方法可能仅仅是基于普遍的模式来训练模型,而忽略了例外情况。然而,要真正做到卓越,尤其是在诸如预测下一个词汇这样的任务中,模型必须能够掌握既能捕捉常见模式,又能识别语言中偶尔出现的独特例外。

虽然大语言模型并非旨在完全模拟人类智能(人类智能是多面的,不仅仅是关于预测序列),但它们确实借鉴了人类的学习策略,以便在其特定任务上变得更加熟练。

大语言模型的设计旨在通过检测大量文本数据中的模式来理解和生成语言。可以考虑以下基本的语言学准则:

  1. 古埃及象形文字提供了一个有趣的例子。在这个早期的书写系统中,一个符号可能代表一个单词、一个声音,甚至是一个概念。例如,虽然一个单一的象形文字可以表示“河流”一词,但多个象形文字的组合可以传达更深的含义,如“生命之源——尼罗河”。

  2. 现在,考虑一下问题是如何形成的。通常,问题的构建始于助动词。然而,间接的询问,例如“我想知道尼罗河今年是否会泛滥”,则偏离了这种常规模式。

为了有效地预测一个序列中的下一个词汇或短语,大语言模型(LLMs)必须掌握既有的语言规范及其偶尔的例外。

表单底部

因此,结合深度和广度模型(图 11.8)已被证明能提升模型在广泛任务上的表现。深度模型的特点是拥有许多隐藏层,能够学习输入与输出之间复杂的关系。

相反,宽度模型旨在学习数据中的简单模式。通过将两者结合,可以同时捕捉复杂关系和简单模式,从而得到更强大、更灵活的模型。

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/B18046_11_08.png

图 11.8:深度与宽度模型的架构

在训练过程中融入例外对于模型在面对新数据时更好地泛化至关重要。例如,只有在包含某个词义的数据上训练的语言模型,可能在遇到新数据时无法识别该词的其他含义。通过融入例外,模型可以学习识别一个词的多种含义,从而提升其在各种自然语言处理任务中的表现。

深度架构通常用于需要学习数据的复杂层次抽象表示的任务。表现出可泛化模式的特征称为密集特征。当我们使用深度架构来制定规则时,我们称之为通过泛化进行学习。为了构建一个宽深网络,我们将稀疏特征直接连接到输出节点。

在机器学习领域,结合深度和宽度模型已被确定为构建更灵活、更强大的模型的重要方法,这些模型可以同时捕捉数据中的复杂关系和简单模式。深度模型擅长学习数据的复杂层次抽象表示,通过多个隐藏层,每一层处理数据并在不同层次上学习不同的特征。相对而言,宽度模型具有最少的隐藏层,通常用于需要学习数据中简单非线性关系的任务,而不会创建任何抽象层。

这些模式通过稀疏特征来表示。当模型的宽部分具有一个或零个隐藏层时,它可以用来记住示例并制定例外。因此,当宽架构用于制定规则时,我们称之为通过记忆进行学习。

深度和宽度模型可以利用深度神经网络来泛化模式。通常,这部分模型需要大量的时间进行训练。宽度部分以及在实时捕捉这些泛化的所有例外的努力,都是持续算法学习过程的一部分。

总结

在本章中,我们讨论了先进的序列模型,这些技术专门用于处理输入序列,尤其是在输出序列的长度可能与输入序列不同的情况下。自编码器是一种神经网络架构,特别擅长压缩数据。它们通过将输入数据编码成更小的表示,然后再解码回来,以便与原始输入相似。这个过程可以用于图像去噪等任务,在这些任务中,图像中的噪声被过滤掉,以生成更清晰的版本。

另一个有影响力的模型是 Seq2Seq 模型。它被设计用来处理输入和输出序列长度不一致的任务,使其在诸如机器翻译等应用中非常理想。然而,传统的 Seq2Seq 模型面临着信息瓶颈问题,即需要将输入序列的整个上下文捕捉到一个固定大小的表示中。为了解决这个问题,引入了注意力机制,使模型能够动态地聚焦于输入序列的不同部分。论文《Attention is All You Need》中提出的 Transformer 架构正是利用了这一机制,彻底改变了序列数据的处理方式。与其前身不同,Transformer 可以同时关注序列中的所有位置,从而捕捉数据中的复杂关系。这一创新为大型语言模型(LLM)的发展铺平了道路,这些模型因其类人文本生成能力而广受关注。

在下一章,我们将探讨如何使用推荐引擎。

了解更多内容请访问 Discord

要加入本书的 Discord 社区——在这里你可以分享反馈、向作者提问,并了解新版本的发布——请扫描以下二维码:

packt.link/WHLel

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/50-algo-evy-prog-shld-know/img/QR_Code1955211820597889031.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值