BDAI FinDA 금융 데이터 분석 심화 과정

FDS 프로젝트 회고 — 시스템을 설계한다는 것의 의미

cheshire5 2026. 2. 26. 07:50

들어가며

모델을 돌리는 건 누구나 할 수 있다.

sklearn 불러서 fit하고 classification_report 뽑는 건 튜토리얼 한 번이면 된다.
그게 프로젝트의 핵심이 되어선 안 된다고 생각했다.

이번 프로젝트에서 내가 집중한 건 하나였다.

실사용을 고려해서 설계했는가.

온라인 카드 거래 사기 탐지 시스템, FDS를 만들면서 배운 것들을 정리한다.


1. 문제는 모델이 아니라 제약이었다

프로젝트 초반에 이런 질문을 했다.

"FDS는 왜 어려운가?"

답은 두 단어였다. LatencyContext.

온라인 카드 거래는 실시간으로 판별해야 한다.
거래가 들어오는 순간 승인 또는 차단 결정이 나야 한다.

그런데 사기를 정확하게 탐지하려면 맥락이 필요하다.
이 고객이 평소에 어떤 업종을 썼는지,
최근에 이상한 패턴이 있었는지,
이런 히스토리 기반 정보가 있어야 정확한 판단이 가능하다.

문제는 이 두 가지가 충돌한다는 것이다.


빠르게 판단하려면 → 히스토리를 볼 시간이 없다
히스토리를 보려면 → 시간이 걸린다

이 충돌을 모델 하나로 풀려고 하면 반드시 어딘가를 포기해야 한다.
우리가 선택한 해결책은 제약을 분리하는 것이었다.

2-Stage 구조

  • Stage1 : 거래 단독 정보만 사용 → 실시간 1차 필터
  • Stage2 : 히스토리 포함 → 정밀 판별

Stage1은 Latency 제약을 담당하고,
Stage2는 Context 제약을 담당한다.
두 제약을 하나의 모델로 동시에 풀지 않고 역할을 분리한 것이 이 설계의 핵심이다.


2. 모델 성능의 핵심은 EDA였다

EDA를 한다고 하면 보통 이런 식이다.

시각화 뽑고 → 정규분포네 아니네 → skewed 그래프네 → OR 돌리고 끝

이번 프로젝트에서 그게 아니라는 걸 몸으로 배웠다.

진짜 EDA는 데이터가 왜 이렇게 생겼는지를 끝까지 추적하는 것이다.

예시 1 — amount_limit_ratio 극단값

처음엔 heavy tail 분포를 보고 "극단값이 있네, clip 처리하자"가 떠올랐다.

그런데 멈추고 물었다.


왜 여기서 급격하게 올라가지?
→ 분포를 더 자세히 뽑아보자
→ 상위 0.1% 구간의 fraud rate가 66.5%다
→ 극단값이 노이즈가 아니라 핵심 신호다

clip 처리 실험을 해봤다.

처리 방법 Δ PR-AUC Δ Lift
원본 유지 +0.036 +0.329
clip 처리 +0.059 +0.008

clip을 하면 전체 PR-AUC는 올라가지만 Top-decile Lift가 사라진다.
FDS에서 중요한 건 전체 점수가 아니라 고위험 구간을 얼마나 잘 잡느냐다.

직관을 따르지 않고 데이터를 따랐더니 반직관적 결론이 나왔다.

예시 2 — MCC long tail noise

MCC(업종 코드)가 사기 신호가 된다는 가설에서 시작했다.
그런데 raw fraud rate를 그대로 쓰면 거래량 1~2건짜리 MCC에서 rate=1.0이 쏟아진다.


long tail 구조 확인
→ 왜 rate=1.0인 MCC가 많지?
→ 거래량이 너무 적어서 신뢰할 수 없는 추정치다
→ Bayesian smoothing으로 저표본은 평균으로 수축시키자
→ α 민감도 분석(100/500/1000/2000)으로 최적값 결정
→ 연도별 Jaccard Overlap으로 시간 안정성까지 검증

