gensim中文文档(翻译练习)

gensim文档英文原文

学习文档的时候顺便翻译了一下,练个手。

核心概念

gensim的核心概念包括:

  1. 文档:一些文本
  2. 语料库:由一些文档组成
  3. 向量:文档的数学表示
  4. 模型:将向量从一种表示转换到另一种表示的算法

文档

在gensim里面,文档是一个文本序列类型的对象(在python3中一般称为str)。一个文档可以是一段tweet一样的140字短文本,一个自然段(如,期刊摘要),一篇新闻或者一本书。

document = 'Human machine interface for lab abc computer application'

语料库

语料库是文档对象的合集。在gensim中,语料库主要有以下两个作用:

  1. 作为模型训练的输入。在训练中,模型使用语料库进行训练,来找到其中共同的主题和话题,来初始化模型的内部参数。
    gensim主要侧重于不需要人为干预(如对文档进行人工标注)的无监督模型。

  2. 组织文档。在训练以后,主题模型可以用来从新的文档(文档不在训练语料库中)中抽取主题。
    这样的语料库可以通过语义相似度、聚类等方法在相似度查询中索引到。

以下为语料库的示例。它包含了9个文档,其中每个文档都是由一单句话组成。

text_corpus = [
    "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",
]

注意

以上的示例在内存中加载了所有的语料库。然而在实际中,语料库可能会非常大,以至于可能无法在内存中全部加载。gensim可以通过每次加载一个文档的数据流方式解决这个问题。参见语料库数据流-一次一个文档

本示例是为说明而准备的一个小例子。另一个例子可以是Shakespeare所有戏剧的列表,维基百科上的所有文章,或者感兴趣的人的所有tweets。

在收集到语料库之后,我们要进行一些预处理工作。如去掉常用的词汇(比如“的”)或者一些只在语料库中出现了一次的词语。在进行这些工作的过程中,我们就对数据进行了标记。通过标记工作将文档转化为词语(在本示例中使用空格作为分隔符)。

注意

除了小写转换(英文语料)、空格分词外,还有很多进行预处理的方法。不过有效的预处理工作不在本教程的介绍范围内:如果感兴趣可以查看gensim.utils.simple_preprocess()函数。

# 制作停用词表
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 text_corpus]

# 统计词频
from collections import defaultdict
frequency = defaultdict(int)
for text in texts:
    for token in text:
        frequency[token] += 1

# 只保留出现次数大于1的词
processed_corpus = [[token for token in text if frequency[token] > 1] for text in texts]
import(pprint)
pprint.pprint(processed_corpus)

在进行下一步之前,需要给每个单词一个特定的整数ID。可以使用gensim.corpora.Dictionary来处理。该字典将对所有需要处理的数据进行定义。

from gensim import corpora

dictionary = corpora.Dictionary(processed_corpus)
print(dictionary)

由于我们的语料库很小,所以gensim.corpora.Dictionary中只有12个不同的token。在大的语料库中,字典一般都有10w级别。

向量

为了推断语料库中的隐藏结构,需要一种方法对文档进行数学表示。其中一种方法就是把每个文档都表示为一个特征向量。比如,一个特征可以看作一个问题对:

  1. splonge在文档中出现了多少次?0。
  2. 文档包含了几个自然段?2。
  3. 文档使用了几种字体?5。

问题一般使用其ID数字表示(如,1,2,3)。这样文档就可以使用一系列的问题对表示:(1, 0.0),(2, 2.0),(3, 5.0)。由于该向量包含了每个问题的直接答案,这样的向量被称为稠密向量(dense vector)。

如果提前知道了所有的问题,那我们可以简单的将文档表示为(0, 2, 5)。这个答案的序列就是代表文档的向量(本例中为3维稠密向量)。出于实践考虑,在gensim中,只有答案才使用或能被转换为单精度浮点数。

在实际中,向量经常会包含很多0值。为了节省内存,gensim会省略所有0.0值的向量元素。故上面的例子为(2, 2.0),(3, 5.0)。这被称为稀疏向量(sparse vector)或词袋向量(bag-of-words vector)。在这种稀疏表达方式中,所有缺失特征的值均为0.0。

