[코드 정리] RNN을 이용한 텍스트 생성(Text Generation using RNN)


앞서 배운 RNN을 이용해서 텍스트 생성 모델을 만들어본다.


참고 사이트 : 딥 러닝을 이용한 자연어 처리 입문 에 대한 글을 요약하였다.

다대일 RNN 사용하여 텍스트를 생성하는 모델 만들기

1) 데이터에 대한 이해와 전처리

  • 아래의 11가지 샘플을 구성하여 다음 텍스트를 생성하는 모델을 만들어 보자
samplesXy
1경마장에있는
2경마장에 있는말이
3경마장에 있는 말이뛰고
4경마장에 있는 말이 뛰고있다
5그의말이
6그의 말이법이다
7가는말이
8가는 말이고와야
9가는 말이 고와야오는
10가는 말이 고와야 오는말이
11가는 말이 고와야 오는 말이곱다
#! pip install Tokenizer
#! pip install pad_sequences
#! pip install numpy
#! pip install to_categorical
Requirement already satisfied: Tokenizer in d:\python_study\text_analysis\venv\lib\site-packages (2.0.3)
Collecting pad_sequences


  Could not find a version that satisfies the requirement pad_sequences (from versions: )
No matching distribution found for pad_sequences


Requirement already satisfied: numpy in d:\python_study\text_analysis\venv\lib\site-packages (1.18.1)
Collecting to_categorical


  Could not find a version that satisfies the requirement to_categorical (from versions: )
No matching distribution found for to_categorical
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
import numpy as np
from tensorflow.keras.utils import to_categorical
text="""경마장에 있는 말이 뛰고 있다\n
그의 말이 법이다\n
가는 말이 고와야 오는 말이 곱다\n"""
t = Tokenizer()
t.fit_on_texts([text]) # 단어 인덱스 만들기
vocab_size = len(t.word_index) + 1
# 케라스 토크나이저의 정수 인코딩은 인덱스가 1부터 시작하지만,
# 케라스 원-핫 인코딩에서 배열의 인덱스가 0부터 시작하기 때문에
# 배열의 크기를 실제 단어 집합의 크기보다 +1로 생성해야하므로 미리 +1 선언 
print('단어 집합의 크기 : %d' % vocab_size)
단어 집합의 크기 : 12
print(t.word_index)
{'말이': 1, '경마장에': 2, '있는': 3, '뛰고': 4, '있다': 5, '그의': 6, '법이다': 7, '가는': 8, '고와야': 9, '오는': 10, '곱다': 11}
sequences = list()
for line in text.split('\n'): # \n 기준으로 문장 토큰화
    encoded = t.texts_to_sequences([line])[0]
    for i in range(1, len(encoded)):
        sequence = encoded[:i+1]
        sequences.append(sequence)

print('학습에 사용할 샘플의 개수: %d' % len(sequences)) # 4+2+5
학습에 사용할 샘플의 개수: 11
print(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]]
max_len=max(len(l) for l in sequences) # 모든 샘플에서 길이가 가장 긴 샘플의 길이 출력
print('샘플의 최대 길이 : {}'.format(max_len))
샘플의 최대 길이 : 6
# 최대 길이로 패딩
sequences = pad_sequences(sequences, maxlen=max_len, padding='pre')
print(sequences)
[[ 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]]
sequences = np.array(sequences)
X = sequences[:,:-1]
y = sequences[:,-1]
# 리스트의 마지막 값을 제외하고 저장한 것은 X
# 리스트의 마지막 값만 저장한 것은 y. 이는 레이블에 해당됨.
print(X)
[[ 0  0  0  0  2]
 [ 0  0  0  2  3]
 [ 0  0  2  3  1]
 [ 0  2  3  1  4]
 [ 0  0  0  0  6]
 [ 0  0  0  6  1]
 [ 0  0  0  0  8]
 [ 0  0  0  8  1]
 [ 0  0  8  1  9]
 [ 0  8  1  9 10]
 [ 8  1  9 10  1]]
print(y)
[ 3  1  4  5  1  7  1  9 10  1 11]
# 원핫인코딩으로 만들기
y = to_categorical(y, num_classes=vocab_size)
print(y)
[[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.]]

2) 모델 설계

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, Dense, SimpleRNN
model = Sequential()
model.add(Embedding(vocab_size, 10, input_length=max_len-1)) 
# 레이블을 분리하였으므로 이제 X의 길이는 5
model.add(SimpleRNN(32))
model.add(Dense(vocab_size, activation='softmax'))
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.4870 - accuracy: 0.0909
Epoch 2/200
11/11 - 0s - loss: 2.4759 - accuracy: 0.0909
...
Epoch 199/200
11/11 - 0s - loss: 0.0985 - accuracy: 1.0000
Epoch 200/200
11/11 - 0s - loss: 0.0970 - accuracy: 1.0000





<tensorflow.python.keras.callbacks.History at 0x18107dcbe48>
def sentence_generation(model, t, current_word, n): # 모델, 토크나이저, 현재 단어, 반복할 횟수
    init_word = current_word # 처음 들어온 단어도 마지막에 같이 출력하기위해 저장
    sentence = ''
    for _ in range(n): # n번 반복
        encoded = t.texts_to_sequences([current_word])[0] # 현재 단어에 대한 정수 인코딩
        encoded = pad_sequences([encoded], maxlen=5, padding='pre') # 데이터에 대한 패딩
        result = model.predict_classes(encoded, verbose=0)
    # 입력한 X(현재 단어)에 대해서 Y를 예측하고 Y(예측한 단어)를 result에 저장.
        for word, index in t.word_index.items(): 
            if index == result: # 만약 예측한 단어와 인덱스와 동일한 단어가 있다면
                break # 해당 단어가 예측 단어이므로 break
        current_word = current_word + ' '  + word # 현재 단어 + ' ' + 예측 단어를 현재 단어로 변경
        sentence = sentence + ' ' + word # 예측 단어를 문장에 저장
    # for문이므로 이 행동을 다시 반복
    sentence = init_word + sentence
    return sentence
print(sentence_generation(model, t, '경마장에', 4))
# '경마장에' 라는 단어 뒤에는 총 4개의 단어가 있으므로 4번 예측
경마장에 있는 말이 뛰고 있다
print(sentence_generation(model, t, '그의', 2)) # 2번 예측
그의 말이 법이다
print(sentence_generation(model, t, '가는', 5)) # 5번 예측
가는 말이 고와야 오는 말이 곱다





© 2018. by statssy

Powered by statssy