【问题标题】:How to calculate sparse values of matrix product memory-efficiently in Python?如何在 Python 中高效地计算矩阵乘积的稀疏值?
【发布时间】:2018-11-18 08:23:48
【问题描述】:

我想以一种在内存使用和计算时间上有效的方式计算矩阵乘积的某些值。问题是中间矩阵有两个非常大的维度,可能无法存储。

带有示例值的维度:

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)

rows 是一个索引数组,其值在 range(N) 和长度 L
cols 是一个索引数组,其值在范围(M)和长度 L

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

我需要以下内容,但由于其大小,无法计算形状为 (MxN) 的矩阵 (a @ b) 作为中间结果:

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

另一种实现可能涉及 切片 a[rows] 和 b[:,cols],创建形状为 (L,K) 和 (K,L) 的矩阵, 但那些也太大了。 Numpy 在进行花式切片时复制值

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

提前致谢

【问题讨论】:

  • 您能否提供K, N, M, L 的实际数字(大概)?
  • [rows, cols] aprrox 有多大。相对于整个输出?只有几个值,5%,50% ?
  • 目前我有 K=1e2, N=1e4, M=1e4, L=1e6 但我希望我的算法能够处理因子 10 K=1e3, N=1e5, M=1e5, L=1e7
  • 无法计算(a @ b),但可以计算np.dot(a[np.unique(rows),:], b[:,np.unique(cols)])吗?当然,这很大程度上取决于rowscols 向量的性质。
  • 我已经删除了没有值的每一行和每一列,所以在这种情况下唯一没有帮助

标签: python numpy scipy sparse-matrix matrix-multiplication


【解决方案1】:

一种可能性是直接计算结果。也许还有一些其他技巧可以在没有巨大临时数组的情况下使用 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 秒内运行。

【讨论】:

  • 接下来打算试试 numba。好电话。
【解决方案2】:

一种可能性是简单地将您的einsum 方法分块。将rowscols 分成大小为20 的位可在我的笔记本电脑上约2 分钟内解决大(10^7)问题。可以通过调整块大小来改善这一点。

但我们可以做得更好:我们可以按行或列进行分组(我选择了列),然后将单个列与所有成对的行相乘。我们可以使用稀疏的 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

【讨论】:

    猜你喜欢
    • 2016-10-01
    • 1970-01-01
    • 2017-07-20
    • 2011-06-01
    • 2015-03-26
    • 2019-07-04
    • 1970-01-01
    • 2011-11-20
    • 1970-01-01
    相关资源
    最近更新 更多