머신러닝 예제

한치록(2022). 계량경제학강의, 제4판, 박영사, 19장 부록 “머신러닝 예제”, 버전 0.2.2.

이 문서에 제시된 R 명령들은 주로 James, Witten, Hastie and Tibshirani (2013, An Introduction to Statistical Learning: with Applications in R, Springer)을 참고하였으며, 그 책으로 충분하지 않은 부분은 패키지 문서에 기초하여 직접 코딩하였거나 인터넷 검색으로부터 얻은 정보를 확인하여 작성하였다.

회귀(regression) 분류(classifiction)
데이터 준비
Subset Selection
Splines
Ridge와 Lasso
PCR과 PLS
Decision Tree
Tree Ensemble
Support Vector Regression
Neural Networks
Super Learner
데이터 준비
Logit, LDA, QDA
ROC와 Cutoff 값
Class Imbalance
Ridge와 Lasso
Decision Tree
Tree Ensemble
Support Vector Machines
Neural Networks
Super Leaner
실습용 데이터 준비는 여기 참조. 본 소절의 전체 코드는 여기에 있음.

Neural Networks

H2O 패키지 설치

Lasso 부분의 마지막에 살펴본 바 있는 h2o 패키지(설치)를 사용한다. 설치 시 파일 다운로드 도중에 중단되면, options(timeout = 600)으로 time-out 시간을 적절히 늘려 주면(600은 10분) 도움이 된다.

options(timeout=600)
install.packages("h2o", type="source", repos=(c("http://h2o-release.s3.amazonaws.com/h2o/latest_stable_R"))) # will take long
options(timeout=60) # default

구버전으로 만족하다면 install.packages("h2o")로도 충분하다. 시스템에 Java가 설치되어 있어야 한다. 설치가 끝났으면 다음으로 시험해 본다.

library(h2o)
h2o.init()
h2o.shutdown(prompt = FALSE)

library(h2o) 부분에서 오류가 발생하는 것은 R h2o 패키지가 제대로 설치되지 않았음을 의미하고, h2o.init() 부분에서 오류가 발생하는 것은 Java와 관련되었을 가능성이 높다. 오류 메시지를 읽어 보고 문제를 해결하기 바란다.

H2O는 배경에 java 엔진을 돌리고 R의 h2o 패키지는 이 엔진에 접속하여 일처리를 한다. 뭔가 알 수 없는 오류가 발생할 수도 있는데, 그럴 때에는 h2o.shutdown(prompt = FALSE)로 H2O를 shutdown한 후 다시 해 보거나, 아니면 시스템을 재부팅해야 할지도 모른다. 가급적 최신 버전을 사용하는 것이 좋겠다.

NN 실습

이 절의 내용은 H2O.ai의 Deep Learning 문서를 참고하였다. 우선 h2o를 시작한다.

library(h2o) # install.packages("h2o") if necessary
h2o.init()

H2O에서 자료 집합은 특별한 형태로 관리된다. as.h2o() 함수를 사용하여 만들 수 있다. 매번 as.h2o(z14)처럼 사용해도 좋으나, 자주 사용할 것이므로 미리 만들어 두고 시작하자.

z14h <- as.h2o(z14)
z15h <- as.h2o(z15)

다음으로 NN을 학습해 본다. 노드 1개짜리 은닉층이 1개 있는 모형을 학습해 보자(hidden = c(1)).

yvar <- 'ynext'
xvar <- setdiff(names(z14), yvar)
nn <- h2o.deeplearning(xvar, yvar, z14h, standardize = TRUE, hidden = c(1), epochs = 100, seed = 1, reproducible = TRUE)
h2o.performance(nn, train=TRUE)
# H2ORegressionMetrics: deeplearning
# ** Reported on training data. **
# ** Metrics reported on full training frame **
# 
# MSE:  2812.667
# RMSE:  53.03459
# MAE:  40.0028
# RMSLE:  0.07964379
# Mean Residual Deviance :  2812.667

위에서 reproducible = TRUE 옵션은 매번 동일한 결과를 얻도록 하기 위함이다. Train set의 RMSE는 53.03459이다. 참고로, 이 RMSE는 다음과 같이 구할 수도 있다.

RMSE(z14h$ynext, h2o.predict(nn, z14h)) # RMSE() defined in index11.php
# [1] 53.03458
# RMSE(z14$ynext, as.vector(h2o.predict(nn, z14h)))

학습 없이 Random Walk로부터 구한 RMSE는 다음과 같다.

RMSE(z14$ynext, z14$deathrate) # RMSE() defined in index11.php
# [1] 57.12526

NN으로부터 얻은 train set의 RMSE (53.03459)는 단순 random walk를 적용한 결과(RMSE = 57.12526)보다는 더 낫다.

