텐서플로우(TensorFlow)를 이용해서 글자 생성(Text Generation) 해보기 – Char-RNN (간결한 코드)

 

이번 시간에는 텐서플로우(TensorFlow)를 이용해서 글자를 생성(Text Generation)하는 예제를 살펴보자.

딥러닝을 이용해서 Text Generation을 수행하는 대표적인 구조는 Char-RNN이다.

그림 1 – Char-RNN 구조 [3]

Char-RNN에 대한 자세한 설명은 이전 포스팅[R1]을 참조하라.

본 포스팅에서는 텐서플로우(TensorFlow)를 이용한 Char-RNN 코드 구현 중에 가장 유명한 Sherjil Ozair의 아래 GitHub 저장소 코드를 간결하게 정리한 코드를 소개한다.

https://github.com/sherjilozair/char-rnn-tensorflow

위 GitHub 저장소는 Char-RNN을 안정적인 성능으로 구현했지만 초심자가 한눈에 보기엔 쉽지 않은 구성이다. 따라서 Sherjil Ozair의 코드를 간결하게 정리하고 한글 주석을 추가해서 초심자분들도 쉽게 코드를 따라올 수 있도록 아래 GitHub 저장소에 올려놓았다.

https://github.com/solaris33/char-rnn-tensorflow/

위 저장소의 train.py 코드를 살펴보면 아래와 같다.

 

  # -*- coding: utf-8 -*-
  # Char-RNN 예제
  # Author : solaris33
  # Project URL : http://solarisailab.com/archives/2487
  # GitHub Repository : https://github.com/solaris33/char-rnn-tensorflow/
  # Reference : https://github.com/sherjilozair/char-rnn-tensorflow
   
  import tensorflow as tf
  import numpy as np
   
  from utils import TextLoader
   
  # 학습에 필요한 설정값들을 지정합니다.
  data_dir = 'data/tinyshakespeare'
  #data_dir = 'data/linux'
  batch_size = 50 # Training : 50, Sampling : 1
  seq_length = 50 # Training : 50, Sampling : 1
  hidden_size = 128 # 히든 레이어의 노드 개수
  learning_rate = 0.002
  num_epochs = 2
  num_hidden_layers = 2
  grad_clip = 5 # Gradient Clipping에 사용할 임계값
   
  # TextLoader를 이용해서 데이터를 불러옵니다.
  data_loader = TextLoader(data_dir, batch_size, seq_length)
  # 학습데이터에 포함된 모든 단어들을 나타내는 변수인 chars와 chars에 id를 부여해 dict 형태로 만든 vocab을 선언합니다.
  chars = data_loader.chars
  vocab = data_loader.vocab
  vocab_size = data_loader.vocab_size # 전체 단어개수
   
  # 인풋데이터와 타겟데이터, 배치 사이즈를 입력받기 위한 플레이스홀더를 설정합니다.
  input_data = tf.placeholder(tf.int32, shape=[None, None]) # input_data : [batch_size, seq_length])
  target_data = tf.placeholder(tf.int32, shape=[None, None]) # target_data : [batch_size, seq_length])
  state_batch_size = tf.placeholder(tf.int32, shape=[]) # Training : 50, Sampling : 1
   
  # RNN의 마지막 히든레이어의 출력을 소프트맥스 출력값으로 변환해주기 위한 변수들을 선언합니다.
  # hidden_size -> vocab_size
  softmax_w = tf.Variable(tf.random_normal(shape=[hidden_size, vocab_size]), dtype=tf.float32)
  softmax_b = tf.Variable(tf.random_normal(shape=[vocab_size]), dtype=tf.float32)
   
  # num_hidden_layers만큼 LSTM cell(히든레이어)를 선언합니다.
  cells = []
  for _ in range(0, num_hidden_layers):
  cell = tf.nn.rnn_cell.BasicLSTMCell(hidden_size)
  cells.append(cell)
   
  # cell을 종합해서 RNN을 정의합니다.
  cell = tf.contrib.rnn.MultiRNNCell(cells, state_is_tuple=True)
   
  # 인풋데이터를 변환하기 위한 Embedding Matrix를 선언합니다.
  # hidden_size -> vocab_size
  embedding = tf.Variable(tf.random_normal(shape=[vocab_size, hidden_size]), dtype=tf.float32)
  inputs = tf.nn.embedding_lookup(embedding, input_data)
   
  # 초기 state 값을 0으로 초기화합니다.
  initial_state = cell.zero_state(state_batch_size, tf.float32)
   
  # 학습을 위한 tf.nn.dynamic_rnn을 선언합니다.
  # outputs : [batch_size, seq_length, hidden_size]
  outputs, final_state = tf.nn.dynamic_rnn(cell, inputs, initial_state=initial_state, dtype=tf.float32)
  # ouputs을 [batch_size * seq_length, hidden_size]] 형태로 바꿉니다.
  output = tf.reshape(outputs, [-1, hidden_size])
   
  # 최종 출력값을 설정합니다.
  # logits : [batch_size * seq_length, vocab_size]
  logits = tf.matmul(output, softmax_w) + softmax_b
  probs = tf.nn.softmax(logits)
   
  # Cross Entropy 손실 함수를 정의합니다.
  loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=target_data))
   
  # 옵티마이저를 선언하고 옵티마이저에 Gradient Clipping을 적용합니다.
  # grad_clip(=5)보다 큰 Gradient를 5로 Clippin합니다.
  tvars = tf.trainable_variables()
  grads, _ = tf.clip_by_global_norm(tf.gradients(loss, tvars), grad_clip)
  optimizer = tf.train.AdamOptimizer(learning_rate)
  train_step = optimizer.apply_gradients(zip(grads, tvars))
   
  # 세션을 열고 학습을 진행합니다.
  with tf.Session() as sess:
  # 변수들에 초기값을 할당합니다.
  sess.run(tf.global_variables_initializer())
   
  for e in range(num_epochs):
  data_loader.reset_batch_pointer()
  # 초기 상태값을 지정합니다.
  state = sess.run(initial_state, feed_dict={state_batch_size : batch_size})
   
  for b in range(data_loader.num_batches):
  # x, y 데이터를 불러옵니다.
  x, y = data_loader.next_batch()
  # y에 one_hot 인코딩을 적용합니다.
  y = tf.one_hot(y, vocab_size) # y : [batch_size, seq_length, vocab_size]
  y = tf.reshape(y, [-1, vocab_size]) # y : [batch_size * seq_length, vocab_size]
  y = y.eval()
   
  # feed-dict에 사용할 값들과 LSTM 초기 cell state(feed_dict[c])값과 hidden layer 출력값(feed_dict[h])을 지정합니다.
  feed_dict = {input_data : x, target_data: y, state_batch_size : batch_size}
  for i, (c, h) in enumerate(initial_state):
  feed_dict[c] = state[i].c
  feed_dict[h] = state[i].h
   
  # 한스텝 학습을 진행합니다.
  _, loss_print, state = sess.run([train_step, loss, final_state], feed_dict=feed_dict)
   
  print("{}(학습한 배치개수)/{}(학습할 배치개수), 반복(epoch): {}, 손실함수(loss): {:.3f}".format(
  e * data_loader.num_batches + b,
  num_epochs * data_loader.num_batches,
  (e+1),
  loss_print))
   
  print("트레이닝이 끝났습니다!")
   
   
  # 샘플링 시작
  print("샘플링을 시작합니다!")
  num_sampling = 4000 # 생성할 글자(Character)의 개수를 지정합니다.
  prime = u' ' # 시작 글자를 ' '(공백)으로 지정합니다.
  sampling_type = 1 # 샘플링 타입을 설정합니다.
  state = sess.run(cell.zero_state(1, tf.float32)) # RNN의 최초 state값을 0으로 초기화합니다.
   
  # Random Sampling을 위한 weighted_pick 함수를 정의합니다.
  def weighted_pick(weights):
  t = np.cumsum(weights)
  s = np.sum(weights)
  return(int(np.searchsorted(t, np.random.rand(1)*s)))
   
  ret = prime # 샘플링 결과를 리턴받을 ret 변수에 첫번째 글자를 할당합니다.
  char = prime # Char-RNN의 첫번쨰 인풋을 지정합니다.
  for n in range(num_sampling):
  x = np.zeros((1, 1))
  x[0, 0] = vocab[char]
   
  # RNN을 한스텝 실행하고 Softmax 행렬을 리턴으로 받습니다.
  feed_dict = {input_data: x, state_batch_size : 1, initial_state: state}
  [probs_result, state] = sess.run([probs, final_state], feed_dict=feed_dict)
   
  # 불필요한 차원을 제거합니다.
  # probs_result : (1,65) -> p : (65)
  p = np.squeeze(probs_result)
   
  # 샘플링 타입에 따라 3가지 종류로 샘플링 합니다.
  # sampling_type : 0 -> 다음 글자를 예측할때 항상 argmax를 사용
  # sampling_type : 1(defualt) -> 다음 글자를 예측할때 항상 random sampling을 사용
  # sampling_type : 2 -> 다음 글자를 예측할때 이전 글자가 ' '(공백)이면 random sampling, 그렇지 않을 경우 argmax를 사용
  if sampling_type == 0:
  sample = np.argmax(p)
  elif sampling_type == 2:
  if char == ' ':
  sample = weighted_pick(p)
  else:
  sample = np.argmax(p)
  else:
  sample = weighted_pick(p)
   
  pred = chars[sample]
  ret += pred # 샘플링 결과에 현재 스텝에서 예측한 글자를 추가합니다. (예를들어 pred=L일 경우, ret = HEL -> HELL)
  char = pred # 예측한 글자를 다음 RNN의 인풋으로 사용합니다.
   
  print("샘플링 결과:")
  print(ret)
