给定新文档重新计算相似度矩阵

问题描述

我正在运行一个包含文本文档的实验,我需要计算它们之间的(余弦)相似度矩阵(用于其他计算)。为此,我使用sklearn的TfidfVectorizer

corpus = [doc1,doc2,doc3,doc4]
vect = TfidfVectorizer(min_df=1,stop_words="english",use_idf=False) 
tfidf = vect.fit_transform(corpus)
similarities = tfidf * tfidf.T
pairwise_similarity_matrix = similarities.A

问题在于,随着实验的每次迭代,我都会发现需要添加到相似性矩阵中的新文档,并根据我正在使用的文档数量(成千上万更多)-这非常耗时。

我希望找到一种方法,仅计算新一批文档与现有文档之间的相似度,而无需再次计算整个数据集。

请注意,我使用的是术语频率(tf)表示形式,而不使用反向文档频率(idf),因此从理论上讲,我不需要每次都重新计算整个矩阵。

解决方法

好的,我明白了。 就像我说的那样,该想法是仅计算新一批文件和现有文件之间的相似度,而它们之间的相似度不变。问题是要用新出现的术语来更新TfidfVectorizer的词汇表。

解决方案包括2个步骤:

  1. 更新词汇表和tf矩阵。
  2. 矩阵乘法和堆叠。

这是整个脚本-我们首先获得了原始语料库以及经过训练和计算的对象和矩阵:

corpus = [doc1,doc2,doc3]
# Build for the first time:
vect = TfidfVectorizer(min_df=1,stop_words="english",use_idf=False) 
tf_matrix = vect.fit_transform(corpus)
similarities = tf_matrix * tf_matrix.T
similarities_matrix = similarities.A # just for printing

现在,有了新文件:

new_docs_corpus = [docx,docy,docz] # New documents
# Building new vectorizer to create the parsed vocabulary of the new documents:
new_vect = TfidfVectorizer(min_df=1,use_idf=False) 
new_vect.fit(new_docs_corpus)

# Merging old and new vocabs:
new_terms_count = 0
for k,v in new_vect.vocabulary_.items():
    if k in vect.vocabulary_.keys():
        continue
    vect.vocabulary_[k] = np.int64(len(vect.vocabulary_)) # important not to assign a simple int
    new_terms_count = new_terms_count + 1
new_vect.vocabulary_ = vect.vocabulary_

# Build new docs represantation using the merged vocabulary:
new_tf_matrix = new_vect.transform(new_docs_corpus)
new_similarities = new_tf_matrix * new_tf_matrix.T

# Get the old tf-matrix with the same dimentions:
if new_terms_count:
    zero_matrix = csr_matrix((tfidf.shape[0],new_terms_count))
    tf_matrix = hstack([tf_matrix,zero_matrix])
# tf_matrix = vect.transform(corpus) # Instead,we just append 0's for the new terms and stack the tf_matrix over the new one,to save time
cross_similarities = new_tf_matrix * tf_matrix.T # Calculate cross-similarities
tf_matrix = vstack([tf_matrix,new_tfidf])
# Stack it all together:
similarities = vstack([hstack([similarities,cross_similarities.T]),hstack([cross_similarities,new_similarities])])
similarities_matrix = similarities.A

# Updating the corpus with the new documents:
corpus = corpus + new_docs_corpus

我们可以通过比较计算得出的similarities_matrix和在联合语料库上训练TfidfVectorizer时得到的corpus + new_docs_corpus来验证这一点。

正如评论中所讨论的,我们之所以能做所有这一切,是因为我们没有使用idf(反文档频率)元素,这会在给定新文档的情况下更改现有文档的表示形式。