Gensim API


介绍

Gensim API

Gensim是一个免费的 Python库,旨在从文档中自动提取语义主题,尽可能高效(计算机方面)和 painlessly(人性化)。

Gensim旨在处理原始的非结构化数字文本(纯文本)。

在Gensim的算法,比如Word2VecFastText,潜在语义分析(LSI,LSA,see LsiModel),隐含狄利克雷分布(LDA,见LdaModel)等,自动训练文档的躯体内检查统计共生模式发现的文件的语义结构。这些算法是无监督的,这意味着不需要人工输入 - 您只需要一个纯文本文档。

一旦找到这些统计模式,任何纯文本文档(句子,短语,单词……)都可以在新的语义表示中简洁地表达,并查询与其他文档(单词,短语……)的主题相似性。

注意 如果前面的段落让您感到困惑,您可以在Wikipedia上阅读有关向量空间模型无监督文档分析的更多信息。

功能

  • 内存独立性 - 任何时候都不需要整个训练语料库完全驻留在RAM中(可以处理大型的Web级语料库)。
  • 内存共享 - 经过训练的模型可以持久保存到磁盘并通过mmap加载回来。多个进程可以共享相同的数据,从而减少RAM占用空间。
  • 一些流行的向量空间算法的高效实现,包括Word2VecDoc2VecFastText,TF-IDF,潜在语义分析(LSI,LSA,见LsiModel),隐含狄利克雷分布(LDA,见LdaModel)或随机投影(见RpModel)。
  • 来自几种流行数据格式的I / O包装器和读卡器。
  • 在语义表示中对文档进行快速相似性查询。

Gensim背后的主要设计目标是:

  1. 为开发人员提供简单的接口和低API学习曲线。适合原型设计。
  2. 记忆独立性与输入语料库的大小有关; 所有中间步骤和算法都以流式方式运行,一次访问一个文档。

我们还为NLP,文档分析,索引,搜索和集群构建了一个高性能的商业服务器:https://scaletext.ai。ScaleText既可以在本地使用,也可以作为SaaS使用。

可用性

Gensim根据OSI批准的GNU LGPLv2.1许可证授权,可以从其Github存储库Python Package Index下载

也可以看看

有关Gensim部署的更多信息,请参阅安装页面。

核心概念

文集

数字文档的集合。Corpora在Gensim担任两个角色:

  1. 模型训练的输入语料库用于自动训练机器学习模型,例如 LsiModelLdaModel

    模型使用此培训语料库来查找共同的主题和主题,初始化其内部模型参数。

    Gensim专注于无监督模型,因此无需人工干预,例如昂贵的注释或手工标记文件。

  2. 要组织的文件。训练之后,可以使用主题模型从新文档中提取主题(培训语料库中未见的文档)。

    这样的语料库可以通过语义相似性,聚类等进行索引,查询。

向量空间模型

在向量空间模型(VSM)中,每个文档由一系列要素表示。例如,单个功能可以被视为问答配对:

  1. 单词splonge在文档中出现了多少次?零。
  2. 该文件包含多少段?二。
  3. 该文档使用了多少种字体?五。

问题通常只能由它的一个整数标识符(如所表示1,2和3在此),因此,该文件的表示变得一系列像(1, 0.0), (2, 2.0), (3, 5.0) 对。

如果我们提前知道所有问题,我们可能会隐瞒并简单地写成 (0.0, 2.0, 5.0)

该答案序列可以被认为是 向量(vector)(在这种情况下是三维密集矢量)。出于实际目的,Gensim中只允许答案(或可以转换为)单个浮点数的问题

每个文档的问题都是相同的,所以看两个向量(代表两个文档),我们希望能够得出结论,例如“这两个向量中的数字非常相似,因此原始文档必须类似“也是”。当然,这些结论是否与现实相符取决于我们选择问题的程度。

Gensim 稀疏向量,Bag-of-words 向量

为了节省空间,在Gensim中我们省略了值为0.0的所有向量元素。例如,我们只写(注意缺失的)而不是三维密集向量。每个向量元素是一对(2元组)。此稀疏表示中所有缺失特征的值可以明确地解析为零。(0.0,<span> 2.0,<span> 5.0)``[(2,<span> 2.0),<span> (3,<span> 5.0)]``(1,<span> 0.0)``(feature_id,feature_value)``0.0

Gensim中的文档由稀疏向量(有时称为词袋向量)表示。

Gensim流式语料库

Gensim没有规定任何特定的语料库格式。语料库只是一个稀疏向量序列(见上文)。

例如 [ [(2, 2.0), (3, 5.0)], [(3, 1.0)] ] 是两个文档的简单语料库=两个稀疏向量:第一个具有两个非零元素,第二个具有一个非零元素。这个特定的语料库表示为普通的Python 。

然而,Gensim的全部功能来自于语料库不必是a list,或NumPy数组,或Pandas数据帧等等。Gensim 接受任何对象,当迭代时,连续产生这些稀疏的袋子向量

这种灵活性允许您创建自己的语料库类,直接从磁盘,网络,数据库,数据帧…流式传输稀疏向量。实现Gensim中的模型,使得它们不需要所有向量一次驻留在RAM中。你甚至可以动态创建稀疏矢量!

Python流数据处理教程

有关直接从磁盘流式传输的高效语料库格式的内置示例,请参阅中的Matrix Market格式mmcorpus。有关如何创建自己的流式语料库的最小蓝图示例,请查看CSV语料库源代码

模型,转型

Gensim使用模型来引用将一个文档表示转换为另一个文档表示所需的代码和相关数据(模型参数)。

在Gensim中,文档被表示为向量(见上文),因此模型可以被认为是从一个向量空间到另一个向量空间的转换。从训练语料库中学习该变换的参数。

训练有素的模型(数据参数)可以持久保存到磁盘,然后加载回来,以继续培训新的培训文档或转换新文档。

Gensim实现多种模式,如Word2VecLsiModelLdaModelFastText等见API参考的完整列表。

安装

快速安装

在您的终端中运行(推荐):

pip install --upgrade gensim

或者,对于conda环境:

conda install -c conda-forge gensim

代码依赖

Gensim在Linux,Windows和Mac OS X上运行,并且应该在支持Python 2.7+和NumPy的任何其他平台上运行。Gensim取决于以下软件:

Core: 概要

  • 语料库和向量空间
    • 从字符串到向量
    • 语料库流 - 一次一个文档
    • 语料库格式
    • 与NumPy和SciPy的兼容性
  • 主题和转换
    • 转换界面
    • 可用的转换
  • 相似性查询
    • 相似界面
    • 下一个在哪里
  • 英语维基百科上的实验
    • 准备语料库
    • 潜在语义分析
    • 潜在的Dirichlet分配
  • 分布式计算
    • 为何分布式计算?
    • 先决条件
    • 核心概念
    • 可用分布式算法

准备

所有示例都可以直接复制到Python解释器shell。IPythoncpaste 命令对于复制代码片段(包括主要 >>> 字符)特别方便。

Gensim使用Python的标准 logging 模块来记录各种优先级的各种东西; 要激活日志记录(这是可选的),请运行

>>> import logging
>>> logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

快速示例

首先,让我们导入gensim并创建一个包含九个文档和十二个特征的小型语料库:

>>> from gensim import corpora, models, similarities
>>>
>>> corpus = [[(0, 1.0), (1, 1.0), (2, 1.0)],
>>>           [(2, 1.0), (3, 1.0), (4, 1.0), (5, 1.0), (6, 1.0), (8, 1.0)],
>>>           [(1, 1.0), (3, 1.0), (4, 1.0), (7, 1.0)],
>>>           [(0, 1.0), (4, 2.0), (7, 1.0)],
>>>           [(3, 1.0), (5, 1.0), (6, 1.0)],
>>>           [(9, 1.0)],
>>>           [(9, 1.0), (10, 1.0)],
>>>           [(9, 1.0), (10, 1.0), (11, 1.0)],
>>>           [(8, 1.0), (10, 1.0), (11, 1.0)]]

在Gensim中,语料库只是一个对象,当迭代时,返回其表示为稀疏向量的文档。在这种情况下,我们使用元组列表的列表。如果您不熟悉矢量空间模型,我们将在下一个关于Corpora和Vector Spaces的教程中弥合原始字符串语料库稀疏矢量之间的差距。

如果您熟悉向量空间模型,您可能会知道解析文档并将其转换为向量的方式会对任何后续应用程序的质量产生重大影响。

注意: 在此示例中,整个语料库作为Python列表存储在内存中。但是,语料库接口只表示语料库必须支持对其组成文档的迭代。对于非常大的语料库,有利的是将语料库保持在磁盘上,并且一次一个地顺序访问其文档。所有操作和转换都以这样的方式实现,使得它们在内存方面独立于语料库的大小。

接下来,让我们初始化一个转换

>>> tfidf = models.TfidfModel(corpus)

转换用于将文档从一个向量表示转换为另一个向量表示:

>>> vec = [(0, 1), (4, 1)]
>>> print(tfidf[vec])
[(0, 0.8075244), (4, 0.5898342)]

在这里,我们使用了Tf-Idf,这是一种简单的转换,它将文档表示为词袋计数,并应用对常用术语进行折扣的权重(或者等同于促销稀有术语)。它还将得到的向量缩放到单位长度(在欧几里德范数中)。

主题和转换教程中详细介绍了转换

要通过TfIdf转换整个语料库并对其进行索引,以准备相似性查询:

>>> index = similarities.SparseMatrixSimilarity(tfidf[corpus], num_features=12)

并查询我们的查询向量<span class="pre">vec</span>与语料库中每个文档的相似性:

>>> sims = index[tfidf[vec]]
>>> print(list(enumerate(sims)))
[(0, 0.4662244), (1, 0.19139354), (2, 0.24600551), (3, 0.82094586), (4, 0.0), (5, 0.0), (6, 0.0), (7, 0.0), (8, 0.0)]

如何阅读此输出?文档编号为零(第一个文档)的相似度得分为0.466 = 46.6%,第二个文档的相似度得分为19.1%等。

因此,根据TfIdf文档表示和余弦相似性度量,最类似于我们的查询文档vec是文档号。3,相似度得分为82.1%。请注意,在TfIdf表示中,任何不具有任何共同特征的 vec 文档(文档编号4-8)的相似性得分均为0.0。有关更多详细信息,请参阅Similarity Queries教程。

语料库和向量空间

Gensim官网

别忘了设置

>>> import logging
>>> logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

如果你想看到记录事件。

从字符串到向量

这一次,让我们从表示为字符串的文档开始:

>>> from gensim import corpora
>>>
>>> documents = ["Human machine interface for lab abc computer applications",
>>>              "A survey of user opinion of computer system response time",
>>>              "The EPS user interface management system",
>>>              "System and human system engineering testing of EPS",
>>>              "Relation of user perceived response time to error measurement",
>>>              "The generation of random binary unordered trees",
>>>              "The intersection graph of paths in trees",
>>>              "Graph minors IV Widths of trees and well quasi ordering",
>>>              "Graph minors A survey"]

这是一个由九个文档组成的小型语料库,每个文档只包含一个句子。

首先,让我们对文档进行标记,删除常用单词(使用玩具停止列表)以及仅在语料库中出现一次的单词:

>>> # remove common words and tokenize
>>> stoplist = set('for a of the and to in'.split())
>>> texts = [[word for word in document.lower().split() if word not in stoplist]
>>>          for document in documents]
>>>
>>> # remove words that appear only once
>>> from collections import defaultdict
>>> frequency = defaultdict(int)
>>> for text in texts:
>>>     for token in text:
>>>         frequency[token] += 1
>>>
>>> texts = [[token for token in text if frequency[token] > 1]
>>>          for text in texts]
>>>
>>> from pprint import pprint  # pretty-printer
>>> pprint(texts)
[['human', 'interface', 'computer'],
 ['survey', 'user', 'computer', 'system', 'response', 'time'],
 ['eps', 'user', 'interface', 'system'],
 ['system', 'human', 'system', 'eps'],
 ['user', 'response', 'time'],
 ['trees'],
 ['graph', 'trees'],
 ['graph', 'minors', 'trees'],
 ['graph', 'minors', 'survey']]

处理文件的方式可能会有所不同; 在这里,只拆分空格来标记,然后小写每个单词。实际上,我使用这种特殊的(简单和低效)设置来模仿Deerwester等人的原始LSA文章所做的实验。

处理文档的方式是多种多样的,依赖于应用程序和语言,我决定通过任何接口约束它们。相反,文档由从中提取的特征表示,而不是由其“表面”字符串形式表示:如何使用这些特征取决于您。下面我描述一种常见的通用方法(称为 词袋),但请记住,不同的应用程序域需要不同的功能,而且,一如既往,它是垃圾,垃圾输出 ……

要将文档转换为向量,我们将使用名为bag-of-words的文档表示 。在此表示中,每个文档由一个向量表示,其中每个向量元素表示问题 - 答案对,格式为:

“单词系统出现在文档中的次数是多少?一旦。”

仅通过它们的(整数)id来表示问题是有利的。问题和ID之间的映射称为字典:

>>> dictionary = corpora.Dictionary(texts)
>>> dictionary.save('/tmp/deerwester.dict')  # store the dictionary, for future reference
>>> print(dictionary)
Dictionary(12 unique tokens)

在这里,我们为语料库中出现的所有单词分配了一个唯一的整数id gensim.corpora.dictionary.Dictionary。这会扫描文本,收集字数和相关统计数据。最后,我们看到在处理过的语料库中有12个不同的单词,这意味着每个文档将由12个数字表示(即,通过12-D向量)。要查看单词及其ID之间的映射:

>>> print(dictionary.token2id)
{'minors': 11, 'graph': 10, 'system': 5, 'trees': 9, 'eps': 8, 'computer': 0,
'survey': 4, 'user': 7, 'human': 1, 'time': 6, 'interface': 2, 'response': 3}

要将标记化文档实际转换为向量:

>>> new_doc = "Human computer interaction"
>>> new_vec = dictionary.doc2bow(new_doc.lower().split())
>>> print(new_vec)  # the word "interaction" does not appear in the dictionary and is ignored
[(0, 1), (1, 1)]

函数doc2bow()只计算每个不同单词的出现次数,将单词转换为整数单词id,并将结果作为稀疏向量返回。 因此,稀疏向量 [(0, 1), (1, 1)] 读取:在文档“人机交互”中,单词computer (id 0)和human(id 1)出现一次; 其他十个字典单词(隐含地)出现零次。

>>> corpus = [dictionary.doc2bow(text) for text in texts]
>>> corpora.MmCorpus.serialize('/tmp/deerwester.mm', corpus)  # store to disk, for later use
>>> print(corpus)
[(0, 1), (1, 1), (2, 1)]
[(0, 1), (3, 1), (4, 1), (5, 1), (6, 1), (7, 1)]
[(2, 1), (5, 1), (7, 1), (8, 1)]
[(1, 1), (5, 2), (8, 1)]
[(3, 1), (6, 1), (7, 1)]
[(9, 1)]
[(9, 1), (10, 1)]
[(9, 1), (10, 1), (11, 1)]
[(4, 1), (10, 1), (11, 1)]

到目前为止,应该清楚的是,矢量要素 id=10 代表问题“文字中出现多少次文字?”,前六个文件的答案为“零”,其余三个答案为“一” 。事实上,我们已经得到了与快速示例中完全相同的向量语料库。

语料库流 - 一次一个文档

请注意,上面的语料库完全驻留在内存中,作为普通的Python列表。在这个简单的例子中,它并不重要,但为了使事情清楚,让我们假设语料库中有数百万个文档。将所有这些存储在RAM中是行不通的。相反,我们假设文档存储在磁盘上的文件中,每行一个文档。gensim只要求语料库必须能够一次返回一个文档向量:

>>> class MyCorpus(object):
>>>     def __iter__(self):
>>>         for line in open('mycorpus.txt'):
>>>             # assume there's one document per line, tokens separated by whitespace
>>>             yield dictionary.doc2bow(line.lower().split())

此处下载示例mycorpus.txt文件。假设每个文档在单个文件中占据一行并不重要; 您可以模拟iter函数以适合您的输入格式,无论它是什么。行走目录,解析XML,访问网络……只需解析输入以在每个文档中检索一个干净的标记列表,然后通过字典将标记转换为它们的ID,并在iter中生成生成的稀疏向量。

>>> corpus_memory_friendly = MyCorpus()  # doesn't load the corpus into memory!
>>> print(corpus_memory_friendly)

语料库现在是一个对象。我们没有定义任何打印方式,因此print只输出内存中对象的地址。不是很有用。要查看构成向量,让我们遍历语料库并打印每个文档向量(一次一个):

>>> for vector in corpus_memory_friendly:  # load one vector into memory at a time
...     print(vector)
[(0, 1), (1, 1), (2, 1)]
[(0, 1), (3, 1), (4, 1), (5, 1), (6, 1), (7, 1)]
[(2, 1), (5, 1), (7, 1), (8, 1)]
[(1, 1), (5, 2), (8, 1)]
[(3, 1), (6, 1), (7, 1)]
[(9, 1)]
[(9, 1), (10, 1)]
[(9, 1), (10, 1), (11, 1)]
[(4, 1), (10, 1), (11, 1)]

尽管输出与普通Python列表的输出相同,但语料库现在更加内存友好,因为一次最多只有一个向量驻留在RAM中。您的语料库现在可以随意扩展。

类似地,构造字典而不将所有文本加载到内存中:

>>> from six import iteritems
>>> # collect statistics about all tokens
>>> dictionary = corpora.Dictionary(line.lower().split() for line in open('mycorpus.txt'))
>>> # remove stop words and words that appear only once
>>> stop_ids = [dictionary.token2id[stopword] for stopword in stoplist
>>>             if stopword in dictionary.token2id]
>>> once_ids = [tokenid for tokenid, docfreq in iteritems(dictionary.dfs) if docfreq == 1]
>>> dictionary.filter_tokens(stop_ids + once_ids)  # remove stop words and words that appear only once
>>> dictionary.compactify()  # remove gaps in id sequence after words that were removed
>>> print(dictionary)
Dictionary(12 unique tokens)

这就是它的全部!至少就字袋表示而言。当然,我们用这种语料库做的是另一个问题; 如何计算不同单词的频率可能是有用的,这一点都不清楚。事实证明,它不是,我们需要首先对这个简单的表示应用转换,然后才能使用它来计算任何有意义的文档与文档的相似性。让我们简单地将注意力转向语料库持久性

语料库格式

存在几种用于将Vector Space语料库(〜矢量序列)序列化到磁盘的文件格式。 gensim通过前面提到的流式语料库接口实现它们:文件以懒惰的方式从(分别存储到)磁盘读取,一次一个文档,而不是一次将整个语料库读入主存储器。

市场矩阵格式是一种比较值得注意的文件格式。要以Matrix Market格式保存语料库:

>>> # create a toy corpus of 2 documents, as a plain Python list
>>> corpus = [[(1, 0.5)], []]  # make one document empty, for the heck of it
>>>
>>> corpora.MmCorpus.serialize('/tmp/corpus.mm', corpus)

其他格式包括Joachim的SVMlight格式Blei的LDA-C格式GibbsLDA ++格式

>>> corpora.SvmLightCorpus.serialize('/tmp/corpus.svmlight', corpus)
>>> corpora.BleiCorpus.serialize('/tmp/corpus.lda-c', corpus)
>>> corpora.LowCorpus.serialize('/tmp/corpus.low', corpus)

相反,要从Matrix Market文件加载语料库迭代器:

>>> corpus = corpora.MmCorpus('/tmp/corpus.mm')

语料库对象是流,因此通常您将无法直接打印它们:

>>> print(corpus)
MmCorpus(2 documents, 2 features, 1 non-zero entries)

相反,要查看语料库的内容:

>>> # one way of printing a corpus: load it entirely into memory
>>> print(list(corpus))  # calling list() will convert any sequence to a plain Python list
[[(1, 0.5)], []]

要么

>>> # another way of doing it: print one document at a time, making use of the streaming interface
>>> for doc in corpus:
...     print(doc)
[(1, 0.5)]
[]

第二种方式显然对内存更友好,但是出于测试和开发目的,没有什么比调用的简单性更好list(corpus)

要以Blei的LDA-C格式保存相同的Matrix Market文档流

>>> corpora.BleiCorpus.serialize('/tmp/corpus.lda-c', corpus)

通过这种方式,gensim还可以用作内存高效的I / O格式转换工具:只需使用一种格式加载文档流,然后立即以另一种格式保存。添加新格式非常容易,请查看SVMlight语料库的代码示例。

与NumPy和SciPy的兼容性

gensim还包含有效的实用程序函数 来帮助转换为/ numpy矩阵:

>>> import gensim
>>> import numpy as np
>>> numpy_matrix = np.random.randint(10, size=[5,2])  # random matrix as an example
>>> corpus = gensim.matutils.Dense2Corpus(numpy_matrix)
>>> numpy_matrix = gensim.matutils.corpus2dense(corpus, num_terms=number_of_corpus_features)

从/到scipy.sparse矩阵:

>>> import scipy.sparse
>>> scipy_sparse_matrix = scipy.sparse.random(5,2)  # random sparse matrix as example
>>> corpus = gensim.matutils.Sparse2Corpus(scipy_sparse_matrix)
>>> scipy_csc_matrix = gensim.matutils.corpus2csc(corpus)

要获得完整的参考(想要将字典修剪为更小的尺寸?优化语料库和NumPy / SciPy数组之间的转换?),请参阅API文档。或者继续下一个关于主题和转换的教程。

主题和转换

转换接口

在上一章,我们创建了一个文档语料库,表示为向量流。要继续,让我们启动gensim并使用该语料库:

>>> from gensim import corpora, models, similarities
>>> if (os.path.exists("/tmp/deerwester.dict")):
>>>    dictionary = corpora.Dictionary.load('/tmp/deerwester.dict')
>>>    corpus = corpora.MmCorpus('/tmp/deerwester.mm')
>>>    print("Used files generated from first tutorial")
>>> else:
>>>    print("Please run first tutorial to generate data set")

MmCorpus(9个文件,12个特征,28个非零项)

我将展示如何将文档从一个矢量表示转换为另一个矢量表示。这个过程有两个目标:

  1. 为了在语料库中显示隐藏的结构,发现单词之间的关系并使用它们以新的(希望)更加语义的方式描述文档。
  2. 使文档表示更紧凑。这既提高了效率(新表示消耗更少的资源)和功效(边际数据趋势被忽略,降噪)。

创建转换

转换是标准的Python对象,通常通过训练语料库进行初始化:

>>> tfidf = models.TfidfModel(corpus) # step 1 -- initialize a model

我们使用上一章中的旧语料库初始化(训练)转换模型。不同的转换可能需要不同的初始化参数; 在TfIdf的情况下,“训练”仅包括通过提供的语料库一次并计算其所有特征的文档频率。训练其他模型,例如潜在语义分析或潜在Dirichlet分配,涉及更多,因此需要更多时间。

注意:

转换总是在两个特定的向量空间之间转换。必须使用相同的向量空间(=同一组特征id)进行训练以及后续的向量转换。无法使用相同的输入要素空间,例如应用不同的字符串预处理,使用不同的特征ID,或使用预期为TfIdf向量的词袋输入向量,将导致转换调用期间的特征不匹配,从而导致垃圾中的任何一个输出和/或运行时异常。

变换向量

从现在开始,tfidf 被视为一个只读对象,可用于将任何向量从旧表示(bag-of-words整数计数)转换为新表示(TfIdf实值权重):

>>> doc_bow = [(0, 1), (1, 1)]
>>> print(tfidf[doc_bow]) # step 2 -- use the model to transform vectors
[(0, 0.70710678), (1, 0.70710678)]

或者将转换应用于整个语料库:

>>> corpus_tfidf = tfidf[corpus]
>>> for doc in corpus_tfidf:
...     print(doc)
[(0, 0.57735026918962573), (1, 0.57735026918962573), (2, 0.57735026918962573)]
[(0, 0.44424552527467476), (3, 0.44424552527467476), (4, 0.44424552527467476), (5, 0.32448702061385548), (6, 0.44424552527467476), (7, 0.32448702061385548)]
[(2, 0.5710059809418182), (5, 0.41707573620227772), (7, 0.41707573620227772), (8, 0.5710059809418182)]
[(1, 0.49182558987264147), (5, 0.71848116070837686), (8, 0.49182558987264147)]
[(3, 0.62825804686700459), (6, 0.62825804686700459), (7, 0.45889394536615247)]
[(9, 1.0)]
[(9, 0.70710678118654746), (10, 0.70710678118654746)]
[(9, 0.50804290089167492), (10, 0.50804290089167492), (11, 0.69554641952003704)]
[(4, 0.62825804686700459), (10, 0.45889394536615247), (11, 0.62825804686700459)]

在这种特殊情况下,我们正在改变我们用于训练的同一语料库,但这只是偶然的。一旦初始化了转换模型,它就可以用在任何向量上(当然它们来自相同的向量空间),即使它们根本没有用在训练语料库中。这是通过LSA的折叠过程,LDA的主题推断等来实现的。

注意 调用model[corpus]仅在旧corpus 文档流周围创建一个包装器- 实际转换在文档迭代期间即时完成。我们无法在调用 corpus_transformed = model[corpus] 时转换整个语料库,因为这意味着将结果存储在主存中,这与gensim的内存独立目标相矛盾。如果您将多次迭代转换,并且转换成本很高,请先将生成的语料库序列化为磁盘并继续使用它。

转换也可以序列化,一个在另一个之上,在一个链中:

>>> lsi = models.LsiModel(corpus_tfidf, id2word=dictionary, num_topics=2) # initialize an LSI transformation
>>> corpus_lsi = lsi[corpus_tfidf] # create a double wrapper over the original corpus: bow->tfidf->fold-in-lsi

在这里,我们通过潜在语义索引将我们的Tf-Idf语料库 转换为潜在的2-D空间(因为我们设置了2-D num_topics=2)。现在你可能想知道:这两个潜在的维度代表什么?让我们检查一下models.LsiModel.print_topics()

>>> lsi.print_topics(2)
topic #0(1.594): -0.703*"trees" + -0.538*"graph" + -0.402*"minors" + -0.187*"survey" + -0.061*"system" + -0.060*"response" + -0.060*"time" + -0.058*"user" + -0.049*"computer" + -0.035*"interface"
topic #1(1.476): -0.460*"system" + -0.373*"user" + -0.332*"eps" + -0.328*"interface" + -0.320*"response" + -0.320*"time" + -0.293*"computer" + -0.280*"human" + -0.171*"survey" + 0.161*"trees"

根据LSI的说法,“树”,“图”和“未成年人”都是相关词(并且对第一个主题的方向贡献最大),而第二个主题实际上与所有其他词有关。正如所料,前五个文件与第二个主题的关联性更强,而剩下的四个文件与第一个主题相关:

>>> for doc in corpus_lsi: # both bow->tfidf and tfidf->lsi transformations are actually executed here, on the fly
...     print(doc)
[(0, -0.066), (1, 0.520)] # "Human machine interface for lab abc computer applications"
[(0, -0.197), (1, 0.761)] # "A survey of user opinion of computer system response time"
[(0, -0.090), (1, 0.724)] # "The EPS user interface management system"
[(0, -0.076), (1, 0.632)] # "System and human system engineering testing of EPS"
[(0, -0.102), (1, 0.574)] # "Relation of user perceived response time to error measurement"
[(0, -0.703), (1, -0.161)] # "The generation of random binary unordered trees"
[(0, -0.877), (1, -0.168)] # "The intersection graph of paths in trees"
[(0, -0.910), (1, -0.141)] # "Graph minors IV Widths of trees and well quasi ordering"
[(0, -0.617), (1, 0.054)] # "Graph minors A survey"

使用save()load()函数实现模型持久性:

>>> lsi.save('/tmp/model.lsi') # same for tfidf, lda, ...
>>> lsi = models.LsiModel.load('/tmp/model.lsi')

接下来的问题可能是:这些文件之间的相似程度如何?有没有办法形式化相似性,以便对于给定的输入文档,我们可以根据它们的相似性订购一些其他文档?

可用的转换

gensim实现了几种流行的矢量空间模型算法:

  • 术语频率*反向文档频率,Tf-Idf 期望初始化期间的词袋(整数值)训练语料库。在变换期间,它将采用向量并返回具有相同维度的另一个向量,除了在训练语料库中罕见的特征将增加其值。因此,它将整数值向量转换为实值向量,同时保持维度的数量不变。它还可以任选地将得到的矢量归一化为(欧几里得)单位长度。

    >>> model = models.TfidfModel(corpus, normalize=True)

  • 潜在语义索引,LSI(或有时LSA) 将文档从单词袋或(优选地)TfIdf加权空间转换为较低维度的潜在空间。对于上面的玩具语料库,我们只使用了2个潜在维度,但在实际语料库中,建议将200-500的目标维度作为“黄金标准” 。

    >>> model = models.LsiModel(tfidf_corpus, id2word=dictionary, num_topics=300)

    LSI培训的独特之处在于我们可以随时继续“培训”,只需提供更多培训文件即可。这是通过在称为在线培训的过程中对底层模型的增量更新来完成的。由于这个特性,输入文档流甚至可能是无限的 - 只需在LSI新文档到达时继续提供它们,同时使用计算的转换模型作为只读!

>>> model.add_documents(another_tfidf_corpus) # now LSI has been trained on tfidf_corpus + another_tfidf_corpus
>>> lsi_vec = model[tfidf_vec] # convert some new document into the LSI space, without affecting the model
>>> ...
>>> model.add_documents(more_documents) # tfidf_corpus + another_tfidf_corpus + more_documents
>>> lsi_vec = model[tfidf_vec]
>>> ...
  • 有关gensim.models.lsimodel如何使LSI逐渐“忘记”无限流中的旧观察的详细信息,请参阅文档。如果你想变脏,还有一些你可以调整的参数会影响速度与内存占用量和LSI算法的数值精度。

    gensim使用了一种新颖的在线增量流分布式训练算法(相当满口!)。gensim还执行Halko等人的随机多遍算法。加速核心部分的计算,另请参阅英语维基百科以上的实验,便通过在计算机集群中分配计算来进一步提高速度。

  • 随机投影,RP旨在减少向量空间维度。这是一种非常有效的(内存和CPU友好的)方法,通过投入一点随机性来近似文档之间的TfIdf距离。建议的目标维度再次为数百/数千,具体取决于您的数据集。

    >>> model = models.RpModel(tfidf_corpus, num_topics=500)

  • Latent Dirichlet Allocation,LDA 是另一种从词袋计数转变为低维度主题空间的转变。LDA是LSA(也称为多项PCA)的概率扩展,因此LDA的主题可以解释为对单词的概率分布。与LSA一样,这些分布也是从训练语料库中自动推断出来的。文档又被解释为这些主题的(软)混合(再次,就像LSA一样)。

    >>> model = models.LdaModel(corpus, id2word=dictionary, num_topics=100)

    gensim使用在线LDA参数估计的快速实现,修改为在计算机集群上以分布式模式运行。

  • 分层Dirichlet过程,HDP 是一种非参数贝叶斯方法(请注意缺少的请求主题数):

    >>> model = models.HdpModel(corpus, id2word=dictionary)

    gensim使用快速在线实现。HDP模型是gensim的新成员,并且在学术方面仍然很粗糙 - 谨慎使用。

添加新的VSM转换(例如不同的加权方案)相当简单; 有关更多信息和示例,请参阅API参考或直接参阅Python代码

值得重申的是,这些都是独特的增量实现,不需要整个训练语料库一次性存在于主存储器中。有了内存,我现在正在改进分布式计算,以提高CPU效率。

相似性查询

相似性界面

为了说明在gensim中如何做到这一点,让我们考虑与之前的例子相同的语料库(它最初来自Deerwester等人的“潜在语义分析索引” 1990年开篇 文章):

>>> from gensim import corpora, models, similarities
>>> dictionary = corpora.Dictionary.load('/tmp/deerwester.dict')
>>> corpus = corpora.MmCorpus('/tmp/deerwester.mm') # comes from the first tutorial, "From strings to vectors"
>>> print(corpus)
MmCorpus(9 documents, 12 features, 28 non-zero entries)

按照Deerwester的例子,我们首先使用这个小的语料库来定义一个二维LSI空间:

>>> lsi = models.LsiModel(corpus, id2word=dictionary, num_topics=2)

现在假设用户输入查询“人机交互”。我们希望按照与此查询相关的递减顺序对我们的九个语料库文档进行排序。与现代搜索引擎不同,这里我们只关注可能相似性的一个方面 - 关于其文本(单词)的明显语义相关性。没有超链接,没有随机游走静态排名,只是布尔关键字匹配的语义扩展:

>>> doc = "Human computer interaction"
>>> vec_bow = dictionary.doc2bow(doc.lower().split())
>>> vec_lsi = lsi[vec_bow] # convert the query to LSI space
>>> print(vec_lsi)
[(0, -0.461821), (1, 0.070028)]

此外,我们将考虑余弦相似性 来确定两个向量的相似性。余弦相似度是向量空间建模中的标准度量,但是无论向量表示概率分布, 不同的相似性度量可能更合适。

初始化查询结构

为了准备相似性查询,我们需要输入我们想要与后续查询进行比较的所有文档。在我们的例子中,它们与用于训练LSI的九个文件相同,转换为二维LSA空间。但这只是偶然的,我们也可能完全索引不同的语料库。

>>> index = similarities.MatrixSimilarity(lsi[corpus]) # transform corpus to LSI space and index it

警告

  • similarities.MatrixSimilarity只有当整个向量集适合内存时,该类才适用。例如,当与此类一起使用时,一百万个文档的语料库在256维LSI空间中将需要2GB的RAM。
  • 如果没有2GB的可用RAM,则需要使用similarities.Similarity该类。此类通过在磁盘上的多个文件(称为分片)之间拆分索引,在固定内存中运行。它使用similarities.MatrixSimilaritysimilarities.SparseMatrixSimilarity内部,所以它仍然很快,虽然稍微复杂一点。

索引持久性通过标准save()load()函数处理:

>>> index.save('/tmp/deerwester.index')
>>> index = similarities.MatrixSimilarity.load('/tmp/deerwester.index')

对于所有相似性索引类(similarities.Similaritysimilarities.MatrixSimilaritysimilarities.SparseMatrixSimilarity)都是如此。同样在下文中,索引可以是任何这些的对象。如果有疑问,请使用similarities.Similarity,因为它是最具扩展性的版本,并且它还支持稍后向索引添加更多文档。

执行查询

要获得我们的查询文档与九个索引文档的相似性:

>>> sims = index[vec_lsi] # perform a similarity query against the corpus
>>> print(list(enumerate(sims))) # print (document_number, document_similarity) 2-tuples
[(0, 0.99809301), (1, 0.93748635), (2, 0.99844527), (3, 0.9865886), (4, 0.90755945),
(5, -0.12416792), (6, -0.1063926), (7, -0.098794639), (8, 0.05004178)]

余弦测量返回范围中的相似度(越大,越相似),因此第一个文档的得分为0.99809301等。

使用一些标准的Python魔术,我们将这些相似性按降序排序,并获得查询 人机交互 的最终答案:

>>> sims = sorted(enumerate(sims), key=lambda item: -item[1])
>>> print(sims) # print sorted (document number, similarity score) 2-tuples
[(2, 0.99844527), # The EPS user interface management system
(0, 0.99809301), # Human machine interface for lab abc computer applications
(3, 0.9865886), # System and human system engineering testing of EPS
(1, 0.93748635), # A survey of user opinion of computer system response time
(4, 0.90755945), # Relation of user perceived response time to error measurement
(8, 0.050041795), # Graph minors A survey
(7, -0.098794639), # Graph minors IV Widths of trees and well quasi ordering
(6, -0.1063926), # The intersection graph of paths in trees
(5, -0.12416792)] # The generation of random binary unordered trees

(我将原始文档以“字符串形式”添加到输出注释中,以提高清晰度。)

这里要注意的是文件没有。标准布尔全文搜索永远不会返回2(EPS用户界面管理系统)和4(用户感知响应时间与错误测量的关系),因为他们不与 人机交互 分享任何常用词。然而,在应用LSI之后,我们可以观察到它们都获得了相当高的相似性得分(第2个实际上是最相似的!),这更符合我们对它们与查询共享 computer-human 相关主题的直觉。事实上,这种语义概括是我们首先应用转换并进行主题建模的原因。

下一个在哪里

恭喜你,你已经完成了教程-现在你知道作品:-)深入到更多的细节如何gensim,您可以通过浏览API文档,请参阅维基百科的实验或者是退房分布式计算中gensim。

gensim是一个相当成熟的软件包,已被许多个人和公司成功使用,用于快速原型制作和生产。这并不意味着它是完美的:

在所有NLP(甚至机器学习)子域中,gensim没有野心成为一个包罗万象的框架。它的使命是帮助NLP从业者轻松地在大型数据集上尝试流行的主题建模算法,并促进研究人员对新算法的原型设计。

英语维基百科上的实验

为了测试gensim性能,我们针对英文版的Wikipedia运行它。

此页面描述了获取和处理Wikipedia的过程,以便任何人都可以重现结果。假设您已正确安装 gensim。

准备语料库

  1. 首先,从http://download.wikimedia.org/enwiki/下载所有维基百科文章的转储 (您需要文件enwiki-latest-pages-articles.xml.bz2或enwiki-YYYYMMDD-pages-articles.xml。 bz2用于特定于日期的转储)。此文件大小约为8GB,包含英语维基百科的所有文章(压缩版本)。

  2. 将文章转换为纯文本(处理Wiki标记)并将结果存储为稀疏TF-IDF向量。在Python中,这很容易在运行中进行,我们甚至不需要将整个存档解压缩到磁盘。gensim中包含一个脚本 可以执行此操作,运行:

    $ python -m gensim.scripts.make_wiki

注意

  • 这个预处理步骤通过8.2GB压缩wiki转储进行两次传递(一次用于提取字典,一次用于创建和存储稀疏向量),并且在笔记本电脑上花费大约9个小时,因此您可能想要喝咖啡或二。
  • 此外,您将需要大约35GB的可用磁盘空间来存储稀疏输出向量。我建议立即压缩这些文件,例如使用bzip2(低至~13GB)。gensim可以直接使用压缩文件,因此可以节省磁盘空间。

潜在语义分析

首先让我们加载在上面第二步中创建的语料库迭代器和字典:

>>> import logging, gensim
>>> logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

>>> # load id->word mapping (the dictionary), one of the results of step 2 above
>>> id2word = gensim.corpora.Dictionary.load_from_text('wiki_en_wordids.txt')
>>> # load corpus iterator
>>> mm = gensim.corpora.MmCorpus('wiki_en_tfidf.mm')
>>> # mm = gensim.corpora.MmCorpus('wiki_en_tfidf.mm.bz2') # use this if you compressed the TFIDF output (recommended)

>>> print(mm)
MmCorpus(3931787 documents, 100000 features, 756379027 non-zero entries)

我们看到我们的语料库包含3.9M文档,100K特征(不同的标记)和稀疏TF-IDF矩阵中的0.76G非零条目。维基百科语料库共包含约22.4亿个令牌。

现在我们准备计算英语维基百科的LSA:

>>> # extract 400 LSI topics; use the default one-pass algorithm
>>> lsi = gensim.models.lsimodel.LsiModel(corpus=mm, id2word=id2word, num_topics=400)

>>> # print the most contributing words (both positively and negatively) for each of the first ten topics
>>> lsi.print_topics(10)
topic #0(332.762): 0.425*"utc" + 0.299*"talk" + 0.293*"page" + 0.226*"article" + 0.224*"delete" + 0.216*"discussion" + 0.205*"deletion" + 0.198*"should" + 0.146*"debate" + 0.132*"be"
topic #1(201.852): 0.282*"link" + 0.209*"he" + 0.145*"com" + 0.139*"his" + -0.137*"page" + -0.118*"delete" + 0.114*"blacklist" + -0.108*"deletion" + -0.105*"discussion" + 0.100*"diff"
topic #2(191.991): -0.565*"link" + -0.241*"com" + -0.238*"blacklist" + -0.202*"diff" + -0.193*"additions" + -0.182*"users" + -0.158*"coibot" + -0.136*"user" + 0.133*"he" + -0.130*"resolves"
topic #3(141.284): -0.476*"image" + -0.255*"copyright" + -0.245*"fair" + -0.225*"use" + -0.173*"album" + -0.163*"cover" + -0.155*"resolution" + -0.141*"licensing" + 0.137*"he" + -0.121*"copies"
topic #4(130.909): 0.264*"population" + 0.246*"age" + 0.243*"median" + 0.213*"income" + 0.195*"census" + -0.189*"he" + 0.184*"households" + 0.175*"were" + 0.167*"females" + 0.166*"males"
topic #5(120.397): 0.304*"diff" + 0.278*"utc" + 0.213*"you" + -0.171*"additions" + 0.165*"talk" + -0.159*"image" + 0.159*"undo" + 0.155*"www" + -0.152*"page" + 0.148*"contribs"
topic #6(115.414): -0.362*"diff" + -0.203*"www" + 0.197*"you" + -0.180*"undo" + -0.180*"kategori" + 0.164*"users" + 0.157*"additions" + -0.150*"contribs" + -0.139*"he" + -0.136*"image"
topic #7(111.440): 0.429*"kategori" + 0.276*"categoria" + 0.251*"category" + 0.207*"kategorija" + 0.198*"kategorie" + -0.188*"diff" + 0.163*"категория" + 0.153*"categoría" + 0.139*"kategoria" + 0.133*"categorie"
topic #8(109.907): 0.385*"album" + 0.224*"song" + 0.209*"chart" + 0.204*"band" + 0.169*"released" + 0.151*"music" + 0.142*"diff" + 0.141*"vocals" + 0.138*"she" + 0.132*"guitar"
topic #9(102.599): -0.237*"league" + -0.214*"he" + -0.180*"season" + -0.174*"football" + -0.166*"team" + 0.159*"station" + -0.137*"played" + -0.131*"cup" + 0.131*"she" + -0.128*"utc"

在我的笔记本电脑上创建维基百科的LSI模型大约需要4小时9分钟。这是约每分钟16000的文件,包括所有的I / O

注意 如果您需要更快的结果,请参阅分布式计算教程。请注意,gensim中的BLAS库透明地使用多个内核,因此可以“免费”在多核计算机上更快地处理相同的数据,而无需任何分布式设置。

我们看到总处理时间主要是从原始维基百科XML转储准备TF-IDF语料库的预处理步骤,花费了9小时。

gensim中使用的算法只需要查看每个输入文档一次,因此它适用于文档作为不可重复的流,或者多次存储/迭代语料库的成本太高的环境。

潜在Dirichlet分配

与上面的Latent Semantic Analysis一样,首先加载语料库迭代器和字典:

>>> import logging, gensim
>>> logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

>>> # load id->word mapping (the dictionary), one of the results of step 2 above
>>> id2word = gensim.corpora.Dictionary.load_from_text('wiki_en_wordids.txt')
>>> # load corpus iterator
>>> mm = gensim.corpora.MmCorpus('wiki_en_tfidf.mm')
>>> # mm = gensim.corpora.MmCorpus('wiki_en_tfidf.mm.bz2') # use this if you compressed the TFIDF output

>>> print(mm)
MmCorpus(3931787 documents, 100000 features, 756379027 non-zero entries)

我们将运行在线LDA,这是一个算法,需要一大堆文件,更新LDA模型,取另一个块,更新模型等。在线LDA可以与批处理LDA进行对比,批处理LDA处理整个语料库(一次完整通过),然后更新模型,然后另一个传递,另一个更新…不同的是,给定一个相当固定的文档流(没有太多的主题漂移),较小的块(子扇区)上的在线更新本身相当不错,因此模型估计收敛更快。因此,我们可能只需要对语料库进行一次完整传递:如果语料库有300万篇文章,并且我们在每10,000篇文章后更新一次,这意味着我们将在一次传递中完成300次更新,很可能足以有一个非常准确的主题估计:

>>> # extract 100 LDA topics, using 1 pass and updating once every 1 chunk (10,000 documents)
>>> lda = gensim.models.ldamodel.LdaModel(corpus=mm, id2word=id2word, num_topics=100, update_every=1, chunksize=10000, passes=1)
using serial LDA version on this node
running online LDA training, 100 topics, 1 passes over the supplied corpus of 3931787 documents, updating model once every 10000 documents
...

与LSA不同,来自LDA的主题更容易理解:

>>> # print the most contributing words for 20 randomly selected topics
>>> lda.print_topics(20)
topic #0: 0.009*river + 0.008*lake + 0.006*island + 0.005*mountain + 0.004*area + 0.004*park + 0.004*antarctic + 0.004*south + 0.004*mountains + 0.004*dam
topic #1: 0.026*relay + 0.026*athletics + 0.025*metres + 0.023*freestyle + 0.022*hurdles + 0.020*ret + 0.017*divisão + 0.017*athletes + 0.016*bundesliga + 0.014*medals
topic #2: 0.002*were + 0.002*he + 0.002*court + 0.002*his + 0.002*had + 0.002*law + 0.002*government + 0.002*police + 0.002*patrolling + 0.002*their
topic #3: 0.040*courcelles + 0.035*centimeters + 0.023*mattythewhite + 0.021*wine + 0.019*stamps + 0.018*oko + 0.017*perennial + 0.014*stubs + 0.012*ovate + 0.011*greyish
topic #4: 0.039*al + 0.029*sysop + 0.019*iran + 0.015*pakistan + 0.014*ali + 0.013*arab + 0.010*islamic + 0.010*arabic + 0.010*saudi + 0.010*muhammad
topic #5: 0.020*copyrighted + 0.020*northamerica + 0.014*uncopyrighted + 0.007*rihanna + 0.005*cloudz + 0.005*knowles + 0.004*gaga + 0.004*zombie + 0.004*wigan + 0.003*maccabi
topic #6: 0.061*israel + 0.056*israeli + 0.030*sockpuppet + 0.025*jerusalem + 0.025*tel + 0.023*aviv + 0.022*palestinian + 0.019*ifk + 0.016*palestine + 0.014*hebrew
topic #7: 0.015*melbourne + 0.014*rovers + 0.013*vfl + 0.012*australian + 0.012*wanderers + 0.011*afl + 0.008*dinamo + 0.008*queensland + 0.008*tracklist + 0.008*brisbane
topic #8: 0.011*film + 0.007*her + 0.007*she + 0.004*he + 0.004*series + 0.004*his + 0.004*episode + 0.003*films + 0.003*television + 0.003*best
topic #9: 0.019*wrestling + 0.013*château + 0.013*ligue + 0.012*discus + 0.012*estonian + 0.009*uci + 0.008*hockeyarchives + 0.008*wwe + 0.008*estonia + 0.007*reign
topic #10: 0.078*edits + 0.059*notability + 0.035*archived + 0.025*clearer + 0.022*speedy + 0.021*deleted + 0.016*hook + 0.015*checkuser + 0.014*ron + 0.011*nominator
topic #11: 0.013*admins + 0.009*acid + 0.009*molniya + 0.009*chemical + 0.007*ch + 0.007*chemistry + 0.007*compound + 0.007*anemone + 0.006*mg + 0.006*reaction
topic #12: 0.018*india + 0.013*indian + 0.010*tamil + 0.009*singh + 0.008*film + 0.008*temple + 0.006*kumar + 0.006*hindi + 0.006*delhi + 0.005*bengal
topic #13: 0.047*bwebs + 0.024*malta + 0.020*hobart + 0.019*basa + 0.019*columella + 0.019*huon + 0.018*tasmania + 0.016*popups + 0.014*tasmanian + 0.014*modèle
topic #14: 0.014*jewish + 0.011*rabbi + 0.008*bgwhite + 0.008*lebanese + 0.007*lebanon + 0.006*homs + 0.005*beirut + 0.004*jews + 0.004*hebrew + 0.004*caligari
topic #15: 0.025*german + 0.020*der + 0.017*von + 0.015*und + 0.014*berlin + 0.012*germany + 0.012*die + 0.010*des + 0.008*kategorie + 0.007*cross
topic #16: 0.003*can + 0.003*system + 0.003*power + 0.003*are + 0.003*energy + 0.002*data + 0.002*be + 0.002*used + 0.002*or + 0.002*using
topic #17: 0.049*indonesia + 0.042*indonesian + 0.031*malaysia + 0.024*singapore + 0.022*greek + 0.021*jakarta + 0.016*greece + 0.015*dord + 0.014*athens + 0.011*malaysian
topic #18: 0.031*stakes + 0.029*webs + 0.018*futsal + 0.014*whitish + 0.013*hyun + 0.012*thoroughbred + 0.012*dnf + 0.012*jockey + 0.011*medalists + 0.011*racehorse
topic #19: 0.119*oblast + 0.034*uploaded + 0.034*uploads + 0.033*nordland + 0.025*selsoviet + 0.023*raion + 0.022*krai + 0.018*okrug + 0.015*hålogaland + 0.015*russiae + 0.020*manga + 0.017*dragon + 0.012*theme + 0.011*dvd + 0.011*super + 0.011*hunter + 0.009*ash + 0.009*dream + 0.009*angel