단순히 "MCC를 피처로 쓴다"에서 끝내지 않고
왜 노이즈가 생기는지, 어떻게 해결하는지, 그 해결이 안정적인지까지 추적했다.

결과: Baseline 대비 Δ PR +0.553, Δ Lift +4.49 — 단일 피처 기여 전체 1위.

예시 3 — 단변량 vs 다변량의 역전

card_fraud_last3의 OR은 1,557이다.
직전 거래가 사기면 현재도 사기일 확률이 압도적이라는 뜻이다.

그런데 단변량 Top-decile Lift는 1.0이다.
단변량만 보면 DROP 후보다.

add-one 다변량 검증을 해보면 Δ Lift +4.831 — 전체 분석 최상위다.

이 케이스는 이런 교훈을 준다.

단변량에서 약하다 ≠ 중요하지 않다
단변량 검증만으로는 절대 보이지 않는 피처가 있다

그래서 피처 선택을 4단계로 구성했다.


1단계 EDA             → 이 피처가 fraud와 통계적으로 연관이 있는가
2단계 다중공선성       → KEEP 후보들 사이에 중복 정보가 있는가 (VIF, 상관계수)
3단계 SHAP/Attention   → 모델이 실제로 이 피처를 어떻게, 얼마나 쓰는가
4단계 Ablation Test    → 이 피처를 빼면 성능이 실제로 떨어지는가

4단계를 모두 통과한 피처만 최종 리스트에 남겼다.


3. 불균형을 다루는 태도 — 자동 보정이 답은 아니다

FDS는 본질적으로 극단적 불균형 문제다.
fraud는 전체 거래의 극히 일부다.

이때 흔히 하는 반응은 이렇다.

"타겟이 적으니 일단 불균형 보정부터 하자."

하지만 이번 프로젝트에서 배운 건,
타겟 비율이 낮다는 사실 자체는 보정의 근거가 되지 않는다는 것이다.

실험한 접근

  • Raw 데이터 그대로 학습
  • class_weight 적용
  • Over-sampling (upper sampling)
  • Under-sampling
  • Threshold 조정 기반 운영 전략

각 방법을 동일한 평가 기준(PR-AUC, Top-decile Lift, Recall@Precision, Latency)으로 비교했다.

그 결과,

  • Under-sampling은 Latency는 줄지만 정보 손실이 크다.
  • Over-sampling은 PR-AUC는 오르지만 Lift 왜곡이 발생한다.
  • 과도한 class_weight는 calibration을 무너뜨린다.
  • Raw 학습 + threshold 전략이 운영 목적과 가장 정합적이었다.

특히 중요한 건 이것이다.

운영에서 중요한 건 데이터 비율이 아니라 비용 구조다.

FDS의 목적은 “fraud 비율을 50:50으로 맞추는 것”이 아니다.
목적은 “고위험 구간에서 사기를 최대한 밀집시키는 것”이다.

불균형 보정은 성능 지표를 개선할 수 있지만,
그 개선이 실제 비즈니스 목적과 일치하는지 별도로 검증해야 한다.

결론은 단순했다.

  • 무조건 보정하지 않는다.
  • Raw, weight, over, under를 모두 실험한다.
  • Lift, Recall@Precision, Alert Rate까지 포함해 판단한다.
  • 가장 논리적으로 설명 가능한 방식을 선택한다.

불균형은 기술적 문제가 아니라 의사결정 문제다.


4. 모델 선택의 기준은 성능표가 아니었다

"이 모델 성능이 가장 높으니 이걸 쓰겠습니다"는 회사에서 통하지 않는다.

실제 운영 환경에서는 성능 외에도 고려해야 할 것들이 있다.
추론 시간, 학습 비용, 모델 크기, 배포 안정성, scale-out 비용.

Stage1 — Logistic Regression 선택

모델 Precision Recall F1 pred_sec
logit 0.834 0.455 0.589 0.037s
logit_calib 0.835 0.455 0.589 0.190s
hgb 0.822 0.448 0.580 0.114s
lgb_small 0.809 0.441 0.571 0.040s

