Ultralytics YOLO27: 대기자 명단에 등록하세요.
Get Started
프로덕션 환경의 Ultralytics YOLO·스케일 및 관찰·레슨 8/9
강의중급

관측 가능성 및 드리프트

정확도가 조용히 떨어지는 상황을 포착할 지표를 기록하고, 드리프트 여부를 확인하기 위해 신뢰도 분포를 테스트하며, 조치 가능한 신호에만 알림을 설정하세요.

비전 서비스는 정상적으로 작동하고 빠르게 응답하면서도 잘못된 결과를 낼 수 있습니다. 카메라가 흔들리거나 조명이 바뀌거나 새 제품군이 추가되어도 모델은 박스를 계속 반환하고, 인프라 대시보드는 모두 정상 상태로 표시됩니다. 이 강의에서는 황혼 상황을 시뮬레이션하기 위해 클립을 어둡게 했습니다. 평균 신뢰도는 차트에서 보이지 않을 정도로 작은 0.01만 변했지만, 두 표본 KS 검정에서는 p = 0.007로 변화가 감지되었습니다. 이 검정과 검정에 필요한 로깅을 구현합니다.

학습 성과

프레임별 JSON 지표를 출력하고, 출시 시점 기준선과 비교하는 KS 드리프트 검정을 실행하며, 홀드아웃 세트를 검증하고, 각각 조치와 연결된 알림을 정의하세요.

속성 과정
이미 사용법을 알고 계신다면, 간단히 요약해 드리겠습니다.
  1. 프레임별 항목: 카메라, r.speed["inference"], 감지 개수, 평균 신뢰도, 원시 신뢰도 값.

  2. 출시 시점의 신뢰도 값을 기준선으로 저장하고, 매일 scipy.stats.ks_2samp과 비교하세요.

  3. 매주 yolo val으로 라벨링된 홀드아웃 세트를 검증하고, mAP가 1포인트 넘게 떨어지면 알림을 설정하세요.

  4. 다음 조치가 문서화된 신호에만 알림을 설정하세요. 그 외의 모든 정보는 대시보드 패널로 표시합니다.

실습형

세 가지 신호 계층#

계층질문신호
인프라서비스가 정상적으로 작동하며 처리 속도를 유지하고 있나요?지연 시간, 처리량, 드롭 프레임, CPU 및 GPU 사용량
모델 동작모델이 지난주와 비슷하게 동작하고 있나요?프레임당 감지 수, 평균 신뢰도, 클래스 구성, 박스 크기
모델 정확도모델이 여전히 정확한 결과를 내고 있나요?홀드아웃 mAP(성능 지표), 표본 추출을 통한 사람의 검토, 오탐률

대부분의 팀은 표준 도구로 첫 번째 계층을 모니터링하고 여기서 멈춥니다. 비전 시스템의 문제가 조용히 발생하는 곳은 두 번째와 세 번째 계층입니다. 모델 모니터링 가이드에서 전체 유지보수 루프를 다룹니다.

Ultralytics Platform 배포 페이지의 Logs 탭. 모니터링 및 샘플 호출을 위한 타임스탬프가 포함된 INFO 요청 로그 위에 Errors 및 All 필터가 표시되어 있습니다

Ultralytics Platform을 통해 배포하는 경우 배포 페이지에서 인프라 계층(요청 수, 오류율, P95 지연 시간)과 오류별 필터링이 가능한 요청 로그를 확인할 수 있습니다. 위 예시와 같습니다. Monitor Health and Accuracy 강의에서 해당 항목을 설명합니다. 동작 및 정확도 계층은 직접 구축해야 하며, 이 강의의 나머지 부분에서 그 방법을 다룹니다.

프레임별 지표 출력하기#

모든 Results 객체에는 처리 시간과 박스가 포함됩니다. 프레임마다 JSON 한 줄을 작성하고 사용하는 지표 시스템으로 전송하세요:

import json
import time

from ultralytics import YOLO

model = YOLO("yolo26n.pt")
video = "https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4"
with open("metrics.jsonl", "w") as log:
    for r in model.predict(video, stream=True, verbose=False):
        conf = r.boxes.conf
        record = {
            "ts": time.time(),
            "camera": "lobby-1",
            "inference_ms": round(r.speed["inference"], 1),
            "detections": len(r.boxes),
            "mean_conf": round(conf.mean().item(), 3) if len(conf) else None,
            "confs": [round(c, 3) for c in conf.tolist()],  # 드리프트 검정에 사용할 원시 값
        }
        log.write(json.dumps(record) + "\n")
