RNN을 이용한 한글 자동 띄어쓰기

필자가 한글자동띄어쓰기를 처음 R로 구현한 결과에 대한 링크를 참고하면 한글 자동띄어쓰기가 어떠한 방식으로 구현되는지 기초적인 부분을 알 수 있을 것이다. 개인적으로 한글 텍스트 분석에서 띄어쓰기는 생각보다 중요한 부분을 차지하고 있다고 생각한다. 이 부분이 잘 되지 않는다면 이후의 다양한 한글분석 퀄리티에 큰 영향을 끼질 수 있기 때문이다.

KoNLP 역시 한글 자동 띄어쓰기 모듈이 있기는하나, 매우 조악한 수준이며 이를 위해 RNN을 이용한 한글 띄어쓰기 모델링을 진행하고 있다. 지금부터 소개할 모듈은 RNN을 이용한 한글 자동띄어쓰기의 일부분을 소개할 것인데, 개인적으로 추가 연구한 부분의 코드는 연구가 진행됨에 따라 이곳에 공개할 예정이다(학습데이터와 코드를 함께 정리해 공개할 예정이다).

이 작업은 크게 두 부분으로 나뉘어져 있다.

  1. 음절단위 속성 벡터 추출
  2. bidirectional LSTM과 Linear CRF를 이용한 모형 학습

일단 필요한 패키지를 로딩한다. hashmap은 문자열에 대한 인덱스를 조회할때 사용되고, caret은 학습셋 셔플링시, 그리고 stringi,stringr의 경우 문자열 전처리에 쓰일 예정이다.

library(tensorflow)
library(hashmap)
library(wordVectors)
library(caret)
library(stringr)
library(stringi)
library(data.table)
library(reshape2)

#문자열 전체 빈도로 분포로 볼때 7회 미만은 빈도 1분위 미만이며, 이들에 대해서는 아래 문자로 대체한다. 
specl_char <- '⊙'

음절단위 속성 벡터 추출

코드가 다소 복잡한데, 사실 과정은 그리 복잡하지는 않다. 코퍼스에서 아래와 같은 문장을 문자열과 공백으로 쪼개는 과정을 수행하고 wordVectors패키지를 이용해 char vector형태의 속성 매트릭스를 만들면 된다. R에서도 Python과 같이 매우 간단한 명령어로 word2vector를 만들 수 있는 패키지가 있는 관계로 굳이 복잡한 코드를 구현하지 않아도 된다.

train_word2vec함수에서는 공백으로 구분되는 문자열을 하나의 학습셋으로 받아들이는 구조를 가지고 있기 때문에 아래와 같이 character레벨의 벡터를 생성하기 위해 공백으로 문장을 구분해 파일을 생성한 뒤 이를 기반으로 학습을 시키도록 한다.

아버지가 방을 나가셨다. -> 아 버 지 가 방 을 나 가 셨 다.

corpus <- readLines(bzfile("input.txt.bz2"))

corpus_cl <- stri_replace_all_regex(stri_trans_nfkc(corpus), pattern = '[:blank:]', replacement = '')

corpus_cl_split <- lapply(corpus_cl, function(x){
   paste(str_split(x, pattern = '')[[1]], collapse = ' ')
})

tbl_a <- table(unlist(corpus_cl_split))
summary(as.numeric(tbl_a))

#빈도 분포 확인 후 7회 이하 빈도는 다른 문자로 대체 
length(tbl_a[tbl_a < 7])
pat <- paste0('[', paste0(Filter(function(x) {nchar(x) == 1}, names(tbl_a[tbl_a < 7])),  collapse = ''), ']')


replaced_sent <- lapply(corpus_cl_split, function(x){str_replace(x, pattern = pat, replacement = specl_char)})


writeLines(unlist(replaced_sent), 'sejong_char_seq.txt')

w2v_model <- train_word2vec("sejong_char_seq.txt","sejong_char_seq.bin",
                       vectors=50,threads=8,window=10,iter=20,negative_samples=10, force=T)

w2v_model %>% closest_to('컴')

