mlrぽいす!
— 職業、イケメン。Nagi Teramo (@teramonagi) 2018年2月15日
ということだったので仏の教えに従ってチュートリアル(mlr tutorial)を読んでいる。mlr
パッケージのチュートリアルは開発版に対応したものとCRANに上がっているものに対応したものの2種類あるが、どちらかというと開発版を参考にしている。翻訳っぽいけど原文を結構無視しているのでアレな部分があるかもしれません。
とりあえず予測して可視化するところまで。前処理とかチューニングは次回に。
- 次: Rの機械学習パッケージmlrのチュートリアル2(前処理からチューニングまで) - Qiita)
- 次の次:Rの機械学習パッケージmlrのチュートリアル3(ベンチマーク試験から可視化まで) - Qiita
- まとめたやつ: mlrパッケージチュートリアル - Quick Walkthrough編
Quick start
mlr
パッケージにおけるワークフローは、ざっくりタスクの作成、学習器の作成、訓練、予測、評価という5つのステップに分けることができる。分類問題を例にみてみよう。
## mlrパッケージをインストールし、読み込んでおく。
# install.packages("mlr", dependencies = TRUE)
library(mlr)
## 1) タスクの定義 ----
# ここではデータと応答変数、問題の種類を定義する。
task = makeClassifTask(data = iris, target = "Species")
## 2) 学習器の定義 ----
# アルゴリズムを選択する。
lrn = makeLearner("classif.lda")
# (データを訓練セットとテストセットに分割する)
n = nrow(iris)
train.set = sample(n, size = 2/3*n)
test.set = setdiff(1:n, train.set)
## 3) 訓練 ----
# 訓練セットのランダムなサブセットを用いて学習器を訓練し、モデルを作成
model = train(lrn, task, subset = train.set)
## 4) 予測 ----
# モデルに基いてテストセットの入力変数に対応する出力を予測する
pred = predict(model, task = task, subset = test.set)
## 5) 評価 ----
# 誤分類率並びに精度を計算する
performance(pred, measures = list(mmce, acc))
$> mmce acc
$> 0.04 0.96
タスク
タスクはデータ及び機械学習問題に関する情報、例えば教師あり学習におけるターゲットの名前などをカプセル化したものだ。
タスクの種類と作成
全てのタスクはTask
クラスを頂点とする階層構造を持っている。以下に示すクラスは全てTask
のサブクラスである。
-
RegrTask
: 回帰分析に関するタスク。 -
ClassifTask
: 2クラス分類または多クラス分類に関するタスク(注: コストがクラス依存であるコスト考慮型分類も扱うことができる)。 -
SurvTask
: 生存時間分析に関するタスク。 -
ClusterTask
: クラスター分析に関するタスク。 -
MultilabelTask
: マルチラベル分類に関するタスク。 -
CostSensTask
: 一般のコスト考慮型分類に関するタスク(コストが事例に依存するもの)。
タスクを作成するには、make<タスク名>
というような名前の関数を使う。例えば分類タスクであればmakeClassifTask
である。全てのタスクはID(引数id
に指定する)とデータフレーム(引数data
に指定する)を最低限必要とする。ただし、IDを未指定の場合は、データの変数名に基づいて自動的に割り当てられる。IDはプロットの際の注釈や、ベンチマークテストの際に名前として使われる。また、問題の性質に応じて、追加の引数が必要となる場合もある。
以下にそれぞれのタスクの生成方法を説明する。
回帰
教師あり学習である回帰では、data
の他に目的変数列名であるtarget
を指定する必要がある。これは後に見る分類と生存時間分析においても同様である。
data(BostonHousing, package = "mlbench")
regr.task = makeRegrTask(id = "bh", data = BostonHousing, target = "medv")
regr.task
$> Supervised task: bh
$> Type: regr
$> Target: medv
$> Observations: 506
$> Features:
$> numerics factors ordered
$> 12 1 0
$> Missings: FALSE
$> Has weights: FALSE
$> Has blocking: FALSE
Task
オブジェクトの中には、学習問題のタイプと、データセットに関する基本的な情報(例えば特徴量の型やデータ数、欠測値の有無など)が格納されている。
分類でも生存時間分析でもタスク作成の基本的な枠組みは同じである。ただし、data
の中の目的変数の種類は異なる。これについては以下で述べる。
分類
分類問題では、目的変数は因子型でなければならない。
以下にBreastCancer
データセットを使って分類タスクを作成する例を示そう。ここではId
列を除外していることに注意してもらいたい(訳注: Id
列はその意味から考えて特徴量に含めるのが適当でないのは当然のこと、character
型であるため特徴量に含めようとしてもエラーがでる)。
data(BreastCancer, package = "mlbench")
df = BreastCancer
df$Id = NULL
classif.task = makeClassifTask(id = "BreastCancer", data = df, target = "Class")
classif.task
$> Supervised task: BreastCancer
$> Type: classif
$> Target: Class
$> Observations: 699
$> Features:
$> numerics factors ordered
$> 0 4 5
$> Missings: TRUE
$> Has weights: FALSE
$> Has blocking: FALSE
$> Classes: 2
$> benign malignant
$> 458 241
$> Positive class: benign
2クラス分類においては、しばしばそれぞれのクラスをpositiveとnegativeに対応させる。上記例を見るとわかるように、デフォルトでは因子型における最初のレベルがpositiveに割り当てられるが、引数positive
によって明示的に指定することもできる。
makeClassifTask(data = df, target = "Class", positive = "malignant")
$> Supervised task: df
$> Type: classif
$> Target: Class
$> Observations: 699
$> Features:
$> numerics factors ordered
$> 0 4 5
$> Missings: TRUE
$> Has weights: FALSE
$> Has blocking: FALSE
$> Classes: 2
$> benign malignant
$> 458 241
$> Positive class: malignant
生存時間分析
生存時間分析においては目的変数列が2つ必要になる。左打ち切り及び右打ち切りデータでは、生存時間と打ち切りかどうかを示す二値変数が必要である。区間打ち切りデータでは、interval2
形式でのデータ指定が必要である(詳しくはSurv function | R Documentationを参照)。
data(lung, package = "survival")
# statusは1=censored, 2=deadとして符号化されているので、
# 論理値に変換する必要がある。
lung$status = (lung$status == 2)
surv.task = makeSurvTask(data = lung, target = c("time", "status"))
surv.task
$> Supervised task: lung
$> Type: surv
$> Target: time,status
$> Events: 165
$> Observations: 228
$> Features:
$> numerics factors ordered
$> 8 0 0
$> Missings: TRUE
$> Has weights: FALSE
$> Has blocking: FALSE
打ち切りの種類はcensoring
引数で明示的に指定できる。デフォルトはrcens
(右打ち切り)である。
マルチラベル分類
マルチラベル分類とは、対象が複数のカテゴリに同時に属す可能性があるような分類問題である。
data
にはクラスラベルと同じだけの数の目的変数列が必要である。また、それぞれの目的変数列は論理値によってそのクラスに属するかどうかを示す必要がある。
以下にyeast
データを用いた例を示そう。
yeast = getTaskData(yeast.task)
labels = colnames(yeast)[1:14]
yeast.task = makeMultilabelTask(id = "multi", data = yeast, target = labels)
yeast.task
$> Supervised task: multi
$> Type: multilabel
$> Target: label1,label2,label3,label4,label5,label6,label7,label8,label9,label10,label11,label12,label13,label14
$> Observations: 2417
$> Features:
$> numerics factors ordered
$> 103 0 0
$> Missings: FALSE
$> Has weights: FALSE
$> Has blocking: FALSE
$> Classes: 14
$> label1 label2 label3 label4 label5 label6 label7 label8 label9
$> 762 1038 983 862 722 597 428 480 178
$> label10 label11 label12 label13 label14
$> 253 289 1816 1799 34
クラスター分析
クラスター分析は教師なし学習の一種である。タスクの作成に必須の引数はdata
だけだ。mtcars
を使ってクラスター分析のタスクを作成する例を示そう。
data(mtcars, package = "datasets")
cluster.task = makeClusterTask(data = mtcars)
cluster.task
$> Unsupervised task: mtcars
$> Type: cluster
$> Observations: 32
$> Features:
$> numerics factors ordered
$> 11 0 0
$> Missings: FALSE
$> Has weights: FALSE
$> Has blocking: FALSE
コスト考慮型分類
一般に分類問題では精度を最大化すること、つまり誤分類の数を最小化することが目的となる。つまり、これは全ての誤分類の価値を平等と考えるということである。しかし、問題によっては間違いの価値は平等とは言えないことがある。例えば健康や金融に関わる問題では、ある間違いは他の間違いよりより深刻であるということがあり得ることは容易に想像できるだろう。
コスト考慮型問題のうち、コストがクラスラベルの種類にのみ依存するような問題は、ClassifTask
にて扱うことができる。
一方、コストが事例に依存するような例はCostSensTask
でタスクを作成する必要がある。このケースでは入力xと出力yからなる事例(x, y)がそれぞれコストベクトルKに結びついていることを想定する。コストベクトルKはクラスラベルの数と同じ長さをもち、k番目の要素はxをクラスkに結びつけた場合のコストを表す。当然、yはコストを最小化するように選択されることが期待される。
コストベクトルはクラスyに関する全ての情報を包含するので、CostSensTask
を作成するために必要なのは、全ての事例に対するコストベクトルをまとめたコスト行列と、特徴量のみである。
iris
と人工的に作成したコスト行列を使ってコスト考慮型分類タスクを作成する例を示そう。
set.seed(123)
df = iris
cost = matrix(runif(150 * 3, 0, 2000), 150) * (1 - diag(3))[df$Species, ]
df$Species = NULL
costsens.task = makeCostSensTask(data = df, cost = cost)
costsens.task
$> Supervised task: df
$> Type: costsens
$> Observations: 150
$> Features:
$> numerics factors ordered
$> 4 0 0
$> Missings: FALSE
$> Has blocking: FALSE
$> Classes: 3
$> y1, y2, y3
その他の設定
それぞれのタスク作成関数のヘルプページを確認すると、その他の引数についての情報を得ることができるだろう。
例えば、blocking
引数は、幾つかの観測値が一緒であることを示す。これによって、リサンプリングの際にそれらのデータが分割されなくなる。
他にweights
という引数がある。これは単に観測頻度やデータ採取方法に由来する重みを表現するための方法であって、重みが本当にタスクに属している場合にのみ使用するようにしてもらいたい。もし、同じタスク内で重みを変化させて訓練をしたいと考えているのであれば、mlr
はそのための他の方法を用意している。詳しくはtraining
のチュートリアルページかmakeWeightedClassesWrapper
関数のヘルプを確認してもらいたい。
タスクへのアクセス
タスクオブジェクト内の要素を取得する方法は複数ある。これらの中で重要なものは各タスクおよびgetTaskData
のヘルプページにリストアップされている。
まずは?TaskDesc
でリストアップされている要素を取得する方法を示そう。
getTaskDesc(classif.task)
$> $id
$> [1] "BreastCancer"
$>
$> $type
$> [1] "classif"
$>
$> $target
$> [1] "Class"
$>
$> $size
$> [1] 699
$>
$> $n.feat
$> numerics factors ordered
$> 0 4 5
$>
$> $has.missings
$> [1] TRUE
$>
$> $has.weights
$> [1] FALSE
$>
$> $has.blocking
$> [1] FALSE
$>
$> $class.levels
$> [1] "benign" "malignant"
$>
$> $positive
$> [1] "benign"
$>
$> $negative
$> [1] "malignant"
$>
$> attr(,"class")
$> [1] "ClassifTaskDesc" "SupervisedTaskDesc" "TaskDesc"
取得できる要素の種類はタスクの種類によって多少異なる。
よく使う要素については、直接アクセスする手段が用意されている。
## ID
getTaskId(classif.task)
$> [1] "BreastCancer"
## タスクの種類
getTaskType(classif.task)
$> [1] "classif"
## 目的変数の列名
getTaskTargetNames(classif.task)
$> [1] "Class"
## 観測値の数
getTaskSize(classif.task)
$> [1] 699
## 特徴量の種類数
getTaskNFeats(classif.task)
$> [1] 9
## クラスレベル
getTaskClassLevels(classif.task)
$> [1] "benign" "malignant"
mlr
はさらに幾つかの関数を提供する。
## タスク内のデータ
str(getTaskData(classif.task))
$> 'data.frame': 699 obs. of 10 variables:
$> $ Cl.thickness : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 5 5 3 6 4 8 1 2 2 4 ...
$> $ Cell.size : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 4 1 8 1 10 1 1 1 2 ...
$> $ Cell.shape : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 4 1 8 1 10 1 2 1 1 ...
$> $ Marg.adhesion : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 5 1 1 3 8 1 1 1 1 ...
$> $ Epith.c.size : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 2 7 2 3 2 7 2 2 2 2 ...
$> $ Bare.nuclei : Factor w/ 10 levels "1","2","3","4",..: 1 10 2 4 1 10 10 1 1 1 ...
$> $ Bl.cromatin : Factor w/ 10 levels "1","2","3","4",..: 3 3 3 3 3 9 3 3 1 2 ...
$> $ Normal.nucleoli: Factor w/ 10 levels "1","2","3","4",..: 1 2 1 7 1 7 1 1 1 1 ...
$> $ Mitoses : Factor w/ 9 levels "1","2","3","4",..: 1 1 1 1 1 1 1 1 5 1 ...
$> $ Class : Factor w/ 2 levels "benign","malignant": 1 1 1 1 1 2 1 1 1 1 ...
## 特徴量の名前
getTaskFeatureNames(cluster.task)
$> [1] "mpg" "cyl" "disp" "hp" "drat" "wt" "qsec" "vs" "am" "gear"
$> [11] "carb"
## 目的変数の値
head(getTaskTargets(surv.task))
$> time status
$> 1 306 TRUE
$> 2 455 TRUE
$> 3 1010 FALSE
$> 4 210 TRUE
$> 5 883 TRUE
$> 6 1022 FALSE
## コスト行列
head(getTaskCosts(costsens.task))
$> y1 y2 y3
$> [1,] 0 1694.9063 1569.15053
$> [2,] 0 995.0545 18.85981
$> [3,] 0 775.8181 1558.13177
$> [4,] 0 492.8980 1458.78130
$> [5,] 0 222.1929 1260.26371
$> [6,] 0 779.9889 961.82166
タスクの編集
mlr
には既存のタスクを編集するための関数も用意されている。既存タスクの編集は、新しいタスクをゼロから作成するよりも便利な場合がある。以下に例を示そう。
## dataの編集
cluster.task2 = subsetTask(cluster.task, subset = 4:17)
getTaskData(cluster.task)
$> mpg cyl disp hp drat wt qsec vs am gear carb
$> Mazda RX4 21.0 6 160.0 110 3.90 2.620 16.46 0 1 4 4
$> Mazda RX4 Wag 21.0 6 160.0 110 3.90 2.875 17.02 0 1 4 4
$> Datsun 710 22.8 4 108.0 93 3.85 2.320 18.61 1 1 4 1
$> Hornet 4 Drive 21.4 6 258.0 110 3.08 3.215 19.44 1 0 3 1
$> Hornet Sportabout 18.7 8 360.0 175 3.15 3.440 17.02 0 0 3 2
$> Valiant 18.1 6 225.0 105 2.76 3.460 20.22 1 0 3 1
$> Duster 360 14.3 8 360.0 245 3.21 3.570 15.84 0 0 3 4
$> Merc 240D 24.4 4 146.7 62 3.69 3.190 20.00 1 0 4 2
$> Merc 230 22.8 4 140.8 95 3.92 3.150 22.90 1 0 4 2
$> Merc 280 19.2 6 167.6 123 3.92 3.440 18.30 1 0 4 4
$> Merc 280C 17.8 6 167.6 123 3.92 3.440 18.90 1 0 4 4
$> Merc 450SE 16.4 8 275.8 180 3.07 4.070 17.40 0 0 3 3
$> Merc 450SL 17.3 8 275.8 180 3.07 3.730 17.60 0 0 3 3
$> Merc 450SLC 15.2 8 275.8 180 3.07 3.780 18.00 0 0 3 3
$> Cadillac Fleetwood 10.4 8 472.0 205 2.93 5.250 17.98 0 0 3 4
$> Lincoln Continental 10.4 8 460.0 215 3.00 5.424 17.82 0 0 3 4
$> Chrysler Imperial 14.7 8 440.0 230 3.23 5.345 17.42 0 0 3 4
$> Fiat 128 32.4 4 78.7 66 4.08 2.200 19.47 1 1 4 1
$> Honda Civic 30.4 4 75.7 52 4.93 1.615 18.52 1 1 4 2
$> Toyota Corolla 33.9 4 71.1 65 4.22 1.835 19.90 1 1 4 1
$> Toyota Corona 21.5 4 120.1 97 3.70 2.465 20.01 1 0 3 1
$> Dodge Challenger 15.5 8 318.0 150 2.76 3.520 16.87 0 0 3 2
$> AMC Javelin 15.2 8 304.0 150 3.15 3.435 17.30 0 0 3 2
$> Camaro Z28 13.3 8 350.0 245 3.73 3.840 15.41 0 0 3 4
$> Pontiac Firebird 19.2 8 400.0 175 3.08 3.845 17.05 0 0 3 2
$> Fiat X1-9 27.3 4 79.0 66 4.08 1.935 18.90 1 1 4 1
$> Porsche 914-2 26.0 4 120.3 91 4.43 2.140 16.70 0 1 5 2
$> Lotus Europa 30.4 4 95.1 113 3.77 1.513 16.90 1 1 5 2
$> Ford Pantera L 15.8 8 351.0 264 4.22 3.170 14.50 0 1 5 4
$> Ferrari Dino 19.7 6 145.0 175 3.62 2.770 15.50 0 1 5 6
$> Maserati Bora 15.0 8 301.0 335 3.54 3.570 14.60 0 1 5 8
$> Volvo 142E 21.4 4 121.0 109 4.11 2.780 18.60 1 1 4 2
getTaskData(cluster.task2)
$> mpg cyl disp hp drat wt qsec vs am gear carb
$> Hornet 4 Drive 21.4 6 258.0 110 3.08 3.215 19.44 1 0 3 1
$> Hornet Sportabout 18.7 8 360.0 175 3.15 3.440 17.02 0 0 3 2
$> Valiant 18.1 6 225.0 105 2.76 3.460 20.22 1 0 3 1
$> Duster 360 14.3 8 360.0 245 3.21 3.570 15.84 0 0 3 4
$> Merc 240D 24.4 4 146.7 62 3.69 3.190 20.00 1 0 4 2
$> Merc 230 22.8 4 140.8 95 3.92 3.150 22.90 1 0 4 2
$> Merc 280 19.2 6 167.6 123 3.92 3.440 18.30 1 0 4 4
$> Merc 280C 17.8 6 167.6 123 3.92 3.440 18.90 1 0 4 4
$> Merc 450SE 16.4 8 275.8 180 3.07 4.070 17.40 0 0 3 3
$> Merc 450SL 17.3 8 275.8 180 3.07 3.730 17.60 0 0 3 3
$> Merc 450SLC 15.2 8 275.8 180 3.07 3.780 18.00 0 0 3 3
$> Cadillac Fleetwood 10.4 8 472.0 205 2.93 5.250 17.98 0 0 3 4
$> Lincoln Continental 10.4 8 460.0 215 3.00 5.424 17.82 0 0 3 4
$> Chrysler Imperial 14.7 8 440.0 230 3.23 5.345 17.42 0 0 3 4
data
のサブセットをとると、特徴量によっては値に変化がなくなる場合がある。上記の例ではam
列の値が全て0になる。このような特徴量を除外する関数としてremoveConstantFeatures
がある。
removeConstantFeatures(cluster.task2)
$> Removing 1 columns: am
$> Unsupervised task: mtcars
$> Type: cluster
$> Observations: 14
$> Features:
$> numerics factors ordered
$> 10 0 0
$> Missings: FALSE
$> Has weights: FALSE
$> Has blocking: FALSE
特定の特徴量を除外したい場合は、dropFeatures
が使える。
dropFeatures(surv.task, c("meal.cal", "wt.loss"))
$> Supervised task: lung
$> Type: surv
$> Target: time,status
$> Events: 165
$> Observations: 228
$> Features:
$> numerics factors ordered
$> 6 0 0
$> Missings: TRUE
$> Has weights: FALSE
$> Has blocking: FALSE
数値型の特徴量を正規化したければnormalizeFeatures
を使おう。
task = normalizeFeatures(cluster.task, method = "range")
summary(getTaskData(task))
$> mpg cyl disp hp
$> Min. :0.0000 Min. :0.0000 Min. :0.0000 Min. :0.0000
$> 1st Qu.:0.2138 1st Qu.:0.0000 1st Qu.:0.1240 1st Qu.:0.1572
$> Median :0.3745 Median :0.5000 Median :0.3123 Median :0.2509
$> Mean :0.4124 Mean :0.5469 Mean :0.3982 Mean :0.3346
$> 3rd Qu.:0.5277 3rd Qu.:1.0000 3rd Qu.:0.6358 3rd Qu.:0.4523
$> Max. :1.0000 Max. :1.0000 Max. :1.0000 Max. :1.0000
$> drat wt qsec vs
$> Min. :0.0000 Min. :0.0000 Min. :0.0000 Min. :0.0000
$> 1st Qu.:0.1475 1st Qu.:0.2731 1st Qu.:0.2848 1st Qu.:0.0000
$> Median :0.4309 Median :0.4633 Median :0.3821 Median :0.0000
$> Mean :0.3855 Mean :0.4358 Mean :0.3987 Mean :0.4375
$> 3rd Qu.:0.5346 3rd Qu.:0.5362 3rd Qu.:0.5238 3rd Qu.:1.0000
$> Max. :1.0000 Max. :1.0000 Max. :1.0000 Max. :1.0000
$> am gear carb
$> Min. :0.0000 Min. :0.0000 Min. :0.0000
$> 1st Qu.:0.0000 1st Qu.:0.0000 1st Qu.:0.1429
$> Median :0.0000 Median :0.5000 Median :0.1429
$> Mean :0.4062 Mean :0.3438 Mean :0.2589
$> 3rd Qu.:1.0000 3rd Qu.:0.5000 3rd Qu.:0.4286
$> Max. :1.0000 Max. :1.0000 Max. :1.0000
タスクの例と便利な関数
利便性のために、mlr
には予めいくつかのタスクが定義してある。チュートリアルでもコードを短くするためにこれらを使う場合がある。これらの一覧はExample Tasks - mlr tutorialを参照のこと。
mlbench
パッケージに含まれるデータセットやデータ生成関数に由来するデータからタスクを生成するための関数として、convertMLBenchObjToTask
が用意してある。
学習器
以下に示すクラスは、(コスト考慮型)分類、回帰、生存時間分析、クラスタリングのための統一的なインターフェースを提供する。多くの手法はすでにmlr
に統合されているが、そうでないものもある。しかし、パッケージは容易に拡張できるよう設計されている。
Integrated Learners - mlr tutorialには実装済みの手法とその特徴について一覧が示してある。もし使いたい手法が見つからなければ、issueに書き込むか、Create Custom Learners - mlr tutorialを確認してもらいたい。
まずは実装済みの手法についていかに使用するかを説明しよう。
学習器を構築する
学習器はmakeLearner
関数で作成する。このとき、どのような学習手法を使うのかを指定する。加えて以下の要素を指定できる。
- ハイパーパラメータの指定。
- 予測後の出力方法(例えば、分類問題において予測されたクラスラベルなのか、確率なのか)
- ID(いくつかの手法ではこのIDを出力やプロット時の名前として利用できる)
## ランダムフォレストによる分類で確率も出力する
classif.lrn = makeLearner(
"classif.randomForest", predict.type = "prob", fix.factors.prediction = TRUE)
## 勾配ブースティング回帰でハイパーパラメータも指定する
regr.lrn = makeLearner(
"regr.gbm", par.vals = list(n.trees = 500, interaction.depth = 3))
## コックス比例ハザードモデルでidも指定する
surv.lrn = makeLearner("surv.coxph", id = "cph")
## K平均法でクラスタ数を指定する
cluster.lrn = makeLearner("cluster.kmeans", centers = 5)
## マルチラベルRandom-Ferns
multilabel.lrn = makeLearner("multilabel.rFerns")
最初の引数はどのアルゴリズムを使うのかを指定する。アルゴリズム名は以下の命名規則に従っている。
-
classif.<Rのメソッド名>
: 分類 -
regr.<Rのメソッド名>
: 回帰 -
surv.<Rのメソッド名>
: 生存時間分析 -
cluster.<Rのメソッド名>
: クラスター分析 -
multilabel.<Rのメソッド名>
: マルチラベル分類
ハイパーパラメータは...
引数として渡すか、par.vals
引数にリストとして渡せる。
因子型の特徴量は、訓練データよりテストデータの方が水準が少なくなってしまうという問題が起こるときがある。fix.factors.prediction = TRUE
を指定しておけば、不足する水準をテストデータに加えるという方法によってこの問題を回避できる。
では、先ほど作成した学習器の中身を見てみよう。
classif.lrn
$> Learner classif.randomForest from package randomForest
$> Type: classif
$> Name: Random Forest; Short name: rf
$> Class: classif.randomForest
$> Properties: twoclass,multiclass,numerics,factors,ordered,prob,class.weights,oobpreds,featimp
$> Predict-Type: prob
$> Hyperparameters:
surv.lrn
$> Learner cph from package survival
$> Type: surv
$> Name: Cox Proportional Hazard Model; Short name: coxph
$> Class: surv.coxph
$> Properties: numerics,factors,weights,rcens
$> Predict-Type: response
$> Hyperparameters:
全ての学習器はLearner
クラスのオブジェクトである。クラスには、どのような種類の特徴量を扱えるのか、予測の際にはどのような種類の出力が可能か、マルチクラス分類の問題なのか、観測値は重み付けられているのか、欠測値はサポートされているのか、といった、手法に関する情報が含まれている。
気づいたかも知れないが、今のところコスト考慮型分類に関する専用のクラスはない。一般的な誤分類コストについては、標準的な分類手法で扱うことができる。事例依存的なコストについては、コスト考慮型の学習器を一般的な回帰と分類の学習機から生成するための方法がいくつかある。この点についてはこのセクションで扱うには大きすぎるので、別途セクションを設けて解説してある。
学習器へアクセスする
Learner
オブジェクトはリストであり、ハイパーパラメータと予測の種類に関する情報を含んでいる。
## デフォルト値以外を指定したハイパーパラメータ
cluster.lrn$par.vals
$> $centers
$> [1] 5
## ハイパーパラメータ一覧
cluster.lrn$par.set
$> Type len Def Constr
$> centers untyped - - -
$> iter.max integer - 10 1 to Inf
$> nstart integer - 1 1 to Inf
$> algorithm discrete - Hartigan-Wong Hartigan-Wong,Lloyd,Forgy,MacQueen
$> trace logical - - -
$> Req Tunable Trafo
$> centers - TRUE -
$> iter.max - TRUE -
$> nstart - TRUE -
$> algorithm - TRUE -
$> trace - FALSE -
##予測のタイプ
regr.lrn$predict.type
$> [1] "response"
$par.set
スロットにはParamSet
クラスのオブジェクトが入っている。これには、ハイパーパラメータの型(数値なのか論理型なのか)、デフォルト値、そして可能な値の範囲が格納されている。
また、mlr
はLerner
の現在のハイパーパラメータの設定にアクセスするgetHyperPars
やgetLernerParVals
、可能な設定項目の詳細を取得するgetParamSet
関数を用意している。これらは、ラップされた学習器において特に有用である場合がある。例えば、学習器が特徴量選択の手法と融合しており、特徴量選択手法と学習器の両方がハイパーパラメータを持つような場合である。この点については別途セクションを設けて解説している。
## ハイパーパラメータのセッティングの取得
getHyperPars(cluster.lrn)
$> $centers
$> [1] 5
## 設定可能なハイパーパラメータの詳細一覧
getParamSet(cluster.lrn)
$> Type len Def Constr
$> centers untyped - - -
$> iter.max integer - 10 1 to Inf
$> nstart integer - 1 1 to Inf
$> algorithm discrete - Hartigan-Wong Hartigan-Wong,Lloyd,Forgy,MacQueen
$> trace logical - - -
$> Req Tunable Trafo
$> centers - TRUE -
$> iter.max - TRUE -
$> nstart - TRUE -
$> algorithm - TRUE -
$> trace - FALSE -
また、getParamSet
(またはそのエイリアスであるgetLearnerParamSet
)を使い、Lerner
オブジェクトを作成すること無くそのデフォルト値を取得することもできる。
getParamSet("classif.randomForest")
$> Type len Def Constr Req Tunable Trafo
$> ntree integer - 500 1 to Inf - TRUE -
$> mtry integer - - 1 to Inf - TRUE -
$> replace logical - TRUE - - TRUE -
$> classwt numericvector <NA> - 0 to Inf - TRUE -
$> cutoff numericvector <NA> - 0 to 1 - TRUE -
$> strata untyped - - - - FALSE -
$> sampsize integervector <NA> - 1 to Inf - TRUE -
$> nodesize integer - 1 1 to Inf - TRUE -
$> maxnodes integer - - 1 to Inf - TRUE -
$> importance logical - FALSE - - TRUE -
$> localImp logical - FALSE - - TRUE -
$> proximity logical - FALSE - - FALSE -
$> oob.prox logical - - - Y FALSE -
$> norm.votes logical - TRUE - - FALSE -
$> do.trace logical - FALSE - - FALSE -
$> keep.forest logical - TRUE - - FALSE -
$> keep.inbag logical - FALSE - - FALSE -
学習器に関するメタデータにアクセスするための関数も用意してある。
## 学習器のID
getLearnerId(surv.lrn)
$> [1] "cph"
## 学習器の略称
getLearnerShortName(classif.lrn)
$> [1] "rf"
## 学習機のタイプ
getLearnerType(multilabel.lrn)
$> [1] "multilabel"
## 学習器に必要なパッケージ
getLearnerPackages(cluster.lrn)
$> [1] "stats" "clue"
学習器の編集
Learner
オブジェクトを作り直すことなしに編集する関数が用意されている。以下に例を示そう。
## IDの変更
surv.lrn = setLearnerId(surv.lrn, "CoxModel")
surv.lrn
$> Learner CoxModel from package survival
$> Type: surv
$> Name: Cox Proportional Hazard Model; Short name: coxph
$> Class: surv.coxph
$> Properties: numerics,factors,weights,rcens
$> Predict-Type: response
$> Hyperparameters:
## 予測タイプの変更
classif.lrn = setPredictType(classif.lrn, "response")
## ハイパーパラメータ
cluster.lrn = setHyperPars(cluster.lrn, centers = 4)
## 設定値を除去してデフォルト値に戻す
regr.lrn = removeHyperPars(regr.lrn, c("n.trees", "interaction.depth"))
学習器一覧
mlr
に統合されている学習器とその特性はIntegrated Learners - mlr tutorialに示してある。
もし、特定のプロパティや特定のタスクに対して利用可能な学習器の一覧がほしければ、listLearners
関数を使うと良いだろう。
## 全ての学習器一覧
head(listLearners()[c("class", "package")])
$> Warning in listLearners.character(obj = NA_character_, properties, quiet, : The following learners could not be constructed, probably because their packages are not installed:
$> classif.bartMachine,classif.extraTrees,classif.IBk,classif.J48,classif.JRip,classif.OneR,classif.PART,cluster.Cobweb,cluster.EM,cluster.FarthestFirst,cluster.SimpleKMeans,cluster.XMeans,regr.bartMachine,regr.extraTrees,regr.IBk
$> Check ?learners to see which packages you need or install mlr with all suggestions.
$> class package
$> 1 classif.ada ada
$> 2 classif.bdk kohonen
$> 3 classif.binomial stats
$> 4 classif.blackboost mboost,party
$> 5 classif.boosting adabag,rpart
$> 6 classif.bst bst
## 確率を出力可能な分類器
head(listLearners("classif", properties = "prob")[c("class", "package")])
$> Warning in listLearners.character("classif", properties = "prob"): The following learners could not be constructed, probably because their packages are not installed:
$> classif.bartMachine,classif.extraTrees,classif.IBk,classif.J48,classif.JRip,classif.OneR,classif.PART,cluster.Cobweb,cluster.EM,cluster.FarthestFirst,cluster.SimpleKMeans,cluster.XMeans,regr.bartMachine,regr.extraTrees,regr.IBk
$> Check ?learners to see which packages you need or install mlr with all suggestions.
$> class package
$> 1 classif.ada ada
$> 2 classif.bdk kohonen
$> 3 classif.binomial stats
$> 4 classif.blackboost mboost,party
$> 5 classif.boosting adabag,rpart
$> 6 classif.C50 C50
## iris(つまり多クラス)に使えて、確率を出力できる
head(listLearners(iris.task, properties = "prob")[c("class", "package")])
$> Warning in listLearners.character(td$type, union(props, properties), quiet, : The following learners could not be constructed, probably because their packages are not installed:
$> classif.bartMachine,classif.extraTrees,classif.IBk,classif.J48,classif.JRip,classif.OneR,classif.PART,cluster.Cobweb,cluster.EM,cluster.FarthestFirst,cluster.SimpleKMeans,cluster.XMeans,regr.bartMachine,regr.extraTrees,regr.IBk
$> Check ?learners to see which packages you need or install mlr with all suggestions.
$> class package
$> 1 classif.bdk kohonen
$> 2 classif.boosting adabag,rpart
$> 3 classif.C50 C50
$> 4 classif.cforest party
$> 5 classif.ctree party
$> 6 classif.cvglmnet glmnet
## Learnerオブジェクトを作成することもできる
head(listLearners("cluster", create = TRUE), 2)
$> Warning in listLearners.character("cluster", create = TRUE): The following learners could not be constructed, probably because their packages are not installed:
$> classif.bartMachine,classif.extraTrees,classif.IBk,classif.J48,classif.JRip,classif.OneR,classif.PART,cluster.Cobweb,cluster.EM,cluster.FarthestFirst,cluster.SimpleKMeans,cluster.XMeans,regr.bartMachine,regr.extraTrees,regr.IBk
$> Check ?learners to see which packages you need or install mlr with all suggestions.
$> [[1]]
$> Learner cluster.cmeans from package e1071,clue
$> Type: cluster
$> Name: Fuzzy C-Means Clustering; Short name: cmeans
$> Class: cluster.cmeans
$> Properties: numerics,prob
$> Predict-Type: response
$> Hyperparameters: centers=2
$>
$>
$> [[2]]
$> Learner cluster.dbscan from package fpc
$> Type: cluster
$> Name: DBScan Clustering; Short name: dbscan
$> Class: cluster.dbscan
$> Properties: numerics
$> Predict-Type: response
$> Hyperparameters: eps=1
学習器の訓練
学習器の訓練というのは要するにモデルをデータセットに適合させることだ。これにより学習器はデータセットに合わせた予測能力を得る。mlr
パッケージでは、train
関数を学習器とタスクに対し呼び出すことで実行できる。
まずは分類問題の例として、irisデータセットで線形判別分析を行ってみよう。
## タスクの作成
task = makeClassifTask(data = iris, target = "Species")
## 学習器の作成
lrn = makeLearner("classif.lda")
## 学習器の訓練
mod = train(lrn, task)
mod
$> Model for learner.id=classif.lda; learner.class=classif.lda
$> Trained on: task.id = iris; obs = 150; features = 4
$> Hyperparameters:
上記の例では実際には明示的に学習器を作成する必要はない。学習器のデフォルト値(ハイパーパラメータや予測タイプなど)を変更したい場合には、明示的に学習器を作成する必要がある。そうでなければ、train
や他の多くの関数にはLernerのクラス名を指定すればよい。そうすればデフォルトの設定でmakeLearner
が呼び出され、学習器に指定される。
mod = train("classif.lda", task)
mod
$> Model for learner.id=classif.lda; learner.class=classif.lda
$> Trained on: task.id = iris; obs = 150; features = 4
$> Hyperparameters:
どのようなタイプの問題でも、学習器の訓練の仕方は同じだ。生存時間分析の例として、コックス比例ハザードモデルをlung
データセットに適用する例を示す(タスクとしてmlr
パッケージに予め用意されているlung.task
を使用している点に注意してもらいたい)。
mod = train("surv.coxph", lung.task)
mod
$> Model for learner.id=surv.coxph; learner.class=surv.coxph
$> Trained on: task.id = lung-example; obs = 167; features = 8
$> Hyperparameters:
学習器モデルへのアクセス
train
関数はWrappedModel
クラスのオブジェクトを返す。このオブジェクトはフィット済みのモデル、すなわち基礎となるRの学習メソッドの出力をカプセル化している。加えて、オブジェクトには学習器、タスク、訓練に使った特徴量と観測値、訓練にかかった時間なども含まれている。WrappedModel
は続けて新しい観測値を使った予測に使用することができる。
フィット済みモデルは$learner.model
スロットに入っており、getLearnerModel
関数でアクセスできる。
以下にruspini
データセット(これは4つのグループと2つの特徴量を持つ)をK=4のK-means法でクラスタ化する例を示すとともに、基本となるkmeans
関数から出力を抽出する。まず、データの中身をプロットして確認しておこう。
data(ruspini, package = "cluster")
plot(y~x, ruspini)
## タスクの作成
ruspini.task = makeClusterTask(data = ruspini)
## 学習器の作成
lrn = makeLearner("cluster.kmeans", centers = 4)
## 学習機の訓練
mod = train(lrn, ruspini.task)
mod
$> Model for learner.id=cluster.kmeans; learner.class=cluster.kmeans
$> Trained on: task.id = ruspini; obs = 75; features = 2
$> Hyperparameters: centers=4
## モデルの中身を覗いてみる
names(mod)
$> [1] "learner" "learner.model" "task.desc" "subset"
$> [5] "features" "factor.levels" "time" "dump"
mod$features
$> [1] "x" "y"
mod$time
$> [1] 0.002
## フィット済みモデルの抽出
getLearnerModel(mod)
$> K-means clustering with 4 clusters of sizes 23, 15, 17, 20
$>
$> Cluster means:
$> x y
$> 1 43.91304 146.0435
$> 2 68.93333 19.4000
$> 3 98.17647 114.8824
$> 4 20.15000 64.9500
$>
$> Clustering vector:
$> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
$> 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 1 1 1 1 1
$> 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
$> 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 3 3 3 3 3 3
$> 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
$> 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
$>
$> Within cluster sum of squares by cluster:
$> [1] 3176.783 1456.533 4558.235 3689.500
$> (between_SS / total_SS = 94.7 %)
$>
$> Available components:
$>
$> [1] "cluster" "centers" "totss" "withinss"
$> [5] "tot.withinss" "betweenss" "size" "iter"
$> [9] "ifault"
その他のオプションとコメント
デフォルトではタスクに含まれる全てのデータが訓練に使用される。subset
引数に論理型または整数ベクトルを与えることで、どのデータを訓練に使用するのかを指定できる。これは例えば、データを訓練データとテストデータに分割したい場合や、データの部分ごとに異なるモデルを適用したい場合などに活用できる。
ランダムに選んだ1/3のデータを訓練データとして、BostonHousing
データに線形回帰モデルを適用する例を示そう。
## 観測値の例数を取得
n = getTaskSize(bh.task)
## 1:nからランダムにn/3個非復元抽出
train.set = sample(n, size = n/3)
## 学習器を訓練する
mod = train("regr.lm", bh.task, subset = train.set)
mod
$> Model for learner.id=regr.lm; learner.class=regr.lm
$> Trained on: task.id = BostonHousing-example; obs = 168; features = 13
$> Hyperparameters:
ところであとで見るように、標準的なリサンプリング手法はサポートされている。したがって、基本的には自分でデータのサブセットを指定する必要はない。
また、学習器がサポートしている場合には、weights
引数にこれを指定することで訓練に観測値の重みを反映させることができる。重みは観測値の信頼性や、外れ値の影響の低減、(長期間に渡ってデータを採取する場合)最近取得したデータの重要性を高めるなど、様々な目的で使用できる。教師あり分類においては、誤分類コストを組み込んだり、クラス間の不均衡を反映したりできる。
例えばBreastCancer
データではbenign
はmalignant
のほぼ2倍発生している。この2つのクラスを平等に扱うために、各事例をクラス頻度の逆数によって重み付けすることができる。以下に例を示そう。
## 観測値の重みを計算する
target = getTaskTargets(bc.task)
tab = as.numeric(table(target))
w = 1/tab[target]
train("classif.rpart", task = bc.task, weights = w)
$> Model for learner.id=classif.rpart; learner.class=classif.rpart
$> Trained on: task.id = BreastCancer-example; obs = 683; features = 9
$> Hyperparameters: xval=0
あとで見るように、mlr
は上記例のような不均衡な分類問題を扱うために非常に多くの機能を備えている。
上級者へ: train
を呼び出す際の重みを変えることで、任意のmlr
の学習器をベースとしたブースティングタイプのアルゴリズムを実装することもできる。
気づいたと思うが、Task
オブジェクト作成時に重み付けをすることもできる。一般的には、重みが本質的にタスクに属しており、常に設定されるべきだと考える場合にはTask
オブジェクト作成時に設定すべきだ。そうでなければ、訓練時に設定しよう。なお、train
呼び出し時に設定した重みは、Task
オブジェクト作成時に設定したものよりも優先される。
予測
新しいデータに対する結果を予測する
新しい観測値に対する目的変数の予測は、Rの他の予測手法と同じように実装されている。一般的には、predict
をtrain
が返すオブジェクトに対して呼び出し、予測したいデータを渡すだけだ。
データを渡す方法は2種類ある。
-
task
引数を通じてTask
オブジェクトを渡す。 -
newdata
引数を通じてdata.frame
を渡す。
最初の方法は、予測したいデータが既にTask
オブジェクトに含まれている場合に適している。
train
と同様に、predict
もsubset
引数を備えている。したがって、Task
オブジェクトに含まれるデータの異なる部分を訓練と予測に割り当てることができる(より進んだデータ分割の方法はリサンプリングのセクションであらためて解説する)。
以下に、BostonHousing
データに対し、1つおきの観測値に勾配ブースティングマシンによるフィットを行い、残った観測値に対して予測を行う例を示す。データはbh.task
に予め入っているものを使用する。
n = getTaskSize(bh.task)
train.set = seq(1, n, by = 2)
test.set = seq(2, n, by = 2)
lrn = makeLearner("regr.gbm", n.trees = 100)
mod = train(lrn, bh.task, subset = train.set)
task.pred = predict(mod, task = bh.task, subset = test.set)
task.pred
$> Prediction: 253 observations
$> predict.type: response
$> threshold:
$> time: 0.00
$> id truth response
$> 2 2 21.6 22.21070
$> 4 4 33.4 23.25389
$> 6 6 28.7 22.30314
$> 8 8 27.1 22.14860
$> 10 10 18.9 22.14860
$> 12 12 18.9 22.14860
$> ... (253 rows, 3 cols)
2つめの方法は予測したいデータがTask
オブジェクトに含まれていない場合に使える。
目的変数を除外したiris
を使ってクラスター分析を行う例を示そう。奇数インデックスの要素をTask
オブジェクトに含めて訓練を行い、残ったオブジェクトに対して予測を行う。
n = nrow(iris)
iris.train = iris[seq(1, n, by = 2), -5]
iris.test = iris[seq(2, n, by = 2), -5]
task = makeClusterTask(data = iris.train)
mod = train("cluster.kmeans", task)
newdata.pred = predict(mod, newdata = iris.test)
newdata.pred
$> Prediction: 75 observations
$> predict.type: response
$> threshold:
$> time: 0.00
$> response
$> 2 1
$> 4 1
$> 6 1
$> 8 1
$> 10 1
$> 12 1
$> ... (75 rows, 1 cols)
なお、教師あり学習の場合はデータセットから目的変数列を削除する必要はない。これはpredict
を呼び出す際に自動的に削除される。
予測へのアクセス
predict
関数はPrediction
クラスの名前付きリストを返す。もっとも重要な要素は$data
であり、このdata.frameは目的変数の真値と予測値の列を含む(教師あり学習の場合)。as.data.frame
を使うとこれに直接アクセスできる。
## task引数を通じてデータを渡した例の結果
head(as.data.frame(task.pred))
$> id truth response
$> 2 2 21.6 22.21070
$> 4 4 33.4 23.25389
$> 6 6 28.7 22.30314
$> 8 8 27.1 22.14860
$> 10 10 18.9 22.14860
$> 12 12 18.9 22.14860
## newdata引数を通じてデータを渡した場合の結果
head(as.data.frame(newdata.pred))
$> response
$> 2 1
$> 4 1
$> 6 1
$> 8 1
$> 10 1
$> 12 1
Task
オブジェクトを通じてデータを渡した例の結果を見るとわかるように、結果のdata.frameにはid
列が追加されている。これは、予測値が元のデータセットのどの値に対応しているのかを示している。
真値と予測値に直接アクセスするための関数としてgetPredictionTruth
関数とgetPredictionResponse
関数が用意されている。
head(getPredictionTruth(task.pred))
$> [1] 21.6 33.4 28.7 27.1 18.9 18.9
head(getPredictionResponse(task.pred))
$> [1] 22.21070 23.25389 22.30314 22.14860 22.14860 22.14860
回帰: 標準誤差を取得する
学習器のなかには標準誤差の出力に対応しているものがあるが、これもmlr
からアクセスできる。対応している学習器の一覧は、listLearners
関数に引数properties = "se"
を指定して呼び出すことで取得できる。このとき、check.packages = FALSE
を指定することで、他のパッケージ由来の学習器で当該パッケージをまだインストールしていないものについても一覧に含めることができる。
listLearners("regr", properties = "se", check.packages = FALSE)[c("class", "name")]
$> Warning in listLearners.character("regr", properties = "se", check.packages = FALSE): The following learners could not be constructed, probably because their packages are not installed:
$> classif.bartMachine,classif.extraTrees,classif.IBk,classif.J48,classif.JRip,classif.OneR,classif.PART,cluster.Cobweb,cluster.EM,cluster.FarthestFirst,cluster.SimpleKMeans,cluster.XMeans,regr.bartMachine,regr.extraTrees,regr.IBk
$> Check ?learners to see which packages you need or install mlr with all suggestions.
$> class
$> 1 regr.bcart
$> 2 regr.bgp
$> 3 regr.bgpllm
$> 4 regr.blm
$> 5 regr.btgp
$> 6 regr.btgpllm
$> name
$> 1 Bayesian CART
$> 2 Bayesian Gaussian Process
$> 3 Bayesian Gaussian Process with jumps to the Limiting Linear Model
$> 4 Bayesian Linear Model
$> 5 Bayesian Treed Gaussian Process
$> 6 Bayesian Treed Gaussian Process with jumps to the Limiting Linear Model
$> ... (15 rows, 2 cols)
標準誤差出力の例として、BostonHousing
に線形回帰モデルを適用する場合を示そう。標準誤差を計算するためには、predict.type
に"se"
を指定する。
lrn.lm = makeLearner("regr.lm", predict.type = "se")
mod.lm = train(lrn.lm, bh.task, subset = train.set)
task.pred.lm = predict(mod.lm, task = bh.task, subset = test.set)
task.pred.lm
$> Prediction: 253 observations
$> predict.type: se
$> threshold:
$> time: 0.00
$> id truth response se
$> 2 2 21.6 24.83734 0.7501615
$> 4 4 33.4 28.38206 0.8742590
$> 6 6 28.7 25.16725 0.8652139
$> 8 8 27.1 19.38145 1.1963265
$> 10 10 18.9 18.66449 1.1793944
$> 12 12 18.9 21.25802 1.0727918
$> ... (253 rows, 4 cols)
標準誤差だけが欲しければ、getPredictionSE
関数を使用する。
head(getPredictionSE(task.pred.lm))
$> [1] 0.7501615 0.8742590 0.8652139 1.1963265 1.1793944 1.0727918
分類とクラスタリング: 確率を取得する
予測値に対する確率はPrediction
オブジェクトにgetPredictionProbabilities
関数を使うことで取得できる。以下にクラスタ分析の別の例を示そう。ここではファジイc-means法によりmtcarsデータセットをクラスタリングしている。
lrn = makeLearner("cluster.cmeans", predict.type = "prob")
mod = train(lrn, mtcars.task)
pred = predict(mod, task = mtcars.task)
head(getPredictionProbabilities(pred))
$> 1 2
$> Mazda RX4 0.97959897 0.020401030
$> Mazda RX4 Wag 0.97963919 0.020360814
$> Datsun 710 0.99265882 0.007341184
$> Hornet 4 Drive 0.54294733 0.457052672
$> Hornet Sportabout 0.01870877 0.981291228
$> Valiant 0.75748529 0.242514707
分類問題においては、注目すべきものがいくつかあるが、デフォルトではクラスラベルが予測される。
mod = train("classif.lda", task = iris.task)
pred = predict(mod, task = iris.task)
pred
$> Prediction: 150 observations
$> predict.type: response
$> threshold:
$> time: 0.00
$> id truth response
$> 1 1 setosa setosa
$> 2 2 setosa setosa
$> 3 3 setosa setosa
$> 4 4 setosa setosa
$> 5 5 setosa setosa
$> 6 6 setosa setosa
$> ... (150 rows, 3 cols)
事後確率を得たければ、学習器を作成する際にpredict.type
引数に適当な値を指定する必要がある。
lrn = makeLearner("classif.rpart", predict.type = "prob")
mod = train(lrn, iris.task)
pred = predict(mod, newdata = iris)
head(as.data.frame(pred))
$> truth prob.setosa prob.versicolor prob.virginica response
$> 1 setosa 1 0 0 setosa
$> 2 setosa 1 0 0 setosa
$> 3 setosa 1 0 0 setosa
$> 4 setosa 1 0 0 setosa
$> 5 setosa 1 0 0 setosa
$> 6 setosa 1 0 0 setosa
クラスラベルは確率が最大のものが選択され、確率がタイの要素があればアトランダムに選択される。
もし事後確率だけがほしければgetPredictionProbabilities
関数を使う。
head(getPredictionProbabilities(pred))
$> setosa versicolor virginica
$> 1 1 0 0
$> 2 1 0 0
$> 3 1 0 0
$> 4 1 0 0
$> 5 1 0 0
$> 6 1 0 0
分類: 混同行列を取得する
混同行列はcalculateConfusionMatrix
関数により得ることが出来る。列は予測したクラス、行は真のクラスのラベルを表す。
calculateConfusionMatrix(pred)
$> predicted
$> true setosa versicolor virginica -err.-
$> setosa 50 0 0 0
$> versicolor 0 49 1 1
$> virginica 0 5 45 5
$> -err.- 0 5 1 6
対角成分には正しく分類された要素の数が、それ以外の部分には誤分類された要素の数が現れる。また、-err.-
の行および列には誤分類された要素の合計数が表示される。
relative=TRUE
を指定することで、相対頻度を得ることも出来る。
conf.matrix = calculateConfusionMatrix(pred, relative = TRUE)
conf.matrix
$> Relative confusion matrix (normalized by row/column):
$> predicted
$> true setosa versicolor virginica -err.-
$> setosa 1.00/1.00 0.00/0.00 0.00/0.00 0.00
$> versicolor 0.00/0.00 0.98/0.91 0.02/0.02 0.02
$> virginica 0.00/0.00 0.10/0.09 0.90/0.98 0.10
$> -err.- 0.00 0.09 0.02 0.04
$>
$>
$> Absolute confusion matrix:
$> predicted
$> true setosa versicolor virginica -err.-
$> setosa 50 0 0 0
$> versicolor 0 49 1 1
$> virginica 0 5 45 5
$> -err.- 0 5 1 6
相対頻度を計算する際、行方向と列方向の2通りの正規化の仕方があるため、上記相対混同行列の中には各要素ごとに2つの値が現れている。セットになった2つの値の1つめは行方向、つまり真のラベルについてグループ化した値で、2つめは予測値についてグループ化した値である。
相対値は$relative.row
および$relative.col
を通して直接アクセスすることもできる。詳しくはConfusionMatrix
のドキュメント(ConfusionMatrix function | R Documentation)を参照してもらいたい。
conf.matrix$relative.row
$> setosa versicolor virginica -err-
$> setosa 1 0.00 0.00 0.00
$> versicolor 0 0.98 0.02 0.02
$> virginica 0 0.10 0.90 0.10
最後に、予測値および真値について、各クラスに振り分けられた要素数をsums=TRUE
を指定することで結果に追加できる。これは相対混同行列と絶対混同行列の両方に追加される(訳注: 相対と絶対で行列が入れ替わっているのはなぜだ…?)。
calculateConfusionMatrix(pred, relative = TRUE, sums = TRUE)
$> Relative confusion matrix (normalized by row/column):
$> predicted
$> true setosa versicolor virginica -err.- -n-
$> setosa 1.00/1.00 0.00/0.00 0.00/0.00 0.00 50
$> versicolor 0.00/0.00 0.98/0.91 0.02/0.02 0.02 54
$> virginica 0.00/0.00 0.10/0.09 0.90/0.98 0.10 46
$> -err.- 0.00 0.09 0.02 0.04 <NA>
$> -n- 50 50 50 <NA> 150
$>
$>
$> Absolute confusion matrix:
$> setosa versicolor virginica -err.- -n-
$> setosa 50 0 0 0 50
$> versicolor 0 49 1 1 50
$> virginica 0 5 45 5 50
$> -err.- 0 5 1 6 NA
$> -n- 50 54 46 NA 150
分類: 決定閾値の調整
事後確率をクラスラベルに割り当てるために用いる閾値は調整することができる。閾値を調整するためには、そもそも確率を予測する学習器を使用する必要があるという点に注意しよう。2クラス分類では、閾値はpositiveクラスに分類するための基準となる。デフォルトは0.5だ。例として閾値を0.9にしてみよう。つまり、事後確率が0.9を上回った時にpositiveに分類するということだ。2つのクラスのどちらがpositiveになっているかは(以前確認したとおり)Task
オブジェクトを通じて確認できる。今回は2クラス分類の例としてmlbench
パッケージのSonar
データを使おう。
## 学習器の作成と訓練。タスクは用意されているものを使う。
lrn = makeLearner("classif.rpart", predict.type = "prob")
mod = train(lrn, task = sonar.task)
## positiveクラスのラベルを確認する
getTaskDesc(sonar.task)$positive
$> [1] "M"
## デフォルトの閾値で予測する
pred1 = predict(mod, sonar.task)
pred1$threshold
$> M R
$> 0.5 0.5
## positiveクラスに分類する閾値を変更する
pred2 = setThreshold(pred1, threshold = 0.9)
pred2$threshold
$> M R
$> 0.9 0.1
pred2
$> Prediction: 208 observations
$> predict.type: prob
$> threshold: M=0.90,R=0.10
$> time: 0.00
$> id truth prob.M prob.R response
$> 1 1 R 0.1060606 0.8939394 R
$> 2 2 R 0.7333333 0.2666667 R
$> 3 3 R 0.0000000 1.0000000 R
$> 4 4 R 0.1060606 0.8939394 R
$> 5 5 R 0.9250000 0.0750000 M
$> 6 6 R 0.0000000 1.0000000 R
$> ... (208 rows, 5 cols)
閾値の変更は混同行列に対しても影響する。
calculateConfusionMatrix(pred1)
$> predicted
$> true M R -err.-
$> M 95 16 16
$> R 10 87 10
$> -err.- 10 16 26
calculateConfusionMatrix(pred2)
$> predicted
$> true M R -err.-
$> M 84 27 27
$> R 6 91 6
$> -err.- 6 27 33
getPredictionProbabilities
はデフォルトではpositiveクラスの事後確率しか返さない事に注意しよう。
head(getPredictionProbabilities(pred1))
$> [1] 0.1060606 0.7333333 0.0000000 0.1060606 0.9250000 0.0000000
次のようにすると全ての事例について確率を得ることができる。
head(getPredictionProbabilities(pred1, cl = c("M", "R")))
$> M R
$> 1 0.1060606 0.8939394
$> 2 0.7333333 0.2666667
$> 3 0.0000000 1.0000000
$> 4 0.1060606 0.8939394
$> 5 0.9250000 0.0750000
$> 6 0.0000000 1.0000000
多クラス分類の場合は、閾値は名前付き数値ベクトルとして与える。予測結果の確率は与えた数値で除算された後に比較され、最大値を持つクラスが予測クラスとして選択される。
lrn = makeLearner("classif.rpart", predict.type = "prob")
mod = train(lrn, iris.task)
pred = predict(mod, newdata = iris)
pred$threshold # デフォルトの閾値
$> setosa versicolor virginica
$> 0.3333333 0.3333333 0.3333333
## 閾値の変更 大きな値を指定するほど予測されにくくなる
pred = setThreshold(pred, c(setosa = 0.01, versicolor = 50, virginica = 1))
pred$threshold
$> setosa versicolor virginica
$> 0.01 50.00 1.00
table(as.data.frame(pred)$response)
$>
$> setosa versicolor virginica
$> 50 0 100
予測の可視化
モデルの説明や教育目的で予測を可視化したければ、plotLearnerPrediction
関数を使うことができる。この関数は学習器から1つないし2つの特徴量を選んで訓練したのち、その結果をggplot2
パッケージを用いてプロットする。
分類では、2つの特徴量(デフォルトではデータセットのはじめの2つが選ばれる)を選んで散布図を作成する。シンボルの形状は真のクラスラベルに対応する。誤分類されたシンボルは、周囲が白色の線で囲われることで強調される。学習器がサポートしていれば、事後確率は背景色の彩度により表現され、事後確率が高い部分ほど高彩度となる。
set.seed(777)
lrn = makeLearner("classif.rpart", id = "CART")
plotLearnerPrediction(lrn, task = iris.task)
クラスター分析も2つの特徴量による散布図を作成する。この場合はシンボルの色がクラスターに対応する。
lrn = makeLearner("cluster.kmeans")
plotLearnerPrediction(lrn, mtcars.task, features = c("disp", "drat"), cv = 0)
回帰に対してはプロットが2種類ある。1Dプロットでは一つの特徴量と目的変数の関係が示される。このとき、回帰曲線と(学習器がサポートしていれば)推定標準誤差が示される。
plotLearnerPrediction("regr.lm", features = "lstat", task = bh.task)
2Dプロットでは分類の場合と同様に2つの特徴量による散布図が作成される。この場合シンボルの塗りつぶし色が目的変数の値に対応し、予測値は背景色として示される。標準誤差は示すことができない。
plotLearnerPrediction("regr.lm", features = c("lstat", "rm"), task = bh.task)