TensorFlow with R

최근 Python이 데이터 분석 및 머신러닝에서 매우 좋은 도구로 인지되는 가장 중요한 역할을 한 부분은 딥러닝 기술을 리딩하고 있는 코어 랭귀지라는 측면이 가장 크다. 그 중심에는 TensorFlow가 있을 것이다. 필자의 경우 MXNet기반으로 몇몇 딥러닝 모형을 만들었고, 그중 몇몇은 실제 중요한 모델로서 역할을 잘 수행하고 있다. 물론 MXNet을 사용한 가장 중요한 이유는 R을 지원하는 몇 안되는 프레임웍이라는 것이었다. H2O라는 것도 있지만 딥러닝에서는 다소 기능적인 측면에서 부족한 부분이 많은 상황이다.

그동안 MXNet을 잘 사용하다가 사용을 포기하게 된 중요한 계기가 된건 MXNet이 GPU 메모리를 효과적으로 활용하지 못하는 것을 직접 경험을 통해 알았을 때였다. 매우 심각한 문제였으나 그 이슈에 대한 어떠한 리소스도 없고 해결방안을 찾기 못해 결국 TensorFlow로 갈아타게 되었다. 물론 TensorFlow로 갈아타기 위해 느낀 부담을 덜 수 있게 도움이 된건 RStudio에서 만든 TensorFlow with R이다..

패키지 자체는 용량이 얼마 크지 않은데, 역시나 R긱들이 모여있는 RStudio에서 만든거라 그런지 다른 언어를 Wrapping한 패키지로는 매우 완성도가 높아 개인적으로 시스템에 설치된 Python 라이브러리들을 호출해서 쓰는데까지 사용한다. 아래처럼 말이다.

library(tensorflow)


sys <- import("sys")
sys$stdout$writelines("test")

R의 tensorflow 패키지는 시스템에 설치된 모든 Python라이브러리들의 클래스 구조를 매핑해 R 함수로 동적으로 Wrapping하는 구조를 가지고 있어 Python 함수를 R함수처럼 사용가능하게 한다. 게다가 Python의 Help 구문도 읽어들여 R Help처럼 툴팁으로 보여준다. 결국 R에서의 데이터 전처리의 간편함과 시각화의 강점을 그대로 살려 TensorFlow를 사용할 수 있게 해준다.

무엇보다 아래 예제에서 보여주겠지만, R의 자료구조를 바로 TensorFlow로 넣어줄 수 있어 별도의 복잡한 데이터 변환작업이 전혀 필요없다. 그리고 이런 과정의 대부분이 Native 함수 호출로 되어 있어 그 속도도 매우 빨라 Python Wrapping인지 느껴지지 않을 정도이다.

DNN 예제

분류 예제를 통해 TensorFlow를 R에서 어떻게 사용하는지 확인해보자.

library(tensorflow)
library(Matrix)
library(caret)
library(pROC)
library(data.table)

tf$set_random_seed(1)

data(Sonar, package="mlbench")

Sonar[,61] = as.numeric(Sonar[,61])-1

#scale ,centering으로 변수 전처리 수행 on caret
sonar_scaled <- predict(preProcess(Sonar[,-61]), Sonar[,-61])

#학습셋과 테스트셋 분류 
train.ind<- createDataPartition(Sonar[,61],p = 0.7, list = F)

train.x <- data.matrix(sonar_scaled[train.ind[,1],])
train.y <- Sonar[train.ind, 61]
test.x <- data.matrix(sonar_scaled[-train.ind[,1],])
test.y <- Sonar[-train.ind, 61]


num_x <- as.integer(ncol(train.x))
num_y <- 1L

x <- tf$placeholder(dtype = tf$float32, shape = list(NULL, num_x))
y <- tf$placeholder(dtype = tf$float32, shape = list(NULL, num_y))

#일반적인 
with(tf$name_scope("DNN"),{ 
    fc1 <- tf$contrib$layers$fully_connected(x,10L,
              activation_fn=tf$nn$relu, weights_initializer=tf$contrib$layers$xavier_initializer(uniform = F))
    pred <- tf$contrib$layers$fully_connected(fc1, num_y, 
              weights_initializer=tf$contrib$layers$xavier_initializer(uniform = F))
     })

