Scalala コンソール

Scalala (https://github.com/scalala/Scalala) をちょこっと使うのに便利なように scalala-console を用意しました。

https://github.com/akr4/scalala-console

sbt の initialCommands で import しているだけですw
たくさん import しないと使えないんですよね

利用例

% sbt console
[info] Set current project to default-435ec2 (in build file:/Users/akira/projects/scalala-console/)
[info] Starting scala interpreter...
[info] 
import scalala.scalar._
import scalala.tensor.$colon$colon
import scalala.tensor.mutable._
import scalala.tensor.dense._
import scalala.tensor.sparse._
import scalala.library.Library._
import scalala.library.LinearAlgebra._
import scalala.library.Statistics._
import scalala.library.Plotting._
import scalala.operators.Implicits._
Welcome to Scala version 2.9.1.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_29).
Type in expressions to have them evaluated.
Type :help for more information.

scala> val a = Matrix((1, 2), (3, 4))
a: scalala.tensor.dense.DenseMatrix[Int] = 
1  2  
3  4  

scala> val b = Matrix((1, 0), (2, 0))
b: scalala.tensor.dense.DenseMatrix[Int] = 
1  0  
2  0  

scala> a * b
res0: scalala.tensor.dense.DenseMatrix[Int] = 
5   0  
11  0  

2.5 Evaluating the GroupLens data set

