Python 每行的Bin元素-NumPy的矢量化二维Bincount
我有一个整数数组。矩阵值的范围是从0到矩阵中的最大元素(换句话说,从0到最大数据元素的所有数字都显示在矩阵中)。我需要建立有效的(有效意味着快速完全矢量化的解决方案)来搜索每行中的元素数量,并根据矩阵值对它们进行编码 我找不到一个类似的问题,或者一个不知何故有助于解决这个问题的问题 因此,如果我在输入中有这个Python 每行的Bin元素-NumPy的矢量化二维Bincount,python,performance,numpy,matrix,vectorization,Python,Performance,Numpy,Matrix,Vectorization,我有一个整数数组。矩阵值的范围是从0到矩阵中的最大元素(换句话说,从0到最大数据元素的所有数字都显示在矩阵中)。我需要建立有效的(有效意味着快速完全矢量化的解决方案)来搜索每行中的元素数量,并根据矩阵值对它们进行编码 我找不到一个类似的问题,或者一个不知何故有助于解决这个问题的问题 因此,如果我在输入中有这个数据: # shape is (N0=4, m0=4) 1 1 0 4 2 4 2 1 1 2 3 5 4 4 4 1 期望输出为: #
数据
:
# 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
我知道如何解决这个问题,只需计算数据
每行中的唯一值,逐个迭代,然后结合结果,考虑数据
数组中所有可能的值
当使用NumPy进行矢量化时,关键问题是逐个搜索每个数字的速度很慢,并且假设存在许多唯一的数字,这不是有效的解决方案。通常,N
和唯一数计数都相当大(顺便说一下,N
似乎比唯一数计数大)
有人有好主意吗?这就是
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)
# 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
样本运行-
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
允许进行一些调整
- 首先,它允许JIT编译
- 此外,最近他们引入了一种实验性的方法,可以自动并行化具有并行语义的函数中的操作
- 最后一个调整是用作
范围的替代品。文档中指出,它并行运行循环,类似于OpenMP并行for loops和Cython的prange
在处理较大的数据集时表现良好,这可能是因为设置并行工作所需的开销prange
# 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
为了完整性和以后的测试,循环版本将是-
# 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)
# 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:
案例2:
案例3:
似乎
numba
变体的性能非常好。从三种变体中选择一种将取决于输入阵列形状参数,并在某种程度上取决于其中唯一元素的数量。基本上这就是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)
# 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
样本运行-
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
允许进行一些调整
- 首先,它允许JIT编译
- 此外,最近他们引入了一种实验性的方法,可以自动并行化具有并行语义的函数中的操作
- 最后一个调整是用作
范围的替代品。文档中指出,它并行运行循环,类似于OpenMP并行for loops和Cython的prange
在处理较大的数据集时表现良好,这可能是因为设置并行工作所需的开销prange
# 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
为了完整性和以后的测试,循环版本将是-
# 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)
# 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:
案例2:
案例3:
似乎
numba
变体的性能非常好。从三种变体中选择一种将取决于输入阵列形状参数,并在某种程度上取决于其中唯一元素的数量。太好了。它完全按照需要工作。非常感谢。a+np.arange(a.shape[0])[:,None]*N
现在看起来很神奇。请您解释一下“偏移”值的概念好吗?哦,我明白了:您偏移每行中的值以使其唯一。@Grigoriy完全正确,因为相同的数字在馈送到bincount
时会累积到展平版本中的相同位置。因此,通过这种偏移,我们在不同的行中保留相同的数字,作为单独的数字,以便bincount使用。这就是整个想法,真的,太好了。它完全按照需要工作。非常感谢。a+np.arange(a.shape[0])[:,None]*N
现在看起来很神奇。请您解释一下“偏移”值的概念好吗?哦,我明白了:您偏移每行中的值以使其唯一。@Grigoriy完全正确,因为相同的数字在馈送到bincount
时会累积到展平版本中的相同位置。因此,通过这种偏移,我们在不同的行中保留相同的数字,作为单独的数字,以便bincount使用。这就是全部的想法。