【问题标题】:Reshaping numpy array without using two for loops在不使用两个 for 循环的情况下重塑 numpy 数组
【发布时间】:2015-07-08 23:59:53
【问题描述】:

我有两个 numpy 数组

import numpy as np
x = np.linspace(1e10, 1e12, num=50) # 50 values
y = np.linspace(1e5, 1e7, num=50)   # 50 values
x.shape # output is (50,)
y.shape # output is (50,)

我想创建一个函数,该函数返回一个形状为 (50,50) 的数组,以便为​​所有 y 值评估第一个 x 值 x0,等等。

我当前使用的函数相当复杂,所以让我们用一个更简单的例子。假设函数是

def func(x,y):
    return x**2 + y**2

如何将其塑造为 (50,50) 数组?目前,它将输出 50 个值。你会在数组中使用 for 循环吗?

类似:

np.array([[func(x,y) for i in x] for j in y)

但不使用两个 for 循环。这需要很长时间才能运行。


编辑:有人要求我分享我的“复杂”功能。就是这样:

有一个数据向量,它是一个包含 4000 个测量值的一维 numpy 数组。还有一个“normalized_matrix”,形状为 (4000,4000)——没什么特别的,只是一个输入值介于 0 和 1 之间的整数的矩阵,例如0.5567878。这是两个“给定”输入。

我的函数返回 transpose(datavector) * matrix * datavector 的矩阵乘积,它是单个值。

现在,正如您在代码中看到的那样,我已经初始化了两个数组,x 和 y,它们传递了一系列“x 参数”和“y 参数”。也就是说,func(x,y) 为值x1 和值y1(即func(x1,y1))返回什么?

matrix1 的形状是 (50, 4000, 4000)。 matrix2 的形状是 (50, 4000, 4000)。 total_matrix 同上。

normalized_matrix 是形状 (4000,4000),id_mat 是形状 (4000,4000)。

normalized_matrix
print normalized_matrix.shape #output (4000,4000)

data_vector = datarr
print datarr.shape #output (4000,)

def func(x, y):
    matrix1 = x [:, None, None] * normalized_matrix[None, :, :]
    matrix2 = y[:, None, None] * id_mat[None, :, :]
    total_matrix = matrix1 + matrix2
    # transpose(datavector) * matrix * datavector
    # by matrix multiplication, equals single value
    return  np.array([ np.dot(datarr.T,  np.dot(total_matrix, datarr) )  ])

如果我尝试使用np.meshgrid(),即如果我尝试

x = np.linspace(1e10, 1e12, num=50) # 50 values
y = np.linspace(1e5, 1e7, num=50)   # 50 values

X, Y = np.meshgrid(x,y)

z = func(X, Y)

我收到以下值错误:ValueError: operands could not be broadcast together with shapes (50,1,1,50) (1,4000,4000)

【问题讨论】:

  • 我对你想要的输出感到困惑。如果输出50× 50的矩阵是M,你期待M[i, j]的公式是什么?
  • x0y0 评估到y49,然后x1y0 评估到y49,等等。
  • 这是似曾相识,并已在其替代化身 stackoverflow.com/questions/31304733/… 中得到解答
  • 你试过np.meshgrid但没有用?
  • 已编辑的问题,更详细的func 很有帮助。但是您应该指出ValueError 出现的位置。人们喜欢对忽略这类信息投反对票。请参阅我的第二个答案。

标签: python arrays numpy


【解决方案1】:

reshapenumpy 中的含义不同。当您从 (100,) 开始并将其更改为 (5,20)(10,10) 2d 数组时,即使用 'reshape. There is anumpy` 函数来执行此操作。

您想要获取 2 个 1d 数组,并使用它们从函数生成 2d 数组。这就像取 2 的外积,将它们的所有值组合传递给您的函数。

某种双循环是执行此操作的一种方式,无论是使用显式循环还是列表解析。但加快速度取决于该功能。

x**2+y**2 为例,它可以很容易地“矢量化”:

In [40]: x=np.linspace(1e10,1e12,num=10)
In [45]: y=np.linspace(1e5,1e7,num=5)
In [46]: z = x[:,None]**2 + y[None,:]**2
In [47]: z.shape
Out[47]: (10, 5)

这利用了numpy 广播。使用Nonex 被重新整形为(10,1)y 被重新整形为(1,5)+ 接受outer 的总和。

X,Y=np.meshgrid(x,y,indexing='ij') 生成两个(10,5) 数组,它们可以以相同的方式使用。查看其他参数的文档。

因此,如果您的更复杂的函数可以用这样的二维数组的方式编写,那么“向量化”很容易。

但是,如果该函数必须采用 2 个标量并返回另一个标量,那么您就会陷入某种双循环。

双循环的列表理解形式是:

np.array([[x1**2+y1**2 for y1 in y] for x1 in x])

另一个是:

z=np.empty((10,5))
for i in range(10):
   for j in range(5):
      z[i,j] = x[i]**2 + y[j]**2

使用np.vectorize 可以稍微加快这个双循环。这需要一个用户定义的函数,并返回一个可以采用可广播数组的函数:

In [65]: vprod=np.vectorize(lambda x,y: x**2+y**2)

In [66]: vprod(x[:,None],y[None,:]).shape
Out[66]: (10, 5)

我过去所做的测试表明,vectorize 可以将列表理解路径提高 20%,但这种改进与编写函数以首先处理二维数组完全不同。

顺便说一句,这种“向量化”问题已经在 SO numpy 上被问过很多次了。除了这些广泛的示例之外,如果不了解更复杂的功能,我们将无法帮助您。只要它是一个接受标量的黑盒,我们可以为您提供的最好的帮助是np.vectorize。而且您仍然需要了解广播(有或没有meshgrid 帮助)。

【讨论】:

  • 感谢您的全面回答。看起来我被两个 for 循环困住了,除非我以某种方式更改网格网格
  • meshgrid 可以轻松地将您的 xy 转换为 (50,50) 数组,但是将它们组合成所需的输出取决于您的代码。 numpy 函数和操作符可以快速完成一些常见操作,例如 +* 和所有 ufunc 函数。但诀窍是用这些术语表达你的功能。
  • 我认为我上面的函数基本上是ufunc 恐怕我自己有点困惑。我们(本质上)有 50 个矩阵(4000,4000)。然后该函数进行矩阵乘法,并产生 50 个值,每个矩阵一个,即 func(x1,y1)、func(x2,y2)、func(x3,y3) 等。我试图弄清楚如何创建形状(50,50)的输出,这样func(x1,y1), func(x1,y2), func(x1,y3),... func(x2,y1), func(x2,y2), func(x2,y3),... func(x3,y1), func(x3,y2), func(x3,y3),.... 我认为meshgrid 是这样做的正确方法......不知何故
【解决方案2】:

我认为有更好的方法,就在我的舌尖上,但作为临时措施:

您正在网格网格的 1x2 窗口上进行操作。您可以使用numpy.lib.stride_tricks 中的as_stridedmeshgrid 重新排列为二元素窗口,然后将您的函数应用于结果数组。我喜欢使用通用的 nd 解决方案,sliding_windows (http://www.johnvinyard.com/blog/?p=268)(不是我的)来转换数组。

import numpy as np
a = np.array([1,2,3])
b = np.array([.1, .2, .3])
z= np.array(np.meshgrid(a,b))
def foo((x,y)):
    return x+y

>>> z.shape
(2, 3, 3)
>>> t = sliding_window(z, (2,1,1))
>>> t
array([[ 1. ,  0.1],
       [ 2. ,  0.1],
       [ 3. ,  0.1],
       [ 1. ,  0.2],
       [ 2. ,  0.2],
       [ 3. ,  0.2],
       [ 1. ,  0.3],
       [ 2. ,  0.3],
       [ 3. ,  0.3]])
>>> v = np.apply_along_axis(foo, 1, t)
>>> v
array([ 1.1,  2.1,  3.1,  1.2,  2.2,  3.2,  1.3,  2.3,  3.3])
>>> v.reshape((len(a), len(b)))
array([[ 1.1,  2.1,  3.1],
       [ 1.2,  2.2,  3.2],
       [ 1.3,  2.3,  3.3]])
>>>

这应该会快一些。

您可能需要修改函数的参数签名

如果指向 johnvinyard.com blog 的链接中断,我已在其他 SO 答案中发布了 sliding_window 实现 - https://stackoverflow.com/a/22749434/2823755

四处搜索,您会发现许多其他棘手的as_strided 解决方案。

【讨论】:

    【解决方案3】:

    回答您编辑的问题:

    normalized_matrix
    print normalized_matrix.shape #output (4000,4000)
    
    data_vector = datarr
    print datarr.shape #output (4000,)
    
    def func(x, y):
        matrix1 = x [:, None, None] * normalized_matrix[None, :, :]
        matrix2 = y[:, None, None] * id_mat[None, :, :]
        total_matrix = matrix1 + matrix2
        # transpose(datavector) * matrix * datavector
        # by matrix multiplication, equals single value
        # return  np.array([ np.dot(datarr.T,  np.dot(total_matrix, datarr))])
        return np.einsum('j,ijk,k->i',datarr,total_matrix,datarr)
    

    由于datarr 是形状(4000,),转置什么也不做。我相信您希望 2 dots 的结果为形状 (50,)。我建议使用einsum。但它可以通过tensordot 完成,或者我认为甚至可以使用np.dot(np.dot(total_matrix, datarr),datarr)。用较小的数组测试表达式,重点是让形状正确。

    x = np.linspace(1e10, 1e12, num=50) # 50 values
    y = np.linspace(1e5, 1e7, num=50)   # 50 values
    z = func(x,y)
    
    # X, Y = np.meshgrid(x,y)
    # z = func(X, Y)
    

    X,Y 是错误的。 func 采用 xy,它们是 1d。请注意如何使用 [:, None, None] 扩展维度。此外,您没有从xyouter 组合创建二维数组。 func 中的所有数组都不是 (50,50)(50,50,...)。更高的维度由nomalied_matrixid_mat 提供。

    在向我们展示ValueError 时,您还应该指出代码中发生的位置。否则我们必须自己猜测或重新创建代码。

    事实上,当我运行我编辑的func(X,Y) 时,我得到了这个错误:

    ----> 2         matrix1 = x [:, None, None] * normalized_matrix[None, :, :]
          3         matrix2 = y[:, None, None] * id_mat[None, :, :]
          4         total_matrix = matrix1 + matrix2
          5         # transpose(datavector) * matrix * datavector
    
    ValueError: operands could not be broadcast together with shapes (50,1,1,50) (1,400,400) 
    

    看,错误发生在开头。 normalized_matrix 扩展为 (1,400,400) [我正在使用较小的示例]。 (50,50) X 扩展为 (50,1,1,50)x 扩展为 (50,1,1),广播正常。

    【讨论】:

    • 感谢您的回复。 ValueError 出现在包含网格的情况下。我不知道为什么你的函数会出错——我不知道。在上面的函数中,matrix1 乘以 x 形状的 (50,)normalized_matrix 形状的 (4000,4000)。生成的matrix1 的形状为(50,4000,4000)。运行代码没有问题。
    • 啊,我明白你现在在说什么了。 x 扩展为(50, 1, 1),它可以工作。使用网格会创建一个形状为 (50,50) 的输入,但不会。
    【解决方案4】:

    解决编辑和编辑中的广播错误:

    在您的函数中,您正在向数组添加维度以尝试让它们广播。

        matrix1 = x [:, None, None] * normalized_matrix[None, :, :]
    

    这个表达式看起来像你想用一个二维数组广播一个一维数组。

    你的网格的结果是两个二维数组:

    X,Y = np.meshgrid(x,y)
    
    >>> X.shape, Y.shape
    ((50, 50), (50, 50))
    >>>
    

    当您尝试在您的广播表达式中使用X 时,尺寸不对齐,这就是导致ValueError 的原因 - 请参阅General Broadcasting Rules

    >>> x1 = X[:, np.newaxis, np.newaxis]
    >>> nm = normalized_matrix[np.newaxis, :, :]
    >>> x1.shape
    (50, 1, 1, 50)
    >>> nm.shape
    (1, 4000, 4000)
    >>> 
    

    【讨论】:

    • 谢谢。我现在明白为什么会出现广播错误。现在,要弄清楚如何创建输出矩阵,(x1,y1), (x1,y2), (x1,y3),... (x2,y1), (x2,y2), ....
    • @ShanZhengYang 在下面看到我的另一个答案 - 使用numpy.meshgrid 和基于numpy.lib.stride_tricks.as_strided滑动窗口 功能。
    【解决方案5】:

    您在列表理解方面走在正确的轨道上,您只需要添加一个额外的迭代级别:

    np.array([[func(i,j) for i in x] for j in y])
    

    【讨论】:

      猜你喜欢
      • 2017-03-25
      • 1970-01-01
      • 2018-10-15
      • 2019-07-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-20
      相关资源
      最近更新 更多