CS Repository/기초 딥러닝

[밑바닥부터 시작하는 딥러닝] RNN

조금씩 차근차근 2025. 8. 15. 23:12

본 내용은 밑바닥부터 시작하는 딥러닝 2도서를 참고하여 작성되었습니다.

 

밑바닥부터 시작하는 딥러닝 2 - 예스24

직접 구현하면서 배우는 본격 딥러닝 입문서 이번에는 순환 신경망과 자연어 처리다! 이 책은 『밑바닥부터 시작하는 딥러닝』에서 다루지 못했던 순환 신경망(RNN)을 자연어 처리와 시계열 데

www.yes24.com

 

지금까지 살펴본 신경망은 피드 포워드(앞먹임) 신경망이라는 유형의 신경망이다.
피드포워드란, 흐름이 단방향인 신경망을 의미한다.

 

하지만 피드포워드 신경망은 시계열 데이터를 다루기 어렵고, 시계열 데이터에는 RNN이 필요하다.

 

피드포워드가 시계열 데이터를 다루지 못하는 이유와 RNN이 시계열 데이터를 다룰 수 있는 이유를 알아보며 RNN을 학습해보자.

확률과 언어 모델

먼저 word2vec을 이용해 자연어에 관한 현상을 '확률'을 사용해 기술해보고, 마지막에는 언어를 확률로 다루는 '언어 모델'에 대해 배워보자.

word2vec을 확률 관점에서 바라보면?

우선 CBOW에서 t번째 단어를 타깃, t-1, t+1 번째 단어를 '맥락'으로 보면 어떻게 될까?

수식으로 나타내면 아래와 같다.

이번엔 왼쪽 윈도우만 맥락으로 고려해보자.

수식으로 나타내면 아래와 같다.


손실함수를 나타내면 이와 같이 될 것이다.

우리는 이전 데이터를 이용해 다음 데이터의 확률을 추론했다.
이를 이용해 언어 모델을 만들어보자.

언어 모델

언어 모델(Language Model)은 단어 나열에 확률을 부여한다.
특정한 단어의 시퀀스에 대해서, 그 시퀀스가 일어날 가능성이 어느 정도인지(얼마나 자연스러운 단어 순서인지)를 확률로 평가한다.

 

이는 기계 번역/음성 인식 분야에 적극적으로 사용된다.

언어 모델을 사용하여 후보 문장이 '문장으로써 자연스러운지'를 기준으로 평가하고, 우선순위를 매긴다.

또한 새로운 문장을 생성하는 용도로 이용한다.
앞에 있는 단어로 다음에 들어올 자연스러운 단어를 '자아낼'(샘플링) 수 있다.

 

언어 모델을 수식으로 표현해보자.


이 식은 아래 과정을 통해 유도할 수 있다.

 

확률의 곱셈정리

이 곱셈 정리를 사용해 m개 단어의 동시 확률 P(w_1, ..., w_m)을 사건 A = w_1, ..., w_m-1에 대한 m의 사후 확률로 나타낼 수 있다.

이는 재귀적으로 적용 가능하고, 다시 P(A)를 다음과 같이 치환할 수 있다.


즉, 동시 확률 P(w_1, ..., w_m)은 사후 확률의 총곱으로 나타낼 수 있는 것이다.

결국 우리의 목표는 P(w_t|w_1, ..., w_t-1) 을 얻는 것이다. 이걸 구하면 언어 모델의 동시 확률 P(w_1, ..., w_m)을 구할 수 있다.

CBOW 모델이 언어 모델로?

맥락의 크기를 특정 값으로 한정하여 근사적으로 나타내보면, 언어 모델처럼 쓸 수 있지 않을까?

맥락의 크기(윈도우 사이즈)는 하이퍼파라미터였다.

머신러닝이나 통계학에서는 마르코프 연쇄 또는 마르코프 모델이라는 말을 자주 듣는다. 마르코프 연쇄란 미래의 상태가 현재 상태에만 의존해 결정되는 것을 말한다. 또한 이 사상의 확률이 '그 직전' N개의 사건에만 의존할 때, 이를 'N층 마르코프 연쇄'라고 한다.
이번 예는 직전 2개의 단어에만 의존해 다음 단어가 정해지는 모델이므로 '2층 마르코프 연쇄'라고 부를 수 있다.

