PandasでJSONファイルを保存するときの注意点

個人的にはTensorFlowでモデリングするようになってから、numpyを触る頻度が高くなって、それに伴いpandasを触る機会が減っていった。
データの可視化もpyplotとかseaboneとかを使って、可視化の方法を調べながら模索している状況が増えていった。

しかし、先日副業の案件で学習データをサクッと可視化させようと思ったときに選んだのは、pandasだった。

やはりpandasは優秀かな

テキストデータを読み込んで、異常値を探すとかその場でフィルタリングしようかと思うと、
やはりpandasは楽だなと改めて思たった。(ただnumpy力が低いだけかもしれないが…) 例えば、とりあえずテキストに保存したデータセットをDataFrameに読み込んで、
データセットの概要を知りたいなーと思ったら、

df.describe()

とすれば、カラム毎の最大値・最小値、四分位数とかを出してくれるし、
histグラムで可視化したいなーとおもったら、

df.plot.hist(y=[<可視化するカラム名>], bins=<ビン数>)

とすれば、指定したカラムだけのヒストグラムが可視化できて、すごく楽だった。
分布はどのような形なのか、どの範囲で異常値を切り出すのかなど判断して
そのままDataFrameでフィルターすればいいので、短時間でデータセットのチェックと更新ができた。

DataFrameの保存には注意が必要

前提

前提として、データセットJSON形式で保存しており、 すでに実験するノートブックを用意してある、としてます。
なので、同じJSONの形式で保存することで実験のノートブックを再利用することが前提です。

問題点

通常、PythonでdictionaryをJSONとして保存した場合、

import json


dict_list = []

for i in range(10):
    dict_obj = {}
    dict_obj["single"] = i
    dict_obj["double"] = i * 2
    dict_obj["square"] = i * i
    dict_list.append(dict_obj)

with open('dict2json.json', 'w') as f:
    json.dump(dict_list, f)

のような処理になる。 そして、この場合テキストにはざっくりと

[{"single": 0, "double": 0, "square": 0}, {"single": 1, "double": 2, "square": 1}, {"single": 2, "double": 4, "square": 4}, {"single": 3, "double": 6, "square": 9}, {"single": 4, "double": 8, "square": 16}, {"single": 5, "double": 10, "square": 25}, {"single": 6, "double": 12, "square": 36}, {"single": 7, "double": 14, "square": 49}, {"single": 8, "double": 16, "square": 64}, {"single": 9, "double": 18, "square": 81}]

このような形式で保存されている。
いわゆる、key-valueが配列に格納されている形である。 なので、配列の1要素が1行(1レコード)のデータを表現している。

そして、何も意識せずにDataFrameをJSONファイルとして保存すると痛い目に遭った。 それが今回の教訓だった。

PandasでJSONを読み込むには、

import pandas as pd

df = pd.read_json('dict2json.json')

とすればよい。 そして、このDataFrameをJSONとして保存する場合、以下のように書けば良い。

df.to_json("df2json.json")

人によりけりだとは思うが、簡単で直感的だ。ただし、この場合保存されたファイルの中身は、

{"double":{"0":0,"1":2,"2":4,"3":6,"4":8,"5":10,"6":12,"7":14,"8":16,"9":18},"single":{"0":0,"1":1,"2":2,"3":3,"4":4,"5":5,"6":6,"7":7,"8":8,"9":9},"square":{"0":0,"1":1,"2":4,"3":9,"4":16,"5":25,"6":36,"7":49,"8":64,"9":81}}

となる。 形としては、カラム毎に、 "index: value" の形で保存されている。

この場合、保存されたファイルを

with open('df2json.json') as f:
    json_data = json.load(f)

として読み込むと、1レコードずつとして読み込まれず、keyが想定していたものと異なる結果になる。

Key-Valueの形式で保存するには、

DataFrameをkey-valueの形式のJSONに保存するときには orient オプションに record を指定する必要があることに注意する。

df.to_json(filtered_data_filepath, orient='records')

そうすると、

[{"double":0,"single":0,"square":0},{"double":2,"single":1,"square":1},{"double":4,"single":2,"square":4},{"double":6,"single":3,"square":9},{"double":8,"single":4,"square":16},{"double":10,"single":5,"square":25},{"double":12,"single":6,"square":36},{"double":14,"single":7,"square":49},{"double":16,"single":8,"square":64},{"double":18,"single":9,"square":81}]

