Эффективный способ вычисления косинусного сходства между одномерным массивом и всеми строками в двумерном массиве

1

У меня есть один массив 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] был заменен двумерным массивом?

  • 0
    Каким будет ваш результат (300,)? если у вас есть 400 векторов для «проверки», то ваш результат будет (400,), и простой точечный продукт подойдет ...
  • 0
    @Julien спасибо, что нашли опечатку. исправил это
Показать ещё 2 комментария
Теги:
arrays
numpy
cosine-similarity

3 ответа

1
Лучший ответ

Здесь один следующий тот же метод, что и с сообщением @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
2

Числитель подобия соосности может быть выражен как матрица умножить, а затем знаменатель должен просто работать :).

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

где a - двумерный массив, а b - 1D-массив (т.е. вектор)

  • 0
    Этот подход в 10 раз быстрее, чем метод использования cdist от scipy.
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 (подробнее здесь), то есть 1 - cosine_similarity поэтому вам нужно преобразовать результат.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню