ここまで学んできた機能を組み合わせると,機械学習のベンチマーク実験が簡単に実行できる.
大規模なベンチマーク実験を行うには並列化が必要になるので,最後にこれを扱う.
以上で基本的な機能の紹介は終わりである.
ベンチマーク実験では
ことを行う.mlrではこのプロセスがここまで学んできた色々な機能と直接対応している. つまり,
LearnerのリストTaskのリストResampleDesc, またはResampleInstanceMeasuresを指定して,benchmark()関数を呼ぶだけでベンチマーク実験ができる.
まずは簡単な例を見てみる.
## 学習器のリスト(判別分析と決定木)
lrns = list(makeLearner("classif.lda"), makeLearner("classif.rpart"))
## リサンプリング法はホールドアウトに設定
rdesc = makeResampleDesc("Holdout")
## ベンチマーク実験を実行(タスクは組み込みのsonar.taskを使用)
res = benchmark(lrns, sonar.task, rdesc)
## Task: Sonar-example, Learner: classif.lda
## [Resample] holdout iter: 1
## [Resample] Result: mmce.test.mean=0.386
## Task: Sonar-example, Learner: classif.rpart
## [Resample] holdout iter: 1
## [Resample] Result: mmce.test.mean=0.314
print(res)
## task.id learner.id mmce.test.mean
## 1 Sonar-example classif.lda 0.3857143
## 2 Sonar-example classif.rpart 0.3142857
見ての通り,ベンチマークが簡単に得られる.benchmark()関数はBenchmarkResultオブジェクトを返すが, これは実質的にはResampleResultのリストになっている.
str(res, max.level = 2)
## List of 1
## $ Sonar-example:List of 2
## ..$ classif.lda :List of 11
## .. ..- attr(*, "class")= chr [1:2] "ResampleResult" "list"
## ..$ classif.rpart:List of 11
## .. ..- attr(*, "class")= chr [1:2] "ResampleResult" "list"
## - attr(*, "class")= chr [1:2] "BenchmarkResult" "list"
ベンチマーク実験の結果を詳しく見るために,getBMR*()というアクセサ関数群が提供されている. getBMR*()には結果をデータフレームで返すas.dfオプションがあり,これを指定しておいた方が便利なことが多い.
# 評価指標
getBMRPerformances(res)
## $`Sonar-example`
## $`Sonar-example`$classif.lda
## iter mmce
## 1 1 0.3857143
##
## $`Sonar-example`$classif.rpart
## iter mmce
## 1 1 0.3142857
# テストデータでの予測結果をデータフレームで
head(getBMRPredictions(res, as.df = TRUE))
## task.id learner.id id truth response iter set
## 1 Sonar-example classif.lda 61 R R 1 test
## 2 Sonar-example classif.lda 96 R M 1 test
## 3 Sonar-example classif.lda 171 M M 1 test
## 4 Sonar-example classif.lda 153 M M 1 test
## 5 Sonar-example classif.lda 149 M M 1 test
## 6 Sonar-example classif.lda 152 M R 1 test
以上でベンチマーク実験の雰囲気がつかめたので,次は少し大き目の例を見る.
# 判別分析・決定木・ランダムフォレスト
lrns = list(makeLearner("classif.lda", predict.type = "prob", id = "lda"),
makeLearner("classif.rpart", predict.type = "prob", id = "rpart"),
makeLearner("classif.randomForest", predict.type = "prob", id = "rF"))
## 組み込みの分類タスク2つ
tasks = list(pid.task, sonar.task)
## ブートストラップ20回
rdesc = makeResampleDesc("Bootstrap", iters = 20)
## ベンチマーク実験実行(評価指標にAccuracyとAUCを指定)
res = benchmark(lrns, tasks, rdesc, measures = list(acc, auc), show.info = FALSE)
res
## task.id learner.id acc.test.mean auc.test.mean
## 1 PimaIndiansDiabetes-example lda 0.7732578 0.8325918
## 2 PimaIndiansDiabetes-example rpart 0.7356923 0.7653765
## 3 PimaIndiansDiabetes-example rF 0.7579275 0.8192924
## 4 Sonar-example lda 0.7238620 0.7762287
## 5 Sonar-example rpart 0.6936603 0.7317217
## 6 Sonar-example rF 0.8155852 0.9135262
さて,前述の通り,ベンチマーク実験結果の詳細はgetBMR*()関数によってデータフレームとして取得できる.
perf = getBMRPerformances(res, as.df = TRUE)
head(perf)
## task.id learner.id iter acc auc
## 1 PimaIndiansDiabetes-example lda 1 0.7615658 0.8089110
## 2 PimaIndiansDiabetes-example lda 2 0.7551020 0.8364223
## 3 PimaIndiansDiabetes-example lda 3 0.7615658 0.8219032
## 4 PimaIndiansDiabetes-example lda 4 0.7900000 0.8506393
## 5 PimaIndiansDiabetes-example lda 5 0.7829181 0.8312394
## 6 PimaIndiansDiabetes-example lda 6 0.7872340 0.8279570
このデータフレームを使えば,ggplot2等によってベンチマーク実験結果の探索的分析が容易に行える. これはかなり便利だ.
# Accuracyの分布を箱ひげ図で比較
ggplot(perf, aes(x = task.id, y = acc, color = learner.id)) + geom_boxplot()
# AUCの分布を密度プロットで比較
ggplot(perf, aes(auc, color = learner.id)) + geom_density() + facet_grid(.~task.id)
# perfをさらに加工して望みのグラフを描く
perfm = tidyr::gather(perf, measure, performance, c(acc, auc))
ggplot(perfm, aes(performance, color = learner.id)) + geom_density() + facet_grid(measure~task.id)
parallelMapパッケージを併用することで,mlrでの計算は容易に並列化できる.
parallelMapはmlr開発チームの一人が開発しており,様々な並列化バックエンドに対して 統一的なインターフェイスを提供するパッケージだ. マシン1台のマルチコアから,ソケットやMPIによるクラスタ,諸々のHPCクラスタまでサポートされている.
mlrでparallelMapを使うのは非常に簡単で,最初にparallelStart*()関数を 呼んでおけば,mlrで並列化が可能な計算は自動的に並列化される.
先ほど実行したベンチマーク実験で並列化を試してみよう.
# ベンチマーク実験の準備
lrns = list(makeLearner("classif.lda", predict.type = "prob", id = "lda"),
makeLearner("classif.rpart", predict.type = "prob", id = "rpart"),
makeLearner("classif.randomForest", predict.type = "prob", id = "rF"))
tasks = list(pid.task, sonar.task)
rdesc = makeResampleDesc("Bootstrap", iters = 100)
# 普通に実行
system.time((res = benchmark(lrns, tasks, rdesc, measures = list(acc, auc), show.info = FALSE)))
## user system elapsed
## 105.18 2.58 96.26
# 並列化して実行
library("parallelMap")
(ncore = parallel::detectCores())
## [1] 4
parallelStartSocket(ncore) # Windowsの場合.LinuxやMac OS XならparallelStartMulticore()を使う.
## Starting parallelization in mode=socket with cpus=4.
system.time((res_p = benchmark(lrns, tasks, rdesc, measures = list(acc, auc), show.info = FALSE)))
## Exporting objects to slaves for mode socket: .mlr.slave.options
## Mapping in parallel: mode = socket; cpus = 4; elements = 6.
## user system elapsed
## 1.18 1.36 70.44
parallelStop()
## Stopped parallelization. All cleaned up.
ちゃんと速くなりました.
parallelMapパッケージのさらに詳しい説明はgithub にあるので,そちらを参照されたし.