Section 10 ベンチマーク試験
ベンチマーク試験では、1つ、あるいは複数の性能指標に基いてアルゴリズムを比較するために、異なる学習手法を1つあるいは複数のデータセットに適用する。
mlr
ではbenchmark
関数に学習器とタスクをリストで渡すことでベンチマーク試験を実施できる。benchmark
は通常、学習器とタスクの対に対してリサンプリングを実行する。タスクと性能指標の組み合わせに対してどのようなリサンプリング手法を適用するかは選択することができる。
10.1 ベンチマーク試験の実施
小さな例から始めよう。線形判別分析(lda)と分類木(rpart)をsonar.task
に適用する。リサンプリング手法はホールドアウト法を用いる。
以下の例ではResampleDesc
オブジェクトを作成する。各リサンプリングのインスタンスはbenchmark
関数によって自動的に作成される。インスタンス化はタスクに対して1度だけ実行される。つまり、全ての学習器は全くおなじ訓練セット、テストセットを用いることになる。なお、明示的にResampleInstance
を渡しても良い。
もし、データセットの作成を無作為ではなく任意に行いたければ、makeFixedHoldoutinstance
を使うと良いだろう。
lrns = list(makeLearner("classif.lda"), makeLearner("classif.rpart"))
rdesc = makeResampleDesc("Holdout")
bmr = benchmark(lrns, sonar.task, rdesc)
$> Task: Sonar-example, Learner: classif.lda
$> [Resample] holdout iter 1: mmce.test.mean=0.271
$> [Resample] Aggr. Result: mmce.test.mean=0.271
$> Task: Sonar-example, Learner: classif.rpart
$> [Resample] holdout iter 1: mmce.test.mean=0.357
$> [Resample] Aggr. Result: mmce.test.mean=0.357
bmr
$> task.id learner.id mmce.test.mean
$> 1 Sonar-example classif.lda 0.2714286
$> 2 Sonar-example classif.rpart 0.3571429
もしmakeLearner
に学習器の種類以外の引数を指定するつもりがなければ、明示的にmakeLearner
を呼び出さずに単に学習器の名前を指定しても良い。上記の例は次のように書き換えることができる。
## 学習器の名前だけをベクトルで指定しても良い
lrns = c("classif.lda", "classif.rpart")
## 学習器の名前とLearnerオブジェクトを混ぜたリストでも良い
lrns = list(makeLearner("classif.lda", predict.type = "prob"), "classif.rpart")
bmr = benchmark(lrns, sonar.task, rdesc)
$> Task: Sonar-example, Learner: classif.lda
$> [Resample] holdout iter 1: mmce.test.mean=0.314
$> [Resample] Aggr. Result: mmce.test.mean=0.314
$> Task: Sonar-example, Learner: classif.rpart
$> [Resample] holdout iter 1: mmce.test.mean=0.329
$> [Resample] Aggr. Result: mmce.test.mean=0.329
bmr
$> task.id learner.id mmce.test.mean
$> 1 Sonar-example classif.lda 0.3142857
$> 2 Sonar-example classif.rpart 0.3285714
print
の結果は各行がタスクと学習器の1つの組合せに対応している。ここでは分類のデフォルトの指標である平均誤分類率が示されている。
bmr
はBenchmarcResult
クラスのオブジェクトである。基本的には、これはResampleResult
クラスのオブジェクトのリストのリストを含んでおり、最初のリストはタスク、その中のリストは学習器に対応した並びになっている。
10.1.1 実験を再現可能にする
一般的にいって、実験は再現可能であることが望ましい。mlr
はset.seed
関数の設定に従うので、スクリプト実行前にset.seed
によって乱数種を固定しておけば再現性が確保できる。
もし並列計算を使用する場合は、ユースケースに合わせてset.seed
の呼び出し方を調整する必要がある。例えば、set.seed(123, "L'Ecuyer")
と指定すると子プロセス単位で再現性が確保できる。mcapplyの例(mclapply function | R Documentation)を見ると並列計算と再現性に関するもう少し詳しい情報が得られるだろう(訳注:こちらのほうが良いかも?R: Parallel version of lapply)。
10.2 ベンチマーク結果へのアクセス
mlr
はgetBMR<抽出対象>
という名前のアクセサ関数を幾つか用意している。これにより、さらなる分析のために情報を探索することができる。これには検討中の学習アルゴリズムに関するパフォーマンスや予測などが含まれる。
10.2.1 学習器の性能
先程のベンチマーク試験の結果を見てみよう。getBMRPerformances
は個々のリサンプリング毎の性能指標を返し、getMBRAggrPerformances
は性能指標の集約値を返す。
getBMRPerformances(bmr)
$> $`Sonar-example`
$> $`Sonar-example`$classif.lda
$> iter mmce
$> 1 1 0.3142857
$>
$> $`Sonar-example`$classif.rpart
$> iter mmce
$> 1 1 0.3285714
getBMRAggrPerformances(bmr)
$> $`Sonar-example`
$> $`Sonar-example`$classif.lda
$> mmce.test.mean
$> 0.3142857
$>
$> $`Sonar-example`$classif.rpart
$> mmce.test.mean
$> 0.3285714
今回の例ではリサンプリング手法にホールドアウト法を選んだので、リサンプリングは1回しか行っていない。そのため、個々のリサンプリング結果に基づく性能指標と集約値はどちらも同じ表示結果になっている。
デフォルトでは、ほぼすべてのゲッター関数ネストされたリストを返す。リストの最初のレベルはタスクで分類されており、二番目のレベルは学習器での分類になる。学習器またはタスクが1つしかない場合は、drop = TRUE
を指定するとフラットなリストを得ることもできる。
getBMRPerformances(bmr, drop = TRUE)
$> $classif.lda
$> iter mmce
$> 1 1 0.3142857
$>
$> $classif.rpart
$> iter mmce
$> 1 1 0.3285714
大抵の場合はデータフレームの方が便利だろう。as.df = TRUE
を指定すると結果をデータフレームに変換できる。
getBMRPerformances(bmr, as.df = TRUE)
$> task.id learner.id iter mmce
$> 1 Sonar-example classif.lda 1 0.3142857
$> 2 Sonar-example classif.rpart 1 0.3285714
10.2.2 予測
デフォルトでは、BenchmarkResult
は学習器の予測結果も含んでいる。もし、メモリ節約などの目的でこれを止めさせたければkeep.pred = FALSE
をbenchmark
関数に指定すれば良い。
予測へのアクセスはgetBMRPredictions
関数を使う。デフォルトでは、ResamplePrediction
オブジェクトのネストされたリストが返ってくる。性能指標の場合と同様に、ここでもdrop
及びas.df
引数を使うことができる。
getBMRPredictions(bmr)
$> $`Sonar-example`
$> $`Sonar-example`$classif.lda
$> Resampled Prediction for:
$> Resample description: holdout with 0.67 split rate.
$> Predict: test
$> Stratification: FALSE
$> predict.type: prob
$> threshold: M=0.50,R=0.50
$> time (mean): 0.01
$> id truth prob.M prob.R response iter set
$> 1 165 M 9.739057e-01 0.0260942571 M 1 test
$> 2 118 M 9.769969e-01 0.0230031469 M 1 test
$> 3 107 M 5.205392e-01 0.4794608221 M 1 test
$> 4 159 M 9.996184e-01 0.0003815522 M 1 test
$> 5 66 R 4.726013e-07 0.9999995274 R 1 test
$> 6 193 M 9.955122e-01 0.0044878035 M 1 test
$> ... (70 rows, 7 cols)
$>
$>
$> $`Sonar-example`$classif.rpart
$> Resampled Prediction for:
$> Resample description: holdout with 0.67 split rate.
$> Predict: test
$> Stratification: FALSE
$> predict.type: response
$> threshold:
$> time (mean): 0.01
$> id truth response iter set
$> 1 165 M M 1 test
$> 2 118 M M 1 test
$> 3 107 M R 1 test
$> 4 159 M M 1 test
$> 5 66 R R 1 test
$> 6 193 M M 1 test
$> ... (70 rows, 5 cols)
head(getBMRPredictions(bmr, as.df = TRUE))
$> task.id learner.id id truth prob.M prob.R response
$> 1 Sonar-example classif.lda 165 M 9.739057e-01 0.0260942571 M
$> 2 Sonar-example classif.lda 118 M 9.769969e-01 0.0230031469 M
$> 3 Sonar-example classif.lda 107 M 5.205392e-01 0.4794608221 M
$> 4 Sonar-example classif.lda 159 M 9.996184e-01 0.0003815522 M
$> 5 Sonar-example classif.lda 66 R 4.726013e-07 0.9999995274 R
$> 6 Sonar-example classif.lda 193 M 9.955122e-01 0.0044878035 M
$> iter set
$> 1 1 test
$> 2 1 test
$> 3 1 test
$> 4 1 test
$> 5 1 test
$> 6 1 test
IDを通じて特定の学習器やタスクの結果にアクセスすることもできる。多くのゲッター関数はIDを指定するためのlearner.ids
引数とtask.ids
引数が用意されている。
head(getBMRPredictions(bmr, learner.ids = "classif.rpart", as.df = TRUE))
$> task.id learner.id id truth response iter set
$> 1 Sonar-example classif.rpart 165 M M 1 test
$> 2 Sonar-example classif.rpart 118 M M 1 test
$> 3 Sonar-example classif.rpart 107 M R 1 test
$> 4 Sonar-example classif.rpart 159 M M 1 test
$> 5 Sonar-example classif.rpart 66 R R 1 test
$> 6 Sonar-example classif.rpart 193 M M 1 test
デフォルトのIDが嫌なら、makeLearner
やmake*Task
関数のid
引数を通じて設定できる。さらに、学習器のIDを簡単に変更するための関数としてsetLearnerId
関数も用意されている。
10.2.3 ID
ベンチマーク試験における学習器、タスク、性能指標のIDは、以下のように取得できる。
getBMRTaskIds(bmr)
$> [1] "Sonar-example"
getBMRLearnerIds(bmr)
$> [1] "classif.lda" "classif.rpart"
getBMRMeasureIds(bmr)
$> [1] "mmce"
10.2.4 フィット済みモデル
デフォルトではBenchmarkResult
オブジェクトはフィット済みモデルも含んでいる。これは、benchmark
関数を呼び出す際にmodels = FALSE
を指定することで抑制できる。フィット済みモデルはgetBMRModels
関数を使うことで確認できる。この関数が返すのは(おそらくネストされた)WrappedModel
オブジェクトのリストである。
getBMRModels(bmr)
$> $`Sonar-example`
$> $`Sonar-example`$classif.lda
$> $`Sonar-example`$classif.lda[[1]]
$> Model for learner.id=classif.lda; learner.class=classif.lda
$> Trained on: task.id = Sonar-example; obs = 138; features = 60
$> Hyperparameters:
$>
$>
$> $`Sonar-example`$classif.rpart
$> $`Sonar-example`$classif.rpart[[1]]
$> Model for learner.id=classif.rpart; learner.class=classif.rpart
$> Trained on: task.id = Sonar-example; obs = 138; features = 60
$> Hyperparameters: xval=0
10.2.5 学習器と性能指標
使用された学習器はgetBMRLearners
で、性能指標はgetBMRMeasures
でそれぞれ抽出できる。
getBMRLearners(bmr)
$> $classif.lda
$> Learner classif.lda from package MASS
$> Type: classif
$> Name: Linear Discriminant Analysis; Short name: lda
$> Class: classif.lda
$> Properties: twoclass,multiclass,numerics,factors,prob
$> Predict-Type: prob
$> Hyperparameters:
$>
$>
$> $classif.rpart
$> Learner classif.rpart from package rpart
$> Type: classif
$> Name: Decision Tree; Short name: rpart
$> Class: classif.rpart
$> Properties: twoclass,multiclass,missings,numerics,factors,ordered,prob,weights,featimp
$> Predict-Type: response
$> Hyperparameters: xval=0
getBMRMeasures(bmr)
$> [[1]]
$> Name: Mean misclassification error
$> Performance measure: mmce
$> Properties: classif,classif.multi,req.pred,req.truth
$> Minimize: TRUE
$> Best: 0; Worst: 1
$> Aggregated by: test.mean
$> Note: Defined as: mean(response != truth)
10.3 ベンチマーク結果のマージ
ベンチマーク試験が終わった後で、他の学習器やタスクを追加したくなったらどうすればよいだろうか。このような場合は、mergeBenchmarkResults
関数を使えば、複数のBenchmarkResutl
オブジェクトをマージすることができる。
先程行った線形判別分析と決定木のベンチマーク試験結果に対し、ランダムフォレストと二次判別分析の結果を追加してみよう。
まず、ランダムフォレストと二次判別分析のベンチマーク試験を行う。
lrns2 = list(makeLearner("classif.randomForest"), makeLearner("classif.qda"))
bmr2 = benchmark(lrns2, sonar.task, rdesc, show.info = FALSE)
bmr2
$> task.id learner.id mmce.test.mean
$> 1 Sonar-example classif.randomForest 0.1857143
$> 2 Sonar-example classif.qda 0.4428571
次に、bmr
とbmr2
をマージする。BenchmarkResult
オブジェクトはリストで渡す。
mergeBenchmarkResults(list(bmr, bmr2))
$> task.id learner.id mmce.test.mean
$> 1 Sonar-example classif.lda 0.3142857
$> 2 Sonar-example classif.rpart 0.3285714
$> 3 Sonar-example classif.randomForest 0.1857143
$> 4 Sonar-example classif.qda 0.4428571
上記の例ではリサンプルdescriptionをbenchmark
関数に指定していることに注意してもらいたい。つまり、lda
とrpart
はrandamForest
およびqda
とは異なる訓練/テストセットを用いて評価された可能性が高い。
異なる訓練/テストセットを用いて評価を行うと、学習器間の正確な性能比較が難しくなる。もし、他の学習器を使った試験を後から追加する見込みがあるのなら、最初からResampleInstances
を使用したほうが良い。リサンプルをインスタンス化しておけば、同じ訓練/テストセットを後の試験でも使用することができるからだ。
あるいは、BenchmarkResult
オブジェクトからResampleInstance
を抽出し、これを他の試験に使用しても良い。例を示そう。
# インスタンスの抽出
rin = getBMRPredictions(bmr)[[1]][[1]]$instance
# インスタンスを用いてベンチマーク試験を行う
bmr3 = benchmark(lrns2, sonar.task, rin, show.info = FALSE)
# 結果をマージする
mergeBenchmarkResults(list(bmr, bmr3))
$> task.id learner.id mmce.test.mean
$> 1 Sonar-example classif.lda 0.3142857
$> 2 Sonar-example classif.rpart 0.3285714
$> 3 Sonar-example classif.randomForest 0.1714286
$> 4 Sonar-example classif.qda 0.4285714
10.4 ベンチマークの分析と可視化
mlr
はベンチマーク試験を分析する機能も備えている。これには、可視化、アルゴリズムの順位付け、パフォーマンスの差に対する仮説検定が含まれる。
今回はこの機能を紹介するために、5つの分類タスクと3つの学習アルゴリズムを使用する、やや大きなベンチマーク試験を実施する。
10.4.1 例:線形判別分析と分類木、ランダムフォレストの比較
3つのアルゴリズムは線形判別分析、決定木、ランダムフォレストである。これらのデフォルトの学習器IDはやや長いので、以下の例ではもう少し短い別名を設定した。
5つの分類タスクと言ったが、3つは既に紹介したものだ。これに加えて、mlbench
パッケージに含まれるデータをconvertMLBenchObjToTask
関数でタスクに変換したものを用いる。
全てのタスクは10分割クロスバリデーションによりリサンプリングを行う。これは、一つのresample descriptionをbenchmark
関数に渡すことで行う。これにより、それぞれのタスクに対するリサンプリングのインスタンス化が自動的に実行される。この方法では、一つのタスク内で全ての学習器に対して同じインスタンスが使用されることになる。
タスクの数と同じ長さのリストでリサンプリング手法を指定すれば、それぞれのタスクに異なるリサンプリング手法を適用することもできる。この際渡すのはresample descriptionsでも良いし、インスタンスでも良い。
評価手法は平均誤分類率を主とするが、合わせてbalanced error rate(ber)と訓練時間(timetrain)も算出する。
## 学習器リストの作成
lrns = list(
makeLearner("classif.lda", id = "lda"),
makeLearner("classif.rpart", id = "rpart"),
makeLearner("classif.randomForest", id = "randomForest")
)
## mlbenchパッケージから追加タスクを生成する
ring.task = convertMLBenchObjToTask("mlbench.ringnorm", n = 600)
$> Loading required package: mlbench
wave.task = convertMLBenchObjToTask("mlbench.waveform", n = 600)
## タスクリストの作成
tasks = list(iris.task, sonar.task, pid.task, ring.task, wave.task)
## リサンプリング手法の指定
rdesc = makeResampleDesc("CV", iters = 10)
## 評価指標の指定
meas = list(mmce, ber, timetrain)
## ベンチマーク試験の実行
bmr = benchmark(lrns, tasks, rdesc, meas, show.info = FALSE)
bmr
$> task.id learner.id mmce.test.mean ber.test.mean
$> 1 iris-example lda 0.02000000 0.01666667
$> 2 iris-example rpart 0.08000000 0.07277778
$> 3 iris-example randomForest 0.06000000 0.05500000
$> 4 mlbench.ringnorm lda 0.40666667 0.40395082
$> 5 mlbench.ringnorm rpart 0.20000000 0.20307555
$> 6 mlbench.ringnorm randomForest 0.04666667 0.04691889
$> 7 mlbench.waveform lda 0.16666667 0.16451184
$> 8 mlbench.waveform rpart 0.24500000 0.24799876
$> 9 mlbench.waveform randomForest 0.16000000 0.16211064
$> 10 PimaIndiansDiabetes-example lda 0.22650376 0.27578705
$> 11 PimaIndiansDiabetes-example rpart 0.25768968 0.30048359
$> 12 PimaIndiansDiabetes-example randomForest 0.22790499 0.27238976
$> 13 Sonar-example lda 0.30285714 0.30454643
$> 14 Sonar-example rpart 0.28428571 0.28040571
$> 15 Sonar-example randomForest 0.16380952 0.16538045
$> timetrain.test.mean
$> 1 0.0027
$> 2 0.0045
$> 3 0.0293
$> 4 0.0090
$> 5 0.0119
$> 6 0.4128
$> 7 0.0091
$> 8 0.0114
$> 9 0.4373
$> 10 0.0046
$> 11 0.0066
$> 12 0.3825
$> 13 0.0170
$> 14 0.0134
$> 15 0.2488
iris-exampleとPimaIndiansDiabetes-exampleでは線形判別分析が優れいているが、他のタスクではランダムフォレストが良い成績を出しているようだ。一方、訓練時間を見るとランダムフォレストは他の学習器よりも長い時間がかかっている。
パフォーマンスの平均値から何らかの結論を言いたければ、値の変動も考慮に入れる必要がある。リサンプリングの繰り返し全体で値がどのように分布しているかも考慮できれば尚良い。
10分割クロスバリデーションにより、各タスクは10回評価されていることになるが、これは以下の様にして詳細を確認できる。
perf = getBMRPerformances(bmr, as.df = TRUE)
head(perf)
$> task.id learner.id iter mmce ber timetrain
$> 1 iris-example lda 1 0.00000000 0.00000000 0.004
$> 2 iris-example lda 2 0.00000000 0.00000000 0.003
$> 3 iris-example lda 3 0.00000000 0.00000000 0.003
$> 4 iris-example lda 4 0.06666667 0.08333333 0.002
$> 5 iris-example lda 5 0.00000000 0.00000000 0.002
$> 6 iris-example lda 6 0.13333333 0.08333333 0.002
結果を詳しく確認すると、ランダムフォレストは全てのインスタンスで分類木より優れており、線形判別分析は時間の面でほとんどどの場合も優れている事が分かる。また、線形判別分析は時々ランダムフォレストより優れた結果を出している。このサイズの試験結果ならまだ「詳しく確認する」は可能であるが、これよりももっと大きなスケールのベンチマーク試験では、このような判断はずっと難しくなる。
mlr
には可視化と仮説検定の仕組みがあるので、より大きなスケールの試験に対してはそれを使おう。
10.4.2 可視化
プロットにはggplot2
パッケージが使用される。したがって、要素のリネームや配色変更などのカスタマイズは容易に行える。
10.4.2.1 パフォーマンスの可視化
plotBMRBoxplot
は箱ひげ図またはバイオリンプロットによって、リサンプリングの繰り返しの間に指標がどのような値をとったのかを可視化する。これはつまりgetBMRPerformances
の可視化である。
以下に箱ひげ図でmmce
の分布を示す。
plotBMRBoxplots(bmr, measure = mmce)
次にバイオリンプロットの例を示す。
# aes, theme, element_textをggplot2::なしで呼び出すためには
# ライブラリのロードが必要
library(ggplot2)
plotBMRBoxplots(bmr, measure = ber, style = "violin",
pretty.names = FALSE) +
aes(color = learner.id) +
theme(strip.text.x = element_text(size = 6))
ここでは、指標としてber
を使用するとともに、learner.id
に基づく色の塗り分けも行っている。また、theme
は各パネルのラベル(strip)のフォントサイズを調整している。
pretty.names=FALSE
は何かというと、性能指標や学習器のラベルとしてidを使ってくれという指定である。これを指定しなかったときにどのような名前が使われるかは、性能指標であれば指標名$name
、学習器ではgetBMRLEarnerShortName
で確認できる。
mmce$name
$> [1] "Mean misclassification error"
getBMRLearnerShortNames(bmr)
$> [1] "lda" "rpart" "rf"
プロットに関してよくある質問は、「パネルのラベルと学習器の名前はどうやったら変更できるのか」といったものだ。例えば、今見せた例で言えば、パネルのラベル名に含まれている-example
やmlbench.
を取り除いたり、学習機の名前を大文字の略称に変更したい、などの思ったのではないだろうか。現状、これを解決する一番簡単なやり方はfactorのlevelを書き換えてしまうことだ。例を示そう。
plt = plotBMRBoxplots(bmr, measure = mmce)
levels(plt$data$task.id) = c("Iris", "Ringnorm", "Waveform", "Diabetes", "Sonar")
levels(plt$data$learner.id) = c("LDA", "CART", "RF")
plt
10.4.3 集約結果の可視化
集約された性能指標(getBMRAggrPerformances
で得られるもの)は、plotBMRSummary
で可視化できる。このプロットでは各タスクの性能指標集約値が同一の線上に並べられる(注: ドットプロット)。標準では、benchmark
で指定された最初の指標がプロット対象となる。今回の例ではmmce
だ。加えて、各ポイントには縦方向に若干の誤差が加えられる。これはデータポイントが重なった場合にも識別できるようにするためだ。
plotBMRSummary(bmr)
10.4.4 順位の計算と可視化
性能指標の値に加えて、順位のような相対指標があると新しい知見が得られる場合がある。
convertBMRToRankMatrix
関数は、集約された性能指標のひとつに基づいて順位を計算する。例としてmmce
に基づくランキングを求めてみよう。
convertBMRToRankMatrix(bmr)
$> iris-example mlbench.ringnorm mlbench.waveform
$> lda 1 3 2
$> rpart 3 2 3
$> randomForest 2 1 1
$> PimaIndiansDiabetes-example Sonar-example
$> lda 1 3
$> rpart 3 2
$> randomForest 2 1
順位は最高のパフォーマンスを示した、つまりmmce
が最も低かった手法に対して小さな値が割り当てられる。今回の例ではLDAがirisとPimaIndiansDiabetesに対して最良の結果を示したが、他のタスクではランダムフォレストが優れていた事がわかる。
plotBMRRanksAsBarChart
関数を使うと順位を可視化することができる。pos = "tile"
を指定すると、順位・学習器・タスクの関係がヒートマップで図示される。この場合、x軸が順位、y軸がタスク、学習器の種類が色にそれぞれ割り当てられる。
plotBMRRanksAsBarChart(bmr, pos = "tile")
なお、plotBMRSummary
でもオプションにtrafo = "rank"
を指定することで、性能指標が順位に変換されるため、同様の情報を含むプロットを作成できる。
plotBMRSummary(bmr, trafo = "rank")
plotBMRRanksAsBarChart
はデフォルトでは積み上げ棒グラフを、pos = "dodge"
オプションを指定すると通常の棒グラフを描画する。これは各種法が獲得した順位の頻度を確認する場合に適している。
plotBMRRanksAsBarChart(bmr)
plotBMRRanksAsBarChart(bmr, pos = "dodge")
10.4.5 仮説検定で学習器を比較する
多くの研究者はアルゴリズムの有意性を仮説検定によって示す必要性を感じていることだろう。ベンチマーク試験の結果の比較などに適していると思われるノンパラメトリック検定として、mlr
はOverall Friedman testとFriedman-Nemenyi post hoc testをサポートしている。
Friedman testはstats
パッケージのfriedman.test
関数に基いており、これは学習器間に差があるかどうかを検定する。一方、Friedman-Nemenyi testは全ての学習器間の組合せについて差があるかどうかを検定する。ノンパラメトリック検定はパラメトリック検定と違い、データの分布に関する仮定を持たなくて済むという利点がある。しかしこれは、妥当な有意水準の元で有意な差を示すためには多くのデータセットが必要であるということを意味していることが多い。
さて、今我々が扱っている例では3つの学習器を比較している。まずは、そもそも学習器間に差があるのかどうかを検定したいのではないだろうか。Friedman testを実行してみよう。
friedmanTestBMR(bmr)
$>
$> Friedman rank sum test
$>
$> data: mmce.test.mean and learner.id and task.id
$> Friedman chi-squared = 3.6, df = 2, p-value = 0.1653
今回の例はチュートリアルなので、計算時間節約のために学習器を評価するタスクは5つしかなかった点に注意してほしい。従って、有意水準として少し甘めの\(\alpha=0.1\)程度を見積もっても良いだろう。この場合帰無仮説は棄却されるので、今度はこの差がどの学習器の間に存在するのかを検定してみよう。
# install.packages("PMCMRplus")
# Friedman testで帰無仮説を棄却できないとNemenyi検定をしてくれないので、
# p.value =で有意水準を引き上げる必要がある。
friedmanPostHocTestBMR(bmr, p.value = 0.1)
$> Loading required package: PMCMR
$> PMCMR is superseded by PMCMRplus and will be no longer maintained. You may wish to install PMCMRplus instead.
$> Warning in friedmanPostHocTestBMR(bmr, p.value = 0.1): Cannot reject null hypothesis of overall Friedman test,
$> returning overall Friedman test.
$>
$> Friedman rank sum test
$>
$> data: mmce.test.mean and learner.id and task.id
$> Friedman chi-squared = 3.6, df = 2, p-value = 0.1653
決定木とランダムフォレストの間の差について帰無仮説を棄却できた。
10.4.6 臨界差ダイアグラム
学習器の性能に関する差を可視化する手法として、critical differences diagramが使用できる。検定手法はNemeny検定(test = "nemenyui"
)またはBonferroni-Dunn検定(test = "bd"
)が選択できる。
プロットの際、x軸には学習器の平均順位が対応する。
test = "nemenyui"
を選択した場合、全てのグループ間で比較が行われるため、出力では互いに有意な差が無い学習器がグループとなるような描画が行われる。グループは学習器間をバーで接続することで図示される。従って、バーで接続されていない学習器間には有意な差が存在しており、平均順位が低い方の学習器の方が(選択した有意水準のもとで)優れていると言うことができる。test = "bd"
を選択した場合は、各学習器はベースラインと比較される。すなわち、ベースラインとして選択した学習器から両方向に臨界差(critical difference)の範囲を示すラインが描画される。従って比較はベースラインの学習器との間でのみ可能となる。臨界差の範囲外の学習器は、ベースラインとした学習器に対して優れている(または劣っている)と考えることができる。
ここで臨界差\(CD\)は次のように計算される値である。
\(CD = q_\alpha\sqrt{\frac{k(k+1)}{6N}}\)
\(N\)はタスクの数を、\(k\)は学習器の数を、\(q_\alpha\)はスチューデント化された範囲を\(\sqrt{2}\)で除した値である。詳しくはDamsar (2006)を参照してほしい。
createCritDifferencesData
関数は、plotCritDifferences
でプロットを作成するために必要な全ての量を計算する。プロット関数に関する詳細は後ほどあらためて可視化のチュートリアルで触れる予定である。
## Nemeyui test
g = generateCritDifferencesData(bmr, p.value = 0.1, test = "nemenyi")
$> Warning in friedmanPostHocTestBMR(bmr, measure, p.value): Cannot reject null hypothesis of overall Friedman test,
$> returning overall Friedman test.
plotCritDifferences(g) + coord_cartesian(xlim = c(-1, 5), ylim = c(0, 2))
## Bonferroni-Dunn test
g = generateCritDifferencesData(bmr, p.value = 0.1, test = "bd", baseline = "randomForest")
$> Warning in friedmanPostHocTestBMR(bmr, measure, p.value): Cannot reject null hypothesis of overall Friedman test,
$> returning overall Friedman test.
plotCritDifferences(g) + coord_cartesian(xlim = c(-1, 5), ylim = c(0, 2))
10.4.7 プロットの調整
プロットはggplot
のオブジェクトなので、これをカスタマイズすることで容易に独自のプロットを作成できる。これは既に見たところだ。一方、getBMRPerformances
やgetBMRAggrPerformances
が返すデータフレームを使って独自のプロットを作成することもできる。以下に例を見よう。
plotBMRBoxplots
では箱ひげ図を作成したが、密度プロットで性能指標を可視化してみよう(訳注: 原文ではqplot
を使用しているが個人的にわかりにくいのでggplot
で書き直した。また、strip textのフォントサイズ調整を省略している)。
pref = getBMRPerformances(bmr, as.df = TRUE)
ggplot(data = pref[pref$task.id %in% c("iris-example", "Sonar-example"), ],
aes(x = mmce, colour = learner.id)) +
facet_grid(.~task.id) +
geom_density()
複数の指標を並列に比較したければ、pref
をlong formatに変換する必要がある。以下にmmce
とtimetrain
を箱ひげ図で表示する例を示そう。
(訳注: 原文とは異なり、tidyr
、dplyr
、ggplot
を使用している。パイプ演算子%>%
を使えばより簡潔に書き換えることもできるだろう。)
df = tidyr::gather(pref, key = variable, value = value, mmce:timetrain)
df = dplyr::filter(df, variable != "ber")
ggplot(df, aes(x = variable, y = value, color = learner.id)) +
geom_boxplot(position = "dodge") +
labs(x = "measure", y = "performance") +
facet_wrap(~ task.id, nrow = 2)
リサンプリング毎の学習器間のパフォーマンスの関係を調べることが有用な場合もある。ある学習器が例外的に良い成績を出している場合に他の学習器の成績が悪いという様な場合があれば、より深い洞察が得られるだろう。これはまた、学習アルゴリズムにおいてアンサンブルを構築する際にも便利な場合がある。以下では、GGally
パッケージのggpairs
関数を使って、Sonar
データセットで学習を行った場合のmmce
の散布図行列を作成する例を見てみよう。
(訳注:ここも書き換えている)
pref = getBMRPerformances(bmr, task.id = "Sonar-example", as.df = TRUE)
df = tidyr::gather(pref, key = variable, value = value, mmce:timetrain)
df = dplyr::filter(df, variable == "mmce")
df = tidyr::spread(df, key = learner.id, value = value)
GGally::ggpairs(df, 4:6)
10.5 その他のコメント
- 教師あり学習については、
mlr
はBenchmarkResult
オブジェクトを使用して学習アルゴリズムを比較するためのプロットをさらにいくつか提供している。以下のページに例がある。 - このセクションの例では「生の」学習アルゴリズムを用いたが、通常はもっと事は複雑である。少なくとも、大抵の学習アルゴリズムは調整すべきハイパーパラメータを持っている。信頼性のある性能指標の推定値を得るためには、nested resampling、すなわち、内側のループでハイパーパラメータをチューニングしつつ外側のループで性能指標を推定するといった作業が必要になる。さらに、補完やスケーリング、外れ値除去、次元削減、特徴量選択などの前処理を組み合わせたい場合もあるだろう。これらの全ての作業は
mlr
のラッパー機能を使用すると容易に実行できる。より詳しい話はチュートリアルのAdvancedパートで説明する。 - ベンチマーク試験を行っていくと、すぐに多量の計算量が必要となるはずだ。
mlr
はこの問題に対応するため、並列計算の機能をいくつか提供している。