डिस्क पर सुन्न सरणियों को संरक्षित करने का सबसे अच्छा तरीका


124

मैं बड़ी खस्ता सरणियों को संरक्षित करने का एक तेज़ तरीका ढूंढ रहा हूं। मैं उन्हें बाइनरी फॉर्मेट में डिस्क पर सहेजना चाहता हूं, फिर उन्हें मेमोरी में अपेक्षाकृत तेजी से पढ़ता हूं। 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

क्या सुन्न सरणियों को संरक्षित करने का कोई बेहतर तरीका है? आदर्श रूप से, मैं एक फ़ाइल में कई सरणियों को संग्रहीत करने में सक्षम होना चाहता हूं।


3
डिफ़ॉल्ट रूप से, फ़ाइल np.loadको छोटा नहीं करना चाहिए ।
फ्रेड फू

6
क्या pytables के बारे में ?
dsign

@larsmans, उत्तर के लिए धन्यवाद। लेकिन मेरे कोड उदाहरण में लुकअप टाइम (z ['a'] क्यों इतना धीमा है?
वेंडेट्टा

1
यह अच्छा होगा यदि हम आपके प्रश्न में थोड़ी और जानकारी रखते हैं, जैसे कि इरेले और उसके आकार में संग्रहीत सरणी, या यदि वे अलग-अलग फ़ाइलों में कई सरणियाँ हैं, या आप वास्तव में उन्हें कैसे बचाते हैं। आपके प्रश्न से, मुझे यह आभास हो गया है कि पहली पंक्ति कुछ नहीं करती है और वास्तविक लोडिंग बाद में होती है, लेकिन वे केवल अनुमान हैं।
dsign

19
@larsmans - एक "npz" फ़ाइल (यानी कई सरणियों के साथ सहेजा गया numpy.savez) के लिए इसके लायक क्या है , डिफ़ॉल्ट सरणियों को "आलसी लोड" करना है। यह उन्हें याद नहीं कर रहा है, लेकिन यह उन्हें तब तक लोड नहीं करता है जब तक कि NpzFileवस्तु अनुक्रमित न हो। (इस प्रकार ओपी का जिक्र करने में देरी हो रही है।) इस के लिए प्रलेखन load, और इसलिए यह एक भ्रामक स्पर्श है ...
जो किंगटन

जवाबों:


63

मैं बड़ी सुन्न सरणियों के भंडारण के लिए hdf5 का बहुत बड़ा प्रशंसक हूं। अजगर में hdf5 से निपटने के लिए दो विकल्प हैं:

http://www.pytables.org/

http://www.h5py.org/

दोनों को कुशलतापूर्वक सुन्न सरणियों के साथ काम करने के लिए डिज़ाइन किया गया है।


35
क्या आप सरणी को बचाने के लिए इन पैकेजों का उपयोग करके कुछ उदाहरण कोड प्रदान करने के लिए तैयार हैं?
dbliss


1
मेरे अनुभवों से, hdf5 ने बहुत ही धीमी गति से पढ़ने और लिखने के साथ चंक स्टोरेज और कम्प्रेशन सक्षम किया। उदाहरण के लिए, मैंने दो 2-डी सरणियाँ आकार (2500,000 * 2000) के साथ चंक आकार (10,000 * 2000) के साथ की हैं। आकृति (2000 * 2000) के साथ एक सरणी का एकल लेखन ऑपरेशन पूरा होने में लगभग 1 ~ 2s लगेगा। क्या आपके पास प्रदर्शन में सुधार के लिए कोई सुझाव है? धन्यवाद।
साइमन ली

206

मैंने कई तरीकों के लिए प्रदर्शन (स्थान और समय) की तुलना की है, जिसमें खस्ता सरणियों को संग्रहीत किया गया है। उनमें से कुछ प्रति फ़ाइल कई सरणियों का समर्थन करते हैं, लेकिन शायद यह वैसे भी उपयोगी है।

मानक सरणी भंडारण के लिए बेंचमार्क

घने डेटा के लिए Npy और बाइनरी फाइलें वास्तव में तेज़ और छोटी दोनों हैं। यदि डेटा विरल है या बहुत संरचित है, तो आप सम्पीडन के साथ npz का उपयोग करना चाह सकते हैं, जो बहुत अधिक स्थान बचाएगा लेकिन कुछ लोड समय खर्च करेगा।

यदि पोर्टेबिलिटी एक मुद्दा है, तो बाइनरी npy से बेहतर है। यदि मानव पठनीयता महत्वपूर्ण है, तो आपको बहुत सारे प्रदर्शन का त्याग करना होगा, लेकिन सीएसवी (जो निश्चित रूप से बहुत पोर्टेबल है) का उपयोग करके इसे काफी अच्छी तरह से प्राप्त किया जा सकता है।

अधिक विवरण और कोड जीथब रेपो में उपलब्ध हैं ।


2
क्या आप बता सकते हैं कि पोर्टेबिलिटी के लिए binaryबेहतर क्यों है npy? क्या यह भी लागू होता है npz?
daniel451

1
@ daniel451 क्योंकि कोई भी भाषा बाइनरी फ़ाइलों को पढ़ सकती है, यदि वे केवल आकृति, डेटा प्रकार और चाहे वह पंक्ति या स्तंभ आधारित हो, जान सकते हैं। यदि आप केवल पायथन का उपयोग कर रहे हैं तो npy ठीक है, शायद बाइनरी की तुलना में थोड़ा आसान है।
मार्क

1
धन्यवाद! एक और सवाल: क्या मैं कुछ अनदेखी करता हूं या आपने एचडीएफ 5 को छोड़ दिया है? चूंकि यह बहुत आम है, मुझे दिलचस्पी होगी कि यह अन्य तरीकों से कैसे तुलना करता है।
daniel451

1
मैंने एक ही छवि को बचाने के लिए png और npy का उपयोग करने की कोशिश की। png केवल 2K स्थान लेता है जबकि npy 307K लेता है। यह परिणाम वास्तव में आपके काम से अलग है। क्या मुझसे कुछ गलत हो रही है? यह छवि एक ग्रेस्केल छवि है और केवल 0 और 255 अंदर हैं। मुझे लगता है कि यह एक विरल डेटा सही है? फिर मैंने भी npz का इस्तेमाल किया लेकिन आकार बिलकुल एक जैसा है।
यॉर्क यांग

3
H5py क्यों गायब है? या क्या मैं कुछ न कुछ भूल रहा हूं?
daniel451

49

अब एक एचडीएफ 5 आधारित क्लोन 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 )

