Der beste Weg, um numpy Arrays auf der Festplatte zu erhalten


125

Ich suche nach einem schnellen Weg, um große numpy Arrays zu erhalten. Ich möchte sie in einem Binärformat auf der Festplatte speichern und sie dann relativ schnell wieder in den Speicher zurücklesen. cPickle ist leider nicht schnell genug.

Ich habe numpy.savez und numpy.load gefunden . Aber das Seltsame ist, dass numpy.load eine npy-Datei in "memory-map" lädt. Das bedeutet, dass Arrays regelmäßig sehr langsam bearbeitet werden. Zum Beispiel wäre so etwas sehr langsam:

#!/usr/bin/python
import numpy as np;
import time; 
from tempfile import TemporaryFile

n = 10000000;

a = np.arange(n)
b = np.arange(n) * 10
c = np.arange(n) * -0.5

file = TemporaryFile()
np.savez(file,a = a, b = b, c = c);

file.seek(0)
t = time.time()
z = np.load(file)
print "loading time = ", time.time() - t

t = time.time()
aa = z['a']
bb = z['b']
cc = z['c']
print "assigning time = ", time.time() - t;

Genauer gesagt wird die erste Zeile sehr schnell sein, aber die verbleibenden Zeilen, denen die Arrays zugewiesen objsind , sind lächerlich langsam:

loading time =  0.000220775604248
assining time =  2.72940087318

Gibt es eine bessere Möglichkeit, numpy Arrays zu erhalten? Idealerweise möchte ich mehrere Arrays in einer Datei speichern können.


3
Standardmäßig np.loadsollte die Datei nicht mmap werden.
Fred Foo

6
Was ist mit Pytables ?
Dsign

@larsmans, danke für die Antwort. aber warum ist die Suchzeit (z ['a'] in meinem Codebeispiel) so langsam?
Vendetta

1
Es wäre schön, wenn Ihre Frage etwas mehr Informationen enthalten würde, z. B. die Art des Arrays, das in ifile gespeichert ist, und seine Größe, oder wenn es sich um mehrere Arrays in verschiedenen Dateien handelt oder wie genau Sie sie speichern. Durch Ihre Frage habe ich den Eindruck, dass die erste Zeile nichts bewirkt und dass das eigentliche Laden danach erfolgt, aber das sind nur Vermutungen.
Unterzeichnen Sie den

19
@larsmans - Für eine "npz" -Datei (dh mehrere Arrays, mit denen gespeichert wurde numpy.savez) wird standardmäßig die Arrays "träge geladen". Sie werden nicht gespeichert, aber erst geladen, wenn das NpzFileObjekt indiziert ist. (Daher die Verzögerung, auf die sich das OP bezieht.) Die Dokumentation für loadüberspringt dies und ist daher ein Hauch irreführend ...
Joe Kington

Antworten:


63

Ich bin ein großer Fan von hdf5 zum Speichern großer numpy Arrays. Es gibt zwei Möglichkeiten, mit hdf5 in Python umzugehen:

http://www.pytables.org/

http://www.h5py.org/

Beide sind so konzipiert, dass sie effizient mit Numpy-Arrays arbeiten.


35
Wären Sie bereit, einen Beispielcode bereitzustellen, der diese Pakete zum Speichern eines Arrays verwendet?
Dbliss


1
Nach meinen Erfahrungen ist das Lesen und Schreiben von HDF5 sehr langsam, wobei Chunk-Speicher und -Komprimierung aktiviert sind. Zum Beispiel habe ich zwei 2-D-Arrays mit einer Form (2500.000 * 2000) und einer Blockgröße (10.000 * 2000). Ein einzelner Schreibvorgang eines Arrays mit Form (2000 * 2000) dauert etwa 1 bis 2 Sekunden. Haben Sie Vorschläge zur Verbesserung der Leistung? Vielen Dank.
Simon. Li

204

Ich habe die Leistung (Raum und Zeit) auf verschiedene Arten verglichen, um Numpy-Arrays zu speichern. Nur wenige von ihnen unterstützen mehrere Arrays pro Datei, aber vielleicht ist es trotzdem nützlich.

Benchmark für die Speicherung von Numpy-Arrays

Npy- und Binärdateien sind sowohl sehr schnell als auch klein für dichte Daten. Wenn die Daten spärlich oder sehr strukturiert sind, möchten Sie möglicherweise npz mit Komprimierung verwenden, was viel Platz spart, aber einige Ladezeit kostet.

Wenn Portabilität ein Problem ist, ist Binär besser als npy. Wenn die Lesbarkeit des Menschen wichtig ist, müssen Sie viel Leistung opfern, aber dies kann mit csv (das natürlich auch sehr portabel ist) ziemlich gut erreicht werden.

Weitere Details und den Code finden Sie im Github Repo .


2
Können Sie erklären, warum binaryes besser ist als npyfür die Portabilität? Gilt das auch für npz?
Daniel451

1
@ daniel451 Weil jede Sprache Binärdateien lesen kann, wenn sie nur die Form, den Datentyp und die Zeilen- oder Spaltenbasis kennt. Wenn Sie nur Python verwenden, ist npy in Ordnung, wahrscheinlich etwas einfacher als binär.
Mark

