Text Generation using RNN

( 참고 : “딥러닝을 이용한 자연어 처리 입문” (https://wikidocs.net/book/2155) )

Text Generation, 말 그대로 텍스트를 생성해내는 것이다.

예를 들면, “내가 제일 좋아하는 음식은~”이라는 말까지 했을 때, 이어지는 말 혹은 단어로는 “사과, 딸기, 당근”등과 같이 “음식의 종류”가 나올 것이라는 것을 우리는 알 수 있다. 이를 RNN, LSTM등을 활용하여 예측해볼 것이다.

1. Data preprocessing

import numpy as np
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.utils import to_categorical


다음과 같은 예시 text를 사용할 것이다.

text="""경마장에 있는 말이 뛰고 있다\n
그의 말이 법이다\n
가는 말이 고와야 오는 말이 곱다\n"""


우선, 위 text를 tokenize 해준다.

t = Tokenizer()
t.fit_on_texts([text])


각각의 단어에 index가 붙은 것을 확인할 수 있다.

t.word_index
{'말이': 1,
 '경마장에': 2,
 '있는': 3,
 '뛰고': 4,
 '있다': 5,
 '그의': 6,
 '법이다': 7,
 '가는': 8,
 '고와야': 9,
 '오는': 10,
 '곱다': 11}


vocab size에 1을 더하는 이유?

  • Keras의 Tokenizer의 정수 인코딩은 index가 1부터 시작하기 때문에!
vocab_size = len(t.word_index) + 1


Example )

“경마장에 있는 말이 뛰고 있다”라는 text가, 어떻게 index의 나열로 표현되는 지 확인해보자.

t.texts_to_sequences(['경마장에 있는 말이 뛰고 있다'])[0]
[2, 3, 1, 4, 5]


sequence라는 list를 생성하여, (위에서 생성한 text-index) 딕셔너리를 통해 인코딩 된 것들을 sequence에 넣어준다. 여기서 넣어줄 때, 문장의 길이를 1부터 n(문장 전체)까지 다양하게 넣어준다.

sequences = list()
for line in text.split('\n'):
    encoded = t.texts_to_sequences([line])[0]
    for i in range(1,len(encoded)):
        sequence = encoded[:i+1]
        sequences.append(sequence)
sequences
[[2, 3],
 [2, 3, 1],
 [2, 3, 1, 4],
 [2, 3, 1, 4, 5],
 [6, 1],
 [6, 1, 7],
 [8, 1],
 [8, 1, 9],
 [8, 1, 9, 10],
 [8, 1, 9, 10, 1],
 [8, 1, 9, 10, 1, 11]]

이와 같이 만드는 이유는 무엇일까?

그 이유는, 우리가 예측하는 방식을 보면 쉽게 이해할 수 있다.

example )

‘내가’를 통해 ‘가장’을 예측하고,

‘내가 가장’을 통해 ‘좋아하는’을 예측하고,

‘내가 가장 좋아하는’을 통해 ‘음식은’을 예측하는 모델을 생성하려하기 때문이다!

샘플의 길이를 padding을 통해 일치시켜준다.

padding size는, 여러 문장 중 가장 길이가 긴 문장의 단어 수로 지정한다.

( 함수 : pad_sequences(list, maxlen, padding) )

max_len = max(len(l) for l in sequences)
sequences = pad_sequences(sequences, maxlen=max_len, padding='pre')
sequences
array([[ 0,  0,  0,  0,  2,  3],
       [ 0,  0,  0,  2,  3,  1],
       [ 0,  0,  2,  3,  1,  4],
       [ 0,  2,  3,  1,  4,  5],
       [ 0,  0,  0,  0,  6,  1],
       [ 0,  0,  0,  6,  1,  7],
       [ 0,  0,  0,  0,  8,  1],
       [ 0,  0,  0,  8,  1,  9],
       [ 0,  0,  8,  1,  9, 10],
       [ 0,  8,  1,  9, 10,  1],
       [ 8,  1,  9, 10,  1, 11]])


가장 마지막 단어를 y로 하고, 그 이전까지 모든 부분을 X로 한다.

X = sequences[:, :-1]
y = sequences[:,-1]


지금 우리가 위에서 만들어낸 숫자들은 사실 ‘숫자(numeric data)’로서의 의미를 가지고 있지 않다.