假设问题是一样的,我们可以比较两个不同文档的向量。比如,向量(0.0, 2.0, 5.0)和(0.1, 1.9, 4.9)。由于这两个向量非常相似,我们可以认为这两个文档也是相似的。当然,这个结论的正确性依赖于我们之前选择的问题。

另一个将文档表示为向量的方法是词袋模型。在词袋模型中,每篇文档都被表示为字典中每个单词词频的向量。比如,假设我们又一个包含[‘coffee’, ‘milk’, ‘sugar’, ‘spoon’]四个单词的词典,包含了“coffee milk coffee”的文档则可以被表示为[2, 1, 0, 0],其中每个数字分别代表“coffee”, “milk”, “sugar” and “spoon”在文档中出现的频数。该向量的长度由字典中词的数量决定。词袋模型的一个重要特点就是它忽略了文档中词的顺序,这也是它名字(词袋)的由来。

我们之前处理好的语料库有12个不一样的词,则在词袋模型中,每个文档将被表示为一个12维的向量。我们可以使用字典将标记好的文档转化为12维向量。我们可以通过以下方式查看词的ID:

pprint.pprint(dictionary.token2id)

假设我们想要将“Human computer interaction”(注意这个短语并不在我们的原始语料库中)向量化。我们可以使用字典的doc2bow方法创建一个词袋向量来表示文档,该方法使用稀疏向量来表示词频:

new_doc = "Human computer interaction"
new_vec = dictionary.doc2bow(new_doc.lower().split())
print(new_vec)

其中每个tuple的第一个数字为字典中token的ID,第二个数字为该token的词频。

注意单词“interaction”并没有出现在原始语料库中,所以在向量化的结果中也没有出现。所以这个向量只能包含在文档中出现过的单词。由于任一文档只包含了字典中的部分词,所以出于节省空间的角度,为0的词就不会在向量中出现。

我们可以将我们的全部原始语料库转化成一个向量列表:

bow_corpus = [dictionary.doc2bow(text) for text in processed_corpus]
pprint.pprint(bow_corpus)

与将这个列表整个加载在内存相比,在很多应用情况下你会希望有一个更可伸缩的方法。gensim就可以让你通过迭代器每次只取一个文档向量。详情请查看文档。

注意

文档和向量的区别在于,文档是一个文本,向量是文本的便捷数学表示。有时人们会混合使用它们,比如,有一个文档D,会直接说向量D或者文档D,而不是说文档D的向量。这些简称可能会引起歧义。

注意

受到向量表示的建立过程的影响,两个不一样的文档可能会得到同样的向量表示。

模型

到这里我们已经将我们的语料库进行了向量化,下面我们将使用模型对它进行转化。我们用“模型”一词来说明将文档由一种表达转换成另一种表达的过程。在gensim中,文档已经被表示为向量,故模型可以看作是在两个向量空间中的转换。当使用语料库进行训练时,模型将在训练中学习这种转换的细节特征(模型参数)。

举一个简单的模型例子,tf-idf。tf-idf模型将词袋向量转换成语料库中每个词相对出现频率的加权词频向量。

下面是一个简单的例子。先初始化tf-idf模型,然后在我们的语料库中进行训练,再对句子“system minors”进行转换。

from gensim import models

# 训练模型
tfidf = models.TfidfModel(bow_corpus)

# 转换句子"system minors"
words = "system minors".lower().split()
print(tfidf[dictionary.doc2bow(words)])

tf-idf模型返回的是一个tuple列表,第一个数字是tokenID,第二个数字是tf-idf的加权值。值得注意的是,“system”(在原始语料库中出现了4次)的加权值要比“minors”(在原始语料库中仅出现了2次)的低。

可以将训练好的模型存在硬盘中,要使用时(如在寻的训练文档上继续训练,或对新文档进行转换)再加载它们。

gensim提供了很多不同的模型/转换方法。详见主题和转换

当你创建好了一个模型时,你可以用它做很多事情。比如,用tf-idf转换整个语料库并建立索引,计算相似度。

from gensim import similarities

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

获取文档与语料库中每个文档的相似度:

query_document = 'system engineering'.split()
query_bow = dictionary.doc2bow(query_document)
sims = index[tfidf[query_bow]]
print(list(enumerate(sims)))