결국 위의 w2v_model(고유문자수 x 50)의 형태를 가지는 가중치 매트릭스를 가지게 된다. 물론 이 부분은 word2vector 알고리즘을 그대로 사용하기도 하지만 목적에 맞는 vector를 생성하는 네트워크를 직접 구축해 볼 수도 있을 것이다.

Korean Word Spacing with RNN

띄어쓰기는 대표적인 sequence-to-sequence 문제이며, “예제문장입니다.”이 입력되었을 때 “01000001”와 같은 띄어쓰기 시퀀스를 출력하는 문제로 정의할 수 있다. 여기서 0은 다음 문자를 붙여쓴다는 의미이고 1은 띄어쓴다는 의미이다.

여러 네트웍 구축 및 테스트해본 결과 필자는 Bidirectional LSTM-CRF Models 모형을 기반으로 학습을 시켰다. 학습 데이터는 세종 코퍼스에서 추출한 약 8만 문장을 활용하였다.

마지막 loss를 계산할때 개별 문자열의 softmax output정보만을 활용하기 보다는 CRF를 이용해서 이전 시퀀스의 결과에 따른 영향을 고려할 수 있도록 했는데, 이러한 이유는 띄어쓰기가 연속으로 일어날 확률은 실제 매우 적기 때문이고 LSTM만으로는 이러한 부분을 케어하지 못하기 때문이다. 이 부분은 기존의 띄어쓰기 알고리즘이 HMM이나 CRF모형을 많이 사용하는 이유와 매우 유사한데, 직접 구현할 필요 없이 TensorFlow에 포함된 함수를 기반으로 구현했다.

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

#analyze to extract word spacing info.
# makeCorpus("옷을 만드느라 늘")
#$status
# [1] 0 1 0 0 0 1 1
# 
# $char
# [1] "옷" "을" "만" "드" "느" "라" "늘"
# 
# $nchar
# [1] 7
makeCorpus <- function(str){
  strv <- strsplit(str,split="")[[1]]
  lenstrv <- length(strv)
  spacev <- vector(mode="numeric",length=lenstrv)
  charv  <- vector(mode="character",length=lenstrv)
  vidx <- 1
  for(i in 1:lenstrv){
    if(strv[i] == " ") {
      next
    }
    if(i + 1 <= lenstrv && strv[i + 1] == " "){
      spacev[vidx] <- 1
    }else{
      if(i == lenstrv){
        spacev[vidx] <- 1
      }else{
        spacev[vidx] <- 0
      }
    }
    charv[vidx] <- strv[i]
    vidx <- vidx + 1
  }
  charv_f <- Filter(function(x){x!=''},charv)
  status <- spacev[1:length(charv_f)]
  char <- charv_f
  nchar <-length(charv_f)
  return(list(status=status, char=char, nchar=nchar))
}

#read char2vector object 
m <- read.vectors("sejong_char_seq.bin")
#read corpus
sents <-readLines(bzfile('input.txt.bz2'),encoding='UTF-8')


#5어절씩 학습 문장을 만든다. 
sents_eojeol <- str_split(stri_trans_nfkc(sents), pattern = '[:blank:]')
wordchunk <- lapply(sents_eojeol, function(x){
  x <- Filter(function(y){y != ''}, x)
  v <- c()
  k <- 5
  if(length(x) < k) return(paste( c( x, ''),collapse = " "))
  
  for(i in 1:length(x)){
    if((i + k - 1) > length(x)) break
    v <- c(v, paste(c(x[i:(i + k - 1)], ''), collapse = " "))
  }
  return(v)
  })


wordchunk <- unlist(wordchunk)

space_coding  <- lapply(wordchunk, makeCorpus)

#extract each word chunk length
charsents <- lapply(space_coding, function(x){x$char})
uniq_chars <- unique(unlist(charsents))
max_seq_len <- max(unlist(lapply(space_coding, function(x){x$nchar})))

#make sentence coding 
seq_mat_x <- matrix(0, ncol=max_seq_len, nrow=length(wordchunk))

