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つの組合せに対応している。ここでは分類のデフォルトの指標である平均誤分類率が示されている。

bmrBenchmarcResultクラスのオブジェクトである。基本的には、これはResampleResultクラスのオブジェクトのリストのリストを含んでおり、最初のリストはタスク、その中のリストは学習器に対応した並びになっている。

10.1.1 実験を再現可能にする

一般的にいって、実験は再現可能であることが望ましい。mlrset.seed関数の設定に従うので、スクリプト実行前にset.seedによって乱数種を固定しておけば再現性が確保できる。

もし並列計算を使用する場合は、ユースケースに合わせてset.seedの呼び出し方を調整する必要がある。例えば、set.seed(123, "L'Ecuyer")と指定すると子プロセス単位で再現性が確保できる。mcapplyの例(mclapply function | R Documentation)を見ると並列計算と再現性に関するもう少し詳しい情報が得られるだろう(訳注:こちらのほうが良いかも?R: Parallel version of lapply)。

10.2 ベンチマーク結果へのアクセス

mlrgetBMR<抽出対象>という名前のアクセサ関数を幾つか用意している。これにより、さらなる分析のために情報を探索することができる。これには検討中の学習アルゴリズムに関するパフォーマンスや予測などが含まれる。

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 = FALSEbenchmark関数に指定すれば良い。

予測へのアクセスは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が嫌なら、makeLearnermake*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

次に、bmrbmr2をマージする。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関数に指定していることに注意してもらいたい。つまり、ldarpartrandamForestおよび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"

プロットに関してよくある質問は、「パネルのラベルと学習器の名前はどうやったら変更できるのか」といったものだ。例えば、今見せた例で言えば、パネルのラベル名に含まれている-examplemlbench.を取り除いたり、学習機の名前を大文字の略称に変更したい、などの思ったのではないだろうか。現状、これを解決する一番簡単なやり方は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のオブジェクトなので、これをカスタマイズすることで容易に独自のプロットを作成できる。これは既に見たところだ。一方、getBMRPerformancesgetBMRAggrPerformancesが返すデータフレームを使って独自のプロットを作成することもできる。以下に例を見よう。

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()

複数の指標を並列に比較したければ、preflong formatに変換する必要がある。以下にmmcetimetrainを箱ひげ図で表示する例を示そう。

(訳注: 原文とは異なり、tidyrdplyrggplotを使用している。パイプ演算子%>%を使えばより簡潔に書き換えることもできるだろう。)

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 その他のコメント

  • 教師あり学習については、mlrBenchmarkResultオブジェクトを使用して学習アルゴリズムを比較するためのプロットをさらにいくつか提供している。以下のページに例がある。
  • このセクションの例では「生の」学習アルゴリズムを用いたが、通常はもっと事は複雑である。少なくとも、大抵の学習アルゴリズムは調整すべきハイパーパラメータを持っている。信頼性のある性能指標の推定値を得るためには、nested resampling、すなわち、内側のループでハイパーパラメータをチューニングしつつ外側のループで性能指標を推定するといった作業が必要になる。さらに、補完やスケーリング、外れ値除去、次元削減、特徴量選択などの前処理を組み合わせたい場合もあるだろう。これらの全ての作業はmlrのラッパー機能を使用すると容易に実行できる。より詳しい話はチュートリアルのAdvancedパートで説明する。
  • ベンチマーク試験を行っていくと、すぐに多量の計算量が必要となるはずだ。mlrはこの問題に対応するため、並列計算の機能をいくつか提供している。