结果可以看出,文档3的相似度为0.718=72%, 文档2的相似度为42%。我们还能对结果进行排序:

for document_number, score in sorted(enumerate(sims), key=lambda x: x[1], reverse=True):
    print(document_number, score)

语料库与向量空间

本节介绍文本到向量空间的转换,语料库流,及其以不同形式存储到硬盘的方法。

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

首先先建立一个有9个文档组成的小语料库。

从字符串到向量

首先,从字符串表示的文档开始:

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",
]

这是一个包含了9个文档的小型语料库,每个文档只有一个句子。

首先对文档进行标记,去停用词,并删除仅在语料库中出现了1次的词:

from pprint import pprint  # 打印的结果比较美观
from collections import defaultdict


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
]# 标记,去停用词


frequency = defaultdict(int)
for text in texts:
    for token in text:
        frequency[token] += 1 # 删除仅在语料库中出现了1次的词

texts = [
    [token for token in text if frequency[token] > 1]
    for text in texts
]

pprint(texts)

对文档进行预处理的方法可能不同,本文仅使用了空格分词进行标记,并进行了小写转换。实际上,使用这种方法(过分简单且低效)对LSA原文的实验进行了模拟。

处理文档的方法多种多样,且与应用场景和语言相关,不应该对其进行限制。此外,文档是使用从文档中抽取的特征来进行表示的,而不是它“表面”的字符串形式,而如果获取特征取决于用户自己的选择。下面将介绍一个较为常用的方法(词袋),但是要记住不同的应用场景需要不同的特征。

我们将使用词袋的文档表示方法将文档转换为向量。在这种方法中,每个文档会被表示为一个向量,每个向量的元素代表了一个问题对,如下:

  • 问题:system在文档中出现了多少次?

  • 答案:一次。

使用ID来表示问题是非常有用的。问题与ID的映射成为字典:

from gensim import corpora
dictionary = corpora.Dictionary(texts)
dictionary.save('/tmp/deerwester.dict')  # 保存字典,后续可引用
print(dictionary)

在这里,我们使用gensim.corpora.dictionary.Dictionary类对每个在语料库中出现的单词赋予了一个特定的整数ID。它扫描了整个文本,对词数和相关数据进行了统计。最后我们在处理好的语料库中可以看到12个不同的单词,同时也说明每个文档将被表示成12个数字(12维向量)。下面展示了词和对应的ID:

print(dictionary.token2id)

将标记好的文档转换为向量:

new_doc = "Human computer interaction"
new_vec = dictionary.doc2bow(new_doc.lower().split())
print(new_vec)  # "interaction" 不在字典中所以被忽略了

函数doc2bow()只是统计了每个单词出现的次数,并将单词转换成对应的整数ID,并且返回一个稀疏向量。稀疏向量[(0, 1), (1, 1)]可看作:在文档“Human computer interaction”中,单词computer(id 0)和human(id 1)都出现一次;字典中其他10个词出现了0次。

corpus = [dictionary.doc2bow(text) for text in texts]
corpora.MmCorpus.serialize('/tmp/deerwester.mm', corpus)  # 保存到硬盘,后续可能使用
print(corpus)

现在应该能很清晰的看出,ID=10表示了问题“graph在文档中出现了多少次?”,在前6个文档中答案为0,在其他三个文档中为1。

语料库流-一次一个文档

上面的语料库是作为python列表全部加载到内存中的。在这个小例子中,这样处理没有问题,但是为了说明问题,我们假设有一个包含了百万文档的语料库,显然这样的语料库存储在内存中是不科学的。我们假设这些文档存储在硬盘上的文件中,一行代表一个文档。gensim要求语料库能每次返回一个文档。

from smart_open import open  # 打开远程文件


class MyCorpus(object):
    def __iter__(self):
        for line in open('https://radimrehurek.com/gensim/mycorpus.txt'):
            # 假设一行代表一个文档,并且使用空格进行标记
            yield dictionary.doc2bow(line.lower().split())

gensim的神奇之处在于,语料库不一定非要是一个list,或者numpy序列,或者pandas数据框之类的。gensim可以接受任何对象,当迭代结束时,返回文档。