하나의 단어를 상징하는 하나의 ‘문자 같은’ 숫자일 뿐이다. 따라서 One-Hot-Encoding을 해준다.

# One-Hot Encoding
y = to_categorical(y, num_classes=vocab_size)
y
array([[0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0.],
       [0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1.]], dtype=float32)


2. Modeling

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, Dense, SimpleRNN

model = Sequential()를 통해 처음 (아무것도 없는) 모델을 만들어 주고,

이 model에 'add'를 통해 layer를 쌓는 방식으로 모델을 만들 것이다.

  • 1) Embedding Layer ( 10차원으로 임베딩할 것이다)
  • 2) Simpe RNN ( 32개의 neuron 사용 )
  • 3) Dense ( Classification 문제이기 때문에 softmax 함수를 사용한다 )
model = Sequential()
model.add(Embedding(vocab_size,10, input_length=max_len-1)) # label을 분리하였으므로 '-1'
model.add(SimpleRNN(32))
model.add(Dense(vocab_size, activation='softmax'))


model.summary를 통해, 우리가 짠 모델의 architecture를 확인할 수 있다.

model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding (Embedding)        (None, 5, 10)             120       
_________________________________________________________________
simple_rnn (SimpleRNN)       (None, 32)                1376      
_________________________________________________________________
dense (Dense)                (None, 12)                396       
=================================================================
Total params: 1,892
Trainable params: 1,892
Non-trainable params: 0
_________________________________________________________________
  • loss = categorical crossentropy
  • optimizer = adam optimizer
  • 평가 metric = accuracy
  • epoch = 200
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
model.fit(X,y,epochs=200, verbose=2)
Train on 11 samples
Epoch 1/200
11/11 - 2s - loss: 2.5168 - accuracy: 0.0000e+00
Epoch 2/200
11/11 - 0s - loss: 2.5045 - accuracy: 0.0000e+00
Epoch 3/200
11/11 - 0s - loss: 2.4925 - accuracy: 0.0000e+00
Epoch 4/200
11/11 - 0s - loss: 2.4807 - accuracy: 0.0000e+00
Epoch 5/200
11/11 - 0s - loss: 2.4690 - accuracy: 0.0909
Epoch 6/200
11/11 - 0s - loss: 2.4573 - accuracy: 0.0909
Epoch 7/200
11/11 - 0s - loss: 2.4455 - accuracy: 0.1818
Epoch 8/200
11/11 - 0s - loss: 2.4334 - accuracy: 0.1818
Epoch 9/200
11/11 - 0s - loss: 2.4210 - accuracy: 0.2727

...
Epoch 199/200
11/11 - 0s - loss: 0.1233 - accuracy: 1.0000
Epoch 200/200
11/11 - 0s - loss: 0.1210 - accuracy: 1.0000


3. Sentence generation function (문장 생성 함수)

지금까지 우리는 문장의 어느 부분까지를 input으로 넣었을 떄, 그 뒤에 이어질 단어를 예측하는 모델을 만들었다. 이를 통해, ‘문장 생성 함수’를 만들 수 있다.

def sentence_generation(model, t, current_word, n): # 모델, tokenizer, 현재 단어, 반복 횟수
    init_word = current_word
    sentence =''
    for _ in range(n):
        encoded = t.texts_to_sequences([current_word])[0]
        encoded = pad_sequences([encoded], maxlen=5, padding='pre')
        result = model.predict_classes(encoded,verbose=0)
        
        for word, index in t.word_index.items():
            if index == result:
                break
        current_word = current_word + ' ' + word
        sentence = sentence + ' ' + word
    
    sentence = init_word + sentence
    return sentence


예를 들어 ‘경마장에’라는 단어를 넣었을 때, 뒤에 이어질 ‘단어 4개’를 활용하여 문장을 만들어라!라는 것은 다음과 같이 표현할 수 있다.

sentence_generation(model,t,'경마장에',4)
'경마장에 있는 말이 뛰고 있다'


또 다른 예시로, ‘그의’라는 input을 주었을 때 이어질 ‘단어 2개’를 예측하여 문장을 만들 수도 있다.

sentence_generation(model,t,'그의',2)
'그의 말이 법이다'