0%

gensim — 主题与转换

gensim学习笔记,主题与转换 ,点击链接可访问官网原文。

如果你想把事件记录在日志立,首先设置日志:

1
2
In [1]: import logging
In [2]: logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

转换接口

在上一节关于文档库和向量空间的教程中,我们创建了一个表示为向量流的文档语料库。继续,让我们启动gensim并使用该文档库:

1
2
3
4
5
6
7
8
9
10
In [3]: import os

In [4]: 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")
...:
Used files generated from first tutorial

MmCorpus(9个文档,12个特征,28个非零条目)

在本教程中,我将演示如何将文档从一个向量表示转换为另一个向量表示。这个过程有两个目标:

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

创建转换

转换是标准的python对象,一般通过训练文档库来初始化:

1
In [5]: tfidf = models.TfidfModel(corpus)  # 第一步 — 初始化模型

我们使用上一个教程中的旧文档库来初始化(训练)变换模型。不同的变换可能需要不同的初始化参数;在TfIdf的情况下,“训练”包括简单地经历所提供的文档库一次并计算其所有特征的文档频率。训练其他模型,如潜在语义分析(Latent Semantic Analysis)或Latent Dirichlet Allocation,需要更多的参与,因此,需也要更多的时间。

注意:转换总是在两个特定向量空间之间进行。必须使用相同的向量空间(=相同的特征ids集)用于训练以及后续向量转换。如果不使用相同的输入特征空间,例如应用不同的字符串预处理,使用不同的特征id,或者使用预期的TfIdf向量的单词包输入向量,将导致在转换调用期间的特征不匹配,输出和/或运行时异常。

转换向量

从现在开始,tfidf被视为只读对象,可以用于将任何向量从旧的表示(词袋整数计数)转换为新的表示(TfIdf实值权重):

1
2
3
4
In [6]: doc_bow = [(0,1),(1,1)]

In [7]: print(tfidf[doc_bow]) # 第二步 — 使用这个模型转换向量
[(0, 0.7071067811865476), (1, 0.7071067811865476)]

或者对整个文档库应用转换:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
In [9]: corpus_tfidf = tfidf[corpus]

In [10]: for doc in corpus_tfidf:
....: print(doc)
....:
[(0, 0.5773502691896257), (1, 0.5773502691896257), (2, 0.5773502691896257)]
[(2, 0.44424552527467476), (3, 0.44424552527467476), (4, 0.3244870206138555), (5, 0.3244870206138555), (6, 0.44424552527467476), (7, 0.44424552527467476)]
[(1, 0.5710059809418182), (4, 0.4170757362022777), (5, 0.4170757362022777), (8, 0.5710059809418182)]
[(0, 0.49182558987264147), (4, 0.7184811607083769), (8, 0.49182558987264147)]
[(3, 0.6282580468670046), (5, 0.45889394536615247), (6, 0.6282580468670046)]
[(9, 1.0)]
[(9, 0.7071067811865475), (10, 0.7071067811865475)]
[(9, 0.5080429008916749), (10, 0.5080429008916749), (11, 0.695546419520037)]
[(7, 0.6282580468670046), (10, 0.45889394536615247), (11, 0.6282580468670046)]

在这种特殊情况下,我们正在转换我们用于训练的同一个文档库,但这种情况并不常见。一旦变换模型已经被初始化,它可以用于任何向量(当然,假设它们来自相同的向量空间),即使它们在训练文档库中没有使用。这是通过称为折叠LSA的过程,通过LDA等的主题推断来实现的。

注意:调用模型[corpus]只会在旧文档库文档流周围创建一个包装器 - 实际转换是在文档迭代期间即时完成的。我们不能在调用corpus_transformed = model [corpus]时转换整个语料库,因为这意味着将结果存储在主内存中,这与gensim的内存独立性的目标相矛盾。如果您将多次迭代转换的corpus_transformed,转换成本较高,应将生成的文档库首先序列化 到磁盘,然后继续使用。

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

1
2
3
In [11]: lsi = models.LsiModel(corpus_tfidf,id2word=dictionary,num_topics=2) #  初始化一个LSI转换

In [12]: corpus_lsi=lsi[corpus_tfidf] # 在原始文档库上创建一个双重包装:bow-> tfidf-> fold-in-lsi

在这里,我们通过潜在语义索引(Latent Semantic Indexing )将我们的Tf-Idf文档库转换为潜在的2-D空间(2-D,因为我们设置num_topics = 2)。现在你可能想知道:这两个潜在维度代表什么?让我们用models.LsiModel.print_topics()检查:
1
2
3
4
5
6
In [13]: lsi.print_topics(2)
Out[13]:
[(0,
'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"'),
(1,
'0.460*"system" + 0.373*"user" + 0.332*"eps" + 0.328*"interface" + 0.320*"time" + 0.320*"response" + 0.293*"computer" + 0.280*"human" + 0.171*"survey" + -0.161*"trees"')]

(主题打印到日志 - 请参阅本页顶部关于激活日志记录的注释)

看来根据LSI,“trees”,“graph”和“minors”都是相关词(并且对第一主题的方向贡献最大),而第二主题实际上与所有其他词相关。如预期,前五个文件与第二个主题更加密切相关,而其余四个文件与第一个主题相关:

1
2
3
4
5
6
7
8
9
10
11
12
In [14]: for doc in corpus_lsi: # 在这里,实时执行bow-> tfidf和tfidf-> lsi变换
....: print(doc)
....:
[(0, 0.066007833960902151), (1, 0.5200703306361848)] # "Human machine interface for lab abc computer applications"
[(0, 0.19667592859142344), (1, 0.76095631677000575)] # "A survey of user opinion of computer system response time"
[(0, 0.08992639972446273), (1, 0.72418606267525132)] # "The EPS user interface management system"
[(0, 0.075858476521779988), (1, 0.63205515860034323)] # "System and human system engineering testing of EPS"
[(0, 0.1015029918498003), (1, 0.57373084830029641)] # "Relation of user perceived response time to error measurement"
[(0, 0.70321089393783165), (1, -0.16115180214025676)] # "The generation of random binary unordered trees"
[(0, 0.87747876731198382), (1, -0.16758906864659251)] # "The intersection graph of paths in trees"
[(0, 0.90986246868185872), (1, -0.14086553628718851)] # "Graph minors IV Widths of trees and well quasi ordering"
[(0, 0.61658253505692862), (1, 0.05392907566389505)] # "Graph minors A survey"

模型持久性通过save()和load()来实现:
1
2
3
In [15]: lsi.save('/tmp/model.lsi') 

In [16]: lsi = models.LsiModel.load('/tmp/model.lsi')

下一个问题可能是:这些文档之间的相似程度如何?有没有一种方式来形式化相似性,所以对于给定的输入文档,我们可以根据他们的相似性订购一些其他文档集?相似性查询在下一个教程中介绍。

可用的转换器

Gensim实现了以下几个常用向量空间模型算法:

  • 术语频率*逆文档频率,Tf-Idf 期望在初始化期间的词袋(整数值)训练语料库。在转换期间,它将输入一个向量并返回相同维度的另一个向量,除了在训练语文档库中罕见的特征将具有增加的值以外。因此,它将整数值向量转换为实值向量,同时保持维数不变。它还可以任选地将所得载体标准化为(欧几里得)单位长度。
    1
    In [18]: model = models.TfidfModel(corpus,normalize=True)
  • 潜在语义索引,LSI (或有时LSA)将文档从字词袋或(优选地)TfIdf加权空间转换为较低维度的潜在空间。对于上面的测试文档库,我们只使用了2个潜在维度,但在真实文档库中,目标维度200-500被推荐为“黄金标准”。LSI训练是独一无二的,因为我们可以随时继续“训练”,只需提供更多的训练文档。这是通过对基础模型的增量更新,在称为在线学习的过程中完成的。由于这个特征,输入文档流甚至可以是无限的 - 只是在其到达时保持馈送LSI新文档,同时使用计算的变换模型作为只读。有关如何使LSI逐渐“忘记”无限流中的旧的观察的详细信息,请参阅gensim.models.lsimodel 文档。如果你想使坏,还有一些参数可以调整影响速度与内存占用与LSI算法的数值精度。 gensim使用了一个新的在线增量流分布式训练算法(相当一口气!),我已在这儿 发表。 gensim还执行Halko等人的随机多遍算法。参见英语维基百科上的实验 ,通过在计算机集群上分布计算,进一步加快速度。
  • 随机投影 ,旨在减少向量空间维数。这是一种非常有效的(内存和CPU友好)方法,通过投入一个随机性来近似文档之间的TfIdf距离。推荐的目标维度是数百/千,取决于您的数据集。
  • 潜在Dirichlet分配Latent Dirichlet Allocation, LDA ,LDA是另一个从词袋数量到较低维度的主题空间的转换。 LDA是LSA(也称为多项PCA)的概率扩展,因此LDA的主题可以被解释为词语的概率分布。这些分布,就像LSA一样,从训练文档库自动推断。文档又被解释为这些主题的(软)混合(就像LSA一样)。

值得重申的是,这些算法都是独特的,增量实现,其不要求整个训练文档库同时加载于主存储器中。为占用尽量少的内存,我正在改进分布式计算,以同时提高CPU效率。如果你觉得你可以贡献(通过测试,提供用例或代码),请让作者 作者知道。