TCN과 Informer로 설계하는 장기 시계열 예측

TCN과 Informer의 구조, 장기 의존성 학습 방식, 시계열 예측 운영 조건과 모델 선택 기준을 정리한다.

2026-08-14 · 최초 발행 2024-04-29

RNN 계열의 순차 처리 이후에 선택할 수 있는 모델

RNN과 LSTM은 시계열 학습에 널리 쓰였지만, 순차 처리 구조 때문에 병렬화가 제한되고 긴 의존 관계를 다루는 데 부담이 있다. 이 제약을 줄이기 위해 CNN과 Transformer 계열이 시계열 문제로 확장됐고, 그 흐름에서 TCN과 Informer는 서로 다른 장점을 가진다.

TCN은 미래 정보를 보지 않는 인과 합성곱과 팽창 합성곱을 결합한 시계열용 CNN 아키텍처다. 잔차 블록을 통해 깊이를 확보하면서 넓은 수용영역을 만들고, 전 구간 합성곱을 병렬로 처리한다.

Informer는 Long Sequence Forecasting(LSF)을 겨냥한 Transformer 모델이다. ProbSparse Self-Attention으로 O(L log L)의 근사 복잡도를 달성하고, Distilling 단계로 시퀀스 길이를 줄인다. Generative Decoder는 장기 다중스텝 예측에 맞춰 설계됐다.

모델 내부에서 긴 의존성을 다루는 방식

TCN의 핵심은 Causal Conv1d와 Dilated Conv의 조합이다. 인과성으로 미래 정보 누출을 막고, 팽창 비율을 키워 수용영역을 지수적으로 넓힌다. Residual block과 LayerNorm 또는 Dropout은 깊은 네트워크의 학습 안정성을 높이고 기울기 소실을 완화한다. 합성곱은 구간 전체를 병렬 처리하므로 GPU 활용과 추론 지연 측면에서도 유리하다.

Informer는 중요 쿼리를 중심으로 Attention을 희소화해 메모리와 시간 복잡도를 줄인다. Encoder Distilling은 Conv와 Pooling으로 길이를 단계적으로 축소하면서 잡음을 줄이고 요약 표현을 강화한다. Decoder는 autoregressive 또는 병렬 예측 설정을 지원하며, 다변량 장기 다스텝 예측에 적합하다.

입력 단계에서는 시계열 정렬, 결측치 처리, 스케일링, 캘린더·외생 변수 인코딩이 필요하다. 이후 수용영역이나 윈도 길이, 채널·헤드, 드롭아웃을 조정하며 모델을 탐색한다. 결과는 다스텝 예측으로 내보내고, 필요하면 엔셈블이나 MC Dropout을 통한 불확실성 추정과 이상치·재스케일 후처리를 붙인다.

TCN은 지연과 메모리 안정성이 좋지만, 초장기 의존성을 포착하려면 더 깊은 팽창 레벨이 필요하다. Informer는 초장기 시퀀스와 고차원 변수에 강점이 있는 대신 메모리 피크와 구현 복잡도를 고려해야 한다. 두 모델 모두 데이터 드리프트 감지, 윈도 재학습 주기, 슬라이딩 배치 추론 설계가 운영 품질에 영향을 준다.

학습부터 배포까지 이어지는 흐름

TCNInformer조건결측 폭증지연 초과오차 급등입력 데이터(시계열, 외생 변수)전처리- 정렬/리샘플링- 결측/이상치 처리- 스케일링모델 선택TCN 구성- Causal/Dilated Conv- Residual BlocksInformer 구성- ProbSparse Attention- Distilling학습- MAE/MSE/Huber- 조기 종료검증- 롤링 윈도 평가- 계절성/드리프트 체크배포온라인 추론- 슬라이딩 윈도- 배치/스트리밍배치 예측- T+1..T+K모니터링- 지표·드리프트·오류오류/이탈

예측 길이와 운영 조건에 따른 차이

항목 TCN Informer
성능(장기 예측) 중상: 수용영역 충분 시 우수 상: 초장기·다변량에 강점
확장성(시퀀스 길이) 우수: 합성곱 병렬, O(L) 우수: ProbSparse, O(L log L)
일관성(예측 안정성) 높음: 구조적 제약, 낮은 분산 중상: 복잡도↑, 튜닝 민감도 존재
안정성(학습/메모리) 높음: 메모리 안정, 구현 단순 중: 메모리 피크, 세팅 복잡
운영 편의(배포/지연) 높음: 저지연, 간단 배포 중: 의존성·튜닝 관리 필요