संपादित करें:

वहाँ भी करने के लिए सीधे एक संभावित संग्रह में "अचार" की संभावना है:

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 के बारे में परवाह कर सकती है वह यह है कि अचार मनमाने कोड को निष्पादित कर सकता है जो डेटा को बचाने के लिए अन्य प्रोटोकॉल की तुलना में कम सुरक्षित बनाता है।
चार्ली पार्कर

यह भी खूब रही! क्या आप lzma या bz2 का उपयोग करके सीधे संपीड़ित फ़ाइलों को पढ़ने के लिए कोड प्रदान कर सकते हैं?
अर्नेस्ट एस किरुबाकरन

14

savez () डेटा को जिप फाइल में सेव करें, फाइल को जिप और अनजिप करने में कुछ समय लग सकता है। आप save () और लोड () फ़ंक्शन का उपयोग कर सकते हैं:

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

एक फ़ाइल में कई सरणियों को सहेजने के लिए, आपको बस पहले फ़ाइल को खोलने की आवश्यकता है, और फिर सरणियों को अनुक्रम में सहेजें या लोड करें।


7

खस्ता सरणियों को कुशलता से संग्रहित करने की एक और संभावना है ब्लास्कपैक :

#!/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 प्रोसेसर के साथ एक अपेक्षाकृत पुरानी मैकबुक एयर):

$ 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

कृपया ध्यान दें कि इसे प्राप्त करने के लिए ब्लॉस्क कंप्रेसर का उपयोग मौलिक है। एक ही स्क्रिप्ट, लेकिन 'क्लीवल' = 0 (यानी कंप्रेशन को अक्षम करना) का उपयोग करना:

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

डिस्क प्रदर्शन द्वारा स्पष्ट रूप से अड़चन है।


2
किसके लिए यह चिंता का विषय हो सकता है: हालांकि ब्लोस्कोप और पायटेबल्स अलग-अलग परियोजनाएं हैं, पूर्व में केवल डिस्क डंप पर ध्यान केंद्रित किया गया है और संग्रहीत सरणियों को स्लाइस नहीं किया गया है, मैंने दोनों का परीक्षण किया और शुद्ध "फ़ाइल डंप प्रोजेक्ट" के लिए ब्लोस्कोप PyTables की तुलना में लगभग 6x तेज है।
मार्सेलो सर्देलिच

4

लुकअप समय धीमा है क्योंकि जब आप विधि mmapका loadउपयोग करते हैं तो मेमोरी की सामग्री लोड नहीं होती है जब आप विधि लागू करते हैं। जब विशेष डेटा की आवश्यकता होती है तो डेटा आलसी लोड होता है। और यह आपके मामले में देखने में होता है। लेकिन दूसरा लुकअप इतना धीमा होना चाहिए।

यह एक अच्छी विशेषता है mmapजब आपके पास एक बड़ा सरणी है, जिसमें आपको पूरे डेटा को मेमोरी में लोड करने की आवश्यकता नहीं है।

जॉबलीब का उपयोग करने के लिए अपने समाधान के लिए आप 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')

लाइब्रेरी अब उपलब्ध नहीं है।
एंड्रिया मोरो
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.