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

CHAPTER 6 게이트가 추가된 RNN

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

5장 RNN에서의 문제점 - 시계열 데이터의 장기 의존 관계 학습 어려움(원인 : BPTT에서의 vanishing gradient , exploding gradient )

 

RNN - 시계열 데이터 $x_t$ 입력시 $h_t$(은닉 상태) 출력

기울기(gradient)

  • 학습해야 할 의미가 있는 정보
  • 과거로 전달, 장기 의존 관계 학습
  • 중간에 정보가 사라지면 가중치 매개변수의 갱신 불가

 

기울기 소실( vanishing gradient ), 기울기 폭발( exploding gradient )의 원인

  • 기울기 소실 - 기울기가 빠르게 작아지며 일정 수준 이하로 작아지면 가중치 매개변수가 갱신되지 않는 문제
  • 기울기 폭발 - 오버플로를 유발하여 NaN(Not a Number) 유발
    • 해결 방법 
      • 기울기 클리핑(gradients clipping)
        • 모든 매개변수에 대해 하나의 기울기로 처리한다고 가정 이를 $\hat g$로 표기
        • threshold를 문턱값으로 설정
        • 기울기의 L2노름 ||$\hat g$|| 이 문턱값 초과시 기울기 수정

 

기울기 클리핑 알고리즘
기울기 클리핑 구현

원인 1 : tanh ( y = tanh(x) )

RNN에서의 기울기 전파

위 그림처럼 RNN에서 역전파는 tanh, MatMul 연산을 통과한다

이 중 tanh의 값과 미분 값을 그래프로 그리면

점선 - 미분

tanh의 값은 1.0이하의 값이며 역전파에서 기울기가 tanh노드를 지날 때마다 값이 계속 작아진다. (vanishing gradient) 

 

원인 2 : MatMul(행렬 곱)

행렬 곱에 주목한 역전파의 기울기

dh라는 기울기가 지나간다고 했을 때, MataMul 에서 $dhW_{h} ^T$ 행렬 곱으로 기울기 계산( 시계열 데이터의 길이만큼 반복하여 ) -> 시간 크기에 비례하여 기울기 증가

-> exploding gradient -> 오버플로 -> NaN 발생

 

 

기울기 소실 문제를 해결하기 위한 LSTM

게이트가 추가된 RNN

RNN, LSTM의 비교(LSTM 계층에 c를 memory cell 이라 부른다.)

 

memory cell - 자기 자신으로만 주고받는 특징을 가진 경로(LSTM 계층 내)

LSTM의 hidden state - h는 다른 계층으로 출력

시각 t에서의 기억이 저장되어 있는 $c_t$의 정보를 활용해 외부 계층에 h_t(memory cell의 값을 tanh 함수로 변환한 값) 출력

memory cell 활용, hidden state $h_t$ 계산

게이트(gate) - 데이터의 흐름을 제어하는 역할

 

output gate - hidden state의 출력 담당하는 게이트

( 입력 $x_t$와 이전 상태 $h_{t-1}$를 활용하여 아래 식으로 계산 )

$o = \sigma(x_tW_x ^{(o)} + h_{t-1}W_h ^{(o)} + b^{(o)}) $ 

  • 게이트 임을 표시하기 위해 o 첨자 사용
  • $\sigma$ - 시그모이드 함수
  • 입력 $x_t$에 가중치 $W_x^{(o)} $, 이전 시각의 hidden state $h_{t-1} $에 가중치 $ W_h ^{(o)}$ 행렬들의 곱, 편향 $b^{(o)} $ 를 모두 더한 후 시그모이드 함수를 거쳐 출력 게이트의 o 출력

forget gate - 불필요한 기억을 잊도록 하는 게이트

$ f = \sigma(x_t W_x^{(f)} + h_{t-1} W_h^{(f)} + b^{(f)}) $

위의 식을 실행하여 forget 게이트의 출력 f를 구하며 이전 기억 셀인 $c_{t-1}$과의 원소별 곱, $ c_t = f \odot c_{t-1} $을 계산하여 $c_t$ 를 구함

새로운 기억 셀

새로운 정보 추가를 위해 tanh노드 추가, 계산하여 $c_{t-1} $에 더함

$ g = tanh(x_t W_x ^{(g)} + h_{t-1} W_h ^{(g)} + b^{(g)}) $ 의 수식으로 계산된다.

g(기억 셀에 추가하는 새로운 기억)에 이전 시각 $c_{t-1} $ 을 더해 새로운 기억 생성

 

