【问题标题】:Numpy ‘smart’ symmetric matrixNumpy“智能”对称矩阵
【发布时间】:2011-02-04 02:24:39
【问题描述】:

在 numpy 中是否有一个智能且节省空间的对称矩阵,当 [i][j] 被写入时,它会自动(并且透明地)填充 [j][i] 的位置?

import numpy
a = numpy.symmetric((3, 3))
a[0][1] = 1
a[1][0] == a[0][1]
# True
print(a)
# [[0 1 0], [1 0 0], [0 0 0]]

assert numpy.all(a == a.T) # for any symmetric matrix

自动 Hermitian 也不错,尽管在撰写本文时我不需要它。

【问题讨论】:

  • 如果可以解决您的问题,您可以考虑将答案标记为已接受。 :)
  • 我想等待一个更好的(即内置和内存效率高的)答案的出现。当然,你的回答没有问题,所以我还是会接受。

标签: python matrix numpy


【解决方案1】:

如果您有能力在进行计算之前使矩阵对称,那么以下应该相当快:

def symmetrize(a):
    """
    Return a symmetrized version of NumPy array a.

    Values 0 are replaced by the array value at the symmetric
    position (with respect to the diagonal), i.e. if a_ij = 0,
    then the returned array a' is such that a'_ij = a_ji.

    Diagonal values are left untouched.

    a -- square NumPy array, such that a_ij = 0 or a_ji = 0, 
    for i != j.
    """
    return a + a.T - numpy.diag(a.diagonal())

这在合理的假设下有效(例如在运行 symmetrize 之前不同时执行 a[0, 1] = 42 和矛盾的 a[1, 0] = 123)。

如果您真的需要透明的对称化,您可以考虑继承 numpy.ndarray 并简单地重新定义 __setitem__

class SymNDArray(numpy.ndarray):
    """
    NumPy array subclass for symmetric matrices.

    A SymNDArray arr is such that doing arr[i,j] = value
    automatically does arr[j,i] = value, so that array
    updates remain symmetrical.
    """

    def __setitem__(self, (i, j), value):
        super(SymNDArray, self).__setitem__((i, j), value)                    
        super(SymNDArray, self).__setitem__((j, i), value)                    

def symarray(input_array):
    """
    Return a symmetrized version of the array-like input_array.

    The returned array has class SymNDArray. Further assignments to the array
    are thus automatically symmetrized.
    """
    return symmetrize(numpy.asarray(input_array)).view(SymNDArray)

# Example:
a = symarray(numpy.zeros((3, 3)))
a[0, 1] = 42
print a  # a[1, 0] == 42 too!

(或使用矩阵代替数组的等价物,具体取决于您的需要)。这种方法甚至可以处理更复杂的赋值,例如 a[:, 1] = -1,它正确设置了 a[1, :] 元素。

请注意,Python 3 消除了编写 def …(…, (i, j),…) 的可能性,因此在使用 Python 3 运行之前必须稍微修改代码:def __setitem__(self, indexes, value): (i, j) = indexes...

【讨论】:

  • 实际上,如果你对它进行子类化,你不应该覆盖 setitem,而应该覆盖 getitem,这样你就不会在创建矩阵。
  • 这是一个非常有趣的想法,但是当在子类实例数组上执行简单的print 时,将其写为等效的__getitem__(self, (i, j)) 会失败。原因是print 使用整数索引调用__getitem__(),因此即使是简单的print 也需要做更多的工作。 __setitem__() 的解决方案适用于print(显然),但遇到类似的问题:a[0] = [1, 2, 3] 不起作用,出于同样的原因(这不是一个完美的解决方案)。 __setitem__() 解决方案的优点是更健壮,因为内存中的数组是正确的。还不错。 :)
  • 你的建议听起来像blog.sopticek.net/2016/07/24/…...你确认它几乎一样吗?麻烦的是这优化了内存使用,而不是计算时间。我正在寻找 python 方法来加速对称矩阵上的一些简单计算。如果您有信息,请告诉我。
  • 这个答案不会节省内存,因此与引用链接中的方法有很大不同。现在,使用对称矩阵节省时间通常涉及使用专用算法而不是通用算法,例如在 NumPy 中使用 eigh() 而不是 eig()。
