高效的numpy子矩阵视图

时间:2017-11-09 18:52:43

标签: numpy scipy hungarian-algorithm

我希望将Hungarian algorithm应用于由列表Crow_ind的叉积索引的numpy矩阵col_ind的多个子集。目前,我看到以下选项:

  1. 双切片:

    linear_sum_assignment(C[row_ind,:][:,col_ind])
    
  2. 问题:每个子集操作两个副本。

    1. 通过np.ix_进行高级切片:

      linear_sum_assignment(C[np.ix_(row_ind, col_ind)])
      
    2. 问题:每个子集一个副本,np.ix_效率低(分配 n x n 矩阵)。

      更新:如@hpaulj所述,np.ix_并不是事实上分配n x n矩阵,但它仍然比某些方法慢。

      1. Masked array
      2. 问题:不适用于linear_sum_assignment

        所以,没有选择令人满意。

        理想情况下,能够使用矩阵C和行和列的几个单维掩码指定子矩阵视图,因此可以将此视图传递给linear_sum_assignment。对于另一个linear_sum_assignment调用,我会快速调整掩码但不会修改或复制/子集完整矩阵。

        numpy中是否有类似的内容?

        处理同一大矩阵的多个子矩阵的最有效方法(尽可能少的副本/内存分配)是什么?

1 个答案:

答案 0 :(得分:1)

使用列表/数组时间索引数组的不同方法大致相同。他们都制作副本,而不是观点。

例如

In [99]: arr = np.ones((1000,1000),int)
In [100]: id1=np.arange(0,1000,10)
In [101]: id2=np.arange(0,1000,20)

In [105]: timeit arr[id1,:][:,id2].shape
52.5 µs ± 243 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [106]: timeit arr[np.ix_(id1,id2)].shape
66.5 µs ± 47.4 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

相反,如果我使用切片(在这种情况下选择相同的元素),我得到view,这要快得多:

In [107]: timeit arr[::10,::20].shape
661 ns ± 18.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

ix_不会创建(m,n)数组;它返回一个经过调整的1d数组的元组。它相当于

In [108]: timeit arr[id1[:,None], id2].shape
54.5 µs ± 1.6 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

时序差异主要是由于额外的一系列函数调用。

您的scipy链接有[来源]链接:

https://github.com/scipy/scipy/blob/v0.19.1/scipy/optimize/_hungarian.py#L13-L107

optimize.linear_sum_assignment函数使用_Hungary创建cost_matrix对象。这是一个副本,并通过搜索和操作其值来解决问题。

使用文档示例:

In [110]: optimize.linear_sum_assignment(cost)
Out[110]: (array([0, 1, 2], dtype=int32), array([1, 0, 2], dtype=int32))

它的作用是创建一个状态对象:

In [111]: H=optimize._hungarian._Hungary(cost)
In [112]: vars(H)
Out[112]: 
{'C': array([[4, 1, 3],
        [2, 0, 5],
        [3, 2, 2]]),
 'Z0_c': 0,
 'Z0_r': 0,
 'col_uncovered': array([ True,  True,  True], dtype=bool),
 'marked': array([[0, 0, 0],
        [0, 0, 0],
        [0, 0, 0]]),
 'path': array([[0, 0],
        [0, 0],
        [0, 0],
        [0, 0],
        [0, 0],
        [0, 0]]),
 'row_uncovered': array([ True,  True,  True], dtype=bool)}

迭代,

In [113]: step=optimize._hungarian._step1
In [114]: while step is not None:
     ...:     step = step(H)
     ...:     

结果状态是:

In [115]: vars(H)
Out[115]: 
{'C': array([[1, 0, 1],
        [0, 0, 4],
        [0, 1, 0]]),
 'Z0_c': 0,
 'Z0_r': 1,
 'col_uncovered': array([False, False, False], dtype=bool),
 'marked': array([[0, 1, 0],
        [1, 0, 0],
        [0, 0, 1]]),
 'path': array([[1, 0],
        [0, 0],
        [0, 0],
        [0, 0],
        [0, 0],
        [0, 0]]),
 'row_uncovered': array([ True,  True,  True], dtype=bool)}

marked数组

中提取解决方案
In [116]: np.where(H.marked)
Out[116]: (array([0, 1, 2], dtype=int32), array([1, 0, 2], dtype=int32))

总费用是这些值的总和:

In [122]: cost[np.where(H.marked)]
Out[122]: array([1, 2, 2])

但最终状态中C数组的成本为0:

In [124]: H.C[np.where(H.marked)]
Out[124]: array([0, 0, 0])

因此,即使您向optimize.linear_sum_assignment提供的子矩阵是视图,搜索仍然涉及副本。搜索空间和时间随着此成本矩阵的大小而显着增加。