하지만 결국 CBOW는 맥락의 길이가 특정 길이로 '고정'된다.

 

그럼 아래와 같은 문제는 어떻게 풀어야 할까?


우리는 손쉽게 'Tom'이라는 대답을 할 수 있지만, CBOW는 예문의 "?"로부터 17번째나 앞에 오는 "Tom"을 기억해야 한다.
만약 CBOW 모델의 맥락이 10개까지였다면 이 문제에 제대로 답할 수 없을 것이다.

 

그렇다고 맥락을 엄청 키우면 해결되는가?
문장의 길이가 길어지면 이제 단어의 순서도 중요해지는데, CBOW는 맥락 안의 단어 순서를 알지 못한다는 한계가 있다.

 

(say, you)와 (you, say)를 동일하게 바라보면 문법적인 문제가 발생할 것이다.

이상적으로는 맥락의 단어 순서도 고려한 모델이 바람직할 것이다.
위 그림의 오른쪽처럼 맥락의 단어 벡터를 은닉층에서 연결(concatenate)하는 방식을 생각할 수 있다.
실제, 신경 확률론적 언어 모델(Neural Probabilistic Language Model)에서 제시한 모델은 이 방식을 취한다. 하지만 연결하는 방식을 취하면 맥락의 크기에 비례해 가중치 매개변수도 늘어나게 된다.

 

그렇다면 이 문제를 어떻게 해결해야 할까? 여기서 등장하는 것이 순환 신경망, 즉 RNN이다. RNN은 맥락이 아무리 길더라도 그 맥락의 정보를 기억하는 메커니즘을 갖추고 있다. 그래서 RNN을 사용하면 아무리 긴 시계열 데이터에라도 대응할 수 있다.

참고로 word2vec이 RNN보다 늦게 나왔다. RNN을 이용한 언어 모델에서도 단어의 분산 표현을 얻을 수 있지만, 어휘 수 증가에 따른 대응이나 단어의 분산 표현의 '질' 개선을 위해 word2vec을 제안했다고 한다.

RNN이란?

RNN(Recurrent Neural Network)의 R은 'Recurrent'로, '몇 번이나 반복해서 일어나는 일'을 뜻한다. 그래서 RNN을 직역하면 '순환하는 신경망'이 되는 것이다.
이번 절에서는 이 '순환한다'는 말에 의미를 곱씹어보자.

순환하는 신경망

'순환한다'에는 '반복해서 되돌아감'이라는 의미가 담겨있다.
결국 순환하기 위해선, '닫힌 경로'가 필요하다.

 

'닫힌 경로' 혹은 '순환하는 경로'가 존재해야 데이터가 같은 장소를 반복해 왕래할 수 있다.
그리고 데이터가 순환하면서 정보가 끊임없이 갱신되게 된다.

 

RNN의 특징은 순환하는 닫힌 경로가 존재한다.
이 순환하는 경로를 따라 데이터는 끊임없이 순환할 수 있다.
그리고 데이터가 순환되기 때문에 과거의 정보를 기억하는 동시에, 최신 데이터로 갱신될 수 있다.

 

RNN 계층을 그림으로 살펴보자.


그림에서는 x_t 를 입력받는데, t는 시각을 뜻한다.
이는 시계열 데이터 (x_0, ..., x_t, ...)가 RNN 계층에 입력됨을 표현한 것이다.
그리고 그 입력에 대응하여 (h_0, ..., h_t, ...)가 출력된다.

 

또한, 각 시각에 입력되는 x_t는 벡터라고 가정한다. 문장(단어 순서)을 다루는 경우를 예로 든다면 각 단어의 분산 표현(단어 벡터)이 x_t가 되며, 이 분산 표현이 순서대로 RNN 계층에 입력되는 것이다.

 

이제 위 순환 구조를 자세히 살펴보기 전에, RNN의 경우 그리는 방식을 살짝 바꿔보자.
RNN의 경우 그림을 다음과 같이 90도 회전시켜 그리는 경우가 많다.

이제 이 순환 구조를 좀 더 자세히 살펴보자.

순환 구조 펼치기

이제 RNN 계층의 순환 구조에 대해 자세히 살펴보자.

 

RNN의 순환 구조는 지금까지의 신경망에 존재하지 않았던 구조이지만, 이 순환 구조를 펼치면 친숙한 신경망으로 '변신'시킬 수 있다.
백문이 불여일견! 실제로 한번 펼쳐보자.