초장기·고차원·멀티스텝 예측에서는 Informer가 우위를 보일 수 있다. 반대로 낮은 지연, 단순한 운영, 중장기 예측이 중요한 환경에서는 TCN이 더 잘 맞는다.

도메인별로 달라지는 선택 기준

전력 수요나 재생에너지 출력은 변동성과 계절성이 강하고, 15~60분 해상도의 다변량 입력을 다룬다. 단기 운영 최적화에는 TCN을, 일간 또는 주간 horizon에는 Informer를 적용할 수 있다.

수요 예측과 재고 최적화는 프로모션, 캘린더, 가격 같은 외생 변수가 많다. Informer는 장기 프로모션 효과를 포착하는 데 쓰고, TCN은 단기 진동과 주기성을 안정적으로 추정하는 데 활용할 수 있다.

교통과 플릿 ETA 예측은 실시간 저지연 요구가 강하다. 엣지나 온디바이스 추론에는 TCN을 두고, 클라우드 대규모 분석에는 Informer를 결합하는 하이브리드 구성이 가능하다.

예지보전 센서 시퀀스는 고빈도 데이터와 많은 이상치를 특징으로 한다. TCN으로 로버스트한 특징을 추출한 뒤 이상을 탐지하고, 장기 열화 추세는 Informer로 보완하는 방식이 가능하다.

기대할 수 있는 변화와 전제

데이터와 환경에 따라 TCN 도입 시 추론 지연은 3070% 감소하고 GPU 메모리 사용은 2040% 절감될 수 있다. Informer 도입 시에는 초장기(>512 스텝) 예측 MAE가 515% 개선되고 학습 시간이 2050% 단축될 수 있다.

병렬화는 스케일 아웃을 쉽게 만들고 배치와 스트리밍을 함께 운영하기 좋은 구조를 제공한다. 장기 의존성을 더 잘 포착하면 계획과 스케줄링 품질, 알람과 운영 의사결정의 신뢰성도 높일 수 있다.

PyTorch로 보는 최소 TCN 구현

전제조건은 다음과 같다.

  • Python 3.10+, PyTorch 2.2+, CUDA 선택
  • pip install torch numpy scikit-learn
# tcn_minimal.py
# 실행: python tcn_minimal.py
import math
import numpy as np
import torch
import torch.nn as nn
from sklearn.preprocessing import StandardScaler

# ----- 데이터: AR 시계열 생성 및 윈도우 구성 -----
def make_series(n=20000, noise=0.1):
    x = np.zeros(n, dtype=np.float32)
    for t in range(3, n):
        x[t] = 0.6*x[t-1] - 0.2*x[t-3] + 0.1*np.sin(2*math.pi*t/50) + noise*np.random.randn()
    return x

def make_windows(x, in_len=64, out_len=16):
    X, Y = [], []
    for i in range(len(x) - in_len - out_len):
        X.append(x[i:i+in_len])
        Y.append(x[i+in_len:i+in_len+out_len])
    X = np.array(X)[:, None, :]  # (N, C=1, L)
    Y = np.array(Y)  # (N, out_len)
    return X.astype(np.float32), Y.astype(np.float32)

# ----- TCN 블록 -----
class TemporalBlock(nn.Module):
    def __init__(self, in_ch, out_ch, k=3, dilation=1, dropout=0.1):
        super().__init__()
        pad = (k - 1) * dilation  # causal padding
        self.conv1 = nn.Conv1d(in_ch, out_ch, k, padding=pad, dilation=dilation)
        self.relu1 = nn.ReLU()
        self.dropout1 = nn.Dropout(dropout)
        self.conv2 = nn.Conv1d(out_ch, out_ch, k, padding=pad, dilation=dilation)
        self.relu2 = nn.ReLU()
        self.dropout2 = nn.Dropout(dropout)
        self.downsample = nn.Conv1d(in_ch, out_ch, 1) if in_ch != out_ch else None
        self.init_weights()

    def init_weights(self):
        for m in [self.conv1, self.conv2]:
            nn.init.kaiming_normal_(m.weight)
            nn.init.zeros_(m.bias)

    def forward(self, x):
        # keep causality by trimming right padding
        out = self.conv1(x)
        out = out[:, :, :x.size(2)]
        out = self.relu1(out)
        out = self.dropout1(out)
        out = self.conv2(out)
        out = out[:, :, :x.size(2)]
        out = self.relu2(out)
        out = self.dropout2(out)
        res = x if self.downsample is None else self.downsample(x)
        res = res[:, :, :out.size(2)]
        return out + res

