Etc/Deep Learning

7장 시계열 분석(2) - RNN 구조(계층, 셀) + RNN 셀 구현

z.zzz 2021. 8. 21. 17:18

7.4 RNN 구조

RNN이란?

은닉층 노드들이 연결되어 이전 단계 정보은닉층 노드에 저장할 수 있도록 구성한 신경망

 

RNN 구조

 

xt-1에서 ht-1을 얻고

다음 단계에서 ht-1 xt를 사용

과거 정보, 현재 정보를 모두 반영

 

 

 

RNN의 입력층, 은닉층, 출력층 + 3개의 가중치(Wxh, Whh, Why)

 - 위 그림 참고

Wxh : 입력층에서 → 은닉층으로 전달되는 가중치

Whh : t 시점은닉층에서 → t+1 시점은닉층으로 전달되는 가중치

Why : 은닉층에서 → 출력층으로 전달되는 가중치

※ 3개의 가중치는 모든 시점에서 동일함(= 같은 값의 가중치를 공유)

 

t단계에서의 RNN 계산

1. 은닉층

  - 계산을 위해 xt, ht-1이 필요  //xt :입력값, ht-1 : 이전 은닉층

  - 계산 : 이전 은닉층 × 은닉층 - 은닉층 가중치 + 입력층 - 은닉층 가중치 × (현재) 입력값

  - RNN의 은닉층에서 일반적으로 사용하는 활성화 함수 : 하이퍼볼릭 탄젠트 활성화 함수

 

2. 출력층

  - 심층 신경망과 계산 방법이 동일

  - 계산 : (은닉층 - 출력층 가중치 × 현재 은닉층)에 소프트맥스 함수 적용

 

3. RNN의 오차

  - 각 단계(t)마다 오차를 측정(≠ 심층 신경망에서의 전방향 학습)

  ⇒ 각 단계마다 실제 값(yt)과 예측값(hat yt)으로 오차를 측정 - '평균 제곱 오차' 적용

RNN의 순방향 학습

 

4. RNN의 역전파

  - BPTT(BackPropagation Through Time)를 이용 → 모든 단계마다 처음부터 끝까지 역전파

       //BPTT란? 각 단계(t)마다 오차를 측정하고, 이전 단계로 전달되는 것

     ⇒ 3에서 구한 오차를 이용 →  Wxh, Whh, Why, 바이어스(bias) 업데이트         

      //바이어스 : 가중합에 더해주는 상수, 출력값을 조절함

  - ❗ 문제점 : BPTT는 오차가 멀리 전파될 때(왼쪽으로 전파) → 계산량 & 전파되는 양이 점차(기울기 소멸 문제)

     ⇒ 보완 방법 : 생략된-BPTT, LSTM, GRU 사용

📝Note. 생략된-BPTT
목적 : 계산량 줄이기
방법 : 현재 단계에서 일정 시점까지만 오류를 역전파함(보통 5단계 이전까지만)

 

RNN의 역방향 학습

 

💻 실습 - RNN 계층과 셀 구현

* IMDB 데이터셋 이용 - 영화 리뷰에 대한 데이터

7.4.1 RNN 셀 구현

1️⃣필요한 값 초기화

tf.random.set_seed(22)    #seed를 사용 → seed값으로 같은 값을 넣을때마다 같은 숫자가 출력
np.random.seed(22)
assert tf.__version__.startswith('2.')    #텐서플로 버전이 2임을 확인  //assert : 프로그램의 내부 점검

batch_size = 128
total_words = 10000
max_review_len = 80
embedding_len = 100

 

2️⃣모형을 적용하기 위한 데이터셋 준비

(x_train, y_train), (x_test, y_test) = tf.keras.datasets.imdb.load_data(num_words=total_words)
  //imdb.load_data() : IMDB 데이터셋을 내려받음
      - num_words : 데이터에서 등장 빈도 순위 - 몇번째에 해당하는 단어까지 사용할지를 의미
        (num_words=10000 : 등장 빈도 순위가 1~10,000에 해당하는 단어만 사용하겠다는 의미)

#패딩 작업 : 모델의 입력으로 사용하려면 모든 샘플 길이를 동일하게 맞춰야함
pad_sequence(): 정해준 길이보다 길이가 긴 샘플은 값을 일부 자르고, 정해준 길이보다 길이가 짧은 샘플은 값을 0으로 채움
  - 첫 번째 인자: 패딩을 진행할 데이터
  - maxlen: 모든 데이터에 대해 정규화할 길이
x_train = tf.keras.preprocessing.sequence.pad_sequences(x_train, maxlen=max_review_len)
x_test = tf.keras.preprocessing.sequence.pad_sequences(x_test, maxlen=max_review_len)

#넘파이 배열을 Dataset으로 변환
from_tensor_slices : tf.data.Dataset 를 생성하는 함수, 입력된 텐서로부터 slices를 생성
train_data = tf.data.Dataset.from_tensor_slices((x_train, y_train))

#위에서 만들어준 데이터셋을 변형함
train_data = train_data.shuffle(10000).batch(batch_size, drop_remainder=True)
- shuffle : 데이터셋을 임의로 섞음
- batch : 데이터셋의 항목들을 하나의 배치로 묶음
  * batch_size : 몇 개의 샘플로 가중치를 갱신할지 지정
