RNN : Recurrent Neural Network
RNN의 기본 구조
Recurrent : 순환한다
Recurrent Neural Network : 순환하는 뉴럴 네트워크
순환을 하기 위해서는 "닫힌 경로" 혹은 "순환하는 경로"가 필요함.
데이터가 순환하면서 끊임없이 정보가 갱신됨
RNN에 이용되는 계층을 "RNN 계층"이라고 부름
RNN의 간단한 구조는 아래와 같음
RNN layer는 순환하는 경로를 포함한다.
- 이 순환 경로를 따라 데이터를 layer 안에서 순환시킬 수 있음.
또한 $x_t$를 입력으로 받으며 $t$는 시각을 뜻함
이는 입력으로 $(x_0, x_1, ..., x_t, ...)$ 의 시계열 데이터가,
출력으로 $(h_0, h_1, ..., h_t, ...)$ 가 입력에 대응되어 나오는 것을 확인할 수 있음
입력 $x_t$는 vector라고 가정. 문장(단어 순서)을 다루는 경우를 예로 든다면, 각 단어의 분산 표연(단어 벡터)이 $x_t$가 되며, 이 분산 표현이 순서대로 하나씩 RNN의 layer에 입력되는 것.
--
위 그림을 보면 출력이 2개의 방향으로 나눠져 있는 것을 확인할 수 있음.
이는 출력이 복제되어 다시 자신의 입력으로 들어가는 것으로 볼 수 있음
--
RNN의 자세한 구조
RNN 구조를 한 번 펼쳐보면 아래와 같다
펼쳐보면 길게 늘어진 형태의 신경망으로 볼 수 있다.
여기서의 RNN layer는 기존 신경망과는 다르게 모두 "같은 layer"를 가진다.
각 시각($t$)의 RNN layer는 layer로의 입력과($x_t$) 1개 전의 RNN layer($h_{t-1}$)의 출력을 받는다.
이 두 정보를 바탕으로 아래의 현재 시점의 출력을 계산한다.
$h_t = tanh(h_{t-1}W_h + x_tW_x + b)$
$W_x$ : input $x$를 출력 $h$로 변환하기 위한 가중치
$W_h$ : 1개의 RNN의 출력($h_{t-1}$)을 다음 시점으로 변환하기 위한 가중치
$x_t$와 $h_{t-1}$은 행벡터
위 입력과 가중치를 통해 연산을 진행한 후 tanh(hyperbolic tangent) activation fuction을 이용하여 최종 output을 내보냄.
그 결과로 output $h_t$가 출력.
이 $h_t$는 다른 layer를 향해 출력되는 동시에, 다음 step($t$)의 RNN layer를 향해서도 출력
위 식을 잘 보면 현재의 출력 $h_t$는 한 시점 이전의 출력 $h_{t-1}$에 기초해 계산됨을 알 수 있음.다른 관점으로 보면, RNN은 $h$라는 '상태'를 가지고 있으며, 위 식으로 갱신된다고 해석 가능.
따라서 RNN layer를 '상태를 가지는 계층' 혹은 '메모리가 있는 layer'라고 한다.
여기서 $h_t$을 hidden state 혹은 hidden state vector라고 표현가능.
Backpropagation Through Time(BPTT)
RNN에는 일반적인 back-propagation을 수행할 수 있음.
이를 시간에 따라 펼친 오차역전파라고 해서 Backpropagation Through Time이라고 표현
그러나 위 방법은 문제가 발생할 수 있음. -> 긴 시계열 데이터를 학습할 때, 데이터의 시간이 커지는 것에 비례하여 소비하는 컴퓨팅 자원도 증가하기 때문에
-> 시간의 크기가 커지면 backpropagation 시 gradient가 불안정해질 수도 있음.
Truncated BPTT
큰 시계열 데이터를 처리할 때는 흔히 neural network 연결을 적당한 길이로 '끊는다'. 시간축 방향으로 너무 길어진 신경망을 적당한 지점에서 잘라내어 작은 신경망 여러 개로 만든다는 아이디어. 그리고 이 잘라낸 신경망에서 backpropagation 수행.
- "back-propagation의 연결"만 끊어야 한다. (feed-forward는 그대로 유지)
- back-propagation의 연결은 적당한 길이로 잘라내, 그 잘라낸 신경망 단위로 학습.
위에서는 RNN layer를 길이 10개 단위로 학습할 수 있도록 back-propagation의 연결을 끊었다.
- 이렇게되면 그보다 미래의 데이터에 대해서는 생각할 필요가 없음.
- 각각의 블록 단위로, 미래 블록과는 독립적으로 back-propagation을 수행
Truncated BPTT의 학습 방식
첫 번째 블록의 순전파와 역전파
- 이 블록 내에선만 역전파 수행
두 번째 블록의 순전파와 역전파
마지막 은닉 상태인 $h_9$이 필요하다는 것.
전체적인 흐름
Truncated BPTT의 미니배치 학습
RNN의 구현
RNN에서 다루는 신경망은 결국 아래의 그림과 같다(가로 길이 고정)
1. 신경망은 길이가 $T$인 시계열 데이터를 받는다 ($T$는 임의의 값)
2. 각 시각의 hidden state $T$개 출력
3. 모듈화를 생각해 위의 그림의 신경망을 '하나의 계층'으로 구현
위와 같이 상하 방향의 입력과 출력을 각각 하나로 묶으면 옆으로 늘어선 일련의 layer를 하나의 layer로 간주할 수 있다.
이때 Time RNN layer 내에서 한 단계 작업을 수행하는 layer를 'RNN layer'라 하고, $T$ 단계의 작업을 한꺼번에 처리하는 계층을 'Time RNN layer'라고 한다.
RNN 계층 구현
미니배치로 모아 처리한다고 가정.
$x_t$와 $h_t$에는 각 샘플 데이터를 행 방향으로 저장
RNN의 순전파
$h_t = tanh(h_{t-1}W_h + x_tW_x + b)$
N : 미니배치
D : 입력차원 수
H : hidden state vector 차원 수
class RNN:
def __init__(self, Wx, Wh, b):
self.params = [Wx, Wh, b] # 인수로 받은 paramters 리스트 저장
self.grads = [np.zeros_like(Wx), np.zeros_like(Wh), np.zeros_like(b)] # 기울기 초기화
self.cache = None # 역전파 계산 시 사용하는 중간 데이터 cache 초기화
def forward(self, x, h_prev): # 입력 x, 이전 t의 output 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)
return h_next
RNN의 순전파와 역전파를를 computational graph로 확인
RNN의 backward
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
이 글의 내용은 많은 부분 아래 링크인 밑바닥부터 시작하는 딥러닝 2을 참고했으며
영리 목적이 아닌 개인적인 학습을 위해 정리한 내용을 바탕으로 작성했음을 밝힙니다.
내용 참고
https://book.naver.com/bookdb/book_detail.nhn?bid=14797086