피드포워드(feed forward) : 흐름이 단방향인 신경망 (예. word2vec 등)
입력신호가 다음 층(중간층)으로 전달되고 그 신호를 받은 층은 그 다음 층으로 전달하는 식으로 한 방향으로만 신호가 전달된다.
구성이 단순하여 구조를 이해하기 쉽기 때문에 많은 문제에 응용할 수 있다.
그러나 시계열 데이터를 잘 다루지 못한다.
이를 해결하기 위해 순환 신경망(Recurrent Neural Network, RNN)이 등장한다.
5.1. 확률과 언어 모델
언어모델(Lanugage Model)은 단어 나열에 확률을 부여한다.
특장 단어의 시퀀스에 대해 그 시퀀스가 일어날 가능성이 어느 정도인지(얼마나 자연스러운 단어 순서인지)를 확률로 평가한다.
m개 단어로 된 문장을 생각했을 때, 단어가 w1, ..., wm 순서로 출현할 확률은 P(w1, ..., wm)으로 나타낸다.
이 확률은 여러 사건이 동시에 일어날 확률이므로 동시확률이다.
이 동시확률을 사후확률의 총곱으로 식 5-4와 같이 표현할 수 있다.
파이기호는 모든 원소를 곱하는 총곱
식 5-4는 곱셈정리로 유도할 수 있다.
A와 B가 모두 일어날 확률 P(A, B)는 B가 일어날 확률 P(B)와 B가 일어난 후 A가 일어날 확률 P(A|B)를 곱한 값과 같다.
식 5-4를 보면 동시확률 P(w1, ..., wm)은 사후 확률의 총곱으로 타깃 단어보다 왼쪽에 있는 모든 단어를 맥락(조건)으로 했을 때의 확률이다.
이를 달리하면 P(w1, ..., wm)을 구하기 위해선 P(wt | w1, ..., wt-1) 확률을 구하면 된다.
✔️ CBOW 모델을 언어 모델로
이러한 언어모델을 CBOW모델에 적용하려면 맥락의 크기를 특정 값으로 한정하여 근사적으료 표현할 수 있다.
맥락을 2개의 단어로 이용하면 식 5-8과 같다.
맥락의 크기를 임의로 설정한다고 해도 결국 특정길이로 '고정'이 된다.
그림 5-4와 같은 문제에서 ?의 답을 하려면 18번째 앞인 Tom을 기억해야 한다.
맥락의 크기를 키워서 문제를 해결할 수도 있지만 결국 단어의 순서가 무시된다는 한계가 있다.
(CBOW = continuous bag-of-words로, 가방 안의 단어는 순서가 무시된다는 뜻도 내포한다.)
그림 5-5의 왼쪽과 같이 CBOW모델의 은닉층에서 단어 벡터들이 더해지므로 맥락의 단어 순서는 무시된다.
(you, say)와 (say, you)는 같은 맥락으로 취급한다.
그림 5-5의 오른쪽과 같이 맥락의 단어 벡터를 은닉층에 연결하는 방식을 생각할수도 있지만 가중치 매개변수도 늘어나 좋지 않은 방법이다.
5.2. RNN이란
RNN(Recurrent Neural Network)은 순환신경망으로, Recurrent는 '재발한다', '주기적으로 일어난다', '순환한다'라는 뜻이다.
'순환한다'는 '반복해서 되돌아감'을 의미한다. 즉, 한 지점에서 시작한 것이 시간을 지나 다시 원래 장소로 돌아오는 것, 그리고 이 과정을 반복하는 것이다.
순환하기 위해선 '닫힌 경로'가 필요하며, '닫힌 경로'가 존재해야 데이터가 같은 장소를 반복해 왕래하며 정보가 끊임없이 갱신된다.
이처럼 RNN은 순환하는 경로(닫힌 경로)가 있고, 이 경로를 따라 데이터가 순환하며 과거의 정보를 기억하는 동시에 최신 데이터로 갱신된다.
그림 5-6처럼 RNN계층은 순환하는 경로를 포함한다.
xt를 입력으로 받는데 여기서 t는 시각을 뜻한다.
이는 시계열 데이터(x0, x1, ..., xt, ...)가 RNN계층에 입력되며, 그에 대응한 (h0, h1, ..., ht, ...)가 출력된다.
RNN계층의 순환구조를 펼치면 그림 5-8과 같이 나타낼 수 있다.
오른쪽으로 성장하는 긴 신경망으로 변신되며, 피드포워드 신경망(데이터가 한 방향으로만 흐르는 신경망)과 같은 구조이다.
다만, 다수의 RNN계층 모두가 '같은 계층'이라는 것이 특징이다.
각 시각의 RNN 계층은 그 계층으로의 입력과 이전의 RNN 계층으로부터의 출력을 받는다.
이 두 정보를 바탕으로 현 시각의 출력을 계산한다.
입력 xt를 h로 변환하기 위한 가중치 Wx, 이전의 RNN 출력을 다음 시각의 출력으로 변환하기 위한 가중치 Wh, 편향 b
RNN은 한 시각 이전 출력에 기초해 계산된다. 이는 h라는 '상태'를 가지며 이것이 갱신된다고 해석할 수 있다.
그래서 RNN 계층은 '상태를 가지는 계층' 혹은 '메모리(기억력)가 있는 계층'이라고 한다.
✔️ BPTT
그림 5-10처럼 순환구조를 펼친 후의 RNN은 오차역전파법을 적용할 수 있다.
즉, 순전파를 수행하고 이어서 역전파를 수행하여 원하는 기울기를 구할 수 있다.
여기서 오차역전파법은 '시간 방향으로 펼친 신경망의 오차역전파법'이라는 뜻으로 BPTT(Backpropagation Through Time)라고 한다.
✔️ Truncated BPTT
하지만 긴 시계열 데이터를 학습할 때, 시간 크기가 커지는 것에 비례해 BPTT가 소비하는 컴퓨팅 자원도 증가한다.
또한, 시간 크기가 커지면 역전파 시의 기울기가 조금씩 작아져 0이 되어 소멸하는 불안정한 문제도 있다.
이를 해결하기 위해 시간축방향으로 너무 길어진 신경망을 적당한 지점에서 잘라낸다.
이 작아진 신경망 여러개로 오차역전파법을 수행하는데, 이를 Truncated BPTT라고 한다.
여기서 '역전파'의 연결만 끊고 순전파의 연결은 그대로 유지해야 한다. 즉 데이터를 순서대로(sequential) 입력해야 한다.
그림 5-11처럼 역전파의 연결을 자르면 그보다 미래의 데이터에 대해서는 생각할 필요가 없다.
따라서 각각의 블록단위로 미래의 블록과는 독립적으로 오차역전파법을 완결시킬 수 있다.
Truncated BPTT 방식으로 RNN을 학습 시켜보자.
첫번째 블록 입력데이터(x0, ..., x9)를 RNN 계층에 제공하면, 순전파를 수행하고 그다음 역전파를 수행한다.
이어서 다음 블록 입력데이터(x10, ..., x19)를 입력해 순전파를 수행하고 역전파를 수행한다.
여기서 순전파 계산에는 앞 블록의 마지막 은닉 상태인 h9가 필요하다. 이로써 순전파는 계속 연결될 수 있다.
이러한 과정이 반복되면서 RNN학습에서는 데이터를 순서대로 입력하며 은닉상태를 계승하면서 학습을 수행한다.
✔️ Truncated BPTT의 미니배치 학습
미치배치 학습할 때는 데이터를 주는 시작 위치를 각 미니배치의 시작 위치로 옮겨주면 된다.
길이가 1000인 시계열 데이터에 대해 시각 길이를 10개 단위로 자른 Truncated BPTT를 미치배치 2개로 구성해 학습하면 그림 5-15와 같다.
5.3. RNN 구현
✔️ RNN 계층 구현
우선 RNN계층(식 5-10)을 구현해보자.
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 = np.matmul(h_prev, Wh) + np.matmul(x, Wx) + b
h_next = np.tanh(t)
self.cache = (x, h_prev, h_next)
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) # tanh 미분
db = np.sum(dt, axis=0)
dWh = np.dot(h_prev.T, dt) # shape: (H, N) x (N, H) = (H, H)
dh_prev = np.dot(dt, Wh.T) # shape: (N, H) x (H, H) = (N, H)
dWx = np.dot(x.T, dt) # shape: (D, N) x (N, H) = (D, H)
dx = np.dot(dt, Wx.T) # shape: (N, H) x (H, D) = (N, D)
self.grads[0][...] = dWx
self.grads[1][...] = dWh
self.grads[2][...] = db
return dx, dh_prev
그림 5-16처럼 RNN은 길이가 T인 시계열 데이터를 받으며, 각 시각의 은닉 상태를 T개 출력한다.
이를 모듈화해보면 그림 5-17처럼 입력xs(x0, ..., xT-1)과 출력hs(h0, ..., hT-1)을 각각 하나로 묶어 T개의 RNN계층을 단일 계층으로 표현할 수 있다. 이를 Time RNN 계층이라고 한다.
여기서 RNN 계층의 은닉 상태 h를 인스턴스 변수로 유지하여 은닉 상태를 '인계'받는 용도로 이용한다.
그림 5-22처럼 RNN 계층의 은닉 상태를 Time RNN 계층이 관리하면 사용자가 은닉상태를 '인계하는 작업'을 생각하지 않아도 된다.
이를 코드에서는 stateful이라는 불리언 값(True/False)로 설정한다.
✔️ Time RNN 계층 구현
class TimeRNN:
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 # RNN 계층을 리스트로 저장
self.h, self.dh = None, None
self.stateful = stateful
def set_state(self, h):
'''hidden state(h)를 설정하는 메서드'''
self.h = h
def reset_state(self):
'''hidden state(h)를 초기화하는 메서드'''
self.h = None
def forward(self, xs):
Wx, Wh, b = self.params
N, T, D = xs.shape # N(batch), T(time steps), D(input size)
D, H = Wx.shape
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')
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, grad in enumerate(layer.grads):
grads[i] += grad
for i, grad in enumerate(grads):
self.grads[i][...] = grad
self.dh = dh
return dxs
5.4. 시계열 데이터 처리 계층 구현
RNN을 이용한 언어모델(RNN Language Model, RNNLM)을 구현해보자.
Embedding 계층에서 단어 ID를 단어의 분산표현(단어 벡터)으로 변환한다.
이 분산표현이 RNN 계층으로 입력되어 은닉 상태를 다음층(위쪽)으로 출력함과 동시에 다음 시각(오른쪽)의 RNN 계층으로 출력한다.
위로 출력한 은닉상태는 Affine 계층을 거쳐 Softmax 계층으로 전해진다.
'You say goodbye and I say hello'로 구현해보면 그림 5-26과 같다.
여기서 두번째 단어인 'say'를 입력하면, 'goodbye'와 'hello'가 높은 점수를 받지만 'you say'라는 맥락을 '기억'하고 'goodbye'가 출력된다.
✔️ Time 계층 구현
그림 5-27처럼 Time RNN뿐만 아니라 Time Affine, Time Softmax처럼 시계열 데이터를 한꺼번에 처리하는 계층을 구현해보자.
Time Embedding 계층과 Time Affine 계층은 순전파 시에 T개의 계층을 준비하고 각 계층이 각 시각의 데이터를 처리한다.
Time Softmax with Loss계층은 그림 5-29와 같다.
x는 아래층에서부터 전해지는 '점수'를 나타내고 t는 정답레이블이다.
이들로 산출된 각각의 손실을 평균한 값이 최종 손실(식 5-11)이 된다.
5.5. RNNLM 학습과 평가
RNNLM의 최종 계층 구성은 그림 5-30과 같으며,
관련 구현 및 학습 코드는 https://github.com/ExcelsiorCJH/DLFromScratch2.git 참고
언어모델은 주어진 과거 단어(정보)로부터 다음에 출현할 단어의 확률분포를 출력한다.
이 때 언어모델의 예측성능을 평가하는 척도로 퍼플렉서티(perplexity 혼란도)를 자주 이용한다.
퍼플렉서티는 '확률의 역수'이다.
정답일 확률이 0.8이면 퍼플렉서티는 1/0.8 = 1.25이고, 0.2이면 1/0.2 = 5이다.
따라서 퍼플렉서티는 작을수록 좋다.
이를 직관적으로 '분기수(number of branches)로 해석할 수 있는데 다음에 취할 수 있는 선택사항의 수(다음에 출현할 수 있는 단어의 후보 수)이다. 퍼플렉서티, 분기 수가 1.25라면 다음에 출현할 수 있는 단어의 후보는 1개정도로 좁혀졌다는 뜻이 된다.
입력데이터가 하나일 때가 아닌 여러 개라면 식 5-13과 같이 구한다.
N은 데이터의 총개수, tn은 원핫벡터로 나타낸 정답 레이블, tnk는 n개째 데이터의 k번째 값, ynk는 확률분포(softmax의 출력),
L은 신경망의 손실, eL은 퍼플렉서티
5.6. 정리
- RNN은 순환하는 경로가 있고, 이를 통해 내부에 '은닉 상태'를 기억할 수 있다.
- RNN의 순환 경로를 펼침으로써 다수의 RNN 계층이 연결된 신경망으로 해석할 수 있으며, 보통의 오차 역전파법으로 학습할 수 있다.(=BPTT)
- 긴 시계열 데이터를 학습할 때는 데이터를 적당한 길이씩 모으고('블록'), 블록단위로 BPTT에 의한 학습을 수행한다.(=Truncated BPTT)
- Truncated BPTT에서는 역전파의 연결만 끊는다.
- Truncated BPTT에서는 순전파의 연결을 유지하기 위해 데이터를 '순차적'으로 입력해야 한다.
- 언어모델은 단어 시퀀스를 확률로 해석한다.
- RNN 계층을 이용한 조건부 언어 모델은 (이론적으로는) 그때까지 등장한 모든 단어의 정보를 기억할 수 있다.
'Deep Learning' 카테고리의 다른 글
[밑바닥부터시작하는딥러닝2] Chapter 7. RNN을 사용한 문장 생성 (0) | 2024.04.22 |
---|---|
[밑바닥부터시작하는딥러닝2] Chapter 6. 게이트가 추가된 RNN (0) | 2024.04.21 |
[밑바닥부터시작하는딥러닝2] Chapter 4. word2vec 속도 개선 (0) | 2024.04.19 |
[밑바닥부터시작하는딥러닝2] Chapter 3. word2vec (0) | 2024.04.18 |
[밑바닥부터시작하는딥러닝2] Chapter 2. 자연어와 단어의 분산 표현 (0) | 2024.04.17 |