위 NN 학습 결과를 test set에 적용할 때 예측 결과는 다음과 같다.

h2o.performance(nn, newdata = z15h)
# H2ORegressionMetrics: deeplearning
# 
# MSE:  2875.945
# RMSE:  53.62784
# MAE:  39.75183
# RMSLE:  0.07874747
# Mean Residual Deviance :  2875.945

결과는 평범하다(nn으로부터의 RMSE 53.62784를 random walk의 RMSE 53.24273과 비교). epochs = 500으로 설정하면 결과는 다음과 같다.

nn500 <- h2o.deeplearning(xvar, yvar, z14h, standardize = TRUE, hidden = c(1), epochs = 500, seed = 1, reproducible = T)
nn500
# Model Details:
# ==============
# 
# H2ORegressionModel: deeplearning
# Model ID:  DeepLearning_model_R_1632757675284_2 
# Status of Neuron Layers: predicting ynext, regression, gaussian distribution, Quadratic loss, 22 weights/biases, 5.5 KB, 38,356 training samples, mini-batch size 1
#   layer units      type dropout       l1       l2 mean_rate rate_rms momentum
# 1     1    19     Input  0.00 %       NA       NA        NA       NA       NA
# 2     2     1 Rectifier  0.00 % 0.000000 0.000000  0.001999 0.000800 0.000000
# 3     3     1    Linear      NA 0.000000 0.000000  0.000424 0.000000 0.000000
#   mean_weight weight_rms mean_bias bias_rms
# 1          NA         NA        NA       NA
# 2    0.078716   0.266824  1.728693 0.000000
# 3    0.639869   0.000000 -1.123834 0.000000
# 
# 
# H2ORegressionMetrics: deeplearning
# ** Reported on training data. **
# ** Metrics reported on full training frame **
# 
# MSE:  2409.193
# RMSE:  49.08353
# MAE:  35.83647
# RMSLE:  0.06359786
# Mean Residual Deviance :  2409.193

h2o.performance(nn500, newdata = z15h)
# H2ORegressionMetrics: deeplearning
# 
# MSE:  2503.499
# RMSE:  50.03498
# MAE:  35.34185
# RMSLE:  0.06333317
# Mean Residual Deviance :  2503.499

Test set에 대한 예측력이 약간 좋아졌다. 거듭 말하지만 test set 예측력이 좋아졌다는 것은 사후적으로 보니 그렇다는 것이며, test set에 대한 예측력을 바탕으로 모델을 고르는 것은 아니다.

위에서 epochsboosting에서 boosting 횟수의 역할을 하며, 튜닝 대상이다. 아무 옵션도 주지 않으면 stopping_rounds = 5 옵션을 준 것과 같아서, 최근 5회 deviance의 단순 이동평균이 5회 이상 개선되지 않으면 자동으로 멈추도록 되어 있다(early stopping). 실제로, epochs = 500으로 주었지만 172회에서 멈추었다.

max(nn500@model$scoring_history$iterations)
# [1] 172
h2o.learning_curve_plot(nn500)
H2O Deep Learning의 training set learning curve

CV를 위해서는 h2o에서 nfolds = 10과 같은 옵션을 주면 CV를 한다. 그러면 stopping_rounds epoch 이상 CV 성능개선이 없으면 멈춘다(early stopping). stopping_rounds를 50으로 설정하면 결과는 다음과 같다.

nn.tuned <- h2o.deeplearning(xvar, yvar, z14h, standardize = TRUE, hidden = c(1), epochs = 1000, seed = 1, nfolds = 10, stopping_rounds = 50, variable_importances = TRUE, reproducible = TRUE)
h2o.learning_curve_plot(nn.tuned)
H2O Deep Learning의 learning curve와 early stopping

위 명령에서 epochs = 1000으로 최대 횟수를 1,000으로 설정하였으나 early stopping 기준에 따라 약 300회 후 종료되었음을 알 수 있다(stopping_rounds를 50으로 설정했는데, 더 작은 숫자로 줄이면 더 일찍 종료된다).

학습의 성능은 다음과 같다.

h2o.performance(nn.tuned, train = TRUE)  # train
# RMSE:  47.38929
h2o.performance(nn.tuned, xval = TRUE)   # cross validation
# RMSE:  51.43421
h2o.performance(nn.tuned, newdata = z15h) # test set
# RMSE:  50.23829
h2o.varimp_plot(nn.tuned)
H2O deeplearning 학습으로부터의 변수 중요도

위 결과에 의하면 stopping_rounds 값이 50일 때 train, CV, test RMSE는 각각 47.38929, 51.43421, 50.23829이다.

참고로, CV 및 early stopping과 관련하여 H2O.ai FAQ에 다음과 같이 설명되어 있다.

Can H2O automatically feed back the implications of the cross-validation results to improve the algorithm during training, as well as tune some of the model’s hyperparamters?

Yes, H2O can use cross-validation for parameter tuning if early stopping is enabled (stopping_rounds>0). In that case, cross-validation is used to automatically tune the optimal number of epochs for Deep Learning or the number of trees for DRF/GBM. The main model will use the mean number of epochs across all cross-validation models.

추가로, 은닉층 node 개수를 1, 2, 3개로 하면서 CV RMSE를 비교해 보자.

nn1 <- h2o.deeplearning(xvar, yvar, z14h, standardize = TRUE, hidden = c(1), epochs = 1000, seed = 1, nfolds = 10, stopping_rounds = 50, reproducible = T)
h2o.performance(nn1, xval = TRUE)  # CV
# RMSE:  51.43421
nn2 <- h2o.deeplearning(xvar, yvar, z14h, standardize = TRUE, hidden = c(2), epochs = 1000, seed = 1, nfolds = 10, stopping_rounds = 50, reproducible = T)
h2o.performance(nn2, xval = TRUE)  # CV
# RMSE:  54.63187
nn3 <- h2o.deeplearning(xvar, yvar, z14h, standardize = TRUE, hidden = c(3), epochs = 1000, seed = 1, nfolds = 10, stopping_rounds = 50, reproducible = T)
h2o.performance(nn3, xval = TRUE)  # CV
# RMSE:  61.67837

작은 NN의 CV 오차수준이 더 낮다. 그러므로 위에서 은닉층 노드 개수를 1개로 한 모형이 2개나 3개보다 선호된다. (은닉층을 아예 없애면 학습에 시간이 아주 오래 걸리고 성과가 더 나아지지도 않는다.)

본 실습에서는 overfitting보다는 underfitting이 문제인 것 같다. 일반적으로는 overfitting이 문제이다. 이 경우 NN에서 overfitting을 피하는 방법으로 early stopping 이외에도 L1 또는 L2 regularisation (lasso와 ridge와 유사, l1l2 옵션 사용), Hinton et al (2012)의 dropout 방법(input_dropout_ratiohidden_dropout_ratios 옵션 사용)도 사용된다.

다음 명령으로 h2o를 종료한다.

h2o.shutdown(prompt = FALSE)
 
## --------------------------------------------------------------------
library(h2o)
h2o.init()
z14h <- as.h2o(z14)
z15h <- as.h2o(z15)
yvar <- 'ynext'
xvar <- setdiff(names(z14), yvar)
nn <- h2o.deeplearning(xvar, yvar, z14h, standardize = TRUE, hidden = c(1), epochs = 100, seed = 1, reproducible = TRUE)
h2o.performance(nn, train=TRUE)
RMSE(z14h$ynext, h2o.predict(nn, z14h))
RMSE(z14$ynext, z14$deathrate)
h2o.performance(nn, newdata = z15h)
nn500 <- h2o.deeplearning(xvar, yvar, z14h, standardize = TRUE, hidden = c(1), epochs = 500, seed = 1, reproducible = T)
nn500
h2o.performance(nn500, newdata = z15h)
max(nn500@model$scoring_history$iterations)
h2o.learning_curve_plot(nn500)
nn.tuned <- h2o.deeplearning(xvar, yvar, z14h, standardize = TRUE, hidden = c(1), epochs = 1000, seed = 1, nfolds = 10, stopping_rounds = 50, variable_importances = TRUE, reproducible = TRUE)
h2o.learning_curve_plot(nn.tuned)
h2o.performance(nn.tuned, train = TRUE)  # train
h2o.performance(nn.tuned, xval = TRUE)   # cross validation
h2o.performance(nn.tuned, newdata = z15h) # test set
h2o.varimp_plot(nn.tuned)
nn1 <- h2o.deeplearning(xvar, yvar, z14h, standardize = TRUE, hidden = c(1), epochs = 1000, seed = 1, nfolds = 10, stopping_rounds = 50, reproducible = T)
h2o.performance(nn1, xval = TRUE)  # CV
nn2 <- h2o.deeplearning(xvar, yvar, z14h, standardize = TRUE, hidden = c(2), epochs = 1000, seed = 1, nfolds = 10, stopping_rounds = 50, reproducible = T)
h2o.performance(nn2, xval = TRUE)  # CV
nn3 <- h2o.deeplearning(xvar, yvar, z14h, standardize = TRUE, hidden = c(3), epochs = 1000, seed = 1, nfolds = 10, stopping_rounds = 50, reproducible = T)
h2o.performance(nn3, xval = TRUE)  # CV
h2o.shutdown(prompt = FALSE)
## --------------------------------------------------------------------