Numpy 'intelligente' symmetrische Matrix


74

Gibt es eine intelligente und platzsparende symmetrische Matrix in numpy , die automatisch (und transparent) an der Position füllt , [j][i]wenn [i][j]geschrieben wird zu?

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

Ein automatischer Hermitianer wäre auch nett, obwohl ich das zum Zeitpunkt des Schreibens nicht brauche.


Sie können die Antwort als akzeptiert markieren, wenn dies Ihr Problem löst. :)
Eric O Lebigot

Ich wollte auf eine bessere (dh eingebaute und speichereffiziente) Antwort warten. An Ihrer Antwort ist natürlich nichts auszusetzen, also werde ich sie trotzdem akzeptieren.
Debilski

Antworten:


83

Wenn Sie es sich leisten können, die Matrix unmittelbar vor den Berechnungen zu symmetrisieren, sollte Folgendes relativ schnell gehen:

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

Dies funktioniert unter vernünftigen Voraussetzungen (z. B. nicht beides a[0, 1] = 42und der Widerspruch a[1, 0] = 123vor dem Ausführen symmetrize).

Wenn Sie wirklich eine transparente Symmetrisierung benötigen, können Sie die Unterklasse numpy.ndarray in Betracht ziehen und einfach neu definieren __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!

(oder das Äquivalent mit Matrizen anstelle von Arrays, je nach Ihren Anforderungen). Dieser Ansatz behandelt sogar kompliziertere Zuweisungen, wie z. B. a[:, 1] = -1das korrekte Festlegen von a[1, :]Elementen.

Beachten Sie, dass Python 3 die Möglichkeit des Schreibens entfernt def …(…, (i, j),…)hat. Daher muss der Code vor der Ausführung mit Python 3 leicht angepasst werden: def __setitem__(self, indexes, value): (i, j) = indexes


5
Wenn Sie eine Unterklasse erstellen , sollten Sie setitem nicht überschreiben , sondern getitem, damit Sie beim Erstellen der Matrix keinen zusätzlichen Aufwand verursachen.
Markus

1
Dies ist eine sehr interessante Idee, aber das Schreiben als Äquivalent __getitem__(self, (i, j))schlägt fehl, wenn ein einfaches printArray für eine Unterklasse erstellt wird. Der Grund dafür ist, dass printAufrufe __getitem__()mit einem ganzzahligen Index ausgeführt werden, sodass selbst für einen einfachen Vorgang mehr Arbeit erforderlich ist print. Die Lösung mit __setitem__()funktioniert mit print(offensichtlich), hat aber ein ähnliches Problem: a[0] = [1, 2, 3]funktioniert aus demselben Grund nicht (dies ist keine perfekte Lösung). Eine __setitem__()Lösung hat den Vorteil, dass sie robuster ist, da das In-Memory-Array korrekt ist. Nicht so schlecht. :)
Eric O Lebigot

Ihr Vorschlag klingt wie blog.sopticek.net/2016/07/24/… ... Bestätigen Sie, dass es fast dasselbe ist? Das Problem ist, dass dies die Speichernutzung optimiert und nicht die Rechenzeit. Ich bin auf der Suche nach Python-Methoden, um einige einfache Berechnungen auf symmetrischen Matrizen zu beschleunigen. Bitte lassen Sie mich wissen, wenn Sie Informationen haben.
Stéphane

Diese Antwort spart keinen Speicher und unterscheidet sich daher stark von dem Ansatz im angegebenen Link. Um Zeit mit symmetrischen Matrizen zu sparen, müssen normalerweise spezielle Algorithmen anstelle allgemeiner Algorithmen verwendet werden, z. B. die Verwendung von eigh () in NumPy anstelle von eig ().
Eric O Lebigot

20

Das allgemeinere Problem der optimalen Behandlung symmetrischer Matrizen in Numpy nervte mich ebenfalls.

Nach eingehender Prüfung denke ich, dass die Antwort wahrscheinlich darin besteht, dass Numpy durch das Speicherlayout, das von den zugrunde liegenden BLAS-Routinen für symmetrische Matrizen unterstützt wird, etwas eingeschränkt wird.

Während einige BLAS-Routinen die Symmetrie ausnutzen, um Berechnungen auf symmetrischen Matrizen zu beschleunigen, verwenden sie immer noch dieselbe Speicherstruktur wie eine vollständige Matrix, dh n^2Platz statt n(n+1)/2. Nur wird ihnen gesagt, dass die Matrix symmetrisch ist und nur die Werte im oberen oder unteren Dreieck verwendet werden sollen.

