Tech Blog機械学習 

ハイパーパラメータの設定

機械学習で使われるモデルには多かれ少なかれ分析者が設定しなければならないパラメータがあります。機械学習で学習されない、機械学習の上にある(ハイパーな)パラメータなのでハイパーパラメータと呼ばれます。この調整をするとモデルの当てはまり(精度)が良くなったり悪くなったりするので、この調整はモデルを作成するときにかなり重要です。 ハイパーパラメータを調整する方法はシンプルです。だいたい良さそうな値を総当たりで入れてみてモデルを作り、その結果を比較して、良さそうな値の中からさらに良さそうな値を選び出すことで可能になります。

グリッドサーチ

とは言っても、ハイパーパラメータのチューニングを人間がやると悲劇になるので、この部分の計算を自動でやってもらいたいです。「だいたい良さそうな値を総当たりで入れてみてモデルを作り、その結果を比較して、良さそうな値の中からさらに良さそうな値を選び出すこと」をグリッドサーチと呼びます。 例えば2つのパラメータα,λα,λを持つ機械学習モデルを考えます。ααは値(0,0.5,1.0)(0,0.5,1.0)をとり、λλは値(0,0.2,0.4,0.6,0.8,1.0)(0,0.2,0.4,0.6,0.8,1.0)をとるとします。この時、グリッドサーチで探索するハイパーパラメータの範囲はα×λ=(0,0),(0,0.2),(0,0.4),…,(1.0,0.2),(1.0,0.4),…,(1.0,1.0)α×λ=(0,0),(0,0.2),(0,0.4),…,(1.0,0.2),(1.0,0.4),…,(1.0,1.0)の18通りです。この18通りのパラメータの組をモデルのパラメータとして代入し、モデルを動かします。そうして得られた18個のモデル出力を比較して、最も精度が高かった出力を与えたパラメータをモデルのハイパーパラメータとして採用します。 モデルが持つハイパーパラメータの数が3個、4個…と増えていってもやることは同じです。全ハイパーパラメータの全範囲の組を探索し、最適なハイパーパラメータの組を発見します。グリッドサーチの「グリッド」は格子とか焼き網とかそういう意味ですが、これは全ハイパーパラメータを軸として考えたとき、その交点に当たる組み合わせについて探索するというところから来ています。

本稿でやること

基本的に福島先生の『データ分析プロセス』の内容をトレースしています。以下では、上記本に記載がなかったelastic netのハイパーパラメータのグリッドサーチをC50パッケージに入っているchurnデータセットを題材に書いてみました。
パッケージの読み込み
require(glmnet)
require(C50)
require(dplyr)
require(caret)
glmnetにデータを読み込ませる際にはデータをmatrix型にしないといけないです。churnデータセットは親切にも訓練データとテストデータが分かれているのですが、敢えてガッチャンコして必要な処理をまとめて行っています。

データセットの準備

データセットの準備
###churnデータセットをglmnetに突っ込めるようにする

##元々のデータを用意
data(churn)

##元々用意されているデータセットをくっつける
churn.bind <- rbind(churnTrain, churnTest)
dim(churn.bind); head(churn.bind)

#factor変数のダミー変数化
noNames <- caret::dummyVars(data = churn.bind, ~state + area_code + international_plan + voice_mail_plan)
churn.dummy <- as.data.frame(predict(noNames, churn.bind))

##元データへの結合
#元データのダミー変数化した列を除く
churn.dummy <- cbind(churn.bind, churn.dummy) %>% select(-c(state, area_code:voice_mail_plan))

##目的変数はあらかじめ数値にしておく
churn.dummy$churn <- ifelse(churn.dummy$churn == 'yes', 1, 0)
str(churn.dummy)

##訓練データとテストデータに分ける
inTrain <- createDataPartition(y = churn.dummy$churn,
                               p = 0.6,
                               list = FALSE)

#データの分割
churnTrain.mtrx <- churn.dummy[inTrain, ]
churnTest.mtrx <- churn.dummy[-inTrain, ]

##matrixにする
#訓練データ
x.tr <- churnTrain.mtrx %>% select(-churn) %>% as.matrix()
y.tr <- churnTrain.mtrx %>% select(churn) %>% unlist

#テストデータ
x.te <- churnTest.mtrx %>% select(-churn) %>% as.matrix()
y.te <- churnTest.mtrx %>% select(churn) %>% unlist
目的変数についてはベクトルにしたいのでunlistを使いました。説明変数は普通にmatrixにしました。 では、グリッドサーチをしていきます。

グリッドサーチ

elastic_netのグリッドサーチ
#パラメータのリスト
lambda.list <- seq(0, 1, 0.05)
alpha.list <- seq(0, 1, 0.05)

#評価指標のリスト
metric.list <- c("tp", "fp", "fn", "tn")

#結果を格納するオブジェクト
res <- cbind(expand.grid(lambda.list, alpha.list), 
             matrix(NA, length(lambda.list) * length(alpha.list), length(metric.list)))

colnames(res) <- c("lambda", "alpha", metric.list)

#パラメータの並び替え
res <- res %>% arrange(lambda, alpha)

#イテレータ番号の設定
iter <- 1

##計算

