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

adding a new document to the term document matrix for similarity calculations

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

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

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

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

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

他写道:

For new documents what do I do when I get a new document doc(k)? Well, I have to compute the similarity of this document with all the previous ones, which doesn't require to build a whole matrix. I can just take the inner-product of doc(k) dot doc(j) for all previous j, and that result in S(k, j), which is great.

  1. 有没有人确切地理解他在这里的意思,或者有任何好的链接来解释这个相当晦涩的主题?他是对的吗?我不知何故认为,如果他是对的,用这个内积添加新文档的能力将取决于如上所述固定词汇表。

好的,我解决了它,花了很多时间,围绕这个主题的另一个 post 使我对它描述线性代数的方式感到困惑,并且没有提到它的一个方面可能对写它的人。

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

所以 vectorizer 是 sklearn.feature_extraction.text.vectorizer 的一个实例。我用vocabulary_的方法把已有的33篇课文的词汇拉出来了:

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

Pickle 这个字典以供将来使用,只是为了测试它是否有效,在包含 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

对代码或理论并不完全有信心,但我相信这是正确的并且已经奏效 - 布丁的证明就在吃的过程中,我后来的代码发现最相似的文档也与烹饪有关。将原文档改成其他几个主题,全部重新运行,相似度完全符合你的预期。

作为我对另一个答案的评论的 sequel:是的,缺少词汇会导致问题,至少对我来说是这样。问题是,在计算 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。从本质上讲,它只是一种向词汇表中添加单词的方法,而不必过多担心正确的索引。

无论如何,现在我们开始要添加到语料库中的查询。

 # 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])

Note from 2020: This part may not be required if you're in the relative future compared to 2018 and you have a newer version of scipy.

Oh-kay, now we have to merge the corpus matrix. That is problematic since the matrices aren't the same size any more. We have to resize the corpus matrix because now we (might) have more words in there and we can't merge them without making them the same size. The funny and sad thing about this is that scipy.sparse supports resizing matrices, but resizing CSR matrices isn't supported in the released version of scipy. Thus I installed the master branch of scipy from an arbitrary commit: pip install git+git://github.com/scipy/scipy.git@b8bf38c555223cca0bcc1e0407587c74ff4b3f2e#egg=scipy. PS! You need cython installed to build scipy in your own machine (just pip install cython).

- me in 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])

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

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

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