Skip to content

딥페이크 탐지 어플리케이션 (Deepfake Detection Service)

공식 구현체가 없는 최신 논문 아키텍처를 직접 분석 및 구현하고, 고부하 AI 모델의 병목을 해결하기 위해 FastAPI와 TaskIQ 기반의 비동기 추론 파이프라인을 구축한 프로젝트입니다.


1. Project Overview (프로젝트 개요)

  • 기간: 2026.01.28 ~ 2026.02.27 (부트캠프 최종 프로젝트 | 5명)
  • 역할: AI 모델 구현, 백엔드 비동기 태스크 큐 설계 및 구현, 개발 환경(uv 워크스페이스) 구축 및 최적화
  • 핵심 기술 스택:
  • AI & Model: PyTorch, PyTorch Lightning, Transformers (SigLIP), ONNX Runtime GPU, WandB
  • Backend: FastAPI, TaskIQ, Redis, SQLModel (ORM), PostgreSQL, Boto3 (S3)
  • DevOps & Tooling: uv (Workspace), Git, Docker
  • Github: https://github.com/AICV-Deepfaker/deepfaker_detection

🌐 비동기 추론 아키텍처 파이프라인 (Architecture Pipeline)

고부하 AI 모델 추론 시 서버 병목을 방지하고 사용자 경험을 극대화하기 위해 설계한 비동기 추론 아키텍처입니다.

sequenceDiagram
    autonumber
    actor User as 사용자 (App)
    participant API as FastAPI 백엔드
    participant DB as PostgreSQL (SQLModel)
    participant Broker as TaskIQ Broker (Redis)
    participant Worker as TaskIQ Worker
    participant S3 as AWS S3 Storage
    participant ORT as ONNX Runtime (GPU)

    User->>API: 비디오 업로드 요청
    API->>S3: 비디오 파일 업로드
    S3-->>API: S3 파일 경로 반환
    API->>DB: 비디오 메타데이터 저장 (상태: PENDING)
    API->>Broker: 비동기 추론 태스크 요청 (video_id)
    API-->>User: 202 ACCEPTED 반환 (즉시 응답)

    Note over Worker, ORT: 백그라운드 비동기 추론 시작
    Broker->>Worker: 태스크 할당 (predict_deepfake_fast)
    Worker->>DB: 비디오 상태 업데이트 (PROCESSING)
    Worker->>S3: S3에서 비디오 다운로드 (임시 디렉토리)
    Worker->>ORT: 비디오 프레임 추출 및 ONNX 추론 실행
    ORT-->>Worker: 탐지 확률 반환 (REAL/FAKE)
    Worker->>DB: 추론 결과 및 리포트 저장 (상태: COMPLETED)
    Worker->>Broker: Redis Pub/Sub 채널로 완료 알림 발행
    Broker-->>User: 실시간 푸시 알림 (추론 완료 및 결과 조회 가능)

2. Repository Structure (레포지토리 구조)

직접 설계하고 구축한 모노레포 워크스페이스 구조와 핵심 기여 모듈(★)입니다.

