通过布尔掩码进行高效的 numpy 值分配

Efficient numpy value assignment via boolean mask

我有一个布尔掩码值分配问题,需要有效的布尔掩码操作。

这是一个多维蒙版,我正在使用 einsum 来实现结果,但是操作效率不是很高,我想知道是否可以得到一些帮助 这是我当前的解决方案:(mask, truth_value, false_value 都是 dtype 和 shape 与我的问题匹配的虚拟数据。

mask = np.random.randn(1000, 50)> 0.5
truth_value = np.random.randn(50, 10)
false_value = np.random.randn(10)
objective = np.einsum('ij,jk->ijk', mask, truth_value) + np.einsum('ij,k->ijk', ~mask, false_value)

给定 mask, truth_value, false_value 有没有更快的方法得到 objective

在等待的过程中,想出了一个更快的方法

objective = np.where(mask[...,np.newaxis], np.broadcast_to(truth_value, (1000, 50, 10)), np.broadcast_to(false_value,  (1000, 50, 10)))

但是有没有更快的选择?

您可以使用 Numba JIT 来更有效地做到这一点。

import numpy as np
import numba as nb

@nb.njit('float64[:,:,::1](bool_[:,::1], float64[:,::1], float64[::1])')
def blend(mask, truth_value, false_value):
    n, m = mask.shape
    l = false_value.shape[0]
    assert truth_value.shape == (m, l)
    result = np.empty((n, m, l), dtype=np.float64)
    for i in range(n):
        for j in range(m):
            if mask[i, j]:
                result[i, j, :] = truth_value[j, :]
            else:
                result[i, j, :] = false_value[:]
    return result

mask = np.random.randn(1000, 50) > 0.5
truth_value = np.random.randn(50, 10)
false_value = np.random.randn(10)
objective = blend(mask, truth_value, false_value)

在我的机器上 objective 的计算速度 4.8 倍

如果这不够快,您可以尝试使用参数 parallel=True 并在基于 i 的循环中使用 nb.prange 而不是 range 来并行化代码。由于创建新线程的开销,这可能不会更快。在我的机器(6 核)上,并行版本 7.4 倍快(与执行时间相比,线程的创建相当昂贵)。

另一种可能的优化是将结果直接写入提前分配的缓冲区中(只有在使用相同的数组大小多次调用此函数时才会更好)。

这是我机器上的总体时间:

np.einsum:         4.32 ms
np.where:          1.72 ms
numba sequential:  0.89 ms
numba parallel:    0.58 ms