독서/밑바닥부터 시작하는 딥러닝 2

[밑바닥부터 시작하는 딥러닝 2]Chapter 6. 게이트가 추가된 RNN

Jesse 2024. 7. 30. 20:49

6.1 RNN의 문제점

BPTT에서의 vanishing gradient , exploding gradient 으로 인해 시계열 데이터의 장기 의존 관계 학습에 어려움이 있다.

 

RNN - 시계열 데이터 xt𝑥𝑡 입력시 htℎ𝑡(은닉 상태) 출력

기울기(gradient)

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

 

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

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

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

RNN에서의 기울기 전파

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

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

점선 - 미분

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

 

원인 2 : MatMul(행렬 곱)

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

dh라는 기울기가 지나간다고 했을 때, MataMul 에서 dhWTh𝑑ℎ𝑊ℎ𝑇 행렬 곱으로 기울기 계산( 시계열 데이터의 길이만큼 반복하여 ) -> 시간 크기에 비례하여 기울기 증가

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

 

 

6.2 기울기 소실과 LSTM

게이트가 추가된 RNN

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

 

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

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

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

memory cell 활용, hidden state htℎ𝑡 계산

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

 

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

( 입력 xt𝑥𝑡와 이전 상태 ht1ℎ𝑡−1를 활용하여 아래 식으로 계산 )

o=σ(xtW(o)x+ht1W(o)h+b(o))𝑜=𝜎(𝑥𝑡𝑊𝑥(𝑜)+ℎ𝑡−1𝑊ℎ(𝑜)+𝑏(𝑜)) 

  • 게이트 임을 표시하기 위해 o 첨자 사용
  • σ𝜎 - 시그모이드 함수
  • 입력 xt𝑥𝑡에 가중치 W(o)x𝑊𝑥(𝑜), 이전 시각의 hidden state ht1ℎ𝑡−1에 가중치 W(o)h𝑊ℎ(𝑜) 행렬들의 곱, 편향 b(o)𝑏(𝑜) 를 모두 더한 후 시그모이드 함수를 거쳐 출력 게이트의 o 출력

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

f=σ(xtW(f)x+ht1W(f)h+b(f))𝑓=𝜎(𝑥𝑡𝑊𝑥(𝑓)+ℎ𝑡−1𝑊ℎ(𝑓)+𝑏(𝑓))

위의 식을 실행하여 forget 게이트의 출력 f를 구하며 이전 기억 셀인 ct1𝑐𝑡−1과의 원소별 곱, ct=fct1𝑐𝑡=𝑓⊙𝑐𝑡−1을 계산하여 ct𝑐𝑡 를 구함

새로운 기억 셀

새로운 정보 추가를 위해 tanh노드 추가, 계산하여 ct1𝑐𝑡−1에 더함

g=tanh(xtW(g)x+ht1W(g)h+b(g))𝑔=𝑡𝑎𝑛ℎ(𝑥𝑡𝑊𝑥(𝑔)+ℎ𝑡−1𝑊ℎ(𝑔)+𝑏(𝑔)) 의 수식으로 계산된다.

g(기억 셀에 추가하는 새로운 기억)에 이전 시각 ct1𝑐𝑡−1 을 더해 새로운 기억 생성

 

input gate - g에 게이트 추가

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

i=σ(xtW(i)x+ht1W(i)h+b(i))𝑖=𝜎(𝑥𝑡𝑊𝑥(𝑖)+ℎ𝑡−1𝑊ℎ(𝑖)+𝑏(𝑖)) 계산 후 원소별 곱 결과를 기억셀에 추가

  • σ𝜎 input 게이트 
  • i - 출력

기억 셀의 역전파

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

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

 

6.3 LSTM 구현 


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

 

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

LSTM에서 수행하는 계산식

각 식의 가중치들을 한번의 affine 변환으로 계산

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

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

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

affine 변환 시의 형상

LSTM - 4개의 가중치를 하나로 보관 -> 매개변수 Wx,Wh,b𝑊𝑥,𝑊ℎ,𝑏 총 3개 관리

RNN - 매개변수 Wx,Wh,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 등) 추가