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.
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)
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.
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()
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)
Essayez traitschema
https://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."
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.