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)
'그의 말이 법이다'