web-dev-qa-db-fra.com

meilleur moyen de préserver les tableaux numpy sur le disque

Je cherche un moyen rapide de préserver de grands tableaux numpy. Je veux les sauvegarder sur le disque dans un format binaire, puis les lire en mémoire assez rapidement. cPickle n'est pas assez rapide, malheureusement.

J'ai trouvé numpy.savez et numpy.load . Mais ce qui est étrange, c’est que numpy.load charge un fichier npy dans "memory-map". Cela signifie une manipulation régulière des tableaux très lente. Par exemple, quelque chose comme ça serait vraiment lent:

#!/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;

plus précisément, la première ligne sera très rapide, mais les lignes restantes qui assignent les tableaux à obj sont ridiculement lentes:

loading time =  0.000220775604248
assining time =  2.72940087318

Existe-t-il un meilleur moyen de préserver les tableaux numpy? Idéalement, je veux pouvoir stocker plusieurs tableaux dans un seul fichier.

106
CodeNoob

Je suis un grand fan de hdf5 pour stocker de grands tableaux numpy. Il existe deux options pour traiter hdf5 en python:

http://www.pytables.org/

http://www.h5py.org/

Les deux sont conçus pour fonctionner efficacement avec les tableaux numpy.

50
JoshAdel

J'ai comparé les performances (espace et temps) de différentes manières pour stocker des tableaux numpy. Peu d'entre eux supportent plusieurs tableaux par fichier, mais c'est peut-être utile quand même.

benchmark for numpy array storage

Les fichiers Npy et binaires sont très rapides et petits pour les données denses. Si les données sont rares ou très structurées, vous voudrez peut-être utiliser npz avec compression, ce qui vous fera économiser beaucoup d’espace, mais vous coûtera un peu de temps de chargement.

Si la portabilité est un problème, le binaire est meilleur que le npy. Si la lisibilité humaine est importante, vous devrez sacrifier beaucoup de performances, mais vous pouvez vous en tirer assez bien avec CSV (également très portable, bien sûr).

Plus de détails et le code sont disponibles sur le repo de github .

166
Mark

Il existe maintenant un clone basé sur HDF5 de pickle appelé 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 )

EDIT:

Il est également possible de "mélanger" directement dans une archive compressée en faisant:

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

compression


Annexe

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' )
38
Suuuehgi

savez () sauvegarder les données dans un fichier Zip, Cela peut prendre un certain temps pour décompresser et décompresser le fichier. Vous pouvez utiliser la fonction save () & load ():

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

Pour enregistrer plusieurs tableaux dans un seul fichier, vous devez d'abord ouvrir le fichier, puis enregistrer ou charger les tableaux dans l'ordre.

14
HYRY

Une autre possibilité de stocker efficacement des tableaux numpy est 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)

et la sortie pour mon ordinateur portable (un MacBook Air relativement ancien avec un processeur Core2):

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

cela signifie qu'il peut stocker très rapidement, c'est-à-dire que le goulot d'étranglement est généralement le disque. Cependant, comme les taux de compression sont plutôt bons ici, la vitesse effective est multipliée par les taux de compression. Voici les tailles de ces baies de 76 Mo:

$ 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

Veuillez noter que l'utilisation du compresseur Blosc est fondamentale pour y parvenir. Le même script mais en utilisant 'clevel' = 0 (c'est-à-dire en désactivant la compression):

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

est clairement goulot d'étranglement par les performances du disque.

7
Francesc

Le temps de recherche est lent car lorsque vous utilisez mmap pour ne pas charger le contenu du tableau en mémoire lorsque vous appelez la méthode load. Les données sont chargées paresseux lorsque des données particulières sont nécessaires. Et cela se produit lors de la recherche dans votre cas. Mais la seconde recherche ne sera pas si lente.

C'est une fonctionnalité intéressante de mmap lorsque vous avez un grand tableau, vous n'avez pas à charger des données entières en mémoire.

Pour résoudre votre peut utiliser joblib vous pouvez vider n'importe quel objet que vous voulez en utilisant joblib.dump même deux ou plus numpy arrays, voir l'exemple

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')
5
Michal