ScalaでRやMatlabみたいに行列の操作をおこなう

2013-12-21

この文書はScala Advent Calendar 2013の21日目のために書きました。

Scalaにも行列操作や線形代数の便利ライブラリがあります。
pythonに負けてないぜってことで。
それがScalaNLPです。

1. ScalaNLPとは?

ScalaNLPの「NLP」とは、
「Natural Language Processing」
つまり自然言語処理のことらしいです。
SaclaNLPは自然言語処理と機会学習のための数値解析ライブラリを提供しています。

ScalaNLPには、RやMatlab、Numpyみたいに、ベクトルや行列の操作をかんたんに行うためのクラスとメソッドが用意されています。

ScalaNLPのすばらしい(というか私の大好きな)ところは、数値演算にJNIを使うところです。
OSコマンドをコールして高速に数値演算を行う仕様なのです。
Rでは破綻してしまう大量データ解析を並列的にサクサクやっちゃえます。

2. ScalaNLPのライブラリについて

ScalaNLPプロジェクトには、BreezeとEpicという2つのライブラリがありますが、

  • Breeze・・・機会学習と自然言語処理
  • Epic・・・ハイパフォーマンス統計パーサー(coming soon)

という住み分けになっています。今回はBreezeの方を使います。

Breezeは更に、下記のようにライブラリがわかれています。

  • breeze・・・数値計算や線形代数ライブラリを含む、breezeの基本ライブラリです。
    ※今回はこれを使います。
  • breeze-viz・・・グラフ描画などを行う視覚化ライブラリです。
  • breeze-learn・・・機会学習ライブラリで、現在はNakというライブラリとして提供されています。
    -> org.scalanlp.nak
  • breeze-process・・・自然言語処理ライブラリで、Chalkというライブラリになりました。
    -> org.scalanlp.chalk

3. ScalaNLPの使い方

3.1. ライブラリにパスを通す

ScalaNLPの使い方はScalaNLPのクイックスタートに書いてありますが、一応ここでもさらっておきます。

ビルドシステムとしてsbtを使っている場合は、クイックスタート通りにライブラリの依存関係とリポジトリをbuild.sbtに記します。

  • 安定バージョンを使う場合
libraryDependencies  ++= Seq(
            "org.scalanlp" % "breeze_2.10" % "0.5.2",
)

resolvers ++= Seq(
            "Sonatype Snapshots" at "https://oss.sonatype.org/content/repositories/releases/"
)
  • snapshotを使う場合
libraryDependencies  ++= Seq(
            "org.scalanlp" %% "breeze" % "0.6-SNAPSHOT",
)
resolvers ++= Seq(
            "Sonatype Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/",
)

ビルドシステムとしてgradleを使っている場合は、下記の記述をbuild,gradleに追加します。

repositories {
    mavenCentral()
    maven {
        url "https://oss.sonatype.org/content/repositories/releases/"
    }
}
dependencies {
    compile "org.scalanlp:breeze_$scalaVersion:0.5.2"
}

3.2. さっそく使ってみる

下記のソースコードはGithubにもあげています。
https://github.com/x1-/nlp_sample

3.2.1. 行列をつくる

行列を作ってみます。

package x1.nlp

import breeze.linalg._

object Main {
  def main( args: Array[String] )  = {

    // #1. 2 x 3行列を作成
    //      1.0 2.0 3.0
    //      4.0 5.0 6.0
    val m23 = DenseMatrix(
      (1.0d, 2.0d, 3.0d),
      (4.0d, 5.0d, 6.0d))

    // #2. 3 x 2のゼロ埋め行列を作成
    //      0 0
    //      0 0
    //      0 0
    val m32z = DenseMatrix.zeros[Double](3, 2)

    // #3. 2 x 2の1埋め行列を作成
    //      1 1
    //      1 1
    val m22o = DenseMatrix.zeros[Double](2, 2)

    // #4. 2 x 3の乱数行列を作成
    //      0.8765... 0.23909... 0.61324...
    //      0.51976... 0.80524... 0.44318...
    val m23r = DenseMatrix.rand(2, 3)
  }
}

3.2.2. 演算

