xwMOOC 딥러닝

\(H_2 O\) GBM 모형 세부조정

학습 목표

  • \(H_2 O\) 활용 최적 예측모형을 개발한다.
  • 타이타닉 생존 데이터로 동일한 방법론을 갈음한다.
  • GBM 기본 모형을 바탕으로 모수 세부조정을 통해 성능을 높인다.

이제부터 타이타닉 생존 정확하게 예측할 수 있는 정말 정확도 높은 예측모형 개발을 위한 눈물겨운 여정을 떠나본다. GBM을 가지고 AUC 0.94가 나오는데 다양한 초모수 세부조정을 통해 0.97까지 높일 수 있다. 물론 상위 10개 앙상블을 사용한다면 0.975까지도 가능하다.

H2O 초모수 세부조정 최적화

1. 타이타닉 생존 데이터 GBM 기본 모형구축 1

  1. \(H_2 O\) 팩키지를 설치하고, \(H_2 O\) 클러스터를 생성한다.
  2. 타이타닉 생존 데이터를 다운로드 한다.
  3. h2o.splitFrame 명령어를 통해 훈련, 타당도검증, 검증 데이터로 구분하고 GBM 모형을 적합시킨다.
##=========================================================================
## 01. H2O 설치: http://blog.h2o.ai/2016/06/h2o-gbm-tuning-tutorial-for-r/
##=========================================================================
# 1. 기존 H2O 제거
if ("package:h2o" %in% search()) { detach("package:h2o", unload=TRUE) }
if ("h2o" %in% rownames(installed.packages())) { remove.packages("h2o") }

# 2. H2O 의존성 설치
pkgs <- c("methods","statmod","stats","graphics","RCurl","jsonlite","tools","utils")
for (pkg in pkgs) {
  if (! (pkg %in% rownames(installed.packages()))) { install.packages(pkg) }
}

# 3. H2O 설치
install.packages("h2o", repos=(c("http://s3.amazonaws.com/h2o-release/h2o/master/1497/R", getOption("repos"))))

#-------------------------------------------------------------------------
# 01.1. H2O 클러스터 환경설정
#-------------------------------------------------------------------------

library(h2o)
h2o.init(nthreads=-1)

##=========================================================================
## 02. H2O 데이터 가져오기
##=========================================================================

df <- h2o.importFile(path = "http://s3.amazonaws.com/h2o-public-test-data/smalldata/gbm_test/titanic.csv")
summary(df, exact_quantiles=TRUE)

##=========================================================================
## 03. H2O 데이터 정제 과정
##=========================================================================

# 데이터 정제 및 변환과정 생략

##=========================================================================
## 04. GBM 모형
##=========================================================================
# 1. 종속변수 선정
response <- "survived"
# 2. 종속변수가 숫자형이라 요인(Factor)으로 자료형 변환
df[[response]] <- as.factor(df[[response]])           

# 3. 종속변수를 제외한 모든 변수를 설명변수
predictors <- setdiff(names(df), c(response, "name")) 

# 4. 훈련, 타당성검증, 검증 데이터로 분리
splits <- h2o.splitFrame(
  data = df, 
  ratios = c(0.6,0.2),   ## 60% 훈련, 20% 타당도검증, 나머지 20% 자동 검증데이터 생성
  destination_frames = c("train.hex", "valid.hex", "test.hex"), seed = 1234
)
train <- splits[[1]]
valid <- splits[[2]]
test  <- splits[[3]]

#--------------------------------------------------------------------------
# 4.1. GBM 초기 모형
#--------------------------------------------------------------------------
# 1. 기본 모형 생성
gbm <- h2o.gbm(x = predictors, y = response, training_frame = train)
gbm

# 타당성검증 데이터 AUC 성능
h2o.auc(h2o.performance(gbm, newdata = valid)) 

# 2. 타당성 검증 데이터 활용 GBM 모형 개발 
gbm <- h2o.gbm(x = predictors, y = response, training_frame = h2o.rbind(train, valid), nfolds = 4, seed = 0xDECAF)

gbm@model$cross_validation_metrics_summary
h2o.auc(h2o.performance(gbm, xval = TRUE))
> gbm
[1] 0.9431953
> gbm@model$cross_validation_metrics_summary
Cross-Validation Metrics Summary: 
                           mean           sd  cv_1_valid cv_2_valid  cv_3_valid cv_4_valid
