머신러닝 | 딥러닝

Deep learning[딥러닝] 심플순환신경망(simple RNN)

멈머이 2024. 1. 8. 23:40
728x90

<순환신경망(RNN; Recurrent Neural Network)>

  - RNN은 텍스트 처리를 위해 고안된 모델 (계층)
  - 바로 이전의 데이터를 재사용하는 신경망 계층임

 


<순환신경망 종류>

  - 심플 순환신경망(simple RNN)
  - 장가기억 순환신경망(LSTM)
  - 게이트웨이 반복 순환신경망(GRU)

 

* 사용 라이브러리

import tensorflow as tf
from tensorflow import keras

### 사용할 데이터셋
from tensorflow.keras.datasets import imdb

import numpy as np

<IMDB : 영화 리뷰 감상평 데이터>
  - 순환신경망에서 대표적으로 사용되는 데이터셋(외국)
  - 케라스에서 영어로 된 문장을 정수(숫자)로 변환하여 제공하는 데이터셋
  - 감상평을 긍정과 부정으로 라벨링 되어있음
  - 총 50,000개의 샘플로 되어 있으며, 훈련 및 테스트로 각각 25,000개씩 분리하여 제공

 

 

* IMDB 데이터 읽어 들이기

imdb.load_data(num_words=500)

  - num_words=500 : 말뭉치 사전의 개수 500개만 추출하겠다는 의미


* 데이터 분리

(train_input, train_target), (test_input, test_target) = imdb.load_data(num_words=500)

print(f"{train_input.shape} / {train_target.shape}")
print(f"{test_input.shape} / {test_target.shape}")
train_target

결과 : array([1, 0, 0, ..., 0, 1, 0], dtype=int64) 

- 0은 부정 1은 긍정

from sklearn.model_selection import train_test_split
train_input, val_input, train_target, val_target = train_test_split(train_input, train_target,
                                                                   test_size=0.2, random_state=42)

- 훈련 : 검증 = 8 : 2로 분리하기

 

print(f"{train_input.shape} / {train_target.shape}")
print(f"{val_input.shape} / {val_target.shape}")
print(f"{test_input.shape} / {test_target.shape}")

결과 : 

(20000,) / (20000,)
(5000,) / (5000,)
(25000,) / (25000,)

 

 

* 정규화 (텍스트 데이터)

  - 텍스트 기반의 데이터인 경우 정규화는 스케일링 처리가 아닌 문자열의 길이를 통일시키는 처리를 진행한다.
  - 훈련 모델은 정해진 행렬의 사이즈를 기준으로 훈련하기 때문에

 

* 훈련 독립변수의 각 데이터(값)의 길이를 배열(리스트) 형태로 추출하기

i = 0
lengths = []
for i in train_input:
    lengths.append(len(i))

lengths = np.array(lengths)
lengths

결과 : array([259, 520, 290, ..., 300,  70,  77])

 

 

* lengths의 값을 이용해서 전체 평균과 중앙값 추출하기

np.mean(lengths), np.median(lengths)

결과 : (239.00925, 178.0)

 

 

* 시각화

단어 개수의 분포를 이용해서
  - 훈련에 사용할 독립변수 각 값들의 길이 기준 정의
  - 전체적으로 왼쪽 편에 집중되어 있으며, X축 125 정도에 많은 빈도를 나타내고 있음
  - 따라서 독립변수의 각 값들의 길이를 100으로 통일 (정규화)

import matplotlib.pyplot as plt
from matplotlib import font_manager, rc
plt.rc("font", family="Malgun Gothic")

plt.grid()
plt.title("택스트 길이 분포 확인")
plt.hist(lengths)
plt.xlabel("length(단어개수)")
plt.ylabel("빈도")

plt.show()


* 각 데이터의 길이를 100으로 통일

"""택스트 길이 정규화 라이브러리"""
from tensorflow.keras.preprocessing.sequence import pad_sequences
train_seq = pad_sequences(train_input, maxlen=100)
train_seq.shape

결과 : (20000, 100)

훈련 독립변수 각 데이터 100개로 통일시키기
  - pad_sequences : 텍스트의 길이를 maxlen 개수로 통일시키기
  - maxlen보다 작으면 0으로 채우고, 크면 제거한다.
  - 결괏값은 => 2차원 리스트로 반환.

 

 