と保存された。

JupyterLabでアニメーションが動作しないときの対処

背景・目的

最近、深層強化学習を勉強していて、教材としては

https://www.amazon.co.jp/dp/4839965625

を使っている。 コードはノートブックに記述していて、JupyterLabを利用している。 Toy problem として自作で3×3マスの迷路を自作して、選択した行動をアニメーションとして描画するところで問題があったのでメモしておく。

エージェントが通るパスをアニメーションとして表示するコードを部分的に抜粋して載せておく。

from matplotlib import animation
from IPython.display import HTML
%matplotlib inline

def init():
    line.set_data([], [])
    return (line,)

def animate(i):
    state = state_history[i]
    x = (state % 3) + 0.5
    y = 2.5 - int(state / 3)
    line.set_data(x, y)
    return (line,)

anim = animation.FuncAnimation(fig, animate, init_func=init, 
                               frames=len(state_history), interval=200, repeat=False)
anim.save("maze.gif", writer = "imagemagick")
HTML(anim.to_jshtml())

state_historyにはエージェントが通るパスのが保存されていて、それをアニメーションとして動かす。 今回問題になったは HTML(anim.to_jshtml()) の部分である。

アニメーションとして動かすには

Jupyter内で図をアニメーションとして動かすには、 matplotlibanimation を利用すると簡単に実現できる。 実際に、書籍では恐らくJupyter Notebookで実験することを想定しており、簡単にアニメーションを動かすことができた。

しかし、JupyterLabではJavaScriptの問題のためかアニメーションを再生することができない。 この問題はGithub上でissueとして上がっている。 ここでは、animationのオブジェクトにある to_jshtml() を使っているが、 その他にも to_html5_video() 関数があり、これを使ってもアニメーションを再生できなかった。

どうやって解決したか

JupyterのNotebookはコードのみでなく、Markdownとして文章を記述することが可能である。 今回は、アニメーションをgifとして保存し、そのファイルをMarkdownで読み込みアニメーションを再生することで回避した。 上記のコードの中の

anim.save("maze.gif", writer = "imagemagick")

でアニメーションをgifとして保存している。 ここで保存したファイルをMarkdown

<div class="pull-left">
![Animated GIF](maze.gif)
<div>

と記述した。 これにより、Jupyter NotebookでもJupyterLabでもアニメーションを表示することができた。 難点としては、アニメーションが無限にリピート再生されてしまうところくらいかなと思っている。 それよりも簡単に可視化されどのような挙動になるのか、可視化されながら説明できる点のほうがはるかに重要だと思うので、 回避方法が見つかってよかった。 おなじ問題に出くわしている人の解決方法になれば幸いです。 また、JupyterLabで to_jshtml()to_html5_video() などコードでアニメーションが実現できた人はコメントなどでぜひとも教えてください!

Kerasで学習したモデルをWebAPIとして提供する


機械学習で学習済みモデルの予測結果をどうやって返すべきか勉強したので、WebAPIとして提供する場合のコードのメモする。 リポジトリこちら

READMEに拙い英語でリポジトリの内容について書いた。

個人的な学びポイントは、

  • load.pyinit 関数でリロードした学習済みモデルを返している部分
  • 画像データの受け取り方

の2つである。

まず、学習済みモデルを返している部分についてだが、 tensorflow の場合、 Saver などを使い、 graph に学習済みモデルを読み込む必要があるが、 Keras の場合は学習済みのモデルをリロードし、 predict 関数を使って推論が行える。

そして、もう一つの画像データの受け取り方について。 今回、画像を DataUrl という形式に変換して渡している。 これを利用することで、 API 側では画像を base64 の文字列として受け取ることができ、 画像として復元し、推論可能なデータサイズにリサイズすることで予測を行っている。

Flask を利用すれば非常に簡単に推論結果を返すことができるので、 モデルを作成して推論結果を検証するためのインターフェイスとしては効率の良い方法の一つではないかと思う。

ブログをはじめました

はじめまして

toohskといいます。よろしくお願いします。

toohskの読み方は決まってません。 いろいろなところでこのIDを使ってるのですが。

友達の仕事を手伝うことを契機に 仕事でのメモや技術的なことをポストしたくなり、ブログをはじめました。 以前自前でブログをやってたのですが、 自分で管理するのも面倒だし、SEO上げるの頑張るのも大変だし… ということではてぶを始めることにしました。 よろしくお願いします。