在 numpy 中编写自定义缩减函数

Writing a custom reduction function in numpy

我想做的: 假设我有一维数组形式的数据。拟合该数据后,(scipy.optimize.curve_fit),这将减少为 skaler/0D 数组。到目前为止,一切都很好。这是最简单的部分。

问题是,数据实际上不是一维的,而是 (n+1)D。所以我将不得不在除一个轴之外的所有轴上遍历整个数组,取一个 1D 切片,适合该切片并将其写入具有 n 维的新数组。为了简单起见,我在这个示例代码中使用了求和函数而不是拟合。

def iter_columns(array: np.ndarray, axis=-1) -> np.ndarray:
    """
    Reduce nd-data to (n-1)d data. By performing the operation on one axis.
    :param array: Input data
    :param axis: Axis to perform reduction over
    :return: Array of reduced data
    """
    reduced_shape = list(array.shape)
    reduced_shape.pop(axis)
    print(reduced_shape)
    a = np.empty(tuple(reduced_shape))
    print(a)
    print(array)
    with np.nditer(a, flags=['multi_index'], op_flags=[['writeonly']]) as it:
        for a_i in it:
            # modify multi index to slice over dimension of axis, append if axis
            mod = list(it.multi_index)
            mod.insert(axis if axis>=0 else len(mod),slice(None))
            print(mod)
            a_i[...] = b[tuple(mod)].sum()
    return a


b = np.arange(10).reshape(5,2)
print(iter_columns(b, axis=-1))

虽然这看起来像它应该做的那样,但它看起来并不优雅。我尝试以其他方式使用 np.nditer,但我不明白如何告诉 nditer 加载块而不是仅加载单个数组条目。我也知道有一个 ufunc.reduce 函数正是为此而设计的,但是我找不到关于如何构造它可以使用的函数的文档。

对于一般的 python 函数,没有一种快速编译的方法来进行这种归约。无论迭代机制如何,您最终都必须为每个 nD 值集调用一次 func

对于 np.sum 你可以只指定 axis。这本质上是一个 np.add.reduce.

np.apply_along_axis 的工作方式与您的 nditer 非常相似,除了它将切片维度移动到末尾,使 'insert' 更容易。它使用 ndindex 生成索引元组——但它也使用 nditer。它的文档是错误的;它并不快。

一些比较时间:

In [223]: timeit iter_columns(b, axis=-1)
55.2 µs ± 1.48 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [224]: timeit iter_columns(b, axis=-1)
54.6 µs ± 63.9 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [225]: timeit np.array([i.sum() for i in b])
40 µs ± 1.63 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [226]: timeit b.sum(-1)
6.86 µs ± 12.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

您对 nditer 的使用是正确的(通常人们对此有疑问),但如您所见,它并没有提供任何速度优势。对于更高的维度,编写多个循环同样快。

处理更高维度的另一种方法是最后移动 sum 轴,将其余轴重塑为 1d,然后做一个简单的循环,然后重塑。

另一种选择是编译您的函数和循环。 numba 是一个强大的工具。 nditer 文档页面之一显示了如何在 cython.

中使用 nditer