Konvertieren einer 1.2 GB Kantenliste in eine dünn besetzte Matrix

8

Ich habe eine 1,2 GB-Liste von Kanten aus einem Graphen in einer Textdatei. Mein ubuntu PC hat 8GB RAM. Jede Zeile in der Eingabe sieht wie

aus %Vor%

Ich möchte es in eine spärliche Adjazenzmatrix konvertieren und diese in eine Datei ausgeben.

Einige Statistiken für meine Daten:

%Vor%

Ich habe die gleiche Frage schon vorher bei Ссылка gestellt und eine tolle Antwort bekommen. Das Problem ist, dass ich es nicht zum Laufen bringen kann.

Ich habe zuerst versucht, np.loadtxt in die Datei zu laden, aber es war sehr langsam und verwendete eine riesige Menge an Speicher. Also bin ich stattdessen zu pandas.read_csv gewechselt, was sehr schnell ist, aber das hat zu eigenen Problemen geführt. Dies ist mein aktueller Code:

%Vor%

Das Problem ist, dass der Pandas Dataframe data sehr groß ist und ich effektiv eine Kopie in A mache, was ineffizient ist. Allerdings sind die Dinge noch schlimmer, wenn der Code mit

abstürzt %Vor%

Meine Fragen sind also:

  1. Kann ich vermeiden, dass sowohl der 1,2 GB große Pandas-Datenrahmen als auch die 1,2 GB große nupy-Array-Kopie im Speicher vorhanden sind?
  2. Gibt es eine Möglichkeit, den Code in 8 GB RAM zu vervollständigen?

Sie können eine Testeingabe der Größe reproduzieren, mit der ich versuchen möchte:

%Vor%

Aktualisieren

Ich habe jetzt eine Reihe verschiedener Ansätze ausprobiert, die alle gescheitert sind. Hier ist eine Zusammenfassung.

  1. Verwenden Sie ifigraph mit g = Graph.Read_Ncol('edges.txt') . Dies verwendet eine große Menge an RAM, die meinen Computer abstürzt.
  2. Verwenden Sie networkit mit G= networkit.graphio.readGraph("edges.txt", networkit.Format.EdgeList, separator=" ", continuous=False) . Dies verwendet eine große Menge an RAM, die meinen Computer abstürzt.
  3. Der Code oben in dieser Frage, aber mit np.loadtxt ("kanten.txt") anstelle von Pandas. Dies verwendet eine große Menge an RAM, die meinen Computer abstürzt.

Ich habe dann einen separaten Code geschrieben, der alle Eckennamen von 1 .. | V | wo | V | ist die Gesamtzahl der Ecken. Dadurch sollte der Code, der die Kantenliste importiert, davor bewahrt werden, eine Tabelle erstellen zu müssen, die die Scheitelpunktnamen abbildet. Mit diesem versuchte ich:

  1. Unter Verwendung dieser neuen neu zugeordneten Kantenlistendatei habe ich wieder igraph mit g = Graph.Read_Edgelist("edges-contig.txt") verwendet. Dies funktioniert jetzt, obwohl es 4 GB RAM benötigt (was viel mehr ist als die theoretische Menge, die es sollte). Es gibt jedoch keine Funktion, um aus einem Graph eine spärliche Adjazenzmatrix herauszuschreiben. Die empfohlene Lösung besteht darin, das Diagramm in eine coo_matrix zu konvertieren. Leider verwendet dies eine riesige Menge an RAM, die meinen Computer abstürzt.
  2. Unter Verwendung der neu zugeordneten Kantenlistendatei habe ich networkit mit G = networkit.readGraph("edges-contig.txt", networkit.Format.EdgeListSpaceOne) verwendet. Dies funktioniert auch mit weniger als 4 GB, die benötigt werden. Networkit enthält auch eine Funktion zum Schreiben von Matlab-Dateien (eine Form der spärlichen Adjazenzmatrix, die scipy lesen kann). Allerdings verwendet networkit.graphio.writeMat(G,"test.mat") eine riesige Menge RAM, die meinen Computer zum Absturz bringt.

Schließlich ist Saschas Antwort unten fertig, dauert aber ungefähr 40 Minuten.

    
eleanora 31.07.2016, 20:18
quelle

5 Antworten

12

Hier ist meine Lösung:

%Vor%

Pandas macht das Parsen mit read_csv schwer. Und Pandas speichert die Daten bereits im Spaltenformat. Die data[0] und data[1] erhalten nur Referenzen, keine Kopien. Dann füttere ich diese an coo_matrix . Benchmarked lokal:

%Vor%

Dann um eine CSR-Matrix in eine Datei zu speichern:

