繁体   English   中英

通过Voxel随机播放4维时间序列

[英]Shuffle 4-dimensional time-series by voxel

我有一个4维数组,它是3维数组的时间序列。 我想沿时间轴对3维数组中的每个点进行混洗。 这是我编写的使用嵌套的for循环执行此操作的代码。 可以通过花哨的numpy索引完成此操作吗? 速度是一个因素。 谢谢。

import numpy as np

timepoints = 2
x = 4
y = 4
z = 3

vol_1 = np.zeros((x, y, z))
vol_2 = np.ones((x, y, z))
timeseries = np.array((vol_1, vol_2))

timeseries.shape  # (2, 4, 4, 3)

# One voxel over time.
timeseries[:, 0, 0, 0]

for xx in range(x):
    for yy in range(y):
        for zz in range(z):
            np.random.shuffle(timeseries[:, xx, yy, zz])

我们可以沿第一个轴生成所有改组后的索引,然后只需使用advanced-indexing即可获得随机版本。 现在,要获得所有这些经过改组的索引,我们可以生成一个与输入数组形状相同的随机数组,并沿第一个轴获取argsort索引。 就像here以前,已经对此进行了探索。

因此,我们将有一个矢量化的实现,如下所示:

m,n,r,p = a.shape # a is the input array
idx = np.random.rand(*a.shape).argsort(0)
out = a[idx, np.arange(n)[:,None,None], np.arange(r)[:,None], np.arange(p)]

只是向读者解释问题的确切原因,下面是一个示例运行-

1)输入4D数组

In [711]: a
Out[711]: 
array([[[[60, 22, 34],
         [29, 18, 79]],

        [[11, 69, 41],
         [75, 30, 30]]],


       [[[63, 61, 42],
         [70, 56, 57]],

        [[70, 98, 71],
         [29, 93, 96]]]])

2)用建议的方法沿第一轴进行索引生成的随机索引:

In [712]: idx
Out[712]: 
array([[[[1, 0, 1],
         [0, 1, 1]],

        [[0, 0, 1],
         [1, 0, 1]]],


       [[[0, 1, 0],
         [1, 0, 0]],

        [[1, 1, 0],
         [0, 1, 0]]]])

3)最后索引到输入数组以随机输出:

In [713]: out
Out[713]: 
array([[[[63, 22, 42],
         [29, 56, 57]],

        [[11, 69, 71],
         [29, 30, 96]]],


       [[[60, 61, 34],
         [70, 18, 79]],

        [[70, 98, 41],
         [75, 93, 30]]]])

仔细观察,我们将看到在a[0,0,0,0]处交换了63 ,在a[1,0,0,0]处交换了60a[1,0,0,0]是因为idx值分别在相应的位置被替换为10 idx 接下来,由于idx值为01 ,依此类推,所以2261停留在原处。

运行时测试

In [726]: timeseries = np.random.rand(10,10,10,10)

In [727]: %timeit org_app(timeseries)
100 loops, best of 3: 5.24 ms per loop

In [728]: %timeit proposed_app(timeseries)
1000 loops, best of 3: 289 µs per loop

In [729]: timeseries = np.random.rand(50,50,50,50)

In [730]: %timeit org_app(timeseries)
1 loop, best of 3: 720 ms per loop

In [731]: %timeit proposed_app(timeseries)
1 loop, best of 3: 426 ms per loop

在大尺寸情况下,创建随机数组的成本被证明是所提出方法的瓶颈,但与原始循环版本相比,仍显示出良好的加速效果。

我将其添加为答案,因为它不适合注释,因为它只是@Divakar出色答案的一小部分:

def divakar(a):
    m,n,r,p = a.shape # a is the input array
    idx = np.random.rand(*a.shape).argsort(0)
    return a[idx, np.arange(n)[:,None,None], np.arange(r)[:,None], np.arange(p)]

a = np.random.rand(50,50,50,50)
%timeit divakar(a)
560 ms ± 2.62 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

我已经观察到通过多次使用重塑而不是广播来提高速度,例如:

def norok2(a):
    shape = a.shape
    idx = np.random.rand(*a.shape).argsort(0).reshape(shape[0], -1)
    return a.reshape(shape[0], -1)[idx, np.arange(shape[1] * shape[2] * shape[3])].reshape(shape)

a = np.random.rand(50,50,50,50)
%timeit norok2(a)
495 ms ± 1.61 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

与OP的提案相比:

def jakub(a):
    t, x, y, z = a.shape
    for xx in range(x):
        for yy in range(y):
            for zz in range(z):
                np.random.shuffle(a[:, xx, yy, zz])


%timeit jakub(a)
2 s ± 30.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

顺便说一句,我提出的修改更容易扩展到n维数组和任意改组轴,例如:

import numpy as np
import functools

def shuffle_axis(arr, axis=0):
    arr = np.swapaxes(arr, 0, axis)
    shape = arr.shape
    i = np.random.rand(*shape).argsort(0).reshape(shape[0], -1)
    return arr.reshape(shape[0], -1)[i, np.arange(functools.reduce(lambda x, y: x * y, shape[1:]))].reshape(shape).swapaxes(axis, 0)

以相似的速度:

a = np.random.rand(50,50,50,50)
%timeit shuffle_axis(a)
499 ms ± 2.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

重新编辑

...而且时机还不比随机化所有事情差很多:

a = np.random.rand(50,50,50,50)
%timeit np.random.shuffle(a.ravel())
310 ms ± 1.84 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

这应该是该问题的任何解决方案性能的某种下限(但不能解决OP问题)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM