【问题标题】:Quicker way to implement numpy.isin followed by sum实现 numpy.isin 后跟 sum 的更快方法
【发布时间】:2019-05-13 21:44:51
【问题描述】:

我正在使用 python 脚本执行数据分析,并从分析中了解到,执行以下操作 np.sum(C[np.isin(A, b)]) 的行占用了超过 95% 的计算时间,其中 AC 是 2D NumPy等维数组m x nb 是一个可变长度的一维数组。我想知道如果不是专用的 NumPy 函数,有没有办法加速这种计算?

A (int64)C (float64) 的典型尺寸:10M x 100

b (int64) 的典型大小:1000

【问题讨论】:

  • 一个典型的b 有多大,它的元素是什么样的?
  • 感谢保罗的评论。我现在已经在帖子中添加了信息。
  • Ab 中的值是在有限范围内还是在整个地方?
  • 不,Ab 的范围有限。现在,它们的范围从 0 到 11,999。但是,当我将来扩展系统时,它们可能会增加 10 倍,达到 119,999。

标签: python arrays python-3.x numpy


【解决方案1】:

由于您的标签来自一个小的整数范围,您应该通过使用下面的np.bincount (pp) 获得相当大的加速。或者,您可以通过创建掩码 (p2) 来加快查找速度。这---就像你的原始代码一样---允许用math.fsum替换np.sum,这保证了机器精度内的精确结果(p3)。或者,我们可以对它进行 pythranize 以实现另一个 40% 加速 (p4)。

在我的装备上,numba soln (mx) 的速度与 pp 差不多,但也许我做得不对。

import numpy as np
import math
from subsum import pflat

MAXIND = 120_000

def OP():
    return sum(C[np.isin(A, b)])

def pp():
    return np.bincount(A.reshape(-1), C.reshape(-1), MAXIND)[np.unique(b)].sum()
def p2():
    grid = np.zeros(MAXIND, bool)
    grid[b] = True
    return C[grid[A]].sum()
def p3():
    grid = np.zeros(MAXIND, bool)
    grid[b] = True
    return math.fsum(C[grid[A]])
def p4():
    return pflat(A.ravel(), C.ravel(), b, MAXIND)

import numba as nb

@nb.njit(parallel=True,fastmath=True)
def nb_ss(A,C,b):
    s=set(b)
    sum=0.
    for i in nb.prange(A.shape[0]):
        for j in range(A.shape[1]):
            if A[i,j] in s:
                sum+=C[i,j]
    return sum

def mx():
    return nb_ss(A,C,b)

sh = 100_000, 100

A = np.random.randint(0, MAXIND, sh)
C = np.random.random(sh)
b = np.random.randint(0, MAXIND, 1000)

print(OP(), pp(), p2(), p3(), p4(), mx())

from timeit import timeit

print("OP", timeit(OP, number=4)*250)
print("pp", timeit(pp, number=10)*100)
print("p2", timeit(p2, number=10)*100)
print("p3", timeit(p3, number=10)*100)
print("p4", timeit(p4, number=10)*100)
print("mx", timeit(mx, number=10)*100)

pythran 模块的代码:

[subsum.py]

import numpy as np

#pythran export pflat(int[:], float[:], int[:], int)

def pflat(A, C, b, MAXIND):
    grid = np.zeros(MAXIND, bool)
    grid[b] = True
    return C[grid[A]].sum()

编译就像pythran subsum.py一样简单

示例运行:

41330.15849965791 41330.15849965748 41330.15849965747 41330.158499657475 41330.15849965791 41330.158499657446
OP 1963.3807722493657
pp 53.23419079941232
p2 21.8758742994396
p3 26.829131800332107
p4 12.988955597393215
mx 52.37018179905135

