cara terbaik untuk mempertahankan array numpy pada disk


124

Saya mencari cara cepat untuk mempertahankan array numpy yang besar. Saya ingin menyimpannya ke disk dalam format biner, lalu membacanya kembali ke memori dengan relatif cepat. Sayangnya, cPickle tidak cukup cepat.

Saya menemukan numpy.savez dan numpy.load . Namun yang aneh adalah, numpy.load memuat file npy ke dalam "memory-map". Itu berarti manipulasi array secara teratur sangat lambat. Misalnya, sesuatu seperti ini akan sangat lambat:

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

lebih tepatnya, baris pertama akan sangat cepat, tetapi baris lainnya yang menetapkan array objsangat lambat:

loading time =  0.000220775604248
assining time =  2.72940087318

Apakah ada cara yang lebih baik untuk mengawetkan array numpy? Idealnya, saya ingin bisa menyimpan banyak array dalam satu file.


3
Secara default, np.loadsebaiknya tidak mmap file.
Fred Foo

6
Bagaimana dengan pytables ?
dsign

@larsmans, terima kasih atas balasannya. tetapi mengapa waktu pencarian (z ['a'] dalam contoh kode saya) sangat lambat?
Vendetta

1
Alangkah baiknya jika kami memiliki lebih banyak informasi dalam pertanyaan Anda, seperti jenis array yang disimpan dalam ifile dan ukurannya, atau jika ada beberapa array dalam file yang berbeda, atau bagaimana tepatnya Anda menyimpannya. Dengan pertanyaan Anda, saya mendapat kesan bahwa baris pertama tidak melakukan apa-apa dan bahwa pemuatan sebenarnya terjadi setelahnya, tetapi itu hanya tebakan.
dsign

19
@larsmans - Untuk apa nilainya, untuk file "npz" (yaitu beberapa array disimpan dengan numpy.savez), defaultnya adalah "memuat dengan malas" array. Ini tidak memetakannya, tetapi tidak memuatnya sampai NpzFileobjek diindeks. (Jadi penundaan yang dimaksud OP.) Dokumentasi untuk loadmelewatkan ini, dan oleh karena itu sedikit menyesatkan ...
Joe Kington

Jawaban:


63

Saya penggemar berat hdf5 karena menyimpan array numpy yang besar. Ada dua opsi untuk menangani hdf5 dengan python:

http://www.pytables.org/

http://www.h5py.org/

Keduanya dirancang untuk bekerja dengan array numpy secara efisien.


35
maukah Anda memberikan beberapa kode contoh menggunakan paket ini untuk menyimpan array?
dbliss


1
Dari pengalaman saya, kinerja hdf5 sangat lambat dalam membaca dan menulis dengan penyimpanan chunk dan kompresi diaktifkan. Misalnya, saya memiliki dua array 2-D dengan bentuk (2500.000 * 2000) dengan ukuran potongan (10.000 * 2000). Satu operasi tulis larik dengan bentuk (2000 * 2000) akan membutuhkan waktu sekitar 1 ~ 2 detik untuk menyelesaikannya. Apakah Anda punya saran untuk meningkatkan kinerja? Terima kasih.
Simon. Li

206

Saya telah membandingkan kinerja (ruang dan waktu) untuk sejumlah cara menyimpan array numpy. Beberapa dari mereka mendukung banyak larik per file, tetapi mungkin itu berguna.

benchmark untuk penyimpanan array numpy

File npy dan binary keduanya sangat cepat dan kecil untuk data yang padat. Jika datanya jarang atau sangat terstruktur, Anda mungkin ingin menggunakan npz dengan kompresi, yang akan menghemat banyak ruang tetapi membutuhkan waktu muat.

Jika portabilitas menjadi masalah, biner lebih baik daripada npy. Jika keterbacaan manusia itu penting, maka Anda harus mengorbankan banyak kinerja, tetapi itu dapat dicapai dengan cukup baik menggunakan csv (yang juga sangat portabel tentunya).

Detail lebih lanjut dan kodenya tersedia di repo github .


2
Bisakah Anda menjelaskan mengapa binarylebih baik daripada npyportabilitas? Apakah ini juga berlaku npz?
daniel451

