【问题标题】:Using numpy.vectorize() to rotate all elements of a NumPy array使用 numpy.vectorize() 旋转 NumPy 数组的所有元素
【发布时间】:2017-11-28 12:25:45
【问题描述】:

我正处于学习 NumPy 的初级阶段。我有一个 3x3 矩阵的 Numpy 数组。我想创建一个新数组,其中每个矩阵都旋转 90 度。我已经研究过这个answer,但我仍然无法弄清楚我做错了什么。

import numpy as np

# 3x3
m = np.array([[1,2,3], [4,5,6], [7,8,9]])

# array of 3x3
a = np.array([m,m,m,m])

# rotate a single matrix counter-clockwise
def rotate90(x):
    return np.rot90(x)

# function that can be called on all elements of an np.array
# Note: I've tried different values for otypes= without success
f = np.vectorize(rotate90)

result = f(a)
# ValueError: Axes=(0, 1) out of range for array of ndim=0.
# The error occurs in NumPy's rot90() function.

注意:我意识到我可以执行以下操作,但我想了解矢量化选项。

t = np.array([ np.rot90(x, k=-1) for x in a])

【问题讨论】:

  • 您的数组 a 是 3d。检查它的形状 (4,3,3) 和 dtype, jnt。所以它的元素是数字,而不是m 2d 数组。 vectorize 将这些数字而不是 3x3 数组提供给您的函数。它不是一种仅在数组的一维上进行迭代的工具。
  • @hpaulj 谢谢。有没有办法设置它,以便我的数组只是一个“3x3 矩阵数组”? (即,vectorize 会将每个元素视为一个单独的实体)
  • 再次查看vectorize 文档。它不会向你承诺太阳月亮和星星。-速度。
  • 但即使map 也不承诺速度。它只是列表理解的替代方案。无论如何,np.vectorize 可能是错误命名的,并且经常被误用。但是速度免责声明非常明确。至于公平 - cmets 不会以任何方式影响您的声誉。
  • 在重读vectorize 文档时,我找到了一种解决问题的方法——一个新的signature 参数。

标签: python numpy vectorization


【解决方案1】:

无需单独进行旋转:numpy 具有内置的numpy.rot90(m, k=1, axes=(0, 1)) 函数。默认情况下,矩阵会在第一维和第二维上旋转。

如果您想更深一层旋转,您只需将发生旋转的轴设置为更深一层(如果您想沿不同方向旋转,还可以选择交换它们)。或者按照文档说明:

axes: (2,) array_like

   阵列在由 轴。轴必须不同。

所以我们在 yz 平面上旋转(如果我们标注尺寸 xyz),因此我们可以指定(2,1)(1,2)

当您想要旋转到右/左时,您只需正确设置axes

np.rot90(a,axes=(2,1)) # right
np.rot90(a,axes=(1,2)) # left

这将旋转所有矩阵,例如:

>>> np.rot90(a,axes=(2,1))
array([[[7, 4, 1],
        [8, 5, 2],
        [9, 6, 3]],

       [[7, 4, 1],
        [8, 5, 2],
        [9, 6, 3]],

       [[7, 4, 1],
        [8, 5, 2],
        [9, 6, 3]],

       [[7, 4, 1],
        [8, 5, 2],
        [9, 6, 3]]])

或者如果你想向左旋转

>>> np.rot90(a,axes=(1,2))
array([[[3, 6, 9],
        [2, 5, 8],
        [1, 4, 7]],

       [[3, 6, 9],
        [2, 5, 8],
        [1, 4, 7]],

       [[3, 6, 9],
        [2, 5, 8],
        [1, 4, 7]],

       [[3, 6, 9],
        [2, 5, 8],
        [1, 4, 7]]])

请注意,您只能从 numpy 1.12 和(可能)未来版本中指定 axes

