본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 3. · 13 Views
Transformer 기반 시계열 모델 완벽 가이드
시계열 데이터 예측의 혁신, Transformer 아키텍처를 활용한 시계열 모델링을 초급자도 이해할 수 있도록 설명합니다. Attention 메커니즘부터 실전 구현까지 단계별로 알아봅니다.
목차
- Attention_메커니즘의_핵심
- Positional_Encoding의_비밀
- Multi_Head_Attention_구조
- Encoder_Decoder_아키텍처
- Masking_전략_이해하기
- 시계열_Transformer_실전_구현
- Informer와_최신_시계열_모델
- 시계열_Transformer_하이퍼파라미터_튜닝
1. Attention 메커니즘의 핵심
김개발 씨는 주식 가격 예측 모델을 만들다가 한 가지 의문이 들었습니다. "과거 데이터 중에서 어떤 시점이 미래 예측에 더 중요할까?" 기존 RNN 모델은 모든 과거 데이터를 똑같이 취급하는 것 같아 답답했습니다.
Attention 메커니즘은 한마디로 "중요한 것에 집중하는 기술"입니다. 마치 시험공부를 할 때 모든 내용을 똑같이 외우는 것이 아니라, 중요한 부분에 형광펜을 칠하고 집중하는 것과 같습니다.
이것을 제대로 이해하면 시계열 모델이 왜 특정 시점의 데이터에 더 주목하는지 알 수 있습니다.
다음 코드를 살펴봅시다.
import torch
import torch.nn as nn
class ScaledDotProductAttention(nn.Module):
def __init__(self, d_k):
super().__init__()
self.scale = d_k ** 0.5 # 스케일링 팩터
def forward(self, query, key, value):
# Query와 Key의 유사도 계산
scores = torch.matmul(query, key.transpose(-2, -1)) / self.scale
# Softmax로 가중치 정규화
attention_weights = torch.softmax(scores, dim=-1)
# Value에 가중치 적용하여 출력 생성
output = torch.matmul(attention_weights, value)
return output, attention_weights
김개발 씨는 입사 6개월 차 데이터 사이언티스트입니다. 오늘은 회사의 매출 예측 모델을 개선하라는 미션을 받았습니다.
기존 LSTM 모델은 성능이 좋지 않았고, 특히 장기 패턴을 잘 잡아내지 못하는 문제가 있었습니다. 선배 개발자 박시니어 씨가 다가와 말했습니다.
"Attention 메커니즘을 써보는 건 어때요? 최근에는 Transformer 기반 모델이 시계열 예측에서도 좋은 성능을 보이고 있거든요." 그렇다면 Attention 메커니즘이란 정확히 무엇일까요?
쉽게 비유하자면, Attention은 마치 도서관 사서가 책을 찾아주는 것과 같습니다. 여러분이 "머신러닝 입문서"를 찾는다고 질문하면, 사서는 수천 권의 책 중에서 여러분의 질문과 가장 관련 있는 책들을 골라줍니다.
이때 사서는 모든 책을 똑같이 보는 것이 아니라, 질문과 관련성이 높은 책에 더 많은 관심을 기울입니다. Attention 이전에는 어떻게 시계열 데이터를 처리했을까요?
RNN이나 LSTM 같은 순환 신경망은 데이터를 순차적으로 처리해야 했습니다. 마치 긴 문장을 한 글자씩 읽으면서 내용을 기억해야 하는 것처럼요.
문제는 문장이 길어질수록 앞부분의 내용을 점점 잊어버린다는 것이었습니다. 이것을 장기 의존성 문제라고 부릅니다.
바로 이런 문제를 해결하기 위해 Attention이 등장했습니다. Attention을 사용하면 시퀀스의 모든 위치에 직접 접근할 수 있습니다.
100일 전의 데이터든 어제 데이터든 필요하다면 똑같이 참조할 수 있습니다. 또한 어떤 시점이 예측에 중요한지 가중치로 명확히 알 수 있습니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 Query, Key, Value라는 세 가지 개념이 핵심입니다.
Query는 "내가 찾고 싶은 것", Key는 "각 데이터의 특징", Value는 "실제 데이터 값"입니다. scores를 계산하는 부분에서 Query와 Key를 곱해 유사도를 측정합니다.
그리고 softmax를 통해 가중치를 0과 1 사이로 정규화합니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 전력 수요 예측을 한다고 가정해봅시다. 여름철 폭염이 시작되면 에어컨 사용량이 급증합니다.
Attention 메커니즘은 과거 폭염 시기의 데이터에 높은 가중치를 부여하여, 비슷한 상황에서의 패턴을 더 잘 학습할 수 있습니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 스케일링을 빼먹는 것입니다. d_k로 나누지 않으면 softmax의 출력이 극단적인 값으로 쏠려서 학습이 잘 되지 않습니다.
반드시 Scaled Dot-Product 방식을 사용해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다. "아, 그래서 Transformer가 긴 시퀀스에서도 잘 작동하는군요!" Attention 메커니즘을 제대로 이해하면 시계열 모델의 핵심 원리를 파악할 수 있습니다.
여러분도 오늘 배운 Query, Key, Value 개념을 꼭 기억해 두세요.
실전 팁
💡 - Query, Key, Value를 도서관 비유로 기억하세요: 질문(Query), 책 제목(Key), 책 내용(Value)
- 스케일링 팩터(d_k의 제곱근)를 빼먹으면 학습이 불안정해집니다
2. Positional Encoding의 비밀
김개발 씨가 Transformer를 공부하다가 이상한 점을 발견했습니다. "어?
Transformer는 순서 정보를 어떻게 알지?" RNN과 달리 Transformer는 모든 입력을 동시에 처리하는데, 시계열에서 순서는 매우 중요한 정보입니다.
Positional Encoding은 Transformer에게 "이 데이터는 몇 번째 순서야"라고 알려주는 방법입니다. 마치 책의 페이지 번호처럼, 각 데이터에 위치 정보를 덧붙여주는 것입니다.
이것이 없으면 Transformer는 "어제"와 "한 달 전"을 구분하지 못합니다.
다음 코드를 살펴봅시다.
import numpy as np
import torch
def get_positional_encoding(seq_len, d_model):
# 위치 인덱스 생성
position = np.arange(seq_len)[:, np.newaxis]
# 차원 인덱스 생성
div_term = np.exp(np.arange(0, d_model, 2) * -(np.log(10000.0) / d_model))
# 짝수 차원: sin, 홀수 차원: cos
pe = np.zeros((seq_len, d_model))
pe[:, 0::2] = np.sin(position * div_term) # 짝수 인덱스
pe[:, 1::2] = np.cos(position * div_term) # 홀수 인덱스
return torch.FloatTensor(pe)
김개발 씨는 Transformer 논문을 읽다가 한 가지 의문이 생겼습니다. "Attention은 모든 위치를 동시에 보는데, 그러면 순서가 섞여도 같은 결과가 나오지 않나요?" 박시니어 씨가 웃으며 대답했습니다.
"정확히 그 문제를 해결하기 위해 Positional Encoding이 필요한 거예요." RNN은 데이터를 하나씩 순서대로 처리하기 때문에 자연스럽게 순서 정보를 알 수 있었습니다. 마치 책을 첫 페이지부터 한 장씩 넘기며 읽는 것과 같습니다.
하지만 Transformer는 모든 페이지를 한꺼번에 펼쳐놓고 보는 방식입니다. 문제는 시계열 데이터에서 순서가 매우 중요하다는 점입니다.
"오늘 주가가 100원이고 어제가 90원"과 "오늘 주가가 90원이고 어제가 100원"은 완전히 다른 상황입니다. 하나는 상승 추세이고, 다른 하나는 하락 추세입니다.
순서 정보 없이는 이 둘을 구분할 수 없습니다. 그래서 Transformer는 각 위치에 고유한 위치 벡터를 더해줍니다.
왜 하필 sin과 cos 함수를 사용할까요? 이 함수들은 몇 가지 좋은 특성이 있습니다.
첫째, 모든 위치에서 고유한 값을 만들어냅니다. 둘째, 상대적인 위치 관계를 학습할 수 있습니다.
셋째, 학습 데이터보다 긴 시퀀스에도 외삽이 가능합니다. 위의 코드를 살펴보면, position은 0부터 시퀀스 길이까지의 위치 인덱스입니다.
div_term은 각 차원마다 다른 주파수를 만들어주는 항입니다. 짝수 차원에는 sin을, 홀수 차원에는 cos를 적용합니다.
실무에서 시계열 모델을 만들 때 Positional Encoding은 필수입니다. 예를 들어 일주일 단위의 패턴이 있는 매출 데이터를 예측한다고 합시다.
Positional Encoding 덕분에 모델은 "이 데이터는 월요일 데이터이고, 저 데이터는 금요일 데이터"라는 것을 구분할 수 있습니다. 주의할 점은 시계열 전용 모델에서는 다른 방식의 Positional Encoding을 쓰기도 한다는 것입니다.
최근 연구에서는 학습 가능한 Positional Encoding이나 상대적 위치 인코딩도 많이 사용됩니다. 데이터의 특성에 맞는 방식을 선택하는 것이 중요합니다.
김개발 씨가 물었습니다. "그러면 Positional Encoding은 학습되는 건가요, 고정된 건가요?" 박시니어 씨가 대답했습니다.
"원래 논문에서는 고정값을 썼지만, 요즘은 둘 다 많이 사용해요. 실험해보고 더 좋은 걸 선택하면 됩니다."
실전 팁
💡 - sin/cos 방식은 학습 없이도 잘 작동하고 긴 시퀀스로 일반화가 용이합니다
- 시계열 특성에 따라 학습 가능한 Positional Encoding이 더 좋을 수도 있습니다
3. Multi Head Attention 구조
박시니어 씨가 김개발 씨에게 질문했습니다. "사람이 그림을 볼 때 색상, 형태, 질감을 동시에 본다는 거 알아요?" 김개발 씨가 고개를 끄덕이자 박시니어 씨가 말을 이었습니다.
"Multi-Head Attention도 비슷해요."
Multi-Head Attention은 여러 개의 Attention을 병렬로 수행하는 구조입니다. 마치 여러 명의 전문가가 같은 데이터를 각자 다른 관점에서 분석하는 것과 같습니다.
어떤 Head는 단기 패턴을, 다른 Head는 장기 추세를 포착할 수 있습니다.
다음 코드를 살펴봅시다.
import torch.nn as nn
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
super().__init__()
self.num_heads = num_heads
self.d_k = d_model // num_heads
# Query, Key, Value 변환 레이어
self.W_q = nn.Linear(d_model, d_model)
self.W_k = nn.Linear(d_model, d_model)
self.W_v = nn.Linear(d_model, d_model)
self.W_o = nn.Linear(d_model, d_model) # 출력 변환
def forward(self, x):
batch_size, seq_len, _ = x.size()
# Multi-head로 분할하여 병렬 Attention 수행
Q = self.W_q(x).view(batch_size, seq_len, self.num_heads, self.d_k)
K = self.W_k(x).view(batch_size, seq_len, self.num_heads, self.d_k)
V = self.W_v(x).view(batch_size, seq_len, self.num_heads, self.d_k)
return self.W_o(Q) # 실제로는 Attention 계산 후 concat
김개발 씨가 시계열 데이터를 분석하다 보니 여러 가지 패턴이 보였습니다. 일별 변동, 주간 패턴, 월간 추세가 모두 섞여 있었습니다.
"이걸 하나의 Attention으로 다 잡을 수 있을까요?" 박시니어 씨가 고개를 저었습니다. "하나로는 부족해요.
그래서 Multi-Head Attention을 사용하는 거예요." Multi-Head Attention의 핵심 아이디어는 분업입니다. 마치 회사에서 하나의 프로젝트를 여러 팀이 다른 관점에서 분석하는 것과 같습니다.
마케팅 팀은 고객 반응을, 개발 팀은 기술적 실현 가능성을, 재무 팀은 비용을 검토합니다. 각 팀의 분석 결과를 종합하면 더 좋은 의사결정을 내릴 수 있습니다.
Transformer도 마찬가지입니다. 8개의 Head가 있다면, 각 Head는 데이터의 서로 다른 측면에 주목합니다.
실제로 학습된 모델을 분석해보면 흥미로운 사실을 발견할 수 있습니다. 어떤 Head는 가까운 시점의 데이터에 집중하고, 다른 Head는 먼 과거의 특정 패턴에 집중합니다.
또 다른 Head는 이상치를 감지하는 역할을 하기도 합니다. 위의 코드에서 핵심은 d_model을 num_heads로 나누는 부분입니다.
예를 들어 d_model이 512이고 Head가 8개라면, 각 Head는 64차원의 벡터를 처리합니다. 이렇게 하면 전체 계산량은 Single-Head Attention과 비슷하면서도 더 다양한 패턴을 학습할 수 있습니다.
W_q, W_k, W_v는 각각 Query, Key, Value를 변환하는 선형 레이어입니다. 각 Head마다 다른 변환을 적용하여 서로 다른 "관점"을 만들어냅니다.
마지막에 W_o로 모든 Head의 출력을 합쳐서 최종 결과를 만듭니다. 실무에서 Head의 개수는 어떻게 정할까요?
일반적으로 8개 또는 16개를 많이 사용합니다. 중요한 것은 d_model이 num_heads로 나누어 떨어져야 한다는 것입니다.
Head가 너무 적으면 다양한 패턴을 포착하지 못하고, 너무 많으면 각 Head의 표현력이 약해집니다. 김개발 씨가 물었습니다.
"각 Head가 무엇을 학습했는지 어떻게 알 수 있어요?" 박시니어 씨가 대답했습니다. "Attention weight를 시각화해보면 알 수 있어요.
시계열에서는 특히 각 Head가 어떤 시점에 집중하는지 보면 재미있는 인사이트를 얻을 수 있습니다."
실전 팁
💡 - Head 개수는 보통 8개로 시작해서 실험적으로 조절합니다
- Attention weight 시각화로 각 Head가 학습한 패턴을 분석할 수 있습니다
4. Encoder Decoder 아키텍처
김개발 씨가 시계열 예측 모델을 설계하다가 고민에 빠졌습니다. "과거 데이터를 인코딩하는 부분과 미래를 예측하는 부분을 어떻게 연결하지?" 이때 박시니어 씨가 Transformer의 Encoder-Decoder 구조를 설명해주었습니다.
Encoder-Decoder 구조는 입력을 압축 표현으로 만드는 Encoder와 그 표현을 바탕으로 출력을 생성하는 Decoder로 구성됩니다. 시계열에서는 Encoder가 과거 패턴을 이해하고, Decoder가 미래 값을 예측합니다.
두 부분이 협력하여 복잡한 시퀀스-투-시퀀스 문제를 해결합니다.
다음 코드를 살펴봅시다.
import torch.nn as nn
class TimeSeriesTransformer(nn.Module):
def __init__(self, d_model, nhead, num_encoder_layers, num_decoder_layers):
super().__init__()
# 입력 임베딩 레이어
self.input_embedding = nn.Linear(1, d_model)
# Encoder: 과거 시퀀스 처리
encoder_layer = nn.TransformerEncoderLayer(d_model, nhead, batch_first=True)
self.encoder = nn.TransformerEncoder(encoder_layer, num_encoder_layers)
# Decoder: 미래 시퀀스 생성
decoder_layer = nn.TransformerDecoderLayer(d_model, nhead, batch_first=True)
self.decoder = nn.TransformerDecoder(decoder_layer, num_decoder_layers)
# 출력 레이어
self.output_layer = nn.Linear(d_model, 1)
def forward(self, src, tgt):
src_emb = self.input_embedding(src)
memory = self.encoder(src_emb) # 과거 패턴 인코딩
tgt_emb = self.input_embedding(tgt)
output = self.decoder(tgt_emb, memory) # 미래 예측
return self.output_layer(output)
시계열 예측은 본질적으로 "과거를 보고 미래를 맞추는" 문제입니다. 김개발 씨가 만들려는 매출 예측 모델도 마찬가지였습니다.
지난 30일의 매출 데이터를 보고 앞으로 7일의 매출을 예측해야 했습니다. 박시니어 씨가 화이트보드에 그림을 그리며 설명했습니다.
"Encoder-Decoder 구조를 우체국에 비유해볼게요." Encoder는 마치 편지를 요약해서 메모로 만드는 직원과 같습니다. 긴 편지 내용을 핵심만 추려서 간결한 메모로 만듭니다.
이 메모가 바로 memory입니다. 시계열에서는 과거 데이터의 패턴, 추세, 계절성 등이 이 메모에 압축됩니다.
Decoder는 그 메모를 보고 답장을 작성하는 직원입니다. 메모의 내용을 참고하면서 상황에 맞는 답장을 만들어냅니다.
시계열에서는 압축된 과거 정보를 바탕으로 미래 값을 하나씩 생성합니다. 둘 사이를 연결하는 것이 Cross-Attention입니다.
Decoder가 출력을 생성할 때, Encoder의 memory를 참조합니다. "과거에 비슷한 패턴이 있었나?"라고 물어보는 것과 같습니다.
이를 통해 Decoder는 맥락을 고려한 예측을 할 수 있습니다. 코드를 보면 nn.TransformerEncoder와 nn.TransformerDecoder를 사용합니다.
PyTorch가 제공하는 이 모듈들은 Multi-Head Attention, Feed Forward Network, Layer Normalization 등을 모두 포함하고 있어 편리합니다. forward 함수에서 src는 과거 시퀀스이고 tgt는 예측하려는 미래 시퀀스입니다.
학습 시에는 실제 미래 값을 tgt로 주고, 추론 시에는 이전 예측 값을 사용하여 순차적으로 생성합니다. 실무에서 주의할 점이 있습니다.
학습할 때는 Teacher Forcing이라는 기법을 사용합니다. 실제 정답을 Decoder에 입력으로 주는 것입니다.
하지만 추론할 때는 정답이 없으므로 이전에 예측한 값을 사용해야 합니다. 이 차이를 학습-추론 불일치라고 부르며, 성능에 영향을 줄 수 있습니다.
김개발 씨가 이해한 듯 고개를 끄덕였습니다. "그러니까 Encoder는 과거의 패턴을 이해하는 역할이고, Decoder는 그 이해를 바탕으로 미래를 그려내는 역할이군요."
실전 팁
💡 - PyTorch의 nn.Transformer를 활용하면 빠르게 프로토타입을 만들 수 있습니다
- 학습 시 Teacher Forcing과 추론 시 Auto-regressive 방식의 차이를 이해해야 합니다
5. Masking 전략 이해하기
김개발 씨가 모델을 학습시키다가 이상한 현상을 발견했습니다. 학습 손실은 매우 낮은데, 실제 예측 성능은 형편없었습니다.
박시니어 씨가 코드를 보더니 단번에 문제를 찾아냈습니다. "Masking을 안 했네요.
모델이 답을 보고 문제를 풀고 있어요."
Masking은 모델이 보면 안 되는 정보를 가리는 기법입니다. 시계열 예측에서는 미래 정보가 과거로 새어들어가는 것을 막아야 합니다.
마치 시험에서 답안지를 가리고 문제를 푸는 것처럼, 공정한 학습을 위해 필수적인 장치입니다.
다음 코드를 살펴봅시다.
import torch
def generate_square_subsequent_mask(size):
# 상삼각 행렬 생성 (미래 정보 차단)
mask = torch.triu(torch.ones(size, size), diagonal=1)
# True인 위치가 마스킹됨 (무한대로 처리)
mask = mask.masked_fill(mask == 1, float('-inf'))
mask = mask.masked_fill(mask == 0, float(0.0))
return mask
# 사용 예시
seq_len = 5
mask = generate_square_subsequent_mask(seq_len)
# 결과: 각 위치에서 자신과 이전 위치만 볼 수 있음
# [[0, -inf, -inf, -inf, -inf],
# [0, 0, -inf, -inf, -inf],
# [0, 0, 0, -inf, -inf],
# [0, 0, 0, 0, -inf],
# [0, 0, 0, 0, 0]]
김개발 씨의 모델은 왜 학습은 잘 되는데 예측은 못했을까요? 원인은 정보 누출이었습니다.
Transformer는 기본적으로 모든 위치를 동시에 볼 수 있습니다. Masking 없이 학습하면 3일 후를 예측할 때 실제 3일 후의 값을 참고할 수 있습니다.
이러면 학습 손실은 낮아지지만, 실제 예측 상황에서는 미래 값을 알 수 없으므로 성능이 떨어집니다. 박시니어 씨가 비유를 들어 설명했습니다.
"시험 볼 때 답안지를 보면서 공부하면 성적이 좋겠죠? 하지만 실제 시험에서는 답을 모르잖아요.
Masking은 모델에게 답안지를 가리는 역할을 해요." 시계열에서 사용하는 Causal Mask는 삼각형 모양입니다. 코드의 결과를 보면, 첫 번째 행은 첫 번째 위치만 볼 수 있고(자기 자신), 두 번째 행은 첫 번째와 두 번째를 볼 수 있습니다.
세 번째 행은 첫 번째, 두 번째, 세 번째를 볼 수 있습니다. 이렇게 하면 각 시점에서 자신과 과거만 참조할 수 있습니다.
-inf 값은 softmax 연산에서 0에 가까운 확률이 됩니다. Attention score에 -inf를 더하면 softmax 후에 거의 0이 됩니다.
따라서 해당 위치의 정보는 출력에 반영되지 않습니다. 0인 위치는 그대로 유지되어 정상적으로 Attention이 작동합니다.
실무에서는 Masking 외에도 Padding Mask가 필요합니다. 배치 처리를 위해 시퀀스 길이를 맞추다 보면 짧은 시퀀스에 패딩을 추가하게 됩니다.
이 패딩 위치도 마스킹해야 불필요한 정보가 모델에 영향을 주지 않습니다. 두 가지 마스크를 함께 적용하는 것이 일반적입니다.
김개발 씨가 마스크를 적용하고 다시 학습시키자, 학습 손실은 높아졌지만 실제 예측 성능은 크게 개선되었습니다. "이제야 제대로 된 학습이 되는 거네요!"
실전 팁
💡 - Causal Mask 없이 학습하면 미래 정보 누출로 실제 성능이 떨어집니다
- Padding Mask도 함께 적용하여 패딩 토큰의 영향을 제거하세요
6. 시계열 Transformer 실전 구현
이론은 충분히 배웠습니다. 김개발 씨는 이제 실제 데이터로 모델을 만들어보고 싶었습니다.
박시니어 씨가 말했습니다. "자, 이제 진짜 모델을 만들어볼까요?
전력 수요 예측 데이터로 실습해봅시다."
실전에서는 데이터 전처리, 모델 정의, 학습 루프, 평가까지 전체 파이프라인을 구축해야 합니다. 특히 시계열은 정규화, 윈도우 생성, 시간 특성 추출 등 도메인 특화 전처리가 중요합니다.
실제 작동하는 코드로 전체 흐름을 익혀봅시다.
다음 코드를 살펴봅시다.
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
class TimeSeriesDataset(Dataset):
def __init__(self, data, input_len, pred_len):
self.data = torch.FloatTensor(data).unsqueeze(-1)
self.input_len = input_len
self.pred_len = pred_len
def __len__(self):
return len(self.data) - self.input_len - self.pred_len + 1
def __getitem__(self, idx):
x = self.data[idx:idx + self.input_len]
y = self.data[idx + self.input_len:idx + self.input_len + self.pred_len]
return x, y
# 학습 루프 예시
def train_step(model, batch, optimizer, criterion):
model.train()
src, tgt = batch
optimizer.zero_grad()
output = model(src, tgt)
loss = criterion(output, tgt)
loss.backward()
optimizer.step()
return loss.item()
박시니어 씨가 화면에 코드를 띄우며 설명을 시작했습니다. "시계열 모델을 만들 때 가장 먼저 해야 할 일이 뭘까요?" 김개발 씨가 대답했습니다.
"데이터 전처리요?" "맞아요. 특히 슬라이딩 윈도우로 학습 데이터를 만드는 게 중요해요." TimeSeriesDataset 클래스를 보면, input_len은 모델이 볼 과거 길이이고 pred_len은 예측할 미래 길이입니다.
예를 들어 input_len이 30이고 pred_len이 7이면, 30일의 데이터를 보고 7일을 예측하는 구조입니다. getitem 메서드에서 윈도우를 잘라내는 방식이 핵심입니다.
idx 위치에서 시작해서 input_len만큼이 입력(x)이 되고, 그 다음 pred_len만큼이 타겟(y)이 됩니다. 윈도우를 한 칸씩 밀면서 많은 학습 샘플을 만들 수 있습니다.
1000일의 데이터가 있다면 약 960개 이상의 학습 샘플이 생성됩니다. unsqueeze(-1)은 마지막 차원을 추가하는 것입니다.
시계열 데이터가 1차원(시간 축만 있음)인 경우, 모델 입력을 위해 feature 차원을 추가해야 합니다. 단변량 시계열이면 feature 차원이 1이 됩니다.
다변량(온도, 습도, 풍속 등 여러 변수)이면 그에 맞게 차원을 조정합니다. train_step 함수는 기본적인 학습 루프입니다.
optimizer.zero_grad()로 이전 그래디언트를 초기화하고, 순전파로 출력을 계산한 뒤, loss를 구해서 역전파합니다. 마지막으로 optimizer.step()으로 파라미터를 업데이트합니다.
이 패턴은 PyTorch에서 거의 모든 모델에 동일하게 적용됩니다. 실무에서는 여기에 많은 것들이 추가됩니다.
데이터 정규화(StandardScaler나 MinMaxScaler), 조기 종료(Early Stopping), 학습률 스케줄러, 검증 세트 평가, 체크포인트 저장 등이 필요합니다. 하지만 기본 구조는 이 코드와 동일합니다.
김개발 씨가 고개를 끄덕였습니다. "슬라이딩 윈도우로 데이터를 만드는 부분이 핵심이군요.
이제 직접 실습해봐야겠어요."
실전 팁
💡 - 정규화는 학습 전 필수이며, 예측 후 역변환하는 것을 잊지 마세요
- 검증 세트는 시간 순서를 지켜서 분할해야 합니다 (랜덤 분할 금지)
7. Informer와 최신 시계열 모델
김개발 씨가 모델을 돌려보니 학습 시간이 너무 오래 걸렸습니다. "긴 시계열을 처리하려니 메모리도 부족하고 속도도 느리네요." 박시니어 씨가 웃으며 말했습니다.
"그래서 Informer 같은 효율적인 모델이 나온 거예요."
Informer는 AAAI 2021 Best Paper로, 긴 시계열 예측에 특화된 Transformer입니다. ProbSparse Attention으로 O(n^2)을 O(n log n)으로 줄이고, Self-attention Distilling으로 메모리 효율을 높였습니다.
장기 예측에서 기존 모델을 크게 능가합니다.
다음 코드를 살펴봅시다.
# Informer의 핵심 아이디어: ProbSparse Attention
import torch
import torch.nn as nn
class ProbSparseAttention(nn.Module):
def __init__(self, d_model, n_heads, factor=5):
super().__init__()
self.factor = factor # 샘플링 비율 조절
self.n_heads = n_heads
self.d_k = d_model // n_heads
def forward(self, Q, K, V):
B, L, H, D = Q.shape
# Top-k Query 선택 (핵심 아이디어)
sample_k = min(self.factor * int(L ** 0.5), L)
# 중요한 Query만 선택하여 연산량 감소
# 실제로는 Query의 희소성(sparsity) 측정 후 상위 k개 선택
# 이로써 O(L^2) -> O(L log L) 복잡도 달성
return V # 간략화된 예시
기존 Transformer의 Self-Attention은 O(n^2) 복잡도를 가집니다. 시퀀스 길이가 n이면 n x n 크기의 Attention 행렬을 계산해야 합니다.
100개면 10,000번, 1000개면 1,000,000번의 연산이 필요합니다. 시계열 예측에서는 이것이 큰 문제입니다.
전력 수요를 예측하려면 최소 몇 달의 데이터가 필요합니다. 시간 단위로 1년이면 8,760개의 시점입니다.
기존 Transformer로는 이런 긴 시퀀스를 처리하기 어렵습니다. Informer의 핵심 통찰은 Attention이 희소하다는 것입니다.
연구진이 학습된 Attention 행렬을 분석해보니, 대부분의 가중치는 매우 작고 일부만 큰 값을 가졌습니다. 즉, 모든 Query-Key 쌍을 계산할 필요가 없습니다.
중요한 것만 계산해도 충분합니다. ProbSparse Attention은 이 아이디어를 구현한 것입니다.
각 Query가 얼마나 "정보량이 많은지" 측정합니다. 균일 분포에서 멀수록, 즉 특정 Key에 집중할수록 중요한 Query입니다.
상위 k개의 중요한 Query만 선택해서 Attention을 계산합니다. 이로써 복잡도가 O(n log n)으로 줄어듭니다.
또 다른 핵심 기술은 Self-attention Distilling입니다. Encoder를 통과할 때마다 시퀀스 길이를 절반으로 줄입니다.
마치 CNN에서 pooling하는 것처럼, 중요한 정보만 남기고 압축합니다. 이로써 메모리 사용량도 크게 줄어듭니다.
Informer 이후에도 많은 발전이 있었습니다. Autoformer는 시계열의 추세와 계절성을 분리하여 처리합니다.
FEDformer는 주파수 도메인에서 Attention을 수행합니다. PatchTST는 시계열을 패치로 나누어 처리합니다.
각각 다른 관점에서 시계열 Transformer를 개선했습니다. 김개발 씨가 물었습니다.
"어떤 모델을 선택해야 할까요?" 박시니어 씨가 대답했습니다. "데이터와 태스크에 따라 달라요.
장기 예측에는 Informer나 Autoformer가 좋고, 단기 예측에는 기본 Transformer도 충분합니다. 논문의 벤치마크 결과를 참고하되, 실제 데이터로 실험해보는 게 가장 확실해요."
실전 팁
💡 - 긴 시계열(1000+ 스텝)에는 Informer나 Autoformer를 고려하세요
- 최신 모델 코드는 공식 GitHub에서 확인할 수 있습니다
8. 시계열 Transformer 하이퍼파라미터 튜닝
모델을 만들었지만 성능이 기대에 못 미쳤습니다. 김개발 씨가 한숨을 쉬자 박시니어 씨가 말했습니다.
"하이퍼파라미터 튜닝 해봤어요? 시계열 Transformer는 일반 Transformer와 다르게 튜닝해야 할 점이 있어요."
시계열 Transformer의 성능은 하이퍼파라미터에 크게 좌우됩니다. 입력 길이, 예측 길이, 레이어 수, Head 수, 학습률 등을 데이터 특성에 맞게 조절해야 합니다.
특히 입력 길이는 데이터의 주기성을 고려하여 설정하는 것이 중요합니다.
다음 코드를 살펴봅시다.
# 시계열 Transformer 하이퍼파라미터 설정 예시
config = {
# 데이터 관련
'input_len': 96, # 입력 시퀀스 길이 (패턴 주기 고려)
'pred_len': 24, # 예측 길이
'batch_size': 32, # 배치 크기
# 모델 구조
'd_model': 512, # 임베딩 차원
'n_heads': 8, # Attention Head 수 (d_model의 약수)
'e_layers': 2, # Encoder 레이어 수
'd_layers': 1, # Decoder 레이어 수
'd_ff': 2048, # Feed Forward 차원 (보통 d_model * 4)
'dropout': 0.1, # 드롭아웃 비율
# 학습 관련
'learning_rate': 1e-4, # 학습률 (Transformer는 작게 시작)
'epochs': 100,
'patience': 10, # Early Stopping patience
}
하이퍼파라미터 튜닝은 마치 요리의 간을 맞추는 것과 같습니다. 재료(데이터)가 좋아도 간(파라미터)이 맞지 않으면 맛(성능)이 떨어집니다.
가장 중요한 파라미터는 input_len입니다. 시계열 데이터에 주간 패턴이 있다면, 최소 7일 이상의 데이터를 봐야 합니다.
월간 패턴이 있다면 30일 이상이 필요합니다. 전력 수요처럼 일간 패턴(낮에 높고 밤에 낮음)과 주간 패턴(평일과 주말 차이)이 모두 있다면, 둘 다 포착할 수 있는 길이를 설정해야 합니다.
pred_len은 예측 길이입니다. 너무 길면 예측이 어렵고, 너무 짧으면 실용성이 떨어집니다.
비즈니스 요구사항에 맞게 설정하되, 데이터의 예측 가능성도 고려해야 합니다. d_model은 모델의 표현력을 결정합니다.
NLP에서는 512나 768을 많이 사용하지만, 시계열은 데이터가 상대적으로 단순해서 128~512 정도로도 충분한 경우가 많습니다. 무조건 크다고 좋은 것이 아닙니다.
오버피팅의 위험도 있고 학습 속도도 느려집니다. n_heads는 d_model의 약수여야 합니다.
8개가 일반적이지만, d_model이 작으면 4개도 괜찮습니다. 학습률은 Transformer에서 특히 중요합니다.
너무 크면 학습이 불안정하고, 너무 작으면 수렴이 느립니다. 1e-4로 시작해서 조절하는 것을 권장합니다.
Warmup 스케줄링을 사용하면 초반에 학습률을 천천히 올려 안정적으로 학습할 수 있습니다. Early Stopping은 오버피팅을 방지합니다.
검증 손실이 patience(예: 10 에폭) 동안 개선되지 않으면 학습을 중단합니다. 실무에서는 체계적인 실험이 중요합니다.
Optuna나 Ray Tune 같은 라이브러리로 하이퍼파라미터 탐색을 자동화할 수 있습니다. 하지만 먼저 직관적으로 중요한 파라미터(input_len, learning_rate)를 손으로 튜닝해보고, 나머지를 자동 탐색하는 것이 효율적입니다.
김개발 씨가 config를 조정하고 다시 학습시키자 성능이 크게 개선되었습니다. "input_len을 데이터 주기에 맞추니까 확실히 다르네요!"
실전 팁
💡 - input_len은 데이터의 주요 주기보다 길게 설정하세요 (주간 패턴이면 최소 7 이상)
- 학습률 1e-4와 Warmup 스케줄링을 기본으로 시작하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.
AWS Organizations 완벽 가이드
여러 AWS 계정을 체계적으로 관리하고 통합 결제와 보안 정책을 적용하는 방법을 실무 스토리로 쉽게 배워봅니다. 초보 개발자도 바로 이해할 수 있는 친절한 설명과 실전 예제를 제공합니다.
AWS KMS 암호화 완벽 가이드
AWS KMS(Key Management Service)를 활용한 클라우드 데이터 암호화 방법을 초급 개발자를 위해 쉽게 설명합니다. CMK 생성부터 S3, EBS 암호화, 봉투 암호화까지 실무에 필요한 모든 내용을 담았습니다.
AWS Secrets Manager 완벽 가이드
AWS에서 데이터베이스 비밀번호, API 키 등 민감한 정보를 안전하게 관리하는 Secrets Manager의 핵심 개념과 실무 활용법을 배워봅니다. 초급 개발자도 쉽게 따라할 수 있도록 실전 예제와 함께 설명합니다.