『ゼロから作るDeep Learning』をScalaで実装する(その1) - Breezeで行列演算

Deep Learningを業務でがっつり使うことは(少なくとも当分は)ないんだけど、さすがに知らないでいろいろ語るのはマズいので、巷で良書と評判の『ゼロから作るDeep Learning』をやろうと思います。

とはいえ今更Pythonでやるのもつまらないので、せっかくだから新しく覚える言語でやろう、ということでScalaで書くことに。

本書の目標とするところは、ゼロからディープラーニングを実装することでした。そのため、外部のライブラリは極力使用しないというのが方針ですが、次の2つのライブラリは例外として用いることにします。ひとつはNumpy、もうひとつはMatplotlibというライブラリです。 (p.2-3)

Scalaで可視化やるつもりはないのでmatplotlibのところは飛ばせばいいんですが、numpy相当の行列演算をスクラッチで書くのはつらいのでScalaの行列演算(以外もあるけど)ライブラリであるところのBreezeを使うことにします。

以下、1章のnumpyの部分をBreezeで書き直します。

1.5.0 インストール

とりあえずREPLを使います。REPLで外部ライブラリ使うにはこうすればいいらしい。

$ sbt
set libraryDependencies += "org.scalanlp" % "breeze_2.11" % "0.12"
set resolvers += "Sonatype Releases" at "https://oss.sonatype.org/content/repositories/releases/"
set scalaVersion := "2.11.6"
console

https://github.com/scalanlp/breeze/wiki#fast-installation

1.5.1 インポート

scala> import breeze.linalg._

1.5.2 配列(ベクトル)の生成

scala> val x = DenseVector(1.0, 2.0, 3.0)
scala> println(x)
DenseVector(1.0, 2.0, 3.0)

わざわざDenseと明示してるってことはSparseもあるに違いない。

1.5.3 算術演算

scala> val x = DenseVector(1.0, 2.0, 3.0)
scala> val y = DenseVector(2.0, 4.0, 6.0)
scala> x + y
res30: breeze.linalg.DenseVector[Double] = DenseVector(3.0, 6.0, 9.0)
scala> x - y
res31: breeze.linalg.DenseVector[Double] = DenseVector(-1.0, -2.0, -3.0)
scala> x :* y
res32: breeze.linalg.DenseVector[Double] = DenseVector(2.0, 8.0, 18.0)
scala> x :/ y
res33: breeze.linalg.DenseVector[Double] = DenseVector(0.5, 0.5, 0.5)
scala> x / 2.0
res34: breeze.linalg.DenseVector[Double] = DenseVector(0.5, 1.0, 1.5)

要素ごとの操作については演算子を変えてますね(:*:/)。そもそもベクトル同士の(内積ではない)積とか商とかってなんやねんって話なのでこういう方針は好き。

1.5.4 N次元配列

scala> val A = DenseMatrix((1, 2), (3, 4))
scala> println(A)
1  2  
3  4  
scala> (A.rows, A.cols)  // A.shapeがやりたい
res40: (Int, Int) = (2,2)
scala> A.getClass  // A.dtypeがやりたい
res41: Class[_ <: breeze.linalg.DenseMatrix[Int]] = class breeze.linalg.DenseMatrix$mcI$sp

numpyのA.shapeA.dtypeに直接相当するメソッドはない模様。

scala> val B = DenseMatrix((3, 0), (0, 6))
scala> A + B
res42: breeze.linalg.DenseMatrix[Int] =
4  2
3  10
scala> A :* B
res44: breeze.linalg.DenseMatrix[Int] =
3  0
0  24

numpyのA * B(要素ごとの積)に相当するのはA :* Bということに注意。

A * Bすると普通に行列同士の積(numpyでいうところのA.dot(B))になりnumpyよりこっちの方が明らかに正しいだろ感。

scala> println(A)
1  2  
3  4  
scala> A * 10
res46: breeze.linalg.DenseMatrix[Int] =
10  20
30  40

1.5.5 ブロードキャスト

scala> val A = DenseMatrix((1, 2), (3, 4))
scala> val B = DenseVector(10, 20)
scala> A(*, ::) :* B
res49: breeze.linalg.DenseMatrix[Int] =
10  40
30  80

Breezeではブロードキャストは暗黙には行われません。「行方向のブロードキャストである」ことを明示するためにA(*, ::)とします。

他にも、BはMatrixじゃなくてVectorとして定義する必要があったり、最終的にはベクトルとベクトルの要素ごとの積になるので:*にする必要があったりといろいろ注意が必要。

1.5.6 要素へのアクセス

scala> val X = DenseMatrix((51, 55), (14, 19), (0, 4))
scala> println(X)
51  55  
14  19  
0   4   
scala> X(0)
<console>:12: error: could not find implicit value for parameter canSlice: breeze.linalg.support.CanSlice[breeze.linalg.DenseMatrix[Int],Int,Result]
              X(0)
               ^
scala> X(0, ::)
res58: breeze.linalg.Transpose[breeze.linalg.DenseVector[Int]] = Transpose(DenseVector(51, 55))
scala> X(0, 1)
res59: Int = 55

X(0)はできない模様。

scala> for (row <- X) println(row)
<console>:12: error: value foreach is not a member of breeze.linalg.DenseMatrix[Int]
              for (row <- X) println(row)
                          ^
scala> for (i <- 0 until X.rows) println(X(i, ::))
Transpose(DenseVector(51, 55))
Transpose(DenseVector(14, 19))
Transpose(DenseVector(0, 4))

Breezeではベクトルはすべて縦ベクトルなので横ベクトルはTranspose(DenseVector(51, 55))のように表現される。

scala> val X1 = X.toDenseVector
scala> println(X1)
DenseVector(51, 14, 0, 55, 19, 4)
scala> val X1 = X.t.toDenseVector
scala> println(X1)
DenseVector(51, 55, 14, 19, 0, 4)
scala> X1(0, 2, 4).toDenseVector  // X1(0, 2, 4)の返り値はbreeze.linalg.SliceVector型
res11: breeze.linalg.DenseVector[Int] = DenseVector(51, 14, 0)

toDenseVectorでは列方向にベクトル化される。なのでnumpyと同じように行方向でやりたかったらX.tで転置しておけばよい。

scala> DenseVector(X1.toArray.filter(_ > 15))
res23: breeze.linalg.DenseVector[Int] = DenseVector(51, 55, 19)

Booleanを使った要素の抜き出しはできなそう。なので一回Arrayにしてfilterして(何故かDenseVectorにはfilterメソッドがない)またDenseVectorに戻せばよい(これが効率のいいやり方かどうかは知らない)。

以上。次はこれを使ってパーセプトロンを実装する。