假设我有一个num_indices * n指数矩阵(在range(m)中)和一个num_indices * n值矩阵,即.,

m, n = 100, 50
num_indices = 100000
indices = np.random.randint(0, m, (num_indices, n))
values = np.random.uniform(0, 1, (num_indices, n))

我想得到一个形状为m * n的结果矩阵,这样每个列都被索引,并在索引和值矩阵中的相应列上求和.以下是一些实现:

  • The basic for loop
tic = time.time()
res1 = np.zeros((m, n))
for i in range(num_indices):
    for j in range(n):
        res1[indices[i, j], j] += values[i, j]
print(f'element-wise for loop used {time.time() - tic:.5f}s')
  • np.add.at, in multidimensional
tic = time.time()
res2 = np.zeros((m, n))
np.add.at(res2, (indices, np.arange(n)[None, :]), values)
print(f'np.add.at used {time.time() - tic:.5f}s')
  • np.add.at, with for loop
tic = time.time()
reslst = []
for colid in range(n):
    rescol = np.zeros((m, 1))
    np.add.at(rescol, (indices[:, colid], np.zeros(num_indices, dtype=int)), values[:, colid])
    reslst.append(rescol)
res3 = np.hstack(reslst)
print(f'np.add.at with loop used {time.time() - tic:.5f}s')
  • scipy.sparse, in multidimensional
from scipy import sparse
tic = time.time()
res4 = sparse.csr_matrix((values.ravel(), (indices.ravel(), np.tile(np.arange(n), num_indices))), shape=(m, n)).toarray()
print(f'sparse.csr used {time.time() - tic:.5f}s')
  • scipy.sparse, with for loop
tic = time.time()
reslst = []
for colid in range(n):
    rescol = sparse.csr_matrix((values[:, colid], (indices[:, colid], np.zeros(num_indices, dtype=int))), shape=(m, 1)).toarray()
    reslst.append(rescol)
res5 = np.hstack(reslst)
print(f'sparse.csr with loop used {time.time() - tic:.5f}s')

结果都是一样的:

assert np.isclose(res2, res1).all()
assert np.isclose(res3, res1).all()
assert np.isclose(res4, res1).all()
assert np.isclose(res5, res1).all()

然而,速度很奇怪,也不令人满意:

for loop used 1.69889s
np.add.at used 0.17287s
np.add.at with loop used 0.47767s
sparse.csr used 0.16847s
sparse.csr with loop used 0.09845s

我的基本问题是:

  • 为什么np.add.at这么慢——甚至比scipy.sparse慢?我觉得numpy会受益匪浅,尤其是在多维的情况下.
  • 对于scipy.sparse,为什么多维比循环更慢?不使用并发吗?(为什么对于numpy来说,多维更快?)
  • 总的来说,有没有更快的方法来实现我想要的?

推荐答案

令人惊讶的是,结果是100 is very inefficiently implemented in Numpy.

关于scipy.sparse,我无法用Scipy 1.7.1在我的机器上重现同样的性能改进:它几乎没有更快的速度.就像Numpy的np.add.at一样,它还远远不够快.

您可以使用以下Numba种方法有效地执行此操作:

import numba as nb

@nb.njit('(int_[:,::1], float64[:,::1], int_, int_)')
def compute(indices, values, n, m):
    res = np.zeros((m, n), dtype=np.float64)
    for i in range(num_indices):
        for j in range(n):
            #assert 0 <= indices[i, j] < m
            res[indices[i, j], j] += values[i, j]
    return res

tic = time.time()
result = compute(indices, values, n, m)
print(f'Numba used {time.time() - tic:.5f}s')

请注意,该函数假定indices包含有效值(即无越界).否则,该函数可能会崩溃,或者会悄悄地损坏程序.如果您不确定这一点,可以启用断言,但代价是计算速度较慢(在我的机器上要慢两倍).

请注意,只要8 * n * m足够小,这个实现就很快,因此输出数组fit in the L1 cache(通常从16 KB到64 KB).否则,由于效率低下的随机访问模式,它可能会稍微慢一点.如果输出数组不适合二级缓存(通常为几百KB),那么这将大大降低速度.

后果

element-wise for loop used 2.45158s
np.add.at used 0.28600s
sparse.csr used 0.19600s
sparse.csr with loop used 0.18900s
Numba used 0.00500s

因此,它比最快的实现快Numba is about 40 times faster倍,比最慢的实现快约500倍.与Numpy和Scipy相比,NUBA代码更快,因为代码被编译为优化的本机二进制文件,没有额外的开销(即绑定判断、临时数组、函数调用).

Python相关问答推荐

决策树分类器的基础sklearn熵和log_loss标准是否有差异?

如何从同一类的多个元素中抓取数据?

X射线扫描显示Docker中的pip漏洞,尽管图像中未安装pip

隐藏QComboBox的指示器(qdarkstyle)

使用itertools出现第n个子串

保留包含pandas pandras中文本的列

如何在超时的情况下同步运行Matplolib服务器端?该过程随机挂起

创建带有二维码的Flask应用程序,可重定向到特定端点

基本链合同的地址是如何计算的?

有什么方法可以避免使用许多if陈述

如何才能知道Python中2列表中的巧合.顺序很重要,但当1个失败时,其余的不应该失败或是0巧合

时间序列分解

类型错误:输入类型不支持ufuncisnan-在执行Mann-Whitney U测试时[SOLVED]

可变参数数量的重载类型(args或kwargs)

使可滚动框架在tkinter环境中看起来自然

NP.round解算数据后NP.unique

基于字符串匹配条件合并两个帧

在pandas中使用group_by,但有条件

当我try 在django中更新模型时,模型表单数据不可见

如何在达到end_time时自动将状态字段从1更改为0