重构numpy数组而不使用两个for循环

时间:2015-07-08 23:59:54

标签: python arrays numpy

我有两个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个测量值的1D 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)

5 个答案:

答案 0 :(得分:3)

reshape中的{p> numpy具有不同的含义。当您从(100,)开始并将其更改为(5,20)(10,10) 2d数组时,即可重新设置. There is a numpy`函数来执行此操作。

您想要获取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)数组,可以使用相同的方式。看看是其他参数的文档。

因此,如果你的更复杂的函数可以采用像这样的2d数组的方式编写,那么它很容易被“矢量化”。

但是如果该函数必须使用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%的事情改进列表理解路线,但改进与编写你的函数来处理2d数组无关。第一名。

顺便说一下,这种矢量化'关于SO numpy已多次询问过这个问题。除了这些广泛的例子之外,我们无法在不了解更复杂功能的情况下帮助您。只要它是一个带有标量的黑盒子,我们可以帮助你的最好的是np.vectorize。你还需要了解广播(有或没有meshgrid帮助)。

答案 1 :(得分:0)

我认为有更好的方法,这是我的舌头,但作为一个临时措施:

您正在使用网格网格的1x2窗口进行操作。您可以使用as_strided中的numpy.lib.stride_tricksmeshgrid重新排列为两个元素的窗口,然后将您的函数应用于结果数组。我喜欢使用通用的nd解决方案,sliding_windowshttp://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的链接中断,我已将sliding_window实施内容发布在其他SO答案中 - https://stackoverflow.com/a/22749434/2823755

搜索并找到许多其他棘手的 as_strided解决方案。

答案 2 :(得分:0)

回答您编辑过的问题:

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 xy1d。请注意您如何使用[:, None, None]扩展维度。您也无法从outerx的{​​{1}}组合创建二维数组。 y中的所有数组都不是func(50,50)。较高的尺寸由(50,50,...)nomalied_matrix提供。

向我们展示id_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,播放效果很好。

答案 3 :(得分:0)

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

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

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

此表达式看起来像是要广播带有2d数组的1d数组。

meshgrid的结果是两个2d数组:

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

>>> X.shape, Y.shape
((50, 50), (50, 50))
>>>

当您尝试在广播表达式中使用X时,维度不会排列,这就是导致ValueError的原因 - 请参阅{{3 }}:

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

答案 4 :(得分:-1)

您使用列表理解进入了正确的轨道,您只需添加额外的迭代级别:

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