엔지니어링 로그 #03 — 정직한 별표가 실현되다 (제품 F1 0.887), 그리고 모델이 마침내 제품이 되다
요약
비디오 액션 분류 및 하이라이트 파이프라인의 성능을 실제 제품 환경(End-to-End)에서 정직하게 측정한 엔지니어링 로그입니다. 기존의 낙관적인 수치를 넘어 실제 트래킹이 포함된 환경에서 F1 스코어 0.887을 달성하며 모델이 제품 단계로 진입했음을 증명합니다.
핵심 포인트
- 가정된 데이터가 아닌 실제 트래킹이 포함된 엔드투엔드 환경에서 성능 측정
- 제품 파이프라인의 F1 스코어 0.887 달성 (Recall 0.868, Precision 0.908)
- 서빙 경로를 CPU에서 GPU로 전환하여 배포 최적화 수행
- 모델 성능 측정 시 유리한 조건(Cherry-picking)을 배제하는 정직한 평가 강조
공개적으로 진행되는(build-in-public) 엔지니어링 로그입니다. 아래의 모든 수치는 테스트 보고서에서 직접 가져온 것이며, 보기 좋게 반올림하거나 지어낸 것이 아닌 **측정된 값(measured values)**입니다. 당신이 유리한 것만 골라낸(cherry-picked) 성공은 성공이 아닙니다.
로그 #02는 별표(*)와 함께 끝났습니다. 우리는 **94% 정확도(accuracy)**의 비디오 액션 분류기(video action classifier)와 재현율(recall) 0.86의 엔드투엔드(end-to-end) 하이라이트 파이프라인을 보유하고 있었지만, 새로 교체된 "픽셀 파인더(pixel finder)"는 관대한 가정 하에 측정되었습니다. 즉, 완벽한 트래킹과 GT-보간된 박스(GT-interpolated boxes)를 가정한 것이었습니다. 실제 운동학(kinematics)에 대해 정직하게 측정한 제품(product) 운영 지점은 R 0.52 / P 0.57에 불과했습니다. 우리는 실제 트래킹 잠금(tracking lock)이 포함된 실제 파이프라인에 이를 연결하고, 무언가를 주장하기 전에 product_eval로 재확인해야 한다고 평문으로 기록했습니다.
이 로그는 그 재확인 과정이며, 동시에 우리가 주의를 기울이지 않는 동안 일어난 더 큰 사건, 즉 모델이 더 이상 모델이 아닌 제품이 된 과정을 담고 있습니다.
현재 상황:
- 제품 파이프라인 (실제 트래킹, 엔드투엔드): F1 0.887 — 홀드아웃(holdout) 설정에서 재현율(recall) 0.868, 정밀도(precision) 0.908.
- 로그 #02에서 제시했던 관대한 상한선은 정직하게 측정했을 때 무너지지 않았습니다. 유지되었습니다.
- 실제 제품 인터페이스를 출시했습니다: 웹 앱 (업로드 → 플레이어 잠금 → 자동 편집 → 완료) 및 시네마틱 슬로 모션 렌더링 파이프라인.
- 배포 현실 점검: 전체 서빙 경로를 CPU에서 GPU로 전환했습니다.
파트 1 — 별표를 현금화하기: 정직하게 측정된 제품 수치
로그 #02에서 우리는 스포터(spotter)의 헤드라인 재현율(recall)을 축하하지 않도록 주의했습니다. 왜냐하면 그것은 GT-보간된 박스, 즉 트래커가 플레이어를 절대 놓치지 않는다고 가정하고 측정되었기 때문입니다. 그것은 스포터의 홈 필드 이점입니다. 진짜 질문은 항상 이것이었습니다: 실제 제품 설정에서 엔드투엔드로 실행되는 실제 트래커로부터 박스가 생성될 때 어떤 일이 벌어지는가?
우리는 이를 실행했습니다. 여기 동일한 45개 비디오 / 68개 핵심(critical) 홀드아웃 세트에 대한 평가 보고서에서 직접 가져온 증거들이 있습니다:
⚠️ [IMG:N] 형식 토큰은 이미지 placeholder 입니다. 번역하지 말고 원래 위치에 그대로 유지하세요.
| Config | Recall | Precision | F1 | Fired | TP |
|---|---|---|---|---|---|
config.yaml (product baseline) | 0.853 | 0.906 | 0.879 | 64 | 58 |
| ... | |||||
| The number to sit with: Log #02 제품 운영 지점은 R 0.52 / P 0.57이었습니다. 재확인된, 실제 추적(real-tracking), 엔드투엔드(end-to-end) 제품 지점은 R 0.87 / P 0.91 / F1 0.887입니다. |
우리가 왜 이것이 속임수가 아닌지 정확히 알고 싶습니다. Log #02의 0.52/0.57은 키네마틱 전용(kinematic-only) 운영 지점이었으며, 이는 픽셀 스포터가 추적 잠금(tracking lock)과 통합되기 이전의 구형 파인더였습니다. 솔직한 우려는 스포터의 0.86 정도의 리콜이 완벽한 박스(perfect boxes)에 의한 인공물일 뿐이며 실제 트랙에서는 무너질 것이라는 것이었습니다. 하지만 그렇지 않았습니다. 우리가 스포터를 실제 추적기와 함께 실제 파이프라인에 통합했을 때, 엔드투엔드 제품 수치는 구성(config)에 따라 F1 0.879–0.887에 도달했습니다. 관대한 가정과 솔직한 측정치가 서로 벗어나기보다는 수렴했습니다. 이것이 좋은 결과이며, 우리가 얻을 수 있을지 가장 확신하지 못했던 부분이었습니다.
솔직하게 확실히 하는 두 가지:
1. GPU 패리티는 근사치가 아닌 정확합니다. Apple Silicon의 config.yaml과 CUDA의 config_gpu_a는 동일한 R 0.853 / P 0.906 / F1 0.879를 산출하며, 동일하게 64개의 발화(fired)와 58개의 TP를 보입니다. 이것이 중요합니다: 이는 GPU로 이동하는 것이 품질 결정이 아니라 처리량(throughput) 결정이라는 의미입니다. 모델은 우리가 출하하는 박스에서 다르게 작동하지 않습니다.
2. gpu_b는 업그레이드가 아닌 조절 장치(knob)입니다. 정밀도에 치우친 변형 버전은 리콜(0.853 → 0.809)을 약간 희생하여 정밀도를 조금 더 높였습니다(0.906 → 0.917). 동일한 68개의 핵심 항목(criticals)이 있지만, 발화는 적습니다. 이것은
렌더링 파이프라인: "순간을 발견하다"에서 "여기에 클립이 있습니다"로
충격 시점 T를 감지하는 것은, 그것이 시청 가능한 형태로 출력되지 않는 한 휴대폰을 든 부모에게는 아무런 가치가 없습니다. 그래서 우리는 출력 레이어(output layer)를 구축했습니다. 즉, _(비디오 + 타겟 궤적 + 충격 시점 T)_를 세로형 슬로우 모션 하이라이트 mp4로 변환하는 오케스트레이터(orchestrator)입니다. 흐름은 의도적으로 순서가 정해져 있습니다(안정화(stabilize)를 수행한 후에 시간 재조정(retime)을 진행):
- 전체 비디오가 아닌,
T주변의 윈도우([pre, post])만을 네이티브 fps로 디코딩합니다. - 타겟 궤적(target trajectory)을 실제 프레임 시간으로 리샘플링(resample)하고, 칼만/RTS(Kalman/RTS) 패스를 통해 부드럽게 만듭니다.
- 무빙 크롭(moving-crop) 가상 카메라를 사용하여 안정화된 프레임을 생성합니다.
- 스피드 램프(speed ramp)를 통합하고 이를 반전시켜 출력 프레임당
(index, α)를 얻습니다. - 보간(interpolate)하여 라이터(writer)로 직접 스트리밍합니다. 전체 클립을 메모리에 버퍼링하지 않습니다.
직관에 반하기 때문에 언급할 가치가 있는 한 가지 설계 결정은 다음과 같습니다: V1은 크롭(crop)이나 줌인(zoom in)을 하지 않습니다. 저해상도의 실제 촬영 영상에서 락온 크롭(lock-on crop)은 이미지 품질을 저하시킬 뿐입니다. 부모가 이미 피사체를 충분히 크게 프레임에 담았기 때문입니다. 세로형 리프레임(vertical reframe)과 락온 줌(lock-on zoom)은 V2로 미루었습니다. 우리는 흔들리는 줌 영상보다는 품질이 저하되지 않은 프레임을 지금 출시하는 쪽을 택했습니다.
슬로우 모션 자체는 리드 앵커드(lead-anchored) 램프를 사용합니다: 운동학적 충격 T는 종종 늦게(착지/팔로스루 시점) 감지되는 경향이 있습니다. 따라서 우리는 슬로우 모션의 중심을 T보다 앞당겨서, 이미 상황이 종료된 후를 슬로우 모션으로 만드는 대신 — 와인드업(wind-up)과 컨택(contact) 같은 — 결정적인 동작을 포함하도록 합니다.
단일 클립 위에는 **릴 컴파일러(reel compiler)**가 있습니다: 하이라이트를 중요도(criticality)에 따라 순위를 매기고, 15/30/60초 예산에 맞는 최적의 클립들을 탐욕적(greedily)으로 선택한 다음, 릴이 여전히 경기의 이야기를 전달할 수 있도록 시간 순서대로 재정렬하며 연결 부위에는 짧은 크로스페이드(crossfade)를 적용합니다. (렌더링과 릴 모두 순수 cv2/numpy 기반이며, FFmpeg를 사용하지 않는 조용한 MVP입니다. 오디오는 2단계(Phase 2) 과제입니다.)
웹 앱
이제 실제 프론트엔드(Front end)가 존재합니다. 업로드, 관심 있는 플레이어 고정, AI 편집 과정 시청, 완성된 세로형 하이라이트 수령까지 비기술적 사용자에게 필요한 전체 흐름을 갖춘 이중 언어(KO/EN) React/TS 앱입니다. 랜딩/업로드, 타겟 고정(Target lock), 처리 중, 갤러리, 완료 화면으로 구성됩니다. 핵심 가치는 한 문장으로 요약됩니다: 아이의 경기 영상을 업로드하면, 하이라이트를 얻을 수 있습니다. 편집기는 건드릴 필요도 없습니다.
Part 3 — 배포의 현실: CPU → GPU
로그 #02의 배포 계획은 Oracle Cloud의 Always Free 티어였습니다. ARM CPU 기반의 torch-CPU를 사용하여 GPU 없이 24/7 가동하는 것이 목표였습니다. 노트북이 잠자기 모드로 들어가도 비용 부담 없이 계속 작동하는 실제 서버를 만드는 것이 목적이었습니다. 하지만 두 가지 이유로 인해 계획을 변경해야 했습니다.
첫째, 무료 티어가 축소되었습니다. Oracle은 Always Free A1 사양을 4 OCPU / 24 GB에서 2 OCPU / 12 GB로 축소했습니다 (2026-06 예정). 우리가 실행하는 워크로드(Workload)를 감당하기에는 여유 공간(Headroom)이 부족해졌습니다.
둘째, 결정적으로, 우리가 실제로 출시한 파이프라인(Pipeline)에 CPU 추론(Inference)은 너무 느립니다. 프로덕션 경로(Production path)는 영상 전체에 대해 RT-DETR 탐지(Detection)와 r2plus1d_18 밀집 점수 산정(Dense scoring)을 수행합니다. CPU에서는 단일 작업에 몇 분이 소요됩니다. 비동기 방식의 "알림을 보내드리겠습니다" 식의 MVP라면 버틸 수 있겠지만, 일일 처리량(Throughput)에 심각한 제한을 줍니다. 그래서 우리는 GPU 서빙(Serving) 경로를 구축했습니다: Dockerfile.cuda, 단 한 번의 명령으로 실행되는 deploy_gpu.sh (rsync → Docker + nvidia-container-toolkit + 방화벽 + 빌드 + 시작의 원격 프로비저닝), 그리고 T4 벤치마크 단계가 포함됩니다. 픽셀 탐지기(video_scorer.pth)는 배포 아티팩트(Artifact)로서 이미지에 포함되며, Part 1에서 언급했듯이 GPU 출력은 로컬과 비트 단위로 동일한 품질을 유지하므로, 이는 순수하게 하루에 더 많은 작업을 처리하기 위한 조치입니다.
Part 4 — 우리가 계속 폐기 상태로 유지할 두 가지 막다른 길 (증거 포함)
정직함이란 작동하지 않았던 것들을 보고하고, 그것들을 다시 살려내지 않도록 절제하는 것을 의미합니다.
RF-DETR 미세 조정 (fine-tuning)은 보류 상태로 유지됩니다. 로그 #01의 영역으로 돌아가 보자면, 우리는 RF-DETR을 mAP 0.716까지 미세 조정(fine-tuning)했으나, 이는 엔드투엔드 재현율 (end-to-end recall)을 오히려 _퇴보_시켰습니다. 실제 도메인(real domain)에서 재확인한 결과, 개선 사항은 없었습니다. 우리는 베이스라인인 RT-DETR을 유지합니다. RF-DETR으로 전환하는 것은 여전히 설정 플래그(config flag) 하나로 가능하지만, 측정된 결론은 해당 모델이 그 복잡성을 정당화할 만큼의 가치가 없다는 것입니다.
pose-TCN 액션 분류기 (action classifier)는 이제 레거시 (legacy)가 되었습니다. Pixel Spotter가 확정된 프로덕션용 탐지 및 판단 모델 (F1 0.887)입니다. 기존의 pose-TCN 경로는 실제 도메인에서 변별력 (discriminative power)을 보여주지 못했기 때문에 사실상 비활성화되었습니다 (crit_tcn_floor: 1.0). 나중에 다시 사용하고 싶다면 언제든 즉시 교체(hot-swappable)가 가능하지만, 기본 경로(default path) 중 그 어느 것도 이에 의존하지 않습니다.
이 두 가지 모두 우리가 숨기는 실패가 아닙니다. 이는 우리가 탐색하고, 측정하고, 종결한 두 갈래의 경로일 뿐입니다. 0.887이라는 수치는 더 많은 모델을 추가해서 얻은 것이 아닙니다. 올바른 모델을 찾아내고, 잘못된 모델을 다시 붙이기를 거부함으로써 얻은 결과입니다.
현재 상황
- 정직한 제품 수치: 실제 트래킹 (tracking)을 포함한 엔드투엔드(end-to-end) F1 0.887 (R 0.868 / P 0.908). 로그 #02의 별표(*) 문제는 우리에게 유리하게 해결되었습니다. 관대한 상한선(upper bound)이 현실과 맞닿은 후에도 살아남았습니다.
- GPU 동등성 (parity): mps와 CUDA에서 동일한 품질을 보입니다. CPU에서 GPU로의 이동은 정확도(accuracy)의 문제가 아니라 처리량(throughput)의 문제입니다.
- 이제는 제품입니다: 실제 웹 앱 뒤에서 업로드 → 잠금(lock) → 자동 편집(auto-edit) → 세로형 슬로 모션 릴(vertical slow-mo reel)로 이어지는 흐름을 갖추었습니다.
- 절제 유지: RF-DETR과 pose-TCN 모두 측정되었고, 모두 종결되었으며, 어느 것도 다시 살려내지 않았습니다.
변하지 않은, 남아있는 별표
로그 #02에서 언급한 도메인 주의 사항(domain caveat)은 여전히 유효하며, 이를 흐지부지 넘기지 않을 것입니다. 이 수치들은 SVW에서 파생된 연구용 푸티지(research footage)를 기반으로 합니다. 진짜 테스트는 실제 사용자 업로드 영상을 활용한 상업적 재구축(commercial rebuild) 단계입니다. 만약 F1 0.887이 해당 도메인 변화(domain shift)에서도 살아남는다면, 우리는 제품을 갖게 되는 것입니다. 만약 수치가 떨어진다면, 이 로그의 다른 모든 수치와 마찬가지로 이곳에서 가장 먼저 그 하락을 보게 될 것입니다.
만약 여러분도 같은 분야에서 제품을 구축하고 있으며 "모델은 작동하지만 이것이 제품인가?"라는 벽에 부딪히고 있다면, 댓글을 통해 의견을 나누고 싶습니다. 특히 비디오 파이프라인 (video pipelines)을 위한 CPU 대 GPU 서빙 (serving) 계산법에 대해 특히 더 그렇습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기