#make hash map to extract row index of m
chmap <- hashmap(rownames(m), 0:(nrow(m)-1))

for(i in 1:length(wordchunk)){
  sent <- space_coding[[i]]$char
  for(j in 1:length(sent)){
    idx <- chmap[[sent[j]]]
    if(is.na(idx)) idx <- chmap[[specl_char]]
    seq_mat_x[i,j] <- idx
  }
}


seq_mat_y <- matrix(0, ncol=max_seq_len, nrow=length(wordchunk))
loss_mask <-matrix(0, ncol=max_seq_len, nrow=length(wordchunk))

for(i in 1:length(wordchunk)){
  sent <- space_coding[[i]]$status
  for(j in 1:length(sent)){
    seq_mat_y[i,j] <- sent[j] 
    loss_mask[i,j] <- 1
  }
}


len_list <-  unlist(lapply(space_coding, function(x){x$nchar}))
sent_chars <- lapply(space_coding, function(x){x$char})
library(R6)

WordSpacing <- R6Class("WordSpacing",
    public = list(
      char_dic_size=NULL, 
      n_neurons=NULL, 
      num_classes=NULL, 
      batch_size=NULL,
      max_sequence_length=NULL, 
      word_spacing_graph=NULL,
      config_proto=NULL, 
      mem_fraction=NULL, 
      x=NULL, y=NULL, 
      sent_len=NULL, loss=NULL, prediction=NULL, optimizer=NULL,
      init=NULL, saver=NULL, global_step=NULL, num_out_classes=NULL,weight_mask=NULL,
      c2v=NULL, embeddings=NULL, seg_loss=NULL, accuracy=NULL,chmap=NULL, 
      transition_params=NULL, logit=NULL,
      
      initialize=function(char_dic_size, n_neurons, num_classes, num_out_classes, max_sequence_length,
                           c2v, mem_fraction=0.999,global_step = 1L){
        self$char_dic_size <- as.integer(char_dic_size)
        self$n_neurons <- as.integer(n_neurons)
        self$num_classes <- as.integer(num_classes)
        self$num_out_classes <- as.integer(num_out_classes)
        self$max_sequence_length <- as.integer(max_sequence_length)
        self$global_step <- as.integer(global_step)
        self$c2v <- c2v
        #self$is_training <- FALSE
        self$chmap <- hashmap(rownames(self$c2v), 0:(nrow(self$c2v)-1))
        
        
        
        gpu_options <- tf$GPUOptions(per_process_gpu_memory_fraction=mem_fraction)
        self$config_proto <- tf$ConfigProto(allow_soft_placement=T,log_device_placement=F, gpu_options=gpu_options)
        
        self$word_spacing_graph <- tf$Graph()
        
        with(self$word_spacing_graph$as_default(), {
          with(tf$name_scope("kor_word_spacing"),{
            with(tf$device("/gpu:0"), {
              
              #(batch  x max_sequence_length)
              self$x <- tf$placeholder(tf$int32, list(NULL, self$max_sequence_length), name='x') 
              # WordVectors로 학습된 char vector (char_dic_size x 100)
              self$embeddings <- tf$Variable(self$c2v, dtype=tf$float32, trainable=FALSE, 
                                             name = 'embeddings')
              # (batch x max_sequence_length)
              self$y <- tf$placeholder(tf$int32, list(NULL, self$max_sequence_length), name='y')  
              # (batch)
              self$sent_len <- tf$placeholder(tf$int32, list(NULL), name='sent_len') 
              #Loss 계산을 위한 masking 생성
              #문장 길이가 서로 다르기 때문임...
              self$weight_mask <- tf$sequence_mask(self$sent_len)
              self$batch_size <-  tf$placeholder(tf$int32, shape = list(), name='batch_size')

            })              
            with(tf$device("/gpu:1"), {

              with(tf$name_scope('rnn_cell'),{
                x_emb <- tf$nn$embedding_lookup(self$embeddings, self$x)
                
                cell <- tf$contrib$rnn$LSTMCell(num_units=self$n_neurons,use_peepholes=T)
                
                outputs_states <- tf$nn$bidirectional_dynamic_rnn(cell, cell, 
                                     x_emb, sequence_length=self$sent_len,dtype=tf$float32)
                
              }) 
              output_fw_output_bw <- outputs_states[[1]]
              #( (max_sequence_length + batch)  x n_neurons * 2)
              outputs <- tf$concat(list(output_fw_output_bw[[1]], output_fw_output_bw[[2]]), axis = -1L)
              
              with(tf$name_scope('fc1'),{
                
                x_fc <- tf$reshape(outputs, list(-1L, self$n_neurons * 2L))
                fc_w <- tf$get_variable("fc_w", list(self$n_neurons * 2L,
                                                     self$num_out_classes),
                                        initializer=tf$contrib$layers$xavier_initializer())
                fc_b <- tf$get_variable("fc_b", list(self$num_out_classes),
                                        initializer=tf$zeros_initializer())
                fc1 <-  tf$matmul(x_fc, fc_w) + fc_b
              })
              
              #l2_losses <- tf$reduce_sum(tf$abs(fc_w)) 
  
              # reshape out for sequence_loss
              self$logit <- tf$reshape(fc1, list(-1L, self$max_sequence_length,
                                              self$num_out_classes))
              
              
              log_likelihood_transition_params <- tf$contrib$crf$crf_log_likelihood(
                  self$logit, self$y, self$sent_len)
              self$loss <- tf$reduce_mean(-log_likelihood_transition_params[[1]])
              self$transition_params <- log_likelihood_transition_params[[2]]
              

              self$optimizer <- tf$train$AdamOptimizer(learning_rate=0.001)$minimize(self$loss, name='optimizer')
                
              # Define a saver op
              self$init <- tf$global_variables_initializer()
              self$saver <- tf$train$Saver(max_to_keep=0L, name='saver')
            })
          })
          
        })
      }, 
      decoding = function(logit, transition_params, nchar){
        
        tags <- tf$contrib$crf$viterbi_decode(
                        logit[1:nchar,], transition_params)
        return(matrix(unlist(tags[[1]]), nrow=1))
      },
      train = function(seq_mat_x, seq_mat_y, sent_len_x, batch_n, epoch=10L, retrain_from=0){
        tr_idx <- 1:(0.95 * nrow(seq_mat_x))
        
        seq_mat_x_train <- seq_mat_x[tr_idx, ]
        seq_mat_x_test <- seq_mat_x[-tr_idx, ]
        
        seq_mat_y_train <- seq_mat_y[tr_idx, ]
        seq_mat_y_test <- seq_mat_y[-tr_idx, ]
        len_list_train <- sent_len_x[tr_idx]
        len_list_test <- sent_len_x[-tr_idx]

        loss_v <- c()
        loss_vt <- c()
        #self$is_training <- TRUE
        

        x <- self$x
        embeddings <- self$embeddings
        y <- self$y
        sent_len <- self$sent_len
        batch_size <- self$batch_size
        
        with(tf$Session(config=self$config_proto, graph=self$word_spacing_graph) %as% sess, {
          if(retrain_from > 0){
            self$saver$restore(sess, sprintf("model/model_%d.chkp-%d",retrain_from, self$global_step))
            st_epoch <- retrain_from + 1
          }else{
            sess$run(self$init)
            st_epoch <- 1
          }
          for(i in st_epoch:epoch){
            #shufle 
            rnd_idx <- sample(1:nrow(seq_mat_x_train), nrow(seq_mat_x_train))
            
            seq_mat_x_ <- seq_mat_x_train[rnd_idx,]
            seq_mat_y_ <- seq_mat_y_train[rnd_idx,]
            sent_len_x_ <- len_list_train[rnd_idx]
            
            j <- 0
            for(k in seq(1, nrow(seq_mat_x_train), batch_n)){
              if( k + batch_n - 1 > nrow(seq_mat_x_train)){
                bat_size <- nrow(seq_mat_x_train)  + 1 - k
              }else{
                bat_size <- batch_n
              }
              self$c2v
              l <- sess$run(list(self$loss, self$optimizer), feed_dict=
                                dict(
                                    x=matrix(seq_mat_x_[k:(k + bat_size - 1),], byrow=T, nrow=bat_size),
                                    y= matrix(seq_mat_y_[k:(k + bat_size - 1),], byrow=T, nrow=bat_size), 
                                    sent_len= sent_len_x_[k:(k + bat_size - 1)],
                                    batch_size=as.integer(bat_size)
                                ))
              j <- j + 1
              if(j %% 300 == 0){
                #self$is_training <- F
                print(sprintf("%d:%d train loss : %f, seg loss : .., accuracy : ..", i, j, l[[1]]))
                loss_v <- c(loss_v, l[[1]])
                test_sent <- "아버지가방에들어가셨다."
                coding_mat <- self$sent_to_code(test_sent)
                logits_transition_params <- sess$run(list(self$logit,self$transition_params),
                                   feed_dict=dict(
                                                  x=coding_mat[[1]],
                                                  sent_len=list(coding_mat[[2]]),
                                                  batch_size=c(1L)))
                result <- self$decoding(logits_transition_params[[1]][1,,], 
                                        logits_transition_params[[2]], nchar(test_sent))
                
                print(self$code_to_sent(test_sent, result, nchar(test_sent)))
                loss_eval <- sess$run(list(self$loss), feed_dict=
                                dict(
                                     x=seq_mat_x_test,
                                     y= seq_mat_y_test, 
                                     sent_len= len_list_test,
                                     batch_size=as.integer(dim(seq_mat_x_test)[1])
                                ))
                print(sprintf("%d:%d test loss : %f, accuracy : ...", i, j, loss_eval[[1]]))
                loss_vt <- c(loss_vt, loss_eval[[1]])
                
              }
            }
           
            save_path <- self$saver$save(sess=sess, save_path = sprintf("model/model_%d.chkp", i),
                                    global_step =self$global_step)
           
            print(sprintf("Model saved in file: %s",  save_path))
          }
          
        })
        return(list(loss_v, loss_vt))
      },
      predict = function(test_sent, best_epoc, glob_step=1L){
        x <- self$x
        embeddings <- self$embeddings
        sent_len <- self$sent_len
        batch_size <- self$batch_size
        coding_mat <- self$sent_to_code(test_sent)
        with(tf$Session(config=self$config_proto, graph=self$word_spacing_graph) %as% sess, {
          self$saver$restore(sess, sprintf("model/model_%d.chkp-%d",best_epoc, glob_step))
          #self$is_training <- F
          logits_transition_params <- sess$run(list(self$logit,self$transition_params),feed_dict=dict(
                                                            x=coding_mat[[1]],
                                                            sent_len=list(coding_mat[[2]]), 
                                                            batch_size=c(1L)))
        })
        #print(logits_transition_params[[2]])
        result <- self$decoding(logits_transition_params[[1]][1,,], 
                                        logits_transition_params[[2]], nchar(test_sent))
        
        return(self$code_to_sent(test_sent, result, nchar(test_sent)))
      },
      code_to_sent=function(input_sent, coding_mat, coding_len){
        char_sent <- str_split(input_sent, '')[[1]]
        ch <- c()
        for(i in 1:coding_len){
          if(coding_mat[1,i] == 1){
            ch <- c(ch, char_sent[i] ,' ')
          }else{
            ch <- c(ch, char_sent[i])
          }
        }
        return(paste0(ch, collapse = ''))
      },
      sent_to_code=function(sentence){
        seq_mat_test <- matrix(0, ncol=max_seq_len, nrow=1)
  
        sent_t <- str_split(sentence, pattern = '')[[1]]
        for(j in 1:length(sent_t)){
          idx <- self$chmap[[sent_t[j]]]
          if(is.na(idx)) idx <- self$chmap[[specl_char]]
          seq_mat_test[1,j] <- idx
        }
        return(list(seq_mat_x=seq_mat_test, nchar=nchar(sentence)))
      }
))

