Je souhaite échanger des données binaires entre Python et C / C ++ pour l'apprentissage automatique et les courses tardives. Je veux terminer avec uniquement les fonctions standard de Python. Pour le texte, il existe JSON et le format de texte numpy (csv), mais les binaires ne sont pas faciles à utiliser du côté C ++.
Envisagez la sérialisation de Pickle.
https://docs.python.org/ja/3/library/pickle.html
Il semble que l'endianité soit également prise en considération.
Le site qui expliquait brièvement le format de sérialisation de Pickle lui-même n'était pas non plus en anglais: Cry: (Une fois que vous le savez, ce n'est pas ce format compliqué, donc ce n'est peut-être pas suffisant pour l'expliquer ...)
Cependant, heureusement, PyTorch JIT prend en charge la sérialisation avec son propre chargeur Pickle C ++ pour implémenter TorchScript (langage de script de type Python), et le code est utile.
https://github.com/pytorch/pytorch/blob/master/torch/csrc/jit/docs/serialization.md
https://github.com/pytorch/pytorch/blob/master/torch/csrc/jit/serialization/pickler.h
Vous pouvez également analyser les données avec Pickletools en Python.
https://docs.python.org/ja/3.6/library/pickletools.html
Pickle a plusieurs versions de protocole.3 est la valeur par défaut dans Python3, mais lorsqu'il est sérialisé dans proto 3 dans Python3, il ne peut pas être lu dans Python2.
Si vous utilisez principalement des données numériques et que vous ne manipulez pas des données qui ne sont pas très étranges, est-ce que proto 2 est recommandé? (TorchScript ne prend en charge que le proto 2)
L'en-tête sera de 2 octets de «0x80» (PROTO, 1 octet) et le numéro de version (1 octet).
Essayons de sérialiser 1.
import pickle
import io
a = 1
f = io.BytesIO()
b = pickle.dump(a, f)
w = open("bora.p", "wb")
w.write(f.getbuffer())
$ od -tx1c bora.p
0000000 80 03 4b 01 2e
200 003 K 001 .
0000005
«K» est «BININT1»
.
(2e) est STOP
. Fin des données.
En regardant unpicker.cpp dans pytorch jit,
case PickleOpCode::BININT1: {
uint8_t value = read<uint8_t>();
stack_.emplace_back(int64_t(value));
} break;
Comme c'est le cas, vous pouvez voir que BININT1
est une valeur de type int qui peut être sérialisée avec 1 octet.
Essayez les données du tableau.
import pickle
import io
a = [1, 2]
f = io.BytesIO()
b = pickle.dump(a, f, protocol=2)
w = open("bora.p", "wb")
w.write(f.getbuffer())
Maintenant, vidons-le avec pickletools.
$ python -m pickletools bora.p
0: \x80 PROTO 2
2: ] EMPTY_LIST
3: q BINPUT 0
5: ( MARK
6: K BININT1 1
8: K BININT1 2
10: e APPENDS (MARK at 5)
11: . STOP
highest protocol among opcodes = 2
Fondamentalement, c'est une combinaison de préfixe + données réelles, donc après cela, vous devriez essayer différentes choses en vous référant à pickler.cpp, unpickler.cpp et pickletools.py de pytorch jit et l'analyser!
numpy array
Sérialisons le tableau numpy (ndarray).
a = numpy.array([1.0, 2.2, 3.3, 4, 5, 6, 7, 8, 9, 10], dtype=numpy.float32)
f = io.BytesIO()
b = pickle.dump(a, f, protocol=2)
w = open("bora.p", "wb")
w.write(f.getbuffer())
0: \x80 PROTO 2
2: c GLOBAL 'numpy.core.multiarray _reconstruct'
38: q BINPUT 0
40: c GLOBAL 'numpy ndarray'
55: q BINPUT 1
57: K BININT1 0
59: \x85 TUPLE1
60: q BINPUT 2
62: c GLOBAL '_codecs encode'
78: q BINPUT 3
80: X BINUNICODE 'b'
86: q BINPUT 4
88: X BINUNICODE 'latin1'
99: q BINPUT 5
101: \x86 TUPLE2
102: q BINPUT 6
104: R REDUCE
105: q BINPUT 7
107: \x87 TUPLE3
108: q BINPUT 8
110: R REDUCE
111: q BINPUT 9
113: ( MARK
114: K BININT1 1
116: K BININT1 10
118: \x85 TUPLE1
119: q BINPUT 10
121: c GLOBAL 'numpy dtype'
134: q BINPUT 11
136: X BINUNICODE 'f4'
143: q BINPUT 12
145: K BININT1 0
147: K BININT1 1
149: \x87 TUPLE3
150: q BINPUT 13
152: R REDUCE
153: q BINPUT 14
155: ( MARK
156: K BININT1 3
158: X BINUNICODE '<'
164: q BINPUT 15
166: N NONE
167: N NONE
168: N NONE
169: J BININT -1
174: J BININT -1
179: K BININT1 0
181: t TUPLE (MARK at 155)
182: q BINPUT 16
184: b BUILD
185: \x89 NEWFALSE
186: h BINGET 3
188: X BINUNICODE '\x00\x00\x80?ÍÌ\x0c@33S@\x00\x00\x80@\x00\x00\xa0@\x00\x00À@\x00\x00à@\x00\x00\x00A\x00\x00\x10A\x00\x00 A'
240: q BINPUT 17
242: h BINGET 5
244: \x86 TUPLE2
245: q BINPUT 18
247: R REDUCE
248: q BINPUT 19
250: t TUPLE (MARK at 113)
251: q BINPUT 20
253: b BUILD
254: . STOP
highest protocol among opcodes = 2
Vous pouvez voir que les données du tableau sont stockées sous forme de chaîne d'octets autour de BINUNICODE. Après avoir analysé le code source de numpy, il semble que vous puissiez charger la version pickle du tableau numpy et du tenseur pytorch (vous pouvez imaginer qu'il a une structure similaire à numpy) avec votre propre chargeur C ++! (numpy native? NPY / NPZ est un format un peu concis, par exemple cnpy peut lire et écrire https://github.com/rogersce/cnpy)
TODO
Recommended Posts