Python 将N-dim阵列广播到(N+;1)-dim阵列并在除1个dim以外的所有阵列上求和
假设您有一个形状为(a,b,c)的numpy数组和一个形状为(a,b,c,d)的布尔掩码。 我想将掩码应用于在最后一个轴上迭代的数组,沿着前三个轴对掩码数组求和,并获得长度/形状(d,)的列表(或数组)。 我试着用一个列表来做这件事:Python 将N-dim阵列广播到(N+;1)-dim阵列并在除1个dim以外的所有阵列上求和,python,arrays,numpy,array-broadcasting,Python,Arrays,Numpy,Array Broadcasting,假设您有一个形状为(a,b,c)的numpy数组和一个形状为(a,b,c,d)的布尔掩码。 我想将掩码应用于在最后一个轴上迭代的数组,沿着前三个轴对掩码数组求和,并获得长度/形状(d,)的列表(或数组)。 我试着用一个列表来做这件事: Result = [np.sum(Array[Mask[:,:,:,i]], axis=(0,1,2)) for i in range(d)] 它可以工作,但看起来不太像蟒蛇,而且速度也有点慢。 我也试过类似的东西 Array = Array[:,:,:,np.
Result = [np.sum(Array[Mask[:,:,:,i]], axis=(0,1,2)) for i in range(d)]
它可以工作,但看起来不太像蟒蛇,而且速度也有点慢。
我也试过类似的东西
Array = Array[:,:,:,np.newaxis]
Result = np.sum(Array[Mask], axis=(0,1,2))
但这当然不起作用,因为遮罩沿最后一个轴d的尺寸大于阵列最后一个轴1的尺寸。
此外,考虑每个轴可以具有100或200阶的维数,因此使用<代码> NP重复新的最后一个轴的数组D倍。重复< /代码>将是真正的内存密集型的,并且我希望避免这种情况。
除了列表理解,还有其他更快、更具Python风格的方法吗?怎么样
Array.reshape(-1)@Mask.reshape(-1,d)
因为你是在前三个轴上求和,所以你也可以合并它们,之后很容易看到操作可以写成矩阵向量积
例如:
a,b,c,d = 4,5,6,7
Mask = np.random.randint(0,2,(a,b,c,d),bool)
Array = np.random.randint(0,10,(a,b,c))
[np.sum(Array[Mask[:,:,:,i]]) for i in range(d)]
# [310, 237, 253, 261, 229, 268, 184]
Array.reshape(-1)@Mask.reshape(-1,d)
# array([310, 237, 253, 261, 229, 268, 184])
将N维数组广播到匹配的(N+1)维数组的最直接方法是使用: 但是,正如@hpaulj已经指出的,如果不松开尺寸,就不能使用
mask
进行切片b_arr
假设您只想将元素相加,并且将零相加“不会造成伤害”,您可以简单地将数组和掩码按元素相乘,以保持正确的维度,但是掩码中
False
的元素与相应数组元素的后续和
无关:
result = np.sum(b_arr * mask, axis=tuple(range(mask.ndim - 1)))
或者,由于*
将自动进行广播:
result = np.sum(arr[..., None] * mask, axis=tuple(range(mask.ndim - 1)))
无需首先使用(但仍需匹配维度数量,即使用arr[…,无]
,而不仅仅是arr
)
作为@PaulPanzer,由于您希望对除一个维度以外的所有维度进行汇总,因此可以使用以下方法进一步简化: 对于涉及求和的更奇特的运算,请查看
编辑 广播的好处在于,它将在表达式求值期间创建临时数组 对于您似乎正在处理的数字,我无法使用广播数组,因为我遇到了
MemoryError
,但从时间角度来看,元素相乘可能仍然是一种比您最初建议的更好的方法
或者,如果您追求速度,您可以在Cython或Numba中使用显式循环在较低的级别上实现这一点
下面您可以找到几个基于Numba的解决方案(处理ravel()
-ed数据):
:不使用任何临时数组\u vector\u matrix\u product()
:如上所述,但使用并行执行\u vector\u matrix\u product\u mp()
:使用\u vector\u matrix\u product\u sum()
和并行执行np.sum()
列表
-基于理解的解决方案相比,时间安排有所改进:
arr = np.random.randint(0, 100, (256, 256, 256))
mask = np.random.randint(0, 2, (256, 256, 256, 128), dtype=bool)
%timeit np.sum(arr[..., None] * mask, axis=tuple(range(mask.ndim - 1)))
# MemoryError
%timeit arr.ravel() @ mask.reshape(-1, mask.shape[-1])
# MemoryError
%timeit np.array([np.sum(arr * mask[..., i], axis=tuple(range(mask.ndim - 1))) for i in range(mask.shape[-1])])
# 24.1 s ± 105 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit np.array([np.sum(arr[mask[..., i]]) for i in range(mask.shape[-1])])
# 46 s ± 119 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit vector_matrix_product(arr.ravel(), mask.reshape(-1, mask.shape[-1]))
# 408 ms ± 2.12 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit vector_matrix_product(arr.ravel(), mask.reshape(-1, mask.shape[-1]), mode='mp')
# 1.63 s ± 3.58 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit vector_matrix_product(arr.ravel(), mask.reshape(-1, mask.shape[-1]), mode='sum')
# 7.17 s ± 258 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
正如预期的那样,JIT加速的版本是最快的,并且在代码上强制执行并行性并不会提高速度。
还要注意的是,元素相乘的方法比切片快(对于这些基准测试,大约是切片速度的两倍)
编辑2 按照@max9111的建议,先按行循环,然后按cols循环会导致最耗时的循环在连续数据上运行,从而显著提高速度。
没有这个技巧,
\u vector\u matrix\u product\u sum()
和\u vector\u matrix\u product\u mp()
将以基本相同的速度运行。进行重复的廉价方法是:np.broadcast\u to(arr[…],None],mask.shape)[mask]
。但结果是1d,丢失了所有“按行”信息。一般来说,每行(最后一个维度)的真实值的数量是不同的;然而,对于我正在使用的数组的大小(通常我有a=b=c=256和d=128),我要么会得到很大的内存消耗,要么当我解决这个问题时,这些解决方案似乎比列表理解慢。这正常吗?还是我遗漏了什么?@Quasark请参见编辑。基本上,是的,广播的内存效率很低,需要一些变通方法来提高速度。通常,此操作需要时间处理您拥有的数字(mask
本身就有20亿个条目)。循环顺序在代码中是次优的。如果你交换循环顺序,你可以在向量矩阵乘积中得到很大的加速。在我的例子中,从15.3秒下降到193毫秒。并行化版本是不必要的,因为它运行速度比单线程版本慢changes@max9111抢手货我更新了我的答案以反映您的建议。@请参阅最新更新,以获得显著的速度提升,同时提高内存效率。
result2 = arr.ravel() @ mask.reshape(-1, mask.shape[-1])
print(np.all(result == result2))
# True
import numpy as np
import numba as nb
@nb.jit(nopython=True)
def _vector_matrix_product(
vect_arr,
mat_arr,
result_arr):
rows, cols = mat_arr.shape
if vect_arr.shape == result_arr.shape:
for i in range(rows):
for j in range(cols):
result_arr[i] += vect_arr[j] * mat_arr[i, j]
else:
for i in range(rows):
for j in range(cols):
result_arr[j] += vect_arr[i] * mat_arr[i, j]
@nb.jit(nopython=True, parallel=True)
def _vector_matrix_product_mp(
vect_arr,
mat_arr,
result_arr):
rows, cols = mat_arr.shape
if vect_arr.shape == result_arr.shape:
for i in nb.prange(rows):
for j in nb.prange(cols):
result_arr[i] += vect_arr[j] * mat_arr[i, j]
else:
for i in nb.prange(rows):
for j in nb.prange(cols):
result_arr[j] += vect_arr[i] * mat_arr[i, j]
@nb.jit(nopython=True, parallel=True)
def _vector_matrix_product_sum(
vect_arr,
mat_arr,
result_arr):
rows, cols = mat_arr.shape
if vect_arr.shape == result_arr.shape:
for i in nb.prange(rows):
result_arr[i] = np.sum(vect_arr * mat_arr[i, :])
else:
for j in nb.prange(cols):
result_arr[j] = np.sum(vect_arr * mat_arr[:, j])
def vector_matrix_product(
vect_arr,
mat_arr,
swap=False,
dtype=None,
mode=None):
rows, cols = mat_arr.shape
if not dtype:
dtype = (vect_arr[0] * mat_arr[0, 0]).dtype
if not swap:
result_arr = np.zeros(cols, dtype=dtype)
else:
result_arr = np.zeros(rows, dtype=dtype)
if mode == 'sum':
_vector_matrix_product_sum(vect_arr, mat_arr, result_arr)
elif mode == 'mp':
_vector_matrix_product_mp(vect_arr, mat_arr, result_arr)
else:
_vector_matrix_product(vect_arr, mat_arr, result_arr)
return result_arr
np.random.seed(0)
arr = np.random.randint(0, 100, (2, 3, 4))
mask = np.random.randint(0, 2, (2, 3, 4, 5), dtype=bool)
target = arr.ravel() @ mask.reshape(-1, mask.shape[-1])
print(target)
# [820 723 861 486 408]
result1 = vector_matrix_product(arr.ravel(), mask.reshape(-1, mask.shape[-1]))
print(result1)
# [820 723 861 486 408]
result2 = vector_matrix_product(arr.ravel(), mask.reshape(-1, mask.shape[-1]), mode='mp')
print(result2)
# [820 723 861 486 408]
result3 = vector_matrix_product(arr.ravel(), mask.reshape(-1, mask.shape[-1]), mode='sum')
print(result3)
# [820 723 861 486 408]
arr = np.random.randint(0, 100, (256, 256, 256))
mask = np.random.randint(0, 2, (256, 256, 256, 128), dtype=bool)
%timeit np.sum(arr[..., None] * mask, axis=tuple(range(mask.ndim - 1)))
# MemoryError
%timeit arr.ravel() @ mask.reshape(-1, mask.shape[-1])
# MemoryError
%timeit np.array([np.sum(arr * mask[..., i], axis=tuple(range(mask.ndim - 1))) for i in range(mask.shape[-1])])
# 24.1 s ± 105 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit np.array([np.sum(arr[mask[..., i]]) for i in range(mask.shape[-1])])
# 46 s ± 119 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit vector_matrix_product(arr.ravel(), mask.reshape(-1, mask.shape[-1]))
# 408 ms ± 2.12 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit vector_matrix_product(arr.ravel(), mask.reshape(-1, mask.shape[-1]), mode='mp')
# 1.63 s ± 3.58 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit vector_matrix_product(arr.ravel(), mask.reshape(-1, mask.shape[-1]), mode='sum')
# 7.17 s ± 258 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)