F0point5              0.9127705  0.010779412  0.92045456  0.9183673   0.8867521  0.9255079
F1                    0.8876407 0.0054373597   0.8756757  0.8959276   0.8924731  0.8864865
F2                   0.86462516  0.016960286  0.83505154  0.8745583   0.8982684  0.8506224
accuracy              0.9174827 0.0030382033   0.9151291  0.9118774   0.9230769  0.9198473
auc                   0.9432912  0.007747688   0.9298538 0.93615246  0.95788044 0.94927806
err                   0.0825173 0.0030382033 0.084870845 0.08812261  0.07692308 0.08015267
err_count                 21.75   0.91855866          23         23          20         21
lift_top_group        2.6130292   0.14742896        2.71   2.269565    2.826087  2.6464646
logloss              0.25946963  0.016240774  0.25494286 0.29227072  0.22781134 0.26285362
max_per_class_error  0.14966843  0.024777662        0.19 0.13913043 0.097826086 0.17171717
mcc                  0.82561684 0.0041895183  0.81807023  0.8217822  0.83272034  0.8298947
mse                 0.071905546 0.0039584376  0.07185569 0.07921115  0.06349978 0.07305556
precision            0.93084264  0.020315822   0.9529412  0.9339623  0.88297874 0.95348835
r2                    0.6953803  0.011497839   0.6913945  0.6786216   0.7222706  0.6892343
recall                0.8503316  0.024777662        0.81  0.8608696  0.90217394 0.82828283
specificity           0.9596617  0.012382206   0.9766082  0.9520548   0.9345238  0.9754601

2. GBM 모수로 설정한 값이 운좋게 최적일 수 있다.

h2o.gbm 모형에 설정한 값이 운좋게도 가장 최적일 수도 있다. 하지만, 그런 경우는 거의 없다.

#--------------------------------------------------------------------------
# 4.2. GBM 정말 운좋은 모형 개발
#--------------------------------------------------------------------------
gbm <- h2o.gbm(
  ## 표준 모형 모수 설정
  x = predictors, 
  y = response, 
  training_frame = train, 
  validation_frame = valid,
  
  ntrees = 10000,                                                            
  
  learn_rate=0.01,                                                         

  stopping_rounds = 5, stopping_tolerance = 1e-4, stopping_metric = "AUC", 

  sample_rate = 0.8,                                                       
  col_sample_rate = 0.8,                                                   

  seed = 1234,                                                             
  score_tree_interval = 10                                                 
)

h2o.auc(h2o.performance(gbm, valid = TRUE))
> h2o.auc(h2o.performance(gbm, valid = TRUE))
[1] 0.939335

3. 초모수(Hyper-parameter) 설정을 통한 GBM 최적 모형 개발

최적의 GBM 모형 구축을 위해 초모수를 최적화하는 기계적인 방법은 존재하지 않으며, 경험에 비추어 다음 모수가 최적 GBM 구축에 도움이 되는 것으로 알려져 있다.

  1. ntrees: 타당도 검증 오차가 증가할 때까지 가능함 많은 나무모형을 생성시킨다.
  2. learn_rate: 가능하면 낮은 학습율을 지정한다. 하지만 댓가로 더 많은 나무모형이 필요하다. learn_rate=0.02learn_rate_annealing=0.995 모수로 설정한다.
  3. max_depth: 나무 깊이는 데이터에 따라 최적 깊이가 달라진다. 더 깊은 나무를 생성시키려면 더 많은 시간이 소요된다. 특히 10보다 큰 경우 깊은 나무모형으로 알려져 있다.
  4. sample_rate, col_sample_rate : 행과 열을 표집추출하는 것으로 보통 0.7 – 0.8 이 무난하다.
  5. sample_rate_per_class: 심각한 불균형 데이터(예를 들어, 연체고객과 정상고객, 이상거래와 정상거래 등)의 경우 층화추출법을 통해 모형 정확도를 높일 수 있다.
  6. 기타 나머지 모수는 상대적으로 적은 기여도를 보이는데, 필요한 경우 임의 초모수 검색법을 도모할 수 있다.

max_depth를 먼저 상정할 수 있고, 데카르트 좌표계(Cartesian Grid) 검색으로 이를 구현한다.

#--------------------------------------------------------------------------
# 4.3. GBM 모수 미세조정
#--------------------------------------------------------------------------
hyper_params = list( max_depth = seq(1,29,2) )
#hyper_params = list( max_depth = c(4,6,8,12,16,20) ) ## 빅데이터의 경우 사용

