본문 바로가기

데이터/머신러닝

[혼공] ch 9. 텍스트를 위한 인공신경망

1. 순차 데이터와 순환 신경망

 

1) 순차 데이터 

 

순차 데이터(sequential data)

 

- 텍스트나 시계열 데이터(time series data)와 같이 순서에 의미가 있는 데이터 

- 순차 데이터를 다룰 때는 이전에 입력한 데이터를 기억하는 기능이 필요하다. 

 

- 합성곱 신경망이나 완전 연결 신경망과 같이 입력 데이터의 흐름이 앞으로만 전달되는 신경망을 피드포워드 신경망(feedforward neural network, FFNN)이라고 한다. 

 

 

2) 순환 신경망 

 

순환 신경망(recurrent neural network, RNN)

 

- 순차 데이터에 잘 맞는 인공 신경망의 한 종류 

- 완전 연결 신경망에 이전 데이터의 처리 흐름을 순환하는 고리 하나만 추가하면 된다. 

 

- 뉴런의 출력이 다시 자기 사진으로 전달된다. 즉, 어떤 샘플을 처리할 때 바로 이전에 사용했던 데이터를 재사용하는 셈이다. 

 

ex)

- A,B,C, 3개의 샘플을 처리하는 순환 신경망의 뉴런이 있다고 가정 

- O는 출력된 결과 

- 첫 번째 샘플 A를 처리하고 난 출력(OA)이 다시 뉴런으로 들어간다. 

 

- 이 출력에는 A에 대한 정보가 들어 있다. 그 다음 B를 처리할 때 앞에서 A를 사용해 만든 출력 OA를 함께 사용한다. 

- OA와 B를 사용해서 만든 OB에는 A에 대한 정보가 어느정도 포함되어 있을 것이다. 

- 그 다음 C를 처리할 때는 OB를 함께 사용한다. 

 

 

- OC에는 OB를 사용했으므로 B에 대한 정보와 A에 대한 정보도 포함되어 있다. 

- 순환 신경망에서는 '이전 샘플에 대한 기억을 가지고 있다.'

 

- 샘플을 처리하는 한 단계를 타임스텝(Timestep)이라고 한다. 

- 순환 신경망에서는 특별히 층을 셀(cell)이라고 부른다, 

- 한 셀에는 여러 개의 뉴런이 있지만 완전 연결 신경망과 달리 뉴런을 모두 표시하지 않고 하나의 셀로 층을 표현한다, 

- 셀의 출력은 은닉 상태(hidden state)라고 부른다. 

 

- 일반적으로 은닉층의 활성화 함수로는 하이퍼볼릭 탄젠트(hyperbolic tangent) 함수인 tanh가 많이 사용된다. 

- tanh 함수도 S자 모양을 띠기 때문에 종종 시그모이드 함수라고 부르기도 한다. 

- tach 함수는 시그모이드 함수와는 달리 -1~1사이의 범위를 가진다. 

 

출처: Comparison of Sigmoid, Tanh and ReLU Activation Functions - AITUDE

 

 

- 합성곱 신경망과 같은 피드포워드 신경망에서 뉴런은 입력과 가중치를 곱한다. 순환 신경망에서도 동일하다. 

- 다만 순환 신경망의 뉴런은 가중치가 하나 더 있다. 바로 이전 타임스텝의 은닉 상태에 곱해지는 가중치이다. 

- 셀은 입력과 이전 타임 스텝의 은닉 상태를 사용하여 현재 타임스텝의 은닉 상태를 만든다. 

 

- wx는 입력에 곱해지는 가중치이고 wh는 이전 타임스텝의 은닉 상태에 곱해지는 가중치이다. 

 

 

- 셀의 출력(은닉 상태)이 다음 타임스텝에 재사용되기 때문에 타임스텝으로 셀을 나누어 그릴 수 있다. 

 

 