#노드 1개 출력이니 binary cross entropy로 계산한다.
#AdaGrad와 Momentum을 융합한것과 같은 효과를 보이는 AdamOptimizer를 사용한다. 
#GPU가 있다면 아래 CPU를 GPU로 바꿔주면 된다. 
with({tf$name_scope("loss");tf$device('/cpu:0')},{
  loss <- tf$reduce_mean(tf$nn$sigmoid_cross_entropy_with_logits(pred, y))
  optimizer <- tf$train$AdamOptimizer(learning_rate=0.01)$minimize(loss)
  })

#Accuracy계산 
#ROC계산은 Tensorflow에서 다소 불편하니 R에서 별도로 계산할 것이다. 
with(tf$name_scope("metric"), {
  compare_pred <- tf$cast(tf$sigmoid(pred) > 0.5, tf$float32)
  accuracy <- tf$reduce_mean(tf$cast(tf$equal(compare_pred, y),tf$float32))
  })



# training 
num_of_epoc <- 100L


sess <- tf$Session()

sess$run(tf$global_variables_initializer())

n_batch <- 5

aucs <- list()
for(i in 1:num_of_epoc){
  #print(sprintf("epoc %d", i))

  folds <- createFolds(train.y, k = n_batch)
  for(j in 1:n_batch){
    sess$run(optimizer, dict(x=train.x[folds[[j]],], y=matrix(train.y[folds[[j]]])))
  }

  accu <- sess$run(tf$sigmoid(pred), dict(x=test.x))
  accur <- sess$run(accuracy, dict(x=test.x, y=matrix(test.y)))

  aucs[[i]]<- data.table(epoc=i,aucs=as.numeric(auc(roc(test.y, accu[,1]))),  accuracy=accur)
}

dt_aucs <- rbindlist(aucs)

knitr::kable(dt_aucs[order(-aucs)][1:5])
epoc aucs accuracy
78 0.8464912 0.7741935
80 0.8464912 0.7741935
81 0.8464912 0.7741935
79 0.8453947 0.7741935
82 0.8453947 0.7741935

0.8464912정도로 가장 좋은 AUC가 도출되었다. 물론 정확한 판단을 위해서는 검증셋으로 검증해야 된다. 오버피팅이 되었을 수 있기 때문이다.

그럼 좀더 네트웍을 깊이 만들면 성능이 어떻게 변화될지 실험해 보자.

아래와 같이 레이어를 추가한다.

x <- tf$placeholder(dtype = tf$float32, shape = list(NULL, num_x))
y <- tf$placeholder(dtype = tf$float32, shape = list(NULL, num_y))



with(tf$name_scope("DNN"),{ 
    fc1 <- tf$contrib$layers$fully_connected(x,10L,
              activation_fn=tf$nn$relu, weights_initializer=tf$contrib$layers$xavier_initializer(uniform = F))
    fc2 <- tf$contrib$layers$fully_connected(fc1,10L,
              activation_fn=tf$nn$relu, weights_initializer=tf$contrib$layers$xavier_initializer(uniform = F))
    pred <- tf$contrib$layers$fully_connected(fc2, num_y, 
              weights_initializer=tf$contrib$layers$xavier_initializer(uniform = F))
     })



#노드 1개 출력이니 binary cross entropy로 계산한다.
#AdaGrad와 Momentum을 융합한것과 같은 효과를 보이는 AdamOptimizer를 사용한다. 
#GPU가 있다면 아래 CPU를 GPU로 바꿔주면 된다. 
with({tf$name_scope("loss");tf$device('/cpu:0')},{
  loss <- tf$reduce_mean(tf$nn$sigmoid_cross_entropy_with_logits(pred, y))
  optimizer <- tf$train$AdamOptimizer(learning_rate=0.01)$minimize(loss)
  })

#Accuracy계산 
#ROC계산은 Tensorflow에서 다소 불편하니 R에서 별도로 계산할 것이다. 
with(tf$name_scope("metric"), {
  compare_pred <- tf$cast(tf$sigmoid(pred) > 0.5, tf$float32)
  accuracy <- tf$reduce_mean(tf$cast(tf$equal(compare_pred, y),tf$float32))
  })



