Ich habe HDF5 und seine Python-Oberfläche (h5py) untersucht und versucht, eine HDF5-Datei (eindimensionales Array mit 100 Millionen Ganzzahlen) in eine normale Liste und ein anderes Mal in ein numpy-Array einzulesen. Das Konvertieren des Datasets in einen Numpy war sehr schnell im Vergleich zu dem Versuch, es in eine normale Python-Liste zu konvertieren (tatsächlich dauerte es sehr lange, bis ich es mit einer Liste beendet hatte, bevor es fertig war).

Kann mir jemand helfen zu verstehen, was intern passiert, wodurch die Konvertierung von HDF5-Datasets in ein Numpy-Array extrem schneller ist als mit einer normalen Liste? Hat es mit h5py-Kompatibilität mit numpy zu tun?

import numpy as np
import hdf5

def readtolist(dataset):
    return "normal list count = {0}".format(len(list(dataset['1'])))

def readtonp(dataset):
    n1 = np.array(dataset)
    return "numpy count = {0}".format(len(n1))

f = h5py.File(path, 'r')    
readtolist(f['1'])
readtonp(f['1'])

Danke für die Hilfe!

1
Shazly 18 Apr. 2018 im 21:10

3 Antworten

Beste Antwort

Verwenden einer Testdatei, die ich kürzlich erstellt habe:

In [78]: f = h5py.File('test.h5')
In [79]: list(f.keys())
Out[79]: ['x']
In [80]: f['x']
Out[80]: <HDF5 dataset "x": shape (2, 5), type "<i8">
In [81]: x = f['x'][:]
In [82]: x
Out[82]: 
array([[0, 2, 4, 6, 8],
       [1, 3, 5, 7, 9]])
In [83]: alist = x.tolist()
In [84]: alist
Out[84]: [[0, 2, 4, 6, 8], [1, 3, 5, 7, 9]]

Die Datenspeicherung in HDF5 ähnelt numpy Arrays. h5py verwendet kompilierten Code (cython) als Schnittstelle zum HDF5 Basiscode. Es lädt die Datensätze als numpy Arrays.

Um eine Liste zu erhalten, müssen Sie das Array in eine Liste konvertieren. Bei einem 1d-Array funktioniert list(x), ist jedoch langsam und unvollständig. tolist() ist der richtige Weg.

list() iteriert über die erste Dimension des Arrays:

In [85]: list(x)
Out[85]: [array([0, 2, 4, 6, 8]), array([1, 3, 5, 7, 9])]
In [86]: list(f['x'])
Out[86]: [array([0, 2, 4, 6, 8]), array([1, 3, 5, 7, 9])]

1211:~/mypy$ h5dump test.h5
HDF5 "test.h5" {
GROUP "/" {
   DATASET "x" {
      DATATYPE  H5T_STD_I64LE
      DATASPACE  SIMPLE { ( 2, 5 ) / ( 2, 5 ) }
      DATA {
      (0,0): 0, 2, 4, 6, 8,
      (1,0): 1, 3, 5, 7, 9
      }
   }
}
}

Ich sollte hinzufügen, dass ein Python list eine eindeutige Datenstruktur ist. Es enthält Zeiger auf Objekte an anderer Stelle im Speicher und kann daher alle Arten von Objekten enthalten - Zahlen, andere Listen, Wörterbücher, Zeichenfolgen, benutzerdefinierte Klassen usw. Ein HDF5 -Datensatz wie ein numpy -Array hat einen einheitlichen Datentyp haben (das DATATYPE im Dump). Es kann beispielsweise kein Objekt-Typ-Array speichern. Wenn Sie eine Liste in einem HDF5 speichern möchten, müssen Sie sie zuerst in ein Array konvertieren.

2
hpaulj 18 Apr. 2018 im 19:23

HDF5 ist ein Dateiformat zum Speichern großer Mengen wissenschaftlicher Array-Daten. Es kann mehrere Datensätze speichern und bietet mehrere On-the-Fly-Komprimierungsmodelle, sodass Daten mit wiederholten Mustern effizienter gespeichert werden können.

Normalerweise ist das Parsen mit Pandas oder Numpy viel schneller, da diese Komprimierung in einer vektorisierten Form erfolgt, während natives Python list dies über das Verschachteln erledigt, was erheblich langsamer ist.

Das ist es im Grunde auf den Punkt gebracht.

Das Folgende ist ein Experiment, bei dem pandas und Numpy unter der Haube verwendet werden, um eine Datei mit 100000 Einträgen zu generieren, diese in HDF5 zu speichern und dann erneut zu analysieren.

Generieren der Daten

frame = pd.DataFrame({'a': np.random.randn(100000)})
store = pd.HDFStore('mydata.h5')
frame.to_hdf('mydata.h5', 'obj1', format='table')
store.close()

Timing der Analyse

%%timeit
df = pd.read_hdf('mydata.h5', 'obj1')

Ausgabe

9.14 ms ± 240 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Sehr schnell im Vergleich zu list.

1
iDrwish 18 Apr. 2018 im 18:37

Ich bin nicht sicher, ob der Unterschied, den Sie sehen, spezifisch für hdf5 ist. Ein ähnlicher Effekt kann erzielt werden, wenn nur eine Zeichenfolge als Datenquelle verwendet wird:

>>> import numpy as np
>>> import string, random
>>> 
>>> from timeit import timeit    
>>> kwds = dict(globals=globals(), number=1000)
>>> 
>>> a = ''.join(random.choice(string.ascii_letters) for _ in range(1000000))
>>> 
>>> timeit("A = np.fromstring(a, dtype='S1')", **kwds)
0.06803569197654724
>>> timeit("L = list(a)", **kwds)
6.131339570041746

Für homogene Daten leicht vereinfacht, speichert numpy sie "so wie sie sind" als Speicherblock, während eine Liste für jedes Element ein Python-Objekt erstellt. Ein Python-Objekt besteht in diesem Fall aus dem Wert, einem Zeiger auf sein Typobjekt und einer Referenzanzahl. Zusammenfassend kann numpy also im Wesentlichen nur einen Speicherblock kopieren, während list all diese Objekte und den Listencontainer zuordnen und erstellen muss.

Die Kehrseite davon ist, dass der Zugriff auf einzelne Elemente über Listen schneller ist, da das Array jetzt unter anderem ein Python-Objekt erstellen muss, während die Liste einfach das gespeicherte zurückgeben kann:

>>> L = list(a)
>>> A = np.fromstring(a, dtype='S1')
>>> 
>>> kwds = dict(globals=globals(), number=100)
>>> 
>>> timeit("[L[i] for i in range(len(L))]", **kwds)
5.607562301913276
>>> timeit("[A[i] for i in range(len(A))]", **kwds)
13.343806453049183
0
Paul Panzer 18 Apr. 2018 im 19:33