大規模な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つのファイルに複数の配列を格納できるようにしたいです。
私は大きなnumpy配列を保存するhdf5の大ファンです。 Pythonでhdf5を処理するには、2つのオプションがあります。
どちらもnumpy配列で効率的に動作するように設計されています。
Numpy配列を格納するいくつかの方法のパフォーマンス(空間と時間)を比較しました。ファイルごとに複数の配列をサポートしているものはほとんどありませんが、とにかく便利かもしれません。
Npyおよびバイナリファイルは、非常に高速であり、高密度のデータの場合は小さくなります。データがスパースまたは非常に構造化されている場合、npzを圧縮とともに使用すると、多くのスペースを節約できますが、ロード時間がかかります。
移植性が問題になる場合、バイナリはnpyよりも優れています。人間の可読性が重要な場合、多くのパフォーマンスを犠牲にする必要がありますが、csvを使用してかなりうまく達成できます(もちろん、非常に移植性が高い)。
詳細とコードは githubリポジトリ で入手できます。
pickle
と呼ばれるhickle
のHDF5ベースのクローンがあります!
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 )
編集:
また、以下を実行することにより、圧縮されたアーカイブに直接「ピクル」する可能性があります。
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ファイルに保存します。ファイルを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()
1つのファイルに複数の配列を保存するには、最初にファイルを開いてから、配列を順番に保存またはロードするだけです。
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)
私のラップトップ(Core2プロセッサを搭載した比較的古いMacBook Air)の出力:
$ python store-blpk.py
store time = 0.19 (1216.45 MB/s)
loading time = 0.25 (898.08 MB/s)
つまり、非常に高速に保存できることを意味します。つまり、通常、ボトルネックはディスクです。ただし、ここでは圧縮率がかなり良いので、実効速度に圧縮率を掛けます。これらの76 MBアレイのサイズは次のとおりです。
$ 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
メソッドを呼び出したときに、load
を使用して配列のコンテンツをメモリにロードしないため、ルックアップ時間が遅くなります。特定のデータが必要な場合、データは遅延ロードされます。そして、これはあなたの場合のルックアップで起こります。しかし、2回目の検索はそれほど遅くなりません。
これはmmap
の素晴らしい機能です。大きな配列がある場合、データ全体をメモリにロードする必要はありません。
joblib を使用して解決するには、joblib.dump
を使用して任意のオブジェクトをダンプできます。2つ以上の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')