* 검증 데이터도 길이 100으로 통일(정규화) 시키기

val_seq = pad_sequences(val_input, maxlen=100)
val_seq.shape

결과 : (5000, 100)

 

 

train_seq = pad_sequences(train_input, maxlen=100,
                         truncating="pre",
                         padding="pre")

텍스트 길이 조정 속성(매개변수)
  * truncating : 추출 위치 (앞 또는 뒤부터)
    - pre : 뒤쪽부터 추출하기 (기본값), 앞쪽 제거하기
    - post : 앞쪽부터 추출하기, 뒤쪽 제거하
    
  * padding : 채울 위치(앞 또는 뒤부터)
    - pre : 앞쪽을 0으로 채우기(기본값)
    - post : 뒤쪽을 0으로 채우기


 

Simple RNN(심플 순환신경망)

 

* 모델 생성하기

model = keras.Sequential()
model

 

 


* 계층 생성 및 모델에 추가하기
  - input_shape=(100, 500) : 100은 특성 개수, 500은 말뭉치 개수

"""입력계층이면서 RNN 계층"""
model.add(keras.layers.SimpleRNN(8, input_shape=(100, 500)))

"""출력계층"""
model.add(keras.layers.Dense(1, activation="sigmoid"))

<RNN에서 사용할 독립변수 처리방식>

  - RNN 모델에서는 독립변수의 데이터를 => 원-핫 인코딩 데이터 또는 임베딩 처리를 통해서 훈련시켜야 한다.

 


<원-핫(One-Hot) 인코딩 방식>

  - 각 데이터(값) 중에 1개의 단어당 분석을 위해 500개의 말뭉치와 비교하여야 함
  - 이때 비교하기 위해 원-핫 인코딩으로 변환하여 비교하는 방식을 따름
  - keras.util.to_catergoical() 함수 사용
  - 프로그램을 통해 변환해야 함.(별도 계층이 존재하지는 않음)
  - 각 단어별로 원-핫 인코딩 처리가 되기에 데이터가 많아지며, 속도가 느림
  - 데이터가 많아지기 때문에 많은 메모리 공간을 차지함
  

 

<단어 임베딩(Embedding) 방식>

  - 원-핫 인코딩의 느린 속도르 개선하기 위하여 개선된 방식
  - 많은 메모리 공간을 차지하지 않음.
  - keras.layers.Embedding(). 계층을 사용함(프로그램 처리방식이 아님)


* 원-핫 인코딩 데이터로 변환하기

train_oh = keras.utils.to_categorical(train_seq)
val_oh = keras.utils.to_categorical(val_seq)

train_oh.shape, val_oh.shape

결과 : ((20000, 100, 500), (5000, 100, 500))  

  - to_categorical() : 훤-핫 인코딩 함수
  - 말뭉치 개수만큼 차원이 발생함 (총 3차원)

 

 

model.summary()

 

 

* 콜백함수 정의 하기

* 모델 설정하기
  - 순환신경망(RNN)에서 주로 사용되는 옵티마이저는 RMSprop
  -RMSprop : 먼 거리는 조금 반영, 가까운 거리는 많이 반영하는 개념을 적용함

rmsprop = keras.optimizers.RMSprop(learning_rate=0.0001)
model.compile(optimizer=rmsprop,
              loss="binary_crossentropy",
              metrics=["accuracy"])

 

 

* 자동 저장 및 종료하는 콜백함수 정의 하기

model_path = "./model/best_simpleTNN_model.h5"


checkpoint_cb = keras.callbacks.ModelCheckpoint(
    model_path,
    save_best_only=True
)

early_stopping_cb = keras.callbacks.EarlyStopping(
    patience=3,
    restore_best_weights=True
)

checkpoint_cb, early_stopping_cb

 

 

* 모델 훈련시키기

history = model.fit(
    train_oh, train_target, epochs=100, batch_size=64,
    validation_data=(val_oh, val_target),
    callbacks=[checkpoint_cb, early_stopping_cb]
)

 - batch_size : 훈련 시 데이터를 배치사이즈만큼 잘라서 종속변수와 검증 진행
   - 배치사이즈만큼 종속변수와 비교 시 틀리게 되면, 다음 배치사이즈에서 오류를 줄여가면서 훈련을 진행하게 된다.
   - 배치사이즈와 값을 정의하지 않으면 전체 데이터를 기준으로 종속변수와 비교 및 훈련 반복 시 오류 조정이 진행됨
   - 배치사이즈와 값은 정의 된 값은 없으며, 보통 32, 64 정도를 주로 사용함.
   - 튜닝 대상 하이퍼파라미터 변수임

 

 

