2017-08-03 86 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