1
Danke dir! Noch eine Frage: Übersehe ich etwas oder hast du HDF5 weggelassen? Da dies ziemlich häufig ist, würde mich interessieren, wie es mit den anderen Methoden verglichen wird.
Daniel451

1
Ich habe versucht, mit png und npy ein und dasselbe Bild zu speichern. png benötigt nur 2K Speicherplatz, während npy 307K benötigt. Dieses Ergebnis unterscheidet sich wirklich von Ihrer Arbeit. Mache ich etwas falsch? Dieses Bild ist ein Graustufenbild und nur 0 und 255 befinden sich im Inneren. Ich denke das sind spärliche Daten richtig? Dann habe ich auch npz verwendet, aber die Größe ist völlig gleich.
York Yang

3
Warum fehlt h5py? Oder fehlt mir etwas?
Daniel451

48

Es gibt jetzt einen HDF5-basierten Klon picklenamens hickle!

https://github.com/telegraphic/hickle

import hickle as hkl 

data = { 'name' : 'test', 'data_arr' : [1, 2, 3, 4] }

# Dump data to file
hkl.dump( data, 'new_data_file.hkl' )

# Load data from file
data2 = hkl.load( 'new_data_file.hkl' )

print( data == data2 )

BEARBEITEN:

Es besteht auch die Möglichkeit, direkt in ein komprimiertes Archiv zu "beizen", indem Sie Folgendes tun:

import pickle, gzip, lzma, bz2

pickle.dump( data, gzip.open( 'data.pkl.gz',   'wb' ) )
pickle.dump( data, lzma.open( 'data.pkl.lzma', 'wb' ) )
pickle.dump( data,  bz2.open( 'data.pkl.bz2',  'wb' ) )

Kompression


Blinddarm

import numpy as np
import matplotlib.pyplot as plt
import pickle, os, time
import gzip, lzma, bz2, h5py

compressions = [ 'pickle', 'h5py', 'gzip', 'lzma', 'bz2' ]
labels = [ 'pickle', 'h5py', 'pickle+gzip', 'pickle+lzma', 'pickle+bz2' ]
size = 1000

data = {}

# Random data
data['random'] = np.random.random((size, size))

# Not that random data
data['semi-random'] = np.zeros((size, size))
for i in range(size):
    for j in range(size):
        data['semi-random'][i,j] = np.sum(data['random'][i,:]) + np.sum(data['random'][:,j])

# Not random data
data['not-random'] = np.arange( size*size, dtype=np.float64 ).reshape( (size, size) )

sizes = {}

for key in data:

    sizes[key] = {}

    for compression in compressions:

        if compression == 'pickle':
            time_start = time.time()
            pickle.dump( data[key], open( 'data.pkl', 'wb' ) )
            time_tot = time.time() - time_start
            sizes[key]['pickle'] = ( os.path.getsize( 'data.pkl' ) * 10**(-6), time_tot )
            os.remove( 'data.pkl' )

        elif compression == 'h5py':
            time_start = time.time()
            with h5py.File( 'data.pkl.{}'.format(compression), 'w' ) as h5f:
                h5f.create_dataset('data', data=data[key])
            time_tot = time.time() - time_start
            sizes[key][compression] = ( os.path.getsize( 'data.pkl.{}'.format(compression) ) * 10**(-6), time_tot)
            os.remove( 'data.pkl.{}'.format(compression) )

        else:
            time_start = time.time()
            pickle.dump( data[key], eval(compression).open( 'data.pkl.{}'.format(compression), 'wb' ) )
            time_tot = time.time() - time_start
            sizes[key][ labels[ compressions.index(compression) ] ] = ( os.path.getsize( 'data.pkl.{}'.format(compression) ) * 10**(-6), time_tot )
            os.remove( 'data.pkl.{}'.format(compression) )


f, ax_size = plt.subplots()
ax_time = ax_size.twinx()

x_ticks = labels
x = np.arange( len(x_ticks) )

y_size = {}
y_time = {}
for key in data:
    y_size[key] = [ sizes[key][ x_ticks[i] ][0] for i in x ]
    y_time[key] = [ sizes[key][ x_ticks[i] ][1] for i in x ]

width = .2
viridis = plt.cm.viridis

p1 = ax_size.bar( x-width, y_size['random']       , width, color = viridis(0)  )
p2 = ax_size.bar( x      , y_size['semi-random']  , width, color = viridis(.45))
p3 = ax_size.bar( x+width, y_size['not-random']   , width, color = viridis(.9) )

p4 = ax_time.bar( x-width, y_time['random']  , .02, color = 'red')
ax_time.bar( x      , y_time['semi-random']  , .02, color = 'red')
ax_time.bar( x+width, y_time['not-random']   , .02, color = 'red')

ax_size.legend( (p1, p2, p3, p4), ('random', 'semi-random', 'not-random', 'saving time'), loc='upper center',bbox_to_anchor=(.5, -.1), ncol=4 )
ax_size.set_xticks( x )
ax_size.set_xticklabels( x_ticks )