grid <- h2o.grid(

  hyper_params = hyper_params,
  search_criteria = list(strategy = "Cartesian"),

  algorithm="gbm",
  grid_id="depth_grid",
  
  x = predictors, 
  y = response, 
  training_frame = train, 
  validation_frame = valid,
  

  ntrees = 10000,                                                            
  learn_rate = 0.05,                                                         
  learn_rate_annealing = 0.99,                                               
  
  sample_rate = 0.8,                                                       
  col_sample_rate = 0.8, 
  
  seed = 1234,                                                             
  
  stopping_rounds = 5,
  stopping_tolerance = 1e-4,
  stopping_metric = "AUC", 
  
  score_tree_interval = 10                                                
)

grid                                                                       

sortedGrid <- h2o.getGrid("depth_grid", sort_by="auc", decreasing = TRUE)    
sortedGrid


topDepths = sortedGrid@summary_table$max_depth[1:5]                       
minDepth = min(as.numeric(topDepths))
maxDepth = max(as.numeric(topDepths))
> sortedGrid
H2O Grid Details
================

Grid ID: depth_grid 
Used hyper parameters: 
  -  max_depth 
Number of models: 15 
Number of failed models: 0 

Hyper-Parameter Search Summary: ordered by decreasing auc
   max_depth           model_ids               auc
1         27 depth_grid_model_13  0.95657931811778
2         25 depth_grid_model_12 0.956353902507749
3         29 depth_grid_model_14 0.956241194702733
4         21 depth_grid_model_10 0.954663285432516
5         19  depth_grid_model_9 0.954494223724993
6         13  depth_grid_model_6 0.954381515919978
7         23 depth_grid_model_11 0.954043392504931
8         11  depth_grid_model_5 0.952183713722175
9         15  depth_grid_model_7 0.951789236404621
10        17  depth_grid_model_8 0.951507466892082
11         9  depth_grid_model_4 0.950436742744435
12         7  depth_grid_model_3 0.946942800788955
13         5  depth_grid_model_2 0.939306846999155
14         3  depth_grid_model_1 0.932713440405748
15         1  depth_grid_model_0  0.92902225979149

4. GBM 초모수 격자탐색

hyper_params 리스트에 격자 탐색할 모수를 설정하고, search_criteria에 탐색기준을 적시하고 나서 h2o.grid를 통해 최적 GBM 초모수를 탐색한다.

#--------------------------------------------------------------------------
# 4.4. GBM 초모수 격자 탐색
#--------------------------------------------------------------------------

hyper_params = list( 

  max_depth = seq(minDepth,maxDepth,1),                                      
  
  sample_rate = seq(0.2,1,0.01),                                             
  
  col_sample_rate = seq(0.2,1,0.01),                                         
  
  col_sample_rate_per_tree = seq(0.2,1,0.01),                                
  
  col_sample_rate_change_per_level = seq(0.9,1.1,0.01),                      
  
  min_rows = 2^seq(0,log2(nrow(train))-1,1),                                 
  
  nbins = 2^seq(4,10,1),                                                     
  
  nbins_cats = 2^seq(4,12,1),                                                
  
  min_split_improvement = c(0,1e-8,1e-6,1e-4),                               
  
  histogram_type = c("UniformAdaptive","QuantilesGlobal","RoundRobin")       
)

search_criteria = list(
  strategy = "RandomDiscrete",      
  
  max_runtime_secs = 3600,         
  
  max_models = 100,                  
  
  seed = 1234,                        
  
  stopping_rounds = 5,                
  stopping_metric = "AUC",
  stopping_tolerance = 1e-3
)

grid <- h2o.grid(

  hyper_params = hyper_params,
  search_criteria = search_criteria,
  
  algorithm = "gbm",
  
  grid_id = "final_grid", 
  
  x = predictors, 
  y = response, 
  training_frame = train, 
  validation_frame = valid,

  ntrees = 10000,                                                            
  
  learn_rate = 0.05,                                                         
  
  learn_rate_annealing = 0.99,                                               
  
  max_runtime_secs = 3600,                                                 
  
  stopping_rounds = 5, stopping_tolerance = 1e-4, stopping_metric = "AUC", 
  
  score_tree_interval = 10,                                                
  
  seed = 1234                                                             
)

## AUC 기준 격자모형 정렬
sortedGrid <- h2o.getGrid("final_grid", sort_by = "auc", decreasing = TRUE)    
sortedGrid

for (i in 1:5) {
  gbm <- h2o.getModel(sortedGrid@model_ids[[i]])
  print(h2o.auc(h2o.performance(gbm, valid = TRUE)))
}
> sortedGrid
H2O Grid Details
================

