Pause & Play
[MS AI School 1차 프로젝트] 오탐 판정 13.3%p 감소: '양치기 소년'을 잡는 시계열 엔진 — E-gle Eye 실시간 시스템 구축기 (2/2) 본문
[MS AI School 1차 프로젝트] 오탐 판정 13.3%p 감소: '양치기 소년'을 잡는 시계열 엔진 — E-gle Eye 실시간 시스템 구축기 (2/2)
eun_ll 2026. 4. 18. 18:21오프라인 지표 95%의 모델이 실전 웹캠에서 오답을 쏟아냈다. 2편에서는 그 간극의 원인(전처리 불일치, 편향, 기하 왜곡)을 하나씩 제거하고, 시계열 신뢰도 엔진과 Grad-CAM, WebSocket 실시간 서빙으로 '시스템'을 완성한 과정을 다룬다.
시리즈 [1편 — 모델 편]: 문제 정의 · 데이터 파이프라인 · 과적합 트러블슈팅 · ResNet50 전이학습
2편 — 실시간 시스템 편 (현재 글): 실전 오탐 제거 · 시계열 엔진 · Grad-CAM · WebSocket · 책임 있는 AI
이 글의 핵심 성과
- 동일 프레임 세트 기준 위험 판정 27/33건 → 23/33건 (오탐 13.3%p 감소, 실제 화재 탐지율 유지) — 시계열 큐 + EWMA + 투표 로직
- Train-Serving Skew 제거 — 전처리 중첩·불일치 교정, Squash → Padding 전환으로 검증 정확도 97.53% → 98.15%, Loss 0.0898 → 0.0586
- Grad-CAM 판단 근거 시각화 — 관리자가 "AI가 어디를 보고 판단했는지" 즉시 확인
- HTTP → WebSocket 전환 — 유튜브 라이브/VOD를 0.1초 단위로 분석·전송하는 실시간 파이프라인
- Azure ML 트랙 최종 정확도 0.993 / 정밀도 0.993 / 재현율 0.996
— 이진 분류 전환 + Hard Negative + Targeted Fine-tuning
1. Problem: 지표는 좋은데 실전에서 틀린다
1편에서 검증 정확도 95%+를 달성했지만, 웹캠·실제 영상 테스트에서 오답이 속출했다. 실전 화재 장면 9장 배치 테스트에서 5장이 오분류 — 특히 화면 가득 불이 타는 장면을 Negative로 판정하는, 화재 감지 시스템으로서는 치명적인 미탐(False Negative)까지 나왔다.
마지막 학습 지표는 정확도 95.63% / Precision·Recall 0.9563 / Loss 0.1283 — 숫자만 보면 문제가 없다. 그렇다면 문제는 모델 바깥, 즉 학습 환경과 추론 환경의 불일치에 있다고 가설을 세우고 Grad-CAM으로 확인했다. 실제로 모델은 실전 프레임에서 불꽃과 연기가 아닌 엉뚱한 영역(벽면·창문)을 보고 있었다.
이 간극을 세 가지 트러블슈팅으로 분해해 제거했다.
2. [Trouble Shooting 1] 전처리 중첩·불일치 — Train-Serving Skew
원인
추론 코드를 추적하자 두 가지 결함이 겹쳐 있었다.
- 전처리 중첩: 1편에서 소개했듯 모델 그래프 내부에 이미 전처리 Lambda 층(resnet_preprocess)이 내장돼 있는데, 추론 코드에서 preprocess_input을 한 번 더 적용 → 픽셀 분포가 이중 변환되어 입력 자체가 손상
- 전처리 불일치: ResNet50 기반 모델에 MobileNetV2용 전처리 함수를 사용 → 백본이 기대하는 입력 스케일과 다른 값이 유입
해결
추론 코드의 외부 전처리를 전면 제거하고, 모델 내부 Lambda 층 단일 경로로 통일했다. 전처리를 모델 그래프 안에 내장한 원래 설계 의도 — 학습과 서빙이 물리적으로 같은 코드를 타게 한다 — 를 지키려면, 바깥에서는 아무것도 하지 말아야 했던 것이다.
💡 교훈: 오프라인 지표와 실전 성능의 간극(Train-Serving Skew)에서 가장 먼저 의심할 곳은 모델이 아니라 전처리 경로의 일관성이다.
3. [Trouble Shooting 2] 편향 — 가습기 수증기를 무조건 화재로 찍는 모델
원인
가습기 수증기가 100% "화재"로 분류되는 현상이 반복됐다. 분석 결과 학습 데이터에서 화재 클래스 비율이 과도해, 모델이 특징을 학습한 것이 아니라 확률적으로 다수 클래스를 찍는 편향(Bias)이 형성돼 있었다.
해결
- 오탐 단골 이미지(가습기, 자동차 전조등 등)를 negative로 356장 보강
- class_weight를 10배로 조정해 소수 클래스의 오답에 강한 페널티 부여
데이터 재밸런싱과 손실 가중치를 함께 조정해, 모델이 '많이 나온 답'이 아니라 '특징'을 근거로 판별하도록 교정했다. 보강 후에는 화재 감지의 최대 함정인 설원의 브레이크등·전조등 차량도 Negative 97~100%로 안정 판별됐다.
4. [Trouble Shooting 3] Squash → Padding — CNN이 보는 '형태'를 지켜라
원인
영상 테스트에서 후반부로 갈수록, 그리고 특정 각도에서 인식률이 급락했다. 원인은 리사이징 방식이었다. 학습 시에는 이미지를 224×224로 강제 압착(Squash)했는데, 이 과정에서 불꽃의 가로세로 비율 — CNN이 인식하는 화재의 고유 형태적 특징 — 이 왜곡되고 있었다. 실시간 프레임의 다양한 화면비에서 왜곡 정도가 제각각이니 판별이 흔들릴 수밖에 없다.
해결
tf.image.resize_with_pad 기반 Padding(Letterbox) 방식으로 전환해 원본 비율을 100% 보존하고, 학습 환경과 실시간 환경의 입력 일관성을 확보했다.
| 항목 | Squash 모델 | Padding 모델 |
| 검증 정확도 | 97.53% | 98.15% |
| 검증 Loss | 0.0898 | 0.0586 |
| 훈련 정확도 | 99.38% | 99.69% |
수치 개선폭 자체보다 중요한 것은 Grad-CAM 히트맵이 불꽃·연기 영역에 정확히 정렬되기 시작했다는 점이다. 웹캠·영상 양쪽에서 실전 인식률이 복구되어 이 모델을 최종본으로 확정했다.
5. 시계열 신뢰도 엔진 — 한 프레임을 믿지 않는다
전처리를 다 고쳐도 단일 프레임 판정에는 한계가 있다. 빛 반사나 순간 섬광은 그 프레임에서는 진짜 불처럼 보이기 때문이다. 그래서 판정의 단위를 프레임에서 시간의 흐름으로 바꿨다. 이것이 E-gle Eye의 핵심 차별점인 시계열 신뢰도 엔진이다.
프레임 확률(raw_prob)
│
▼
① Sliding Window ─ deque(maxlen=8): 최근 8회 탐지 결과를 버퍼링
│
▼
② EWMA (동적 α) ─ alpha = 0.6 if raw_prob > FIRE_THRESHOLD else 0.2
│ 위험 신호는 3배 민감하게, 평상시는 안정적으로
▼
③ Voting ─ 윈도우 내 화재 판정 비율(fire_ratio) 산출
│
▼
④ 상태 판정 ─ 🟢 Green(정상) / 🟠 Orange(경계) / 🔴 Red(위험·대응)
설계 포인트
- 동적 가중 EWMA: 확률이 임계치를 넘는 순간 α를 0.2 → 0.6으로 전환. 화재 상황에는 현재 신호를 3배 강하게 반영해 빠르게 반응하고, 평상시에는 이전 흐름을 유지해 수치 출렁임(오탐)을 흡수하는 비대칭 반응 곡선이다.
- 조기 경보 튜닝: 초기에는 8회 중 과반(4~5회) 감지 시 경보였으나, 골든타임 도메인 특성상 놓침(False Negative)의 비용이 더 크다고 판단 — EMERGENCY_COUNT = 2, 즉 약 1초 구간에서 2프레임만 포착돼도 즉시 EMERGENCY로 격상하도록 재조정했다. 오탐 억제는 EWMA와 투표가, 신속성은 완화된 카운트가 분담하는 구조다.
- 신호등 상태 머신: fire_ratio 구간에 따라 Green / Orange / Red 3단계로 상태를 표시해, 관리자가 이진 알람이 아닌 위험의 추세를 읽을 수 있게 했다.
결과 — 동일 프레임 세트 A/B 검증
오탐이 잦았던 연속 프레임 33장으로 구모델(단일 프레임 판정)과 신모델+시계열 로직을 비교했다.
구분 위험(Danger) 판정 비고
| 구모델 (단일 프레임) | 27/33 = 90.0% | 노이즈 프레임까지 위험 판정 |
| 신모델 + 시계열 로직 | 23/33 = 76.7% | Red 18 + Orange 5 |
오탐 판정 13.3%p 감소, 실제 화재 구간 탐지율은 유지 — '민감함'과 '신뢰성'을 동시에 잡는 것이 시계열 설계의 목적이었고, 계측으로 확인했다.
6. Grad-CAM — "왜 화재라고 판단했는가"를 보여주기
오탐을 줄이는 것만큼 중요한 것이 판단 근거의 공개다. 관리자가 AI의 판정을 신뢰하려면, 근거를 눈으로 확인할 수 있어야 한다.
- 히트맵 추출: ResNet50 최심층 특징 레이어(conv5_block3_out)에서 클래스 활성화 맵을 추출
- 정규화·오버레이: 중요도를 0~1로 정규화한 열지표를 원본 프레임에 합성, 화재 판정 영역을 Bounding Box와 함께 표시
Grad-CAM은 이번 프로젝트에서 두 가지 역할을 했다.
개발 단계에서는 디버깅 도구 — 1장에 나와있는 것처럼 모델이 불꽃 대신 배경을 보고 있음을 밝혀내 전처리 문제(2·4장)를 추적하는 단서가 됐고, 운영 단계에서는 제품 기능 — 관리자가 판단 근거를 즉시 확인하고 오탐 시 "왜 틀렸는지"까지 설명 가능한 인터페이스가 됐다.

