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

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

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

#!/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? В идеале я хочу иметь возможность хранить несколько массивов в одном файле.

Ответы

Ответ 1

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

http://www.pytables.org/

http://www.h5py.org/

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

Ответ 2

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

тест для хранения массива numpy

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

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

Более подробную информацию и код можно найти в github repo.

Ответ 3

Теперь существует клон на основе 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' )

Ответ 4

savez() сохранить данные в 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()

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

Ответ 5

Время поиска медленное, потому что при использовании mmap для не загружает содержимое массива в память при вызове метода 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')

Ответ 6

Еще одна возможность эффективного хранения массивов 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 является основополагающим для достижения этого. Тот же script, но с использованием "clevel" = 0 (то есть отключение сжатия):

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

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