RNN을 활용한 로터스 뉴스 분류

  • 영국의 뉴스 통신사 로이터의 ’기사 내용’이 들어가면 어떤 ’주제’인지 분류하는 RNN 모델 실습

keras에서 제공하는 데이터셋 불러오기

  • 실제 뉴스 기사의 내용이 이미 단어단위로 토큰화, 라벨 인코딩 되어 저장이 된 상태이다.
  • 단어가 많은 경우 원핫인코딩 상태로 저장을 한다면, 저장공간 측면에서 매우 비효율적임
  • 또한, 텍스트 처리 분야에서는 문제 데이터에 라벨 인코딩을 활용하면 단어의 빈도수까지 고려해줄 수 있음
  • 1: 뉴스의 시작을 알리는 인덱스 번호
  • 2 : oov(Out of Vocabulary)로 사용
  • 3 ~ : 뉴스 전체 내용에서 빈도수 기준으로 랭크를 나타낸다
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from tensorflow.keras.datasets import reuters

data = reuters.load_data()
(X_train, y_train), (X_test, y_test) = data

X_train.shape, y_train.shape, X_test.shape, y_test.shape
((8982,), (8982,), (2246,), (2246,))

0번째 훈련 데이터 에는 87개의 단어가 포함되어 있다.

len(X_train[0])
87

로이터 뉴스 기사의 단어들이 어떤 숫자로 인코딩 되어 있는지 확인

  1. reuters.get_word_index()로 확인이 가능
    • key, value 쌍의 딕셔너리 형태
news_words = reuters.get_word_index()
news_words
{'mdbl': 10996,
 'fawc': 16260,
 'degussa': 12089,
 'woods': 8803,
 'hanging': 13796,
 ...}
  1. value값 기준으로 news_words 정렬하기
sorted(news_words.items(), key=lambda x : x[1])
[('the', 1),
 ('of', 2),
 ('to', 3),
 ('in', 4),
 ('said', 5),
 ...]
  1. 기사가 어떤 단어로 구성되어 있는지 단어들을 이어붙여 확인해보기
    • key값과 value 값의 위치를 교체
word_of_news = {}

for k, v in news_words.items():
    word_of_news[v] = k
  • 단어 이어 붙이기
print(' '.join([word_of_news[w] for w in X_train[0]]))
the wattie nondiscriminatory mln loss for plc said at only ended said commonwealth could 1 traders now april 0 a after said from 1985 and from foreign 000 april 0 prices its account year a but in this mln home an states earlier and rise and revs vs 000 its 16 vs 000 a but 3 psbr oils several and shareholders and dividend vs 000 its all 4 vs 000 1 mln agreed largely april 0 are 2 states will billion total and against 000 pct dlrs

뉴스 주제의 개수를 알아보기

  • 뉴스 카테고리 갯수 : 46가지
np.unique(y_train)
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
       17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
       34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45])

데이터 가공

  • timestpes를 설정해야 하는데 각 문제 데이터(X_train)가 포함하는 단어의 갯수는 모두 다르다.
  • 그러므로, 문제 데이터의 단어 개수를 같게(입력 시퀀스 맞춰주기) 맞춰줘야 학습이 가능하다.
  • 즉, 긴 기사는 잘라내고, 짧은 기사는 붙여 넣는 작업이 필요하다.

시퀀스 길이 맞추기

몇번 순환 시킬지(timesteps) 고정 시켜주기 위함 - 각 문제 데이터의 길이들을 불러와서 기술 통계값 확인해보기

# 각 문제 데이터의 길이 저장
train_len = [len(x) for x in X_train]

# 기술 통계값 확인
print(f'최댓값 : {max(train_len)}')
print(f'최솟값 : {min(train_len)}')
print(f'평균값 : {np.mean(train_len)}')
print(f'중앙값 : {np.median(train_len)}')
최댓값 : 2376
최솟값 : 13
평균값 : 145.5398574927633
중앙값 : 95.0
  • 히스토그램으로 데이터 밀도 구성 확인
  • x축은 뉴스 길이를 각 구간별로 표시, y축은 누적개수
plt.hist(train_len, bins=20)
plt.xlabel('news_len')
plt.ylabel('count')plt.show()

output_19_0.png

  • 라인 차트로 기사 별 길이 시각화
plt.figure(figsize=(15, 5))
plt.plot(train_len)
plt.xlabel('news_index')
plt.ylabel('news_len')
plt.show()

output_21_0.png

시퀀스 길이 맞추는 작업 수행

  • 기술통계값을 확인해본 결과 시퀀스의 길이를 120으로 맞추는게 적절하다고 생각함
  • 긴 기사는 잘라내고, 짧은 기사는 패딩 작업을 해주기
  • sequence.pad_sequences() : maxlen에 지정된 수만큼 길이를 앞에서부터 자르거나 패딩하여 맞춘다. 옵션을 주면 뒤에서 부터 가능
from tensorflow.keras.preprocessing import sequence

