2个数字表的余弦相似度

我想计算两个列表之间的余弦相似度,让我们以列表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))
487421 次浏览
import math
from itertools import izip


def dot_product(v1, v2):
return sum(map(lambda x: x[0] * x[1], izip(v1, v2)))


def cosine_measure(v1, v2):
prod = dot_product(v1, v2)
len1 = math.sqrt(dot_product(v1, v1))
len2 = math.sqrt(dot_product(v2, v2))
return prod / (len1 * len2)

你可以在计算后四舍五入:

cosine = format(round(cosine_measure(v1, v2), 3))

如果你想让它真的很短,你可以使用下面的一行代码:

from math import sqrt
from itertools import izip


def cosine_measure(v1, v2):
return (lambda (x, y, z): x / sqrt(y * z))(reduce(lambda x, y: (x[0] + y[0] * y[1], x[1] + y[0]**2, x[2] + y[1]**2), izip(v1, v2), (0, 0, 0)))

你应该试试SciPy。它有一堆有用的科学例程,例如“数值计算积分、求解微分方程、优化和稀疏矩阵的例程”。它使用超高速优化的NumPy进行数字处理。安装请参见在这里

注意,space .distance.cos计算距离,而不是相似度。因此,必须从1减去该值才能得到相似

from scipy import spatial


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

我想性能在这里不太重要,但我忍不住。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倍。

可以从sklearn.metrics.pairwise 文档中使用cosine_similarity函数

In [23]: from sklearn.metrics.pairwise import cosine_similarity


In [24]: cosine_similarity([[1, 0, -1]], [[-1,-1, 0]])
Out[24]: array([[-0.5]])

你可以在Python中使用简单的函数来实现:

def get_cosine(text1, text2):
vec1 = text1
vec2 = text2
intersection = set(vec1.keys()) & set(vec2.keys())
numerator = sum([vec1[x] * vec2[x] for x in intersection])
sum1 = sum([vec1[x]**2 for x in vec1.keys()])
sum2 = sum([vec2[x]**2 for x in vec2.keys()])
denominator = math.sqrt(sum1) * math.sqrt(sum2)
if not denominator:
return 0.0
else:
return round(float(numerator) / denominator, 3)
dataSet1 = [3, 45, 7, 2]
dataSet2 = [2, 54, 13, 15]
get_cosine(dataSet1, dataSet2)

我根据问题中的几个答案做了一个基准,下面的代码片段被认为是最好的选择:

def dot_product2(v1, v2):
return sum(map(operator.mul, v1, v2))




def vector_cos5(v1, v2):
prod = dot_product2(v1, v2)
len1 = math.sqrt(dot_product2(v1, v1))
len2 = math.sqrt(dot_product2(v2, v2))
return prod / (len1 * len2)

结果让我很惊讶,基于scipy的实现不是最快的。我分析发现,scipy中的余弦需要大量时间从python列表转换到numpy数组。

enter image description here

你可以使用这个简单的函数来计算余弦相似度:

def cosine_similarity(a, b):
return sum([i*j for i,j in zip(a, b)])/(math.sqrt(sum([i*i for i in a]))* math.sqrt(sum([i*i for i in b])))

使用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))

不使用任何导入

math.sqrt (x)

可以用

x * * 5

如果不使用numpy.dot(),您必须使用列表理解创建自己的dot函数:

def dot(A,B):
return (sum(a*b for a,b in zip(A,B)))

然后它只是一个应用余弦相似度公式的简单问题:

def cosine_similarity(a,b):
return dot(a,b) / ( (dot(a,a) **.5) * (dot(b,b) ** .5) )

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

假设你有两个__abc0维的__abc1, v1v2,即它们的形状都是(n,)。以下是如何获得它们的余弦相似度:

import torch
import torch.nn as nn


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

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

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

所有答案都非常适合不能使用NumPy的情况。如果可以的话,这里有另一种方法:

def cosine(x, y):
dot_products = np.dot(x, y.T)
norm_products = np.linalg.norm(x) * np.linalg.norm(y)
return dot_products / (norm_products + EPSILON)

还要记住EPSILON = 1e-07来确保除法的安全。

另一个版本,如果你有一个场景,你有一个向量列表和一个查询向量,你想要计算查询向量与列表中所有向量的余弦相似度,你可以用下面的方式一次性完成:

>>> import numpy as np


>>> A      # list of vectors, shape -> m x n
array([[ 3, 45,  7,  2],
[ 1, 23,  3,  4]])


>>> B      # query vector, shape -> 1 x n
array([ 2, 54, 13, 15])


>>> similarity_scores = A.dot(B)/ (np.linalg.norm(A, axis=1) * np.linalg.norm(B))


>>> similarity_scores
array([0.97228425, 0.99026919])
我们可以用简单的数学方程轻松计算余弦相似度。 Cosine_similarity = 1-(向量的点积/向量范数的积)。我们可以定义两个函数,分别用于计算点积和范数
def dprod(a,b):
sum=0
for i in range(len(a)):
sum+=a[i]*b[i]
return sum


def norm(a):


norm=0
for i in range(len(a)):
norm+=a[i]**2
return norm**0.5


cosine_a_b = 1-(dprod(a,b)/(norm(a)*norm(b)))

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嵌入(1000000x512)中找到形状为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()

对于更高级的相似性搜索,您可以使用MilvusWeaviate


这里有一个实现,也适用于矩阵。它的行为完全像sklearn余弦相似度:

def cosine_similarity(a, b):
return np.divide(
np.dot(a, b.T),
np.linalg.norm(
a,
axis=1,
keepdims=True
)
@ # matrix multiplication
np.linalg.norm(
b,
axis=1,
keepdims=True
).T
)
@符号代表矩阵乘法。看到 “at"(@)符号在Python中做什么? < / p >

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

from scipy import spatial


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

注意,spatial.distance.cosine()给了你一个dissimilarity (distance)值,因此要得到相似度,你需要从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))