* 훈련 및 검증에 대한 손실 곡선 그리기

plt.title("epoch - loss -acc")

plt.plot(history.epoch, history.history["loss"])
plt.plot(history.epoch, history.history["val_loss"])

plt.grid()

plt.legend(["loss", "val_loss"])

plt.show()

* 훈련 및 검증에 대한 정확도 곡선 그리기

plt.title("epoch - acc")

plt.plot(history.epoch, history.history["accuracy"])
plt.plot(history.epoch, history.history["val_accuracy"])

plt.grid()

plt.legend(["acc", "val_acc"])

plt.show()

 


 

* 단어 임베딩 방식을 사용

* 모델 생성하기

model2 = keras.Sequential()
model2

 

 

* 계층 생성 및 추가하기
입력 계층 생성하기(단어 임베딩 계층으로 생성)
  - 500 : 말뭉치 개수
  - 16 : 출력크기(개수)
  - input_length : 사용할 특성 개수(input_shape와 동일)

model2.add(keras.layers.Embedding(500, 16, input_length=100))

"""Simple RNN계층 추가하기"""
model2.add(keras.layers.SimpleRNN(8))

"""출력계층 추가"""
model2.add(keras.layers.Dense(1, activation="sigmoid"))
model2.summary()

 

 

* 모델 설정하기 

rmsprop = keras.optimizers.RMSprop(learning_rate=0.0001)
model2.compile(optimizer=rmsprop,
              loss="binary_crossentropy",
              metrics=["accuracy"])

* 콜백함수

model_path = "./model/best_simpleRNN_model.h5"


checkpoint_cb2 = keras.callbacks.ModelCheckpoint(
    model_path,
    save_best_only=True
)

early_stopping_cb2 = keras.callbacks.EarlyStopping(
    patience=3,
    restore_best_weights=True
)

checkpoint_cb2, early_stopping_cb2

 

* 모델 훈련 후 정확도 시각화

history2 = model2.fit(
            train_seq, train_target, epochs=100, batch_size=64,
            validation_data=(val_seq, val_target),
            callbacks=[checkpoint_cb2, early_stopping_cb2]
)

* 시각화

plt.title("epoch - loss")

plt.plot(history2.epoch, history2.history["loss"])
plt.plot(history2.epoch, history2.history["val_loss"])

plt.grid()

plt.legend(["loss", "val_loss"])

plt.show()

plt.title("epoch - acc")

plt.plot(history2.epoch, history2.history["accuracy"])
plt.plot(history2.epoch, history2.history["val_accuracy"])

plt.grid()

plt.legend(["acc", "val_acc"])

plt.show()

 

 

<성능 평가하기>

 

* 원-핫데이터 모델로 검증데이터 평가하기

model.evaluate(val_oh, val_target)

결과 :

157/157 [==============================] - 2s 7ms/step - loss: 0.4800 - accuracy: 0.7764
[0.4799714982509613, 0.7764000296592712]

 

* 단어 임베딩 모델로 검증데이터 평가하기

model2.evaluate(val_seq, val_target)

결과 : 

157/157 [==============================] - 1s 4ms/step - loss: 0.4626 - accuracy: 0.7902
[0.46264517307281494, 0.7901999950408936]

 

 

<테스트 데이터로 원-핫 모델평가 및 단어 임베딩 모델 평가하기>

test_seq = pad_sequences(test_input, maxlen=100)

test_oh = keras.utils.to_categorical(test_seq)

 

* 원 핫 모델

pred = model.evaluate(test_oh, test_target)
pred

결과 : 

782/782 [==============================] - 17s 9ms/step - loss: 0.4765 - accuracy: 0.7799
[0.47645336389541626, 0.7798799872398376]

 

 

* 임베딩 모델

pred2 = model2.evaluate(test_seq, test_target)
pred2

결과 : 

782/782 [==============================] - 3s 4ms/step - loss: 0.4577 - accuracy: 0.7912
[0.45769429206848145, 0.7911999821662903]

728x90