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계층은 그 계층으로부터의 입력, 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
- 적당 지점에서 끊어 학습
- 각각의 블록 단위로, 미래의 블록과는 독립적인 오차역전파 완결
- 역전파의 연결은 끊어짐
- 순전파의 연결은 끊어지지 않음 (데이터를 순서대로 입력해야 함)
RNN 구현
상하 방향의 입력과 출력을 각각 하나로 묶어 옆으로 늘어선 일련의 계층을 하나의 계층으로 간주
# ($x_{0}, x_{1}, ..., x_{T-1}$)을 묶은 xs 입력, ($h_{0}, h_{1}, ..., h_{T-1}$)을 묶은 hs를 출력
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
Time RNN 계층 구현 - 시계열 데이터를 한번에 처리하는 계층
- RNN 계층을 임의의 T개를 연결한 신경망
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
- (상류) 출력 층에서는 전해지는 기울기 dhs로 사용
- (하류) 입력 층으로 내보내는 기울기 dxs로 사용
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) 구현
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)로 해석
- 예측사항에 출현 가능한 후보 수
- perplexity - 확률의 역수(ex A 다음에 B가 나올 확률 0.8 -> $\frac{1}{0.8}$ = 1.25)
- 입력 데이터가 여러 개일 경우
- $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를 읽고 정리한 노트입니다. ###
'----------책---------- > 밑바닥부터 시작하는 딥러닝2' 카테고리의 다른 글
CHAPTER 7 RNN을 사용한 문장생성 (0) | 2020.03.09 |
---|---|
CHAPTER 6 게이트가 추가된 RNN (0) | 2020.02.27 |
CHAPTER 4 word2vec 속도 개선 (0) | 2020.02.06 |
CHAPTER 3 word2vec (0) | 2020.02.03 |
CHAPTER2 - 자연어와 단어의 분산 표현 (0) | 2020.01.31 |
댓글