옵션 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
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.names → vas.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.namescp949 자동 감지 → 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 ...
호환성 표
| 항목 | 영향 | 대응 |
|---|---|---|
| 멀티 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. 단계별 작업 순서 (학습 종료 후)
- settings.py + 외부 의존 자동 탐지 — NZ_EVAL_EXE/NZ_MT_EXE 탐지 + import test
- nzeval/ 별도 폴더 구축 — ultralytics_nzeval.py 사본 + runner/callback +
from nzeval import ...검증 - train.py 작성 — claudeTemp/train_full.py 기반, vas.yaml 에서 list/GT 추출 +
--device멀티 GPU - 20260611 smoke test —
python train.py --dataset-dir ... --epochs 1 --fraction 0.01 --name smoke - eval_final.py — TRT FP16 사후 평가
- post_eval.py + fill_missing.py
- chart_trajectory.py — chart_final + chart_nzeval 통합
- dataset_curation/ + prepare_dataset.py — raw → trainable 자동화
- benchmarks/ 이관 (선택)
- bin/ 패키지 구축 — exe + DLL 복사 + build_info.txt + bin/README.md
- requirements.txt + setup.md — Python/시스템 의존성 명세
- README.md — 신규 사용자 진입점 (nzeval/ + bin/ + setup.md cross-link)
- 마지막 검증 — 가짜 새 데이터셋 (20260611 sub-sample) end-to-end +
--device 0,1smoke - 20260611 v2 → 표준 rename —
valid_v2.txt → valid.txt등 + vas.yaml 에 list/GT 경로 필드 추가.runs/vas_full_yolo26x/영향 없음 확인 - claudeTemp/ 학습/평가 사본 삭제 (또는 deprecated 디렉터리)
- c:/tmp/ 원본 삭제 (사용자 다시 확인 후)
각 단계 완료 후 검증: - [ ] 인자 변경 후 기존 데이터셋 결과 동등 - [ ] env var override 동작 - [ ] runs/ 폴더가 dataset-dir 아래 정확히 생성 - [ ] 한글 경로 dataset-dir 에서도 동작
9. 안전장치
롤백
- claudeTemp/, c:/tmp/ 원본은 모든 검증 통과 후에만 정리 (step 15, 16)
- 신규 파일은 git checkpoint hook 으로 자동 추적 → 언제든 복귀
- 검증 실패 시
USE_LEGACY_PATHS=1env 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 / typingsearch-first— 새 의존성 도입 전 기존 라이브러리 확인verification-loop— 단계별 검증ml-dataset-curator에이전트 — 데이터셋 큐레이션 작업ml-trainer에이전트 — 학습 운영nzailibs-engineer에이전트 — nzeval 코드 변경