• Home

R TensorFlow 코드 깃헙 공개

딥러닝을 공부하는 가장 좋은 방법은 몇몇 대표적인 모형을 직접 코드로 작성해보고 모델링을 해보는 것이다. 최근에 많은 책들이 책을 출간하기 전에 코드를 깃헙에 공개하고 있는데, 이들 책 중에서 하나를 골라서 R로 코드를 구현하는 작업을 틈틈이 해왔다. 이 작업이 다소 번거로운건 Python코드와 R코드를 모두 잘 이해하고 구현해야 된다는 것이다. 다행히 Python에 대한 구현 경험이 있어서 큰 문제가 되지는 않았으나, 배열 인덱스 차이와 매트릭스 형태에 대한 기본 사고 방식이 달라서 다소 어려움이 있었다. 그러나 이런 코드 구현이 재밋는 점은 하나의 코드를 완벽히 이해하고 구현을 해야 정확히 동작한다는 것이여서 꽤 많은 공부가 되고 있다.

이 작업을 한 두번째 이유는 R기반 텐서플로 모델링이 Python이 하는 그것만큼 가능할 것인가?라는 질문에 스스로 답을 얻고 싶어서였다. 이 부분에 대한 개인적인 해답은 YES이다. Python이 하는 것 모두 가능하다! 게다가 R의 모델링 패키지와 데이터 전처리 패키지들의 잇점을 그대로 텐서플로에 활용할 수 있다. 물론 코드 구현시 최근에 나온 reticulate패키지를 활용하면 전처리 코드까지 쉽게 구현이 가능한데, 필자가 구현할때는 CNN예제에서 이미지 형태가 Python Pickle 형태로 제공되는 데이터를 핸들링 하는것 이외에는 사용하지 않았다. reticulate를 사용하는건 텐서플로 R way를 찾는걸 포기하는것과 같다고 생각했다.

텐서플로를 R로 모델링 한다는건 몇가지 장점과 더불어 단점도 존재한다.

결국 텐서플로 코드는 R이나 Python이나 큰 차이가 없다. 거의 99% 같다고 보면 된다. 그렇다면 차이가 나는 부분은 결국 데이터 전처리나 결과 시각화 부분 뿐이다. 데이터 전처리 부분은 이미지를 핸들링 한다면 3차원 이상의 array를 핸들링하는 연습이 충분히 되어 있어야 되는데, 신기하게도 이 부분에 대한 자료가 R에서는 그렇게 많지않아서 필자도 애로사항이 많다. 그래서 3차원 이상의 데이터를 핸들링 하는 코드가 Python보다 길어지는 경우가 많아 필자 역시 이 부분에 대한 경험이 부족함을 통감하고 있는 상황이다. 그러나 만일 2차원 테이블 형태의 데이터를 기반으로 텐서플로를 한다면 전처리 부분이 Python에 대비해 많은 잇점을 가져갈 수 있는데, data.table, caret과 같은 패키지를 적절히 활용하면 좋은 퍼포먼스로 다양한 데이터 전처리/피딩 방식을 적용하면서 모델링의 묘미를 만끽할 수 있다.

필자가 R 코드를 구현하고 있는 책은 Machine Learning with TensorFlow이며, 코드가 상당히 잘 정리되어 있고 책 자체도 아주 적절한 수준으로 코드 정리가 잘 되어있어 텐서플로를 처음 시작하는 사람들에게 좋은 시작점을 제공하고 있다. 아쉽지만 책은 아직 출간전이다.

책에 나온 전체 코드를 구현하는것 뿐만 아니라 개인적인 모델링 관련 코드도 올려 두었는데, 예를 들어 CNN 학습된 모형에 대한 시각화 코드가 그것이다. 이런 방식으로 다른 모형들에 대한 정리도 비슷한 방식으로 정리할 예정이다. 구현상 텐서플로 코드는 일반적인 나열 형태의 R코드로 구현하게 되면 나중에 텐서객체들이 꼬여서 큰 문제가 생길 수 있어 Python과 같이 클래스 형태로 구현하는게 가장 좋다. R에서는 많은 객체지향 코딩 방식이 존재하는데, 가장 기존 언어와 유사한 Reference Class로 구현했다. AutoEncoder 클래스가 대표적인 예이다.

