5.1 확률과 언어 모델
5.2 RNN이란
5.3 RNN 구현
5.4 시계열 데이터 처리 계층 구현
5.5 RNNLM 학습과 평가
5.6 정리
지금까지 살펴본 신경망은 피드포워드라는 유형의 신경망.
피드포워드란 흐름이 단방향인 신경망이다. 피드포워드 신경망은 구성이 단순하여 구조를 이해하기 쉽고 많은 문제에 응용이 가능하다.
그러나 피드포워드 신경망은 시계열 데이터의 성질을 충분히 학습할 수 없다. => 순환신경망 RNN 등장
5.1 확률과 언어 모델
1) word2vec을 확률 관점에서 바라보다.
지금까지는 맥락을 항상 좌우 대칭으로 생각해왔다. 맥락을 왼쪽 윈도우만 고려보자
이 경우 CBOW 모델이 출력할 확률이다.
CBOW모델이 다루는 손실 함수이다. (교차 엔트로피 오차에 의해 유도한 결과)
2) 언어 모델
언어 모델은 단어 나열에 확률을 부여한다. 특정한 단어의 시퀀스에 대해서, 그 시퀀스가 일어날 가능성이 어느 정도인지를 확률로 평가하는 것이다.
w1,...,wm 이라는 m개 단어로 된 문장이 있을 때
w1,...,wm 순서로 출현할 확률 P(w1,...,wm)
(여러 사건이 동시에 일어날 확률이므로 동시확률이라고 한다.)
이 식의 결과는 확률의 곱셈정리로부터 유도할 수 있다.
이 곱셈정리를 사용하면 m개 단어의 동시 확률 P(w1,...,wm)을 사후 확률로 나타낼 수 있다.
이 사후 확률은 타깃 단어보다 왼쪽에 있는 모든 단어를 맥락(조건)으로 했을 때의 확률이다.
즉 우리의 목표는
이라는 확률을 얻는 것이다. 이 확률을 계산할 수 있다면 언어 모델의 동시 확률을 구할 수 있다.
3) CBOW모델을 언어 모델로?
word2vec의 CBOW모델을 언어 모델에 적용하려면 어떻게 하는게 좋을까?
맥락의 크기를 특정 값으로 한정하여 근사적으로 나타낼 수 있다.
여기서는 맥락을 왼쪽 2개의 단어로 한정한다. 그러면 CBOW모델에 따라 근사적으로 나타낼 수 있다.
맥락의 크기는 임의 길이로 설정할 수 있지만 결국 특정 길이로 '고정'된다.
- 예를 들어 왼쪽 10개의 단어를 맥락으로 CBOW 모델을 만든다고 하면 그 맥락보다 더 왼쪽에 있는 단어의 정보는 무시된다.
CBOW모델의 맥락 크기를 키울 수는 있으나 맥락 안의 단어 순서가 무시된다는 한계가 있다.
맥락의 단어 순서를 고려하기 위해 맥락의 단어 벡터를 은닉층에서 연결(concatenate)하는 방식을 생각할 수 있으나 맥락의 크기에 비례해 가중치 매개변수가 늘어난다는 문제가 발생한다.
따라서, 순환 신경망 RNN이 등장하는데, RNN은 맥락이 아무리 길더라고 맥락의 정보를 기억하는 메커니즘을 갖추고 있기에 아무리 긴 시계열 데이터에도 대응할 수 있다.
5.2 RNN이란
순환하는 신경망이다.
1) 순환하는 신경망
순환하기 위해서는 닫힌 경로가 필요하다.
닫힌 경로 혹은 순환하는 경로가 존재해야 데이터가 같은 장소를 반복해 왕래할 수 있고 데이터가 순환하면서 정보가 끊임없이 갱신된다.
t : 시각
xt : 벡터라고 가정한다. (문장을 다루는 경우를 예로 들면 단어의 분산 표현이 xt가 되며 이 분산 표현이 하나씩 RNN계층에 입력되는 것이다.)
순환구조를 자세히 살펴보자
먼저 계층을 데이터가 왼쪽에서 오른쪽으로 흐르는 형태에서 아래에서 위로 흐르는 형태로 회전시킨다.
2) 순환 구조 펼치기
지금까지 본 피드포워드 신경망과 같은 구조이다.(데이터가 한 방향으로 흐르는 신경망)
다만 위의 그림에서는 다수의 RNN 계층 모두가 실제로는 같은 계층인 것이 지금까지의 신경망과 다르다.
각 시각의 RNN계층은 그 계층으로의 입력과 1개 전의 RNN계층으로부터의 출력을 받는데 이 두 정보를 바탕으로 현 시각의 출력을 계산한다.
RNN에는 가중치가 2개이다.
=> 입력 x를 출력h로 변환하기 위한 가중치 Wx.
=> 1개의 RNN 출력을 다음 시각의 출력으로 변환하기 위한 가중치 Wh.
b : 편향
h(t-1) / xt : 행벡터
ht는 다른 계층을 향해 위쪽으로 출력되는 동시에 다음 시각의 RNN계층(자기 자신)을 향해 오른쪽으로도 출력된다. RNN의 출력 ht는 은닉 상태 혹은 은닉 상태 벡터라고 한다.
RNN은 h라는 상태를 가지고 있으며 위 식의 형태로 갱신된다고 해석할 수 있다. 즉 RNN 계층을 상태를 가지는 계층 혹은 메모리가 있는 계층 이라고 한다.
3) BPTT
순환 구조를 펼친 후의 RNN에는 일반적인 오차역전파법을 적용할 수 있다.
여기서의 오차역전파법은 '시간 방향으로 펼친 신경망의 오차역전파법'이란 뜻으로 BPTT(Backpropagation Through Time)라고 한다.
문제점
- 긴 시계열 데이터를 학습할 때 문제가 생김
: 시계열 데이터의 시간 크기가 커지는 것에 비례하여 BPTT가 소비하는 컴퓨팅 자원도 증가한다.
시간 크기가 커지면 역전파 시의 기울기가 불안정해진다.
4) Truncated BPTT
Truncated BPTT : 시간축 방향으로 너무 길어진 신경망을 적당한 지점에서 잘라내어 작은 신경망 여러 개로 만든다. 이 잘라낸 작은 신경망에서 오차역전파법을 수행한다.
제대로 구현하기 위해서는 역전파의 연결만 끊고 순전파의 연결은 그대로 유지해야한다.
순전파의 연결을 그대로 유지하면서(데이터를 순서대로 입력해야 한다) 역전파의 연결은 적당한 길이로 잘라내 잘라낸 신경망 단위로 학습을 수행한다.
역전파의 연결을 잘라버리면 그보다 미래의 데이터에 대해서는 생각할 필요가 없어지기 때문에 각각의 블록 단위로 미래의 블록과는 독립적으로 오차역전파법을 완결시킨다.
- 블록: 역전파가 연결되는 일련의 RNN계층
순전파를 수행하고 그 다음 역전파를 수행하여 원하는 기울기를 구한다
이제 Truncated BPTT 방식으로 RNN을 학습시킨다.
첫째로, 첫번째 블록 입력데이터 (를 RNN 계층에 제공
먼저 순전파를 수행하고, 역전파를 수행한다.
이렇게 원하는 기울기를 구할 수 있다.
이어서, 다음 블록의 입력 데이터 ()를 입력해 오차역전파법을 수행
가장 중요한 것은 이번 순전파 계산에는 앞 블록의 마지막 은닉상태인 가 필요하다는 것이다.
이렇게 순전파는 계속 연결된다.
이어서 계속 3번째의 블록도 수행한다. 이때도 두 번째 블록의 마지막 은닉상태 를 이용한다.
이렇게 RNN 학습에서는 데이터를 순서대로 입력하며, 은닉 상태를 계승하면서 학습을 수행한다.
5) Truncated BPTT의 미니배치 학습
지금까지는 미니배치 수가 1일 때에 해당한다.
원래대로라면 구체적인 배치 방식을 고려해 데이터를 순서대로 입력해야 한다.
그렇게 하려면 데이터를 주는 시작 위치를 각 미니배치의 시작 위치로 '옮겨줘야' 한다.
길이가 1000인 시계열 데이터에 대해서, 시각의 길이를 10개 단위로 잘라 Truncated BPTT로 학습하는 경우 미니배치의 수를 두 개로 구성해 학습하려면 어떻게 해야될까?
RNN 계층의 입력 데이터로, 첫 번째 미니배치 때는 처음부터 순서대로 데이터를 제공한다.
두 번째 미니배치 때는 500번째의 데이터를 시작 위치로 정하고, 그 위치부터 다시 순서대로 데이터를 제공한다.
(= 시작 위치를 500만큼 '옮겨'준다)
첫 번째 미니배치 원소는 x0, ..., x9가 되고, 두 번째 미니배치 원소는 x500, ..., x509가 된다.
그리고 이 미니배치 데이터를 RNN의 입력 데이터로 사용해 학습을 수행한다.
이후로는 순서대로 진행되므로 다음에 넘길 데이터는 각각 시계열 데이터의 10~19번째 데이터와 510~519번째의 데이터가 되는 식이다.
주의할점
- 데이터를 순서대로 제공하기
- 미니배치별로 데이터를 제공하는 시작 위치를 옮기기
5.3 RNN 구현
가로 방향으로 성장한 신경망 + Truncated BPTT 방식 = 가로크기가 일정한 일련의 신경망을 만들 수 있다.
우리가 다룰 신경망은 길이가 인 시계열 데이터를 받고 각 시각의 은닉 상태를 개 출력한다.
모듈화를 생각해, 옆으로 성장한 신경망을 하나의 계층으로 구현.
상하 방향의 입력과 출력을 각각 하나로 묶으면 옆으로 늘어선 일련의 계층을 하나의 계층으로 간주할 수 있다.
Time RNN 계층 내에서 한 단계의 작업을 수행하는 계층을 RNN계층이라 하고,
T개 단계분의 작업을 한꺼번에 처리하는 계층을 Time RNN 계층이라 한다.
1) RNN 계층 구현
한 단계만 수행하는 RNN 클래스부터 구현하자.
여기서 우리는 데이터를 미니배치로 모아 처리한다. 따라서 xt에는 각 샘플 데이터를 행 방향에 저장한다.
미니배치 크기가 , 입력 벡터의 차원 수가 , 은닉 상태 벡터의 차원 수가 라면 계산에서의 형상 확인은
행렬의 형상 확인을 수행함으로써 올바로 구현되었는지 적어도 계산이 성립하는지를 확인할 수 있다.
이를 바탕으로 RNN클래스의 초기화와 순전파 메서드를 구현해보자.
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.dot(h_prev, Wh) + np.dot(x, Wx) + b
h_next = np.tanh(t)
self.cache = (x, h_prev, h_next)
return h_next
초기화 메서드는
- 가중치 2개와 편향 1개를 인수로 받는다.
- 각 매개변수에 대응하는 형태로 기울기를 초기화 한 후 grads에 저장하고
- 역전파 계산 시 사용하는 중간 데이터를 담을 cache를 None으로 초기화
순전파인 forward(x, h_prev)에서는
인수 2개(아래로부터의 입력x, 왼쪽으로부터의 입력 h_prev)를 받는다.
그리고 식을 적용함.
RNN의 역전파를 구현해보자
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.dot(h_prev.T, dt)
dh_prev = np.dot(dt, Wh.T)
dWx = np.dot(x.T, dt)
dx = np.dot(dt, Wx.T)
self.grads[0][...] = dWx
self.grads[1][...] = dWh
self.grads[2][...] = db
return dx, dh_prev
2) Time RNN 계층 구현
Time RNN 계층은 개의 RNN 계층으로 구성된다. (T는 임의의 수로 설정 가능)
RNN 계층의 은닉상태 h를 보관하고, '인계'받는 용도로 이용한다.
이 기능(은닉 상태를 인계받을지)을 stateful이라는 인수로 저장한다.
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
self.h, self.dh = None, None
self.stateful = stateful
def set_state(self, h):
self.h = h
def reset_state(self):
self.h = None
초기화 메서드는
- 가중치와 편향, stateful을 인수로 받는다.
- 인스턴스변수 layers은 다수의 RNN 계층을 리스트로 저장
- 인스턴스변수 h는 forward()메서드를 불렀을 때의 마지막 RNN 계층의 은닉상태 저장
- dh는 backward()를 불렀을 때 하나 앞 블록의 은닉 상태의 기울기를 저장
stateful = True라면 RNN 계층은 '상태가 있다'고 말한다. 이는 곧, 은닉 상태를 유지한다는 뜻.
아무리 긴 시계열 데이터라도 순전파를 끊지 않고 전달한다는 의미다.
순전파의 구현이다.
def forward(self, xs):
Wx, Wh, b = self.params
N, T, D = xs.shape
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
순전파 메서드인 forward(xs)는
- 아래로부터 입력 xs를 받는다. (xs는 T개 분량의 시계열 데이터를 하나로 모은 것)
- xs의 형상은 (미니배치크기 N, T, 입력벡터차원수 D) => (N, T, D)
- h는 처음 호출 시 영행렬로 초기화
- hs = np.empty((N, T, H), dtype='f') 문장에서 출력값을 담을 그릇 hs 준비
- for문안에선 RNN 계층을 생성하여 인스턴스변수 layers에 추가
- RNN 계층이 각 시각 t의 은닉상태 h를 계산하고 이를 hs에 해당 인덱스(시각)의 값으로 설정
역전파 구현은 상류(출력)에서부터 전해지는 기울기를 dhs로 쓰고, 하류로 내보내는 기울기를 dxs로 쓴다
Truncated BPTT를 수행하기 때문에 이 블록 해당 블록 이전 시각 역전파는 필요하지 않다.
단, 이전 시각의 은닉상태 기울기는 dh에 저장한다.
Time RNN 계층에서 이뤄지는 역전파의 전체 그림이다.
주의할 점은 RNN 계층의 순전파에서는 출력이 2개로 분기된다는 것이다.
순전파시 분기했을 경우, 그 역전파에서는 각 기울기가 합산되어 전해진다.
따라서 역전파 시 RNN계층에서는 합산된 기울기(dh_t+dh_next)가 입력된다.
이를 주의하여 역전파를 구현한다.
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
역전파 메소드에서는
- 하류로 흘러보낼 기울기를 담을 그릇인 dxs를 만든다.
- 순전파 때와는 반대 순서로 backward()를 호출하여 각 시각의 기울기 dx를 구해 dxs내 해당 인덱스(시각)에 저장한다.
- 가중치 매개변수에 대해서도 각 RNN계층의 가중치 기울기를 합산하여 최종 결과를 멤버 변수 self.grads 에 덮어쓴다.
< 주의 >
Time RNN 계층 안에는 RNN 계층이 여러개 있다.
그 RNN 계층들에서 똑같은 가중치를 사용하고 있다.
따라서 Time RNN 계층의 최종 가중치 기울기는 각 RNN 계층의 가중치 기울기를 모두 더한 값.
이상으로 Time RNN 계층의 구현을 살펴보았다.
5.4 시계열 데이터 처리 계층 구현
목표는 RNN을 이용하여 언어 모델을 구현하는 것이다.
이번 절에서는 시계열 데이터를 처리하는 계층을 더 만들어본다.
RNN을 사용한 언어모델은 RNNLM이라 칭한다.
1) RNNLM의 전체 그림
y1은 w1단어 다음으로 올 가장 유력한 단어를 뽑는 것이다.
첫번째(하단)층은 임베딩 계층이다. 이 계층은 단어 ID를 단어의 분산표현(RNN의 입력)으로 변환한다.
RNN계층은 은닉상태를 다음 층(위쪽)으로 출력함과 동시에 다음 시각의 RNN계층(오른쪽)으로 출력한다.
그러면 Affine계층을 거쳐 Softmax 계층으로 전해진다.
입력데이터는 단어 ID의 배열.
첫단어로 단어 ID가 0인 'you'가 입력된다. 이때, Softmax 확률분포를 보면 'say'에서 가장 높게 나옴.
두번째 단어인 'say'를 입력하는 부분에선 'goodbye', 'hello'둘다 확률이 높음 (둘다 말이되니까)
여기서는 RNN 계층이 'you say'라는 맥락을 기억하고 있다는 것이다.
2) Time 계층 구현
시계열 데이터를 한꺼번에 처리하는 계층을 Time Embedding, Time Affine형태의 이름으로 구현한다.
Time Affine 계층은 Affine계층을 개 준비해서, 각 시각의 데이터를 개별적으로 처리하면 된다.
Time Embedding 계층도 순전파 시에 개의 Embedding 계층을 준비하고 각 Embedding 계층이 각 시각의 데이터를 처리한다.
Softmax계층을 구현할 때는 손실오차를 구하는 크로스엔트로피오차 계층도 함께 구현한다.
Time Softmax with Loss 계층의 그림은 다음과 같다.
이나 등의 데이터는 아래로부터 전해지는 '점수'를 나타낸다. 1
나 0등의 데이터는 정답 레이블을 나타낸다. 1
개의 Softmax with Loss 계층 각각이 손실을 산출한다. 그리고 그 손실들을 합산해 평균한 값이 최종 손실.
5.5 RNNLM 학습과 평가
1) RNNLM 구현
SimepleRnnlm이라는 이름의 클래스로 구현한다.
이를 코드로 나타내보자
#import sys
#sys.path.append('..')
import numpy as np
#from common.time_layers import *
class SimpleRnnlm:
def __init__(self, vocab_size, wordvec_size, hidden_size):
V, D, H = vocab_size, wordvec_size, hidden_size # wordvec_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
초기화 메서드
- 각 계층에서 사용하는 매개변수(가중치, 편향) 초기화, 필요한 계층 생성
- stateful이 True이므로 Time RNN 계층은 이전 시각의 은닉 상태를 계승할 수 있다.
- RNN 계층과 Affine 계층에서 사비에르 초깃값 이용
순전파, 역전파 메서드의 구현이다.
def forward(self, xs, ts):
for layer 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()
reset_state는 신경망의 상태를 초기화하는 편의 메서드이다.
2) 언어 모델 평가
언어 모델은 주어진 과거 단어로부터 다음에 출현할 단어의 확률분포를 출력한다.
이 때 언어 모델의 예측 성능을 평가하는 척도로 퍼플렉서티를 자주 이용한다.
퍼플렉서티 = 확률의 역수
ex) 'you'가 주어질 때 'say'가 출현할 확률이 0.8이라면, 퍼플렉서티 = 1.25
퍼플렉시티는 작을수록 좋다.
퍼플렉서티 값은 분기 수로 해석할 수 있다. (분기 수 = 다음에 취할 수 있는 선택사항의 수)
좋은 모델은 정답 단어를 높은 확률로 예측하므로 퍼플렉서티 값이 작다.
입력 데이터가 여러개라면?
다음 공식에 따라 계산한다.
N : 데이터의 총 개수
tn : 원핫 벡터로 나타낸 정답 레이블
tnk : n 개째 데이터의 k번째 값을 의미
ynk : 확률분포(신경망에서는 softmax의 출력)
3) RNNLM의 학습 코드
# coding: utf-8
import sys
sys.path.append('..')
import matplotlib.pyplot as plt
import numpy as np
from common.optimizer import SGD
from dataset import ptb
from simple_rnnlm import SimpleRnnlm
# 하이퍼파라미터 설정
batch_size = 10
wordvec_size = 100
hidden_size = 100 # RNN의 은닉 상태 벡터의 원소 수
time_size = 5 # Truncated BPTT가 한 번에 펼치는 시간 크기
lr = 0.1
max_epoch = 100
# 학습 데이터 읽기(전체 중 1000개만)
corpus, word_to_id, id_to_word = ptb.load_data('train')
corpus_size = 1000
corpus = corpus[:corpus_size]
print(id_to_word)
vocab_size = int(max(corpus) + 1)
xs = corpus[:-1] # 입력
ts = corpus[1:] # 출력(정답 레이블)
data_size = len(xs)
print('말뭉치 크기: %d, 어휘 수: %d' % (corpus_size, vocab_size))
# 학습 시 사용하는 변수
max_iters = data_size // (batch_size * time_size)
time_idx = 0
total_loss = 0
loss_count = 0
ppl_list = []
# 모델 생성
model = SimpleRnnlm(vocab_size, wordvec_size, hidden_size)
optimizer = SGD(lr)
# 미니배치의 각 샘플의 읽기 시작 위치를 계산 (1)
jump = (corpus_size - 1) // batch_size
offsets = [i * jump for i in range(batch_size)]
for epoch in range(max_epoch):
for iter in range(max_iters):
# 미니배치 취득 (2)
batch_x = np.empty((batch_size, time_size), dtype='i')
batch_t = np.empty((batch_size, time_size), dtype='i')
for t in range(time_size):
for i, offset in enumerate(offsets):
batch_x[i, t] = xs[(offset + time_idx) % data_size]
batch_t[i, t] = ts[(offset + time_idx) % data_size]
time_idx += 1
# 기울기를 구하여 매개변수 갱신
loss = model.forward(batch_x, batch_t)
model.backward()
optimizer.update(model.params, model.grads)
total_loss += loss
loss_count += 1
# 에폭마다 퍼플렉서티 평가 (3)
ppl = np.exp(total_loss / loss_count)
print('| 에폭 %d | 퍼플렉서티 %.2f'
% (epoch+1, ppl))
ppl_list.append(float(ppl))
total_loss, loss_count = 0, 0
# 그래프 그리기
x = np.arange(len(ppl_list))
plt.plot(x, ppl_list, label='train')
plt.xlabel('epochs')
plt.ylabel('perplexity')
plt.show()
이제껏 신경망 학습과 다른점
1. 데이터 제공 방법
2. 퍼플렉서티 계산 부분
데이터 제공 방법에 대해선 Truncated BPTT 방식으로 수행한다.
따라서 데이터는 순차적으로 주고 각 미니배치에서 데이터를 읽는 시작위치를 조정한다.
소스코드에서 (1)부분이 각 미니배치가 데이터를 읽기 시작하는 위치를 계산한다.
(2)에서는 데이터를 순차적으로 읽는다.
말뭉치를 읽는 위치가 크기를 넘어설 경우 말뭉치의 처음으로 돌아와야한다.
마지막으로 퍼플렉서티는 (3)부분에서 계산한다.
에폭마다의 퍼플렉서티를 구하기 위해 에폭마다 손실의 평균을 구하고, 그 값을 사용해 퍼플렉서티를 구한다.
다만 큰 말뭉치에는 전혀 대응할 수 없다.
이는 다음 장에서 개선할 수 있다.
4) Trainer 클래스
RNNLM 학습을 수행해주는 RnnlmTrainer 클래스를 제공한다.
이 클래스는 RNNLM 학습을 클래스 안으로 숨겨준다.
# 모델 생성
model = SimpleRnnlm(vocab_size, wordvec_size, hidden_size)
optimizer = SGD(lr)
trainer = RnnlmTrainer(model, optimizer)
trainer.fit(xs, ts, max_epoch, batch_size, time_size)
내부에서 수행되는 작업은
1. 미니배치를 순차적으로 만들어
2. 모델의 순전파와 역전파를 호출하고
3. 옵티마이저로 가중치를 갱신하고
4. 퍼플렉서티를 구한다.
5.6 정리
- RNN은 순환하는 경로가 있고, 이를 통해 내부에 '은닉 상태'를 기억할 수 있다.
- RNN의 순환 경로를 펼침으로써 다수의 RNN 계층이 연결된 신경망으로 해석할 수 있으며, 보통의 오차역전파법으로 학습할 수 있다.(= BPTT)
- 긴 시계열 데이터를 학습할 때는 데이터를 적당한 길이씩 모으고(이를 블록이라고 한다.), 블록 단위로 BPTT에 의한 학습을 수행한다.(= Truncated BPTT)
- Truncated BPTT에서는 역전파의 연결만 끊는다.
- Truncated BPTT에서는 순전파의 연결을 유지하기 위해 데이터를 순차적으로 입력해야 한다.
- 언어 모델은 단어 시퀀스를 확률로 해석한다.
- RNN 계층을 이용한 조건부 언어 모델은 그때까지 등장한 모든 단어의 정보를 기억할 수 있다. (이론적으로는)