行列演算してみます。

    // #5. 行列の全ての要素に0.1足す
    //    1.1  2.1  3.1
    //    4.1  5.1  6.1
    val plusall = m23 + 0.1d

    val m23a = DenseMatrix(
      (2d, 2d, 2d),
      (2d, 2d, 2d))

    val m32 = DenseMatrix(
       (2d, 2d)
      ,(2d, 2d)
      ,(2d, 2d))

    // #6. 行列同士の足し算
    //    3.0  4.0  5.0
    //    6.0  7.0  8.0
    // 要素数が異なるとエラーになります。
    val plus = m23 + m23a

    // #7. 行列の要素ごとの積
    //    2.0  4.0   6.0
    //    8.0  10.0  12.0
    val eprod = m23 :* m23a

    // #8. 行列の積
    //    12.0  12.0
    //    30.0  30.0
    val prod = m23 * m32

    // #9. 行列の全ての要素に5.0かける
    //    5.0   10.0  15.0
    //    20.0  25.0  30.0
    val prodall = m23 * 0.5d

    // #10. 行列の割り算
    //    0.5  1.0  1.5
    //    2.0  2.5  3.0
    val div = m23 / m23a

3.2.3. その他行列操作

    import breeze.numerics._

    // #11. 行列の行同士sumする
    //    5.0  7.0  9.0
    // このメソッドは、Double型の行列でないと失敗しました。
    val sumedr = sum( m23, Axis._0 )

    // #12. 行列の列同士sumする
    //    6.0  15.0
    val sumedc = sum( m23, Axis._1 )

    // #13. 行列のすべての要素をsumする
    //    21.0
    val sumed = sum( m23 )

    // #14. 行列のそれぞれの要素の平方根を計算する
    //    1.0  1.4142135623730951  1.7320508075688772
    //    2.0  2.23606797749979    2.449489742783178
    val sqrted = sqrt( m23 )

    // #15. 行列のそれぞれの要素の自然対数を計算する
    //    0.0                 0.6931471805599453  1.0986122886681098
    //    1.3862943611198906  1.6094379124341003  1.791759469228055
    val logged = log( m23 )

    // #16. 行列の転置を行う(行と列を入れ替える)
    //    1.0 2.0 3.0
    //    4.0 5.0 6.0
    //       ↓
    //    1.0 4.0
    //    2.0 5.0
    //    3.0 6.0
    val t = m23.t

    // #17. 単位行列を作成する
    // 1.0  0.0  0.0
    // 0.0  1.0  0.0
    // 0.0  0.0  1.0
    val dg = diag( DenseVector.ones[Double](3) )

その他、三角関数(sin, cos, tan)や丸め(round)などのメソッドも用意されています。
※breeze.numerics.の名前空間内に定義されているためimport breeze.numerics._が必要です。

4. numpy, Matlab互換チートシートの和訳

作者のDavid Hallさんに許可を頂き、チートシートを和訳しました。
※原文:Breeze Linear Algebra
勝手に、「Rだったらこれ」みたいなのも追加してます。

行列作成 操作

操作 Breeze Matlab Numpy R
ゼロ埋め行列 val c = DenseMatrix.zeros[Double] (n,m) c = zeros(n,m) c = zeros((n,m)) c <- matrix(0,n,m)
ゼロ埋めベクトル val a = DenseVector.zeros[Double] (n) a = zeros(n) a = zeros(n) a <- matrix(0,ncol=n)
1埋めベクトル val a = DenseVector.ones[Double] (n) a = ones(n) a = ones(n) a <- matrix(1,ncol=n)
任意の値で埋めたベクトル val b = DenseVector.fill(n){5.0} a = ones(n) * 5 a = ones(n) * 5 a <- matrix(5,ncol=n)
n×nの分散単位行列 DenseMatrix.eye[Double](n) eye(n) eye(n) diag(1,n)
単位行列 diag( DenseVector(1.0,2.0,3.0) ) diag([1 2 3]) diag((1,2,3)) diag(c(1,2,3))
一行で行列作成 val a = DenseMatrix((1.0,2.0), (3.0,4.0)) a = [1 2; 3 4] a = array( [[1,2], [3,4]] ) a <- matrix(1:4,2,2)
一行でベクトル作成 val a = DenseVector(1,2,3,4) a = [1 2 3 4] a=array([1,2,3,4]) a <- c(1,2,3,4)
一行で一列の行列作成 val a = DenseVector(1,2,3,4).t a = [1,2,3,4]’ a = array( [1,2,3] ).reshape(-1,1) a <- matrix(1:4,nrow=4)

行列操作

