1 Temporal Convolutional Network (TCN)
1.1 LSTM의 한계 → TCN 동기
LSTM은 시점 \(t\)의 hidden state \(h_t\)를 계산하려면 반드시 \(h_{t-1}\)이 필요하다. 이 순차적 의존성 때문에:
| 문제 | 설명 |
|---|---|
| 병렬화 불가 | GPU의 병렬 연산 능력을 활용 못함 |
| 긴 시퀀스 느림 | T=100이면 100번 순차 연산 필요 |
| 메모리 비효율 | 역전파 시 모든 시점의 상태를 저장 |
TCN(Temporal Convolutional Network)은 합성곱(convolution)으로 시퀀스를 처리한다. 합성곱은 위치 독립적이므로 모든 시점을 동시에 계산할 수 있다.
LSTM: h₁ → h₂ → h₃ → h₄ → h₅ (순차: 5단계)
TCN: [x₁,x₂,x₃,x₄,x₅] → Conv → 한 번에 처리 (병렬: 1단계)
1.2 1D Convolution 복습
1.2.1 기본 개념
1D Convolution은 커널(filter)을 시퀀스 위에 슬라이딩하면서 적용한다.
입력 시퀀스 \(\mathbf{x} = (x_1, x_2, ..., x_T)\), 커널 \(\mathbf{w} = (w_1, w_2, ..., w_k)\)에 대해:
\[y_t = \sum_{i=0}^{k-1} w_i \cdot x_{t-i}\]
1.2.2 주요 파라미터
| 파라미터 | 의미 | 효과 |
|---|---|---|
| kernel_size (\(k\)) | 한 번에 보는 시점 수 | 클수록 넓은 범위 커버 |
| stride (\(s\)) | 커널 이동 간격 | 1이면 모든 시점 출력 |
| dilation (\(d\)) | 커널 원소 간 간격 | 클수록 넓은 범위를 희소하게 커버 |
| padding (\(p\)) | 입출력 길이 맞추기 | 입출력 길이 동일하게 유지 |
수용 범위(Receptive Field): 하나의 출력 값이 참조하는 입력 범위.
- 단일 층: \(\text{RF} = k + (k-1)(d-1) = 1 + (k-1) \cdot d\)
- \(L\)개 층 스택: \(\text{RF} = 1 + \sum_{l=1}^{L} (k-1) \cdot d_l\)
1.3 Causal Convolution: 미래 정보 차단
1.3.1 왜 필요한가
일반 Conv1D는 시점 \(t\)를 계산할 때 \(t\) 전후 모두를 참조한다. 시계열 예측에서는 미래 정보를 사용할 수 없다 (정보 누수).
1.3.2 원리: 왼쪽 패딩
Causal convolution은 커널이 현재와 과거 시점만 참조하도록 패딩을 왼쪽에만 추가한다.
일반 Conv (kernel=3):
입력: x₁ x₂ x₃ x₄ x₅
y₃ = w₁·x₂ + w₂·x₃ + w₃·x₄ ← x₄(미래) 참조!
Causal Conv (kernel=3, left padding=2):
입력: [0] [0] x₁ x₂ x₃ x₄ x₅
y₃ = w₁·x₁ + w₂·x₂ + w₃·x₃ ← 과거+현재만 참조
구현: padding = (kernel_size - 1) * dilation을 왼쪽에 추가하고, 출력에서 오른쪽을 잘라낸다.
1.4 Dilated Convolution: 지수적 수용 범위 확장
1.4.1 기본 Causal Conv의 한계
kernel_size=3인 Causal Conv를 3층 쌓으면:
- 수용 범위 = \(1 + 3 \times (3-1) = 7\) 시점
T=100인 시퀀스를 커버하려면 약 50층이 필요하다. 비효율적이다.
1.4.2 Dilated Convolution의 해결
Dilation factor \(d\)는 커널 원소 사이에 간격을 둔다.
dilation=1 (일반):
커널이 보는 위치: [t, t-1, t-2]
수용 범위: 3
dilation=2:
커널이 보는 위치: [t, t-2, t-4]
수용 범위: 5
dilation=4:
커널이 보는 위치: [t, t-4, t-8]
수용 범위: 9
1.4.3 지수적 dilation 스케줄
TCN은 dilation을 \(d = 2^l\) (\(l\)=층 번호)로 설정한다:
| 층 | dilation | kernel=3 기준 참조 범위 | 누적 수용 범위 |
|---|---|---|---|
| 1 | 1 | \([t, t-1, t-2]\) | 3 |
| 2 | 2 | \([t, t-2, t-4]\) | 7 |
| 3 | 4 | \([t, t-4, t-8]\) | 15 |
| 4 | 8 | \([t, t-8, t-16]\) | 31 |
| 5 | 16 | \([t, t-16, t-32]\) | 63 |
| 6 | 32 | \([t, t-32, t-64]\) | 127 |
수식:
\[\text{RF} = 1 + (k-1) \sum_{l=0}^{L-1} 2^l = 1 + (k-1)(2^L - 1)\]
kernel_size=3, 6개 층이면 \(\text{RF} = 1 + 2 \times 63 = 127\) 시점.
6개 층으로 127 시점을 커버한다. LSTM이 127번 순차 연산하는 것과 대비된다.
1.5 Residual Connection
1.5.1 필요성
TCN이 깊어지면 (10층 이상) 기울기 소실이 다시 발생한다. Residual connection이 이를 해결한다.
1.5.2 TemporalBlock 구조
입력 x ──→ CausalConv → ReLU → Dropout → CausalConv → ReLU → Dropout ──→ (+) → 출력
│ ↑
└────────────────── 1x1 Conv (차원 맞춤) ────────────────────────────────┘
\[\text{output} = \text{ReLU}(F(x) + x)\]
채널 수가 다르면 \(1 \times 1\) convolution으로 차원을 맞춘다.
1.6 PyTorch 구현: 완전한 TCN 모듈
1.6.1 CausalConv1d
import torch
import torch.nn as nn
import torch.nn.functional as F
class CausalConv1d(nn.Module):
"""인과적 1D 합성곱 — 미래 정보 차단"""
def __init__(self, in_channels, out_channels, kernel_size, dilation=1):
super().__init__()
self.padding = (kernel_size - 1) * dilation
self.conv = nn.Conv1d(
in_channels, out_channels, kernel_size,
dilation=dilation, padding=self.padding
)
# 가중치 초기화
nn.init.kaiming_normal_(self.conv.weight)
def forward(self, x):
out = self.conv(x)
# 오른쪽(미래) 패딩 제거 → causal 보장
if self.padding > 0:
out = out[:, :, :-self.padding]
return out1.6.2 TemporalBlock (Residual 포함)
class TemporalBlock(nn.Module):
"""TCN의 기본 블록: 2개 CausalConv + Residual"""
def __init__(self, in_channels, out_channels, kernel_size, dilation, dropout=0.2):
super().__init__()
# 첫 번째 CausalConv
self.conv1 = CausalConv1d(in_channels, out_channels, kernel_size, dilation)
self.bn1 = nn.BatchNorm1d(out_channels)
# 두 번째 CausalConv
self.conv2 = CausalConv1d(out_channels, out_channels, kernel_size, dilation)
self.bn2 = nn.BatchNorm1d(out_channels)
self.dropout = nn.Dropout(dropout)
self.relu = nn.ReLU()
# Residual connection (차원 맞춤)
self.downsample = (
nn.Conv1d(in_channels, out_channels, 1)
if in_channels != out_channels else None
)
def forward(self, x):
# 메인 경로
out = self.dropout(self.relu(self.bn1(self.conv1(x))))
out = self.dropout(self.relu(self.bn2(self.conv2(out))))
# Residual
res = self.downsample(x) if self.downsample else x
return self.relu(out + res)1.6.3 TCN 전체 모델
class TCN(nn.Module):
"""Temporal Convolutional Network — 종단 데이터 분류용"""
def __init__(self, input_size, num_channels, kernel_size=3, dropout=0.2):
"""
Args:
input_size: 입력 피처 수
num_channels: 각 층의 채널 수 리스트 (예: [32, 64, 64, 128])
kernel_size: 커널 크기
dropout: 드롭아웃 비율
"""
super().__init__()
layers = []
num_levels = len(num_channels)
for i in range(num_levels):
dilation = 2 ** i
in_ch = input_size if i == 0 else num_channels[i - 1]
out_ch = num_channels[i]
layers.append(
TemporalBlock(in_ch, out_ch, kernel_size, dilation, dropout)
)
self.network = nn.Sequential(*layers)
# 분류 헤드
self.classifier = nn.Sequential(
nn.Linear(num_channels[-1], 64),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(64, 1),
nn.Sigmoid()
)
# 수용 범위 계산
self.receptive_field = 1 + (kernel_size - 1) * (2 ** num_levels - 1)
def forward(self, x, mask=None):
"""
Args:
x: (batch, time, features)
mask: (batch, time) - 선택적
Returns:
(batch, 1) - 예측 확률
"""
# (batch, time, features) → (batch, features, time)
x = x.transpose(1, 2)
# TCN 통과
out = self.network(x)
# 마지막 유효 시점 추출
if mask is not None:
seq_lengths = mask.sum(dim=1).long()
last = out[torch.arange(x.size(0)), :, (seq_lengths - 1).clamp(min=0)]
else:
last = out[:, :, -1]
return self.classifier(last)1.6.4 수용 범위 확인
# 수용 범위 확인
channels = [32, 64, 64, 128] # 4개 층
model_tcn = TCN(input_size=4, num_channels=channels, kernel_size=3)
print(f"수용 범위: {model_tcn.receptive_field} 시점")
# 출력: 수용 범위: 31 시점
# 더 넓은 수용 범위가 필요하면
channels_deep = [32, 64, 64, 128, 128, 256] # 6개 층
model_tcn_deep = TCN(input_size=4, num_channels=channels_deep, kernel_size=3)
print(f"수용 범위: {model_tcn_deep.receptive_field} 시점")
# 출력: 수용 범위: 127 시점1.7 LSTM vs TCN 비교
1.7.1 이론적 비교
| 항목 | LSTM | TCN |
|---|---|---|
| 시퀀스 처리 | 순차적 (\(O(T)\) 단계) | 병렬 (\(O(\log T)\) 단계) |
| 수용 범위 | 이론적 무한, 실제 유한 | \(1 + (k-1)(2^L - 1)\), 설계로 결정 |
| 메모리 (학습) | 모든 시점 상태 저장 | 역전파 경로 짧음 |
| 메모리 (추론) | 상태만 유지 (효율적) | 전체 시퀀스 필요 |
| 가변 길이 | 자연스러움 | 패딩 필요 |
| 기울기 소실 | 게이트로 완화 | Residual + 짧은 경로 |
1.7.2 속도 벤치마크 (참고)
import time
# 속도 비교 (T=100, batch=64, features=4)
x = torch.randn(64, 100, 4)
mask = torch.ones(64, 100)
lengths = torch.full((64, 1), 100)
model_lstm = LSTMChurnPredictor(input_size=4, hidden_size=64)
model_tcn = TCN(input_size=4, num_channels=[32, 64, 64, 128, 128, 256])
# LSTM
start = time.time()
for _ in range(100):
_ = model_lstm(x, mask, lengths)
lstm_time = time.time() - start
# TCN
start = time.time()
for _ in range(100):
_ = model_tcn(x, mask)
tcn_time = time.time() - start
print(f"LSTM: {lstm_time:.2f}s, TCN: {tcn_time:.2f}s")
print(f"TCN 속도: LSTM 대비 {lstm_time/tcn_time:.1f}배 빠름")
# 참고 결과: TCN이 약 2~5배 빠름 (시퀀스 길이에 비례하여 차이 증가)1.7.3 정확도 비교 기준
시퀀스 길이 < 50: LSTM ≈ TCN (성능 비슷)
시퀀스 길이 50~200: TCN > LSTM (TCN 약간 우세)
시퀀스 길이 > 200: TCN >> LSTM (TCN 확실히 우세)
소규모 데이터: LSTM > TCN (LSTM이 파라미터 효율적)
1.8 실무 예시: AI Agent 세션 시퀀스 → 다음 만족도 예측
1.8.1 문제 설정
8주간 AI Agent 사용 기록에서 다음 주 만족도가 하락할지 예측한다.
# 데이터: 사용자별 주간 시퀀스
# 피처: satisfaction, turn_count, emotion_score, personalized
# 타겟: satisfaction_drop_next_week (0/1)
features = ["satisfaction", "turn_count", "emotion_score", "personalized"]
# TCN 학습
model = TCN(
input_size=len(features),
num_channels=[32, 64, 64], # 3층, 수용범위=15
kernel_size=3,
dropout=0.2
)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
criterion = nn.BCELoss()
for epoch in range(100):
model.train()
total_loss = 0
for seq, mask, target, lengths in train_loader:
pred = model(seq, mask)
loss = criterion(pred, target)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
if (epoch + 1) % 10 == 0:
print(f"Epoch {epoch+1}: Loss = {total_loss/len(train_loader):.4f}")1.8.2 TCN의 장점이 빛나는 경우
사례: 대규모 AI Agent 플랫폼
- 사용자 수: 100,000명
- 시퀀스 길이: 최대 52주 (1년)
- 일일 배치 추론 필요
LSTM: 52 시점 × 100K 사용자 → 순차 연산으로 ~30분
TCN: 병렬 연산 → ~8분 (약 4배 빠름)
1.9 요약
| 항목 | 내용 |
|---|---|
| TCN 핵심 | Causal Conv + Dilated Conv + Residual |
| Causal Conv | 왼쪽 패딩으로 미래 정보 차단 |
| Dilated Conv | dilation=\(2^l\)로 지수적 수용 범위 확장 |
| 수용 범위 | \(1 + (k-1)(2^L - 1)\), 6층이면 127 시점 |
| LSTM 대비 장점 | 병렬 연산, 긴 시퀀스 효율, 안정적 기울기 |
| LSTM 대비 단점 | 가변 길이 처리 덜 자연스러움, 추론 시 전체 시퀀스 필요 |
다음: 27 — Temporal Transformer: Self-Attention으로 시점 간 관계를 직접 학습