- 타임스텝 1에서 셀의 출력 h1이 타임스텝 2의 셀로 주입된다 이때 wh와 곱해진다. 마찬가지로 타임스텝 2에서 셀의 출력 h2가 타임스텝 3의 셀로 주입된다. 이때에도 wb와 곱해진다 

- 여기에서 알 수 있는 것은 모든 타임스텝에서 사용되는 가중치는 wb하나라는 점이다. 

- 가중치 wb는 타임스텝에 따라 변화되는 뉴런의 출력을 학습한다. 

 

 

2) 셀의 가중치와 입출력

 

- 순환 신경망의 셀에서 필요한 가중치 크기를 계산 

 

ex)

- 순환층에 입력되는 특성의 개수가 4개이고 순환층의 뉴런이 3개라고 가정 

- 입력층과 순환층의 뉴런이 모두 완전 연결되기 때문에 가중치 Wx의 크기는 4*3=12가 된다. 

 

 

 

- 순환층에 있는 첫 번째 뉴런(r1)의 은닉 상태가 다음 타임스텝에 재사용될 떄 첫 번째 뉴런과 두 번째 뉴런, 세 번째 뉴런에 모두 전달된다. 

- 즉, 이전 타임스텝의 은닉 상태는 다음 타임스텝의 뉴런에 완전히 연결된다. 

- 따라서 이 순환층의 은닉 상태를 위한 가중치 Wh는 3*3=9개이다. 

 

- 모델 파라미터의 개수는 가중치에 절편을 더하면 된다. 

- 이 순환층은 모두 12+9+3=24개의 모델 파라미터를 가지고 있다. 

 

모델 파라미터 수 = Wx + Wh + 절편 = 12+9+3 = 24

 

- 순환층은 일반적으로 샘플마다 2개의 차원을 가진다. 보통 하나의 샘플을 하나의 시퀀스(sequence)라고 말한다. 

- 시퀀스 안에는 여러 개의 아이템이 들어있다. 여기에서 시퀀스의 길이가 바로 타임스텝의 길이가 된다.. 

 

- 순환층은 기본적으로 마지막 타임스텝의 은닉상태만 출력으로 내보낸다. 

- 이는 마치 입력된 시퀀스 길이를 모두 읽어서 정보를 마지막 은닉 상태에 압축하여 전달하는 것 처럼 볼 수 있다. 

 

- 순환 신경망도 완전 연결 신경망이나 합성곱 신경망처럼 여러 개의 층을 쌓을 수 있다. 

- 셀의 입력은 샘플마다 타임스텝과 단어 표현으로 이루어진 2차원 배열이어야 한다. 따라서 첫 번째 셀이 마지막 타임스텝의 은닉 상태만 출력해서는 안된다. 이런 경우에는 마지막 셀을 제외한 다른 모든 셀은 모든 타임 스텝의 은닉 상태만 출력한다. 

 

 

- 첫 번째 셀은 모든 타임스텝의 은닉 상태를 출력하고, 두 번째 셀은 마지막 타임스텝의 은닉 상태만 출력한다. 

 

- 합성곱 신경망과 마찬가지로 순환 신경망도 마지막에는 밀집층을 두어 클래스를 분류한다. 

- 다중 분류일 경우에는 출력층에 클래스 개수만큼 뉴런을 두고 소프트맥스 활성화 함수를 사용한다. 이진 분류일 경우에는 하나의 뉴런을 두고 시그모이드 활성화 함수를 사용한다. 

 

- 합성곱 신경망과 다른 점은 마지막 셀의 출력이 1차원이기 때문에 Flatten 클래스로 펼칠 필요가 없다. 

 

 

2. 순환 신경망으로 IMDB 리뷰 분류하기 

 

1) IMDB 리뷰 데이터셋

 

- IMDB 리뷰 데이터셋은 유명한 인터넷 영화 데이터베이스인 imdb.com에서 수집한 리뷰를 감상평에 따라 긍정과 부정으로 분류해 놓은 데이터셋이다. 총 50,000개의 샘플로 이루어져 있고 훈련 데이터와 테스트 데이터에 각각 25,000개씩 나누어져 있다. 

 

- 텍스트 자체를 신경망에 전달하지는 않는다. 텍스트 데이터의 경우 단어를 숫자 데이터로 바꾸는 일반적인 방법은 데이터에 등장하는 단어마다 고유한 정수를 부여하는 것이다. 

 

- 일반적으로 영어 문장은 모두 소문자로 바꾸고 구둣점을 삭제한 다음 공백을 기준으로 분리한다. 이렇게 분리된 단어를 토큰(token)이라고 부른다.

- 하나의 샘플은 여러 개의 토큰으로 이루어져 있고 1개의 토큰이 하나의 타임스텝에 해당한다. 

 

- tensorflow.keras.datasets 패키지 아래 imdb 모듈을 임포트하여 이 데이터를 적재 

- 전체 데이터셋에서 가장 자주 등장하는 단어 300개의 사용하기 위해 load_data() 함수의 num_words 매개변수를 300으로 지정

from tensorflow.keras.datasets import imdb

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

 

 

 

- 훈련 세트와 테스트 세트의 크기를 확인

print(train_input.shape, test_input.shape)

 

 

- IMDB 리뷰 텍스트는 길이가 제각각이다. 따라서 고정 크기의 2차원 배열에 담기 보다는 리뷰마다 별도의 파이썬 리스트로 담아야 메모리를 효율적으로 사용할 수 있다. 

- 이 데이터는 개별 리뷰를 담은 파이썬 리스트 객체로 이루어진 넘파이 배열이다. 

 

- 첫 번째 리뷰의 길이를 출력 

print(len(train_input[0]))

 

- 첫 번째 리뷰의 길이는 218개의 토큰으로 이루어져 있다. 

- 여기서 하나의 리뷰가 하나의 샘플이 된다. 

 

- 첫 번째 리뷰에 담긴 내용 출력

print(train_input[0])

 

 

 

- 타깃 데이터 출력 

print(train_target[:20])

 

 

- 해결할 문제는 리뷰가 긍정인지 부정인지를 판단하는 것이다. 그러면 이진 분류 문제로 볼 수 있으므로 타깃값이 0(부정), 1(긍정)으로 나누어진다. 

 

- 훈련세트에서 20%를 검증세트로 떼어놓기 

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)

 

- 평균적인 리뷰의 길이와 가장 짧은 리뷰의 길이 그리고 가장 긴 리뷰의 길이를 확인하기 위해 각 리뷰의 길이를 계산해 넘파이 배열에 담기 

 

- 넘파이 mean() 함수와 median() 함수를 사용해 리뷰 길이의 평균과 중간값 구하기 

import numpy as np

lengths = np.array([len(x) for x in train_input])


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

 

- lengths 배열을 히스토그램으로 표현 

 

import matplotlib.pyplot as plt

plt.hist(lengths)
plt.xlabel('length')
plt.ylabel('frequency')
plt.show()

 

 

 

 

- 리뷰는 대부분 짧아서 중간값보다 훨씬 짧은 100개의 단어만 사용한다. 그러나 여전히 100개의 단어보다 작은 리뷰가 있으므로 이런 리뷰들을 100에 맞추기 위해 패딩이 필요 

- 보통 패딩을 나타내는 토큰으로는 0을 사용한다. 

 

- 케라스는 시퀀스 데이터의 길이를 맞추는 pad_sequences() 함수를 제공한다. 

- maxlen에 원하는 길이를 지정하면 이보다 긴 경우는 잘라내고 짧은 경우는 0으로 패딩한다. 

from tensorflow.keras.preprocessing.sequence import pad_sequences

train_seq = pad_sequences(train_input, maxlen=100)

 

- train_input은 파이썬 리스트의 배열이었지만 길이를 100으로 맞춘 train_seq는 이제 (20000,100) 크기의 2차원 배열이 되었다. 

print(train_seq.shape)

 

- train_seq에 있는 첫 번째 샘플을 출력

print(train_seq[0])

 

 

