在二维数组列上映射 Dask bincount

Map Dask bincount over 2d array columns

我正在尝试对二维数组使用 bincount。具体来说,我有这段代码:

import numpy as np
import dask.array as da

def dask_bincount(weights, x):
    da.bincount(x, weights)

idx = da.random.random_integers(0, 1024, 1000)
weight = da.random.random((1000, 2))
bin_count = da.apply_along_axis(dask_bincount, 1, weight, idx)

我们的想法是,可以在每个权重列上使用相同的 idx 数组创建 bincount。如果我是正确的,那将 return 一个大小为 (np.amax(x) + 1, 2) 的数组。 但是,这样做时我收到此错误消息:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-17-5b8eed89ad32> in <module>
----> 1 bin_count = da.apply_along_axis(dask_bincount, 1, weight, idx)

~/.local/lib/python3.9/site-packages/dask/array/routines.py in apply_along_axis(func1d, axis, arr, dtype, shape, *args, **kwargs)
    454     if shape is None or dtype is None:
    455         test_data = np.ones((1,), dtype=arr.dtype)
--> 456         test_result = np.array(func1d(test_data, *args, **kwargs))
    457         if shape is None:
    458             shape = test_result.shape

<ipython-input-14-34fd0eb9b775> in dask_bincount(weights, x)
      1 def dask_bincount(weights, x):
----> 2     da.bincount(x, weights)

~/.local/lib/python3.9/site-packages/dask/array/routines.py in bincount(x, weights, minlength, split_every)
    670         raise ValueError("Input array must be one dimensional. Try using x.ravel()")
    671     if weights is not None:
--> 672         if weights.chunks != x.chunks:
    673             raise ValueError("Chunks of input array x and weights must match.")
    674 

AttributeError: 'numpy.ndarray' object has no attribute 'chunks'

我以为创建 dask 数组时库会自动为它们分配块,所以错误并没有说明太多。我该如何解决这个问题?

我用 map 在 numpy 上做了一个脚本。

idx_np = np.random.randint(0, 1024, 1000)
weight_np = np.random.random((1000,2))
f = lambda y: np.bincount(idx_np, weight_np[:,y])
result = map(f, [i for i in range(2)])
np.array(list(result))
array([[0.9885341 , 0.9977873 , 0.24937023, ..., 0.31024526, 1.40754883,
        0.87609759],
       [1.77406303, 0.84787723, 0.14591474, ..., 0.54584068, 0.38357015,
        0.85202672]])

我也想这样做,但有 dask

有多个问题在起作用。

权重应该是(2, 1000)

您通过尝试使用 apply_along_axis 在 numpy 中编写相同的函数来发现这一点。

idx_np = np.random.random_integers(0, 1024, 1000)
weight_np = np.random.random((2, 1000))  # <- transposed
# This gives the same result as the code you provided
np.apply_along_axis(lambda weight, idx: np.bincount(idx, weight), 1, weight_np, idx_np)

da.apply_along_axis 将函数应用于 numpy 数组

你遇到了错误

AttributeError: 'numpy.ndarray' object has no attribute 'chunks'

这表明进入 da.bincount 方法的实际上是一个 numpy 数组。事实是 da.apply_along_axis 实际上获取 weight 的每一行并将其作为 numpy 数组发送到函数。

因此,您的函数实际上应该是一个 numpy 函数:

def bincount(weights, x):
    return np.bincount(x, weights)

但是,如果您尝试这样做,您仍然会遇到同样的错误。我相信这完全是另一个原因:

Dask 不知道输出形状是什么并试图推断它

apply_along_axis 的代码 and/or 文档中,我们可以看到 Dask 试图推断输出形状和 dtype by passing in the array [1] (related question)。这是个问题,因为 bincount 不能接受这样的论点。

我们可以做的是为方法提供 shapedtype,这样 Dask 就不必推断它了。

这里的问题是bincount的输出形状取决于输入数组的最大值。除非您事先知道,否则您将很遗憾地需要计算它。因此整个操作不会完全懒惰。

这是完整答案:

import numpy as np
import dask.array as da

idx = da.random.random_integers(0, 1024, 1000)
weight = da.random.random((2, 1000))

def bincount(weights, x):
    return np.bincount(x, weights)

m = idx.max().compute()

da.apply_along_axis(bincount, 1, weight, idx, shape=(m,), dtype=weight.dtype)

附录:randint 对比 random_integers

小心,因为它们有细微的不同

  • randint 取从 low(含)到 high(不含)的整数
  • random_integers取从low(含)到high(含)的整数

因此您必须调用 randinthigh + 1 以获得相同的值。