在我的笔记本电脑上创建维基百科的这个LDA模型需要大约6小时20分钟。如果您需要更快地获得结果,请考虑在计算机群集上运行Distributed Latent Dirichlet Allocation

注意LDA和LSA运行之间的两个区别:我们要求LSA提取400个主题,LDA只有100个主题(因此速度差异实际上更大)。其次,gensim中的LSA实现是真正的在线:如果输入流的性质随时间变化,LSA将在相当少量的更新中重新定位自己以反映这些变化。相比之下,LDA并不是真正的在线,因为后来更新对模型的影响逐渐减弱。如果输入文档流中存在主题偏差,LDA将会变得混乱,并且在调整自身以适应新的状态时会越来越慢。

简而言之,如果使用LDA逐步将新文档添加到模型中,请务必小心。批量使用LDA,其中整个训练语料库事先已知或未显示主题漂移,是可以的并且不受影响

要运行批量LDA(不在线),请使用以下方法训练LdaModel:

>>> # extract 100 LDA topics, using 20 full passes, no online updates
>>> lda = gensim.models.ldamodel.LdaModel(corpus=mm, id2word=id2word, num_topics=100, update_every=0, passes=20)

像往常一样,训练有素的模型可以用来将新的,看不见的文档(简单的词袋计数向量)转换为LDA主题分布:

>>> doc_lda = lda[doc_bow]

分布式计算

为何分布式计算?

需要构建一个语料库的语义表示,这个语料库是数百万个大型文档并且它将永远存在?您可以使用几台闲置的机器吗? 分布式计算试图通过将给定任务拆分为几个较小的子任务来加速计算,并将它们并行传递到多个计算节点。

在gensim的上下文中,计算节点是由其IP地址/端口标识的计算机,并且通过TCP / IP进行通信。整个可用机器集合称为集群。分布非常粗糙(进行的通信不多),因此允许网络具有相对较高的延迟。

警告

  • 使用分布式计算的主要原因是使事情运行得更快。在gensim中,大多数耗时的东西是在NumPy内部的线性代数的低级例程中完成的,与任何gensim代码无关。 *为NumPy 安装快速 BLAS(基本线性代数) *库可以将性能提高15倍!因此,在开始购买这些额外的计算机之前,请考虑安装一个针对您的特定计算机优化的快速线程BLAS(而不是通用的二进制分布式库)。选项包括供应商的BLAS库(英特尔的MKL,AMD的ACML,OS X的vecLib,Sun的Sunperf,……)或一些开源替代品(GotoBLAS,ALTAS)。
  • 要查看您正在使用的BLAS和LAPACK,请键入您的shell: python -c 'import scipy; scipy.show_config()'

先决条件

对于节点之间的通信,gensim使用Pyro(PYthon远程对象),版本> = 4.27。这是一个用于Python中的低级套接字通信和远程过程调用(RPC)的库。Pyro4是一个纯Python库,因此它的安装非常简单,只需将* .py文件复制到Python的导入路径上:

pip install Pyro4

您不必安装Pyro来运行gensim,但如果不这样做,您将无法访问分布式功能(即,所有内容都将始终以串行模式运行,此页面上的示例不适用)。

核心概念

与往常一样,gensim努力寻求一个清晰明了的API(见功能)。为此,您无需在代码中进行任何更改,以便在计算机集群上运行它!

您需要做的是在开始计算之前在每个集群节点上运行一个工作脚本(见下文)。运行此脚本告诉gensim它可以使用该节点作为从属程序将某些工作委托给它。在初始化期间,gensim中的算法将尝试查找并奴役所有可用的工作节点。

  • Node

一个逻辑工作单位。可以对应单个物理计算机,但您也可以在一台计算机上运行多个工作程序,从而生成多个逻辑节点。

  • Cluster

通过TCP / IP进行通信的几个节点。目前,网络广播用于发现和连接所有通信节点,因此节点必须位于同一广播域内

  • Worker

在每个节点上创建的进程。要从群集中删除节点,只需终止其工作进程。

  • Dispatcher

调度员将负责协商所有计算,排队(“调度”)个人工作给工人。计算永远不会直接与工作节点“交谈”,只能通过此调度程序。与worker不同,集群中一次只能有一个活动调度程序。

可用的分布式算法

Core End


Model Start

Word2Vec Model

如果你错过了热门话题,Word2Vec 是一种广泛使用的基于神经网络的算法,通常被称为“深度学习”(尽管 word2vec 本身相当浅)。word2vec 使用大量未注释的纯文本,自动学习单词之间的关系。输出是向量,每个词一个向量,具有显着的线性关系,允许我们做如下事情:

  • vec(“king”) - vec(“man”) + vec(“woman”) =~ vec(“queen”)
  • vec(“蒙特利尔加拿大人队”) – vec(“蒙特利尔”) + vec(“多伦多”) =~ vec(“多伦多枫叶队”)。

Word2vec 在自动文本标记、推荐系统和机器翻译中非常有用。

  1. 引入Word2Vec作为对传统词袋的改进
  2. 展示Word2Vec使用预训练模型的演示
  3. 演示从您自己的数据训练新模型
  4. 演示加载和保存模型
  5. 介绍几个训练参数并展示它们的效果
  6. 讨论内存要求
  7. 通过应用降维来可视化 Word2Vec 嵌入

Review: Bag-of-words

You may be familiar with the bag-of-words model from the Vector section. This model transforms each document to a fixed-length vector of integers. For example, given the sentences:

  • John likes to watch movies. Mary likes movies too.
  • John also likes to watch football games. Mary hates football.

The model outputs the vectors:

  • [1, 2, 1, 1, 2, 1, 1, 0, 0, 0, 0]
  • [1, 1, 1, 1, 0, 1, 0, 1, 2, 1, 1]

Each vector has 10 elements, where each element counts the number of times a particular word occurred in the document. The order of elements is arbitrary. In the example above, the order of the elements corresponds to the words: ["John", "likes", "to", "watch", "movies", "Mary", "too", "also", "football", "games", "hates"].

Bag-of-words models are surprisingly effective, but have several weaknesses.

First, they lose all information about word order: “John likes Mary” and “Mary likes John” correspond to identical vectors. There is a solution: bag of n-grams models consider word phrases of length n to represent documents as fixed-length vectors to capture local word order but suffer from data sparsity and high dimensionality.

Second, the model does not attempt to learn the meaning of the underlying words, and as a consequence, the distance between vectors doesn’t always reflect the difference in meaning. The Word2Vec model addresses this second problem.

介绍:Word2Vec模型

Word2Vec是一个更新的模型,它使用浅层神经网络将单词嵌入到低维向量空间中。结果是一组词向量,其中向量空间中靠近的向量根据上下文具有相似的含义,而彼此远离的词向量具有不同的含义。例如,strongpowerful会靠近在一起,strong并且Paris会相对较远。

这个模型有两个版本,Word2Vec 类都实现了它们:

  1. Skip-grams (SG)
  2. Continuous-bag-of-words (CBOW)

The Word2Vec Skip-gram model, for example, takes in pairs (word1, word2) generated by moving a window across text data, and trains a 1-hidden-layer neural network based on the synthetic task of given an input word, giving us a predicted probability distribution of nearby words to the input. A virtual one-hot encoding of words goes through a ‘projection layer’ to the hidden layer; these projection weights are later interpreted as the word embeddings. So if the hidden layer has 300 neurons, this network will give us 300-dimensional word embeddings.

Continuous-bag-of-words Word2vec is very similar to the skip-gram model. It is also a 1-hidden-layer neural network. The synthetic training task now uses the average of multiple input context words, rather than a single word as in skip-gram, to predict the center word. Again, the projection weights that turn one-hot words into averageable vectors, of the same width as the hidden layer, are interpreted as the word embeddings.

Word2Vec Demo

为了看看能做什么Word2Vec,让我们下载一个预先训练的模型并使用它。我们将获取在部分 Google 新闻数据集上训练的 Word2Vec 模型,该模型涵盖大约 300 万个单词和短语。这样的模型可能需要几个小时来训练,但由于它已经可用,用 Gensim 下载和加载它只需要几分钟。

该模型大约为 2GB,因此您需要良好的网络连接才能继续。否则,请跳到下面的“训练你自己的模型”部分。

您还可以查看在线 word2vec 演示,您可以在其中亲自尝试此向量代数。该演示word2vec大约 1000 亿字整个Google 新闻数据集上 运行。

import gensim.downloader as api
wv = api.load('word2vec-google-news-300')

一个常见的操作是检索模型的词汇表。这是微不足道的:

for index, word in enumerate(wv.index_to_key):
    if index == 10:
        break
    print(f"word #{index}/{len(wv.index_to_key)} is {word}")
word #0/3000000 is </s>
word #1/3000000 is in
word #2/3000000 is for
word #3/3000000 is that
word #4/3000000 is is
word #5/3000000 is on
word #6/3000000 is ##
word #7/3000000 is The
word #8/3000000 is with
word #9/3000000 is said

我们可以轻松获得模型熟悉的术语的向量:

vec_king = wv['king']

不幸的是,该模型无法为不熟悉的单词推断向量。这是 Word2Vec 的一个限制:如果这个限制对您很重要,请查看 FastText 模型。

try:
    vec_cameroon = wv['cameroon']
except KeyError:
    print("The word 'cameroon' does not appear in this model")
The word 'cameroon' does not appear in this model

继续,Word2Vec支持多个开箱即用的单词相似性任务。您可以看到随着单词变得越来越不相似,相似度是如何直观地降低的。

pairs = [
    ('car', 'minivan'),   # a minivan is a kind of car
    ('car', 'bicycle'),   # still a wheeled vehicle
    ('car', 'airplane'),  # ok, no wheels, but still a vehicle
    ('car', 'cereal'),    # ... and so on
    ('car', 'communism'),
]
for w1, w2 in pairs:
    print('%r\t%r\t%.2f' % (w1, w2, wv.similarity(w1, w2)))
'car'   'minivan'       0.69
'car'   'bicycle'       0.54
'car'   'airplane'      0.42
'car'   'cereal'        0.14
'car'   'communism'     0.06

打印与“car”或“minivan”最相似的 5 个词

print(wv.most_similar(positive=['car', 'minivan'], topn=5))
[('SUV', 0.8532192707061768), ('vehicle', 0.8175783753395081), ('pickup_truck', 0.7763688564300537), ('Jeep', 0.7567334175109863), ('Ford_Explorer', 0.7565720081329346)]

下面哪个不属于这个序列?

print(wv.doesnt_match(['fire', 'water', 'land', 'sea', 'air', 'car']))
car

Training Your Own Model

首先,您需要一些数据来训练模型。对于下面的例子中,我们将使用利评价语料库 (你已经有了 ,如果你已经安装了Gensim)。

这个语料库足够小,可以完全放在内存中,但我们将实现一个内存友好的迭代器,逐行读取它以演示如何处理更大的语料库。

from gensim.test.utils import datapath
from gensim import utils

class MyCorpus:
    """An iterator that yields sentences (lists of str)."""

    def __iter__(self):
        corpus_path = datapath('lee_background.cor')
        for line in open(corpus_path):
            # assume there's one document per line, tokens separated by whitespace
            yield utils.simple_preprocess(line)

如果我们想做任何自定义的预处理,例如解码非标准编码、小写、删除数字、提取命名实体……所有这些都可以在MyCorpus迭代器内部完成,word2vec不需要知道。所需要的只是输入产生一个又一个的句子(utf8 单词列表)。

让我们继续在我们的语料库上训练一个模型。现在不要太担心训练参数,我们稍后会重新讨论它们。

import gensim.models

sentences = MyCorpus()
model = gensim.models.Word2Vec(sentences=sentences)

一旦我们有了我们的模型,我们就可以像上面的演示一样使用它。

模型的主要部分是model.wv,其中“wv”代表“词向量”。

vec_king = model.wv['king']

检索词汇表的工作方式相同:

for index, word in enumerate(wv.index_to_key):
    if index == 10:
        break
    print(f"word #{index}/{len(wv.index_to_key)} is {word}")
word #0/3000000 is </s>
word #1/3000000 is in
word #2/3000000 is for
word #3/3000000 is that
word #4/3000000 is is
word #5/3000000 is on
word #6/3000000 is ##
word #7/3000000 is The
word #8/3000000 is with
word #9/3000000 is said

Storing and loading models

您会注意到训练非平凡模型可能需要时间。训练模型并按预期工作后,您可以将其保存到磁盘。这样,您以后就不必再花时间重新训练它。

您可以使用标准的 gensim 方法存储/加载模型:

import tempfile

with tempfile.NamedTemporaryFile(prefix='gensim-model-', delete=False) as tmp:
    temporary_filepath = tmp.name
    model.save(temporary_filepath)
    #
    # The model is now safely stored in the filepath.
    # You can copy it to other machines, share it with others, etc.
    #
    # To load a saved model:
    #
    new_model = gensim.models.Word2Vec.load(temporary_filepath)

它在内部使用 pickle,可选择mmap将模型的内部大型 NumPy 矩阵直接从磁盘文件放入虚拟内存中,以实现进程间内存共享。

此外,您可以加载由原始 C 工具创建的模型,使用其文本和二进制格式:

model = gensim.models.KeyedVectors.load_word2vec_format('/tmp/vectors.txt', binary=False)
# using gzipped/bz2 input works too, no need to unzip
model = gensim.models.KeyedVectors.load_word2vec_format('/tmp/vectors.bin.gz', binary=True)

Training Parameters

Word2Vec 接受几个影响训练速度和质量的参数。

min_count

min_count用于修剪内部字典。在十亿字的语料库中只出现一次或两次的单词可能是无趣的错别字和垃圾。此外,没有足够的数据对这些词进行任何有意义的训练,所以最好忽略它们:

min_count 的默认值=5

model = gensim.models.Word2Vec(sentences, min_count=10)

vector_size

vector_size 是 gensim Word2Vec 将单词映射到的 N 维空间的维数 (N)。

更大的尺寸值需要更多的训练数据,但可以产生更好(更准确)的模型。合理的值在数十到数百之间。

# The default value of vector_size is 100.
model = gensim.models.Word2Vec(sentences, vector_size=200)

workers

workers,最后一个主要参数(此处为完整列表)用于训练并行化,以加快训练速度:

# default value of workers=3 (tutorial says 1...)
model = gensim.models.Word2Vec(sentences, workers=4)

workers参数仅在您安装了Cython 时才有效。如果没有用Cython,你只可以使用的,因为一个核心GIL

Memory

在其核心,word2vec模型参数存储为矩阵(NumPy 数组)。每个数组是#vocabulary(由min_count参数控制)乘以浮点数(单精度又名 4 字节)的向量大小vector_size参数)。

三个这样的矩阵保存在 RAM 中(正在努力将这个数字减少到两个,甚至一个)。因此,如果您的输入包含 100,000 个唯一单词,并且您要求 layer vector_size=200,则模型将需要大约。 .100,000*200*4*3 bytes = ~229MB

存储词汇树需要一点额外的内存(100,000 个单词将占用几兆字节),但除非您的单词是非常长的字符串,否则内存占用将由上述三个矩阵支配。

Evaluating

Word2Vec训练是一项无监督的任务,没有好的方法可以客观地评估结果。评估取决于您的最终应用。

谷歌发布了大约 20,000 个句法和语义测试示例的测试集,遵循“A 对 B 就像 C 对 D”的任务。它在“数据集”文件夹中提供。

例如,比较类型的句法类比是bad:worse;good:?。数据集中共有 9 种句法比较,如复数名词和相反含义的名词。

语义问题包含五种类型的语义类比,例如首都(Paris:France;Tokyo:?)或家庭成员(brother:sister;dad:?)。

Gensim 支持相同的评估集,格式完全相同:

model.wv.evaluate_word_analogies(datapath('questions-words.txt'))
(0.0, [{'section': 'capital-common-countries', 'correct': [], 'incorrect': [('CANBERRA', 'AUSTRALIA', 'KABUL', 'AFGHANISTAN'), ('CANBERRA', 'AUSTRALIA', 'PARIS', 'FRANCE'), ('KABUL', 'AFGHANISTAN', 'PARIS', 'FRANCE'), ('KABUL', 'AFGHANISTAN', 'CANBERRA', 'AUSTRALIA'), ('PARIS', 'FRANCE', 'CANBERRA', 'AUSTRALIA'), ('PARIS', 'FRANCE', 'KABUL', 'AFGHANISTAN')]}, {'section': 'capital-world', 'correct': [], 'incorrect': [('CANBERRA', 'AUSTRALIA', 'KABUL', 'AFGHANISTAN'), ('KABUL', 'AFGHANISTAN', 'PARIS', 'FRANCE')]}, {'section': 'currency', 'correct': [], 'incorrect': []}, {'section': 'city-in-state', 'correct': [], 'incorrect': []}, {'section': 'family', 'correct': [], 'incorrect': [('HE', 'SHE', 'HIS', 'HER'), ('HE', 'SHE', 'MAN', 'WOMAN'), ('HIS', 'HER', 'MAN', 'WOMAN'), ('HIS', 'HER', 'HE', 'SHE'), ('MAN', 'WOMAN', 'HE', 'SHE'), ('MAN', 'WOMAN', 'HIS', 'HER')]}, {'section': 'gram1-adjective-to-adverb', 'correct': [], 'incorrect': []}, {'section': 'gram2-opposite', 'correct': [], 'incorrect': []}, {'section': 'gram3-comparative', 'correct': [], 'incorrect': [('GOOD', 'BETTER', 'GREAT', 'GREATER'), ('GOOD', 'BETTER', 'LONG', 'LONGER'), ('GOOD', 'BETTER', 'LOW', 'LOWER'), ('GOOD', 'BETTER', 'SMALL', 'SMALLER'), ('GREAT', 'GREATER', 'LONG', 'LONGER'), ('GREAT', 'GREATER', 'LOW', 'LOWER'), ('GREAT', 'GREATER', 'SMALL', 'SMALLER'), ('GREAT', 'GREATER', 'GOOD', 'BETTER'), ('LONG', 'LONGER', 'LOW', 'LOWER'), ('LONG', 'LONGER', 'SMALL', 'SMALLER'), ('LONG', 'LONGER', 'GOOD', 'BETTER'), ('LONG', 'LONGER', 'GREAT', 'GREATER'), ('LOW', 'LOWER', 'SMALL', 'SMALLER'), ('LOW', 'LOWER', 'GOOD', 'BETTER'), ('LOW', 'LOWER', 'GREAT', 'GREATER'), ('LOW', 'LOWER', 'LONG', 'LONGER'), ('SMALL', 'SMALLER', 'GOOD', 'BETTER'), ('SMALL', 'SMALLER', 'GREAT', 'GREATER'), ('SMALL', 'SMALLER', 'LONG', 'LONGER'), ('SMALL', 'SMALLER', 'LOW', 'LOWER')]}, {'section': 'gram4-superlative', 'correct': [], 'incorrect': [('BIG', 'BIGGEST', 'GOOD', 'BEST'), ('BIG', 'BIGGEST', 'GREAT', 'GREATEST'), ('BIG', 'BIGGEST', 'LARGE', 'LARGEST'), ('GOOD', 'BEST', 'GREAT', 'GREATEST'), ('GOOD', 'BEST', 'LARGE', 'LARGEST'), ('GOOD', 'BEST', 'BIG', 'BIGGEST'), ('GREAT', 'GREATEST', 'LARGE', 'LARGEST'), ('GREAT', 'GREATEST', 'BIG', 'BIGGEST'), ('GREAT', 'GREATEST', 'GOOD', 'BEST'), ('LARGE', 'LARGEST', 'BIG', 'BIGGEST'), ('LARGE', 'LARGEST', 'GOOD', 'BEST'), ('LARGE', 'LARGEST', 'GREAT', 'GREATEST')]}, {'section': 'gram5-present-participle', 'correct': [], 'incorrect': [('GO', 'GOING', 'LOOK', 'LOOKING'), ('GO', 'GOING', 'PLAY', 'PLAYING'), ('GO', 'GOING', 'RUN', 'RUNNING'), ('GO', 'GOING', 'SAY', 'SAYING'), ('LOOK', 'LOOKING', 'PLAY', 'PLAYING'), ('LOOK', 'LOOKING', 'RUN', 'RUNNING'), ('LOOK', 'LOOKING', 'SAY', 'SAYING'), ('LOOK', 'LOOKING', 'GO', 'GOING'), ('PLAY', 'PLAYING', 'RUN', 'RUNNING'), ('PLAY', 'PLAYING', 'SAY', 'SAYING'), ('PLAY', 'PLAYING', 'GO', 'GOING'), ('PLAY', 'PLAYING', 'LOOK', 'LOOKING'), ('RUN', 'RUNNING', 'SAY', 'SAYING'), ('RUN', 'RUNNING', 'GO', 'GOING'), ('RUN', 'RUNNING', 'LOOK', 'LOOKING'), ('RUN', 'RUNNING', 'PLAY', 'PLAYING'), ('SAY', 'SAYING', 'GO', 'GOING'), ('SAY', 'SAYING', 'LOOK', 'LOOKING'), ('SAY', 'SAYING', 'PLAY', 'PLAYING'), ('SAY', 'SAYING', 'RUN', 'RUNNING')]}, {'section': 'gram6-nationality-adjective', 'correct': [], 'incorrect': [('AUSTRALIA', 'AUSTRALIAN', 'FRANCE', 'FRENCH'), ('AUSTRALIA', 'AUSTRALIAN', 'INDIA', 'INDIAN'), ('AUSTRALIA', 'AUSTRALIAN', 'ISRAEL', 'ISRAELI'), ('AUSTRALIA', 'AUSTRALIAN', 'JAPAN', 'JAPANESE'), ('AUSTRALIA', 'AUSTRALIAN', 'SWITZERLAND', 'SWISS'), ('FRANCE', 'FRENCH', 'INDIA', 'INDIAN'), ('FRANCE', 'FRENCH', 'ISRAEL', 'ISRAELI'), ('FRANCE', 'FRENCH', 'JAPAN', 'JAPANESE'), ('FRANCE', 'FRENCH', 'SWITZERLAND', 'SWISS'), ('FRANCE', 'FRENCH', 'AUSTRALIA', 'AUSTRALIAN'), ('INDIA', 'INDIAN', 'ISRAEL', 'ISRAELI'), ('INDIA', 'INDIAN', 'JAPAN', 'JAPANESE'), ('INDIA', 'INDIAN', 'SWITZERLAND', 'SWISS'), ('INDIA', 'INDIAN', 'AUSTRALIA', 'AUSTRALIAN'), ('INDIA', 'INDIAN', 'FRANCE', 'FRENCH'), ('ISRAEL', 'ISRAELI', 'JAPAN', 'JAPANESE'), ('ISRAEL', 'ISRAELI', 'SWITZERLAND', 'SWISS'), ('ISRAEL', 'ISRAELI', 'AUSTRALIA', 'AUSTRALIAN'), ('ISRAEL', 'ISRAELI', 'FRANCE', 'FRENCH'), ('ISRAEL', 'ISRAELI', 'INDIA', 'INDIAN'), ('JAPAN', 'JAPANESE', 'SWITZERLAND', 'SWISS'), ('JAPAN', 'JAPANESE', 'AUSTRALIA', 'AUSTRALIAN'), ('JAPAN', 'JAPANESE', 'FRANCE', 'FRENCH'), ('JAPAN', 'JAPANESE', 'INDIA', 'INDIAN'), ('JAPAN', 'JAPANESE', 'ISRAEL', 'ISRAELI'), ('SWITZERLAND', 'SWISS', 'AUSTRALIA', 'AUSTRALIAN'), ('SWITZERLAND', 'SWISS', 'FRANCE', 'FRENCH'), ('SWITZERLAND', 'SWISS', 'INDIA', 'INDIAN'), ('SWITZERLAND', 'SWISS', 'ISRAEL', 'ISRAELI'), ('SWITZERLAND', 'SWISS', 'JAPAN', 'JAPANESE')]}, {'section': 'gram7-past-tense', 'correct': [], 'incorrect': [('GOING', 'WENT', 'PAYING', 'PAID'), ('GOING', 'WENT', 'PLAYING', 'PLAYED'), ('GOING', 'WENT', 'SAYING', 'SAID'), ('GOING', 'WENT', 'TAKING', 'TOOK'), ('PAYING', 'PAID', 'PLAYING', 'PLAYED'), ('PAYING', 'PAID', 'SAYING', 'SAID'), ('PAYING', 'PAID', 'TAKING', 'TOOK'), ('PAYING', 'PAID', 'GOING', 'WENT'), ('PLAYING', 'PLAYED', 'SAYING', 'SAID'), ('PLAYING', 'PLAYED', 'TAKING', 'TOOK'), ('PLAYING', 'PLAYED', 'GOING', 'WENT'), ('PLAYING', 'PLAYED', 'PAYING', 'PAID'), ('SAYING', 'SAID', 'TAKING', 'TOOK'), ('SAYING', 'SAID', 'GOING', 'WENT'), ('SAYING', 'SAID', 'PAYING', 'PAID'), ('SAYING', 'SAID', 'PLAYING', 'PLAYED'), ('TAKING', 'TOOK', 'GOING', 'WENT'), ('TAKING', 'TOOK', 'PAYING', 'PAID'), ('TAKING', 'TOOK', 'PLAYING', 'PLAYED'), ('TAKING', 'TOOK', 'SAYING', 'SAID')]}, {'section': 'gram8-plural', 'correct': [], 'incorrect': [('BUILDING', 'BUILDINGS', 'CAR', 'CARS'), ('BUILDING', 'BUILDINGS', 'CHILD', 'CHILDREN'), ('BUILDING', 'BUILDINGS', 'MAN', 'MEN'), ('BUILDING', 'BUILDINGS', 'ROAD', 'ROADS'), ('BUILDING', 'BUILDINGS', 'WOMAN', 'WOMEN'), ('CAR', 'CARS', 'CHILD', 'CHILDREN'), ('CAR', 'CARS', 'MAN', 'MEN'), ('CAR', 'CARS', 'ROAD', 'ROADS'), ('CAR', 'CARS', 'WOMAN', 'WOMEN'), ('CAR', 'CARS', 'BUILDING', 'BUILDINGS'), ('CHILD', 'CHILDREN', 'MAN', 'MEN'), ('CHILD', 'CHILDREN', 'ROAD', 'ROADS'), ('CHILD', 'CHILDREN', 'WOMAN', 'WOMEN'), ('CHILD', 'CHILDREN', 'BUILDING', 'BUILDINGS'), ('CHILD', 'CHILDREN', 'CAR', 'CARS'), ('MAN', 'MEN', 'ROAD', 'ROADS'), ('MAN', 'MEN', 'WOMAN', 'WOMEN'), ('MAN', 'MEN', 'BUILDING', 'BUILDINGS'), ('MAN', 'MEN', 'CAR', 'CARS'), ('MAN', 'MEN', 'CHILD', 'CHILDREN'), ('ROAD', 'ROADS', 'WOMAN', 'WOMEN'), ('ROAD', 'ROADS', 'BUILDING', 'BUILDINGS'), ('ROAD', 'ROADS', 'CAR', 'CARS'), ('ROAD', 'ROADS', 'CHILD', 'CHILDREN'), ('ROAD', 'ROADS', 'MAN', 'MEN'), ('WOMAN', 'WOMEN', 'BUILDING', 'BUILDINGS'), ('WOMAN', 'WOMEN', 'CAR', 'CARS'), ('WOMAN', 'WOMEN', 'CHILD', 'CHILDREN'), ('WOMAN', 'WOMEN', 'MAN', 'MEN'), ('WOMAN', 'WOMEN', 'ROAD', 'ROADS')]}, {'section': 'gram9-plural-verbs', 'correct': [], 'incorrect': []}, {'section': 'Total accuracy', 'correct': [], 'incorrect': [('CANBERRA', 'AUSTRALIA', 'KABUL', 'AFGHANISTAN'), ('CANBERRA', 'AUSTRALIA', 'PARIS', 'FRANCE'), ('KABUL', 'AFGHANISTAN', 'PARIS', 'FRANCE'), ('KABUL', 'AFGHANISTAN', 'CANBERRA', 'AUSTRALIA'), ('PARIS', 'FRANCE', 'CANBERRA', 'AUSTRALIA'), ('PARIS', 'FRANCE', 'KABUL', 'AFGHANISTAN'), ('CANBERRA', 'AUSTRALIA', 'KABUL', 'AFGHANISTAN'), ('KABUL', 'AFGHANISTAN', 'PARIS', 'FRANCE'), ('HE', 'SHE', 'HIS', 'HER'), ('HE', 'SHE', 'MAN', 'WOMAN'), ('HIS', 'HER', 'MAN', 'WOMAN'), ('HIS', 'HER', 'HE', 'SHE'), ('MAN', 'WOMAN', 'HE', 'SHE'), ('MAN', 'WOMAN', 'HIS', 'HER'), ('GOOD', 'BETTER', 'GREAT', 'GREATER'), ('GOOD', 'BETTER', 'LONG', 'LONGER'), ('GOOD', 'BETTER', 'LOW', 'LOWER'), ('GOOD', 'BETTER', 'SMALL', 'SMALLER'), ('GREAT', 'GREATER', 'LONG', 'LONGER'), ('GREAT', 'GREATER', 'LOW', 'LOWER'), ('GREAT', 'GREATER', 'SMALL', 'SMALLER'), ('GREAT', 'GREATER', 'GOOD', 'BETTER'), ('LONG', 'LONGER', 'LOW', 'LOWER'), ('LONG', 'LONGER', 'SMALL', 'SMALLER'), ('LONG', 'LONGER', 'GOOD', 'BETTER'), ('LONG', 'LONGER', 'GREAT', 'GREATER'), ('LOW', 'LOWER', 'SMALL', 'SMALLER'), ('LOW', 'LOWER', 'GOOD', 'BETTER'), ('LOW', 'LOWER', 'GREAT', 'GREATER'), ('LOW', 'LOWER', 'LONG', 'LONGER'), ('SMALL', 'SMALLER', 'GOOD', 'BETTER'), ('SMALL', 'SMALLER', 'GREAT', 'GREATER'), ('SMALL', 'SMALLER', 'LONG', 'LONGER'), ('SMALL', 'SMALLER', 'LOW', 'LOWER'), ('BIG', 'BIGGEST', 'GOOD', 'BEST'), ('BIG', 'BIGGEST', 'GREAT', 'GREATEST'), ('BIG', 'BIGGEST', 'LARGE', 'LARGEST'), ('GOOD', 'BEST', 'GREAT', 'GREATEST'), ('GOOD', 'BEST', 'LARGE', 'LARGEST'), ('GOOD', 'BEST', 'BIG', 'BIGGEST'), ('GREAT', 'GREATEST', 'LARGE', 'LARGEST'), ('GREAT', 'GREATEST', 'BIG', 'BIGGEST'), ('GREAT', 'GREATEST', 'GOOD', 'BEST'), ('LARGE', 'LARGEST', 'BIG', 'BIGGEST'), ('LARGE', 'LARGEST', 'GOOD', 'BEST'), ('LARGE', 'LARGEST', 'GREAT', 'GREATEST'), ('GO', 'GOING', 'LOOK', 'LOOKING'), ('GO', 'GOING', 'PLAY', 'PLAYING'), ('GO', 'GOING', 'RUN', 'RUNNING'), ('GO', 'GOING', 'SAY', 'SAYING'), ('LOOK', 'LOOKING', 'PLAY', 'PLAYING'), ('LOOK', 'LOOKING', 'RUN', 'RUNNING'), ('LOOK', 'LOOKING', 'SAY', 'SAYING'), ('LOOK', 'LOOKING', 'GO', 'GOING'), ('PLAY', 'PLAYING', 'RUN', 'RUNNING'), ('PLAY', 'PLAYING', 'SAY', 'SAYING'), ('PLAY', 'PLAYING', 'GO', 'GOING'), ('PLAY', 'PLAYING', 'LOOK', 'LOOKING'), ('RUN', 'RUNNING', 'SAY', 'SAYING'), ('RUN', 'RUNNING', 'GO', 'GOING'), ('RUN', 'RUNNING', 'LOOK', 'LOOKING'), ('RUN', 'RUNNING', 'PLAY', 'PLAYING'), ('SAY', 'SAYING', 'GO', 'GOING'), ('SAY', 'SAYING', 'LOOK', 'LOOKING'), ('SAY', 'SAYING', 'PLAY', 'PLAYING'), ('SAY', 'SAYING', 'RUN', 'RUNNING'), ('AUSTRALIA', 'AUSTRALIAN', 'FRANCE', 'FRENCH'), ('AUSTRALIA', 'AUSTRALIAN', 'INDIA', 'INDIAN'), ('AUSTRALIA', 'AUSTRALIAN', 'ISRAEL', 'ISRAELI'), ('AUSTRALIA', 'AUSTRALIAN', 'JAPAN', 'JAPANESE'), ('AUSTRALIA', 'AUSTRALIAN', 'SWITZERLAND', 'SWISS'), ('FRANCE', 'FRENCH', 'INDIA', 'INDIAN'), ('FRANCE', 'FRENCH', 'ISRAEL', 'ISRAELI'), ('FRANCE', 'FRENCH', 'JAPAN', 'JAPANESE'), ('FRANCE', 'FRENCH', 'SWITZERLAND', 'SWISS'), ('FRANCE', 'FRENCH', 'AUSTRALIA', 'AUSTRALIAN'), ('INDIA', 'INDIAN', 'ISRAEL', 'ISRAELI'), ('INDIA', 'INDIAN', 'JAPAN', 'JAPANESE'), ('INDIA', 'INDIAN', 'SWITZERLAND', 'SWISS'), ('INDIA', 'INDIAN', 'AUSTRALIA', 'AUSTRALIAN'), ('INDIA', 'INDIAN', 'FRANCE', 'FRENCH'), ('ISRAEL', 'ISRAELI', 'JAPAN', 'JAPANESE'), ('ISRAEL', 'ISRAELI', 'SWITZERLAND', 'SWISS'), ('ISRAEL', 'ISRAELI', 'AUSTRALIA', 'AUSTRALIAN'), ('ISRAEL', 'ISRAELI', 'FRANCE', 'FRENCH'), ('ISRAEL', 'ISRAELI', 'INDIA', 'INDIAN'), ('JAPAN', 'JAPANESE', 'SWITZERLAND', 'SWISS'), ('JAPAN', 'JAPANESE', 'AUSTRALIA', 'AUSTRALIAN'), ('JAPAN', 'JAPANESE', 'FRANCE', 'FRENCH'), ('JAPAN', 'JAPANESE', 'INDIA', 'INDIAN'), ('JAPAN', 'JAPANESE', 'ISRAEL', 'ISRAELI'), ('SWITZERLAND', 'SWISS', 'AUSTRALIA', 'AUSTRALIAN'), ('SWITZERLAND', 'SWISS', 'FRANCE', 'FRENCH'), ('SWITZERLAND', 'SWISS', 'INDIA', 'INDIAN'), ('SWITZERLAND', 'SWISS', 'ISRAEL', 'ISRAELI'), ('SWITZERLAND', 'SWISS', 'JAPAN', 'JAPANESE'), ('GOING', 'WENT', 'PAYING', 'PAID'), ('GOING', 'WENT', 'PLAYING', 'PLAYED'), ('GOING', 'WENT', 'SAYING', 'SAID'), ('GOING', 'WENT', 'TAKING', 'TOOK'), ('PAYING', 'PAID', 'PLAYING', 'PLAYED'), ('PAYING', 'PAID', 'SAYING', 'SAID'), ('PAYING', 'PAID', 'TAKING', 'TOOK'), ('PAYING', 'PAID', 'GOING', 'WENT'), ('PLAYING', 'PLAYED', 'SAYING', 'SAID'), ('PLAYING', 'PLAYED', 'TAKING', 'TOOK'), ('PLAYING', 'PLAYED', 'GOING', 'WENT'), ('PLAYING', 'PLAYED', 'PAYING', 'PAID'), ('SAYING', 'SAID', 'TAKING', 'TOOK'), ('SAYING', 'SAID', 'GOING', 'WENT'), ('SAYING', 'SAID', 'PAYING', 'PAID'), ('SAYING', 'SAID', 'PLAYING', 'PLAYED'), ('TAKING', 'TOOK', 'GOING', 'WENT'), ('TAKING', 'TOOK', 'PAYING', 'PAID'), ('TAKING', 'TOOK', 'PLAYING', 'PLAYED'), ('TAKING', 'TOOK', 'SAYING', 'SAID'), ('BUILDING', 'BUILDINGS', 'CAR', 'CARS'), ('BUILDING', 'BUILDINGS', 'CHILD', 'CHILDREN'), ('BUILDING', 'BUILDINGS', 'MAN', 'MEN'), ('BUILDING', 'BUILDINGS', 'ROAD', 'ROADS'), ('BUILDING', 'BUILDINGS', 'WOMAN', 'WOMEN'), ('CAR', 'CARS', 'CHILD', 'CHILDREN'), ('CAR', 'CARS', 'MAN', 'MEN'), ('CAR', 'CARS', 'ROAD', 'ROADS'), ('CAR', 'CARS', 'WOMAN', 'WOMEN'), ('CAR', 'CARS', 'BUILDING', 'BUILDINGS'), ('CHILD', 'CHILDREN', 'MAN', 'MEN'), ('CHILD', 'CHILDREN', 'ROAD', 'ROADS'), ('CHILD', 'CHILDREN', 'WOMAN', 'WOMEN'), ('CHILD', 'CHILDREN', 'BUILDING', 'BUILDINGS'), ('CHILD', 'CHILDREN', 'CAR', 'CARS'), ('MAN', 'MEN', 'ROAD', 'ROADS'), ('MAN', 'MEN', 'WOMAN', 'WOMEN'), ('MAN', 'MEN', 'BUILDING', 'BUILDINGS'), ('MAN', 'MEN', 'CAR', 'CARS'), ('MAN', 'MEN', 'CHILD', 'CHILDREN'), ('ROAD', 'ROADS', 'WOMAN', 'WOMEN'), ('ROAD', 'ROADS', 'BUILDING', 'BUILDINGS'), ('ROAD', 'ROADS', 'CAR', 'CARS'), ('ROAD', 'ROADS', 'CHILD', 'CHILDREN'), ('ROAD', 'ROADS', 'MAN', 'MEN'), ('WOMAN', 'WOMEN', 'BUILDING', 'BUILDINGS'), ('WOMAN', 'WOMEN', 'CAR', 'CARS'), ('WOMAN', 'WOMEN', 'CHILD', 'CHILDREN'), ('WOMAN', 'WOMEN', 'MAN', 'MEN'), ('WOMAN', 'WOMEN', 'ROAD', 'ROADS')]}])

evaluate_word_analogies方法采用一个可选参数 restrict_vocab,该参数限制了要考虑的测试示例。

在 2016 年 12 月发布的 Gensim 中,我们添加了一种更好的方法来评估语义相似性。

默认情况下,它使用学术数据集 WS-353,但可以基于它创建特定于您的业务的数据集。它包含单词对以及人工分配的相似性判断。它衡量两个词的相关性或共现。例如,“coast”和“shore”非常相似,因为它们出现在相同的上下文中。同时,“衣服”和“壁橱”不太相似,因为它们相关但不可互换。

model.wv.evaluate_word_pairs(datapath('wordsim353.tsv'))
((0.1014236962315867, 0.44065378924434523), SpearmanrResult(correlation=0.07441989763914543, pvalue=0.571997364

在 Google 或 WS-353 测试集上的良好性能并不意味着 word2vec 将在您的应用程序中运行良好,反之亦然。最好直接评估您的预期任务。有关如何在分类器管道中使用 word2vec 的示例,请参阅本教程

Online training / Resuming training

高级用户可以加载模型并使用更多句子和新词汇继续训练它:

model = gensim.models.Word2Vec.load(temporary_filepath)
more_sentences = [
    ['Advanced', 'users', 'can', 'load', 'a', 'model',
     'and', 'continue', 'training', 'it', 'with', 'more', 'sentences'],
]
model.build_vocab(more_sentences, update=True)
model.train(more_sentences, total_examples=model.corpus_count, epochs=model.epochs)

# cleaning up temporary file
import os
os.remove(temporary_filepath)

您可能需要将total_words参数调整为train(),具体取决于您要模拟的学习率衰减。

请注意,无法使用 C 工具生成的模型恢复训练,KeyedVectors.load_word2vec_format(). 您仍然可以将它们用于查询/相似性,但缺少对训练(词汇树)至关重要的信息。

Training Loss Computation

该参数compute_loss可用于在训练 Word2Vec 模型时切换损失计算。计算出的损失存储在模型属性中running_training_loss,可以使用get_latest_training_loss如下函数检索 :

# instantiating and training the Word2Vec model
model_with_loss = gensim.models.Word2Vec(
    sentences,
    min_count=1,
    compute_loss=True,
    hs=0,
    sg=1,
    seed=42,
)

# getting the training loss value
training_loss = model_with_loss.get_latest_training_loss()
print(training_loss)
1369454.25

Benchmarks

让我们运行一些基准测试,看看训练损失计算代码对训练时间的影响。

我们将使用以下数据作为基准:

  1. Lee背景语料库:包含在gensim的测试数据中
  2. Text8 语料库。为了演示语料库大小的影响,我们将查看语料库的前 1MB、10MB、50MB 以及整个内容。
import io
import os

import gensim.models.word2vec
import gensim.downloader as api
import smart_open


def head(path, size):
    with smart_open.open(path) as fin:
        return io.StringIO(fin.read(size))


def generate_input_data():
    lee_path = datapath('lee_background.cor')
    ls = gensim.models.word2vec.LineSentence(lee_path)
    ls.name = '25kB'
    yield ls

    text8_path = api.load('text8').fn
    labels = ('1MB', '10MB', '50MB', '100MB')
    sizes = (1024 ** 2, 10 * 1024 ** 2, 50 * 1024 ** 2, 100 * 1024 ** 2)
    for l, s in zip(labels, sizes):
        ls = gensim.models.word2vec.LineSentence(head(text8_path, s))
        ls.name = l
        yield ls


input_data = list(generate_input_data())

我们现在比较输入数据和模型训练参数(如hs和 )的不同组合所花费的训练时间sg

对于每个组合,我们重复测试几次以获得测试持续时间的平均值和标准偏差。

# Temporarily reduce logging verbosity
logging.root.level = logging.ERROR

import time
import numpy as np
import pandas as pd

train_time_values = []
seed_val = 42
sg_values = [0, 1]
hs_values = [0, 1]

fast = True
if fast:
    input_data_subset = input_data[:3]
else:
    input_data_subset = input_data


for data in input_data_subset:
    for sg_val in sg_values:
        for hs_val in hs_values:
            for loss_flag in [True, False]:
                time_taken_list = []
                for i in range(3):
                    start_time = time.time()
                    w2v_model = gensim.models.Word2Vec(
                        data,
                        compute_loss=loss_flag,
                        sg=sg_val,
                        hs=hs_val,
                        seed=seed_val,
                    )
                    time_taken_list.append(time.time() - start_time)

                time_taken_list = np.array(time_taken_list)
                time_mean = np.mean(time_taken_list)
                time_std = np.std(time_taken_list)

                model_result = {
                    'train_data': data.name,
                    'compute_loss': loss_flag,
                    'sg': sg_val,
                    'hs': hs_val,
                    'train_time_mean': time_mean,
                    'train_time_std': time_std,
                }
                print("Word2vec model #%i: %s" % (len(train_time_values), model_result))
                train_time_values.append(model_result)

train_times_table = pd.DataFrame(train_time_values)
train_times_table = train_times_table.sort_values(
    by=['train_data', 'sg', 'hs', 'compute_loss'],
    ascending=[False, False, True, False],
)
print(train_times_table)

Visualising Word Embeddings

通过使用 tSNE 将单词的维数减少到 2 维,可以将模型制作的词嵌入可视化。

可视化可用于注意数据中的语义和句法趋势。

例子:

  • 语义:像 cat、dog、cow 等词有靠近的倾向
  • 句法:像 run、running 或 cut、cutting 这样的词靠得很近。

也可以注意到像 vKing - vMan = vQueen - vWoman 这样的向量关系。

用于可视化的模型是在一个小语料库上训练的。因此,有些关系可能不是那么清楚。

from sklearn.decomposition import IncrementalPCA    # inital reduction
from sklearn.manifold import TSNE                   # final reduction
import numpy as np                                  # array handling


def reduce_dimensions(model):
    num_dimensions = 2  # final num dimensions (2D, 3D, etc)

    # extract the words & their vectors, as numpy arrays
    vectors = np.asarray(model.wv.vectors)
    labels = np.asarray(model.wv.index_to_key)  # fixed-width numpy strings

    # reduce using t-SNE
    tsne = TSNE(n_components=num_dimensions, random_state=0)
    vectors = tsne.fit_transform(vectors)

    x_vals = [v[0] for v in vectors]
    y_vals = [v[1] for v in vectors]
    return x_vals, y_vals, labels


x_vals, y_vals, labels = reduce_dimensions(model)

def plot_with_plotly(x_vals, y_vals, labels, plot_in_notebook=True):
    from plotly.offline import init_notebook_mode, iplot, plot
    import plotly.graph_objs as go

    trace = go.Scatter(x=x_vals, y=y_vals, mode='text', text=labels)
    data = [trace]

    if plot_in_notebook:
        init_notebook_mode(connected=True)
        iplot(data, filename='word-embedding-plot')
    else:
        plot(data, filename='word-embedding-plot.html')


def plot_with_matplotlib(x_vals, y_vals, labels):
    import matplotlib.pyplot as plt
    import random

    random.seed(0)

    plt.figure(figsize=(12, 12))
    plt.scatter(x_vals, y_vals)

    #
    # Label randomly subsampled 25 data points
    #
    indices = list(range(len(labels)))
    selected_indices = random.sample(indices, 25)
    for i in selected_indices:
        plt.annotate(labels[i], (x_vals[i], y_vals[i]))

try:
    get_ipython()
except Exception:
    plot_function = plot_with_matplotlib
else:
    plot_function = plot_with_plotly

plot_function(x_vals, y_vals, labels)

Doc2Vec Model

介绍 Gensim 的 Doc2Vec 模型并演示其在Lee Corpus上的使用 。

Doc2Vec 是一个模型,将每个文档表示 为一个Vector。下面介绍了模型并演示了如何训练和评估它。

以下是我们将要做的事情的列表:

  1. 查看相关模型:bag-of-words、Word2Vec、Doc2Vec
  2. 加载和预处理训练和测试语料库(参见语料库
  3. 使用训练语料库训练Doc2Vec模型模型
  4. 演示如何使用经过训练的模型来推断向量
  5. 评估模型
  6. 在测试语料库上测试模型

Introducing: Paragraph Vector

In Gensim, we refer to the Paragraph Vector model as Doc2Vec.

Le and Mikolov in 2014 introduced the Doc2Vec algorithm, which usually outperforms such simple-averaging of Word2Vec vectors.

The basic idea is: act as if a document has another floating word-like vector, which contributes to all training predictions, and is updated like other word-vectors, but we will call it a doc-vector. Gensim’s Doc2Vec class implements this algorithm.

There are two implementations:

  1. Paragraph Vector - Distributed Memory (PV-DM)
  2. Paragraph Vector - Distributed Bag of Words (PV-DBOW)

Don’t let the implementation details below scare you. They’re advanced material: if it’s too much, then move on to the next section.

PV-DM is analogous to Word2Vec CBOW. The doc-vectors are obtained by training a neural network on the synthetic task of predicting a center word based an average of both context word-vectors and the full document’s doc-vector.

PV-DBOW is analogous to Word2Vec SG. The doc-vectors are obtained by training a neural network on the synthetic task of predicting a target word just from the full document’s doc-vector. (It is also common to combine this with skip-gram testing, using both the doc-vector and nearby word-vectors to predict a single target word, but only one at a time.)

Prepare the Training and Test Data

我们将使用 gensim 中包含的Lee 背景语料库来训练我们的模型。该语料库包含 314 份选自澳大利亚广播公司新闻邮件服务的文档,该服务提供标题故事的文本电子邮件,涵盖多个广泛主题。

我们将使用 包含 50 个文档的更短的Lee 语料库通过眼睛测试我们的模型。

import os
import gensim
# Set file names for train and test data
test_data_dir = os.path.join(gensim.__path__[0], 'test', 'test_data')
lee_train_file = os.path.join(test_data_dir, 'lee_background.cor')
lee_test_file = os.path.join(test_data_dir, 'lee.cor')

Define a Function to Read and Preprocess Text

下面,我们定义一个函数来:

  • 打开训练/测试文件(使用拉丁语编码)
  • 逐行读取文件
  • 预处理每一行(将文本标记为单个单词,删除标点符号,设置为小写等)

我们正在阅读的文件是corpus。文件的每一行都是一个文档

要训练模型,我们需要将标签/编号与训练语料库的每个文档相关联。在我们的例子中,标签只是从零开始的行号。

import smart_open

def read_corpus(fname, tokens_only=False):
    with smart_open.open(fname, encoding="iso-8859-1") as f:
        for i, line in enumerate(f):
            tokens = gensim.utils.simple_preprocess(line)
            if tokens_only:
                yield tokens
            else:
                # For training data, add tags
                yield gensim.models.doc2vec.TaggedDocument(tokens, [i])

train_corpus = list(read_corpus(lee_train_file))
test_corpus = list(read_corpus(lee_test_file, tokens_only=True))

我们来看看训练语料

print(train_corpus[:2])
[TaggedDocument(words=['hundreds', 'of', 'people', 'have', 'been', 'forced', 'to', 'vacate', 'their', 'homes', 'in', 'the', 'southern', 'highlands', 'of', 'new', 'south', 'wales', 'as', 'strong', 'winds', 'today', 'pushed', 'huge', 'bushfire', 'towards', 'the', 'town', 'of', 'hill', 'top', 'new', 'blaze', 'near', 'goulburn', 'south', 'west', 'of', 'sydney', 'has', 'forced', 'the', 'closure', 'of', 'the', 'hume', 'highway', 'at', 'about', 'pm', 'aedt', 'marked', 'deterioration', 'in', 'the', 'weather', 'as', 'storm', 'cell', 'moved', 'east', 'across', 'the', 'blue', 'mountains', 'forced', 'authorities', 'to', 'make', 'decision', 'to', 'evacuate', 'people', 'from', 'homes', 'in', 'outlying', 'streets', 'at', 'hill', 'top', 'in', 'the', 'new', 'south', 'wales', 'southern', 'highlands', 'an', 'estimated', 'residents', 'have', 'left', 'their', 'homes', 'for', 'nearby', 'mittagong', 'the', 'new', 'south', 'wales', 'rural', 'fire', 'service', 'says', 'the', 'weather', 'conditions', 'which', 'caused', 'the', 'fire', 'to', 'burn', 'in', 'finger', 'formation', 'have', 'now', 'eased', 'and', 'about', 'fire', 'units', 'in', 'and', 'around', 'hill', 'top', 'are', 'optimistic', 'of', 'defending', 'all', 'properties', 'as', 'more', 'than', 'blazes', 'burn', 'on', 'new', 'year', 'eve', 'in', 'new', 'south', 'wales', 'fire', 'crews', 'have', 'been', 'called', 'to', 'new', 'fire', 'at', 'gunning', 'south', 'of', 'goulburn', 'while', 'few', 'details', 'are', 'available', 'at', 'this', 'stage', 'fire', 'authorities', 'says', 'it', 'has', 'closed', 'the', 'hume', 'highway', 'in', 'both', 'directions', 'meanwhile', 'new', 'fire', 'in', 'sydney', 'west', 'is', 'no', 'longer', 'threatening', 'properties', 'in', 'the', 'cranebrook', 'area', 'rain', 'has', 'fallen', 'in', 'some', 'parts', 'of', 'the', 'illawarra', 'sydney', 'the', 'hunter', 'valley', 'and', 'the', 'north', 'coast', 'but', 'the', 'bureau', 'of', 'meteorology', 'claire', 'richards', 'says', 'the', 'rain', 'has', 'done', 'little', 'to', 'ease', 'any', 'of', 'the', 'hundred', 'fires', 'still', 'burning', 'across', 'the', 'state', 'the', 'falls', 'have', 'been', 'quite', 'isolated', 'in', 'those', 'areas', 'and', 'generally', 'the', 'falls', 'have', 'been', 'less', 'than', 'about', 'five', 'millimetres', 'she', 'said', 'in', 'some', 'places', 'really', 'not', 'significant', 'at', 'all', 'less', 'than', 'millimetre', 'so', 'there', 'hasn', 'been', 'much', 'relief', 'as', 'far', 'as', 'rain', 'is', 'concerned', 'in', 'fact', 'they', 've', 'probably', 'hampered', 'the', 'efforts', 'of', 'the', 'firefighters', 'more', 'because', 'of', 'the', 'wind', 'gusts', 'that', 'are', 'associated', 'with', 'those', 'thunderstorms'], tags=[0]), TaggedDocument(words=['indian', 'security', 'forces', 'have', 'shot', 'dead', 'eight', 'suspected', 'militants', 'in', 'night', 'long', 'encounter', 'in', 'southern', 'kashmir', 'the', 'shootout', 'took', 'place', 'at', 'dora', 'village', 'some', 'kilometers', 'south', 'of', 'the', 'kashmiri', 'summer', 'capital', 'srinagar', 'the', 'deaths', 'came', 'as', 'pakistani', 'police', 'arrested', 'more', 'than', 'two', 'dozen', 'militants', 'from', 'extremist', 'groups', 'accused', 'of', 'staging', 'an', 'attack', 'on', 'india', 'parliament', 'india', 'has', 'accused', 'pakistan', 'based', 'lashkar', 'taiba', 'and', 'jaish', 'mohammad', 'of', 'carrying', 'out', 'the', 'attack', 'on', 'december', 'at', 'the', 'behest', 'of', 'pakistani', 'military', 'intelligence', 'military', 'tensions', 'have', 'soared', 'since', 'the', 'raid', 'with', 'both', 'sides', 'massing', 'troops', 'along', 'their', 'border', 'and', 'trading', 'tit', 'for', 'tat', 'diplomatic', 'sanctions', 'yesterday', 'pakistan', 'announced', 'it', 'had', 'arrested', 'lashkar', 'taiba', 'chief', 'hafiz', 'mohammed', 'saeed', 'police', 'in', 'karachi', 'say', 'it', 'is', 'likely', 'more', 'raids', 'will', 'be', 'launched', 'against', 'the', 'two', 'groups', 'as', 'well', 'as', 'other', 'militant', 'organisations', 'accused', 'of', 'targetting', 'india', 'military', 'tensions', 'between', 'india', 'and', 'pakistan', 'have', 'escalated', 'to', 'level', 'not', 'seen', 'since', 'their', 'war'], tags=[1])]

测试语料库如下所示:

print(test_corpus[:2])
[['the', 'national', 'executive', 'of', 'the', 'strife', 'torn', 'democrats', 'last', 'night', 'appointed', 'little', 'known', 'west', 'australian', 'senator', 'brian', 'greig', 'as', 'interim', 'leader', 'shock', 'move', 'likely', 'to', 'provoke', 'further', 'conflict', 'between', 'the', 'party', 'senators', 'and', 'its', 'organisation', 'in', 'move', 'to', 'reassert', 'control', 'over', 'the', 'party', 'seven', 'senators', 'the', 'national', 'executive', 'last', 'night', 'rejected', 'aden', 'ridgeway', 'bid', 'to', 'become', 'interim', 'leader', 'in', 'favour', 'of', 'senator', 'greig', 'supporter', 'of', 'deposed', 'leader', 'natasha', 'stott', 'despoja', 'and', 'an', 'outspoken', 'gay', 'rights', 'activist'], ['cash', 'strapped', 'financial', 'services', 'group', 'amp', 'has', 'shelved', 'million', 'plan', 'to', 'buy', 'shares', 'back', 'from', 'investors', 'and', 'will', 'raise', 'million', 'in', 'fresh', 'capital', 'after', 'profits', 'crashed', 'in', 'the', 'six', 'months', 'to', 'june', 'chief', 'executive', 'paul', 'batchelor', 'said', 'the', 'result', 'was', 'solid', 'in', 'what', 'he', 'described', 'as', 'the', 'worst', 'conditions', 'for', 'stock', 'markets', 'in', 'years', 'amp', 'half', 'year', 'profit', 'sank', 'per', 'cent', 'to', 'million', 'or', 'share', 'as', 'australia', 'largest', 'investor', 'and', 'fund', 'manager', 'failed', 'to', 'hit', 'projected', 'per', 'cent', 'earnings', 'growth', 'targets', 'and', 'was', 'battered', 'by', 'falling', 'returns', 'on', 'share', 'markets']]

请注意,测试语料库只是一个列表列表,不包含任何标签。

Training the Model

现在,我们将实例化一个 Doc2Vec 模型,其向量大小为 50 维,并在训练语料库上迭代 40 次。我们将最小字数设置为 2,以便丢弃出现次数很少的单词。(没有各种代表性的例子,保留这种不常用的词通常会使模型变得更糟!)已发表的段落向量论文 结果中的典型迭代次数,使用数万到数百万的文档,为 10-20。更多的迭代需要更多的时间并最终达到收益递减的点。

然而,这是一个非常非常小的数据集(300 个文档),文档很短(几百个单词)。添加训练通道有时可以帮助处理如此小的数据集。

model = gensim.models.doc2vec.Doc2Vec(vector_size=50, min_count=2, epochs=40)

建立词汇

model.build_vocab(train_corpus)
2020-09-30 21:08:55,026 : INFO : collecting all words and their counts
2020-09-30 21:08:55,027 : INFO : PROGRESS: at example #0, processed 0 words (0/s), 0 word types, 0 tags
2020-09-30 21:08:55,043 : INFO : collected 6981 word types and 300 unique tags from a corpus of 300 examples and 58152 words
2020-09-30 21:08:55,043 : INFO : Loading a fresh vocabulary
2020-09-30 21:08:55,064 : INFO : effective_min_count=2 retains 3955 unique words (56% of original 6981, drops 3026)
2020-09-30 21:08:55,064 : INFO : effective_min_count=2 leaves 55126 word corpus (94% of original 58152, drops 3026)
2020-09-30 21:08:55,098 : INFO : deleting the raw counts dictionary of 6981 items
2020-09-30 21:08:55,100 : INFO : sample=0.001 downsamples 46 most-common words
2020-09-30 21:08:55,100 : INFO : downsampling leaves estimated 42390 word corpus (76.9% of prior 55126)
2020-09-30 21:08:55,149 : INFO : estimated required memory for 3955 words and 50 dimensions: 3679500 bytes
2020-09-30 21:08:55,149 : INFO : resetting layer weights

本质上,词汇表是model.wv.index_to_key从训练语料库中提取的所有独特单词的列表(可通过 访问 )。使用该model.wv.get_vecattr()方法可以获得每个单词的附加属性,例如,查看penalty在训练语料库中出现的次数:

print(f"Word 'penalty' appeared {model.wv.get_vecattr('penalty', 'count')} times in the training corpus.")
Word 'penalty' appeared 4 times in the training corpus.

接下来,在语料库上训练模型。如果正在使用优化的 Gensim(带有 BLAS 库),这应该不会超过 3 秒。如果不使用 BLAS 库,这应该不会超过 2 分钟,因此如果您珍惜时间,请使用优化的 Gensim 和 BLAS。

model.train(train_corpus, total_examples=model.corpus_count, epochs=model.epochs)

现在,我们可以使用经过训练的模型通过将单词列表传递给model.infer_vector函数来推断任何文本片段的向量。然后可以通过余弦相似度将该向量与其他向量进行比较。

vector = model.infer_vector(['only', 'you', 'can', 'prevent', 'forest', 'fires'])
print(vector)

出去:

[-0.08478509  0.05011684  0.0675064  -0.19926868 -0.1235586   0.01768214
 -0.12645927  0.01062329  0.06113973  0.35424358  0.01320948  0.07561274
 -0.01645093  0.0692549   0.08346193 -0.01599065  0.08287009 -0.0139379
 -0.17772709 -0.26271465  0.0442089  -0.04659882 -0.12873884  0.28799203
 -0.13040264  0.12478471 -0.14091878 -0.09698066 -0.07903259 -0.10124907
 -0.28239366  0.13270256  0.04445919 -0.24210942 -0.1907376  -0.07264525
 -0.14167067 -0.22816683 -0.00663796  0.23165748 -0.10436232 -0.01028251
 -0.04064698  0.08813146  0.01072008 -0.149789    0.05923386  0.16301566
  0.05815683  0.1258063 ]

请注意,infer_vector()接受一个字符串,而是一个字符串标记列表,它应该已经以与words原始训练文档对象的属性相同的方式进行标记 。

另请注意,由于底层训练/推理算法是使用内部随机化的迭代逼近问题,因此同一文本的重复推理将返回略有不同的向量。

Assessing the Model

为了评估我们的新模型,我们将首先为训练语料库的每个文档推断新向量,将推断的向量与训练语料库进行比较,然后根据自相似性返回文档的排名。基本上,我们假装训练语料库是一些新的未见数据,然后查看它们与训练模型的比较。预期我们可能会过度拟合我们的模型(即,所有等级都将小于 2),因此我们应该能够非常轻松地找到类似的文档。此外,我们将跟踪第二个等级以比较不太相似的文档。

ranks = []
second_ranks = []
for doc_id in range(len(train_corpus)):
    inferred_vector = model.infer_vector(train_corpus[doc_id].words)
    sims = model.dv.most_similar([inferred_vector], topn=len(model.dv))
    rank = [docid for docid, sim in sims].index(doc_id)
    ranks.append(rank)

    second_ranks.append(sims[1])

让我们计算每个文档相对于训练语料库的排名

注意。由于随机播种和非常小的语料库,结果因运行而异

import collections

counter = collections.Counter(ranks)
print(counter)
Counter({0: 292, 1: 8})

基本上,超过 95% 的推断文档被发现与自身最相似,大约 5% 的时间被错误地认为与另一个文档最相似。根据训练向量检查推断向量是一种关于模型是否以有用一致的方式运行的“健全性检查”,尽管不是真正的“准确度”值。

这很好,并不完全令人惊讶。我们可以看一个例子:

print('Document ({}): «{}»\n'.format(doc_id, ' '.join(train_corpus[doc_id].words)))
print(u'SIMILAR/DISSIMILAR DOCS PER MODEL %s:\n' % model)
for label, index in [('MOST', 0), ('SECOND-MOST', 1), ('MEDIAN', len(sims)//2), ('LEAST', len(sims) - 1)]:
    print(u'%s %s: «%s»\n' % (label, sims[index], ' '.join(train_corpus[sims[index][0]].words)))
Document (299): «australia will take on france in the doubles rubber of the davis cup tennis final today with the tie levelled at wayne arthurs and todd woodbridge are scheduled to lead australia in the doubles against cedric pioline and fabrice santoro however changes can be made to the line up up to an hour before the match and australian team captain john fitzgerald suggested he might do just that we ll make team appraisal of the whole situation go over the pros and cons and make decision french team captain guy forget says he will not make changes but does not know what to expect from australia todd is the best doubles player in the world right now so expect him to play he said would probably use wayne arthurs but don know what to expect really pat rafter salvaged australia davis cup campaign yesterday with win in the second singles match rafter overcame an arm injury to defeat french number one sebastien grosjean in three sets the australian says he is happy with his form it not very pretty tennis there isn too many consistent bounces you are playing like said bit of classic old grass court rafter said rafter levelled the score after lleyton hewitt shock five set loss to nicholas escude in the first singles rubber but rafter says he felt no added pressure after hewitt defeat knew had good team to back me up even if we were down he said knew could win on the last day know the boys can win doubles so even if we were down still feel we are good enough team to win and vice versa they are good enough team to beat us as well»

SIMILAR/DISSIMILAR DOCS PER MODEL Doc2Vec(dm/m,d50,n5,w5,mc2,s0.001,t3):

MOST (299, 0.9482713341712952): «australia will take on france in the doubles rubber of the davis cup tennis final today with the tie levelled at wayne arthurs and todd woodbridge are scheduled to lead australia in the doubles against cedric pioline and fabrice santoro however changes can be made to the line up up to an hour before the match and australian team captain john fitzgerald suggested he might do just that we ll make team appraisal of the whole situation go over the pros and cons and make decision french team captain guy forget says he will not make changes but does not know what to expect from australia todd is the best doubles player in the world right now so expect him to play he said would probably use wayne arthurs but don know what to expect really pat rafter salvaged australia davis cup campaign yesterday with win in the second singles match rafter overcame an arm injury to defeat french number one sebastien grosjean in three sets the australian says he is happy with his form it not very pretty tennis there isn too many consistent bounces you are playing like said bit of classic old grass court rafter said rafter levelled the score after lleyton hewitt shock five set loss to nicholas escude in the first singles rubber but rafter says he felt no added pressure after hewitt defeat knew had good team to back me up even if we were down he said knew could win on the last day know the boys can win doubles so even if we were down still feel we are good enough team to win and vice versa they are good enough team to beat us as well»

SECOND-MOST (104, 0.8029672503471375): «australian cricket captain steve waugh has supported fast bowler brett lee after criticism of his intimidatory bowling to the south african tailenders in the first test in adelaide earlier this month lee was fined for giving new zealand tailender shane bond an unsportsmanlike send off during the third test in perth waugh says tailenders should not be protected from short pitched bowling these days you re earning big money you ve got responsibility to learn how to bat he said mean there no times like years ago when it was not professional and sort of bowlers code these days you re professional our batsmen work very hard at their batting and expect other tailenders to do likewise meanwhile waugh says his side will need to guard against complacency after convincingly winning the first test by runs waugh says despite the dominance of his side in the first test south africa can never be taken lightly it only one test match out of three or six whichever way you want to look at it so there lot of work to go he said but it nice to win the first battle definitely it gives us lot of confidence going into melbourne you know the big crowd there we love playing in front of the boxing day crowd so that will be to our advantage as well south africa begins four day match against new south wales in sydney on thursday in the lead up to the boxing day test veteran fast bowler allan donald will play in the warm up match and is likely to take his place in the team for the second test south african captain shaun pollock expects much better performance from his side in the melbourne test we still believe that we didn play to our full potential so if we can improve on our aspects the output we put out on the field will be lot better and we still believe we have side that is good enough to beat australia on our day he said»

MEDIAN (238, 0.2635717988014221): «centrelink is urging people affected by job cuts at regional pay tv operator austar and travel company traveland to seek information about their income support options traveland has announced it is shedding more than jobs around australia and austar is letting employees go centrelink finance information officer peter murray says those facing uncertain futures should head to centrelink in the next few days centrelink is the shopfront now for commonwealth services for income support and the employment network so that it is important if people haven been to us before they might get pleasant surprise at the range of services that we do offer to try and help them through situations where things might have changed for them mr murray said»

LEAST (243, -0.13247375190258026): «four afghan factions have reached agreement on an interim cabinet during talks in germany the united nations says the administration which will take over from december will be headed by the royalist anti taliban commander hamed karzai it concludes more than week of negotiations outside bonn and is aimed at restoring peace and stability to the war ravaged country the year old former deputy foreign minister who is currently battling the taliban around the southern city of kandahar is an ally of the exiled afghan king mohammed zahir shah he will serve as chairman of an interim authority that will govern afghanistan for six month period before loya jirga or grand traditional assembly of elders in turn appoints an month transitional government meanwhile united states marines are now reported to have been deployed in eastern afghanistan where opposition forces are closing in on al qaeda soldiers reports from the area say there has been gun battle between the opposition and al qaeda close to the tora bora cave complex where osama bin laden is thought to be hiding in the south of the country american marines are taking part in patrols around the air base they have secured near kandahar but are unlikely to take part in any assault on the city however the chairman of the joint chiefs of staff general richard myers says they are prepared for anything they are prepared for engagements they re robust fighting force and they re absolutely ready to engage if that required he said»

请注意,最相似的文档(通常是相同的文本)的相似度得分接近 1.0。然而,排名第二的文档的相似度得分应该明显较低(假设文档实际上不同),并且当我们检查文本本身时,推理变得明显。

我们可以重复运行下一个单元格以查看其他目标文档比较的采样。

# Pick a random document from the corpus and infer a vector from the model
import random
doc_id = random.randint(0, len(train_corpus) - 1)

# Compare and print the second-most-similar document
print('Train Document ({}): «{}»\n'.format(doc_id, ' '.join(train_corpus[doc_id].words)))
sim_id = second_ranks[doc_id]
print('Similar Document {}: «{}»\n'.format(sim_id, ' '.join(train_corpus[sim_id[0]].words)))
Train Document (292): «rival afghan factions are deadlocked over the shape of future government the northern alliance has demanded day adjournment of power sharing talks in germany after its president burhanuddin rabbani objected to the appointment system for an interim administration president rabbani has objected to the plans for an interim government to be drawn up by appointment as discussed in bonn saying the interim leaders should be voted in by afghans themselves he also says there is no real need for sizeable international security force president rabbani says he would prefer local afghan factions drew up their own internal security forces of around personnel but if the world insisted there should be an international security presence there should be no more than or personnel in their security forces he says president rabbani objections are likely to cast doubt on his delegation ability to commit the northern alliance to any course of action decided upon in bonn he now threatens to undermine the very process he claims to support in the quest for stable government in afghanistan»

Similar Document (13, 0.7867921590805054): «talks between afghan and british officials in kabul have ended without final ag

Testing the Model

使用上述相同的方法,我们将推断随机选择的测试文档的向量,并通过眼睛将文档与我们的模型进行比较。

# Pick a random document from the test corpus and infer a vector from the model
doc_id = random.randint(0, len(test_corpus) - 1)
inferred_vector = model.infer_vector(test_corpus[doc_id])
sims = model.dv.most_similar([inferred_vector], topn=len(model.dv))

# Compare and print the most/median/least similar documents from the train corpus
print('Test Document ({}): «{}»\n'.format(doc_id, ' '.join(test_corpus[doc_id])))
print(u'SIMILAR/DISSIMILAR DOCS PER MODEL %s:\n' % model)
for label, index in [('MOST', 0), ('MEDIAN', len(sims)//2), ('LEAST', len(sims) - 1)]:
    print(u'%s %s: «%s»\n' % (label, sims[index], ' '.join(train_corpus[sims[index][0]].words)))
Test Document (49): «labor needed to distinguish itself from the government on the issue of asylum seekers greens leader bob brown has said his senate colleague kerry nettle intends to move motion today on the first anniversary of the tampa crisis condemning the government over its refugee policy and calling for an end to mandatory detention we greens want to bring the government to book over its serial breach of international obligations as far as asylum seekers in this country are concerned senator brown said today»

SIMILAR/DISSIMILAR DOCS PER MODEL Doc2Vec(dm/m,d50,n5,w5,mc2,s0.001,t3):

MOST (218, 0.8016394376754761): «refugee support groups are strongly critical of federal government claims that the pacific solution program is working well the immigration minister philip ruddock says he is pleased with the program which uses pacific island nations to process asylum seekers wanting to come to australia president of the hazara ethnic society of australia hassan ghulam says the australian government is bullying smaller nations into accepting asylum seekers if the pacific countries wanted refugees they can clearly raise their voice in the united nations and say yes we are accepting refugees and why australia who gives this authority to the australian government to force the pacific countries to accept refugees in this form or in the other form he asked»

MEDIAN (204, 0.3319269120693207): «an iraqi doctor being held at sydney villawood detention centre claims he was prevented from receiving human rights award dr aamer sultan had been awarded special commendation at yesterday human rights and equal opportunity commission awards in sydney but was not able to receive the honour in person dr sultan says he had been hoping to attend the ceremony but says the management at villawood stopped him from going submitted formal request to the centre manager who promised me that he will present the matter to migration management here who are the main authority here they also came back that unfortunately we can not fulfill this request for you but they didn give any explanation dr sultan says he was disappointed by the decision the immigration minister philip ruddock has written letter of complaint to the medical journal of australia about an article penned by dr sultan on the psychological state of detainees at villawood the journal has published research dr sultan conducted with former visiting psychologist to the centre kevin sullivan their survey of detainees over nine months found all but one displayed symptoms of psychological distress at some time the article says per cent acknowledged chronic depressive symptoms and close to half of the group had reached severe stages of depression»

LEAST (157, -0.10524928569793701): «british man has been found guilty by unanimous verdict of the kidnap and murder of an e

Additional Resources

FastText Model

介绍 Gensim 的 fastText 模型并演示其在 Lee 语料库上的使用。

在这里,我们将学习使用 fastText 库来训练词嵌入模型、保存和加载它们并执行类似于 Word2Vec 的相似性操作和向量查找。

什么时候使用 fastText?

fastText背后的主要原则是单词的形态结构携带有关单词含义的重要信息。像 Word2Vec 这样的传统词嵌入没有考虑这种结构,它为每个单独的词训练一个独特的词嵌入。这对于形态丰富的语言(德语、土耳其语)尤其重要,在这些语言中,单个单词可以有大量的形态形式,每种形式都可能很少出现,因此很难训练好的词嵌入。

fastText 试图通过将每个单词视为其子单词的聚合来解决这个问题。为了简单和语言独立,子词被视为词的字符 ngrams。单词的向量被简单地视为其组成部分 char-ngram 的所有向量的总和。

根据笔记中 Word2Vec 和 fastText 的详细比较,fastText 在句法任务上的表现明显优于原始 Word2Vec,尤其是在训练语料库规模较小的情况下。不过,Word2Vec 在语义任务上略胜于 fastText。随着训练语料库规模的增加,差异变得越来越小。

只要训练数据中至少存在一个 char-ngram,fastText 甚至可以通过对其组成部分 char-ngram 的向量求和来获得词汇外 (OOV) 词的向量。

训练模型

对于以下示例,我们将使用 Lee 语料库(如果您安装了 Gensim,您已经拥有)来训练我们的模型。

from pprint import pprint as print
from gensim.models.fasttext import FastText
from gensim.test.utils import datapath

# Set file names for train and test data
corpus_file = datapath('lee_background.cor')

model = FastText(vector_size=100)

# build the vocabulary
model.build_vocab(corpus_file=corpus_file)

# train the model
model.train(
    corpus_file=corpus_file, epochs=model.epochs,
    total_examples=model.corpus_count, total_words=model.corpus_total_words,
)

print(model)

出去:

<gensim.models.fasttext.FastText object at 0x20ce0d390>

训练超参数

用于训练模型的超参数遵循与 Word2Vec 相同的模式。FastText 支持原始 word2vec 中的以下参数:

  • model: Training architecture. Allowed values: cbow, skipgram (Default cbow)
  • vector_size: Dimensionality of vector embeddings to be learnt (Default 100)
  • alpha: Initial learning rate (Default 0.025)
  • window: Context window size (Default 5)
  • min_count: Ignore words with number of occurrences below this (Default 5)
  • loss: Training objective. Allowed values: ns, hs, softmax (Default ns)
  • sample: Threshold for downsampling higher-frequency words (Default 0.001)
  • negative: Number of negative words to sample, for ns (Default 5)
  • epochs: Number of epochs (Default 5)
  • sorted_vocab: Sort vocab by descending frequency (Default 1)
  • threads: Number of threads to use (Default 12)

In addition, fastText has three additional parameters:

  • min_n: min length of char ngrams (Default 3)
  • max_n: max length of char ngrams (Default 6)
  • bucket: number of buckets used for hashing ngrams (Default 2000000)

参数min_nmax_n控制在训练和查找嵌入时每个单词分解成的字符 ngram 的长度。如果max_n设置为 0 或小于min_n,则不使用字符 ngram,模型有效地简化为 Word2Vec。

为了限制正在训练的模型的内存要求,使用了将 ngram 映射到 1 到 K 中的整数的散列函数。为了散列这些字符序列,使用了Fowler-Noll-Vo 散列函数(FNV-1a 变体)。

注意:您可以在使用 Gensim 的 fastText 本地实现的同时继续训练您的模型。

保存/加载模型

模型可以通过loadsave方法保存和加载,就像 Gensim 中的任何其他模型一样。

# Save a model trained via Gensim's fastText implementation to temp.
import tempfile
import os
with tempfile.NamedTemporaryFile(prefix='saved_model_gensim-', delete=False) as tmp:
    model.save(tmp.name, separately=[])

# Load back the same model.
loaded_model = FastText.load(tmp.name)
print(loaded_model)

os.unlink(tmp.name)  # demonstration complete, don't need the temp file anymore

出去:

<gensim.models.fasttext.FastText object at 0x20cc99d30>

save_word2vec_format也可用于fastText车型,但将导致所有向量丢失的n-gram。因此,以这种方式加载的模型将表现为常规 word2vec 模型。

词向量查找

查找 fastText 词(包括 OOV 词)所需的所有信息都包含在其model.wv属性中。

如果您不需要继续训练您的模型,您可以导出并保存此.wv 属性并丢弃模型,以节省空间和 RAM。

wv = model.wv
print(wv)

#
# FastText models support vector lookups for out-of-vocabulary words by summing up character ngrams belonging to the word.
#
print('night' in wv.key_to_index)
<gensim.models.fasttext.FastTextKeyedVectors object at 0x20ce0d828>
True

print('nights' in wv.key_to_index)
False

print(wv['night'])
array([ 0.12453239, -0.26018462, -0.04087191,  0.2563215 ,  0.31401935,
        0.16155584,  0.39527607,  0.27404118, -0.45236284,  0.06942682,
        0.36584955,  0.51162827, -0.51161295, -0.192019  , -0.5068029 ,
       -0.07426998, -0.6276584 ,  0.22271585,  0.19990133,  0.2582401 ,
        0.14329399, -0.01959469, -0.45576197, -0.06447829,  0.1493489 ,
        0.17261286, -0.13472046,  0.26546794, -0.34596932,  0.5626187 ,
       -0.7038802 ,  0.15603925, -0.03104019, -0.06228801, -0.13480644,
       -0.0684596 ,  0.24728075,  0.55081636,  0.07330963,  0.32814154,
        0.1574982 ,  0.56742406, -0.31233737,  0.14195296,  0.0540203 ,
        0.01718009,  0.05519052, -0.04002226,  0.16157456, -0.5134223 ,
       -0.01033936,  0.05745083, -0.39208183,  0.52553374, -1.0542839 ,
        0.2145304 , -0.15234643, -0.35197273, -0.6215585 ,  0.01796502,
        0.21242104,  0.30762967,  0.2787644 , -0.19908747,  0.7144409 ,
        0.45586124, -0.21344525,  0.26920903, -0.651759  , -0.37096855,
       -0.16243419, -0.3085725 , -0.70485127, -0.04926324, -0.80278563,
       -0.24352737,  0.6427129 , -0.3530421 , -0.29960123,  0.01466726,
       -0.18253349, -0.2489397 ,  0.00648343,  0.18057272, -0.11812428,
       -0.49044088,  0.1847386 , -0.27946883,  0.3941279 , -0.39211616,
        0.26847798,  0.41468227, -0.3953728 , -0.25371104,  0.3390468 ,
       -0.16447693, -0.18722224,  0.2782088 , -0.0696249 ,  0.4313547 ],
      dtype=float32)

print(wv['nights'])
array([ 0.10586783, -0.22489995, -0.03636307,  0.22263278,  0.27037606,
        0.1394871 ,  0.3411114 ,  0.2369042 , -0.38989475,  0.05935   ,
        0.31713557,  0.44301754, -0.44249156, -0.16652377, -0.4388366 ,
       -0.06266895, -0.5436303 ,  0.19294666,  0.17363031,  0.22459263,
        0.12532061, -0.01866964, -0.3936521 , -0.05507145,  0.12905194,
        0.14942174, -0.11657442,  0.22935589, -0.29934618,  0.4859668 ,
       -0.6073519 ,  0.13433163, -0.02491274, -0.05468523, -0.11884545,
       -0.06117092,  0.21444008,  0.4775469 ,  0.06227469,  0.28350767,
        0.13580805,  0.48993143, -0.27067345,  0.1252003 ,  0.04606731,
        0.01598426,  0.04640368, -0.03456376,  0.14138013, -0.44429192,
       -0.00865329,  0.05027836, -0.341311  ,  0.45402458, -0.91097856,
        0.1868968 , -0.13116683, -0.30361563, -0.5364188 ,  0.01603454,
        0.18146741,  0.26708448,  0.24074472, -0.17163375,  0.61906886,
        0.39530373, -0.18259627,  0.23319626, -0.5634787 , -0.31959867,
       -0.13945322, -0.269441  , -0.60941464, -0.0403638 , -0.69563633,
       -0.2098089 ,  0.5569868 , -0.30320194, -0.25840232,  0.01436759,
       -0.15632603, -0.21624804,  0.00434287,  0.15566474, -0.10228094,
       -0.4249678 ,  0.16197811, -0.24147548,  0.34205705, -0.3391568 ,
        0.23235887,  0.35860622, -0.34247142, -0.21777524,  0.29318404,
       -0.1407287 , -0.16115218,  0.24247572, -0.06217333,  0.37221798],
      dtype=float32)

相似操作

相似性操作的工作方式与 word2vec 相同。也可以使用词汇表外的单词,前提是它们在训练数据中至少有一个字符 ngram。

print("nights" in wv.key_to_index)
False

print("night" in wv.key_to_index)
True

print(wv.similarity("night", "nights"))
0.9999929

句法相似的词在 fastText 模型中通常具有很高的相似性,因为大量的组成字符-ngram 将是相同的。因此,fastText 在语法任务上通常比 Word2Vec 做得更好。此处提供了详细的比较。

其他相似操作

示例训练语料库是一个玩具语料库,预计结果不会很好,仅用于概念验证

print(wv.most_similar("nights"))
[('night', 0.9999929070472717),
 ('night.', 0.9999895095825195),
 ('flights', 0.999988853931427),
 ('rights', 0.9999886751174927),
 ('residents', 0.9999884366989136),
 ('overnight', 0.9999883770942688),
 ('commanders', 0.999988317489624),
 ('reached', 0.9999881386756897),
 ('commander', 0.9999880790710449),
 ('leading', 0.999987781047821)]

print(wv.n_similarity(['sushi', 'shop'], ['japanese', 'restaurant']))
0.9999402

print(wv.doesnt_match("breakfast cereal dinner lunch".split()))
'lunch'

print(wv.most_similar(positive=['baghdad', 'england'], negative=['london']))
[('attempt', 0.999660074710846),
 ('biggest', 0.9996545314788818),
 ('again', 0.9996527433395386),
 ('against', 0.9996523857116699),
 ('doubles', 0.9996522068977356),
 ('Royal', 0.9996512532234192),
 ('Airlines', 0.9996494054794312),
 ('forced', 0.9996494054794312),
 ('arrest', 0.9996492266654968),
 ('follows', 0.999649167060852)]

print(wv.evaluate_word_analogies(datapath('questions-words.txt')))
(0.24489795918367346,
 [{'correct': [], 'incorrect': [], 'section': 'capital-common-countries'},
  {'correct': [], 'incorrect': [], 'section': 'capital-world'},
  {'correct': [], 'incorrect': [], 'section': 'currency'},
  {'correct': [], 'incorrect': [], 'section': 'city-in-state'},
  {'correct': [],
   'incorrect': [('HE', 'SHE', 'HIS', 'HER'), ('HIS', 'HER', 'HE', 'SHE')],
   'section': 'family'},
  {'correct': [], 'incorrect': [], 'section': 'gram1-adjective-to-adverb'},
  {'correct': [], 'incorrect': [], 'section': 'gram2-opposite'},
  {'correct': [('GOOD', 'BETTER', 'LOW', 'LOWER'),
               ('GREAT', 'GREATER', 'LOW', 'LOWER'),
               ('LONG', 'LONGER', 'LOW', 'LOWER')],
   'incorrect': [('GOOD', 'BETTER', 'GREAT', 'GREATER'),
                 ('GOOD', 'BETTER', 'LONG', 'LONGER'),
                 ('GREAT', 'GREATER', 'LONG', 'LONGER'),
                 ('GREAT', 'GREATER', 'GOOD', 'BETTER'),
                 ('LONG', 'LONGER', 'GOOD', 'BETTER'),
                 ('LONG', 'LONGER', 'GREAT', 'GREATER'),
                 ('LOW', 'LOWER', 'GOOD', 'BETTER'),
                 ('LOW', 'LOWER', 'GREAT', 'GREATER'),
                 ('LOW', 'LOWER', 'LONG', 'LONGER')],
   'section': 'gram3-comparative'},
  {'correct': [('BIG', 'BIGGEST', 'LARGE', 'LARGEST'),
               ('GOOD', 'BEST', 'LARGE', 'LARGEST'),
               ('GREAT', 'GREATEST', 'LARGE', 'LARGEST')],
   'incorrect': [('BIG', 'BIGGEST', 'GOOD', 'BEST'),
                 ('BIG', 'BIGGEST', 'GREAT', 'GREATEST'),
                 ('GOOD', 'BEST', 'GREAT', 'GREATEST'),
                 ('GOOD', 'BEST', 'BIG', 'BIGGEST'),
                 ('GREAT', 'GREATEST', 'BIG', 'BIGGEST'),
                 ('GREAT', 'GREATEST', 'GOOD', 'BEST'),
                 ('LARGE', 'LARGEST', 'BIG', 'BIGGEST'),
                 ('LARGE', 'LARGEST', 'GOOD', 'BEST'),
                 ('LARGE', 'LARGEST', 'GREAT', 'GREATEST')],
   'section': 'gram4-superlative'},
  {'correct': [('GO', 'GOING', 'SAY', 'SAYING'),
               ('LOOK', 'LOOKING', 'PLAY', 'PLAYING'),
               ('LOOK', 'LOOKING', 'SAY', 'SAYING'),
               ('LOOK', 'LOOKING', 'GO', 'GOING'),
               ('PLAY', 'PLAYING', 'SAY', 'SAYING'),
               ('PLAY', 'PLAYING', 'GO', 'GOING'),
               ('SAY', 'SAYING', 'GO', 'GOING')],
   'incorrect': [('GO', 'GOING', 'LOOK', 'LOOKING'),
                 ('GO', 'GOING', 'PLAY', 'PLAYING'),
                 ('GO', 'GOING', 'RUN', 'RUNNING'),
                 ('LOOK', 'LOOKING', 'RUN', 'RUNNING'),
                 ('PLAY', 'PLAYING', 'RUN', 'RUNNING'),
                 ('PLAY', 'PLAYING', 'LOOK', 'LOOKING'),
                 ('RUN', 'RUNNING', 'SAY', 'SAYING'),
                 ('RUN', 'RUNNING', 'GO', 'GOING'),
                 ('RUN', 'RUNNING', 'LOOK', 'LOOKING'),
                 ('RUN', 'RUNNING', 'PLAY', 'PLAYING'),
                 ('SAY', 'SAYING', 'LOOK', 'LOOKING'),
                 ('SAY', 'SAYING', 'PLAY', 'PLAYING'),
                 ('SAY', 'SAYING', 'RUN', 'RUNNING')],
   'section': 'gram5-present-participle'},
  {'correct': [('AUSTRALIA', 'AUSTRALIAN', 'INDIA', 'INDIAN'),
               ('AUSTRALIA', 'AUSTRALIAN', 'ISRAEL', 'ISRAELI'),
               ('FRANCE', 'FRENCH', 'INDIA', 'INDIAN'),
               ('INDIA', 'INDIAN', 'ISRAEL', 'ISRAELI'),
               ('ISRAEL', 'ISRAELI', 'INDIA', 'INDIAN'),
               ('SWITZERLAND', 'SWISS', 'INDIA', 'INDIAN')],
   'incorrect': [('AUSTRALIA', 'AUSTRALIAN', 'FRANCE', 'FRENCH'),
                 ('AUSTRALIA', 'AUSTRALIAN', 'SWITZERLAND', 'SWISS'),
                 ('FRANCE', 'FRENCH', 'ISRAEL', 'ISRAELI'),
                 ('FRANCE', 'FRENCH', 'SWITZERLAND', 'SWISS'),
                 ('FRANCE', 'FRENCH', 'AUSTRALIA', 'AUSTRALIAN'),
                 ('INDIA', 'INDIAN', 'SWITZERLAND', 'SWISS'),
                 ('INDIA', 'INDIAN', 'AUSTRALIA', 'AUSTRALIAN'),
                 ('INDIA', 'INDIAN', 'FRANCE', 'FRENCH'),
                 ('ISRAEL', 'ISRAELI', 'SWITZERLAND', 'SWISS'),
                 ('ISRAEL', 'ISRAELI', 'AUSTRALIA', 'AUSTRALIAN'),
                 ('ISRAEL', 'ISRAELI', 'FRANCE', 'FRENCH'),
                 ('SWITZERLAND', 'SWISS', 'AUSTRALIA', 'AUSTRALIAN'),
                 ('SWITZERLAND', 'SWISS', 'FRANCE', 'FRENCH'),
                 ('SWITZERLAND', 'SWISS', 'ISRAEL', 'ISRAELI')],
   'section': 'gram6-nationality-adjective'},
  {'correct': [],
   'incorrect': [('GOING', 'WENT', 'PAYING', 'PAID'),
                 ('GOING', 'WENT', 'PLAYING', 'PLAYED'),
                 ('GOING', 'WENT', 'SAYING', 'SAID'),
                 ('GOING', 'WENT', 'TAKING', 'TOOK'),
                 ('PAYING', 'PAID', 'PLAYING', 'PLAYED'),
                 ('PAYING', 'PAID', 'SAYING', 'SAID'),
                 ('PAYING', 'PAID', 'TAKING', 'TOOK'),
                 ('PAYING', 'PAID', 'GOING', 'WENT'),
                 ('PLAYING', 'PLAYED', 'SAYING', 'SAID'),
                 ('PLAYING', 'PLAYED', 'TAKING', 'TOOK'),
                 ('PLAYING', 'PLAYED', 'GOING', 'WENT'),
                 ('PLAYING', 'PLAYED', 'PAYING', 'PAID'),
                 ('SAYING', 'SAID', 'TAKING', 'TOOK'),
                 ('SAYING', 'SAID', 'GOING', 'WENT'),
                 ('SAYING', 'SAID', 'PAYING', 'PAID'),
                 ('SAYING', 'SAID', 'PLAYING', 'PLAYED'),
                 ('TAKING', 'TOOK', 'GOING', 'WENT'),
                 ('TAKING', 'TOOK', 'PAYING', 'PAID'),
                 ('TAKING', 'TOOK', 'PLAYING', 'PLAYED'),
                 ('TAKING', 'TOOK', 'SAYING', 'SAID')],
   'section': 'gram7-past-tense'},
  {'correct': [('BUILDING', 'BUILDINGS', 'CAR', 'CARS'),
               ('BUILDING', 'BUILDINGS', 'CHILD', 'CHILDREN'),
               ('CAR', 'CARS', 'BUILDING', 'BUILDINGS'),
               ('CHILD', 'CHILDREN', 'CAR', 'CARS'),
               ('MAN', 'MEN', 'CAR', 'CARS')],
   'incorrect': [('BUILDING', 'BUILDINGS', 'MAN', 'MEN'),
                 ('CAR', 'CARS', 'CHILD', 'CHILDREN'),
                 ('CAR', 'CARS', 'MAN', 'MEN'),
                 ('CHILD', 'CHILDREN', 'MAN', 'MEN'),
                 ('CHILD', 'CHILDREN', 'BUILDING', 'BUILDINGS'),
                 ('MAN', 'MEN', 'BUILDING', 'BUILDINGS'),
                 ('MAN', 'MEN', 'CHILD', 'CHILDREN')],
   'section': 'gram8-plural'},
  {'correct': [], 'incorrect': [], 'section': 'gram9-plural-verbs'},
  {'correct': [('GOOD', 'BETTER', 'LOW', 'LOWER'),
               ('GREAT', 'GREATER', 'LOW', 'LOWER'),
               ('LONG', 'LONGER', 'LOW', 'LOWER'),
               ('BIG', 'BIGGEST', 'LARGE', 'LARGEST'),
               ('GOOD', 'BEST', 'LARGE', 'LARGEST'),
               ('GREAT', 'GREATEST', 'LARGE', 'LARGEST'),
               ('GO', 'GOING', 'SAY', 'SAYING'),
               ('LOOK', 'LOOKING', 'PLAY', 'PLAYING'),
               ('LOOK', 'LOOKING', 'SAY', 'SAYING'),
               ('LOOK', 'LOOKING', 'GO', 'GOING'),
               ('PLAY', 'PLAYING', 'SAY', 'SAYING'),
               ('PLAY', 'PLAYING', 'GO', 'GOING'),
               ('SAY', 'SAYING', 'GO', 'GOING'),
               ('AUSTRALIA', 'AUSTRALIAN', 'INDIA', 'INDIAN'),
               ('AUSTRALIA', 'AUSTRALIAN', 'ISRAEL', 'ISRAELI'),
               ('FRANCE', 'FRENCH', 'INDIA', 'INDIAN'),
               ('INDIA', 'INDIAN', 'ISRAEL', 'ISRAELI'),
               ('ISRAEL', 'ISRAELI', 'INDIA', 'INDIAN'),
               ('SWITZERLAND', 'SWISS', 'INDIA', 'INDIAN'),
               ('BUILDING', 'BUILDINGS', 'CAR', 'CARS'),
               ('BUILDING', 'BUILDINGS', 'CHILD', 'CHILDREN'),
               ('CAR', 'CARS', 'BUILDING', 'BUILDINGS'),
               ('CHILD', 'CHILDREN', 'CAR', 'CARS'),
               ('MAN', 'MEN', 'CAR', 'CARS')],
   'incorrect': [('HE', 'SHE', 'HIS', 'HER'),
                 ('HIS', 'HER', 'HE', 'SHE'),
                 ('GOOD', 'BETTER', 'GREAT', 'GREATER'),
                 ('GOOD', 'BETTER', 'LONG', 'LONGER'),
                 ('GREAT', 'GREATER', 'LONG', 'LONGER'),
                 ('GREAT', 'GREATER', 'GOOD', 'BETTER'),
                 ('LONG', 'LONGER', 'GOOD', 'BETTER'),
                 ('LONG', 'LONGER', 'GREAT', 'GREATER'),
                 ('LOW', 'LOWER', 'GOOD', 'BETTER'),
                 ('LOW', 'LOWER', 'GREAT', 'GREATER'),
                 ('LOW', 'LOWER', 'LONG', 'LONGER'),
                 ('BIG', 'BIGGEST', 'GOOD', 'BEST'),
                 ('BIG', 'BIGGEST', 'GREAT', 'GREATEST'),
                 ('GOOD', 'BEST', 'GREAT', 'GREATEST'),
                 ('GOOD', 'BEST', 'BIG', 'BIGGEST'),
                 ('GREAT', 'GREATEST', 'BIG', 'BIGGEST'),
                 ('GREAT', 'GREATEST', 'GOOD', 'BEST'),
                 ('LARGE', 'LARGEST', 'BIG', 'BIGGEST'),
                 ('LARGE', 'LARGEST', 'GOOD', 'BEST'),
                 ('LARGE', 'LARGEST', 'GREAT', 'GREATEST'),
                 ('GO', 'GOING', 'LOOK', 'LOOKING'),
                 ('GO', 'GOING', 'PLAY', 'PLAYING'),
                 ('GO', 'GOING', 'RUN', 'RUNNING'),
                 ('LOOK', 'LOOKING', 'RUN', 'RUNNING'),
                 ('PLAY', 'PLAYING', 'RUN', 'RUNNING'),
                 ('PLAY', 'PLAYING', 'LOOK', 'LOOKING'),
                 ('RUN', 'RUNNING', 'SAY', 'SAYING'),
                 ('RUN', 'RUNNING', 'GO', 'GOING'),
                 ('RUN', 'RUNNING', 'LOOK', 'LOOKING'),
                 ('RUN', 'RUNNING', 'PLAY', 'PLAYING'),
                 ('SAY', 'SAYING', 'LOOK', 'LOOKING'),
                 ('SAY', 'SAYING', 'PLAY', 'PLAYING'),
                 ('SAY', 'SAYING', 'RUN', 'RUNNING'),
                 ('AUSTRALIA', 'AUSTRALIAN', 'FRANCE', 'FRENCH'),
                 ('AUSTRALIA', 'AUSTRALIAN', 'SWITZERLAND', 'SWISS'),
                 ('FRANCE', 'FRENCH', 'ISRAEL', 'ISRAELI'),
                 ('FRANCE', 'FRENCH', 'SWITZERLAND', 'SWISS'),
                 ('FRANCE', 'FRENCH', 'AUSTRALIA', 'AUSTRALIAN'),
                 ('INDIA', 'INDIAN', 'SWITZERLAND', 'SWISS'),
                 ('INDIA', 'INDIAN', 'AUSTRALIA', 'AUSTRALIAN'),
                 ('INDIA', 'INDIAN', 'FRANCE', 'FRENCH'),
                 ('ISRAEL', 'ISRAELI', 'SWITZERLAND', 'SWISS'),
                 ('ISRAEL', 'ISRAELI', 'AUSTRALIA', 'AUSTRALIAN'),
                 ('ISRAEL', 'ISRAELI', 'FRANCE', 'FRENCH'),
                 ('SWITZERLAND', 'SWISS', 'AUSTRALIA', 'AUSTRALIAN'),
                 ('SWITZERLAND', 'SWISS', 'FRANCE', 'FRENCH'),
                 ('SWITZERLAND', 'SWISS', 'ISRAEL', 'ISRAELI'),
                 ('GOING', 'WENT', 'PAYING', 'PAID'),
                 ('GOING', 'WENT', 'PLAYING', 'PLAYED'),
                 ('GOING', 'WENT', 'SAYING', 'SAID'),
                 ('GOING', 'WENT', 'TAKING', 'TOOK'),
                 ('PAYING', 'PAID', 'PLAYING', 'PLAYED'),
                 ('PAYING', 'PAID', 'SAYING', 'SAID'),
                 ('PAYING', 'PAID', 'TAKING', 'TOOK'),
                 ('PAYING', 'PAID', 'GOING', 'WENT'),
                 ('PLAYING', 'PLAYED', 'SAYING', 'SAID'),
                 ('PLAYING', 'PLAYED', 'TAKING', 'TOOK'),
                 ('PLAYING', 'PLAYED', 'GOING', 'WENT'),
                 ('PLAYING', 'PLAYED', 'PAYING', 'PAID'),
                 ('SAYING', 'SAID', 'TAKING', 'TOOK'),
                 ('SAYING', 'SAID', 'GOING', 'WENT'),
                 ('SAYING', 'SAID', 'PAYING', 'PAID'),
                 ('SAYING', 'SAID', 'PLAYING', 'PLAYED'),
                 ('TAKING', 'TOOK', 'GOING', 'WENT'),
                 ('TAKING', 'TOOK', 'PAYING', 'PAID'),
                 ('TAKING', 'TOOK', 'PLAYING', 'PLAYED'),
                 ('TAKING', 'TOOK', 'SAYING', 'SAID'),
                 ('BUILDING', 'BUILDINGS', 'MAN', 'MEN'),
                 ('CAR', 'CARS', 'CHILD', 'CHILDREN'),
                 ('CAR', 'CARS', 'MAN', 'MEN'),
                 ('CHILD', 'CHILDREN', 'MAN', 'MEN'),
                 ('CHILD', 'CHILDREN', 'BUILDING', 'BUILDINGS'),
                 ('MAN', 'MEN', 'BUILDING', 'BUILDINGS'),
                 ('MAN', 'MEN', 'CHILD', 'CHILDREN')],
   'section': 'Total accuracy'}])

文字移动距离

您将需要pyemd本节的可选库.pip install pyemd

让我们从两句话开始:

sentence_obama = 'Obama speaks to the media in Illinois'.lower().split()
sentence_president = 'The president greets the press in Chicago'.lower().split()

删除他们的停用词。

from gensim.parsing.preprocessing import STOPWORDS
sentence_obama = [w for w in sentence_obama if w not in STOPWORDS]
sentence_president = [w for w in sentence_president if w not in STOPWORDS]

计算两个句子之间的词移动距离。

distance = wv.wmdistance(sentence_obama, sentence_president)
print(f"Word Movers Distance is {distance} (lower means closer)")
'Word Movers Distance is 0.015923231075180694 (lower means closer)'
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
img = mpimg.imread('fasttext-logo-color-web.png')
imgplot = plt.imshow(img)
_ = plt.axis('off')

Fast Similarity Queries with Annoy and Word2Vec

介绍 Annoy 库,用于基于 Word2Vec 学习的向量进行相似性查询。

LOGS = False  # Set to True if you want to see progress in logs.
if LOGS:
    import logging
    logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

Annoy “Approximate Nearest Neighbors Oh Yeah”库使相似的查询与Word2Vec模型。当前在 Gensim 的向量空间中寻找 k 个最近邻的实现在索引文档的数量上通过蛮力具有线性复杂性,尽管具有极低的常数因子。检索到的结果是准确的,这在许多应用程序中是一种矫枉过正:在亚线性时间内检索到的近似结果可能就足够了。Annoy 可以更快地找到近似最近邻。

  1. Download Text8 Corpus
  2. Train the Word2Vec model
  3. Construct AnnoyIndex with model & make a similarity query
  4. Compare to the traditional indexer
  5. Persist indices to disk
  6. Save memory by via memory-mapping indices saved to disk
  7. Evaluate relationship of num_trees to initialization time and accuracy
  8. Work with Google’s word2vec C formats

1.下载Text8语料库

import gensim.downloader as api
text8_path = api.load('text8', return_path=True)
print("Using corpus from", text8_path)
Using corpus from /Users/kofola3/gensim-data/text8/text8.gz

2. 训练 Word2Vec 模型

from gensim.models import Word2Vec, KeyedVectors
from gensim.models.word2vec import Text8Corpus

# Using params from Word2Vec_FastText_Comparison
params = {
    'alpha': 0.05,
    'vector_size': 100,
    'window': 5,
    'epochs': 5,
    'min_count': 5,
    'sample': 1e-4,
    'sg': 1,
    'hs': 0,
    'negative': 5,
}
model = Word2Vec(Text8Corpus(text8_path), **params)
wv = model.wv
print("Using trained model", wv)
Using trained model <gensim.models.keyedvectors.KeyedVectors object at 0x2095fb0f0>

3. 用模型构建 AnnoyIndex 并进行相似度查询

AnnoyIndexer为了在 Gensim 中使用 Annoy ,需要创建一个实例。该AnnoyIndexer班位于gensim.similarities.annoy

AnnoyIndexer() 需要两个参数:

  • 型号: AWord2VecDoc2Vec型号。
  • num_trees : 一个正整数。num_trees影响构建时间和索引大小。值越大,结果越准确,但索引越大。可以在此处找到有关 Annoy 中树木功能的更多信息 。num_trees、构建时间和准确性之间的关系将在本教程的后面进行研究。

现在我们已准备好进行查询,让我们在 Text8 语料库中找出与“science”最相似的前 5 个词。为了进行相似性查询,我们Word2Vec.most_similar像传统上一样调用 ,但增加了一个参数,indexer

除了 Annoy,Gensim 还支持 NMSLIB 索引器。NMSLIB 是一个类似于 Annoy 的库——两者都支持对相似向量的快速近似搜索。

from gensim.similarities.annoy import AnnoyIndexer

# 100 trees are being used in this example
annoy_index = AnnoyIndexer(model, 100)
# Derive the vector for the word "science" in our model
vector = wv["science"]
# The instance of AnnoyIndexer we just created is passed
approximate_neighbors = wv.most_similar([vector], topn=11, indexer=annoy_index)
# Neatly print the approximate_neighbors and their corresponding cosine similarity values
print("Approximate Neighbors")
for neighbor in approximate_neighbors:
    print(neighbor)

normal_neighbors = wv.most_similar([vector], topn=11)
print("\nExact Neighbors")
for neighbor in normal_neighbors:
    print(neighbor)
Approximate Neighbors
('science', 1.0)
('fiction', 0.6577868759632111)
('crichton', 0.5896251797676086)
('interdisciplinary', 0.5887056291103363)
('astrobiology', 0.5863820314407349)
('multidisciplinary', 0.5813699960708618)
('protoscience', 0.5805026590824127)
('vinge', 0.5781905055046082)
('astronautics', 0.5768974423408508)
('aaas', 0.574912428855896)
('brookings', 0.5739299058914185)

Exact Neighbors
('science', 1.0)
('fiction', 0.7657802700996399)
('crichton', 0.6631850600242615)
('interdisciplinary', 0.661673903465271)
('astrobiology', 0.6578403115272522)
('bimonthly', 0.6501255631446838)
('actuarial', 0.6495736837387085)
('multidisciplinary', 0.6494976878166199)
('protoscience', 0.6480439305305481)
('vinge', 0.6441534757614136)
('xenobiology', 0.6438207030296326)

向量的余弦相似度越接近 1,该词与我们的查询越相似,即“科学”的向量。相似词的排名和包含在 10 个最相似词中的词组存在一些差异。

4. 与传统索引器的比较

# Set up the model and vector that we are using in the comparison
annoy_index = AnnoyIndexer(model, 100)

# Dry run to make sure both indexes are fully in RAM
normed_vectors = wv.get_normed_vectors()
vector = normed_vectors[0]
wv.most_similar([vector], topn=5, indexer=annoy_index)
wv.most_similar([vector], topn=5)

import time
import numpy as np

def avg_query_time(annoy_index=None, queries=1000):
    """Average query time of a most_similar method over 1000 random queries."""
    total_time = 0
    for _ in range(queries):
        rand_vec = normed_vectors[np.random.randint(0, len(wv))]
        start_time = time.process_time()
        wv.most_similar([rand_vec], topn=5, indexer=annoy_index)
        total_time += time.process_time() - start_time
    return total_time / queries

queries = 1000

gensim_time = avg_query_time(queries=queries)
annoy_time = avg_query_time(annoy_index, queries=queries)
print("Gensim (s/query):\t{0:.5f}".format(gensim_time))
print("Annoy (s/query):\t{0:.5f}".format(annoy_time))
speed_improvement = gensim_time / annoy_time
print ("\nAnnoy is {0:.2f} times faster on average on this particular run".format(speed_improvement))

出去:

Gensim (s/query):       0.00585
Annoy (s/query):        0.00052

Annoy is 11.25 times faster on average on this particular run

此加速因子绝不是恒定的,并且会因运行而异,并且特别针对此数据集、BLAS 设置、Annoy 参数(随着树大小的增加加速因子减小)、机器规格以及其他因素。

annoy indexer 的初始化时间不包括在时间中。您使用的最佳 knn 算法将取决于您需要进行多少查询以及语料库的大小。如果您只进行很少的相似性查询,则初始化 annoy 索引器所花费的时间将比使用蛮力方法检索结果所花费的时间更长。但是,如果您要进行许多查询,则初始化烦人的索引器所需的时间将被索引器初始化后令人难以置信的快速检索时间所弥补

Gensim 的 ‘most_similar’ 方法使用点积形式的 numpy 操作,而 Annoy 的方法不是。如果您机器上的“numpy”正在使用 ATLAS 或 LAPACK 等 BLAS 库之一,它将在多核上运行(仅当您的机器具有多核支持时)。查看SciPy Cookbook 了解更多详细信息。

5. 将索引持久化到磁盘

您可以从/向磁盘保存和加载索引,以防止每次都必须构建它们。这将在磁盘上创建两个文件,fnamefname.d。需要这两个文件才能正确恢复所有属性。在加载索引之前,您必须创建一个空的 AnnoyIndexer 对象。

fname = '/tmp/mymodel.index'

# Persist index to disk
annoy_index.save(fname)

# Load index back
import os.path
if os.path.exists(fname):
    annoy_index2 = AnnoyIndexer()
    annoy_index2.load(fname)
    annoy_index2.model = model

# Results should be identical to above
vector = wv["science"]
approximate_neighbors2 = wv.most_similar([vector], topn=11, indexer=annoy_index2)
for neighbor in approximate_neighbors2:
    print(neighbor)

assert approximate_neighbors == approximate_neighbors2
('science', 1.0)
('fiction', 0.6577868759632111)
('crichton', 0.5896251797676086)
('interdisciplinary', 0.5887056291103363)
('astrobiology', 0.5863820314407349)
('multidisciplinary', 0.5813699960708618)
('protoscience', 0.5805026590824127)
('vinge', 0.5781905055046082)
('astronautics', 0.5768974423408508)
('aaas', 0.574912428855896)
('brookings', 0.5739299058914185)

请务必在负载时使用与最初使用的模型相同的模型,否则会出现意外行为。

6. 通过保存到磁盘的内存映射索引来节省内存

Annoy 库有一个有用的功能,即索引可以从磁盘进行内存映射。当多个进程使用相同的索引时,它可以节省内存。

下面是两段代码。第一个对每个进程都有一个单独的索引。第二个片段通过内存映射在两个进程之间共享索引。第二个示例使用较少的总 RAM,因为它是共享的。

# Remove verbosity from code below (if logging active)
if LOGS:
    logging.disable(logging.CRITICAL)

from multiprocessing import Process
import os
import psutil

坏例子:两个进程从磁盘加载 Word2vec 模型并从该模型创建自己的 Annoy 索引。

model.save('/tmp/mymodel.pkl')

def f(process_id):
    print('Process Id: {}'.format(os.getpid()))
    process = psutil.Process(os.getpid())
    new_model = Word2Vec.load('/tmp/mymodel.pkl')
    vector = new_model.wv["science"]
    annoy_index = AnnoyIndexer(new_model, 100)
    approximate_neighbors = new_model.wv.most_similar([vector], topn=5, indexer=annoy_index)
    print('\nMemory used by process {}: {}\n---'.format(os.getpid(), process.memory_info()))

# Create and run two parallel processes to share the same index file.
p1 = Process(target=f, args=('1',))
p1.start()
p1.join()
p2 = Process(target=f, args=('2',))
p2.start()
p2.join()

很好的例子:两个进程从磁盘加载 Word2vec 模型和索引,并对索引进行内存映射。

model.save('/tmp/mymodel.pkl')

def f(process_id):
    print('Process Id: {}'.format(os.getpid()))
    process = psutil.Process(os.getpid())
    new_model = Word2Vec.load('/tmp/mymodel.pkl')
    vector = new_model.wv["science"]
    annoy_index = AnnoyIndexer()
    annoy_index.load('/tmp/mymodel.index')
    annoy_index.model = new_model
    approximate_neighbors = new_model.wv.most_similar([vector], topn=5, indexer=annoy_index)
    print('\nMemory used by process {}: {}\n---'.format(os.getpid(), process.memory_info()))

# Creating and running two parallel process to share the same index file.
p1 = Process(target=f, args=('1',))
p1.start()
p1.join()
p2 = Process(target=f, args=('2',))
p2.start()
p2.join()

7. 评估与num_trees初始化时间和准确性的关系

import matplotlib.pyplot as plt

构建初始化时间和准确度度量的数据集:

exact_results = [element[0] for element in wv.most_similar([normed_vectors[0]], topn=100)]

x_values = []
y_values_init = []
y_values_accuracy = []

for x in range(1, 300, 10):
    x_values.append(x)
    start_time = time.time()
    annoy_index = AnnoyIndexer(model, x)
    y_values_init.append(time.time() - start_time)
    approximate_results = wv.most_similar([normed_vectors[0]], topn=100, indexer=annoy_index)
    top_words = [result[0] for result in approximate_results]
    y_values_accuracy.append(len(set(top_words).intersection(exact_results)))

绘图结果:

plt.figure(1, figsize=(12, 6))
plt.subplot(121)
plt.plot(x_values, y_values_init)
plt.title("num_trees vs initalization time")
plt.ylabel("Initialization time (s)")
plt.xlabel("num_trees")
plt.subplot(122)
plt.plot(x_values, y_values_accuracy)
plt.title("num_trees vs accuracy")
plt.ylabel("%% accuracy")
plt.xlabel("num_trees")
plt.tight_layout()
plt.show()

num_trees 与初始化时间,num_trees 与准确性

出去:

/Volumes/work/workspace/vew/gensim3.6/lib/python3.6/site-packages/matplotlib/figure.py:445: UserWarning:

Matplotlib is currently using agg, which is a non-GUI backend, so cannot show the figure.

从上面可以看出, annoy indexer 的初始化时间随着 num_trees 的增加呈线性增长。初始化时间会因语料库而异。在上图中,我们使用了(微小的)Lee 语料库。

此外,在这个数据集中,准确性似乎与树的数量呈对数相关。我们看到更多树的准确性有所提高,但这种关系是非线性的。

8. 使用 Google 的 word2vec 文件

我们的模型可以导出为 word2vec C 格式。有二进制和纯文本 word2vec 格式。两者都可以用各种其他软件读取,或作为KeyedVectors对象导入回 Gensim 。

# To export our model as text
wv.save_word2vec_format('/tmp/vectors.txt', binary=False)

from smart_open import open
# View the first 3 lines of the exported file
# The first line has the total number of entries and the vector dimension count.
# The next lines have a key (a string) followed by its vector.
with open('/tmp/vectors.txt', encoding='utf8') as myfile:
    for i in range(3):
        print(myfile.readline().strip())

# To import a word2vec text model
wv = KeyedVectors.load_word2vec_format('/tmp/vectors.txt', binary=False)

# To export a model as binary
wv.save_word2vec_format('/tmp/vectors.bin', binary=True)

# To import a word2vec binary model
wv = KeyedVectors.load_word2vec_format('/tmp/vectors.bin', binary=True)

# To create and save Annoy Index from a loaded `KeyedVectors` object (with 100 trees)
annoy_index = AnnoyIndexer(wv, 100)
annoy_index.save('/tmp/mymodel.index')

# Load and test the saved word vectors and saved Annoy index
wv = KeyedVectors.load_word2vec_format('/tmp/vectors.bin', binary=True)
annoy_index = AnnoyIndexer()
annoy_index.load('/tmp/mymodel.index')
annoy_index.model = wv

vector = wv["cat"]
approximate_neighbors = wv.most_similar([vector], topn=11, indexer=annoy_index)
# Neatly print the approximate_neighbors and their corresponding cosine similarity values
print("Approximate Neighbors")
for neighbor in approximate_neighbors:
    print(neighbor)

normal_neighbors = wv.most_similar([vector], topn=11)
print("\nExact Neighbors")
for neighbor in normal_neighbors:
    print(neighbor)
71290 100
the 0.1645237 0.049031682 -0.11330697 0.097082675 -0.099474825 -0.08294691 0.007256336 -0.113704175 0.24664731 -0.062123552 -0.024763709 0.25688595 0.059356388 0.28822595 0.18409002 0.17533085 0.12412363 0.05312752 -0.10347493 0.07136696 0.050333817 0.03533254 0.07569087 -0.41796425 -0.13256022 0.30041444 0.26416314 -0.022389138 -0.20686609 -0.21565206 -0.25032488 -0.12548248 0.077188216 0.2432488 -0.1458781 -0.23084323 -0.13360116 -0.01887776 0.21207437 -0.0022163654 0.047225904 0.18978342 0.19625767 -0.02934954 0.005101277 0.11670754 0.11398655 0.33111402 -0.037173223 0.21018152 -0.07217948 -0.0045775156 -0.18228853 -0.065637104 0.16755614 0.20857134 0.1822439 -0.17496146 0.034775164 0.09327986 -0.011131699 -0.009912837 -0.18504283 -0.0043261787 0.03363841 -0.054994233 0.18313456 -0.22603175 0.15427239 0.22330661 0.026417818 0.09543534 0.09841086 -0.41345838 0.14082615 0.13712159 0.070771925 0.06285282 5.9063022e-05 -0.15651035 -0.016906142 0.14885448 0.07121329 -0.23360902 -0.09033932 -0.11270273 -0.0059097605 -0.04875052 -0.04409246 0.103411175 0.00074150774 -0.08402691 -0.07324047 -0.20355953 -0.091564305 -0.11138651 -0.18119322 0.21025972 -0.06939676 0.0016936468
of 0.19971648 0.15359728 -0.1338489 0.12083505 -0.005847811 -0.085402876 -0.075938866 -0.13501053 0.18837708 -0.1259633 0.110350266 0.108376145 0.015276252 0.33608598 0.22733492 0.11238891 -0.053862635 0.073887356 -0.20558539 -0.099394076 -0.0069137346 -0.114128046 0.027444497 -0.35551408 0.007910002 0.23189865 0.2650087 0.03700684 -0.17699398 -0.35950723 -0.32789174 -0.30379272 0.02704152 0.21078588 -0.023837725 -0.21654394 -0.166978 -0.08431874 0.2691367 -0.0023258273 0.06707064 0.09761329 0.24171327 -0.093486875 0.12232643 0.096265465 0.12889618 0.17138048 0.015292533 0.013243989 -0.09338309 0.0905355 -0.26343557 -0.2523928 0.07358186 0.17042407 0.266381 -0.218722 0.059136674 -0.00048657134 -0.0690399 -0.03615013 -0.059233107 -0.066501416 0.04838442 -0.11165278 0.09096755 -0.18076046 0.20482069 0.34460145 0.03740757 0.019260708 0.03930956 -0.37160733 -0.10296658 0.075969525 0.09362528 0.04970148 -0.07688446 -0.12854671 -0.10089095 0.01764436 0.1420408 -0.17590913 -0.20053966 0.14636976 -0.18029185 -0.081263 -0.048385028 0.26456535 -0.055859976 -0.08821882 -0.15724823 -0.17458497 0.010780472 -0.13346615 -0.12641737 0.16775236 -0.20294443 -0.115340725
Approximate Neighbors
('cat', 1.0)
('cats', 0.5968745350837708)
('meow', 0.5941576957702637)
('leopardus', 0.5938971042633057)
('prionailurus', 0.5928952395915985)
('felis', 0.5831491053104401)
('saimiri', 0.5817937552928925)
('rabbits', 0.5794903337955475)
('caracal', 0.5760406851768494)
('sighthound', 0.5754748582839966)
('oncifelis', 0.5718523561954498)

Exact Neighbors
('cat', 1.0000001192092896)
('cats', 0.6749798059463501)
('meow', 0.6705840826034546)
('leopardus', 0.6701608896255493)
('prionailurus', 0.6685314774513245)
('felis', 0.6524706482887268)
('saimiri', 0.6502071619033813)
('rabbits', 0.6463432312011719)
('purr', 0.6449686288833618)
('caracal', 0.640516996383667)
('sighthound', 0.639556884765625)

LDA Model

介绍 Gensim 的 LDA 模型并演示其在 NIPS 语料库中的使用。

  • 加载输入数据。
  • 预处理该数据。
  • 将文档转换为BOW向量。
  • 训练 LDA 模型。

如果您不熟悉 LDA 模型或如何在 Gensim 中使用它,建议您在继续本教程之前先阅读该模型。对 LDA 模型有基本的了解就足够了。例子:

LDA(Latent Dirichlet Allocation)是一种文档生成模型。它认为一篇文章是有多个主题的,而每个主题又对应着不同的词。一篇文章的构造过程,首先是以一定的概率选择某个主题,然后再在这个主题下以一定的概率选出某一个词,这样就生成了这篇文章的第一个词。不断重复这个过程,就生成了整片文章。当然这里假定词与词之间是没顺序的。

LDA的使用是上述文档生成的逆过程,它将根据一篇得到的文章,去寻找出这篇文章的主题,以及这些主题对应的词。

现在来看怎么用LDA,LDA会给我们返回什么结果。

LDA是非监督的机器学习模型,并且使用了词袋模型。一篇文章将会用词袋模型构造成词向量。LDA需要我们手动确定要划分的主题的个数,超参数将会在后面讲述,一般超参数对结果无很大影响。

数据

NIPS(神经信息处理系统)是一个机器学习会议,因此主题应该非常适合本教程的大多数目标受众。您可以从 Sam Roweis 的网站下载原始数据 。下面的代码也将为您做到这一点。

语料库包含 1740 个文档,而且不是特别长的文档。所以请记住,本教程不是为了提高效率,在将代码应用于大型数据集之前要小心。

import io
import os.path
import re
import tarfile

import smart_open

def extract_documents(url='https://cs.nyu.edu/~roweis/data/nips12raw_str602.tgz'):
    with smart_open.open(url, "rb") as file:
        with tarfile.open(fileobj=file) as tar:
            for member in tar.getmembers():
                if member.isfile() and re.search(r'nipstxt/nips\d+/\d+\.txt', member.name):
                    member_bytes = tar.extractfile(member).read()
                    yield member_bytes.decode('utf-8', errors='replace')

docs = list(extract_documents())

所以我们有一个包含 1740 个文档的列表,其中每个文档都是一个 Unicode 字符串。如果您正在考虑使用自己的语料库,那么在继续本教程的其余部分之前,您需要确保它的格式相同(Unicode 字符串列表)。

print(len(docs))
print(docs[0][:500])
1740
387
Neural Net and Traditional Classifiers 
William Y. Huang and Richard P. Lippmann
MIT Lincoln Laboratory
Lexington, MA 02173, USA
Abstract
Previous work on nets with continuous-valued inputs led to generative
procedures to construct convex decision regions with two-layer percepttons (one hidden
layer) and arbitrary decision regions with three-layer percepttons (two hidden layers).
Here we demonstrate that two-layer perceptton classifiers trained with back propagation
can form both c

对文档进行预处理和矢量化

作为预处理的一部分,我们将:

  • 标记化(将文档拆分为标记)。
  • 对令牌进行词形还原。
  • 计算二元组。
  • 计算数据的词袋表示。

首先,我们使用来自 NLTK 的正则表达式标记器对文本进行标记。我们删除了数字标记和仅单个字符的标记,因为它们往往没有用处,并且数据集包含很多它们。

# Tokenize the documents.
from nltk.tokenize import RegexpTokenizer

# Split the documents into tokens.
tokenizer = RegexpTokenizer(r'\w+')
for idx in range(len(docs)):
    docs[idx] = docs[idx].lower()  # Convert to lowercase.
    docs[idx] = tokenizer.tokenize(docs[idx])  # Split into words.

# Remove numbers, but not words that contain numbers.
docs = [[token for token in doc if not token.isnumeric()] for doc in docs]

# Remove words that are only one character.
docs = [[token for token in doc if len(token) > 1] for doc in docs]
/home/jonaschn/.pyenv/versions/anaconda3-5.3.1/lib/python3.7/site-packages/sklearn/feature_extraction/image.py:167: DeprecationWarning: `np.int` is a deprecated alias for the builtin `int`. To silence this warning, use `int` by itself. Doing this will not modify any behavior and is safe. When replacing `np.int`, you may wish to use e.g. `np.int64` or `np.int32` to specify the precision. If you wish to review your current use, check the release note link for additional information.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  dtype=np.int):
/home/jonaschn/.pyenv/versions/anaconda3-5.3.1/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:35: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  eps=np.finfo(np.float).eps,
/home/jonaschn/.pyenv/versions/anaconda3-5.3.1/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:597: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  eps=np.finfo(np.float).eps, copy_X=True, fit_path=True,
/home/jonaschn/.pyenv/versions/anaconda3-5.3.1/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:836: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  eps=np.finfo(np.float).eps, copy_X=True, fit_path=True,
/home/jonaschn/.pyenv/versions/anaconda3-5.3.1/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:862: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  eps=np.finfo(np.float).eps, positive=False):
/home/jonaschn/.pyenv/versions/anaconda3-5.3.1/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:1074: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  max_n_alphas=1000, n_jobs=1, eps=np.finfo(np.float).eps,
/home/jonaschn/.pyenv/versions/anaconda3-5.3.1/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:1306: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  max_n_alphas=1000, n_jobs=1, eps=np.finfo(np.float).eps,
/home/jonaschn/.pyenv/versions/anaconda3-5.3.1/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:1442: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  eps=np.finfo(np.float).eps, copy_X=True, positive=False):
/home/jonaschn/.pyenv/versions/anaconda3-5.3.1/lib/python3.7/site-packages/sklearn/linear_model/randomized_l1.py:152: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  precompute=False, eps=np.finfo(np.float).eps,
/home/jonaschn/.pyenv/versions/anaconda3-5.3.1/lib/python3.7/site-packages/sklearn/linear_model/randomized_l1.py:318: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  eps=np.finfo(np.float).eps, random_state=None,
/home/jonaschn/.pyenv/versions/anaconda3-5.3.1/lib/python3.7/site-packages/sklearn/linear_model/randomized_l1.py:575: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  eps=4 * np.finfo(np.float).eps, n_jobs=1,

我们使用 NLTK 的 WordNet lemmatizer。在这种情况下,词形还原器比词干分析器更受欢迎,因为它会产生更易读的单词。易于阅读的输出在主题建模中是非常可取的。

# Lemmatize the documents.
from nltk.stem.wordnet import WordNetLemmatizer

lemmatizer = WordNetLemmatizer()
docs = [[lemmatizer.lemmatize(token) for token in doc] for doc in docs]

我们在文档中找到了二元组。Bigrams 是两个相邻单词的集合。使用二元组,我们可以在输出中得到像“machine_learning”这样的短语(空格被下划线替换);没有二元组,我们只会得到“机器”和“学习”。

请注意,在下面的代码中,我们找到了 bigram,然后将它们添加到原始数据中,因为我们希望保留单词“machine”和“learning”以及 bigram“machine_learning”。

计算大型数据集的 n-gram 可能非常需要计算和内存密集型。

# Compute bigrams.
from gensim.models import Phrases

# Add bigrams and trigrams to docs (only ones that appear 20 times or more).
bigram = Phrases(docs, min_count=20)
for idx in range(len(docs)):
    for token in bigram[docs[idx]]:
        if '_' in token:
            # Token is a bigram, add to document.
            docs[idx].append(token)
/home/jonaschn/Projects/gensim/gensim/similarities/__init__.py:11: UserWarning: The gensim.similarities.levenshtein submodule is disabled, because the optional Levenshtein package <https://pypi.org/project/python-Levenshtein/> is unavailable. Install Levenhstein (e.g. `pip install python-Levenshtein`) to suppress this warning.
  "The gensim.similarities.levenshtein submodule is disabled, because the optional "
2021-03-19 14:09:53,817 : INFO : collecting all words and their counts
2021-03-19 14:09:53,817 : INFO : PROGRESS: at sentence #0, processed 0 words and 0 word types
2021-03-19 14:09:59,172 : INFO : collected 1120198 token types (unigram + bigrams) from a corpus of 4629808 words and 1740 sentences
2021-03-19 14:09:59,172 : INFO : merged Phrases<1120198 vocab, min_count=20, threshold=10.0, max_vocab_size=40000000>
2021-03-19 14:09:59,190 : INFO : Phrases lifecycle event {'msg': 'built Phrases<1120198 vocab, min_count=20, threshold=10.0, max_vocab_size=40000000> in 5.36s', 'datetime': '2021-03-19T14:09:59.189253', 'gensim': '4.0.0.rc1', 'python': '3.7.0 (default, Jun 28 2018, 13:15:42) \n[GCC 7.2.0]', 'platform': 'Linux-4.15.0-136-generic-x86_64-with-debian-buster-sid', 'event': 'created'}

我们根据文档频率删除稀有词和常用词。下面我们删除出现在少于 20 个文档或超过 50% 的文档中的词。考虑尝试仅根据单词的频率来删除单词,或者将其与这种方法相结合。

# Remove rare and common tokens.
from gensim.corpora import Dictionary

# Create a dictionary representation of the documents.
dictionary = Dictionary(docs)

# Filter out words that occur less than 20 documents, or more than 50% of the documents.
dictionary.filter_extremes(no_below=20, no_above=0.5)
2021-03-19 14:10:07,280 : INFO : adding document #0 to Dictionary(0 unique tokens: [])
2021-03-19 14:10:09,906 : INFO : built Dictionary(79429 unique tokens: ['1ooooo', '1st', '25oo', '2o00', '4ooo']...) from 1740 documents (total 4953968 corpus positions)
2021-03-19 14:10:09,906 : INFO : Dictionary lifecycle event {'msg': "built Dictionary(79429 unique tokens: ['1ooooo', '1st', '25oo', '2o00', '4ooo']...) from 1740 documents (total 4953968 corpus positions)", 'datetime': '2021-03-19T14:10:09.906597', 'gensim': '4.0.0.rc1', 'python': '3.7.0 (default, Jun 28 2018, 13:15:42) \n[GCC 7.2.0]', 'platform': 'Linux-4.15.0-136-generic-x86_64-with-debian-buster-sid', 'event': 'created'}
2021-03-19 14:10:10,101 : INFO : discarding 70785 tokens: [('1ooooo', 1), ('25oo', 2), ('2o00', 6), ('4ooo', 2), ('64k', 6), ('a', 1740), ('aaditional', 1), ('above', 1114), ('abstract', 1740), ('acase', 1)]...
2021-03-19 14:10:10,102 : INFO : keeping 8644 tokens which were in no less than 20 and no more than 870 (=50.0%) documents
2021-03-19 14:10:10,128 : INFO : resulting dictionary: Dictionary(8644 unique tokens: ['1st', '5oo', '7th', 'a2', 'a_well']...)

最后,我们将文档转换为矢量化形式。我们简单地计算每个单词的频率,包括二元组。

# Bag-of-words representation of the documents.
corpus = [dictionary.doc2bow(doc) for doc in docs]

让我们看看我们必须训练多少令牌和文档。

print('Number of unique tokens: %d' % len(dictionary))
print('Number of documents: %d' % len(corpus))
Number of unique tokens: 8644
Number of documents: 1740

训练

我们已准备好训练 LDA 模型。我们将首先讨论如何设置一些训练参数。

首先,房间里的大象:我需要多少主题?对此确实没有简单的答案,这取决于您的数据和应用程序。我在这里使用了 10 个主题,因为我想要一些我可以解释和“标记”的主题,而且结果证明这给了我相当不错的结果。您可能不需要解释所有主题,因此您可以使用大量主题,例如 100。

chunksize控制在训练算法中一次处理多少文档。增加块大小将加快训练速度,至少只要文档块可以轻松放入内存。我已经设置了,超过了文档数量,所以我一次性处理所有数据。然而,块大小会影响模型的质量,但在这种情况下差异不大。chunksize = 2000

passes控制我们在整个语料库上训练模型的频率。通行证的另一个词可能是“纪元”。iterations有点技术性,但本质上它控制了我们对每个文档重复特定循环的频率。将“passes”和“iterations”的数量设置得足够高很重要。

我建议使用以下方法来选择迭代和传递。首先,启用日志记录(如许多 Gensim 教程中所述),并 在. 训练模型时,会在日志中查找如下所示的行:eval_every = 1 LdaModel

2016-06-21 15:40:06,753 - gensim.models.ldamodel - DEBUG - 68/1566 documents converged within 400 iterations

如果您设置,您将看到此行 20 次。确保在最后通过时,大多数文档已经收敛。因此,您希望选择足够高的传递和迭代次数,以便发生这种情况。passes = 20

我们设置和。同样,这有点技术性,但本质上我们正在自动学习模型中通常必须明确指定的两个参数。alpha = 'auto' eta = 'auto'

# Train LDA model.
from gensim.models import LdaModel

# Set training parameters.
num_topics = 10
chunksize = 2000
passes = 20
iterations = 400
eval_every = None  # Don't evaluate model perplexity, takes too much time.

# Make a index to word dictionary.
temp = dictionary[0]  # This is only to "load" the dictionary.
id2word = dictionary.id2token

model = LdaModel(
    corpus=corpus,
    id2word=id2word,
    chunksize=chunksize,
    alpha='auto',
    eta='auto',
    iterations=iterations,
    num_topics=num_topics,
    passes=passes,
    eval_every=eval_every
)

我们可以计算每个主题的主题一致性。下面我们显示平均主题连贯性并按主题连贯性顺序打印主题。

请注意,我们在这里使用了“Umass”主题连贯性度量(参见 gensim.models.ldamodel.LdaModel.top_topics()参考资料),Gensim 最近获得了“AKSW”主题连贯性度量的实现。

如果你熟悉这个数据集中文章的主题,你会发现下面的主题很有意义。然而,它们并非没有缺陷。我们可以看到一些主题之间存在大量重叠,其他主题很难解释,并且其中大多数至少有一些看起来不合适的术语。如果您能够做得更好,请随时在http://rare-technologies.com/lda-training-tips/的博客上分享您的方法!

top_topics = model.top_topics(corpus) #, num_words=20)

# Average topic coherence is the sum of topic coherences of all topics, divided by the number of topics.
avg_topic_coherence = sum([t[1] for t in top_topics]) / num_topics
print('Average topic coherence: %.4f.' % avg_topic_coherence)

from pprint import pprint
pprint(top_topics)

Things to experiment with

  • no_above和方法中的no_below参数filter_extremes
  • 添加三元组甚至更高阶的 n-gram。
  • 考虑使用保留集或交叉验证是否适合您。
  • 尝试其他数据集。

Where to go from here

Soft Cosine Measure

演示使用 Gensim 的 SCM 实现。

Soft Cosine Measure (SCM) 是机器学习中一种很有前途的新工具,它允许我们提交查询并返回最相关的文档。本教程介绍了 SCM 并展示了如何使用该inner_product方法计算两个文档之间的 SCM 相似度。

软余弦测量基础

Soft Cosine Measure (SCM) 是一种允许我们以有意义的方式评估两个文档之间的相似性的方法,即使它们没有共同的单词。它使用单词之间的相似性度量,可以使用单词的 [word2vec] 向量嵌入。

下面针对两个非常相似的句子说明了 SCM。这些句子没有共同的词,但是通过对同义词建模,SCM 能够准确地衡量两个句子之间的相似度。该方法还使用了文档的词袋向量表示(简单地说,就是单词在文档中的频率)。该方法背后的直觉是我们计算标准余弦相似度,假设文档向量以非正交基表示,其中两个基向量之间的角度来自相应单词的 word2vec 嵌入之间的角度。

import matplotlib.pyplot as plt
import matplotlib.image as mpimg
img = mpimg.imread('scm-hello.png')
imgplot = plt.imshow(img)
plt.axis('off')
plt.show()

这种方法可能是在 Grigori Sidorov、Alexander Gelbukh、Helena Gomez-Adorno 和 David Pinto 的文章“软度量和软余弦度量:向量空间模型中的特征度量”中首次引入的。

在本教程中,我们将学习如何使用 Gensim 的 SCM 功能,该功能由inner_product一次性计算方法和 SoftCosineSimilarity基于语料库的相似性查询类组成。

计算软余弦测度

要使用 SCM,您需要一些现有的词嵌入。您可以训练自己的 Word2Vec 模型。

让我们用一些句子来计算之间的距离。

# Initialize logging.
import logging
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

sentence_obama = 'Obama speaks to the media in Illinois'
sentence_president = 'The president greets the press in Chicago'
sentence_orange = 'Oranges are my favorite fruit'

前两句句子的内容非常相似,因此SCM应该很高。相比之下,第三句与前两句无关,SCM应该低。

在计算 SCM 之前,我们要删除停用词(“the”、“to”等),因为这些对句子中的信息贡献不大。

# Import and download stopwords from NLTK.
from nltk.corpus import stopwords
from nltk import download
download('stopwords')  # Download stopwords list.
stop_words = stopwords.words('english')

def preprocess(sentence):
    return [w for w in sentence.lower().split() if w not in stop_words]

sentence_obama = preprocess(sentence_obama)
sentence_president = preprocess(sentence_president)
sentence_orange = preprocess(sentence_orange)
/home/witiko/.virtualenvs/gensim4/lib/python3.7/site-packages/sklearn/feature_extraction/image.py:167: DeprecationWarning: `np.int` is a deprecated alias for the builtin `int`. To silence this warning, use `int` by itself. Doing this will not modify any behavior and is safe. When replacing `np.int`, you may wish to use e.g. `np.int64` or `np.int32` to specify the precision. If you wish to review your current use, check the release note link for additional information.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  dtype=np.int):
/home/witiko/.virtualenvs/gensim4/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:30: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  method='lar', copy_X=True, eps=np.finfo(np.float).eps,
/home/witiko/.virtualenvs/gensim4/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:167: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  method='lar', copy_X=True, eps=np.finfo(np.float).eps,
/home/witiko/.virtualenvs/gensim4/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:284: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  eps=np.finfo(np.float).eps, copy_Gram=True, verbose=0,
/home/witiko/.virtualenvs/gensim4/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:862: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  eps=np.finfo(np.float).eps, copy_X=True, fit_path=True,
/home/witiko/.virtualenvs/gensim4/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:1101: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  eps=np.finfo(np.float).eps, copy_X=True, fit_path=True,
/home/witiko/.virtualenvs/gensim4/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:1127: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  eps=np.finfo(np.float).eps, positive=False):
/home/witiko/.virtualenvs/gensim4/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:1362: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  max_n_alphas=1000, n_jobs=None, eps=np.finfo(np.float).eps,
/home/witiko/.virtualenvs/gensim4/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:1602: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  max_n_alphas=1000, n_jobs=None, eps=np.finfo(np.float).eps,
/home/witiko/.virtualenvs/gensim4/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:1738: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  eps=np.finfo(np.float).eps, copy_X=True, positive=False):
[nltk_data] Downloading package stopwords to /home/witiko/nltk_data...
[nltk_data]   Package stopwords is already up-to-date!

接下来,我们将构建一个字典和一个 TF-IDF 模型,并将句子转换为词袋格式。

from gensim.corpora import Dictionary
documents = [sentence_obama, sentence_president, sentence_orange]
dictionary = Dictionary(documents)

sentence_obama = dictionary.doc2bow(sentence_obama)
sentence_president = dictionary.doc2bow(sentence_president)
sentence_orange = dictionary.doc2bow(sentence_orange)

from gensim.models import TfidfModel
documents = [sentence_obama, sentence_president, sentence_orange]
tfidf = TfidfModel(documents)

sentence_obama = tfidf[sentence_obama]
sentence_president = tfidf[sentence_president]
sentence_orange = tfidf[sentence_orange]

现在,如前所述,我们将使用一些下载的预训练嵌入。我们将这些加载到一个 Gensim Word2Vec 模型类中,并使用嵌入构建一个术语相似度矩阵。

我们在这里选择的嵌入需要大量内存。

import gensim.downloader as api
model = api.load('word2vec-google-news-300')

from gensim.similarities import SparseTermSimilarityMatrix, WordEmbeddingSimilarityIndex
termsim_index = WordEmbeddingSimilarityIndex(model)
termsim_matrix = SparseTermSimilarityMatrix(termsim_index, dictionary, tfidf)

因此,让我们使用该inner_product方法计算 SCM 。

similarity = termsim_matrix.inner_product(sentence_obama, sentence_president, normalized=(True, True))
print('similarity = %.4f' % similarity)
similarity = 0.2575

让我们用两个完全不相关的句子尝试同样的事情。请注意,相似度较小。

similarity = termsim_matrix.inner_product(sentence_obama, sentence_orange, normalized=(True, True))
print('similarity = %.4f' % similarity)
similarity = 0.0000

Word Mover’s Distance

演示使用 Gensim 的 WMD 实现。

Word Mover 的距离 (WMD) 是机器学习中一种很有前途的新工具,它允许我们提交查询并返回最相关的文档。本教程介绍了 WMD 并展示了如何使用wmdistance.

WMD Basic

WMD 使我们能够以有意义的方式评估两个文档之间的“距离”,即使它们没有共同的词。它使用word2vec向量嵌入。它已被证明在 k-近邻分类中优于许多最先进的方法。

下面用两个非常相似的句子说明了 WMD。句子没有共同的词,但通过匹配相关词,WMD 能够准确衡量两个句子之间的(异)相似度。该方法还使用了文档的词袋表示(简单地说,是单词在文档中的频率),在下图中用 $d$ 表示。该方法背后的直觉是我们找到了文档之间的最小“行进距离”,换句话说,是将文档 1 的分布“移动”到文档 2 的分布的最有效方法。

# Image from https://vene.ro/images/wmd-obama.png
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
img = mpimg.imread('wmd-obama.png')
imgplot = plt.imshow(img)
plt.axis('off')
plt.show()

这种方法是在 Matt Kusner 等人的文章“From Word Embeddings To Document Distances”中介绍的。(链接到 PDF)。它的灵感来自“地球移动者的距离”,并采用了“运输问题”的求解器。

在本教程中,我们将学习如何使用 Gensim 的 WMD 功能,该功能包括wmdistance距离计算方法和 WmdSimilarity基于语料库的相似性查询类。

计算 Word Mover 的距离

要使用 WMD,您需要一些现有的词嵌入。您可以训练自己的 Word2Vec 模型。

让我们用一些句子来计算之间的距离。

# Initialize logging.
import logging
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

sentence_obama = 'Obama speaks to the media in Illinois'
sentence_president = 'The president greets the press in Chicago'

这些句子的内容非常相似,因此 WMD 应该很低。在计算 WMD 之前,我们要删除停用词(“the”、“to”等),因为这些对句子中的信息贡献不大。

# Import and download stopwords from NLTK.
from nltk.corpus import stopwords
from nltk import download
download('stopwords')  # Download stopwords list.
stop_words = stopwords.words('english')

def preprocess(sentence):
    return [w for w in sentence.lower().split() if w not in stop_words]

sentence_obama = preprocess(sentence_obama)
sentence_president = preprocess(sentence_president)
/home/witiko/.virtualenvs/gensim4/lib/python3.7/site-packages/sklearn/feature_extraction/image.py:167: DeprecationWarning: `np.int` is a deprecated alias for the builtin `int`. To silence this warning, use `int` by itself. Doing this will not modify any behavior and is safe. When replacing `np.int`, you may wish to use e.g. `np.int64` or `np.int32` to specify the precision. If you wish to review your current use, check the release note link for additional information.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  dtype=np.int):
/home/witiko/.virtualenvs/gensim4/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:30: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  method='lar', copy_X=True, eps=np.finfo(np.float).eps,
/home/witiko/.virtualenvs/gensim4/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:167: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  method='lar', copy_X=True, eps=np.finfo(np.float).eps,
/home/witiko/.virtualenvs/gensim4/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:284: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  eps=np.finfo(np.float).eps, copy_Gram=True, verbose=0,
/home/witiko/.virtualenvs/gensim4/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:862: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  eps=np.finfo(np.float).eps, copy_X=True, fit_path=True,
/home/witiko/.virtualenvs/gensim4/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:1101: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  eps=np.finfo(np.float).eps, copy_X=True, fit_path=True,
/home/witiko/.virtualenvs/gensim4/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:1127: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  eps=np.finfo(np.float).eps, positive=False):
/home/witiko/.virtualenvs/gensim4/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:1362: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  max_n_alphas=1000, n_jobs=None, eps=np.finfo(np.float).eps,
/home/witiko/.virtualenvs/gensim4/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:1602: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  max_n_alphas=1000, n_jobs=None, eps=np.finfo(np.float).eps,
/home/witiko/.virtualenvs/gensim4/lib/python3.7/site-packages/sklearn/linear_model/least_angle.py:1738: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  eps=np.finfo(np.float).eps, copy_X=True, positive=False):
[nltk_data] Downloading package stopwords to /home/witiko/nltk_data...
[nltk_data]   Package stopwords is already up-to-date!

现在,如前所述,我们将使用一些下载的预训练嵌入。我们将这些加载到 Gensim Word2Vec 模型类中。

我们在这里选择的嵌入需要大量内存。

import gensim.downloader as api
model = api.load('word2vec-google-news-300')

因此,让我们使用该wmdistance方法计算 WMD 。

distance = model.wmdistance(sentence_obama, sentence_president)
print('distance = %.4f' % distance)
distance = 1.0175

让我们用两个完全不相关的句子尝试同样的事情。注意距离更大。

sentence_orange = preprocess('Oranges are my favorite fruit')
distance = model.wmdistance(sentence_obama, sentence_orange)
print('distance = %.4f' % distance)
distance = 1.3663

Other

How to reproduce the doc2vec ‘Paragraph Vector’ paper

How to Compare LDA Models


文章作者: 杰克成
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 杰克成 !
评论
  目录