class TCN(nn.Module):
    def __init__(self, in_ch=1, chs=(32,64,64), k=3, dropout=0.1, out_len=16):
        super().__init__()
        layers = []
        prev = in_ch
        for i, c in enumerate(chs):
            layers.append(TemporalBlock(prev, c, k=k, dilation=2**i, dropout=dropout))
            prev = c
        self.network = nn.Sequential(*layers)
        self.head = nn.Sequential(
            nn.AdaptiveAvgPool1d(1),
            nn.Flatten(),
            nn.Linear(prev, out_len)
        )

    def forward(self, x):
        feat = self.network(x)
        return self.head(feat)

def train(device="cuda" if torch.cuda.is_available() else "cpu"):
    # 데이터 생성
    raw = make_series()
    scaler = StandardScaler()
    raw_s = scaler.fit_transform(raw.reshape(-1,1)).flatten()
    X, Y = make_windows(raw_s, in_len=64, out_len=16)
    n = len(X)
    idx = int(n*0.8)
    Xtr, Ytr = torch.tensor(X[:idx]).to(device), torch.tensor(Y[:idx]).to(device)
    Xva, Yva = torch.tensor(X[idx:]).to(device), torch.tensor(Y[idx:]).to(device)

    # 모델
    model = TCN(in_ch=1, chs=(32,64,64), k=3, dropout=0.1, out_len=16).to(device)
    opt = torch.optim.AdamW(model.parameters(), lr=3e-3, weight_decay=1e-4)
    sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=30)
    loss_fn = nn.SmoothL1Loss()

    # 학습
    model.train()
    bs = 256
    for epoch in range(30):
        # 셔플 미니배치
        perm = torch.randperm(Xtr.size(0), device=device)
        ep_loss = 0.0
        for i in range(0, Xtr.size(0), bs):
            idxb = perm[i:i+bs]
            xb, yb = Xtr[idxb], Ytr[idxb]
            opt.zero_grad(set_to_none=True)
            pred = model(xb)
            loss = loss_fn(pred, yb)
            loss.backward()
            nn.utils.clip_grad_norm_(model.parameters(), 1.0)
            opt.step()
            ep_loss += loss.item() * xb.size(0)
        sched.step()

        # 검증
        model.eval()
        with torch.no_grad():
            va_loss = loss_fn(model(Xva), Yva).item()
        model.train()
        print(f"epoch {epoch+1:02d} train_loss={ep_loss/len(Xtr):.4f} val_loss={va_loss:.4f}")

    # 예측 데모
    model.eval()
    with torch.no_grad():
        sample = Xva[:1]
        pred = model(sample).cpu().numpy()[0]
        print("예측(표준화 스케일):", np.round(pred[:5], 3))

if __name__ == "__main__":
    train()

입력 모양은 (배치, 채널=1, 길이)를 유지한다. 수용영역은 Σ dilation별 (kernel_size-1)*d + 입력 길이 근사로 보고, 예측 horizon에 비해 충분히 확보해야 한다. 오버피팅 경향이 보이면 Dropout을 높이고 채널을 줄이거나 조기 종료를 적용한다.

Informer는 ProbSparse Attention, Distilling, Decoder를 포함하는 전용 구현이 필요하다. 공개 레퍼런스(Informer 2020, PyTorch 구현) 활용 권장(최신 정보 확인 필요).

데이터와 운영 제약을 모델 설계에 반영하기

결측치는 시계열 보간과 마스크 피처를 함께 사용한다. 스케일링은 그룹 또는 아이템별 RobustScaler·StandardScaler를 적용하고, 예측 뒤에는 역변환한다.

TCN은 팽창 레벨을 1,2,4,…로 확대하며 수용영역을 horizon의 24배로 설정한다. Informer는 윈도 길이 5122048을 탐색하고 헤드·임베딩 균형과 Distilling 깊이를 조절한다. 이상치와 불확실성에는 Huber 또는 Quantile 손실을 사용할 수 있다.

운영 단계에서는 슬라이딩 배치 추론으로 지연을 평준화하고 GPU 활용률을 높인다. PSI, KS, MAE 이동평균으로 드리프트를 감지해 기준 초과 시 재학습을 트리거한다. 엣지처럼 리소스가 제한된 환경에서는 TCN을 우선하고, 클라우드에서 대규모 LSF를 처리할 때는 Informer를 채택한다.

운영 제약과 지연 요구가 크다면 TCN을 먼저 평가할 수 있다. 장기 예측 정확도가 최우선인 경우에는 Informer를 우선 검토하고, 필요하면 하이브리드나 엔셈블로 두 모델을 상호 보완한다.

시계열 예측딥러닝TCNInformerTransformer