2017-08-03 105 views
1

我想创建一个NumPy的阵列具有稍微重复结构:特定的函数(这里,作为一个例子,shuffle()),需要两个号码并返回阵列(这里与长度为8,可能会更多,但)。这些数组然后连接起来。高效地创建NumPy的阵列重复结构

import numpy 


def shuffle(a, b): 
    return numpy.array([ 
     [+a, +b], [-a, +b], [+a, -b], [-a, -b], 
     [+b, +a], [-b, +a], [+b, -a], [-b, -a], 
     ]) 


pairs = [ 
    (0.1, 0.2), 
    (3.14, 2.71), 
    # ... many, without a particular pattern ... 
    (0.707, 0.577) 
    ] 
out = numpy.concatenate([shuffle(*pair) for pair in pairs]) 

我想,这里所发生的是,长度为8的所有子阵列在内存独立创建,只是在马上被复制,形成更大的阵列out。当存在许多对(a, b)或当shuffle被返回更多数据的东西所取代时,这会变得毫无必要地低效。解决此

一种方法是硬编码out点菜

out = numpy.array([ 
    [+0.1, +0.2], 
    [-0.1, +0.2], 
    # ... 
    [-0.2, -0.1], 
    [+3.14, +2.71], 
    # ... 
    ]) 

但是这显然是不可取的要么。

在C中,我可能会使用预处理器分析的宏。

有关如何安排上述代码以避免不必要副本的任何提示?

+0

你可以使用矩阵运算更有效地做到这一点,我期望。 – will

+0

听起来像是为'itertools.permutations'构建的东西 –

+0

如果您分配一个空数组'np.empty(dims)'然后逐块填充它,这将避免它。 –

回答

1

此:

[ 
    [+a, +b], [-a, +b], [+a, -b], [-a, -b], 
    [+b, +a], [-b, +a], [+b, -a], [-b, -a], 
    ] 

是列表的列表。对数字进行硬编码几乎没有区别。

np.array(...)然后将列表转换为数组。

np.fromiterable往往会更快,但只适用于1d数据,因此需要重新塑形。

这一步真的是那么大的时间消费者吗?

一段时间的探索:

In [245]: timeit shuffle(1,2) 
9.29 µs ± 12.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) 
... 
In [248]: out=np.concatenate([shuffle(1,2) for _ in range(100)]) 
In [249]: out.shape 
Out[249]: (800, 2) 
In [250]: timeit out=np.concatenate([shuffle(1,2) for _ in range(100)]) 
1.02 ms ± 4.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) 

产生相同大小的数组,但有一个简单的串联。这可能,如果它产生正确的数字可选速度:

In [251]: np.stack([np.arange(800),np.arange(800)],1).shape 
Out[251]: (800, 2) 
In [252]: timeit np.stack([np.arange(800),np.arange(800)],1).shape 
21.4 µs ± 902 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each) 

我们可以探索的替代品,但在一定程度上要以清晰放在首位。什么是生成所需数组的最清晰的方法?

让我们尝试没有中间array呼叫

def shuffle1(a, b): 
    return [ 
     [+a, +b], [-a, +b], [+a, -b], [-a, -b], 
     [+b, +a], [-b, +a], [+b, -a], [-b, -a], 
     ] 

In [259]: timeit np.array([shuffle1(1,2) for _ in range(100)]).reshape(-1,2) 
765 µs ± 14.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) 

1ms的v .75ms - 一个温和的速度提高。

在洗牌使用fromiter代替np.array减少了一半时间:

def shuffle2(a, b): 
    return np.fromiter(
     [+a, +b, -a, +b, +a, -b, -a, -b, 
     +b, +a, -b, +a, +b, -a, -b, -a, 
     ],int).reshape(-1,2) 

In [279]: timeit out=np.concatenate([shuffle2(1,2) for _ in range(100)]) 
503 µs ± 4.56 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) 
+0

啊,没错,在'numpy.array([a,b,c])'中,第一件事就是创建列表'[a,b,c]'。也许不是从'shuffle()'返回'numpy.array',我可以返回一个'list',这样我以后只需要'np.array'一次。 –

+0

哈!我刚刚检查并发现,令我惊讶的是,'np.concatenate'实际上是列表列表上的_lowlow_,而不是'np.array'列表,即使这些列表转换为np.array's第一。 –

+0

在列表(列表)的较大嵌套列表中使用'np.array(...)可以适度提高速度。查看我的编辑。 – hpaulj

1

这是一个使用索引看中的方法。

pairs是你的样品输入,存储在阵列numpy的:

In [7]: pairs 
Out[7]: 
array([[ 0.1 , 0.2 ], 
     [ 3.14 , 2.71 ], 
     [ 0.707, 0.577]]) 

pairspm是一个数组,其行是[a, b, -a, -b]

In [8]: pairspm = np.hstack((pairs, -pairs)) 

indices的值是索引为对应于8×2图案形式[a, b, -a, -b]的阵列在shuffle(a, b)

In [9]: indices = np.array([[0, 1], [2, 1], [0, 3], [2, 3], [1, 0], [3, 0], [1, 2], [3, 2]]) 

out现在的pairspm只是看上索引,后跟一个重塑到倒塌的pairspm[:, indices]前两个维度为一个:

In [10]: out = pairspm[:, indices].reshape(-1, 2) 

In [11]: out 
Out[11]: 
array([[ 0.1 , 0.2 ], 
     [-0.1 , 0.2 ], 
     [ 0.1 , -0.2 ], 
     [-0.1 , -0.2 ], 
     [ 0.2 , 0.1 ], 
     [-0.2 , 0.1 ], 
     [ 0.2 , -0.1 ], 
     [-0.2 , -0.1 ], 
     [ 3.14 , 2.71 ], 
     [-3.14 , 2.71 ], 
     [ 3.14 , -2.71 ], 
     [-3.14 , -2.71 ], 
     [ 2.71 , 3.14 ], 
     [-2.71 , 3.14 ], 
     [ 2.71 , -3.14 ], 
     [-2.71 , -3.14 ], 
     [ 0.707, 0.577], 
     [-0.707, 0.577], 
     [ 0.707, -0.577], 
     [-0.707, -0.577], 
     [ 0.577, 0.707], 
     [-0.577, 0.707], 
     [ 0.577, -0.707], 
     [-0.577, -0.707]]) 

(随着一点点更多的工作,你可以消除对pairspm的需要。)

0

这里是另一种方法是建立在整个输出结果,无堆叠单个阵列:

import numpy as np 
# generate some data: 
pairs = np.random.randint(1, 100, (1000, 2)) 
# create "sign" array: 
u = np.array([[[1, 1], [-1, 1], [1, -1], [-1, -1]]]) 
# create full output array: 
out = (pairs[:, None, :] * u).reshape((-1, 2)) 

时间:

%timeit (pairs[:, None, :] * u).reshape((-1, 2)) 
10000 loops, best of 3: 49 µs per loop 
0

如果您事先知道尺寸,可以分配一个空数组然后填充它。假设你知道对的长度,从一开始就知道最终的数组大小,然后我们可以在16个块的“平坦”视图中跨越数组并填充它。

def gen(pairs): 
    out = np.empty((8 * len(pairs), 2), dtype=float) 
    for n, (a, b) in enumerate(pairs): 
     out.flat[16*n:16*(n+1)] = [ 
      +a, +b, -a, +b, +a, -b, -a, -b, 
      +b, +a, -b, +a, +b, -a, -b, -a, 
     ] 
    return out