GroupLens (http://www.grouplens.org/) が提供しているデータセットを使ってみようの回。

映画のリコメンデーションサイト MovieLens のデータが様々なサイズで提供されています。http://www.grouplens.org/node/73
今回はこのうち MovieLens 100k を使います。

前回 (http://d.hatena.ne.jp/akr4/20111121/1321878009) のコード例の FileDataModel のパスを書き換えてやるだけ。FileDataModel は csv でも tsv でもうまく読んでくれるそうです。

val model = new FileDataModel(new File("data/ml-100k/ua.base"))

AverageAbsoluteDifferenceRecommenderEvaluator で試すと 0.98 でした。1-5 の preference だからこの結果はまずまずですかな?


続いて Recommender を SlopeOneRecommender に切り替えてみます。

  lazy val slopeOne = new RecommenderBuilder {
    def buildRecommender(model: DataModel) = new SlopeOneRecommender(model) 
  }

AverageAbsoluteDifferenceRecommenderEvaluator の結果は 0.74 と出ました。GenericUserBasedRecommender より良い結果ですが、データセットによって変わりますよとのこと。

これで 2 章おわり。

2.3 Evaluating a recommender/2.4 Evaluating precision and recall

今回は 2.3 Evaluating a recommender と 2.4 Evaluating precision and recall です。

Recommender を評価するために Evaluator が用意されています。
ここで使うのは以下 3 種。

AverageAbsoluteDifferenceRecommenderEvaluator 予測と実際の値の差 (絶対値) の平均
RMSRecommenderEvaluator 予測と実際の値の差の RMS (2 乗平均平方根)
GenericRecommenderIRStatsEvaluator precision/recall など

2.3 Evaluating a recommender

まず差の平均を使う例

object EvaluatorIntro extends App {

  RandomUtils.useTestSeed()
  val model = new FileDataModel(new File("../MIA/src/main/java/mia/recommender/ch02/intro.csv"))
  val evaluator = new AverageAbsoluteDifferenceRecommenderEvaluator
  //val evaluator = new RMSRecommenderEvaluator

  val builder = new RecommenderBuilder {
    def buildRecommender(model: DataModel) = {
      val similarity = new PearsonCorrelationSimilarity(model)
      val neighborhood = new NearestNUserNeighborhood(2, similarity, model)
      new GenericUserBasedRecommender(model, neighborhood, similarity)
    }
  }

  val score = evaluator.evaluate(builder, null, model, 0.7, 1.0)
  println(score)
}

RecommenderEvaluator の evaluate メソッドの定義は以下の通り。

  double evaluate(RecommenderBuilder recommenderBuilder,
                  DataModelBuilder dataModelBuilder,
                  DataModel dataModel,
                  double trainingPercentage,
                  double evaluationPercentage) throws TasteException;

dataModelBuilder にはここでは null を渡しています。DataModel をカスタマイズしたい場合に指定するらしい。
trainingPercentage はトレーニングデータセットの割合を指定します。残りはテストデータセットとして使われます。
evaluationPercentage は全体のデータセットのうち評価で使う割合を指定します。上の例では 100% 全部使います。評価にかかる時間を短くしたい場合に調整するそうです。

2.4 Evaluating precision and recall

続いて GenericRecommenderIRStatsEvaluator を使って precision と recall を計算する例です。ちなみに IR は Information Retrieval の略です。Wikipedia が詳しいですね。http://en.wikipedia.org/wiki/Information_retrieval

この Evaluator により Precision, Recall を計算できます。

object IREvaluatorIntro extends App {

  RandomUtils.useTestSeed()
  val model = new FileDataModel(new File("../MIA/src/main/java/mia/recommender/ch02/intro.csv"))
  val evaluator = new GenericRecommenderIRStatsEvaluator

  val builder = new RecommenderBuilder {
    def buildRecommender(model: DataModel) = {
      val similarity = new PearsonCorrelationSimilarity(model)
      val neighborhood = new NearestNUserNeighborhood(2, similarity, model)
      new GenericUserBasedRecommender(model, neighborhood, similarity)
    }
  }

  val stats = evaluator.evaluate(
    builder, null, model, null, 2,
    GenericRecommenderIRStatsEvaluator.CHOOSE_THRESHOLD,
    1.0)
  println(stats)
}

GenericRecommenderIRStatsEvaluator は RecommenderIRStatsEvaluator インタフェースを実装していて、その evaluate メソッドの定義は以下の通りです。

  IRStatistics evaluate(RecommenderBuilder recommenderBuilder,
                        DataModelBuilder dataModelBuilder,
                        DataModel dataModel,
                        IDRescorer rescorer,
                        int at,
                        double relevanceThreshold,
                        double evaluationPercentage) throws TasteException;

なんか全体的に引数が多いですね。
relevanceThreshold は関連があるとみなす最低の preference 値、上の例のように CHOOSE_THRESHOLD とするとその人の preference 値の平均 + 標準偏差にしてくれるとのこと。
低い preference 値を付けると予測されているのに推薦しないように設定するそうです。

実行結果はこんなの

IRStatisticsImpl[precision:0.75,recall:1.0,fallOut:0.08333333333333333,nDCG:1.0]

precision, recall が計算できました。

今回はここまで。
コードはこちらにあります https://github.com/akr4/mia-study

whitepaper-config: アプリケーション設定ツールキット

Scala アプリケーションの設定をサポートするツールキット whitepaper-config を作りました。
https://github.com/akr4/whitepaper/tree/develop/config


設定を Scala コードとして書いておくと、実行時に環境に応じた設定を選択します。
設定ファイルを外出しにする必要のない web アプリケーションなどに向いています。


環境の選択には

  1. JVMシステムプロパティ
  2. ホスト名

を使います。

使い方

まず設定クラスを用意します。名前、型はなんでも構いません。

trait Config {
  val dbUser: String
  val dbPassword: String
}

次に環境ごとの設定を Environments に登録します。

val env = Environments("whitepaper",
    "dev" -> new Config {
      val dbUser = "dbUser_dev"
      val dbPassword = "dbPassword_dev"
    },
    "prod" -> new Config {
      val dbUser = "dbUser_prod"
      val dbPassword = "dbPassword_prod"
    }
  )

あとは Environments.current メソッドにより環境に応じた設定を取り出すことができます。

env.current.dbUser // dbUser_dev または dbUser_prod


システムプロパティで環境を指定するには Environments の第1引数 + ".env" を使って

java -Dwhitepaper.env=dev ....

とします。

システムプロパティの指定がなければホスト名に一致する環境の設定が取り出されます。

まとめ

Scala で書く点は Twitter の util-eval と似ていますが web アプリケーションの場合たいてい動的に eval する必要がないので環境切り替えにフォーカスして作りました。
実際たいしたことやってなくて、Environments のコードはとても短いです。
Scala は少しのコードでも強力なので、小さめの小道具を組み合わせてシステムを構築するスタイルが向いているんじゃないかなと思っています。(Unfiltered の受け売りですw)

try-catch-finally を scala.util.control.Exception で書いてみる

11 月 10 日に daimon.scala Scala School#1 に参加してきました。

Scala School (http://twitter.github.com/scala_school/basics2.html) の Exceptions のところ、try-catch-finally を使う例に対して「優れたプログラミングスタイルの例ではありません。」(@seratch による日本語訳はこちら http://d.hatena.ne.jp/seratch2/20111101/1320162713) とあるけどじゃあ優れたスタイルってなんだよという話がありました。もしかしたら scala.util.control.Exception で書くのが良いかもという意見があり、僕もそう思ったので実装してみました。

Scala School の例

val result: Int = try {
  remoteCalculatorService.add(1, 2)
} catch {
  case e: ServerIsDownException => {
    log.error(e, "the remote calculator service is unavailble. should have kept your trustry HP.")
    0
  }
} finally {
  remoteCalculatorService.close()
}

scala.util.control.Exception を使って書いてみた

import scala.util.control.Exception._

class ServerIsDownException extends Exception

object A extends App {
  val remoteCalculatorService = new AnyRef {
    def add(a: Int, b: Int): Int = {
      a + b match {
        case odd if odd % 2 == 1 => odd
        case _ => throw new ServerIsDownException
      }  
    }
    def close {}
  }
  val log = new AnyRef { def error(t: Throwable, m: String) { println(m) } }

  def add(a: Int, b: Int): Int = {
    catching(classOf[ServerIsDownException]).
      andFinally { remoteCalculatorService.close }. 
      either { remoteCalculatorService.add(a, b) } match {
        case Right(x) => x
        case Left(t)=> {
          log.error(t, "the remote calculator service is unavailble. should have kept your trustry HP.")
          0
        }
      }
  }
    
  println("1 + 2 =" + add(1, 2))
  println("1 + 1 =" + add(1, 1))
} 

んー、わかりにくいかな?
scala.util.control.Exception は

ignoring(classOf[SQLException]) opt conn.close

とかやるときは try-finally より簡潔に書けるけど、catch 節の表現はわかりにくくなるかも?

MIA勉強会: ch02 RecommenderIntro

最初のプログラム例、2.2.2 Creating a recommender の RecommenderIntro クラスを動かしてみます。最近はなんでも Scala で書きたいので Scala で書き直します。

まず sbt の設定。project/build.scala

ローカルリポジトリにインストールした mahout を参照できるようにリポジトリを定義。

val localResolver = "Local Maven Repository" at "file:///" + System.getProperty("user.home") + "/.m2/repository/"

次、mahout 関連の依存関係を定義。

val mahoutVersion = "0.6-SNAPSHOT"

val mahoutDependencies = Seq(
  "org.apache.mahout" % "mahout-core" % mahoutVersion intransitive,
  "org.apache.mahout" % "mahout-math" % mahoutVersion,
  "com.google.guava" % "guava" % "r09"
)

mahout-core -> parent -> uncommons-maths -> jfreechart とたどって jfreechart がないよって怒られたので mahout-core を intrasitive (依存関係をたどらない) にして、かわりに必要な依存関係を明示的に定義しています。(実行して NoClassDefFoundError を消していった)

parent の pom.xml でちゃんと exclude 指定されていますが、sbt が対応していないのかもしれません。

プロジェクトを定義。

lazy val root = Project("mia-study", file("."),
  settings = buildSettings ++ Seq(
    version := "0.1",
    resolvers += localResolver,
    libraryDependencies := mahoutDependencies ++ loggingDependencies ++ testDependencies
  )
)

次、プログラム。

src/main/scala/ch02/intro.scala

object RecommenderIntro extends App {

  val model = new FileDataModel(new File("../MIA/src/main/java/mia/recommender/ch02/intro.csv"))
  val similarity = new PearsonCorrelationSimilarity(model)
  val neighborhood = new NearestNUserNeighborhood(2, similarity, model)
  val recommender = new GenericUserBasedRecommender(model, neighborhood, similarity)
  val recommendations = recommender.recommend(1, 1)

  recommendations.asScala.foreach(println _)
}

Java のをそのまま移しました。
FileDataModel に指定しているところにデータファイルがあることを想定しています。これは

git clone https://github.com/tdunning/MiA.git

でクローンしておきます。

さて実行。

% sbt run
09:18:51.235 [run-main] INFO  o.a.m.c.t.i.model.file.FileDataModel - Creating FileDataModel for file ../MIA/src/main/java/mia/recommender/ch02/intro.csv
09:18:51.268 [run-main] INFO  o.a.m.c.t.i.model.file.FileDataModel - Reading file info...
09:18:51.270 [run-main] INFO  o.a.m.c.t.i.model.file.FileDataModel - Read lines: 21
09:18:51.280 [run-main] INFO  o.a.m.c.t.i.model.GenericDataModel - Processed 5 users
09:18:51.289 [run-main] DEBUG o.a.m.c.t.i.r.GenericUserBasedRecommender - Recommending items for user ID '1'
09:18:51.296 [run-main] DEBUG o.a.m.c.t.i.r.GenericUserBasedRecommender - Recommendations are: [RecommendedItem[item:104, value:4.257081]]
RecommendedItem[item:104, value:4.257081]
[success] Total time: 1 s, completed 2011/11/09 9:18:51

よしできた。

今回のソースコードhttps://github.com/akr4/mia-study にあります。