web-dev-qa-db-fra.com

Comment puis-je sérialiser un tableau numpy tout en préservant les dimensions de la matrice?

numpy.array.tostring ne semble pas conserver les informations sur les dimensions de la matrice (voir cette question ), obligeant l'utilisateur à appeler le numpy.array.reshape

Existe-t-il un moyen de sérialiser un tableau numpy au format JSON tout en préservant ces informations?

Remarque: Les tableaux peuvent contenir des éléments, des flotteurs ou des bools. Il est raisonnable de s'attendre à un tableau transposé.

Note 2: ceci est fait dans l'intention de faire passer le tableau numpy par le biais d'une topologie Storm en utilisant streamparse, au cas où ces informations seraient pertinentes.

30
blz

pickle.dumps ou numpy.save code toutes les informations nécessaires pour reconstruire un tableau NumPy arbitraire, même en présence de problèmes d'endianisme, de tableaux non contigus ou de dtypes étranges. Les problèmes d’endianisme sont probablement les plus importants; vous ne voulez pas que array([1]) devienne soudainement array([16777216]) car vous avez chargé votre tableau sur une machine big-endian. pickle est probablement l’option la plus pratique, bien que save ait ses propres avantages, présentés dans le format npy .

L'option 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 utilise un format binaire et il doit écrire dans un fichier, mais vous pouvez contourner ce problème avec 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

Et désérialiser:

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

EDIT: Comme on peut le lire dans les commentaires de la question, cette solution concerne les tableaux numpy "normaux" (floats, ints, bools ...) et non les tableaux structurés multi-types.

Solution de sérialisation d'un tableau numpy de dimensions et types de données quelconques

Autant que je sache, vous ne pouvez pas simplement sérialiser un tableau numpy avec tout type de données et toute dimension ... mais vous pouvez stocker son type de données, sa dimension et ses informations dans une représentation sous forme de liste, puis le sérialiser à l'aide de JSON.

Importations nécessaires :

import json
import base64

Pour l'encodage vous pouvez utiliser (nparray est un tableau numpy de tout type de données et de toute dimensionnalité):

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

Après cela, vous obtenez une image JSON (chaîne) de vos données, contenant une représentation sous forme de liste de son type et de sa forme, ainsi que des tableaux de données/contenus codés en base64.

Et pour le décodage this effectue le travail (encStr est la chaîne JSON codée, chargée de quelque part):

# 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

Les dumps JSON sont efficaces et compatibles entre eux pour de nombreuses raisons, mais le simple fait de prendre JSON entraîne des résultats inattendus si vous souhaitez stocker et charger des tableaux numpy de tout type et toute dimension .

Cette solution stocke et charge les tableaux numpy indépendamment du type ou de la dimension et les restaure correctement (type de données, dimension, ...).

J'ai essayé plusieurs solutions moi-même il y a des mois et c'était la seule solution efficace et polyvalente que je rencontrais.

10
daniel451

J'ai trouvé le code dans Msgpack-numpy utile . https://github.com/lebedov/msgpack-numpy/blob/master/msgpack_numpy.py

J'ai légèrement modifié le dict sérialisé et ajouté le codage base64 pour réduire la taille sérialisée.

En utilisant la même interface que json (fournissant des charges, dump (s)), vous pouvez fournir une solution de remplacement pour la sérialisation json.

Cette même logique peut être étendue pour ajouter toute sérialisation automatique non triviale, telle que des objets datetime.


EDIT J'ai écrit un analyseur générique, modulaire, qui fait ceci et plus encore . https://github.com/someones/jaweson


Mon code est le suivant:

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)

Vous devriez pouvoir faire ce qui suit:

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
Rebs

Msgpack offre les meilleures performances de sérialisation: http://www.benfrederickson.com/dont-pickle-your-data/

Utilisez msgpack-numpy. Voir https://github.com/lebedov/msgpack-numpy

Installez-le:

pip install msgpack-numpy

Ensuite:

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)
1
thayne

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

"Créez un schéma sérialisable et vérifié par type à l'aide de traits et de Numpy. Un cas d'utilisation typique consiste à enregistrer plusieurs tableaux Numpy de formes et de types variés."

0
SemanticBeeng

S'il doit être lisible par l'homme et que vous savez qu'il s'agit d'un tableau 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()

Peut-être pas la plus efficace à mesure que la taille des baies augmente, mais fonctionne pour les baies plus petites.

0
Chris.Wilson