165 votes

meilleure façon de préserver les tableaux numpy sur le disque

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

J'ai trouvé numpy.savez y numpy.load . Mais la chose étrange est que numpy.load charge un fichier npy dans "memory-map". Cela signifie que la manipulation régulière des tableaux est très lente. Par exemple, quelque chose comme ceci 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 lents :

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.

286voto

Mark Points 1278

J'ai comparé les performances (espace et temps) pour un certain nombre de façons de stocker les tableaux numpy. Peu d'entre elles prennent en charge plusieurs tableaux par fichier, mais cela peut être utile de toute façon.

benchmark for numpy array storage

Les fichiers Npy et binaires sont tous deux très rapides et petits pour les données denses. Si les données sont éparses ou très structurées, il est préférable d'utiliser npz avec compression, ce qui permet d'économiser beaucoup d'espace mais coûte un peu de temps de chargement.

Si la portabilité est un problème, binaire est meilleur que npy. Si la lisibilité humaine est importante, alors vous devrez sacrifier beaucoup de performance, mais cela peut être réalisé assez bien en utilisant csv (qui est aussi très portable bien sûr).

Plus de détails et le code sont disponibles à l'adresse suivante le dépôt Github .

72voto

JoshAdel Points 15911

Je suis un grand fan de hdf5 pour le stockage de grands tableaux numpy. Il existe deux options pour utiliser hdf5 en python :

http://www.pytables.org/

http://www.h5py.org/

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

53voto

Suuuehgi Points 926

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)

EDITAR:

Il est également possible de "décaper" 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']
modules = dict(
    pickle=pickle, h5py=h5py, gzip=gzip, lzma=lzma, bz2=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:
        path = 'data.pkl.{}'.format(compression)

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

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

        else:
            time_start = time.time()
            with modules[compression].open(path, 'wb') as fout:
                pickle.dump(data[key], fout)
            time_tot = time.time() - time_start
            sizes[key][labels[compressions.index(compression)]] = (
                os.path.getsize(path) * 10**-6, 
                time_tot,
            )
            os.remove(path)

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

17voto

HYRY Points 26340

Savez() sauvegarde les données dans un fichier zip, Cela peut prendre un certain temps pour compresser et décompresser le fichier. Vous pouvez utiliser les fonctions save() et 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, il suffit d'ouvrir d'abord le fichier, puis d'enregistrer ou de charger les tableaux dans l'ordre.

8voto

Francesc Points 121

Une autre possibilité pour stocker les tableaux numpy de manière efficace 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 vieux 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 matrices 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 de la Blosc Le compresseur est fondamental 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ée par les performances du disque.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X