如何将一个数组复制/重复N次到一个新数组中?

17

我有:

test = np.random.randn(40,40,3)

我希望你能翻译:

result = Repeat(test, 10)

所以result包含重复了10次的数组test,具有以下形状:

(10, 40, 40, 3)

那么创建一个带有新轴的张量来容纳10个test的副本。我还希望尽可能高效地完成这项工作。如何使用Numpy实现这一点?

因此,使用Numpy创建一个带有新轴的张量,其中包含10个test的副本。如何以最佳方式执行此操作?


2
如果符合您的需求,np.repeat 是有效的。否则,请尝试 np.tileas_strided 可能更有效,但它实际上不会复制。np.repeat(test[None], 10,0) - hpaulj
3个回答

20

可以使用np.repeat方法和np.newaxis一起使用:

import numpy as np

test = np.random.randn(40,40,3)
result = np.repeat(test[np.newaxis,...], 10, axis=0)
print(result.shape)
>> (10, 40, 40, 3)

10
假设您想复制值10次,您可以使用stack函数将数组堆叠10次:
def repeat(arr, count):
    return np.stack([arr for _ in range(count)], axis=0)

axis=0实际上是默认值,所以在这里并不是必要的,但我认为它使你在前面添加新轴更清晰。


事实上,这与stack的示例几乎完全相同:

>>> arrays = [np.random.randn(3, 4) for _ in range(10)]
>>> np.stack(arrays, axis=0).shape
(10, 3, 4)

乍一看,您可能认为 repeat 或者 tile 更适合。
但是,repeat 用于沿着现有轴重复(或展平数组),因此您需要先后使用 reshape。(这样效率也很高,但我认为不太简单。)
而且,tile(假设您使用类似于数组的 reps —— 对于标量 reps,它基本上是 repeat)用于在所有方向填充多维规格,这比您想要的这种简单情况要复杂得多。
所有这些选项的效率都是相同的。它们都复制了数据 10 次,这是昂贵的部分;任何内部处理、构建小型中间对象等的成本都是无关紧要的。唯一使其更快的方法是避免复制。但这可能不是您想要的。
但是,如果您要这样做… 如果要在 10 个副本之间共享行存储,则可能需要使用 broadcast_to
def repeat(arr, count):
    return np.broadcast_to(arr, (count,)+arr.shape)

请注意,broadcast_to实际上并不保证避免复制,只是返回一种只读视图,其中“广播数组的多个元素可能引用单个内存位置”。在实践中,它将避免复制。如果您确实需要出于某种原因保证这一点(或者如果您想要一个可写视图——通常是一个可怕的想法,但也许你有一个好的理由……),您必须下降到as_strided
def repeat(arr, count):
    shape = (count,) + arr.shape
    strides = (0,) + arr.strides
    return np.lib.stride_tricks.as_strided(
        arr, shape=shape, strides=strides, writeable=False)

请注意,对于as_strided的一半文档警告您可能不应该使用它,而另一半文档警告您绝对不应该将其用于可写视图,因此在使用之前,请确保这是您想要的。


1
“broadcast_to” 可能也可以处理无需复制的情况,np.broadcast_to(arr, (count,)+arr.shape) - miradulo
@miradulo 很好的观点。事实上,我认为broadcast_to基本上是一个便利函数,旨在确保没有人会将as_strided用于此用例... - abarnert
我是stack的粉丝,但在这里concatenate更快。stack会为列表中的每个参数添加一个维度。将其添加一次,进行列表复制,然后连接起来会更快。 - hpaulj
@hpaulj 但是 concatenate 沿着现有的轴进行连接,而不是创建一个新的轴。因此,它既需要像 repeat 一样预先或后重塑,又需要像 stack 一样明确地传递 arr 的10个参数。 - abarnert
除非您传入[[arr for _ in range(10)]]而不是仅传入[arr for _ in range(10)]以模拟额外的轴,但这样会变慢而不是更快。从一个快速的%timeitstack需要27.3微秒,带有错误答案的concatenate需要23.6微秒,带有正确答案的concatenate需要35.6微秒。同时,对于1000倍大小的数组,它们都在17毫秒左右。 - abarnert

2
在许多创建适当副本的方法中,预分配+广播似乎是最快的。
import numpy as np

def f_pp_0():
    out = np.empty((10, *a.shape), a.dtype)
    out[...] = a
    return out

def f_pp_1():
    out = np.empty((10, *a.shape), a.dtype)
    np.copyto(out, a)
    return out

def f_oddn():
    return np.repeat(a[np.newaxis,...], 10, axis=0)

def f_abar():
    return np.stack([a for _ in range(10)], axis=0)

def f_arry():
    return np.array(10*[a])

from timeit import timeit

a = np.random.random((40, 40, 3))

for f in list(locals().values()):
    if callable(f) and f.__name__.startswith('f_'):
        print(f.__name__, timeit(f, number=100000)/100, 'ms')

样例执行:

f_pp_0 0.019641224660445003 ms
f_pp_1 0.019557840081397444 ms
f_oddn 0.01983011547010392 ms
f_abar 0.03257150553865358 ms
f_arry 0.02305851033888757 ms

但是差异很小,例如repeat几乎没有变慢。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接