print({k: v for k, v in record.items() if k != "confs"})
{'ts': 1792003254.33, 'camera': 'lobby-1', 'inference_ms': 34.7, 'detections': 22, 'mean_conf': 0.543}

각 필드의 용도:

  • inference_ms: p95가 예산을 초과하면 알림을 보냅니다.
  • detections: 값이 갑자기 거의 0으로 떨어지면 카메라가 차단되었거나 고장 났을 가능성이 높고, 급증하면 장면이 바뀌었거나 오탐이 발생한 것입니다.
  • mean_conf: 빠르게 추세를 파악할 수 있지만, 이것만으로는 드리프트를 감지하기에 너무 거친 지표입니다.
  • confs: 아래의 드리프트 테스트에 필요한 전체 분포입니다.

신뢰도 분포를 테스트하여 드리프트 감지하기#

데이터 드리프트는 새 데이터에 누군가 라벨을 지정하기 훨씬 전부터 신뢰도에 나타납니다. 출시 시 신뢰도를 수집해 기준선으로 삼고, 최근 구간의 신뢰도를 수집한 다음 두 분포를 2표본 Kolmogorov-Smirnov 검정(pip install scipy)으로 비교합니다. 이 스크립트는 같은 클립을 어둡게 만들어 황혼을 시뮬레이션합니다:

import cv2
import numpy as np
from scipy.stats import ks_2samp

from ultralytics import YOLO
from ultralytics.utils.downloads import safe_download

model = YOLO("yolo26n.pt")
video = str(safe_download("https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4"))

def confidences(darken=1.0):
    """Collect every detection confidence from the clip, optionally darkened to simulate dusk."""
    cap, out = cv2.VideoCapture(video), []
    while True:
        ok, frame = cap.read()
        if not ok:
            break
        out += model((frame * darken).astype(np.uint8), verbose=False)[0].boxes.conf.tolist()
    return np.array(out)

baseline = confidences()  # what "normal" looked like at launch
for name, recent in (("same scene", confidences()), ("dusk (35% brightness)", confidences(darken=0.35))):
    stat, p = ks_2samp(baseline, recent)
    print(f"{name:22s} n={len(recent):4d} mean_conf={recent.mean():.3f} KS={stat:.3f} p={p:.2g}")
same scene             n=1376 mean_conf=0.519 KS=0.000 p=1
dusk (35% brightness)  n=1337 mean_conf=0.508 KS=0.064 p=0.0068

각 항목을 하나씩 살펴보겠습니다.

  • 같은 장면: 분포가 동일하면 KS 통계량은 0, p = 1이며, 이는 대조군입니다.
  • 황혼: 검출 수가 39건 줄고 평균은 0.011만 낮아져 차트로는 알아차리기 어렵습니다. 두 누적 분포 사이의 최대 간격인 KS 통계량은 0.064이며, p = 0.0068 < 0.01이므로 실제로 변화가 발생했음을 나타냅니다.

드리프트 알림은 샘플 프레임을 살펴보라는 신호이지, 실패의 증거는 아닙니다. 실제 드리프트라면 영향을 받은 카메라에서 프레임을 수집하고 라벨을 지정합니다. 데이터 수집 및 어노테이션 가이드에서는 수집할 항목을, 파인튜닝 가이드에서는 재학습 방법을 설명합니다. 바운딩 박스 크기(너비 × 높이)는 간단한 두 번째 드리프트 신호입니다. 새 마운트와 광학 장비의 영향이 가장 먼저 나타납니다.

홀드아웃 세트를 매주 검증하기#

동작 신호는 무언가 바뀌었다는 점을 알려주지만, 정확도가 떨어졌는지는 라벨이 있어야 알 수 있습니다. 라벨이 지정된 프로덕션 이미지 200~500장으로 홀드아웃 세트를 유지하고, 모든 모델 버전과 매주 검증합니다:

yolo val model=yolo26n.pt data=coco8.yaml > weekly_metrics.txt  # use your production model and holdout YAML

시간에 따른 mAP50-95 및 클래스별 AP를 기록하고, mAP가 전주 대비 1포인트 넘게 떨어지면 알림을 보냅니다. 가장 흔한 원인은 아무런 변화가 없어야 하는 모델 교체이며, 두 번째 원인은 드리프트입니다. 같은 홀드아웃 세트의 혼동 행렬을 사용하면 클래스별 성능 저하를 쉽게 파악할 수 있습니다.

