リコメンドエンジンの仕事で行列の内積を取る必要があったのでざっと触ったのでメモ。
やりたいことは以下のイメージです。
\begin{pmatrix}
1.0 & 2.0 & 3.0 \\
4.0 & 5.0 & 6.0
\end{pmatrix}
\times
\begin{pmatrix}
1.0 \\
2.0 \\
3.0
\end{pmatrix}
=
\begin{pmatrix}
14.0 \\
32.0 \\
\end{pmatrix}
※以下では楽なのでscalaで書いていますが、僕は仕事では、がっつりチューニングしたい&他ロジックとの兼ね合いでjavaで書いています。
breeze
下記のような感じで書くと、
val matrix_A = DenseMatrix(Array(1.0, 2.0, 3.0), Array(4.0, 5.0, 6.0))
println("matrix_A")
println(matrix_A)
val vector_B = DenseMatrix(Array(1.0), Array(2.0), Array(3.0))
println("\nvector_B")
println(vector_B)
val result = matrix_A * vector_B
println("\nresult")
println(result)
このような出力をしてくれます。
matrix_A
1.0 2.0 3.0
4.0 5.0 6.0
vector_B
1.0
2.0
3.0
result
14.0
32.0
netlib.ddot
下記のような感じで書くと、
val blas: BLAS = BLAS.getInstance
val (a, b) = makeRandomMatrixAndVector(10, 3)
val vector_A = Array(1.0, 2.0, 3.0)
println("vector_A")
println(vector_A.toSeq.mkString(","))
val vector_B = Array(4.0, 5.0, 6.0)
println("\nvector_B")
println(vector_B.toSeq.mkString(","))
val dotProduct = blas.ddot(vector_A.length, vector_A, 1, vector_B, 1)
println("\ndotProduct")
println(dotProduct)
このように出力してくれます。
vector_A
1.0,2.0,3.0
vector_B
4.0,5.0,6.0
dotProduct
32.0
netlib.dgemv
以下のように書くと、
val blas: BLAS = BLAS.getInstance
val m = 3
val n = 2
val matrix_A = new Array[Double](m* n)
matrix_A(0)=1; matrix_A(3)=2
matrix_A(1)=0; matrix_A(4)=2
matrix_A(2)=3; matrix_A(5)=3
val vector_B = new Array[Double](n)
vector_B(0)=1
vector_B(1)=2
//結果は返り値でなくここに格納される
val result = new Array[Double](m)
blas.dgemv("N", m, n, 1.0, matrix_A, m, vector_B, 1, 0.0, result, 1)
println("\nresult")
result.foreach(println)
このように出力します。
result
5.0
4.0
9.0
パフォーマンス
(Cモジュール導入は、デプロイ面倒なのでまだやってません)
ソースコードを雑に置いています。
https://github.com/uryyyyyyy/matrixVectorSample
あまりチューニングできていませんが、僕の仕事の箇所でいうとnetlibとbreezeの差よりも、
- 前後の処理の計算量のオーダー
- 無駄なオブジェクト生成によるメモリ消費(GCタイミング)
- double値などプリミティブ型のboxing/unboxingコスト
が支配的でした。プロダクトによって自然な形で取り入れられる形のものを使うのが良さそうです。(Javaの方がチューニングしやすいかなって個人的には思います。)
現場からは以上です。