네트워크 아키텍처는 아래 도식과 같다.

bi-LSTM-CRF

bi-LSTM-CRF

bi-LSTM을 통해서 문장의 context를 학습하고 Fully Connected를 통해 나온 logit값과 state transition 정보를 가지고 log likelihood를 최대화 하는 과정으로 학습이 진행된다. 코드에서 다소 생소한 부분은 실제 최적 시퀀스를 구하는 viterbi 함수(viterbi_decode)가 TensorFlow Tensor를 입력으로 받지 않는다는 것이다. 다소 복잡도가 있는 알고리즘이기 때문에 accuracy와 같은 지표를 모델링이 진행되면서 대량의 셋으로 확인하기는 다소 무리가 있을 것으로 예상된다.

train and Evaluation


#각 셀별 뉴론의 개수는 5개, 음절속성벡터의 수는 50개로한다. 
wsp <- WordSpacing$new(char_dic_size=dim(m)[1], n_neurons=5L, num_out_classes=2L, 
                       num_classes=50L, 
                       max_sequence_length=max_seq_len, c2v=m, global_step = 1L)


tr_loss <- wsp$train(seq_mat_x, seq_mat_y, len_list, batch_n=100L, epoch = 1)
## [1] "1:300 train loss : 1.390187, seg loss : .., accuracy : .."
## [1] "아버지가방에들어가셨 다 . "
## [1] "1:300 test loss : 9.816381, accuracy : ..."
## [1] "1:600 train loss : 0.931453, seg loss : .., accuracy : .."
## [1] "아버지가방에들어가 셨 다 . "
## [1] "1:600 test loss : 8.425335, accuracy : ..."
## [1] "1:900 train loss : 0.982449, seg loss : .., accuracy : .."
## [1] "아버지가방에 들어가 셨 다 . "
## [1] "1:900 test loss : 7.537846, accuracy : ..."
## [1] "1:1200 train loss : 1.071610, seg loss : .., accuracy : .."
## [1] "아버지가방에 들어가 셨 다. "
## [1] "1:1200 test loss : 7.055751, accuracy : ..."
## [1] "1:1500 train loss : 0.964378, seg loss : .., accuracy : .."
## [1] "아버지가방에 들어가셨다. "
## [1] "1:1500 test loss : 6.723727, accuracy : ..."
## [1] "1:1800 train loss : 0.865009, seg loss : .., accuracy : .."
## [1] "아버지가 방에 들어가셨다. "
## [1] "1:1800 test loss : 6.533759, accuracy : ..."
## [1] "1:2100 train loss : 0.937080, seg loss : .., accuracy : .."
## [1] "아버지가 방에 들어가셨다. "
## [1] "1:2100 test loss : 6.376132, accuracy : ..."
## [1] "1:2400 train loss : 0.782063, seg loss : .., accuracy : .."
## [1] "아버지가 방에 들어가셨다. "
## [1] "1:2400 test loss : 6.290747, accuracy : ..."
## [1] "1:2700 train loss : 0.958704, seg loss : .., accuracy : .."
## [1] "아버지가 방에 들어가셨다. "
## [1] "1:2700 test loss : 6.205781, accuracy : ..."
## [1] "1:3000 train loss : 0.918696, seg loss : .., accuracy : .."
## [1] "아버지가 방에 들어가셨다. "
## [1] "1:3000 test loss : 6.123788, accuracy : ..."
## [1] "1:3300 train loss : 0.769656, seg loss : .., accuracy : .."
## [1] "아버지가 방에 들어가셨다. "
## [1] "1:3300 test loss : 6.074191, accuracy : ..."
## [1] "1:3600 train loss : 0.966061, seg loss : .., accuracy : .."
## [1] "아버지가방에 들어가셨다. "
## [1] "1:3600 test loss : 6.045763, accuracy : ..."
## [1] "1:3900 train loss : 0.957004, seg loss : .., accuracy : .."
## [1] "아버지가 방에 들어가셨다. "
## [1] "1:3900 test loss : 5.997510, accuracy : ..."
## [1] "1:4200 train loss : 0.879998, seg loss : .., accuracy : .."
## [1] "아버지가 방에 들어가셨다. "
## [1] "1:4200 test loss : 5.992090, accuracy : ..."
## [1] "1:4500 train loss : 0.816798, seg loss : .., accuracy : .."
## [1] "아버지가 방에 들어가셨다. "
## [1] "1:4500 test loss : 5.972754, accuracy : ..."
## [1] "1:4800 train loss : 0.872573, seg loss : .., accuracy : .."
## [1] "아버지가 방에 들어가셨다. "
## [1] "1:4800 test loss : 5.969009, accuracy : ..."
## [1] "1:5100 train loss : 0.977625, seg loss : .., accuracy : .."
## [1] "아버지가 방에 들어가셨다. "
## [1] "1:5100 test loss : 5.955671, accuracy : ..."
## [1] "1:5400 train loss : 0.999991, seg loss : .., accuracy : .."
## [1] "아버지가 방에 들어가 셨다. "
## [1] "1:5400 test loss : 5.947856, accuracy : ..."
## [1] "1:5700 train loss : 1.029374, seg loss : .., accuracy : .."
## [1] "아버지가 방에 들어가셨다. "
## [1] "1:5700 test loss : 5.944112, accuracy : ..."
## [1] "1:6000 train loss : 0.970027, seg loss : .., accuracy : .."
## [1] "아버지가 방에 들어가 셨다. "
## [1] "1:6000 test loss : 5.926543, accuracy : ..."
## [1] "1:6300 train loss : 0.764954, seg loss : .., accuracy : .."
## [1] "아버지가 방에 들어가 셨다. "
## [1] "1:6300 test loss : 5.959241, accuracy : ..."
## [1] "Model saved in file: model/model_1.chkp-1"
tr_te_loss <- data.table(train_loss= tr_loss[[1]], test_loss=tr_loss[[2]])

