OCaml でデータ分析 ― チュートリアル編

関数型プログラミング言語 OCamlJupyter 上で動かすため、OCaml Jupyter kernel を開発しています。 仕事でも、OCaml+Jupyter 環境でデータ分析を行っており、それなりに実用的だと思います。 ML数値計算勢の方々と情報共有し、同志を増やしていければ良いな、と思って、この記事を書いています。

OCaml でデータ分析 ― 紹介編(第2回ML勉強会)

OCaml でデータ分析する話については、第2回ML勉強会で発表した資料があるので、こちらをご覧いただければ、大体わかると思います。

www.slideshare.net

要約: Jupyter という、対話環境に画像の埋め込みや Markdown (LaTeX) でのコメント機能などを追加したような超便利なソフトウェアがあります。 データ分析作業(解析の試行錯誤、その可視化や保存・管理)にとても便利で、おそらく、データ分析系のお仕事の人は皆知っているのではないでしょうか? Jupyter の対話環境の実行部分は「カーネル」と呼ばれており、一番メジャーなのは Python を動かす IPython カーネルです。 カーネルと Jupyter の通信プロトコルは公開されているので、自分の好きな言語のカーネルを実装できます。 OCaml には IOCaml というカーネルがすでに存在しますが、

  • PPX(OCamlプリプロセッサ機能)が使えない
  • 補完機能が不完全(ロードしたライブラリに含まれる識別子が候補に出ない、など)
  • 外部コマンド呼び出しで IOCaml がフリーズすることがある(原因不明)

など、いろいろ問題があるのですが、事実上メンテナンスされてないため、PR も無視され、何も改善できない状態でした。

仕方がないので、自分でカーネルをスクラッチで作り直すことをML勉強会で宣言しました。 今日はその続きです。

OCaml Jupyter の紹介

IOCaml をフルスクラッチで再実装する件は完了しており、OCaml Jupyterという名前でリリースしています。 IOCaml に実装されている機能は OCaml Jupyter でも(たぶん)全て対応済みなので、乗り換えない理由は無いでしょう。 PPX もサポートされていますし、merlinによる補完機能が実装されています。 他にも、細々としたバグ修正や機能追加があり、IOCaml で致命的だった不具合や機能不足は概ね解消したと感じています。

インストール

Jupyter 自体は Python で書かれています。 Python のパッケージマネージャは Anaconda や pip など幾つかあるのですが、ここでは、pip の方法を紹介します。

$ pip install jupyter

これだけ。簡単でしょ? Python は v2 系と v3 系で言語仕様がだいぶ違うのですが、気にしなくて結構です。 Jupyter はどちらでもインストールできますし、OCaml Jupyter は Python 非依存なので、どちらでも大丈夫です。

OCaml Jupyter カーネルOPAMOCamlのパッケージマネージャ)に登録されています。 OPAM をまだ持っていない人は、 How to install OPAM に従ってインストールしてください。 カーネル自体は

$ opam install jupyter
$ jupyter kernelspec install --name ocaml-jupyter "$(opam config var share)/jupyter"

でインストールできます。以下のコマンドで、Jupyter notebook が起動します。

$ jupyter notebook

勝手にブラウザが開いて、こんな感じの画面が立ち上がります。

f:id:aabe0:20171205225100p:plain

Docker イメージを使う

「正直、インスール面倒」とか思っている貴方、朗報です! 数値計算系のパッケージをありったけ詰め込んだ Docker イメージ akabe/ocaml-jupyter-datascience (GitHub) を公開しています。 圧縮済みの Docker イメージでも 1GB となかなかエグいデータ量ですが、Python 版データ分析用イメージ jupyter/datascience-notebook は 2GB なので、まだ軽い方でしょう。

コマンドを叩いて、しばらく待つと、コンソールに URL が表示されるので、これをブラウザに貼り付けてください。 さっきの画像のような画面が立ち上がるはずです。

