TensorFlow 使ってるときの json.dumps と numpy.float32 の罠

# 背景

TensorFlow の出力を json で dump して...などとしていたときに次のようなエラーが出て、少しはまったので書く。
もしかすると常識かもしれないが、自分は知らなかった。

```
TypeError: Object of type 'float32' is not JSON serializable
```

例えば、外から API 叩いて TensorFlow で書いたモデルの出力を json にして返すようなときにはまる。

# 問題

1. TensorFlow で書いたあるネットワークの出力(実数値) `x` の List を受け取る
2. それを json で dump して返す ( `json.dumps(x)`)

上記のようなシンプルな関数があったとき、実行すると下記のエラーがでた。

```
TypeError: Object of type 'float32' is not JSON serializable
```

# 原因


結論: `json.dumps` は `numpy.float32` を受け取れず、上記の例外が発生する。


計算時、変数の型は次のようになる。

1. TensorFlow では `tf.float32` で定義されている
2. 出力は numpy の型になるので、`session.run` の結果 `numpy.float32` の List を受け取る
3. そのまま `json.dumps` の引数が `numpy.float32` の List が渡り、エラーになる。

なぜこうなるかというと...

[python の `float` はCの `double`](https://docs.python.jp/3/library/stdtypes.html#numeric-types-int-float-complex) なので、 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 エンコーダおよびデコーダ](https://docs.python.jp/3/library/json.html) 及び [変換表](https://docs.python.jp/3/library/json.html#py-to-json-table) を見ると、デフォルトでサポートされる型の中に

```
int、float と int や float の派生列挙型
```
と書かれていて、float 32 などについては書かれていない。
派生列挙型ってなんだろう、基数が違うとかかな?
残念ながら `numpy.float32` は上述の通り今の所エラーになる。

# 一応コードで見る

```py
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'>
```

```py
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等) になる)なんてこともあったので、少しめんどくさい。
気をつけましょう。