밑바닥부터 시작하는 딥러닝 - 순환 신경망(RNN)(1)
지금까지 살펴본 신경망은 피드포워드라는 유형의 신경망이다. 피드포워드란 흐름이 단방향인 신경망을 말한다. 즉 입력 신호가 다음 층(중간층)으로 전달되고, 그 신호를 받은 층은 그다음 층으로 전달하고, 또 다음 층으로 전달되는 한 방향으로만 신호가 전달된다.
피드포워드 신경망은 구성이 단순하여 구조를 이해하기 쉽고, 많은 문제에 응용할 수 있다. 그러나 시계열 데이터를 잘 다루지 못한다는 단점이 있다. 단순한 피드포워드 신경망에서는 시계열 데이터의 성질을 충분히 학습할 수 없다.
그래서 순환 신경망(CNN)이 등장하게 된다.
RNN
RNN이란 '몇 번이나 반복해서 일어나는 일'을 뜻한다. 즉 '순환하는 신경망'이라는 뜻이 된다.
어느 한 지점에서 시작한 것이, 시간을 지나 다시 원래 장소로 돌아오는 것, 그리고 이 과정을 반복하는 것이 바로 순환이다. 순환하기 위해서는 '닫힌 경로가' 필요하다.
닫힌 경로 혹은 순환하는 경로가 존재해야 데이터가 같은 장소를 반복해 왕래할 수 있다.
그리고 데이터가 순환하면서 정보가 끊임없이 갱신하게 된다.
RNN의 특징은 순환하는 경로가 있다는 것이다. 이 순환 경로를 따라 데이터는 끊임없이 순환할 수 있다.
그리고 데이터가 순환되기 때문에 과거의 정보를 기억하는 동시에 최신 데이터로 갱신될 수 있다.
위의 그림처럼 RNN 계층은 순환하는 경로를 포함한다. 이 순환 경로를 따라 데이터를 계층 안에서 순활시킬 수 있다.
위 그림은 시계열 데이터가 RNN 계층에 입력됨을 표현한 것이다. 그리고 그 입력에 대응하여 H(t)가 출력된다.
순환 구조 펼치기
RNN의 순환 구조는 지금까지의 신경망에는 존재하지 않던 구조이다.
RNN 계층의 순환 구조를 펼침으로써 오른쪽으로 성장하는 긴 신경망으로 변신시킬 수 있다. 다만 위의 그림에 등장하는 다수의 RNN 계층 모두가 실제로는 '같은 계층'인 것이 지금까지의 신경망과는 다르다.
각 시각의 RNN 계층은 그 계층으로의 입력과 1개 전의 RNN 계층으로부터의 출력을 받는다.
그리고 이 두 정보를 바탕으로 현 시각의 출력을 계산한다. 이때 수행하는 계산의 수식은 다음과 같다.
RNN에서는 가중치가 2개가 있다. 하나는 입력 x를 출력 h로 변환하기 위한 가중치 W(x)이고, 다른 하나는 1개의 RNN 출력을 다음 시각의 출력으로 변환하기 위한 가중치 W(h)이다. 또한 편향 b도 있다.
행렬 곱을 계산하고, 그 합을 쌍곡탄젠트 함수를 이용해 변환한다.
그 결과가 시각 t의 출력 H(t)가 된다. 이 H(t)는 다른 계층을 향해 위쪽으로 출력되는 동시에, 다음 시각의 RNN 계층을 향해 오른쪽으로도 출력된다.
BPTT
RNN 계층은 가로로 펼친 신경망으로 간주할 수 있다.
따라서 RNN의 학습도 보통의 신경망과 같은 순서로 진행할 수 있다.
순환 구조를 펼친 후의 RNN에는 오차역전파법을 적용할 수 있다.
먼저 순전파를 수행하고, 이어서 역전파를 수행하여 원하는 기울기를 구할 수 있다. 여기서의 오차역전파법은 '시간 방향으로 펼친 신경망의 오차역전파법'이란 뜻으로 BPTT(Backpropagation Through Time)라고 한다.
이를 이용하면 RNN을 학습할 수 있을 듯 보인다. 하지만 해결해야 할 문제가 하나 있다.
그것은 바로 긴 시계열 데이터를 학습할 때의 문제이다. 시계열 데이터의 시간 크기가 커지는 것에 비례하여 BPTT가 소비하는 컴퓨팅 자원도 증가하기 때문이다. 또한 시간 크기가 커지면 역전파 시의 기울기가 불안정해지는 것도 문제이다.
Truncated BPTT
큰 시계열 데이터를 취급할 때는 흔히 신경망 연결을 적당한 길이로 끊는다. 그리고 이 잘라낸 작은 신경망에서 오차역전파법을 수행한다. 이것이 Truncated BPTT 기법이다.
Truncated BPTT에서는 신경망의 연결을 끊지만, 제대로 구현하려면 '역전파'의 연결만 끊어야 한다. 순전파의 연결은 반드시 그대로 유지해야 한다. 즉, 순전파의 흐름은 끊어지지 않고 전파된다. 한편, 역전파의 연결은 적당한 길이로 잘라내, 그 잘라낸 신경망 단위로 학습을 수행한다.
길이가 1,000인 시계열 데이터가 있다고 해보자.
길이가 1,000인 시계열 데이터를 다루면서 RNN 계층을 펼치면 계층이 가로로 1,000개나 늘어선 신경망이 된다. 물론 계층이 아무리 늘어서더라도 오차역전파법으로 기울기를 계산할 수는 있다.
하지만 너무 길면 계산량과 메모리 사용량 등이 문제가 된다.
계층이 길어짐에 따라 신경망을 하나 통과할 때마다 기울기 값이 조금씩 작아져서, 이전 시각 t까지 역전파되기 전에 0이 되어 소멸할 수도 있다.
위의 그림처럼 가로로 길게 뻗은 신경망의 역전파에서는 연결을 적당한 길이로 끊을 생각을 한 것이다.
RNN 계층을 길이 10개 단위로 학습할 수 있도록 역전파의 연결을 끊었다. 이처럼 역전파의 연결을 잘라버리면, 그보다 미래의 데이터에 대해서는 생각할 필요가 없어진다. 따라서 각각의 블록 단위로, 미래의 블록과는 독립적으로 오차역전파법을 완결시킬 수 있다.
먼저 순전파를 수행하고, 그다음 역전파를 수행한다. 이렇게 하여 원하는 기울기를 구할 수 있다. 이어서 다음 블록의 입력 데이터를 입력해 오차역전파법을 수행한다.
이번 순전파 계산에는 앞 블록의 마지막 은닉 상태인 h(9)가 필요하다는 것이다. 이것으로 순전파는 계속 연결될 수 있다.
RNN 학습의 흐름은 위의 그림처럼 된다.
Truncated BPTT에서는 데이터를 순서대로 입력해 학습한다. 이런 식으로 순전파의 연결을 유지하면서 블록 단위로 오차역전파법을 적용할 수 있다.
RNN 구현
지금부터 구현해야 할 것은 가로 방향으로 성장한 신경망이다.
다룰 신경망은 길이가 T인 시계열 데이터를 받는다. 그리고 각 시각의 은닉 상태를 T개 출력한다. 그리고 모듈화를 생각해, 옆으로 성장한 신경망을 '하나의 계층'으로 구현한다.
위의 그림처럼 구현되게 된다. 상하 방향의 입력과 출력을 각각 하나로 묶으면 옆으로 늘어선 일련의 계층을 하나의 계층으로 간주할 수 있다. T개 단계분의 작업을 한꺼번에 처리하는 계층을 'Time RNN 계층'이라고 한다.
RNN 계층 구현
RNN 처리를 한 단계만 수행하는 RNN 클래스를 구현해보자.
여기에서 데이터를 미니배치로 모아 처리한다. 따라서 X(t)에는 각 샘플 데이터를 행 방향에 저장한다.
class RNN:
def __init__(self, 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
def forward(self, x, h_prev):
Wx, Wh, b = self.params
t = no.matmul(h_prev, Wh) + np.matmul(x, Wx) + b
h_next = np.tanh(t)
self.cache = (x, h_prev, h_next)
return h_next
RNN 초기화 메서드는 가중치 2개와 편향 1개를 인수로 받는다. 여기에서는 인수로 받은 매개변수를 인스턴스 변수 params에 리스트로 저장한다. 그리고 각 매개변수에 대응하는 형태로 기울기를 초기화한 후 grads에 저장한다. 마지막으로 역전파 계산 시 사용하는 중간 데이터를 담을 cache를 None으로 초기화한다.
순전파인 forward(x, h_prev) 메서드에서는 인수 2개(아래로부터의 입력 x와 왼쪽으로부터의 입력 h_prev)를 받는다.
또한 앞의 RNN 계층으로부터 받는 입력이 h_prev이고, 현 시각 RNN 계층으로부터의 출력은 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
역전파는 순전파 때와는 반대 방향으로 각 연산자의 역전파를 수행하면 된다.
'딥러닝' 카테고리의 다른 글
밑바닥부터 시작하는 딥러닝 - 게이트가 추가된 RNN(1) (0) | 2021.10.29 |
---|---|
밑바닥부터 시작하는 딥러닝 - 순환 신경망(RNN)(2) (0) | 2021.10.27 |
밑바닥부터 시작하는 딥러닝 - word2vec 속도 개선(3) (0) | 2021.10.25 |
밑바닥부터 시작하는 딥러닝 - word2vec 속도 개선(2) (0) | 2021.10.24 |
밑바닥부터 시작하는 딥러닝 - word2vec 속도 개선(1) (0) | 2021.10.23 |