【讨论】:

    【解决方案2】:

    通常np.vectorize 用于将标量(Python,非numpy)函数应用于数组或数组集的所有元素。有一个注释经常被忽略:

    vectorize 函数主要是为了方便,而不是为了 表现。该实现本质上是一个 for 循环。

    In [278]: m = np.array([[1,2,3],[4,5,6]])
    In [279]: np.vectorize(lambda x:2*x)(m)
    Out[279]: 
    array([[ 2,  4,  6],
           [ 8, 10, 12]])
    

    这会将m 的每个元素乘以 2,为我们处理循环文书工作。

    更好的是,当给定几个数组时,它会广播(“外积”的概括)。

    In [280]: np.vectorize(lambda x,y:2*x+y)(np.arange(3), np.arange(2)[:,None])
    Out[280]: 
    array([[0, 2, 4],
           [1, 3, 5]])
    

    这会将(x,y) 标量元组提供给 lambda,用于针对 (2,1) 数组广播的 (3,) 数组的所有组合,从而生成 (2,3) 数组。可以看成是map的广播扩展。

    np.vectorize(np.rot90) 的问题在于 rot90 采用二维数组,但 vectorize 将提供标量。

    但是我在文档中看到v1.12 他们添加了一个签名参数。这是我第一次使用它。

    您的问题 - 将 np.rot90 应用于 3d 数组的 2d 元素:

    In [266]: m = np.array([[1,2,3],[4,5,6]])
    In [267]: a = np.stack([m,m])
    In [268]: a
    Out[268]: 
    array([[[1, 2, 3],
            [4, 5, 6]],
    
           [[1, 2, 3],
            [4, 5, 6]]])
    

    虽然您可以将此 a 描述为 2d 数组的数组,但最好将其视为 3d 整数数组。这就是np.vectorize(myfun)(a) 看到它的方式,给myfun 每个数字。

    应用于二维m

    In [269]: np.rot90(m)
    Out[269]: 
    array([[3, 6],
           [2, 5],
           [1, 4]])
    

    借助 Python 工作马,列表推导:

    In [270]: [np.rot90(i) for i in a]
    Out[270]: 
    [array([[3, 6],
            [2, 5],
            [1, 4]]), array([[3, 6],
            [2, 5],
            [1, 4]])]
    

    结果是一个列表,但我们可以将其包装在 np.array 中。

    Python map 做同样的事情。

    In [271]: list(map(np.rot90, a))
    Out[271]: 
    [array([[3, 6],
            [2, 5],
            [1, 4]]), array([[3, 6],
            [2, 5],
            [1, 4]])]
    

    理解和映射都在 a 的第一个维度上进行迭代,对生成的 2d 元素执行操作。

    vectorizesignature

    In [272]: f = np.vectorize(np.rot90, signature='(n,m)->(k,l)')
    In [273]: f(a)
    Out[273]: 
    array([[[3, 6],
            [2, 5],
            [1, 4]],
    
           [[3, 6],
            [2, 5],
            [1, 4]]])
    

    signature 告诉它传递一个二维数组并期望返回一个二维数组。 (我应该探索signature 如何使用otypes 参数。)

    一些快速的时间比较:

    In [287]: timeit np.array([np.rot90(i) for i in a])
    10000 loops, best of 3: 40 µs per loop
    In [288]: timeit np.array(list(map(np.rot90, a)))
    10000 loops, best of 3: 41.1 µs per loop
    In [289]: timeit np.vectorize(np.rot90, signature='(n,m)->(k,l)')(a)
    1000 loops, best of 3: 234 µs per loop
    In [290]: %%timeit f=np.vectorize(np.rot90, signature='(n,m)->(k,l)')
         ...: f(a)
         ...: 
    1000 loops, best of 3: 196 µs per loop
    

    所以对于一个小数组,Python 列表方法要快很多。有时,numpy 方法在更大的数组上效果更好,尽管我对此表示怀疑。

    rot90 带有轴参数更好,并且可以很好地处理更大的数组:

    In [292]: timeit np.rot90(a,axes=(1,2))
    100000 loops, best of 3: 15.7 µs per loop
    

    查看np.rot90 代码,我发现它只是在执行np.flip(反向)和np.transpose,根据k 进行各种组合。在这种情况下,它正在执行以下操作:

    In [295]: a.transpose(0,2,1)[:,::-1,:]
    Out[295]: 
    array([[[3, 6],
            [2, 5],
            [1, 4]],
    
           [[3, 6],
            [2, 5],
            [1, 4]]])
    

    (这比rot90 还要快。)


    我怀疑 vectorizesignature 正在做类似的事情:

    In [301]: b = np.zeros(2,dtype=object)
    In [302]: b[...] = [m,m]
    In [303]: f = np.frompyfunc(np.rot90, 1,1)
    In [304]: f(b)
    Out[304]: 
    array([array([[3, 6],
           [2, 5],
           [1, 4]]),
           array([[3, 6],
           [2, 5],
           [1, 4]])], dtype=object)
    

    np.stack(f(b)) 会像其他代码一样将对象数组转换为 3d 数组。

    frompyfuncvectorize 的底层函数,并返回一个对象数组。在这里,我创建了一个类似于您的 a 的数组,但它是 1d 的,包含多个 m 数组。它是一个数组数组,而不是 3d 数组。

    【讨论】:

    • 非常感谢非常;你的解释非常有帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-13
    • 2019-07-12
    • 1970-01-01
    • 1970-01-01
    • 2017-05-21
    • 1970-01-01
    相关资源
    最近更新 更多