这个弹性化的属性让用户能创建自己的语料库类,并且使用流方法直接从硬盘、网络、数据库、数据框等获取文档。
gensim中的模型在使用时不需要要求一次性把所有向量加载到内存。即使是在建模过程中也能添加文档。

可以在这里下载示例数据mycorpus.txt。这里的每个文档作为一条记录存储在单个文件中,不过用户可以通过__iter__函数自己调整输入的形式。文件夹、XML、网络等形式都可以,只要能获取每个文档标记后的列表即可,然后在__iter__函数中通过字典将这些token转换成ID并得到稀疏向量。

corpus_memory_friendly = MyCorpus()  # 并没有将语料库加载到内存!
print(corpus_memory_friendly)

这里的语料库是一个对象了。我们没有定义任何方法去打印它,所以print只是打印了这个对象在内存中的地址。这个结果在实际中没有什么用处。可以通过迭代语料库打印每个文档向量(一次一个),来查看里面的向量。

for vector in corpus_memory_friendly:  # 一次加载内存一个向量
    print(vector)

虽然这里的输出和纯python列表是一致的,但是因为每次只加载一条向量,这样的语料库对内存更为友好。这样用户的语料库大小可以随意制定。

同样的,我们可以创建一个字典,但是不将它整个加载到内存中。

from six import iteritems

dictionary = corpora.Dictionary(line.lower().split() for line in open('https://radimrehurek.com/gensim/mycorpus.txt'))# 统计每个token

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]# 删除仅在语料库中出现了1次的词
dictionary.filter_tokens(stop_ids + once_ids)  
dictionary.compactify()  # 在去掉停用词和少数词后,ID重新排序
print(dictionary)

以上就是词袋表示法语料库流的全部内容。当然如何使用这个语料库就是另一个话题了,并且计算每个词的词频是否有用也不是很明确。实际上,这个方法并不是很有效,接下来首先会对这个表示进行一个转换,然后再用它来计算文档与文档之间的相似度。转换方法将在下一篇教程主题和转换中进行介绍,不过在这之前,将简单看一下语料库的持久性。

语料库格式

有几种文件格式将向量空间语料库序列保存到硬盘上。gensim可以通过上面提到过的流语料库接口streaming corpus interface来实现:可以每次从硬盘中读取一个文档,而不是一次将整个语料库加载到硬盘。

一个比较通用的文件形式是集市矩阵Market Matrix format。下面展示如何将语料库保存为矩阵集市格式:
创建一个python list,作为包含两个文档的示例语料库。

corpus = [[(1, 0.5)], []]  # 将其中一个文档置空

corpora.MmCorpus.serialize('/tmp/corpus.mm', corpus)

其他的格式包括Joachim’s SVMlight formatBlei’s LDA-C formatGibbsLDA++ format

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

下面展示了如何从矩阵集市文件中读取语料库数据:

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

由于语料库对象是一个数据流,所以没有办法直接print,可以通过以下方式查看语料库:

# 方法一:直接一次性全部加载到内存
print(list(corpus))

# 方法二:使用数据流接口,每次打印一个文档
for doc in corpus:
    print(doc)

显然第二种方法对内存更加友好,不过为了测试或者开发,还是直接使用list(corpus)比较简单。

由此,gensim也可以作为一个内存友好的I/O格式转换工具:使用一种格式加载文档流,然后再保存为另一种格式。添加新的文件格式是非常容易的,可以参考这里SVMlight语料库格式

兼容numpy和scipy

gensim也有很多有效实用的函数来与numpy矩阵的进行相互转换:

import gensim
import numpy as np
numpy_matrix = np.random.randint(10, size=[5, 2])  

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)  
corpus = gensim.matutils.Sparse2Corpus(scipy_sparse_matrix)
scipy_csc_matrix = gensim.matutils.corpus2csc(corpus)

主题和转换器

本节将介绍如何将文档从一个向量表示转换到另一个。这个过程主要有两个目的:

  1. 提取语料库中的隐藏结构,发现词语见的关系,然后用这些关系对文档进行新的更具有语义的描述。
  2. 使文档的表达更为凝练。这样不仅能提高效率(新的表达回使用更少的资源),并且能提高效能(忽略了无意义的数据,对数据进行了降噪)