操作 Breeze Matlab Numpy R
インデックスで要素を取り出す a(0,1) a(1,2) a[0,1] a[1,2]
ベクトルのサブセットを取り出す a.slice(1,4) or a(1 until 4) a(2:5) a[1:4] a[2:5]
ベクトルを一行取り出す a.slice(1,a.length) a(2:end) a[1:] a[1,]
ベクトルの最後の要素を取り出す a(a.length-1) a(end) a[-1]
行列のカラムを取り出す a(::, 2) a(:,3) a[:,2] a[,2]
行列の形を変えちゃう reshape(a, n, m) a.reshape(3,2)
行列をフラットなベクトルにしちゃう DenseVector(a.copy.data) (Makes copy) a(:) a.flatten() as.vector(a)
行列の下三角部分をコピーする lowerTriangular(a) tril(a) tril(a) a[lower.tri(a)] <- 0
行列の上三角部分をコピーする upperTriangular(a) triu(a) triu(a) a[lower.tri(a)] <- 0
単位行列を表示する diag(a) NA diagonal(a) (Numpy >= 1.9) diag(a)
ベクトル単位の値置き換え1 a.slice(1,4) := 5.0 a(2:5) = 5 a[1:4] = 5 a[1,] <- 5
ベクトル単位の値置き換え2 a.slice(1,4) := DenseVector(1.0,2.0,3.0) a(2:5) = [1 2 3] a[1:4] = array([1,2,3]) a[1,] <- c(1,2,3)
行列のサブセットを置き換える a(1 until 3,1 until 3) := 5.0 a(2:4,2:4) = 5 a[1:3,1:3] = 5 a[2:4,2:4] <- 5
カラム単位のあたい置き換え a(::, 2) := 5.0 a(:,3) = 5 a[:,2] = 5 a[,3] <- 5
複数の行列を行方向にくっつけちゃう DenseMatrix.vertcat(a,b) [a ; b] vstack((a,b)) rbind(a,b)
複数の行列を列方向にくっつけちゃう DenseMatrix.horzcat(d,e) [a , b] hstack((a,b)) cbind(a,b)
ベクトルをくっつけちゃう DenseVector.vertcat(a,b) [a b] concatenate((a,b)) c(a,b)

行列演算

操作 Breeze Matlab Numpy R
要素ごとの和 a + b a + b a + b a + b
要素ごとの積 a :* b a .* b a * b a * b
要素ごとの比較 a :< b a < b (gives matrix of 1/0 instead of true/false) a < b a < b
加算して置き換え a :+= 1.0 a += 1 a += 1 a <- a + 1
乗算して置き換え a :*= 2.0 a *= 2 a *= 2 a <- a * 2
ドット積 dot(a,b) dot(a,b) dot(a,b)
全要素の和 a.sum (note a.sum() doesn’t work, here or below) or sum(a) sum(sum(a)) a.sum() sum(a)
要素の中の最大値 a.max max(a) a.max()
戻り値を最大にする値 a.argmax argmax(a) a.argmax()
切り上げ ceil(a) ceil(a) ceil(a) ceil(a)
切り捨て floor(a) floor(a) floor(a) floor(a)

基本的な関数

操作 Breeze Matlab Numpy R
行方向に加算(カラム単位の和) sum(a, Axis._0) sum(a) sum(a,0) apply(a,2,sum)
カラム方向に加算(行単位の和) sum(a, Axis._1) sum(a’) sum(a,1) apply(a,1,sum)
線形方程式解 a \ b a \ b linalg.solve(a,b) solve(a,b)
転置する a.t a’ a.T t(a)
行列式 det(a) det(a) linalg.det(a) det(a)
逆行列 inv(a) inv(a) linalg.inv(a) solve(a)
擬似逆行列 pinv(a) pinv(a) linalg.pinv(a) ginv(a)
ノルム norm(a) norm(a) norm(a)
固有値 eigsym(a, rightEigenvectors=false) [v,l] = eig(a) linalg.eig(a)[0] eigen(a)$values[1]
固有値 val (er, ei, _) = eig(a) (Separate real & imaginary part) eig(a) linalg.eig(a)[0] eigen(a)$values[1]
固有ベクトル eig(a)._3 [v,l] = eig(a) linalg.eig(a)[1] eigen(a)$values
特異値分解 val (u,s,v) = svd(a) svd(a) linalg.svd(a) svd(a)$d
ランク rank(a) rank(a) rank(a) rank(a)
ベクトルのサイズ a.length size(a) a.size dim(a)
行数 a.rows size(a,1) a.shape[0] nrow(a)
カラム数 a.cols size(a,2) a.shape[1] ncol(a)