필자가 코드 정리중인 github의 README를 붙이며 글을 마친다.


Machine Learning with TensorFlow(R version)

This is the unofficial code repository for Machine Learning with TensorFlow with R.

This repository is for practicing R tensorflow modeling exercises. I'm personally writing this code for me to know that there are some areas where you can get the benefits of R, and that the code in the book may contain partially improved or experimented code. (example: CNN model view )

TODO

  • make full book example code with R.
  • make use of R Reference Class for code reusablilty.
  • adding GAN code.

Requirement

Summary

Chapter 2 – TensorFlow Basics

Chapter 3 – Regression

Chapter 4 – Classification

Chapter 5 – Clustering (working)

  • Concept 1: Clustering
  • Concept 2: Segmentation
  • Concept 3: Self-organizing map

Chapter 6 – Hidden markov models

Chapter 7 – Autoencoders

Chapter 8 – Reinforcement learning (working)

  • Concept 1: Reinforcement learning

Chapter 9 – Convolutional Neural Networks

Chapter 10 – Recurrent Neural Network(working)

  • Concept 1: Loading timeseries data
  • Concept 2: Recurrent neural networks
  • Concept 3: Applying RNN to real-world data for timeseries prediction

딥러닝 머신 그리고 TensorFlow R word2vec 코드 구현/모델링

TensorFlow Life

최근 TensorFlow를 팀에서 주로 사용하면서 이런저런 내부 프로젝트를 진행하고 있고, 과거에 보지 못했던 성과도 볼 수 있었으며, 이런 도구 사용과 경험을 통해 무엇보다 1년 전과는 문제를 바라보는 관점이 달라졌다는 것을 깊히 실감할 수 있었다. 이 때문에 거의 매일매일 새로운 경험을 하는 셈인데 그러면서 알고리즘에 대한 더 깊은 이해를 하게 되는거 같다.

과거 모든 속성에 대해서 뭔가의 가정을 기반으로 데이터를 준비해서 모델링을 했다면, 딥러닝의 경우 속성에 대해서는 머신에게 최대한 자유를 주고 더 많은 데이터와 복잡한 아키텍처와 많은 학습을 기반으로 기존 모형보다 더 좋은 성능을 꾀하는 것에 방점을 두고 있다. 필자도 과거 오해를 하고 딥러닝을 바라봤으나 결국 어떠한 문제이든 많은 데이터가 준비되어 있으면 기존 모델링 방법보다 좋은 성능을 보장한다는걸 경험적으로 알게 되었다. 그러나 이런 결과를 보기 위해서는 도구에 대한 확실한 이해와 알고리즘에 대한 이해를 담보하고 있다. 만일 그렇지 않고 사용하게 되면 큰 성능은 커녕 실망만 하게 된다.

성능 이외에 느낄 수 있는 딥러닝의 좋은 장점은 역시나 온라인 러닝에 있다고 생각한다. 이 부분은 학술 연구를 하는 분들에게는 중요하지 않을 수 있으나, 나와 같이 실무 모델링을 하는 사람에게는 매우 큰 장점으로 다가오고 모델 운영의 묘미를 느끼게 하는 중요한 부분이다. 어떻게 모형을 Fresh하게 유지할 수 있느냐의 문제는 딥러닝에서는 다소 어렵지 않은 문제이다.

