【问题标题】:Efficient numpy submatrix view高效的numpy子矩阵视图
【发布时间】:2018-04-22 21:33:27
【问题描述】:

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

  1. 双切片:

    linear_sum_assignment(C[row_ind,:][:,col_ind])
    

问题:每个子集操作有两个副本。

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

    linear_sum_assignment(C[np.ix_(row_ind, col_ind)])
    

问题:每个子集一个副本,np.ix_ 效率低下(分配 n x n 矩阵)。

更新:正如@hpaulj 所述,np.ix_ 实际上不是分配 n x n 矩阵,但它仍然比 1 慢。

  1. Masked array

问题:不适用于linear_sum_assignment

所以,没有一个选项是令人满意的。

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

numpy 中是否已经有类似的东西可用?

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

【问题讨论】:

  • 匈牙利算法将在时间/复杂性方面轻松主导这个子集准备,对吧?
  • 这里的C 是什么?我并不完全清楚C 与匈牙利算法的输入之间的关系是什么。
  • 更多关于匈牙利算法和 python 的信息在stackoverflow.com/questions/43162526/…(和链接)。掩蔽创建副本,而不是视图。为了显着改进 scipy munkres 实现,我不得不在选定的步骤中使用 cython(尤其是第一个非零搜索)。
  • @hpaulj 对于所有情况或特定情况?对于大型矩阵,scipy 的 IPM 应该会胜过它(至少我的 IPM 做到了;但它与 scipy 中使用的不同)。
  • 最初我以为你是在尝试从头开始实现munkres,因为(如scipy 代码中所示),它有一个2d 成本矩阵和4 个1d 掩码数组。但在进一步阅读时,您似乎在更高层次上工作,掩盖了更大的矩阵。只要row_indcol_ind 是列表或数组,而不是切片,您就会将副本传递给scipy 函数。

标签: numpy scipy hungarian-algorithm


【解决方案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) 数组;它返回一个调整后的一维数组的元组。相当于

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 链接有一个 [source] 链接:

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

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

使用文档示例:

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 的子矩阵是视图,搜索仍然涉及副本。搜索空间和时间随着这个成本矩阵的大小而显着增加。

【讨论】:

  • 奇怪的是np.ix_ 与双切片相比没有任何好处。我希望单片切片应该总是更快!无论如何,您知道为什么不是这样吗?
猜你喜欢
  • 2018-11-16
  • 1970-01-01
  • 2013-10-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-28
  • 2011-11-07
相关资源
最近更新 更多