Как эффективно рассчитать разреженные значения памяти матричного продукта в Python? - PullRequest
0 голосов
/ 08 июня 2018

Я хотел бы рассчитать некоторые определенные значения матричного произведения таким образом, чтобы это было эффективно при использовании памяти и времени вычислений.Проблема в том, что промежуточная матрица имеет два очень больших размера и не может быть сохранена.

Размеры с примерами значений:

N = 7  # very large
K = 3
M = 10 # very large
L = 8  # very very large

'a' - это матрица формы (N, K)'b' - это матрица формы (K, N)

a = np.arange(N*K).reshape(N,K)
b = np.arange(K*M).reshape(K,M)

, строки - это массив индексов со значениями в диапазоне (N) и длине Lcols - это массив индексов со значениями в диапазоне (M) и длиной L

rows = [0,0,1,2,3,3,4,6]
cols = [0,9,5,8,2,8,3,6]

Мне нужно следующее, но невозможно вычислить матрицу (a @ b) с формой (MxN) в качестве промежуточного результатаиз-за его размера:

values = (a @ b)[rows, cols]

Альтернативная реализация может включать в себя нарезку [row] и b [:, cols], создание матриц с формами (L, K) и (K, L), но теслишком большие тоже.Numpy копирует значения, когда выполняет нарезку

values = np.einsum("ij,ji->i", a[rows], b[:,cols])

Заранее спасибо

Ответы [ 2 ]

0 голосов
/ 08 июня 2018

Одна из возможностей - просто разделить ваш подход einsum.Нарезка rows и cols на биты размером 20 решает большую (10 ^ 7) проблему за ~ 2 минуты на моем ноутбуке.Возможно, это можно улучшить, изменив размер фрагмента.

Но мы можем добиться большего: мы можем сгруппировать либо по строкам, либо по столбцам (я выбрал столбцы), а затем умножить отдельные столбцы на все парные строки.Мы можем использовать разреженные матрицы csc / csr, чтобы выполнить всю сортировку / перемешивание / переиндексацию за нас.Этот метод на тех же данных заканчивается через ~ 30 сек.

import numpy as np
from scipy import sparse

def f_sparse_helper(a, b, rows, cols):
    h = sparse.csr_matrix((np.empty(L), cols, np.arange(L+1)), (L, M)) \
              .tocsc()
    for i in range(M):
        l, r = h.indptr[i:i+2]
        h.data[l:r] = a[rows[h.indices[l:r]]] @ b[:, i]
    return h.tocsr().data

def f_chunk(a, b, rows, cols, chunk=20):
    out = np.empty(L)
    for j in range(0, rows.size, chunk):
        l = j+chunk
        out[j:l] = np.einsum("ij,ji->i", a[rows[j:l]], b[:,cols[j:l]])
    return out

def prep_data(K, M, N, L):
    a = np.random.uniform(0, 10, (N, K))
    b = np.random.uniform(0, 10, (K, M))
    rows = np.random.randint(0, N, (L,))
    cols = np.random.randint(0, M, (L,))
    return a, b, rows, cols

# use small exmpl to check correct
K, M, N, L = 10, 100, 100, 1000
a, b, rows, cols = prep_data(K, M, N, L)
res = f_sparse_helper(a, b, rows, cols)
assert np.allclose(res, np.einsum("ij,ji->i", a[rows], b[:,cols]))
assert np.allclose(res, f_chunk(a, b, rows, cols))

# timeit on big one
from time import perf_counter as pc
K, M, N, L = 1_000, 10_000, 10_000, 10_000_000
a, b, rows, cols = prep_data(K, M, N, L)
t = pc()
res_ch = f_chunk(a, b, rows, cols)
s = pc()
print('chunked      ', s-t, 'seconds')
t = pc()
res_sh = f_sparse_helper(a, b, rows, cols)
s = pc()
print('sparse helper', s-t, 'seconds')
assert np.allclose(res_sh, res_ch)

Пример выполнения:

chunked       121.16188396583311 seconds
sparse helper 31.172512074932456 seconds
0 голосов
/ 08 июня 2018

Одной из возможностей является прямой расчет результатов.Возможно, есть и другие приемы использования подпрограммы BLAS без огромного временного массива, но это также сработает.

Пример

import numpy as np
import numba as nb
import time


@nb.njit(fastmath=True,parallel=True)
def sparse_mult(a,b_Trans,inds):
  res=np.empty(inds.shape[0],dtype=a.dtype)

  for x in nb.prange(inds.shape[0]):
    i=inds[x,0]
    j=inds[x,1]
    sum=0.
    for k in range(a.shape[1]):
      sum+=a[i,k]*b_Trans[j,k]
    res[x]=sum
  return res


#-------------------------------------------------
K=int(1e3)
N=int(1e5)
M=int(1e5)
L=int(1e7)

a = np.arange(N*K).reshape(N,K).astype(np.float64)
b = np.arange(K*M).reshape(K,M).astype(np.float64)

inds=np.empty((L,2),dtype=np.uint64)
inds[:,0] = np.random.randint(low=0,high=N,size=L) #rows
inds[:,1] = np.random.randint(low=0,high=M,size=L) #cols

#prepare
#-----------------------------------------------
#sort inds for better cache usage
inds=inds[np.argsort(inds[:,1]),:]

# transpose b for easy SIMD-usage
# we wan't a real transpose here not a view
b_T=np.copy(np.transpose(b))

#calculate results
values=sparse_mult(a,b_T,inds)

Шаг вычисления, включаяпредварительная подготовка (сортировка, транспонирование матрицы b) должна выполняться менее чем за 60 с.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...