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.
Je suis un grand fan de hdf5 pour stocker de grands tableaux numpy. Il existe deux options pour traiter hdf5 en python:
Les deux sont conçus pour fonctionner efficacement avec les tableaux numpy.
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.
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 .
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' ) )
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' )
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.
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.
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')