лучший способ сохранить массивы numpy на диске

Я ищу быстрый способ сохранить большие массивы numpy. Я хочу сохранить их на диск в двоичном формате, а затем относительно быстро прочитать их обратно в память. К сожалению, 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

Есть ли лучший способ сохранить массивы numpy? В идеале я хочу иметь возможность хранить несколько массивов в одном файле.


person Vendetta    schedule 08.03.2012    source источник
comment
По умолчанию np.load не должен не отображать файл.   -  person Fred Foo    schedule 08.03.2012
comment
А как насчет pytables?   -  person dsign    schedule 08.03.2012
comment
@larsmans, спасибо за ответ. но почему время поиска (z ['a'] в моем примере кода) такое медленное?   -  person Vendetta    schedule 08.03.2012
comment
@dsign Спасибо за ответ. Я обдумываю это ... но прежде чем я перейду к добавлению дополнительных сторонних библиотек, я хотел сначала найти решение numpy ...   -  person Vendetta    schedule 08.03.2012
comment
Было бы неплохо, если бы в вашем вопросе было немного больше информации, например, тип массива, который хранится в ifile, и его размер, или если это несколько массивов в разных файлах, или как именно вы их сохраняете. По вашему вопросу у меня сложилось впечатление, что первая строка ничего не делает и что фактическая загрузка происходит после, но это только предположения.   -  person dsign    schedule 08.03.2012
comment
@larsmans - Как бы то ни было, для файла npz (т.е. нескольких массивов, сохраненных с numpy.savez) по умолчанию массивы загружаются лениво. Он не отображает их в памяти, но и не загружает их, пока объект NpzFile не будет проиндексирован. (Таким образом, задержка, на которую ссылается OP.) Документация для load пропускает это, и поэтому вводит в заблуждение ...   -  person Joe Kington    schedule 08.03.2012
comment
@JoeKington Спасибо, Джо. Но как мне не лениво загрузить файл npz?   -  person Vendetta    schedule 08.03.2012
comment
Если pickle был медленным, может быть, вы не установили флаг протокола? pickle.dump(obj, file, -1) Без флага протокола pickle будет использовать медленный формат ASCII. Вот документация: pickle.dump   -  person klaus se    schedule 03.09.2013
comment
@codenoob, не могли бы вы высказать свое мнение? что помогло вам принять решение? У меня есть аналогичный вопрос, и нам было бы интересно узнать, какие решения являются лучшими и почему. Заранее спасибо!   -  person Basj    schedule 11.03.2014
comment
Я получил loading time = 0.00024962425231933594 assigning time = 0.3003871440887451 с python 3 и numpy 1.13. Я сомневаюсь, что время ленивой загрузки можно значительно сократить с помощью других пакетов, и, поскольку я не слишком озабочен сжатием, я вполне доволен numpy.savez.   -  person Yibo Yang    schedule 04.10.2017
comment
Одно предупреждение, которое может волновать некоторых ppl, заключается в том, что pickle может выполнять произвольный код, что делает его менее безопасным, чем другие протоколы для сохранения данных.   -  person Charlie Parker    schedule 13.07.2020


Ответы (7)


Я большой поклонник hdf5 для хранения больших массивов numpy. Есть два варианта работы с hdf5 в python:

http://www.pytables.org/

http://www.h5py.org/

Оба предназначены для эффективной работы с массивами numpy.

person JoshAdel    schedule 08.03.2012

Я сравнил производительность (пространство и время) для нескольких способов хранения массивов numpy. Некоторые из них поддерживают несколько массивов для каждого файла, но, возможно, это все равно полезно.

Тест для массива numpy  хранилище

Npy и двоичные файлы действительно быстрые и маленькие для плотных данных. Если данные разреженные или очень структурированные, вы можете использовать npz со сжатием, что сэкономит много места, но потребует некоторого времени загрузки.

Если переносимость является проблемой, двоичный код лучше, чем npy. Если удобочитаемость для человека важна, вам придется пожертвовать большой производительностью, но этого можно довольно хорошо добиться с помощью csv (который, конечно, также очень переносим).

Дополнительные сведения и код доступны в репозитории github.

