본문 바로가기
----------책----------/밑바닥부터 시작하는 딥러닝2

CHAPTER 5 순환 신경망(RNN)

by 탶선 2020. 2. 18.
반응형

4장까지의 신경망 - feed forward(흐름이 단방향인 신경망)

  • 구성 단순
  • 이해 쉬움
  • 응용 쉬움
  • 시계열 데이터를 잘 다루지 못함

 

 

순환 신경망(Recurrent Neural Network)(RNN)

  • 순환하는 경로(닫힌 경로)
  • 순환 경로를 따라 데이터 순환
  • 과거의 정보를 기억 및 최신 데이터로 갱신 가능(시계열 데이터에 적합)

 

$x_{t}$ - 각 시간에 입력되는 벡터( t : 시각 )

입력 값 - $x_{0}, x_{1},x_{2}, ..., x_{t}, ...)$ 

출력 값 - 입력에 대응하여 $(h_{0}, h_{1}, ..., h_{t}, ...) $

RNN
90도 회전한 RNN

각 시각의 RNN계층은 그 계층으로부터의 입력, 1개 전의 RNN계층으로부터의 출력을 입력받음

-> 두 정보를 바탕으로 현 시각의 출력을 계산

 계산식 -> $h_{t} = tanh(h_{t-1} W_{h} + x_{t} W_{x} + b)$

  • $W_{x}$         - 입력 x를 출력 h로 변환하기 위한 가중치
  • $W_{h}$        - 1개의 RNN 출력을 다음 시각의 출력으로 변환하기 위한 가중치
  • $h_{t-1}, x_{t}$ - 행 벡터
  • b                 - bias

행렬 곱 계산 후, 그 합을 tanh 함수를 이용해 변환

변환된 결과는 시각 t의 출력 $h_{t}$

 

$h_{t}$

  • 다른 계층을 향해 출력되는 동시에, 다음 시각의 RNN계층을 향해 출력
  • RNN의 출력 $h_{t}$ 를 은닉상태(hidden state), 은닉 상태 벡터(hieend state vector)라고 불림

 

 

BPTT (Backpropagation Through Time) 

- 시간에 따라 펼친 신경망의 오차역전파법

문제점 :

  • 시계열 데이터가 너무 길면 계층이 길어짐에 따라 신경망 통과시 기울기 값이 조금씩 작아지는 문제
  • 이전 시각 t까지 역전파되기 전에 소멸 가능

해결법 : Truncated BPTT

  • 적당 지점에서 끊어 학습
  • 각각의 블록 단위로, 미래의 블록과는 독립적인 오차역전파 완결
  • 역전파의 연결은 끊어짐
  • 순전파의 연결은 끊어지지 않음 (데이터를 순서대로 입력해야 함)

Truncated BPTT의 데이터 처리 순서

 

 

 

 

RNN 구현

상하 방향의 입력과 출력을 각각 하나로 묶어 옆으로 늘어선 일련의 계층을 하나의 계층으로 간주

# ($x_{0}, x_{1}, ..., x_{T-1}$)을 묶은 xs 입력, ($h_{0}, h_{1}, ..., h_{T-1}$)을 묶은 hs를 출력

RNN클래스의 초기화, 순전파 메서드
RNN 계층의 backward()

class RNN:
    def __init__(self, Wx ,Wh ,b): #가중치 2개, 편향 1개
        self.params = [Wx, Wh, b] # 인수로 매개변수를 인스턴스 변수 params에 리스트로 저장
        self.grads = [np.zeros_like(Wx), np.zeros_like(Wh), np.zeros_like(b)]
        self.cache = None
        
    def forward(self, x, h_prev): # 아래로부터의 입력 x, 왼쪽으로부터의 입력 h_prev
        Wx, Wh, b = self.params
        t = np.matmul(h_prev, Wh) + np.matmul(x, Wx) + b  # RNN 계산식
        h_next = np.tanh(t)
        
        self.cache = (x,h_prev, h_next) # h_prev : 이전 RNN계층으로의 입력, h_next : 현 RNN 계층으로부터의 출력
        return h_next
        
    def backward(self, dh_next):
        Wx, Wh, b = self.params
        x, h_prev, h_next = self.cache

        dt = dh_next * (1 - h_next ** 2)
        db = np.sum(dt, axis=0)
        dWh = np.matmul(h_prev.T, dt)
        dh_prev = np.matmul(dt, Wh.T)
        dWx = np.matmul(x.T, dt)
        dx = np.matmul(dt, Wx.T)

        self.grads[0][...] = dWx
        self.grads[1][...] = dWh
        self.grads[2][...] = db

        return dx, dh_prev

RNN계층의 계산 그래프( 빨강 - 역전파 )

 

 

 

 

Time RNN 계층 구현  -  시계열 데이터를 한번에 처리하는 계층

- RNN 계층을 임의의 T개를 연결한 신경망

TimeRNN 계층의 초기화
TimeRNN 계층의 순전파
TimeRNN 계층의 역전파

