Как я могу сериализовать массив numpy при сохранении размеров матрицы?

numpy.array.tostring, похоже, не сохраняет информацию о размерах матрицы (см. этот вопрос), требуя от пользователя вызывать вызов numpy.array.reshape.

Есть ли способ сериализации массива numpy в формате JSON при сохранении этой информации?

Примечание. Массивы могут содержать int, floats или bools. Разумно ожидать транспонированный массив.

Примечание 2: это выполняется с целью передачи массива numpy через топологию Storm с использованием streamparse, в случае, если такая информация окажется актуальной.

Ответы

Ответ 1

pickle.dumps или numpy.save закодировать все информация, необходимая для восстановления произвольного массива NumPy, даже при наличии проблем с контентом, несмежных массивов или странных кортежей. Проблемы с предельной вероятностью, вероятно, наиболее важны; вы не хотите, чтобы array([1]) внезапно становился array([16777216]), потому что вы загрузили свой массив на машине большого конца. pickle, вероятно, является более удобным вариантом, хотя save имеет свои преимущества, приведенные в npy аргументах формата.

Параметр pickle:

import pickle
a = # some NumPy array
serialized = pickle.dumps(a, protocol=0) # protocol 0 is printable ASCII
deserialized_a = pickle.loads(serialized)

numpy.save использует двоичный формат, и его нужно записать в файл, но вы можете обойти это с помощью StringIO:

a = # any NumPy array
memfile = StringIO.StringIO()
numpy.save(memfile, a)
memfile.seek(0)
serialized = json.dumps(memfile.read().decode('latin-1'))
# latin-1 maps byte n to unicode code point n

И для десериализации:

memfile = StringIO.StringIO()
memfile.write(json.loads(serialized).encode('latin-1'))
memfile.seek(0)
a = numpy.load(memfile)

Ответ 2

РЕДАКТИРОВАТЬ:. Как можно прочитать в комментариях к вопросу, это решение имеет дело с "нормальными" массивами numpy (float, ints, bools...), а не с многотипными структурированными массивами.

Решение для сериализации массива numpy любых размеров и типов данных

Насколько я знаю, вы не можете просто сериализовать массив numpy с любым типом данных и любым измерением... но вы можете сохранить его тип данных, размер и информацию в представлении списка, а затем сериализовать его с помощью JSON.

Необходимый импорт:

import json
import base64

Для кодирования вы можете использовать (nparray - это некоторый массив numpy любого типа данных и любая размерность):

json.dumps([str(nparray.dtype), base64.b64encode(nparray), nparray.shape])

После этого вы получаете дамп JSON (строку) ваших данных, содержащий представление списка его типа и формы данных, а также данные массива данных/содержимого base64.

И для декодирования это делает работу (encStr - закодированная строка JSON, загруженная откуда-то):

# get the encoded json dump
enc = json.loads(encStr)

# build the numpy data type
dataType = numpy.dtype(enc[0])

# decode the base64 encoded numpy array data and create a new numpy array with this data & type
dataArray = numpy.frombuffer(base64.decodestring(enc[1]), dataType)

# if the array had more than one data set it has to be reshaped
if len(enc) > 2:
     dataArray.reshape(enc[2])   # return the reshaped numpy array containing several data sets

Дампы JSON эффективны и кросс-совместимы по многим причинам, но просто использование JSON приводит к неожиданным результатам, если вы хотите хранить и загружать массивы numpy любого типа и любого измерения.

Это решение хранит и загружает массивы numpy независимо от типа или размера, а также восстанавливает его правильно (тип данных, размерность...)

Я попробовал несколько решений самостоятельно несколько месяцев назад, и это было единственное эффективное, универсальное решение, с которым я столкнулся.

Ответ 3

Я нашел код в Msgpack-numpy полезным. https://github.com/lebedov/msgpack-numpy/blob/master/msgpack_numpy.py

