VAE와 그래프 표현 학습으로 설계하는 이상 탐지

VAE와 그래프 기반 이상 탐지의 모델링, 점수화, 임계값 보정, 드리프트 운영 방식을 정리합니다.

2026-08-14 · 최초 발행 2025-10-14

정상 분포와 관계망에서 이상을 읽는 방식

규칙만으로는 복합적인 이상 징후를 모두 포착하기 어렵다. VAE 기반 이상 탐지는 정상 데이터의 분포를 학습한 뒤 재구성 오차 또는 음의 로그가능도(−log p(x))를 이상 점수로 사용한다. ELBO 최적화와 KL 정규화는 잠재 공간의 연속성과 일반화를 확보하는 데 쓰인다.

그래프 기반 이상 탐지는 데이터 간 관계를 그래프로 표현한다. 노드, 엣지, 부분그래프에서 구조적 또는 문맥적으로 벗어난 대상을 탐지하며, DeepWalk·node2vec 임베딩이나 GNN의 Message Passing으로 표현을 학습한 뒤 통계·밀도 기반 점수로 판별한다.

입력부터 알림까지 연결되는 탐지 경로

드리프트·성능 저하에러입력 데이터탭룰러·시계열이벤트·로그그래프 엣지 리스트전처리스케일링·인코딩·결측 처리모델 분기VAE 학습·추론q(z|x), p(x|z)그래프 표현 학습node2vec·GNN점수 산출재구성 오차·−ELBO점수 산출임베딩 거리·LOF·링크 잔차임계값 보정퍼센타일·EVT알림·티켓·대시보드모니터링PSI·KS, 성능·지연재학습·임계값 재보정에러 처리결측 과다·고립 노드·스키마변경샘플 드롭·대치·경고·스키마롤백

탭룰러·시계열·이벤트·로그·그래프 엣지 리스트를 입력으로 받아 스케일링, 인코딩, 결측 처리를 수행한다. 이후 VAE와 그래프 표현 학습 경로에서 각각 점수를 만들고, 퍼센타일 또는 EVT로 임계값을 보정해 알림과 운영 시스템으로 연결한다.

전처리 단계에서는 입력 정규화, 결측 대치, 카디널리티 억제를 적용한다. 타겟 인코딩이나 빈도 컷오프를 쓸 수 있으며, 스케일러와 인코더는 학습·추론 간 일관성을 위해 아티팩트로 버저닝한다. 그래프에서는 ID 매핑, 다중 관계 레이어 분리, 연결 성분 처리와 고립 노드 정책이 함께 필요하다.

점수화 방법과 운영상 차이

VAE는 인코더 q(z|x), 디코더 p(x|z), 재분산 기법으로 구성된다. β-VAE에서는 β를 조정해 표현력과 규제 사이의 균형을 잡는다. 연속형 데이터는 Gaussian, 이산형 데이터는 Bernoulli 또는 Concrete로 모델링할 수 있다. 점수는 재구성 MSE·MAE, 음의 ELBO를 사용할 수 있고, 퍼센타일 기반 임계값이나 EVT 피팅으로 보정한다. OOD 특징량에는 마할라노비스 거리를 보조 채널로 둘 수 있다.

그래프 모델은 node2vec·DeepWalk 임베딩 또는 GCN·GAT 같은 GNN을 활용한다. 정적 그래프는 배치 리빌드, 동적 그래프는 증분 업데이트 전략을 선택한다. 임베딩 거리, 지역 밀도(LOF), 링크 예측 잔차, 이상 차수와 삼각형 계수 편차 같은 구조 지표를 결합해 앙상블 점수를 만들 수 있다.

관점 VAE 기반 그래프 기반
적합한 신호 독립적이거나 약한 상관의 특성, 비선형 패턴 관계성·구조적 이상, 문맥 의존 패턴
확장 방식 미니배치 추론과 선형 확장 임베딩·메시지 패싱 비용을 고려한 샤딩·증분 처리
재현성 관리 피처 아티팩트 관리 그래프 스냅샷 동기화와 ID 안정성
주의할 변화 OOD 입력과 임계값 보정 토폴로지 변동과 완화 전략
운영 난이도 배치·스트리밍 처리에 적합 데이터 조인·ID 관리·증분 업데이트가 복잡

탐지 효과가 필요한 장면

금융 사기 탐지에서는 거래 탭룰러 데이터를 VAE로 점수화하고, 고객-가맹점 그래프에서 고립 서브그래프와 비정상 링크를 병행해 찾을 수 있다. 룰·시그니처 방식보다 재현율을 높이는 동시에 오탐률을 관리하는 방식이다.