# training 
num_of_epoc <- 100L


sess <- tf$Session()

sess$run(tf$global_variables_initializer())

n_batch <- 5

aucs <- list()
for(i in 1:num_of_epoc){
  #print(sprintf("epoc %d", i))

  folds <- createFolds(train.y, k = n_batch)
  for(j in 1:n_batch){
    sess$run(optimizer, dict(x=train.x[folds[[j]],], y=matrix(train.y[folds[[j]]])))
  }

  accu <- sess$run(tf$sigmoid(pred), dict(x=test.x))
  accur <- sess$run(accuracy, dict(x=test.x, y=matrix(test.y)))

  aucs[[i]]<- data.table(epoc=i,aucs=as.numeric(auc(roc(test.y, accu[,1]))),  accuracy=accur)
}

dt_aucs_2nd <- rbindlist(aucs)

knitr::kable(dt_aucs_2nd[order(-aucs)][1:5])
epoc aucs accuracy
89 0.8750000 0.8225806
87 0.8640351 0.7903226
84 0.8607456 0.8225806
86 0.8607456 0.7903226
82 0.8541667 0.8064516

위에서 보이는바와 같이 레이어를 추가해주면서 성능이 다소 상승한 모습을 볼 수 있다. 물론 문제 자체가 복잡하지 않아서 딥러닝으로 더 이상의 성능향상이 가능할지는 모르겠지만 간단한 예제로서는 손색이 없다고 본다. (참고로 여기서 Sonar데이터의 알고리즘별 성능을 볼 수 있으니 비교를 해보는것도 좋을 것이다.)

정리를 해보면 아래와 같다.

  1. R에서도 무리없이 tensorFlow를 사용할 수 있다. 100%
  2. DNN에서 레이어를 추가하면 예측성능이 좋이질 수 있다.

필자의 코드에서 보는것과 같이 caret,pROC 등등의 R 패키지들과 결합해서 쓰면 매우 편리하게 TensorFolw를 사용할 수 있는데, 이는 TensorFlow와 R간의 데이터 변환이 매우 원활하기 때문이라 생각한다. 필자의 경우 심지어 R의 animation 패키지로 모형이 학습되는 과정을 동영상으로 만들어 보기도 한다.

딥러닝은 “복잡한 문제 + 많은 양의 데이터"의 조건에 부합하는 영역이면 다른 어떠한 모형보다 성능이 잘 나올 수 있다고 생각한다. 하지만 그 궁극의 모형에 가기 위해서는 다소 많은 양의 시행착오가 필요하다. 개인적인 경험으로는 입력 포맷과 아키텍처를 잡는데 시간을 많이 쓰는 편이고 효과도 좋았다. 그 이외의 부분들은 도메인과 모델링 목적 그리고 학습 데이터의 차원 그리고 양에 따라 매우 다른 파라메터를 요구하는 편이다. 결국 속성을 자동으로 찾아주는 장점 대비 딥러닝 아키텍처를 시행착오를 통해 찾아야 되는 또 다른 부담이 생기는 것이다. 하지만 다른 어떠한 모형보다 잘 나올 수 있는 잠재력이 있는 방법론이기 때문에 이를 잘 다룰 수 있는 경험과 노력이 필요하다고 생각한다.

개인 분석으로는 윈도우 머신에서 docker 설정을 통해 tensorFlow + RStudio 기반으로 사용중인데, 현재 업데이트를 통해 도커 없이 사용할 수 있으며, 테스트 결과 Python3.5 + Rtools + RStudio 조합으로 가능했다. 회사에서는 GPU를 사용해 원활하게 사용중이다. Deep Learning 용도만이 아니라 수치 연산의 중요 플랫폼으로 tensorFlow가 큰 가지를 지니고 있음을 실무에서 사용해보고 연구해 보면서 느낄 수 있었으며, 중요한 분석 도구로 항상 옆에 둬야 겠다는 생각도 들었다.

CC BY-NC 4.0 TensorFlow with R by from __future__ import dream is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.