$ docker run -it -p 8888:8888 akabe/ocaml-jupyter-datascience
[I 15:38:04.170 NotebookApp] Writing notebook server cookie secret to /home/opam/.local/share/jupyter/runtime/notebook_cookie_secret
[W 15:38:04.190 NotebookApp] WARNING: The notebook server is listening on all IP addresses and not using encryption. This is not recommended.
[I 15:38:04.197 NotebookApp] Serving notebooks from local directory: /notebooks
[I 15:38:04.197 NotebookApp] 0 active kernels
[I 15:38:04.197 NotebookApp] The Jupyter Notebook is running at: http://[all ip addresses on your system]:8888/?token=4df0fee0719115f474c8dd9f9281abed28db140d25f933e9
[I 15:38:04.197 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[W 15:38:04.198 NotebookApp] No web browser found: could not locate runnable browser.
[C 15:38:04.198 NotebookApp]

    Copy/paste this URL into your browser when you connect for the first time,
    to login with a token:
        http://localhost:8888/?token=4df0fee0719115f474c8dd9f9281abed28db140d25f933e9

ちなみに、この Docker image は weekly で自動的にリビルドされ、DockerHub にアップロードされるようになっています。 なので、自動的にパッケージや辞書等のデータセットが更新されています。

個人的には、Docker イメージの方がオススメです。何故かというと、

  • OCaml 界では、小さい数計算系ライブラリが散乱しています。手動でインストールすると、複数のマシンに同じ環境を整えるのはかなりの手間になります。
  • 他人に分析結果を共有するだけなら、notebook ファイル (.ipynb) を GitHub とかにアップすれば十分です。しかし、実験の再現や条件を変えて再施行などを行う場合、環境を合わせておかないとバグったりします。ローカルで環境構築しちゃうと、「他人の環境で動かない!」なんてことになりがちです。*1

どちらの問題も、Docker なら Dockerfile を渡せば良いので楽に解決できます。 実際、仕事でもこの Docker image に社内 API サーバのクライアントライブラリ等を追加してカスタマイズしたものを使用しています。

チュートリアル

これ以降は、色々なパッケージを扱うので、Docker 環境を前提とします。ローカルで環境構築しても README.md に書いてあるパッケージをインストールすれば、動くはずです。 基本的には、OCaml の対話環境と同じなので、Jupyter 特有の部分に焦点を当てつつ、色々な処理をやらせてみようと思います。

https://github.com/akabe/docker-ocaml-jupyter-datascience/tree/master/notebooks に色々な例を上げています。Jupyter 上で見たい人は

$ git clone https://github.com/akabe/docker-ocaml-jupyter-datascience
$ cd docker-ocaml-jupyter-datascience/notebooks
$ docker run -it -p 8888:8888 -v $PWD:/notebooks akabe/ocaml-jupyter-datascience

とすると、色々な notebook を実行できて楽しいと思います。

この例の中では、 formant_estimation_by_AR.ipynb が一番面白く、かつ実用的なノウハウが詰め込まれています。 データのダウンロード・読み込み・前処理・分析・可視化の全工程とその解説を1ファイルで簡潔にまとめています。 ただ、数学的なところを説明するのが面倒なので、今回は、簡単な数値計算と可視化だけやってみます。 要望が多ければ、解説するので、Twitter で @ackey_65535 に声をかけてください。

1. ノートブックの作成

コンソールに表示された URL をブラウザに貼り付け、画面右上の、New というボタンを押すと、OCamlPython が選択できるようになっているはずです(バージョンは人によって違うかもしれません)。 ここで、OCaml をクリックします。 間違っても Python は押しちゃダメだ!そっちには静的型検査のない暗闇が待っている…!

f:id:aabe0:20171207225151p:plain

OCaml の世界に正しく入り込めた皆さんは、下のような画面を目にすることでしょう。 Logout ボタンの下には OCaml と表示されていますね?

f:id:aabe0:20171207225621p:plain

2. 対話環境を使ってみる

まずは、OCaml 対話環境としての機能を試してみます。 In [ ] の右側のテキストボックスに適当な OCaml コードを貼り付けて、Shift+Enter を押してみてください。

f:id:aabe0:20171207230135p:plain

こんな感じで実行結果が表示されます。ちなみに、この OCaml コードを書いたテキストボックスを「セル」と呼びます。

もう少し、数値計算っぽいことをやってみましょう。 皆さん、ランダムウォークはご存知でしょうか? まず、0 からスタートして、前回の値に +1 するか -1 するかを確率 1/2 で決めながら、時系列データを作っていきます。 なんだか酔っぱらいが歩いていてるみたいですね。 OCaml で書くとこんな感じ。

f:id:aabe0:20171207231124p:plain

ちゃんと動きましたか?

でも、これだけだと、シンタックスハイライトが効くだけの単なる対話環境って感じですよね。 Jupyter の真髄はここからです。

3. グラフの描画

データを作ると、可視化したくなりますよね?そんな時のために、jupyter-archimedes という簡単な2次元チャートライブラリが用意されています。 このライブラリは Archimedes という OCaml 製のチャートライブラリを Jupyter 上で使えるようにしたものです。

早速、これで先程のランダムウォーク系列を可視化してみましょう。

f:id:aabe0:20171207231941p:plain

素晴らしいことに、画像が notebook に埋め込まれて表示されます。 これは、コンソールの対話環境では真似できない機能ですね。 一気に、データ分析してる感が出てきました!画像表示しただけなのに!分析してないのに!テンション上がります!

4. Markdown で説明を書く

セルを選択(セル周囲が枠線で囲まれた状態)で、画面上部のドロップダウンからセルの種類を Code から Markdown に切り替えます。

f:id:aabe0:20171207233158p:plain

Markdown cell では、こんな感じで Markdownシンタックスハイライトが効くようになります(MathJax の数式も使えます)。

f:id:aabe0:20171207234244p:plain

入力が終わって、Shift+Enter を押すと、Markdown が HTML として表示されるようになります。

f:id:aabe0:20171207234329p:plain

Markdown cell はダブルクリックすると、再度編集可能になります。

Markdown cell も、コンソールの対話環境にはない機能です。 データ分析系の作業をやっていると、コメントに表や数式を入れたくなりますが、この Markdown cell はその要望に答えられる機能です。 個人的には、

  • ノートブックを他人と共有するときは、Markdown の説明をメインにして、コードを併記する書籍スタイルにする
  • 分析結果だけほしい時(ノートブックを共有しない場合)は、コードをメインに Markdown をコメント代わりにする

という感じで書いています。

5. セルの移動

選択中のセルを移動させることもできます。上のメニューの矢印ボタンを押してみてください。

f:id:aabe0:20171207234743p:plain

他にも、削除・挿入・コピーなどができるので、ノートブック全体を見やすくいい感じに成形できます。

6. ノートブックの保存

最終的に、いい感じにまとまったら、Ctrl+S でノートブックを保存します。画面上にノートブックのファイル名が表示されています。

f:id:aabe0:20171207235909p:plain

ここをダブルクリックすると、ファイル名を編集できます。

f:id:aabe0:20171208000041p:plain

画面左上の OCaml ロゴをクリックすると、ファイル一覧に戻ります。

f:id:aabe0:20171208000206p:plain

さっき作ったノートブックが「Running」と表示されています。これは、「バックグラウンドで OCaml Jupyter カーネルが起動中だよ」という意味です。 左のチェックボックスにチェックを入れて、カーネルをシャットダウンすることができますが、対話環境で保持していた変数の値などは消えてしまいます。

しかし、コードと実行結果(対話環境のレスポンスや埋め込まれた画像)や Markdown の内容は保存されているので、消えません。 カーネルや Jupyter サーバ自体を停止させても、保存済みの実行結果を閲覧したり、コードの再実行ができます。 対話環境の結果やコードを整理・保存して、後から読み返せるのはなかなか便利です。

このノートブックは .ipynb という拡張子ですが中身は JSON、つまりただのテキストファイルなので、git などで管理できます。 ノートブックを編集・再実行すると無駄に差分が大きくなるので、merge とかはやめたほうが良いですが、他人と共有しやすいのは良いところです。 しかも、GitHub は Jupyter notebook を人間可読な形で表示してくれます。 業務で GitHub を使っているなら、「分析したで、URL これ!」って言って渡すだけで誰でも結果を見られます。超便利です。

まとめ

すごく基礎的な所だけ、Jupyter notebook の使い方を説明しました。 急いで書いたので、読みにくいところがあるかもしれません。 もっと色々い知りたいことがあったら、Twitter @ackey_65535 で声をかけてください。 バグとか見つけたら、GitHub Issues に報告していただけると嬉しいです(PR はもっと助かります)。

*1:今の所職場で私以外に OCaml を使っている人がいないので、幸か不幸かこの問題に直面したことがありません。あぁ、OCamler と仕事したい...