创建语料库

首先我们需要创建一个语料库。下面的步骤和前面章节的内容基本一致,可以直接跳过。

from collections import defaultdict
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",
]

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
]

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
]

dictionary = corpora.Dictionary(texts)
corpus = [dictionary.doc2bow(text) for text in texts]

创建转换器

这些转换器是标准的python对象,一般用training语料库进行初始化:

from gensim import models

tfidf = models.TfidfModel(corpus) # 初始化模型

我们使用第一节教程中的语料库对转换器模型进行了初始化。不同的转换器可能需要初始化不同的参数。在tf-idf中,training只是简单的扫描了一次语料库,然后计算了文档的所有特征频率。在训练其他模型时,比如LSA(Latent Semantic Analysis)或者LDA(Latent Dirichlet Allocation),会需要更复杂更耗时。

注意

转换器一般都在两个向量空间之间进行转换。训练和后续进行向量转换的向量空间必须是同一个,即为同一个特征集(ID也相同)。如果输入的特征空间不同,比如使用不同的字符串预处理方式,使用不同的特征ID,或者应该使用tf-idf向量却使用了词袋作为输入向量等,在进行使用转换器的时候就会导致特征不一致,进而得到垃圾输出和/或耗时超过预期。

转换向量

在这之前,tf-idf一直都是作为只读的对象,将向量从任意旧的表达(词袋计数)转换为新的表示(tf-idf加权):

doc_bow = [(0, 1), (1, 1)]
print(tfidf[doc_bow])  # 使用模型转换成向量

或在整个语料库中应用转换器:

corpus_tfidf = tfidf[corpus]
for doc in corpus_tfidf:
    print(doc)

在这个特定的例子中,我们转换了之前用来训练的向量。一旦转换器模型被初始化,它可以用在任何向量(当然它们应该属于同一向量空间),虽然这些向量并没有被用于训练。这些可以通过LSA中的折叠folding-in,或者LDA中的话题推断实现。

注意

调用模型时,仅会在原始语料库流外创建一个包装-实际的转换工作可以在文档迭代时随时进行。我们不能在调用corpus_transformed = model[corpus]时转换整个语料库,因为这意味着要将结果存储在内存中,这样与gensim的内存独立对象理念是相互违背的。如果你会在转换后的corpus_transformed上进行多次迭代,这样的转换成本会比较高,就需要先将结果语料库序列化以后存入硬盘然后再使用。

转换器也能够链式一个一个被序列化:

lsi_model = models.LsiModel(corpus_tfidf, id2word=dictionary, num_topics=2)  # 初始化LSI模型
corpus_lsi = lsi_model[corpus_tfidf]  # 在原始语料库上创建一个双包装:bow->tfidf->fold-in-lsi

在这里我们使用LSI(Latent Semantic Indexing)来将tf-idf语料库转化到一个隐藏2维空间(2维是因为设置了话题数量为2num_topics=2)。你现在可能在想:这两个隐藏维度是什么?让我们一起来看看models.LsiModel.print_topics()

lsi_model.print_topics(2)

(主题将被打印到日志中-可以在本节开头查看激活日志的方法)

根据LSI的结果,“tree”,"graph"和"minors"都是有关联的词(并且是第一个主题的主要内容),第二个主题则包含了所有其他的词。和预计的结果一致,前五个文档和第二个主题非常相关,后四个文档和第一个主题相关。

# bow->tfidf和tfidf->lsi转换器都在这里执行
for doc, as_text in zip(corpus_lsi, documents):
    print(doc, as_text)

可以使用save()和load()函数对模型进行存储和调用:

import os
import tempfile

with tempfile.NamedTemporaryFile(prefix='model-', suffix='.lsi', delete=False) as tmp:
    lsi_model.save(tmp.name)  # same for tfidf, lda, ...

loaded_lsi_model = models.LsiModel.load(tmp.name)

os.unlink(tmp.name)

接下来的问题就是:这些文档之间的相似度到底是多少?是不是有方法对这个相似度进行测量,比如对一个给定的输入文档,我们可以根据相似度对其他文档进行排序?下一节将对相似度查询进行介绍。