- pad_sequences() 함수는 기본적으로 maxlen보다 긴 시퀀스의 앞부분을 자른다. 

- 만약 시퀀스의 뒷부분을 잘라내고 싶다면 pad_sequences() 함수의 truncating 매개변수의 값을 기본값 'pre'가 아닌 'post'로 바꾸면 된다. 

 

- 검증 세트의 길이도 100으로 맞추기 

val_seq = pad_sequences(val_input, maxlen=100)

 

 

2) 순환 신경망 만들기 

 

- 케라스는 여러 종류의 순환층 클래스를 제공하는 데  그 중 가장 간단한 것은 SimpleRNN 클래스이다. 

- 첫 번째 매개변수에는 사용할 뉴런의 개수를 지정하고, input_shape에 입력차원을 (100,300)으로 지정한다. 

from tensorflow import keras

model = keras.Sequential()

model.add(keras.layers.SimpleRNN(8, input_shape=(100, 300)))
model.add(keras.layers.Dense(1, activation='sigmoid'))

 

- 순환층도 활성화 함수를 사용해야 한다. SimpleRNN 클래스의  activation 매개변수의 기본값은 'tanh'로 하이퍼볼릭 탄젠트 함수를 사용한다. 

 

- 정숫값에 있는 크기 속성을 없애고 각 정수를 고유하게 표현하는 방법은 원-핫인코딩이다. 

- imdb.load_data() 함수에서 300개의 단어만 사용하도록 지정했기 때문에 고유한 단어는 모두 300개이다. 

- 즉, 훈련 데이터에 포함될 수 있는 정숫값에 범위는 0(패딩 토큰)에서 299까지이다. 

- 케라스에서 원-핫 인코딩을 위한 유틸리티를 제공 

- keras.utils 패키지 아래에 있는 to_categorical() 함수는 정수 배열을 입력하면 자동으로 원-핫 인코딩된 배열을 반환 

 

train_oh = keras.utils.to_categorical(train_seq)

print(train_oh.shape)

 

 

- 정수 하나마다 모두 300차원의 배열로 변경되었기 때문에 (20000,100) 크기의 train_seq가 (20000,100,300) 크기의 train_oh로 바뀐다. 

 

- train_oh의 첫 번째 샘플의 첫 번째 토큰 10이 잘 인코딩되었는지 출력 

print(train_oh[0][0][:12])

 

 

- 넘파이 sum() 함수로 모든 원소의 값을 더해서 1이 되는지 확인 

 

print(np.sum(train_oh[0][0]))

 

 

- val_seq도 원-핫 인코딩으로 바꾸기 

val_oh = keras.utils.to_categorical(val_seq)

 

- 모델 구조 출력

model.summary()

 

 

- SimpleRNN에 전달한 샘플의 크기는 (100,300)이지만 이 순환층은 마지막 타입스텝의 은닉 상태만 출력한다. 

 

- 순환층의 사용된 모델 파라미터의 개수 계산

  - 입력 토큰은 300차원의 원-핫 인코딩 배열

  - 이 배열이 순환층의 뉴런 8개와 완전히 연결되기 때문에 총 300*8=2400개의 가중치가 있다. 

  - 순환층의 은닉 상태는 다시 다음 타임 스텝에 사용되기 위해 또 다른 가중치와 곱해진다. 

  - 이 은닉 상태도 순환층의 뉴런과 완전히 연결되기 때문에 8(은닉 상태 크기) * 8(뉴런개수) = 64개의 가중치가 필요하다 

  - 마지막으로, 뉴런마다 하나의 절편이 있다. 따라서 모두 2,400+64+8 = 2,272개의 모델 파라미터가 필요하다

 

 

3) 순환 신경망 훈련하기 

 

- 기본 RMSprop의 학습률 0.001을 사용하지 않기 위해 별도의 RMSprop 객체를 만들어 학습률을 0.0001fh wlwjd

- 그 다음 에포크 횟수를 100으로 늘리고 배치 크기는 64개로 설정

 

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

