【问题标题】:How can I serialize a numpy array while preserving matrix dimensions?如何在保留矩阵维度的同时序列化 numpy 数组?
【发布时间】:2015-08-22 06:17:08
【问题描述】:

numpy.array.tostring 似乎没有保留有关矩阵维度的信息(请参阅this question),需要用户调用numpy.array.reshape

有没有办法在保留此信息的同时将 numpy 数组序列化为 JSON 格式?

注意:数组可能包含整数、浮点数或布尔值。期望转置数组是合理的。

注意 2: 这样做的目的是使用 streamparse 通过 Storm 拓扑传递 numpy 数组,以防此类信息最终相关。

【问题讨论】:

  • 你为什么投反对票?我的解决方案是正确的,适用于任何维度和任何数据类型的 numpy 数组。
  • @ascenator,否决票不是来自我。我猜有人今天过得很糟糕:/
  • 哇...谁在他自己不是所有者的线程中对解决方案投反对票?^^ 那么..抱歉给您带来不便。我希望您对解决方案感到满意:)
  • @ascenator:可能是因为它在structured arrays 上失败了?它还要求数组是 C 连续的,我怀疑如果数组在 little-endian 系统上序列化并在 big-endian 系统上反序列化,或者反之亦然,它也可能会做错事,但我没有要检查的设备。我不是反对者,也不知道反对者的原因,但我不会赞成。
  • 必须是文本格式吗?因为numpy.savenumpy.load(使用二进制格式)保存数组的形状(以及类型和顺序)。

标签: python json numpy apache-storm


【解决方案1】:

pickle.dumpsnumpy.save 编码重建任意 NumPy 数组所需的所有信息,即使存在字节顺序问题、非连续数组或奇怪的元组 dtypes。字节顺序问题可能是最重要的。您不希望 array([1]) 突然变为 array([16777216]),因为您将阵列加载到大端机器上。 pickle 可能是更方便的选择,尽管save 有其自身的好处,在npy format rationale 中给出。

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 使用二进制格式,它需要写入文件,但您可以通过 io.BytesIO 解决这个问题:

a = # any NumPy array
memfile = io.BytesIO()
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 = io.BytesIO()
memfile.write(json.loads(serialized).encode('latin-1'))
memfile.seek(0)
a = numpy.load(memfile)

【讨论】:

  • 您能解释一下为什么包含json.dumps(memfile.read().decode('latin-1')) 吗?
  • @FGreg:它可以将原始字节序列化为 JSON,因为提问者要求提供 JSON 输出。我不记得为什么我没有pickle 选项添加类似的内容;它可能与 bytestring 与 unicode 字符串问题有关。
  • 在python 3中,我不得不将StringIO.StringIO()替换为io.BytesIO()作为hinted here
【解决方案2】:

编辑:正如人们可以在问题的 cmets 中看到的那样,此解决方案处理“普通”numpy 数组(浮点数、整数、布尔值 ...),而不是多类型结构化数组。

序列化任意维度和数据类型的 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 转储高效且交叉兼容,但如果您要存储和加载 任何类型任何维度的 numpy 数组,仅采用 JSON 会导致意外结果>.

此解决方案无论类型或维度如何都存储和加载 numpy 数组,并且还可以正确恢复它(数据类型、维度、...)

几个月前我自己尝试了几种解决方案,这是我遇到的唯一一种高效、通用的解决方案。

【讨论】:

  • 赞成,因为它是一个可用的答案。两个次要但相关的挑剔。首先,我建议将数组数据写为格式化文本。这样它是人类可读的,你可以绕过可能的字节顺序问题。其次,我会将 dtype 和形状都放在数据之前,作为一种“标题”。
  • 这仍然存在要求数组是 C 连续的问题,我高度怀疑如果序列化数组的机器和反序列化它的机器具有不同的字节序,它将产生不正确的输出。
  • 我做了类似于您的“将 np.ndarray 对象分解为 [dtype, data_buffer, shape] 发送,并使用 np.frombuffer() 和/或 np.reshape() 重构”以进行推送的操作将 numpy 数组放入共享内存中以用于并行进程。但我忘记了形状。所以我的解决方案仅限于 1d。
【解决方案3】:

我发现 Msgpack-numpy 中的代码很有帮助。 https://github.com/lebedov/msgpack-numpy/blob/master/msgpack_numpy.py

我稍微修改了序列化的dict并添加了base64编码以减小序列化的大小。

通过使用与 json 相同的接口(提供 load(s)、dump(s)),您可以为 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()

【讨论】:

    【解决方案4】:

    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)
    

    【讨论】:

      【解决方案5】:

      如果它需要人类可读并且你知道这是一个 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()
      

      随着数组大小变大,可能不是最有效的,但适用于较小的数组。

      【讨论】:

        【解决方案6】:

        试试traitschemahttps://traitschema.readthedocs.io/en/latest/

        “使用特征和 Numpy 创建可序列化、类型检查的模式。一个典型的用例涉及保存多个不同形状和类型的 Numpy 数组。”

        【讨论】:

          【解决方案7】:

          这包含了@user2357112 的基于pickle 的答案,以便于JSON 集成

          下面的代码会将其编码为 base64。它将处理任何类型/大小的 numpy 数组,而无需记住它是什么。它还将处理其他可以腌制的任意对象。

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

          【讨论】:

            【解决方案8】:

            尝试 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)
            

            【讨论】:

              【解决方案9】:

              尝试使用numpy.array_reprnumpy.array_str

              【讨论】:

              • @Ken,这将返回人类可读的字符串表示形式。它不会序列化数组。
              • 除非你用numpy.set_printoptions更改全局打印选项,否则这些只显示数组的一小部分。
              猜你喜欢
              • 2020-10-08
              • 1970-01-01
              • 2019-09-14
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-05-31
              • 2021-12-01
              • 1970-01-01
              相关资源
              最近更新 更多