可用的转换器

gensim提供了一些比较常用的向量空间模型算法:

  • Term Frequency * Inverse Document Frequency, Tf-Idf,该算法在初始化时需要词袋(整数值)训练语料库。在转换时,它会将输入的向量转换成同一维度的另一个向量,并且认为特征值(在训练语料库中是稀疏的)会提高。因此它会保持原来的纬度,只将整数向量转换成实数向量。此外,它还可以同时对结果向量进行标准化。
model = models.TfidfModel(corpus, normalize=True)
  • Latent Semantic Indexing, LSI (or sometimes LSA)可以将文档从词袋或者tf-idf加权空间转换到低维的隐藏空间。在示例中仅使用了2维隐藏维度,但是在实际语料库中,目标维度的“金标准”是200-500。
model = models.LsiModel(tfidf_corpus, id2word=dictionary, num_topics=300)

LSI训练比较特别,可以在任何时候继续训练,只需要提供训练文档即可。这是通过在线训练(online training)模型来实现增量更新的。得益于这个特点,输入文档可以是无限大的,只需要将新文档送入LSI训练即可,同时用只读模式来调用算好的转换器。

model.add_documents(another_tfidf_corpus)  # 这里LSI在tfidf_corpus + another_tfidf_corpus上训练
lsi_vec = model[tfidf_vec]  # 将新文档转换到LSI空间,但是不对模型做改变

model.add_documents(more_documents)  # tfidf_corpus + another_tfidf_corpus + more_documents
lsi_vec = model[tfidf_vec]

查看gensim.models.lsimodel文档,了解如何在“无限”数据流的情况下让LSI忘记旧样本。同样里面还介绍了LSI算法的影响速度(affect speed)、内存记录(memory footprint)、精确度(numerical precision)等参数。

gensim使用了一个奇异在线增量数据流分布训练算法(novel online incremental streamed distributed training algorithm Řehůřek-2011)。gensim还使用了随机多路算法(stochastic multi-pass algorithm Halko, Martinsson, Tropp. 2009)来对内部计算进行加速。点击这里进一步了解如何在分布式计算中进行加速。

  • Random Projections, RP旨在对向量空间进行降维。这是一种通过加入随机来对文档tfidf向量距离进行近似计算的高效(对内存及CPU友好)方法。根据用户数据集的情况,建议目标维度在100/1000数量级。
model = models.RpModel(tfidf_corpus, num_topics=500)
  • Latent Dirichlet Allocation, LDA是将词袋计数转换到低维主题空间的另一种方法。LDA是LSA(也被称作多项式PCA)的概率扩展,所以LDA的主题可以看作是词的概率分布。和LSA类似,这些主题也是从训练语料库中自动推理的。文档则被看作是这些主题的混合。
model = models.LdaModel(corpus, id2word=dictionary, num_topics=100)

gensim使用了在线LDA参数估计Hoffman, Blei, Bach. 2010方法,并对其进行了修改使得该方法能在分布式环境使用。

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

gensim使用了该方法的快速在线应用Wang, Paisley, Blei. 2011。HDP是gensim新添加的模型,并且还在学术层面试用阶段,使用的时候请注意。

添加新的VSM转换器是比较复杂的;详情请查看API接口,或者直接查看python脚本

值得强调的是,这些都是可以增量使用的,不需要将整个训练语料库全部一次性加载到内存中。考虑到内存问题,gensim在考虑使用分布式计算,这个方法同时也能提高CPU的效率。如果你想参与测试、提供使用示例或者协助编写代码,请查看gensim开发者手册

相似度查询

本节内容主要介绍查询语料库中的相似文档:

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

创建语料库

首先需要创建一个语料库。这个步骤和之前教程中的一样,如果已经完成可以选择跳过。

from collections import defaultdict
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",
]

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
]

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
]

dictionary = corpora.Dictionary(texts)
corpus = [dictionary.doc2bow(text) for text in texts]

相似度界面

在之前的语料库与向量空间以及主题和转换器两节内容中,介绍了如何在向量空间模型中创建语料库以及如何在两个向量空间中进行转换。这样做的目的是为了定义文档间的相似度,或者说一个特定文档与一组文档的相似度(比如用户的查询问题与索引文档)。

