spark mlで分類器に関するTips

この記事は、[Machine Learning Advent Calendar 2016](http://qiita.com/advent-calendar/2016/machine-learning) 8日目の記事です。

spark mlの分類器をお試しする上でのTipsを4つご紹介します！

# 1. お試しに便利なlibsvmデータセット
機械学習を試す上で、データセットを用意する必要があります。
よく知られているものとして、
UCI http://archive.ics.uci.edu/ml/index.html
kaggle https://www.kaggle.com/datasets
MovieLens http://grouplens.org/datasets/movielens/
などを利用することが多いと思います。
ただ、クラス数、特徴量の数、データ量など、適切なものを探すのは大変です。
LIBSVMでは、UCI等で取得できる分類や回帰に利用できるデータが、それぞれのクラス数、特徴量数、データ量、ダウンロード元が一目でわかるようにまとまっています。
また、データはすべてlibsvm形式で格納されています。
libsvmはsparseなデータに対する分類や回帰を行うために適したデータレイアウトのテキストフォーマットのデータソースです。
実際のデータレイアウトはこんな感じです。
```
label index1:value1 index2:value2 ・・・
```

また、sparkのdataFrameとしてロードすると、このような形で見えます。

```scala
// sample 1
import org.apache.spark.sql.SparkSession

val spark = SparkSession.builder.appName("VectorIndexerSample").getOrCreate()
spark.read.format("libsvm").load("/path/to/data").head(1)

//出力結果
//Array[label,(num features,[index1,index2,・・・],[value1,value2,・・・])]
```
このように、inputFormatによって、Sparkのmllib/mlを使用する際の入力データの形式であるSparseMarixの形でロードされるため、データをロードした後の前処理に苦労しなくて良いという利点があります。

なお、libsvmは、データセット以外にも様々なライブラリでlibsvmフォーマットを利用するためのパッケージなども置いてあります。

# 2. 前処理
データを分類器の学習に適した形にするための特徴量変換のAPIが幾つかあります。
注意が必要な点として、分類器を使う上で、特徴量を連続値として扱うかを、制御する必要があります。
mllibでは、分類器の学習時に与えるパラメータとして、カテゴリ値として利用する特徴量をcategoricalFeaturesInfo: Map[Int, Int]の形式で与えるようになっていました。
ただし、このやり方の場合、カテゴリ値として使用する特徴量を手動で与えるので、特徴量の数が膨大な場合は実装を行う上で少し面倒です。
そこで、spark.mlでは、`VectorIndexer`というAPIで自動的にスキーマのmetadataとしてカテゴリ値か連続値かを判別するためのattributeを付与するようになっています。`VectorIndexer`では、カーディナリティがmaxCategoriesで与えた数値以下である特徴量はカテゴリ値、カーディナリティがmaxCategoriesで与えた数値よりも大きい場合は連続値としてattributeを付与します。

以下サンプルコードです。

```scala
// sample 2
import org.apache.spark.sql.SparkSession
org.apache.spark.ml.feature.VectorIndexer

val spark = SparkSession.builder.appName("VectorIndexerSample").getOrCreate()
val data = spark.read.format("libsvm").load("/path/to/data")
val featureIndexer = new VectorIndexer()
                     .setInputCol("features")
                     .setOutputCol("indexedfeatures")
                     .setMaxCategories(20)
                     .fit(data)
val featureIndexed = featureIndexer.transform(data)
```

# 3. 多クラス分類への対応
sparkのmllib/mlの分類器は、多クラス分類に対応しているものと、２クラス分類のみに対応しているものがあります。
以下、対応を表にしてみました。

|     |mllib |spark.ml               |
|-----|------|---------------------|
|DecisionTree|２クラス/多クラス|2クラス/多クラス|
|RandomForest|２クラス/多クラス|２クラス/多クラス|
|GBT(勾配ブースティングツリー)|２クラスのみ|２クラスのみ|
|LogisticRegression|２クラス/多クラス|２クラスのみ|
|NaiveBayes|２クラス/多クラス|２クラス/多クラス|
|SVM |２クラスのみ|実装なし|


# 4.分類木のメモリ設定
決定木、ランダムフォレスト、GBTといった分類木で、特に深い木の学習を行う際など、パフォーマンスが気になる場合は、
`maxMemoryInMB`の設定を行うと良いです。
Sparkでは、分類木の学習において、この`maxMemoryInMB`で指定したメモリサイズで扱える数のツリーノードの計算を並列化します。この`maxMemoryInMB`は、初期値が256MBと小さいため、クラスタのexecutorメモリのサイズに合わせてチューニングを行うことで、並列度を上げて、学習のイテレーション数を減らすことができます。

以下、決定木の学習におけるサンプルコードです。
（sample 2 の続き）

```scala
// sample 3
import org.apache.spark.ml.classification.DecisionTreeClassifier

//maxMemoryInMBの値を1024MBに設定
val model = new DecisionTreeClassifier()
            .setLabelCol("label")
            .setFeaturesCol("indexedFeatures")
            .setMaxMemoryInMB(1024)
            .fit(featureIndexed)
```

本当は最後に各分類器のパフォーマンスやAccuracyを比較したかったのですが、
時間が取れなかったのでまたの機会に。。。