위 그림에서 보듯, RNN 계층의 순환 구조를 펼침으로써 오른쪽으로 성장하는 긴 신경망으로 변신시킬 수 있다. 이는 지금까지 본 피드포워드 신경망과 같은 구조이다.
다만 위 그림에서 등장하는 다수의 RNN 계층 모두가 실제로는 '같은 계층'인것이 지금까지의 신경망과는 다르다.

시계열 데이터는 시간 방향으로 데이터가 나열된다. 따라서 시계열 데이터의 인덱스를 가리킬 때는 '시각'이라는 용어를 사용한다.(예를 들어 시각 t의 입력 데이터 X_t 등) 자연어의 경우에도 't번째 단어'나 't번째 RNN 계층'이라는 표현도 사용하지만, '시각 t의 단어'나 '시각 t의 RNN 계층'이라는 용어를 사용하기도 한다.

 

위 그림을 보면 알 수 있듯, 각 시각의 RNN 계층은 그 계층으로의 입력과 1개 전의 RNN 계층으로부터의 출력을 받는다. 그리고 이 두 정보를 바탕으로 현 시각의 출력을 계산한다.
이때 수행하는 계산의 수식은 다음과 같다.

    • W_x = 입력 x를 출력 h로 변환하기 위한 가중치
    • W_h = 이전 시각의 1개의 RNN 출력을 다음 시각의 출력으로 변환하기 위한 가중치
    • b = 편향
RNN에는 가중치가 2개 있다.

 

참고로 h_{t-1}과 x_t는 행 벡터이다.
또한 RNN는 활성함수로 tanh함수를 사용한다. 그 결과가 시각 t의 출력 h_t가 된다.

 

위 식을 보면, 현재의 출력은 한 시각 이전 출력에 기초해 계산됨을 알 수 있다.
다른 관점으로 보면, RNN은 h라는 '상태'를 가지고 있으며, 위 식의 형태로 갱신된다고 해석할 수 있다.
그래서 RNN 계층을 '상태를 가지는 계층' 혹은 '메모리가 있는 계층'이라고 한다.

RNN의 h는 '상태'를 기억해 시각이 1스텝(1단위) 진행될 때마다 위 식의 형태로 갱신된다.
많은 문헌에서 RNN의 출력 h_t 를 은닉 상태 혹은 은닉 상태 벡터라고 한다. 이 책에서도 마찬가지로 RNN의 출력 h_t를 '은닉 상태' 혹은 '은닉 상태 벡터'로 부른다.

또한 많은 문헌에서 펼쳐진 RNN계층을 아래 그림의 왼쪽처럼 그린다.


우리는 RNN의 출력이 그대로 복사되 다음 계층의 입력이 된다는 것을 강조하기 위해, 우측처럼 그리도록 하겠다.

BPTT

지금까지 RNN의 순전파 방식을 알아보았다.
그렇다면 RNN의 역전파는 어떠한 방식으로 이루어질까?

 

이를 그림으로 표현하면 아래와 같다.


순환 구조의 RNN을 펼치고 나면, 오차 역전파법을 적용할 수 있다.
즉, 먼저 순전파를 수행하고, 이어서 역전파를 수행하여 원하는 기울기를 구할 수 있다.
여기서의 오차역전파법은 '시간 방향으로 펼친 신경망의 역전파법'이라는 뜻으로
BPTT(BackPropagation Through Time)라고 한다.

 

이 BPTT를 이용하면 RNN을 학습할 수 있을 듯 보이지만, 그 전에 해결해야 할 문제가 하나 있다.

이는 "긴 시계열 데이터"를 학습할 때의 문제이다.

  • 시계열 데이터의 시간 크기가 커지는 것에 비례하여 BPTT가 소비하는 컴퓨팅 자원도 증가하는 문제
    • "매 시각" RNN 계층의 중간 데이터를 메모리에 유지해두어야 역전파를 구할 수 있다.
    • 시간의 길이가 길어지면, 이 메모리를 유지하는 오버헤드가 커진다.
  • 시간 크기가 커지면 역전파 시의 기울기가 불안정해지는 문제
    • 기울기 소실/기울기 폭발

Truncated BPTT

큰 시계열 데이터를 취급할 때는 흔히 신경망 연결을 적당한 길이로 '끊는다'.
시간축 방향으로 너무 길어진 신경망을 적당한 지점에서 잘라내어 작은 신경망 여러개로 만드는 것이다.
그리고 이 잘라낸 작은 신경망에서 오차역전파법을 수행한다. 이것이 바로 Truncated BPTT이다.

Truncated BPTT는 신경망의 연결을 '역전파'에서만 끊어야 한다.
순전파의 연결은 예측을 위해 반드시 그대로 유지해야 한다.

 

예시를 통해 이해해보자.
길이가 1,000인 시계열 데이터가 있다고 해보자. 자연어 문제라면 단어 1,000개짜리 말뭉치에 해당할 것이다.
덧붙여서, 여러 문장을 연결한 것을 하나의 시계열 데이터로 취급하도록 하자.

 

그런데 길이가 1,000인 시계열 데이터를 다루면서 RNN 계층을 펼치면 계층이 가로로 1,000개나 늘어선 신경망이 된다.
물론 계층이 아무리 늘어서더라도 오차역전파법으로 기울기를 계산할 수는 있지만, 너무 길면 계산량과 메모리 사용량이 문제가 된다.
또한, 계층이 길어짐에 따라 신경망을 하나 통과할 때마다 기울기 값이 조금씩 작아져서, 과거 기울기가 소실될 수 있다.

역전파를 통해 현재 출력값의 손실함수 기울기값을 찾아야 가중치의 기울기를 찾을 수 있는데, 현재 가중치가 너무 많은 레이어를 통과하면서 0에 가까워지면 가중치를 제대로 학습시킬 수 없다. 이를 기울기 소실이라고 한다.

 

바로 그러한 이유로 아래처럼 기울기를 적당한 시점에 끊는다.


위 예에서는 RNN 계층을 길이 10개 단위로 학습할 수 있도록 역전파의 연결을 끊는다.

 

여기서 중요한 점이 하나 있는데, 바로 역전파의 연결은 끊어지지만, 순전파의 연결은 끊어지지 않는다는 점이다.
그래서 RNN을 학습시킬 때는 순전파가 연결된다는 점을 고려해야 하고, 이는 데이터를 "순서대로" 입력해야 한다는 뜻이다.

지금까지 본 신경망에서는 미니배치 학습을 수행할 때 데이터를 무작위로 선택해 입력했다. 그러나 RNN에서 Truncated BPTT를 수행할 때는 데이터를 '순서대로' 입력해야 한다.

 

이제 Truncated BPTT 방식으로 RNN을 학습시켜보자.


위 그림에서 보듯, 첫 번째 블록(x0, x9)에서 먼저 순전파를 수행하고, 이후 역전파를 수행한다.

 

이어서 다음 블록의 입력 데이터(x10, x19)를 입력해 오차역전파법을 수행해보자.
그림으로는 아래와 같다.


여기서 중요한 점은 이번 순전파 계산에는 앞 블록의 마지막 은닉 상태인 h9가 필요하다는 것이다.
이것으로 순전파는 계속 연결된다.

 

지금까지의 과정을 그림으로 나타내면 아래와 같다.

즉, 정리하면 다음과 같다.

  • 순전파 - 순환하는 구조로 돌아감
  • 역전파 - N개씩 블록으로 나눠서 역전파됨, 순환하지 않음

Truncated BPTT의 미니배치 학습

지금까지의 Truncated BPTT 이야기에는 미니배치 언급이 없었다.
미니배치로 학습을 수행하려면 어떻게 해야 할까?

 

원래라면 앞서 설명한 그림처럼 데이터를 순서대로 줘야 하는데, 잘라서 학습을 시키려면 어떻게 이전 출력값을 가져와야 할까?
그렇게 하려면 데이터를 주는 시작 위치를 각 미니 배치의 시작 위치로 '옮겨와야' 한다.

 

예시를 보며 좀 더 자세히 이해해보자.
앞과 같이 길이 1000의 시계열 데이터에 대해서, 시각의 길이를 10개 단위로 잘라 Truncated BPTT로 학습하는 경우를 예로 들어보자.

 

만약 미니배치 수를 두개로 구성해 학습(500개, 500개)하려고 하면 어떻게 해야 할까?
이 경우 RNN 계층의 입력 데이터로, 첫 번째 미니배치(샘플 데이터) 때는 처음부터 순서대로 데이터를 제공한다.
그리고 두 번째 미니배치 때는 500번째의 데이터를 시작 위치로 정하고, 그 위치부터 다시 순서대로 데이터를 제공하는 것이다.
(즉, 500번째 데이터는 이전 은닉상태 h499를 받지 않고 0에서 시작하는 것이다.)

RNN 구현

앞 절까지의 이야기에서 RNN의 전체 모습을 볼 수 있었다.

 

결국 우리가 지금부터 구현해야 할 것은 가로 방향으로 성장한 신경망이다.
그리고 Truncated BPTT방식의 학습을 따른다면, 가로 크기가 일정한 일련의 신경망을 만들면 된다.


위 그림에서 보듯, 우리는 블록 단위로 학습을 진행할 것이고, 따라서 우리가 다룰 신경망은 길이가 T인 시계열 데이터를 받는다.
그리고 각 시각의 은닉 상태를 T개 출력한다.
그리고 모듈화를 생각해, 옆으로 성장한 이 신경망을 '하나의 블록'으로 구현할 것이다.
그림으로 보면 아래와 같다.


위 그림과 같이 상하 바람의 입력과 출력을 각각 하나로 묶으면 옆으로 늘어선 일련의 계층을 하나의 계층으로 간주할 수 있다.
즉, (x0, x1, ..., xT-1)을 묶은 xs를 입력하면 (h0, h1, ..., hT-1)을 묶은 hs를 출력하는 "단일 블록"으로 볼 수 있다.

 

이때 T개 단계분의 작업을 한꺼번에 처리하는 계층을 'Time RNN 계층'이라고 하고, Time RNN 계층 내에서 한 단계의 작업을 수행하는 계층을 'RNN 계층'이라고 하자.

 

우리는 다음을 구현할 것이다.

  1. RNN의 한 단계를 처리하는 클래스 -> class RNN
  2. RNN 클래스를 이용해 T개 단계의 처리를 한꺼번에 수행하는 계층 -> class TimeRNN

RNN 계층 구현

RNN 처리를 한 단계만 수행하는 RNN 클래스부터 구현해보자.

이때 우리는 행렬곱 연산 특성 상 형삭을 검증해야 한다.
형상을 어떤 방식으로 구성해야 하는지 확인해보자.

그럼 이상을 바탕으로 RNN클래스의 초기화와 순전파 메소드를 구현해보자.

RNN의 초기화 메소드는 가중치 2개와 편향 1개를 인수로 받는다.
여기에서는 인수로 받은 매개변수를 인스턴스 변수 params에 리스트로 저장한다.
그리고 각 매개변수에 대응하는 형태로 기울기를 초기화한 후 grads에 저장한다.
마지막으로 역전파 시 사용하는 중간 데이터 cache를 None으로 초기화한다.

 

RNN의 순전파 메소드는 인수 두개를 받는다.

  • 아래에서 오는 x
  • 이전 은닉 상태에서 오는 h_prev

그리고 위에서 정의한 식을 그대로 수행한다.

 

 

이제 역전파를 구현할 차례이다.

우선 RNN 순전파의 계산 그래프를 확인해보자.


그럼 이제 위 그림의 역전파를 구현해보자.
순전파 때와는 반대 방향으로 각 연산자의 역전파를 수행하기만 하면 된다.

이대로 구현하면 아래와 같다.

Time RNN 계층 구현

이제 T개의 RNN을 묶은 TimeRNN을 구하자.
여기서 T는 임의의 수로 설정할 수 있다.

 

그림으로 표현하면 아래와 같다.


이 그림에서 보듯, Time RNN 계층은 은닉상태 h를 인스턴스 상태로 유지한다.
그리고 이 변수를 인계하는 방식으로 직접 전달한다.

 

그런데, 이 은닉 상태를 반드시 해당 RNN 객체 하나가 갖고 직접 전달해야 할까?
상위 클래스인 Time RNN이 이 h를 내부 인스턴스 변수로 유지하면, 은닉 상태를 직접 참조하는 방식으로 전달할 수 있다.


이렇게 하면 Time RNN 사용자는 RNN 계층 사이에서 은닉 상태를 직접 전달하는 작업을 생각하지 않아도 되는 장점이 생긴다.
그리고 이 책에서는 이 기능(은닉 상태를 인계받을지)을 stateful이라는 인수로 조정할 수 있게 했다.

이젠 순전파를 구현해보자.