我们将通过之前例子中使用过的语料库(来源于Deerwester et al.在1990年的文章“Indexing by Latent Semantic Analysis”)来展示这些功能是如何在gensim中是实现的。为了实现Deerwester的例子,我们首先使用语料库定义一个2维的LSI空间:

from gensim import models
lsi = models.LsiModel(corpus, id2word=dictionary, num_topics=2)

在本节教程中,只需要了解LSI的两件事:第一,它只是一个将向量从一个空间转换到另一个空间的转换器;第二,LSI可以识别词(在本例中为文档中的词)以及主题之间的模式和关系。本例中的LSI空间是2维(num_topics = 2)的,所以有两个主题,不过这是主观随意选择的。如果想进一步了解LSI请点击这里

假设用户查询问题是“人机交互”。我们需要将语料库中的9个文档,根据与提问的相关度进行降序排序。和现代搜索引擎不同的是,这里只考虑了单个方面的相似度-文字(词)表面的语义相似度。没有超链接、没有随机游走静态排序(random-walk static ranks),只是布尔关键词匹配的语义扩展:

doc = "Human computer interaction"
vec_bow = dictionary.doc2bow(doc.lower().split())
vec_lsi = lsi[vec_bow]  # 将问题转换到LSI空间
print(vec_lsi)

此外,我们将使用余弦相似度来计算两个向量的相似度。余弦相似度是向量空间模型中的标准测量方法,不过不管向量的概率分布是什么样子的,不同的相似度测量方法结果可能都是差不多的。

初始化查询结构

在进行相似度查询之前,需准备好所有要与接下来的查询进行比较的文档。在本例中,就是用来训练LSI模型并且转换到了2维LSA空间的9个文档。不过我们完全可以使用其他的语料库。

from gensim import similarities
index = similarities.MatrixSimilarity(lsi[corpus])  # 将语料库转换到LSI空间并进行索引

注意

similarities.MatrixSimilarity这个类仅适用于向量全部加载到内存的情况。比如,在使用这个类时,一个100w的文档在256维的LSI空间会消耗2G内存。

如果没有2G内存的话,可以使用similarities.Similarity类。这个类通过对硬盘文件索引的分割(碎片化),来对内存进行弹性操作。它会在内部使用similarities.MatrixSimilarity和similarities.SparseMatrixSimilarity,所以虽然看起来复杂,但在速度上还是很快的。

可以使用save()和load()函数来保存和调用索引:

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

这对所有类型的相似度索引都适用(similarities.Similarity, similarities.MatrixSimilarity, similarities.SparseMatrixSimilarity)。同样在接下来的内容里,索引可以是这里的任何类型的对象。在不确定的情况下,建议使用similarities.Similarity,因为它最具可扩展性,并且支持后续增量添加文档索引。

展示查询结果

可以通过以下方式获取9个文档与查询问题的相似度:

sims = index[vec_lsi]  #展示语料库的相似度查询结果
print(list(enumerate(sims)))

余弦相似度的结果在[-1, 1]之间(值越大越相似),故第一个文档的得分为0.99809301。

可以将这些结果进行降序排序:

sims = sorted(enumerate(sims), key=lambda item: -item[1])
for i, s in enumerate(sims):
    print(s, documents[i])

这里值得注意的是,在标准布尔全文索引的结果中,“The EPS user interface management system”和“Relation of user perceived response time to error measurement”这两个文档是不会出现的,因为它们和“人机交互”并没有共有词。然而,在使用LSI计算后,它们都有比较高的相似度得分,这正确并且更好的反映了它们与查询问题都属于“人机”主题。实际上,这种语义概括能力就是我们考虑使用转换器及主题建模的初衷。

参考文献

[Řehůřek. 2011] Subspace tracking for Latent Semantic Analysis.
[Halko, Martinsson, Tropp. 2009] Finding structure with randomness.
[Hoffman, Blei, Bach. 2010] Online learning for Latent Dirichlet Allocation.
[Wang, Paisley, Blei. 2011] Online variational inference for the hierarchical Dirichlet process.
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值