Hoovik 내부 들여다보기: 실시간 멀티모달 감정 AI 파이프라인 구축하기
요약
분산 비디오 회의 플랫폼 Hoovik의 실시간 멀티모달 감정 인식 엔진 구축 과정을 다룹니다. 엄격한 지연 시간 제약 조건과 불안정한 네트워크 환경 속에서 FastAPI, PyTorch, MediaPipe 등을 활용하여 안정적인 추론 파이프라인을 설계한 기술적 해결책을 제시합니다.
핵심 포인트
- 멀티 클라우드 아키텍처를 통해 웹소켓 시그널링과 무거운 ML 워크로드를 분리하여 확장성 확보
- Python GIL 병목 현상을 해결하기 위해 전용 실행기 풀(executor pools)을 사용하여 CPU 집약적 작업 격리
- 실시간 비디오 회의의 불안정한 환경(패킷 손실, 조명 변화 등)을 견디는 프로덕션급 ML 시스템 설계
- FastAPI, PyTorch, MediaPipe, Azure를 결합한 멀티모달 감정 추론 파이프라인 구축
👉 GitHub 🌐 라이브 데모
분산 비디오 회의 플랫폼인 Hoovik을 구축하기 시작했을 때, 저는 WebRTC 시그널링 (signaling)과 전사 (transcription) 파이프라인이 가장 어려운 문제일 것이라고 예상했습니다. 하지만 그렇지 않았습니다. 진짜 엔지니어링 과제는 엄격한 지연 시간 (latency) 제약 조건 하에서 라이브 비디오 회의를 처리할 수 있는, 프로덕션 준비가 된 실시간 멀티모달 감정 추론 엔진을 구축하는 것이었습니다.
오프라인 ML 시스템과 달리, 라이브 회의 환경은 기본적으로 불안정합니다: 마이크가 음소거되거나, 웹캠이 사라지고, 패킷 손실 (packet loss)이 무작위로 급증하며, 조명이 끊임없이 변하고, 트래픽 폭증 시 CPU 압박이 가중됩니다. 그리고 연구용 노트북 (research notebooks)과 달리, 프로덕션 시스템은 애플리케이션 루프 (application loop)를 멈추지 않고 이 모든 상황을 견뎌내야 합니다.
이 글에서는 다음을 사용하여 Hoovik의 감정 인식 백엔드를 어떻게 설계했는지 상세히 설명합니다:
FastAPI, PyTorch, MediaPipe, XGBoost, Isolation Forests, Socket.IO, Azure 컴퓨팅 노드 (compute nodes)
현재 프로덕션 설정은 다음과 같이 작동합니다:
seq_len = 10
audio_dim = 1024
face_dim = 326
d_model = 256
nhead = 8
3 encoder layers
🏗️ 멀티 클라우드 시스템 아키텍처 (Multi-Cloud System Architecture)
경량 웹소켓 오케스트레이션 (websocket orchestration)을 무거운 ML 워크로드로부터 분리하기 위해, Hoovik은 분산된 멀티 클라우드 토폴로지 (topology)에서 실행됩니다.
Render 담당:
React 프론트엔드, Express 백엔드, Socket.IO 시그널링 룸 상태 조정
Azure VM 담당:
감정 추론 (emotion inference), Whisper 전사 (transcription), 특징 추출 (feature extraction), 모델 실행
이러한 분리를 통해 시그널링 지연 시간 (signaling latency)을 안정적으로 유지하는 동시에, 컴퓨팅 서비스가 독립적으로 확장(scale)될 수 있도록 합니다.
⚡ Python GIL 병목 현상 해결하기
emotion_service는 비동기 FastAPI + Socket.IO 서버로 실행됩니다. 단순한 구현 방식은 다음과 같은 이유로 부하가 걸리면 빠르게 무너집니다:
- JPEG 디코딩은 CPU 집약적임
- MediaPipe가 공격적으로 블로킹(blocking)함
- 트랜스포머 (transformer) 순전파 (forward passes)가 이벤트 루프 (event loop)를 멈추게 함
- 동시 특징 추출 (concurrent feature extraction)이 백프레셔 (backpressure)를 생성함
모든 것을 하나의 비동기 경로 (async path) 내에서 실행하는 대신, 저는 워크로드를 전용 실행기 풀 (executor pools)로 격리했습니다.
➕---------------------------------------+ | Per-Participant Sockets | +---------------------------------------+
/ \ [audio_chunk (Float32 PCM)] [emotion.frame (JPEG)] / \ v v +
--------------------------------------+ +------------------------+
| _audio_executor (2T) | | _face_executor (2T) |
| - Wav2Vec2 Features | | - MediaPipe Tracking |
+----------------------+ +------------------------+
--------------------------------------+ +------------------------+
| rac{}{
ightarrow} Normalization
ightarrow / \ |
| v +---------------------------+ |
| _inference_executor (1T) | |
| - Transformer | | - XGBoost |
| - Isolation Forest | +--------------------------+
--------------------------------------+ | emotion.result Worker Pools |
_audio_executor Handles: PCM deserialization, audio normalization, Wav2Vec2 embedding extraction
_face_executor Handles: JPEG decoding, OpenCV preprocessing, MediaPipe landmark tracking
_inference_executor Runs: PyTorch inference, XGBoost evaluation, anomaly detection
Inference is intentionally serialized to guarantee thread safety without adding explicit locking overhead around model state. 🔄 Graceful Modality Degradation
실제 회의는 소음이 심하고 일관성이 부족합니다. 사용자들은 웹캠을 비활성화하거나 마이크를 음소거하고 패킷 손실이 발생하며 세션 중간에 재연결할 수 있습니다. 만약 하나의 스트림이 0.4초 이상 사라지면, 백엔드는 자동으로 실행 프로파일을 전환합니다: audio_only 또는 video_only. 이는 런타임 충돌을 방지하고 저하된 세션 동안 안정적인 예측을 유지하게 합니다.
🎛️ Feature Engineering Pipeline
이 엔진은 동기화된 오디오 및 얼굴 특징들을 정렬된 시간적 임베딩으로 결합합니다. 🎤 Audio Pipeline
들어오는 오디오 청크는 16kHz로 리샘플링되고, 다음을 사용하여 정규화 처리됩니다: audeering/wav2vec2-large-robust-12-ft-emotion-msp-dim 시스템은 다음을 추출합니다: 마지막 은닉 상태 임베딩(last hidden state embeddings), 평균 풀링된 시간 벡터(mean pooled temporal vectors) 1024차원 음향 특징(acoustic features) (중심 0.6초 창 사용).
🙂 비디오 파이프라인 (Video Pipeline)
MediaPipe는 프레임당 326개의 얼굴 특징(facial features)을 추출합니다:
- 공간 랜드마크 (Spatial Landmarks): 136개의 정규화된 얼굴 랜드마크 (normalized facial landmarks)
- 블렌드셰이프 (Blendshapes): 51개의 얼굴 근육 활성화 (facial muscle activations)
- 머리 포즈 (Head Pose): 피치(pitch), 요(yaw), 롤(roll)
모든 랜드마크는 양안 거리(inter-ocular distance)를 기준으로 정규화되어, 카메라와의 근접도에 관계없이 모델이 불변성(invariant)을 유지하도록 합니다. 백엔드는 다음과 같은 순환적 시간 시퀀스(rolling temporal sequences)를 저장합니다: - seq_len = 1024 (모델이 시간에 따른 얼굴 움직임을 추적할 수 있도록 허용)
🧠 뇌 A (Brain A) — EmotionTransformer
주요 딥러닝 모델은 다음과 같은 요소를 사용하는 커스텀 멀티모달 트랜스포머(multimodal transformer) 아키텍처입니다:
- 투영 계층 (projection layers)
- 시간 합성곱 (temporal convolutions)
- 양방향 교차 주의 집중 (bidirectional cross-attention)
- 게이트형 양식 융합 (gated modality fusion)
모델 설정 (Model Configuration):
- d_model = 256
- nhead = 8
- encoder_layers = 3
네트워크는 양방향 주의 집중(bidirectional attention)을 학습합니다:
- 시각적 쿼리(visual queries)는 음향 특징(acoustic features)에 주의를 기울입니다.
- 음향 쿼리(acoustic queries)는 시각적 특징(visual features)에 주의를 기울입니다.
- 학습된 교차 게이트(cross_gate)는 목소리 톤(voice tone)과 얼굴 움직임(facial motion) 사이의 균형을 동적으로 조절합니다.
📚 커리큘럼 학습 전략 (Curriculum Training Strategy)
멀티모달 시스템을 학습시킬 때 양식(modalities)이 무작위로 사라지면 불안정해질 수 있습니다. 이를 해결하기 위해 저는 3단계로 네트워크를 학습시켰습니다.
단계 A — 완전 이중 양식 학습 (Phase A — Full Bimodal Learning)
에포크(Epochs): 1–15
모델은 오직 완전한 오디오-비디오(audio-video) 쌍으로만 학습합니다. 이는 교차 주의 집중(cross-attention) 계층이 공동 표현(joint representations)을 먼저 학습하도록 강제합니다.
단계 B — 누락된 양식에 대한 강건성 (Phase B — Missing Modality Robustness)
에포크(Epochs): 16–55
학습 파이프라인에 다음을 도입합니다:
- 오디오 전용 샘플 (audio-only samples)
- 비디오 전용 샘플 (video-only samples)
- 믹스업 증강 (mixup augmentation)
- 양식 드롭아웃 (modality dropout)
이를 통해 모델은 스트림이 완전히 사라지더라도 임베딩(embeddings)을 보존하는 법을 배웁니다.
단계 C — 보정 및 안정화 (Phase C — Calibration & Stabilization)
에포크(Epochs): 56–90
최종 학습에는 다음을 사용합니다:
- 감소된 학습률 (reduced learning rate)
- SWA (Stochastic Weight Averaging): 최종 가중치를 부드럽게 하고 일반화(generalization) 성능을 향상시키기 위함
현재 학습 설정:
- batch_size = 64
- mixup_alpha = 0.166
- modality_drop_prob = 0.068
- label_smoothing = 0.077
- grad_clip = 1.0
🌲 뇌 B (Brain B) — XGBoost 앙상블 (Ensemble)
딥 네트워크는 잠재 표현 학습(latent representation learning)에 탁월합니다. 하지만 트리 앙상블(tree ensembles)은 명시적인 통계적 경계(explicit statistical boundaries)를 학습하는 데 여전히 매우 효과적입니다.
Transformer를 보완하기 위해, 백엔드 엔지니어들은 매 추론 주기(inference cycle)마다 8,149차원의 특징 벡터(feature vector)를 설계합니다.
설계된 특징 (Engineered Features)
- 통계적 윈도우 (Statistical Windows): 이동 평균(rolling mean), 표준 편차(std deviation), 최솟값/최댓값(min/max) 윈도우
- 시간적 지표 (Temporal Metrics): 프레임 변화량(frame deltas), OLS 추세 기울기(trend slopes), 궤적 변화(trajectory shifts)
- 움직임 에너지 특징 (Motion Energy Features): 얼굴 비대칭(facial asymmetry) 랜드마크 MSE, 음성 분산(voice variance)
학습 전, 특징들은 다음을 사용하여 압축됩니다:
PCA → 512차원
XGBoost 모델은 missing = np.nan을 사용하여 결측된 모달리티(missing modalities)를 자연스럽게 처리하며, 이는 데이터 품질이 저하된 스트림 추론(degraded stream inference) 시 매우 높은 회복탄력성(resilient)을 제공합니다.
현재 XGBoost 설정:
n_estimators = 3150
max_depth = 5
learning_rate = 0.0308
tree_method = hist
📊 특징 중요도 (Feature Importance)
설계된 XGBoost 특징 공간(feature space)에서 추출된 상위 예측 마커들입니다. 현재 가장 강력한 특징들은 여전히 PCA로 압축된 잠재 표현(latent representations)으로 남아 있지만, 분포를 통해 앙상블 학습(ensemble learning)이 시간적 움직임 패턴 전반에 걸쳐 안정적인 통계적 경계(statistical boundaries)를 형성하고 있음을 명확히 보여줍니다.
📉 혼동 행렬 (Confusion Matrix)
보정된 앙상블 분류기(ensemble classifier)의 정규화된 혼동 행렬입니다. 모델은 다음 상태에서 가장 강력한 성능을 보입니다:
angry (분노), happy (행복), neutral/calm (중립/차분)
반면, fearful (공포), sad (슬픔), disgust (혐오)와 같은 더 부드러운 정동 상태(affective states)는 낮은 얼굴 움직임 강도와 음향적 모호성(acoustic ambiguity)으로 인해 더 많은 중첩(overlap)을 보입니다.
가장 높은 정규화된 재현율(normalized recalls)은 다음과 같습니다:
angry → angry = 0.81
happy → happy = 0.77
neutral/calm → neutral/calm = 0.73
🧪 앙상블 보정 (Ensemble Calibration)
확률을 직접 평균 내는 대신, Hoovik은 Optuna를 사용하여 최적화된 가중 앙상블 보정(weighted ensemble calibration)을 사용합니다.
최종 확률 출력: P_final = 0.455 × P_Transformer + 0.545 × P_XGBoost
두 모델은 결합(fusion) 전 단계에서 온도 스케일링(temperature scaling) 및 Platt 보정(Platt calibration)을 거칩니다. 이는 과도하게 확신에 찬 예측(overconfident predictions)을 크게 줄이고, 데이터가 저하된 모달리티(degraded modalities) 전반에서 보정 안정성(calibration stability)을 향상시켰습니다.
🚨 모달리티별 이상 탐지 (Modality-Specific Anomaly Detection)
실제 회의 환경은 매우 혼란스럽습니다:
조명 불량, 모션 블러(motion blur), 오디오 클리핑(audio clipping), 잔향(reverb), 배경 간섭(background interference)
안전장치가 없다면, 모델은 확신에 찬 쓰레기 값(confident garbage predictions)을 생성하게 됩니다.
이를 해결하기 위해 모든 특징 벡터(feature vector)는 전용 Isolation Forest 파이프라인을 통과합니다. 활성 Isolation 모델은 iso_both, iso_audio_only, iso_video_only, iso_global_fallback로 구성되며, 각 모델은 다음 기준에 따라 보정(calibrated)됩니다:
- 모달리티별 분산 (modality-specific variance)
- 명시적인 10% FPR (False Positive Rate, 허위 양성률) 목표
현재 배포된 임계값(thresholds)은 다음과 같이 작동합니다:
- both = 0.0525
- audio_only = 0.0884
- video_only = -0.0264
video_only의 음수 임계값은 분리된 시각 스트림(visual streams)이 본질적으로 더 노이즈가 많은 분산 분포를 가진다는 점을 반영합니다.
📦 이상치 분포 (Anomaly Distribution)
분산은 이봉형(bimodal) 및 단봉형(unimodal) 실행 경로 전체에 걸쳐 퍼져 있습니다. 보정된 임계값 미만으로 떨어지는 샘플은 이상치(anomalous)로 표시됩니다. 만약 다음과 같은 이유로 얼굴 랜드마크(facial landmarks)가 붕괴될 경우:
- 역광 (backlighting)
- 패킷 손상 (packet corruption)
- 불안정한 카메라 프레임 (unstable camera frames)
백엔드는 다음을 출력합니다:
{ "anomaly" : true }
이를 통해 프론트엔드는 신뢰할 수 없는 감정 예측을 억제(suppress)할 수 있습니다.
🛑 실시간 백프레셔 보호 (Real-Time Backpressure Protection)
운영 중 발견된 숨겨진 문제 중 하나는 프레임 버스트 과부하(frame burst overload)였습니다. 브라우저가 높은 FPS로 프레임을 지속적으로 업로드하면, 실행기 큐(executor queues)가 결국 폭발적으로 증가하게 됩니다. 메모리 붕괴를 방지하기 위해 백엔드는 큐 깊이(queue depth)를 지속적으로 모니터링합니다. 만약 _face_executor가 다음 조건을 초과하면:
queue_depth >= 3
백엔드는 웹소켓 백프레셔(websocket backpressure) 이벤트를 발생시킵니다. 프론트엔드는 즉시 다음 항목을 줄입니다:
- 캡처 FPS (capture FPS)
- 업로드 속도 (upload rate)
큐가 회복될 때까지 처리 압력(processing pressure)을 조절합니다. 이는 버스트 트래픽 상황에서 장시간 세션을 극적으로 안정화했습니다.
📈 런타임 텔레메트리 (Runtime Telemetry)
서비스는 추론 워커(inference workers)를 차단하지 않으면서 다음 항목을 추적하는 라이브 텔레메트리 엔드포인트를 노출합니다:
GET /stats
GET /stats/json
- P50 지연 시간 (P50 latency)
- P90 지연 시간 (P90 latency)
- P95 지연 시간 (P95 latency)
- 참가자 수 (participant counts)
- 큐 깊이 (queue depth)
- 활성 방 수 (active rooms)
⚙️ 런타임 성능 (Runtime Performance)
초기 추론 프로파일링(inference profiling) 및 웹소켓 부하 테스트는 Locust 기반의 스트레스 테스트와 동시 Socket.IO 세션 시뮬레이션을 사용하여 Apple Silicon 하드웨어에서 로컬로 수행되었습니다.
관찰된 런타임 특성(runtime characteristics)은 다음과 같습니다:
- 안정적인 저지연(low-latency) 멀티모달 추론 (multimodal inference)
- 지속적인 동시 WebSocket 세션 유지
- 버스트 트래픽(burst traffic) 상황에서의 제어된 실행기 큐(executor queue) 성장
- 성공적인 WebSocket 백프레셔 스로틀링 (backpressure throttling)
- 신뢰할 수 있는 성능 저하 모드(degraded-mode) 폴백(fallback) 처리 (audio_only / video_only)
현재 보정된 앙상블(ensemble)은 다음과 같은 성능을 달성하고 있습니다:
- 앙상블 테스트 정확도 (Ensemble test accuracy): 74.34%
- 균형 테스트 정확도 (Balanced test accuracy): 73.84%
- 보정된 검증 정확도 (Calibrated validation accuracy): 78.68%
- Transformer 전용 정확도 (Transformer-only accuracy): 74.25%
- XGBoost 전용 정확도 (XGBoost-only accuracy): 66.03%
- 최종 확률 출력 (Final probability output): P_final = 0.455 × P_Transformer + 0.545 × P_XGBoost (온도 보정(temperature calibration) 값 T = 0.3으로 고정)
🧩 주요 학습 교훈 (Key Lessons Learned)
-
커리큘럼 학습 (Curriculum Learning)의 중요성
완벽한 멀티모달 샘플로만 학습할 경우, 스트림이 사라질 때 치명적인 실패(catastrophic failure)가 발생합니다. 점진적인 모달리티 저하(modality degradation) 학습이 필수적이었습니다. -
직렬화된 추론 (Serialized Inference)이 더 단순하다
모델 실행을 공격적으로 병렬화하려고 시도했을 때 다음과 같은 문제가 발생했습니다:
- 레이스 컨디션 (race conditions)
- 큐 불안정성 (queue instability)
- 예측 불가능한 지연 시간 급증 (unpredictable latency spikes)
스레드 격리 추출(Thread-isolated extraction)과 직렬화된 추론(serialized inference) 방식이 훨씬 더 안정적임이 증명되었습니다.
- 정확도보다 중요한 프로덕션 안전장치 (Production Safeguards)
백프레셔(Backpressure) 보호, 이상 탐지(anomaly detection), 그리고 우아한 성능 저하(graceful degradation) 처리는 모델의 원시 정확도만큼이나 중요했습니다.
🚀 오픈 소스 및 기여자 (Open Source & Contributors)
Hoovik은 완전한 오픈 소스이며, 다음과 같은 분야의 기여자를 적극적으로 찾고 있습니다:
- Docker화 (Dockerization)
- Redis 백플레인 (Redis backplanes)
- 수평 확장 (horizontal scaling)
- 텔레메트리 인프라 (telemetry infrastructure)
- WebRTC 최적화 (WebRTC optimization)
GitHub : https://github.com/AnupamKumar-1/Hoovik
시스템 엔지니어링, 실시간 ML 인프라, 또는 멀티모달 AI 파이프라인에 관심이 있다면 여러분의 기여를 환영합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기