Grid ID: final_grid 
Used hyper parameters: 
  -  histogram_type 
  -  sample_rate 
  -  nbins_cats 
  -  nbins 
  -  min_rows 
  -  col_sample_rate_change_per_level 
  -  min_split_improvement 
  -  max_depth 
  -  col_sample_rate 
  -  col_sample_rate_per_tree 
Number of models: 199 
Number of failed models: 0 

Hyper-Parameter Search Summary: ordered by decreasing auc
  histogram_type sample_rate nbins_cats nbins min_rows  ... max_depth
1     RoundRobin         0.9        256  1024        8  ...        28
2     RoundRobin         0.9        256  1024        8  ...        28
3     RoundRobin        0.39        256   512        4  ...        21
4     RoundRobin        0.39        256   512        4  ...        21
5     RoundRobin        0.31        256   128        2  ...        20
  col_sample_rate ...            model_ids               auc
1            0.65 ...  final_grid_model_59 0.970498732037194
2            0.65 ... final_grid_model_159 0.970498732037194
3            0.43 ... final_grid_model_142 0.969822485207101
4            0.43 ...  final_grid_model_42 0.969822485207101
5            0.37 ... final_grid_model_172 0.969202592279515

---
     histogram_type sample_rate nbins_cats nbins min_rows ... min_split_improvement
194 UniformAdaptive        0.96       2048   512      256 ...                 1e-08
195 UniformAdaptive        0.96       2048   512      256 ...                 1e-08
196 UniformAdaptive        0.82         64    16      256 ...                 1e-04
197 UniformAdaptive        0.82         64    16      256 ...                 1e-04
198 QuantilesGlobal        0.64        512    32      256 ...                 1e-08
199 QuantilesGlobal        0.64        512    32      256 ...                 1e-08
    max_depth col_sample_rate ...            model_ids               auc
194        28            0.56 ...  final_grid_model_58 0.794449140602987
195        28            0.56 ... final_grid_model_158 0.794449140602987
196        21             0.5 ... final_grid_model_189 0.791180614257537
197        21             0.5 ...  final_grid_model_89 0.791180614257537
198        19             0.9 ...  final_grid_model_64 0.741617357001972
199        19             0.9 ... final_grid_model_164 0.741617357001972

5. 최종모형 정리

AUC 기준 가장 좋은 모형을 하나 선정하고, 이를 기준으로 검증데이터 혹은 예측이 필요한 데이터에 예측확률을 붙여 저장한다.

##=========================================================================
## 05. 최종 모형 정리
##=========================================================================

gbm <- h2o.getModel(sortedGrid@model_ids[[1]])
print(h2o.auc(h2o.performance(gbm, newdata = test)))

gbm@parameters

model <- do.call(h2o.gbm,
                 ## update parameters in place
                 {
                   p <- gbm@parameters
                   p$model_id = NULL          ## do not overwrite the original grid model
                   p$training_frame = df      ## use the full dataset
                   p$validation_frame = NULL  ## no validation frame
                   p$nfolds = 5               ## cross-validation
                   p
                 }
)
model@model$cross_validation_metrics_summary

for (i in 1:5) {
  gbm <- h2o.getModel(sortedGrid@model_ids[[i]])
  cvgbm <- do.call(h2o.gbm,
                   ## update parameters in place
                   {
                     p <- gbm@parameters
                     p$model_id = NULL          ## do not overwrite the original grid model
                     p$training_frame = df      ## use the full dataset
                     p$validation_frame = NULL  ## no validation frame
                     p$nfolds = 5               ## cross-validation
                     p
                   }
  )
  print(gbm@model_id)
  print(cvgbm@model$cross_validation_metrics_summary[5,]) ## Pick out the "AUC" row
}

gbm <- h2o.getModel(sortedGrid@model_ids[[1]])
preds <- h2o.predict(gbm, test)
head(preds)
gbm@model$validation_metrics@metrics$max_criteria_and_metric_scores


h2o.saveModel(gbm, "~/30-neural-network/bestModel.csv", force=TRUE)
h2o.exportFile(preds, "~/30-neural-network/bestPreds.csv", force=TRUE)
> print(h2o.auc(h2o.performance(gbm, newdata = test)))
[1] 0.9743624
> 
> gbm@parameters
$model_id
[1] "final_grid_model_59"

$training_frame
[1] "train.hex"

$validation_frame
[1] "valid.hex"

$score_tree_interval
[1] 10

$ntrees
[1] 10000

$max_depth
[1] 28