deepfaker_detection/ (★ uv 워크스페이스 모노레포)
├── pyproject.toml          # ★ uv 워크스페이스 및 공통 의존성 설정
├── uv.lock                 # ★ 전체 워크스페이스 통합 락 파일
├── ddp_backend/            # ★ FastAPI 백엔드 서비스
│   ├── core/
│   │   ├── database.py     # SQLModel DB 세션 및 커넥션 풀 설정
│   │   ├── tk_broker.py    # TaskIQ Redis 브로커 설정
│   │   └── s3.py           # Boto3 기반 S3 업로드/다운로드 유틸리티
│   ├── detectors/          # ★ AI 추론 엔진 래퍼
│   │   └── visual/
│   │       ├── base.py     # ★ BaseVideoDetector 추상 클래스
│   │       └── unite.py    # ★ UNITE ONNX 추론 및 CUDA VRAM 최적화 구현
│   ├── models/             # ★ SQLModel 기반 DB 테이블 정의
│   │   ├── models.py       # Result, FastReport, DeepReport 테이블
│   │   └── user.py         # User 테이블 및 OAuth2 스키마
│   ├── routers/            # ★ FastAPI API 엔드포인트
│   │   └── detection.py    # ★ 비동기 태스크 트리거 및 다형성 리포트 응답 라우터
│   └── task/               # ★ TaskIQ 비동기 작업 정의
│       └── detection.py    # ★ 비동기 추론 태스크 (S3 다운로드 -> 추론 -> DB 저장)
└── unite_detection/        # ★ [Submodule] UNITE 모델 학습 및 ONNX 내보내기 엔진
    ├── pyproject.toml      # ★ CPU/GPU/Flash-Attn 환경 분기 및 의존성 설정
    └── src/unite_detection/
        ├── dataset/        # ★ 이기종/다기능 커스텀 데이터셋 로더 모듈
        │   ├── base_dataset.py       # ★ DeepFakeBaseDataset 추상 클래스 (Lazy Loading & Chunking)
        │   ├── celeb_df_dataset.py   # ★ Celeb-DF 영상/이미지 포맷 지원 데이터셋
        │   ├── ff_dataset.py         # ★ FaceForensics++ 비디오 데이터셋
        │   ├── sail_vos_dataset.py   # ★ SAIL-VOS 프레임 시퀀스 데이터셋
        │   └── custom_video_dataset.py # ★ 범용 비디오 데이터셋
        ├── lit_modules/    # ★ PyTorch Lightning 모듈 및 콜백
        │   ├── datamodule.py
        │   ├── unite_classifier.py  # ★ LightningModule (체크포인트 경량화 포함)
        │   └── visualization_callback.py # ★ W&B 연동 자동 시각화 콜백
        ├── losses/         # ★ 커스텀 손실 함수
        │   └── ad_loss.py  # ★ 논문 구현체 보완 (Differentiable AD Loss)
        ├── models/         # ★ 신경망 아키텍처 구현
        │   ├── unite_model.py # ★ UNITE 메인 모델 (SigLIP + Temporal ViT)
        │   └── vit_encoder.py # ★ Temporal ViT Encoder
        └── train.py        # 학습 엔트리포인트

3. Key Contributions & Code Highlights (핵심 기여 및 코드 하이라이트)

① Modern Python Tooling: uv 워크스페이스 환경 구축

  • 도전 과제: 로컬 개발 환경(Mac/Windows)과 학습 서버(Linux GPU) 간의 PyTorch CUDA 버전 및 flash-attn 빌드 의존성 충돌로 인해 환경 구축 시 잦은 오류 발생.
  • 해결 방안: 현대적인 Python 패키지 매니저인 uv 워크스페이스를 도입하여 모노레포 내의 여러 패키지를 통합 관리하고, 환경별 의존성을 명확히 분리.
  • 코드 하이라이트 (pyproject.toml - 루트):
    [tool.uv]
    # flash-attn 빌드 시 PyTorch가 격리되지 않고 먼저 참조되도록 설정 (빌드 실패 방지)
    build-constraint-dependencies = ["setuptools<82.0.0"]
    no-build-isolation-package = ["flash-attn"]
    override-dependencies = [
      "torch>=2.10.0",
      "torchvision>=0.25.0",
      "torchcodec==0.10.0",
    ]
    
    [tool.uv.workspace]
    members = ["Wavelet-CLIP", "ddp_backend", "unite_detection", "STT"]
    
    [tool.uv.sources]
    # Linux 환경에서만 PyTorch CUDA 12.8 인덱스를 타겟팅하도록 설정
    torch = [
      { index = "pytorch-cu128", marker = "sys_platform == 'linux'" },
    ]
    torchvision = [
      { index = "pytorch-cu128", marker = "sys_platform == 'linux'" },
    ]
    unite-detection = { workspace = true, editable = true }
    