제조 설비에서는 센서 시계열을 윈도잉해 VAE 점수를 계산하고, 라인과 설비 사이의 상호의존 그래프에서 문맥적 이상을 탐지해 원인 구간을 줄인다. IT 운영과 보안에서는 서비스 호출 그래프의 비정상 경로, 계정-자원 접근 그래프의 권한 남용을 대상으로 삼을 수 있으며, 일일 배치와 분 단위 스트리밍 파이프라인을 함께 운영한다.

규칙 기반과 비교해 재현율(Recall)을 1535%p 높이고 동등 오탐률(FPR)을 유지할 수 있다. >1e7 이벤트/일 규모에서는 분산 추론으로 p95 지연 200500ms 수준을 달성하며, 임베딩 재학습을 주간 배치로 제한하면 GPU 시간을 30~60% 절감할 수 있다. 새 패턴에 대한 적응력과 관계성 기반 설명가능성도 높아지고, 드리프트 내성과 정책 의존도 측면의 이점이 있다.

운영 안정성을 위한 선택

모델, 스케일러, 인코더, 임계값은 함께 버전을 고정하고 스키마 변경을 감지했을 때 안전하게 롤백할 수 있어야 한다. 다만 고정 정책이 엄격할수록 온더플라이 개선은 늦어진다.

임계값은 밸리데이션 상위 q-퍼센타일과 EVT Tail Fit을 조합하고, 시간대와 셀별 동적 베이스라인을 적용할 수 있다. 다중 임계값은 알림 품질을 높일 수 있지만 운영 복잡도를 키운다. 하드 임계값과 요일·시간대별 가변 베이스라인을 섞고, 서프레션 쿨다운과 케이스 머징을 두면 알림 소음을 줄일 수 있다.

PSI·KS를 주기적으로 확인하고, AUROC·AUPRC 성능 가드레일과 최근 N일 데이터 커팅을 운영에 넣는다. 카나리 또는 블루그린 배포도 재학습의 영향을 제한하는 데 유용하다. 잦은 재학습은 비용과 과적합 위험을 높인다.

그래프의 신규 노드는 근접 이웃 평균으로 Warm-start할 수 있으며, node2vec 임베딩을 증분 학습하거나 주기적으로 리셋할 수 있다. 증분 품질 저하와 전체 리빌드 비용 사이의 선택이며, 고립 노드는 별도 정책으로 다뤄야 한다. 학습·추론 경로 암호화, 입력 스키마 서명, 데이터 최소 수집, 이상 알림 접근 통제도 함께 고려한다. 개인정보 비식별화는 탐지 감도를 낮출 수 있다.

VAE로 탭룰러 데이터를 점수화하는 예시

Python 3.9+, CPU 환경을 가정한다. 패키지는 numpy, scikit-learn, torch(>=2.0), networkx, node2vec이며, 설치 예시는 pip install numpy scikit-learn torch networkx node2vec이다.

# env: Python 3.9+, torch>=2.0
import torch, torch.nn as nn, torch.nn.functional as F
from sklearn.datasets import make_classification
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
import numpy as np

# 1) 데이터 생성(정상/이상 혼합)
X, y = make_classification(n_samples=10000, n_features=20, n_informative=8,
                           n_redundant=2, weights=[0.97, 0.03], random_state=42)
X_train, X_val, y_train, y_val = train_test_split(X[y==0], y[y==0], test_size=0.2, random_state=42)  # 정상만 학습
scaler = StandardScaler().fit(X_train)
X_train = scaler.transform(X_train).astype(np.float32)
X_val_all = scaler.transform(X_val).astype(np.float32)
X_test = scaler.transform(X).astype(np.float32)
y_test = y

# 2) VAE 정의
class VAE(nn.Module):
    def __init__(self, d, h=64, z=16):
        super().__init__()
        self.enc = nn.Sequential(nn.Linear(d, h), nn.ReLU(), nn.Linear(h, h), nn.ReLU())
        self.mu = nn.Linear(h, z)
        self.logvar = nn.Linear(h, z)
        self.dec = nn.Sequential(nn.Linear(z, h), nn.ReLU(), nn.Linear(h, d))
    def encode(self, x):
        h = self.enc(x)
        return self.mu(h), self.logvar(h)
    def reparam(self, mu, logvar):
        std = torch.exp(0.5*logvar)
        eps = torch.randn_like(std)
        return mu + eps * std
    def decode(self, z):
        return self.dec(z)
    def forward(self, x):
        mu, logvar = self.encode(x)
        z = self.reparam(mu, logvar)
        recon = self.decode(z)
        return recon, mu, logvar

def elbo_loss(x, recon, mu, logvar, beta=1.0):
    recon_loss = F.mse_loss(recon, x, reduction='mean')
    kld = -0.5 * torch.mean(1 + logvar - mu.pow(2) - logvar.exp())
    return recon_loss + beta * kld, recon_loss, kld

