我想计算两个列表之间的余弦相似度,比如说,列表1是dataSetI,列表2是dataSetII。

假设dataSetI是[3,45,7,2],dataSetII是[2,54,13,15]。列表的长度总是相等的。我想将余弦相似度报告为0到1之间的数。

dataSetI = [3, 45, 7, 2]
dataSetII = [2, 54, 13, 15]

def cosine_similarity(list1, list2):
  # How to?
  pass

print(cosine_similarity(dataSetI, dataSetII))

当前回答

使用numpy比较一个数字列表和多个列表(矩阵):

def cosine_similarity(vector,matrix):
   return ( np.sum(vector*matrix,axis=1) / ( np.sqrt(np.sum(matrix**2,axis=1)) * np.sqrt(np.sum(vector**2)) ) )[::-1]

其他回答

另一个仅基于numpy的版本

from numpy import dot
from numpy.linalg import norm

cos_sim = dot(a, b)/(norm(a)*norm(b))

你可以使用SciPy(最简单的方法):

from scipy import spatial

dataSetI = [3, 45, 7, 2]
dataSetII = [2, 54, 13, 15]
print(1 - spatial.distance.cosine(dataSetI, dataSetII))

注意,space .distance.cos()给出了一个不相似度(距离)值,因此要获得相似度,需要从1中减去该值。

另一种解决方法是自己编写函数,甚至考虑不同长度的列表的可能性:

def cosineSimilarity(v1, v2):
  scalarProduct = moduloV1 = moduloV2 = 0

  if len(v1) > len(v2):
    v2.extend(0 for _ in range(len(v1) - len(v2)))
  else:
    v2.extend(0 for _ in range(len(v2) - len(v1)))

  for i in range(len(v1)):
    scalarProduct += v1[i] * v2[i]
    moduloV1 += v1[i] * v1[i]
    moduloV2 += v2[i] * v2[i]

  return round(scalarProduct/(math.sqrt(moduloV1) * math.sqrt(moduloV2)), 3)

dataSetI = [3, 45, 7, 2]
dataSetII = [2, 54, 13, 15]
print(cosineSimilarity(dataSetI, dataSetII))

Python代码计算:

余弦距离 余弦相似度 角距离 角相似


import math

from scipy import spatial


def calculate_cosine_distance(a, b):
    cosine_distance = float(spatial.distance.cosine(a, b))
    return cosine_distance


def calculate_cosine_similarity(a, b):
    cosine_similarity = 1 - calculate_cosine_distance(a, b)
    return cosine_similarity


def calculate_angular_distance(a, b):
    cosine_similarity = calculate_cosine_similarity(a, b)
    angular_distance = math.acos(cosine_similarity) / math.pi
    return angular_distance


def calculate_angular_similarity(a, b):
    angular_similarity = 1 - calculate_angular_distance(a, b)
    return angular_similarity

相似性搜索:

如果你想在嵌入数组中找到最接近的余弦相似度,你可以使用Tensorflow,就像下面的代码。

在我的测试中,在不到一秒钟(使用GPU)的时间内,在1M嵌入(1' 000,000 '000 x512)中找到形状为1x512的嵌入的最接近值。

import time

import numpy as np  # np.__version__ == '1.23.5'
import tensorflow as tf  # tf.__version__ == '2.11.0'

EMBEDDINGS_LENGTH = 512
NUMBER_OF_EMBEDDINGS = 1000 * 1000


def calculate_cosine_similarities(x, embeddings):
    cosine_similarities = -1 * tf.keras.losses.cosine_similarity(x, embeddings)
    return cosine_similarities.numpy()


def find_closest_embeddings(x, embeddings, top_k=1):
    cosine_similarities = calculate_cosine_similarities(x, embeddings)
    values, indices = tf.math.top_k(cosine_similarities, k=top_k)
    return values.numpy(), indices.numpy()


def main():
    # x shape: (512)
    # Embeddings shape: (1000000, 512)
    x = np.random.rand(EMBEDDINGS_LENGTH).astype(np.float32)
    embeddings = np.random.rand(NUMBER_OF_EMBEDDINGS, EMBEDDINGS_LENGTH).astype(np.float32)

    print('Embeddings shape: ', embeddings.shape)

    n = 100
    sum_duration = 0
    for i in range(n):
        start = time.time()
        best_values, best_indices = find_closest_embeddings(x, embeddings, top_k=1)
        end = time.time()

        duration = end - start
        sum_duration += duration

        print('Duration (seconds): {}, Best value: {}, Best index: {}'.format(duration, best_values[0], best_indices[0]))

    # Average duration (seconds): 1.707 for Intel(R) Core(TM) i7-10700 CPU @ 2.90GHz
    # Average duration (seconds): 0.961 for NVIDIA 1080 ti
    print('Average duration (seconds): ', sum_duration / n)


if __name__ == '__main__':
    main()

对于更高级的相似度搜索,你可以使用Milvus, Weaviate或Faiss。


https://en.wikipedia.org/wiki/Cosine_similarity https://gist.github.com/amir-saniyan/e102de09b01c4ed1632e3d1a1a1cbf64

我想性能在这里不太重要,但我忍不住。zip()函数完全复制了两个向量(实际上更像是矩阵转置),只是为了以“python”顺序获取数据。计算具体实现的时间会很有趣:

import math
def cosine_similarity(v1,v2):
    "compute cosine similarity of v1 to v2: (v1 dot v2)/{||v1||*||v2||)"
    sumxx, sumxy, sumyy = 0, 0, 0
    for i in range(len(v1)):
        x = v1[i]; y = v2[i]
        sumxx += x*x
        sumyy += y*y
        sumxy += x*y
    return sumxy/math.sqrt(sumxx*sumyy)

v1,v2 = [3, 45, 7, 2], [2, 54, 13, 15]
print(v1, v2, cosine_similarity(v1,v2))

Output: [3, 45, 7, 2] [2, 54, 13, 15] 0.972284251712

这将经历一次提取一个元素的类似c的噪音,但不进行批量数组复制,并在单个for循环中完成所有重要的工作,并使用单个平方根。

ETA:更新打印调用为函数。(最初的版本是Python 2.7,不是3.3。当前在Python 2.7下运行,使用from __future__ import print_function语句。)无论哪种方式,输出都是相同的。

3.0GHz Core 2 Duo上的CPYthon 2.7.3

>>> timeit.timeit("cosine_similarity(v1,v2)",setup="from __main__ import cosine_similarity, v1, v2")
2.4261788514654654
>>> timeit.timeit("cosine_measure(v1,v2)",setup="from __main__ import cosine_measure, v1, v2")
8.794677709375264

所以,在这种情况下,非python的方式要快3.6倍。

如果你碰巧已经在使用PyTorch,你应该使用他们的cosessimilarity实现。

假设有两个n维的numpy。ndarray, v1和v2,即它们的形状都是(n,)。以下是如何获得它们的余弦相似度:

import torch
import torch.nn as nn

cos = nn.CosineSimilarity()
cos(torch.tensor([v1]), torch.tensor([v2])).item()

或者假设有两个numpy。ndarray w1和w2,它们的形状都是(m, n)。下面给你一个余弦相似度列表,每个都是w1中的一行和w2中的相应行之间的余弦相似度:

cos(torch.tensor(w1), torch.tensor(w2)).tolist()