순전파의 경우, xs를 입력받는다.
xs는 T개 분량의 시계열 데이터를 하나로 모은 것으로, 미니배치 크기를 N, 입력 벡터의 차원 수를 D라고 하면 xs의 형상은 (N, T, D)가 된다.

 

RNN 계층의 은닉상태 h는 처음 호출 시에는 원소가 모두 0인 영행렬로 초기화된다.
그리고 인스턴스 변수 stateful이 False인 경우에도 항상 영행렬로 초기화한다.

 

기본 구현에서는 처음 hs = np.empty((N, T, H))로 출력값을 담을 자료구조를 생성한다.
이어서 총 T회 반복되는 for문 안에서 RNN계층을 생성하여 인스턴스 변수 layers에 추가한다.
그 사이에 RNN 계층이 각 시각 t의 은닉 상태 h를 계산하고, 이를 hs에 해당 인덱스(시각)의 값으로 설정한다.

Time RNN 계층의 forward() 메소드가 불리면, 인스턴스 변수 h에는 마지막 RNN 계층의 은닉 상태가 저장된다.
그래서 다음번 forward() 메소드 호출 시 stateful=True면 먼저 저장된 h값이 그대로 이용되고, stateful=False 면 h가 다시 영행렬로 초기화된다.

 

이어서 Time RNN 블록의 역전파를 구현해보자.


상류(출력 쪽 층)에서부터 전해지는 기울기를 dhs로 쓰고, 하류에 내보내는 기울기를 dxs로 쓰겠다.

우리는 Truncated BPTT를 쓰기 때문에 이전 시간의 역전파는 사용하지 않는다.
하지만 seq2seq를 위해 이전 시간의 은닉상태 기울기 dh를 인스턴스 변수로 저장해두도록 하겠다.

 

이 그림을 좀 더 확대해서 상세히 보면 아래와 같다.


t번째 RNN 계층에서는 위로부터의 기울기 dh_t와 '한 시각 뒤(미래) 계층'으로부터의 기울기 dh_next 가 전해진다.

 

여기서의 주의점은 RNN계층의 순전파에서는 출력이 2개로 분기된다는 것이다.
순전파 시 분기했을 경우, 그 역전파에서는 각 기울기가 합산되어 전해진다.
따라서 역전파 시 RNN 계층에는 합산된 기울기(dh_t + dh_next)가 입력된다.

 

이를 주의하며 역전파를 구현하면 아래와 같다.


여기에서도 가장 먼저 하류로 흘려보낼 기울기를 담을 그릇인 dxs를 만든다.
그리고 순전파때와는 반대 순서로 RNN 계층의 backward() 메소드를 호출하여, 각 시각의 기울기 dx를 구해 dxs의 해당 인덱스(시각)에 저장한다.
그리고 가중치 매개변수에 대해서도 각 RNN 계층의 가중치 기울기를 합산하여 최종 결과를 멤버 변수 self.grads에 덮어쓴다.

Time RNN 계층 안에는 RNN 계층이 여러 개 있다.
그리고 그 RNN 계층들에서 똑같은 가중치를 사용하고 있다.
따라서 Time RNN 계층의 최종 가중치의 기울기는 각 RNN 계층의 가중치 기울기를 모두 더한 게 된다.

시계열 데이터 처리 계층 구현

이제 RNN을 사용하여 '언어 모델'을 구현해보자.
지금까지 우리는 RNN 계층을 구현했는데, 이번 절에서는 시계열 데이터를 처리하는 계층을 몇개 더 만들어보면서 RNN Language Model을 완성해보자.

 

그리고 지금부터 RNN을 사용한 언어 모델을 RNNLM이라고 부르도록 하겠다.

RNNLM의 전체 그림

먼저 RNNLM에 사용되는 신경망을 한번 보고 시작하자.
왼쪽은 RMMLN의 계층 구성이고, 오른쪽에는 이를 x축에 대하여 시간축으로 펼친 신경망이다.

  1. 먼저 Embedding 계층이 단어 ID를 단어의 분산 표현(단어 벡터)로 변환한다.
  2. 그리고 그 분산 표현이 RNN 계층으로 입력된다.
  3. RNN 계층은 은닉 상태를 다음 층으로(위쪽으로) 출력함과 동시에, 다음 시각의 RNN계층으로(오른쪽으로) 출력한다.
  4. 그리고 RNN 계층이 위로 출력한 은닉 상태는 Affine 계층을 거쳐 Softmax 계층으로 전해진다.