checkpoint_cb = keras.callbacks.ModelCheckpoint('best-simplernn-model.h5',
                                                save_best_only=True)
early_stopping_cb = keras.callbacks.EarlyStopping(patience=3,
                                                  restore_best_weights=True)

history = model.fit(train_oh, train_target, epochs=100, batch_size=64,
                    validation_data=(val_oh, 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()

 

 

- 원-핫 인코딩의 단점은 입력 데이터가 엄청 커진다는 것이다. 

 

 

4) 단어 임베딩 사용하기 

 

- 단어 임베딩(word embeding)은 각 단어를 고정된 크기의 실수 벡터로 바꾸어준다. 

- 원-핫 인코딩된 벡터보다 훨씬 의미 있는 값으로 채워져 있기 때문에 자연어 처리에서 더 좋은 성능을 내는 경우가 많다. 

- 케라스에서는 keras.layers 패키지 아래 Embeding 클래스로 임베딩 기능을 제공한다. 

- 이 클래스를 다른 층처럼 모델에 추가하면 처음에는 모든 벡터가 랜덤하게 초기화되지만 훈련을 통해 데이터에서 좋은 단어 임베딩을 학습한다. 

 

- 단어 임베딩의 장점은 입력으로 정수 데이터를 받는다는 것이다.  즉, 원-핫 인코딩으로 변경된 train_oh 배열이 아니라 train_seq를 사용할 수 있다. 

 

-

model2 = keras.Sequential()

model2.add(keras.layers.Embedding(300, 16, input_length=100))
model2.add(keras.layers.SimpleRNN(8))
model2.add(keras.layers.Dense(1, activation='sigmoid'))

model2.summary()

 

 

 

- Embeding 클래스의 첫 번째 매개변수(300)은 어휘사전의 크기이다. 

- 두 번째 매개변수(16)은 임베딩 벡터의 크기이다. 

- 세 번째 input_length 매개변수는 입력 시퀀스의 길이이다. 

 

- Embeding 클래스는 300개의 각 토큰을 크기가 16인 벡터로 변경하기 때문에 총 300*16=4,800개의 모델 파라미터를 가진다. 

- SimpleRNN 층은 임베딩 벡터의 크기가 16이므로 8개의 뉴런과 곱하기 위해 필요한 가중치 16*8=128개를 가진다. 

- 또한 은닉 상태에 곱해지는 가중치 8*8=64개가 있다. 

- 마지막으로 8개의 절편이 있으므로 이 순환층에 있는 전체 모델 파라미터의 개수는 128+64+8 = 200개이다. 

- Dense 층의 가중치 개수는 이전과 동일하게 9개이다. 

 

- 모델 훈련과정은 이전과 동일하다. 

 

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

checkpoint_cb = keras.callbacks.ModelCheckpoint('best-embedding-model.h5',
                                                save_best_only=True)
early_stopping_cb = keras.callbacks.EarlyStopping(patience=3,
                                                  restore_best_weights=True)

history = model2.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()

 

 

- 검증 손실이 더 감소되지 않아 훈련이 적절히 조기 종료되었다. 이에 비해 훈련 손실은 계속 감소한다. 

 

 

3. LSTM과 GRU 셀 

 

1) LSTM 구조 

 

- LSTM은 Long Short-Term Memory의 약자이다. 단기 기억을 오래 기억하기 위해 고안되었다. 

- LSTM에는 입려과 가중치를 곱하고 절편을 더해 활성화 함수를 통과시키는 구조를 여러 개 가지고 있다. 

- 이런 계산 결과는 다음 타임스텝에 재사용된다. 

 

- 은닉 상태는 입력과 이전 타임스텝의 은닉 상태를 가중치에 곱한 후 활성화 함수를 통과시켜 다음 은닉 상태를 만든다. 이 때 기본 순환층과는 달리 시그모이드 활성화 함수를 사용한다. 또 tanh 활성화 함수를 통과한 어떤 값과 곱해져서 은닉 상태를 만든다. 

 

 

