| |
| """YOLO11-P2 detection training (Ultralytics wrapper). |
| |
| 5-fold CV support via tablet_view_fold. |
| BF16 + compile. |
| """ |
| import os, sys, json, argparse, tempfile, shutil, time |
| from pathlib import Path |
| import yaml |
| import torch |
|
|
| ROOT = Path("/arf/scratch/stakan/hitit-proje") |
|
|
| def _ensure_cuda(): |
| """Work around transient NVML / CUDA_VISIBLE_DEVICES init races. |
| |
| Retry torch.cuda probe up to 3×; on persistent failure, clear any stale |
| CUDA_VISIBLE_DEVICES and re-probe.""" |
| for attempt in range(3): |
| try: |
| n = torch.cuda.device_count() |
| ok = torch.cuda.is_available() |
| if ok and n > 0: return n |
| except Exception as e: |
| print(f"CUDA probe {attempt}: {e}", flush=True) |
| time.sleep(2) |
| |
| vis = os.environ.get('CUDA_VISIBLE_DEVICES', '') |
| print(f"[warn] CUDA init failed; CUDA_VISIBLE_DEVICES={vis!r}. Attempting reset.", flush=True) |
| os.environ.pop('CUDA_VISIBLE_DEVICES', None) |
| try: |
| import importlib |
| importlib.reload(torch.cuda) |
| n = torch.cuda.device_count() |
| if n > 0: return n |
| except Exception: pass |
| return 0 |
|
|
| def build_fold_data_yaml(fold, output_dir, n_folds=5): |
| """ |
| Her fold için ultralytics data.yaml oluştur. |
| tablet_view_fold != fold → train, == fold → val. |
| Manifest'te tablet_view_fold yoksa tablet_id (yoksa path) hash'inden |
| deterministik fold üret — aynı tabletin farklı view'leri aynı fold'a düşer. |
| """ |
| manifest_path = ROOT / 'datasets/unified/detection/manifest.jsonl' |
|
|
| train_imgs, val_imgs = [], [] |
| train_lbls, val_lbls = [], [] |
|
|
| def derive_fold(record): |
| tvf = record.get('tablet_view_fold') |
| if tvf is not None: |
| return int(tvf) |
| key = record.get('tablet_id') or record.get('path', '') |
| return (hash(key) & 0x7fffffff) % n_folds |
|
|
| with open(manifest_path) as f: |
| for line in f: |
| r = json.loads(line) |
| if r.get('storage') != 'fs' or not r.get('path'): continue |
| img_path = r['path'] |
| lbl_path = r.get('label_path') |
| if not lbl_path or not Path(lbl_path).exists(): continue |
| if not Path(img_path).exists(): continue |
|
|
| tvf = derive_fold(r) |
| if tvf == fold: |
| val_imgs.append(img_path) |
| val_lbls.append(lbl_path) |
| else: |
| train_imgs.append(img_path) |
| train_lbls.append(lbl_path) |
|
|
| print(f"Fold {fold}: train={len(train_imgs)}, val={len(val_imgs)}") |
|
|
| |
| output_dir = Path(output_dir) |
| output_dir.mkdir(parents=True, exist_ok=True) |
| train_list = output_dir / 'train.txt' |
| val_list = output_dir / 'val.txt' |
| with open(train_list, 'w') as f: |
| for p in train_imgs: f.write(f"{p}\n") |
| with open(val_list, 'w') as f: |
| for p in val_imgs: f.write(f"{p}\n") |
|
|
| |
| label_dict_path = ROOT / 'datasets/sources/yeni_veri/label_dict.txt' |
| if label_dict_path.exists(): |
| names = {} |
| with open(label_dict_path) as f: |
| for line in f: |
| if ':' in line: |
| idx, name = line.split(':', 1) |
| names[int(idx.strip())] = name.strip() |
| |
| max_idx = max(names.keys()) + 1 |
| names_list = [names.get(i, f'class_{i}') for i in range(max_idx)] |
| else: |
| names_list = [f'class_{i}' for i in range(400)] |
|
|
| data_yaml = { |
| 'path': str(output_dir), |
| 'train': 'train.txt', |
| 'val': 'val.txt', |
| 'names': names_list, |
| 'nc': len(names_list), |
| } |
| yaml_path = output_dir / 'data.yaml' |
| with open(yaml_path, 'w') as f: |
| yaml.dump(data_yaml, f) |
| return str(yaml_path) |
|
|
| def main(): |
| ap = argparse.ArgumentParser() |
| ap.add_argument('--config', default=str(ROOT / 'hitit_ocr/configs/detection_hitit_v1.yaml')) |
| ap.add_argument('--fold', type=int, default=0) |
| ap.add_argument('--split-field', default='tablet_view_fold') |
| ap.add_argument('--output', default=None) |
| args = ap.parse_args() |
|
|
| cfg = yaml.safe_load(open(args.config)) |
| output = Path(args.output or f'hitit_ocr/runs/detection_fold{args.fold}/') |
| output.mkdir(parents=True, exist_ok=True) |
|
|
| |
| |
| |
| |
| candidates = [ |
| ROOT / f'datasets/ready/detection_tablets_v2/fold_{args.fold}/data.yaml', |
| ROOT / f'datasets/ready/detection_tablets/fold_{args.fold}/data.yaml', |
| ROOT / f'datasets/ready/detection/fold_{args.fold}/data.yaml', |
| ] |
| data_yaml = None |
| for c in candidates: |
| if c.exists(): |
| data_yaml = str(c); print(f"Using fold data.yaml: {data_yaml}"); break |
| if data_yaml is None: |
| data_yaml = build_fold_data_yaml(args.fold, output / 'data') |
| print(f"Built fold data.yaml from manifest: {data_yaml}") |
|
|
| |
| try: |
| from ultralytics import YOLO |
| except ImportError: |
| print("Ultralytics not installed — pip install ultralytics") |
| sys.exit(1) |
|
|
| |
| model_name = cfg.get('model', 'yolo11m-p2.yaml') |
| model = YOLO(model_name) |
|
|
| |
| train_args = { |
| 'data': data_yaml, |
| 'epochs': cfg.get('epochs', 150), |
| 'imgsz': cfg.get('imgsz', 1280), |
| 'batch': cfg.get('batch', 4), |
| 'device': (lambda n: list(range(n)) if n > 0 else 'cpu')(_ensure_cuda()), |
| 'project': str(output.parent), |
| 'name': output.name, |
| 'optimizer': cfg.get('optimizer', 'AdamW'), |
| 'lr0': cfg.get('lr0', 0.001), |
| 'mosaic': cfg.get('mosaic', 0.8), |
| 'mixup': cfg.get('mixup', 0.15), |
| 'copy_paste': cfg.get('copy_paste', 0.15), |
| 'fliplr': cfg.get('fliplr', 0.0), |
| 'flipud': cfg.get('flipud', 0.0), |
| 'degrees': cfg.get('degrees', 5), |
| 'close_mosaic': cfg.get('close_mosaic', 10), |
| 'amp': True, |
| 'exist_ok': True, |
| 'cache': cfg.get('cache', 'ram'), |
| 'workers': cfg.get('workers', 16), |
| 'patience': cfg.get('patience', 20), |
| 'save_period': cfg.get('save_period', 5), |
| 'iou': cfg.get('iou', 0.7), |
| } |
|
|
| |
| if cfg.get('efficiency', {}).get('torch_compile'): |
| train_args['compile'] = True |
|
|
| print(f"Training with args: {train_args}") |
| results = model.train(**train_args) |
|
|
| |
| best_src = output / 'weights' / 'best.pt' |
| if best_src.exists(): |
| shutil.copy(best_src, output / 'best.pt') |
| print(f"DONE: {output}") |
|
|
| if __name__ == '__main__': |
| main() |
|
|