【讨论】:

  • 感谢保罗的解决方案。使用np.bincount 的计算在减少到一维后几乎比np.isinnp.in1d 快60 倍。尽管两种计算都应该产生相同的值,但我发现最终结果略有不同。 OP=1273046188243834.8 和 pp=1273046188243834.5。我看不出可能导致这种差异的两种实现之间的任何差异。有什么想法吗?
  • @Viswanath 是的,这很可能是求和的顺序。您的实现一次性对整个匹配集求和,而我的实现首先对每个标签求和,然后对子和求和。但是差异很小,在您的示例中,它只能以机器精度解决(尝试np.nextafter(OP, 0),您将得到pp)。
  • 哇,你说得对。这只是单个浮点值的差异。非常感谢@Paul。另一方面,如果您有机会查看这篇文章的其他解决方案,您是否有任何关于 Numba 实现的 cmets?
  • @Viswanath 不,我是麻木的菜鸟。但是我添加了另一个解决方案 (p2),它在示例中又快了 2.5 倍,但仍然只有三行。作为奖励,如果小的数字差异困扰您,它可以与 math.fsum 结合使用,以适度的计算成本保证精确的结果 (p3)。
  • @Viswanath 尚未完成 ;-) 如果仍然不够快:您可以在 p2 代码上使用 pythran 以获得另一个加速 (p4)。
【解决方案2】:

我假设您已根据需要将 int64 更改为 int8。

您可以使用 Numba 的并行和 It 功能来加快 Numpy 计算并利用内核。

@numba.jit(nopython=True, parallel=True)
def (A,B,c):
    return np.sum(C[np.isin(A, b)])

Documentation for Numba Parallel

【讨论】:

  • 不,我仍在使用 int64 作为范围。不过,这是一个好主意,内存占用更少。我检查了 Numba 的文档,结果发现 np.isin() 不是 Numba 在 nopython 模式下支持的通用功能。
【解决方案3】:

我不知道为什么np.isin 这么慢,但是您可以更快地实现您的功能。 以下 Numba 解决方案使用一组用于快速查找值并且是并行化的。内存占用也比 Numpy 实现中的小。

代码

import numpy as np
import numba as nb


@nb.njit(parallel=True,fastmath=True)
def nb_pp(A,C,b):
    s=set(b)
    sum=0.
    for i in nb.prange(A.shape[0]):
        for j in range(A.shape[1]):
            if A[i,j] in s:
                sum+=C[i,j]
    return sum

时间

pp 实现和第一个数据样本是上面 Paul Panzers 的回答。

MAXIND = 120_000
sh = 100_000, 100
A = np.random.randint(0, MAXIND, sh)
C = np.random.random(sh)
b = np.random.randint(0, MAXIND, 1000)

MAXIND = 120_000
%timeit res_1=np.sum(C[np.isin(A, b)])
1.5 s ± 10.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit res_2=pp(A,C,b)
62.5 ms ± 624 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit res_3=nb_pp(A,C,b)
17.1 ms ± 141 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)


MAXIND = 10_000_000
%timeit res_1=np.sum(C[np.isin(A, b)])
2.06 s ± 27.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit res_2=pp(A,C,b)
206 ms ± 3.67 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit res_3=nb_pp(A,C,b)
17.6 ms ± 332 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

MAXIND = 100
%timeit res_1=np.sum(C[np.isin(A, b)])
1.01 s ± 20.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit res_2=pp(A,C,b)
46.8 ms ± 538 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit res_3=nb_pp(A,C,b)
3.88 ms ± 84.8 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

【讨论】:

  • 感谢您提供有趣的解决方案。不知何故,我无法让 numba 使用我的代码。我收到此错误:numba.errors.TypingError: Failed in nopython mode pipeline (step: nopython frontend)
  • @Viswanath 它是否与样本数据一起运行?如果输入的类型不受支持(例如,具有混合 dtype 的列表),则这是一个典型的错误。它们都是真的 numpy 数组还是不同的东西?
  • @max9111 我不认为MAXIND 是超出OP 给出的12'000120'000 值的正确参数。它应该是b 的大小和较小程度的sh,你不觉得吗?
  • @max9111 我已将您的解决方案添加到我的计时中,但它在我的机器上没有预期的那么快。有什么明显的我可能做错了吗?
  • @Paul Panzer 您正在测量编译和运行时的混合。在第二次调用print("mx", timeit(mx, number=10)*100) 时,您应该获得运行时。
猜你喜欢
  • 2011-01-21
  • 2020-09-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-06
  • 1970-01-01
相关资源
最近更新 更多