나를 속인 mAP50: 온디바이스(On-Device) 안전 AI에 관한 디버깅 이야기
요약
온디바이스 안전 탐지 모델 개발 중 mAP50 지표의 함정에 빠져 실제 성능 저하를 놓쳤던 디버깅 사례를 다룹니다. 클래스별 세부 지표 확인의 중요성과 데이터셋 보강을 통한 해결 과정을 설명합니다.
핵심 포인트
- 통합 지표(mAP)에 의존하지 말고 클래스별 세부 내역을 반드시 확인해야 함
- 모델 성능 검증 시 실제 예시를 통한 직접적인 시각적 확인이 필수적임
- 학습 데이터 불균형 해결을 위해 과소 대표된 클래스의 데이터셋 보강 필요
- 학습 프로세스 중 하드웨어 이슈에 대비한 체크포인트 및 자동 재개 전략 중요
저는 건설 안전 관리자를 위한 오프라인 우선(offline-first) 현장 점검 앱인 GroundCheck를 구축하고 있습니다. 우리가 초기에 승부수를 던진 것 중 하나는 온디바이스(on-device) 위험 탐지였습니다. 즉, 인터넷 연결 없이도 휴대폰에서 완전히 실행되며, 사진을 찍는 즉시 안전모(hardhats)나 안전 조끼(safety vests) 미착용을 찾아내는 YOLO 모델입니다. 이 분야에서 이를 수행하는 경쟁자는 없습니다. 다른 모든 업체는 클라우드 전용(cloud-only) 탐지 방식을 사용하는데, 이는 건설 현장에서 빈번하게 발생하는 신호 끊김 현상이 발생하면 즉시 작동을 멈춥니다.
이 이야기는 겉보기에는 훌륭해 보였지만 실제로는 심각하게 망가져 있었던 하나의 지표(metric)에 관한 이야기이자, 이를 알아내기 위해 무엇이 필요했는지, 그리고 모델과는 전혀 상관없는 문제들로 인해 두 번이나 탈선했던 학습 과정에 대한 이야기입니다.
망가진 탐지기를 숨기고 있던 좋은 점수
10개 클래스 분류 체계(안전모, 조끼, 마스크, 콘 및 기타 몇 가지)로 학습된 우리의 첫 번째 실제 모델은 0.510 mAP50을 기록했습니다. 이는 우리의 내부 기준치인 0.50을 넘긴 수치였습니다. 모델은 배포되었고, 기능은 완료된 것으로 선언되었으며, 다음 작업으로 넘어갔습니다.
그 후 저는 간단한 테스트 사진으로 모델을 실행해 보았습니다. 6명의 건설 노동자가 등장하는 표준적인 작업 현장 사진이었으며, 일부는 안전모를 쓰고 있었고 모두가 고시인성(hi-vis) 조끼를 입고 있었습니다. 모델은 조끼를 단 하나도 찾지 못했습니다.
교훈: 단일 통합 지표(aggregate metric)는 요약일 뿐, 판결이 아닙니다. 만약 클래스(class)들이 대략적으로 비슷하게 쉽거나 비슷하게 중요하지 않다면, 헤드라인 숫자를 신뢰하기 전에 클래스별 세부 내역(per-class breakdown)을 살펴보세요. 그리고 가능하다면, 실제 예시를 대상으로 모델을 실행하고 직접 확인해 보세요. 조끼를 하나도 찾지 못한 실패는 제 눈으로 직접 확인하는 데 30초밖에 걸리지 않았지만, 바로 그 부분을 측정하기로 되어 있었던 지표에서는 보이지 않았습니다.
해결 방법
두 가지를 변경했습니다. 첫째, 일관된 학습 데이터가 너무 부족하여 제대로 작동할 수 없었던 두 개의 클래스(NO-Safety Vest / NO-Mask)를 제거했습니다. 이제 앱은 조끼의 부재를 직접 탐지하려고 시도하는 대신(이는 필요 이상으로 어려운 학습 문제임이 밝혀졌습니다), 탐지된 사람에게 조끼 박스(box)가 겹치지 않는 것을 보고 "조끼 없음"을 추론합니다. 둘째, 과소 대표된(underrepresented) 클래스들을 보강하기 위해 특별히 세 개의 새로운 오픈 데이터셋(open datasets)을 찾아냈으며, 이를 통해 가장 문제가 되었던 클래스들의 검증(validation) 볼륨을 대략 3배로 늘렸습니다.
재학습을 진행했습니다. 동일한 아키텍처(YOLOv8s), 동일한 목표: 0.510을 넘어서는 것, 그리고 이번에는 실제로 중요한 클래스들을 검증하는 것입니다.
저항하는 학습 과정
이 부분은 모델과는 아무런 관련이 없으며, 가끔씩 딴짓을 하는 하드웨어에서 긴 학습 작업(training jobs)을 실행하는 것과 전적으로 관련이 있습니다. 머신러닝(ML) 문제가 전혀 아닌 두 가지 문제가 발생했습니다.
첫째, 학습 도중 기기가 재부팅되었습니다(관련 없는 유지보수 작업 때문). 이로 인해 계획된 200 에포크(epoch) 중 134 에포크에서 프로세스가 종료되었습니다. 복구 가능한 수준이었습니다. 체크포인트(checkpoints)는 프로세스 종료 후에도 살아남으므로, last.pt부터 다시 시작하면 됩니다.
둘째로, 더 흥미로운 점은 그러한 충돌(crash)을 처리하기 위해 특별히 작성했던 자동 재개(auto-resume) 스크립트에 버그가 있었다는 것입니다. yolo detect train resume=True model=<checkpoint> 명령은 정확히 중단된 지점부터 다시 시작해야 합니다. 하지만 제 환경에서는 조용히 작동하지 않았습니다. 실제 32,000장의 이미지 학습 데이터셋 대신 Ultralytics의 내장된 4장짜리 토이 데이터셋(toy dataset)으로 대체되었고, 아무 문제 없이 성공했다고 보고했습니다. 에러도, 경고도 없었습니다. 그저 손실 곡선(loss curves)이 지나치게 깨끗해 보인다는 것을 알아차릴 때까지, 학습 프로세스는 아무런 유용한 작업도 하지 않은 채 조용히 돌아가고 있었을 뿐입니다.
제가 이를 발견한 이유는 프로세스(process) 수가 잘못되어 보였기 때문입니다. 하나의 학습 작업은 하나의 Python 프로세스를 의미해야 하는데, 저는 25개의 프로세스를 보고 있었습니다. 그 실마리를 따라가 보니, 인자 파서(argument parser)가 제 데이터셋 설정(dataset config)을 조용히 버리고 자체 기본값(defaults)으로 대체하고 있었습니다.
실제로 효과가 있었던 해결책은 다음과 같습니다. "스마트한" 재개 기능을 신뢰하지 말고, 매번 평범하고 지루하며 명시적으로 지정된 재시작을 수행하는 것입니다. 모든 인자(argument)를 일일이 나열하고, 자리를 뜨기 전에 로그에 실제 데이터셋 경로가 표시되는지 확인하십시오. 모든 것을 추론해 줄 것이라 기대되는 플래그(flag)에 의존하지 마세요. 백그라운드 프로세스가 요란하게 에러를 내는 대신 조용히 실패할 때는, 언제나 영리하고 조용한 방식보다 지루하고 검증된 방식이 승리합니다.
언제 멈춰야 하는지 아는 것
재학습된 모델은 에폭(epoch) 177 부근에서 mAP50 0.682로 정체되었으며, 여전히 도전 목표였던 0.80에는 미치지 못했습니다. 저는 몇 점의 점수를 더 얻을 수 있기를 바라며, 최종 가중치(final weights)로부터 새로운 40에폭 패스를 웜 스타트(warm-starting)하는 짧은 "연속(continuation)" 실행을 시도했습니다.
그 결과 상황은 즉각적이고 지속적으로 더 악화되었습니다 — 매 에폭(epoch)마다 정확도가 떨어졌습니다. 이미 178 에폭 이상 수렴한 모델 위에 전체 학습률 스케줄(learning-rate schedule)을 재설정하는 것은 너무 큰 충격이었습니다. 미세 조정(fine-tuning)을 하는 대신, 모델이 망각(forgetting)을 시작한 것입니다. 추세가 명확해진 4 에폭 후에 실행을 중단했고, 원래의 체크포인트(checkpoint)를 유지했습니다. 많은 최적화 문제와 마찬가지로, 학습 실행 시 때로는 이미 좋은 중단 지점을 찾았음을 인지하고, 추가적인 조작이 도움이 되기보다 해가 될 가능성이 높다는 것을 깨닫는 것이 올바른 선택일 때가 있습니다.
이번에는 제대로 된 검증
이전과 동일한 두 가지 확인 절차를 이번에는 제대로 수행했습니다:
전체 검증 세트에 대한 클래스별 mAP50 (Per-class mAP50):
| 클래스 | mAP50 | 이전 |
|---|---|---|
| Hardhat | 0.855 | 0.924 |
| ... |
테스트 사진. 이전과 동일한 6명의 작업자입니다. 이번에는: 보이는 조끼 4개가 정확히 탐지되었고, 착용 중인 안전모 3개 모두 정확히 탐지되었으며, 맨머리(bare heads)도 정확히 플래그(flagged)되었습니다. 완벽하지는 않습니다 — 조끼 몇 개가 여전히 누락되었고, 신뢰도 점수(confidence scores)도 매우 높기보다는 중간 수준이지만 — 분명히 눈에 보이는 개선이 이루어졌습니다. 이전에는 조용히 망가져 있었던 클래스들이 이제는 진정으로 기능합니다.
교훈
두 가지 지루한 습관이 이번 재학습이 고장 난 탐지기를 두 번이나 배포하는 사태를 막아주었습니다. 바로 클래스별 세부 분석(per-class breakdown) 없이 단일 집계 지표(aggregate metric)를 신뢰하지 않는 것과, 자동 복구 메커니즘이 실제로 주장하는 바를 수행했는지 구체적으로 확인하지 않고는 신뢰하지 않는 것입니다. 둘 다 흥미로운 일은 아닙니다. 하지만 이 두 가지가 제대로 작동하는 안전 기능과, 아무도 재확인하지 않는 대시보드에서 그저 작동하는 것처럼 보이는 안전 기능 사이의 차이를 만듭니다.
GroundCheck는 건설 현장 안전 관리자를 위한 오프라인 우선(offline-first) 점검 앱으로, 연결성이 전혀 없는 상태에서도 작동하는 온디바이스(on-device) AI 위험 탐지를 중심으로 구축되었습니다. 건설 현장에는 항상 신호가 잡히는 것이 아니며, 안전 점검이 클라우드 API를 기다리며 지체되어서는 안 되기 때문입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기