1
@ daniel451 Karena bahasa apa pun dapat membaca file biner jika mereka hanya mengetahui bentuk, tipe datanya, dan apakah itu berbasis baris atau kolom. Jika Anda hanya menggunakan Python maka npy baik-baik saja, mungkin sedikit lebih mudah daripada biner.
Tandai

1
Terima kasih! Satu pertanyaan lagi: apakah saya melewatkan sesuatu atau apakah Anda mengabaikan HDF5? Karena ini cukup umum, saya akan tertarik bagaimana perbandingannya dengan metode lain.
daniel451

1
Saya mencoba menggunakan png dan npy untuk menyimpan gambar yang sama. png hanya membutuhkan ruang 2K sedangkan npy membutuhkan 307K. Hasil ini sangat berbeda dengan pekerjaan Anda. Apakah saya melakukan sesuatu yang salah? Gambar ini adalah gambar skala abu-abu dan di dalamnya hanya ada 0 dan 255. Saya pikir ini adalah data yang jarang, benar? Lalu saya juga menggunakan npz tapi ukurannya sama sekali.
York Yang

3
Mengapa h5py hilang? Atau apakah saya melewatkan sesuatu?
daniel451

49

Sekarang ada tiruan berbasis HDF5 yang pickledisebut 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:

Ada juga kemungkinan untuk "mengawetkan" langsung ke dalam arsip terkompresi dengan melakukan:

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

kompresi


Lampiran

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

satu peringatan yang mungkin diperhatikan oleh beberapa ppl adalah bahwa acar dapat mengeksekusi kode arbitrer yang membuatnya kurang aman dibandingkan protokol lain untuk menyimpan data.
Charlie Parker

Ini bagus! Dapatkah Anda juga memberikan kode untuk membaca file yang diawetkan langsung ke kompresi menggunakan lzma atau bz2?
Ernest S Kirubakaran

14

savez () menyimpan data dalam file zip, Mungkin perlu beberapa saat untuk membuat zip & mengekstrak file. Anda dapat menggunakan fungsi 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()

Untuk menyimpan beberapa array dalam satu file, Anda hanya perlu membuka file terlebih dahulu, lalu menyimpan atau memuat array secara berurutan.


7

Kemungkinan lain untuk menyimpan array numpy secara efisien adalah 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)

dan output untuk laptop saya (MacBook Air yang relatif lama dengan prosesor Core2):

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

itu berarti dapat menyimpan dengan sangat cepat, yaitu kemacetan biasanya adalah disk. Namun, karena rasio kompresinya cukup bagus di sini, kecepatan efektif dikalikan dengan rasio kompresi. Berikut adalah ukuran untuk array 76 MB ini:

$ 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

Harap dicatat bahwa penggunaan kompresor Blosc sangat penting untuk mencapai hal ini. Skrip yang sama tetapi menggunakan 'clevel' = 0 (yaitu menonaktifkan kompresi):

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

jelas terhambat oleh kinerja disk.


2
Kepada pihak yang berkepentingan: Meskipun Bloscpack dan PyTables adalah proyek yang berbeda, yang pertama hanya berfokus pada disk dump dan tidak disimpan mengiris array, saya menguji keduanya dan untuk "proyek dump file" murni Bloscpack hampir 6x lebih cepat daripada PyTable.
Marcelo Sardelich

4

Waktu pencarian lambat karena saat Anda menggunakan mmapuntuk tidak memuat konten larik ke memori saat Anda memanggilload metode. Data dimuat lambat saat data tertentu diperlukan. Dan ini terjadi dalam pencarian dalam kasus Anda. Tapi pencarian kedua tidak akan terlalu lambat.

Ini adalah fitur yang bagus dari mmap ketika Anda memiliki array yang besar Anda tidak perlu memuat seluruh data ke dalam memori.

Untuk menyelesaikannya, Anda dapat menggunakan joblib, Anda dapat membuang objek apa pun yang Anda inginkan menggunakan joblib.dumpdua atau lebih numpy arrays, lihat contoh

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

Perpustakaan tidak lagi tersedia.
Andrea Moro
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.