python及数据分析基础(Python数据分析系列21.)

数据与智能 出版了专著「构建企业级推荐系统:算法、工程实现与案例分析」。每周输出7篇推荐系统、数据分析、大数据、AI原创文章。「数据与智能」(同名视频号、知乎、头条、B站、快手、抖音、小红书等自媒体平台号) 社区,聚焦数据、智能领域的知识分享与传播。

来源 | Data Science from Scratch, Second Edition

作者 | Joel Grus

译者 | cloverErna

校对 | gongyouliu

编辑 | auroral-L

全文共5042字,预计阅读时间35分钟。

第二十一章 自然语言处理

6. Word 向量

7. 递归神经网络

8. 示例:使用一个字符级的RNN

9. 以供进一步探索

6. Word 向量

NLP方面的许多最新进展都涉及到深度学习。在本章的其余部分,我们将使用第19章形成的机制。

一个重要的创新包括将单词表示为低维向量。这些向量可以被比较,加在一起,输入到机器学习模型中,或者任何你想用它们做的任何事情。它们通常具有良好的属性;例如,类似的词往往有相似的向量。也就是说,big的词向量非常接近large的词向量,所以在词向量上操作的模型可以(在某种程度上)免费处理像同义词这样的东西。

这些向量通常也会表现出令人愉快的算术性质。例如,在一些这样的模型中,如果你取了国王的向量,减去男人的向量,然后加上女人的向量,你最终会得到一个非常接近女王的向量的向量。思考这对向量实际上“学习”的含义是很有趣的,尽管我们不会在这里花时间。

想出大量词汇的这样的向量是一项困难的任务,所以我们通常会从文本语料库中学习它们。有几种不同的方案,但在高级级别上,任务通常是这样的:

1. 获取一堆文字。

2. 创建一个数据集,其目标是预测一个给定附近单词的单词(或者,预测给定一个单词的附近单词)。

3. 训练一个神经网络来完成这项任务。

4. 以训练过的神经网络的内部状态作为单词向量。

特别是,由于任务是预测一个给出附近单词的单词,发生在相似上下文中(因此有相似的附近单词)的单词应该具有相似的内部状态,因此具有相似的单词向量。

这里我们将使用余弦相似度来度量“相似度”,它是-1和1之间的一个数字,用来测量两个向量指向相同方向的程度:

python及数据分析基础(Python数据分析系列21.)(1)

让我们学习一些单词向量,看看这是如何工作的。

首先,我们需要一个玩具数据集。常用的单词向量通常是从数百万甚至数十亿单词的训练中得到的。由于我们的玩具库无法处理这么多的数据,我们将创建一个具有一定结构的人工数据集:

python及数据分析基础(Python数据分析系列21.)(2)

这将产生很多结构相似但单词不同的句子;例如,“绿色的船似乎很慢”。鉴于这种设置,颜色将主要出现在“类似的”的上下文中,名词也会出现,等等。所以如果我们能很好地分配单词向量,颜色应该得到类似的向量,等等。

注意

在实际使用中,你可能会有数百万个句子的语料库,在这种情况下,你会从句子中得到“足够”的上下文。在这里,只有50句话,我们必须让它们有些人工化。

如前所述,我们希望对我们的单词进行一次热编码,这意味着我们需要将它们转换为ID。我们将介绍一个词汇类来跟踪此映射:

python及数据分析基础(Python数据分析系列21.)(3)

python及数据分析基础(Python数据分析系列21.)(4)

这些都是我们可以手动做的事情,但是在课堂上使用它很不方便。我们应该测试一下:

python及数据分析基础(Python数据分析系列21.)(5)

python及数据分析基础(Python数据分析系列21.)(6)

我们还应该编写简单的辅助函数来保存和加载一个词汇表,就像我们的深度学习模型一样:

python及数据分析基础(Python数据分析系列21.)(7)

我们将使用一个叫做跳过图的单词向量模型,该模型以一个单词作为输入,并生成在它附近可能看到的单词的概率。我们将给它训练对(单词,nearby_word),并尽量减少最小最大熵损失。

注意

另一个常见的模型,连续的单词包(CBOW),以附近的单词作为输入,并试图预测原始单词。

让我们来设计我们的神经网络。它的核心将是一个嵌入层,它以一个词ID作为输入,并返回一个词向量。在已知条件下,我们可以只为此使用一个查找表。

