Точечное произведение двух разреженных матриц, влияющих только на нулевые значения
Я пытаюсь вычислить простой точечный продукт, но оставить ненулевые значения из исходной матрицы без изменений. Пример игрушки:
import numpy as np
A = np.array([[2, 1, 1, 2],
[0, 2, 1, 0],
[1, 0, 1, 1],
[2, 2, 1, 0]])
B = np.array([[ 0.54331039, 0.41018682, 0.1582158 , 0.3486124 ],
[ 0.68804647, 0.29520239, 0.40654206, 0.20473451],
[ 0.69857579, 0.38958572, 0.30361365, 0.32256483],
[ 0.46195299, 0.79863505, 0.22431876, 0.59054473]])
ожидаемый результат:
C = np.array([[ 2. , 1. , 1. , 2. ],
[ 2.07466874, 2. , 1. , 0.73203386],
[ 1. , 1.5984076 , 1. , 1. ],
[ 2. , 2. , 1. , 1.42925865]])
фактические матрицы, о которых идет речь, однако, разрежены и выглядят более так:
A = sparse.rand(250000, 1700, density=0.001, format='csr')
B = sparse.rand(1700, 1700, density=0.02, format='csr')
одним из простых способов было бы просто установить значения с помощью индекса маски, например:
mask = A != 0
C = A.dot(B)
C[mask] = A[mask]
однако мои исходные массивы разрежены и довольно большие, поэтому их изменение через назначение индекса болезненно медленно. Преобразование матрицы лил немного помогает, Но опять же, само преобразование занимает много времени.
другой очевидный подход, я думаю, будет просто прибегать к итерации и пропускать замаскированные значения, но я бы не хотел отказываться от преимуществ умножения массива NumPy/scipy.
некоторые уточнения: меня на самом деле интересует какой-то особый случай, где B
- это всегда квадрат, и поэтому, A
и C
имеют одинаковую форму. Так что если есть решение, которое не работает на произвольных массивах, но подходит в моем случае, это нормально.
обновление: некоторые попытки:
from scipy import sparse
import numpy as np
def naive(A, B):
mask = A != 0
out = A.dot(B).tolil()
out[mask] = A[mask]
return out.tocsr()
def proposed(A, B):
Az = A == 0
R, C = np.where(Az)
out = A.copy()
out[Az] = np.einsum('ij,ji->i', A[R], B[:, C])
return out
%timeit naive(A, B)
1 loops, best of 3: 4.04 s per loop
%timeit proposed(A, B)
/usr/local/lib/python2.7/dist-packages/scipy/sparse/compressed.py:215: SparseEfficiencyWarning: Comparing a sparse matrix with 0 using == is inefficient, try using != instead.
/usr/local/lib/python2.7/dist-packages/scipy/sparse/coo.pyc in __init__(self, arg1, shape, dtype, copy)
173 self.shape = M.shape
174
--> 175 self.row, self.col = M.nonzero()
176 self.data = M[self.row, self.col]
177 self.has_canonical_format = True
MemoryError:
ЕЩЕ ОДНО ОБНОВЛЕНИЕ:
не мог сделать ничего более или менее полезного из Цитона, по крайней мере, не уходя слишком далеко от Python. Идея заключалась в том, чтобы оставить точечный продукт scipy и просто попытаться установить эти исходные значения так же быстро, как возможно, что-то вроде этого:
cimport cython
@cython.cdivision(True)
@cython.boundscheck(False)
@cython.wraparound(False)
cpdef coo_replace(int [:] row1, int [:] col1, float [:] data1, int[:] row2, int[:] col2, float[:] data2):
cdef int N = row1.shape[0]
cdef int M = row2.shape[0]
cdef int i, j
cdef dict d = {}
for i in range(M):
d[(row2[i], col2[i])] = data2[i]
for j in range(N):
if (row1[j], col1[j]) in d:
data1[j] = d[(row1[j], col1[j])]
это было немного лучше, чем моя предварительная "наивная" реализация (используя .tolil()
), но следуя подходу hpaulj, лил может быть выброшен. Возможно, замена python dict чем-то вроде std::map поможет.
3 ответов
возможно, более чистая и быстрая версия вашего naive
код:
In [57]: r,c=A.nonzero() # this uses A.tocoo()
In [58]: C=A*B
In [59]: Cl=C.tolil()
In [60]: Cl[r,c]=A.tolil()[r,c]
In [61]: Cl.tocsr()
C[r,c]=A[r,c]
дает предупреждение об эффективности, но я думаю, что это нацелено на то, чтобы люди делали такое назначение в цикле.
In [63]: %%timeit C=A*B
...: C[r,c]=A[r,c]
...
The slowest run took 7.32 times longer than the fastest....
1000 loops, best of 3: 334 µs per loop
In [64]: %%timeit C=A*B
...: Cl=C.tolil()
...: Cl[r,c]=A.tolil()[r,c]
...: Cl.tocsr()
...:
100 loops, best of 3: 2.83 ms per loop
мой A
маленький, только (250,100), но похоже, что туда и обратно в lil
не экономия времени, несмотря на предупреждение.
маска с A==0
обязательно даст проблемы, когда A
is редкий
In [66]: Az=A==0
....SparseEfficiencyWarning...
In [67]: r1,c1=Az.nonzero()
по сравнению с nonzero
r
на A
этот r1
намного больше-индекс строки всех нулей в разреженной матрице; все, кроме 25 nonzeros.
In [70]: r.shape
Out[70]: (25,)
In [71]: r1.shape
Out[71]: (24975,)
если я индекс A
С r1
я получаю гораздо больший массив. Фактически я повторяю каждую строку по количеству нулей в ней
In [72]: A[r1,:]
Out[72]:
<24975x100 sparse matrix of type '<class 'numpy.float64'>'
with 2473 stored elements in Compressed Sparse Row format>
In [73]: A
Out[73]:
<250x100 sparse matrix of type '<class 'numpy.float64'>'
with 25 stored elements in Compressed Sparse Row format>
я увеличил форму и количество ненулевых элементов примерно на 100 (количество столбцы.)
определение foo
и копирование Divakar тесты:
def foo(A,B):
r,c = A.nonzero()
C = A*B
C[r,c] = A[r,c]
return C
In [83]: timeit naive(A,B)
100 loops, best of 3: 2.53 ms per loop
In [84]: timeit proposed(A,B)
/...
SparseEfficiencyWarning)
100 loops, best of 3: 4.48 ms per loop
In [85]: timeit foo(A,B)
...
SparseEfficiencyWarning)
100 loops, best of 3: 2.13 ms per loop
таким образом, моя версия имеет скромную скорость улучшения. Как выяснил Дивакар, изменение разреженности изменяет относительные преимущества. Я ожидаю, что размер также изменит их.
тот факт, что A.nonzero
использует coo
format, предполагает, что может быть возможно построить новый массив с этим форматом. Много sparse
код строит новую матрицу через coo
ценности.
In [97]: Co=C.tocoo()
In [98]: Ao=A.tocoo()
In [99]: r=np.concatenate((Co.row,Ao.row))
In [100]: c=np.concatenate((Co.col,Ao.col))
In [101]: d=np.concatenate((Co.data,Ao.data))
In [102]: r.shape
Out[102]: (79,)
In [103]: C1=sparse.csr_matrix((d,(r,c)),shape=A.shape)
In [104]: C1
Out[104]:
<250x100 sparse matrix of type '<class 'numpy.float64'>'
with 78 stored elements in Compressed Sparse Row format>
этой C1
имеет, я думаю, те же ненулевые элементы, что и C
построенный другими средствами. Но я думаю, что одно значение отличается, потому что r
больше. В этом конкретном примере, C
и A
поделитесь одним ненулевым элементом и coo
стиль ввода суммирует те, где как мы бы предпочли иметь A
значения перезаписать все.
если вы можете терпеть это несоответствие, это более быстрый способ (по крайней мере для этого test case):
def bar(A,B):
C=A*B
Co=C.tocoo()
Ao=A.tocoo()
r=np.concatenate((Co.row,Ao.row))
c=np.concatenate((Co.col,Ao.col))
d=np.concatenate((Co.data,Ao.data))
return sparse.csr_matrix((d,(r,c)),shape=A.shape)
In [107]: timeit bar(A,B)
1000 loops, best of 3: 1.03 ms per loop
взломал его! Ну, тут много материалов составляющей, характерные для разреженных матриц, которые я узнал по пути. Вот реализация, которую я мог бы собрать -
# Find the indices in output array that are to be updated
R,C = ((A!=0).dot(B!=0)).nonzero()
mask = np.asarray(A[R,C]==0).ravel()
R,C = R[mask],C[mask]
# Make a copy of A and get the dot product through sliced rows and columns
# off A and B using the definition of matrix-multiplication
out = A.copy()
out[R,C] = (A[R].multiply(B[:,C].T).sum(1)).ravel()
самой дорогой частью, по-видимому, является умножение и суммирование по элементам. На некоторых быстрых тестах синхронизации кажется, что это было бы хорошо на разреженных матрицах с высокой степенью разреженности, чтобы превзойти оригинальное решение на основе точечной маски с точки зрения производительности, которое, я думаю, исходит из его фокусировки на памяти эффективность.
во время выполнения теста
функции определения -
def naive(A, B):
mask = A != 0
out = A.dot(B).tolil()
out[mask] = A[mask]
return out.tocsr()
def proposed(A, B):
R,C = ((A!=0).dot(B!=0)).nonzero()
mask = np.asarray(A[R,C]==0).ravel()
R,C = R[mask],C[mask]
out = A.copy()
out[R,C] = (A[R].multiply(B[:,C].T).sum(1)).ravel()
return out
тайминги -
In [57]: # Input matrices
...: M,N = 25000, 170
...: A = sparse.rand(M, N, density=0.001, format='csr')
...: B = sparse.rand(N, N, density=0.02, format='csr')
...:
In [58]: %timeit naive(A, B)
10 loops, best of 3: 92.2 ms per loop
In [59]: %timeit proposed(A, B)
10 loops, best of 3: 132 ms per loop
In [60]: # Input matrices with increased sparse-ness
...: M,N = 25000, 170
...: A = sparse.rand(M, N, density=0.0001, format='csr')
...: B = sparse.rand(N, N, density=0.002, format='csr')
...:
In [61]: %timeit naive(A, B)
10 loops, best of 3: 78.1 ms per loop
In [62]: %timeit proposed(A, B)
100 loops, best of 3: 8.03 ms per loop
Python не является моим основным языком, но я думал, что это интересная проблема, и я хотел дать этому удар:)
предисловий:
import numpy
import scipy.sparse
# example matrices and sparse versions
A = numpy.array([[1, 2, 0, 1], [1, 0, 1, 2], [0, 1, 2 ,1], [1, 2, 1, 0]])
B = numpy.array([[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4]])
A_s = scipy.sparse.lil_matrix(A)
B_s = scipy.sparse.lil_matrix(B)
таким образом, вы хотите преобразовать оригинальную проблему:
C = A.dot(B)
C[A.nonzero()] = A[A.nonzero()]
к чему-то разреженному. Чтобы убрать это с пути, прямой" разреженный " перевод вышеизложенного:
C_s = A_s.dot(B_s)
C_s[A_s.nonzero()] = A_s[A_s.nonzero()]
но похоже, что вы не довольны этим, так как он сначала вычисляет все продукты dot, которые вас беспокоят это может быть неэффективно.
Итак, ваш вопрос: если вы сначала найдете нули и только оцените точечные продукты на этих элементах, это будет быстрее? Т. е. для плотной матрицы это может быть что-то вроде:
Xs, Ys = numpy.nonzero(A==0)
D = A[:]
D[Xs, Ys] = map ( lambda x,y: A[x,:].dot(B[:,y]), Xs, Ys)
давайте переведем это на разреженную матрицу. Моим главным камнем преткновения здесь было нахождение" нулевых " индексов; так как A_s==0
не имеет смысла для разреженных матриц, я нашел их так:
Xmax, Ymax = A_s.shape
DenseSize = Xmax * Ymax
Xgrid, Ygrid = numpy.mgrid[0:Xmax, 0:Ymax]
Ygrid = Ygrid.reshape([DenseSize,1])[:,0]
Xgrid = Xgrid.reshape([DenseSize,1])[:,0]
AllIndices = numpy.array([Xgrid, Ygrid])
NonzeroIndices = numpy.array(A_s.nonzero())
ZeroIndices = numpy.array([x for x in AllIndices.T.tolist() if x not in NonzeroIndices.T.tolist()]).T
если вы знаете лучше / быстрее, все значит, попробуй. Как только у нас есть нулевые индексы, мы можем сделать аналогичное отображение, как и раньше:
D_s = A_s[:]
D_s[ZeroIndices[0], ZeroIndices[1]] = map ( lambda x, y : A_s[x,:].dot(B[:,y])[0], ZeroIndices[0], ZeroIndices[1] )
что дает вам результат разреженной матрицы.
теперь я не знаю, быстрее это или нет. Я в основном сделал удар, потому что это была интересная проблема, и посмотреть, смогу ли я сделать это на python. На самом деле я подозреваю, что это может быть не быстрее, чем direct whole-matrix dotproduct, потому что он использует listcomprehensions и отображение в большом наборе данных (как вы говорите, вы ожидаете много ноли.) Но это!--27-- > is ответ на ваш вопрос "как я могу вычислять только точечные продукты для нулевых значений, не умножая матрицы в целом". Мне было бы интересно посмотреть, если вы попробуете это, как это сравнивается с точки зрения скорости на ваших наборах данных.
EDIT: я помещаю ниже пример версии "обработка блоков" на основе вышеизложенного, которая, я думаю, должна позволить вам обрабатывать ваш большой набор данных без проблем. Дайте мне знать, если это завод.
import numpy
import scipy.sparse
# example matrices and sparse versions
A = numpy.array([[1, 2, 0, 1], [1, 0, 1, 2], [0, 1, 2 ,1], [1, 2, 1, 0]])
B = numpy.array([[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4]])
A_s = scipy.sparse.lil_matrix(A)
B_s = scipy.sparse.lil_matrix(B)
# Choose a grid division (i.e. how many processing blocks you want to create)
BlockGrid = numpy.array([2,2])
D_s = A_s[:] # initialise from A
Xmax, Ymax = A_s.shape
BaseBSiz = numpy.array([Xmax, Ymax]) / BlockGrid
for BIndX in range(0, Xmax, BlockGrid[0]):
for BIndY in range(0, Ymax, BlockGrid[1]):
BSizX, BSizY = D_s[ BIndX : BIndX + BaseBSiz[0], BIndY : BIndY + BaseBSiz[1] ].shape
Xgrid, Ygrid = numpy.mgrid[BIndX : BIndX + BSizX, BIndY : BIndY + BSizY]
Xgrid = Xgrid.reshape([BSizX*BSizY,1])[:,0]
Ygrid = Ygrid.reshape([BSizX*BSizY,1])[:,0]
AllInd = numpy.array([Xgrid, Ygrid]).T
NZeroInd = numpy.array(A_s[Xgrid, Ygrid].reshape((BSizX,BSizY)).nonzero()).T + numpy.array([[BIndX],[BIndY]]).T
ZeroInd = numpy.array([x for x in AllInd.tolist() if x not in NZeroInd.tolist()]).T
#
# Replace zero-values in current block
D_s[ZeroInd[0], ZeroInd[1]] = map ( lambda x, y : A_s[x,:].dot(B[:,y])[0], ZeroInd[0], ZeroInd[1] )