디스크에 numpy 배열을 보존하는 가장 좋은 방법


124

큰 numpy 배열을 보존하는 빠른 방법을 찾고 있습니다. 바이너리 형식으로 디스크에 저장 한 다음 비교적 빠르게 메모리에 다시 읽어 들이고 싶습니다. cPickle은 불행히도 충분히 빠르지 않습니다.

numpy.saveznumpy.load를 찾았 습니다 . 하지만 이상한 점은 numpy.load가 npy 파일을 "memory-map"에로드한다는 것입니다. 이것은 배열의 규칙적인 조작이 정말 느리다는 것을 의미합니다. 예를 들어, 다음과 같은 것은 정말 느립니다.

#!/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 배열을 보존하는 더 좋은 방법이 있습니까? 이상적으로는 하나의 파일에 여러 배열을 저장할 수 있기를 원합니다.


3
기본적으로 np.load해야 하지 파일을 메모리 매핑.
Fred Foo 2012 년

6
pytables어떻 습니까?
dsign

@larsmans, 답장 주셔서 감사합니다. 하지만 조회 시간 (코드 예제에서 z [ 'a'])이 왜 그렇게 느린가요?
Vendetta 2012 년

1
ifile에 저장된 배열의 종류와 크기, 다른 파일에 여러 배열이 있는지 또는 정확히 저장하는 방법과 같은 질문에 약간 더 많은 정보가 있으면 좋을 것입니다. 귀하의 질문에 따르면 첫 번째 줄은 아무것도하지 않고 실제 로딩이 나중에 발생한다는 인상을 받았지만 추측 일뿐입니다.
dsign

19
@larsmans- "npz"파일 (예 :로 저장된 여러 배열 numpy.savez)의 경우 기본값은 배열을 "지연로드"하는 것입니다. 그것들을 memmapping하지는 않지만, NpzFile객체가 인덱싱 될 때까지로드하지 않습니다 . 에 대한 설명서를 (. 따라서 지연 영업 이익은 참조한다) load이 건너 뛰고, 따라서 오해의 소지가 터치는 ...
조 킹톤

답변:


63

나는 대용량 배열을 저장하는 hdf5의 열렬한 팬입니다. 파이썬에서 hdf5를 처리하는 데는 두 가지 옵션이 있습니다.

http://www.pytables.org/

http://www.h5py.org/

둘 다 numpy 배열과 효율적으로 작동하도록 설계되었습니다.


35
이 패키지를 사용하여 배열을 저장하는 몇 가지 예제 코드를 제공 하시겠습니까?
dbliss


1
내 경험으로 볼 때 hdf5는 청크 저장 및 압축을 활성화하여 읽기 및 쓰기 속도가 매우 느립니다. 예를 들어, 청크 크기 (10,000 * 2000)의 모양 (2500,000 * 2000)을 가진 두 개의 2D 배열이 있습니다. 모양 (2000 * 2000)을 가진 배열의 단일 쓰기 작업은 완료하는 데 약 1 ~ 2 초가 걸립니다. 성능 향상에 대한 제안이 있습니까? 고마워.
사이먼. Li

206

numpy 배열을 저장하는 여러 방법에 대해 성능 (공간 및 시간)을 비교했습니다. 그들 중 일부는 파일 당 여러 배열을 지원하지만 어쨌든 유용 할 수도 있습니다.

numpy 어레이 스토리지에 대한 벤치 마크

Npy 및 바이너리 파일은 고밀도 데이터의 경우 정말 빠르고 작습니다. 데이터가 희박하거나 매우 구조화 된 경우 npz를 압축과 함께 사용하면 많은 공간이 절약되지만로드 시간이 약간 소요됩니다.

이식성이 문제라면 바이너리가 npy보다 낫습니다. 사람의 가독성이 중요하다면 많은 성능을 희생해야하지만 csv (물론 이식성이 매우 뛰어남)를 사용하면 상당히 잘 달성 할 수 있습니다.

자세한 내용과 코드는 github repo 에서 확인할 수 있습니다 .


2
이식성 binary보다 나은 이유를 설명해 주 npy시겠습니까? 이것도 적용 npz됩니까?
daniel451

1
@ daniel451 모양, 데이터 유형 및 행 기반인지 열 기반인지 알면 모든 언어가 이진 파일을 읽을 수 있기 때문입니다. Python을 사용하는 경우 npy는 괜찮습니다. 아마도 바이너리보다 조금 더 쉬울 것입니다.
Mark

1
감사합니다! 한 가지 더 질문 : 내가 간과하거나 HDF5를 생략 했습니까? 이것은 매우 일반적이기 때문에 다른 방법과 비교하는 방법에 관심이 있습니다.
daniel451

1
같은 이미지를 저장하기 위해 png와 npy를 사용하려고했습니다. png는 2K 공간 만 차지하고 npy는 307K를 차지합니다. 이 결과는 당신의 작업과 정말 다릅니다. 내가 뭘 잘못하고 있니? 이 이미지는 회색조 이미지이며 내부에는 0과 255 만 있습니다. 나는 이것이 희소 데이터라고 생각합니까? 그런 다음 npz도 사용했지만 크기는 완전히 동일합니다.
뉴욕 양

3
h5py가 누락 된 이유는 무엇입니까? 아니면 내가 뭔가를 놓치고 있습니까?
daniel451

49

의 HDF5 기반 클론 지금이 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이 관심을 가질 수있는 한 가지 경고는 pickle이 데이터 저장을위한 다른 프로토콜보다 덜 안전한 임의의 코드를 실행할 수 있다는 것입니다.
Charlie Parker

이것은 대단합니다! lzma 또는 bz2를 사용하여 압축으로 직접 피클 된 파일을 읽는 코드도 제공 할 수 있습니까?
Ernest S Kirubakaran

14

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

여러 배열을 하나의 파일에 저장하려면 먼저 파일을 연 다음 배열을 순서대로 저장하거나로드하면됩니다.


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)

즉, 매우 빠르게 저장할 수 있습니다. 즉, 병목 현상은 일반적으로 디스크입니다. 그러나 여기서 압축 비율이 꽤 좋기 때문에 유효 속도에 압축 비율을 곱합니다. 이러한 76MB 어레이의 크기는 다음과 같습니다.

$ 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

사용 mmap하면 load메서드 를 호출 할 때 배열의 내용을 메모리에로드하지 않기 때문에 조회 시간이 느립니다 . 특정 데이터가 필요할 때 데이터가 지연로드됩니다. 그리고 이것은 귀하의 경우 조회에서 발생합니다. 그러나 두 번째 조회는 그렇게 느리지 않습니다.

이것은 mmap큰 배열을 가질 때 전체 데이터를 메모리에로드 할 필요가없는 좋은 기능입니다 .

joblib 사용을 해결하려면 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')

라이브러리를 더 이상 사용할 수 없습니다.
Andrea Moro
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.