매우 안타까운 이야기지만, TensorFlow로 이런 저런 실전 모델링 및 실전 차원 축소를 해본 결과 텐서플로와 같은 딥러닝 프레임웍은 개발자(여기서 개발자라 칭함은 개발 경험이 충분히 있는 부류를 의미한다.)들이 더 유리한 접근을 할 수 있다라는 생각에 다시한번 확신이 들게 된다. 일단 대용량,고차원 데이터 핸들링 경험이 없으면 실전에서 딥러닝 모델링을 할 수 있는 문턱에도 갈 수 없으며, 이를 넘었다 하더라도 API 레벨을 원활히 검증하면서 흡사 개발과 같은 과정을 거쳐야 제대로 돌려볼 수 있다. 다소 머신러닝 알고리즘에 대해서 패키지 레벨로만 접근해서 돌려봤던 분석가 및 데이터 과학자들은 매우 어려운 도구이나, 직접 머신러닝을 구현해본 개발자들에게 확실히 유리한 도구다라는 생각을 해본다.

물론 keras같은 하이레벨 wrapper를 쓰면 어느정도 쉽게 접근이 가능하나, 실전문제에서는 그보다 더 큰 데이터와 차원을 다뤄야 되어서 결국 로 레벨 API를 써야 된다. 이때 개발자적인 경험/능력이 매우 큰 도움이 된다.

하지만 개발자들이 항상 유리한건 아니다. 뭔가 모델이 잘 못 돌고 있을때 개발자들은 데이터 자체를 리뷰하거나 중간 결과에 대한 EDA를 잘 하지 않는 경향이 있고, 그냥 바로 API 수정이나 하이퍼파라메터를 추가/제거 하고 돌리는 것을 반복하곤 한다. 데이터의 특징을 파악하고 입력이든 중간 데이터 변환이든 목적에 맞는 통계적 데이터 변환 방법은 무수히 많은데, 이를 잘 할 수 있는 사람들은 전통적인 통계 기반의 사람들이다. 하지만 통계기반의 사람들이 그러한 문제점을 파악하고 조언하는 역할만 할 것인가이다. 현실적으로 조언만 해서는 자신이 한 성과라 할 수 없다. 결국 상호의 영역에 대한 공부를 하고 경험을 쌓아야 풀스텍 딥러닝 모델러가 될 수 있는것이다.

딥러닝 같은거 쓰지 않고서라도 데이터 과학자 역할을 할 수 있으나, 일반적인 모델링 패키지를 사용하면 성능의 편차가 개인마다 크지 않아 고수든 하수든 별다른 경험적 차별점을 도모하기 쉽지 않으나, 딥러닝의 경우 프레임웍에 익숙해지고 경험이 많을수록 성능을 짧은 시간에 극한으로 끌어올릴 수 있는 가능성이 매우 커진다. 물론 큰 데이터를 손쉽게 핸들링하고 이를 적합한 딥러닝 구조에 녹여 돌릴 수 있는 경험적인 부분과 이론적인 부분에 대한 밸런스를 반드시 이뤄야 된다.

무엇보다, 누구에게 어렵거나 쉬운것에 상관없이, 누구든 데이터로 밥먹고 산다면 반드시 한번정도는 충분히 경험해보고 넘어가야 되는 방법론이고 프레임웍이다라는 생각은 하면 할수록 확신이 든다.

딥러닝 머신 구축

작년에 개인적인 프로젝트를 수행하면서 개인 리서치에 쓸 수 있는 자금이 생겼는데, 이 자금으로 개인 딥러닝 머신을 조립했다. 뭔가 올해 들어서 딥러닝에 대한 경험과 학습에 대한 의지의 표현이라 보면 된다. ㅋ

  • 보드 : ASUS PRIME Z270-A
  • 메모리 : SD4 16GB x 4
  • CPU : I7-7700K
  • SSD : 삼성 500GB
  • HDD : WD 2TB
  • 파워 : 정격 850W
  • GPU : GTX 1080 x 2
  • 모니터 : LG 27인치 IPS

사실 GTX 1080 2개까지 설치할 생각은 없었는데, 용산 조립 대행 업체에 방문한날 그 자리에서 성급하게 한장 더 넣어달라고 했다. 당시 가격으로 80만원 정도가 더 나갔는데, 후회는 없다. ㅋ

