非常大数据集的余弦相似度

Posted

技术标签:

【中文标题】非常大数据集的余弦相似度【英文标题】:Cosine similarity for very large dataset 【发布时间】:2019-05-21 09:02:00 【问题描述】:

我在计算 100 维向量的大型列表之间的余弦相似度时遇到问题。当我使用 from sklearn.metrics.pairwise import cosine_similarity 时,我在 16 GB 的机器上得到了 MemoryError。每个数组都非常适合我的记忆,但在 np.dot() 内部调用期间我得到了 MemoryError

这是我的用例以及我目前的处理方式。

这是我的 100 维父向量,我需要将其与其他 500,000 个相同维度(即 100)的不同向量进行比较

parent_vector = [1, 2, 3, 4 ..., 100]

这是我的子向量(本例中包含一些虚构的随机数)

child_vector_1 = [2, 3, 4, ....., 101]
child_vector_2 = [3, 4, 5, ....., 102]
child_vector_3 = [4, 5, 6, ....., 103]
.......
.......
child_vector_500000 = [3, 4, 5, ....., 103]

我的最终目标是获得与父向量具有非常高余弦相似度的前 N ​​个子向量(其名称如 child_vector_1 及其对应的余弦分数)。

我目前的方法(我知道这种方法效率低且消耗内存):

第 1 步:创建以下形状的超级数据框

parent_vector         1,    2,    3, .....,    100   
child_vector_1        2,    3,    4, .....,    101   
child_vector_2        3,    4,    5, .....,    102   
child_vector_3        4,    5,    6, .....,    103   
......................................   
child_vector_500000   3,    4,    5, .....,    103

第2步:使用

from sklearn.metrics.pairwise import cosine_similarity
cosine_similarity(df)

获取所有向量之间的成对余弦相似度(如上图所示)

第 3 步:制作一个元组列表以存储 key(例如 child_vector_1)和值(例如所有此类组合的余弦相似度数)。

第 4 步: 使用列表中的sort() 获取前 N 个 - 这样我就可以得到子向量名称以及它与父向量的余弦相似度分数。

PS:我知道这非常低效,但我想不出更好的方法 更快地计算每个子向量之间的余弦相似度的方法 和父向量,并获取前 N 个值。

任何帮助将不胜感激。

【问题讨论】:

您能否提供一些示例数据以供使用。另外,请提供关于小样本数据和所需输出的工作解决方案,以便 SO 社区可以提出更有效的替代方案。 @sgokhales 即使我也面临同样的问题。您解决了问题吗? 【参考方案1】:

即使您的 (500000, 100) 数组(父级及其子级)适合内存 它的任何成对度量都不会。其原因是,顾名思义,成对度量计算任何两个孩子的距离。为了存储这些距离,您需要一个 (500000,500000) 大小的浮点数组,如果我的计算正确的话,这将占用大约 100 GB 的内存。

谢天谢地,有一个简单的解决方案可以解决您的问题。如果我理解正确,您只想知道孩子和父母之间的距离,这将导致长度为 500000 的向量很容易存储在内存中。

为此,您只需向 cosine_similarity 提供仅包含 parent_vector 的第二个参数

import pandas as pd
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

df = pd.DataFrame(np.random.rand(500000,100)) 
df['distances'] = cosine_similarity(df, df.iloc[0:1]) # Here I assume that the parent vector is stored as the first row in the dataframe, but you could also store it separately

n = 10 # or however many you want
n_largest = df['distances'].nlargest(n + 1) # this contains the parent itself as the most similar entry, hence n+1 to get n children

希望能解决您的问题。

【讨论】:

即使我面临同样的问题,我的数据框大小为(32593, 12),我需要计算所有对的余弦相似度,即 32593*32593,它不适合内存。这种情况我该如何处理?【参考方案2】:

这个解决方案非常快

child_vectors = np.array(child_vector_1, child_vector_2, ....., child_vector_500000)
input_norm = parent_vector / np.linalg.norm(parent_vector, axis=-1)[:, np.newaxis]
embed_norm =  child_vectors/ np.linalg.norm(child_vectors, axis=-1)[:, np.newaxis]
cosine_similarities = np.sort(np.round(np.dot(input_norm, embed_norm.T), 3)[0])[::-1]
paiswise_distances = 1 - cosine_similarities

【讨论】:

你能提供一个时间估计吗?【参考方案3】:

我什至无法将整个语料库放入内存中,因此我的解决方案是逐渐加载它并在较小的批次上计算余弦相似度,始终保留最少/最多的 n(取决于您的用例)类似项目:

data = []
iterations = 0
with open('/media/corpus.txt', 'r') as f:
    for line in f:
        data.append(line)
        if len(data) <= 1000:
            pass
        else:
            print('Getting bottom k, iteration x'.format(x=iterations))
            data = get_bottom_k(data, 500)
            iterations += 1
filtered = get_bottom_k(data, 500) # final most different 500 texts in corpus


def get_bottom_k(corpus:list, k:int):
    pairwise_similarity = make_similarity_matrix(corpus) # returns pairwise similarity matrix
    sums = csr_matrix.sum(pairwise_similarity, axis=1)  # Similarity index for each item in corpus. Bigger > more
    sums = np.squeeze(np.asarray(sums))
    # similar to other txt.
    indexes = np.argpartition(sums, k, axis=0)[:k] # Bottom k in terms of similarity (-k for top and [-k:])
    return [corpus[i] for i in indexes]

到目前为止,这是一个最佳解决方案,但它是我迄今为止发现的最简单的解决方案,也许它会对某人有所帮助。

【讨论】:

以上是关于非常大数据集的余弦相似度的主要内容,如果未能解决你的问题,请参考以下文章

余弦计算相似度理解以及计算

求问:余弦相似度和皮尔逊相关系数的区别

余弦相似度

余弦相似度的应用

Spark笔记(1) :余弦相似度计算

20-余弦相似度及其R实现