%Vor%

Benchmarked lokal:

%Vor%

Und später laden Sie es aus einer Datei zurück:

%Vor%

Benchmarked lokal:

%Vor%

Und schließlich alles testen:

%Vor%

Wenn test() ausgeführt wird, dauert es etwa 30 Sekunden:

%Vor%

Und der Speicher High-Water-Mark war ~ 1,79 GB.

Beachten Sie, dass nach dem Konvertieren von "kanten.txt" nach "kanten.npz" im CSR-Matrix-Format das Laden weniger als eine Sekunde dauert.

    
GrantJ 03.08.2016, 05:04
quelle
3

Aktualisierte Version

Wie in den Kommentaren angegeben, passte der Ansatz nicht zu Ihrem Anwendungsfall. Lassen Sie uns einige Änderungen vornehmen:

  • benutze pandas zum Einlesen der Daten (statt numpy: ich bin ziemlich überrascht, dass np.loadtxt so schlecht läuft!)
  • Verwenden Sie eine externe Bibliothek sortedcontainers , um einen mehr speichereffizienten Ansatz (anstelle eines Wörterbuchs) zu erreichen
  • Der grundlegende Ansatz ist der gleiche

Dieser Ansatz dauert ~ 45 Minuten (das ist langsam; aber Sie können das Ergebnis pickle / speichern, so dass Sie nur einmal machen müssen) und ~ 5 GB Speicher zur Vorbereitung der Sparse-Matrix für Ihre Daten, generiert mit:

%Vor%

Code

%Vor%

Erste Version

Hier ist ein sehr einfacher und sehr ineffizienter (in Bezug auf Zeit und Raum) Code, um diese spärliche Matrix zu erstellen. Ich poste diesen Code, weil ich glaube, dass es wichtig ist, die Kernteile zu verstehen, wenn man diese in etwas Größerem verwendet.

Lassen Sie uns sehen, ob dieser Code für Ihren Anwendungsfall effizient genug ist oder ob er funktioniert. Aus der Entfernung ist es schwer zu sagen, weil wir Ihre Daten nicht haben.

Der Wörterbuchteil, der für das Mapping verwendet wird, ist ein Kandidat, um Ihr Gedächtnis in die Luft zu jagen. Aber es ist sinnlos, dies zu optimieren, ohne zu wissen, ob es überhaupt benötigt wird. Vor allem, weil dieser Teil des Codes von der Anzahl der Scheitelpunkte in Ihrem Graph abhängt (und ich habe keine Kenntnis von dieser Kardinalität).

%Vor%

Ausgabe für Kanten-10.txt :

%Vor%     
sascha 31.07.2016 21:16
quelle
3

Ich habe die verschiedenen verfügbaren Methoden abgesehen von den bereits verwendeten Methoden ausprobiert. Ich fand Folgendes gut.

Methode 1 - Lesen Sie die Datei in eine Zeichenfolge und analysieren Sie die Zeichenfolge in eine 1-D-Array mit numpy fromstring.

%Vor%

Ausgabe:

%Vor%

Methode 2 - Wie Methode 1, außer dass die Datei nicht in eine Zeichenfolge geladen wird, sondern die Speicherabbildschnittstelle verwendet wird.

%Vor%

Ausgabe:

%Vor%

Überwacht mit /usr/bin/time , beide Methoden verwenden maximal ca. 2 GB Arbeitsspeicher.

Wenige Anmerkungen:

  1. Es scheint etwas besser zu sein als Pandas read_csv . Mit pandas read_csv ist die Ausgabe auf demselben Rechner

    5 loops, best of 3: 16.2 s per loop

  2. Die Umstellung von COO auf CSR / CSC verbraucht ebenfalls viel Zeit. In @ GrantJ's Antwort dauert es weniger Zeit, da die Initialisierung der COO-Matrix nicht korrekt ist. Das Argument muss als Tupel angegeben werden. Ich wollte hier einen Kommentar hinterlassen, aber ich habe noch keine Kommentarrechte.

  3. Meine Vermutung, warum dies etwas besser ist als Pandas read_csv ist die vorherige Annahme von 1D-Daten.

Walter 05.08.2016 03:26
quelle
2

In meiner Antwort betrachte ich den Fall, in dem die IDs der Knoten durch 9 Zeichen lange Zeichenfolgen für jedes Zeichen von [0-9A-Za-z] gegeben sind. n dieser Knoten-IDs sollten auf die Werte [0,n-1] abgebildet werden (was für Ihre Anwendung möglicherweise nicht notwendig ist, aber dennoch von allgemeinem Interesse ist).

Die nächsten Überlegungen, von denen Sie sicherlich wissen, sind hier der Vollständigkeit halber:

  1. Erinnerung ist der Flaschenhals.
  2. Es gibt ungefähr 10^8 strings in der Datei.
  3. ein 9 Zeichen langes string + int32 -Wertpaar kostet um 120 Bytes in einem Wörterbuch, was zu einer Speicherauslastung von 12 GB für die Datei führt.
  4. Eine String-ID aus der Datei kann auf ein int64 abgebildet werden: Es gibt 62 verschiedene Zeichen - & gt; kann mit 6 Bits, 9 Zeichen im String codiert werden - & gt; 6 * 9 = 54 & lt; 64 Bit. Siehe auch toInt64() Methode weiter unten.
  5. es gibt int64 + int32 = 12 Byte "echte" Information = & gt; ca. 1,2 GB könnten ausreichen, aber die Kosten für ein solches Paar in einem Wörterbuch betragen ungefähr 60 Bytes (ungefähr 6 GB RAM werden benötigt).
  6. Das Erstellen kleiner Objekte (auf dem Heap) führt zu viel Speicher-Overhead, so dass das Bündeln dieser Objekte in Arrays vorteilhaft ist. Interessante Informationen zum Speicher, der von Python-Objekten verwendet wird, finden Sie in seinem Tutorial-Stil Artikel . Interessante Erfahrungen mit der Reduzierung der Speichernutzung werden in diesem Blogeintrag .
  7. Python-Liste kommt als Datenstruktur und Wörterbuch nicht in Frage. array.array könnte alternativ sein, aber wir verwenden np.array (weil es Sortieralgorithmen für np.array gibt, aber nicht array.array ).

1. step: Lesen der Datei und Zuordnen von Zeichenfolgen zu int64 . Es ist ein Schmerz, eine np.array dynamisch wachsen zu lassen, also nehmen wir jetzt die Anzahl der Kanten in der Datei an (es wäre schön, sie in der Kopfzeile zu haben, aber sie kann auch aus der Dateigröße abgeleitet werden):

%Vor%

2. step: konvertiert die int64-Werte in Werte [0,n-1] :

Möglichkeit A , benötigt 3 * 0.8GB:

%Vor%

Möglichkeit B , benötigt 2 * 0.8GB, ist aber etwas langsamer:

%Vor%

3. step: setze alles in coo_matrix:

%Vor%

Für den Aufruf von data_as_coo_matrix("data.txt", 62500000) benötigt der Speicher Spitzen bei 2,5 GB (aber mit int32 anstelle von int64 werden nur 1,5 GB benötigt). Es dauerte ungefähr 5 Minuten auf meiner Maschine, aber meine Maschine ist ziemlich langsam ...

Was unterscheidet sich also von Ihrer Lösung?

  1. Ich bekomme nur eindeutige Werte von np.unique (und nicht alle Indizes und die Umkehrung), so dass etwas Speicher gespart wird - ich kann die alten IDs durch die neuen In-Place ersetzen.
  2. Ich habe keine Erfahrung mit pandas , also ist vielleicht etwas kopiert zwischen pandas & lt; - & gt; numpy Datenstrukturen?

Was ist der Unterschied zu Saschas Lösung?

  1. Es ist nicht notwendig, dass die Liste ständig sortiert ist - es ist genug zu sortieren, nachdem alle Elemente in der Liste sind, das ist np.unique() . Die Lösung von sascha hält die Liste die ganze Zeit sortiert - Sie müssen dafür zumindest mit einem konstanten Faktor bezahlen, auch wenn die Laufzeit O(n log(n)) bleibt. Ich nahm an, dass ein add-Vorgang wäre O(n) , aber wie bereits erwähnt, ist es O(log(n) .

Was ist der Unterschied zur Lösung von GrantJ?

  1. Die Größe der resultierenden dünn besetzten Matrix ist NxN - mit N - Anzahl der verschiedenen Knoten und nicht 2^54x2^54 (mit sehr vielen leeren Zeilen und Spalten).

PS:
Hier ist meine Idee, wie die 9 Zeichenfolgen-ID auf einen int64 Wert abgebildet werden kann, aber ich denke, dass diese Funktion ein Flaschenhals werden könnte, wie er geschrieben wurde und optimiert werden sollte.

%Vor%     
ead 04.08.2016 14:26
quelle
0

Vielleicht möchten Sie sich das Projekt iigraph ansehen, dies ist eine GPL-Bibliothek mit C-Code, die für diese Art entwickelt wurde der Sache, und hat eine nette Python-API. Ich denke in Ihrem Fall wäre der Python-Code so etwas wie

%Vor%     
maxymoo 01.08.2016 06:25
quelle