【解决方案2】:

在 numpy 中优化对称矩阵的更普遍问题也困扰着我。

在研究之后,我认为答案可能是 numpy 在某种程度上受到对称矩阵的底层 BLAS 例程支持的内存布局的限制。

虽然一些 BLAS 例程确实利用对称性来加速对称矩阵的计算,但它们仍然使用与完整矩阵相同的内存结构,即 n^2 空间而不是 n(n+1)/2。只是他们被告知矩阵是对称的,并且只能使用上三角形或下三角形中的值。

一些scipy.linalg 例程确实接受标志(如linalg.solve 上的sym_pos=True),这些标志会传递给BLAS 例程,尽管在numpy 中对此提供更多支持会很好,特别是像DSYRK 这样的例程的包装器(对称秩 k 更新),这将允许 Gram 矩阵的计算比 dot(MT, M) 快一点。

(担心在时间和/或空间上优化 2 倍常数因子似乎有些挑剔,但它可能会影响您在单台机器上可以处理多大问题的阈值...)

【讨论】:

  • 问题是关于如何通过分配单个条目来自动创建对称矩阵(而不是关于如何指示 BLAS 在其计算中使用对称矩阵或原则上如何存储对称矩阵更有效)。
  • 这个问题也是关于空间效率的,所以 BLAS 问题是热门话题。
  • @EOL,问题不在于如何通过单个条目的分配自动创建对称矩阵。
  • 当然,“创建”可以更恰当地替换为“更新”。现在,由于问题明确是关于在设置 M_ji 时透明地设置 M_ji ,而这个答案与此无关,你明白这本质上就是我提出的观点。问题是关于如何有效地做到这一点(而不是有效地处理对称矩阵,即使这可能是正确的问题:更好地放在 cmets 上,或者作为解决更普遍问题的答案而不是仅仅讨论它)。
【解决方案3】:

有许多众所周知的存储对称矩阵的方法,因此它们不需要占用 n^2 个存储元素。此外,重写常用操作以访问这些修改后的存储方式是可行的。权威著作是 Golub 和 Van Loan,矩阵计算,1996 年第 3 版,约翰霍普金斯大学出版社,第 1.27-1.2.9 节。例如,从表格(1.2.2)中引用它们,在对称矩阵中只需要存储A = [a_{i,j} ] fori >= j。然后,假设保存矩阵的 vector 表示为 V,并且 A 是 n×n,将 a_{i,j} 放入

V[(j-1)n - j(j-1)/2 + i]

这假定为 1 索引。

Golub 和 Van Loan 提供了一个算法 1.2.3,它展示了如何访问这样一个存储的 V 来计算 y = V x + y

Golub 和 Van Loan 还提供了一种以对角占优形式存储矩阵的方法。这不会节省存储空间,但支持某些其他类型的操作的就绪访问。

【讨论】:

  • 还有 Rectangular Full Packed storage (RFP),例如 Lapack ZPPTRF 使用它。 numpy 支持吗?
  • @isti_spl:不,但你可以实现一个包装器
【解决方案4】:

这是普通的python而不是numpy,但我只是拼凑了一个例程来填充 一个对称矩阵(以及一个确保它正确的测试程序):

import random

# fill a symmetric matrix with costs (i.e. m[x][y] == m[y][x]
# For demonstration purposes, this routine connect each node to all the others
# Since a matrix stores the costs, numbers are used to represent the nodes
# so the row and column indices can represent nodes

def fillCostMatrix(dim):        # square array of arrays
    # Create zero matrix
    new_square = [[0 for row in range(dim)] for col in range(dim)]
    # fill in main diagonal
    for v in range(0,dim):
        new_square[v][v] = random.randrange(1,10)

    # fill upper and lower triangles symmetrically by replicating diagonally
    for v in range(1,dim):
        iterations = dim - v
        x = v
        y = 0
        while iterations > 0:
            new_square[x][y] = new_square[y][x] = random.randrange(1,10)
            x += 1
            y += 1
            iterations -= 1
    return new_square