view raw train.py hosted with ❤ by GitHub

 

코드를 실행하면 Char-RNN이 셰익스피어의 희곡 <리처드 3세>로 학습을 진행한뒤,  샘플링하는 모습을 볼 수 있다. 실험 결과 중 일부를 발췌하면 아래와 같다.

First Lord:
Or Ray Cluicians? you appeace, advain.

Shepherd:
Then? Who do valiantage, the trail swoed's Englong
Offords in you, why this paliancac's.

KING RICHARD III:
O, and his in plawful your joyters.

First MuntaSTsa:
All
If I sir? I'll none may to see Zhose than saught;
Beven play'st scalk in Rich,
I dolicides are when I mays good friend;
Hath no sunbs her side to lip him; for you,
And she offendeed: I dear, alome no;
Have their leave his briefer's proceasing in these
his sain whice captas wind supposent.
And such I showners of ever my lease be sing
A fall before the base grown for Edward these comprace.

BENVOLIO:
Have action, Ant confeigh'd.

ANGELO:
That you say go may truon a have lender mother suffe:
How shord, as mengef, King side Lancast, may.

First Duky you, my raz?

그림 2 – Char-RNN으로 셰익스피어 희곡 <리처드 3세>를 학습하고 샘플링으로 새로운 데이터를 생성한 결과

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

한 눈에 봐도 그럴듯한 텍스트를 생성해낸 모습을 볼 수 있다. 희곡은 Char-RNN의 학습 데이터로 널리 사용되는데 RNN이 학습하는 것은 글자배열의 패턴이기 때문이다. 희곡 같은 경우 문장의 첫 부분에 “등장인물:” 형태의 고정된 패턴이 반복해서 등장하기 때문에 Char-RNN의 학습에 적합하다.

그림 2의 <리처드 3세> 샘플링 결과를 보면 <리처드 3세>의 실제 등장인물인 (First Lord, Shepherd, KING RICHARD III, BENVOLIO, ANGELO) 등의 이름을  정확히 생성해낸 모습을 볼 수 있다. 하지만 (First MuntaSTsa)와 이상한 이름을 생성한 경우도 있다. 또한 생성한 영어 대사의 경우, 의미론적으로는 말이 안되지만 구조적으로는 그럴듯한 모습을 띄는 것을 알 수 있다.

이제 다른 학습데이터로 재밌는 실험을 하나 더 해보자. 프로그래밍 언어 또한 언어의 문법에 따라 일정한 패턴으로 코드가 작성되기 때문에 Char-RNN의 학습에 적합하다. 그림 3은 C언어로 작성된 리눅스 소스코드를 이용해서 Char-RNN을 학습시킨 뒤 샘플링한 결과 중 일부를 발췌한 것이다.

void graph_index(struct hrtime_hardetr, bolaction, struct hectx *tsk)
{
    struct plog_batch;
    get_rrgbum_shifock_remove_elaper(L)) {
        dr.name_graph(return, tsk);
            if (index = &bay->count - (smap, hardid_usigned && tr->sched_name && ~SUPTION_BT(032))
        return 1;

    raw_spin_unlock(dstat_val)))
        return -EINVAL;
        i+++)
                        return -EINVA;
}

static inline bufferpoid(tp, char *timer, struct rq *rq)
{ }
#else
    return rq->ret_module_aps(p, i) gore, &path) &   " factive it rcu\bled tsk0.
         *
                 * Arce a dis:
        return error = 1;
    retuct task_next;

    if (hadd_piocup.ext, sizeof(queued);
            result = &context->trace_hung_twueued entry->graph_find_rmatf("Tode, infags or */
        return prink;

        timekeeping_write(, p2))
            return;
    }

그림 3 – Char-RNN으로 <Linux 소스코드>를 학습하고 샘플링으로 새로운 데이터를 생성한 결과

희곡의 경우와 마찬가지로 의미론적으로는 말이 안되지만 구조적으로는 그럴듯한 모습을 띄는 것을 알 수 있다.

 

연관 포스팅

[R1] 12. 텐서플로우(TensorFlow)를 이용해서 글자 생성(Text Generation) 해보기 – Recurrent Neural Networks(RNNs) 예제 – Char-RNN

 

References

[1] https://github.com/sherjilozair/char-rnn-tensorflow

[2] https://github.com/solaris33/char-rnn-tensorflow/

[3] http://karpathy.github.io/2015/05/21/rnn-effectiveness/

 

[출처] http://solarisailab.com/archives/2487

 

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
공지 오라클 기본 샘플 데이터베이스 졸리운_곰 2014.01.02 86621
공지 [SQL컨셉] 서적 "SQL컨셉"의 샘플 데이타 베이스 SAMPLE DATABASE of ORACLE 가을의 곰을... 2013.02.10 79006
공지 [G_SQL] Sample Database 가을의 곰을... 2012.05.20 95751
78 [Kafka] Kafka 한번 살펴보자... Quickstart file 졸리운_곰 2021.06.18 1312
77 Java Kafka Producer, Consumer 예제 구현 Java를 이용하여 Kafka Producer와 Kakfa Consumer를 구현해보자. file 졸리운_곰 2021.06.18 1172
76 Beginner’s Guide to Understand Kafka file 졸리운_곰 2021.06.18 1574
75 [Kafka] Kafka 설치/실행 및 테스트 file 졸리운_곰 2021.06.18 1066
74 [java] [kafka] [Kafka] 개념 및 기본예제 file 졸리운_곰 2021.06.16 2170
73 Getting started with Apache Kafka in Python file 졸리운_곰 2020.09.10 2261
72 [Kafka] 다운로드 및 Quick Start file 졸리운_곰 2020.09.07 1896
71 [Kafka] 기본 개념잡기 file 졸리운_곰 2020.09.07 1709
70 Flume Integration with Kafka file 졸리운_곰 2019.04.16 2081
69 빅데이터: 플럼(Flume) 토폴로지 설계 file 졸리운_곰 2019.04.16 1602
68 실시간 처리를 위한 분산 메시징 시스템 카프카(Kafka) file 졸리운_곰 2018.05.12 1410
67 Flume과 Kafka를 사용한 초당 100만개 로그 수집 테스트 file 졸리운_곰 2018.05.12 1393
66 웹 크롤링 / web crwaling / web scraping / 웹 스크래핑 file 졸리운_곰 2017.07.09 1848
65 빅데이터 단지 몇퍼센트의 예측 정확성을 위하여 장애로 가득찬 빅데이터 시스템을 도입하여야 하는가에 대한 의문! file 졸리운_곰 2017.03.20 1607
64 빅데이터: 플럼(Flume) 토폴로지 설계 file 졸리운_곰 2017.03.20 1418
63 [실시간 분석 시스템] Apache Flume를 활용한 데이터 수집(1) file 졸리운_곰 2017.03.06 1319
62 [실시간 분석 시스템] 데이터 수집 #2 Apache Sqoop을 활용하여 RDBMS 데이터 수집(2) file 졸리운_곰 2017.03.06 1385
61 [실시간 분석 시스템] 데이터 수집 #2 Apache Sqoop을 활용하여 RDBMS 데이터 수집(1) file 졸리운_곰 2017.03.06 1104
60 [실시간 분석 시스템] 데이터 수집 #1 오픈 소스 수집기 비교 file 졸리운_곰 2017.03.06 1748
59 [실시간 분석 시스템] 일단 데이터 들여다 보기 file 졸리운_곰 2017.03.06 1939
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED