목차
- INTRO
- 확률과 언어 모델
- RNN(순환 신경망)이란
- RNN 구현
- 시계열 데이터 처리 계층 구현 RNNLM
- RNNLM 학습과 평가
- 언어 모델의 평가 perplexity
- RNNLM의 Trainer 클래스
- 정리
👀, 🤷♀️ , 📜
이 아이콘들을 누르시면 코드, 개념 부가 설명을 보실 수 있습니다:)
INTRO
⛔ RNN 전 word2vec과 같은 피드포워드 feed forward
지금까지 살펴본 신경망, 앞먹임 라는 유형의 신경망
흐름이 단방향인 신경망
[장점]
- 입력 신호가 다음 층(중간층)으로 전달되고, 그신호를 받은 층은 그다음 층으로 전달하고, 다시 다음 층으로…식으로 한 방향 으로만 신호가 전달
- 구성이 단순하여 구조를 이해하기 쉽고, 그래서 많은 문제에 응용 가능.
[단점]
- 시계열 데이터를 잘 다루지 못함.
- 단순한 피드포워드 신경망에서는 시계열 데이터의 성질(패턴)을 충분히 학습 불가.
확률과 언어 모델
준비 과정,
- word2vec을 복습(word2vec을 확률 관점에서 바라보다)
- 자연어에 관한 현상을 ‘확률’을 사용해 기술
- 언어를 확률로 다루는 ‘언어 모델’에 대해 설명
word2vec의 CBOW 모델 복습
word2vec 자세히 알아보기
✔ 가정
- 말뭉치: \(w_1 , w_2 , …, w_T\) 라는 단어열로 표현
- 타깃: t번째 단어
- 맥락: 그 전후 단어(t-1번째와 t +1번째)
- CBOW 모델: 맥락 \(w_{t-1}\) 과 \(w_{t+1}\) 로부터 타깃 \(w_t\) 를 추측하는 일을 수행
확률
- w_t-1 과 w_t +1 이 주어졌을 때 타깃이 w_t 가 될 확률.
- \(P(w_t \| w_{t-1} , w_{t+1})\)
- CBOW 모델은 위 식의 사후 확률(\(w_{t-1}\) 과 \(w_{t+1}\) 이 주어졌을때 \(w_t\) 가 일어날 확률)을 모델링(윈도우 크기가 1일 때)
✔ 가정 +
- 지금까지는 맥락을 항상 좌우 대칭만 생각.
- 이번에는 맥락을 왼쪽 윈도우 만으로 한정해보겠습니다.
확률 \(P(w_t |w_{t -2} , w_{t -1} )\).
📜 word2vec에서 맥락의 윈도우 크기
word2vec에서 맥락의 윈도우 크기는 하이퍼파라미터(임의의 값으로 설정 불가)임.
이번 예에서는 윈도우 크기를 ‘왼쪽 2 단어, 오른쪽 0 단어’라는 좌우 비대칭으로 설정됨.
이렇게 설정한 이유는 나중에 설명하는 ‘언어 모델’에서 이야기하겠습니다.
CBOW 모델이 다루는 손실 함수
교차 엔트로피 오차에 의해 유도하면 \(L = -logP(w_t |w_{t -2} , w_{t -1} )\)
[CBOW 모델의 학습으로 수행하는 일]
위의 손실 함수(정확히는 말뭉치 전체의 손실 함수의 총합)를 최소화하는 가중치 매개변수를 찾는 것.
이러한 가중치 매개변수가 발견되면 CBOW 모델은 맥락으로부터 타깃을 더 정확하게 추측 가능
- CBOW 모델을 학습시키는 본래 목적
- 맥락으로부터 타깃을 정확하게 추측하는 것
- 이 목적을 위해 학습을 진행하면, (그 부산물로) 단어의 의미가 인코딩된 ‘단어의 분산 표현’ 얻기 가능.
[QUESTION]
그럼 CBOW 모델의 본래 목적인 ‘맥락으로부터 타깃을 추측하는 것’은 의 실용적인 쓰임?
➡ 여기서 ‘언어 모델’이 등장
[ANSWER]
언어 모델(Language Model)
- 단어 나열에 확률을 부여.
- 특정한 단어의 시퀀스에 대해서, 그 시퀀스가 일어날 가능성이 어느 정도인지(얼마나 자연스러운 단어 순서인지)를 확률로 평가하는 것.
ex)
“you say goodbye”라는 단어 시퀀스에는 높은 확률(예: 0.092 )을 출력하고, “you say good die”에는 낮은 확률 (예: 0.0000000000032 )을 출력하는 것이 일종의 언어 모델.
응용)
이 언어 모델은 다양하게 응용 가능.
(1) 기계 번역
(2) 음성 인식
- 음성 인식 시스템의 경우, 사람의 음성으로부터 몇 개의 문장을 후보로 생성.
- 그런 다음 언어 모델을 사용하여 후보 문장이 ‘문장으로써 자연스러운지’를 기준으로 순서를 매기기 가능.
수식)
언어 모델을 수식으로 설명.
- 가정: \(W_1 , …, w_m\) 이라는 m개 단어로 된 문장.
- 동시 확률: 이때 단어가\(w_1 , …, w_m\) 이라는 순서로 출현할 확률을 \(P(w_1 , …, w_m )\) 으로 표현.
- 이 확률은 여러 사건이 동시에 일어날 확률
- 동시확률 분해
- 수식을 간소화하기 위해 여기에서는 \(P(w_1 \|w_0 )\)를 \(P(w_1)\)로 처리합니다.
📜 위의 결과 유도
확률의 곱셈정리 사용
확률의 곱셈정리: P(A, B) = P(A|B)P(B)
- 의미:‘A와 B 가 모두 일어날 확률 P (A, B )’는 ‘B가 일어날 확률 P (B )’와 ‘B가 일어난 후 A가 일어날 확률 P (A|B )’를 곱한 값과 같다는 것입니다(아주 자연스러운 해석이라 느껴질 것입니다).
- = P (A, B )를 P (A, B ) = P (B|A )P (A )
- 이 곱셈정리를 사용하여 m개 단어의 동시 확률 P (w_1 , …, w_m )을 사후 확률로 나타내기.
1) 마지막 것 빼고 A로 묶기
2) 여기서 P(A)에 대해 다시 정리
3) 이처럼 단어 시퀀스를 하나씩 줄여가면서 매번 사후 확률로 분해해 감.
같은 과정을 반복하면
\(P (w_1 , …, w_m ) =ΠP(├ w_t ┤| w_1,⋯,w_{t-1} )\)
여기에서 주목할 것은 이 사후 확률은 타깃 단어보다 왼쪽에 있는 모든 단어를 맥락(조건)으로 했을 때의 확률이라는 것
CBOW 모델 ➡ 언어 모델의 문제점
그렇다면 word2vec의 CBOW 모델을 (억지로) 언어 모델에 적용하는 방법?
- 이는 맥락의 크기를 특정 값으로 한정하여 근사적으로 나타낼 수 있음
- 여기에서는 맥락을 왼쪽 2개의 단어로 한정: 그러면 CBOW 모델에 따라(CBOW 모델의 사후 확률에 따라) 근사적 표현 가능
📜 마르코프 연쇄 Markov Chain
미래의 상태가 현재 상태에만 의존해 결정되는 것.
- N층 마르코프 연쇄: 이 사상의 확률이 ‘그 직전’ N개의 사건에만 의존할 때
- 이번 예
- 2층 마르코프 연쇄: 직전 2개의 단어에만 의존해 다음 단어가 정해지는 모델이므로
위에서는 맥락으로 2개의 단어를 이용하는 예를 나타냈지만, 이 맥락의 크기는 임의 길이로 설정 가능(예컨대 5나 10 등).
⛔ PROBLEM
임의 길이로 설정할 수 있다고 해도, 결국 특정 길이로 ‘고정’
(ex)
왼쪽 10개의 단어를 맥락으로 CBOW 모델을 만든다고 하면, 그 맥락보다 더 왼쪽에 있는 단어의 정보는 무시됨.
이것이 문제가 될 때가 있는 데,
예를들어 아래와 같이 ?에 들어갈 단어 문제가 있다고 보자(긴 맥락이 필요하다)
- 위의 문제에서 “Tom이 방에서 TV를 보고 있었고, Mary가 그 방에 들어왔다”고 한다.
- 정답: 이 문맥(맥락)을 고려하면 “Mary가 Tom (혹은 he )에게 인사를 건넸다”가 정답
[1_problem]
이 문제에서 정답을 구하려면 예문의 “?”로부터 18번째나 앞에 나오는 “Tom”을 기억해야 함
하지만 만약 CBOW 모델의 맥락이 10개까지였다면 이 문제에 제대로 답할 수 없음
[1_solve]
그럼 CBOW 모델의 맥락 크기를 20이나 30까지 키우면?
물론 CBOW 모델의 맥락 크기는 얼마든지 키울 수 있음.
그러나 CBOW 모델에서는 맥락 안의 단어 순서가 무시된다는 한계 존재
📜 CBOW의 의미
CBOW란 continuous bag-of-words의 약어.
bag-of-words: ‘가방 안의 단어’를 뜻
- 여기에는 가방 속의 단어 ‘순서’는 무시된다는 뜻도 내포(‘순서’ 대신 ‘분포’를 이용합니다).
[2_problem]
맥락의 단어 순서가 무시되는 문제.
(EX)
예컨대 맥락으로 2개의 단어를 다루는 경우, CBOW 모델에서는 아래 그림처럼 이 2개의 단어 벡터의 ‘합’ 이 은닉층에 옴.
- 왼쪽 그림과 같이 CBOW 모델의 은닉층에서는 단어 벡터들이 더해지므로 맥락의 단어 순서는 무시됨.
- EX) (you, say )와 (say, you )라는 맥락을 똑같이 취급
[2_solve]
이상적으로는 맥락의 단어 순서도 고려한 모델이 바람직할 것.
1) concatenate 사용
- 위 그림의 오른쪽처럼 맥락의 단어 벡터를 은닉층에서 연결.
- 실제, 신경 확률론적 언어 모델 Neural Probabilistic Language Model 에서 제안한 모델은 이 방식을 취함
- 단점: 연결하는 방식을 취하면 맥락의 크기에 비례해 가중치 매개변수도 늘어나게 됨.
2) RNN
- 위의 단점 해결
- RNN은 맥락이 아무리 길더라도 그 맥락의 정보를 기억하는 메커니즘을 갖추고 있음
- 아무리 긴 시계열 데이터에라도 대응 가능
RNN(순환 신경망)이란
RNN(Recurrent Neural Network) 의미
- ‘Recurrent’는 라틴어에서 온 말로, ‘몇 번이나 반복해서 일어나는 일’을 뜻함.
- 우리말로는 ‘재발한다’, ‘주기적으로 일어난다’, ‘순환한다’ 등으로 번역
- 그래서 RNN을 직역하면 ‘순환하는 신경망’
📜 주의
- Recursive Neural Network (재귀 신경망)
- 이는 주로 트리 구조의 데이터를 처리하기 위한 신경망으로, 순환 신경망과는 다른 것.
순환하는 신경망
- 순환: 시간을 지나 다시 원래 장소로 돌아오는 것, 그리고 이 과정을 반복하는 것
- 여기서 하나 주목할 사실은, 순환하기 위해서는 ‘닫힌 경로’필요
- ‘닫힌 경로’ 혹은 ‘순환하는 경로’가 존재해야 데이터가 같은 장소를 반복해 왕래 가능
- 그리고 데이터가 순환하면서 정보가 끊임없이 갱신되게 됨
RNN의 특징
- 순환하는 경로(닫힌 경로)가 있다는 것.
- 이 순환 경로를 따라 데이터는 끊임없이 순환가능.
- 데이터가 순환되기 때문에 과거의 정보를 기억하는 동시에 최신 데이터로 갱신 가능
RNN 계층
- RNN에 이용되는 계층
- RNN 계층은 순환하는 경로를 포함
- 이 순환 경로를 따라 데이터를 계층 안에서 순환시킬 수 있음
- \(X_t\): 입력
- \(t\): 시각
이는 시계열 데이터 \((x_0 , x_1 , …, x_t , …)\)가 RNN 계층에 입력됨을 표현한 것.
그리고 그 입력에 대응하여 \((h_0 , h_1 , …, h_t , …)\)가 출력
각 시각에 입력되는 \(x_t\) 는 벡터라고 가정
- 문장(단어 순서)을 다루는 경우를 예로 든다면 각 단어의 분산 표현(단어 벡터)이 \(x_t\) 가 되며,
- 이 분산 표현이 순서대로 하나씩 RNN 계층에 입력되는 것.
WARNING
그림을 잘 보면 출력이 2개로 분기하고 있음
- 분기: 같은 것이 복제되어 분기함을 의미.
그리고 이렇게 분기된 출력 중 하나가 자기 자신에 입력되죠(즉, 순환합니다).
순환 구조를 자세히 살펴보기 전, RNN 계층을 그리는 방식을 다음과 같이 변경
지면 관계상 아래에서 위로 흐르도록 그리겠음
(곧이어 순환 구조를 펼쳐볼 텐데, 이때 계층을 양옆으로 펼치기 위해서).
순환 구조 펼치기
순환 구조: 지금까지의 신경망에는 존재하지 않던 구조.
- 그러나 이 순환 구조를 펼치면 친숙한 신경망으로 변신가능
WARNING 시계열 데이터는 시간 방향으로 데이터가 나열.
따라서 시계열 데이터의 인덱스를 가리킬 때는 ‘시각’이라는 용어를 사용 (예컨대 시각 t의 입력 데이터 \(x_t\) 등).
자연어의 경우에도 ‘t번째 단어’나 ‘t번째 RNN 계층’이라는 표현도 사용하지만, ‘시각 t의 단어’나 ‘시각 t의 RNN 계층’처럼 표현하기도 함.
각 시각의 RNN 계층은 그 계층으로의 입력과 1개 전의 RNN 계층으로부터의 출력을 받음
이 두 정보를 바탕으로 현 시각의 출력을 계산
[이때 수행하는 계산의 수식]
- RNN에 있는 가중치가 2개
- (1) 가중치 \(W_x\): 입력 x를 출력 h로 변환하기 위한 가중치
- (2) 가중치 \(W_h\): 1개의 RNN 출력을 다음 시각의 출력으로 변환하기 위한 가중치
- b: 편향
- 참고로 \(h_{t-1}\) 과 \(x_t\) 는 행벡터.
식 설명
1) 행렬 곱을 계산
2) 그 합을 tanh 함수(쌍곡탄젠트 hyperbolic tangent 함수)를 이용해 변환.
3) 그 결과가 시각t의 출력 \(h_t\) 가 됨.
- \(h_t\) : 다른 계층을 향해 위쪽으로 출력되는 동시에, 다음 시각의 RNN 계층(자기 자신)을 향해 오른쪽으로도 출력.
➡ 현재의 출력(\(h_t\) )은 한 시각 이전 출력(\(h_{t-1}\) )에 기초해 계산됨.
RNN은 h라는 ‘상태’를 가지고 있으며, 위 식의 형태로 갱신 됨
그래서 RNN 계층을 ‘상태를 가지는 계층’ 혹은 ‘메모리(기억력)가 있는 계층’이라고 함.
\(h_t\): RNN의 h는 ‘상태’를 기억해 시각이 1 스텝(1 단위) 진행될 때마다 위 식의 형태로 갱신됨.
많은 문헌에서 RNN의 출력 \(h_t\) 를 은닉 상태 hidden state 혹은 은닉 상태 벡터 hidden state vector 라고 함.
BPTT
앞에서 봤듯이 RNN 계층은 가로로 펼친 신경망으로 간주 가능.
- RNN의 학습도 보통의 신경망과 같은 순서로 진행 가능
그림과 같이, 순환 구조를 펼친 후의 RNN에는 (일반적인) 오차역전파법을 적용 가능.
즉, 먼저 순전파를 수행하고, 이어서 역전파를 수행하여 원하는 기울기를 구할 수 있음.
BPTT(Backpropagation Through Time)
여기서의 오차역전파법은 ‘시간 방향으로 펼친 신경망의 오차역전파법’이란 뜻으로 BPTT라고 함.
[준비]
이 BPTT를 이용하여 RNN을 학습하기 전, 해결해야 할 문제: 긴 시계열 데이터를 학습할 때의 문제.
- 시계열 데이터의 시간 크기가 커지는 것에 비례하여 BPTT가 소비하는 컴퓨팅 자원도 증가하기 때문.
- 시간 크기가 커지면 역전파 시의 기울기가 불안정해짐.
📜 주의
BPTT를 이용해 기울기를 구하려면,
매 시각 RNN 계층의 중간 데이터를 메모리에 유지해두지 않으면 안되기 때문(RNN 계층의 역전파는 나중에 설명합니다).
따라서 시계열 데이터가 길어짐에 따라 (계산 량뿐 아니라) 메모리 사용량도 증가하게 됩니다.
Truncated BPTT
[과정]
1) 큰 시계열 데이터를 취급할 때: 흔히 신경망 연결을 적당한 길이로 ‘끊음’.
시간축 방향으로 너무 길어진 신경망을 적당한 지점에서 잘라내어 작은 신경망 여러 개로 만든다는 아이디어
2) 이 잘라낸 작은 신경망에서 오차역전파법을 수행
NOTE: Truncated는 ‘잘린’이란 뜻. 따라서 Truncated BPTT는 적당한 길이로 ‘잘라낸’ 오차역 전파법이 되는 것.
[구현시 주의점]
‘역전파’의 연결만 끊어야 함.
- 역전파의 연결: 적당한 길이로 잘라내, 그 잘라낸 신경망 단위로 학습을 수행
- 순전파의 연결: 반드시 그대로 유지
- 데이터를 ‘순서대로 sequential’ 입력해야 함
WARNING
지금까지 본 신경망에서는 미니배치 학습을 수행할 때 데이터를 무작위로 선택해 입력함.
그러나 RNN에서 Truncated BPTT를 수행할 때는 데이터를 ‘순서대로’ 입력해야 함.
[Ex] 가정: 길이가 1,000인 시계열 데이터가 존재
- 자연어 문제에서라면 단어 1,000개짜리 말뭉치에 해당함.
- 우리가 지금까지 다룬 PTB 데이터셋에서는 여러 문장을 연결한 것을 하나의 큰 시계열 데이터로 취급.
- 여기에서도 마찬가지로 여러 문장을 연결한 것을 하나의 시계열 데이터로 취급하겠습니다.
(problem)
길이가 1,000인 시계열 데이터를 다루면서 RNN 계층을 펼치면 계층이 가로로 1,000개나 늘어선 신경망이 됨.
P1) 계산량과 메모리 사용량 등이 문제
P2) 또한,계층이 길어짐에 따라 신경망을 하나 통과할 때마다 기울기 값이 조금씩 작아져서, 이전 시각 t 까지 역전파되기 전에 0이 되어 소멸할 수 있음.
(solve) 각각의 블록 단위로, 미래의 블록과는 독립적으로 오차역전파법을 완결시킬 수 있음.
[Truncated BPTT 방식으로 RNN 학습]
1) 첫 번째 블록 입력 데이터\((x_0 , …, x_9)\)를 RNN 계층에 제공
(1) 먼저 순전파를 수행
(2) 그 다음 역전파를 수행.
(3) 이렇게 하여 원하는 기울기를 구함
2) 다음 블록의 입력 데이터\((x_{10} 에서 x_{19})\)를 입력해 오차역전파법을 수행
첫 번째 블록과 마찬가지로 순전파를 수행한 다음 역전파를 수행
(여기서 중요한 점)
- 이번 순전파 계산에는 앞 블록의 마지막 은닉 상태인 \(h_9\)필요.
- 이것으로 순전파는 계속 연결될 수 있음.
3) 반복
이처럼 RNN 학습에서는 데이터를 순서대로 입력하며, 은닉 상태를 계승하면서 학습을 수행.
- 데이터를 순서대로 입력해 학습.
- 이런 식으로 순전파의 연결을 유지하면서 블록 단위로 오차역전파법을 적용 가능
[Truncated BPTT의 미니배치 학습]
미니배치 학습 시 각각의 미니배치가 어떤 식으로 이뤄지는지 알아보자
- 지금까지는 미니배치 수가 1일 때에 해당.
우리는 미니배치 학습을 수행하기 때문에, 원래대로면 구체적인 배치 방식을 고려해 3) 처럼 데이터를 순서대로 입력해야 함.
- 데이터를 주는 시작 위치를 각 미니배치의 시작 위치로 ‘옮겨줘야’ 함
[‘옮긴다’라는 뜻]
가정: 길이가 1,000인 시계열 데이터에 대해서, 시각의 길이를 10개 단위로 잘라 Truncated BPTT로 학습하는 경우
그러면 이때 미니배치의 수를 두 개로 구성해 학습하려면 어떻게 해야 할까요?
1) 이 경우 RNN 계층의 입력 데이터로, 첫 번째 미니배치(샘플 데이터) 때는 처음부터 순서대로 데이터를 제공.
2) 그리고 두 번째 미니배치 때는 500번째의 데이터를 시작 위치로 정하고, 그 위치부터 다시 순서대로 데이터를 제공하는 것입니다 (즉, 시작 위치를 500만큼 ‘옮겨’줍니다).
- 첫 번째 미니배치 원소는 \(x_0 , …, x_9\)
- 두 번째 미니배치 원소는 \(x_{500} , …, x_{509}\)
그리고 이 미니배치 데이터를 RNN의 입력 데이터로 사용해 학습을 수행
이후로는 순서대로 진행되므로 다음에 넘길 데이터는 각각 시계열 데이터의 10~19번째 데이터와 510~519번째의 데이터가 되는 식.
이처럼 미니배치 학습을 수행할 때는 각 미니배치의 시작 위치를 오프셋으로 옮겨준 후 순서대로 제공.
또한 데이터를 순서대로 입력하다가 끝에 도달하면 다시 처음부터 입력하도록 함
[‘데이터 제공 방법’ 면에서 주의점]
(1) ‘데이터를 순서대로 제공하기’
(2) ‘미니배치별로 데이터를 제공하는 시작 위치를 옮기기’
RNN 구현
Truncated BPTT 방식의 학습을 따른다면, 가로 크기가 일정한 일련의 신경망을 만들면 됨
1) 우리가 다룰 신경망은 길이가 T인 시계열 데이터를 받음(T는 임의 값).
2) 그리고 각 시각의 은닉 상태를 T개 출력.
3) 그리고 모듈화를 생각해, 옆으로 성장한 위의 신경망을 ‘하나의 계층’ 으로 구현
- 상하 방향의 입력과 출력을 각각 하나로 묶으면 옆으로 늘어선 일련의 계층을 하나의 계층으로 간주 가능.
- 즉, \((x 0 , x 1 , …, x_{T-1})\) 을 묶은 xs를 입력하면 \((h 0 , h 1 , …, h_{T-1})\)을 묶은 hs를 출력하는 단일 계층으로 볼 수 있음.
RNN 계층: 이때 Time RNN 계층 내에서 한 단계의 작업을 수행하는 계층
Time RNN 계층: T개 단계분의 작업을 한꺼번에 처리하는 계층
NOTE: Time RNN 같이 시계열 데이터를 한꺼번에 처리하는 계층에는 앞에 ‘Time’을 붙이겠음.
[앞으로 할 구현의 흐름]
1) RNN의 한 단계를 처리하는 클래스를 RNN이란 이름으로 구현.
2) 이 RNN 클래스를 이용해 T개 단계의 처리를 한꺼번에 수행하는 계층을 TimeRNN이란 이름의 클래스로 완성시킵니다.
1) RNN 계층 구현
RNN 처리를 한 단계만 수행하는 RNN 클래스부터 구현.
복습해보자면, RNN의 순전파 식은,
- 여기에서 우리는 데이터를 미니배치로 모아 처리.
- \(x_t\) (와 \(h_t\) )에는 각 샘플 데이터를 행 방향에 저장.
미니배치 형상확인
- 미니배치 크기가 N
- 입력 벡터의 차원 수가 D
- 은닉 상태 벡터의 차원 수가 H
[RNN 클래스의 초기화 & 순전파 메서드 구현]
👀코드 보기
class RNN:
# RNN의 초기화 메서드
# 가중치 2개와 편향 1개를 인수로 받음.
def __init __(self, Wx, Wh, b):
# 인수로 받은 매개변수를 인스턴스 변수 params에 리스트로 저장.
self.params = [Wx, Wh, b]
# 각 매개변수에 대응하는 형태로 기울기를 초기화한 후 grads에 저장.
self.grads = [np.zeros_like(Wx), np.zeros_like(Wh), np.zeros_like(b)]
# 역전파 계산 시 사용하는 중간 데이터를 담을 cache를 None으로 초기화
self.cache = None
# 순전파 메서드
# 인수 2개(아래로부터의 입력 x와 왼쪽으로부 터의 입력 h_prev ) 받음
# 그다음은 위 의 식을 그대로 코드로 옮겼을 뿐
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
h_prev
: 하나 앞의 RNN 계층으로부터 받는 입력
h_next
: 현 시각 RNN 계층으로부터의 출력 (=다음 시각 계층으로의 입력)
[RNN 역전파 구현]
RNN 계층의 순전파에서 수행하는 계산
- 행렬의 곱 ‘MatMul’
- 덧셈 ‘+’,
- ‘tanh’
참고로 편향 b의 덧셈에서는 브로드캐스트가 일어나기 때문에 정확하게는 Repeat 노드를 이용합니다만,
여기에서는 그림을 간략히 그리고자 일부러 표기하지 않음
👀코드 보기
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) Time RNN 계층 구현
위의 RNN 클래스를 이용해 T개 단계의 처리를 한꺼번에 수행하는 계층을 TimeRNN이란 이름의 클래스로 완성시킵니다.
Time RNN 계층은 T개의 RNN 계층으로 구성
(T는 임의의 수로 설정 가능).
- h
- 여기에서는 RNN 계층의 은닉 상태 h를 인스턴스 변수로 유지.
- 이 변수를 은닉 상태를 ‘인계’받는 용도로 이용함.
RNN 계층의 은닉 상태를 Time RNN 계층에서 관리하기로 하자.
- Time RNN 사용자는 RNN 계층 사이에서 은닉 상태를 ‘인계하는 작업’을 생각 하지 않아도 된다는 장점이 생김.
- 이 기능(은닉 상태를 인계받을지)을 stateful이라는 인수로 조정할 수 있도록 함.
[Time RNN 계층의 초기화 메서드]
👀코드 보기
class TimeRNN:
# 가중치와 편향, 그리고 stateful이라는 불리언 값(True/False )을 인수로 받음
def __init__(self, Wx, Wh, b, stateful=False):
self.params = [Wx, Wh, b]
self.grads = [np.zero _like(Wx), np.zeros_like(Wh), np.zeros_like(b)]
# 인스턴스 변수 layers: 다수의 RNN 계층을 리스트로 저장하는 용도
self.layers = None
# 인스턴스 변수 h: forward ( ) 메서드를 불렀을 때의 마지막 RNN 계층의 은닉 상태를 저장
# dh: backward ( )를 불렀을 때 하나 앞 블록의 은닉 상태의 기울기를 저장
(dh에 관해서는 역전파 구현에서 설명합니다).
self.h, self.dh = None, None
self.stateful = stateful
def set_state(self, h):
self.h = h
def reset_state(self):
self.h = None
매서드 추가 설명
set_state (h )
: TimeRNN 클래스는 확장성을 고려하여 Time RNN 계층의 은닉 상태를 설정하는 메서드reset_state ( )
: 은닉 상태를 초기화하는 메서드
인수 stateful
- ‘상태가 있는’이란 뜻의 단어.
Stateful = True
- Time RNN 계층은 ‘상태가 있다’
- ‘상태가 있다’란, Time RNN 계층이 은닉 상태를 유지한다는 뜻
-
즉, 아무리 긴 시계열 데이터라도 Time RNN 계층의 순전파를 끊지 않고 전파한다는 의미
stateful= False
- Time RNN 계층은 은닉 상태를 ‘영행렬’(모든 요소가 0인 행렬)로 초기화.
- 이것이 상태가 없는 모드이며, ‘무상태’라고 함.
📜 은닉상태
긴 시계열 데이터를 처리할 때는 RNN의 은닉 상태를 유지해야 함.
- 은닉 상태를 유지 하는 기능을 흔히 ‘stateful’이라는 단어로 표현.
많은 딥러닝 프레임워크에서 RNN 계층을 살펴보면 인수로 stateful이라는 것을 받으며, 이를 통해 이전 시각의 은닉 상태를 유지할지 지정가능
[순전파 구현]
👀코드 보기
# 아래로부터 입력 xs를 받음
# xs: T개 분량의 시계열 데이터를 하나로 모은 것
# 미니배치 크기를 N, 입력 벡터의 차원 수를 D라고 하면, xs의 형상은 (N, T, D )
def forward(self, xs):
Wx, Wh, b = self.params
N, T, D = xs.shape
D, H = Wx.shape
self.layers = []
# 출력값을 담을 그릇 (hs )을 준비
hs = np.empty((N, T, H), dtype='f')
# RNN 계층의 은닉 상태 h는 처음 호출 시(self.h가 None일 때)에는 원소가 모두 0인 영행렬로 초기화
# 인스턴스 변수 stateful이 False일 때도 영행렬로 초기화
if not self.stateful or self.h is None:
self.h = np.zeros((N, H), dtype='f')
# 총 T회 반복되는 for 문 안에서 RNN 계층을 생성*하여 인스턴스변수 layers에 추가
# 그 사이에 RNN 계층이 각 시각 t의 은닉 상태 h를 계산하고,
# 이를 hs에 해당 인덱스(시각)의 값으로 설정합니다.
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
*self.params
: *는 리스트의 원소들을 추출하여 메서드의 인수로 전달합니다.
* 즉, self.params에 들어 있는 Wx, Wh, b를추출하여 RNN 클래스의 init( ) 메서드에 전달.
Time RNN 계층의 forward ( ) 메서드가 불리면, 인스턴스 변수 h에는 마지막 RNN 계층의 은닉 상태가 저장됨.
- 그래서 다음번 forward ( ) 메서드 호출 시 stateful이 True면 먼저 저장된 h 값이 그대로 이용됨
- stateful이 False면 h가 다시 영행렬로 초기화됨
[Time RNN 계층의 역전파 구현]
이 역전파의 계산 그래프
- dhs: 상류(출력 쪽 층)에서부터 전해지는 기울기
- dxs: 하류로 내보내는 기울기
여기에서 우리는 Truncated BPTT를 수행하기 때문에 이 블록의 이전 시각 역전파는 필요하지 않음
단, dh에 이전 시각의 은닉 상태 기울기 저장.
seq2seq (시퀀스 투 시퀀스)에 필요 하기 때문
이때 t번째의 RNN 계층 역전파
t번째 RNN 계층에 전해지는 것
- dh_t: 위로부터의 기울기
- dh_next: ‘한 시각 뒤(미래) 계층’으로부터의 기울기
주의점
RNN 계층의 순전파에서는 출력이 2개로 분기됨
- 순전파 시 분기했을 경우, 그 역전파에서는 각 기울기가 합산되어 전해짐.
- 따라서 역전파 시 RNN 계층에는 합산된 기울기(dh_t + dh next )가 입력됨
👀코드 보기
# 순전파때와는 반대 순서로 RNN 계층의 backward ( ) 메서드를 호출하여,
# 각 시각의 기울기 dx를 구해 dxs의 해당 인덱스(시각)에 저장
def backward(self, dhs):
Wx, Wh, b = self.params
N, T, H = dhs.shape
D, H = Wx.shape
# dxs: 가장 먼저 하류로 흘려보낼 기울기를 담을 그릇
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
# 가중치 매개변수에 대해서도 각 RNN 계층의 가중치 기울기를 합산하여 최종 결과를 멤버 변수 self.grads에 덮어씀
# 이때 ... 문법을 사용
for i, grad in enumerate(grads):
self.grads[i][...] = grad
self.dh = dh
return dxs
WARNING
Time RNN 계층 안에는 RNN 계층이 여러 개 있음.
그리고 그 RNN 계층들에서 똑같은 가중치를 사용하고 있음.
- Time RNN 계층의 (최종) 가중치의 기울기는 각 RNN 계층의 가중치 기울기를 모두 더한 게 됨.
시계열 데이터 처리 계층 구현 RNNLM
이번 장의 목표는 RNN을 사용하여 ‘언어 모델’을 구현하는 것
- 지금까지: RNN 계층(과 시계열 데이터를 한꺼번에 처리하는 Time RNN 계층)을 구현
- 이번 절: 시계열 데이터를 처리하는 계층을 몇 개 더 만들기
또한, RNN을 사용한 언어 모델은 영어로 RNN Language Model이므로 앞으로 RNNLM이라 칭함
그럼, RNNLM 완성을 목표로 달려보자
RNNLM의 전체 그림
- 왼쪽: RNNLM의 계층 구성
- 오른쪽: 이를 시간축으로 펼친 신경망
[샘플 말뭉치로 “you say goodbye and I say hello.”를 처리하는 예]
- 입력 데이터: 단어 ID의 배열
(첫 번째 시각)
- 첫 단어로 단어 ID가 0인 “you”가 입력.
- 이때 Softmax 계층이 출력하는 확률분포는 “say”가 가장 높음.
- 다시 말해 “you” 다음에 출현하는 단어가 “say”라는 것을 올바르게 예측.
- 이처럼 제대로 예측하려면 좋은 가중치(잘 학습된 가중치)를 사용 필요
(두 번째 시각)
- 두번째 단어로 “say”가 입력
- Softmax 계층 출력은 “goodbye”와 “hello”이 높음
- “you say goodbye”와 “you say hello”는 모두 자연스러움 (참고로 여기에서의 정답은 “goodbye”).
여기서 주목할 것
- RNN 계층은 “you say”라는 맥락을 ‘기억’하고 있음
- RNN은 “you say”라는 과거의 정보를 응집된 은닉 상태 벡터로 저장해두고 있음.
- 그러한 정보를 더 위의 Affine 계층에, 그리고 다음 시각의 RNN 계층에 전달하는 것이 RNN 계층이 하는 일입니다.
RNNLM: 지금까지 입력된 단어를 ‘기억’ 하고, 그것을 바탕으로 다음에 출현할 단어를 예측.
- 이 일을 가능하게 하는 비결이 바로 RNN 계층.
- RNN 계층이 과거 에서 현재로 데이터를 계속 흘려보내줌으로써 과거의 정보를 인코딩해 저장(기억)할 수 있는 것.
[Time 계층 구현]
- (전 챕터)Time RNN: 시계열 데이터를 한꺼번에 처리하는 계층
- (이번 챕터)Time Embedding, Time Affine: 시계열 데이터를 한꺼번에 처리하는 계층 구현.
Time XX 계층으로 구현
📜Time XX 계층
T개분의 시계열 데이터를 한꺼번에 처리하는 계층을 ‘Time XX 계층’이라 부르겠음.
이러한 계층들이 구현돼 있다면, 그 계층들을 레고 블록처럼 조립하는 것만으로 시계열 데이터를 다루는 신경망을 완성할 수 있습니다.
[Time Affine 계층]
Affin 함수란?
- Affine 계층을 T개 준비해서, 각 시각의 데이터를 개별적으로 처리
- 순전파 시에 T개의 Embedding 계층을 준비하고 각 Embedding 계층이 각 시각의 데이터를 처리
시계열 버전의 Softmax
Softmax 함수란?
- Softmax 계층을 구현할 때는 손실 오차를 구하는 Cross Entropy Error 계층도 함께 구현.
- 여기에서는 Time Softmax with Loss 계층으로 구현.
데이터 설명
- \(X_0\) 이나 \(x_1\) 등의 데이터: 아래층에서부터 전해지는 ‘점수’(점수란 확률로 정규화되기 전의 값)
- \(t_0\) 나 \(t_1\) 등의 데이터: 정답 레이블
구현 설명
1) T개의 Softmax with Loss 계층 각각이 손실을 산출.
2) 그리고 그 손실들을 합산해 평균한 값이 최종 손실
이때 수행하는 계산의 수식
그런데 이 책의 Softmax with Loss 계층은 미니배치에 해당하는 손실의 평균을 구함
- 데이터 N개짜리 미니배치라면, 그 N개의 손실을 더해 다시 N으로 나눠 데이터 1개당 평균 손실을 구한 것.
이와 마찬가지로 Time Softmax with Loss 계층도 시계열에 대한 평균을 구하는 것으로, 데이터 1개당 평균 손실을 구해 최종 출력으로 내보냄.
RNNLM 학습과 평가
RNNLM 구현
SimpleRnnlm: RNNLM에서 사용하는 신경망을 클래스로 구현한 것
[SimpleRnnlm의 계층 구성]
RNN 계층의 상태는 클래스 내부에서 관리
SimpleRnnlm 클래스는 4개의 Time 계층을 쌓은 신경망.
[초기화 코드] 이 초기화 메서드는 각 계층에서 사용하는 매개변수(가중치와 편향)를 초기화하고 필요한 계층을 생성
👀코드 보기
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
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
Truncated BPTT로 학습한다고 가정하여 Time RNN 계층의 stateful 을 True로 설정
- 그 결과 Time RNN 계층은 이전 시각의 은닉 상태를 계승할 수 있음
이 초기화 코드는 RNN 계층과 Affine 계층에서 ‘Xavier 초깃값 사비에르 초깃값 ’을 이용
- Xavier 초깃값: 이전 계층의 노드가 n개라면 표준편차가 1/√n 인 분포를 초깃값으로 사용
참고로 표준편차는 데이터의 차이를 직관적으로 나타내는 척도로 해석 가능
[WARNING]
딥러닝에서는 가중치의 초깃값이 중요.
마찬가지로 RNN에서도 가중치의 초깃값은 매우 중요
초깃값을 어떻게 설정하느냐에 따라 학습이 진행되는 방법과 최종 정확도가 크게 달라짐
여기선 이후로도 가중치의 초깃값으로는 ‘Xavier 초깃값’을 사용
또한, 언어 모델을 다루는 연구에 서는 0.01 * np.random.uniform (…)처럼 스케일을 변환한 균일분포를 이용하는 사례를 많이 볼 수 있음
[forward ( ), backward ( ), reset_state ( ) 메서드의 구현]
👀코드 보기
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 ( )
: 신경망의 상태를 초기화하는 편의 메서드
언어 모델의 평가 perplexity
SimpleRnnlm 구현이 끝남
신경망에 데이터를 주고 학습을 수행 필요.
[언어 모델의 ‘평가 방법’]
퍼플렉서티 perplexity, 혼란도
- 언어 모델은 주어진 과거 단어(정보)로부터 다음에 출현할 단어의 확률분포를 출력
- 이때 언어 모델의 예측 성능을 평가하는 척도
- 간단히 말하면,‘확률의 역수’입니다(이 해석은 데이터 수가 하나일 때에 정확히 일치).
[입력 데이터가 하나일 때의 퍼플렉서티]
EX) “you say goodbye and I say hello .”
‘모델 1’의 언어 모델에 “you” 제공
- 정답(다음에 출현할 단어)이 “say”라면, 그 확률은 0.8
- 제법 괜찮은 예측
- 이때의 퍼플렉서티는 이 확률의 역수, 즉, 1/0.8 = 1.25로 계산할 수 있습니다.
- 정답인 “say”의 확률이 0.2라고 예측
- 퍼플렉서티는 1/0.2= 5
➡ 퍼플렉서티는 작을수록 좋다
1.25나 5.0이라는 값의 직관적 해석
- 이 값은 ‘분기수 number of branches ’로 해석 가능
📜 분기 수
다음에 취할 수 있는 선택사항의 수
(구체적 으로 말하면, 다음에 출현할 수 있는 단어의 후보 수)
좋은 모델이 예측한 ‘분기 수’가 1.25 = 다음에 출현할 수 있는 단어의 후보를 1개 정도로 좁혔다는 뜻.
반면 나쁜 모델에서는 후보가 아직 5개나 된다는 의미
좋은 모델
- 정답 단어를 높은 확률로 예측 가능.
- 따라서 퍼플렉서티 값이 작음(최솟값은 1.0 )
나쁜 모델
- 정답 단어를 낮은 확률로 예측
- 퍼플렉서티 값이 큼.
[입력 데이터가 여러개일 때의 퍼플렉서티]
이럴 때는 다음 공식에 따라 계산
- \(N\): 데이터의 총 개수
- \(t_n\): 원핫 벡터로 나타낸 정답 레이블
- \(t_nk\): n개째 데이터의 k번째 값을 의미
- \(y_nk\): 는 확률분포(신경망에서는 Softmax의 출력)
-
\(L\): 신경망의 손실
- 사실 교차 엔트로피 오차와 완전히 같은 식.
- 이 L을 사용해 \(e^L\) 을 계산한 값이 곧 퍼플렉서티입니다.
RNNLM의 학습 코드
이번에 구현할 RNNLM은 PTB데이터셋(훈련 데이터) 전부를 대상으로 학습하면 전혀 좋은 결과를 낼 수 없음
- 처음 1,000개 단어만 이용할 것임 이 문제는 다음 장에서 개선합니다. 자, 다음은 학습을 수행 하는 코드
👀코드 보기
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]
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)
# ❶ 각 미니배치에서 샘플을 읽기 시작 위치를 계산
jump = (corpus_size – 1) // batch_size
offsets = [I * jump for I in range(batch_size)]
각 미니배치가 데이터를 읽기 시작하는 위치를 계산해 offsets에 저장.
다시 말해 이 offsets의 각 원소에 데이터를 읽는 시작 위치(오프 셋)가 담기게 됨
for epoch in range(max _epoch):
for iter in range(max _iters):
# ❷ 미니배치 획득
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
이어서 소스 코드의 ❷에서는 데이터를 순차적으로 읽음.
먼저 ‘그릇’인 batch _x와 batch _t를 준비
그런 다음 변수 time _idx를 1씩(순차적으로) 늘리면서, 말뭉치에서 time _idx 위치의 데이터를 얻음
여기서 ❶에서 계산한 offsets를 이용하여 각 미니배치에서 오프셋(각 원소에 데이터를 읽는 시작 위치)을 추가.
또한, 말뭉치를 읽는 위치가 말뭉치 크기를 넘어설 경우 말뭉치의 처음으로 돌아와야 하는데, 이를 위해 말뭉치의 크기로 나눈 나머지를 인덱스로 사용
# 기울기를 구하여 매개변수 갱신
loss = model.forward(batch_x, batch_t)
model.backward()
optimizer.update(model.params, model.grads)
total_loss += loss
loss_count += 1
# ❸ 에폭마다 퍼플렉서티 평가
ppl = np.exp(total_loss / loss_count)
print(‘| 에폭 %d | 퍼플렉서티 %.2f’ % (epoch+1, ppl))
ppl_list.append(float(ppl))
total_loss, loss_count = 0, 0
마지막으로 위 식을 따라 퍼플렉서티를 계산
여기에서는 에폭마다의 퍼플렉서티를 구하기 위해 에폭마다 손실의 평균을 구하고, 그 값을 사용해 퍼플렉서티를 구함
[코드해석]
전반적으론 신경망 학습과 같음
(차이점)
- ‘데이터 제공 방법’
- 여기서 우리는 Truncated BPTT 방식으로 학습을 수행
- 따라서 데이터는 순차적으로 주고 각각의 미니배치에서 데이터를 읽는 시작 위치를 조정 필요
- ‘퍼플렉서티 계산’ 부분
[학습 결과]
앞의 코드에서 에폭별 퍼플 렉서티 결과를 perplexity_list
에 저장했으므로, 이 리스트를 플롯하면
- 학습을 진행할수록 퍼플렉서티가 순조롭게 낮아짐.
-
처음에는 300을 넘던 값이 마지막에는 (최솟값인) 1에 근접
- 다만, 이번에는 크기가 작은 말뭉치로 실험한 것
- 실제로 현재의 모델로는 큰 말뭉치에는 전혀 대응 불가
RNNLM의 Trainer 클래스
여기서는 RNNLM 학습을 수행해주는 RnnlmTrainer 클래스를 제공
이 클래스는 방금 수행한 RNNLM 학습을 클래스 안으로 숨겨줌.
그래서 앞 절의 학습 코드를 RnnlmTrainer 클래스를 사용해 다시 쓰면
👀코드 보기
# coding: utf-8
import sys
sys.path.append('..')
from common.optimizer import SGD
from common.trainer import RnnlmTrainer
from dataset import ptb
from simple_rnnlm import SimpleRnnlm
batch_size = 10
wordvec_size = 100
hidden_size = 100 # RNNの隠れ状態ベクトルの要素数
time_size = 5 # RNNを展開するサイズ
lr = 0.1
max_epoch = 100
corpus, word_to_id, id_to_word = ptb.load_data('train')
corpus_size = 1000
corpus = corpus[:corpus_size]
vocab_size = int(max(corpus) + 1)
xs = corpus[:-1]
ts = corpus[1:]
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)
trainer.plot()
[코드 설명] 1) 먼저 RnnlmTrainer 클래스에 model과 optimizer를 줘서 초기화.
2) 그런 다음 fit ( ) 메서드를 호출해 학습을 수행.
이때 그 내부에서는 앞 절에서 수행한 일련의 작업이 진행
(작업)
- 미니배치를 ‘순차적’으로 만들어
- 모델의 순전파와 역전파를 호출하고
- 옵티마이저로 가중치를 갱신하고
- 퍼플렉서티를 구한다
신경망의 일반적인 학습은 Trainer 클래스를 사용하고, RNNLM 학습에는 RnnlmTrainer 클래스를 사용하면 된다..
RnnlmTrainer 클래스를 사용하면 똑같은 코드를 매번 작성하지 않아도 된다
정리
이번 장의 주제는 순환 신경망(RNN )이었습니다. RNN은 데이터를 순환시킴으로써 과거에서 현재, 그리고 미래로 데이터를 계속해서 흘려보냄.
이를 위해 RNN 계층 내부에는 ‘은닉 상태’를 기억하는 능력이 추가됨.
그러나 실제 문제에서는 잘 학습하지 못하는 경우가 많음.
다음 장에서는 RNN의 문제점을 살펴보고 RNN을 대체하는 새로운 계층들(LSTM 계층과 GRU 계층)을 살펴볼 것임.
곧 만나볼 이 새로운 계층들은 실제로 최첨단 연구에서도 많이 이용되는, 시계열 데이터 처리에 있어서 가장 중요한 계층에 속함
● RNN은 순환하는 경로가 있고, 이를 통해 내부에 ‘은닉 상태’를 기억할 수 있다.
● RNN의 순환 경로를 펼침으로써 다수의 RNN 계층이 연결된 신경망으로 해석할 수 있으며, 보통의 오차 역전파법으로 학습할 수 있다( =BPTT ).
● 긴 시계열 데이터를 학습할 때는 데이터를 적당한 길이씩 모으고(이를 ‘블록’이라 한다), 블록 단위로 BPTT에 의한 학습을 수행한다( =Truncated BPTT ).
● Truncated BPTT에서는 역전파의 연결만 끊는다.
● Truncated BPTT에서는 순전파의 연결을 유지하기 위해 데이터를 ‘순차적’으로 입력해야 한다.
● 언어 모델은 단어 시퀀스를 확률로 해석한다.
● RNN 계층을 이용한 조건부 언어 모델은 (이론적으로는) 그때까지 등장한 모든 단어의 정보를 기억할 수있다.