hitit-cuneiform-ocr / code /src /preprocessing /uncertainty_dinov3.py
savastakan's picture
Initial upload: code + 5 record checkpoints + fuse
f211247 verified
Raw
History Blame Contribute Delete
6.93 kB
#!/usr/bin/env python3
"""DINOv3 tabanlı uncertainty score.
Yaklaşım: DINOv3 backbone'u ile feature çıkar, her unified_label'ın centroid'ini hesapla,
her kayıt için (1 - max_similarity_to_own_centroid) → uncertainty.
Hard cases (düşük similarity) = yüksek uncertainty = tekrar annotate önerilir.
"""
import json, os, argparse, time
from pathlib import Path
import numpy as np
ROOT = Path("/arf/scratch/stakan/hitit-proje")
SOURCES = ROOT / "datasets" / "sources"
def main():
import torch
from PIL import Image
from collections import defaultdict
ap = argparse.ArgumentParser()
ap.add_argument('--batch', type=int, default=64)
ap.add_argument('--model', default='dinov2_vitb14') # DINOv3 eğer torch hub'da varsa dinov3_vitb16
ap.add_argument('--sample', type=int, default=0)
args = ap.parse_args()
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Device: {device}", flush=True)
# DINOv3 (Meta) — Py3.11 + ai-tools-kolyoz-1.0 env'i ile çalışır.
# Fallbacks: DINOv2 → timm DINOv2 (en kötü senaryo).
try:
model = torch.hub.load('facebookresearch/dinov3', 'dinov3_vitb16',
trust_repo=True).eval().to(device)
emb_dim = 768
input_size = 224
model_name = "dinov3_vitb16"
except Exception as e:
print(f"DINOv3 yüklenemedi ({type(e).__name__}: {str(e)[:80]}), DINOv2'ye düşüldü", flush=True)
try:
model = torch.hub.load('facebookresearch/dinov2', 'dinov2_vitb14',
trust_repo=True).eval().to(device)
emb_dim = 768
input_size = 224
model_name = "dinov2_vitb14"
except Exception as e2:
print(f"DINOv2 de yüklenemedi ({e2}), timm DINOv2'ye düşüldü", flush=True)
import timm
model = timm.create_model('vit_base_patch14_dinov2.lvd142m',
pretrained=True, num_classes=0).eval().to(device)
emb_dim = 768
input_size = 518
model_name = "dinov2_vitb14_timm"
from torchvision import transforms
tf = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(input_size),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225]),
])
# Topla: sadece classification
items = []
for d in sorted(SOURCES.iterdir()):
if not d.is_dir(): continue
mp = d / "manifest_classification.jsonl"
if not mp.exists(): continue
with open(mp) as f:
for line in f:
r = json.loads(line)
p = r.get('path')
ul = r.get('unified_label')
if p and ul and r.get('integrity_ok') is True and os.path.exists(p):
items.append((r['id'], p, ul))
if args.sample:
import random; random.seed(42)
items = random.sample(items, min(args.sample, len(items)))
print(f"Feature çıkarılacak: {len(items):,}", flush=True)
# Feature extraction
features = np.zeros((len(items), emb_dim), dtype=np.float32)
labels_list = [None] * len(items)
ids_list = [None] * len(items)
t0 = time.time()
idx = 0
for bi in range(0, len(items), args.batch):
batch = items[bi:bi+args.batch]
imgs = []
for rid, path, ul in batch:
try:
img = tf(Image.open(path).convert('RGB'))
imgs.append(img)
except:
imgs.append(None)
valid = [(i,img) for i,img in enumerate(imgs) if img is not None]
if not valid: continue
x = torch.stack([v[1] for v in valid]).to(device)
with torch.no_grad(), torch.amp.autocast('cuda', enabled=(device=="cuda")):
feats = model(x)
if isinstance(feats, dict): feats = feats.get('x_norm_clstoken', feats.get('cls', list(feats.values())[0]))
feats = feats.cpu().numpy().astype(np.float32)
feats /= (np.linalg.norm(feats, axis=1, keepdims=True) + 1e-8)
for k, (i, _) in enumerate(valid):
rid, path, ul = batch[i]
features[idx] = feats[k]
labels_list[idx] = ul
ids_list[idx] = rid
idx += 1
if (bi // args.batch) % 50 == 0:
print(f" {idx}/{len(items)} ({100*idx/len(items):.1f}%) {idx/max(time.time()-t0,1):.0f} img/s", flush=True)
features = features[:idx]
labels_list = labels_list[:idx]
ids_list = ids_list[:idx]
# Per-class centroid
from collections import defaultdict
sums = defaultdict(lambda: np.zeros(emb_dim, dtype=np.float32))
counts = defaultdict(int)
for f, l in zip(features, labels_list):
sums[l] += f
counts[l] += 1
centroids = {l: sums[l] / counts[l] for l in sums}
# Normalize
centroids = {l: c / (np.linalg.norm(c) + 1e-8) for l, c in centroids.items()}
# Uncertainty = 1 - cos_sim(feature, own_centroid)
uncertainty = {}
for f, l, rid in zip(features, labels_list, ids_list):
c = centroids.get(l)
if c is None: continue
sim = float(f @ c)
uncertainty[rid] = max(0.0, min(1.0, 1.0 - sim))
# Manifest güncelle
updated = 0
for d in sorted(SOURCES.iterdir()):
if not d.is_dir(): continue
for mf in ['manifest.jsonl', 'manifest_classification.jsonl', 'manifest_detection.jsonl']:
mp = d / mf
if not mp.exists(): continue
records = []
with open(mp) as f:
for line in f:
r = json.loads(line)
u = uncertainty.get(r.get('id'))
if u is not None:
r['uncertainty_score'] = round(u, 4)
r['uncertainty_source'] = f'{model_name}_centroid_v1'
updated += 1
records.append(r)
with open(mp, 'w') as f:
for r in records:
f.write(json.dumps(r, ensure_ascii=False) + '\n')
print(f"\nuncertainty_score güncellendi: {updated:,} kayıt")
# Summary
vals = list(uncertainty.values())
vals_sorted = sorted(vals)
n = len(vals_sorted)
out = {
"model": model_name,
"n_scored": n,
"percentiles": {f"p{p}": round(vals_sorted[int(n*p/100)], 4) for p in [5,25,50,75,95]} if n else {},
"n_classes_with_centroid": len(centroids),
"high_uncertainty_threshold": 0.5,
"n_above_threshold": sum(1 for v in vals if v > 0.5),
}
with open(ROOT / "datasets" / "processed" / "uncertainty_dinov3_report.json", 'w') as f:
json.dump(out, f, indent=2, ensure_ascii=False)
print(f"\n{json.dumps(out, indent=2)}")
if __name__ == '__main__':
main()