'딥러닝 머신'

신형 보드라 우분투를 설치하는데 애를 좀 먹었지만 현재 아주 환경이 잘 설정되었다. 두개의 GPU를 엮어 사용하는 SLI의 경우 현재 CUDA에서 이 부분에 대한 지원이 없어서 활용은 못하고 있지만 큰 가중치 매트릭스 몇개를 두개의 GPU에 적절히 나눠 계산할 수 있어서 핸들링 할 수 있는 문제 크기는 1개일 때보다 커졌다(예를 들어 autoencoder encoder를 1번 GPU에 올리고 decoder를 2번 GPU에 올려서 사용할 수 있을 것이다). 물론 게임할때는 1.5배 이상의 퍼포먼스를 가져온다. ㅎㅎ

딥러닝 학습

일반적인 정형 데이터를 학습하기 위해 딥러닝을 사용하는건 비정형 데이터를 사용해 모델링 하는것 보다 훨씬 쉬운 문제라고 생각한다. 그럼에도 불구하고 정형 기반 모델링을 하더라도 데이터를 어떻게 모형에 입력하는지에 따라 매우 큰 퍼포먼스 차이를 불러온다. 그리고 텐서플로는 이에 대한 다양한 수단을 제공하고 있어서 그것들 사이에 어떻게 최적의 방법을 찾는지가 빠른 모델링을 하기 위한 큰 장애물이다. 따라서 아래에서 제공하는 코드는 필자가 현재 테스트를 수행중인 개인 프로젝트이며 최적의 솔루션은 아니라는 점을 밝힌다.

개인적인 생각은 주어진 데이터를 기반으로 모델링 학습 코드를 수행하는건 딥러닝을 이해하는데 충분한 도구가 되지 않는다고 생각한다. 왜냐면 입력 데이터 포맷이나 형태와 그에 적합한 아키텍처가 절묘하게 결합될때 원하는 성능이 나오기 때문이다. 따라서 이들에 대한 테스트와 실패를 통해서 감을 찾아가는게 딥러닝 실무를 하는데 매우 중요한 부분이라 생각한다. 따라서 예제만 돌려보기 보다는 직접 문제를 적용하고 모델링하는 시행착오가 꼭 필요한 영역이다.

Word2Vec with R

아래부터 설명할 내용은 Word2Vector를 R로 구현한 구현체이다. 물론 핵심이 되는 부분은 tensorflow 패키지이며, TensorFlow v1.0을 기준으로 작성되었다.

멀티코어 프로세싱 명사 추출 – 데이터 전처리

대상이 되는 데이터는 나무위키 데이터이며, https://github.com/hyeon0145/namu-wiki-extractor를 이용해 json파일을 문장 단위의 텍스트 파일로 만들었다. 결국 이후에 할 작업은 문장에서 명사만을 추출해 명사 주변의 단어를 기반으로 학습 데이터를 만드는 것이다.

아래 코드는 문장 단위의 데이터를 기반으로 KoNLP를 활용해 명사를 추출하는 작업이다. 나무위키에서 추출된 문장단위 데이터의 크기는 3GB정도 되며 필자의 경우 편의를 위해 이를 namus.RData파일로 저장해 두었다.

#문장 단위로 추출된 문장 벡터(3GB) 
load('namus.RData')

library(parallel)
no_cores <- detectCores() - 1

cl <- makeCluster(no_cores)

pids <- clusterApply(cl, seq(along=cl), function(id) WORKER.ID <<- id)

pidds     <- unlist(pids)
timesleep <- seq(0, 40, length.out = 7)

cnt <- 0

clusterExport(cl, c('pidds', 'timesleep', 'cnt'))
clusterEvalQ(cl, {print(WORKER.ID)})

clusterEvalQ(cl, {
  sink(sprintf("%d.log", WORKER.ID))
  print(sprintf('workder %d', WORKER.ID))
  flush.console()
               })