시간에 따른 모델 품질: 새 라벨 데이터가 유입되는 동안 품질이 유지되다가 예상된 저하 시점 이후 하락하며, 지금 재학습할지 기다릴지 묻는 그래프

이 차트의 질문에 답해 주는 것이 홀드아웃입니다. 품질이 떨어지기 시작하는 것으로 나타나거나 드리프트가 발생한 조건의 새 라벨 데이터가 충분히 쌓였을 때 재학습합니다. 정해진 일정에 맞춰 재학습하지 않습니다.

수치가 아닌 조치에 알림을 설정하기#

신호알림 발생 조건조치
p95 inference_ms3분 이상 예산 초과규모를 확장하거나 최적화된 내보내기 형식으로 전환합니다
프레임당 검출 수5분 동안 50% 이상 감소카메라 피드를 확인한 다음 모델 버전을 확인합니다
드롭된 프레임10분 동안 5% 초과용량을 추가하거나 스트림별 FPS를 낮춥니다
신뢰도 KS 검정일별 구간에서 p < 0.01프레임을 검토한 다음 홀드아웃을 실행합니다
홀드아웃 mAP50-95전주 대비 1포인트 이상 하락데이터를 수집하고 라벨을 지정한 다음 파인튜닝합니다

주의할 점#

비교할 수 없는 시간 구간 비교하기

증상: KS 검정이 매일 저녁과 매주 월요일마다 경고를 발생시킵니다. 해결 방법: 일별 및 주별 주기는 실제 분포 변화입니다. 동일한 조건끼리 비교하고(같은 카메라, 같은 시간대, 같은 요일), 수천 건의 탐지를 포함하는 시간 구간을 사용합니다.

오래된 홀드아웃 데이터

증상: 홀드아웃 mAP는 양호하지만 사용자는 누락 사례를 보고합니다. 해결 방법: 홀드아웃 데이터는 현재 프로덕션 환경을 반영해야 합니다. 중요한 카메라에서 새로 레이블을 지정한 프레임을 추가해 분기마다 갱신합니다.

아무도 대응하지 않을 문제라면 알림을 보내지 마세요

모든 알림에는 서면으로 작성된 다음 조치가 필요합니다. 단순히 지켜보고 싶은 신호는 호출 알림이 아니라 대시보드에 표시해야 합니다.

사용해 보기

JSON 로깅을 파이프라인에 추가하고 한 시간 동안(또는 긴 동영상 전체에서) 실행합니다. 첫 번째 구간을 baseline.jsonl으로 유지한 다음, 두 번째 구간에 대해 솔루션의 KS 검사를 실행합니다. 다른 시간대, 다른 카메라 또는 어둡게 만든 복사본을 사용할 수 있습니다. KS 통계량과 p값을 제공하고, 모든 행에 조치가 포함된 알림 표를 제출합니다.

완료 조건
다음 조건을 모두 충족하면 레슨을 완료한 것입니다.
  • 파이프라인은 지연 시간, 검출 수, 평균 신뢰도, 원시 신뢰도를 포함하는 JSON 라인을 프레임마다 하나씩 기록합니다.

  • 기준선과 최근 구간 사이에 KS 검정을 실행하고 통계량과 p값을 기록했습니다.

  • 정의한 모든 알림에 임계값, 지속 시간, 문서화된 후속 조치가 설정되어 있습니다.

정답 보기

정상 상태에서 생성된 monitor.py 출력의 첫 구간을 baseline.jsonl로 유지하고, 새 구간을 각각 이 구간과 비교합니다:

import json

import numpy as np
from scipy.stats import ks_2samp

def load(path):
    """Flatten the per-frame 'confs' lists written by monitor.py."""
    with open(path) as f:
        return np.array([c for line in f for c in json.loads(line)["confs"]])

baseline, recent = load("baseline.jsonl"), load("metrics.jsonl")
stat, p = ks_2samp(baseline, recent)
print(f"KS={stat:.3f} p={p:.2g} -> {'review frames' if p < 0.01 else 'ok'}")
KS=0.000 p=1 -> ok

두 파일이 같은 영상에서 나온 경우 기대되는 결과입니다. 스케줄러를 사용해 매일 실행하고 통계량과 p값을 프레임별 로그와 같은 메트릭 시스템으로 보내며, review frames은 호출 알림이 아니라 담당자에게 전달합니다.

다음 단계

마지막 강의에서는 대시보드에서 지연 시간이나 비용이 예산을 초과한다고 표시될 때 어떤 조치를 먼저 취할지 알아봅니다.