Scala でロジスティック回帰
サイボウズ・ラボの @shuyo さんの連載でロジスティック回帰を Python で実装されていたので,Scalaでも実装してみた.第18回 ロジスティック回帰
ロジスティック回帰とは
ロジスティック回帰は,基本的にはパーセプトロンと同様に分類器です.パーセプトロンでは正負で分類するだけで,その値に関わらず(0.000001でも100000同様に扱う)分類します.従って,その分類の”信頼度”,つまり,どのくらい正しいのかを得ることができません.そこで,分類モデルを確率化することで"信頼度"を定義したのが,ロジスティック回帰です.詳細な解説は元記事参照.
ロジスティック回帰の実装
Scalaの実装を Github にアップしてます.以下に簡単な解説.
main.scala
def main(args: Array[String]) { val gold:List[(Double, Double, Double)] = this.makeTestData(100) val logisticregression = new LogisticRegression val w:List[Double] = logisticregression.train(gold) }
sampleでは,二次元データの分類を想定.
教師データを,下記のフォーマットで無作為に100個生成しています.
(x軸座標, y軸座標, 正解:1.0 or 不正解0.0)
wが学習された重みのListになります.つまり,分離平面は次のように表せます.
w(0) * x + w(1) * y + w(2)
LogisticRegression.scala
学習全体
def train(gold:List[(Double, Double, Double)], w:List[Double] = List[Double](0.0, 0.0, 0.0), eta:Double = 0.1 ,limit:Int = 50): List[Double] = { val r = new Random val new_w:List[Double] = this.trial(r.shuffle(gold), w, eta) if (w == new_w || limit <= 0) { return new_w } else { train(gold, new_w, eta*0.9, limit-1) } }
trainは,引数として,訓練データのList, 重みベクトルの初期値,学習率eta,イテレーション回数を取ります.
学習の終了条件は,limit回に達したとき,または,更新が無くなったときとしています.
本来は w の差分を判定にした方が良いです.
また,イテレーションごとに学習率を0.9倍し,再帰呼び出ししています.
private def trial(gold:List[(Double, Double, Double)], w:List[Double], eta:Double): List[Double] = { var new_w:List[Double] = w gold.foreach { p => val feature: List[Double] = this.phi(p._1, p._2) val predict: Double = this.predicate(new_w, feature) new_w = new_w.zip(feature).map((t) => // aw - eta + (predict - aglod._3) * aphi t._1 - eta * (predict - p._3) * t._2 ) } new_w }
trial は,引数として,訓練データのList, 重みベクトルの初期値,学習率etaを取ります.
ここでは,個々の訓練データに対して、featureとpredictを求め,重みwを更新しています.
最後に各関数群
private def predicate(w: List[Double], phi: List[Double]):Double = { this.sigmoid(this.innerProduct(w, phi)) } private def innerProduct(a: List[Double], b: List[Double]): Double = { if (a.size != b.size) { throw new RuntimeException("list size isn't equal.") } val products:List[Double] = a.zip(b).map((t) => t._1 * t._2) products.reduceLeft {(a,b) => a + b} } private def phi(x:Double, y:Double):List[Double] = { List[Double](x * 1.0, y * 1.0, 1.0) } private def sigmoid(z:Double): Double = { 1.0 / (1.0 + Math.exp(-z)) }
上から順に,wとphiの内積,内積,phi, sigmoidを求める関数です.
出力例
Scala でパーセプトロン
久しぶりにScalaを使うことになったので,練習がてらに Scala でパーセプトロンを書いてみた.
二次元データを対象とし,シンプルに実装してみた.(GitHubで公開)
テスト
今回は Scalatest のFlatSpecを使ってみた.
sbt 環境での使い方は,公式のUsing ScalaTest with sbtに書いてあったのですが,上手くいかず.... .
使っているScalaのバーションが 2.10 系だと思います.
結局,下記の用に記述したら上手く行きました.
libraryDependencies ++= Seq( "org.scalatest" %% "scalatest" % "2.0.M6-SNAP9" % "test" )
基本的には,flatstrapの公式ドキュメントを参照で問題なかったです.
一点だけ,privateメソッドのunittestの書き方が分からなかったので,調べた結果,下記の用に書いてみました.
import org.scalatest.FlatSpec import org.scalatest.PrivateMethodTester._ class HogeSpec extends FlatSpe { it should "hoge hoge" in { val ins = new CLASS val pmethod = PrivateMethod[List[Double]]('PRIVATE_MTHOD_NAME) ins invokePrivate pmethod() } }
詳細はドキュメントを参照下さい.
結果
下記に出力例を示しておきます.
恐らく上手く分離できてると思います.