자연어 처리 - 간단한 RNN 예제
간단한 RNN 예제
RNN(Recurrent Neural Network)
text = '''저 별을 따다가 니 귀에 걸어주고파
저 달 따다가 니 목에 걸어주고파
세상 모든 좋은 것만 해주고 싶은
이런 내 맘을 그댄 아나요'''
저? -> 별을
저 별을? -> 따다가
저 별을 따다가? -> 니
저 별을 따다가 니 귀에? -> 걸어주고파
다음에 올 단어를 예측! -> RNN
데이터 전처리
import numpy as np
from keras.preprocessing.text import Tokenizer
from keras.utils import to_categorical, pad_sequences
단어 정수화
tok = Tokenizer()
tok.fit_on_texts([text])
tok.word_index
{'저': 1,
'따다가': 2,
'니': 3,
'걸어주고파': 4,
'별을': 5,
'귀에': 6,
'달': 7,
'목에': 8,
'세상': 9,
'모든': 10,
'좋은': 11,
'것만': 12,
'해주고': 13,
'싶은': 14,
'이런': 15,
'내': 16,
'맘을': 17,
'그댄': 18,
'아나요': 19}
vocab_size = len(tok.word_index) + 1 # zero padding용 공간 추가
vocab_size
20
문단 정수화
for sentence in text.split('\n'):
res = tok.texts_to_sequences([sentence])[0]
print(res)
[1, 5, 2, 3, 6, 4]
[1, 7, 2, 3, 8, 4]
[9, 10, 11, 12, 13, 14]
[15, 16, 17, 18, 19]
저? -> 별을
저 별을? -> 따다가
저 별을 따다가? -> 니
저 별을 따다가 니 귀에? -> 걸어주고파
# 다음 단어를 유추할 수 있게 확장
seq_list = []
for sentence in text.split('\n'):
res = tok.texts_to_sequences([sentence])[0]
for i in range(1, len(res)):
seq = res[:i+1]
seq_list.append(seq)
seq_list
[[1, 5],
[1, 5, 2],
[1, 5, 2, 3],
[1, 5, 2, 3, 6],
[1, 5, 2, 3, 6, 4],
[1, 7],
[1, 7, 2],
[1, 7, 2, 3],
[1, 7, 2, 3, 8],
[1, 7, 2, 3, 8, 4],
[9, 10],
[9, 10, 11],
[9, 10, 11, 12],
[9, 10, 11, 12, 13],
[9, 10, 11, 12, 13, 14],
[15, 16],
[15, 16, 17],
[15, 16, 17, 18],
[15, 16, 17, 18, 19]]
# 최대 문장 길이 구하기
max_len = max(len(sent) for sent in seq_list)
max_len
6
zero padding
seq_padded = pad_sequences(seq_list, maxlen = max_len)
seq_padded
array([[ 0, 0, 0, 0, 1, 5],
[ 0, 0, 0, 1, 5, 2],
[ 0, 0, 1, 5, 2, 3],
[ 0, 1, 5, 2, 3, 6],
[ 1, 5, 2, 3, 6, 4],
[ 0, 0, 0, 0, 1, 7],
[ 0, 0, 0, 1, 7, 2],
[ 0, 0, 1, 7, 2, 3],
[ 0, 1, 7, 2, 3, 8],
[ 1, 7, 2, 3, 8, 4],
[ 0, 0, 0, 0, 9, 10],
[ 0, 0, 0, 9, 10, 11],
[ 0, 0, 9, 10, 11, 12],
[ 0, 9, 10, 11, 12, 13],
[ 9, 10, 11, 12, 13, 14],
[ 0, 0, 0, 0, 15, 16],
[ 0, 0, 0, 15, 16, 17],
[ 0, 0, 15, 16, 17, 18],
[ 0, 15, 16, 17, 18, 19]])
# 앞의 단어들(X)이 오면 뒤에 올 단어(y)가 온다고 학습
X = seq_padded[:, :-1]
y = seq_padded[:, -1]
X, y
(array([[ 0, 0, 0, 0, 1],
[ 0, 0, 0, 1, 5],
[ 0, 0, 1, 5, 2],
[ 0, 1, 5, 2, 3],
[ 1, 5, 2, 3, 6],
[ 0, 0, 0, 0, 1],
[ 0, 0, 0, 1, 7],
[ 0, 0, 1, 7, 2],
[ 0, 1, 7, 2, 3],
[ 1, 7, 2, 3, 8],
[ 0, 0, 0, 0, 9],
[ 0, 0, 0, 9, 10],
[ 0, 0, 9, 10, 11],
[ 0, 9, 10, 11, 12],
[ 9, 10, 11, 12, 13],
[ 0, 0, 0, 0, 15],
[ 0, 0, 0, 15, 16],
[ 0, 0, 15, 16, 17],
[ 0, 15, 16, 17, 18]]),
array([ 5, 2, 3, 6, 4, 7, 2, 3, 8, 4, 10, 11, 12, 13, 14, 16, 17,
18, 19]))
label값의 원-핫 인코딩
y_hot = to_categorical(y, num_classes=vocab_size)
y_hot
array([[0., 0., 0., 0., 0., 1., 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., 0.,
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., 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., 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.],
[0., 0., 1., 0., 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.,
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., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 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., 0., 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., 0., 0., 0., 1., 0., 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., 0., 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., 0., 0., 0., 0.,
1., 0., 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., 0., 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.,
0., 0., 0., 1.]], dtype=float32)
딥러닝
모델 정의, 학습
from keras.models import Sequential
from keras.layers import Embedding, Dense, SimpleRNN
model = Sequential([
Embedding(vocab_size, 10), # vocab_size개의 단어를 10개의 특성으로 나타냄-다음 단어를 유추하기 위한 단어배치 Embedding (Word2Vec의 vector_size)
SimpleRNN(32),
Dense(vocab_size, activation='sigmoid')
])
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
embedding (Embedding) (None, None, 10) 200
simple_rnn (SimpleRNN) (None, 32) 1376
dense (Dense) (None, 20) 660
=================================================================
Total params: 2,236
Trainable params: 2,236
Non-trainable params: 0
_________________________________________________________________
model.compile(loss='sparse_categorical_crossentropy',
optimizer='adam',
metrics=['accuracy'])
history = model.fit(X, y, epochs=200, verbose=1)
Epoch 1/200
1/1 [==============================] - 1s 923ms/step - loss: 2.9929 - accuracy: 0.0526
Epoch 2/200
1/1 [==============================] - 0s 4ms/step - loss: 2.9843 - accuracy: 0.0526
Epoch 3/200
1/1 [==============================] - 0s 7ms/step - loss: 2.9757 - accuracy: 0.1053
Epoch 4/200
1/1 [==============================] - 0s 4ms/step - loss: 2.9671 - accuracy: 0.1053
Epoch 5/200
1/1 [==============================] - 0s 5ms/step - loss: 2.9583 - accuracy: 0.1053
...
Epoch 196/200
1/1 [==============================] - 0s 5ms/step - loss: 0.3381 - accuracy: 0.9474
Epoch 197/200
1/1 [==============================] - 0s 5ms/step - loss: 0.3345 - accuracy: 0.9474
Epoch 198/200
1/1 [==============================] - 0s 3ms/step - loss: 0.3310 - accuracy: 0.9474
Epoch 199/200
1/1 [==============================] - 0s 3ms/step - loss: 0.3275 - accuracy: 0.9474
Epoch 200/200
1/1 [==============================] - 0s 5ms/step - loss: 0.3241 - accuracy: 0.9474
import matplotlib.pyplot as plt
plt.plot(history.history['accuracy'])
[<matplotlib.lines.Line2D at 0x22c66f06670>]
예측
start_text = '이런 내 맘을 그댄' # expect: 아나요
encoded = tok.texts_to_sequences([start_text])[0]
encoded
[15, 16, 17, 18]
pedded = pad_sequences([encoded], maxlen=max_len)
pedded
array([[ 0, 0, 15, 16, 17, 18]])
res = model.predict(pedded, verbose=1)
res
1/1 [==============================] - 0s 151ms/step
array([[0.23961598, 0.5145148 , 0.23955514, 0.47244322, 0.64895254,
0.10036285, 0.8044668 , 0.11209651, 0.5889684 , 0.44754112,
0.19595115, 0.24980979, 0.5465937 , 0.86290205, 0.06897352,
0.5562962 , 0.23613842, 0.04350452, 0.5932769 , 0.99096566]],
dtype=float32)
res_softmax = np.argmax(res, axis=1)
res_softmax
array([19], dtype=int64)
start_text, *tok.sequences_to_texts([res_softmax])
('이런 내 맘을 그댄', '아나요')
start_text = '저 별을 따다가 니' # expect: 귀에
encoded = tok.texts_to_sequences([start_text])[0]
pedded = pad_sequences([encoded], maxlen=max_len)
res = model.predict(pedded, verbose=1)
res_softmax = np.argmax(res, axis=1)
print(f'{start_text}:', *tok.sequences_to_texts([res_softmax]))
1/1 [==============================] - 0s 17ms/step
저 별을 따다가 니: 귀에
def word_predict(string):
start_text = string
encoded = tok.texts_to_sequences([start_text])[0]
pedded = pad_sequences([encoded], maxlen=max_len)
res = model.predict(pedded, verbose=1)
res_softmax = np.argmax(res, axis=1)
print(f'{start_text}:', *tok.sequences_to_texts([res_softmax]))
word_predict('저 별을 따다가 니')
1/1 [==============================] - 0s 15ms/step
저 별을 따다가 니: 귀에
단순히 어떤 단어가 뒤에 올지에 대한 특성이므로 Embedding으로 문맥을 학습하기는 제한적이다.
원-핫 인코딩한 label로 학습
model2 = Sequential([
Embedding(vocab_size, 10), # vocab_size개의 단어를 10개의 특성으로 나타냄-다음 단어를 유추하기 위한 단어배치 Embedding (Word2Vec의 vector_size)
SimpleRNN(32),
Dense(vocab_size, activation='softmax')
])
model2.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
embedding_1 (Embedding) (None, None, 10) 200
simple_rnn_1 (SimpleRNN) (None, 32) 1376
dense_1 (Dense) (None, 20) 660
=================================================================
Total params: 2,236
Trainable params: 2,236
Non-trainable params: 0
_________________________________________________________________
model2.compile(loss='categorical_crossentropy',
optimizer='adam',
metrics=['accuracy'])
history2 = model2.fit(X, y_hot, epochs=200, verbose=1)
Epoch 1/200
1/1 [==============================] - 1s 807ms/step - loss: 2.9847 - accuracy: 0.1053
Epoch 2/200
1/1 [==============================] - 0s 5ms/step - loss: 2.9756 - accuracy: 0.1053
Epoch 3/200
1/1 [==============================] - 0s 4ms/step - loss: 2.9664 - accuracy: 0.2105
Epoch 4/200
1/1 [==============================] - 0s 4ms/step - loss: 2.9572 - accuracy: 0.2105
Epoch 5/200
1/1 [==============================] - 0s 6ms/step - loss: 2.9479 - accuracy: 0.2105
...
Epoch 196/200
1/1 [==============================] - 0s 4ms/step - loss: 0.2396 - accuracy: 0.9474
Epoch 197/200
1/1 [==============================] - 0s 5ms/step - loss: 0.2371 - accuracy: 0.9474
Epoch 198/200
1/1 [==============================] - 0s 4ms/step - loss: 0.2348 - accuracy: 0.9474
Epoch 199/200
1/1 [==============================] - 0s 6ms/step - loss: 0.2324 - accuracy: 0.9474
Epoch 200/200
1/1 [==============================] - 0s 3ms/step - loss: 0.2301 - accuracy: 0.9474
plt.plot(history2.history['accuracy'])
[<matplotlib.lines.Line2D at 0x22c6987fdf0>]
댓글남기기