$min_rows
[1] 8

$nbins
[1] 1024

$nbins_cats
[1] 256

$stopping_rounds
[1] 5

$stopping_metric
[1] "AUC"

$stopping_tolerance
[1] 1e-04

$max_runtime_secs
[1] 3461.373

$seed
[1] 1234

$learn_rate
[1] 0.05

$learn_rate_annealing
[1] 0.99

$distribution
[1] "bernoulli"

$sample_rate
[1] 0.9

$col_sample_rate
[1] 0.65

$col_sample_rate_change_per_level
[1] 1.02

$col_sample_rate_per_tree
[1] 0.67

$histogram_type
[1] "RoundRobin"

$x
 [1] "pclass"    "sex"       "age"       "sibsp"     "parch"     "ticket"    "fare"      "cabin"     "embarked" 
[10] "boat"      "body"      "home.dest"

$y
[1] "survived"

> model@model$cross_validation_metrics_summary
Cross-Validation Metrics Summary: 
                           mean           sd  cv_1_valid cv_2_valid  cv_3_valid  cv_4_valid  cv_5_valid
F0point5             0.93560344  0.014515156   0.9448819  0.9404762  0.89641434   0.9567198   0.9395248
F1                    0.9102194  0.008333748   0.9099526  0.8926554   0.9045226   0.9281768   0.9157895
F2                    0.8868533  0.015512505   0.8775137  0.8494624   0.9127789  0.90128756   0.8932238
accuracy             0.93442553 0.0058180066  0.92883897  0.9298893   0.9263566   0.9488189  0.93822396
auc                   0.9695648 0.0060331114   0.9673963  0.9566369   0.9658801  0.98000664    0.977904
err                  0.06557447 0.0058180066  0.07116105  0.0701107  0.07364341 0.051181104  0.06177606
err_count                  17.2    1.6970563          19         19          19          13          16
lift_top_group        2.6258688  0.099894695   2.3839285  2.8229167    2.632653   2.6736841   2.6161616
logloss              0.19936788  0.015208231  0.21330717 0.22592694   0.2054847  0.16403295  0.18808767
max_per_class_error  0.12771495  0.022303823  0.14285715 0.17708333  0.08163265  0.11578947 0.121212125
mcc                   0.8617862  0.011978933   0.8559271   0.847847   0.8448622   0.8912296   0.8690649
mse                 0.055098847 0.0046449783 0.059775334 0.06225182 0.058212284  0.04435746  0.05089733
precision             0.9537766  0.022758426    0.969697 0.97530866   0.8910891   0.9767442  0.95604396
r2                     0.766055   0.02020471  0.75453204  0.7278669   0.7528799   0.8105418   0.7844543
recall               0.87228507  0.022303823  0.85714287  0.8229167   0.9183673   0.8842105   0.8787879
specificity           0.9725776  0.015016872   0.9806452  0.9885714     0.93125   0.9874214       0.975

> gbm@model$validation_metrics@metrics$max_criteria_and_metric_scores
Maximum Metrics: Maximum metrics at their respective thresholds
                      metric threshold    value idx
1                     max f1  0.421230 0.935961  97
2                     max f2  0.262753 0.928030 107
3               max f0point5  0.729648 0.962801  87
4               max accuracy  0.478497 0.952555  95
5              max precision  0.988875 1.000000   0
6                 max recall  0.013276 1.000000 252
7            max specificity  0.988875 1.000000   0
8           max absolute_MCC  0.478497 0.900226  95
9 max min_per_class_accuracy  0.262753 0.933333 107

초모수 미세조정을 통해 최적화된 GBM 모형 하나보다 경우에 따라서는 앙상블 기법을 사용한 방법이 더 좋은 성능을 보여주기도 한다.

#--------------------------------------------------------------------------
# 5.1. 앙상블 기법
#--------------------------------------------------------------------------

prob = NULL
k=10
for (i in 1:k) {
  gbm <- h2o.getModel(sortedGrid@model_ids[[i]])
  if (is.null(prob)) prob = h2o.predict(gbm, test)$p1
  else prob = prob + h2o.predict(gbm, test)$p1
}
prob <- prob/k
head(prob)

probInR  <- as.vector(prob)
labelInR <- as.vector(as.numeric(test[[response]]))
if (! ("cvAUC" %in% rownames(installed.packages()))) { install.packages("cvAUC") }
library(cvAUC)
cvAUC::AUC(probInR, labelInR)
> cvAUC::AUC(probInR, labelInR)
[1] 0.9748249