위 그림에서 순전파를 예시로 한번 출력 결과를 관찰해보자.
예제는 이번에도 'you say goodbye and i say hello.' 이다.


확률분포 결과를 보니 정상적으로 다음 단어들을 잘 예측하고 있다.

 

 

여기서 중요한 부분은 say를 입력하는 부분이다.
이때의 softmax 계층 출력은 'goodbye'와 'hello' 두 곳에서 높게 나왔는데, RNN계층은 "you say" 라는 맥락을 기억하고 있다는 것이다.
더 정확하게 말하면, RNN은 "you say"라는 과거의 정보를 응집된 은닉 상태 벡터로 저장해두고 있다.
그러한 정보를 더 위의 Affine 계층에, 그리고 다음 시각의 RNN계층에 전달하는 것이 RNN 계층이 하는 일이다.

 

 

이처럼 RNNLM은 지금까지 입력된 단어를 '기억'하고, 그것을 바탕으로 다음에 출현할 단어를 예측한다.
이 일을 가능하게 하는 비결이 바로 RNN 계층의 존재이다.
RNN 계층이 과거에서 현제로 데이터를 계속 흘려보내줌으로써 과거의 정보를 인코딩해 저장(기억)할 수 있는 것이다.

Time 계층 구현

지금까지는 시계열 데이터를 한꺼번에 처리하는 계층을 Time RNN 이라는 계층으로 구현했다. 이번 절에서는, 기존에 사용하던 Embedding과 Affine계층, Softmax with Loss 계층을 Time 계층에 맞추기 위한 Time Embedding, Time Affine, Time Softmax 계층을 구현하겠다.

 

그렇게 하면 우측처럼 우리가 원하는 방식으로 구현할 수 있게 된다.

Time Embedding, Time Affine계층은 간단하게 구현이 가능하다.
예를 들어 Time Affine 계층은 Affine 계층을 T개 준비해서, 각 시각의 데이터를 개별적으로 처리하면 된다.

그럼 Time Softmax with Loss 계층은 어떻게 구현해야 할까?


그림에서 보듯, 정답 레이블은 T개만큼 존재한다.
그리고 일반적으로 손실함수 값은 그 손실 값들을 합산해 평균한 값이 최종 손실이 된다.


우리는 데이터 N개 짜리 미니배치라면 그 N개의 손실을 더해 다시 N으로 나눠 데이터 1개당 평균 손실을 구했다.
Time Softmax with Loss도 이와 유사하게 T개의 손실을 더해 T로 나누면 된다.

RNNLM 학습과 평가

이제 이 RNN을 학습시키고 이를 평가해보자.

RNNLM 구현

이제 RNNLM 신경망을 구현해보자.
신경망은 아래와 같이 구성될 것이다.

초기화 메소드부터 구현해보자.


이 초기화 메소드는 각 계층에서 사용하는 매개변수(가중치와 편향)를 초기화하고 필요한 계층을 생성한다.
또, Truncated BPTT로 학습한다고 가정하여 TimeRNN 계층의 stateful=True 를 설정했다. 그 결과 TimeRNN 계층은 이전 시각의 은닉 상태를 계승할 수 있게 된다.

 

또한 이 초기화 코드는 RNN 계층과 Affine 계층에서 Xavier 초깃값을 사용했다.

관련 내용은 여기를 참고하길 바랍니다.

 

[밑바닥부터 시작하는 딥러닝] 학습 관련 기술들

본 내용은 밑바닥부터 시작하는 딥러닝 1도서를 참고하여 작성되었습니다. 밑바닥부터 시작하는 딥러닝 1 - 예스24딥러닝 분야 부동의 베스트셀러!머리로 이해하고 손으로 익히는 가장 쉬운 딥

dev.go-gradually.me

 

계속해서 순전파, 역전파, reset_state() 메소드를 구현해보자.


reset_state는 신경망의 상태를 초기화하는 편의 메소드이다.

언어 모델의 평가 - 퍼플렉서티, 혼란도

완전 연결 계층은 정확도를 통해 모델을 평가할 수 있었다.
언어 모델은 어떠한 방식으로 평가할까?
언어 모델은 주어진 과거 단어(정보)로부터 다음에 출현할 단어의 확률분포를 출력한다.
이때 언어 모델의 예측 성능을 평가하는 척도로 퍼플렉서티(perplexity, 혼란도)를 자주 이용한다.

 