X_train_seq = sequence.pad_sequences(X_train, maxlen=120)
X_test_seq = sequence.pad_sequences(X_test, maxlen=120)
  • 각 단어 데이터는 라벨 인코딩 상태이므로 feature는 1이된다.
  • RNN 학습을 위해 input_shape을 맞춰주기 : (samples, timesteps, feature)
X_train_seq = X_train_seq.reshape(8982, 120, 1)
X_test_seq = X_test_seq.reshape(2246, 120, 1)

X_train_seq.shape, X_test_seq.shape
((8982, 120, 1), (2246, 120, 1))

SimpleRNN 모델링

  • 현재 한개의 feature는 단어의 빈도수를 의미하므로, 빈도수만을 이용해서 예측하는 RNN 모델을 설계하게 되는것이다
  • 다수 입력(input 120) 단일 출력(output 1) 형태
from tensorflow.keras import Sequential
from tensorflow.keras.layers import InputLayer, Dense, SimpleRNN
from tensorflow.keras.optimizers import Adam

model = Sequential()
model.add(InputLayer(input_shape=(120, 1)))
model.add(SimpleRNN(128))model.add(Dense(46, activation='softmax'))

model.compile(loss='sparse_categorical_crossentropy',
              optimizer=Adam(learning_rate=0.001),
              metrics=['acc'])
history = model.fit(X_train_seq, y_train, validation_split=0.2, batch_size=128, epochs=20)
plt.figure(figsize=(15, 5))
plt.plot(history.history['acc'], label='train_acc')
plt.plot(history.history['val_acc'], label='val_acc')
plt.legend()
plt.show()

output_29_0.png

데이터가 빈도수를 나타내는 feature만 존재하긴 하지만, SimpleRNN으로는 높은 성능을 얻기 힘들다는 것을 확인

LSTM 모델 학습

  • 다수 입력(input 120) 단일 출력(output 1)
  • LSTM을 여러 층으로 쌓기 위해서는 이전 LSTM층이 다수입력 다수출력 상태가 되어야한다.
  • 그래야 다음 LSTM층의 input 형태가 이전 LSTM층과 같은 형태가 되기 때문이다
  • LSTM, RNN층에서 순환할때마다 출력값을 만들어주는 (다수입력 다수출력 형태) return_sequences=True 옵션을 사용해야 한다.
from tensorflow.keras.layers import LSTM, GRU

model2 = Sequential()
model2.add(InputLayer(input_shape=(120, 1)))
model2.add(LSTM(128, return_sequences=True))
model2.add(LSTM(128))model2.add(Dense(46, activation='softmax'))

model2.compile(loss='sparse_categorical_crossentropy',
               optimizer=Adam(learning_rate=0.001),
               metrics=['acc'])
history2 = model2.fit(X_train_seq, y_train, validation_split=0.2, batch_size=128, epochs=20)
plt.figure(figsize=(15, 5))
plt.plot(history2.history['acc'], label='train_acc')
plt.plot(history2.history['val_acc'], label='val_acc')
plt.legend()
plt.show()

output_34_0.png

워드 임베딩(Word Embedding)

  • 언어 모델의 성능을 높이는 방법은 모델의 구조를 변경하는 방법과 단어의 표현방법을 고도화 하는방법이 있다,
  • 단어의 표현을 밀집되게 실수형태로 표현하게 하는 방법론이 워드 임베딩이다.
  • 학습을 통해서 각 단어들의 수치값을 정밀하게 만드는 방법이다.

데이터 불러올 때 등장 빈도가 낮은 단어 제거하기

  • num_words=1500 : reuters의 수치데이터 의미는 단어 빈도수 랭크이다. 해당 옵션을 주면 각 기사내용에 1500위까지의 단어만 가져오게 된다.
  • 1500위 외의 단어들은 oov 즉, 2값으로 표현된다.
from tensorflow.keras.layers import Embedding

(X_train, y_train), (X_test, y_test) = reuters.load_data(num_words=1500)

# 시퀀스 길이 맞추기
X_train_seq = sequence.pad_sequences(X_train, maxlen=120)
X_test_seq = sequence.pad_sequences(X_test, maxlen=120)

Embedding 추가 GRU 모델 설계

  • Embedding(사용하는 단어사전의 수, 한 단어를 표현할 특징의 수)을 가장 앞 층에 추가하기
  • 사용하는 단어 사전의 수는 학습에 사용되는 단어의 갯수이다
  • 한 단어를 표현할 특징의 수는 적절한 수로 설정해서 모델이 알아서 특징을 검출하게 해준다
embedding_model = Sequential()

embedding_model.add(Embedding(1500, 50))
embedding_model.add(GRU(128, return_sequences=True))
embedding_model.add(GRU(128))
embedding_model.add(Dense(46, activation='softmax'))
embedding_model.compile(loss='sparse_categorical_crossentropy',
                        optimizer=Adam(learning_rate=0.001),
                        metrics=['acc'])
history3 = embedding_model.fit(X_train_seq, y_train, epochs=20, validation_split=0.2, batch_size=128)
plt.figure(figsize=(15, 5))
plt.plot(history3.history['acc'], label='train_acc')
plt.plot(history3.history['val_acc'], label='val_acc')
plt.legend()
plt.show()

output_40_0.png

댓글남기기