【问题标题】:Python matrix diagonal of inf without fill_diagonal没有fill_diagonal的inf的Python矩阵对角线
【发布时间】:2017-10-01 05:17:28
【问题描述】:

我需要将矩阵的对角元素设置为 Inf。

一个简单的方法是使用np.fill_diagonal

np.fill_diagonal(my_matrix, float('inf')

但是fill_diagonal 修改了输入矩阵,而不是返回一个填充了对角线的新矩阵。 这对我不起作用。我需要在不修改原始矩阵的情况下填充对角线。

当然我可以克隆原始矩阵,所以我会一直保留原始矩阵的副本。但是我不太喜欢这个解决方案,因为我会经常更新我的原始矩阵,因此每次我需要对角线为 inf 时我都必须复制它。

是否有与fill_diagonal 相同但不修改输入矩阵的函数?比如:

new_matrix = np.fill_diagonal(original_matrix, float('inf') 

为什么我需要这个:

我的矩阵是点之间的距离矩阵,我想在每一步计算两个最近的点。当然这个矩阵的对角线是0(因为一个点到它自己的距离是0)。所以我确保我不采取同一点的解决方案是将对角线设置为 Inf。

但是一旦找到这两个点,我需要计算这两个点与其余点之间距离的平均值,所以我实际上需要对角线为 0 而不是 Inf。

目前我正在做的是:

  • 用 Inf 填充对角线
  • 找到最近的 2 个点
  • 用 0 填充对角线
  • 计算这两个点与其余点之间的平均距离。

    # fill diagonal with Inf to avoid taking the diagonals
    np.fill_diagonal(data, float('inf'))  
    # find the minimum distance
    idx = np.argmin(data)
    # fill the diagonals back to 0
    np.fill_diagonal(data, 0.0) 
    # get the coordinates of the minimum distance
    row, col =  np.unravel_index(idx,data.shape)
    # compute the new node as the average distance between the two points
    new_node = np.mean((data[:,row],data[:,col]),0)
    # replace the first node (row) with the new node
    data[:,row] = new_node
    data[row,:] = new_node.T
    # delete the second node (col) from the matrix
    data = np.delete(data, col, 0)  # delete row
    data = np.delete(data, col, 1)  # delete column
    

但是我不喜欢将对角线设置为 Inf 然后返回 0 的想法,我宁愿只将一个函数传递给 argmax,它返回对角线填充有 Inf 的数据,而不实际修改矩阵数据。

类似:

idx = np.argmin(return_filled_diagonals(data, float('Inf'))
# here I can operate with data as usual since it has not been modified.

【问题讨论】:

  • 我不确定我是否理解正确:您想要一个填充了对角线的新矩阵,但又不想复制,因为您经常更新它?那究竟应该如何工作?你有用例吗?何时何地(任何需要的特殊功能?)您需要填充对角线吗?

标签: python numpy


【解决方案1】:

方法#1

您正在寻找的魔法在NumPy strides 中,它可以让我们看到没有对角元素的数组视图,因此不再占用内存空间。这是获得这样一个视图的实现 -

def nodiag_view(a):
    m = a.shape[0]
    p,q = a.strides
    return np.lib.stride_tricks.as_strided(a[:,1:], (m-1,m), (p+q,q))

让我们看一个示例运行来验证它是一个视图 -

In [124]: a  # Input array
Out[124]: 
array([[ 0, 61, 43, 26, 21],
       [20,  0, 78, 29, 64],
       [34, 49,  0, 64, 60],
       [36, 96, 67,  0, 75],
       [36, 85, 40, 74,  0]])

# Get the no-diag view
In [125]: a_nodiag = nodiag_view(a)

# Lets's verify by changing elements in the view and that should change
# elements in the original array too
In [126]: a_nodiag[:] = 999

In [127]: a
Out[127]: 
array([[  0, 999, 999, 999, 999],
       [999,   0, 999, 999, 999],
       [999, 999,   0, 999, 999],
       [999, 999, 999,   0, 999],
       [999, 999, 999, 999,   0]])

最后,让我们看看如何设置它来解决您的整个问题 -

def argmin_without_diag(a):
    a_nodiag = nodiag_view(a)
    idx_nodiag = np.argmin(a_nodiag)
    m = a.shape[0]
    idx = idx_nodiag + np.unravel_index(idx_nodiag, (m-1,m))[0]+1
    return  np.unravel_index(idx, a.shape)

示例运行 -

In [142]: a
Out[142]: 
array([[ 0, 60, 79, 55, 77],
       [62,  0, 86, 84, 25],
       [32, 96,  0, 74, 89],
       [24, 33, 64,  0, 93],
       [14, 74, 30, 44,  0]])

In [143]: argmin_without_diag(a)
Out[143]: (4, 0)

方法 #2

如果您担心内存和性能,您可以暂时将对角线设置为infnite,然后获取argmin 索引,然后放回原来的对角线值。因此,实际上输入数组是不变的。实现看起来像这样 -

def argmin_without_diag_replacement(a):
    # Store diagonal values
    vals = a.ravel()[::a.shape[1]+1].copy()

    # Set diag ones as infinites
    a.ravel()[::a.shape[1]+1] = np.inf

    # Get argmin index
    idx = np.argmin(a)

    # Put back the original diag values
    a.ravel()[::a.shape[1]+1] = vals
    return np.unravel_index(idx, a.shape)

因此,对于(n x n) 形状的数组,临时数组将只有n 元素。

示例运行 -

In [237]: a
Out[237]: 
array([[  0.,  95.,  57.,  75.,  92.],
       [ 37.,   0.,  69.,  71.,  62.],
       [ 42.,  72.,   0.,  30.,  57.],
       [ 41.,  80.,  94.,   0.,  26.],
       [ 36.,  45.,  71.,  76.,   0.]])

In [238]: argmin_without_diag_replacement(a)
Out[238]: (3, 4)

运行时测试

In [271]: a = np.random.randint(11,99,(1000,1000)).astype(float)

In [272]: np.fill_diagonal(a,0)

In [273]: %timeit argmin_without_diag(a)
1000 loops, best of 3: 1.76 ms per loop

In [274]: %timeit argmin_without_diag_replacement(a)
1000 loops, best of 3: 688 µs per loop

【讨论】:

    【解决方案2】:

    我的提名是明确的复制和填充,包装在一个函数中

    def foo(mat):
        a = mat.copy()
        np.fill_diagonal(a,np.inf)
        return a
    

    这确实制作了一个副本,但只有一个。

    orig_mat + np.where(np.eye(orig_mat.shape[0])>0,np.inf,0))  
    

    也返回一个新数组,但在此过程中必须添加两个临时数组。所以它最终会变慢:

    orig_mat=np.ones((1000,1000))

    In [67]: timeit np.argmin(orig_mat + np.where(np.eye(orig_mat.shape[0])>0, np.inf, 0))
    100 loops, best of 3: 18.7 ms per loop
    In [68]: timeit np.argmin(foo(orig_mat))
    100 loops, best of 3: 6.94 ms per loop
    

    令人惊讶的是@Divakar's 解决方案并没有快多少:

    In [69]: timeit argmin_without_diag(orig_mat)
    100 loops, best of 3: 5.97 ms per loop
    

    【讨论】:

    • 添加了另一个专注于性能的产品,但我不确定 OP 是否第一次追求性能。
    【解决方案3】:

    据我了解,您希望找到矩阵最小值的索引,但不包括对角线元素:

    orig_mat = np.array([[1.2,2,3],[4,5,6],[7,8,9]])
    min_idx = np.argmin(orig_mat+np.multiply(np.eye(orig_mat.shape[0]),1e9))
    

    【讨论】:

    • 但是在这里您还修改了 x 并将对角线元素设置为 Inf。我需要返回一个新矩阵 y,它基本上是带有 inf 对角线的矩阵 x,保持原始矩阵 x 不变
    • 是的,但您仍在制作矩阵的副本并将其保存到变量中。我只想将设置对角线到 Inf 的结果传递给函数 argmin,然后不再使用该矩阵,因此无需复制,将其存储到变量等...这只是为了提高效率。跨度>
    • 好的,现在我想我明白了——最后一次尝试。查看我的编辑。
    • 抱歉,multiply 不能与 Inf 一起使用,因为 0*Inf 未确定。并且使用大数字1e9 这不是一个好的解决方案,因为距离可能会任意大。无论如何,已经有一个可行的答案了,无论如何谢谢:)
    【解决方案4】:
    orig_mat = np.array([[1.2,2,3],[4,5,6],[7,8,9]])
    
    #set diagonal to inf without making a copy of the array.
    orig_mat + np.where(np.eye(orig_mat.shape[0])>0,np.inf,0)
    array([[ inf,   2.,   3.],
           [  4.,  inf,   6.],
           [  7.,   8.,  inf]])
    
    #the original array remains untorched.
    print(orig_mat)
    [[ 1.2  2.   3. ]
     [ 4.   5.   6. ]
     [ 7.   8.   9. ]]
    

    【讨论】:

    • 这确实可行。仅在调用 np.argmin 时使用要添加到原始矩阵的掩码对我来说看起来不错。
    • 这最终生成了三个(临时)数组,大小为orig_mateyewheresum 的结果。我怀疑制作副本并填充它的简单函数会更快(更高效)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-25
    • 2018-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多