Python numpy:高效地添加矩阵的行
我有一个矩阵Python numpy:高效地添加矩阵的行,python,numpy,indexing,Python,Numpy,Indexing,我有一个矩阵 mat = array([ [ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11] ]) 我想得到某些索引处的行的总和:例如 ixs = np.array([0,2,0,0,0,1,1]) 我知道我可以将答案计算为: mat[ixs].sum(axis=0) > array([16, 23, 30, 37]) 问题是ixs可能很长,我不想用所有的内存来创建中间产品mat[ixs],只想用总和再次减
mat = array([
[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]
])
我想得到某些索引处的行的总和:例如
ixs = np.array([0,2,0,0,0,1,1])
我知道我可以将答案计算为:
mat[ixs].sum(axis=0)
> array([16, 23, 30, 37])
问题是ixs可能很长,我不想用所有的内存来创建中间产品mat[ixs],只想用总和再次减少它
我也知道我可以简单地计算指数,然后用乘法代替
np.bincount(ixs, minlength=mat.shape[0).dot(mat)
> array([16, 23, 30, 37])
但是如果我的IX是稀疏的,那么这将是昂贵的
我知道scipy的稀疏矩阵,我想我可以使用它们,但我更喜欢纯numpy解决方案,因为稀疏矩阵在各种方面都有限制(例如仅为二维)
那么,在这种情况下,有没有一种纯粹的numpy方法来合并索引和求和缩减
结论:
感谢Divakar和hpaulj的详尽回复。“稀疏”是指范围(w.shape[0])
中的大多数值不在ixs中。使用新定义(以及更真实的数据大小),我重新运行了Divakar测试,添加了一些新函数:
rng = np.random.RandomState(1234)
mat = rng.randn(1000, 500)
ixs = rng.choice(rng.randint(mat.shape[0], size=mat.shape[0]/10), size=1000)
# Divakar's solutions
In[42]: %timeit org_indexing_app(mat, ixs)
1000 loops, best of 3: 1.82 ms per loop
In[43]: %timeit org_bincount_app(mat, ixs)
The slowest run took 4.07 times longer than the fastest. This could mean that an intermediate result is being cached.
10000 loops, best of 3: 177 µs per loop
In[44]: %timeit indexing_modified_app(mat, ixs)
1000 loops, best of 3: 1.81 ms per loop
In[45]: %timeit bincount_modified_app(mat, ixs)
1000 loops, best of 3: 258 µs per loop
In[46]: %timeit simply_indexing_app(mat, ixs)
1000 loops, best of 3: 1.86 ms per loop
In[47]: %timeit take_app(mat, ixs)
1000 loops, best of 3: 1.82 ms per loop
In[48]: %timeit unq_mask_einsum_app(mat, ixs)
10 loops, best of 3: 58.2 ms per loop
# hpaulj's solutions
In[53]: %timeit hpauljs_sparse_solution(mat, ixs)
The slowest run took 9.34 times longer than the fastest. This could mean that an intermediate result is being cached.
1000 loops, best of 3: 524 µs per loop
%timeit hpauljs_second_sparse_solution(mat, ixs)
100 loops, best of 3: 9.91 ms per loop
# Sparse version of original bincount solution (see below):
In[60]: %timeit sparse_bincount(mat, ixs)
10000 loops, best of 3: 71.7 µs per loop
本例中的赢家是bincount解决方案的稀疏版本
def sparse_bincount(mat, ixs):
x = np.bincount(ixs)
nonzeros, = np.nonzero(x)
x[nonzeros].dot(mat[nonzeros])
由于我们假设
ixs
可能是sparsey,因此我们可以修改策略,根据给定的行索引分别从zero th
行和其余行中获取行的总和。因此,我们可以使用bincount
方法对非zero th
索引行求和,并将其与<代码>(第零行xixs
中的零编号)
因此,第二种方法可以这样修改-
nzmask = ixs!=0
nzsum = np.bincount(ixs[nzmask]-1, minlength=mat.shape[0]-1).dot(mat[1:])
row0_sum = mat[0]*(len(ixs) - np.count_nonzero(nzmask))
out = nzsum + row0_sum
out = mat[0]*(len(ixs) - len(nzidx)) + mat[ixs[nzidx]].sum(axis=0)
nzmask = ixs!=0
unq,tags = np.unique(ixs[nzmask],return_inverse=1)
nzsum = np.einsum('ji,jk->k',np.arange(len(unq))[:,None] == tags,mat[unq])
out = mat[0]*(len(ixs) - np.count_nonzero(nzmask)) + nzsum
我们也可以将这一策略扩展到第一种方法,就像这样-
nzmask = ixs!=0
nzsum = np.bincount(ixs[nzmask]-1, minlength=mat.shape[0]-1).dot(mat[1:])
row0_sum = mat[0]*(len(ixs) - np.count_nonzero(nzmask))
out = nzsum + row0_sum
out = mat[0]*(len(ixs) - len(nzidx)) + mat[ixs[nzidx]].sum(axis=0)
nzmask = ixs!=0
unq,tags = np.unique(ixs[nzmask],return_inverse=1)
nzsum = np.einsum('ji,jk->k',np.arange(len(unq))[:,None] == tags,mat[unq])
out = mat[0]*(len(ixs) - np.count_nonzero(nzmask)) + nzsum
如果我们使用大量重复的非零指数,我们可以选择使用np.take
,关注性能。因此,mat[ixs[nzidx]
可以被np.take(mat,ixs[nzidx],axis=0)
和类似的mat[ixs]
替换为np.take(mat,ixs,axis=0)
。与简单索引相比,基于重复索引的索引np.take
带来了一些明显的加速
最后,我们可以使用执行这些基于行ID的选择和求和,如下所示-
nzmask = ixs!=0
nzsum = np.bincount(ixs[nzmask]-1, minlength=mat.shape[0]-1).dot(mat[1:])
row0_sum = mat[0]*(len(ixs) - np.count_nonzero(nzmask))
out = nzsum + row0_sum
out = mat[0]*(len(ixs) - len(nzidx)) + mat[ixs[nzidx]].sum(axis=0)
nzmask = ixs!=0
unq,tags = np.unique(ixs[nzmask],return_inverse=1)
nzsum = np.einsum('ji,jk->k',np.arange(len(unq))[:,None] == tags,mat[unq])
out = mat[0]*(len(ixs) - np.count_nonzero(nzmask)) + nzsum
标杆管理
让我们列出这篇文章到目前为止发布的所有五种方法,并将问题中发布的两种方法作为函数用于一些运行时测试-
def org_indexing_app(mat,ixs):
return mat[ixs].sum(axis=0)
def org_bincount_app(mat,ixs):
return np.bincount(ixs, minlength=mat.shape[0]).dot(mat)
def indexing_modified_app(mat,ixs):
return np.take(mat,ixs,axis=0).sum(axis=0)
def bincount_modified_app(mat,ixs):
nzmask = ixs!=0
nzsum = np.bincount(ixs[nzmask]-1, minlength=mat.shape[0]-1).dot(mat[1:])
row0_sum = mat[0]*(len(ixs) - np.count_nonzero(nzmask))
return nzsum + row0_sum
def simply_indexing_app(mat,ixs):
nzmask = ixs!=0
nzsum = mat[ixs[nzmask]].sum(axis=0)
return mat[0]*(len(ixs) - np.count_nonzero(nzmask)) + nzsum
def take_app(mat,ixs):
nzmask = ixs!=0
nzsum = np.take(mat,ixs[nzmask],axis=0).sum(axis=0)
return mat[0]*(len(ixs) - np.count_nonzero(nzmask)) + nzsum
def unq_mask_einsum_app(mat,ixs):
nzmask = ixs!=0
unq,tags = np.unique(ixs[nzmask],return_inverse=1)
nzsum = np.einsum('ji,jk->k',np.arange(len(unq))[:,None] == tags,mat[unq])
return mat[0]*(len(ixs) - np.count_nonzero(nzmask)) + nzsum
计时
案例1(ixs
为95%sparsey):
bincount
的替代方法是add.at
:
In [193]: mat
Out[193]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
In [194]: ixs
Out[194]: array([0, 2, 0, 0, 0, 1, 1])
In [195]: J = np.zeros(mat.shape[0],int)
In [196]: np.add.at(J, ixs, 1)
In [197]: J
Out[197]: array([4, 2, 1])
In [198]: np.dot(J, mat)
Out[198]: array([16, 23, 30, 37])
通过稀疏性,您的意思是,我假设,ixs
可能不包括所有行,例如,ixs
没有0:
In [199]: ixs = np.array([2,1,1])
In [200]: J=np.zeros(mat.shape[0],int)
In [201]: np.add.at(J, ixs, 1)
In [202]: J
Out[202]: array([0, 2, 1])
In [203]: np.dot(J, mat)
Out[203]: array([16, 19, 22, 25])
J
仍具有mat.shape[0]
形状。但是add.at
应按ixs
的长度缩放
稀疏解决方案类似于:
从ixs
生成一个稀疏矩阵,如下所示:
In [204]: I
Out[204]:
array([[1, 0, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 1, 1],
[0, 1, 0, 0, 0, 0, 0]])
对行求和;稀疏使用矩阵乘法进行此操作,如:
In [205]: np.dot(I, np.ones((7,),int))
Out[205]: array([4, 2, 1])
然后做我们的点:
In [206]: np.dot(np.dot(I, np.ones((7,),int)), mat)
Out[206]: array([16, 23, 30, 37])
或者在稀疏代码中:
In [225]: J = sparse.coo_matrix((np.ones_like(ixs,int),(np.arange(ixs.shape[0]), ixs)))
In [226]: J.A
Out[226]:
array([[1, 0, 0],
[0, 0, 1],
[1, 0, 0],
[1, 0, 0],
[1, 0, 0],
[0, 1, 0],
[0, 1, 0]])
In [227]: J.sum(axis=0)*mat
Out[227]: matrix([[16, 23, 30, 37]])
sparse
,当从coo
转换为csr
时,对重复项求和。我可以利用
In [229]: J = sparse.coo_matrix((np.ones_like(ixs,int), (np.zeros_like(ixs,int), ixs)))
In [230]: J
Out[230]:
<1x3 sparse matrix of type '<class 'numpy.int32'>'
with 7 stored elements in COOrdinate format>
In [231]: J.A
Out[231]: array([[4, 2, 1]])
In [232]: J*mat
Out[232]: array([[16, 23, 30, 37]], dtype=int32)
[229]中的J=sparse.coo_矩阵((np.one_-like(ixs,int),(np.zero_-like(ixs,int),ixs)))
In[230]:J
出[230]:
在[231]中:J.A
Out[231]:数组([[4,2,1]])
In[232]:J*mat
Out[232]:数组([[16,23,30,37]],dtype=int32)
经过大量的数字运算(参见原始问题的结论),当输入定义如下时,最佳答案:
rng = np.random.RandomState(1234)
mat = rng.randn(1000, 500)
ixs = rng.choice(rng.randint(mat.shape[0], size=mat.shape[0]/10), size=1000)
似乎是:
def sparse_bincount(mat, ixs):
x = np.bincount(ixs)
nonzeros, = np.nonzero(x)
x[nonzeros].dot(mat[nonzeros])
你说的sparsey
ixs
,是指里面有很多zeor
还是其他什么?我的意思是范围(w.shape[0])
中的大多数值都不是ixsIn,换句话说np.bincount(ixs,minlength=mat.shape[0])
可以是正确的,对于这种情况,请查看解决方案中发布的bincount\u modified\u app
方法。