다 대 일(many-to-one) 구조의 RNN을 사용하여 문맥을 반영해서 텍스트를 생성하는 모델
경마장에 있는 말이 뛰고 있다와 그의 말이 법이다와 가는 말이 고와야 오는 말이 곱다라는 세 가지 문장이 있다고 가정| samples | ||
|---|---|---|
| 1. | 경마장에 | 있는 |
| 2. | 경마장에 있는 | 말이 |
| 3. | 경마장에 있는 말이 | 뛰고 |
| 4. | 경마장에 있는 말이 뛰고 | 있다 |
| 5. | 그의 | 말이 |
| 6. | 그의 말이 | 법이다 |
| 7. | 가는 | 말이 |
| 8. | 가는 말이 | 고와야 |
| 9. | 가는 말이 고와야 | 오는 |
| 10. | 가는 말이 고와야 오는 | 말이 |
| 11. | 가는 말이 고와야 오는 말이 | 곱다 |
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'): # Wn을 기준으로 문장 토큰화
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))
학습에 사용할 샘플의 개수: 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')
# 인자 'pre'는 길이가 6보다 짧은 샘플의 앞을 0을 채움
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.] # 3에 대한 원-핫 벡터
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.] # 1에 대한 원-핫 벡터
[0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] # 4에 대한 원-핫 벡터
[0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0.] # 5에 대한 원-핫 벡터
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.] # 1에 대한 원-핫 벡터
[0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.] # 7에 대한 원-핫 벡터
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.] # 1에 대한 원-핫 벡터
[0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.] # 9에 대한 원-핫 벡터
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.] # 10에 대한 원-핫 벡터
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.] # 1에 대한 원-핫 벡터
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]] # 11에 대한 원-핫 벡터
RNN 모델에 데이터 훈련
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)
임베딩 벡터는 10차원, 32의 은닉 상태 크기 가짐
Epoch 1/200
- 1s - loss: 2.4945 - acc: 0.0909
... 중략 ...
Epoch 200/200
- 0s - loss: 0.1299 - acc: 1.0000
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번 예측
경마장에 있는 말이 뛰고 있다
한계: 이 모델은 충분한 훈련 데이터를 갖고 있지 못하므로 위에서 문장의 길이에 맞게 적절하게 예측해야하는 횟수 4를 인자값으로 줌. 4 이상의 숫자 주면 '있다' 다음의 단어가 무엇인지 배운 적이 없으므로 임의 예측을 함
딥러닝을 이용한 자연어 처리
https://wikidocs.net/45101