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を求める関数です.
出力例