# sanity test
def test_symmetry(square):
    dim = len(square[0])
    isSymmetric = ''
    for x in range(0, dim):
        for y in range(0, dim):
            if square[x][y] != square[y][x]:
                isSymmetric = 'NOT'
    print "Matrix is", isSymmetric, "symmetric"

def showSquare(square):
    # Print out square matrix
    columnHeader = ' '
    for i in range(len(square)):
        columnHeader += '  ' + str(i)
    print columnHeader

    i = 0;
    for col in square:
        print i, col    # print row number and data
        i += 1

def myMain(argv):
    if len(argv) == 1:
        nodeCount = 6
    else:
        try:
            nodeCount = int(argv[1])
        except:
            print  "argument must be numeric"
            quit()

    # keep nodeCount <= 9 to keep the cost matrix pretty
    costMatrix = fillCostMatrix(nodeCount)
    print  "Cost Matrix"
    showSquare(costMatrix)
    test_symmetry(costMatrix)   # sanity test
if __name__ == "__main__":
    import sys
    myMain(sys.argv)

# vim:tabstop=8:shiftwidth=4:expandtab

【讨论】:

    【解决方案5】:

    要构造一个沿主对角线对称且主对角线上为 0 的 NxN 矩阵,您可以这样做:

    a = np.array([1, 2, 3, 4, 5])
    b = np.zeros(shape=(a.shape[0], a.shape[0]))
    upper = np.triu(b + a)
    lower = np.tril(np.transpose(b + a))
    D = (upper + lower) * (np.full(a.shape[0], fill_value=1) - np.eye(a.shape[0]))
    

    这是一种特殊情况,但最近我使用这种矩阵来表示网络邻接关系。

    希望对您有所帮助。 干杯。

    【讨论】:

      【解决方案6】:

      如果填写[j][i],用Python 方式填写[i][j] 很简单。存储问题有点意思。可以使用packed 属性来扩充 numpy 数组类,该属性对于节省存储空间和以后读取数据都很有用。

      class Sym(np.ndarray):
      
          # wrapper class for numpy array for symmetric matrices. New attribute can pack matrix to optimize storage.
          # Usage:
          # If you have a symmetric matrix A as a shape (n,n) numpy ndarray, Sym(A).packed is a shape (n(n+1)/2,) numpy array 
          # that is a packed version of A.  To convert it back, just wrap the flat list in Sym().  Note that Sym(Sym(A).packed)
      
      
          def __new__(cls, input_array):
              obj = np.asarray(input_array).view(cls)
      
              if len(obj.shape) == 1:
                  l = obj.copy()
                  p = obj.copy()
                  m = int((np.sqrt(8 * len(obj) + 1) - 1) / 2)
                  sqrt_m = np.sqrt(m)
      
                  if np.isclose(sqrt_m, np.round(sqrt_m)):
                      A = np.zeros((m, m))
                      for i in range(m):
                          A[i, i:] = l[:(m-i)]
                          A[i:, i] = l[:(m-i)]
                          l = l[(m-i):]
                      obj = np.asarray(A).view(cls)
                      obj.packed = p
      
                  else:
                      raise ValueError('One dimensional input length must be a triangular number.')
      
              elif len(obj.shape) == 2:
                  if obj.shape[0] != obj.shape[1]:
                      raise ValueError('Two dimensional input must be a square matrix.')
                  packed_out = []
                  for i in range(obj.shape[0]):
                      packed_out.append(obj[i, i:])
                  obj.packed = np.concatenate(packed_out)
      
              else:
                  raise ValueError('Input array must be 1 or 2 dimensional.')
      
              return obj
      
          def __array_finalize__(self, obj):
              if obj is None: return
              self.packed = getattr(obj, 'packed', None)
      

      ```

      【讨论】:

        猜你喜欢
        • 2018-01-18
        • 2015-05-08
        • 1970-01-01
        • 1970-01-01
        • 2012-06-04
        • 1970-01-01
        • 2011-08-17
        • 1970-01-01
        相关资源
        最近更新 更多