for(lambda in lambda.list){
  for(alpha in alpha.list){
    ##予測モデルの構築
    fit.enet <- cv.glmnet(x = x.tr,
                          y = y.tr, 
                          alpha = alpha,
                          lambda = lambda.list,
                          family = "binomial")

    ##テストデータに対する予測
    pred <- predict(fit.enet, 
                    newx = x.te, 
                    type = 'class', 
                    s = 'lambda.min')

    ##混同行列
    conf.mat <- table(pred, y.te)

    ##結果の格納
    res[iter, metric.list] <- conf.mat %>% t %>% as.vector
    iter <- iter + 1
  }
}

##予測モデルの評価
res.eval <- res %>% mutate(Prec = tp/(tp + fp),
                           Rec = tp/(tp + fn),
                           Acc = (tp + tn)/(tp + fp + tn + fn))

res.eval %>% head()

#  lambda alpha   tp  fp fn tn      Prec       Rec    Acc
#1      0  0.00 1715 233 19 33 0.8803901 0.9890427 0.8740
#2      0  0.05 1717 236 17 30 0.8791603 0.9901961 0.8735
#3      0  0.10 1718 236 16 30 0.8792221 0.9907728 0.8740
#4      0  0.15 1720 237 14 29 0.8788963 0.9919262 0.8745
#5      0  0.20 1682 204 52 62 0.8918346 0.9700115 0.8720
#6      0  0.25 1682 204 52 62 0.8918346 0.9700115 0.8720

##Accuracyが最大になるパラメータの表示
idx.best <- which.max(res.eval$Acc)
res.eval[idx.best, c("lambda", "alpha", metric.list, "Prec", "Rec", "Acc")]

# lambda alpha   tp  fp fn tn      Prec       Rec    Acc
#4      0  0.15 1720 237 14 29 0.8788963 0.9919262 0.8745
グリグリしている感じが伝わってきたでしょうか。 glmnetパッケージのめんどくさいところはデータフレームをマトリックスにしないといけない点です。これ非常にめんどくさいです。この問題を解決しつつ、グリッドサーチをいい感じにしてくれるパッケージがcaretパッケージです。正確には、グリッドサーチをいい感じにしてくれつつ、この問題を解決してくれるのがcaretパッケージです。 ここから、いまグリッドサーチした内容をcaretパッケージで書き直します。このページを参考に、動くように微修正しました。

caretパッケージを使用した場合

まずはモデルの評価関数を作ります。これは上のコードでいうところの
##予測モデルの評価
res.eval <- res %>% mutate(Prec = tp/(tp + fp),
                           Rec = tp/(tp + fn),
                           Acc = (tp + tn)/(tp + fp + tn + fn))
にあたります。
評価関数の作成
##福島先生本から写経
my.summary <- function(data, lev = NULL, model = NULL){
  if(is.character(data$obs)){
    data$obs <- factor(data$obs, levels = lev)
  }

  #混同行列
  conf <- table(data$pred, data$obs)
  #Precision
  prec <- conf[1,1]/sum(conf[1,])
  #Recall
  rec <- conf[1,1]/sum(conf[,1])
  #Accuracy
  acc <- sum(diag(conf))/sum(conf)

  out <- c(Precision = prec, Recall = rec, Accuracy = acc)
  return(out)
}
続いて、caretの学習に指定できる引数を記載し、学習させます。
学習
##学習の調整
tr = trainControl(method="cv", 
                  number = 10,
                  summaryFunction = my.summary)

##パラメータ範囲の設定
train_grid = expand.grid(alpha = seq(0, 1, 0.05), lambda = seq(0, 1, 0.05))

##学習
enet_fit_caret = train(as.factor(churn) ~ ., 
                     data = churnTrain.mtrx, #caretはデータフレームでOK
                     method = "glmnet", 
                     tuneGrid = train_grid,
                     trControl=tr,
                     metric = "Accuracy", #Accuracyが一番高くなるようなハイパーパラメータの組み合わせを探す
                     preProc = c("center", "scale"))

##モデルをテストデータに適用
enet_predict_res = predict(enet_fit_caret, churnTest.mtrx)

##混同行列を表示
(enet_predict_table = table(enet_predict_res, churnTest.mtrx$churn))
グリグリしている感はだいぶ薄くなったのですが、これでもしっかり予測できています。混同行列を表示すると
混同行列の結果
#enet_predict_res    0    1
#               0 1717  236
#               1   17   30

##予測したAccuracy
(enet_score = sum(enet_predict_table[c(1,4)]) / sum(enet_predict_table))
#0.8735
と出ます。

今後の展望

グリッドサーチの問題点は、「だいたい良さそうな値」の範囲を分析者が決めることです。そのため、その範囲に「本当の」良い値がない場合、指定した範囲内のベストまでしかわかりません。 ハイパーパラメータの範囲の指定を間違えるとモデルの精度を引き出すことはできません。かといって−∞から∞までグリッドサーチしても大変なことになります。 というわけで、次回は分析者が範囲指定をしないハイパーパラメータの調整方法であるベイズ最適化について書きたいと思います。

このページをシェアする:



DATUM STUDIOは、クライアントの事業成長と経営課題解決を最適な形でサポートする、データ・ビジネスパートナーです。
データ分析の分野でお客様に最適なソリューションをご提供します。まずはご相談ください。