f.suptitle( 'Pickle Compression Comparison' )
ax_size.set_ylabel( 'Size [MB]' )
ax_time.set_ylabel( 'Time [s]' )

f.savefig( 'sizes.pdf', bbox_inches='tight' )

Eine Warnung, die einige Leute interessieren könnten, ist, dass pickle beliebigen Code ausführen kann, was ihn weniger sicher macht als andere Protokolle zum Speichern von Daten.
Charlie Parker

Das ist toll! Können Sie auch den Code zum Lesen der Dateien bereitstellen, die mit lzma oder bz2 direkt in die Komprimierung eingelegt wurden?
Ernest S Kirubakaran vor

14

savez () speichert Daten in einer Zip-Datei. Das Komprimieren und Entpacken der Datei kann einige Zeit dauern. Sie können die Funktion save () & load () verwenden:

f = file("tmp.bin","wb")
np.save(f,a)
np.save(f,b)
np.save(f,c)
f.close()

f = file("tmp.bin","rb")
aa = np.load(f)
bb = np.load(f)
cc = np.load(f)
f.close()

Um mehrere Arrays in einer Datei zu speichern, müssen Sie nur zuerst die Datei öffnen und dann die Arrays nacheinander speichern oder laden.


7

Eine weitere Möglichkeit, Numpy-Arrays effizient zu speichern, ist Bloscpack :

#!/usr/bin/python
import numpy as np
import bloscpack as bp
import time

n = 10000000

a = np.arange(n)
b = np.arange(n) * 10
c = np.arange(n) * -0.5
tsizeMB = sum(i.size*i.itemsize for i in (a,b,c)) / 2**20.

blosc_args = bp.DEFAULT_BLOSC_ARGS
blosc_args['clevel'] = 6
t = time.time()
bp.pack_ndarray_file(a, 'a.blp', blosc_args=blosc_args)
bp.pack_ndarray_file(b, 'b.blp', blosc_args=blosc_args)
bp.pack_ndarray_file(c, 'c.blp', blosc_args=blosc_args)
t1 = time.time() - t
print "store time = %.2f (%.2f MB/s)" % (t1, tsizeMB / t1)

t = time.time()
a1 = bp.unpack_ndarray_file('a.blp')
b1 = bp.unpack_ndarray_file('b.blp')
c1 = bp.unpack_ndarray_file('c.blp')
t1 = time.time() - t
print "loading time = %.2f (%.2f MB/s)" % (t1, tsizeMB / t1)

und die Ausgabe für meinen Laptop (ein relativ altes MacBook Air mit einem Core2-Prozessor):

$ python store-blpk.py
store time = 0.19 (1216.45 MB/s)
loading time = 0.25 (898.08 MB/s)

Das bedeutet, dass es sehr schnell gespeichert werden kann, dh der Engpass ist normalerweise die Festplatte. Da die Kompressionsverhältnisse hier jedoch ziemlich gut sind, wird die effektive Geschwindigkeit mit den Kompressionsverhältnissen multipliziert. Hier sind die Größen für diese 76-MB-Arrays:

$ ll -h *.blp
-rw-r--r--  1 faltet  staff   921K Mar  6 13:50 a.blp
-rw-r--r--  1 faltet  staff   2.2M Mar  6 13:50 b.blp
-rw-r--r--  1 faltet  staff   1.4M Mar  6 13:50 c.blp

Bitte beachten Sie, dass die Verwendung des Blosc- Kompressors von grundlegender Bedeutung ist, um dies zu erreichen. Das gleiche Skript, jedoch mit 'clevel' = 0 (dh Deaktivierung der Komprimierung):

$ python bench/store-blpk.py
store time = 3.36 (68.04 MB/s)
loading time = 2.61 (87.80 MB/s)

ist eindeutig ein Engpass durch die Festplattenleistung.


2
Für wen es wichtig sein könnte: Obwohl Bloscpack und PyTables unterschiedliche Projekte sind, wobei sich das erstere nur auf das Dumping von Festplatten und nicht auf gespeicherte Arrays konzentriert, habe ich beide getestet und für reine "File Dump-Projekte" ist Bloscpack fast 6x schneller als PyTables.
Marcelo Sardelich

4

Die Suchzeit ist langsam, da beim mmapAufrufen der loadMethode der Inhalt des Arrays nicht in den Speicher geladen wird. Daten werden verzögert geladen, wenn bestimmte Daten benötigt werden. Und dies geschieht in Ihrem Fall bei der Suche. Aber die zweite Suche wird nicht so langsam sein.

Dies ist eine nette Funktion, mmapwenn Sie ein großes Array haben und nicht ganze Daten in den Speicher laden müssen.

Um Ihre Joblib zu lösen, können Sie jedes gewünschte Objekt mit joblib.dumpzwei oder mehr Objekten sichernnumpy arrays (siehe Beispiel)

firstArray = np.arange(100)
secondArray = np.arange(50)
# I will put two arrays in dictionary and save to one file
my_dict = {'first' : firstArray, 'second' : secondArray}
joblib.dump(my_dict, 'file_name.dat')

Die Bibliothek ist nicht mehr verfügbar.
Andrea Moro
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.