미래를 예측하는 것은 우리가 늘 하는 일입니다.
이번 포스트에서는 미래를 어느 정도 예측할 수 있는 네트워크인 순환 신경망을 알아보겠습니다.
이 신경망은 시계열 데이터를 분석해서 주식가격 같은 것을 예측해 언제 사고팔지 알려줄 수 있습니다.
일반적으로 이 신경망은 지금까지 봤던 모든 네트워크처럼 고정 길이 입력이 아닌 임의 길이를 가진 시퀀스를 다룰 수 있습니다. 예를 들어, 문장, 문서, 오디오 샘플을 입력으로 받을 수 있고, 자동 번역, 스피치 투 텍스트같은 자연어 처리(NLP)에 매우 유용합니다.
순환 뉴런과 순환 층
지금까지는 활성화 신호가 입력층에서 출력층으로 한 방향으로만 흐르는 피드포워드 신경망에 초점을 맞추었습니다.
순환 신경망은 피드포워드 신경망과 매우 비슷하지만 뒤쪽으로 순환하는 연결도 있다는 점이 다릅니다.
아래에서 왼쪽 그림을 보면 입력을 받아서 출력을 만들고 자신에게도 출력을 보내는 뉴런 하나로 구성된 가장 간단한 RNN을 살펴보겠습니다. 각 타임 스텝 t마다 이 순환 뉴런은 물론 \(x_{(t)}\)와 이전 타임 스텝의 출력인 \(y_{(t - 1)}\)을 입력으로 받습니다. 첫 번째 타임 스텝에서는 이전 출력이 없으므로 일반적으로 0으로 설정합니다. 이 작은 네트워크를 오른쪽 그림처럼 시간을 축으로 하여 표현했을 때, 이를 시간에 따라 네트워크를 펼쳤다 라고 합니다.
순환 뉴런으로 된 층은 쉽게 만들 수 있습니다. 아래 그림처럼 모든 뉴런은 입력 벡터 \(x_{(t)}\)와 이전 타임 스텝의 출력 벡터 \(y_{(t - 1)}\)을 받습니다.
각 순환 뉴런은 두 벌의 가중치를 가집니다. 하나는 입력 \(x_{(t)}\)를 위한 것이고 다른 하나는 이전 타임 스텝의 출력 \(y_{(t - 1)}\)을 위한 것입니다. 이 가중치 벡터를 \(w_{x}\)와 \(w_{y}\)라고 하겠습니다. 하나의 순환 뉴런이 아니라 순환 층 전체를 생각하면 가중치 벡터를 가중치 행렬 \(W_{x}\)와 \(W_{y}\)로 바꿀 수 있습니다. 이렇게 하면 순환 층 전체의 출력 벡터는 다음과 같은 식으로 계산됩니다. (b는 편향이고 \(\phi\)는 ReLU와 같은 활성화 함수입니다.
\[ \displaystyle y_{(t)} = \phi(W_{x}^{T}x_{(t)} + W_{y}^{T}y_{(t - 1)} + b) \]
타임 스텝 t에서의 모든 입력을 행렬 \(X_{(t)}\)로 만들어 미니배치 전체에 대해 순환 층의 출력을 아래와 같은 식으로 한 번에 계산할 수 있습니다.
\[ \displaystyle Y_{(t)} = \phi(X_{(t)}W_{x} + Y_{(t - 1)}W_{y} + b) \]
\[ = \phi(\begin{bmatrix} X_{(t)} & Y_{(t - 1)} \end{bmatrix}W + b) \hbox{, 여기에서 }W = \begin{bmatrix} W_{x} \newline W_{y} \end{bmatrix} \]
\(Y_{(t)}\)는 \(X_{(t)}\)와 \(Y_{(t - 1)}\)의 함수고 \(Y_{(t - 1)}\)은 \(X_{(t - 1)}\)과 \(Y_{(t - 2)}\)의 함수가 됩니다.
결국 \(Y_{(t)}\)는 시간 \(t = 0\)부터 모든 입력에 대한 함수가 됩니다.
메모리 셀
타임 스텝 t에서 순환 뉴런의 출력은 이전 타임 스텝의 모든 입력에 대한 함수이므로 이를 일종의 메모리 형태라고 할 수 있습니다. 타임 스텝에 걸쳐 어떤 상태를 보존하는 신경망의 구성 요소를 메모리 셀 이라고 합니다. 일반적으로 타임 스텝 t에서의 셀의 상태 \(h_{(t)}\)는 그 타임 스텝의 입력과 이전 타임 스텝의 상태에 대한 함수입니다. 타임 스텝 t에서의 출력 \(y_{(t)}\)도 이전 상태와 현재 입력에 대한 함수입니다.
입력과 출력 시퀀스
RNN은 입력 시퀀스를 받아 출력 시퀀스를 만들 수 있습니다(아래 그림의 왼쪽 위 네트워크). 이는 시퀀스-투 시퀀스 네트워크로, 예를 들어 주식 가격 같은 시계열 데이터를 예측하는 데 유용합니다. 최근 N일치의 주식가격을 주입하면 네트워크는 각 입력값보다 하루 앞선 가격을 출력해야 합니다(즉, N - 1일 전부터 내일까지).
또는 입력 시퀀스를 네트워크에 주입하고 마지막을 제외한 모든 출력을 무시할 수 있습니다(아래 그림의 오른쪽 위 네트워크). 이는 시퀀스-투-벡터 네트워크로, 예를 들어 영화 리뷰에 있는 연속된 단어를 주입하면 네트워크는 감성 점수를 출력합니다.
반대로 각 타임 스텝에서 하나의 입력 벡터를 반복해서 네트워크에 주입하고, 하나의 시퀀스를 출력할 수 있습니다(아래 그림의 왼쪽 아래 네트워크). 이는 벡터-투-시퀀스 네트워크로, 예를 들어 이미지를 입력하여 이미지에 대한 캡션을 출력할 수 있습니다.
마지막이라 인코더라 부르는 시퀀스-투-벡터 네트워크 뒤에 디코더라 부르는 벡터-투-시퀀스 네트워크를 연결할 수 있습니다(아래 그림의 오른쪽 아래 네트워크). 예를 들어 한 언어의 문장을 다른 언어로 번역하는 데 사용할 수 있습니다. 한 언어의 문장을 네트워크에 주입하면 인코더는 이 문장을 하나의 벡터 표현으로 변환하고, 그 다음에 디코더가 이 벡터를 다른 언어의 문장으로 디코딩합니다. 인코더-디코더라 부르는 이런 이중 단계 모델은 하나의 시퀀스-투-시퀀스 RNN을 사용하여 한 단어씩 번역하는 것보다 훨씬 더 잘 작동합니다. 문장의 마지막 단어가 번역의 첫 번째 단어에 영향을 줄 수 있기 때문입니다. 그래서 번역하기 전에 전체 문장이 주입될 때까지 기다릴 필요가 있습니다.
RNN 훈련하기
RNN을 훈련하기 위한 기법은 타임 스텝으로 네트워크를 펼치고 보통의 역전파를 사용하는 것입니다. 이런 전략을 BPTT(backpropagation through time)이라고 합니다.
보통의 역전파처럼 첫 번째 정방향 패스가 펼쳐진 네트워크를 통과합니다. 그러면 비용 함수 \( C(Y_{(0)}, Y_{(1)}, \dots, Y_{(T)}) \hbox{ (여기서 T는 최대 타임 스텝)} \)를 사용하여 출력 시퀀스가 평가됩니다. 이 비용 함수는 아래 그림처럼 일부 출력을 무시할 수 있습니다. 그리고 이 비용 함수의 그레이디언트는 펼쳐진 네트워크를 따라 역방향으로 전파됩니다. 결국 모델 파라미터는 BPTT동안 계산된 그레이디언트를 사용하여 업데이트됩니다. 그레이디언트가 마지막 출력뿐만 아니라 비용 함수를 사용한 모든 출력에서 역방향으로 전파됩니다(아래 그림에서는 그레이디언트가 네트워크의 마지막 세 개의 출력을 사용하여 계산됩니다.). 따라서 그레이디언트는 이 세 개의 출력을 거쳐 흐르지만 \(Y_{(0)}, Y_{(1)}\)은 거치지 않습니다. 또한 각 타임 스텝마다 같은 매개변수 W와 b가 사용되기 때문에 역전파가 진행되면 모든 타임 스텝에 걸쳐 합산될 것입니다.
시계열 예측하기
웹사이트에서 시간당 접속 사용자의 수, 도시의 날짜별 온도, 여러 지표를 사용한 기업의 분기별 재정 안정성 등을 연구한다고 하겠습니다. 이런 경우 모두 데이터는 타임 스텝마다 하나 이상의 값을 가진 시퀀스입니다. 이를 시계열(time series)라고 부릅니다. 처음 두 예는 타임 스텝마다 하나의 값을 가지므로 단변량 시계열이고, 재정 안정성 예시는 타임 스텝마다 여러 값을 가지므로 다변량 시계열입니다. 미래의 값을 예측하는 것은 전형적 작업이고, 이를 예측이라고 합니다. 그리고 비어 있는 값을 채우는 것도 또다른 전형적인 작업이고, 값 대체라고 합니다.
간단한 예시를 보며 살펴보겠습니다.
데이터셋 생성
간단하게 다음 코드와 같은 방법으로 생성한 시계열을 사용하겠습니다.
기준 성능
RNN 시작 전 기준 성능을 몇 개 준비하는 게 좋습니다.
그렇지 않으면 실제 기본 모델보다 성능이 나쁠 때도 잘 작동한다고 착각할 수 있습니다.
간단한 RNN 구현하기
간단한 RNN을 사용해 기준 성능을 앞지를 수 있는지 확인하겠습니다.
심층 RNN
RNN은 아래 그림처럼 셀을 여러 층으로 쌓는 게 일반적입니다. 이렇게 만든 게 심층 RNN 입니다.
여러 타임 스텝 앞을 예측하기
지금까지는 다음 타임 스텝만 예측했지만 여러 타임 스텝 앞의 값도 예측할 수 있습니다.
긴 시퀀스 다루기
긴 시퀀스로 RNN을 훈련하려면 많은 타임 스텝에 걸쳐 실행해야 하므로 펼친 RNN이 매우 깊은 네트워크가 됩니다.
보통 심층 신경망처럼 그레이디언트 소실과 폭주 문제가 있을 수 있고, 훈련에 아주 오랜 시간이 걸리거나 훈련이 불안정할 수 있습니다. 또한 RNN이 긴 시퀀스를 처리할 때 입력의 첫 부분을 조금씩 잊어버릴 것입니다. 해결해야 할 문제들을 살펴보겠습니다.
불안정한 그레이디언트 문제와 싸우기
불안정한 그레이디언트 문제를 완화하기 위해 심층 신경망에서 사용했던 많은 기법을 RNN에서도 사용할 수 있습니다. 좋은 가중치 초기화, 빠른 옵티마이저, 드롭아웃 등입니다. 그러나 수렴하지 않는 활성화 함수(예를 들어 ReLU)는 큰 도움이 되지 않습니다(이런 함수는 훈련 동안 RNN을 더 불안정하게 만들 수 있습니다.). 왜냐하면 경사 하강법이 어떤 타임 스텝에서 출력을 조금 증가시키는 방향으로 가중치를 업데이트한다고 가정한다면, 동일한 가중치가 모든 타임 스텝에서 사용되기 때문에 그 다음 타임 스텝의 출력도 증가할 것이고, 또 그 다음 출력도 마찬가지로 증가하는 식으로 출력이 폭주합니다. 수렴하지 않는 활성화 함수는 이를 막지 못합니다. 학습률을 낮춰 이런 위험을 감소시킬 수는 있지만 간단히 하이퍼볼릭 탄젠트 같은 수렴하는 활성화 함수를 사용할 수 있습니다. 같은 방식으로 그레이디언트 자체도 폭주할 수 있습니다. 이런 문제가 있다고 생각되면 그레이디언트 크기를 모니터링하고 그레이디언트 클리핑을 사용하는 게 좋습니다.
배치 정규화는 심층 피드포워드 네트워크처럼 RNN과 효율적으로 사용할 수 없습니다. 사실 타임 스텝 사이에 사용할 수 없고 순환 층 사이에만 가능합니다. 정확히 말하자면 메모리 셀에 배치 정규화 층을 추가할 수는 있지만 좋은 결과를 만들지는 못합니다. 정리하면 순환 층 안이 아니라 순환 층 사이에 적용했을 때 없는 것보다 조금 나은 정도입니다. 케라스에서는 간단히 각 순환 층 이전에 BatchNormalization 층을 추가합니다.
RNN에서 잘 맞는 다른 종류의 정규화는 층 정규화입니다. 배치 정규화와 매우 비슷하지만 배치 차원에 대해 정규화하는 대신 특성 차원에 대해 정규화합니다. 이 방식의 한 가지 장점은 샘플에 독립적으로 타임 스텝마다 동적으로 필요한 통계를 계산할 수 있다는 것입니다. 배치 정규화와 마찬가지로 층 정규화는 입력마다 하나의 스케일과 이동 파라미터를 학습합니다. RNN에서 층 정규화는 일반적으로 입력과 은닉 상태의 선형 조합 직후에 사용됩니다.
단기 기억 문제 해결하기
RNN을 거치면서 데이터가 변환되므로 일부 정보는 매 훈련 스텝 후 사라집니다.
어느 정도 시간이 지나면 RNN의 상태는 사실상 첫 번째 입력의 흔적을 가지고 있지 않습니다.
이런 문제를 해결하기 위해 장기 메모리를 가진 여러 종류의 셀이 연구됐습니다.
요즘 이런 셀들의 성능이 매우 좋아 기본 셀이 많이 사용되지 않습니다.
LSTM 셀
장기 메모리를 가진 셀에서 가장 인기 있는 LSTM 셀을 먼저 보겠습니다.
장단기 메모리(long short-term memory, LSTM) 셀을 블랙박스처럼 생각하면 기본 셀과 매우 비슷하게 사용할 수 있지만 성능은 훨씬 좋을 것입니다. 즉, 훈련이 빠르게 수렴하고 데이터에 있는 장기간의 의존성을 감지할 것입니다. 케라스에서는 간단하게 SimpleRNN 대신 LSTM 층을 사용하면 됩니다. 또는 범용 목적의 keras.layers.RNN 층에 LSTMCell을 매개변수로 지정할 수 있습니다(하지만 LSTM 층이 GPU에서 실행할 때 최적화된 구현을 사용하므로 일반적으로 선호됩니다. RNN 층은 사용자 정의 셀을 정의할 때 많이 사용됩니다.).
이런 LSTM 셀은 중요한 입력을 인식하고, 장기 상태에 저장하고, 필요한 기간 동안 이를 보존하고, 필요할 때마다 이를 추출하기 위해 학습합니다. 그러므로 LSTM 셀은 시계열, 긴 텍스트, 오디오 녹음 등에서 장기 패턴을 잡아내는 데 놀라운 성과를 냅니다.
핍홀 연결
일반 LSTM 셀에서 게이트 제어기는 입력과 이전 단기 상태만 볼 수 있습니다. 그런데 게이트 제어기에 장기 상태도 조금 노출시켜 좀 더 많은 문맥을 감지하게 만들면 좋을 수 있습니다. 이 아이디어를 2000년에 펠릭스 제르와 위르겐 슈미트후버가 제안했는데, 이들이 제안한 것은 핍홀 연결이라 부르는 추가적인 연결이 있는 LSTM 변종입니다. 이는 성능을 향상하는 경우가 많지만 늘 그렇지는 않습니다. 케라스에서는 LSTM 층이 핍홀을 지원하지 않는 keras.layers.LSTMCell 셀을 기반으로 합니다. 하지만 실험적인 tf.keras.experimental.PeepholeLSTMCell 층이 있습니다. 따라서 keras.layers.RNN 층의 생성자에 PeepholeLSTMCell을 전달하여 만들 수 있습니다.
GRU 셀
게이트 순환 유닛(gated recurrent unit, GRU) 셀은 LSTM 셀의 간소화된 버전이고 유사하게 작동하는 것처럼 보입니다(그래서 인기가 많습니다.). 케라스는 keras.layers.GRUCell 셀을 기반으로 한 keras.layers.GRU 층을 제공합니다. 이 층을 사용하려면 SimpleRNN이나 LSTM을 GRU로 바꾸면 됩니다.
LSTM과 GRU 셀은 RNN의 성공의 주역 중 하나입니다. 이 셀들이 단순한 RNN보다 훨씬 긴 시퀀스를 다룰 수 있지만 매우 제한적인 단기 기억을 가집니다. 100 타임 스텝 이상의 시퀀스에서 장기 패턴을 학습하는 데 어려움이 있습니다. 이 문제를 해결하는 한 가지 방법은 1D 합성곱 층을 사용해 입력 시퀀스를 짧게 줄이는 것입니다.
1D 합성곱 층은 몇 개의 커널을 시퀀스 위를 슬라이딩하여 커널마다 1D 특성 맵을 출력합니다. 각 커널은 매우 짧은 하나의 순차 패턴을 감지하도록 학습됩니다. 10개의 커널을 사용하면 이 층의 출력은 10개의 1차원 시퀀스로 구성됩니다. 또는 이 출력을 10차원 시퀀스 하나로 볼 수 있습니다. 이는 순환 층과 1D 합성곱 층(1D 풀링 층도)을 섞어서 신경망을 구성할 수 있다는 것입니다. 스트라이드 1과 'same' 패딩으로 1D 합성곱 층을 사용하면 출력 시퀀스는 입력 시퀀스와 길이가 같고, 'valid' 패딩을 사용하고 1보다 큰 스트라이드를 사용하면 출력 시퀀스는 입력 시퀀스보다 짧아집니다. 따라서 적절한 타깃이 만들어지는지 확인해야합니다. 합성곱 층으로 시퀀스 길이를 줄이면 GRU 층이 더 긴 패턴을 감지하는 데 도움이 됩니다.
WAVENET
WaveNet이라는 네트워크는 층마다 팽창 비율을 두배로 늘리는 1D 합성곱 층을 쌓습니다. 첫 번째 합성곱 층이 한 번에 2개의 타임 스텝만 바라봅니다. 다음 층은 4개의 타임 스텝을 보고 다음은 8개의 타임 스텝을 보는 식입니다. 이런 식으로 하위 층은 단기 패넡을 학습하고 상위 층은 장기 패턴을 학습합니다. 팽창 비율을 두 배로 늘린 덕분에 네트워크는 아주 긴 시퀀스를 매우 효율적으로 처리할 수 있습니다.
'DATA > 머신 러닝' 카테고리의 다른 글
[머신 러닝] 합성곱 신경망을 사용한 컴퓨터 비전 (0) | 2022.02.13 |
---|---|
[머신 러닝] 텐서플로를 사용한 사용자 정의 모델과 훈련 (0) | 2022.02.04 |
[머신 러닝] 머신러닝을 위한 텐서플로 (0) | 2022.02.02 |
[머신 러닝] 심층 신경망 훈련하기 (0) | 2022.02.01 |
[머신 러닝] 신경망 하이퍼파라미터 튜닝하기 (0) | 2022.01.23 |