Section 8 リサンプリング
一般的に学習機の性能評価はリサンプリングを通じて行われる。リサンプリングの概要は次のようなものである。まず、データセット全体を\(D\)として、これを訓練セット\(D^{*b}\)とテストセット\(D\setminus D^{*b}\)に分割する。この種の分割を\(B\)回行う(つまり、\(b = 1,...,B\)とする)。そして、それぞれのテストセット、訓練セットの対を用いて訓練と予測を行い、性能指標\(S(D^{*b}, D\setminus D^{*b}\))を計算する。これにより\(B\)個の性能指標が得られるが、これを集約する(一般的には平均値が用いられる)。リサンプリングの方法には、クロスバリデーションやブートストラップなど様々な手法が存在する。
もしさらに詳しく知りたいのであれば、Simonによる論文(Resampling Strategies for Model Assessment and Selection | SpringerLink)を読むのは悪い選択ではないだろう。また、Berndらによる論文、Resampling methods for meta-model validation with recommendations for evolutionary computationでは、リサンプリング手法の統計的な背景に対して多くの説明がなされている。
8.1 リサンプリング手法を決める
mlr
ではmakeResampleDesc
関数を使ってリサンプリング手法を設定する。この関数にはリサンプリング手法の名前とともに、手法に応じてその他の情報(例えば繰り返し数など)を指定する。サポートしているサンプリング手法は以下のとおりである。
CV
: クロスバリデーション(Cross-varidation)LOO
: 一つ抜き法(Leave-one-out cross-varidation)RepCV
: Repeatedクロスバリデーション(Repeated cross-varidation)Bootstrap
: out-of-bagブートストラップとそのバリエーション(b632等)Subsample
: サブサンプリング(モンテカルロクロスバリデーションとも呼ばれる)Holdout
: ホールドアウト法
3-fold(3分割)クロスバリデーションの場合は
rdesc = makeResampleDesc("CV", iters = 3)
rdesc
$> Resample description: cross-validation with 3 iterations.
$> Predict: test
$> Stratification: FALSE
ホールドアウト法の場合は
rdesc = makeResampleDesc("Holdout")
rdesc
$> Resample description: holdout with 0.67 split rate.
$> Predict: test
$> Stratification: FALSE
という具合だ。
これらのリサンプルdescriptionのうち、よく使うものは予め別名が用意してある。例えばホールドアウト法はhout
、クロスバリデーションはcv5
やcv10
などよく使う分割数に対して定義してある。
hout
$> Resample description: holdout with 0.67 split rate.
$> Predict: test
$> Stratification: FALSE
cv3
$> Resample description: cross-validation with 3 iterations.
$> Predict: test
$> Stratification: FALSE
8.2 リサンプリングを実行する
resample
関数は指定されたリサンプリング手法により、学習機をタスク上で評価する。
最初の例として、BostonHousing
データに対する線形回帰を3分割クロスバリデーションで評価してみよう。
\(K\)分割クロスバリデーションはデータセット\(D\)を\(K\)個の(ほぼ)等しいサイズのサブセットに分割する。\(K\)回の繰り返しの\(b\)番目では、\(b\)番目のサブセットがテストに、残りが訓練に使用される。
resample
関数に学習器を指定する際には、Learner
クラスのオブジェクトか学習器の名前(regr.lm
など)のいずれを渡しても良い。性能指標は指定しなければ学習器に応じたデフォルトが使用される(回帰の場合は平均二乗誤差)。
rdesc = makeResampleDesc("CV", iters = 3)
r = resample("regr.lm", bh.task, rdesc)
$> [Resample] cross-validation iter 1: mse.test.mean=20.7
$> [Resample] cross-validation iter 2: mse.test.mean= 25
$> [Resample] cross-validation iter 3: mse.test.mean=25.3
$> [Resample] Aggr. Result: mse.test.mean=23.7
r
$> Resample Result
$> Task: BostonHousing-example
$> Learner: regr.lm
$> Aggr perf: mse.test.mean=23.7
$> Runtime: 0.034143
ここでr
に格納したオブジェクトはResampleResult
クラスである。この中には評価結果の他に、実行時間や予測値、リサンプリング毎のフィット済みモデルなどが格納されている。
## 中身をざっと確認
names(r)
$> [1] "learner.id" "task.id" "task.desc" "measures.train"
$> [5] "measures.test" "aggr" "pred" "models"
$> [9] "err.msgs" "err.dumps" "extract" "runtime"
r$measures.test
には各テストセットの性能指標が入っている。
## 各テストセットの性能指標
r$measures.test
$> iter mse
$> 1 1 20.74068
$> 2 2 25.00755
$> 3 3 25.25456
r$aggr
には集約(aggrigate)後の性能指標が入っている。
## 集約後の性能指標
r$aggr
$> mse.test.mean
$> 23.6676
名前mse.test.mean
は、性能指標がmse
であり、test.mean
によりデータが集約されていることを表している。test.mean
は多くの性能指標においてデフォルトの集約方法であり、その名前が示すようにテストデータの性能指標の平均値である。
mlr
ではどのような種類の学習器も同じようにリサンプリングを行える。以下では、分類問題の例としてSonar
データセットに対する分類木を5反復のサブサンプリングで評価してみよう。
サブサンプリングの各繰り返しでは、データセット\(D\)はランダムに訓練データとテストデータに分割される。このとき、テストデータには指定の割合のデータ数が割り当てられる。この反復が1の場合はホールドアウト法と同じである。
評価したい性能指標はリストとしてまとめて指定することもできる。以下の例では平均誤分類、偽陽性・偽陰性率、訓練時間を指定している。
rdesc = makeResampleDesc("Subsample", iter = 5, split = 4/5)
lrn = makeLearner("classif.rpart", parms = list(split = "information"))
r = resample(lrn, sonar.task, rdesc, measures = list(mmce, fpr, fnr, timetrain))
$> [Resample] subsampling iter 1: mmce.test.mean=0.19,fpr.test.mean=0.263,fnr.test.mean=0.13,timetrain.test.mean=0.015
$> [Resample] subsampling iter 2: mmce.test.mean=0.286,fpr.test.mean= 0.2,fnr.test.mean=0.333,timetrain.test.mean=0.016
$> [Resample] subsampling iter 3: mmce.test.mean=0.167,fpr.test.mean=0.263,fnr.test.mean=0.087,timetrain.test.mean=0.013
$> [Resample] subsampling iter 4: mmce.test.mean=0.286,fpr.test.mean= 0.4,fnr.test.mean=0.182,timetrain.test.mean=0.017
$> [Resample] subsampling iter 5: mmce.test.mean=0.238,fpr.test.mean=0.278,fnr.test.mean=0.208,timetrain.test.mean=0.013
$> [Resample] Aggr. Result: mmce.test.mean=0.233,fpr.test.mean=0.281,fnr.test.mean=0.188,timetrain.test.mean=0.0148
r
$> Resample Result
$> Task: Sonar-example
$> Learner: classif.rpart
$> Aggr perf: mmce.test.mean=0.233,fpr.test.mean=0.281,fnr.test.mean=0.188,timetrain.test.mean=0.0148
$> Runtime: 0.136948
もし指標を後から追加したくなったら、addRRMeasure
関数を使うと良い。
addRRMeasure(r, list(ber, timepredict))
$> Resample Result
$> Task: Sonar-example
$> Learner: classif.rpart
$> Aggr perf: mmce.test.mean=0.233,fpr.test.mean=0.281,fnr.test.mean=0.188,timetrain.test.mean=0.0148,ber.test.mean=0.234,timepredict.test.mean=0.005
$> Runtime: 0.136948
デフォルトではresample
関数は進捗と途中結果を表示するが、show.info=FALSE
で非表示にもできる。このようなメッセージを完全に制御したかったら、Configuration - mlr tutorialを確認してもらいたい。
上記例では学習器を明示的に作成してからresample
に渡したが、代わりに学習器の名前を指定しても良い。その場合、学習器のパラメータは...
引数を通じて渡すことができる。
resample("classif.rpart", parms = list(split = "information"), sonar.task, rdesc,
measures = list(mmce, fpr, fnr, timetrain), show.info = FALSE)
$> Resample Result
$> Task: Sonar-example
$> Learner: classif.rpart
$> Aggr perf: mmce.test.mean=0.262,fpr.test.mean=0.256,fnr.test.mean=0.273,timetrain.test.mean=0.0148
$> Runtime: 0.135859
8.3 リサンプル結果へのアクセス
学習器の性能以外にも、リサンプル結果から様々な情報を得ることが出来る。例えばリサンプリングの各繰り返しに対応する予測値やフィット済みモデル等だ。以下で情報の取得の仕方をみていこう。
8.3.1 予測値
デフォルトでは、ResampleResult
はリサンプリングで得た予測値を含んでいる。メモリ節約などの目的でこれを止めさせたければ、resample
関数にkeep.pred = FALSE
を指定する。
予測値は$pred
スロットに格納されている。また、getRRPredictions
関数を使ってアクセスすることもできる。
r$pred
$> Resampled Prediction for:
$> Resample description: subsampling with 5 iterations and 0.80 split rate.
$> Predict: test
$> Stratification: FALSE
$> predict.type: response
$> threshold:
$> time (mean): 0.01
$> id truth response iter set
$> 1 194 M M 1 test
$> 2 59 R R 1 test
$> 3 113 M R 1 test
$> 4 191 M M 1 test
$> 5 32 R M 1 test
$> 6 170 M M 1 test
$> ... (210 rows, 5 cols)
pred = getRRPredictions(r)
pred
$> Resampled Prediction for:
$> Resample description: subsampling with 5 iterations and 0.80 split rate.
$> Predict: test
$> Stratification: FALSE
$> predict.type: response
$> threshold:
$> time (mean): 0.01
$> id truth response iter set
$> 1 194 M M 1 test
$> 2 59 R R 1 test
$> 3 113 M R 1 test
$> 4 191 M M 1 test
$> 5 32 R M 1 test
$> 6 170 M M 1 test
$> ... (210 rows, 5 cols)
ここで作成したpred
はResamplePrediction
クラスのオブジェクトである。これはPrediction
オブジェクトのように$data
にデータフレームとして予測値と真値(教師あり学習の場合)が格納されている。as.data.frame
を使って直接$data
スロットの中身を取得できる。さらに、Prediction
オブジェクトに対するゲッター関数は全て利用可能である。
head(as.data.frame(pred))
$> id truth response iter set
$> 1 194 M M 1 test
$> 2 59 R R 1 test
$> 3 113 M R 1 test
$> 4 191 M M 1 test
$> 5 32 R M 1 test
$> 6 170 M M 1 test
head(getPredictionTruth(pred))
$> [1] M R M M R M
$> Levels: M R
データフレームのiter
とset
は繰り返し回数とデータセットの種類(訓練なのかテストなのか)を示している。
デフォルトでは予測はテストセットだけに行われるが、makeResampleDesc
に対し、predict = "train"
を指定で訓練セットだけに、predict = "both"
を指定で訓練セットとテストセットの両方に予測を行うことが出来る。後で例を見るが、b632やb632+のようなブートストラップ手法ではこれらの設定が必要となる。
以下では単純なホールドアウト法の例を見よう。つまり、テストセットと訓練セットへの分割は一度だけ行い、予測は両方のデータセットを用いて行う。
rdesc = makeResampleDesc("Holdout", predict = "both")
r = resample("classif.lda", iris.task, rdesc, show.info = FALSE)
r
$> Resample Result
$> Task: iris-example
$> Learner: classif.lda
$> Aggr perf: mmce.test.mean=0.02
$> Runtime: 0.0158811
r$aggr
$> mmce.test.mean
$> 0.02
(predict="both"
の指定にかかわらず、r$aggr
ではテストデータに対するmmceしか計算しないことに注意してもらいたい。訓練セットに対して計算する方法はこの後で説明する。)
リサンプリング結果から予測を取り出す方法として、getRRPredictionList
を使う方法もある。これは、分割されたデータセット(訓練/テスト)それぞれと、リサンプリングの繰り返し毎に分割した単位でまとめた予測結果のリストを返す。
getRRPredictionList(r)
$> $train
$> $train$`1`
$> Prediction: 100 observations
$> predict.type: response
$> threshold:
$> time: 0.00
$> id truth response
$> 2 2 setosa setosa
$> 118 118 virginica virginica
$> 65 65 versicolor versicolor
$> 42 42 setosa setosa
$> 124 124 virginica virginica
$> 34 34 setosa setosa
$> ... (100 rows, 3 cols)
$>
$>
$>
$> $test
$> $test$`1`
$> Prediction: 50 observations
$> predict.type: response
$> threshold:
$> time: 0.00
$> id truth response
$> 91 91 versicolor versicolor
$> 78 78 versicolor versicolor
$> 7 7 setosa setosa
$> 146 146 virginica virginica
$> 139 139 virginica virginica
$> 9 9 setosa setosa
$> ... (50 rows, 3 cols)
8.3.2 訓練済みモデルの抽出
リサンプリング毎に学習器は訓練セットにフィットさせられる。標準では、WrappedModel
はResampleResult
オブジェクトには含まれておらず、$models
スロットは空だ。これを保持するためには、resample
関数を呼び出す際に引数models = TRUE
を指定する必要がある。以下に生存時間分析の例を見よう。
## 3分割クロスバリデーション
rdesc = makeResampleDesc("CV", iters = 3)
r = resample("surv.coxph", lung.task, rdesc, show.info = FALSE, models = TRUE)
r$models
$> [[1]]
$> Model for learner.id=surv.coxph; learner.class=surv.coxph
$> Trained on: task.id = lung-example; obs = 111; features = 8
$> Hyperparameters:
$>
$> [[2]]
$> Model for learner.id=surv.coxph; learner.class=surv.coxph
$> Trained on: task.id = lung-example; obs = 111; features = 8
$> Hyperparameters:
$>
$> [[3]]
$> Model for learner.id=surv.coxph; learner.class=surv.coxph
$> Trained on: task.id = lung-example; obs = 112; features = 8
$> Hyperparameters:
8.3.3 他の抽出方法
完全なフィット済みモデルを保持しようとすると、リサンプリングの繰り返し数が多かったりオブジェクトが大きかったりする場合にメモリの消費量が大きくなってしまう。モデルの全ての情報を保持する代わりに、resample
関数のextract
引数に指定することで必要な情報だけを保持することができる。引数extract
に対しては、リサンプリング毎の各WrapedModel
オブジェクトに適用するための関数を渡す必要がある。
以下では、mtcars
データセットをk=3のk-meansでクラスタリングし、クラスター中心だけを保持する例を紹介する。
rdesc = makeResampleDesc("CV", iter = 3)
r = resample("cluster.kmeans", mtcars.task, rdesc, show.info = FALSE,
centers = 3, extract = function(x){getLearnerModel(x)$centers})
r$extract
$> [[1]]
$> mpg cyl disp hp drat wt qsec vs
$> 1 24.33333 4.666667 119.9111 105.4444 3.972222 2.388667 17.98556 0.6666667
$> 2 17.31667 7.333333 271.4000 150.8333 2.968333 3.629167 18.25500 0.3333333
$> 3 14.53333 8.000000 386.8333 229.0000 3.423333 4.131500 16.33500 0.0000000
$> am gear carb
$> 1 0.7777778 4.222222 2.555556
$> 2 0.0000000 3.000000 2.166667
$> 3 0.1666667 3.333333 3.666667
$>
$> [[2]]
$> mpg cyl disp hp drat wt qsec vs
$> 1 24.69167 4.666667 121.1333 93.83333 4.018333 2.560833 18.68167 0.75
$> 2 14.76667 8.000000 437.3333 203.33333 3.080000 4.813333 17.48333 0.00
$> 3 15.35714 8.000000 328.8286 227.71429 3.438571 3.543571 16.09571 0.00
$> am gear carb
$> 1 0.7500000 4.083333 2.500000
$> 2 0.0000000 3.000000 3.333333
$> 3 0.2857143 3.571429 3.857143
$>
$> [[3]]
$> mpg cyl disp hp drat wt qsec vs am gear
$> 1 19.5000 6 195.640 114.200 3.51600 3.286000 18.77600 0.800 0.200 3.600
$> 2 14.9250 8 350.825 198.750 3.07500 4.105500 17.07750 0.000 0.125 3.250
$> 3 26.3375 4 110.675 83.625 4.04625 2.324125 19.13875 0.875 0.625 4.125
$> carb
$> 1 2.800
$> 2 3.500
$> 3 1.625
他の例として、フィット済みの回帰木から変数の重要度をgetFeatureImportance
を使って抽出してみよう(より詳しい内容はFeature Selection - mlr tutorialを確認してもらいたい)。
r = resample("regr.rpart", bh.task, rdesc, show.info = FALSE, extract = getFeatureImportance)
r$extract
$> [[1]]
$> FeatureImportance:
$> Task: BostonHousing-example
$>
$> Learner: regr.rpart
$> Measure: NA
$> Contrast: NA
$> Aggregation: function (x) x
$> Replace: NA
$> Number of Monte-Carlo iterations: NA
$> Local: FALSE
$> crim zn indus chas nox rm age dis
$> 1 3399.046 1192.183 4051.922 0 2303.742 15941.78 2269.408 2636.903
$> rad tax ptratio b lstat
$> 1 830.9189 1045.856 2626.595 0 11415.22
$>
$> [[2]]
$> FeatureImportance:
$> Task: BostonHousing-example
$>
$> Learner: regr.rpart
$> Measure: NA
$> Contrast: NA
$> Aggregation: function (x) x
$> Replace: NA
$> Number of Monte-Carlo iterations: NA
$> Local: FALSE
$> crim zn indus chas nox rm age dis
$> 1 2122.88 579.2956 4377.805 188.1338 3020.542 14975.56 3097.911 3183.877
$> rad tax ptratio b lstat
$> 1 657.2457 2952.654 2856.316 0 10148.19
$>
$> [[3]]
$> FeatureImportance:
$> Task: BostonHousing-example
$>
$> Learner: regr.rpart
$> Measure: NA
$> Contrast: NA
$> Aggregation: function (x) x
$> Replace: NA
$> Number of Monte-Carlo iterations: NA
$> Local: FALSE
$> crim zn indus chas nox rm age dis
$> 1 2588.126 1981.96 3616.241 365.7574 3703.958 16503.61 3212.413 4428.26
$> rad tax ptratio b lstat
$> 1 639.1926 2930.041 2398.208 747.6475 11787.72
8.4 階層化とブロック化
- カテゴリー変数に対する階層化とは、訓練セットとテストセット内で各値の比率が変わらないようにすることを指す。階層化が可能なのは目的変数がカテゴリーである場合(教師あり学習における分類や生存時間分析)や、説明変数がカテゴリーである場合に限られる。
- ブロック化とは、観測値の一部分をブロックとして扱い、リサンプリングの間にブロックが分割されないように扱うことを指す。つまり、ブロック全体は訓練セットかテストセットのいずれかにまとまって属すことになる。
8.4.1 目的変数の階層化
分類においては、元のデータと同じ比率で各クラスの値が含まれていることが望ましい。これはクラス間の観測数が不均衡であったり、データセットの大きさが小さい場合に有効である。さもなければ、観測数が少ないクラスのデータが訓練セットに含まれないということが起こりうる。これは分類性能の低下やモデルのクラッシュにつながる。階層化リサンプリングを行うためには、makeResampleDesc
実行時にstratify = TRUE
を指定する。
rdesc = makeResampleDesc("CV", iters = 3, stratify = TRUE)
r = resample("classif.lda", iris.task, rdesc, show.info = FALSE)
r
$> Resample Result
$> Task: iris-example
$> Learner: classif.lda
$> Aggr perf: mmce.test.mean=0.0199
$> Runtime: 0.0313618
階層化を生存時間分析に対して行う場合は、打ち切りの割合が制御される。
8.4.2 説明変数の階層化
説明変数の階層化が必要な場合もある。この場合は、stratify.cols
引数に対して階層化したい因子型変数を指定する。
rdesc = makeResampleDesc("CV", iter = 3, stratify.cols = "chas")
r = resample("regr.rpart", bh.task, rdesc, show.info = FALSE)
r
$> Resample Result
$> Task: BostonHousing-example
$> Learner: regr.rpart
$> Aggr perf: mse.test.mean= 23
$> Runtime: 0.045078
8.4.3 ブロック化
いくつかの観測値が互いに関連しており、これらが訓練データとテストデータに分割されるのが望ましくない場合には、タスク作成時にその情報をblocking
引数に因子型ベクトルを与えることで指定する。
## それぞれ30の観測値からなる5つのブロックを指定する例
task = makeClassifTask(data = iris, target = "Species", blocking = factor(rep(1:5, each = 30)))
task
$> Supervised task: iris
$> Type: classif
$> Target: Species
$> Observations: 150
$> Features:
$> numerics factors ordered
$> 4 0 0
$> Missings: FALSE
$> Has weights: FALSE
$> Has blocking: TRUE
$> Classes: 3
$> setosa versicolor virginica
$> 50 50 50
$> Positive class: NA
8.5 リサンプリングの詳細とリサンプルのインスタンス
既に説明したように、リサンプリング手法はmakeResampleDesc
関数を使って指定する。
rdesc = makeResampleDesc("CV", iter = 3)
rdesc
$> Resample description: cross-validation with 3 iterations.
$> Predict: test
$> Stratification: FALSE
str(rdesc)
$> List of 4
$> $ id : chr "cross-validation"
$> $ iters : int 3
$> $ predict : chr "test"
$> $ stratify: logi FALSE
$> - attr(*, "class")= chr [1:2] "CVDesc" "ResampleDesc"
上記rdesc
はResampleDesc
クラス(resample descriptionの略)を継承しており、原則として、リサンプリング手法に関する必要な情報(繰り返し数、訓練セットとテストセットの比率、階層化したい変数など)を全て含んでいる。
makeResampleInstance
関数は、データセットに含まれるデータ数を直接指定するか、タスクを指定することで、ResampleDesc
に従って訓練セットとテストセットの概要を生成する。
## taskに基づくリサンプルインスタンスの生成
rin = makeResampleInstance(rdesc, iris.task)
rin
$> Resample instance for 150 cases.
$> Resample description: cross-validation with 3 iterations.
$> Predict: test
$> Stratification: FALSE
str(rin)
$> List of 5
$> $ desc :List of 4
$> ..$ id : chr "cross-validation"
$> ..$ iters : int 3
$> ..$ predict : chr "test"
$> ..$ stratify: logi FALSE
$> ..- attr(*, "class")= chr [1:2] "CVDesc" "ResampleDesc"
$> $ size : int 150
$> $ train.inds:List of 3
$> ..$ : int [1:100] 104 35 40 114 123 13 9 31 146 16 ...
$> ..$ : int [1:100] 104 55 35 114 13 8 17 97 78 111 ...
$> ..$ : int [1:100] 55 40 123 9 8 31 146 16 17 128 ...
$> $ test.inds :List of 3
$> ..$ : int [1:50] 6 8 12 15 17 24 26 29 33 36 ...
$> ..$ : int [1:50] 2 4 9 14 16 18 20 22 23 31 ...
$> ..$ : int [1:50] 1 3 5 7 10 11 13 19 21 25 ...
$> $ group : Factor w/ 0 levels:
$> - attr(*, "class")= chr "ResampleInstance"
## データセットのサイズを指定してリサンプルインスタンスを生成する例
rin = makeResampleInstance(rdesc, size = nrow(iris))
str(rin)
$> List of 5
$> $ desc :List of 4
$> ..$ id : chr "cross-validation"
$> ..$ iters : int 3
$> ..$ predict : chr "test"
$> ..$ stratify: logi FALSE
$> ..- attr(*, "class")= chr [1:2] "CVDesc" "ResampleDesc"
$> $ size : int 150
$> $ train.inds:List of 3
$> ..$ : int [1:100] 119 64 54 128 100 34 63 110 22 56 ...
$> ..$ : int [1:100] 119 128 140 47 100 34 115 139 95 58 ...
$> ..$ : int [1:100] 64 54 140 47 63 110 22 56 115 139 ...
$> $ test.inds :List of 3
$> ..$ : int [1:50] 1 4 8 11 12 17 18 19 24 26 ...
$> ..$ : int [1:50] 3 6 7 10 14 20 22 23 25 28 ...
$> ..$ : int [1:50] 2 5 9 13 15 16 21 27 29 31 ...
$> $ group : Factor w/ 0 levels:
$> - attr(*, "class")= chr "ResampleInstance"
ここでrin
はResampleInstance
クラスを継承しており、訓練セットとテストセットのインデックスをリストとして含んでいる。
ResampleDesc
がresample
に渡されると、インスタンスの生成は内部的に行われる。もちろん、ResampleInstance
を直接渡すこともできる。
リサンプルの詳細(resample description)とリサンプルのインスタンス、そしてリサンプル関数と分割するのは、複雑にしすぎているのではと感じるかもしれないが、幾つかの利点がある。
- リサンプルインスタンスを用いると、同じ訓練セットとテストセットを用いて学習器の性能比較を行うことが容易になる。これは、既に実施した性能比較試験に対し、他の手法を追加したい場合などに特に便利である。また、後で結果を再現するためにデータとリサンプルインスタンスをセットで保管しておくこともできる。
rdesc = makeResampleDesc("CV", iter = 3)
rin = makeResampleInstance(rdesc, task = iris.task)
## 同じインスタンスを使い、2種類の学習器で性能指標を計算する
r.lda = resample("classif.lda", iris.task, rin, show.info = FALSE)
r.rpart = resample("classif.rpart", iris.task, rin, show.info = FALSE)
c("lda" = r.lda$aggr, "rpart" = r.rpart$aggr)
$> lda.mmce.test.mean rpart.mmce.test.mean
$> 0.04000000 0.06666667
- 新しいリサンプリング手法を追加したければ、
ResampleDesc
およびResampleInstance
クラスのインスタンスを作成すればよく、resample
関数やそれ以上のメソッドに触る必要はない。
通常、makeResampleInstance
を呼び出したときの訓練セットとテストセットのインデックスはランダムに割り当てられる。主にホールドアウト法においては、これを完全にマニュアルで行わなければならない場面がある。これはmakeFixedHoldoutInstance
関数を使うと実現できる。
rin = makeFixedHoldoutInstance(train.inds = 1:100, test.inds = 101:150, size = 150)
rin
$> Resample instance for 150 cases.
$> Resample description: holdout with 0.67 split rate.
$> Predict: test
$> Stratification: FALSE
8.6 性能指標の集約
リサンプリングそれぞれに対して性能指標を計算したら、それを集計する必要がある。
大半のリサンプリング手法(ホールドアウト法、クロスバリデーション、サブサンプリングなど)では、性能指標はテストデータのみで計算され、平均によって集約される。
mlr
における性能指標を表現するMeasure
クラスのオブジェクトは、$aggr
スロットに対応するデフォルトの集約手法を格納している。大半はtest.mean
である。例外の一つは平均二乗誤差平方根(rmse)である。
## 一般的な集約手法
mmce$aggr
$> Aggregation function: test.mean
## 具体的な計算方法
mmce$aggr$fun
$> function (task, perf.test, perf.train, measure, group, pred)
$> mean(perf.test)
$> <bytecode: 0x7f824614ebc8>
$> <environment: namespace:mlr>
## rmseの場合
rmse$aggr
$> Aggregation function: test.rmse
## test.rmseの具体的な計算方法
rmse$aggr$fun
$> function (task, perf.test, perf.train, measure, group, pred)
$> sqrt(mean(perf.test^2))
$> <bytecode: 0x7f8260ba6eb0>
$> <environment: namespace:mlr>
setAggrigation
関数を使うと、集約方法を変更することも出来る。利用可能な集約手法の一覧はaggregations function | R Documentationを確認してほしい。
8.6.1 例: 一つの指標に複数の集約方法
test.median
、test.min
、test.max
はそれぞれテストセットから求めた性能指標を中央値、最小値、最大値で集約する。
mseTestMedian = setAggregation(mse, test.median)
mseTestMin = setAggregation(mse, test.min)
mseTestMax = setAggregation(mse, test.max)
rdesc = makeResampleDesc("CV", iter = 3)
r = resample("regr.lm", bh.task, rdesc, show.info = FALSE,
measures = list(mse, mseTestMedian, mseTestMin, mseTestMax))
r
$> Resample Result
$> Task: BostonHousing-example
$> Learner: regr.lm
$> Aggr perf: mse.test.mean=25.7,mse.test.median=25.5,mse.test.min=18.5,mse.test.max=33.1
$> Runtime: 0.0360069
r$aggr
$> mse.test.mean mse.test.median mse.test.min mse.test.max
$> 25.69050 25.50319 18.46241 33.10591
8.6.2 例: 訓練セットの誤差を計算する
平均誤分類率を訓練セットとテストセットに対して計算する例を示す。makeResampleDesc
実行時にpredict = "both"
を指定しておく必要があることに注意してもらいたい。
mmceTrainMean = setAggregation(mmce, train.mean)
rdesc = makeResampleDesc("CV", iters = 3, predict = "both")
r = resample("classif.rpart", iris.task, rdesc, measures = list(mmce, mmceTrainMean))
$> [Resample] cross-validation iter 1: mmce.train.mean=0.03,mmce.test.mean=0.18
$> [Resample] cross-validation iter 2: mmce.train.mean=0.04,mmce.test.mean=0.04
$> [Resample] cross-validation iter 3: mmce.train.mean=0.03,mmce.test.mean=0.06
$> [Resample] Aggr. Result: mmce.test.mean=0.0933,mmce.train.mean=0.0333
8.6.3 例: ブートストラップ
out-of-bagブートストラップ推定では、まず元のデータセット\(D\)から重複ありの抽出によって\(D^{*1}, ...,D^{*B}\)と\(B\)個の新しいデータセット(要素数は元のデータセットと同じ)を作成する。そして、\(b\)回目の繰り返しでは、\(D^{*b}\)を訓練セットに使い、使われなかった要素\(D\setminus D^{*b}\)をテストセットに用いて各繰り返しに対する推定値を計算し、最終的に\(B\)個の推定値を得る。
out-of-bagブートストラップの変種であるb632とb632+では、訓練セットのパフォーマンスとOOBサンプルのパフォーマンスの凸結合を計算するため、訓練セットに対する予測と適切な集計方法を必要とする。
## ブートストラップをリサンプリング手法に選び、予測は訓練セットとテストセットの両方に行う
rdesc = makeResampleDesc("Bootstrap", predict = "both", iters = 10)
## b632およびb632+専用の集計手法を設定する
mmceB632 = setAggregation(mmce, b632)
mmceB632plus = setAggregation(mmce, b632plus)
r = resample("classif.rpart", iris.task, rdesc, measures = list(mmce, mmceB632, mmceB632plus),
show.info = FALSE)
r$measures.train
$> iter mmce mmce mmce
$> 1 1 0.026666667 0.026666667 0.026666667
$> 2 2 0.020000000 0.020000000 0.020000000
$> 3 3 0.026666667 0.026666667 0.026666667
$> 4 4 0.026666667 0.026666667 0.026666667
$> 5 5 0.013333333 0.013333333 0.013333333
$> 6 6 0.020000000 0.020000000 0.020000000
$> 7 7 0.020000000 0.020000000 0.020000000
$> 8 8 0.020000000 0.020000000 0.020000000
$> 9 9 0.006666667 0.006666667 0.006666667
$> 10 10 0.020000000 0.020000000 0.020000000
r$aggr
$> mmce.test.mean mmce.b632 mmce.b632plus
$> 0.07758371 0.05639290 0.05790858
8.7 便利な関数
これまでに説明した方法は柔軟ではあるが、学習器を少し試してみたい場合にはタイプ数が多くて面倒である。mlr
には様々な略記法が用意してあるが、リサンプリング手法についても同様である。ホールドアウトやクロスバリデーション、ブートストラップ(b632)等のよく使うリサンプリング手法にはそれぞれ特有の関数が用意してある。
crossval("classif.lda", iris.task, iters = 3, measures = list(mmce, ber))
$> [Resample] cross-validation iter 1: mmce.test.mean=0.02,ber.test.mean=0.0222
$> [Resample] cross-validation iter 2: mmce.test.mean=0.04,ber.test.mean=0.0333
$> [Resample] cross-validation iter 3: mmce.test.mean=0.02,ber.test.mean=0.0167
$> [Resample] Aggr. Result: mmce.test.mean=0.0267,ber.test.mean=0.0241
$> Resample Result
$> Task: iris-example
$> Learner: classif.lda
$> Aggr perf: mmce.test.mean=0.0267,ber.test.mean=0.0241
$> Runtime: 0.035866
bootstrapB632plus("regr.lm", bh.task, iters = 3, measures = list(mse, mae))
$> [Resample] OOB bootstrapping iter 1: mse.b632plus=18.6,mae.b632plus=3.06,mse.b632plus=30.8,mae.b632plus=3.42
$> [Resample] OOB bootstrapping iter 2: mse.b632plus=23.2,mae.b632plus=3.49,mse.b632plus=17.3,mae.b632plus=3.06
$> [Resample] OOB bootstrapping iter 3: mse.b632plus=18.9,mae.b632plus=2.96,mse.b632plus=27.4,mae.b632plus=3.58
$> [Resample] Aggr. Result: mse.b632plus=23.5,mae.b632plus=3.29
$> Resample Result
$> Task: BostonHousing-example
$> Learner: regr.lm
$> Aggr perf: mse.b632plus=23.5,mae.b632plus=3.29
$> Runtime: 0.0782452