# 3) 학습
device = "cpu"
model = VAE(d=X_train.shape[1]).to(device)
opt = torch.optim.Adam(model.parameters(), lr=1e-3)
Xtr = torch.tensor(X_train)
for epoch in range(30):
    model.train()
    idx = torch.randperm(len(Xtr))
    for i in range(0, len(Xtr), 256):
        batch = Xtr[idx[i:i+256]].to(device)
        recon, mu, logvar = model(batch)
        loss, _, _ = elbo_loss(batch, recon, mu, logvar, beta=1.0)
        opt.zero_grad(); loss.backward(); opt.step()

# 4) 스코어링: −ELBO 대용으로 재구성 오차 사용
model.eval()
with torch.no_grad():
    Xt = torch.tensor(X_test).to(device)
    recon, mu, logvar = model(Xt)
    mse = torch.mean((recon - Xt)**2, dim=1).cpu().numpy()

# 5) 임계값: 정상 검증 분포 99퍼센타일
with torch.no_grad():
    Xv = torch.tensor(X_val_all).to(device)
    rv, _, _ = model(Xv)
    val_mse = torch.mean((rv - Xv)**2, dim=1).cpu().numpy()
threshold = float(np.quantile(val_mse, 0.99))
pred = (mse > threshold).astype(int)

# 6) 간단 평가
from sklearn.metrics import classification_report
print("Threshold:", threshold)
print(classification_report(y_test, pred, digits=4))

이산 피처에는 적절한 인코딩이 필요하며, OOD 입력에는 마할라노비스 거리 보조 채널을 고려한다.

관계망의 밀도를 이용한 이상 탐지 예시

# env: Python 3.9+, networkx, node2vec, scikit-learn
import networkx as nx
from node2vec import Node2Vec
import numpy as np
from sklearn.neighbors import LocalOutlierFactor

# 1) 그래프 생성(정상 커뮤니티 + 이상 고립 노드)
G = nx.barabasi_albert_graph(800, 3, seed=42)
isolates = [900+i for i in range(20)]
G.add_nodes_from(isolates)
# 일부 이상 엣지(무작위 연결)
for i in range(50):
    G.add_edge(np.random.randint(0, 800), np.random.choice(isolates))

# 2) 임베딩 학습(node2vec)
n2v = Node2Vec(G, dimensions=64, walk_length=20, num_walks=200, p=1, q=1, workers=1, seed=42)
model = n2v.fit(window=10, min_count=1, batch_words=256)
nodes = list(G.nodes())
X = np.vstack([model.wv[str(n)] for n in nodes])

# 3) 밀도 기반 이상 탐지(LOF)
lof = LocalOutlierFactor(n_neighbors=20, contamination=0.03, novelty=False)
y_pred = lof.fit_predict(X)  # -1: 이상
scores = -lof.negative_outlier_factor_

# 4) 결과 매핑
node_scores = dict(zip(nodes, scores))
print(sorted(node_scores.items(), key=lambda x: x[1], reverse=True)[:10])

동적 그래프에서는 최근 엣지만 반영하는 증분 임베딩이나 GNN 미니배치 추론을 고려할 수 있다. 이때 임베딩과 식별자(ID)의 안정성을 관리해야 한다.

검증과 예외를 운영 경로에 포함하기

오프라인에서는 AUROC·AUPRC, PR@k, Alert-per-Case를 확인하고 합성 이상 주입으로 민감도를 점검한다. 상위 점수 케이스를 수동 라벨링하고, 재구성 오차의 Shapley와 임베딩 근접 이웃을 통해 피처 기여를 검토한다. 카나리 배포에서는 트래픽 5~10%로 사전 검증하고, 오탐률 가드레일과 롤백 스위치를 둔다.

스키마가 바뀌면 피처 카탈로그 이력을 비교해 미일치 입력을 차단하고 이전 버전 모델로 페일오버한다. 결측 또는 희소성이 허용 임계를 넘은 샘플은 드롭하고 경고 로그를 남긴다. 범주형 신규 값은 “UNK” 버킷으로 처리한다. 그래프가 단절됐을 때는 고립 노드를 차수·클러스터링 계수의 별도 점수 채널로 평가하고, 임베딩을 생성하지 못하면 기본 위험도를 적용한다.

VAE는 정상 분포를 기준으로 한 범용적 이상 판별에, 그래프 기반 탐지는 관계와 구조의 문맥적 이상에 강점이 있다. 탭룰러 스코어와 그래프 스코어를 앙상블하면 재현율과 정밀도의 균형을 조정할 수 있으며, 이를 뒷받침하려면 아티팩트 버저닝, 임계값 보정, 드리프트 감시, 증분 업데이트를 하나의 운영 체계로 다뤄야 한다.

이상 탐지변분 오토인코더그래프 분석머신러닝드리프트 감지