numpy配列をディスクに保存する最良の方法


124

私は大きな派手な配列を保持するための高速な方法を探しています。それらをバイナリ形式でディスクに保存してから、比較的高速にメモリに読み戻します。残念ながらcPickleは十分な速度ではありません。

numpy.saveznumpy.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つのファイルに格納できるようにしたいと考えています。


3
デフォルトでnp.loadは、ファイルをmmapしないでください。
Fred Foo

6
何についてpytables
dsign 2012年

@larsmans、返信ありがとうございます。しかし、なぜルックアップ時間(私のコード例ではz ['a'])がとても遅いのですか?
ヴェンデッタ2012年

1
ifileに格納される配列の種類とそのサイズ、またはそれらが異なるファイル内のいくつかの配列である場合、またはどのように正確に保存するかなど、質問にもう少し情報があればよいでしょう。あなたの質問では、最初の行は何もせず、実際のロードはその後に発生するという印象を受けましたが、それらは推測にすぎません。
dsign

19
@larsmans-「npz」ファイル(つまりnumpy.savez、で保存された複数の配列)の場合、デフォルトは配列を「遅延ロード」することです。それらのマッピングは行いませんが、NpzFileオブジェクトにインデックスが作成されるまで読み込まれません。(したがって、OPが参照している遅延。)のドキュメントloadはこれをスキップするため、誤解を招く恐れがあります...
Joe Kington

回答:


63

私は大きなnumpy配列を格納するためのhdf5の大ファンです。Pythonでhdf5を処理するには、2つのオプションがあります。

http://www.pytables.org/

http://www.h5py.org/

どちらも、numpy配列を効率的に処理するように設計されています。


35
これらのパッケージを使用して配列を保存するコード例を提供してもよろしいですか?
dbliss 2015


1
私の経験から、チャンクストレージと圧縮を有効にすると、hdf5のパフォーマンスは読み取りと書き込みが非常に遅くなります。たとえば、チャンクサイズ(10,000 * 2000)の形状(2500,000 * 2000)の2つの2次元配列があります。形状(2000 * 2000)の配列の単一の書き込み操作は、完了するまでに約1〜2秒かかります。パフォーマンスの改善について何か提案はありますか?どうも。
Simon。リ・

205

numpy配列を格納するさまざまな方法でパフォーマンス(空間と時間)を比較しました。それらのいくつかは、ファイルごとに複数の配列をサポートしますが、おそらくそれはとにかく便利です。

numpy配列ストレージのベンチマーク

Npyファイルとバイナリファイルは、どちらも非常に高速で、密度の高いデータに対しては小さいです。データがスパースまたは非常に構造化されている場合は、npzと圧縮を併用すると、多くのスペースを節約できますが、ロード時間が多少かかります。

移植性が問題である場合、バイナリはnpyよりも優れています。人間の可読性が重要な場合は、多くのパフォーマンスを犠牲にする必要がありますが、csv(もちろん非常に移植性があります)を使用すると、かなりうまく達成できます。

詳細とコードはgithub repoで入手できます。


2
移植性binaryよりも優れている理由を説明npyしていただけますか?これはまた適用されnpzますか?
daniel451 2017年

1
@ daniel451形状、データ型、行ベースか列ベースかを知っていれば、どの言語でもバイナリファイルを読み取ることができるからです。Pythonだけを使用している場合は、npyで十分です。おそらくバイナリより少し簡単です。
Mark

1
ありがとうございました!もう1つの質問:私は見落としているのですか、それともHDF5を除外しましたか?これはかなり一般的であるため、他の方法と比較する方法に興味があります。
daniel451

1
pngとnpyを使用して同じ画像を保存しようとしました。pngは2Kのスペースしか使用しませんが、npyは307Kを使用します。この結果は実際の作業とは異なります。私は何か間違ったことをしていますか?この画像はグレースケール画像で、0と255のみが内部にあります。これはまばらなデータだと思いますか?次に、npzも使用しましたが、サイズはまったく同じです。
ニューヨークヤン

3
なぜh5pyがないのですか?それとも何か不足していますか?
daniel451

49

pickleと呼ばれるのHDF5ベースのクローンがあります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 )

編集:

以下を実行することにより、圧縮アーカイブに直接「ピックル」する可能性もあります。

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

一部のPPLが気にする可能性がある警告の1つは、pickleが任意のコードを実行できるため、データを保存するための他のプロトコルよりも安全性が低くなることです。
チャーリーパーカー

これは素晴らしい!lzmaまたはbz2を使用して、直接ピクルス化したファイルを読み取るためのコードを提供することもできますか?
アーネストSキルバカラン

14

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

複数の配列を1つのファイルに保存するには、まずファイルを開いてから、配列を順番に保存またはロードするだけです。


7

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)

ディスクのパフォーマンスが明らかにボトルネックになっています。


2
BloscpackとPyTablesは別のプロジェクトですが、前者はディスクダンプのみに重点を置き、格納された配列のスライスは対象としていませんでした。両方をテストし、純粋な「ファイルダンププロジェクト」のBloscpackはPyTablesよりもほぼ6倍高速です。
Marcelo Sardelich 2015年

4

mmapto を使用すると、loadメソッドを呼び出すときに配列の内容をメモリに読み込まないため、ルックアップ時間は遅くなります。特定のデータが必要な場合、データは遅延読み込みされます。そして、これはあなたの場合のルックアップで起こります。しかし、2番目の検索はそれほど遅くありません。

これはmmap、大きな配列がある場合に、データ全体をメモリにロードする必要がないという優れた機能です。

使用できるjoblibを解決するために、joblib.dump2つ以上でも使用する任意のオブジェクトをダンプできます。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')

ライブラリは使用できなくなりました。
Andrea Moro
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.