numpy的“智能”对称矩阵


74

numpy中是否存在一个智能且节省空间的对称矩阵,该矩阵可以自动(透明地)填充[j][i]何时[i][j]写入的位置?

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也会很好,尽管在撰写本文时我不需要。


如果可以解决问题,则可以考虑将答案标记为接受。:)
Eric O Lebigot

我想等待一个更好的(即内置的和内存有效的)答案。您的答案当然没有错,所以我还是会接受。
Debilski

Answers:


83

如果您有能力在进行计算之前就对称矩阵,则以下操作应相当快:

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())

这在合理的假设下有效(例如,在运行之前不做任何事情a[0, 1] = 42并且矛盾)。a[1, 0] = 123symmetrize

如果您确实需要透明的对称化,则可以考虑子类化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


5
其实,如果你继承它,你不应该覆盖setitem,而是GetItem方法,这样你就不会引起更多的开销上创建矩阵。
Markus

1
这是一个非常有趣的想法,但是__getitem__(self, (i, j))print在子类实例数组上执行简单操作时,将其写为等效项将失败。原因是使用整数索引进行print调用__getitem__(),因此即使使用简单的索引也需要做更多的工作print。使用的解决方案__setitem__()可以print(显然)使用,但是存在一个类似的问题:a[0] = [1, 2, 3]由于相同的原因,该方法不起作用(这不是一个完美的解决方案)。甲__setitem__()方案具有更稳健的,因为在存储器阵列是正确的优点。还不错 :)
Eric O Lebigot

您的建议听起来像blog.sopticek.net/2016/07/24/… ...您确认这几乎相同吗?问题是这会优化内存使用率,而不是计算时间。我正在寻找python方法来加快对称矩阵的一些简单计算。如果您有信息,请告诉我。
斯特凡(Stéphane)'18年

该答案不会节省内存,因此与引用链接中的方法有很大不同。现在,使用对称矩阵节省时间通常涉及通过专用算法而不是通用算法,例如在NumPy中使用eigh()而不是eig()。
Eric O Lebigot

20

numpy中关于对称矩阵最佳处理的更普遍的问题也困扰着我。

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

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

一些scipy.linalg例程确实接受传递给BLAS例程的标志(如sym_pos=Trueon linalg.solve),尽管在numpy中对此标记提供更多支持会更好,尤其是像DSYRK(对称秩k更新)这样的例程的包装器,它允许使用Gram矩阵比点(MT,M)快一点

(担心优化时间和/或空间的2倍常数因子似乎显得有些挑剔,但这可能会影响您在一台计算机上可以解决多大问题的阈值。)


问题在于如何通过分配单个条目来自动创建对称矩阵(而不是如何指示BLAS在其计算中使用对称矩阵,或者原则上如何更有效地存储对称矩阵)。
Eric O Lebigot

3
问题还与空间效率有关,因此BLAS问题是热门话题。
jmmcd 2013年

@EOL,问题不在于如何通过分配单个条目自动创建对称矩阵。
阿列克谢

当然,“创建”可以更适当地替换为“更新”。现在,由于问题明确地是关于在设置M_ji时透明地设置M_ji的问题,而这个答案不是关于此的,因此您可以理解,这实质上是我提出的观点。问题是关于如何有效地做到这一点(而不是有效地处理对称矩阵,即使这可能是正确的问题:可以在注释中添加一些更好的内容,或者给出确实可以解决更普遍问题的答案,而不仅仅是讨论它)。
Eric O Lebigot

7

有许多众所周知的存储对称矩阵的方式,因此它们不需要占用n ^ 2个存储元素。此外,重写通用操作以访问这些修改后的存储方式是可行的。权威著作是Golub和Van Loan,《矩阵计算》,第三版,1996年,约翰·霍普金斯大学出版社,第1.27-1.2.9节。例如,在对称矩阵中用形式(1.2.2)引用它们,只需存储A = [a_{i,j} ]for i >= j。然后,假设向量保持矩阵被表示为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还提供了一种以对角线主导形式存储矩阵的方法。这不会节省存储空间,但支持对某些其他类型的操作进行随时访问。


1
还有矩形全包装存储(RFP),例如Lapack ZPPTRF使用它。numpy支持吗?
isti_spl

@isti_spl:没有,但你可以实现一个包装,做
埃里克·

1

这是普通的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

0

[i][j]如果[j][i]已填写,以Python方式进行填写很简单。存储问题更有趣。可以用一种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)

```


0

构造一个沿着主对角线对称的NxN矩阵,并且在主对角线上带有0,您可以执行以下操作:

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]))

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

希望能有所帮助。干杯。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.