콘텐츠로 이동

옵션 C 마이그레이션 계획 — 학습/평가 셋업의 경로 추상화

상태: 계획 단계 (본 학습 yolo26x × 150 epoch 종료 후 실행) 목표: 데이터셋 폴더만 다른 머신/사용자에게 전달해도 학습·평가 재현 가능하도록 경로/의존성 추상화 위치: D:/YOLO_train_dataset/training/

핵심 결정 요약

결정 의미
경로 추상화 hard-coded → --dataset-dir CLI 인자 + vas.yaml
vas.yaml 이 단일 진실 소스 train/val/val_quick/gt_json/gt_coco/nc/names 모두 yaml 에
nzeval/ 별도 폴더 NZVideoDetector_eval 호출 layer 를 한 곳에 집중
dataset_curation/prepare_dataset.py raw (JPEGImages+labels+vas.names) → 학습 가능 데이터셋, in-place, idempotent. train.py 가 자동 호출 (변환 전 상태면 자동 실행, 변환 완료면 skip)
bin/ + requirements.txt + setup.md 다른 학습 서버에 통째로 이관 가능
멀티 GPU --device 0,1,2,3 (ultralytics DDP 자동)
파일명 v2 제거 train.txt, valid.txt, valid_strid_coco.json 등 표준화
--no-nzeval (1차 구성) callback cache bug 회피. 학습 후 fill_missing → post_eval 로 정확한 trajectory 재산출

1. Before / After 한눈에

Before (현재 — 의존성 분산)

[데이터] D:/YOLO_train_dataset/20260611/
    ├─ images/, labels/
    ├─ vas.yaml, valid_v2.txt, train_v2_oversampled.txt
    └─ valid_v2_strid_coco.json

