본문 바로가기
AI 빅데이터/후려치는 데이터분석과 AI 알고리즘

[자연어처리] 텍스트 생성으로 이해하는 RNN

by 마고커 2020. 8. 2.


이미 오래전 기술이지만, 간단한 텍스트 생성 예제를 통해 RNN 활용(!)을 이해해본다. 아래 링크와 전적으로 동일한 것이며, 개인의 이해를 위해 정리해 두는 것 뿐임. 링크를 통해 이해하는 것이 더 확실하다.

 

 

위키독스

온라인 책을 제작 공유하는 플랫폼 서비스

wikidocs.net

아래 세 문장으로 학습을 하고, 첫 단어를 입력하면 나머지 문장을 만들어 주는 것이다.

 

경마장에 있는 말이 뛰고 있다
그의 말이 법이다
가는 말이 고와야 오는 말이 곱다

 

RNN의 구조

 

이전에도 이야기했듯이 수식으로 이해할 생각은 전혀 없고, 활용에만 집중하고 싶다. 위의 그림을 간단히 이해하자면 다음 Step의 결과는 현재의 입력과 직전의 Hidden State가 영향을 준다는 것이다. 얼마나 영향을 주는 지를 나타내는 Weight를 찾아 모델을 구성해 주는 것으로 이는 라이브러리가 해 주니 간단히 넘어가자. 

 

이전 포스팅에서도 이해했듯이 자연어처리는 1) 데이터를 읽어들여 이를 토큰화하고 인덱싱 한뒤, 2) 임베딩하여 모델을 구성하는 단계로 구성된다. 우선 세 문장을 읽어들여 사전을 만든다. 

 

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()

vocab_size = len(t.word_index)+1
t.word_index

==> 

{'말이': 1,
 '경마장에': 2,
 '있는': 3,
 '뛰고': 4,
 '있다': 5,
 '그의': 6,
 '법이다': 7,
 '가는': 8,
 '고와야': 9,
 '오는': 10,
 '곱다': 11}

 

사전을 만들었으니, 자연어 처리를 위해 각 문장의 단어를 인덱싱하고 모두 길이가 같도록 패딩한다.

 

sequences = list()

// texts_to_sequences가 단어를 인덱스로 바꿈
// RNN은 문장 내 단어의 순서를 학습하는 것이므로 한 문장이 이루어지기까지의 순서를 X로 활용한다.
// 예) 그의 말이 법이다는 '그의' '말이'와 '그의' '말이' '법이다'의 두 개의 데이터가 된다.
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)

// 제일 긴 문장의 단어수가 최대 길이
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]], dtype=int32)
       
// 마지막 칼럼의 값이 다음에 올 거라 예상되는 단어, 즉 Y값이 된다.
// 예) '그의' '말이' 라는 데이터에서 '말이'가 Y값이 된다.

X = sequences[:,:-1]
y = sequences[:, -1]

// 학습을 위해 Y를 One-Hot Vector로 변경
y = to_categorical(y, num_classes = vocab_size)

==>

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)

 

데이터 준비가 되었으므로 Keras를 이용해 모델을 만들어 준다.

 

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))
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)

 

학습 문장이 적어 순식간에 학습이 마쳐진다. 학습 결과를 간단히 테스트 해 보자

 

// 단어를 indexing하고 패딩해서 배열을 만든 후 테스트
encoded = t.texts_to_sequences(['경마장에'])[0] # 현재 단어에 대한 정수 인코딩
encoded = pad_sequences([encoded], maxlen=5, padding='pre') # 데이터에 대한 패딩
model.predict_classes(encoded)

==>

array([3])

 

위의 사전에 배열 세번째는 '있는'으로 우리가 학습한 '경마장에 있는 말이 뛰고 있다'를 고려하면 '경마장에' 다음의 단어를 제대로 찾았다. 이를 문장으로 구성할 수 있게 함수를 만들어본다.

 

def sentence_generation(model, token, current_word, n_word):
    init_word = current_word
    sentence = ''
    
    for _ in range(n_word):
        encoded = token.texts_to_sequences([current_word])[0] # 현재 단어에 대한 정수 인코딩
        encoded = pad_sequences([encoded], maxlen=5, padding='pre') # 데이터에 대한 패딩
        result = model.predict_classes(encoded)
        
        for word, index in token.word_index.items():
            if index == result:
                break;
        
        // 현재 단어 그룹에 위에서 찾은 단어를 덧붙여 n_word만큼 학습한다.
        // '그의' -> '그의', '말이'
        current_word = current_word + ' ' + word
        sentence = sentence + ' ' + word
    
    sentence = init_word + sentence
    return sentence
    
sentence_generation(model, t, '그의',2)

==> '그의 말이 법이다'

sentence_generation(model, t, '경마장에',4)

==> '경마장에 있는 말이 뛰고 있다'

 

너무 간단해서 AI를 써야하는 일인지 헷갈리겠지만, 기본 구조만 이해하고 가자.

 

※ Disclaimer: 본인은 AI알고리즘을 연구하지 않습니다. 의미 부분만 대충 파악하고 싶어서 정리하는 것이고 내용에 다소 때로는 상당히 오류가 있을 수 있으며, 아주아주 단편적 내용만 담고 있습니다. 참고만 하시고 각자 열심히 공부하시길 바랍니다 :D



댓글