logit_calib는 성능이 사실상 동일한데 pred_sec이 5배 증가한다.
트리 계열은 recall과 precision이 동시에 열세다.
거래 단독 피처는 선형 분리 가능성이 높아서 트리 구조의 이점이 발현되지 않는다.

Recall 최상위 + pred_sec 0.037s → Logistic Regression.

Stage2 — LightGBM 선택

모델 F1 lat_p50 lat_p95
rf 0.972 259ms 291ms
hgb 0.970 1.95ms 2.04ms
lgb_small 0.969 0.96ms 1.02ms

RF는 F1이 미세하게 높지만 lat_p50이 259ms다.
lgb_small 대비 270배 느리고, 학습 시간 45초, 모델 크기도 크다.

F1 0.003 차이가 운영 비용 열세를 정당화하지 못한다.

lgb_small은 RF 수준 F1을 유지하면서 latency를 270배 단축한다.
히스토리 피처의 비선형 상호작용 포착에도 트리 기반이 필요하다.

모델 선택은 성능표에서 나온 게 아니라 각 스테이지의 제약 조건에서 나왔다.


5. 성능이 좋은 모델과 안정적인 모델은 다르다

모델이 Test set에서 좋은 성능을 보였다고 끝이 아니다.
실제 운영 환경에서도 그 성능이 유지되는지가 더 중요하다.

라벨이 없는 미래 환경 데이터(CHECK, 166,523건)를 Train과 비교해서
Covariate Shift, 즉 입력 분포 변화를 측정했다.

피처 PSI KS 결론
mcc_smoothed_risk 1.805 0.476 강한 drift
month_sin 0.102 0.164 기간 차이 기인
is_risky_month 0.061 0.111 룰 firing 감소
나머지 피처 이상 없음

중요한 발견은 피처 전체가 drift된 게 아니라 MCC 관련 피처에 집중되어 있다는 것이다.

대응도 달라진다.

  • 모델 재학습 → 불필요
  • MCC risk map artifact 업데이트 → 이것만으로 충분

처음 설계할 때부터 artifact를 분리해두었기 때문에 가능한 대응이다.
drift 검증까지 해야 모델이 완성된다.


6. 이번 프로젝트에서 배운 것

설계가 먼저다

모델을 먼저 고르지 않았다.
제약을 먼저 정의하고, 제약이 피처를 결정하고, 피처가 모델을 결정했다.


제약 정의
→ Latency 제약 → 거래 단독 피처 → LR (빠르고 선형)
→ Context 제약 → 히스토리 피처 → LGB (비선형 상호작용)
→ 2-Stage 구조로 두 제약을 분리 해소

EDA는 끝까지 추적하는 것이다

그래프 뽑고 분포 확인하는 게 EDA가 아니다.
왜 이렇게 생겼는지, 그게 타겟과 어떤 관계인지, 그 관계가 안정적인지까지
논리적 절차로 추적하는 것이 EDA다.

불균형은 자동 처리 대상이 아니다

타겟이 적다고 해서 자동으로 보정하지 않는다.
모든 보정 방법을 실험하고, 운영 목적과 정합적인 방식을 선택한다.
불균형은 수학 문제가 아니라 비용 구조와 의사결정 구조의 문제다.

선택의 기준을 설명할 수 있어야 한다

왜 이 모델인가, 왜 이 피처인가, 왜 이 threshold인가.
모든 선택에 이유가 있어야 한다.
이유가 없는 선택은 운영 환경에서 무너진다.

모델의 끝은 배포가 아니라 운영이다

성능이 좋은 모델을 만드는 것과
환경 변화에 유연한 모델을 만드는 것은 다르다.
drift 검증까지 포함해야 진짜 완성이다.


마무리

모델을 돌리는 건 누구나 할 수 있다.
중요한 건 왜 이 모델인가를 설명할 수 있는가이다.
그 설명이 제약으로부터 출발할 때, 비로소 시스템이 된다.