Python tf-idf: 快速更新tf-idf矩阵的方法

8

我有一个包含数千行文本的数据集,我的目标是计算tfidf分数以及文档之间的余弦相似度。我使用Python中的gensim库和教程完成了这个任务:

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

tfidf = models.TfidfModel(corpus)
corpus_tfidf = tfidf[corpus]
index = similarities.MatrixSimilarity(corpus_tfidf)

假设我们已经建立了tfidf矩阵和相似度,当有新的文档进来时,我想查询现有数据集中与其最相似的文档。

问题:有没有办法更新tf-idf矩阵,以便无需将新文本文档附加到原始数据集并重新计算整个过程?

2个回答

2

让我分享一下我的想法。

一个是语料库,另一个是模型,还有一个是查询。有时候很容易混淆它们。

1) 语料库和模型

语料库是一组文档,即您的图书馆,其中每个文档以某种格式表示。例如,Corpus_BOW将您的文档表示为词袋。Corpus_TFIDF通过它们的TFIDF表示您的文档。

模型是将语料库表示转换为另一种表示的东西。例如,Model_TFIDFCorpus_BOW --> Corpus_TFIDF。您可以拥有其他模型,例如用于Corpus_TFIDF --> Corpus_LSI或Corpus_BOW --> Corpus_LSI的模型。

我认为这是神奇的Gensim的主要性质,是一个语料库转换器。目标是找到更好地表示应用程序文档之间相似性的语料库表示。

一对重要的思想:

  • 首先,模型始终从输入语料库构建,例如: Model_TFIDF = models.TfidfModel(Corpus_BOW, id2word = yourDictionary)
  • 其次,如果您想要以某种格式(Corpus_TFIDF)使用您的语料库,则需要首先构建模型(Model_TFIDF),然后转换您的输入语料库:Corpus_TFIDF = Model_TFIDF[Corpus_BOW]

因此,我们首先使用输入语料库构建模型,然后将该模型应用于相同的输入语料库,以获得输出语料库。也许可以合并一些步骤,但这些是概念上的步骤。

2) 查询和更新

可以将给定的模型应用于新文档,以获取新文档的TFIDF。例如,New_Corpus_TFIDF = Model_TFIDF[New_Corpus_BOW]。但这只是查询。该模型未更新为新的语料库/文档。也就是说,该模型是使用原始语料库建模,并像原来一样与新文档一起使用。

当新文档只是短用户查询时,这很有用,我们希望在原始语料库中找到最相似的文档。或者当我们只有一个新文档时,我们希望在我们的语料库中找到最相似的文档。在这些情况下,如果您的语料库足够大,则不需要更新模型。

假设您的库、语料库是活的,您想要使用新文档更新模型,就像它们从一开始就存在一样。有些模型可以通过提供新文档来进行更新。例如,models.LsiModel 有一个 "add_documents" 方法,用于将新语料库包含在 LSI 模型中(因此,如果您使用 Corpus_BOW 构建了它,只需提供 New_Corpus_BOW 即可进行更新)。
但是,TFIDF 模型没有这个 "add_documents" 方法。我不知道是否有一种复杂而聪明的数学方法来克服这个问题,但事实是 TFIDF 的 "IDF" 部分取决于完整的语料库(以前和新的)。因此,如果您添加了一个新文档,则每个以前文档的 IDF 都会发生变化。更新 TFIDF 模型的唯一方法是重新计算它。
无论如何,请注意,即使您可以更新模型,然后您需要再次将其应用于入口语料库,以获得输出语料库,并重新构建相似性。
正如之前有人所说,如果您的库足够大,您可以使用原始的 TFIDF 模型并将其应用于新文档,而无需更新模型。结果可能已经足够好了。然后,当新文档数量很大时,您需要重新构建 TFIDF 模型。

从概念上讲,只要您知道文档的总数,就可以更新IDF部分,而无需完整的语料库。例如,如果一个术语在10个文档中具有0.5的文档频率(为简单起见,省略对数缩放和倒数),则添加一个不包含该术语的文档会将文档频率降低到0.45(11个文档中的5个),无需实际文档本身。 - Swier

2

因为没有其他答案,我将发布我的解决方案。假设我们处于以下情况:

import gensim
from gensim import models
from gensim import corpora
from gensim import similarities
from nltk.tokenize import word_tokenize
import pandas as pd

# routines:
text = "I work on natural language processing and I want to figure out how does gensim work"
text2 = "I love computer science and I code in Python"
dat = pd.Series([text,text2])
dat = dat.apply(lambda x: str(x).lower()) 
dat = dat.apply(lambda x: word_tokenize(x))


dictionary = corpora.Dictionary(dat)
corpus = [dictionary.doc2bow(doc) for doc in dat]
tfidf = models.TfidfModel(corpus)
corpus_tfidf = tfidf[corpus]


#Query:
query_text = "I love icecream and gensim"
query_text = query_text.lower()
query_text = word_tokenize(query_text)
vec_bow = dictionary.doc2bow(query_text)
vec_tfidf = tfidf[vec_bow]

如果我们看一下:

print(vec_bow)
[(0, 1), (7, 1), (12, 1), (15, 1)]

并且:

print(tfidf[vec_bow])
[(12, 0.7071067811865475), (15, 0.7071067811865475)]

请注意id和doc:

print(dictionary.items())

[(0, u'and'),
 (1, u'on'),
 (8, u'processing'),
 (3, u'natural'),
 (4, u'figure'),
 (5, u'language'),
 (9, u'how'),
 (7, u'i'),
 (14, u'code'),
 (19, u'in'),
 (2, u'work'),
 (16, u'python'),
 (6, u'to'),
 (10, u'does'),
 (11, u'want'),
 (17, u'science'),
 (15, u'love'),
 (18, u'computer'),
 (12, u'gensim'),
 (13, u'out')]

看起来查询只选择了现有的术语,并使用预先计算的权重来给您tfidf分数。所以我的解决方法是每周或每日重建模型,因为这样做很快。


2
这真的有效吗?我本以为由于tfidf的性质,基本上无法增量更新模型(更新tfidf矩阵),因为每次有新文档进来时,您都必须在整个语料库中更新所有相关单词特征的IDF值。此外,当出现一个包含新单词的文档时会发生什么-难道您不会有一个特征长度不匹配的问题吗?请告诉我,因为我也在积极研究这个问题。 - PyRsquared
它可以工作,但我认为它只是使用现有模型查询您的新文档。我将编辑我的答案以展示这项工作。 - snowneji
哇!这真的很酷——非常感谢您分享这个。所以,如果我理解正确,当一个新的查询文档进来时,gensim会从预先计算好的tfidf矩阵和新的查询文档中计算tfidf分数?还是只从预先计算好的tfidf矩阵中计算?如果有不断涌入的新查询,特别是更新模型很昂贵的情况下,定期更新模型更有意义。 - PyRsquared
还没有查看源代码,但是由于实际查询仅发生在此行代码 tfidf [vec_bow] 中,我认为它仅查询预先计算的 tfidf 矩阵而不更新任何内容。所以你是对的,定期更新可以弥补更新部分。 - snowneji
1
我最近遇到了类似的问题。谢谢。我对如何增量更新矩阵感到相当困惑。 - Chao
很遗憾您没有找到解决方案。 - Cerin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接