У меня два массива 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)))
Итерация в 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 секунд.