然后,我们将将单词向量传递给线性层,其输出数量与词汇表中的单词相同。与以前一样,我们将使用softmax将这些输出转换为附近单词上的概率。当我们使用梯度下降来训练模型时,我们将更新查找表中的向量。完成训练后,该查找表就会提供单词向量。

让我们来创建那个嵌入层。在实践中,我们可能希望嵌入单词以外的东西,因此我们将构建一个更通用的嵌入层。(稍后我们将编写一个专门为词向量编写的TextEmbedding子类。)

在其构造函数中,我们将提供嵌入向量的数量和维数,以便它可以创建嵌入(最初是标准随机法):

python及数据分析基础(Python数据分析系列21.)(8)

在我们的例子中,我们一次只嵌入一个单词。然而,在其他模型中,我们可能想要嵌入一系列单词,并得到一系列单词向量。(例如,如果我们想训练前面描述的CBOW模型。)所以,另一种设计将采用单词ID序列。我们将坚持一次做一个,让事情更简单。

python及数据分析基础(Python数据分析系列21.)(9)

对于向后传递,我们将得到一个与所选嵌入向量对应的梯度,我们需要为self.embeddings构造相应的梯度,除了所选嵌入之外,每个嵌入都为零:

python及数据分析基础(Python数据分析系列21.)(10)

因为我们有参数和梯度,所以我们需要覆盖这些方法:

python及数据分析基础(Python数据分析系列21.)(11)

如前所述,我们将需要一个专门用于词向量的子类。在这种情况下,我们的嵌入数量由我们的词汇量决定,所以我们将其传递入:

python及数据分析基础(Python数据分析系列21.)(12)

其他内置方法将正常工作,但我们将添加一些比较适用于文本处理的方法。例如,我们希望能够检索一个给定单词的向量。(这不是Layer界面的一部分,但我们总是可以自由地向特定的Layer添加额外的方法。)

python及数据分析基础(Python数据分析系列21.)(13)

这个数据下的方法将允许我们检索使用索引的字向量:

python及数据分析基础(Python数据分析系列21.)(14)

我们还希望嵌入层能告诉我们最接近给定单词的单词:

python及数据分析基础(Python数据分析系列21.)(15)

python及数据分析基础(Python数据分析系列21.)(16)

我们的嵌入层只是输出向量,我们可以将其输入到一个线性层中。

现在,我们已经准备好收集培训数据了。对于每个输入词,我们将选择它左边的两个单词和它右边的两个单词作为目标词。

让我们先将句子转为小写形式,并将其分割成单词:

python及数据分析基础(Python数据分析系列21.)(17)

此时,我们可以构建一个词汇表:

python及数据分析基础(Python数据分析系列21.)(18)

python及数据分析基础(Python数据分析系列21.)(19)

使用我们建立的机制,现在很容易创建我们的模型:

python及数据分析基础(Python数据分析系列21.)(20)

使用第19章中的技术,可以轻松训练我们的模型:

python及数据分析基础(Python数据分析系列21.)(21)

python及数据分析基础(Python数据分析系列21.)(22)

当你看这列火车时,你可以看到颜色越来越接近,形容词越来越接近,名词越来越接近。

训练模型后,探索最相似的词语会非常有趣:

python及数据分析基础(Python数据分析系列21.)(23)

哪个结果(对我来说)会导致:

python及数据分析基础(Python数据分析系列21.)(24)

(很明显,床和猫并不是很相似,但在我们的训练句子中,它们看起来是,这就是模型捕捉到的。)

我们还可以提取前两个主要组件并绘制它们:

python及数据分析基础(Python数据分析系列21.)(25)

python及数据分析基础(Python数据分析系列21.)(26)

这表明类似的词语确实聚集在一起(图21-3):

python及数据分析基础(Python数据分析系列21.)(27)

图21-3。Word向量

如果你感兴趣,那么训练CBOW词向量并不难。你得做一点工作。首先,你需要修改嵌入层,以便使用ID列表并输出嵌入向量列表作为输入。然后,你必须创建一个新的Layer(总和?)它取一个向量列表,并返回它们的和。

每个单词都表示一个训练示例,其中输入是周围单词的单词Id,目标是单词本身的独热编码。

修改后的嵌入层将周围的单词转换为向量列表,新的Sum层将向量列表分解为单个向量,线性层可以生成分数,然后可以softmax以得到一个概率分布,这个分布表示“鉴于此上下文最有可能的单词”概率。

我发现CBOW模型比skip-gram模型更难训练,但我鼓励你尝试一下。

7. 递归神经网络

我们在前一节中开发的单词向量通常被用作神经网络的输入。这样做的一个挑战是句子的长度不同:你可以认为3个单词的句子为[3,embedding_dim]张量,10个字的句子为[10,embedding_dim]张量。为了将它们传递到一个线性层,我们需要对第一个变长维数做一些事情。

一种选择是使用Sum层(或取平均值的变体);然而,句子中单词的顺序通常对其意义很重要。举个例子,“狗咬人”和“人咬狗”是两个非常不同的故事!

另一种处理这一问题的方法是使用递归神经网络(RNNs),它在输入之间保持一个隐藏状态。在最简单的情况下,每个输入与当前隐藏状态组合以产生输出,然后将输出用作新的隐藏状态。这使得这些网络能够“记住”(在某种意义上)他们所看到的输入,并建立一个取决于所有输入及其顺序的最终输出。

我们将创建几乎最简单的RNN层,它将接受单个输入(例如,句子中的一个单词或单词中的一个字符),并在调用之间保持隐藏状态。

回想一下,我们的线性层有一个权重w,和一个偏差b。它接受了一个向量输入,并使用下面的逻辑产生了一个不同的向量作为输出:

python及数据分析基础(Python数据分析系列21.)(28)

这里我们希望合并隐藏状态,因此我们将有两组权重—一组应用于输入,另一组应用于以前的隐藏状态:

python及数据分析基础(Python数据分析系列21.)(29)

接下来,我们将使用输出向量作为隐藏值的新值。这并不是一个巨大的变化,但它将让我们的网络更好。

python及数据分析基础(Python数据分析系列21.)(30)

你可以看到,我们将隐藏状态作为全0的向量开始,我们提供了一个函数,使用网络的人可以调用来重置隐藏状态。

考虑到此设置,forword函数就相当简单(至少,如果你能记住并理解我们的线性层是如何工作的):

python及数据分析基础(Python数据分析系列21.)(31)

python及数据分析基础(Python数据分析系列21.)(32)

向后传递与我们的线性层中的一个相似,除了它需要计算一组额外的梯度:

python及数据分析基础(Python数据分析系列21.)(33)

最后,我们需要重载params和grads的方法:

python及数据分析基础(Python数据分析系列21.)(34)

注意

这个“简单”的RNN非常简单,因此你可能不应该在实践中使用它。

我们的SimpleRnn有一些不受欢迎的特性。一种是它的整个隐藏状态用于在每次调用输入时更新输入。另一种情况是,每次调用整个隐藏状态时都会被覆盖。这两种方法都使训练变得困难;特别是,它们使模型难以学习远程依赖关系。

由于这个原因,几乎没有人使用这种简单的RNN。相反,它们使用更复杂的变体,如LSTM(“长短期记忆”)或GRU(“门控单元”),它们具有更多的参数和用参数化的“门”,只允许每次更新一些状态(而某些状态被使用)。

这些变体并没有什么特别困难的;然而,它们涉及更多的代码,(在我看来)不会相应地更有启发意义。本章关于GitHub的代码包括一个LSTM实现。我鼓励你去看看,但这有点乏味,所以我们不会在这里进一步讨论。

我们实现的另一个奇怪之处是,它一次只需要“一步”,需要我们手动重置隐藏状态。一个更实际的RNN实现可以接受输入序列,在每个序列开始时将其隐藏状态设置为0s,并产生输出序列。我们的代码当然可以这样修改;同样,这将需要更多的代码和复杂性,而不会带来更深入的理解。

8. 示例:使用一个字符级的RNN

新聘请的品牌副总裁自己并没有想到达塔西安斯特这个名字,(因此)他怀疑一个更好的名字可能会给公司带来更多的成功。他要求你使用数据科学来提供被替代的候选建议。

RNNs的一个“很酷的”应用程序包括使用字符(而不是单词)作为输入,训练他们学习某些数据集中微妙的语言模式,然后使用它们从该数据集中生成虚构的实例。

