Ответ 1
Я большой поклонник hdf5 для хранения больших массивов numpy. Существует два способа работы с hdf5 в python:
Оба предназначены для эффективной работы с массивами 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? В идеале я хочу иметь возможность хранить несколько массивов в одном файле.
Я большой поклонник hdf5 для хранения больших массивов numpy. Существует два способа работы с hdf5 в python:
Оба предназначены для эффективной работы с массивами numpy.
Я сравнивал производительность (пространство и время) для нескольких способов хранения массивов numpy. Мало кто из них поддерживает несколько массивов на файл, но, возможно, он все равно полезен.
Файлы Npy и двоичные файлы очень быстры и малы для плотных данных. Если данные разрежены или очень структурированы, вы можете использовать npz со сжатием, что позволит сэкономить много места, но будет стоить некоторое время загрузки.
Если переносимость является проблемой, двоичный код лучше npy. Если человеческая читаемость важна, тогда вам придется пожертвовать большим количеством производительности, но ее можно добиться достаточно хорошо, используя csv (который также очень портативен, конечно).
Более подробную информацию и код можно найти в github repo.
Теперь существует клон на основе 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' ) )
Приложение
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 файле, может потребоваться некоторое время, чтобы почтить и распаковать файл. Вы можете использовать функции 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()
Чтобы сохранить несколько массивов в одном файле, вам просто нужно сначала открыть файл, а затем сохранить или загрузить массивы в последовательности.
Время поиска медленное, потому что при использовании 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')
Еще одна возможность эффективного хранения массивов 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)
явно узкое место в производительности диска.