diskte numpy dizileri korumanın en iyi yolu


124

Büyük uyuşmuş dizileri korumanın hızlı bir yolunu arıyorum. Bunları ikili biçimde diske kaydetmek, ardından nispeten hızlı bir şekilde belleğe geri okumak istiyorum. cPickle maalesef yeterince hızlı değil.

Bulduğum numpy.savez ve numpy.load . Ama tuhaf olan şu ki, numpy.load "bellek haritasına" bir npy dosyası yüklüyor. Bu, dizilerin düzenli olarak işlenmesinin gerçekten yavaş olduğu anlamına gelir. Örneğin, böyle bir şey gerçekten çok yavaş olurdu:

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

daha doğrusu, ilk satır gerçekten hızlı olacaktır, ancak dizileri atayan kalan satırlar objgülünç derecede yavaştır:

loading time =  0.000220775604248
assining time =  2.72940087318

Uyuşmuş dizileri korumanın daha iyi bir yolu var mı? İdeal olarak, birden çok diziyi tek bir dosyada saklayabilmek istiyorum.


3
Varsayılan olarak, np.loadgerektiği değil dosyayı mmap.
Fred Foo


@larsmans, cevabınız için teşekkürler. ama arama süresi (kod örneğimdeki z ['a']) neden bu kadar yavaş?
Vendetta

1
Sorunuzda, ifile'de depolanan dizi türü ve boyutu gibi veya farklı dosyalarda birkaç dizi olup olmadıkları veya bunları tam olarak nasıl kaydedeceğiniz gibi biraz daha fazla bilgi olsaydı iyi olurdu. Sorunuzdan, ilk satırın hiçbir şey yapmadığı ve asıl yüklemenin daha sonra gerçekleştiği izlenimini edindim, ancak bunlar yalnızca tahminler.
dsign

19
@larsmans - Değeri ne olursa olsun, bir "npz" dosyası için (yani numpy.savez, kaydedilmiş birden çok dizi ), varsayılan değer dizileri "tembel olarak yüklemek" tir . Onları eşleştirmiyor, ancak NpzFilenesne indekslenene kadar onları yüklemiyor . (Böylece OP'nin bahsettiği gecikme.) Belgeler loadbunu atlıyor ve bu nedenle yanıltıcı bir dokunuş ...
Joe Kington

Yanıtlar:


63

Büyük uyuşmuş dizileri depolamak için büyük bir hdf5 hayranıyım. Python'da hdf5 ile uğraşmak için iki seçenek vardır:

http://www.pytables.org/

http://www.h5py.org/

Her ikisi de uyuşuk dizilerle verimli bir şekilde çalışmak üzere tasarlanmıştır.


35
Bir diziyi kaydetmek için bu paketleri kullanarak bazı örnek kodlar sağlamaya istekli olur musunuz?
dbliss


1
Deneyimlerime göre, hdf5 performansları yığın depolama ve sıkıştırma etkinken çok yavaş okuma ve yazma sağlıyor. Örneğin, yığın boyutlu (10.000 * 2000) şekilli (2500.000 * 2000) iki 2-D dizim var. Şekilli (2000 * 2000) bir dizinin tek bir yazma işleminin tamamlanması yaklaşık 1 ~ 2s sürer. Performansı iyileştirmek için herhangi bir öneriniz var mı? Teşekkürler.
Simon. Li

206

Uyuşmuş dizileri depolamanın birkaç yolu için performansı (alan ve zaman) karşılaştırdım. Çok azı dosya başına birden çok diziyi destekler, ancak yine de yararlı olabilir.

numpy dizi depolaması için kıyaslama

Npy ve ikili dosyalar yoğun veriler için hem gerçekten hızlı hem de küçüktür. Veriler seyrekse veya çok yapılandırılmışsa, npz'yi sıkıştırma ile kullanmak isteyebilirsiniz, bu da çok fazla alan tasarrufu sağlar ancak yükleme süresine mal olur.

Taşınabilirlik bir sorunsa, ikili program npy'den daha iyidir. İnsan tarafından okunabilirlik önemliyse, çok fazla performanstan ödün vermeniz gerekir, ancak csv kullanarak oldukça iyi bir şekilde elde edilebilir (ki bu da elbette çok taşınabilir).

Daha fazla ayrıntı ve kod , github deposunda mevcuttur .


2
Neden taşınabilirlikten binarydaha iyi olduğunu açıklayabilir misiniz npy? Bu da geçerli npzmi?
daniel451

1
@ daniel451 Çünkü herhangi bir dil, sadece şekli, veri türünü ve satır veya sütun tabanlı olup olmadığını bilirlerse ikili dosyaları okuyabilir. Sadece Python kullanıyorsanız, npy iyidir, muhtemelen ikiliden biraz daha kolaydır.
Mark

1
Teşekkür ederim! Bir soru daha: Bir şeyi gözden mi kaçırdım yoksa HDF5'i mi atladın? Bu oldukça yaygın olduğu için, diğer yöntemlerle karşılaştırılmasıyla ilgilenirim.
daniel451

1
Aynı resmi kaydetmek için png ve npy'yi kullanmayı denedim. npy 307K alırken png yalnızca 2K yer kaplar. Bu sonuç işinizden gerçekten farklı. Yanlış bir şey mi yapıyorum? Bu resim gri tonlamalı bir resimdir ve içinde yalnızca 0 ve 255 vardır. Sanırım bu seyrek bir veri doğru mu? Sonra npz de kullandım ama boyut tamamen aynı.
York Yang

3
H5py neden eksik? Yoksa bir şey mi kaçırıyorum?
daniel451

49

Bir HDF5 bazlı klon şimdi vardır pickledenir 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 )

