機械学習のための実験プログラムについて
Aizu advent calendar 2017 8日目の記事である。
まえがき
本稿では機械学習(または数値解析)における「実験を行うプログラムの作成」から「実験結果資料の作成」までの流れとプログラムの構成を、僕がどのようにやっているかを紹介していく。 僕はPython3を用いて研究を進めているので、いくつかPythonライブラリの紹介もする。 書いているうちに去年書いた記事と似ていて新規性に欠ける気がしてきてけど、今回は過去記事の詳細版ということで。
本題
機械学習アルゴリズムの精度確認のための実験を行う際には、基本的に下記の4つのプログラムに分けて書くようにしている。
- 学習アルゴリズムのプログラム
- 入力として学習データ及びパラメータを受けとり、それを元に1.のプログラムを動かし、その結果を出力するプログラム
- 2.を行うために共通な処理を行うためのプログラム
- 入力として2.の結果を受け取り、グラフ画像や表を出力するプログラム
つまり、下の図になる。
また、この図を元にしたディレクトリ構成は下記のようになる。
├── docs │ ├── experiment_01.md │ └── experiment_02.md ├── experiment_01 │ ├── algorithm.py │ ├── experiment.py │ ├── graph_generator.py │ ├── utils.py │ ├── graphs/ │ │ ├── {dataset_name}_{prameters}.png │ │ └── {dataset_name}_{prameters}.eps │ └── results │ ├── {dataset_name}_{prameters}.csv │ └── summary.md └─── experiment_02 ├── hogehoge.py ├── fugafuga.py .......
このようにプログラムを分割しておくと、追加実験による仕様変更に比較的少ないプログラム変更で対応できるかなと思う。
登場するものは、以下の6つである。
これら6つについてそれぞれ書いていく。
入力データセット
僕の研究では、公開データセットを使っている。 基本的には、UCI machine learning repositoryや、LIBSVM Dataで公開されているものである。 データのフォーマットがcsvやlibSVM形式などの一般的な形式だと、大抵読み込むライブラリ(scikit-learnなど)が存在している。 しかし、たまに独自フォーマット(僕が知らないだけかもしれないが)のデータががあったりする。(あるいは2クラス分類問題の教師信号が{-1, 1}でない場合がある)その場合は、それ用のプログラムを書かねばならないので、非常にめんどうだったりする。これに限っては頑張るしかないかなと思っている。
Pythonでscikit-learnを使用している場合は、sklearn.datasets.fetch_mldata()
という便利な関数がある。
fetch_mldata
これはデータセットの名前の文字列を引数に与えるとmldata.orgから、ダウンロードしてきて、データセットのオブジェクト(Bunch object, sklearn.datasets.base.Bunch)で返してくれる。デフォルトでは、自身のホームディレクトリに~/scikit_learn_data
というディレクトリが生成され、その中にデータセットのファイルが入る。
まとめると、とりあえずmldata.orgを探して、あったらfetch_mldata()
、なかったらUCIやLIBSVMから持ってきて、読み込んでBunch objectを返すって流れだと良いと思う。
scikit-learnを使わない場合も同様にデータセットを置いとくディレクトリを作っといて、データセット名を指定すると、データを特定の形式で持ってくるというプログラムを作ればよい。
入力パラメータ
パラメータの入力方法は実験プログラムを実行する際のコマンドライン引数にしている。 僕の場合は標準ライブラリのargparseを使っている。他にもpython-fireやClickがあるが僕はまだためしていない。
複数の入力パラメータを用いて、それぞれで実験を試すことはよくあるので、使用したパラメータを実験結果の出力ファイル名にしておくと、わかりやすくて良いと思う。
実験プログラム
上記で紹介したデータセットの読み込み処理と、コマンドライン引数を用いたパラメータ指定は、この実験プログラムに記述する。 つまり、実験を行う際にはこのプログラムを実行し開始することになる。 実験プログラムは、入力にデータセットとパラメータを受けとり、アルゴリズムのプログラムやユーティリティプログラムを適宜呼ぶような構成になる。(アルゴリズムは複数の場合もある)
ユーティリティプログラム
Datasetの読み込みや交差検定、データの前処理など、プログラム中でよく使う関数はutilityとしてまとめたり、ライブラリ化すると便利である。
アルゴリズムのプラグラム
このプログラムは前節の実験プログラムから呼ばれることになる。
scikit-learnに準拠させる場合はBaseEstimator
とClassifierMixin
を継承させて、学習用の関数fit()
と推論用の関数predict()
を実装させればよい。
プログラムにおいて(これに限らず)、必要に応じてテストコードを書くとより良い。doctestを用いると関数定義の下に簡単にテストを書くことができるので、おすすめである。
また、ここもアルゴリズムのプラグラムとして独立させておき、ライブラリ化すると便利である。
参考
sklearn準拠モデルの作り方 - Qiita
GitHub - masaponto/Python-ELM
GitHub - masaponto/Python-DBM
実験結果
実験結果はcsvファイル、またはmarkdownやtex形式の表にして実験プログラムから出力するようにしている。これは、後のグラフ生成や資料作成を楽にするためである。また、実験結果は、指定ディレクトリに保存すると同時に、ぼっちslackに送るようにしている。簡易的なバックアップやログにもなるし、計算用のサーバで実験を行った最には結果を通知させる役割にもなり非常に便利である。slackに送るにはslackteeを用いると良い。slackteeは標準出力した文字列を任意のslackに投稿する機能を提供する。実験プログラム実行の際に、$ python experiment.py | slacktee.sh -c <chanel-name>
とすれば良い。これがなかなか便利で、過去に僕はkamebotを作ったりしたが、全然使わなくなってしまった。ちなみに、kamebotはPythonコードの中の関数にデコレータをつけることで、その関数内の標準出力を指定slackに送るというものである。
一方、表の生成にはpython-tabulateが便利である。markdownやtexなどの表形式に対応しているので、何も考えずにそのまま研究資料に貼り付けるだけで済む。
参考: ぼっちsalck
グラフ生成
結果のcsvファイルの読み込みには、Numpyのnumpy.load_txt()
という関数が便利である。ファイルの文字列を引数にとり、Numpy array形式にして返してくれる。また、pandasのpandas.read_csv()
で読み込むと、csvにヘッダとして文字列が入っていても問題なく読み込むことができる。
グラフの生成にはmatplotlibを使う。この際、epsファイル(などのベクター画像)とpngファイルを生成するようにしている。epsファイルは論文用、pngファイルはmarkdownドキュメントとスライド用である。
グラフを生成する際に注意することは2つある。グラフの軸のラベルを必ずつけることと、グラフのタイトルはつけないことである。 前者、後者ともに読者にわかりやすくするためである。後者の方は、グラフ画像にタイトルをつけると、論文に画像として読み込んだ際に、latexのfigureのcaptionとでグラフの名前の記述が重複してしまい読みにくくなるからである。
実験資料
実験結果として他人に伝えるためには、得られたものを資料にまとめなければならない。
僕はmarkdwonで資料を書いて、pandocを使って、pdfを生成するようにしている。
pandocとはドキュメント向けのファイルを様々な形式で変換できるツールである。また、pdfへの変換にはlatexを経由するので、数式も対応する。(latexの形式で数式を記述する)
適当な資料なら$ pandoc document.md -o document.pdf
でpdfが生成されてOKである。
しかし、表紙のついた洒落た資料にしたいときもある。
その場合は、下記のテンプレートと使うと良い。
これを使ってpandoc on Dockerで資料を作るためのキットを作ってみた。
Docker環境さえあればどこでも$ make
で動く。
おわりに
3年生のときに研究をはじめて、実験プログラムを作りはじめたとき、よく考えずにファイルを作っていたため、ディレクトリの構成がごちゃごちゃになっていた。 そういう過去があったので、本稿のような記事があれば少し役立つかなと思い、書くことにした。 今でも、追加実験が複数続いたりすると、構成がわかりにくくなってきてしまうことがあり、難しいなと感じている。 Pythonのようにゴリゴリ書けてしまう言語だと余計そうなりやすいのかもしれない。 大切なのは、できるだけ共通部分とそうでない部分に分割し、前者をライブラリ化していくことだと思う。
本稿が機械学習や数値解析を用いた研究を進めていく際の参考の一つになるとよいと思う。
そんな感じで修論がんばるぞい!
おまけに修論用Dockerを置いておく。
修論用 · GitHub