간단히 말하면 퍼플렉시티는 "확률의 역수"이다.(이 해석은 데이터 수가 하나일 때에 정확히 일치한다.)
확률의 역수라는 개념을 설명하기 위해, 다시 한번 'you say hello and ~'를 다시 가져와보자.


'모델 1'의 언어 모델에 "you" 라는 단어를 주니 "say"가 등장할 확률을 0.8로 출력했다.
이때의 퍼플렉시티는 이 확률의 역수, 즉 아래와 같이 계산할 수 있다.


우측 모델의 경우에는, 정답인 "say"의 확률이 0.2 라고 예측했다.
이때의 퍼플렉시티는 다음과 같다.


위 예에서 퍼플렉시티가 낮은 쪽이 더 성능이 좋다는 것을 직관적으로 확인할 수 있었다.

그럼 이 퍼플렉시티는 직관적으로는 어떻게 해석할 수 있을까?
이 값은 '분기수'(number of branches)로 해석할 수 있다.
앞의 예시에서, 좋은 모델이 예측한 '분기 수'가 1.25라는 것은 다음의 출현할 수 있는 단어의 후보를 1개 정도로 좁혔다는 뜻이 된다.
반면 나쁜 모델에서는 후보가 아직 5개나 된다는 의미이다.

지금까지는 입력 데이터가 하나일 때의 퍼플렉서티를 이야기했다. 그렇다면 입력 데이터가 여러 개일 때는 어떻게 될까?

  • N = 데이터의 총 개수
  • t_n = 원-핫 벡터로 나타낸 정답 레이블
  • t_nk = n개째 데이터의 k번째 값
  • y_nk = 확률 분포(softmax 출력)

즉, e를 CEE로 제곱한 값이 퍼플렉시티이다.
퍼플렉시티가 작아질수록 분기 수가 줄어 좋은 모델이 된다.

RNNLM의 학습 코드

PTB 데이터셋을 이용해 RNNLM 학습을 수행해보자.

단, 이번에 구현할 RNNLM은 PTB 데이터셋 (훈련 데이터) 전부를 사용하면 전혀 좋은 결과를 낼 수 없기 때문에, 처음 1000개 단어만 사용한다.
이는 뒷장 LSTM에서 해결하도록 한다.

 

우선 설정/하이퍼파라미터 세팅부터 수행해보자.


초기 하이퍼파라미터 세팅을 위와 같이 수행했다.

 

 

이제 학습 코드를 구현해보자.


우선 우리는 Truncated BPTT 방식으로 학습을 수행하기에, 데이터는 순차적으로 주고 각각의 미니배치에서 데이터를 읽는 시작 위치를 조정했다.

 

이어서 데이터를 순서대로 읽으며 미니배치를 획득한다.

 

그리고 학습을 수행한 뒤, 에폭마다 퍼플렉시티를 평가했다.

 

그리고 아래에 퍼플렉시티를 plt를 이용해 출력했다.


처음에는 400 가까이 되던 값이 마지막에는 1에 근접했다.

 

다음 장에서 추후 큰 말뭉치에 대처하기 위해 LSTM을 학습해보도록 하자.

이번 장에서 배운 것

  • RNN은 순환하는 경로가 있고, 이를 통해 내부에 '은닉 상태'를 기억할 수 있다.
  • RNN의 순환 경로를 펼침으로써 다수의 RNN 계층이 연결된 신경망으로 해석할 수 있으며, 보통 오차 역전파법으로 학습할 수 있다.(=BPTT)
  • 긴 시계열 데이터를 학습할 때는 데이터를 적당한 길이씩 모으고(이를 '블록'이라 한다), 블록 단위로 BPTT에 의한 학습을 수행한다. (=Truncated BPTT)
  • Truncated BPTT에서는 역전파의 연결만 끊는다.
  • Truncated BPTT에서는 순전파의 연결을 유지하기 위해 데이터를 '순차적'으로 입력해야 한다.
  • 언어 모델은 단어 시퀀스를 확률로 해석한다.
  • RNN 계층을 이용한 조건부 언어 모델은 (이론적으로는) 그때까지 등장한 모든 단어의 정보를 기억할 수 있다.