计算一维数组与二维数组中所有行之间余弦相似度的有效方法
Efficient way to compute cosine similarity between 1D array and all rows in a 2D array
我有一个形状为 (300, )
的一维数组和一个形状为 (400, 300)
的二维数组。现在,我想计算此二维数组中每一行与一维数组之间的余弦相似度。因此,我的结果应该是 (400, )
的形状,表示这些向量的相似程度。
我最初的想法是使用 for
循环遍历二维数组中的行,然后计算向量之间的余弦相似度。是否有使用广播方法更快的替代方法?
这是一个人为的例子:
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]
被二维数组替换?
您可以使用 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
returnscosine_distance
(更多here),即1 - cosine_similarity
所以需要转换结果
cos相似度的分子可以表示为矩阵相乘,然后分母就可以了:).
a_norm = np.linalg.norm(a, axis=1)
b_norm = np.linalg.norm(b)
(a @ b) / (a_norm * b_norm)
其中 a
是二维数组,b
是一维数组(即向量)
这里的方法与 相同,但 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
我有一个形状为 (300, )
的一维数组和一个形状为 (400, 300)
的二维数组。现在,我想计算此二维数组中每一行与一维数组之间的余弦相似度。因此,我的结果应该是 (400, )
的形状,表示这些向量的相似程度。
我最初的想法是使用 for
循环遍历二维数组中的行,然后计算向量之间的余弦相似度。是否有使用广播方法更快的替代方法?
这是一个人为的例子:
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]
被二维数组替换?
您可以使用 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
returnscosine_distance
(更多here),即1 - cosine_similarity
所以需要转换结果
cos相似度的分子可以表示为矩阵相乘,然后分母就可以了:).
a_norm = np.linalg.norm(a, axis=1)
b_norm = np.linalg.norm(b)
(a @ b) / (a_norm * b_norm)
其中 a
是二维数组,b
是一维数组(即向量)
这里的方法与 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