例如,你可以训练一个RNN关于另类乐队的名字,使用训练过的模型为假的另类乐队生成新的名字,然后手工选择最有趣的乐队,并在推特上分享它们。真是太棒了!

看到这个技巧很多次后,你不再认为它非常聪明,你决定试试。

经过一番挖掘,你会发现启动加速器Y Combinator发布了其前100家(实际上是101家)最成功的初创公司的列表,这似乎是一个很好的起点。检查页面,你会发现公司的名称都生活在<b类=“h4“>标签中,这意味着很容易使用你的网络抓取技能来检索它们:

python及数据分析基础(Python数据分析系列21.)(35)

和往常一样,页面可能会更改(或消失),在这种情况下,此代码将无法工作。如果是这样,你可以使用新学习的数据科学技能来修复它,或者只是从书的GitHub网站获取列表。

那么,我们的计划是什么呢?我们将训练一个模型来预测名称的下一个字符,并给定当前字符和表示我们迄今为止看到的所有字符的隐藏状态。

与往常一样,我们将实际预测字符上的概率分布,并训练模型以最小化SoftmaxCrossEntropy损失。

一旦我们的模型被训练,我们可以使用它来生成一些概率,根据这些概率随机采样一个字符,然后将该字符作为下一个输入。这将允许我们使用学习到的权重来生成公司名称。

首先,我们应该根据名称中的字符构建一个词汇:

python及数据分析基础(Python数据分析系列21.)(36)

此外,我们将使用特殊令牌来表示公司名称的开始和结束。这允许模型了解哪些字符应该开始一个公司名称,也可以了解一个公司名称何时完成。

我们只使用正则表达式字符开始和结束,(幸运的是)它们不会出现在我们的公司列表中:

python及数据分析基础(Python数据分析系列21.)(37)

对于我们的模型,我们将对每个字符进行一次独热编码,通过两个SimpleRnns,然后使用线性层为每个可能的下一个字符生成分数:

python及数据分析基础(Python数据分析系列21.)(38)

想象一下,目前我们已经训练了这个模型。让我们使用“主题建模”中的sample_from函数,编写使用它生成新公司名称的函数:

python及数据分析基础(Python数据分析系列21.)(39)

python及数据分析基础(Python数据分析系列21.)(40)

最后,我们已经准备好训练我们的角色级的RNN了。这将需要一段时间!

python及数据分析基础(Python数据分析系列21.)(41)

python及数据分析基础(Python数据分析系列21.)(42)

训练后,模型从列表中生成一些实际名称(这并不奇怪,因为模型具有相当多的容量而没有大量的训练数据),以及一些与训练名称(脚本、链接、诗歌)类似看起来真正有创意的名称(Benuus, Cletpo, Equite, Vivest),以及垃圾但仍然类似单词的名称(SFitreasy, Sint ocanelp, GliyOx, Doorboronelhav)。

不幸的是,就像大多数角色级的RNN输出一样,这些输出只是有点聪明,而品牌的副总裁最终无法使用它们。

如果我将隐藏的维度提升到64,我就会从列表中逐字得到更多的名字;如果我把它放到8,我得到的大多是垃圾。所有这些模型大小的词汇表和最终权重可以在该书的GitHub网站上找到,你可以自己使用load_weights和load_vocab来使用它们。

如前所述,本章的GitHub代码还包含一个LSTM实现,你可以作为公司名称模型中SimpleRnns的替换品。

9. 以供进一步探索

• NLTK是Python的一个流行的NLP工具库。它有自己的整本书,可以在网上阅读。

• gensim是一个用于主题建模的Python库,它比我们的从头开始的模型更好。

• spaCy是“Python中的工业强度的自然语言处理”的库,也很受欢迎。

• 安德烈·卡帕蒂有一篇著名的博客文章,“递归神经网络的不合理有效性”,非常值得一读。

• 我的日常工作包括建立AllenNLP,一个用于做NLP研究的Python库。(至少,在这本书出版的时候,它确实是这样的。)该库完全超出了这本书的范围,但你可能仍然会发现它很有趣,而且它有一个很酷的交互式演示界面,可以演示许多最先进的NLP模型。

python及数据分析基础(Python数据分析系列21.)(43)

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页