딥러닝(AI학습 48)

이유진·2024년 7월 8일

--29.LSTM, GRU.ipynb--

LSTM

Long Short-term Memory

순환신경망에서 빼놓을수 없는 핵심기술 LSTM 와 GRU셀 을 사용한 모델

SimpleRNN 보다 훨~씬 계산이 복잡. 그러나 '성능'이 뛰어남! 순환신경망에서 많이 채택

일반적으로 기본 순환층은 '긴 시퀀스'를 학습하기 어렵다.

시퀀스가 길수록 순환되는 은닉상태에 담긴 정보가 희석되기 때문

따라서 멀리 떨어져 있는 단어 정보를 인식하는데 어려울수 있다.

이를 위해 LSTM 과 GRU셀이 등장!

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import os

import tensorflow as tf
from tensorflow import keras

tf.keras.utils.set_random_seed(42) # 랜덤 시드 사용
tf.config.experimental.enable_op_determinism()

base_path = r'/content/drive/MyDrive/dataset'

IMDB 데이터 준비

이전 예제 처럼 IMDB 리뷰 데이터를 로드하고 훈련세트와 검증세트로 나눔

from tensorflow.keras.datasets import imdb
from sklearn.model_selection import train_test_split

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

한번만 실행!

train_input, val_input, train_target, val_target = \
train_test_split(train_input, train_target, test_size=0.2, random_state=42)

from tensorflow.keras.preprocessing.sequence import pad_sequences

max_len = 100

train_seq = pad_sequences(train_input, maxlen=max_len)
val_seq = pad_sequences(val_input, maxlen=max_len)
test_seq = pad_sequences(test_input, maxlen=max_len)

모델

LSTM 셀을 사용한 순환층

model = keras.Sequential()
model.add(keras.layers.Input(shape=(100,)))
model.add(keras.layers.Embedding(500, 16))
model.add(keras.layers.LSTM(8))
model.add(keras.layers.Dense(1, activation='sigmoid'))

model.summary()

"""


Layer (type) Output Shape Param #

embedding (Embedding) (None, 100, 16) 8000

lstm (LSTM) (None, 8) 800

        # SimpleRNN 은 모델 파라미터 개수가 200개였다
        # LSTM 셀에는 작은셀이 4개 있으므로 정확히 x4개가 되어 800개가 됨.

dense (Dense) (None, 1) 9

=================================================================
Total params: 8809 (34.41 KB)
Trainable params: 8809 (34.41 KB)
Non-trainable params: 0 (0.00 Byte)


"""
None

훈련

rmsprop = keras.optimizers.RMSprop(learning_rate=1e-4)
model.compile(optimizer=rmsprop, loss='binary_crossentropy', metrics=['accuracy'])

checkpoint_cb = keras.callbacks.ModelCheckpoint(os.path.join(base_path, 'best-lstm-model.h5'),
save_best_only=True)

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

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

early_stopping_cb.best_epoch # 27번째 epoch가 best

plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()

"""
↑ train loss 와 val loss 의 '격차' 가 이전의 SimpleRNN 때 보다 훨씬 좁혀졌다
기본 순환층 보다 LSTM이 overfit 을 잘 억제하면서 훈련을 잘 수행했다.
"""
None

순환층에 Dropout 적용하기

완전연결신경망과 CNN 에서는 Dropout 클래스를 사용해서 dropout 을 적용했었다

순환층은 '자체적으로 dropout 기능을 제공'

SimpleRNN, LSTM 클래스에 제공하는 2개의 dropout 매개변수

dropout= : 셀의 입력에 dropout 적용

recurrent_dropout= : 순환되는 은닉상태에 dropout 적용

※recurrent_dropout 은 GPU를 통한 훈련 못함.

model = keras.Sequential()
model.add(keras.layers.Input(shape=(100,)))
model.add(keras.layers.Embedding(500, 16))
model.add(keras.layers.LSTM(8, dropout=0.3))
model.add(keras.layers.Dense(1, activation='sigmoid'))

이전과 동일 조건으로 학습 (모델 저장 파일명만 변경)

rmsprop = keras.optimizers.RMSprop(learning_rate=1e-4)
model.compile(optimizer=rmsprop, loss='binary_crossentropy', metrics=['accuracy'])

checkpoint_cb = keras.callbacks.ModelCheckpoint(os.path.join(base_path, 'best-dropout-model.h5'),
save_best_only=True)

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

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

plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()

↑ LSTM에 dropout을 적용하니

train loss 와 val loss 간의 격차가 훨씬 좁혀졌다!

순환층 여러개 연결하기

순환층 연결시 주의!

  • 순환층의 은닉상태는 샘플의 마지막 스텝에 대한 은닉 상태만 다음층으로 전달한다
  • 그러나, 순환층을 쌓게 되면 모든 순환층에는 순차 데이터가 필요하다.
  • 따라서
    • 앞쪽의 순환층은 '모든 타임스텝'에 대한 은닉상태를 출력해야 하고,
    • 마지막 순환층만 '마지막 타임스텝의 은닉상태'를 출력해야 한다

keras의 순환층에서 '모든타임스텝의 은닉상태' 를 출력하려면 마지막을 제외한 모든 순환층에

return_sequences=True 를 지정해준다

model = keras.Sequential()
model.add(keras.layers.Input(shape=(100,)))
model.add(keras.layers.Embedding(500, 16))

model.add(keras.layers.LSTM(8, dropout=0.3, return_sequences=True))
model.add(keras.layers.LSTM(8, dropout=0.3))

model.add(keras.layers.Dense(1, activation='sigmoid'))

model.summary()

"""


Layer (type) Output Shape Param #

embedding_2 (Embedding) (None, 100, 16) 8000

lstm_2 (LSTM) (None, 100, 8) 800

          # 첫번째 LSTM 층이 모든 타임스텝(100개) 의 은닉상태를 출력하기 때문에
          # 출력크기가 (?, 100, 8) 2차원

lstm_3 (LSTM) (None, 8) 544

          # 마지막 LSTM 층의 출력크기는 마지막 타임 스텝의 은닉상태만 출력하기 떄문에
          # 출력 크기가 (?, 8) 이다.

dense_2 (Dense) (None, 1) 9

=================================================================
Total params: 9353 (36.54 KB)
Trainable params: 9353 (36.54 KB)
Non-trainable params: 0 (0.00 Byte)


"""
None

이전과 동일 조건으로 학습 (모델 저장 파일명만 변경)

rmsprop = keras.optimizers.RMSprop(learning_rate=1e-4)
model.compile(optimizer=rmsprop, loss='binary_crossentropy', metrics=['accuracy'])

checkpoint_cb = keras.callbacks.ModelCheckpoint(os.path.join(base_path, 'best-2lstm-model.h5'),
save_best_only=True)

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

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

plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()

-----------------------------------02:30:00 부터 시작 꼭 다시 봐야함 ---------------------

GRU

Gated Recurrent Unit

model = keras.Sequential()
model.add(keras.layers.Input(shape=(100,)))
model.add(keras.layers.Embedding(500, 16))

model.add(keras.layers.GRU(8))

model.add(keras.layers.Dense(1, activation='sigmoid'))

model.summary()

"""


Layer (type) Output Shape Param #

embedding_4 (Embedding) (None, 100, 16) 8000

gru (GRU) (None, 8) 624
parmeter 개수
GRU 에는 작은 셀이 3개가 있다
입력에 곱하는 weights 16 8 = 128개
은닉상태에 곱하는 weight 8
8 = 64개
bias 가 뉴런마다 하나씩 = 8개
모두 더하면 128 + 64 + 8 = 200개
이런 셀이 3개가 있다 200 * 3개 => 600개

       # TF GRU 는 내부적으로 작은 셀마다 하나의 bias 가 추가된다
       #   작은셀 3개 x 8개 뉴런 = 24개

dense_4 (Dense) (None, 1) 9

=================================================================
Total params: 8633 (33.72 KB)
Trainable params: 8633 (33.72 KB)
Non-trainable params: 0 (0.00 Byte)
"""
None

이전 과 동일 조건으로 학습 (모델 저장 파일명만 변경)

rmsprop = keras.optimizers.RMSprop(learning_rate=1e-4)
model.compile(optimizer=rmsprop, loss='binary_crossentropy', metrics=['accuracy'])

checkpoint_cb = keras.callbacks.ModelCheckpoint(os.path.join(base_path, 'best-gru-model.h5'),
save_best_only=True)

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

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

plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()

테스트 세트

rnn_model = keras.models.load_model(os.path.join(base_path, 'best-2lstm-model.h5'))
rnn_model.evaluate(test_seq, test_target)

profile
독해지자

0개의 댓글