将新文档添加到术语文档矩阵以进行相似度计算

Posted

技术标签:

【中文标题】将新文档添加到术语文档矩阵以进行相似度计算【英文标题】:adding a new document to the term document matrix for similarity calculations 【发布时间】:2017-11-11 17:10:56 【问题描述】:

所以我知道有几种方法可以在文档语料库中找到最相似或说三个最相似的文档。我知道可能存在扩展问题,目前我有大约一万个文档,并且已经在大约 30 个子集上运行测试。这是我现在所拥有的,但如果这被证明是不可能或效率低下,我正在考虑研究 elasticsearch 或 doc2vec。

到目前为止,这些脚本运行得非常好,它们使用 spaCy 对文本进行标记,并使用 Sklearn TfidfVectorizer 来适应所有文档,并且发现了非常相似的文档。我注意到从管道中出来的 NumPy 对象的形状是 (33, 104354),这可能意味着 104354 词汇不包括所有 33 个文档中的停用词。这一步需要 20 分钟才能运行,但下一步是计算所有余弦相似度的矩阵乘法非常快,但我知道它可能会减慢,因为该矩阵得到数千行而不是 30 行。

如果您可以有效地将新文档添加到矩阵中,那么如果您保存了该计算的结果,那么初始计算是否需要十个小时甚至几天都没有关系。

    当我在 .向量化器上似乎有一个名为 vectorizer.fixed_vocabulary_ 的方法。我在谷歌或 SKlearn 中找不到这种方法。无论如何,当我运行该方法时,它会返回False。有谁知道这是什么?我认为如果可能的话修复词汇表可能很有用,否则将新文档添加到术语文档矩阵可能会很麻烦,尽管我不确定如何做到这一点。

有人在这里问了similar question,得到了投票,但没有人回答。

他写道:

对于新文件 当我得到一个新的文档 doc(k) 时我该怎么办?好吧,我必须计算这个文档与之前所有文档的相似度,这不需要构建整个矩阵。我可以对所有之前的 j 取 doc(k) dot doc(j) 的内积,得到 S(k, j),这很棒。

    有没有人明白他在这里的意思,或者有任何好的链接来解释这个相当晦涩的话题?他是对的吗?我不知何故认为,如果他是对的,那么用这个内积添加新文档的能力将取决于修复上面提到的词汇表。

【问题讨论】:

fixed_vocabulary_ 对应于构造函数中的vocabulary 参数。它只告诉它是否发送了自定义词汇表,或者它是从提供的数据中学习的。 谢谢,我明白了:词汇表:映射或可迭代,可选。一个映射(例如,一个字典),其中键是术语,值是特征矩阵中的索引,或者是可迭代的术语。如果没有给出,则从输入文档中确定一个词汇表。 必须找到一个词汇表然后给它一个。 【参考方案1】:

作为我对另一个答案的评论的续集:是的,缺少词汇会导致问题,至少对我来说确实如此。问题是在计算 tf-idf 值(或其他值)时,不考虑不在词汇表中的单词。想象一下,当我们有一个句子“This is karamba”时。并且只有前两个词在词汇表中,然后他们得到更高的分数,因为“karamba”是一个未知词,根本没有得分。因此,“this”和“is”在句子中比“karamba”在词汇表中更重要(请记住,karamba 是我们在这个句子中真正想要查看的唯一词)。

好的,如果语料库中根本没有“karamba”,为什么会有问题呢?因为我们在“this”和“is”非常重要的基础上得到了很多误报,即使它们有点meh。 我是怎么解决的?不方便,但可行。

首先,我按照另一个答案中的建议创建我的语料库词汇。

import copy
from sklearn.feature_extraction.text import TfidfVectorizer
from collections import defaultdict

corpus = []

# Populate the corpus with some data that I have
for d in sorted(os.listdir('d'), key=lambda x: int(x.split('.')[0])):
    with open(os.path.join('d', d)) as f:
        corpus.append(f.read())

