04-26 04:04
Notice
Recent Posts
Recent Comments
관리 메뉴

Scientific Computing & Data Science

[Artificial Intelligence / H2O] R에서 H2O 라이브러리를 이용한 딥러닝(CNN) 구현하기 본문

Artificial Intelligence/H2O

[Artificial Intelligence / H2O] R에서 H2O 라이브러리를 이용한 딥러닝(CNN) 구현하기

cinema4dr12 2017. 4. 1. 15:33

by Geol Choi | April


이번 포스팅에서는 R에서 h2o(https://www.h2o.ai) 라이브러리를 이용하여 MNIST 손글씨 숫자(Hand-written Digits) 이미지 데이터세트에 대하여 딥러닝 CNN(Convolutional Neural Network)을 통하여 학습을 시키고, 학습된 결과를 기반으로 테스트 데이터세트에 대하여 인식률을 계산해 보도록 하겠다.


MNIST 데이터세트는 NIST라는 표준 참고용 데이터 중 일부로서 총 60,000개의 학습용 데이터세트와 10,000개의 테스트용 데이터세트로 구성된다.


MNIST 데이터세트는 NIST의 오리지널 흑백 이미지를 20×20 픽셀 크기로 정규화  한 것이다. 결과 이미지들은 정규화 알고리즘을 이용하여 안티앨리어싱 기술을 기반으로 0-255 범위의 회색조(Greyscale)로 변환된다. 이미지 해상도는 28×28(각 이미지는 총 784개의 픽셀을 가지고 있음)있다.


데이터 다운로드 및 확인

Kaggle 사이트에서 간단한 가입절차를 마친 후 MNIST 데이터 이미지를 선처리(픽셀 이미지 밝기값을 디지타이즈(Digitize)한 것) 데이터를 다운받을 수 있다. train.csv 데이터세트는 42000×785 사이즈의 크기를 갖는데, 행 크기인 28000은 손글씨로 쓴 숫자 이미지의 개수이며, 열 크기인 785 중 첫번째 열(Column)은 각 이미지에 대한 라벨(0-9의 숫자)이며, 2-785번째(총 784개(= 28×28) 픽셀) 열(Columns)들은 각 이미지에 대한 픽셀의 밝기값이다.


test.csv 데이터세트는 28000×784 사이즈의 크기를 갖는데, 28000개의 손글씨 숫자 이미지로 구성되는데, train.csv 보다 열의 개수가 1개 작은 것은 라벨이 없기 때문이다. train.csv와 test.csv의 이미지 데이터 수를 합치면 60000개이다.


만약 Digitize 된 데이터를 이미지로 출력해 보고 싶은 호기심이 생긴다면, 다음 코드와 같이 R의 imager 라이브러리 패키지를 이용하여 출력해 보도록 한다.


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
26
27
28
29
base::rm(list = ls())
base::gc()
 
if (! ("imager" %in% rownames(installed.packages()))) { install.packages("imager") }
base::library(imager)
 
train <- utils::read.csv("./data/train.csv")
 
# make path if doesn't exist
dir.create(file.path("./images/"), showWarnings = FALSE)
 
for(i in 0:9) {
  imgPath <- base::sprintf("./images/%d", i)
  dir.create(file.path(imgPath), showWarnings = FALSE)
}
 
for(i in 1:base::nrow(train)) {
  digit <- train[i,1]
  tmp <- base::as.vector(train[i,-1]/255, mode = "double")
  img <- base::array(tmp, base::c(28,28,1,1))
  num <- imager::as.cimg(img)
  
  fileName <- base::sprintf("./images/%d/%05d.png", digit, i)
  
  imager::save.image(im = num, file = fileName)
  
  proc <- base::sprintf("Process: %10.6f%%"100.0*i/(base::nrow(train)))
  print(proc)
}
cs


위의 코드는 train.csv 데이터세트로부터 각 이미지 정보(각 행)를 불러와서 이미지로 구성 후 0-9의 숫자 라벨로 분류하여 저장하도록 한 것이다.


Deep Learning CNN 구현하기

오픈소스 Machine Learning 라이브러리 중 하나인 h2o 라이브러리를 이용하여 숫자를 인식시키는 코드를 작성해 보도록 한다.


Step 1. h2o 라이브러리 로딩하기


h2o 라이브러리 로딩은 h2o 기존 라이브러리를 삭제하고, 의존하는 라이브러리들을 설치하고, 라이브러리를 로딩하는 순서로 진행된다:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#####################################################################################
# Step 1: 필요한 라이브러리 로딩하기
#####################################################################################
# 기존에 설치되어 있는 h2o 라이브러리 제거
if ("package:h2o" %in% search()) { detach("package:h2o", unload=TRUE) }
if ("h2o" %in% rownames(installed.packages())) { remove.packages("h2o") }
 
# H2O가 의존하는 라이브러리들 설치
if (! ("methods" %in% rownames(installed.packages()))) { install.packages("methods") }
if (! ("statmod" %in% rownames(installed.packages()))) { install.packages("statmod") }
if (! ("stats" %in% rownames(installed.packages()))) { install.packages("stats") }
if (! ("graphics" %in% rownames(installed.packages()))) { install.packages("graphics") }
if (! ("RCurl" %in% rownames(installed.packages()))) { install.packages("RCurl") }
if (! ("jsonlite" %in% rownames(installed.packages()))) { install.packages("jsonlite") }
if (! ("tools" %in% rownames(installed.packages()))) { install.packages("tools") }
if (! ("utils" %in% rownames(installed.packages()))) { install.packages("utils") }
 
# h2o 라이브러리 설치 후 라이브러리 로딩
install.packages(pkgs = "h2o",
                 type = "source",
                 repos = (base::c("http://h2o-release.s3.amazonaws.com/h2o/rel-ueno/2/R")))
base::library(h2o)
cs


training 데이터와 test 데이터를 분류하기 위해 caret 라이브러리를 로딩한다:


1
2
if (! ("caret" %in% rownames(installed.packages()))) { install.packages("caret") }
base::library(caret)
cs


Step 2. training 데이터세트 로딩하기


Kaggle 사이트에서 다운로드 한 training.csv 데이터를 로딩한다.


1
2
3
4
5
#####################################################################################
# Step 2: training 데이터세트 로딩
#####################################################################################
# MNIST 데이터 로딩
train <- utils::read.csv("./data/train.csv")
cs


Step 3. 데이터세트 분류: training & testing 데이터


주어진 데이터세트를 training용과 testing용으로 분류하되, training용으로 80%를, 나머지 20%를 testing용으로 한다. 이를 위해 caret 패키지 라이브러리의 createDataPartition() 함수를 이용하는데 옵션 중 p는 [0,1] 사이의 값으로 추출할 비율을 의미한다:


1
2
3
4
5
6
7
8
9
10
#####################################################################################
# Step 3: 데이터세트 분류: training 데이터 80%, testing 데이터 20%
#####################################################################################
inTrain <- caret::createDataPartition(train$label, p = 0.8, list = FALSE)
training <- train[inTrain,]
testing <- train[-inTrain,]
 
# datasets를 .csv 파일로 저장
utils::write.csv (training , file = "./data/train-data.csv", row.names = FALSE) 
utils::write.csv (testing , file = "./data/test-data.csv", row.names = FALSE)
cs


Step 4. h2o 클러스터 초기화 

다음 코드는 로컬호스트로 h2o 클러스터를 시작하기 위한 것이다:


1
2
3
4
5
#####################################################################################
# Step 4: h2o 클러스터 초기화
#####################################################################################
# 로컬 h2o 클러스터 시작
local.h2o <- h2o::h2o.init(ip = "localhost", port = 54321, startH2O = TRUE, nthreads = -1)
cs


여기서 초기화 옵션 중 nthreads를 -1로 지정한 것은 호스트 머신의 사용가능한 쓰레드를 모두 사용하겠다는 의미이다. 이외에도 최대/최소 메모리 크기 설정 등 많은 옵션을 제공하는 자세한 내용은 h2o 라이브러리의 개발 가이드 문서를 참고하기 바란다.


이제 training 데이터세트의 라벨을 Classification을 위한 factor로 변환한다 (앞서 언급한 바와 같이 training[,1]에는 0-9의 숫자에 대한 라벨이 기록되어 있다):


1
2
# classification을 위해 라벨을 factor로 변환
training[,1<- h2o::as.factor(training[,1])
cs


R 환경에서 H2O 인스턴스로 training 및 testing Data Frame을 전달한다:


1
2
3
# R 환경에서 H2O 인스턴스로 데이터 전달
trData <- h2o::as.h2o(training)
tsData <- h2o::as.h2o(testing)
cs


R 환경에서 H2O 인스턴스로 데이터를 전달한다는 의미는 H2O가 R이 아닌 JVM(Java Virtual Machine) 환경에서 실행되기 때문이다. 즉, R로부터 JVM으로 데이터를 전달하고 JVM 환경에서 실질적인 DNN(Deep Neural Network) 연산이 실행되고 결과를 다시 R로 받아오는 과정이 진행되는 것이다. 그래서 R에 설치된 H2O 라이브러리는 R-JVM의 인터페이스 역할을 하는 것이다 (그래서 가이드 문서를 보면 64-bit JRE가 필요하다는 설명이 있다.)


Step 5. DNN을 이용하여 예측 모델 생성하기

이제 DNN을 이용하여 training 데이터를 학습시키는데 이를 위한 h2o 라이브러리의 함수는 h2o.deeplearning()이다.  우선, 다음 코드를 작성해 보도록 한다:


1
2
3
4
5
6
7
8
9
10
#####################################################################################
# Step 5: DNN을 이용하여 학습시키기
#####################################################################################
base::system.time(train_result <- h2o::h2o.deeplearning(x = 2:785,
                                                        y = 1,
                                                        training_frame = trData,
                                                        activation = "Rectifier",
                                                        hidden = base::rep(160,5),
                                                        epochs = 20)
)
cs


R의 헬프로부터 h2o.deeplearning()를 검색해보면 다음과 같이 해당 함수의 옵션이 어마어마하게 많이 나온다.


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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
h2o.deeplearning(x, y, training_frame, model_id = NULL,
  validation_frame = NULL, nfolds = 0,
  keep_cross_validation_predictions = FALSE,
  keep_cross_validation_fold_assignment = FALSE, fold_assignment = c("AUTO",
  "Random""Modulo""Stratified"), fold_column = NULL,
  ignore_const_cols = TRUE, score_each_iteration = FALSE,
  weights_column = NULL, offset_column = NULL, balance_classes = FALSE,
  class_sampling_factors = NULL, max_after_balance_size = 5,
  max_hit_ratio_k = 0, checkpoint = NULL, pretrained_autoencoder = NULL,
  overwrite_with_best_model = TRUE, use_all_factor_levels = TRUE,
  standardize = TRUE, activation = c("Tanh""TanhWithDropout""Rectifier",
  "RectifierWithDropout""Maxout""MaxoutWithDropout"), hidden = c(200,
  200), epochs = 10, train_samples_per_iteration = -2,
  target_ratio_comm_to_comp = 0.05, seed = -1, adaptive_rate = TRUE,
  rho = 0.99, epsilon = 1e-08, rate = 0.005, rate_annealing = 1e-06,
  rate_decay = 1, momentum_start = 0, momentum_ramp = 1e+06,
  momentum_stable = 0, nesterov_accelerated_gradient = TRUE,
  input_dropout_ratio = 0, hidden_dropout_ratios = NULL, l1 = 0, l2 = 0,
  max_w2 = 3.4028235e+38, initial_weight_distribution = c("UniformAdaptive",
  "Uniform""Normal"), initial_weight_scale = 1, initial_weights = NULL,
  initial_biases = NULL, loss = c("Automatic""CrossEntropy""Quadratic",
  "Huber""Absolute""Quantile"), distribution = c("AUTO""bernoulli",
  "multinomial""gaussian""poisson""gamma""tweedie""laplace",
  "quantile""huber"), quantile_alpha = 0.5, tweedie_power = 1.5,
  huber_alpha = 0.9, score_interval = 5, score_training_samples = 10000,
  score_validation_samples = 0, score_duty_cycle = 0.1,
  classification_stop = 0, regression_stop = 1e-06, stopping_rounds = 5,
  stopping_metric = c("AUTO""deviance""logloss""MSE""RMSE""MAE",
  "RMSLE""AUC""lift_top_group""misclassification",
  "mean_per_class_error"), stopping_tolerance = 0, max_runtime_secs = 0,
  score_validation_sampling = c("Uniform""Stratified"),
  diagnostics = TRUE, fast_mode = TRUE, force_load_balance = TRUE,
  variable_importances = FALSE, replicate_training_data = TRUE,
  single_node_mode = FALSE, shuffle_training_data = FALSE,
  missing_values_handling = c("MeanImputation""Skip"), quiet_mode = FALSE,
  autoencoder = FALSE, sparse = FALSE, col_major = FALSE,
  average_activation = 0, sparsity_beta = 0,
  max_categorical_features = 2147483647, reproducible = FALSE,
  export_weights_and_biases = FALSE, mini_batch_size = 1,
  categorical_encoding = c("AUTO""Enum""OneHotInternal""OneHotExplicit",
  "Binary""Eigen"), elastic_averaging = FALSE,
  elastic_averaging_moving_rate = 0.9,
  elastic_averaging_regularization = 0.001)
cs


모든 옵션을 다 이해하고 쓰면 좋겠지만 일단 지금 필요한 몇몇 옵션에 대해서만 설정하였다.


코드에서 설정한 옵션은 다음과 같다:

  • x: 예측 모델 생성을 위해 사용할 열(Column) 인덱스(벡터), 픽셀 정보를 저장하고 있는 열(2~785)을 지정함.

  • y: 모델의 응답 변수, 라벨 정보를 저장하고 있는 열(첫번째)을 지정함.

  • training_frame: training Data Frame.

  • activation: 활성화 함수, h2o가 지원하는 활성화 함수에는 "Tanh", "TanhWithDropout", "Rectifier", "RectifierWithDropout", "Maxout", "MaxoutWithDropout"이 있으며, 이 중 "Rectifier"(Rectified Linear Unit; ReLU)를 사용함.

  • hidden: 은닉 레이어 개수. 5개의 레이어를 사용하였으며, 각 레이어 당 160개의 노드를 갖도록 함.

  • epochs: 반복 계산 회수로 생각하면 됨.


system.time() 함수는 h2o.deeplearning()를 연산하는데 걸린시간을 출력하기 위해 사용하였다.


필자가 가지고 있는 머신(Intel(R) Core(TM) i5-4670 CPU@3.40GHz)으로 353초 정도가 걸렸다:


사용자  시스템 elapsed 
 0.46    0.07  352.97


Step 6. 모델을 이용하여 예측하기

Step 5까지의 과정을 통해 예측 모델이 완성되었다 (즉, Weights와 Biases가 모두 정해졌다).

이제 계산된 예측 모델을 통해 testing 데이터세트를 예측하는 코드를 다음과 같이 작성하도록 한다:


1
2
3
4
5
6
7
#####################################################################################
# Step 6: 모델을 이용하여 예측하기
#####################################################################################
# 모델을 이용하여 testing 데이터세트 예측
base::system.time(predict_result <- h2o::h2o.predict(object = train_result,
                                                     newdata = tsData[,-1])
)
cs


h2o.predict()의 옵션 중 object는 예측 모델 (즉 Step 5에서 계산된 train_result를 입력)이며, newdata는 예측에 사용할 데이터이다. tsData[,-1]는 tsData에서 첫번째 열을 제외한 것이다.


필자의 머신으로 모델을 이용하여 testing 데이터를 예측하는데 걸린 시간은 다음과 같았다:


 사용자  시스템 elapsed 
   0.03    0.01    1.98


이제 계산된 모델이 얼마나 예측을 잘 했는지 살펴보기 위해 predict_result를 Data Frame으로 변환한다:


1
predict_result.df <- base::as.data.frame(predict_result)
cs


testing 데이터세트로부터 라벨(첫번째 열)을 추출하고:


1
test_labels <- testing[,1]
cs


올바르게 예측된 요소들의 개수를 계산한다:


1
2
# 올바르게 예측한 것의 개수를 계산함
num.correct.pred <- base::sum(base::diag(base::table(test_labels, predict_result.df[,1])))
cs


위의 코드는 test_lables와 predict_result.df[,1]의 각 요소를 비교하여 일치하는 것들의 개수를 카운트하는 것이다:


> base::table(test_labels, predict_result.df[,1])
           
test_labels   0   1   2   3   4   5   6   7   8   9
          0 804   0   2   0   2   4   5   0   1   1
          1   0 911   1   1   4   0   1   4   2   1
          2   2   1 819  12   1   4   3   5   5   2
          3   0   0   6 826   0  12   0   3   5   4
          4   0   0   0   0 810   0   4   5   0   9
          5   1   0   0   7   1 731   3   1   2   6
          6   2   1   0   1   5   6 789   0   2   0
          7   1   0   6   1   5   1   0 886   2   6
          8   1   4   6   3   7  13   2   0 745   3
          9   0   0   1   6  10   7   0   5   1 836


위의 결과에서 대각선 요소들이 일치하는 요소들의 개수이며, 이들을 합한 것이 올바르게 예측된 요소들의 총 개수이다.


이제 전체 개수 중 올바르게 예측된 요소들의 총 비율을 계산해 보도록 한다:


1
correct.rate <- 100 * num.correct.pred / base::length(test_labels)
cs


계산 결과를 출력해 보면:


> print(correct.rate)
[1] 97.13027


97.1% 정도의 예측율을 보이고 있음을 알 수 있다.


Step 7. H2O 클러스터 셧다운

h2o의 모든 연산이 완료되면, h2o 클러스터를 셧다운하도록 한다:


1
2
3
4
#####################################################################################
# Step 7: H2O 클러스터 셧다운
#####################################################################################
h2o::h2o.shutdown(prompt = FALSE)
cs


prompt는 셧다운 시 셧다운을 정말로 할 것인지 확인을 하는 옵션이다. FALSE인 경우, 이 과정을 거치지 않는다.


전체 코드

지금까지 작성해 본 전체 코드는 다음과 같다:


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
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
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
base::rm(list = ls())
base::gc()
 
#####################################################################################
# Step 1: 필요한 라이브러리 로딩하기
#####################################################################################
# 기존에 설치되어 있는 h2o 라이브러리 제거
if ("package:h2o" %in% search()) { detach("package:h2o", unload=TRUE) }
if ("h2o" %in% rownames(installed.packages())) { remove.packages("h2o") }
 
# H2O가 의존하는 라이브러리들 설치
if (! ("methods" %in% rownames(installed.packages()))) { install.packages("methods") }
if (! ("statmod" %in% rownames(installed.packages()))) { install.packages("statmod") }
if (! ("stats" %in% rownames(installed.packages()))) { install.packages("stats") }
if (! ("graphics" %in% rownames(installed.packages()))) { install.packages("graphics") }
if (! ("RCurl" %in% rownames(installed.packages()))) { install.packages("RCurl") }
if (! ("jsonlite" %in% rownames(installed.packages()))) { install.packages("jsonlite") }
if (! ("tools" %in% rownames(installed.packages()))) { install.packages("tools") }
if (! ("utils" %in% rownames(installed.packages()))) { install.packages("utils") }
 
# h2o 라이브러리 설치 후 라이브러리 로딩
install.packages(pkgs = "h2o",
                 type = "source",
                 repos = (base::c("http://h2o-release.s3.amazonaws.com/h2o/rel-ueno/2/R")))
base::library(h2o)
 
if (! ("caret" %in% rownames(installed.packages()))) { install.packages("caret") }
base::library(caret)
 
#####################################################################################
# Step 2: training 데이터세트 로딩
#####################################################################################
# MNIST 데이터 로딩
train <- utils::read.csv("./data/train.csv")
 
# 28*28 픽셀 matrix 생성
= base::matrix(base::unlist(train[10,-1]), nrow = 28, byrow = TRUE)
 
# matrix 플롯
graphics::image(m, col = grey.colors(255))
 
# matrix 회전
rotate <- function(x) base::t(base::apply(x, 2, base::rev)) 
 
# 일부 이미지 플롯
graphics::par(mfrow = base::c(2,3))
base::lapply(1:6,
             function(x) image(
               rotate(base::matrix(base::unlist(train[x,-1]), nrow = 23, byrow = TRUE)),
               col = grey.colors(255),
               xlab = train[x,1]
      )
)
 
# set plot options back to default
graphics::par(mfrow = base::c(1,1))
 
#####################################################################################
# Step 3: 데이터세트 분류: training 데이터 80%, testing 데이터 20%
#####################################################################################
inTrain <- caret::createDataPartition(train$label, p = 0.8, list = FALSE)
training <- train[inTrain,]
testing <- train[-inTrain,]
 
# datasets를 .csv 파일로 저장
utils::write.csv (training , file = "./data/train-data.csv", row.names = FALSE) 
utils::write.csv (testing , file = "./data/test-data.csv", row.names = FALSE)
 
#####################################################################################
# Step 4: h2o 클러스터 초기화
#####################################################################################
# 로컬 h2o 클러스터 시작
local.h2o <- h2o::h2o.init(ip = "localhost", port = 54321, startH2O = TRUE, nthreads = -1)
 
# classification을 위해 라벨을 factor로 변환
training[,1<- h2o::as.factor(training[,1])
 
# R 환경에서 H2O 인스턴스로 데이터 전달
trData <- h2o::as.h2o(training)
tsData <- h2o::as.h2o(testing)
 
#####################################################################################
# Step 5: DNN을 이용하여 예측 모델 생성하기
#####################################################################################
base::system.time(train_result <- h2o::h2o.deeplearning(x = 2:785,
                                                        y = 1,
                                                        training_frame = trData,
                                                        activation = "Rectifier",
                                                        hidden = base::rep(160,5),
                                                        epochs = 20)
)
 
#####################################################################################
# Step 6: 모델을 이용하여 예측하기
#####################################################################################
# 모델을 이용하여 testing 데이터세트 예측
base::system.time(predict_result <- h2o::h2o.predict(object = train_result,
                                                     newdata = tsData[,-1])
)
predict_result.df <- base::as.data.frame(predict_result)
 
test_labels <- testing[,1]
 
# 올바르게 예측한 것의 개수를 계산함
num.correct.pred <- base::sum(base::diag(base::table(test_labels, predict_result.df[,1])))
correct.rate <- 100 * num.correct.pred / base::length(test_labels)
print(correct.rate)
 
#####################################################################################
# Step 7: H2O 클러스터 셧다운
#####################################################################################
h2o::h2o.shutdown(prompt = TRUE)
cs



Comments