39
41

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rの機械学習パッケージmlrのチュートリアル(タスクの作成から予測まで)

Last updated at Posted at 2018-02-20

ということだったので仏の教えに従ってチュートリアル(mlr tutorial)を読んでいる。mlrパッケージのチュートリアルは開発版に対応したものとCRANに上がっているものに対応したものの2種類あるが、どちらかというと開発版を参考にしている。翻訳っぽいけど原文を結構無視しているのでアレな部分があるかもしれません。

とりあえず予測して可視化するところまで。前処理とかチューニングは次回に。

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クラス分類においては、しばしばそれぞれのクラスをpositivenegativeに対応させる。上記例を見るとわかるように、デフォルトでは因子型における最初のレベルが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クラスのオブジェクトが入っている。これには、ハイパーパラメータの型(数値なのか論理型なのか)、デフォルト値、そして可能な値の範囲が格納されている。

また、mlrLernerの現在のハイパーパラメータの設定にアクセスするgetHyperParsgetLernerParVals、可能な設定項目の詳細を取得する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)

unnamed-chunk-4-1.png

## タスクの作成
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データではbenignmalignantのほぼ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の他の予測手法と同じように実装されている。一般的には、predicttrainが返すオブジェクトに対して呼び出し、予測したいデータを渡すだけだ。

データを渡す方法は2種類ある。

  • task引数を通じてTaskオブジェクトを渡す。
  • newdata引数を通じてdata.frameを渡す。

最初の方法は、予測したいデータが既にTaskオブジェクトに含まれている場合に適している。

trainと同様に、predictsubset引数を備えている。したがって、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)

unnamed-chunk-29-1.png

クラスター分析も2つの特徴量による散布図を作成する。この場合はシンボルの色がクラスターに対応する。

lrn = makeLearner("cluster.kmeans")
plotLearnerPrediction(lrn, mtcars.task, features = c("disp", "drat"), cv = 0)

unnamed-chunk-30-1.png

回帰に対してはプロットが2種類ある。1Dプロットでは一つの特徴量と目的変数の関係が示される。このとき、回帰曲線と(学習器がサポートしていれば)推定標準誤差が示される。

plotLearnerPrediction("regr.lm", features = "lstat", task = bh.task)

unnamed-chunk-31-1.png

2Dプロットでは分類の場合と同様に2つの特徴量による散布図が作成される。この場合シンボルの塗りつぶし色が目的変数の値に対応し、予測値は背景色として示される。標準誤差は示すことができない。

plotLearnerPrediction("regr.lm", features = c("lstat", "rm"), task = bh.task)

unnamed-chunk-32-1.png

39
41
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
39
41

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?