我有一个带整数值的NumPy数组。矩阵的值在矩阵中从0到最大元素(换句话说,在其中呈现的从0到最大数据元素的所有数字)。我需要构建有效的(有效方法快速全矢量化解决方案)来搜索每行中的元素数量并根据矩阵值对其进行编码。
我找不到类似的问题,或者以某种方式帮助解决这个问题的问题。
所以如果我在输入中有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
似乎比唯一数字计数大。)
有人有很棒的想法吗?)
答案 0 :(得分:9)
基本上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
允许进行一些调整。
首先,它允许JIT编译。
此外,最近他们引入了实验parallel
,它可以自动并行化已知具有并行语义的函数中的操作。
最后的调整是使用prange
作为range
的替代品。文档声明它并行运行循环,类似于OpenMP parallel for循环和Cython的prange。 prange
适用于较大的数据集,这可能是因为设置并行工作所需的开销。
因此,通过这些新的两个调整以及用于无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版本将是 -
# 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
变体表现得非常好。从三个变量中选择一个将取决于输入数组形状参数,并在某种程度上取决于其中的唯一元素数量。