person Mark    schedule 02.01.2017
comment
Не могли бы вы объяснить, почему binary лучше, чем npy с точки зрения переносимости? Это также относится к npz? - person daniel451; 01.06.2017
comment
@ daniel451 Потому что любой язык может читать двоичные файлы, если он просто знает форму, тип данных и то, на основе ли он строк или столбцов. Если вы просто используете Python, тогда npy в порядке, возможно, немного проще, чем двоичный. - person Mark; 02.06.2017
comment
Спасибо! Еще один вопрос: я что-то упускаю или вы не учли HDF5? Поскольку это довольно распространено, мне было бы интересно, как он сравнивается с другими методами. - person daniel451; 03.06.2017
comment
Я пробовал использовать png и npy, чтобы сохранить одно и то же изображение. png занимает всего 2 КБ, а npy занимает 307 КБ. Этот результат действительно отличается от вашего. Я делаю что-то неправильно? Это изображение в оттенках серого, внутри которого находятся только 0 и 255. Думаю, это скудные данные, правильно? Затем я также использовал npz, но размер тот же. - person York Yang; 06.08.2017
comment
@YorkYang Эта ситуация отличается от теста, где я сохранил данные float64 как npy и png, а не изображение (вероятно, данные 3D uint8?). Возможно, что-то не так с типом данных, и, возможно, png имеет лучшее сжатие для этого типа вещей. Но я сомневаюсь, что это полностью объясняет разницу в 154 раза. Лично я бы просто использовал png; для реальных изображений я думаю, что его вряд ли можно значительно улучшить (кроме хранилища с потерями). - person Mark; 07.08.2017
comment
Почему отсутствует h5py? Или я что-то упускаю? - person daniel451; 26.02.2018
comment
Неофициальные результаты hdf5 vs npy. Код нарушил несколько пунктов для других форматов. Кроме того, я jt_dump жаловался в нескольких местах - это было закомментировано. Вы можете найти результаты на этой вилке: github.com/epignatelli/array_storage_benchmark - person Eduardo Pignatelli; 29.10.2019
comment
ваш ответ очень хорош. Чтобы сделать его лучше и иметь все возможные плюсы и минусы, один недостаток, который может волновать некоторых ppl, заключается в том, что pickle может выполнять произвольный код, что делает его менее безопасным, чем другие протоколы для сохранения данных. - person Charlie Parker; 13.07.2020
comment
Что такое FortUnf? - person argentum2f; 23.12.2020
comment
@ argentum2f Неформатированный Фортран, двоичный формат, используемый Фортраном с десятилетиями истории, но я думаю, что сегодня не так много пользователей. Подробности смотрите в репо. - person Mark; 27.12.2020
comment
@YorkYang, PNG поддерживает до 16 бит, поэтому я подозреваю, что PNG преобразует тип данных из numpy, в то время как NPY оставляет формат неизменным. - person David Diaz; 23.01.2021
comment
????Это должно быть ответом! - person yoursbh; 02.03.2021
comment
Проведите тест. Спасибо, что сделали доступным исходный код! На моей машине и с Python 3.8 различия между pickle, binary и numpy теперь незначительны. Возможно, вы захотите обновить свой ответ, чтобы отразить текущее состояние, учитывая, что ваш ответ очень популярен и теперь может ввести читателей в заблуждение. - person ARF; 08.03.2021

Теперь существует клон 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')
person Suuuehgi    schedule 05.03.2014
comment
Одно предупреждение, которое может волновать некоторых ppl, заключается в том, что pickle может выполнять произвольный код, что делает его менее безопасным, чем другие протоколы для сохранения данных. - person Charlie Parker; 13.07.2020
comment
Отлично! Можете ли вы также предоставить код для чтения файлов, подвергнутых сжатию непосредственно с использованием lzma или bz2? - person Ernest S Kirubakaran; 30.07.2020
comment
@ErnestSKirubakaran В основном то же самое: если вы сохранили его с помощью pickle.dump( obj, gzip.open( 'filename.pkl.gz', 'wb' ) ), вы можете загрузить его с помощью pickle.load( gzip.open( 'filename.pkl.gz', 'r' ) ) - person Suuuehgi; 23.07.2021

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

Чтобы сохранить несколько массивов в одном файле, вам просто нужно сначала открыть файл, а затем последовательно сохранить или загрузить массивы.

person HYRY    schedule 09.03.2012

Еще одна возможность эффективно хранить массивы 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)

и вывод для моего ноутбука (относительно старый MacBook Air с процессором 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

Обратите внимание, что использование компрессора Blosc является основополагающим для достижения этой цели. Тот же сценарий, но с параметром clevel = 0 (т.е. отключение сжатия):

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

явно узким местом из-за производительности диска.

person Francesc    schedule 06.03.2014
comment
Кого это может касаться: хотя Bloscpack и PyTables - это разные проекты, первый из которых фокусируется только на дампе диска, а не на нарезке сохраненных массивов, я тестировал оба проекта и для проектов чистого дампа файлов. Bloscpack почти в 6 раз быстрее, чем PyTables. - person Marcelo Sardelich; 23.03.2015

Время поиска медленное, потому что когда вы используете 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')
person Michal    schedule 27.03.2014
comment
Библиотека больше не доступна. - person Andrea Moro; 25.03.2020

«Лучшее» зависит от вашей цели. Как говорили другие, двоичный файл максимально переносим, ​​но проблема в том, что вам нужно знать, как хранятся данные.

Darr сохраняет ваш массив numpy самодокументированным способом на основе плоских двоичных и текстовых файлов. Это обеспечивает максимальную читаемость. Он также автоматически включает код о том, как читать ваш массив на различных языках науки о данных, таких как сам numpy, а также R, Matlab, Julia и т. Д.

Раскрытие информации: я написал библиотеку.

person Gabriel    schedule 19.07.2021