- 위 그림에서는 편의상 은닉 상태를 계산할 때 사용하는 가중치 Wx와 Wh를 통틀어 Wo로 표시했다. 

- 파란색 원은 tanh 함수를 나타내고 주황색 원은 시그모이드 함수를 나타낸다. 

- X는 곱셈을 나타낸다. 

 

- LSTM에는 은닉상태셀 상태(cell state)로 순환되는 상태가 2개이다. 

- 은닉 상태와 달리 셀 상태는 다음 층으로 전달되지 않고 LSTM 셀에서 순환만 되는 값이다. 

 

- 셀 상태를 은닉 상태 h와 구분하여 c로 표시했다. 

 

셀 상태를 계산하는 과정

- 입력과 은닉 상태를 또 다른 가중치 Wf에 곱한 다음 시그모이드 함수를 통과시킨다. 그 다음 이전 타임스텝의 셀 상태와 곱하여 새로운 셀 상태를 만든다. 이 셀 상태가 오른쪽에서 tanh 함수를 통과하여 새로운 은닉 상태를 만드는 데 기여한다. 

 

- 입력과 은닉 상태에 곱해지는 가중치 Wo과 Wf가 다르다. 이 두 작은 셀은 각기 다른 기능을 위해 훈련된다. 

 

 

- 입력과 은닉 상태를 각기 다른 가중치에 곱한 다음, 하나는 시그모이드 함수를 통과시키고 다른 하나는 tanh 함수를 통과시킨다. 그 다음 두 결과를 곱한 후 이전 셀 상태와 더한다. 이 결과가 최종적인 다음 셀 상태가 된다. 

 

삭제 게이트 셀 상태에 있는 정보를 제거하는 역할
입력 게이트  새로운 정보를 셀 상태에 추가
출력 게이트 셀 상태가 다음 은닉 상태로 출력 

 

 

2) LSTM 신경망 훈련하기 

 

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

 

 

#케라스의 pad_sequences() 함술 각 샘플의 길이를 100에 맞추고 부족할 때는 패딩을 추가 

from tensorflow.keras.preprocessing.sequence import pad_sequences

train_seq = pad_sequences(train_input, maxlen=100)
val_seq = pad_sequences(val_input, maxlen=100)

 

- LSTM 셀을 사용한 순환층 만들기 

from tensorflow import keras

model = keras.Sequential()

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

model.summary()

 

 

- SimpleRNN 클래스의 모델 파라미터 개수는 200개였다. LSTM 셀에는 작은 셀이 4개 있으므로 정확히 4배가 늘어 모델 파라미터 개수는 800개가 되었다. 

 

- 모델을 컴파일하고 훈련 

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

checkpoint_cb = keras.callbacks.ModelCheckpoint('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])

 

 

- 훈련 손실과 검증 손실 그래프 그리기 

import matplotlib.pyplot as plt

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

 

 

 

3) 순환층에 드롭아웃 적용하기 

 

- 순환층은 자체적으로 드롭아웃 기능을 제공한다. 

- SimpleRNN과 LSTM 클래스 모두 dropout 매개변수와 recurrent_dropout 매개변수를 가지고 있다. 

- dropout 매개변수셀의 입력에 드롭아웃을 적용하고 recurrent_dropout순환되는 은닉상태에 드롭아웃을 적용한다. 

 

- LSTM 클래스에 dropout 매개변수를 0.3으로 지정하여 30%의 입력을 드롭아웃한다. 

model2 = keras.Sequential()

model2.add(keras.layers.Embedding(500, 16, input_length=100))
model2.add(keras.layers.LSTM(8, dropout=0.3))
model2.add(keras.layers.Dense(1, activation='sigmoid'))

 

- 이 모델을 이전과 동일한 조건으로 훈련 

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

checkpoint_cb = keras.callbacks.ModelCheckpoint('best-dropout-model.h5',
                                                save_best_only=True)
