Python: косинусное сходство между двумя большими массивами

1

У меня два массива numpy:

Массив 1: 500 000 строк x 100 cols

Массив 2: 160 000 строк х 100 колос

Я хотел бы найти наибольшее сходство косинусов между каждой строкой в массиве 1 и массиве 2. Другими словами, я вычисляю сходства косинусов между первой строкой в массиве 1 и всеми строками в массиве 2 и нахожу максимальное подобие косинуса, а затем вычисляю сходства косинусов между второй строкой в массиве 1 и всеми строками в Массив 2 и найти максимальное совпадение косинусов; и сделайте это для остальной части массива 1.

В настоящее время я использую sklearn cosine_similarity() и делаю следующее, но она очень медленная. Интересно, есть ли более быстрый способ, который не требует многопроцессорности/многопоточности для выполнения того, что я хочу делать. Кроме того, массивы, которые у меня есть, не являются разреженными.

from sklearn.metrics.pairwise import cosine_similarity as cosine

results = []
for i in range(Array1.shape[0]):
     results.append(numpy.max(cosine(Array1[None,i,:], Array2)))
  • 0
    Если я не понял проблему, вы знаете, что всегда будет требоваться 80000000000 операций над строками?
  • 0
    Да ... вот почему это так медленно. Характер задачи следующий: Array2 - это числовые представления 160 тыс. Документов. Array1 - это числовые повторения 500 тыс. Документов. Я хочу выяснить, на какой из 160 тыс. Документов каждый из 500 тыс. Документов больше всего похож, поэтому используется косинусное сходство l.
Показать ещё 4 комментария
Теги:
numpy
scikit-learn
cosine-similarity

1 ответ

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

Итерация в Python происходит медленно. Всегда лучше "векторизовать" и максимально использовать операции numpy на массивах, которые передают работу на низкоуровневую реализацию numpy, которая выполняется быстро.

cosine_similarity уже векторизована. Поэтому идеальное решение просто будет включать cosine_similarity(A, B) где A и B - ваш первый и второй массивы. К сожалению, эта матрица составляет 500 000 на 160 000, что слишком велико, чтобы делать это в памяти (это вызывает ошибку).

Следующим лучшим решением является разделение A (по строкам) на большие блоки (вместо отдельных строк), чтобы результат все еще вписывался в память и перебирал их. Я нахожу для ваших данных, что использование 100 строк в каждом блоке соответствует памяти; гораздо больше, и это не сработает. Затем мы просто используем .max и получаем 100 максимальных значений для каждой итерации, которые мы можем собирать вместе в конце.

Таким образом, мы настоятельно рекомендуем сохранить дополнительное время. Формула для косинусного подобия двух векторов равна uv/| u || v | , и это косинус угла между ними. Поскольку мы итерации, мы продолжаем пересчитывать длины строк B каждый раз и отбрасывая результат. Хорошим способом для этого является использование того факта, что сходство косинусов не меняется, если вы масштабируете векторы (угол один и тот же). Таким образом, мы можем рассчитать всю длину строки только один раз и разделить на них, чтобы сделать векторы строк. И тогда мы вычисляем подобие косинуса просто как uv, что можно сделать для массивов с помощью матричного умножения. Я быстро проверил это, и это было примерно в 3 раза быстрее.

Объединяя все это:

import numpy as np

# Example data
A = np.random.random([500000, 100])
B = np.random.random([160000, 100])

# There may be a proper numpy method for this function, but it won't be much faster.
def normalise(A):
    lengths = (A**2).sum(axis=1, keepdims=True)**.5
    return A/lengths

A = normalise(A)
B = normalise(B)

results = []

rows_in_slice = 100

slice_start = 0
slice_end = slice_start + rows_in_slice

while slice_end <= A.shape[0]:

    results.append(A[slice_start:slice_end].dot(B.T).max(axis=1))

    slice_start += rows_in_slice
    slice_end = slice_start + rows_in_slice

result = np.concatenate(results)

Это занимает около 2 секунд на 1000 строк A для запуска. Таким образом, для ваших данных должно быть около 1000 секунд.

  • 0
    Спасибо! Это определенно ускоряет процесс.

Ещё вопросы

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