43. TensorFlow에서 RNN 구현하기 (lab 12) 소스코드

lab 12 동영상의 내용이 너무 길어서 소스코드를 이곳에 별도로 정리했다.

먼저 동영상에 나온 코드를 있는 그대로 작성한 코드이다. 교수님께서 알려주신 사이트에 있는 코드는 동영상과 많이 달라서 싣지 않았다. 이번 코드는 이전 글에서 부분적으로 충분히 설명했기 때문에 설명은 생략한다.


import tensorflow as tf
import numpy as np

char_rdic = ['h', 'e', 'l', 'o'] # id -> char
char_dic = {w : i for i, w in enumerate(char_rdic)} # char -> id

sample = [char_dic[c] for c in 'hello']

x_data = np.array([[1,0,0,0], # h
[0,1,0,0], # e
[0,0,1,0], # l
[0,0,1,0]], # l
dtype = 'f')

# Configuration
char_vocab_size = len(char_dic)
rnn_size = char_vocab_size # 1 hot coding (one of 4)
time_step_size = 4 # 'hell' -> predict 'ello'
batch_size = 1 # one sample

# RNN Model
rnn_cell = tf.nn.rnn_cell.BasicRNNCell(rnn_size)
state = tf.zeros([batch_size, rnn_cell.state_size])
X_split = tf.split(0, time_step_size, x_data)

outputs, state = tf.nn.rnn(rnn_cell, X_split, state)

logits = tf.reshape(tf.concat(1, outputs), [-1, rnn_size])
targets = tf.reshape(sample[1:], [-1])
weights = tf.ones([len(char_dic) * batch_size])

loss = tf.nn.seq2seq.sequence_loss_by_example([logits], [targets], [weights])
cost = tf.reduce_sum(loss) / batch_size
train_op = tf.train.RMSPropOptimizer(0.01, 0.9).minimize(cost)

# Launch the graph in a session
with tf.Session() as sess:
tf.initialize_all_variables().run()
for i in range(100):
sess.run(train_op)
result = sess.run(tf.argmax(logits, 1))
print(result, [char_rdic[t] for t in result])


두 번째는 생각보다 복잡한 구석이 많아서 변수를 상수로 대체해서 다시 작성해 봤다. 이름까지 정리하고 나니까 코드를 읽기가 많이 편해져서 소개한다.

import tensorflow as tf
import numpy as np

unique = 'helo'
y_data = [1, 2, 2, 3] # 'ello'. index from 'helo'
x_data = np.array([[1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,1,0]], dtype='f') # 'hell'

cells = tf.nn.rnn_cell.BasicRNNCell(4) # 출력 결과(4가지 중에서 선택)
state = tf.zeros([1, cells.state_size]) # shape(1, 4), [[0, 0, 0, 0]]
x_data = tf.split(0, 4, x_data) # layer에 포함될 cell 갯수(4). time_step_size

# outputs = [shape(1, 4), shape(1, 4), shape(1, 4), shape(1, 4)]
# state = shape(1, 4)
outputs, state = tf.nn.rnn(cells, x_data, state)

# tf.reshape(tensor, shape, name=None)
# tf.concat(1, outputs) --> shape(1, 16)
logits = tf.reshape(tf.concat(1, outputs), [-1, 4]) # shape(4, 4)
targets = tf.reshape(y_data, [-1]) # shape(4), [1, 2, 2, 3]
weights = tf.ones([4]) # shape(4), [1, 1, 1, 1]

loss = tf.nn.seq2seq.sequence_loss_by_example([logits], [targets], [weights])
cost = tf.reduce_sum(loss)
train_op = tf.train.RMSPropOptimizer(0.01, 0.9).minimize(cost)

with tf.Session() as sess:
tf.initialize_all_variables().run()
for i in range(100):
sess.run(train_op)
r0, r1, r2, r3 = sess.run(tf.argmax(logits, 1))
print(r0, r1, r2, r3, ':', unique[r0], unique[r1], unique[r2], unique[r3])

코드를 볼 때, 중간에 생성되는 데이터가 어떤 형태인지 아는 것이 매우 중요하다. shape(4)는 1차원 배열로 4개의 요소를 갖고 있고, shape(4, 3)은 2차원 배열로 4개의 행과 3개의 열을 요소로 갖고 있다. [1, 2, 3]은 1차원 배열이고, [[1, 2, 3]]은 2차원 해열로 1x3의 크기를 갖는다.

'hello'를 다룬다고 했는데, 실제로는 마지막 글자를 제외한 'hell'만 사용된다. language model은 다음에 올 글자나 단어를 예측하는 모델이어서, 마지막 글자가 입력으로 들어와도 예측할 수가 없다. 정답에 해당하는 label이 없기 때문에 전달하지 않는 것이 맞다.

tf.split 함수는 데이터를 지정한 갯수로 나눈 다음, 나누어진 요소를 Tensor 객체로 만들어 리스트에 넣어서 반환한다. x_data 변수는 Tensor가 4개 들어간 리스트라는 뜻이다. 처음 만들 때는 np.array로 만들었다가 Tensor 리스트로 변환했다. 첫 번째 매개변수인 0은 행에 대해 나누라는 뜻이다. 1을 전달하면 열을 기준으로 나눈다.

tf.concat 함수는 입력을 연결해서 새로운 Tensor를 만든다. 0을 전달하면 행 기준, 1을 전달하면 열 기준이 된다. 1을 전달했으므로 0번째부터 3번째까지 순서대로 사용해서 1x16 크기의 Tensor를 만든다. 매개변수로 넘어온 outputs는 4개짜리 리스트인데, 3차원으로 해석한다면 4x1x4로 볼 수 있다. reshape 함수를 사용해서 4x4 크기의 Tensor 객체로 바꾸고 있다.

reshape 함수에 [-1]이라고 전달하면 1차원으로 만들겠다는 뜻이고, [-1, 4]로 전달하면 2차원으로 만드는데, 행은 4로 나눈 값을 쓰겠다는 뜻이 된다. 1차원에서는 나눌 값이 없기 때문에 전체를 1차원으로 늘어 놓는 형태가 된다. flat이라고 한다.

tf.argmax 함수는 one-hot encoding을 처리해 주고, 이 값을 run 함수에 전달해 결과를 계산했다. 이번 모델은 입력도 4개, 출력도 4개였다. run 함수의 결과를 리스트 1개로 받을 수도 있지만, 변수 4개로 받았다. r0, r1, r2, r3은 예측한 값의 인덱스로, 순서대로 h, e, l, l 다음에 오는 예측 값을 가리킨다. 원본 문자열은 unique 변수에 넣었고, unique로부터 인덱스 번째의 문자를 출력하면 그것이 예측한 문자가 된다.