corpus_tfidf_vectorizer = TfidfVectorizer()
corpus_tfidf_matrix = corpus_tfidf_vectorizer.fit_transform(corpus)

corpus_vocabulary = defaultdict(None, copy.deepcopy(corpus_tfidf_vectorizer.vocabulary_))
corpus_vocabulary.default_factory = corpus_vocabulary.__len__

为什么是defaultdict?这是我从TfidfVectorizer 中的词汇创建实现中偷来的一个巧妙的技巧。如果您想查看它,请查看sklearn.feature_extraction.text.CountVectorizer._count_vocab。从本质上讲,它只是一种将单词添加到词汇表中的方法,而无需过多担心正确的索引。

Anywhoo,现在我们开始处理要添加到语料库中的查询。

 # Let's say I got a query value from somewhere
 query = f.read()

 query_vocabulary_vectorizer = TfidfVectorizer()
 query_vocabulary_vectorizer.fit_transform([query])
 for word in query_vocabulary_vectorizer.vocabulary_.keys():
     # Added with proper index if not in vocabulary
     corpus_vocabulary[word]

 # Nice, everything in the vocabulary now!

 query_tfidf_matrix = TfidfVectorizer(vocabulary=corpus_vocabulary).fit_transform([query])

2020 年的注意事项:如果您与 2018 年相比处于相对的未来,并且您拥有更新版本的 scipy,则可能不需要此部分。

哦,好吧,现在我们必须合并语料库矩阵。这是有问题的 因为矩阵的大小不再相同。我们必须调整大小 语料库矩阵,因为现在我们(可能)有更多的词在那里 如果不使它们具有相同的大小,我们就无法合​​并它们。有趣的和 可悲的是scipy.sparse 支持调整大小 矩阵,但在已发布的版本中不支持调整 CSR 矩阵的大小 scipy 的版本。因此我安装了scipymaster 分支 来自任意提交:pip install git+git://github.com/scipy/scipy.git@b8bf38c555223cca0bcc1e0407587c74ff4b3f2e#egg=scipy。 PS!您需要安装cython 以在您自己的机器上构建scipy (只是pip install cython)。

- 2018 年的我

所以这很麻烦,但现在我们可以愉快地声明:

from scipy import sparse as sp

corpus_tfidf_matrix.resize((corpus_tfidf_matrix.shape[0], query_tfidf_matrix.shape[1]))
# And voilà, we can merge now!
tfidf_matrix = sp.vstack([corpus_tfidf_matrix, query_tfidf_matrix])

Bam,完成。另一个答案仍然是正确的,我只是在详细说明该解决方案。

【讨论】:

感谢@wanaryytel 的回答!我想你可能有错字。在代码的第一部分,您使用corpus_vocabulary = defaultdict(None, copy.deepcopy(corpus_count_vectorizer.vocabulary_))。我认为应该是corpus_tfidf_vectorizer 代替corpus_count_vectorizer 另一条评论@wanaryytel:您正在代码的第二块中创建query_vocabulary,但是据我所知,您没有使用它。当您迭代以将查询中的单词添加到 corpus_vocabulary 时,您将迭代 query_vocabulary_vectorizer 而不是 query_vocabulary。干杯:) @ayalaall 嗯,确实,我从一个更大的代码库中浓缩了这个例子,所以很明显我搞砸了一些地方。 :) query_vocabulary 确实没有使用,但我认为 query_vocabulary_vectorizer.fit_transform([query]) 调用仍然是填充内部词汇所必需的......对吗?自从我这样做以来已经有一段时间了。 :) 无论如何,修复它,谢谢! 我同意!您仍然需要致电query_vocabulary_vectorizer.fit_transform([query])【参考方案2】:

我们将拟合然后将新消息添加到经过训练的模型中

tf_one = TfidfVectorizer(analyzer='word', stop_words = "english", lowercase = True)
X_train_one = tf_one.fit_transform(X_train)
nb_one = MultinomialNB()
nb_one.fit(X_train_one , Y_train)

# When you receive a new document
X = tf_one.transform([mymessage_X])
prediction = nb_one.predict(X)
print(prediction)


 # New document 
mymessage_X ="This message will be added to the existing model"
label_Y=" your label"



 tf_two = TfidfVectorizer(analyzer='word', stop_words = "english", lowercase = True ,vocabulary = tf_one.vocabulary_)

X_train_two = tf_two.fit_transform([mymessage_X])
nb = MultinomialNB()
nb.fit(X_train_two, [label_Y])

#print the length of the tf_two vocabulary
len(tf_two.vocabulary_)    

from scipy.sparse import vstack as vstack_sparse_matrices
p_combined = vstack_sparse_matrices([X_train_one, X_train_two])
print (p_combined.shape) 

pairwise_similarity = (p_combined * p_combined.T).A
pairwise_similarity

【讨论】:

【参考方案3】:

好的,我解决了,花了好几个小时,围绕这个主题的另一篇文章让我对它描述线性代数的方式感到困惑,并且没有提到它的一个方面,这对于编写它的人来说可能是显而易见的。

非常感谢您提供有关词汇的信息..

所以矢量化器是sklearn.feature_extraction.text.vectorizer 的一个实例。我用vocabulary_的方法把现有33篇文本的词汇拉出来了:

v = vectorizer.vocabulary_
print (type(v))
>> dict
print (len(v))
>> 104354

腌制此字典以备将来使用,只是为了测试它是否有效,在包含 TfidfVectorizer 的管道对象上重新运行 fit_transform,并使用参数 vocabulary=v

原始的成对相似矩阵由 pairwise_similarity = (p * p.T).A 其中 p 是拟合的管道对象,也是术语文档矩阵。

添加了一个小的新文档:

new_document= """

Remove the lamb from the fridge 1 hour before you want to cook it, to let it come up to room temperature. Preheat the oven to 200ºC/400ºC/gas 6 and place a roasting dish for the potatoes on the bottom. Break the garlic bulb up into cloves, then peel 3, leaving the rest whole.
"""

使用现在固定的词汇表使管道仅适用于一个文档:

p_new = pipe.fit_transform([new_document]) 
print (p_new.shape)
> (1, 104354)

然后像这样把它们放在一起:

from scipy.sparse import vstack as vstack_sparse_matrices
p_combined = vstack_sparse_matrices([p, p_new])
print (p_combined.shape)
>> (34, 104354)

并重新计算成对相似度方程:

pairwise_similarity = (p_combined * p_combined.T).A

对代码或理论并不完全有信心,但我相信这是正确的并且有效 - 布丁的证据在于吃东西,我后来的代码发现最相似的文件也与烹饪有关。将原始文档更改为其他几个主题并重新运行它,它们的相似之处完全符合您的预期。

【讨论】:

谢谢!我对优化我的代码有同样的想法,但不知道该怎么做——你帮了我。但是这种方法有一个警告(这可能不是问题,但值得注意)。当您为TfidfVectorizer 指定词汇表时,将不会添加新词——至少对我来说没有。因此,如果new_document 中有任何不在词汇表中的单词,那么它们将被完全忽略并忘记。不确定在某些情况下是否存在问题。 我知道,当我构建我的应用程序时 - 努力使用尽可能多的文档来组装最终达到数十万的词汇表,因为我知道它会从那得到修复点上(不重复该练习)。假设所有重要的东西都已经在那个词汇中了。很高兴你发现这很有用,因为我上次使用它已经有一段时间了.. 谢谢!很有用。您对如何从组合矩阵计算线性核或余弦相似度有任何想法吗?或者也许如何获得新文档的相似度分数?

以上是关于将新文档添加到术语文档矩阵以进行相似度计算的主要内容,如果未能解决你的问题,请参考以下文章

R中的余弦相似度矩阵

如何计算两个文档的相似度

文本相似度算法

在 python 中使用余弦相似度返回与查询文档相比最相似的文档

如何有效地计算文档流中的文档之间的相似性

如何使用python通过余弦相似度有效地检索前K相似文档?