② Paper-to-Code: UNITE 모델 직접 구현 및 수식 보완

  • 도전 과제: 공식 코드가 없는 최신 논문(UNITE)을 분석하여 모델을 직접 구현해야 했음. 특히 논문의 핵심인 Attention-based Anomaly Detection Loss (ADLoss) 수식 중 클래스 센터 $C$가 연산 그래프에서 분리(detached)되어 있어 PyTorch에서 역전파(Backpropagation)가 불가능한 수학적 한계 발견.
  • 해결 방안:
  • SigLIP 이미지 인코더를 Frozen 백본으로 두고, 프레임 간 관계를 학습하는 Temporal Positional EncodingTemporal ViT Encoder를 직접 설계.
  • ad_loss.py에서 역전파가 불가능한 센터 $C$ 대신, 미분 가능한 배치 클래스 평균 $P$를 사용하여 클래스 간 손실(Between-class loss)을 계산하도록 수식을 보완하여 학습이 정상적으로 진행되도록 개선.
  • 코드 하이라이트 (unite_detection/src/unite_detection/losses/ad_loss.py):
    # 1. 센터 업데이트 (현재 배치 정보를 활용해 이동 평균 업데이트 - Gradient 분리)
    if self.training:
        for c in range(self.config.arch.num_cls):
            mask: Bool[Tensor, "batch"] = labels == c
            if mask.any():
                valid_cls += 1
                batch_class_mean: Float[Tensor, "head frame"] = P_norm[mask].mean(dim=0)
                with torch.no_grad():
                    # C[c]는 역전파가 되지 않는 버퍼로 업데이트
                    self.C[c] = (1 - self.config.eta) * self.C[c] + self.config.eta * batch_class_mean.detach()
    
                # [수식 보완] 논문에서는 C와 C 사이의 거리를 계산하라고 되어 있으나,
                # C는 gradient가 흐르지 않으므로 미분 가능한 batch_class_mean(P)을 사용하여 Loss 계산!
                dist_matrix: Float[Tensor, "head head"] = torch.cdist(batch_class_mean, batch_class_mean, p=2)
                # ... 이후 loss_between 계산 진행
    

③ PyTorch Lightning 기반 학습 파이프라인 최적화

  • 도전 과제: 대용량 Frozen 백본(SigLIP)이 체크포인트마다 중복 저장되어 디스크 공간을 기가바이트 단위로 낭비하고, 체크포인트 저장 속도가 매우 느려짐.
  • 해결 방안: on_save_checkpoint를 오버라이드하여 학습 대상이 아닌 Frozen 파라미터(vis_encoder)를 체크포인트 저장 대상에서 제외하는 최적화 적용.
  • 코드 하이라이트 (unite_detection/src/unite_detection/lit_modules/unite_classifier.py):
    @override
    def on_save_checkpoint(self, checkpoint):
        """체크포인트 저장 시 Frozen 파라미터(Backbone)를 제외하여 용량을 획기적으로 줄입니다."""
        state_dict = checkpoint["state_dict"]
    
        # 저장하지 않을 키 필터링 (vis_encoder 관련 키들 제거)
        keys_to_remove = [k for k in state_dict if "model.vis_encoder" in k]
        for k in keys_to_remove:
            del state_dict[k]
    
        print(f"[Optimizer] Removed {len(keys_to_remove)} frozen backbone keys from checkpoint.")
    