class TimeRNN:
    def __init__(self, Wx, Wh, b, stateful = False):
        self.parms = [Wx, Wh, b]
        self.grads = [np.zeros_like(Wx), np.zeros_like(Wh), np.zeros_like(b)]
        self.layers = None
        
        self.h, self.dh = None, None
        self.stateful = stateful 
        # stateful - Time RNN 계층의 은닉상태 유지 확인 parameter
        # True  : Time RNN 계층의 hidden state 유지
        # False : Time RNN 계층에서의 hidden state를 영행렬로 초기화
        
    def set_state(self, h):
        self.h = h
        
    def reset_state(self):
        self.h = None
        
        
    def forward(self, xs): # 아래로부터의 T개 분량의 시계열 데이터를 하나로 모은 xs
        Wx, Wh, b = self.params
        N, T, D = xs.shape
        D, h = Wx.shape

        self.layers = []
        hs = np.empty((N, T, H), dype = 'f')

        if not self.stateful or self.h is None:
            self.h = np.zeros((N,H), dtype='f')

        for t in range(T):
            layer = RNN(*self.params)
            self.h = layer.forward(xs[:, t, :], self.h)
            hs[:, t, :] = self.h
            self.layers.append(layer)

        return hs
    
    
    def backward(self, dhs):
        Wx, Wh, b = self.params
        N, T, H = dhs.shape
        D, H = Wx.shape

        dxs = np.empty((N, T, D), dtype = 'f') # 각 시각의 기울기를 넣을 
        dh = 0
        grads = [0,0,0]
        for t in reversed(range(T)):
            layer = self.layers[t]
            dx, dh = layer.backward(dhs[:, t, :] + dh)
            dxs[:, t, :] = dx

            for i, grads in enumerate(layer.grads):
                grads[i] += grad

        for i, grad in enumerate(grads):
            self.grads[i][...] = grad
        self.dh = dg

        return dxs
    

Time RNN 계층의 계산 그래프( 빨강 - 역전파 )

  • (상류) 출력 층에서는 전해지는 기울기 dhs로 사용
  • (하류) 입력 층으로 내보내는 기울기 dxs로 사용

t번째 RNN 계층의 역전파

t번째 RNN 계층에서는 기울기 $dh_{t}$, 미래 계층으로부터의 기울기 $dh_{next}$ 가 전해짐

RNN계층의 순전파에서는 출력이 2개로 분기

  • 순전파 분기시 역전파에서 각 기울기가 합산되어 전해짐
  • 역전파시 RNN 계층에서는 합산된 기울기 $dh_{t} + dh_{next} 입력

 

Time 계층

  • Time Embedding - 순전파 시 T개의 Embedding계층 준비, 각 계층이 각 시각의 데이터 처리
  • Time Affine 계층 - Affine 계층을 T개 준비하여 각 시각의 데이터를 개별 처리

시계열 버전의 Softmax

$ x_{0}, x_{1} $ - 아래층에서 전해지는 점수(확률로 정규화 되기 전의 값)

$ t_{0}, t_{1} $ - 정답 레이블

최종 loss - T개의 Softmax with Loss 계층 각각의 손실을 산출하여 합산해 평균한 값

$$ L = \frac{1}{T}(L_{0} + L_{1} + ... + L_{T-1}) $$

 

 

RNNLM (RNN Language Model) 구현

RNNLM의 계층 구성(4개의 Time 계층을 쌓은 신경망)
RNNLM 초기화 코드
RNNML forward,backward, reset_state 메서드

 

import numpy as np

class SimpleRnnlm:
    def __init(self, vocab_size, wordvec_size, hidden_size):
        V, D, H = vocab_size, woordvec_size, hidden_size
        rn = np.random.randn
        
        # 가중치 초기화
        embed_W = (rn(V, D) / 100).astype('f')
        rnn_Wx = (rn(D, H) / np.sqrt(D)).astype('f')
        rnn_Wh = (rn(H, H) / np.sqrt(H)).astype('f')
        
        rnn_b = np.zeros(H).astype('f')
        affine_W = (rn(H, V) / np.sqrt(H)).astype('f')
        affine_b = np.zeros(V).astype('f')
        
        #계층 생성
        self.layers = [
            TimeEmbedding(embed_W),
            TimeRNN(rnn_Wx, rnn_Wh, rnn_b, stateful = True),
            TimeAffine(affine_W, affine_b)
        ]
        self.loss_layer = TimeSoftmaxWithLoss()
        self.rnn_layer = self.layers[1]
        
        # 모든 가중치와 기울기를 리스트에 모은다.
        self.params, self.grads = [], []
        for layer in self.layers:
            self.params += layer.params
            self.grads += layer.grads
            
            
    def forward(self, xs, ts):
        for layter in self.layers:
              xs = layer.forward(xs)
          loss = self.loss_layer.forward(xs, ts)
          return loss

    def backward(self, dout=1):
        dout = self.loss_layer.backward(dout)
        for layer in reversed(self.layers):
            dout = layer.backward(dout)
        return dout

    def reset_state(self): # 신경망의 상태 초기화
        self.rnn_layer.reset_state()

 

 

언어 모델의 평가

언어모델 

  • 주어진 과거 단어(정보)로부터 다음에 출현할 단어의 확률분포 출력
  • 언어 모델의 예측 성능 평가 척도로 perplexity(혼란도) 사용
    • 입력 데이터가 하나일 경우 
      • perplexity - 확률의 역수(ex A 다음에 B가 나올 확률 0.8 -> $\frac{1}{0.8}$ = 1.25)
        • 작을수록 좋음
        • 분기수(number of branches)로 해석
          • 예측사항에 출현 가능한 후보 수
    • 입력 데이터가 여러 개일 경우
      • $L = -\frac{1}{N}\sum_{n}\sum_{k}t_{nk}logy_{nk} $
      • perplexity = $e^L$
      • N - 데이터의 총개수
      • $t_{n}$ - 원핫 벡터로 나타낸 정답 레이블
      • $t_{nk}$ - n개째 데이터의 k번째 값
      • $y_{nk}$ - 확률분포
      • L - loss
      • $e^L$ - perplexity

            

### 본 게시물은 밑바닥부터 시작하는 딥러닝 2를 읽고 정리한 노트입니다. ###

반응형

댓글