Rによる機械学習の例として、分類を行う方法を紹介する。各手法の説明は、今後の授業で行う。今回は、(どれくらいうまくいくかは別として)Rで分類が行えるということを確認する。
必要なパッケージを読み込む。パッケージの読み込みにはpacman::p_load()
を使う。pacmanパッケージがインストール済みでない場合は、まずインストールする。
if (!require(pacman)) install.packages("pacman")
::p_load(tidyverse,
pacman# データソース
palmerpenguins, # 混同行列, kNN
caret, # LDA
MASS, # 多項ロジスティック回帰
nnet, # SVM
e1071, # 決定木
rpart, # ランダムフォレスト
randomForest, # expand_range() scales)
次に、ggplot2のテーマとフォントの設定を行う。自分好みの設定がある場合は自由に変えてよい。LinuxにはIPAexフォント がインストールされていることを想定している(IPAex はインストールすれば maxOSやWindows でも使える)。
if (.Platform$OS.type == "windows") {
if (require(fontregisterer)) {
<- "Yu Gothic"
my_font else {
} <- "Japan1"
my_font
}else if (capabilities("aqua")) {
} <- "HiraginoSans-W3"
my_font else {
} <- "IPAexGothic"
my_font
}
theme_set(theme_gray(base_size = 9,
base_family = my_font))
今回は、palmerpenguinsパッケージのpenguins
データを使う。
data(penguins)
データセットの詳細は?penguins
で確認できる。
いくつかの方法で、データの中身を確認してみよう。 まず、データセットのサイズ(次元, 行数と列数)を確認する。
dim(penguins)
## [1] 344 8
行数(観測数, サンプルサイズ)。
nrow(penguins)
## [1] 344
列数(変数の数)。
ncol(penguins)
## [1] 8
変数名(列名)。
names(penguins)
## [1] "species" "island" "bill_length_mm"
## [4] "bill_depth_mm" "flipper_length_mm" "body_mass_g"
## [7] "sex" "year"
データセットの基本情報を確認する。データセットを読み込んだ直後に、毎回これを実行することを推奨。ただし、R Markdown ファイルには書かない(あるいはチャンクオプションで eval = FALSE
を指定する)ほうがいいかもしれない。
glimpse(penguins)
## Rows: 344
## Columns: 8
## $ species <fct> Adelie, Adelie, Adelie, Adelie, Adelie, Adelie, Adel…
## $ island <fct> Torgersen, Torgersen, Torgersen, Torgersen, Torgerse…
## $ bill_length_mm <dbl> 39.1, 39.5, 40.3, NA, 36.7, 39.3, 38.9, 39.2, 34.1, …
## $ bill_depth_mm <dbl> 18.7, 17.4, 18.0, NA, 19.3, 20.6, 17.8, 19.6, 18.1, …
## $ flipper_length_mm <int> 181, 186, 195, NA, 193, 190, 181, 195, 193, 190, 186…
## $ body_mass_g <int> 3750, 3800, 3250, NA, 3450, 3650, 3625, 4675, 3475, …
## $ sex <fct> male, female, female, NA, female, male, female, male…
## $ year <int> 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007…
同上(tidyverse を読み込まなくても使える)。
str(penguins)
## tibble [344 × 8] (S3: tbl_df/tbl/data.frame)
## $ species : Factor w/ 3 levels "Adelie","Chinstrap",..: 1 1 1 1 1 1 1 1 1 1 ...
## $ island : Factor w/ 3 levels "Biscoe","Dream",..: 3 3 3 3 3 3 3 3 3 3 ...
## $ bill_length_mm : num [1:344] 39.1 39.5 40.3 NA 36.7 39.3 38.9 39.2 34.1 42 ...
## $ bill_depth_mm : num [1:344] 18.7 17.4 18 NA 19.3 20.6 17.8 19.6 18.1 20.2 ...
## $ flipper_length_mm: int [1:344] 181 186 195 NA 193 190 181 195 193 190 ...
## $ body_mass_g : int [1:344] 3750 3800 3250 NA 3450 3650 3625 4675 3475 4250 ...
## $ sex : Factor w/ 2 levels "female","male": 2 1 1 NA 1 2 1 2 NA NA ...
## $ year : int [1:344] 2007 2007 2007 2007 2007 2007 2007 2007 2007 2007 ...
データセットの先頭部(最初の\(n\) 行。デフォルトはn=6
)を表示。
head(penguins)
## # A tibble: 6 × 8
## species island bill_length_mm bill_depth_mm flipper_length_… body_mass_g sex
## <fct> <fct> <dbl> <dbl> <int> <int> <fct>
## 1 Adelie Torge… 39.1 18.7 181 3750 male
## 2 Adelie Torge… 39.5 17.4 186 3800 fema…
## 3 Adelie Torge… 40.3 18 195 3250 fema…
## 4 Adelie Torge… NA NA NA NA <NA>
## 5 Adelie Torge… 36.7 19.3 193 3450 fema…
## 6 Adelie Torge… 39.3 20.6 190 3650 male
## # … with 1 more variable: year <int>
データセットの最後部(最後の\(n\) 行。デフォルトはn=6
)を表示。
tail(penguins, n = 3)
## # A tibble: 3 × 8
## species island bill_length_mm bill_depth_mm flipper_length_… body_mass_g sex
## <fct> <fct> <dbl> <dbl> <int> <int> <fct>
## 1 Chinst… Dream 49.6 18.2 193 3775 male
## 2 Chinst… Dream 50.8 19 210 4100 male
## 3 Chinst… Dream 50.2 18.7 198 3775 fema…
## # … with 1 more variable: year <int>
基本的な記述統計を表示。
summary(penguins)
## species island bill_length_mm bill_depth_mm
## Adelie :152 Biscoe :168 Min. :32.10 Min. :13.10
## Chinstrap: 68 Dream :124 1st Qu.:39.23 1st Qu.:15.60
## Gentoo :124 Torgersen: 52 Median :44.45 Median :17.30
## Mean :43.92 Mean :17.15
## 3rd Qu.:48.50 3rd Qu.:18.70
## Max. :59.60 Max. :21.50
## NA's :2 NA's :2
## flipper_length_mm body_mass_g sex year
## Min. :172.0 Min. :2700 female:165 Min. :2007
## 1st Qu.:190.0 1st Qu.:3550 male :168 1st Qu.:2007
## Median :197.0 Median :4050 NA's : 11 Median :2008
## Mean :200.9 Mean :4202 Mean :2008
## 3rd Qu.:213.0 3rd Qu.:4750 3rd Qu.:2009
## Max. :231.0 Max. :6300 Max. :2009
## NA's :2 NA's :2
今回は、くちばしの長さ (bill_length_mm
) と くちばしの厚み (bill_depth_mm
) からペンギンの種類 (species
) を予測(分類)する。
with(penguins, table(species))
## species
## Adelie Chinstrap Gentoo
## 152 68 124
種類が英語で記録されているので日本語に変え、利用する3つの変数のなかに欠測値があるものを(とりあえず)取り除く。
<- penguins %>%
penguins_s1 mutate(species = case_when(
== "Adelie" ~ 1L,
species == "Chinstrap" ~ 2L,
species TRUE ~ 3L,
%>%
)) mutate(species = factor(species,
levels = 1:3,
labels = c("アデリー", "ヒゲ", "ジェンツー"))) %>%
::select(species, bill_length_mm, bill_depth_mm) %>%
dplyrna.omit()
予測に使う2つの変数(機械学習ではこれを特徴量 (feature)と呼ぶ)と、実際の種類(つまり、分類問題の正解。機械学習における分類問題ではこれをラベル (label)と呼ぶ)の関係を図示する。
<- ggplot(penguins_s1,
p1 aes(x = bill_length_mm,
y = bill_depth_mm)) +
geom_point(aes(color = species,
shape = species)) +
scale_color_brewer(palette = "Accent") +
labs(x = "くちばしの長さ (mm)",
y = "くちばしの厚み (mm)",
color = "ペンギンの種類",
shape = "ペンギンの種類")
plot(p1)
予測に使うRのformulaを用意しておこう。 species
を bill_length_mm
と bill_depth_mm
から予測する(species
を bill_length_mm
と bill_depth_mm
に回帰する)。
<- formula(species ~ bill_length_mm + bill_depth_mm) pgnf
分類の境界線 (decision boundaries; discrimination lines) を求める範囲のグリッドデータを用意しておく。
<- range(penguins_s1$bill_length_mm) %>%
bill_len_lim expand_range(mul = 0.05)
<- range(penguins_s1$bill_depth_mm) %>%
bill_dep_lim expand_range(mul = 0.05)
<- expand_grid(
newdf bill_length_mm = seq(bill_len_lim[1], bill_len_lim[2], length.out = 500),
bill_depth_mm = seq(bill_dep_lim[1], bill_dep_lim[2], length.out = 500)
)glimpse(newdf, n = 5)
## Rows: 250,000
## Columns: 2
## $ bill_length_mm <dbl> 30.725, 30.725, 30.725, 30.725, 30.725, 30.725, 30.725,…
## $ bill_depth_mm <dbl> 12.68000, 12.69852, 12.71703, 12.73555, 12.75407, 12.77…
これからいくつかの方法で分類を行い、そのたびに上で作った散布図に、分類の境界線を上書きした図を作る。繰り返し行う作業は関数化してしまったほうが効率が良い。 そこで、分類結果を図示するための関数decision_region()
を自作する。
<- function(pred_class, title = NULL) {
decision_region # pred_class には予測(分類)結果を数値で表したベクトルを渡す
<- newdf %>%
df mutate(predicted = pred_class)
<- p1 +
p geom_raster(data = df,
aes(fill = as.factor(predicted)),
alpha = 0.2,
show.legend = FALSE) +
geom_contour(data = df,
aes(z = predicted),
color = "gray") +
scale_fill_brewer(palette = "Accent") +
scale_x_continuous(limits = bill_len_lim, expand = c(0, 0)) +
scale_y_continuous(limits = bill_dep_lim, expand = c(0, 0)) +
ggtitle(title)
return(p)
}
LDA (Linear Discriminant Analysis) で分類してみよう。MASS::lda()
を使う。
<- lda(pgnf, data = penguins_s1) classifier_lda
予測(分類)がどれだけうまくできたか確認するために、混同行列 (confusion matrix) を表示する。 predict()
で予測を行い、caret::confusionMatrix()
で混同行列を作る。
<- predict(classifier_lda)
pred_lda confusionMatrix(pred_lda$class,
reference = penguins_s1$species)
## Confusion Matrix and Statistics
##
## Reference
## Prediction アデリー ヒゲ ジェンツー
## アデリー 149 5 0
## ヒゲ 2 59 2
## ジェンツー 0 4 121
##
## Overall Statistics
##
## Accuracy : 0.962
## 95% CI : (0.9359, 0.9796)
## No Information Rate : 0.4415
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.94
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: アデリー Class: ヒゲ Class: ジェンツー
## Sensitivity 0.9868 0.8676 0.9837
## Specificity 0.9738 0.9854 0.9817
## Pos Pred Value 0.9675 0.9365 0.9680
## Neg Pred Value 0.9894 0.9677 0.9908
## Prevalence 0.4415 0.1988 0.3596
## Detection Rate 0.4357 0.1725 0.3538
## Detection Prevalence 0.4503 0.1842 0.3655
## Balanced Accuracy 0.9803 0.9265 0.9827
Prediction が予測結果、Reference が正解である。Accuracy に予測の正解率が示されている。他の部分の読み方については、今後の授業で説明する。
分類の境界線 (decision boundaries; discrimination lines) を図示する。
<- predict(classifier_lda, newdata = newdf)$class %>%
p_lda as.numeric() %>%
decision_region(title = "LDAによる分類")
plot(p_lda)
ロジスティック回帰による分類を行う。分類するカテゴリが3つあるので、多項ロジスティック回帰 (multinomial logistic regression) を利用する。nnet::multinom()
を使う。
<- multinom(pgnf, data = penguins_s1) classifier_logit
## # weights: 12 (6 variable)
## initial value 375.725403
## iter 10 value 26.783497
## iter 20 value 24.005561
## iter 30 value 23.973372
## iter 40 value 23.956920
## iter 50 value 23.951745
## iter 60 value 23.950452
## final value 23.948361
## converged
混同行列。
predict(classifier_logit) %>%
confusionMatrix(reference = penguins_s1$species)
## Confusion Matrix and Statistics
##
## Reference
## Prediction アデリー ヒゲ ジェンツー
## アデリー 148 3 0
## ヒゲ 3 61 2
## ジェンツー 0 4 121
##
## Overall Statistics
##
## Accuracy : 0.9649
## 95% CI : (0.9395, 0.9817)
## No Information Rate : 0.4415
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.9448
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: アデリー Class: ヒゲ Class: ジェンツー
## Sensitivity 0.9801 0.8971 0.9837
## Specificity 0.9843 0.9818 0.9817
## Pos Pred Value 0.9801 0.9242 0.9680
## Neg Pred Value 0.9843 0.9746 0.9908
## Prevalence 0.4415 0.1988 0.3596
## Detection Rate 0.4327 0.1784 0.3538
## Detection Prevalence 0.4415 0.1930 0.3655
## Balanced Accuracy 0.9822 0.9394 0.9827
分類の境界線を図示する。
<- predict(classifier_logit, newdata = newdf) %>%
p_logit as.numeric() %>%
decision_region(title = "多項ロジスティック回帰による分類")
plot(p_logit)
SVM (support vector machine) による分類を行う。 e1071::svm()
を使う。kernel
引数で利用するカーネルを指定する。
線形カーネル (linear kernel) を使ってSVMで分類を行う。
<- svm(pgnf,
svm_linear data = penguins_s1,
kernel = "linear")
混同行列。
predict(svm_linear) %>%
confusionMatrix(reference = penguins_s1$species)
## Confusion Matrix and Statistics
##
## Reference
## Prediction アデリー ヒゲ ジェンツー
## アデリー 149 5 0
## ヒゲ 2 60 3
## ジェンツー 0 3 120
##
## Overall Statistics
##
## Accuracy : 0.962
## 95% CI : (0.9359, 0.9796)
## No Information Rate : 0.4415
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.94
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: アデリー Class: ヒゲ Class: ジェンツー
## Sensitivity 0.9868 0.8824 0.9756
## Specificity 0.9738 0.9818 0.9863
## Pos Pred Value 0.9675 0.9231 0.9756
## Neg Pred Value 0.9894 0.9711 0.9863
## Prevalence 0.4415 0.1988 0.3596
## Detection Rate 0.4357 0.1754 0.3509
## Detection Prevalence 0.4503 0.1901 0.3596
## Balanced Accuracy 0.9803 0.9321 0.9810
e1071パッケージには、分類境界を可視化する関数が用意されている。
par(family = my_font)
plot(svm_linear, data = penguins_s1)
横軸と縦軸が入れ替わっている。
ggplot2 で描き直す。
<- predict(svm_linear, newdata = newdf) %>%
p_svm_linear as.numeric() %>%
decision_region(title = "SVM(線形カーネル)による分類")
plot(p_svm_linear)
ガウスカーネル (Gaussian kernel or radial basis function; RBF) を使う。 gamma
引数で、関数の複雑さ(円の曲率)を調整する。大きな値にするほど、複雑な関数を使った分類になる。 まず、gamma = 0.5
でやってみる。
<- svm(pgnf,
svm_rbf1 data = penguins_s1,
kernel = "radial",
gamma = 0.5) # 関数の複雑さを決めるパラメタ
混同行列。
predict(svm_rbf1) %>%
confusionMatrix(reference = penguins_s1$species)
## Confusion Matrix and Statistics
##
## Reference
## Prediction アデリー ヒゲ ジェンツー
## アデリー 149 5 0
## ヒゲ 2 60 3
## ジェンツー 0 3 120
##
## Overall Statistics
##
## Accuracy : 0.962
## 95% CI : (0.9359, 0.9796)
## No Information Rate : 0.4415
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.94
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: アデリー Class: ヒゲ Class: ジェンツー
## Sensitivity 0.9868 0.8824 0.9756
## Specificity 0.9738 0.9818 0.9863
## Pos Pred Value 0.9675 0.9231 0.9756
## Neg Pred Value 0.9894 0.9711 0.9863
## Prevalence 0.4415 0.1988 0.3596
## Detection Rate 0.4357 0.1754 0.3509
## Detection Prevalence 0.4503 0.1901 0.3596
## Balanced Accuracy 0.9803 0.9321 0.9810
分類境界を図示する。
<- predict(svm_rbf1, newdata = newdf) %>%
p_svm_rbf1 as.numeric() %>%
decision_region(title = "SVM(ガウシアンカーネル)による分類: gamma = 0.5")
plot(p_svm_rbf1)
境界線が直線ではなくなったことがわかる。
次に、gamma = 10
にしてみる。
<- svm(pgnf,
svm_rbf2 data = penguins_s1,
kernel = "radial",
gamma = 10) # 関数の複雑さを決めるパラメタ
混同行列。
predict(svm_rbf2) %>%
confusionMatrix(reference = penguins_s1$species)
## Confusion Matrix and Statistics
##
## Reference
## Prediction アデリー ヒゲ ジェンツー
## アデリー 149 2 0
## ヒゲ 2 65 2
## ジェンツー 0 1 121
##
## Overall Statistics
##
## Accuracy : 0.9795
## 95% CI : (0.9583, 0.9917)
## No Information Rate : 0.4415
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.9679
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: アデリー Class: ヒゲ Class: ジェンツー
## Sensitivity 0.9868 0.9559 0.9837
## Specificity 0.9895 0.9854 0.9954
## Pos Pred Value 0.9868 0.9420 0.9918
## Neg Pred Value 0.9895 0.9890 0.9909
## Prevalence 0.4415 0.1988 0.3596
## Detection Rate 0.4357 0.1901 0.3538
## Detection Prevalence 0.4415 0.2018 0.3567
## Balanced Accuracy 0.9881 0.9706 0.9896
分類境界を図示する。
<- predict(svm_rbf2, newdata = newdf) %>%
p_svm_rbf2 as.numeric() %>%
decision_region(title = "SVM(ガウシアンカーネル)による分類: gamma = 10")
plot(p_svm_rbf2)
先程よりも複雑な境界線が引かれている。その結果、観測データの分類がうまくできている一方で、グラフの右下や右上が緑色(アデリーペンギン)と予測(分類)されており、関数を複雑にし過ぎると弊害がありそうだということがわかる。
決定木 (decision tree) による分類を行う。 rpart::rpart()
を使う。
<- rpart(pgnf, data = penguins_s1) classifier_tree
混同行列。
predict(classifier_tree, type = "class") %>%
confusionMatrix(reference = penguins_s1$species)
## Confusion Matrix and Statistics
##
## Reference
## Prediction アデリー ヒゲ ジェンツー
## アデリー 148 6 4
## ヒゲ 3 62 8
## ジェンツー 0 0 111
##
## Overall Statistics
##
## Accuracy : 0.9386
## 95% CI : (0.9077, 0.9616)
## No Information Rate : 0.4415
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.9036
##
## Mcnemar's Test P-Value : 0.004637
##
## Statistics by Class:
##
## Class: アデリー Class: ヒゲ Class: ジェンツー
## Sensitivity 0.9801 0.9118 0.9024
## Specificity 0.9476 0.9599 1.0000
## Pos Pred Value 0.9367 0.8493 1.0000
## Neg Pred Value 0.9837 0.9777 0.9481
## Prevalence 0.4415 0.1988 0.3596
## Detection Rate 0.4327 0.1813 0.3246
## Detection Prevalence 0.4620 0.2135 0.3246
## Balanced Accuracy 0.9639 0.9358 0.9512
分類境界を図示する。
<- predict(classifier_tree,
p_tree newdata = newdf,
type = "class") %>%
as.numeric() %>%
decision_region(title = "決定木による分類")
plot(p_tree)
境界線がそれぞれの特徴量(説明変数)の軸と垂直(平行)に引かれていることがわかる。
ランダムフォレスト (random forest) による分類を行う。randomForest::randomForest()
を使う。ntree
引数で、いくつの決定木を使うかを決める。
<- randomForest(pgnf,
classifier_rf data = penguins_s1,
ntree = 50)
混同行列。
predict(classifier_rf) %>%
confusionMatrix(reference = penguins_s1$species)
## Confusion Matrix and Statistics
##
## Reference
## Prediction アデリー ヒゲ ジェンツー
## アデリー 149 3 0
## ヒゲ 2 63 4
## ジェンツー 0 2 119
##
## Overall Statistics
##
## Accuracy : 0.9678
## 95% CI : (0.9432, 0.9838)
## No Information Rate : 0.4415
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.9495
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: アデリー Class: ヒゲ Class: ジェンツー
## Sensitivity 0.9868 0.9265 0.9675
## Specificity 0.9843 0.9781 0.9909
## Pos Pred Value 0.9803 0.9130 0.9835
## Neg Pred Value 0.9895 0.9817 0.9819
## Prevalence 0.4415 0.1988 0.3596
## Detection Rate 0.4357 0.1842 0.3480
## Detection Prevalence 0.4444 0.2018 0.3538
## Balanced Accuracy 0.9855 0.9523 0.9792
分類境界を図示する。
<- predict(classifier_rf,
p_rf newdata = newdf,
type = "class") %>%
as.numeric() %>%
decision_region(title = "ランダムフォレストによる分類")
plot(p_rf)
決定木と比較すると、複雑な境界線になることがわかる。
\(k\)近傍 (\(k\)-nearest neighbor; kNN) 法による分類を行う。 caret::knn3()
を使う。
<- knn3(pgnf,
classifier_kNN data = penguins_s1,
k = 5)
混同行列。
predict(classifier_kNN, newdata = penguins_s1, type = "class") %>%
confusionMatrix(reference = penguins_s1$species)
## Confusion Matrix and Statistics
##
## Reference
## Prediction アデリー ヒゲ ジェンツー
## アデリー 149 2 0
## ヒゲ 2 64 3
## ジェンツー 0 2 120
##
## Overall Statistics
##
## Accuracy : 0.9737
## 95% CI : (0.9506, 0.9879)
## No Information Rate : 0.4415
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.9587
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: アデリー Class: ヒゲ Class: ジェンツー
## Sensitivity 0.9868 0.9412 0.9756
## Specificity 0.9895 0.9818 0.9909
## Pos Pred Value 0.9868 0.9275 0.9836
## Neg Pred Value 0.9895 0.9853 0.9864
## Prevalence 0.4415 0.1988 0.3596
## Detection Rate 0.4357 0.1871 0.3509
## Detection Prevalence 0.4415 0.2018 0.3567
## Balanced Accuracy 0.9881 0.9615 0.9832
分類境界を図示する。
<- predict(classifier_kNN,
p_kNN newdata = newdf,
type = "class") %>%
as.numeric() %>%
decision_region(title = "k近傍法による分類 (k=5)")
plot(p_kNN)
このように、Rを使うとさまざまな方法で分類を行うことができる(もちろんPythonでもできるが、機械学習の基本的な問題については、Rだけでも対処できる。RもPythonも使えるのが理想)。方法ごとに異なるパッケージを利用することになるが、それぞれの方法に固有のパラメタ(引数)の指定が必要だという点を除けば、基本的な使い方はほぼ同じである。
ここで実行した分類を、引数(たとえば、svm()
のgamma
や knn3()
のk
)の値を変えて実行してみよう。どのような結果が得られるだろうか。