Download code/src/preprocessing/clip_leakage_scan.py from savastakan/hitit-cuneiform-ocr: direct link, hf CLI and curl.
- Browser
- Download file 5.62 kB
-
https://huggingface.co/savastakan/hitit-cuneiform-ocr/resolve/main/code/src/preprocessing/clip_leakage_scan.py
- Command line
-
hf download hf://savastakan/hitit-cuneiform-ocr/code/src/preprocessing/clip_leakage_scan.py
-
curl -L -o clip_leakage_scan.py https://huggingface.co/savastakan/hitit-cuneiform-ocr/resolve/main/code/src/preprocessing/clip_leakage_scan.py
5.62 kB
| #!/usr/bin/env python3 | |
| """CLIP-L/14 semantic near-duplicate leakage scan. | |
| Reference: Ramos et al. ICCVW 2025 "Data Leakage in Visual Datasets" | |
| Tüm 293K image'ı OpenCLIP ViT-L/14 ile embed et, cosine>0.95 çiftleri topla, | |
| aynı canonical_tablet_id değilse "semantic leakage" işaretle. | |
| Manifest alanı: clip_embedding_norm_id, clip_neighbor_ids (top-5) | |
| Rapor: datasets/processed/clip_leakage_report.json | |
| """ | |
| 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 | |
| import open_clip | |
| from concurrent.futures import ThreadPoolExecutor | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument('--batch', type=int, default=128) | |
| ap.add_argument('--model', default='ViT-L-14') | |
| ap.add_argument('--pretrained', default='openai') | |
| ap.add_argument('--sample', type=int, default=0, help="0=tüm, N=sample") | |
| ap.add_argument('--out', default=str(ROOT / "datasets" / "processed" / "clip_leakage_report.json")) | |
| args = ap.parse_args() | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| print(f"Device: {device}", flush=True) | |
| model, _, preprocess = open_clip.create_model_and_transforms(args.model, pretrained=args.pretrained) | |
| model = model.eval().to(device) | |
| # Path topla | |
| items = [] | |
| seen = set() | |
| 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') | |
| if (p and p not in seen and r.get('integrity_ok') is True | |
| and os.path.exists(p)): | |
| seen.add(p) | |
| items.append((r['id'], p, r.get('source',''), r.get('canonical_tablet_id'))) | |
| if args.sample: | |
| import random | |
| random.seed(42) | |
| items = random.sample(items, min(args.sample, len(items))) | |
| print(f"Embed edilecek: {len(items):,}", flush=True) | |
| ids = [] | |
| sources = [] | |
| tablet_ids = [] | |
| feats = [] | |
| t0 = time.time() | |
| for bi in range(0, len(items), args.batch): | |
| batch = items[bi:bi+args.batch] | |
| imgs = [] | |
| good_idx = [] | |
| for i, (rid, path, src, tid) in enumerate(batch): | |
| try: | |
| img = preprocess(Image.open(path).convert('RGB')) | |
| imgs.append(img) | |
| good_idx.append(i) | |
| except Exception: | |
| continue | |
| if not imgs: continue | |
| with torch.no_grad(), torch.amp.autocast('cuda', enabled=(device=="cuda")): | |
| x = torch.stack(imgs).to(device) | |
| feat = model.encode_image(x) | |
| feat = feat / (feat.norm(dim=-1, keepdim=True) + 1e-8) | |
| feat = feat.cpu().numpy().astype(np.float32) | |
| for j, k in enumerate(good_idx): | |
| rid, path, src, tid = batch[k] | |
| ids.append(rid) | |
| sources.append(src) | |
| tablet_ids.append(tid) | |
| feats.append(feat) | |
| if (bi // args.batch) % 50 == 0: | |
| elapsed = time.time() - t0 | |
| rate = (bi + len(batch)) / max(elapsed, 1) | |
| print(f" {bi+len(batch)}/{len(items)} ({100*(bi+len(batch))/len(items):.1f}%) {rate:.0f} img/s", flush=True) | |
| embeddings = np.concatenate(feats, axis=0) if feats else np.zeros((0,768), dtype=np.float32) | |
| print(f"Embeddings shape: {embeddings.shape}", flush=True) | |
| # Embeddings kaydet (compressed) | |
| emb_out = ROOT / "datasets" / "processed" / "clip_embeddings.npz" | |
| np.savez_compressed(emb_out, embeddings=embeddings, ids=np.array(ids), sources=np.array(sources)) | |
| print(f"Saved: {emb_out}", flush=True) | |
| # Leakage scan: batch-wise cosine matrix; memory için chunk-based | |
| # NumPy A @ A.T -> N×N (293K×293K = 320GB) — imkansız | |
| # Bunun yerine: FAISS kullan (approx NN) | |
| try: | |
| import faiss | |
| index = faiss.IndexFlatIP(embeddings.shape[1]) | |
| index.add(embeddings) | |
| print("FAISS indexed", flush=True) | |
| k = 5 # top-5 nearest neighbors | |
| sims, idx = index.search(embeddings, k+1) # +1 kendi | |
| leakage_threshold = 0.95 | |
| leakage_pairs = [] | |
| for i in range(len(embeddings)): | |
| for j, s in zip(idx[i][1:], sims[i][1:]): # 0 kendi | |
| if s >= leakage_threshold: | |
| if sources[i] != sources[j] or (tablet_ids[i] and tablet_ids[j] and tablet_ids[i] != tablet_ids[j]): | |
| leakage_pairs.append({ | |
| "id1": ids[i], "source1": sources[i], "tablet1": tablet_ids[i], | |
| "id2": ids[j], "source2": sources[j], "tablet2": tablet_ids[j], | |
| "cosine": float(s) | |
| }) | |
| print(f"Leakage pairs (cosine>={leakage_threshold}): {len(leakage_pairs)}", flush=True) | |
| with open(args.out, 'w') as f: | |
| json.dump({ | |
| "method": "OpenCLIP {}/{}".format(args.model, args.pretrained), | |
| "n_embeddings": len(embeddings), | |
| "leakage_threshold": leakage_threshold, | |
| "leakage_pairs_total": len(leakage_pairs), | |
| "leakage_pairs_sample": leakage_pairs[:500], | |
| }, f, indent=2, ensure_ascii=False) | |
| except ImportError: | |
| print("faiss yok; np.dot ile chunk-based hesap kullanılabilir", flush=True) | |
| if __name__ == '__main__': | |
| main() | |