7. 실시간 서빙 — HTTP를 버리고 WebSocket으로
Problem
프레임 단위 분석 결과를 HTTP 요청/응답으로 주고받자 지연과 끊김이 누적됐다. 초당 수 회의 판정을 스트리밍해야 하는 워크로드에 요청-응답 모델은 구조적으로 맞지 않았다.
Solution
[영상 소스] [Python 백엔드] [React 프런트]
YouTube Live/VOD ──> yt-dlp 스트림 추출 + cv2.VideoCapture
웹캠 │
▼
ResNet50 추론 + 시계열 엔진 + Grad-CAM
│
▼
WebSocket (FastAPI) ──0.1초 단위──> JSON + Base64 프레임
ws://<local-ip>:8000/ws/analyze 상태 게이지·히트맵 렌더링
- 통신 전환: HTTP → WebSocket 전면 재구축. 연결을 유지한 채 분석 결과를 0.1초 단위로 푸시
- 유튜브 라이브 파이프라인: yt-dlp로 라이브/VOD 스트림 주소를 추출해 FPS 30 프레임을 실시간 분석하는 입력 어댑터 구성. 산불 연기 영상 테스트에서 프레임 전 구간 Smoke 99%+로 연속 추적됨을 확인
- 배포 판단: 데모 범위에서는 클라우드 배포의 이득이 없다고 판단, 로컬 네트워크에 서버를 개방해 프런트엔드 팀과 연동 — 범위에 맞는 인프라 최소화도 의사결정이다
오탐 발생 시 해당 프레임과 분석 JSON은 temp_logs에 저장하고 신뢰도 수치는 CSV로 연속 기록해, 오탐 데이터가 재학습 셋으로 순환하는 피드백 루프의 기반을 마련했다.
8. Azure ML 트랙 최종 고도화 — v4 · v5
1편의 성능 역전(v2, 0.8738) → ResNet50 반등(v3, 0.9078)에 이어, 실전 오탐을 잡기 위한 마지막 두 단계를 진행했다.
v4 — 문제를 다시 정의하다: 삼중 분류 → 이진 분류
불/연기/정상 삼중 분류에서 모델이 불과 연기를 서로 혼동하며 재현율이 정체됐다 — 1장의 배치 테스트에서 확인한 오분류가 바로 이 지점이다. 그런데 서비스 관점에서 자문해 보면, 관리자에게 필요한 정보는 "불이냐 연기냐"가 아니라 화재위험의 골든타임을 잡는 것이다.
- 로직 단순화: 화재(불+연기) / 정상의 이진 분류로 전환, 데이터 1:1 재구성
- Hard Negative 보강: Grad-CAM으로 확인된 오탐 유발 사물(브레이크등, 소방복, 안개·수증기, 회색 배경)을 1:1.2 비율로 강제 삽입해 판별 경계를 교정
결과: 정확도 0.984 — 분류 체계 재설계만으로 7%p 이상 상승했다.
v5 — Grad-CAM 기반 Targeted Fine-tuning
잔존하는 1% 미만의 오차를 잡기 위해, Grad-CAM 전수 조사로 취약 케이스만 골라 전용 데이터셋을 구성해 정밀 재학습(Targeted Fine-tuning)을 진행했다.
| 버전 | 전략 | 정확도 |
| v1 Baseline | 초기 모델 수립 | 0.8939 |
| v2 Big Data | 데이터 양 증대 | 0.8738 (성능 역전) |
| v3 Fine-Tune | ResNet50 교체 + LR 조정 | 0.9078 |
| v4 Optimized | 이진 분류 + Hard Negative | 0.984 |
| v5 Final Polish | Grad-CAM 취약 케이스 정밀 보강 | 정확도 0.993 · 정밀도 0.993 · 재현율 0.996 |
v1→v5의 궤적이 곧 이 프로젝트의 요약이다: 감(양 늘리기) -> 데이터 기반 튜닝(취약점 조준)
9. 책임 있는 AI — 사람이 최종 결정권을 갖는 설계
생명과 직결된 도메인인 만큼, 성능 밖의 설계 원칙도 명시했다.
- 재현율 우선 + 관리자 최종 통제: 인명 피해 방지를 위해 재현율을 높게 유지하되, 위험 팝업에서 최종 판단 버튼은 관리자가 누른다 — AI는 판정하고, 결정은 사람이 한다
- 판단 투명성: Grad-CAM 히트맵, 시계열 게이지, 프레임 분석 그래프로 성능 수치가 아닌 판단 과정을 공개
- 데이터 휘발성: 영상 프레임은 Base64 1회성 정보로 표시 후 파기, 시연 시 60초 자동 파기·운영 시 7일 후 영구 삭제 정책으로 개인정보 리스크 차단
- 포용성: 청각 장애인용 화면 테두리 점멸 경보(Barrier-free), 4개국어 TTS, 다크모드
- 피드백 루프: 오탐 신고 데이터를 재학습 셋으로 자동 분류하는 지속 개선 구조
10. 마치며 (Retrospective)
2주라는 일정 속에서 이 프로젝트가 남긴 인사이트는 네 가지다.
첫째, 오프라인 지표와 실전 성능 사이에는 '전처리'라는 협곡이 있다. 검증 정확도 95%의 모델이 실전에서 무너진 원인은 모델이 아니라 전처리 중첩, 백본 불일치, 리사이징 왜곡이었다. Train-Serving Skew를 의심하는 습관, 그리고 전처리를 모델 그래프 안에 내장하는 설계가 답이었다.
둘째, 화재 감지의 본질은 분류가 아니라 신뢰다. 정확도를 1%p 올리는 것보다, 오탐 판정을 13.3%p 줄여 관리자가 경보를 믿게 만드는 것이 제품 가치에 가깝다. 시계열 엔진과 Grad-CAM은 그 신뢰를 만드는 두 축 — 하나는 판정의 안정성, 하나는 판단의 투명성 — 이었다.aa
셋째, "데이터는 많을수록 좋다"는 조건부 참이다. v2의 성능 역전은 데이터 양·품질·모델 수용량이 함께 설계돼야 함을 보여줬고, v4~v5의 도약은 무차별 증량이 아닌 **문제 재정의(이진 분류)와 조준 사격(Hard Negative, Targeted FT)**에서 나왔다.
넷째, 설명가능성은 도덕이 아니라 품질 기준이다. Grad-CAM은 윤리 체크리스트 항목으로 넣었다가, 전처리 버그를 찾아낸 디버깅 도구이자 관리자 신뢰를 만드는 핵심 기능이 됐다. 투명성 장치는 시스템의 품질을 끌어올리는 엔지니어링 자산이었다.
클라우드(Azure)에서 리소스를 직접 다뤄본 경험, 그리고 모델을 '시스템'으로 완성해 본 경험 — 이 두 가지가 이번 1차 프로젝트의 가장 큰 수확이다. 아쉬움으로 남은 웹 대시보드 고도화는 다음 프로젝트의 출발점으로 삼는다.
'MS ai School' 카테고리의 다른 글
| [MS AI School] 6개월 수료 — 전공자의 솔직한 후기 (0) | 2026.07.09 |
|---|---|
| [MS AI School 최종 프로젝트] AI 응답 14초 → 1.5초, 성능 최적화 전면전 — SimSpeak 개발기 (2/2) (0) | 2026.07.07 |
| [MS AI School 최종 프로젝트] 하루 만에 DB 비용 10만 원이 청구됐다 — AI 회화 서비스 SimSpeak 개발기 (1/2) (0) | 2026.07.07 |
| [MS AI School 1차 프로젝트] 검증 정확도 52% → 98%: 화재 감지 AI 'E-gle Eye' 모델 신뢰도 구축기 (1/2) (0) | 2026.04.18 |
| [MS ai school] 최종합격 후기 (0) | 2026.04.07 |