tr_te_loss[,idx:=1:nrow(tr_te_loss)]

ggplot(melt(tr_te_loss, id.vars = 'idx'), aes(idx, value)) + 
  geom_line(aes(colour=variable)) + xlab('every 300 batch') + ylab('loss')

1에폭만 실행한 loss 추이를 보여주고 있는데, 좀더 학습을 오래 하면 최적화된 성능을 보여줄 수 있을거라 생각한다.

wsp$predict("크리스마스는친구와함께!",best_epoc = 1)
## [1] "크리스마스는 친구와 함께 !"
wsp$predict("대표직을사퇴한그는새로운사업을시작했다.",best_epoc = 1)
## [1] "대표직을 사퇴한 그는 새로 운사업을 시작했다. "
wsp$predict("일정한조건에따르면-자유롭게-이것을재배포할수가있습니다.",best_epoc = 1)
## [1] "일정한 조건에 따르면 -자유롭게 -이것을 재배포할 수가있습니다. "

 

후기

약 1주일 동안 틈틈히 이런 저런 실험을 하면서 많은 부분을 찾아보고 공부하고 했었던거 같다. 아주 재미 있었고 만족스런 성능이 도출되면 KoNLP에도 탑재를 할 수 있을거로 예상된다. 다만 아쉬운 부분은 양질의 학습셋 확보가 어렵다는 것인데, 이 부분도 조만간 주변의 도움을 통해 가능할 수 있을 것이라 생각된다. HMM으로 하는 띄어쓰기와 RNN으로 하는 띄어쓰기가 서로 아이디어를 공유하고 있다는 것을 이번 기회에 알았으며, 모델링 페러다임이 바뀌었다는 점이 서로 다르게 보이게 하는 요인이 되었다는 것을 인지한게 가장 큰 성과였다. 따라서 성급한 마음을 이 개인 연구를 통해 가라앉힐 수 있게 되는 계기가 되었다.

