【问题标题】:How to speed up computation of cosine similarity between set of vectors如何加快向量集之间余弦相似度的计算
【发布时间】:2018-07-28 01:06:12
【问题描述】:

我有一组向量(~30k),每个向量由fasttext生成的300个元素组成,每个向量代表一个实体的含义,我想计算所有实体之间的相似度,所以我迭代嵌套问题中的向量,O(N^2) 复杂度,在时间上不实用。

您能推荐我另一种计算方法吗,或者我该如何并行化它?

def calculate_similarity(v1, v2):
    """
    Calculate cosine distance between two vectors
    """
    n1 = np.linalg.norm(v1)
    n2 = np.linalg.norm(v2)
    return np.dot(v1, v2) / n1 / n2


similarities = {}
for ith_entity, ith_vector in vectors.items():
    for jth_entity, jth_vector in vectors.items():
        if ith_entity == jth_entity:
            continue
        if (ith_entity, jth_entity) in similarities.keys() or (jth_entity, ith_entity) in similarities.keys():
            continue
        similarities[(ith_entity, jth_entity)] = calculate_similarity(ith_vector, jth_vector)

【问题讨论】:

  • 300 个维度还是 300 个元素?
  • @MadPhysicist 元素,我来编辑一下
  • 不确定,但KMeans 之类的集群算法会在此处提供帮助吗?
  • 简单的加速思路:1) 使用对称性作为 i, j 的结果与 j, i 相同。即在所有项目上运行 i,但 j 仅从第一个项目到第 i 个项目。 2)提前对所有项目进行归一化,防止在循环内进行归一化。
  • 这个问题对 SO 来说太宽泛了。从您自己的研究开始,尝试一些事情,如果您当时遇到特定问题,请提出另一个问题。

标签: python multithreading multiprocessing python-multiprocessing cosine-similarity


【解决方案1】:

我尝试过对其进行矢量化。

import numpy as np
from itertools import combinations

np.random.seed(1)

vector_data = np.random.randn(3, 3)

v1, v2, v3 = vector_data[0], vector_data[1], vector_data[2]

def similarities_vectorized(vector_data):
    norms = np.linalg.norm(vector_data, axis=1)
    combs = np.stack(combinations(range(vector_data.shape[0]),2))
    similarities = (vector_data[combs[:,0]]*vector_data[combs[:,1]]).sum(axis=1)/norms[combs][:,0]/norms[combs][:,1]
    return combs, similarities

combs, similarities = similarities_vectorized(vector_data)

for comb, similarity in zip(combs, similarities):
    print(comb, similarity)

输出:

[0 1] -0.217095007411
[0 2] 0.894174618451
[1 2] -0.630555641519

将结果与问题中的代码进行比较:

def calculate_similarity(v1, v2):
    """
    Calculate cosine distance between two vectors
    """
    n1 = np.linalg.norm(v1)
    n2 = np.linalg.norm(v2)
    return np.dot(v1, v2) / n1 / n2

def calculate_simularities(vectors):
    similarities = {}
    for ith_entity, ith_vector in vectors.items():
        for jth_entity, jth_vector in vectors.items():
            if ith_entity == jth_entity:
                continue
            if (ith_entity, jth_entity) in similarities.keys() or (jth_entity, ith_entity) in similarities.keys():
                continue
            similarities[(ith_entity, jth_entity)] = calculate_similarity(ith_vector, jth_vector)
    return similarities

vectors = {'A': v1, 'B': v2, 'C': v3}

print(calculate_simularities(vectors))

输出:

{('A', 'B'): -0.21709500741113338, ('A', 'C'): 0.89417461845058566, ('B', 'C'): -0.63055564151883581}

当我在一组 300 个向量上运行时,向量化版本的速度大约快了 3.3 倍。

更新:

这个版本比原版快了大约 50 倍:

def similarities_vectorized2(vector_data):
    norms = np.linalg.norm(vector_data, axis=1)
    combs = np.fromiter(combinations(range(vector_data.shape[0]),2), dtype='i,i')
    similarities = (vector_data[combs['f0']]*vector_data[combs['f1']]).sum(axis=1)/norms[combs['f0']]/norms[combs['f1']]
    return combs, similarities

combs, similarities = similarities_vectorized2(vector_data)

for comb, similarity in zip(combs, similarities):
    print(comb, similarity)

输出:

(0, 1) -0.217095007411
(0, 2) 0.894174618451
(1, 2) -0.630555641519

【讨论】:

    【解决方案2】:

    您可以通过使用scipy 的距离模块摆脱嵌套循环,这很慢。

    假设 vectors = {'k1':v1, 'k2':v2, ..., 'km':vm}vi 是一个长度为 n 的 Python 列表。

    import numpy as np 
    from scipy.spatial import distance
    
    # transfrom vectors to m x n numpy array 
    data = np.array(list(vectors.values())
    
    # compute pairwise cosine distance 
    pws = distance.pdist(data, metric='cosine')
    

    pws 是压缩距离矩阵。它是一维的,按以下顺序保存距离:

    pws = np.array([ (k1, k2), (k1, k3), (k1, k4), ..., (k1, km),
                               (k2, k3), (k2, k4), ..., (k2, km),
                                          ...,
                                                       (km-1, km) ])
    

    还要注意distance.pdist 计算的是余弦距离,而不是余弦相似度。

    【讨论】:

      【解决方案3】:

      使用球树,我在一个非常大的具有形状的特征向量(16460,4096)上使用了它。首先使用下面的块构造一棵树

      from sklearn.neighbors import BallTree
      tree = BallTree(features_tsvd, metric = spatial.distance.cosine)
      

      现在要在树中搜索查询,请尝试以下操作:

      dists, ind = tree.query(query, k=10)
      

      【讨论】:

        猜你喜欢
        • 2017-09-07
        • 2016-03-06
        • 2019-12-27
        • 2010-10-05
        • 2017-03-19
        • 2016-10-28
        • 2013-06-24
        • 2021-07-19
        • 2018-01-04
        相关资源
        最近更新 更多