## Initiate cluster
clusterEvalQ(cl, {
  library(KoNLP)
  Sys.sleep(timesleep[which(WORKER.ID %in% pidds)])
  buildDictionary(ext_dic = c('woorimalsam'))
})

#테스트
clusterEvalQ(cl, {
  extractNoun('롯데마트는 좋은 음식을 판매하기 위해 노력하고 있다.')
  })

system.time({
  namu_nouns <- clusterApply(cl, namus,function(sent){
    cnt <<- cnt + 1
    if(cnt %% 5000 == 0){
      print(sprintf('%s , worker id : %s, cnt : %d', date(), WORKER.ID, cnt))
      flush.console()
    }
    extractNoun(sent)
    })
})

clusterEvalQ(cl, {sink()})


stopCluster(cl)

위 코드는 KoNLP의 명사추출 함수를 멀티코어로 수행하기 위한 코드이다. 코드를 보면 알겠지만 사전 적용 시간을 워커마다 다르게 줬다는 걸 알 수 있는데, 이는 멀티코어 용으로 개발되지 않은 KoNLP의 오작동을 방지하기 위한 방어로직 부분이다. 나머지 부분들은 일반적인 명사 추출 로직이다. 3GB 텍스트에서 명사를 모두 추출하는데 필자의 머신으로 약 3시간 정도 소요되었다(필자 머신의 코어수는 8개다).

학습셋 만들기 – 데이터 전처리

각 단어마다 인덱스를 부여하고 이 인덱스 숫자를 기반으로 학습셋을 구축하는 코드이다. skip range는 3으로 두고 작업했다.

library(hashmap)

build_dataset <- function(words){
  uniq_words <- unique(unlist(words))
  uniq_words <- Filter(function(x){nchar(x) <= 10 & nchar(x) >= 1}, uniq_words)
  voca <- hashmap(keys=uniq_words, values=seq(0, length(uniq_words) -1))
  voca_rev <- hashmap(keys=seq(0, length(uniq_words) - 1), values=uniq_words)
  return(list(voca, voca_rev))
}
#이전 코드에서 만들어둔 문장별 명사 리스트 
load('namu_wiki_nouns.RData')

voca<- build_dataset(namu_nouns)
voca_dic <- voca[[1]]
voca_revdic <- voca[[2]]
voca_dic_raw <- voca_dic$data()

#일단 저장 
save(voca_dic_raw, file='voca_dic_raw.RData')



#voca$keys()[1:100]
library(data.table)
library(parallel)

no_cores <- detectCores()  + 20


cl <- makeCluster(no_cores,type = 'FORK')


pids <- clusterApply(cl, seq(along=cl), function(id) WORKER.ID <<- id)

cnt <- 0

#문장 처리수 로깅을 위함 
clusterExport(cl, c('cnt'))

clusterEvalQ(cl, {
  head(voca_dic$data())
  print(cnt)
  })


clusterEvalQ(cl, {
  sink(sprintf("%d_cooccr.log", WORKER.ID))
  print(sprintf('workder %d', WORKER.ID))
  flush.console()
})


## 실제 학습 데이터를 생성하는 단계 
## 최종적으로 word, label 컬럼을 가지고 있는 큰 data.table이 생성된다. 
system.time({
  word_labels <- clusterApply(cl, namu_nouns, function(sent, n=3){
    cnt <<- cnt + 1
    if(cnt %% 5000 == 0){
      print(sprintf('%s , worker id : %s, cnt : %d', date(), WORKER.ID, cnt))
      flush.console()
    }
    labels.dt <- list()
    sent <- Filter(function(x){nchar(x) <= 10 & nchar(x) >= 1}, sent)
    i <- 1
    for(w_idx in 1:length(sent)){
      w <- voca_dic[[sent[w_idx]]]
      pr <- n
      if(w_idx + pr > length(sent)){
        pr <- length(sent) - w_idx
      }
      if(pr >= 1){
        sibr <- voca_dic[[sent[(w_idx + 1):(w_idx+pr)]]]
        labels.dt[[i]] <- data.table(word=w,label=sibr)
        i <- i + 1
      }
      pl <- n
      if(w_idx - pl <= 0){
        pl <- w_idx - 1
      }

      if(pl >= 1){
        sibl <- voca_dic[[sent[(w_idx-pl):(w_idx - 1)]]]
        labels.dt[[i]] <- data.table(word=w,label=sibl)
        i <- i + 1
      }
    }
    rbindlist(labels.dt)
  })
})