- drop_reminder : 마지막 배치 크기를 무시, 지정한 배치 크기를 사용할 수 있음

#x_test, y_test 데이터에 대한 넘파이 배열을 바로 Dataset으로 변환
test_data = tf.data.Dataset.from_tensor_slices((x_test, y_test))
test_data = test_data.batch(batch_size, drop_remainder=True)

#테스트 데이터셋을 변환
 - reduce_max : 지정된 차원을 따라 최대값을 선택
 - reduce_min : 지정된 차원을 따라 최소값을 선택
print('x_train_shape: ', x_train.shape, tf.reduce_max(y_train), tf.reduce_min(y_train))
print('x_test_shape: ', x_test.shape)

sample = next(iter(test_data))
print(sample[0].shape)

'훈련과 검증 용도 데이터셋에 대한 형태' 출력의 결과

 

3️⃣RNN 셀을 이용한 네트워크(신경망) 생성

#객체 지향 프로그램을 파이썬에서 구현한 것(재사용성 고려, 코드의 반복을 최소화)
class RNN_Build(tf.keras.Model):
   
    #__init__, call : 클래스 인스턴스를 생성할 때 초기화하는 부분

    #__init__ : 객체가 생성될 때 호출됨 | call : 인스턴스가 생성될 때 호출됨
    def __init__(self, units):
        super(RNN_Build, self).__init__()    #클래스 기반의 __init__ 메서드를 호출
        
        self.state0 = [tf.zeros([batch_size, units])]    #self : 자신의 인스턴스를 의미
        self.state1 = [tf.zeros([batch_size, units])]    #tf.zeros를 사용 → 0값으로 채워진 텐서를 생성, state0에 저장

        #임베딩층 : 텍스트 데이터에 대해 워드 임베딩을 수행(임베딩층 사용하려면 각 입력이 모두 정수로 인코딩되어 있어야함)
        self.embedding = tf.keras.layers.Embedding(total_words, embedding_len, input_length=max_review_len)
        - 첫번째 인자 : 텍스트 데이터의 전체 단어 집합 크기
        - 두번째 인자 : 임베딩이 되고 난 후 단어의 차원 지정
        - input_length : 입력 데이터의 길이

        #SimpleRNN의 셀 클래스를 의미
        #unit : 출력 공간의 차원
        #dropout : 0과 1 사이의 부동소수점(입력 중 삭제할 유닛의 비율)
        self.RNNCell0 = tf.keras.layers.SimpleRNNCell(units, dropout=0.2)
        self.RNNCell1 = tf.keras.layers.SimpleRNNCell(units, dropout=0.2)
        self.outlayer = tf.keras.layers.Dense(1)

    def call(self, inputs, training=None):
        x = inputs
        x = self.embedding(x)    #입력데이터에 원핫인코딩 적용
        state0 = self.state0
        state1 = self.state1

        #unstack() : 중복된 값이 있을때 사용하면 유용(그룹으로 묶은 데이터를 행렬 형태로 전환하여 연산할때)
        for word in tf.unstack(x, axis=1):
            out0, state0 = self.RNNCell0(word, state0, training)  #out, state 각각에 self.RNNCell에서 받아온 값을 저장
            out1, state1 = self.RNNCell1(word, state0, training)
        x = self.outlayer(out1)     #출력층 out1을 적용한 후, 그 값을 x 변수에 저장
        prob = tf.sigmoid(x)       #마지막으로 x에 시그모이드 활성화 함수를 적용, prob에 저장

        return prob     #prob 값을 반환
📝Note. unstack() 예시

 

4️⃣생성된 네트워크를 활용한 모형(모델) 훈련

import time
units = 64
epochs = 4
t0 = time.time()
model = RNN_Build(units)

model.compile(optimizer = tf.keras.optimizers.Adam(0.001),
                    loss = tf.losses.BinaryCrossentropy(),
                    metrics = ['accuracy'],    #훈련 모니터링 지표로 정확도 사용
                    experimental_run_tf_function=False)   #모델을 인스턴스화하는 기능을 제공(이게 호출돼야 컴파일이 실행됨)

model.fit(train_data, epochs=epochs, validation_data=test_data, validation_freq=2)
/* - epochs : 학습 데이터 반복 횟수
    - validation_data : 검증 데이터
    - validation_freq : 에포크마다 무조건 검증 데이터셋에 대한 계산 수행X(적절한 간격을 두고 계산)  */

 

5️⃣모델 평가

훈련 데이터의 정확도 : 99%

테스트 데이터의 정확도 : 80%

⇒ 결과가 나쁘지않음


7.4.2 RNN 계층 구현

네트워크 구축 - def call(..., training=None)

training mode vs inference mode

 

워드 임베딩 - tf.keras.layers.Embedding(...)

 


from_tensor_slices : https://hiseon.me/data-analytics/tensorflow/tensorflow-dataset/

tf.reduce_max, reduce_min : https://tensorflow.blog/3-%ED%85%90%EC%84%9C%ED%94%8C%EB%A1%9C%EC%9A%B0-%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0%EB%A7%81-first-contact-with-tensorflow/

training/inference mode : https://blogs.nvidia.com/blog/2016/08/22/difference-deep-learning-training-inference-ai/

워드 임베딩 : https://davinci-ai.tistory.com/30