10.1 다양한 종류의 시계열 작업
시계열(timeseries) 데이터 : 일정한 간격으로 측정하여 얻은 모든 데이터
ㄴ 주식의 일별 가격, 도시의 시간별 전력 소모량, 상점의 주간별 판매량 등
시계열을 다루려면 시스템의 역학(dynamics)(주기성, 시간에 따른 트렌드, 규칙적인 형태와 급격한 증가 등)을 이해해야 한다.
가장 일반적인 시계열 관련 작업은 예측(forecasting)이다.
이외에도 다양한 시계열 작업이 있다.
- 분류: 하나 이상의 범주형 레이블을 시계열에 부여한다.
- 이벤트 감지: 연속된 데이터 스트림에서 예상되는 특정 이벤트 발생을 식별한다.
- 이상치 탐지(anomaly detection): 연속된 데이터 스트림에서 발생하는 비정상적인 현상을 감지한다. 일반적으로 비지도 학습(unsupervised learning)으로 수행된다.
데이터 표현 기법
시계열을 다룰 때 여러 다른 주파수 성분으로 분리하는 푸리에 변환(Fourier transform)을 사용한다.
ㄴ 주로 주기와 진동이 특징인 데이터(소리, 고층 건물의 진동, 뇌파 등)를 처리할 때 매우 유용할 수 있다.
다른 도메인 특화된 표현을 특성 공학의 형태로 사용하여 모델 훈련 전에 데이터를 전처리함으로써 모델이 처리할 작업을 더 쉽게 만들 수 있다.
10.2 온도 예측 문제
독일 예나( Jena)시에 있는 막스 플랑크 생물지구화학연구소(Max Planck Institute for Biogeochemistry)의 기상 관측소에서 수집한 데이터셋을 사용한다. 이 데이터셋에는 수년간에 걸쳐 (온도, 기압, 습도, 풍향 등) 14개의 관측치가 10분마다 기록되어 있다. 원본 데이터는 2003년부터 기록되어 있지만 이 예제에서는 2009~2016년 사이의 데이터만 사용한다.
10.2.1 데이터 준비
각 시계열을 독립적으로 정규화하여 비슷한 범위를 가진 작은 값으로 바꾸겠다. 처음 21만 225의 타임스텝(timestep)을 훈련 데이터로 사용하므로 이 범위에서 평균과 표준 편차를 계산다.
timeseries_dataset_from_array()를 사용해서 훈련, 검증, 테스트를 위해 3개의 데이터셋을 만들겠다.
다음과 같은 매개변수 값을 사용한다.
- sampling_rate = 6: 시간당 하나의 데이터 포인트가 샘플링됩니다. 즉, 6개의 데이터 포인트 중 하나만 사용하겠다.
- sequence_length = 120: 이전 5일간(120시간) 데이터를 사용한다.
- delay = sampling_rate * (sequence_length + 24 - 1): 시퀀스의 타깃은 시퀀스 끝에서 24시간 후의 온도다.
훈련 데이터셋을 만들 때 처음 50%의 데이터만 사용하기 위해 start_index = 0과 end_index = num_train_samples로 지정한다. 검증 데이터셋의 경우 그다음 25%를 사용하기 위해 start_index = num_train_sample와 end_index = num_train_samples + num_val_samples로 지정한다. 마지막으로 테스트 데이터셋의 경우 남은 샘플을 사용하기 위해 start_index = num_train_samples + num_val_samples로 지정한다.
10.2.2 상식 수준의 기준점
블랙박스 같은 딥러닝 모델을 사용하여 온도 예측 문제를 풀기 전에 간단한 상식 수준의 해법을 시도해 보겠다. 이는 정상적인 문제인지 확인하기 위한 용도이며 고수준 머신 러닝 모델이라면 뛰어넘어야 할 기준점이 된다. 이런 상식 수준의 해법은 알려진 해결책이 없는 새로운 문제를 다루어야 할 때 유용하다.
시계열 데이터는 연속성이 있고 일자별로 주기성을 가진다고 가정할 수 있다(오늘 온도는 내일 온도와 비슷할 가능성이 높습니다). 그렇기 때문에 상식 수준의 해결책은 지금으로부터 24시간 후 온도는 지금과 동일하다고 예측하는 것이다. 이 방법을 다음과 같이 정의된 평균 절댓값 오차(MAE)로 평가해 본다.
10.2.3 기본적인 머신 러닝 모델 시도해 보기
머신 러닝 모델을 시도하기 전에 상식 수준의 기준점을 세웠다. 비슷하게 RNN처럼 복잡하고 연산 비용이 많이 드는 모델을 시도하기 전에 간단하고 손쉽게 만들 수 있는 머신 러닝 모델(예를 들어 소규모의 완전 연결 네트워크)을 먼저 만드는 것이 좋다. 이를 바탕으로 더 복잡한 방법을 도입하는 근거가 마련되고 실제적인 이득도 얻게 될 것이다.
다음 코드는 데이터를 펼쳐서 2개의 Dense 층을 통과시키는 완전 연결 네트워크를 보여 줍니다. 전형적인 회귀 문제이므로 마지막 Dense 층에 활성화 함수를 두지 않았다. 손실 함수로 MAE 대신 평균 제곱 오차(MSE)를 사용한다. MSE는 원점에서 미분 가능하기 때문에 경사 하강법에 잘 맞다. compile() 메서드에 모니터링할 지표로 MAE를 추가한다.
간단하고 괜찮은 성능을 내는 모델(상식 수준의 기준 모델)이 데이터와 타깃을 매핑할 수 있다면 왜 훈련한 모델은 이를 찾지 못하고 성능이 낮을까?
문제 해결을 위해 탐색하는 모델의 공간, 즉 가설 공간은 우리가 매개변수로 설정한 2개의 층을 가진 네트워크의 모든 가능한 가중치 조합이다. 상식 수준의 모델은 이 공간에서 표현할 수 있는 수백만 가지 중 하나일 뿐이다.
이것이 일반적으로 머신 러닝이 가진 심각한 제약 사항이다. 특정한 종류의 간단한 모델을 찾도록 학습 알고리즘을 하드코딩하지 않았다면, 종종 간단한 문제를 위한 간략한 해결책을 찾지 못할 수 있다. 이것이 좋은 특성 공학과 문제와 관련된 아키텍처 구조를 활용하는 것이 중요한 이유다. 즉, 모델이 찾아야 할 것을 정확하게 알려 주어야 한다.
10.2.4 1D 합성곱 모델 시도해 보기
올바른 아키텍처 구조를 활용하는 측면을 보면 입력 시퀀스가 일별 주기를 가지기 때문에 합성곱 모델을 적용할 수 있다. 시간 축에 대한 합성곱은 다른 날에 있는 동일한 표현을 재사용할 수 있다. 마치 공간 방향 합성곱이 이미지에서 다른 위치에 있는 같은 표현을 재사용하는 것과 같다.
결과를 보면 이 모델은 밀집 연결 모델보다 더 성능이 나쁘다. 약 2.9도의 검증 MAE를 달성하여 상식 수준의 모델과 차이가 크다. 두 가지의 문제점이 있다.
• 첫째, 날씨 데이터는 평행 이동 불변성 가정을 많이 따르지 않다. 데이터에 일별 주기성이 있지만 아침 데이터는 저녁이나 한밤중의 데이터와 성질이 다르다. 날씨 데이터는 매우 특정한 시간 범위에 대해서만 평행 이동 불변성을 가진다.
• 둘째, 이 데이터는 순서가 많이 중요하다. 최근 데이터가 5일 전 데이터보다 내일 온도를 예측하는 데 훨씬 더 유용하다. 1D 컨브넷은 이런 사실을 활용할 수 없다. 특히 최대 풀링과 전역 평균 풀링 층 때문에 순서 정보가 많이 삭제다.
10.2.5 첫 번째 순환 신경망
밀집 연결 모델이나 합성곱 모델이 잘 작동하지 않았지만 그렇다고 이 문제에 머신 러닝이 적합하지 않다는 뜻은 아니다. 밀집 연결 모델은 시계열 데이터를 펼쳤기 때문에 입력 데이터에서 시간 개념을 잃어버렸다. 합성곱 모델은 데이터의 모든 부분을 비슷한 방식으로 처리했으며 풀링을 적용하여 순서 정보를 잃어버렸다. 이런 방법 대신 인과 관계와 순서가 의미 있는 시퀀스 데이터를 그대로 사용해 보겠다.
이런 문제를 위해 특별히 고안된 신경망 구조가 순환 신경망이다. 그중에서도 LSTM(Long Short-Term Memory) 층이 오랫동안 인기가 많았다.
10.3 순환 신경망 이해하기
네트워크로 시퀀스나 시계열 데이터 포인트를 처리하려면 네트워크에 전체 시퀀스를 주입해야 한다. 즉, 전체 시퀀스를 하나의 데이터 포인트로 변환해야 한다. 예를 들어 밀집 연결 모델에서 보았듯이 5일치 데이터를 펼쳐서 하나의 큰 벡터로 만들어 처리했습니다. 이런 네트워크를 피드포워드 네트워크(feedforward network)라고 합니다.
비록 극단적으로 단순화시킨 버전이지만 순환 신경망(Recurrent Neural Network, RNN)은 같은 원리를 적용한 것입니다. 시퀀스의 원소를 순회하면서 지금까지 처리한 정보를 상태(state)에 저장합니다. 실제로 RNN은 내부에 루프(loop)를 가진 신경망의 한 종류입니다
RNN의 상태는 (배치에 있는 2개의 다른 샘플처럼) 2개의 다른 시퀀스를 처리하는 사이에 재설정됩니다. 따라서 하나의 시퀀스를 여전히 하나의 데이터 포인트, 즉 네트워크에 주입되는 하나의 입력으로 간주할 수 있습니다. 이 데이터 포인트가 한 번에 처리되지 않는다는 것이 다릅니다. 그 대신 네트워크는 내부적으로 시퀀스의 원소를 순회합니다.
루프와 상태에 대한 개념을 명확히 하기 위해 간단한 RNN 정방향 계산을 구현해 보죠. 이 RNN은 (timesteps, input_features) 크기의 랭크-2 텐서로 인코딩된 벡터의 시퀀스를 입력받습니다. 타임스텝을 따라 루프를 돌면서 각 타임스텝 t에서 현재 상태와 (크기가 (input_features,)인) 입력을 연결하여 출력을 계산합니다. 그다음 이 출력을 다음 스텝의 상태로 설정합니다. 첫 번째 타임스텝에서는 이전 출력이 정의되지 않으므로 현재 상태가 없습니다. 이때는 네트워크의 초기 상태(initial state)인 0 벡터로 상태를 초기화합니다.
10.3.1 케라스의 순환 층
넘파이로 간단하게 구현한 과정이 실제 케라스의 SimpleRNN 층에 해당한다.
SimpleRNN이 한 가지 다른 점은 넘파이 예제처럼 하나의 시퀀스가 아니라 케라스의 다른 층과 마찬가지로 시퀀스의 배치를 처리한다는 것이다. 즉, (timesteps, input_features) 크기가 아니라 (batch_size, timesteps, input_features) 크기의 입력을 받는다. 시작할 때 Input() 함수의 shape 매개변수에 timesteps 항목을 None으로 지정할 수 있다. 이렇게 하면 임의의 길이를 가진 시퀀스를 처리할 수 있다.
케라스에 있는 모든 순환 층(SimpleRNN, LSTM, GRU)은 두 가지 모드로 실행할 수 있다. 각 타임스텝의 출력을 모은 전체 시퀀스((batch_size, timesteps, output_features) 크기의 랭크-3 텐서)를 반환하거나 입력 시퀀스의 마지막 출력((batch_size, output_features) 크기의 랭크-2 텐서)만 반환할 수 있다. 이 두 모드는 생성자의 return_sequences 매개변수로 제어할 수 있다.
실제로는 SimpleRNN 층을 일반적으로 실전에 쓰기에는 너무 단순하여 사용하지 않는다. SimpleRNN은 이론적으로 시간 t에서 이전의 모든 타임스텝의 정보를 유지할 수 있다. 하지만 실제로는 긴 시간에 걸친 의존성은 학습할 수 없다.
이제 복잡한 부분은 데이터 흐름에서 다음 이동 상태(c_t+1)가 계산되는 방식이다. 여기에는 3개의 다른 변환이 관련되어 있다. 3개 모두 SimpleRNN과 같은 형태를 가진다.
이 연산들이 하는 일을 해석하면 각 의미에 대해 통찰을 얻을 수 있다. 예를 들어 c_t와 f_t의 곱셈은 이동을 위한 데이터 흐름에서 관련이 적은 정보를 의도적으로 삭제한다고 볼 수 있다. 한편 i_t와 k_t는 현재에 대한 정보를 제공하고 이동 트랙을 새로운 정보로 업데이트한다. 하지만 결국 이런 해석은 큰 의미가 없다. 이 연산들이 실제로 하는 일은 연산에 관련된 가중치 행렬에 따라 결정되기 때문이다. 이 가중치는 엔드-투-엔드 방식으로 학습된다. 이 과정은 훈련 반복마다 매번 새로 시작되며 이런저런 연산들에 특정 목적을 부여하기가 불가능하다. RNN 셀의 구조는 가설 공간을 결정한다. 훈련할 때 이 공간에서 좋은 모델 파라미터를 찾는다. 셀의 구조가 셀이 하는 일을 결정하지 않는다. 이는 셀의 가중치에 달려 있다. 같은 셀이더라도 다른 가중치를 가지는 경우 매우 다른 작업을 수행한다. 따라서 RNN 셀을 구성하는 연산 조합은 엔지니어링적인 설계가 아니라 가설 공간의 제약 조건으로 해석하는 것이 낫다.
확실히 RNN 셀의 구현 방법 같은 제약 조건의 선택을 엔지니어보다 (유전 알고리즘이나 강화 학습 알고리즘 같은) 최적화 알고리즘에 맡기면 더 나아 보일 것이다. 미래에는 이런 식으로 네트워크를 만들게 될 것이다. 요약하면 LSTM 셀의 구체적인 구조에 대해 이해할 필요가 전혀 없다. 이를 이해하는 것이 우리가 해야 할 일이 아니다. LSTM 셀의 역할만 기억하면 된다. 바로 과거 정보를 나중에 다시 주입하여 그레이디언트 소실 문제를 해결하는 것이다.
10.4 순환 신경망의 고급 사용법
- 순환 드롭아웃(recurrent dropout): 드롭아웃의 한 종류로 순환 층에서 과대적합을 방지하기 위해 사용한다.
- 스태킹 순환 층(stacking recurrent layer): 모델의 표현 능력(representational power)을 증가시킨다(그 대신 계산 비용이 많이 든다).
- 양방향 순환 층(bidirectional recurrent layer): 순환 네트워크에 같은 정보를 다른 방향으로 주입 하여 정확도를 높이고 기억을 좀 더 오래 유지시킨다.
10.4.1 과대적합을 감소하기 위해 순환 드롭아웃 사용하기
훈련 손실과 검증 손실 곡선을 보면 모델이 과대적합인지 알 수 있다. 몇 번의 에포크 이후에 훈련 손실과 검증 손실이 현저하게 벌어지기 시작한다. 이런 현상을 해결하기 위해 잘 알려진 드롭아웃 기법을 이미 보았다. 훈련 데이터를 층에 주입할 때 데이터에 있는 우연한 상관관계를 깨뜨리기 위해 입력 층의 유닛을 랜덤하게 끄는 기법이다. 하지만 순환 신경망에 드롭아웃을 올바르게 적용하는 일은 간단하지 않다.
GRU나 LSTM 같은 순환 게이트에 의해 만들어지는 표현을 규제하려면 순환 층 내부 계산에 사용된 활성화 함수에 타임스텝마다 동일한 드롭아웃 마스크를 적용해야 한다(순환 드롭 아웃 마스크). 모든 타임스텝에 동일한 드롭아웃 마스크를 적용하면 네트워크가 학습 오차를 타입스텝에 걸쳐 적절하게 전파할 수 있다.
10.4.2 스태킹 순환 층
과대적합은 더 이상 없지만 성능상 병목이 있는 것 같으므로 네트워크의 용량과 표현력을 늘려야 한다. (드롭아웃 등을 사용하여 과대적합을 줄이는 기본 단계를 거쳤다 가정하고) 과대적합이 일어날 때까지 모델의 용량을 늘리는 것이 좋다. 너무 많이 과대적합되지 않는 한 아직 충분한 용량에 도달한 것이 아니다.
네트워크의 용량을 늘리려면 일반적으로 층에 있는 유닛의 개수를 늘리거나 층을 더 많이 추가한다. 순환 층 스태킹은 더 강력한 순환 네트워크를 만드는 고전적인 방법이다.
케라스에서 순환 층을 차례대로 쌓으려면 모든 중간층은 마지막 타임스텝 출력만 아니고 전체 시퀀스(랭크-3 텐서)를 출력해야 합니다. 이미 배웠듯이 return_sequences=True로 지정하면 됩니다.
드롭아웃 규제를 사용한 2개의 순환 층을 스태킹해 본다. 변화를 주어 LSTM 대신에 GRU(Gated Recurrent Unit)를 사용하겠다. GRU는 LSTM과 매우 비슷하다. GRU를 LSTM 구조의 간단하고 간소화된 버전으로 생각할 수 있다.
10.4.3 양방향 RNN 사용하기
양방향 RNN은 RNN의 한 변종이고 특정 작업에서 기본 RNN보다 훨씬 좋은 성능을 낸다. 자연어 처리에서는 맥가이버 칼이라고 할 정도로 즐겨 사용된다.
RNN은 특히 순서에 민감하다. 즉, 입력 시퀀스의 타입스텝 순서대로 처리한다. 타입스텝을 섞거나 거꾸로 하면 RNN이 시퀀스에서 학습하는 표현을 완전히 바꾸어 버린다. 이는 온도 예측처럼 순서에 의미가 있는 문제에 잘 맞는 이유이기도 하다. 양방향 RNN은 RNN이 순서에 민감하다는 성질을 사용한다. 앞서 보았던 GRU나 LSTM 같은 RNN 2개를 사용한다. 각 RNN은 입력 시퀀스를 한 방향(시간 순서나 반대 순서)으로 처리한 후 각 표현을 합친다. 시퀀스를 양쪽 방향으로 처리하기 때문에 양방향 RNN은 단방향 RNN이 놓치기 쉬운 패턴을 감지할 수 있다.
순서를 뒤집은 LSTM은 상식 수준의 기준점보다도 성능이 낮다. 이 경우에는 시간 순서대로 처리하는 것이 중요한 역할을 한다. 일반적으로 LSTM 층은 먼 과거보다 최근 내용을 잘 기억한다. 또한, 최근에 가까운 날씨 데이터 포인트일수록 오래된 데이터 포인트보다 예측에 유용하다(상식 수준의 기준점이 꽤 강력한 이유입니다). 따라서 시간 순서대로 처리하는 네트워크가 거꾸로 처리하는 것보다 성능이 높아야만 한다.
하지만 자연어 처리를 포함하여 다른 많은 문제에서는 그렇지 않다. 문장을 이해하는 데 단어의 중요성은 단어가 문장 어디에 놓여 있는지에 따라 결정되지 않는다. 텍스트 데이터셋에는 순서를 뒤집어 처리하는 것이 시간 순서대로 처리하는 것과 거의 동일하게 잘 작동한다. 사람은 텍스트를 거꾸로 읽을 수 있다. 언어를 이해하는 데 단어의 순서가 중요하지만 결정적이지 않다.
거꾸로 된 시퀀스에서 훈련한 RNN은 원래 시퀀스에서 훈련한 것과는 다른 표현을 학습한다. 이와 비슷하게 시작할 때 죽고 마지막 날 태어나는 삶처럼 실제 세상의 시간이 거꾸로 흘러간다면 우리의 정신 세계는 달라질 것이다. 머신 러닝에서 다른 표현이 유용하다면 항상 사용할 가치가 있다. 이 표현이 많이 다를수록 더 좋다. 이 표현이 데이터를 바라보는 새로운 시각을 제공하고 다른 방식에서는 놓칠 수 있는 데이터의 특징을 잡아낸다. 이런 표현은 작업 성능을 올리는 데 도움을 준다. 이것이 13장에서 살펴볼 앙상블(ensemble) 개념이다.
케라스에서는 Bidirectional 층을 사용하여 양방향 RNN을 만든다. 이 클래스는 첫 번째 매개변수로 순환 층의 객체를 전달받는다. Bidirectional 클래스는 전달받은 순환 층으로 새로운 두 번째 객체를 만든다. 하나는 시간 순서대로 입력 시퀀스를 처리하고, 다른 하나는 반대 순서로 입력 시퀀스를 처리한다.
이 모델은 평범한 LSTM 층만큼 성능이 좋지 않다. 이유는 모든 예측 성능은 시간 순서대로 처리하는 네트워크의 절반에서 온다. 시간 반대 순서로 처리하는 절반은 이런 작업에 성능이 매우 좋지 않기 때문이다(최근 정보가 오래전 정보보다 훨씬 더 중요합니다). 동시에 시간 반대 순서로 처리하는 층 때문에 네트워크의 용량이 2배가 되고 훨씬 더 일찍 과대적합이 시작된다.
하지만 양방향 RNN은 텍스트 데이터 또는 순서가 중요한 (하지만 사용하는 순서는 중요하지 않은) 다른 종류의 데이터에 잘 맞다. 사실 (다음 장에서 배울 트랜스포머(Transformer) 구조가 등장하기 전) 2016년 잠시 동안 양방향 LSTM 층이 많은 자연어 처리 작업에서 최고 수준의 성능을 냈다.
10.4.4 더 나아가서
온도 예측 문제의 성능을 향상하기 위해 시도해 볼 수 있는 것이 많이 있다.
- 스태킹한 각 순환 층의 유닛 개수와 드롭아웃의 양을 조정합니다. 지금 설정은 대부분 임의로 한 것이라 최적화가 덜 되었을 것입니다.
- RMSprop 옵티마이저의 학습률을 조정하거나 다른 옵티마이저를 사용합니다.
- 순환 층 위에 놓을 회귀 모델을 위해 하나가 아니라 여러 개의 Dense 층을 쌓습니다.
- 모델의 입력을 개선합니다. 더 길거나 짧은 시퀀스를 테스트해 보거나 샘플링 간격(sampling_rate)을 바꿉니다. 또는 특성 공학을 수행합니다.
10.5 요약
- 5장에서 처음 배웠던 것처럼 새로운 문제를 해결할 때는 선택한 지표에서 상식 수준의 기준점을 설정하는 것이 좋습니다. 기준점을 가지고 있지 않으면 실제로 향상되었는지 알 수 없습니다.
- 추가 비용이 합리적인지 판단하기 위해 계산 비용이 높은 모델 전에 간단한 모델을 시도합니다. 이따금 간단한 모델이 최선일 경우가 있습니다.
- 시간 순서가 중요한 데이터, 특히 시계열 데이터가 있다면 순환 신경망이 적합합니다. 시계열 데이터를 펼쳐서 처리하는 모델의 성능을 쉽게 앞지를 것입니다. 케라스에서 핵심적인 RNN 층 2개는 LSTM 층과 GRU 층입니다.
- 순환 네트워크에 드롭아웃을 사용하려면 타임스텝 동안 일정한 드롭아웃 마스크와 순환 드롭아웃 마스크를 사용해야 합니다. 둘 다 케라스 순환 층에 포함되어 있습니다. 순환 층에 있는 recurrent_dropout 매개변수를 사용하면 됩니다.
- 스태킹 RNN은 단일 RNN 층보다 더 강력한 표현 능력을 제공합니다. 하지만 계산 비용이 많이 들기 때문에 항상 시도할 가치가 있지는 않습니다. (기계 번역 같은) 복잡한 문제에서 확실히 도움이 되지만 작고 간단한 문제에서는 항상 그렇지 않습니다.
'AI 입문 > Deep Learning' 카테고리의 다른 글
12장 - 생성 모델을 위한 딥러닝 (0) | 2023.02.23 |
---|---|
11장 - 텍스트를 위한 딥러닝 (1) | 2023.02.12 |
9장 - 컴퓨터 비전을 위한 고급 딥러닝 (0) | 2023.01.22 |
8장 - 컴퓨터 비전을 위한 딥러닝 (2) | 2023.01.22 |
7장 - 케라스 완전 정복 (0) | 2023.01.15 |