④ 이기종/다양한 구조 데이터셋 통합을 위한 커스텀 데이터셋 아키텍처 구축

  • 도전 과제: Celeb-DF, FaceForensics++ (FF++), SAIL-VOS 등 다양한 딥페이크 데이터셋마다 저장 형태(MP4 비디오 파일 vs 이미지 프레임 디렉토리), 파일 포맷 및 네이밍 규격(frame_000001.jpg vs 000000.png), 경로 기반 라벨링 규칙이 각각 달라 개별 파이프라인 작성 시 중복 코드가 급증하고 관리 및 확장에 어려움 발생.
  • 해결 방안:
  • Template Method Pattern & 추상화: 공통 로직(Lazy Initialization, 청크 단위 프레임 인덱싱, 샘플 메타데이터 생성)을 캡슐화한 DeepFakeBaseDataset 추상 클래스를 설계하여 코드 중복 최소화.
  • Strategy Pattern (Processor 분리): 데이터 읽기/전처리 방식을 VideoProcessorImageProcessor로 전략 분리하여, 디렉토리 기반 이미지 프레임 로딩과 동영상 파일의 실시간 스트리밍 추출을 동일한 추상 인터페이스로 유연하게 처리.
  • 다형성(Polymorphism) 기반 데이터 통합: CelebDFVideoDataset, CelebDFImageDataset, FFDataset, SailVosDataset 등이 동일한 기반 인터페이스를 구현하도록 하여, PyTorch Lightning DataModule에서 이기종 데이터셋을 동일한 방식으로 유연하게 결합하고 통합 학습 파이프라인 구축.
  • 코드 하이라이트 (unite_detection/src/unite_detection/dataset/base_dataset.py):
    class DeepFakeBaseDataset(Dataset[tuple[Tensor, Tensor]], ABC):
        """이기종 딥페이크 데이터셋 통합을 위한 공통 추상 클래스"""
        def __init__(
            self,
            paths: Sequence[Path | str],
            config: DatasetConfig | None = None,
        ):
            self.config = config or DatasetConfig()
            self._samples: list[FileMeta] | None = None
            self._processor: BaseProcessor | None = None
            self._paths = paths
    
        @property
        def samples(self) -> list[FileMeta]:
            """Lazy Initialization으로 메타데이터 및 청크 목록 지연 생성"""
            if self._samples is None:
                self._samples = []
                self._prepare_samples(self._paths)
            return self._samples
    
        def _prepare_samples(self, paths: Sequence[Path | str]):
            """데이터 형태에 맞춰 프레임 청크(Chunk) 단위로 샘플 인덱싱"""
            for path in map(Path, paths):
                label = self._get_label(path)
                if label is None:
                    continue
    
                frame_cnt = self.processor.get_frame_count(path)
                if frame_cnt <= 0:
                    continue
    
                num_chunks = self.processor.calculate_num_chunks(frame_cnt)
                for i in range(num_chunks):
                    self._samples.append({
                        "path": path,
                        "chunk_idx": i,
                        "label": label,
                        "total_frames": frame_cnt,
                    })
    
        @abstractmethod
        def _create_processor(self) -> BaseProcessor:
            raise NotImplementedError
    
        @abstractmethod
        def _get_label(self, path: Path) -> int | None:
            raise NotImplementedError
    
        def __getitem__(self, idx: int) -> tuple[Float[Tensor, "channel batch h w"], Int[Tensor, "batch"]]:
            # Processor에 전처리 전략을 위임하여 동일한 인터페이스로 텐서 반환
            return self.processor.getitem(self.samples[idx])
    

4. Troubleshooting Stories (트러블슈팅 경험)

🚨 [Data Leakage] Validation 성능은 비정상적으로 높은데 Test 성능은 낮은 현상

  • 문제 현상: 모델 학습 시 Validation Accuracy가 98% 이상으로 비정상적으로 높게 나왔으나, 실제 Test 데이터셋에서는 성능이 70% 수준으로 급격히 하락하는 과적합/일반화 실패 현상 발생.
  • 원인 분석:
  • 데이터셋을 Train/Validation으로 나눌 때 무작위(Random Split)로 분할함.
  • 그러나 비디오 데이터 특성상 하나의 영상에서 여러 프레임(32프레임 단위)을 추출하여 학습 데이터로 사용하기 때문에, 영상 단위로 분할하지 않으면 동일한 비디오에서 나온 매우 유사한 프레임들이 Train과 Validation에 동시에 들어가게 되는 데이터 누수(Data Leakage)가 발생함이 원인이었음.
  • 해결 방안: 데이터셋 분할 방식을 프레임 레벨 무작위 분할에서 비디오 ID 기준 그룹 분할(Video-level Split)로 전면 수정함. 수정 후 Validation 지표가 Test 지표와 유사한 수준(약 82%)으로 조정되었으며, 데이터 누수 없는 신뢰할 수 있는 평가 파이프라인을 확보함.

🚨 [Git] Rebase 중 발생한 대형 커밋 소실 복구

  • 문제 현상: 충돌(Conflict)이 발생한 브랜치를 Rebase하는 도중, 실수로 하루 동안 작업한 대규모 커밋이 날아가 버리는 상황 발생.
  • 해결 방안: Git의 커밋 이력 추적 도구인 git reflog를 실행하여 잃어버린 커밋의 해시값을 찾아내고, git reset --hard로 헤드를 복구한 뒤, 충돌이 발생한 커밋을 체리피킹(git cherry-pick)하여 안전하게 작업을 복구함. 이 경험을 통해 Git의 내부 동작 원리와 복구 메커니즘을 깊이 이해하게 됨.