15장
RNN과 CNN을 사용해 시퀀스 처리하기
과거를 학습해 미래를 예측하는 네트워크인 순환 신경망recurrent neural network 를 알아본다. 시계열time seies 데이터를 분석해 주식가격을 분석하거나, 자율주행 시스템에서 차의 이동경로를 예측하는등, 고정 길이 입력이 아닌 임의 길이를 가지는 시퀀스sequence 데이터를 다룰 수 있다. 문장, 문서, 오디오 샘플을 입력받아, 자동번역, 스피치 투 텍스트speech to text 같은 자연어 처리netural language processing(NLP)에 유용하게 사용된다.
RNN 네트워크의 난제
- 불안정한 그레디언트 ( 11장에서 활성화 함수나 초기화로 해결했었던 )는 순환 드롭아웃과 순환 층 정규화를 포함한 여러가지 기법으로 안정적이게 만들 수 있다.
- ( 매우 ) 제한적인 단기 기억은 LSTM과 GRU셀을 사용할 수 있다.
RNN이 시퀀스 데이터를 처리하는 유일한 방법은 아니다. 작은 시퀀스의 경우 밀집층을 사용할수도, 매우 긴 시퀀스는 합성곱 층을 사용할 수 있다. 이 가능성을 이용해 WaveNet 이라는 수만개의 타임 스텝을 가진 시퀀스를 다루는 CNN 네트워크를 구현해 본다.
15.1 순환 뉴런과 순환 층
이전까지 네트워크는 입력층에서 출력층으로 한방향으로 흐르는 피드 포워드 신경망이었지만, 순환 신경망은 피드 포워드 신경망 처럼 출력을 내보내기는 하지만, 자신에게도 다시 출력을 보내는 뉴런이 존재한다. 각 타임 스텝time step ( or 프레임frame ) t 마다 입력 x_t 와 이전 뉴런의 출력 y_t-1 를 입력으로 받는다. 첫번째 타임스텝에서는 이전 출력을 0 으로 설정한다. 이 하나의 작은 네트워크를 시간에 따라 네트워크를 펼치도록 표현할 수 있다.
각 순환 뉴런은 두개의 입력을 받고, 입력에 따라 다른 가중치 벡터 w_x, w_y 를 가진다. 층 전체의 가중치 행렬 W_x, W_y 로 생각해 익숙하게 계산된다.
b 는 편향, phi 는 활성화 함수를 나타낸다.
피드포워드 신경망처럼 미니배치계산도 수행가능하다.
Y_t : 스텝 t 에서 미니배치에 있는 각 샘플에 대한 층의 출력을 담은 m x n_neurons 행렬 (m = batch_size, n = neuron 수 )
X_t : 스텝 t에서 모든 샘플의 입력값을 담은 m x n_inputs 행렬 ( n = 입력 특성 수 )
W_x : 현재 타임스텝의 입력에 대한 연결 가중치 n_inputs x n_neurons
W_y : 이전 타임 스텝의 출력에 대한 연결 가중치 n_neurons x n_neurons
b : 각 뉴런의 편향을 담은 n_neuorns 크기 벡터
15.1.1 메모리 셀
타입스텝 t에서 순환 뉴런의 출력은 이전 타임스텝에 대한 함수이고, 이를 메모리 라고 생각할 수 있다. 타임 스텝에 걸쳐서 어떤 상태를 보존하려는 신경망의 구성요소를 메모리 셀memory cell ( or cell ) 이라고 한다. 이 셀은 완전 연결 신경망의 layer 을 의미한다. 하나의 순환 뉴런 또는 층은 짧은 패턴 ( 10 스텝 길이 )만 학습할 수 있다. 긴 패턴을 학습하기 위해선 더 복잡한 종류의 셀이 필요하다.
타임 스텝 t 에서의 셀의 상태를 h_t ( h 는 hidden ), h_t 는 현재 타임스텝의 입력과 이전 타임스텝 상태에 대한 함수이고, 현재 타임스텝의 출력 y_t 도 이전 상태와 현재 입력에 대한 함수이다. 이전과 조금 다른점은 출력이 전달되지않고, 상태가 입력으로 전달된다는 것이다.
15.1.2 입력과 출력 시퀀스
RNN은 입력 시퀀스를 받아 출력 시퀀스를 만들 수 있다. 이를 시퀀스-투-시퀀스 네트워크sequence-to-sequnce network 라고 한다. 예를들어 1~10일 주식가격을 주입하면, 네트워크는 2~11일 주식가격을 출력한다. 또는 입력 시퀀스를 받아 마지막을 제외한 모든 출력을 무시하는 시퀀스-투-벡터 네트워크sequence-to-vector network 로, 영화리뷰의 연속된 단어를 주입하면 감성 점수를 출력하는 예가 있다. 반대로 하나의 입력 벡터를 주입하면, 타임 스텝에 따라 시퀀스를 출력하는 벡터-투-시퀀스 네트워크 vector-to-sequence network 로, 이미지를 입력하면 이미지에 대한 캡션( 설명, 단어의 시퀀스 )을 출력할 수 있다.
마지막으로 인코더encoder 라 부르는 시퀀스-투-벡터 네트워크 뒤에 디코더decoder 라는 벡터-투-시퀀스 네트워크를 연결해서 한언어를 다른 언어로 번역하는 데 사용할 수 있다. 이러한 인코더-디코더 모델은 그냥 시퀀스-투-시퀀스 모델보다 더 잘 작동한다. 마지막 단어가 첫번째 단어에 영향을 줄 수 있기 때문이다.
15.2 RNN 훈련하기
RNN을 훈련하기 위해서 네트워크를 타입스텝으로 펼치고 보통의 역전파 수행하듯이 한다. 이를 BPTT backpropagation through time 이라고 한다. 정방향이 흐르고, 역방향을 수행할 때 비용함수 C ( Y_0, Y_1, ... , Y_T ) 를 이용해서 그레디언트를 계산하고 펼쳐진 네트워크에 따라 역방향으로 전파된다. 비용함수에서 특성 출력을 무시하도록 할 수도 있다. 각 타임 스텝마다 같은 매개변수 W, b를 사용하기 때문에 비용함수가 사용한 출력들에 계산된 그레디언트의 합이 적용된다.
15.3 시계열 예측하기
웹사이트 시간당 접속 사용자 수, 도시의 날짜별 온도 같은 타임 스텝마다 하나의 값을 가지는 단변량 시계열 univariate time serise 이고, 여러 지표를 사용한 기업의 분기별 안정성을 연구하는데는 다변량 시계열multivariate time serise 의 데이터를 사용한다. 이러한 데이터를 통해 미래의 값을 예측하는 것을, 예측forecasting ( 시계열에서는 forecasting, 나머지는 predict ) 이라고 한다. 미래 예측뿐만아니라 비어있는 값을 채우는 작업도 수행할 수 있다. 예를들어 과거에 누락된 값을 예측한다. 이를 값 대체imputation 이라고 한다.
15.3.1 기준 성능
RNN 의 성능을 평가하기 전에, 기준이 될만한 성능 몇개를 준비해서 기본 모델보다 성능이 좋아졌는지 판단할 수 있도록 한다. 가장 간단한 방법으로 각 시계열의 마지막 값을 그대로 예측하는 것이 순진한 예측naive forecasting 이라고 한다. 또다른 방법으로 완전 연결 네트워크를 사용한다. 전체 시계열 시퀀스를 입력으로 받아 1개의 출력을 학습한다. mse 손실과 adam 옵티마이저를 사용해 컴파일하고 훈련한다.
15.3.2 간단한 RNN 구현하기
하나의 뉴런으로 이루어진 하나의 층을 가진 네트워크를 구현해본다. 어떤 길이의 타임 스템프도 처리가능해 입력 시퀀스 길이를 지정할 필요는 없다. 기본적으로 활성화함수로 하이퍼볼릭 탄젠트를 사용한다. 초기 상태 h_init = 0 와, 첫 입력 x_0 이 뉴런으로 전달되 가중치 합을 계산, 활성화 함수를 통해 첫번째 y_0 을 출력하고 이 출력이 새로운 상태 h_0 이 된다. 이과정을 마지막 스텝까지 반복한다. 케라스 순환층은 기본적으로 최종 출력만 반환하고, return_sequence=True 이면 매 타임스텝마다 결과를 출력한다.
선형모델은 입력마다 ( 각 타임 스텝 마다 ) 하나의 파라미터를 가지고 편향을 가지기 때문에 51개의 파라미터, RNN의 경우 입력과 은닉 상태 차원마다 하나의 파라미터를 가져 3개의 파라미터가 있다.
===
트렌드와 계절성
가중 이동 평균weighted moving average 이나 자동 회귀 누적 이동 평균autoregressive intergrated moving average (ARIMA) 같이 시계열을 예측하는 방법이 있다. 방법들중 일부는 트렌드trand나 계절성seasonality을 제거해야 한다. 예를들어 매년10% 성장하는 회사의 웹사이트 접속 사용자 수를 예측하기 위해서 이러한 10%라는 트렌드를 삭제하고 훈련시키고, 출력에 다시 트렌드를 더하거나, 선크림을 판매하는 회사의 여름의 판매량 증가인 계절성을 제거하고 훈련해 예측을 만들뒤 계절 패턴을 최종 예측에 더하는 작업이 필요하다. 일반적으로 이런 작업을 사용할 필요는 없지만, 모델이 트렌드나 계절성을 합습할 필요가 없기 때문에 성능을 향상시킬 수 있다.
===
15.3.3 심층 RNN
https://colab.research.google.com/drive/1iKbasqR-j88anvoO08l5EGYxN8SuoBRn#scrollTo=Deep_RNN
심층 RNN는 그냥 순환 층을 쌓으면되는데, LSTM 층이나 GRU 같은 다른 종류의 순환층을 쌓을 수도 있다.
단변량 시계열을 예측하는 심층 RNN의 경우 마지막 출력층을 RNN으로 구성하면 유효한 은닉상태를 저장하지않고, 타임 스텝마다 출력을 만들게되고, 활성화 함수의 제약이 생기기 때문에 Dense 층으로 바꾸는 경우가 많다.
15.3.4 여러 타임 스텝 앞을 예측하기
https://colab.research.google.com/drive/1iKbasqR-j88anvoO08l5EGYxN8SuoBRn#scrollTo=Multi_Forecasting
1 스텝 앞이아니라 여러 스텝 앞의 값을 예측하는 방법으로
1 - 1스텝앞을 예측하고 이 예측을 입력으로 추가해 다시 다음 값을 예측하는 식의 방법이다. 이 방법은 타임 스텝의 오차가 누적되어 타임 스텝이 갈수록 정확도가 낮아질 수 있다.
2 - RNN을 훈련하여 다음 10개의 값을 한번에 예측하는 방법이다. 시퀀스-투-벡터 모델을 사용하지만 1개가 아니라 값 10개를 출력한다. 그런데 훈련시에 타깃을 10개값이 담긴 벡터로 바꿔야한다. 마지막 RNN의 return_sequence을 지정하지 않아 벡터로 출력하게 한다.
3 - 2번째 방법을 좀 개선하여 마지막 타임 스텝에서만 결과를 출력하기보다, 모든 타임 스텝에서 다음 값 10개를 예측하도록 모델을 훈련한다. 즉 시퀀스-투-벡터 에서 시퀀스-투-시퀀스 형태의 모델로 변경하는 것이다. 장점은 모든 타임 스텝에 대한 RNN 출력 항이 손실에 포함되어 그레디언트가 마지막 타임스텝에서 처음으로 흐를 필요가없고, 각각의 타임스텝의 출력에서 그레디언트가 흐를 수 있어 훈련을 안정적이고 속도를 높인다.
타임스텝 0 에서 1~10 까지 예측을 담은 벡터를 출력 -> 1 에서 2~11까지 예측 벡터 출력, ... , 각 타깃은 입력 시퀀스와 동일한 길이의 시퀀스이고 각 타임 스텝마다 10차원 벡터를 담는다.
모든 타임 스텝에서 출력하기 위해서는 모든 RNN 에 return_seuquence=True 으로, Dense 층 같은 경우에도 TimeDistributed 층으로 감싸서 사용한다. 각 타임 스텝을 별개의 샘플처럼 다루도록 입력의 크기를 변경시켜 효율적으로 계싼한다. [ 배치 크기, 타임 스텝 수, 입력 차원 ] -> [ 배치 크기 X 타임 스텝 수 , 입력 차원 ] 크기로 바꾸고 Dense 층 적용후, [ 배치 크기 X 타임 스텝수, 출력 차원 ] -> [ 배치 크기, 타임 스텝 수, 출력 차원 ] 으로 변경한다.
간단한 RNN은 긴 시계열이나 시퀀스에서는 잘 작동하지 않는다.
15.4 긴 시퀀스 다루기
긴 시퀀스를 훈련하는 네트워크는 매우 깊게 펼친 RNN을 가진 네트워크가 된다. 보통 심층 신경망처럼 그레디언트 소실, 폭주문제와 훈련이 길고 불안정할 수 있다. 또한 긴 시퀀스의 처음 부분을 조금씩 잊어버릴 수 있는 문제가 있다.
15.4.1 불안정한 그레디언트 문제와 싸우기
DNN에서 사용했던 가중치 초기화, 빠른 옵티마이저, 드롭아웃 등을 사용할 수 있지만, 수렴하지 않는 활성화 함수는 네트워크가 매번 같은 가중치 매개변수를 사용하기 때문에 오히려 출력이 폭주할 위험이있다. 그래서 하이퍼 볼릭 탄젠트 같은 수렴하는 활성화 함수를 사용한다. 또한 그레디언트 자체도 폭주할 위험성도 있다. 따라서 훈련중 그레디언트를 모니터링(텐서보드)하고 그레디언트 클리핑gradient clipping 을 사용하는것이 좋다.
배치 정규화는 심층 피드포워드 네트워크 처럼 RNN에서 효율적으로 사용할 수 없다. 순환 신경망 사이에만 사용 가능하고, 타임 스텝사이에는 타입 스텝 입력과, 은닉 상태 모두에 배치 정규화층을 추가해서 사용할 수는 있는데, 같은 파라미터를 가진 배치 정규화가 타임 스텝마다 사용되서 별 효과는 없다.
RNN에 잘맞는 정규화는 층 정규화layer normalization 을 이용한다. 배치 차원에 대해 정규화하는 배치 정규화와 다르게, 특성 차원에 대해 정규화를 수행한다. 사용자 정의 셀을 사용하려면 keras.layers.RNN 층에 셀 객체를 전달한다. 층 정규화말고 타임 스텝 사이에 드롭아웃을 사용하는 사용자 정의 셀을 만들 수 있다. 그런데 케라스에서 모든 순환 층과 모든 셀에 dropout, recurrent_dropout 매개변수를 적용해서 드롭아웃을 적용할 수 있다. (전자는 입력, 후자는 상태의 드롭아웃 비율이다.)
15.4.2 단기 기억 문제 해결하기
RNN을 거쳐 데이터의 변환이 일부 정보의 손실로 이어진다. 손실이 누적되면 RNN의 상태는 첫번째 입력의 정보를 거의 가지지 않는다. 장기 메모리를 가진 셀에 인기있는 LSTM 셀을 살펴본다.
LSTM 셀
장단기 메모리long stort-term memory (LSTM) 셀은 여러 연구자에의해 점차 향상되었다. 기본셀보다 훈련이 빠르게 수렴하고, 데이터에 있는 장기간 의존성을 감지한다. keras.layers.LSTM 을 사용한다. 또는 keras.layers.LSTMCell 을 사용해서 keras.layers.RNN 에 전달하기도 하지만, LSTM층은 GPU에서 최적화되기 때문에 전자를 선호한다.
박스를 들여다보지않으면, 입력 x_t, 이전 상태 h_t-1, 이전 셀 c_t-1 을 입력받아, 상태 h_t, 셀 c_t, 을 출력하는 작업이다. h_t를 단기 상태, c_t 를 장기 상태라고 생각할 수 있다.
여기서 sigmoid, tanh 은 활성화 함수를 의미하고 x 는 원소별 곱, + 는 덧셈을 의미한다.
핵심 아이디어는 네트워크가 장기 상태에 저장할것, 버릴것, 읽어들일 것을 학습하는 것이다.
c_t-1 ( 장기 상태 )은 네트워크를 왼쪽에서 오른쪽으로 관통하면서, 삭제 게이트forget gate( 원소별 곱 )를 지나 일부 기억을 잃고, 덧셈 연산으로 기억 일부를 추가한다 ( 입력 게이트input gate 에서 선택된 기억 ) .이렇게 c_t 가 만들어 진다.
h_t-1 ( 단기 상태 ), x_t 는 각기 f_t, i_t, g_t ( 사진의 C_t ), o_t 의 4개의 층으로 주입된다. 주 층은 g_t 로 현재 입력과 이전 (단기)상태를 분석하는 일반적인 층인데 이 장기 상태에 중요한 부분이 저장되는 부분이 추가됬다. f_t, i_t, o_t 의 3층은 게이트 제어기gate controller 으로, 로지스틱 활성화 함수를 사용하기 때문에 0~1로 출력되어 원소별 곱으로 각각에 주입된다. 0을 출력하면 게이트를 닫고, 1 을 출력하면 게이트를 여는 방식으로 작동된다. 삭제 게이트 f_t 는 장기 상태의 어느 부분이 삭제되어야 하는지 결정하고, 입력 게이트 i_t 는 g_t ( 새로 만든 상태 ) 에 어느 부분이 장기 상태에 저장되어야 하는지 결정하고, 출력 게이트 o_t 는 장기 상태의 어느부분을 읽어서 이 타임 스텝의 h_t, y_t 로 출력해야 하는지를 결정한다.
LSTM은 중요한 입력을 인식, 장기 상태에 저장, 보존, 필요할 때마다 추출 하기 위해 학습한다. 그래서 LSTM 셀은 시계열,, 긴 텍스트, 오디오 녹음 등에서 장기 패턴을 잡아내는 데 놀라운 성과를 보여준다.
복잡한데 비슷한 형식의 식 반복이다. W는 각 층별 가중치, b 는 편향이다.
핍홀 연결
기존 LSTM 셀에서는 게이트 제어기 f , i , o 가 단기 상태만 입력으로 받는데, 여기에 장기 상태를 좀 노출시켜서 더 많은 문맥을 감지하게 만들도록 제안한 핍홀 연결peephole connection 이다. 장기 상태 c_t-1 이 삭제 게이트와 입력 게이트 제어기 f, i 에 입력으로 추가되고, 현재 장기 기억상태 c_t 는 출력 게이트 o_t 에 입력으로 추가된다. 성능이 좋아지기도 하는데 안좋아질 때도 있어서 도움이 되는지 확인해 보아야 한다. keras.experimental.PeepholeLSTMCell 층이 있어서 layers.RNN에 전달하여 층을 만들 수 있다.
GRU 셀
게이트 순환 유닛gated recurrent unit (GRU) 셀은 LSTM의 간소화 버전이고 유사하게 작동하는 듯 하다.
- 두개의 상태 벡터가 h_t 하나로 합쳐짐
- 하나의 게이트 제어기 z_t 가 삭제 게이트 f_t, 입력 게이트 o_t 를 모두 제어, 게이트 = 1 -> 삭제 = 1, 입력 = 0, 게이트 = 0 -> 삭제 = 0, 입력 = 1, 즉 기억이 저장되면 저장될 위치가 삭제됨.
- 출력 게이트가 없음. 전체 상태 백터가 매 스텝마다 출력되지만, 이전상태의 어느 부분이 주 층에 노출될지 제어하는 게이트 제어기 r_t 가 존재
LSTM, GRU는 단순 RNN 보다 훨씬 긴 시퀀스를 다룰 수 있지만, 매우 제한적인 단기 기억을 가져서 100 타임스텝 이상에 장기 패턴을 학습하는데 어려움이 있다.
1D 합성곱 층을 이용해 시퀀스 처리하기
https://colab.research.google.com/drive/1iKbasqR-j88anvoO08l5EGYxN8SuoBRn#scrollTo=1D_Conv
1D 합성곱 층이 몇개의 커널을 통해 시퀀스를 슬라이딩하여 매우 짧은 하나의 순차 패턴을 감지하도록 학습된다. 10개의 커널을 사용하면 10개의 각기 다른 1차원 시퀀스로 구성되고, 이것을 2차원으로 볼수도 있다. 이는 순환층과 1D 합성곱 층을 섞어서 신경망 구성이 가능하다.
WaveNet
층마다 팽창 비율dilation rate ( 뉴런의 입력이 떨어져 있는 간격 ) 을 2배로 늘리는 1D 합성곱을 쌓는 방식을 사용한다. 합성곱 층이kerne_size = 2 , padding="causal" ( causal 은 입력의 왼쪽에 0을 알맞게 패딩하고 valid 패딩을 상용하는 것과 같다 ) 을 기본적으로 사용하고, dilation_rate = 1, 2, 4, 8 으로 늘려가는 작업을 통해서 아주 긴 시퀀스를 매우 효율적으로 처리한다.
위와 같이, 처음 합성곱 층은 그냥 kernel_size = 2 인 합성곱과 동일하다. 입력 2개씩 슬라이딩하면서 출력을 만들어낸다.
두번째 팽창 비율이 2 인 합성곱은, kernel_size = 2 인데, 받아들이는 입력을 한칸 띄워서 받는다. 첫번째 입력과, 3번째 입력을 받아서 합성곱을 수행한다. 세번째 네번째도 팽창비율이 4, 8로 다를뿐 2번째 합성곱과 방식은 동일하다.
결국 층을 통과할수록, 첫번째 output을 만들기위해서, 1~16의 입력을 사용했고, 그중에 유효한 정보들을 추출해 아주 긴 시퀀스를 매우 효율적으로 처리할 수 있게된다.
WaveNet 논문에서는 팽창 비율이 1,2,4, ... 256, 512 인 10개의 층을 쌓았다. 이 10개의 층은 커널 크기가 1024인 매우 효율적인 합성곱 층처럼 작동하는 것을 보였다. 이러한 이유로 이 블럭을 3개나 쌓았다고 한다.
오디오의 경우 1초에 수만개의 타임 스텝이 포함되는 아주 긴 시퀀스인데 WaveNet은 최상의 성능을 달성했다. LSTM 과 GRU도 이렇게 긴 시퀀스를 다룰수 없다.