背景
TensorFlow の出力を json で dump して...などとしていたときに次のようなエラーが出て、少しはまったので書く。
もしかすると常識かもしれないが、自分は知らなかった。
TypeError: Object of type 'float32' is not JSON serializable
例えば、外から API 叩いて TensorFlow で書いたモデルの出力を json にして返すようなときにはまる。
問題
- TensorFlow で書いたあるネットワークの出力(実数値)
x
の List を受け取る - それを json で dump して返す (
json.dumps(x)
)
上記のようなシンプルな関数があったとき、実行すると下記のエラーがでた。
TypeError: Object of type 'float32' is not JSON serializable
原因
結論: json.dumps
は numpy.float32
を受け取れず、上記の例外が発生する。
計算時、変数の型は次のようになる。
- TensorFlow では
tf.float32
で定義されている - 出力は numpy の型になるので、
session.run
の結果numpy.float32
の List を受け取る - そのまま
json.dumps
の引数がnumpy.float32
の List が渡り、エラーになる。
なぜこうなるかというと...
python の float
はCの double
なので、 64bit である。
numpy
には、 numpy.float32
と numpy.float64
があり(16
,128
もある)、何も考えなければ python のデフォルトと同じ numpy.float64
が使われる。
ただし、 TensorFlow では基本的に tf.float32
を使うことが多い(最近は float16
とかも?)ため、そのまま出力すると numpy.float32
の値になる。
別に numpy.float32
でも普通の用途で困ることはないが、なぜか json.dump
json.dumps
では対応しておらず、エラーになった。
numpy.float64
では、自動で python の float
にキャストされる?(未確認)ので問題ないようだ。
19.2. json — JSON エンコーダおよびデコーダ 及び 変換表 を見ると、デフォルトでサポートされる型の中に
int、float と int や float の派生列挙型
と書かれていて、float 32 などについては書かれていない。
派生列挙型ってなんだろう、基数が違うとかかな?
残念ながら numpy.float32
は上述の通り今の所エラーになる。
一応コードで見る
import numpy as np
pa64 = [1., 1., 1.]
npa64 = np.ones([3])
npa32 = np.ones([3], dtype=np.float32)
print('pa64: ', type(pa64[0]))
print('npa64: ', type(npa64[0]))
print('npa32: ', type(npa32[0]))
# outputs
pa64: <class 'float'>
npa64: <class 'numpy.float64'>
npa32: <class 'numpy.float32'>
import json
json.dumps({'pa64': pa64[0]})
# '{"pa64": 1.0}'
json.dumps({'npa64': npa64[0]})
# '{"npa64": 1.0}'
json.dumps({'npa32': npa32[0]})
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-6-2aa16547afeb> in <module>()
----> 1 json.dumps({'npa32': npa32[0]})
~/.pyenv/versions/3.6.5/lib/python3.6/json/__init__.py in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, default, sort_keys, **kw)
229 cls is None and indent is None and separators is None and
230 default is None and not sort_keys and not kw):
--> 231 return _default_encoder.encode(obj)
232 if cls is None:
233 cls = JSONEncoder
~/.pyenv/versions/3.6.5/lib/python3.6/json/encoder.py in encode(self, o)
197 # exceptions aren't as detailed. The list call should be roughly
198 # equivalent to the PySequence_Fast that ''.join() would do.
--> 199 chunks = self.iterencode(o, _one_shot=True)
200 if not isinstance(chunks, (list, tuple)):
201 chunks = list(chunks)
~/.pyenv/versions/3.6.5/lib/python3.6/json/encoder.py in iterencode(self, o, _one_shot)
255 self.key_separator, self.item_separator, self.sort_keys,
256 self.skipkeys, _one_shot)
--> 257 return _iterencode(o, 0)
258
259 def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
~/.pyenv/versions/3.6.5/lib/python3.6/json/encoder.py in default(self, o)
178 """
179 raise TypeError("Object of type '%s' is not JSON serializable" %
--> 180 o.__class__.__name__)
181
182 def encode(self, o):
TypeError: Object of type 'float32' is not JSON serializable
確かに3つ目だけ落ちる。
その他
json がダメってことは、python の他のモジュールでもダメなやつがあるかも...。
また、作業中に原因が分かり float64 にキャストしてから作業していると、 float32 のときは大丈夫だったのに float64 のときは問題が発生する(例えば、確率を扱っているときは List の合計が1になるはずが、float64 にすると少しズレる (1.000000002等) になる)なんてこともあったので、少しめんどくさい。
気をつけましょう。