Pythonの(といいつつCで速度を稼いでいる)Numpyという数値計算用のライブラリがあり、広く使われているのは誰もが知るところだと思う。実際には、WebエンジニアからはPandasといったフレームワークを被せて使うことが多い。
そんな中、NumpyのCommon Lisp実装があることを知った。その名もNumcl。
READMEなどを読む限り、Numclは以下のような方針で実装されているようだ:
- 可能な限りNumpyのAPIを模倣する。ところどころよりLispyな方法を使う。
- Portabilityを重視して、Numpyのように新たなデータ構造は導入せず、Common Lisp標準のarrayを使う。(Common Lispは、デフォルトで多次元配列をサポートしている)
- 可能な限りCommon Lispの既存の関数を拡張するような名前を使う。将来的に標準関数を置き換えることもできるようにするため。
ネイティブなCommon Lisp上で実装されているのが面白いけれど、Common Lispは意外と速い(失礼)ので、それでもかまわないようだ。
実際に遊ぶ
installation
いつも通り、Numclはquicklispからインストールできる:
(ql:quickload :numcl)
numclは大きめのヒープを要求するので、処理系をあらかじめ設定しておく必要があるかもしれない。Roswellを使っている場合は以下のコマンドをシェルで実行すればよい:
$ ros config set dynamic-space-size 4gb
これで4GBのヒープが利用可能になる。
使う
NumpyのAPIに準拠しているので、当然といえば当然だがNumpyと同じような動作をする。
(defpackage :numcl-exercise (:use :cl)) (in-package :numcl-exercise) ;; numpy同様にarangeを使うことができる (defparameter arr (numcl:arange 100)) ;; => #(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ;; 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 ;; 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 ;; 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99) ;; numpy同様にスカラー演算を行うことができる ;; ただし、演算子の名前空間はnumclのもとにある (defparameter arr2 (numcl:+ arr 1)) ;; => #(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 ;; 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 ;; 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 ;; 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100) ;; numpy同様に配列同士の演算を行うことができる (defparameter arr3 (numcl:* arr arr)) ;; => #(0 1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361 400 441 484 ;; 529 576 625 676 729 784 841 900 961 1024 1089 1156 1225 1296 1369 1444 1521 ;; 1600 1681 1764 1849 1936 2025 2116 2209 2304 2401 2500 2601 2704 2809 2916 ;; 3025 3136 3249 3364 3481 3600 3721 3844 3969 4096 4225 4356 4489 4624 4761 ;; 4900 5041 5184 5329 5476 5625 5776 5929 6084 6241 6400 6561 6724 6889 7056 ;; 7225 7396 7569 7744 7921 8100 8281 8464 8649 8836 9025 9216 9409 9604 9801) ;; numpyでおなじみの操作が用意されている (defparameter arr4 (numcl:reshape arr '(9 9))) ;; => #2A((0 1 2 3 4 5 6 7 8) ;; (9 10 11 12 13 14 15 16 17) ;; (18 19 20 21 22 23 24 25 26) ;; (27 28 29 30 31 32 33 34 35) ;; (36 37 38 39 40 41 42 43 44) ;; (45 46 47 48 49 50 51 52 53) ;; (54 55 56 57 58 59 60 61 62) ;; (63 64 65 66 67 68 69 70 71) ;; (72 73 74 75 76 77 78 79 80)) ;; numpyにおけるrange accessはarefを使う ;; 行が最初に指定される (numcl:aref arr4 4) ;; => #(36 37 38 39 40 41 42 43 44) (numcl:aref arr4 '(4 6)) ;; => #2A((36 37 38 39 40 41 42 43 44) (45 46 47 48 49 50 51 52 53)) ;; 全て指定するにはtを使う (numcl:aref arr4 t 3) ;; #(3 12 21 30 39 48 57 66 75) ;; transposeもある (numcl:transpose arr4) ;; 行列積(matrix multiply) (numcl:matmul arr4 (numcl:transpose arr4)) ;; 平方根 (numcl:sqrt arr4)
手元に巨大なデータセットがないので、手元でベンチマークをすることができないのだが、Common Lispの場合速度が必要な箇所を最適化したりといった手法が適用できるのでそれなりの速度が出るのだろうと思う。
また、特殊なデータ形式ではなくCommon Lispの標準的なarrayを使っているので、CSVなどの入出力関連の関数は提供されておらず、別途どこかにあるパーサを持ってきて、arrayに変換してくれということらしい。
PandasのCommon Lisp実装、Teddy
ところで、NumpyのCommon Lisp実装があるならPandasのCommon Lisp実装もあるのではないかと思って調べたところ、Teddyというのがヒットする。
だが、2年前に開発が止まっているし*1、シンボルのexportまわりがおかしいのでちゃんと動かない。がんばれば動くみたいな完成度の状態になっている。
Common Lispの計算系ライブラリを他にも探すと、他にも機械学習系のライブラリであるMGL(こちらはNumclを使わずcl-cudaに依存している)などもあり、細々と更新されているようだ。
Common Lispには強力なREPLがあるので、このまま頑張ってほしい。
*1:Numclも8ヶ月前に更新が停止しているが…………