лучший способ сохранить массивы numpy на диске


Я ищу быстрый способ сохранить большие массивы numpy. Я хочу сохранить их на диск в двоичном формате, а затем прочитать их обратно в память относительно быстро. cPickle не достаточно быстро, к сожалению.

нашел numpy.savez и numpy.загрузить. Но самое странное, тупица.load загружает файл npy в "memory-map". Это означает, что регулярное манипулирование массивами очень медленно. Например, что-то вроде этого будет очень медленно:

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

точнее, первая строка будет очень быстрой, но остальные строки, которые присваивают массивы obj до смешного медленно:

loading time =  0.000220775604248
assining time =  2.72940087318

есть ли лучший способ сохранения массивов numpy? В идеале, я хочу иметь возможность хранить несколько массивов в одном файле.

6 81

6 ответов:

Я большой поклонник hdf5 для хранения больших массивов numpy. Есть два варианта работы с hdf5 в python:

http://www.pytables.org/

http://www.h5py.org/

оба предназначены для эффективной работы с массивами numpy.

Я сравнил производительность (пространство и время) для нескольких способов хранения массивов numpy. Немногие из них поддерживают несколько массивов в файл, но, возможно, это полезно в любом случае.

benchmark for numpy array storage

Npy и двоичные файлы являются очень быстрыми и маленькими для плотных данных. Если данные разрежены или очень структурированы, вы можете использовать npz со сжатием, что сэкономит много места, но будет стоить некоторого времени загрузки.

Если переносимость является проблемой, двоичный лучше, чем npy. Если человеческая читаемость важна, то вам придется пожертвовать большой производительностью, но это может быть достигнуто довольно хорошо с помощью csv (который также очень портативен, конечно).

более подробная информация и код доступны по адресу РЕПО github.

теперь есть клон на основе HDF5 pickle под названием 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:

также есть возможность "засолить" непосредственно в сжатый архив, выполнив:

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


приложение

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 () сохранить данные в zip-файл, это может занять некоторое время, чтобы zip & распаковать файл. Вы можете использовать функцию 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()

чтобы сохранить несколько массивов в одном файле, вам просто нужно сначала открыть файл, а затем сохранить или загрузить массивы в последовательности.

еще одна возможность эффективно хранить массивы numpy-это 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)

и выход для моего ноутбука (относительно старый MacBook Air с процессором Core2):

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

это означает, что он может хранить очень быстро, т. е. узким местом обычно является диск. Однако, поскольку коэффициенты сжатия здесь довольно хороши, эффективная скорость умножается на коэффициенты сжатия. Здесь представлены размеры для этих 76 МБ массивы:

$ 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

обратите внимание, что использование Blosc компрессор является основополагающим для достижения этой цели. Тот же скрипт, но с использованием 'clevel' = 0 (т. е. отключение сжатия):

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

явно узким местом является производительность диска.

время поиска медленно, потому что при использовании mmap to не загружает содержимое массива в память при вызове load метод. Данные лениво загружаются, когда необходимы определенные данные. И это происходит в поиске в вашем случае. Но второй поиск не будет таким медленным.

это хорошая особенность mmap когда у вас есть большой массив, вам не обязательно загружать все данные в память.

чтобы решить ваш может использовать joblib вы можете сбросить любой объект, который вы хотите использовать joblib.dump даже два или больше numpy arrays см. Пример

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