early_stopping_cb = keras.callbacks.EarlyStopping(patience=3,
                                                  restore_best_weights=True)

history = model2.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()

 

 

- 훈련 손실과 검증 손실 간의 차이가 좁혀진 것을 확인할 수 있다. 

 

 

4) 2개의 층을 연결하기 

 

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

 

- 케라스의 순환층에서 모든 타임스텝의 은닉 상태를 출력하려면 마지막을 제외한 다른 모든 순환층에 return_sequencees 매개변수를 True로 지정하면 된다. 

 

model3 = keras.Sequential()

model3.add(keras.layers.Embedding(500, 16, input_length=100))
model3.add(keras.layers.LSTM(8, dropout=0.3, return_sequences=True))
model3.add(keras.layers.LSTM(8, dropout=0.3))
model3.add(keras.layers.Dense(1, activation='sigmoid'))

model3.summary()

 

 

- 2개의 LSTM 층을 쌓았고 모두 드롭아웃을 0.3으로 지정했다. 

- 첫 번째 LTSM 클래스에는 return_sequence 매개변수를 True로 지정한 것을 볼 수 있다. 

 

- 모델 훈련 

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

checkpoint_cb = keras.callbacks.ModelCheckpoint('best-2rnn-model.h5',
                                                save_best_only=True)
early_stopping_cb = keras.callbacks.EarlyStopping(patience=3,
                                                  restore_best_weights=True)

history = model3.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()

 

 

- 과대적합을 제어하면서 손실을 최대한 낮춘 것 같다. 

 

5) GRU 구조 

 

- GRU는 Gated Recurrent Unit의 약자이다. 이 셀은 LSTM을 간소화한 버전으로 생각할 수 있다. 

- 이 셀은 LSTM처럼 셀 상태를 계산하지 않고 은닉 상태 하나만 포함하고 있다. 

 

 

 

- GRU 셀에는 은닉 상태와 입력에 가중치를 곱하고 절편을 더하는 작은 셀이 3개 들어있다. 

- 2개는 시그모이드 활성화 함수를 사용하고 하나는 tanh 활성화 함수를 사용한다. 

 

- 맨 왼쪽에서 Wz를 사용하는 셀의 출력이 은닉 상태에 바로 곱해져 삭제 게이트 역할을 수행한다. 이와 똑같은 출력을 1에서 뺀 다음 가장 오른쪽 Wg를 사용하는 셀의 출력에 곱한다.  이는 입력되는 정보를 제어하는 역할을 수행한다. 

- 가운데 Wr을 사용하는 셀에서 출력된 값은 Wg셀이 사용할 은닉 상태의 정보를 제어한다. 

 

 

6) GRU 신경망 훈련하기 

 

 

model4 = keras.Sequential()

model4.add(keras.layers.Embedding(500, 16, input_length=100))
model4.add(keras.layers.GRU(8))
model4.add(keras.layers.Dense(1, activation='sigmoid'))

model4.summary()

 

 

 

- GRU 셀에는 3개의 작은 셀이 있다. 작은 셀에는 입력과 은닉 상태에 곱하는 가중치와 절편이 있다. 

- 입력에 곱하는 가중치는 16*8=128개이고 은닉 상태를 곱하는 가중치는 8*8=64개이다. 그리고 절편은 뉴런마다 하나씩이므로 8개이다. 모두 더하면 128+64+8 =200개이다. 이런 작은 셀이 3개이므로 모두 600개의 모델 파라미터가 필요하다. 

 

- 훈련 

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

checkpoint_cb = keras.callbacks.ModelCheckpoint('best-gru-model.h5',
                                                save_best_only=True)
early_stopping_cb = keras.callbacks.EarlyStopping(patience=3,
                                                  restore_best_weights=True)

history = model4.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()

 

 

- 드롭아웃을 사용하지 않았기 떄문에 이전보다 훈련 손실과 검증 손실 사이에 차이가 있지만 훈련 과정이 잘 수렴되고 있는 것을 확인할 수 있다.