(追記)ここでやってること、Pandas使えば簡単にできるやん…
Pythonで書いている研究用のプログラムに並列計算を入れようと思った。ご存知のようにPythonにはGlobal Interpreter Lockという仕組みがあって「マルチスレッド」なプログラムを書いてもマルチコアCPUの恩恵は受けられない。細かいことは正直わかんないんだけど、あくまで1つのプロセスの上でやりくりしてくれるだけらしい。I/OとかCPUを使わない処理がある場合は早くなるのかな?
しかし最近ではPythonにも'multiprocessing'という標準ライブラリがあり、一回のコード実行で「マルチプロセス」な並列計算をすることができる。
なかでもmultiprocessing.Poolというのを使うと、超お手軽に並列計算ができる。
使い方は簡単で、以上のようにするだけ。するとresults_setの中にshori関数の結果がリストとして格納される。
並列計算させる関数に共通する引数をあげたい場合は、リスト内包表記でやっているようにmapに渡すイテレーターの要素をタプルにして入れておく。
たとえばサイコロの出る目の期待値は?という問題にシミュレーションでせまろうとするならば、サイコロを同時にたくさん振って出た目の数字の平均を取ればいい。これに並列計算を導入すると
このPoolは同時に何個プロセスを走らせるかをCPUコア数などから自動設定してくれるらしく、nの値によらず環境に応じた並列数で計算してくれる。
さきほどのサイコロ問題の場合、実行したPCのCPUはCore i5 4670Tという4コア4スレッドのCPUなので、おそらく4並列で動いている。プログラムもだいたい3~4倍早くなっている。
で、ここからが本題。
自分の書いていたプログラムの場合、計算結果をdict形式で返していた。このdict内には使用したパラメーターと結果が両方格納されている。
{'param1': 0.1, 'param2': 2.5, 'res1': 5.4, 'res2': 8.2} ・・・αとする
という具合。これが一回の試行あたり複数パラメーターで実行されるので、
[α, α', α''.......]
という風にdictのlistとして返ってくるようになっている。(これが"shori"関数だと思ってもらって)
このため、並列計算した結果としては
[
[α, α', α''.......],
[α^6, α^', α^''.......],
...........................
...........................
]
]
と、dictのlistをネストしたlistというようにかえってきてしまう。
一回の試行では各パラメーター一回ずつしか計算しないのでネストされたリストの中身は全部異なるものだけれども、並列計算それぞれに渡すパラメーターは共通。そして自分がやりたいのは、共通のパラメーターで行った計算の結果を平均したいというものだった。
上のつたない例で言うとαとα^、α'とα^'、それぞれの平均をだしたいということ。
SQLiteのDBを作ってしまったほうが楽だったのかもしれないけど、そこはPythonだけでやることにした。
1. リストのFlat化
まず、ネストがあるとややこしいのでリストを
[α, α', α''...α^6, α^', α^''......]
形式に。これは"Python flatten"とかで検索。
2. キーの設定
αでいうと、params1とparams2が集計のキーであって、res1,res2は集計対象の値である。
なので、集計キーのdictをlistとしてつくる。
3. 検索
3-1. 比較関数
比較対象のキーに対応する値さえ一致していればその辞書を受理する比較、というのは一筋縄ではいかない。
{'a': 0.1}を使って{'a': 0.1, 'b': 0.2}を探すことはPython組み込み関数ではできない…はず。
なので泥臭く。
3-2. 一致する結果を取得、集計
あとはcompで一致するdictを集計して、持っている値の平均をとる。
要するにdictのlistをテーブル、dict1レコードとしてみてSQLのAVGみたいなことをしたということになる。
リレーションを扱う能力はないのでリレーショナルデータベースではないわけだけど、軽いデータ集計にこまごました処理書きたくないけどリレーショナルなデータじゃないしSQLite持ち出すまでもないな、という需要が割と個人的にあるんだけど、世間ではどうなんだろうか。今回はAVGだけだけど、他の機能もつけてライブラリ化の練習も兼ねてライブラリ化してみようかな。