티스토리 뷰
기본적인 RNN 구조의 문제점
1. 기울기 손실 / 기울기 폭발
장기 기억의 문제점 : 예를 들어 "Tom was watching TV in his room. Mary came into the room. Mary said hi to ?" 라는 문장에서 ?=Tom 이지만, Tom은 첫 단어 이외에 등장하지 않습니다. 순차적으로 진행해나가면서 Tom이라는 정답레이블이 주어질 때 역전파 과정에서 의미 있는 기울기를 전달해야하지만, 전달 과정에서 기울기 가중치가 0이 될 수 있습니다. 이를 기울기 손실이라 합니다. 그리고 역전파 과정에서의 행렬 곱에서는 기울기 폭발이 일어납니다.
(자세한 내용 책 참고)
2. 기울기 폭발 대책 - 기울기 클리핑
식은 다음과 같습니다.
hat(g) = 신경망에서 사용되는 매개변수의 기울기를 모은 것
threshold = 문턱값
이는 다음과 같이 구현할 수 있습니다.
# 기울기 폭발 대체
import numpy as np
dW1 = np.random.rand(3,3) * 10
dW2 = np.random.rand(3,3) * 10
grads = [dW1, dW2]
max_norm = 5.0
def clip_grads(grads, max_norm):
total_norm = 0
for grad in grads:
total_norm += np.sum(grad**2)
total_norm = np.sqrt(total_norm)
rate = max_norm / (total_norm + 1e-6)
if rate < 1:
for grad in grads:
grad *= rate
print('before:', dW1.flatten())
clip_grads(grads, max_norm)
print('after:', dW1.flatten())
3. LSTM
LSTM의 기본 구조는 다음과 같습니다. (n_t 는 h_t가 잘린 것)
c_t = 시각 t에서의 LSTM의 기억이 저장, 과거로부터 t까지 필요한 정보가 저장되도록 학습 수행
h_t = 은닉 상태
x_t = 입력 값
sigma = sigmoid 함수
f = forget게이트, c_(t-1)에서 불필요한 기억 제거하는 역할
( f = sigma(x_t * W_x + h_(t-1) * W_h + b) ( * : 행렬곱 ) => c_t = f * c_(t-1) ( * : 원소별 곱)
g = 새로 기억해야할 정보 추가
( g = tanh(x_t * W_x + h_(t-1) * W_h + b )
i = input 게이트, g의 각 원소가 새로 추가되는 정보로써의 가치가 얼마나 큰지 판단
( i = sigma(x_t * W_x + h_(t-1) * W_h + b) )
o = output 게이트, 다음 은닉 상태 h_t를 출력
( o = sigma(x_t * W_x + h_(t-1) * W_h + b) )
=> c_t = f * c_(t-1) + g * i , h_t = o * tanh(c_t) ( * : 원소 별 곱)
실제 구현시 겹치는 부분이 많으므로 공통 부분 (x_t, h_(t-1), b) 를 묶어 Affine 변환을 이용해서 계산합니다.
from common.np import * # import numpy as np (or import cupy as np)
from common.layers import *
from common.functions import sigmoid
class LSTM:
def __init__(self, Wx, Wh, b):
'''
Parameters
----------
Wx: 입력 x에 대한 가중치 매개변수(4개분의 가중치가 담겨 있음)
Wh: 은닉 상태 h에 대한 가장추 매개변수(4개분의 가중치가 담겨 있음)
b: 편향(4개분의 편향이 담겨 있음)
'''
self.params = [Wx, Wh, b]
self.grads = [np.zeros_like(Wx), np.zeros_like(Wh), np.zeros_like(b)]
self.cache = None
def forward(self, x, h_prev, c_prev):
Wx, Wh, b = self.params
N, H = h_prev.shape
A = np.dot(x, Wx) + np.dot(h_prev, Wh) + b
f = A[:, :H]
g = A[:, H:2*H]
i = A[:, 2*H:3*H]
o = A[:, 3*H:]
f = sigmoid(f)
g = np.tanh(g)
i = sigmoid(i)
o = sigmoid(o)
c_next = f * c_prev + g * i
h_next = o * np.tanh(c_next)
self.cache = (x, h_prev, c_prev, i, f, g, o, c_next)
return h_next, c_next
def backward(self, dh_next, dc_next):
Wx, Wh, b = self.params
x, h_prev, c_prev, i, f, g, o, c_next = self.cache
tanh_c_next = np.tanh(c_next)
ds = dc_next + (dh_next * o) * (1 - tanh_c_next ** 2)
dc_prev = ds * f
di = ds * g
df = ds * c_prev
do = dh_next * tanh_c_next
dg = ds * i
di *= i * (1 - i)
df *= f * (1 - f)
do *= o * (1 - o)
dg *= (1 - g ** 2)
dA = np.hstack((df, dg, di, do))
dWh = np.dot(h_prev.T, dA)
dWx = np.dot(x.T, dA)
db = dA.sum(axis=0)
self.grads[0][...] = dWx
self.grads[1][...] = dWh
self.grads[2][...] = db
dx = np.dot(dA, Wx.T)
dh_prev = np.dot(dA, Wh.T)
return dx, dh_prev, dc_prev
4. TimeLSTM
LSTM T개를 묶은 TimeLSTM (책에서 명명한 것)은 다음과 같습니다.
from common.np import * # import numpy as np (or import cupy as np)
from common.layers import *
from common.functions import sigmoid
class TimeLSTM:
def __init__(self, Wx, Wh, b, stateful=False):
self.params = [Wx, Wh, b]
self.grads = [np.zeros_like(Wx), np.zeros_like(Wh), np.zeros_like(b)]
self.layers = None
self.h, self.c = None, None
self.dh = None
self.stateful = stateful
def forward(self, xs):
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
'딥러닝' 카테고리의 다른 글
keras를 활용한 MNIST 모델 훈련시키기 (기본) (0) | 2021.03.09 |
---|---|
밑바닥부터 시작하는 딥러닝 2권 후기 및 앞으로 계획 (0) | 2021.02.19 |
BPTT (0) | 2021.01.29 |
네거티브 샘플링 (0) | 2021.01.27 |
CBOW 1 (0) | 2021.01.26 |