153

我正在寻找一种快速保存大型 numpy 数组的方法。我想以二进制格式将它们保存到磁盘,然后相对快速地将它们读回内存。不幸的是,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 数组?理想情况下,我希望能够在一个文件中存储多个数组。

4

7 回答 7

260

我比较了存储 numpy 数组的多种方式的性能(空间和时间)。他们中很少有人支持每个文件多个数组,但无论如何它也许很有用。

numpy 数组存储的基准

对于密集数据,Npy 和二进制文件都非常快而且很小。如果数据稀疏或非常结构化,您可能希望使用带有压缩功能的 npz,这将节省大量空间但会花费一些加载时间。

如果可移植性是一个问题,二进制比 npy 更好。如果人类可读性很重要,那么您将不得不牺牲很多性能,但使用 csv 可以很好地实现(当然这也是非常便携的)。

更多详细信息和代码可在github repo中找到。

于 2017-01-02T11:21:27.957 回答
69

我是 hdf5 的忠实粉丝,用于存储大型 numpy 数组。在 python 中处理 hdf5 有两种选择:

http://www.pytables.org/

http://www.h5py.org/

两者都旨在有效地使用 numpy 数组。

于 2012-03-08T15:02:40.573 回答
52

现在有一个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']
modules = dict(
    pickle=pickle, h5py=h5py, gzip=gzip, lzma=lzma, bz2=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:
        path = 'data.pkl.{}'.format(compression)

        if compression == 'pickle':
            time_start = time.time()
            pickle.dump(data[key], open(path, 'wb'))
            time_tot = time.time() - time_start
            sizes[key]['pickle'] = (
                os.path.getsize(path) * 10**-6, 
                time_tot.
            )
            os.remove(path)

        elif compression == 'h5py':
            time_start = time.time()
            with h5py.File(path, 'w') as h5f:
                h5f.create_dataset('data', data=data[key])
            time_tot = time.time() - time_start
            sizes[key][compression] = (os.path.getsize(path) * 10**-6, time_tot)
            os.remove(path)

        else:
            time_start = time.time()
            with modules[compression].open(path, 'wb') as fout:
                pickle.dump(data[key], fout)
            time_tot = time.time() - time_start
            sizes[key][labels[compressions.index(compression)]] = (
                os.path.getsize(path) * 10**-6, 
                time_tot,
            )
            os.remove(path)


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')
于 2014-03-05T13:10:04.253 回答
15

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

要将多个数组保存在一个文件中,只需先打开文件,然后依次保存或加载数组即可。

于 2012-03-09T06:45:38.963 回答
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)

显然是磁盘性能的瓶颈。

于 2014-03-06T13:01:21.633 回答
5

查找时间很慢,因为当您使用to 时,调用方法mmap时不会将数组的内容加载到内存中。load当需要特定数据时,数据是延迟加载的。在您的情况下,这发生在查找中。但是第二次查找不会那么慢。

这是一个很好的功能,mmap当您拥有一个大数组时,您不必将整个数据加载到内存中。

为了解决您可以使用joblibjoblib.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')
于 2014-03-27T11:25:52.173 回答
0

“最佳”取决于您的目标是什么。正如其他人所说,二进制文件具有最大的可移植性,但问题是您需要了解数据的存储方式。

Darr以基于平面二进制和文本文件的自记录方式保存您的 numpy 数组。这最大限度地提高了广泛的可读性。它还自动包含有关如何以各种数据科学语言(例如 numpy 本身,以及 R、Matlab、Julia 等)读取数组的代码。

披露:我写了图书馆。

于 2021-07-19T14:10:08.293 回答