Einige der scipy.linalgRoutinen akzeptieren Flags (wie sym_pos=Trueon linalg.solve), die an BLAS-Routinen weitergegeben werden, obwohl eine stärkere Unterstützung in numpy sinnvoll wäre, insbesondere Wrapper für Routinen wie DSYRK (Symmetric Rank K Update), die eine Gram-Matrix ermöglichen würden ein gutes Stück schneller als Punkt (MT, M) berechnet werden.

(Es mag nicht schwierig erscheinen, sich Gedanken über die Optimierung auf einen 2x konstanten Faktor für Zeit und / oder Raum zu machen, aber es kann einen Unterschied zu dieser Schwelle machen, wie groß ein Problem ist, das Sie auf einer einzelnen Maschine bewältigen können ...)


Die Frage ist, wie durch Zuweisung eines einzelnen Eintrags automatisch eine symmetrische Matrix erstellt werden kann (nicht, wie BLAS angewiesen werden kann, symmetrische Matrizen in seinen Berechnungen zu verwenden, oder wie symmetrische Matrizen im Prinzip effizienter gespeichert werden können).
Eric O Lebigot

3
Die Frage betrifft auch die Raumeffizienz, sodass BLAS-Themen zum Thema gehören.
jmmcd

@EOL, es geht nicht darum, wie durch Zuweisung eines einzelnen Eintrags automatisch eine symmetrische Matrix erstellt wird.
Alexey

Zugegeben, "Erstellen" könnte angemessener durch "Aktualisiert" ersetzt werden. Da es bei der Frage explizit darum geht, M_ji transparent zu setzen, wenn M_ji gesetzt ist, und diese Antwort nicht darum geht, verstehen Sie, dass dies im Wesentlichen der Punkt ist, den ich angesprochen habe. Die Frage ist, wie man dies effizient macht (nicht wie man symmetrische Matrizen effizient handhabt, auch wenn dies die richtige Frage sein könnte: etwas, das besser in die Kommentare aufgenommen oder als Antwort gegeben wird, die das allgemeinere Problem löst, anstatt es nur zu diskutieren ).
Eric O Lebigot

7

Es gibt eine Reihe bekannter Möglichkeiten zum Speichern symmetrischer Matrizen, sodass sie nicht n ^ 2 Speicherelemente belegen müssen. Darüber hinaus ist es möglich, allgemeine Vorgänge neu zu schreiben, um auf diese überarbeiteten Speichermittel zuzugreifen. Die endgültige Arbeit ist Golub und Van Loan, Matrix Computations , 3. Auflage 1996, Johns Hopkins University Press, Abschnitte 1.27-1.2.9. Zum Beispiel zitieren sie von der Form (1.2.2) in einer symmetrischen Matrix müssen Speicher nur A = [a_{i,j} ]für i >= j. Dann wird die unter der Annahme Vektor der Matrix hält , wird V bezeichnet, und daß ein n-von-n, PUT a_{i,j}in

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

Dies setzt eine 1-Indizierung voraus.

Golub und Van Loan bieten einen Algorithmus 1.2.3 an, der zeigt, wie auf ein solches gespeichertes V zugegriffen werden kann, um zu berechnen y = V x + y.

Golub und Van Loan bieten auch die Möglichkeit, eine Matrix in diagonal dominanter Form zu speichern. Dies spart keinen Speicherplatz, unterstützt jedoch den sofortigen Zugriff für bestimmte andere Arten von Vorgängen.


1
Es gibt auch den rechteckigen Full Packed Storage (RFP), den beispielsweise Lapack ZPPTRF verwendet. Wird es von numpy unterstützt?
isti_spl

@isti_spl: Nein, aber Sie könnten einen Wrapper implementieren, der dies tut
Eric

1

Dies ist einfaches Python und nicht numpy, aber ich habe gerade eine Routine zusammengestellt, um eine symmetrische Matrix zu füllen (und ein Testprogramm, um sicherzustellen, dass es korrekt ist):

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

Es ist trivial, Pythonisch auszufüllen, [i][j]wenn [j][i]es ausgefüllt ist. Die Speicherfrage ist etwas interessanter. Man kann die numpy-Array-Klasse mit einem packedAttribut erweitern, das sowohl zum Speichern von Speicher als auch zum späteren Lesen der Daten nützlich ist.

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

So erstellen Sie eine NxN-Matrix, die entlang der Hauptdiagonale symmetrisch ist und auf der Hauptdiagonale Nullen enthält:

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

Dies ist eine Art Sonderfall, aber kürzlich habe ich diese Art von Matrix für die Darstellung der Netzwerkadjazenz verwendet.

Hoffentlich hilft das. Prost.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.