Каков самый быстрый способ в Python вычислить косинусное сходство с учетом разреженных матричных данных?

учитывая разреженный список матриц, каков наилучший способ вычисления косинусного сходства между каждым из столбцов (или строк) в матрице? Я бы предпочел не повторять n-choose-два раза.

скажем, входная матрица:

A= 
[0 1 0 0 1
 0 0 1 1 1
 1 1 0 1 0]

разреженное представление:

A = 
0, 1
0, 4
1, 2
1, 3
1, 4
2, 0
2, 1
2, 3

в Python легко работать с матричным форматом ввода:

import numpy as np
from sklearn.metrics import pairwise_distances
from scipy.spatial.distance import cosine

A = np.array(
[[0, 1, 0, 0, 1],
[0, 0, 1, 1, 1],
[1, 1, 0, 1, 0]])

dist_out = 1-pairwise_distances(A, metric="cosine")
dist_out

выдает:

array([[ 1.        ,  0.40824829,  0.40824829],
       [ 0.40824829,  1.        ,  0.33333333],
       [ 0.40824829,  0.33333333,  1.        ]])

это нормально для входа с полной матрицей, но я очень хочу начать с разреженного представления (из-за размера и разреженности моя матрица). Есть идеи, как это лучше всего сделать? Спасибо заранее.

7 ответов


вы можете вычислить попарное косинусное сходство на строках разреженной матрицы непосредственно с помощью sklearn. Начиная с версии 0.17, он также поддерживает разреженные выход:

from sklearn.metrics.pairwise import cosine_similarity
from scipy import sparse

A =  np.array([[0, 1, 0, 0, 1], [0, 0, 1, 1, 1],[1, 1, 0, 1, 0]])
A_sparse = sparse.csr_matrix(A)

similarities = cosine_similarity(A_sparse)
print('pairwise dense output:\n {}\n'.format(similarities))

#also can output sparse matrices
similarities_sparse = cosine_similarity(A_sparse,dense_output=False)
print('pairwise sparse output:\n {}\n'.format(similarities_sparse))

результаты:

pairwise dense output:
[[ 1.          0.40824829  0.40824829]
[ 0.40824829  1.          0.33333333]
[ 0.40824829  0.33333333  1.        ]]

pairwise sparse output:
(0, 1)  0.408248290464
(0, 2)  0.408248290464
(0, 0)  1.0
(1, 0)  0.408248290464
(1, 2)  0.333333333333
(1, 1)  1.0
(2, 1)  0.333333333333
(2, 0)  0.408248290464
(2, 2)  1.0

Если вы хотите, чтобы сходства Косинуса по столбцам просто транспонировали вашу входную матрицу заранее:

A_sparse.transpose()

следующий метод примерно в 30 раз быстрее, чем scipy.spatial.distance.pdist. Он работает довольно быстро на больших матрицах (при условии, что у вас достаточно ОЗУ)

см. ниже обсуждение того, как оптимизировать для разреженности.

# base similarity matrix (all dot products)
# replace this with A.dot(A.T).toarray() for sparse representation
similarity = numpy.dot(A, A.T)


# squared magnitude of preference vectors (number of occurrences)
square_mag = numpy.diag(similarity)

# inverse squared magnitude
inv_square_mag = 1 / square_mag

# if it doesn't occur, set it's inverse magnitude to zero (instead of inf)
inv_square_mag[numpy.isinf(inv_square_mag)] = 0

# inverse of the magnitude
inv_mag = numpy.sqrt(inv_square_mag)

# cosine similarity (elementwise multiply by inverse magnitudes)
cosine = similarity * inv_mag
cosine = cosine.T * inv_mag

если ваша проблема типична для крупномасштабных двоичных проблем предпочтений, у вас есть намного больше записей в одном измерении, чем в другом. Кроме того, короткое измерение-это то, записи которого вы хотите рассчитать сходства между ними. Назовем это измерение измерение "номенклатура".

если это так, перечислите свои "элементы" в строках и создайте A используя scipy.sparse. Затем замените первую строку, как указано.

если ваша проблема нетипична вам понадобится больше изменений. Это должны быть довольно простые замены basic numpy операции с их scipy.sparse эквиваленты.


Я пробовал некоторые методы выше. Однако эксперимент @zbinsd имеет свои ограничения. Разреженность матрицы, используемой в эксперименте, чрезвычайно низка, в то время как реальная разреженность обычно превышает 90%. В моем состоянии разреженность имеет форму (7000, 25000) и разреженность 97%. Метод 4 очень медленный, и я не могу терпеть получение результатов. Я использую метод 6, который заканчивается через 10 секунд. Удивительно, но я пробую метод ниже, и он завершен только в 0.247 s.

import sklearn.preprocessing as pp

def cosine_similarities(mat):
    col_normed_mat = pp.normalize(mat.tocsc(), axis=0)
    return col_normed_mat.T * col_normed_mat

этот эффективный метод связан Введите описание ссылки здесь


Я взял все эти ответы и написал сценарий для 1. проверьте каждый из результатов (см. утверждение ниже) и 2. посмотрим, какой быстрее. Код и результаты ниже:

# Imports
import numpy as np
import scipy.sparse as sp
from scipy.spatial.distance import squareform, pdist
from sklearn.metrics.pairwise import linear_kernel
from sklearn.preprocessing import normalize
from sklearn.metrics.pairwise import cosine_similarity

# Create an adjacency matrix
np.random.seed(42)
A = np.random.randint(0, 2, (10000, 100)).astype(float).T

# Make it sparse
rows, cols = np.where(A)
data = np.ones(len(rows))
Asp = sp.csr_matrix((data, (rows, cols)), shape = (rows.max()+1, cols.max()+1))

print "Input data shape:", Asp.shape

# Define a function to calculate the cosine similarities a few different ways
def calc_sim(A, method=1):
    if method == 1:
        return 1 - squareform(pdist(A, metric='cosine'))
    if method == 2:
        Anorm = A / np.linalg.norm(A, axis=-1)[:, np.newaxis]
        return np.dot(Anorm, Anorm.T)
    if method == 3:
        Anorm = A / np.linalg.norm(A, axis=-1)[:, np.newaxis]
        return linear_kernel(Anorm)
    if method == 4:
        similarity = np.dot(A, A.T)

        # squared magnitude of preference vectors (number of occurrences)
        square_mag = np.diag(similarity)

        # inverse squared magnitude
        inv_square_mag = 1 / square_mag

        # if it doesn't occur, set it's inverse magnitude to zero (instead of inf)
        inv_square_mag[np.isinf(inv_square_mag)] = 0

        # inverse of the magnitude
        inv_mag = np.sqrt(inv_square_mag)

        # cosine similarity (elementwise multiply by inverse magnitudes)
        cosine = similarity * inv_mag
        return cosine.T * inv_mag
    if method == 5:
        '''
        Just a version of method 4 that takes in sparse arrays
        '''
        similarity = A*A.T
        square_mag = np.array(A.sum(axis=1))
        # inverse squared magnitude
        inv_square_mag = 1 / square_mag

        # if it doesn't occur, set it's inverse magnitude to zero (instead of inf)
        inv_square_mag[np.isinf(inv_square_mag)] = 0

        # inverse of the magnitude
        inv_mag = np.sqrt(inv_square_mag).T

        # cosine similarity (elementwise multiply by inverse magnitudes)
        cosine = np.array(similarity.multiply(inv_mag))
        return cosine * inv_mag.T
    if method == 6:
        return cosine_similarity(A)

# Assert that all results are consistent with the first model ("truth")
for m in range(1, 7):
    if m in [5]: # The sparse case
        np.testing.assert_allclose(calc_sim(A, method=1), calc_sim(Asp, method=m))
    else:
        np.testing.assert_allclose(calc_sim(A, method=1), calc_sim(A, method=m))

# Time them:
print "Method 1"
%timeit calc_sim(A, method=1)
print "Method 2"
%timeit calc_sim(A, method=2)
print "Method 3"
%timeit calc_sim(A, method=3)
print "Method 4"
%timeit calc_sim(A, method=4)
print "Method 5"
%timeit calc_sim(Asp, method=5)
print "Method 6"
%timeit calc_sim(A, method=6)

результаты:

Input data shape: (100, 10000)
Method 1
10 loops, best of 3: 71.3 ms per loop
Method 2
100 loops, best of 3: 8.2 ms per loop
Method 3
100 loops, best of 3: 8.6 ms per loop
Method 4
100 loops, best of 3: 2.54 ms per loop
Method 5
10 loops, best of 3: 73.7 ms per loop
Method 6
10 loops, best of 3: 77.3 ms per loop

вы должны проверить scipy.sparse (ссылке). Вы можете применить операции к этим разреженным матрицам так же, как вы используете обычную матрицу.


привет вы можете сделать это таким образом

    temp = sp.coo_matrix((data, (row, col)), shape=(3, 59))
    temp1 = temp.tocsr()

    #Cosine similarity
    row_sums = ((temp1.multiply(temp1)).sum(axis=1))
    rows_sums_sqrt = np.array(np.sqrt(row_sums))[:,0]
    row_indices, col_indices = temp1.nonzero()
    temp1.data /= rows_sums_sqrt[row_indices]
    temp2 = temp1.transpose()
    temp3 = temp1*temp2

построение решения Ваали:

def sparse_cosine_similarity(sparse_matrix):
    out = (sparse_matrix.copy() if type(sparse_matrix) is csr_matrix else
           sparse_matrix.tocsr())
    squared = out.multiply(out)
    sqrt_sum_squared_rows = np.array(np.sqrt(squared.sum(axis=1)))[:, 0]
    row_indices, col_indices = out.nonzero()
    out.data /= sqrt_sum_squared_rows[row_indices]
    return out.dot(out.T)

это принимает разреженную матрицу (предпочтительно csr_matrix) и возвращает csr_matrix. Он должен делать более интенсивные части, используя разреженные вычисления с довольно минимальными затратами памяти. Я не тестировал его широко, хотя, так что будьте осторожны emptor (обновление: я уверен в этом решении теперь, когда я протестировал и сравнил его)

кроме того, вот разреженная версия решения Waylon в случае, если это помогает никому, не уверен, какое решение на самом деле лучше.

def sparse_cosine_similarity_b(sparse_matrix):
    input_csr_matrix = sparse_matrix.tocsr()
    similarity = input_csr_matrix * input_csr_matrix.T
    square_mag = similarity.diagonal()
    inv_square_mag = 1 / square_mag
    inv_square_mag[np.isinf(inv_square_mag)] = 0
    inv_mag = np.sqrt(inv_square_mag)
    return similarity.multiply(inv_mag).T.multiply(inv_mag)

оба решения, похоже, имеют четность со sklearn.метрика.попарно.cosine_similarity

: - D

обновление:

теперь я протестировал оба решения против моей существующей реализации Cython: https://github.com/davidmashburn/sparse_dot/blob/master/test/benchmarks_v3_output_table.txt и похоже, что первый алгоритм выполняет лучшее из трех большую часть времени.