Python (稀疏)2D numpy阵列的每行/每列快速非零索引
我正在寻找最快的方法来获得2D数组每行每列的非零索引列表。以下是一段工作代码:Python (稀疏)2D numpy阵列的每行/每列快速非零索引,python,arrays,numpy,scipy,sparse-matrix,Python,Arrays,Numpy,Scipy,Sparse Matrix,我正在寻找最快的方法来获得2D数组每行每列的非零索引列表。以下是一段工作代码: preds = [matrix[:,v].nonzero()[0] for v in range(matrix.shape[1])] descs = [matrix[v].nonzero()[0] for v in range(matrix.shape[0])] 输入示例: matrix = np.array([[0,0,0,0],[1,0,0,0],[1,1,0,0],[1,1,1,0]]) 示例输出 pred
preds = [matrix[:,v].nonzero()[0] for v in range(matrix.shape[1])]
descs = [matrix[v].nonzero()[0] for v in range(matrix.shape[0])]
输入示例:
matrix = np.array([[0,0,0,0],[1,0,0,0],[1,1,0,0],[1,1,1,0]])
示例输出
preds = [array([1, 2, 3]), array([2, 3]), array([3]), array([], dtype=int64)]
descs = [array([], dtype=int64), array([0]), array([0, 1]), array([0, 1, 2])]
(这些列表称为pred和desc,因为当矩阵被解释为邻接矩阵时,它们指的是DAG中的前辈和后辈,但这不是问题的关键。)
计时示例:
出于计时目的,以下矩阵具有很好的代表性:
test_matrix = np.zeros(shape=(4096,4096),dtype=np.float32)
for k in range(16):
test_matrix[256*(k+1):256*(k+2),256*k:256*(k+1)]=1
背景:在我的代码中,对于4000x4000矩阵,这两行占用了75%的时间,而随后的拓扑排序和DP算法只占用了剩余的时间。矩阵中大约5%的值为非零,因此稀疏矩阵解决方案可能适用
多谢各位
(根据此处张贴的建议:
还有一些答案,我将在评论中提供时间安排。此链接包含一个可接受的答案,速度是原来的两倍。)
数据存在于整个数组非零
,只是没有分解成每行/每列数组:
In [183]: np.nonzero(arr)
Out[183]: (array([1, 2, 2, 3, 3, 3]), array([0, 0, 1, 0, 1, 2]))
In [184]: np.argwhere(arr)
Out[184]:
array([[1, 0],
[2, 0],
[2, 1],
[3, 0],
[3, 1],
[3, 2]])
可以将数组([1,2,2,3,3,3])
拆分为子列表,[1,2,3],[2,3],[3],][/code>基于另一个数组。但这可能需要一些时间来确定逻辑,并且不能保证它会比您的行/列迭代更快
逻辑操作可以将布尔数组缩减为列或行,给出出现非零但又不参差不齐的行或列:
In [185]: arr!=0
Out[185]:
array([[False, False, False, False],
[ True, False, False, False],
[ True, True, False, False],
[ True, True, True, False]])
In [186]: (arr!=0).any(axis=0)
Out[186]: array([ True, True, True, False])
In [187]: np.nonzero((arr!=0).any(axis=0))
Out[187]: (array([0, 1, 2]),)
In [188]: np.nonzero((arr!=0).any(axis=1))
Out[188]: (array([1, 2, 3]),)
In [189]: arr
Out[189]:
array([[0, 0, 0, 0],
[1, 0, 0, 0],
[1, 1, 0, 0],
[1, 1, 1, 0]])
scipy.sparse
lil
格式确实会生成所需的数据:
In [190]: sparse
Out[190]: <module 'scipy.sparse' from '/usr/local/lib/python3.6/dist-packages/scipy/sparse/__init__.py'>
In [191]: M = sparse.lil_matrix(arr)
In [192]: M
Out[192]:
<4x4 sparse matrix of type '<class 'numpy.longlong'>'
with 6 stored elements in List of Lists format>
In [193]: M.rows
Out[193]: array([list([]), list([0]), list([0, 1]), list([0, 1, 2])], dtype=object)
In [194]: M.T
Out[194]:
<4x4 sparse matrix of type '<class 'numpy.longlong'>'
with 6 stored elements in List of Lists format>
In [195]: M.T.rows
Out[195]: array([list([1, 2, 3]), list([2, 3]), list([3]), list([])], dtype=object)
[190]中的:稀疏
出[190]:
In[191]:M=稀疏的lil_矩阵(arr)
In[192]:M
出[192]:
在[193]中:M行
Out[193]:数组([list([]),list([0]),list([0,1]),list([0,1,2]),dtype=object)
在[194]中:M.T
Out[194]:
在[195]中:M.T.行
Out[195]:数组([list([1,2,3]),list([2,3]),list([3]),list([])],dtype=object)
但时间安排可能并不比行或列迭代更好 如果你有足够的动力,Numba可以做令人惊奇的事情。
下面是所需逻辑的快速实现。
简单地说,它计算的等价物是np.nonzero()
,但它包含了随后将索引分派到所需格式的信息。
该信息的灵感来源于sparse.csr.indptr
和sparse.csc.indptr
import numpy as np
import numba as nb
@nb.jit
def cumsum(arr):
result = np.empty_like(arr)
cumsum = result[0] = arr[0]
for i in range(1, len(arr)):
cumsum += arr[i]
result[i] = cumsum
return result
@nb.jit
def count_nonzero(arr):
arr = arr.ravel()
n = 0
for x in arr:
if x != 0:
n += 1
return n
@nb.jit
def row_col_nonzero_nb(arr):
n, m = arr.shape
max_k = count_nonzero(arr)
indices = np.empty((2, max_k), dtype=np.uint32)
i_offset = np.zeros(n + 1, dtype=np.uint32)
j_offset = np.zeros(m + 1, dtype=np.uint32)
n, m = arr.shape
k = 0
for i in range(n):
for j in range(m):
if arr[i, j] != 0:
indices[:, k] = i, j
i_offset[i + 1] += 1
j_offset[j + 1] += 1
k += 1
return indices, cumsum(i_offset), cumsum(j_offset)
def row_col_idx_nonzero_nb(arr):
(ii, jj), jj_split, ii_split = row_col_nonzero_nb(arr)
ii_ = np.argsort(jj)
ii = ii[ii_]
return np.split(ii, ii_split[1:-1]), np.split(jj, jj_split[1:-1])
与您的方法(下面的row\u col\u idx\u sep()
)和其他一些方法相比,按照(row\u col\u idx\u sparse\u lil()
)和(row\u col\u idx\u sparse\u coo()):
对于使用以下方法生成的输入:
def gen_input(n, density=0.1, dtype=np.float32):
arr = np.zeros(shape=(n, n), dtype=dtype)
indices = tuple(np.random.randint(0, n, (2, int(n * n * density))).tolist())
arr[indices] = 1.0
return arr
可以得到(您的测试矩阵的非零密度约为0.06):
表明这一速度接近基于最快的scipy.sparse
方法的两倍。我建议你问这个问题,有更好的地方吗?你能提供一个包含玩具输入/输出的方法吗?@norok2是的,你是对的。我添加了它。你是否也有一些输入大小的指示,典型的稀疏系数,或者对输入有任何假设(例如,你的玩具输入是低三角形的)?@eusoubrasileiro,你为什么推荐scicomp
板?这看起来像Python/numpy编程问题,而不是理论问题。你在那块板上很活跃,准备好回答这个问题了吗?谢谢你的回答。添加一些计时:对于4096x4096测试矩阵上的10次运行,此代码的运行速度比原始代码慢约1.5倍。但是,对于比我提供的矩阵大100倍的矩阵,您的代码大约快20%。因此,根据矩阵,这可能是一个很好的解决方案!另请注意,另一块板上提供了一个解决方案,但我不知道如何解决两块板上的两个不同问题。@RichardSchoonhoven独立地对两块板进行测试,这可能会对您有所帮助。在我自己的测试中,列表方法更适合于小型阵列。csr
matrix方法的扩展性更好(数组大于(10001000))。我的lil
版本较慢;计时表明lil\u矩阵
首先生成csr
,然后将其转换为lil
。在scipy.sparse
代码的封面下有很多事情要做,从头开始创建一个矩阵可能很耗时。很大程度上取决于矩阵的稀疏性。感谢您的广泛回复。非常有用!
def row_col_idx_sep(arr):
return (
[arr[:, j].nonzero()[0] for j in range(arr.shape[1])],
[arr[i, :].nonzero()[0] for i in range(arr.shape[0])],)
def row_col_idx_zip(arr):
n, m = arr.shape
ii = [[] for _ in range(n)]
jj = [[] for _ in range(m)]
x, y = np.nonzero(arr)
for i, j in zip(x, y):
ii[i].append(j)
jj[j].append(i)
return jj, ii
import scipy as sp
import scipy.sparse
def row_col_idx_sparse_coo(arr):
coo_mat = sp.sparse.coo_matrix(arr)
csr_mat = coo_mat.tocsr()
csc_mat = coo_mat.tocsc()
return (
np.split(csc_mat.indices, csc_mat.indptr)[1:-1],
np.split(csr_mat.indices, csr_mat.indptr)[1:-1],)
def row_col_idx_sparse_lil(arr):
lil_mat = sp.sparse.lil_matrix(arr)
return lil_mat.T.rows, lil_mat.rows
def gen_input(n, density=0.1, dtype=np.float32):
arr = np.zeros(shape=(n, n), dtype=dtype)
indices = tuple(np.random.randint(0, n, (2, int(n * n * density))).tolist())
arr[indices] = 1.0
return arr
m = gen_input(4096, density=0.06)
%timeit row_col_idx_sep(m)
# 1 loop, best of 3: 767 ms per loop
%timeit row_col_idx_zip(m)
# 1 loop, best of 3: 660 ms per loop
%timeit row_col_idx_sparse_coo(m)
# 1 loop, best of 3: 205 ms per loop
%timeit row_col_idx_sparse_lil(m)
# 1 loop, best of 3: 498 ms per loop
%timeit row_col_idx_nonzero_nb(m)
# 10 loops, best of 3: 130 ms per loop