我想创建一个具有某种重复结构的NumPy数组:一个特定的函数(这里,作为一个例子,shuffle()
),取两个数字并返回一个数组(这里长度为8,可能更多虽然)。然后连接这些数组。
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
àla
out = numpy.array([
[+0.1, +0.2],
[-0.1, +0.2],
# ...
[-0.2, -0.1],
[+3.14, +2.71],
# ...
])
但这显然也不可取。
在C中,我可能会使用由预处理器解析的宏。
有关如何安排上述代码的任何提示,以避免不必要的副本?
答案 0 :(得分: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)
答案 1 :(得分: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
中的值是与[a, b, -a, -b]
中的8x2模式对应的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
的需求。)
答案 2 :(得分: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
答案 3 :(得分: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