Я немного изменил сериализованный диктофон и добавил кодировку base64, чтобы уменьшить размер сериализованного размера.

Используя тот же интерфейс, что и json (обеспечивая нагрузку, дамп (ы)), вы можете предоставить замену для json-сериализации.

Эта же логика может быть расширена, чтобы добавить любую автоматическую нетривиальную сериализацию, такую ​​как объекты datetime.


ИЗМЕНИТЬ Я написал общий, модульный, парсер, который делает это и многое другое. https://github.com/someones/jaweson


Мой код выглядит следующим образом:

np_json.py

from json import *
import json
import numpy as np
import base64

def to_json(obj):
    if isinstance(obj, (np.ndarray, np.generic)):
        if isinstance(obj, np.ndarray):
            return {
                '__ndarray__': base64.b64encode(obj.tostring()),
                'dtype': obj.dtype.str,
                'shape': obj.shape,
            }
        elif isinstance(obj, (np.bool_, np.number)):
            return {
                '__npgeneric__': base64.b64encode(obj.tostring()),
                'dtype': obj.dtype.str,
            }
    if isinstance(obj, set):
        return {'__set__': list(obj)}
    if isinstance(obj, tuple):
        return {'__tuple__': list(obj)}
    if isinstance(obj, complex):
        return {'__complex__': obj.__repr__()}

    # Let the base class default method raise the TypeError
    raise TypeError('Unable to serialise object of type {}'.format(type(obj)))


def from_json(obj):
    # check for numpy
    if isinstance(obj, dict):
        if '__ndarray__' in obj:
            return np.fromstring(
                base64.b64decode(obj['__ndarray__']),
                dtype=np.dtype(obj['dtype'])
            ).reshape(obj['shape'])
        if '__npgeneric__' in obj:
            return np.fromstring(
                base64.b64decode(obj['__npgeneric__']),
                dtype=np.dtype(obj['dtype'])
            )[0]
        if '__set__' in obj:
            return set(obj['__set__'])
        if '__tuple__' in obj:
            return tuple(obj['__tuple__'])
        if '__complex__' in obj:
            return complex(obj['__complex__'])

    return obj

# over-write the load(s)/dump(s) functions
def load(*args, **kwargs):
    kwargs['object_hook'] = from_json
    return json.load(*args, **kwargs)


def loads(*args, **kwargs):
    kwargs['object_hook'] = from_json
    return json.loads(*args, **kwargs)


def dump(*args, **kwargs):
    kwargs['default'] = to_json
    return json.dump(*args, **kwargs)


def dumps(*args, **kwargs):
    kwargs['default'] = to_json
    return json.dumps(*args, **kwargs)

Вы должны иметь возможность сделать следующее:

import numpy as np
import np_json as json
np_data = np.zeros((10,10), dtype=np.float32)
new_data = json.loads(json.dumps(np_data))
assert (np_data == new_data).all()

Ответ 4

Если это нужно для чтения человеком, и вы знаете, что это массив numpy:

import numpy as np; 
import json;

a = np.random.normal(size=(50,120,150))
a_reconstructed = np.asarray(json.loads(json.dumps(a.tolist())))
print np.allclose(a,a_reconstructed)
print (a==a_reconstructed).all()

Может быть, не самый эффективный, поскольку размеры массива растут больше, но работают для меньших массивов.

Ответ 5

Msgpack имеет лучшую производительность сериализации: http://www.benfrederickson.com/dont-pickle-your-data/

Используйте msgpack-numpy. См. https://github.com/lebedov/msgpack-numpy

Установите его:

pip install msgpack-numpy

Тогда:

import msgpack
import msgpack_numpy as m
import numpy as np

x = np.random.rand(5)
x_enc = msgpack.packb(x, default=m.encode)
x_rec = msgpack.unpackb(x_enc, object_hook=m.decode)

Ответ 6

Попробуйте использовать numpy.array_repr или numpy.array_str.