[코드 정리] RNN을 이용한 텍스트 생성(Text Generation using RNN)
앞서 배운 RNN을 이용해서 텍스트 생성 모델을 만들어본다.
참고 사이트 : 딥 러닝을 이용한 자연어 처리 입문 에 대한 글을 요약하였다.
다대일 RNN 사용하여 텍스트를 생성하는 모델 만들기
1) 데이터에 대한 이해와 전처리
- 아래의 11가지 샘플을 구성하여 다음 텍스트를 생성하는 모델을 만들어 보자
samples | X | y |
---|---|---|
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번 예측
가는 말이 고와야 오는 말이 곱다