DÜZENLE:

Aşağıdakileri yaparak sıkıştırılmış bir arşive doğrudan "toplama" olasılığı da vardır:

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

sıkıştırma


apandis

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

Bazı ppl'lerin umursayabileceği bir uyarı, pickle'ın keyfi kod çalıştırabilmesidir, bu da onu veri kaydetmek için diğer protokollerden daha az güvenli hale getirir.
Charlie Parker

Bu harika! Ayrıca, lzma veya bz2 kullanarak sıkıştırılmış dosyaları doğrudan sıkıştırmak için okuma kodunu sağlayabilir misiniz?
Ernest S Kirubakaran

14

savez () verileri bir zip dosyasına kaydederseniz, dosyayı sıkıştırıp açmak biraz zaman alabilir. Kaydet () ve yükle () işlevini kullanabilirsiniz:

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

Birden çok diziyi tek bir dosyaya kaydetmek için önce dosyayı açmanız ve ardından dizileri sırayla kaydetmeniz veya yüklemeniz gerekir.


7

Uyuşmuş dizileri verimli bir şekilde depolamanın bir başka yolu da 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)

ve dizüstü bilgisayarım için çıktı (Core2 işlemcili nispeten eski bir MacBook Air):

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

bu, gerçekten hızlı depolayabileceği anlamına gelir, yani darboğaz tipik olarak disktir. Ancak burada sıkıştırma oranları oldukça iyi olduğundan, efektif hız sıkıştırma oranları ile çarpılır. Bu 76 MB dizilerin boyutları şunlardır:

$ 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

Bunu başarmak için Blosc kompresörünün kullanımının temel olduğunu lütfen unutmayın . Aynı komut dosyası, ancak 'clevel' = 0 kullanılıyor (yani sıkıştırmayı devre dışı bırakma):

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

disk performansı açıkça darboğazla karşılaşıyor.


2
Kimin ilgisini çekebilir: Bloscpack ve PyTables farklı projeler olsa da, birincisi yalnızca disk dökümüne odaklanıyor ve depolanmış dizileri dilimlemeye değil, her ikisini de test ettim ve saf "dosya dökümü projeleri" için Bloscpack, PyTables'dan neredeyse 6 kat daha hızlı.
Marcelo Sardelich

4

Yöntemi mmapçağırdığınızda, to kullandığınızda dizinin içeriğini belleğe yüklemediği için arama süresi yavaştır load. Veriler, belirli verilere ihtiyaç duyulduğunda tembel yüklenir. Ve bu sizin durumunuzda aramada olur. Ancak ikinci arama çok yavaş olmayacak.

Bu, mmapbüyük bir diziniz olduğunda tüm verileri belleğe yüklemenize gerek kalmaması için güzel bir özelliktir .

Kullanabileceğiniz joblib'i çözmek için, joblib.dumpiki veya daha fazlasını kullanarak istediğiniz herhangi bir nesneyi boşaltabilirsiniznumpy arrays , örneğe bakın

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

Kitaplık artık mevcut değil.
Andrea Moro
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.