はじめに
numpy の計算効率化に取り組む機会があったのですが、
その中で dtype の重要さを学びました。
個人の備忘録としては勿論、誰かの役に立つといいな〜と思い
書いたものが本記事となります。
パフォーマンス前後比較
ザックリ書くと、
- サイズにして (1万,400) @ (400,55万) 程度の行列計算を複数個行う
- それらの積集合を取る
という処理を行う必要があったのですが、 numpy の dtype 変更だけで
以下のようにだいぶ改善できました。(最初はどんだけ酷かったんだよ…というね)
項目 | before | after |
---|---|---|
処理時間 | 70分 | 10分 |
使用メモリ | 100GB超 | 30GB強 |
※ メモリはMacアクティビティモニタの「メモリ」の値より。
(jupyter カーネルが落ちるレベルだったが、余裕で生き残るようになりました)
以下、どんな変更をしたか書いていきます。
実施したこと
高速化について
numpy.array の dtype を float32 or float64
にする
numpy の行列計算では、 BLAS というものを使っているらしい。
で、どうやらこいつが上記データ型だといい仕事をしてくれる模様。
(参考リンクの一番上の記事参照)
自分の場合、最初 int になっていたのですが、.astype(np.float32)
で
float32 に型を変えただけで処理時間がなんと70分 → 10分に短縮されました!!
【追記】
一晩寝て読み直したら状況説明が不十分に思ったので、少し補足します。
元はpd.get_dummies
で one hot encoding した後に、 .values
で
numpy.ndarray を取り出し計算していました。
このやり方だとデータ型が uint8 になるのですが、これを float32 に
変えてやることで高速化を実現出来ました。
コードにすると、こんな感じです。
pd.get_dummies([PandasのSerise]).astype(np.float32).values
省メモリについて
可能なら 行列の値を bool 値
にする
前述の float 化で処理は爆速になりましたが、メモリが爆食いされてしまいました。
こまめに不要なオブジェクトをdel
する等、抵抗をしていたのですが大きな改善に至らず。
しかし計算結果を bool に持たせ直すことで、めちゃくちゃメモリを削減できました。
(bool型 と int, float 型で予め確保しておくメモリが違うっぽいですね)
…でも、これをやると速度は少し失われます。メモリと速度、どっちをとるか?ケースバイケースで。
【補足】
python では1/0 と True/Falseに以下のような関係が成り立ちます
。
なので、1/0 で表現出来る行列(例えば one hot encoding 行列とか) は、
bool 値でも表現できます。
そしてあわせ技
以上、2点を組みわせただけで、大幅に効率化出来ました。
最終的には、こんな処理の流れになりました。
- float32 型で行列計算を行う
・arrayA = float32行列 @ float32行列
- 得られた結果を bool 型に変換する
・arrayA = (arrayA >= 1)
とかやるだけ - それらの積集合を取る
・result = arrayA & arrayB & arrayC
みたいな
感想
float の方は色々なケースで使えそうだけど、 bool の方は限られるかな?
結果が 0/1 で表すことが出来るケースじゃないと適用できないと思うので。
その他
- Cpython で numpy 型指定して書いてやれば超早くなるという記事も見ました。
が、今回そこまでは実際に試しませんでした。
必要となる機会があれば、試してみたいと思います。 -
Intel MKL の方がパフォーマンスが良いらしい ですが、実行環境がインテルCPU
とも限らないので、OpenBLASで自分は実行しました。
参考リンク
- https://stackoverflow.com/questions/19839539/how-to-get-faster-code-than-numpy-dot-for-matrix-multiplication
- https://www.benjaminjohnston.com.au/matmul
- https://stackoverflow.com/questions/18743397/python-numpy-np-int32-slower-than-np-float64
- https://insilico-notebook.com/python-blas-performance/
ついでに
2020/08/26 追記。
パフォーマンスとは微妙に違うかもだけど、
せっかくなので記録しておく。
ちょっとした理由があり、lightgbmのソース読んでいた所、
pandas DataFrame をデータに渡した時は
内部で np.float32 にキャストするのを見つけた。
https://github.com/microsoft/LightGBM/blob/877d58fac74731a5feba8d6ca3bb0cc97d154eb0/python-package/lightgbm/basic.py#L399-L400
なので lightgbm & pandas コンビを使う場合、
pandasデータ型はカテゴリ・文字列以外は何も考えず
全部 float 型にして良さそうにみえる。
(「この変数はint型にしよう」とか考えなくて良さそう)