巨人の肩の上に登る

先人の積み重ねた発見に基づいて、なにかを発見しようとすることを指す。

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

出力例

f:id:mayo_yamasaki:20131222153417p:plain