什么是矢量化方式来创建NumPy数组的多个权力?

时间:2018-05-19 19:18:55

标签: python arrays numpy vectorization

我有一个NumPy数组:

arr = [[1, 2],
       [3, 4]]

我想创建一个新数组,其中包含arr最大幂order的权力:

# arr_new = [arr^0, arr^1, arr^2, arr^3,...arr^order]
arr_new = [[1, 1, 1, 2, 1, 4, 1, 8],
           [1, 1, 3, 4, 9, 16, 27, 64]]

我当前的方法使用for循环:

# Pre-allocate an array for powers
arr = np.array([[1, 2],[3,4]])
order = 3
rows, cols = arr.shape
arr_new = np.zeros((rows, (order+1) * cols))

# Iterate over each exponent
for i in range(order + 1):
    arr_new[:, (i * cols) : (i + 1) * cols] = arr**i
print(arr_new)

是否有更快(即矢量化)的方法来创建数组的权力?

基准

感谢@hpaulj和@Divakar以及@Paul Panzer的答案。我在以下测试阵列上对基于循环和基于广播的操作进行了基准测试。

arr = np.array([[1, 2],
                [3,4]])
order = 3

arrLarge = np.random.randint(0, 10, (100, 100))  # 100 x 100 array
orderLarge = 10

loop_based功能是:

def loop_based(arr, order):
    # pre-allocate an array for powers
    rows, cols = arr.shape
    arr_new = np.zeros((rows, (order+1) * cols))
    # iterate over each exponent
    for i in range(order + 1):
        arr_new[:, (i * cols) : (i + 1) * cols] = arr**i
    return arr_new

使用broadcast_based的{​​{1}}功能是:

hstack

使用def broadcast_based_hstack(arr, order): # Create a 3D exponent array for a 2D input array to force broadcasting powers = np.arange(order + 1)[:, None, None] # Generate values (third axis contains array at various powers) exponentiated = arr ** powers # Reshape and return array return np.hstack(exponentiated) # <== using hstack function 的{​​{1}}功能是:

broadcast_based

reshape功能使用累积产品def broadcast_based_reshape(arr, order): # Create a 3D exponent array for a 2D input array to force broadcasting powers = np.arange(order + 1)[:, None] # Generate values (3-rd axis contains array at various powers) exponentiated = arr[:, None] ** powers # reshape and return array return exponentiated.reshape(arr.shape[0], -1) # <== using reshape function broadcast_based

cumprod

在Jupyter笔记本上,我使用reshape命令得到了这些结果:

小阵列(2x2)

def broadcast_cumprod_reshape(arr, order):
    rows, cols = arr.shape
    # Create 3D empty array where the middle dimension is
    # the array at powers 0 through order
    out = np.empty((rows, order + 1, cols), dtype=arr.dtype)
    out[:, 0, :] = 1   # 0th power is always 1
    a = np.broadcast_to(arr[:, None], (rows, order, cols))
    # Cumulatively multiply arrays so each multiplication produces the next order
    np.cumprod(a, axis=1, out=out[:,1:,:])
    return out.reshape(rows, -1)

大型阵列(100x100)

timeit

结论:

对于较小的阵列,使用%timeit -n 100000 loop_based(arr, order) 7.41 µs ± 174 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) %timeit -n 100000 broadcast_based_hstack(arr, order) 10.1 µs ± 137 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) %timeit -n 100000 broadcast_based_reshape(arr, order) 3.31 µs ± 61.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) %timeit -n 100000 broadcast_cumprod_reshape(arr, order) 11 µs ± 102 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) 的基于广播的方法似乎更快。但是,对于大型数组,%timeit -n 1000 loop_based(arrLarge, orderLarge) 261 µs ± 5.82 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit -n 1000 broadcast_based_hstack(arrLarge, orderLarge) 225 µs ± 4.15 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit -n 1000 broadcast_based_reshape(arrLarge, orderLarge) 223 µs ± 2.16 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit -n 1000 broadcast_cumprod_reshape(arrLarge, orderLarge) 157 µs ± 1.02 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) 方法可以更好地扩展并且速度更快。

4 个答案:

答案 0 :(得分:7)

reshaping -

的帮助下,将数组扩展到更高的dims并让broadcasting执行 magic
In [16]: arr = np.array([[1, 2],[3,4]])

In [17]: order = 3

In [18]: (arr[:,None]**np.arange(order+1)[:,None]).reshape(arr.shape[0],-1)
Out[18]: 
array([[ 1,  1,  1,  2,  1,  4,  1,  8],
       [ 1,  1,  3,  4,  9, 16, 27, 64]])

请注意,arr[:,None]基本上是arr[:,None,:],但为简洁起见,我们可以跳过尾随省略号。

更大数据集上的计时 -

In [40]: np.random.seed(0)
    ...: arr = np.random.randint(0,9,(100,100))
    ...: order = 10

# @hpaulj's soln with broadcasting and stacking
In [41]: %timeit np.hstack(arr **np.arange(order+1)[:,None,None])
1000 loops, best of 3: 734 µs per loop

In [42]: %timeit (arr[:,None]**np.arange(order+1)[:,None]).reshape(arr.shape[0],-1)
1000 loops, best of 3: 401 µs per loop

重塑部分实际上是免费的,这就是我们在这里与广播部分一起获得表现的地方,如下面的细分所示 -

In [52]: %timeit (arr[:,None]**np.arange(order+1)[:,None])
1000 loops, best of 3: 390 µs per loop

In [53]: %timeit (arr[:,None]**np.arange(order+1)[:,None]).reshape(arr.shape[0],-1)
1000 loops, best of 3: 401 µs per loop

答案 1 :(得分:7)

使用广播生成值,并根据需要重新整形或重新排列值:

> x2 <- as.POSIXct(Sys.time() - 0:2)
> as.list(x2)
[[1]]
[1] "2018-05-19 22:25:57 EEST"

[[2]]
[1] "2018-05-19 22:25:56 EEST"

[[3]]
[1] "2018-05-19 22:25:55 EEST"

答案 2 :(得分:6)

这是一个使用累积乘法的解决方案,它比基于幂的方法更好地扩展,特别是如果输入数组是background-size: contain !important; background-repeat: no-repeat !important; background-position: 0 50px !important; dtype:

float

时序:

import numpy as np

def f_mult(a, k):
    m, n = a.shape
    out = np.empty((m, k, n), dtype=a.dtype)
    out[:, 0, :] = 1
    a = np.broadcast_to(a[:, None], (m, k-1, n))
    a.cumprod(axis=1, out=out[:, 1:])
    return out.reshape(m, -1)

时间码,来自@Divakar:

int up to power 9
divakar: 0.4342731796205044 ms
hpaulj:  0.794165057130158 ms
pp:      0.20520629966631532 ms
float up to power 39
divakar: 29.056487752124667 ms
hpaulj:  31.773792404681444 ms
pp:      1.0329263447783887 ms

答案 3 :(得分:2)

您正在创建一个带有重塑的Vandermonde matrix,因此最好使用numpy.vander来制作它,并让其他人处理最佳算法。

这样你的代码就是:

np.vander(arr.ravel(), order).reshape((arr.shape[0], -1))

那就是说,似乎他们使用像Paul Panzer的cumprod方法under the hood这样的东西,所以它应该很好地扩展。