clusterEvalQ(cl, {sink()})

stopCluster(cl)

word_labels_dt <- rbindlist(word_labels)

word2vector TensorFlow 학습

아래 코드는 TensorFlow R기반 코드이며, 시중에 있는 코드와 크게 다를 부분은 없으니, 관련된 부가적 설명은 인터넷 자료를 참고하기 바란다.

학습중 GPU 사용량은 대략적으로 아래와 같다. 첫번째 GPU는 모형 평가쪽 코드가 구동되며, 두번째 GPU에서는 전반적인 학습이 수행되게 했다.

''

library(tensorflow)
library(hashmap)
library(data.table)

load("voca_dic_raw.RData")

voca_dic <- hashmap(names(voca_dic_raw), values = as.integer(voca_dic_raw))
voca_revdic <- hashmap( as.integer(voca_dic_raw), values=names(voca_dic_raw))


load("word_labels_dt.RData")


### tensorflow code 
vocabulary_size <- as.integer(voca_dic$size())

batch_size <- 500L
num_batch <- nrow(word_labels_dt)/batch_size

embedding_size = 300L 
valid_examples <- as.integer(c(voca_dic[[c('한국', '사랑', '사과')]]))
valid_size = length(valid_examples) ## Random set of words to evaluate similarity on.
num_sampled = 250L ## Number of negative examples to sample.


tf$reset_default_graph()
w2v_graph <- tf$Graph()

with(w2v_graph$as_default(),{
  with(tf$device('/gpu:1'), {
    ## Input data.
    train_dataset <- tf$placeholder(tf$int32, shape=c(NULL))
    train_labels <- tf$placeholder(tf$int32, shape=c(NULL))
    valid_dataset <- tf$constant(valid_examples, dtype=tf$int32)

    ## Variables.
    embeddings <- tf$get_variable(name='embedding',shape = c(vocabulary_size, embedding_size), 
                    initializer=tf$contrib$layers$xavier_initializer())
    softmax_weights <- tf$Variable(
      tf$truncated_normal(c(vocabulary_size, embedding_size),
                          stddev=1.0 / sqrt(embedding_size)))
    softmax_biases <- tf$Variable(tf$zeros(c(vocabulary_size)))

    ## Model.
    embed <- tf$nn$embedding_lookup(embeddings, train_dataset)
    ## Compute the softmax loss, using a sample of the negative labels each time.
    loss <- tf$reduce_mean(
      tf$nn$nce_loss(weights=softmax_weights, biases=softmax_biases, inputs=embed,
                     labels=tf$to_float(train_labels), num_sampled=num_sampled, num_classes=vocabulary_size))

    optimizer <- tf$train$AdagradOptimizer(0.8)$minimize(loss)
  })
  with(tf$device('/gpu:0'), {
    norm <- tf$sqrt(tf$reduce_sum(tf$square(embeddings), 1L, keep_dims=TRUE))
    normalized_embeddings <- embeddings / norm
    valid_embeddings <- tf$nn$embedding_lookup(
      normalized_embeddings, valid_dataset)
    similarity <- tf$matmul(valid_embeddings, tf$transpose(normalized_embeddings))
  })
  saver <- tf$train$Saver(max_to_keep=0L)
})


num_epoc <- 1000
config_proto <- tf$ConfigProto(allow_soft_placement=T,log_device_placement=T) 



