Text Generation using LSTM

( 참고 : “딥러닝을 이용한 자연어 처리 입문” (https://wikidocs.net/book/2155) )

Text Generation, 말 그대로 텍스트를 생성해내는 것이다.

예를 들면, “내가 제일 좋아하는 음식은~”이라는 말까지 했을 때, 이어지는 말 혹은 단어로는 “사과, 딸기, 당근”등과 같이 “음식의 종류”가 나올 것이라는 것을 우리는 알 수 있다. 이를 RNN, LSTM등을 활용하여 예측해볼 것이다.

저번 포스트와의 차이점은, RNN 대신 LSTM 을 사용했다는 점과, 데이터가 다르다는 점 말고는 전부 동일하다.

1. Data preprocessing

import pandas as pd
import numpy as np
from string import punctuation
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.utils import to_categorical

2018년 4월에 작성된 기사에 관련된 데이터이다.

df = pd.read_csv('ArticlesApril2018.csv')


총 1324편의 기사가 있고, 다음과 같은 column들을 가지고 있다.

df.shape, df.columns
((1324, 15),
 Index(['articleID', 'articleWordCount', 'byline', 'documentType', 'headline',
        'keywords', 'multimedia', 'newDesk', 'printPage', 'pubDate',
        'sectionName', 'snippet', 'source', 'typeOfMaterial', 'webURL'],
       dtype='object'))


우선, 모든 기사에 headline이 있다는 것을 확인했다.

df['headline'].isnull().values.any()
False


기사들의 headline만을 따와서 (‘Unknown’이라고 적힌 것 제외) list에 담아둔다.

headline = []
headline.extend(list(df.headline.values))
headline = [n for n in headline if n!= 'Unknown']


첫 5개의 기사의 headline은 다음과 같다.

headline[:5]
['Former N.F.L. Cheerleaders’ Settlement Offer: $1 and a Meeting With Goodell',
 'E.P.A. to Unveil a New Rule. Its Effect: Less Science in Policymaking.',
 'The New Noma, Explained',
 'How a Bag of Texas Dirt  Became a Times Tradition',
 'Is School a Place for Self-Expression?']


headline들 중, punctuation(‘,’,.’등 )을 모두 제거하고, 대문자는 소문자로 바꿔준다.

def repreprocessing(s):
    s = s.encode('utf8').decode('ascii','ignore')
    return ''.join(c for c in s if c not in punctuation).lower()

text = [repreprocessing(x) for x in headline]


기본적인 전처리를 완료한 이후, 다음과 같이 모든 단어들을 tokenize 해준다.

( vocab size에 1을 더하는 이유? Keras의 Tokenizer의 정수 인코딩은 index가 1부터 시작하기 때문에! )

t = Tokenizer()
t.fit_on_texts(text)
vocab_size = len(t.word_index) + 1


sequence라는 list를 생성하여, (위에서 생성한 text-index) 딕셔너리를 통해 인코딩 된 것들을 sequence에 넣어준다.

sequences = list()

for line in text:
    encoded = t.texts_to_sequences([line])[0]
    for i in range(1,len(encoded)):
        sequence = encoded[:i+1]
        sequences.append(sequence)


sequence list의 첫 10개의 원소를 보면 다음과 같다.

sequences[:10]
[[99, 269],
 [99, 269, 371],
 [99, 269, 371, 1115],
 [99, 269, 371, 1115, 582],
 [99, 269, 371, 1115, 582, 52],
 [99, 269, 371, 1115, 582, 52, 7],
 [99, 269, 371, 1115, 582, 52, 7, 2],
 [99, 269, 371, 1115, 582, 52, 7, 2, 372],
 [99, 269, 371, 1115, 582, 52, 7, 2, 372, 10],
 [99, 269, 371, 1115, 582, 52, 7, 2, 372, 10, 1116]]


다음을 통해 각 단어들이 어떻게 index와 매칭이 되는지를 확인할 수 있다.

t.word_index.items()
dict_items([('the', 1), ('a', 2), ('to', 3), ('of', 4), ('in', 5), ('for', 6), ('and', 7), ('is', 8), ('on', 9), ('with', 10), ('trump', 11), ('as', 12), ('at', 13), ('new', 14), ('how', 15), ('from', 16), ('it', 17), ('an', 18), ('that', 19), ('be', 20), ('season', 21), ('us', 22), ('you', 23), ('its', 24), ('what', 25), ...


여기서 indexing된 숫자는, 가장 많이 등장하는 단어부터 오름차순으로 인덱싱이 된다.

( 예를 들면, ‘the’는 가장 많이 등장한 단어이기 때문에 인덱스 ‘1’이 부여된 것이다. )

index_to_word = {}
for key,value in t.word_index.items():
    index_to_word[value] = key

print('빈도수 상위 100등 단어 : {}'.format(index_to_word[100]))
빈도수 상위 100등 단어 : epa


가장 길었던 문장의 단어 수를 보면, 24 단어라는 것을 알 수 있다.

max_len = max(len(l) for l in sequences)
max_len
24

이 24를 길이로하는 padding을 만들어준다.

sequences = pad_sequences(sequences, maxlen=max_len, padding='pre')


가장 마지막 단어를 y로 하고, 그 이전까지 모든 부분을 X로 한다.

sequences = np.array(sequences)
X = sequences[:,:-1]
y = sequences[:,-1]


지금 우리가 위에서 만들어낸 숫자들은 사실 ‘숫자(numeric data)’로서의 의미를 가지고 있지 않다. 하나의 단어를 상징하는 하나의 ‘문자 같은’ 숫자일 뿐이다. 따라서 One-Hot-Encoding을 해준다.

y = to_categorical(y, num_classes=vocab_size)


2. Modeling

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding,Dense,LSTM

model = Sequential()를 통해 처음 (아무것도 없는) 모델을 만들어 주고,

이 model에 'add'를 통해 layer를 쌓는 방식으로 모델을 만들 것이다.

  • 1) Embedding Layer ( 10차원으로 임베딩할 것이다)
  • 2) LSTM ( 128개의 neuron 사용 )
  • 3) Dense ( Classification 문제이기 때문에 softmax 함수를 사용한다 )
model = Sequential()
model.add(Embedding(vocab_size,10,input_length=max_len-1))
model.add(LSTM(128))
model.add(Dense(vocab_size, activation='softmax'))


model.summary를 통해, 우리가 짠 모델의 architecture를 확인할 수 있다.

model.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_1 (Embedding)      (None, 23, 10)            34940     
_________________________________________________________________
lstm_1 (LSTM)                (None, 128)               71168     
_________________________________________________________________
dense_1 (Dense)              (None, 3494)              450726    
=================================================================
Total params: 556,834
Trainable params: 556,834
Non-trainable params: 0
_________________________________________________________________


  • loss = categorical crossentropy
  • optimizer = adam optimizer
  • 평가 metric = accuracy
  • epoch = 200
model.compile(loss='categorical_crossentropy', optimizer='adam',metrics=['accuracy'])
model.fit(X,y,epochs=200,verbose=2)
Train on 7803 samples
Epoch 1/200
7803/7803 - 13s - loss: 7.6463 - accuracy: 0.0299
Epoch 2/200
7803/7803 - 10s - loss: 7.1249 - accuracy: 0.0300
Epoch 3/200
7803/7803 - 10s - loss: 6.9879 - accuracy: 0.0323
...
Epoch 199/200
7803/7803 - 10s - loss: 0.2934 - accuracy: 0.9117
Epoch 200/200
7803/7803 - 10s - loss: 0.2968 - accuracy: 0.9104


3. Sentence generation function (문장 생성 함수)

지금까지 우리는 문장의 어느 부분까지를 input으로 넣었을 떄, 그 뒤에 이어질 단어를 예측하는 모델을 만들었다. 이를 통해, ‘문장 생성 함수’를 만들 수 있다.

sentence_generation(model, t, current_word, n) 함수는, 이전 포스트 참조!


sentence_generation(model, t, 'i', 10)
'i want to be rich and im not sorry attack when'


sentence_generation(model, t, 'how', 10)
'how to make facebook more accountable attracts talk can a pulitzer'