查找每个唯一子数组的位置

时间:2019-06-28 22:06:06

标签: python arrays numpy vectorization

情况:

我正在填充形状为(2N,2N)的n数组,其中N接近8000,将其称为A,使用通过嵌套的for循环从函数获取的值调用函数,该函数将以下参数作为子数组形状(N,N,2)数组的最后一个维度中的形状(2,),将其称为B。

这显然是昂贵的,并且尽管我没有成功地对这部分代码进行矢量化(也非常欢迎朝这个方向提供任何帮助),但我知道B在最后一个维度上有许多重复的子数组。因此,我想要的是找出唯一的子数组以及每个子数组的发生位置。然后,通过迭代这些唯一子数组中的每个子数组并使用函数返回的值(只需计算一次)填充出现的所有位置,就可以加快A的填充速度。

我所要做的是以下几点,但这似乎不是最直接的方法,也不是最笨拙的方法。

我一直在使用的代码填充矩阵如下:

translat_avg_disloc_matrix = np.zeros([2*n, 2*n])

for i in range(n):
    for alpha in range(2):

        for j in range(n):
            for beta in range(2):

                    translat_avg_disloc_matrix[2*i+alpha,2*j+beta] = average_lat_pos(alpha, beta, b_matrix[i][j])

虽然我可以通过执行类似Efficiently count the number of occurrences of unique subarrays in NumPy?的操作来找到唯一的子数组,但是在查找每个出现索引的位置时遇到了问题。

我尝试过的事情正在执行以下操作:

1)通过norm = (B*B).sum(axis=2)计算B的最后一个维度中的子数组的范数,并计算B-1的最后一个维度中的子数组的范数

norm_ = ((B-1)*(B-1)).sum(axis=2)

2)使用norm.reshape((norm.size,1))

为这两个规范重塑这些narrays

3)以tile_norm = np.tile(norm.T, (len(norm),1))

的形式创建图块矩阵

4)然后执行np.unique(np.non_zero(np.abs(tile_norm - norm)+np.abs(tile_norm_-norm_) == 0), axis=0),这使我们得到了类似的内容:array([[0, 0, 0, 4], [4, 4, 4, 0]]) 在每一行中,零表示这些索引与B矩阵中的相同(2,)向量相对应。

换句话说,我发现(2,)个数组的范式保持原样,并且当从数组中减去1时,它们也符合-两个方程式,两个变量。

我正在寻找的是一种找到每个唯一子数组在B中出现的位置的方法,因此使用一些精美的索引将使我无需重复调用该函数即可填充矩阵average_lat_pos(此处重复表示要求使用相同的(alpha,beta,(2))数组对)。

2 个答案:

答案 0 :(得分:0)

引导通常更好。稍微调整一下功能即可简化工作:

import numpy as np
def average_lat_pos(a,b,x,y):  # all arguments are scalars
    return a*x+2*b*y           # as example

n=1000    
B=np.random.rand(n,n,2)


def loops():
    A=np.empty((2*n,2*n))
    for i in range(n):
        for alpha in range(2):

          for j in range(n):
            for beta in range(2):

              A[2*i+alpha,2*j+beta] = average_lat_pos(alpha, beta, 
              B[i,j,0],B[i,j,1])
    return A

要矢量化,只需将循环级别转换为相应的尺寸:

Z=average_lat_pos(np.arange(2).reshape(1,2,1,1),np.arange(2).reshape(1,1,1,2),
B[:,:,0].reshape(n,1,n,1),B[:,:,1].reshape(n,1,n,1)).reshape(2*n,2*n)

测试:

np.allclose(loops(),Z)
Out[105]: True

%timeit loops()
3.73 s ± 9.28 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit average_lat_pos(np.arange(2).reshape(1,2,1,1),np.arange(2).reshape(1,1,1,2),
   B[:,:,0].reshape(n,1,n,1),B[:,:,1].reshape(n,1,n,1)).reshape(2*n,2*n)
38.7 ms ± 211 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit np.unique(B,axis=2)
1.31 s ± 6.12 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

np.unique意味着进行O(n ln n)操作,这将扼杀任何潜在的收益。

但是在这里更好的是,使用numba装饰两个函数:

@numba.njit
def average_lat_pos(a,b,x,y):
   ....

@numba.njit
def loops(B):
   ....

然后

%timeit loops(B)
3.04 ms ± 34.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)


it's now 1000x faster.

答案 1 :(得分:0)

一个简单的技巧是将average_lat_pos自变量存储在字典中。因此,如果键在字典中,则返回它的值,而无需为重复的参数计算average_lat_pos。 由于average_lat_pos函数不可用,因此我将虚拟函数用作:

def average_lat_pos(alpha, betha, td):
    result = 0
    for i in range(1000):
        result += 1
    return result

和最终代码:

import numpy as np

def average_lat_pos_1(alpha, betha, td):
    get_result = dictionary.get((alpha, betha, td[0], td[1]), 'N')
    if get_result == 'N':
        result = 0
        for i in range(1000):
            result += 1
        dictionary[(alpha, betha, td[0], td[1])] = result
        return result
    else:
        return get_result

def average_lat_pos_2(alpha, betha, td):
    result = 0
    for i in range(1000):
        result += 1
    return result


N = 500
B = np.random.rand(N*N*2) * 100
B = np.floor(B)
B = B.reshape(N,N,2)

dictionary = dict()
A = np.empty([2*N, 2*N])
for i in range(2*N):
    for j in range(2*N):
        A[i, j] = average_lat_pos_1(i%2, j%2, B[i//2, j//2])

对于N = 500,average_lat_pos_1的执行速度比average_lat_pos_2快X10