input gate - g에 게이트 추가

g의 각 원소(추가되는 정보)의 가치가 얼마나 큰지에 대한 판단하기 위한 역할

$ i = \sigma (x_t W_x ^{(i)} + h_{t-1} W_h ^{(i)} + b^{(i)}) $ 계산 후 원소별 곱 결과를 기억셀에 추가

  • $\sigma$ - input 게이트 
  • i - 출력

기억 셀의 역전파

+ 노드 - 상류에서의 기울기를 그대로 전달

x 노드 - 원소별 곱(아마다르 곱)을 계산 -> 기울기 소실, 기울기 폭발 방지

 

LSTM 구현


LSTM(Long Short -Term Memory) - 직영하면 장,단기 메모리로 단기기억을 긴 시간 지속한다는 의미로 사용

 

최초 한 단계 처리 후 T개의 단계를 한번에 처리하는 Time LSTM 클래스 구현

LSTM에서 수행하는 계산식
각 식의 가중치들을 한번의 affine 변환으로 계산

위 예에서는 4개의 가중치, 편향을 하나로 모았다(개별적 수행으로 4번의 계산을 affine 변환을 통해 1번의 계산으로)     -> 계산 속도 향상 

* Affine transformation - 행렬 변환, 평행 이동(편향)을 결합한 형태

4개의 가중치를 모아 affine변환을 수행한 LSTM 계산 그래프
affine 변환 시의 형상

LSTM - 4개의 가중치를 하나로 보관 -> 매개변수 $W_x , W_h , b $ 총 3개 관리

RNN - 매개변수 $W_x , W_h , b $ 3개의 매개변수 관리 

LSTM, RNN - 매개변수의 수는 같다 / 형상이 다르다

 

Time LSTM 구현

class LSTM:
    def __init__(self, Wx, Wh, b): # 가중치 Wx, Wh, 편향 b 초기화
        self.params = [Wx, Wh, b]
        self.grads = [np.zeros_like(Wx), np.zeros_like(Wh), np.zeros_like(b)]
        self.cache = None
        
        self.h, self.c = None, None
        self.dh = None
        self.stateful = stateful
        
    def forward(self, x, h_prev, c_prev): # h_prev - 이전 시각 hidden state, x - 현 시각, c_prev - 이전 시각 memory cell
        Wx, Wh, b = self.params
        N, T, D = xs.shape
        H = Wh.shape[0]
        
        self.layers = []
        hs = np.empty((N, T, H), dtype = 'f')
        
        if not self.stateful or self.h is None:
            self.h = np.zeros((N,H), dtype = 'f')
        if not self.stateful or self.c is None:
            self.c = np.zeros((N,H), dtype ='f')
            
        for t in range(T):
            layer = LSTM(*self.params)
            self.h, self.c = layer.forward(xs[:,t,:], self.h, self.c)
            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 = Wx.shape[0]
        
        dxs = np.empty((N, T, D), dtype = 'f')
        dh, dc = 0,0
        
        grads = [0,0,0]
        for t in reversed(range(T)):
            layer = self.layers[t]
            dx, dh, dc = layer.backward(dhs[:, t, :] + dh, dc)
            dxs[:, t, :] = dx
            for i, grad in enumerate(layer.grads):
                grads[i] += grad
                
        for i, grad in enumerate(grads):
            self.grads[i][...] = grad
            self.dh = dh
            return dxs
            
	def set_state(self, h, c=None):
    	self.h, self.c = h, c
        
    def reset_state(self):
    	self.h, self.c = None, None

Time RNN, Time LSTM 모델

현 RNNLM의 개선점

  • LSTM 계층 다층화 (LSTM 계층을 깊게 쌓음)
    • -> 정확도 향상
  • overfitting을 막기위한 normalization의 일종 dropout 
    • 변형 드롭아웃의 예 (mask - 통과 차단을 결정하는 binary 형태의 무작위 패턴, 같은 계층의 드롭아웃끼리 마스크를 고정하여 정보의 손실을 최소화 하는 방법)
    • dropout의 경우 깊이 방향(시간축과 독립적인)으로 하여 정보 손실 최소화
  • 가중치 공유(weight tying)
    • 예( Embedding 계층, Sfotmax 앞의 Affine 계층의 가중치를 공유)
      • 학습하는 매개변수를 줄여(과적합을 줄여) 정확도를 향상

 

기존 RNN의 문제점

  • 기울기 소실
    • 해결방안 - 기존 RNN에 게이트(input, forget, output 등) 추가

 

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

반응형

댓글