R기반으로 TensorFlow 코드를 구현하는 방식은 이제 어느정도 익숙해진거 같다. 얼마전까지만 해도 feed_dict 인자에 값을 주는 방식의 미묘한 문제 때문에 고생을 좀 하긴 했지만, 이제는 어느정도 생각대로 코드를 작성할 수 있게 되었다.  사실 데이터 핸들링 코드 빼놓고는 Python이랑 99% 같으나, R을 사용하는 이유는 데이터 핸들링이 어떠한 도구보다 간단하고 명료하기 때문이다.

R을 사용하면서 클래스를 거의 사용할 일이 없었는데 TensorFlow를 사용하면서 많이 사용하게 되었다. 경험상 TensorFlow 코드를 네임스페이스를 사용하지 않는 방식으로 코드를 구성하다보면 Tensor 객체가 꼬이는 문제가 발생해 잘못된 모델링 결과나 오류가 날 수 있어 반드시 사용해야 된다(이 부분은 Python도 마찬가지로 알고 있다). 그리고 가장 적합한 클래싱 방식은 R6라는 것도 이번 코드를 구현하면서 정리할 수 있었다.

 

[출처] http://freesearch.pe.kr/archives/4617

 

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
공지 오라클 기본 샘플 데이터베이스 졸리운_곰 2014.01.02 86411
공지 [SQL컨셉] 서적 "SQL컨셉"의 샘플 데이타 베이스 SAMPLE DATABASE of ORACLE 가을의 곰을... 2013.02.10 78845
공지 [G_SQL] Sample Database 가을의 곰을... 2012.05.20 95611
26 감석분석 작업 로그 : 감성분석(Sentiment Analysis) - 깔끔한 텍스트 방식(tidytext) : xwMOOC 자연어 처리 졸리운_곰 2019.12.24 1243
25 '애자일과 데이터 관리의 결합'··· '데이터옵스'의 정의와 주요 기술 file 졸리운_곰 2019.11.17 1684
24 데브옵스와 분석의 결합··· ‘데이터옵스’를 아시나요? file 졸리운_곰 2019.11.17 1233
23 데이터옵스(DATAOPS) 란 무엇일까? file 졸리운_곰 2019.11.17 1636
22 데이터옵스는 단순히 데이터에 대한 데브옵스가 아님니다. DataOps is NOT Just DevOps for Data file 졸리운_곰 2019.11.17 1636
21 R에서 파이썬까지…데이터과학 학습 사이트 8곳 file 졸리운_곰 2019.04.21 1758
20 [통계] prediction VS forecast file 졸리운_곰 2019.04.03 1572
19 forecast 와 prediction의 차이를 아시나요? 졸리운_곰 2019.04.03 896
18 10분만에 끝내는 데이터분석 file 졸리운_곰 2019.04.03 1867
17 처음으로 케글 데이터분석에 도전하기 : Competing on kaggle.com for the First Time file 졸리운_곰 2019.01.27 4405
16 데이터 과학자가 갖춰야 할 5가지 스킬셋 file 졸리운_곰 2018.11.11 1374
15 데이터 사이언스 괜찮은 강의들 리스트 1 file 졸리운_곰 2018.11.11 2461
14 데이터 사이언스 학습 안내 졸리운_곰 2018.11.11 1667
13 Prophet: Automatic Forecasting Procedure 자동 예측 프로시져 프로그램 /데이터분석 / 데이터 과학 file 졸리운_곰 2018.09.04 1311
12 데이터 분석 어디에 집중할 것인가? 가장 먼저 실험에 집중하라 file 졸리운_곰 2018.02.06 1398
11 빅데이터 융합기획전문가 1기 교육 표창장 file 졸리운_곰 2018.01.03 1312
10 빅 데이터 기획에 대한 이해 file 졸리운_곰 2017.12.09 2016
9 분야별 빅데이터 애널리틱스 적용 사례 및 성공의 비결 file 졸리운_곰 2017.12.08 2150
8 하둡 에코시스템을 활용한 Hybrid DW 구축 사례 file 졸리운_곰 2017.12.08 2003
7 gmail 수신 메일로 워드클라우드 생성 : Creating a gmail wordcloud 졸리운_곰 2017.11.20 1890
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED