У меня есть один массив 1D формы (300, )
и 2D массив формы (400, 300)
. Теперь я хочу вычислить сходство косинусов между каждой из строк в этом двумерном массиве с массивом 1D. Таким образом, мой результат должен иметь форму (400, )
которая представляет собой сходство этих векторов.
Моя первоначальная идея состоит в том, чтобы перебирать строки в 2D-массиве с использованием цикла for
а затем вычислять сходство косинусов между векторами. Существует ли более быстрая альтернатива, использующая метод широковещания?
Вот надуманный пример:
In [29]: vec = np.random.randn(300,)
In [30]: arr = np.random.randn(400, 300)
Ниже я хочу рассчитать сходство между 1D массивами:
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]
был заменен двумерным массивом?
Здесь один следующий тот же метод, что и с сообщением @Bi Rico 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 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
Числитель подобия соосности может быть выражен как матрица умножить, а затем знаменатель должен просто работать :).
a_norm = np.linalg.norm(a, axis=1)
b_norm = np.linalg.norm(b)
(a @ b) / (a_norm * b_norm)
где a
- двумерный массив, а b
- 1D-массив (т.е. вектор)
cdist
от scipy.
Вы можете использовать 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
(подробнее здесь), то есть 1 - cosine_similarity
поэтому вам нужно преобразовать результат.