计算1D数组与2D数组中所有行之间的余弦相似度的有效方法

时间:2018-08-28 00:34:30

标签: python arrays numpy cosine-similarity

我有一个形状为(300, )的1D数组和一个形状为(400, 300)的2D数组。现在,我想计算此2D数组中的每一行与1D数组之间的余弦相似度。因此,我的结果应为(400, )形状,代表这些向量有多相似。

我的最初想法是使用for循环遍历2D数组中的行,然后计算向量之间的余弦相似度。有没有一种更快的广播方式替代方法?

这是一个人为的示例:

In [29]: vec = np.random.randn(300,)
In [30]: arr = np.random.randn(400, 300)

下面是我要计算一维数组之间相似度的方法:

inn = (vec * arr[0]).sum()  
vecnorm = numpy.sqrt((vec * vec).sum())  
rownorm = numpy.sqrt((arr[0] * arr[0]).sum())  
similarity_score = inn / vecnorm / rownorm  

如何将其概括为arr[0]被2D数组取代?

3 个答案:

答案 0 :(得分:2)

cos相似度的分子可以表示为矩阵乘法,然后分母就可以工作了:)。

a_norm = np.linalg.norm(a, axis=1)
b_norm = np.linalg.norm(b)
(a @ b) / (a_norm * b_norm)

其中a是2D数组,而b是1D数组(即矢量)

答案 1 :(得分:1)

您可以使用cdist

import numpy as np
from scipy.spatial.distance import cdist


x = np.random.rand(1, 300)
Y = np.random.rand(400, 300)

similarities = 1 - cdist(x, Y, metric='cosine')
print(similarities.shape)

输出

(1, 400)

请注意,cdist返回cosine_distance(更多here),即1 - cosine_similarity,因此您需要转换结果。

答案 2 :(得分:1)

以下是一种与@Bi Rico's post相同的方法,但对于einsum的计算使用的是norm-

den = np.sqrt(np.einsum('ij,ij->i',arr,arr)*np.einsum('j,j',vec,vec))
out = arr.dot(vec) / den

此外,我们可以使用vec.dot(vec)代替np.einsum('j,j',vec,vec)进行一些改进。

时间-

In [45]: vec = np.random.randn(300,)
    ...: arr = np.random.randn(400, 300)

# @Bi Rico's soln with norm
In [46]: %timeit (np.linalg.norm(arr, axis=1) * np.linalg.norm(vec))
10000 loops, best of 3: 100 µs per loop

In [47]: %timeit np.sqrt(np.einsum('ij,ij->i',arr,arr)*np.einsum('j,j',vec,vec))
10000 loops, best of 3: 77.4 µs per loop

在更大的数组上-

In [48]: vec = np.random.randn(3000,)
    ...: arr = np.random.randn(4000, 3000)

In [49]: %timeit (np.linalg.norm(arr, axis=1) * np.linalg.norm(vec))
10 loops, best of 3: 22.2 ms per loop

In [50]: %timeit np.sqrt(np.einsum('ij,ij->i',arr,arr)*np.einsum('j,j',vec,vec))
100 loops, best of 3: 8.18 ms per loop