File size: 5,623 Bytes
f211247
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#!/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()