如何在保留矩阵维度的同时序列化 numpy 数组?
How can I serialize a numpy array while preserving matrix dimensions?
numpy.array.tostring
似乎不保留有关矩阵维度的信息(请参阅 ),需要用户发出对 numpy.array.reshape
的调用。
有没有办法在保留此信息的同时将 numpy 数组序列化为 JSON 格式?
注意:数组可能包含整数、浮点数或布尔值。期待一个转置数组是合理的。
注 2:这样做的目的是使用流解析通过 Storm 拓扑传递 numpy 数组,以防此类信息最终变得相关。
编辑: 正如可以在问题的评论中读到的那样,该解决方案处理 "normal" numpy 数组(浮点数、整数、布尔值...)而不是具有多类型结构化数组。
任意维度任意数据类型的numpy数组序列化解决方案
据我所知,您不能简单地序列化具有任何数据类型和任何维度的 numpy 数组...但您可以将其数据类型、维度和信息存储在列表表示中,然后使用 JSON.
需要进口:
import json
import base64
对于编码,您可以使用(nparray
是任何数据类型和任何维度的一些 numpy 数组):
json.dumps([str(nparray.dtype), base64.b64encode(nparray), nparray.shape])
在此之后,您将获得数据的 JSON 转储(字符串),其中包含其数据类型和形状的列表表示以及 data/contents 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 转储是高效且交叉兼容的,原因有很多,但如果您想存储和加载 任何类型的 numpy 数组,仅采用 JSON 会导致意想不到的结果 和 任何维度 .
此解决方案存储和加载 numpy 数组,无论类型或维度如何,并正确恢复它(数据类型、维度...)
几个月前我自己尝试了几种解决方案,这是我遇到的唯一有效、通用的解决方案。
尝试使用 numpy.array_repr
或 numpy.array_str
。
pickle.dumps
or numpy.save
encode all the information needed to reconstruct an arbitrary NumPy array, even in the presence of endianness issues, non-contiguous arrays, or weird structured dtypes. Endianness issues are probably the most important; you don't want array([1])
to suddenly become array([16777216])
because you loaded your array on a big-endian machine. pickle
is probably the more convenient option, though save
has its own benefits, given in the npy
format rationale.
我给出了序列化为 JSON 或字节串的选项,因为最初的提问者需要 JSON-可序列化的输出,但大多数来这里的人可能不需要。
pickle
方式:
import pickle
a = # some NumPy array
# Bytestring option
serialized = pickle.dumps(a)
deserialized_a = pickle.loads(serialized)
# JSON option
# latin-1 maps byte n to unicode code point n
serialized_as_json = json.dumps(pickle.dumps(a).decode('latin-1'))
deserialized_from_json = pickle.loads(json.loads(serialized_as_json).encode('latin-1'))
numpy.save
使用二进制格式,它需要写入文件,但您可以使用 io.BytesIO
:
来解决这个问题
a = # any NumPy array
memfile = io.BytesIO()
numpy.save(memfile, a)
serialized = memfile.getvalue()
serialized_as_json = json.dumps(serialized.decode('latin-1'))
# latin-1 maps byte n to unicode code point n
反序列化:
memfile = io.BytesIO()
# If you're deserializing from a bytestring:
memfile.write(serialized)
# Or if you're deserializing from JSON:
# memfile.write(json.loads(serialized_as_json).encode('latin-1'))
memfile.seek(0)
a = numpy.load(memfile)
我发现 Msgpack-numpy 中的代码很有帮助。
https://github.com/lebedov/msgpack-numpy/blob/master/msgpack_numpy.py
我对序列化的dict进行了轻微的修改,并添加了base64编码以减少序列化的大小。
通过使用与 json 相同的接口(提供加载、转储),您可以为 json 序列化提供直接替代。
可以扩展相同的逻辑以添加任何自动的非平凡序列化,例如日期时间对象。
编辑
我已经编写了一个通用的、模块化的解析器来执行此操作以及更多操作。
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()
如果它需要人类可读并且您知道这是一个 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()
随着数组大小变大,可能不是最有效的,但适用于较小的数组。
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)
尝试traitschema
https://traitschema.readthedocs.io/en/latest/
"Create serializable, type-checked schema using traits and Numpy. A typical use case involves saving several Numpy arrays of varying shape and type."
这包装了@user2357112 基于 pickle 的答案,以便于 JSON 集成
下面的代码会将其编码为 base64。它将处理任何 type/size 的 numpy 数组,而无需记住它是什么。它还将处理其他可以 pickle 的任意对象。
import numpy as np
import json
import pickle
import codecs
class PythonObjectEncoder(json.JSONEncoder):
def default(self, obj):
return {
'_type': str(type(obj)),
'value': codecs.encode(pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL), "base64").decode('latin1')
}
class PythonObjectDecoder(json.JSONDecoder):
def __init__(self, *args, **kwargs):
json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)
def object_hook(self, obj):
if '_type' in obj:
try:
return pickle.loads(codecs.decode(obj['value'].encode('latin1'), "base64"))
except KeyError:
return obj
return obj
# Create arbitrary array
originalNumpyArray = np.random.normal(size=(3, 3))
print(originalNumpyArray)
# Serialization
numpyData = {
"array": originalNumpyArray
}
encodedNumpyData = json.dumps(numpyData, cls=PythonObjectEncoder)
print(encodedNumpyData)
# Deserialization
decodedArrays = json.loads(encodedNumpyData, cls=PythonObjectDecoder)
finalNumpyArray = decodedArrays["array"]
# Verify
print(finalNumpyArray)
print(np.allclose(originalNumpyArray, finalNumpyArray))
print((originalNumpyArray==finalNumpyArray).all())
试试 numpy-serializer:
下载
pip install numpy-serializer
用法
import numpy_serializer as ns
import numpy as np
a = np.random.normal(size=(50,120,150))
b = ns.to_bytes(a)
c = ns.from_bytes(b)
assert np.array_equal(a,c)
numpy.array.tostring
似乎不保留有关矩阵维度的信息(请参阅 numpy.array.reshape
的调用。
有没有办法在保留此信息的同时将 numpy 数组序列化为 JSON 格式?
注意:数组可能包含整数、浮点数或布尔值。期待一个转置数组是合理的。
注 2:这样做的目的是使用流解析通过 Storm 拓扑传递 numpy 数组,以防此类信息最终变得相关。
编辑: 正如可以在问题的评论中读到的那样,该解决方案处理 "normal" numpy 数组(浮点数、整数、布尔值...)而不是具有多类型结构化数组。
任意维度任意数据类型的numpy数组序列化解决方案
据我所知,您不能简单地序列化具有任何数据类型和任何维度的 numpy 数组...但您可以将其数据类型、维度和信息存储在列表表示中,然后使用 JSON.
需要进口:
import json
import base64
对于编码,您可以使用(nparray
是任何数据类型和任何维度的一些 numpy 数组):
json.dumps([str(nparray.dtype), base64.b64encode(nparray), nparray.shape])
在此之后,您将获得数据的 JSON 转储(字符串),其中包含其数据类型和形状的列表表示以及 data/contents 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 转储是高效且交叉兼容的,原因有很多,但如果您想存储和加载 任何类型的 numpy 数组,仅采用 JSON 会导致意想不到的结果 和 任何维度 .
此解决方案存储和加载 numpy 数组,无论类型或维度如何,并正确恢复它(数据类型、维度...)
几个月前我自己尝试了几种解决方案,这是我遇到的唯一有效、通用的解决方案。
尝试使用 numpy.array_repr
或 numpy.array_str
。
pickle.dumps
or numpy.save
encode all the information needed to reconstruct an arbitrary NumPy array, even in the presence of endianness issues, non-contiguous arrays, or weird structured dtypes. Endianness issues are probably the most important; you don't want array([1])
to suddenly become array([16777216])
because you loaded your array on a big-endian machine. pickle
is probably the more convenient option, though save
has its own benefits, given in the npy
format rationale.
我给出了序列化为 JSON 或字节串的选项,因为最初的提问者需要 JSON-可序列化的输出,但大多数来这里的人可能不需要。
pickle
方式:
import pickle
a = # some NumPy array
# Bytestring option
serialized = pickle.dumps(a)
deserialized_a = pickle.loads(serialized)
# JSON option
# latin-1 maps byte n to unicode code point n
serialized_as_json = json.dumps(pickle.dumps(a).decode('latin-1'))
deserialized_from_json = pickle.loads(json.loads(serialized_as_json).encode('latin-1'))
numpy.save
使用二进制格式,它需要写入文件,但您可以使用 io.BytesIO
:
a = # any NumPy array
memfile = io.BytesIO()
numpy.save(memfile, a)
serialized = memfile.getvalue()
serialized_as_json = json.dumps(serialized.decode('latin-1'))
# latin-1 maps byte n to unicode code point n
反序列化:
memfile = io.BytesIO()
# If you're deserializing from a bytestring:
memfile.write(serialized)
# Or if you're deserializing from JSON:
# memfile.write(json.loads(serialized_as_json).encode('latin-1'))
memfile.seek(0)
a = numpy.load(memfile)
我发现 Msgpack-numpy 中的代码很有帮助。 https://github.com/lebedov/msgpack-numpy/blob/master/msgpack_numpy.py
我对序列化的dict进行了轻微的修改,并添加了base64编码以减少序列化的大小。
通过使用与 json 相同的接口(提供加载、转储),您可以为 json 序列化提供直接替代。
可以扩展相同的逻辑以添加任何自动的非平凡序列化,例如日期时间对象。
编辑 我已经编写了一个通用的、模块化的解析器来执行此操作以及更多操作。 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()
如果它需要人类可读并且您知道这是一个 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()
随着数组大小变大,可能不是最有效的,但适用于较小的数组。
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)
尝试traitschema
https://traitschema.readthedocs.io/en/latest/
"Create serializable, type-checked schema using traits and Numpy. A typical use case involves saving several Numpy arrays of varying shape and type."
这包装了@user2357112 基于 pickle 的答案,以便于 JSON 集成
下面的代码会将其编码为 base64。它将处理任何 type/size 的 numpy 数组,而无需记住它是什么。它还将处理其他可以 pickle 的任意对象。
import numpy as np
import json
import pickle
import codecs
class PythonObjectEncoder(json.JSONEncoder):
def default(self, obj):
return {
'_type': str(type(obj)),
'value': codecs.encode(pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL), "base64").decode('latin1')
}
class PythonObjectDecoder(json.JSONDecoder):
def __init__(self, *args, **kwargs):
json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)
def object_hook(self, obj):
if '_type' in obj:
try:
return pickle.loads(codecs.decode(obj['value'].encode('latin1'), "base64"))
except KeyError:
return obj
return obj
# Create arbitrary array
originalNumpyArray = np.random.normal(size=(3, 3))
print(originalNumpyArray)
# Serialization
numpyData = {
"array": originalNumpyArray
}
encodedNumpyData = json.dumps(numpyData, cls=PythonObjectEncoder)
print(encodedNumpyData)
# Deserialization
decodedArrays = json.loads(encodedNumpyData, cls=PythonObjectDecoder)
finalNumpyArray = decodedArrays["array"]
# Verify
print(finalNumpyArray)
print(np.allclose(originalNumpyArray, finalNumpyArray))
print((originalNumpyArray==finalNumpyArray).all())
试试 numpy-serializer:
下载
pip install numpy-serializer
用法
import numpy_serializer as ns
import numpy as np
a = np.random.normal(size=(50,120,150))
b = ns.to_bytes(a)
c = ns.from_bytes(b)
assert np.array_equal(a,c)