[코드] c:/work/claudeTemp/*.py (또는 c:/tmp/*.py)
    └─ train_full.py:  hard-coded 절대 경로 + c:/work import

[빌드] c:/work/NZDeeplearningSDK_V7/NZAILibs/build/bin/x64/Release/
    └─ NZVideoDetector_eval.exe, NZVideoDetector_mt_test.exe
→ 데이터셋만 이관해도 학습 불가능 (코드 + exe + 절대경로 c:/work 종속).

After (목표 — 자족적 구조)

D:/YOLO_train_dataset/
├─ training/                            ← 코드 + exe + 의존성 한 곳
│  ├─ settings.py                         (NZ_EVAL_EXE/NZ_MT_EXE auto-detect + nzeval sys.path)
│  ├─ train.py                            (vas.yaml 에서 list/GT 경로 자동 추출)
│  ├─ eval_final.py                       (TRT FP16 사후 평가)
│  ├─ post_eval.py, fill_missing.py
│  ├─ chart_trajectory.py                 (mAP/loss/S/M/L trajectory)
│  ├─ nzeval/                             ← NZVideoDetector_eval 호출 layer
│  │  ├─ __init__.py                       (attach_nz_callback, run_nz_eval 재노출)
│  │  ├─ ultralytics_nzeval.py             (c:/work 본체 사본 또는 shim)
│  │  ├─ runner.py                         (NZ_EVAL_EXE subprocess wrapper)
│  │  ├─ callback.py                       (eval_every_n / metric_key 캡슐화)
│  │  └─ README.md                         (의존성, env var)
│  ├─ dataset_curation/                   ← raw → trainable 파이프라인
│  │  ├─ prepare_dataset.py                (통합 entry, 9 step, idempotent)
│  │  ├─ rename_to_images.py               (JPEGImages → images in-place)
│  │  ├─ cleanup_and_bbox.py
│  │  ├─ make_vas_yaml.py                  (vas.names → vas.yaml)
│  │  ├─ video_split.py, verify_split.py, check_video_leak.py
│  │  ├─ analyze_dist.py, oversample.py
│  │  ├─ valid_quick.py
│  │  └─ yolo2coco.py, make_strid_gt.py
│  ├─ benchmarks/                         ← 운영 모델 벤치 (선택)
│  ├─ bin/                                ← exe + DLL (다른 서버 이관)
│  │  ├─ NZVideoDetector_eval.exe
│  │  ├─ NZVideoDetector_mt_test.exe
│  │  ├─ onnxruntime*.dll, opencv*.dll, ...
│  │  ├─ build_info.txt                    (빌드 SHA + ORT/CUDA/TRT 버전)
│  │  └─ README.md
│  ├─ requirements.txt                    (pip 의존성)
│  ├─ setup.md                            (시스템 의존: CUDA/cuDNN/TRT 버전)
│  ├─ README.md                           (신규 사용자 진입점)
│  └─ MIGRATION_PLAN.md                   (본 문서, 작업 완료 후 ARCHIVE/ 이동)
│
├─ 20260611/                             ← 현재 데이터셋 (v2 → 표준 rename 예정)
│  ├─ images/, labels/, vas.yaml, vas.names
│  ├─ train.txt, valid.txt, valid_quick.txt, train_oversampled.txt
│  ├─ valid_coco.json, valid_strid_coco.json
│  ├─ DATASET_INFO.md
│  └─ runs/vas_full_yolo26x/              ← 본 학습 산출
│
└─ 20260801/                             ← (예시) 새 데이터셋, 동일 구조

2. vas.yaml — 단일 진실 소스

prepare_dataset.py 가 생성, train.py 가 읽음.

# ultralytics 표준
path: .                              # 데이터셋 root (vas.yaml 위치 기준 상대)
train: train_oversampled.txt
val:   valid.txt
nc: 71
names:
  - person
  - car
  - bicycle
  - ...

# 커스텀 (train.py / nzeval 사용, ultralytics 는 무시)
val_quick: valid_quick.txt           # callback 빠른 평가용
gt_json:   valid_strid_coco.json     # NZVideoDetector_eval 호환 stem-id GT
gt_coco:   valid_coco.json           # 표준 COCO GT (참고)
_prepare_dataset_version: "1.0"      # idempotent skip 판정용

효과: 경로 변경 시 yaml 만 수정. train.py 코드는 그대로.


3. train.py CLI (멀티 GPU 포함)

import argparse, yaml
from pathlib import Path

p = argparse.ArgumentParser()
p.add_argument('--dataset-dir', required=True, type=Path,
               help='데이터셋 루트 (vas.yaml 위치)')
p.add_argument('--model', default='yolo26x.pt')
p.add_argument('--epochs', type=int, default=150)
p.add_argument('--batch', type=int, default=16,
               help='전체 batch (멀티 GPU 시 per-GPU 가 아님)')
p.add_argument('--imgsz', type=int, default=640)
p.add_argument('--patience', type=int, default=30)
p.add_argument('--eval-every-n', type=int, default=10)
p.add_argument('--ep', choices=['cuda','trt'], default='cuda')
p.add_argument('--device', default='0',
               help='"0" | "0,1" | "0,1,2,3" (DDP 자동)')
p.add_argument('--workers', type=int, default=4)
p.add_argument('--name', default=None)
p.add_argument('--skip-prepare', action='store_true',
               help='prepare_dataset.py 자동 호출 건너뛰기 (기본: 변환 전 상태면 자동 실행)')
args = p.parse_args()

# 변환 전 상태면 prepare_dataset.py 자동 호출 (idempotent — 이미 변환됐으면 즉시 skip)
DATA_YAML = args.dataset_dir / 'vas.yaml'
if not args.skip_prepare:
    from dataset_curation.prepare_dataset import main as prepare_main
    prepare_main(['--dir', str(args.dataset_dir)])
    # 산출물 보장 — vas.yaml + train/valid/quick lists + COCO/strid GT
assert DATA_YAML.exists(), f'vas.yaml 없음. --skip-prepare 끄거나 수동으로 prepare_dataset 실행'

# vas.yaml 파싱 → 모든 경로 자동 추출
with open(DATA_YAML, encoding='utf-8') as f:
    yml = yaml.safe_load(f)
root      = (args.dataset_dir / yml.get('path', '.')).resolve()
VAL_LIST  = root / yml.get('val_quick', yml['val'])
GT_JSON   = root / yml['gt_json']
RUN_NAME  = args.name or f'vas_{Path(args.model).stem}'

device = ([int(x) for x in args.device.split(',')]
          if ',' in args.device else int(args.device))

사용 예

# 단일 GPU (이미 변환된 데이터셋 — prepare_dataset 자동 호출돼도 즉시 skip)
python training/train.py --dataset-dir D:/YOLO_train_dataset/20260611 --batch 16

# raw 상태 (JPEGImages + labels + vas.names) — prepare_dataset 자동 실행 후 학습
python training/train.py --dataset-dir D:/YOLO_train_dataset/20260801 --batch 16

# 4-GPU DDP (batch 는 전체)
python training/train.py --dataset-dir D:/YOLO_train_dataset/20260801 --batch 64 --device 0,1,2,3

# 2-GPU
python training/train.py --dataset-dir D:/YOLO_train_dataset/20260801 --batch 32 --device 0,1

# prepare 명시 건너뛰기 (디버그용)
python training/train.py --dataset-dir D:/YOLO_train_dataset/20260611 --skip-prepare

멀티 GPU 주의

  • batch 는 전체 값 (ultralytics 8.x 규약, DDP 시 각 GPU 가 batch/N 처리)
  • workers ×N (RAM 부담)
  • 첫 epoch NCCL init ~30s
  • Windows = gloo backend (느림). 4-GPU+ 학습은 Linux 권장

4. settings.py — 외부 의존 자동 탐지

# training/settings.py
"""외부 의존성 (NZ exe들, nzeval 패키지) 자동 탐지 + env var override.
우선순위: env var > training/bin/ > 개발 환경(c:/work)
"""
import os, sys
from pathlib import Path

THIS_DIR   = Path(__file__).resolve().parent
NZEVAL_PKG = THIS_DIR / 'nzeval'

def _find_exe(name, env_var):
    env = os.environ.get(env_var)
    if env and Path(env).exists():
        return Path(env)
    candidates = [
        THIS_DIR / 'bin' / name,                                                  # 1순위: 배포 패키지
        Path(r'c:/work/NZDeeplearningSDK_V7/NZAILibs/build/bin/x64/Release') / name,  # 2순위: 개발
    ]
    for c in candidates:
        if c.exists():
            return c
    raise FileNotFoundError(
        f'{name} not found. Set {env_var}, copy to training/bin/, or build NZAILibs.sln'
    )

NZ_EVAL_EXE = _find_exe('NZVideoDetector_eval.exe',     'NZ_EVAL_EXE')
NZ_MT_EXE   = _find_exe('NZVideoDetector_mt_test.exe',  'NZ_MT_EXE')

# nzeval 패키지 import 가능하도록
if str(NZEVAL_PKG.parent) not in sys.path:
    sys.path.insert(0, str(NZEVAL_PKG.parent))

학습/평가 코드에서: from nzeval import attach_nz_callback, run_nz_eval


5. dataset_curation/prepare_dataset.py — raw → trainable

입력 가정 (in-place 동일 폴더)

<dir>/
├─ JPEGImages/      ← ultralytics 가 인식 못 함 (images/ 만)
├─ labels/          ← YOLO 포맷 (cls xc yc w h, 0~1)
└─ vas.names        ← 클래스명 한 줄당 하나 (UTF-8 권장)

출력 (같은 폴더, 9개 신규 산출)

<dir>/
├─ images/                       ← in-place rename
├─ labels/, .excluded/, vas.names (보존)
├─ vas.yaml                      ← list/GT 경로 포함
├─ train.txt, valid.txt, valid_quick.txt, train_oversampled.txt
├─ valid_coco.json, valid_strid_coco.json
└─ DATASET_INFO.md

9-step 파이프라인 (idempotent — 산출물 존재 + 검증 통과면 skip)

Step 모듈 동작 skip 판정
1 rename_to_images.py JPEGImages/images/ in-place rename images/ 존재 + JPEGImages/ 없음
2 cleanup_and_bbox.py 검증 실패는 .excluded/ 로 이동 .cleanup_done sentinel
3 make_vas_yaml.py vas.namesvas.yaml (list/GT 경로 + nc/names) yaml 의 필수 필드 검증
4 video_split.py video 단위 train/valid split 두 list 존재 + stem 매칭
5 verify_split.py + check_video_leak.py leakage=0 강제 (실패시 abort) (항상 수행)
6 analyze_dist.py class_distribution.json + rare class 식별 json + 라벨 수 일치
7 oversample.py train_oversampled.txt (rare class boost) 파일 + rare class ≥ target
8 valid_quick.py valid_quick.txt (1000장 stratified) 파일 + 줄 수 일치
9 yolo2coco.py + make_strid_gt.py valid_coco.json, valid_strid_coco.json 두 json + image 수 일치

완전 skip: vas.yaml _prepare_dataset_version + 모든 산출물 검증 통과 → 즉시 종료 (no-op). 강제 재실행은 --force.

CLI

p.add_argument('--dir', required=True, type=Path,
               help='데이터셋 폴더 (JPEGImages/, labels/, vas.names 있는 곳). in-place 변환')
p.add_argument('--train-ratio',    type=float, default=0.85)
p.add_argument('--rare-threshold', type=int,   default=200)
p.add_argument('--rare-target',    type=int,   default=500)
p.add_argument('--valid-quick-n',  type=int,   default=1000)
p.add_argument('--skip',  nargs='*', default=[], help='건너뛸 step')
p.add_argument('--force', action='store_true',   help='산출물 있어도 강제 재실행')
p.add_argument('--verify-only', action='store_true', help='변환 안 함, 무결성만 검증')

새 데이터셋 추가 워크플로 (1-step 또는 2-step)

1-step (자동): train.py 가 prepare_dataset 을 내부에서 자동 호출

# 1. 폴더 만들고 raw 자료 배치
mkdir D:/YOLO_train_dataset/20260801
cp -r <raw>/JPEGImages D:/YOLO_train_dataset/20260801/
cp -r <raw>/labels     D:/YOLO_train_dataset/20260801/
cp    <raw>/vas.names  D:/YOLO_train_dataset/20260801/

# 2. 학습 시작 (prepare_dataset 자동 실행 → 학습)
python training/train.py --dataset-dir D:/YOLO_train_dataset/20260801

2-step (명시): 변환과 학습을 분리하여 변환 결과 먼저 점검

# 1. raw 자료 배치 (위와 동일)
# 2. 명시적 변환
python training/dataset_curation/prepare_dataset.py --dir D:/YOLO_train_dataset/20260801

# 3. DATASET_INFO.md 확인 후 학습
python training/train.py --dataset-dir D:/YOLO_train_dataset/20260801

인코딩 hazard (이미 알려진 함정)

  • vas.names cp949 자동 감지 → UTF-8 변환
  • list 파일 UTF-8 no BOM (BOM 있으면 ultralytics 깨짐)
  • COCO JSON pycocotools 호환 (UTF-8 명시 + dict 주입)
  • 한글 경로 → cv::imread 호환 (utf8_to_acp/acp_to_utf8)

6. nzeval/ — NZVideoDetector_eval 호출 layer

c:/work/NZDeeplearningSDK_V7/NZAILibs/NZVideoDetector_eval/ultralytics_nzeval.py 의 사본을 training/nzeval/ 로. 의존 호출이 한 폴더에 집중.

파일 역할
__init__.py attach_nz_callback, run_nz_eval, evaluate_two_onnx 재노출
ultralytics_nzeval.py 본체 사본 (또는 sys.path import shim)
runner.py NZ_EVAL_EXE subprocess wrapper (UTF-8/ACP 인코딩 처리)
callback.py ultralytics callback 부착 (eval_every_n / metric_key 캡슐화)
README.md exe 위치, env var, 의존성 명시

사용: from nzeval import attach_nz_callback


7. 다른 학습 서버 이관 (Portability)

이관 단위

D:/YOLO_train_dataset/ 전체 (또는 일부) — training/ + <date>/ 데이터셋. training/ 안에 코드 + exe + 의존성 모두 포함.

새 서버 셋업 절차

# 1. 폴더 통째로 복사
robocopy \\old-server\D$\YOLO_train_dataset  D:\YOLO_train_dataset  /MIR

# 2. 시스템 의존성 확인 (setup.md 참조)
nvcc --version          # CUDA 12.5
nvidia-smi              # GPU + driver 545+
# cuDNN 9, TRT 10.7 path 설정

# 3. Python 의존성
cd D:\YOLO_train_dataset\training
pip install -r requirements.txt

# 4. (선택) env var 명시 — 자동 탐지 fallback 가능
set NZ_EVAL_EXE=D:\YOLO_train_dataset\training\bin\NZVideoDetector_eval.exe
set NZ_MT_EXE=D:\YOLO_train_dataset\training\bin\NZVideoDetector_mt_test.exe

# 5. smoke test
python training\train.py --dataset-dir D:\YOLO_train_dataset\20260611 \
                       --epochs 1 --fraction 0.01 --name smoke_test

bin/ 의 필수 DLL (시스템 PATH 외)

필수 (bin/ 동봉):
  onnxruntime.dll
  onnxruntime_providers_shared.dll
  onnxruntime_providers_cuda.dll
  onnxruntime_providers_tensorrt.dll
  opencv_world4xx.dll
  libfbgemm.dll / dnnl.dll
  vcruntime140.dll / msvcp140.dll

시스템 (CUDA Toolkit / TRT 설치 제공):
  cudart64_*.dll, cudnn*.dll, nvinfer*.dll, cublas64_*.dll ...
bin/ 사이즈: ~500MB-1GB

호환성 표

항목 영향 대응
멀티 GPU (1 → 2/4/8) DDP 자동. batch=전체값. NCCL init ~30s --device 0,1,2,3. Windows 는 gloo (느림), 4-GPU+ Linux 권장
GPU 모델 (4090 → 3090) TRT engine cache 재빌드 (~3-4분/모델) 새 GPU 첫 실행에서 자동
GPU SM (sm_89 → sm_86) engine 재빌드 + ORT compile 재 동일 — 첫 실행에서 자동
CUDA 버전 mismatch ORT/TRT import 실패 setup.md 에 정확한 버전 핀 (12.5 / 9 / 10.7)
한글 경로 cv::imread, fs::path 실패 nzeval/runner.py 의 utf8_to_acp/acp_to_utf8 (이미 처리)
DataLoader workers RAM/CPU 따라 안정 worker 수 다름 --workers 인자 (CPU 코어/2 default 검토)
Path separator Windows \ ↔ Linux / pathlib.Path (이미 적용)

배포 패키징 옵션

  • zip: 7z a training_package.zip training/ (~1 GB, 데이터셋 제외)
  • git repo: training/ 만 별도 repo, 데이터셋은 별도 (DVC/rsync/S3)
  • (향후) Docker: 시스템 의존 봉인, 이미지 5GB+, GPU passthrough 셋업

8. 단계별 작업 순서 (학습 종료 후)

  1. settings.py + 외부 의존 자동 탐지 — NZ_EVAL_EXE/NZ_MT_EXE 탐지 + import test
  2. nzeval/ 별도 폴더 구축 — ultralytics_nzeval.py 사본 + runner/callback + from nzeval import ... 검증
  3. train.py 작성 — claudeTemp/train_full.py 기반, vas.yaml 에서 list/GT 추출 + --device 멀티 GPU
  4. 20260611 smoke testpython train.py --dataset-dir ... --epochs 1 --fraction 0.01 --name smoke
  5. eval_final.py — TRT FP16 사후 평가
  6. post_eval.py + fill_missing.py
  7. chart_trajectory.py — chart_final + chart_nzeval 통합
  8. dataset_curation/ + prepare_dataset.py — raw → trainable 자동화
  9. benchmarks/ 이관 (선택)
  10. bin/ 패키지 구축 — exe + DLL 복사 + build_info.txt + bin/README.md
  11. requirements.txt + setup.md — Python/시스템 의존성 명세
  12. README.md — 신규 사용자 진입점 (nzeval/ + bin/ + setup.md cross-link)
  13. 마지막 검증 — 가짜 새 데이터셋 (20260611 sub-sample) end-to-end + --device 0,1 smoke
  14. 20260611 v2 → 표준 renamevalid_v2.txt → valid.txt 등 + vas.yaml 에 list/GT 경로 필드 추가. runs/vas_full_yolo26x/ 영향 없음 확인
  15. claudeTemp/ 학습/평가 사본 삭제 (또는 deprecated 디렉터리)
  16. c:/tmp/ 원본 삭제 (사용자 다시 확인 후)

각 단계 완료 후 검증: - [ ] 인자 변경 후 기존 데이터셋 결과 동등 - [ ] env var override 동작 - [ ] runs/ 폴더가 dataset-dir 아래 정확히 생성 - [ ] 한글 경로 dataset-dir 에서도 동작


9. 안전장치

롤백

  • claudeTemp/, c:/tmp/ 원본은 모든 검증 통과 후에만 정리 (step 15, 16)
  • 신규 파일은 git checkpoint hook 으로 자동 추적 → 언제든 복귀
  • 검증 실패 시 USE_LEGACY_PATHS=1 env var 로 hard-coded 경로 fallback 옵션 검토

시작 트리거

본 학습 (yolo26x × 150 epoch, task bow35c4sg) 완료 시: 1. task notification 도착 2. 본 계획서 다시 읽기 3. Step 1 부터 순차 실행 4. claudeTemp/ 의 사본을 reference 로 사용


10. 미해결 항목 (실제 작업 시 검증)

  • [ ] dumpbin /dependents NZVideoDetector_eval.exe 로 정확한 DLL 의존 목록 추출 → bin/ 에 모두 동봉
  • [ ] 새 서버에서 TRT engine 재빌드 시간 측정 (4-mode × 5-model = 20 engine, 추정 ~70분)
  • [ ] CUDA runtime DLL — bin/ 동봉 vs 시스템 PATH 트레이드오프 결정
  • [ ] requirements.txt 정확한 버전 핀 (ultralytics 8.4.14, torch 2.5.1+cu121, pycocotools, opencv-python, matplotlib, pandas, ...)
  • [ ] (옵션) Docker 이미지로 시스템 의존까지 봉인 — 향후 결정

부록: 작업 시 참고할 ECC skill

  • coding-standards — 일반 패턴
  • python-patterns — argparse / pathlib / typing
  • search-first — 새 의존성 도입 전 기존 라이브러리 확인
  • verification-loop — 단계별 검증
  • ml-dataset-curator 에이전트 — 데이터셋 큐레이션 작업
  • ml-trainer 에이전트 — 학습 운영
  • nzailibs-engineer 에이전트 — nzeval 코드 변경