我有一个带有整数值的NumPy数组。矩阵的值的范围是从0到矩阵中的最大元素(换句话说,其中显示的所有数字是从0到最大数据元素)。我需要建立有效的( 有效的手段是快速的完全矢量化解决方案 )来搜索每一行中的元素数量,并根据矩阵值对它们进行编码。
我找不到类似的问题,或者以某种方式帮助解决了这个问题。
所以如果我data在输入中有这个:
data
# shape is (N0=4, m0=4) 1 1 0 4 2 4 2 1 1 2 3 5 4 4 4 1
所需的输出是:
# shape(N=N0, m=data.max()+1): 1 2 0 0 1 0 0 1 2 0 1 0 0 1 1 1 0 1 0 1 0 0 3 0
我知道如何解决这个问题,方法是简单地逐一data 迭代的每一行中计算唯一值,然后结合考虑data数组中所有可能值的结果。
使用NumPy进行矢量化处理时,关键问题在于逐个搜索每个数字的速度很慢,并且假设存在很多唯一数字,但这并不是有效的解决方案。通常N,唯一数字计数都很大(顺便说一句,N似乎比唯一数字计数大)。
N
有人有好主意吗?)
嗯,这基本上是做np.bincount与做1D阵列。但是,我们需要在每行上迭代使用它(简单地考虑一下)。为了使其向量化,我们可以使每行偏移最大数量。想法是每行具有不同的bin,以使它们不受编号相同的其他行元素的影响。
np.bincount
1D
因此,实施将是-
# Vectorized solution def bincount2D_vectorized(a): N = a.max()+1 a_offs = a + np.arange(a.shape[0])[:,None]*N return np.bincount(a_offs.ravel(), minlength=a.shape[0]*N).reshape(-1,N)
样品运行-
In [189]: a Out[189]: array([[1, 1, 0, 4], [2, 4, 2, 1], [1, 2, 3, 5], [4, 4, 4, 1]]) In [190]: bincount2D_vectorized(a) Out[190]: array([[1, 2, 0, 0, 1, 0], [0, 1, 2, 0, 1, 0], [0, 1, 1, 1, 0, 1], [0, 1, 0, 0, 3, 0]])
Numba调整
我们可以带来numba进一步的提速。现在,numba允许进行一些调整。
numba
首先,它允许JIT编译。
同样,最近他们引入了实验性parallel功能,该功能可自动并行化已知具有并行语义的功能中的操作。
parallel
最终的调整将prange用作的替代range。文档指出,这可以并行运行循环,类似于OpenMP并行循环和Cython的prange。prange在较大的数据集上表现良好,这可能是由于设置并行工作所需的开销。
prange
range
因此,通过这两项新的调整以及njit针对非Python模式的调整,我们将获得三种变体-
njit
# Numba solutions def bincount2D_numba(a, use_parallel=False, use_prange=False): N = a.max()+1 m,n = a.shape out = np.zeros((m,N),dtype=int) # Choose fucntion based on args func = bincount2D_numba_func0 if use_parallel: if use_prange: func = bincount2D_numba_func2 else: func = bincount2D_numba_func1 # Run chosen function on input data and output func(a, out, m, n) return out @njit def bincount2D_numba_func0(a, out, m, n): for i in range(m): for j in range(n): out[i,a[i,j]] += 1 @njit(parallel=True) def bincount2D_numba_func1(a, out, m, n): for i in range(m): for j in range(n): out[i,a[i,j]] += 1 @njit(parallel=True) def bincount2D_numba_func2(a, out, m, n): for i in prange(m): for j in prange(n): out[i,a[i,j]] += 1
为了完整起见并在以后进行测试,该循环版本为-
# Loopy solution def bincount2D_loopy(a): N = a.max()+1 m,n = a.shape out = np.zeros((m,N),dtype=int) for i in range(m): out[i] = np.bincount(a[i], minlength=N) return out
运行时测试
情况1 :
In [312]: a = np.random.randint(0,100,(100,100)) In [313]: %timeit bincount2D_loopy(a) ...: %timeit bincount2D_vectorized(a) ...: %timeit bincount2D_numba(a, use_parallel=False, use_prange=False) ...: %timeit bincount2D_numba(a, use_parallel=True, use_prange=False) ...: %timeit bincount2D_numba(a, use_parallel=True, use_prange=True) 10000 loops, best of 3: 115 µs per loop 10000 loops, best of 3: 36.7 µs per loop 10000 loops, best of 3: 22.6 µs per loop 10000 loops, best of 3: 22.7 µs per loop 10000 loops, best of 3: 39.9 µs per loop
案例2:
In [316]: a = np.random.randint(0,100,(1000,1000)) In [317]: %timeit bincount2D_loopy(a) ...: %timeit bincount2D_vectorized(a) ...: %timeit bincount2D_numba(a, use_parallel=False, use_prange=False) ...: %timeit bincount2D_numba(a, use_parallel=True, use_prange=False) ...: %timeit bincount2D_numba(a, use_parallel=True, use_prange=True) 100 loops, best of 3: 2.97 ms per loop 100 loops, best of 3: 3.54 ms per loop 1000 loops, best of 3: 1.83 ms per loop 100 loops, best of 3: 1.78 ms per loop 1000 loops, best of 3: 1.4 ms per loop
案例3:
In [318]: a = np.random.randint(0,1000,(1000,1000)) In [319]: %timeit bincount2D_loopy(a) ...: %timeit bincount2D_vectorized(a) ...: %timeit bincount2D_numba(a, use_parallel=False, use_prange=False) ...: %timeit bincount2D_numba(a, use_parallel=True, use_prange=False) ...: %timeit bincount2D_numba(a, use_parallel=True, use_prange=True) 100 loops, best of 3: 4.01 ms per loop 100 loops, best of 3: 4.86 ms per loop 100 loops, best of 3: 3.21 ms per loop 100 loops, best of 3: 3.18 ms per loop 100 loops, best of 3: 2.45 ms per loop
看起来这些numba变体的效果非常好。从三个变体中选择一个将取决于输入数组的形状参数,并在某种程度上取决于其中的唯一元素的数量。