with(tf$Session(graph=w2v_graph, config=config_proto) %as% sess, {
  tf$global_variables_initializer()$run()
  print('Initialized')
  average_loss <- 0

  for(epoc in 1:num_epoc){
    trainingIndices <- sample(1:num_batch, nrow(word_labels_dt), replace = TRUE)  
    word_labels_dt[,idx:=trainingIndices]
    split_word_labels_dt <- split(word_labels_dt,by='idx')
    print('starting batch!')
    bat_cnt <- 0 
    for(i in 1:num_batch){
      batch_x <- split_word_labels_dt[[i]][,word]
      batch_y <- as.matrix(split_word_labels_dt[[i]][,label])
      bat_cnt <- bat_cnt + 1
      if(bat_cnt %% 1000 == 0)
        print(sprintf("batch progress %f", bat_cnt/num_batch))
      loss_eval <- sess$run(list(optimizer, loss), feed_dict=dict(train_dataset=batch_x, train_labels=batch_y))

      average_loss <- average_loss + loss_eval[[2]]
    }
    save_path <- saver$save(sess=sess, save_path = sprintf("tf_model/model_%s.chkp", epoc), global_step =1L)

    average_loss <- average_loss/batch_size
    cat("\n\n")
    print(sprintf('Average loss at epoc %d: %f' , epoc, average_loss))
    average_loss <- 0

    sim <- similarity$eval()
    for(j in 1:valid_size){
      valid_word <- voca_revdic[[valid_examples[j]]]
      top_k <- 8 ## number of nearest neighbors
      nearest <- voca_revdic[[order(-sim[j,])[1:top_k]]]
      print(sprintf("%s nearest words %s/n", valid_word, paste0(nearest,collapse = ",")))
    }
  }
})

아래는 1 epoc 수행한 결과이다. 1 epoc 그러니까 셋의 전체(약 4천만건)를 한번 학습하는데 약 2분이 소요된 것을 알 수 있다. 필자가 이를 CPU에서 수행했을때는 이보다 6배 정도 시간이 더 소요 되었는데, 그나마 이정도 차이는 TensorFlow 컴파일 최적화의 덕택이라 판단된다(필자의 경우 직접 TensorFlow를 컴파일해 CPU최적화가 되어 있는 상황이었다. 이 때문에 Blas(행렬연산라이브러리) 최적화도 되었을 것이다.).

위 이미지에 따르면 쓸만한 정도의 word2vector 그래프를 얻기위해 갈길이 좀 멀것이라는 예상이 된다. 이런 저런 전처리와 수많은 재학습이 예상되는데 이에 적합한 머신은 많은 시간과 노력을 줄여줄 거라는건 자명한거 같다.


필자가 딥러닝 그리고 텐서플로를 R 기반으로 사용해본 결과 텐서플로를 위해서 Python을 해야될 필요를 전혀 느끼지 못했다. 그 정도로 패키지가 잘 구현되어 있다는 건데, 덕분에 팀 프로젝트에서 이를 기반으로 활발하게 모델링을 하고 있다.
RStudio github에 들어가 보면 거의 rmarkdown 정도의 별수를 기록하는 있는 것으로 보아 RStudio에서 내부적으로도 관심있게 개발하고 있는 패키지일 거라 예상한다. 이 패키지 덕분에 R의 데이터 전처리, 시각화 쪽의 간결함과 파워를 그대로 텐서플로에 활용할 수 있어서 너무 편리하다.

R, Python을 떠나서 텐서플로를 위시한 딥러닝 기술은 데이터 사이언스에 몸담고 있는 사람이라면 반드시 경험해야 될 필수 코스가 되었다고 생각한다. 그러니 시간과 장비 투자에 인색하지 말길 바란다.

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가 큰 가지를 지니고 있음을 실무에서 사용해보고 연구해 보면서 느낄 수 있었으며, 중요한 분석 도구로 항상 옆에 둬야 겠다는 생각도 들었다.