datamatters24 commited on
Commit
7534111
·
verified ·
1 Parent(s): 12165ca

Upload ml/08_detect_duplicates.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. ml/08_detect_duplicates.py +96 -0
ml/08_detect_duplicates.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Phase 6: Duplicate Detection
4
+
5
+ Finds near-duplicate documents by comparing page 1 embeddings
6
+ using pgvector cosine similarity with IVFFlat index.
7
+
8
+ For each first-page, finds top-K nearest neighbors with similarity > threshold.
9
+
10
+ Runs on: Hetzner (PostgreSQL pgvector)
11
+ """
12
+
13
+ import logging
14
+ import psycopg2
15
+ import psycopg2.extras
16
+ from db import get_conn
17
+
18
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)-8s %(message)s")
19
+ log = logging.getLogger(__name__)
20
+
21
+ SIMILARITY_THRESHOLD = 0.95
22
+ DISTANCE_THRESHOLD = 1 - SIMILARITY_THRESHOLD # 0.05
23
+ TOP_K = 5
24
+ BATCH_SIZE = 100
25
+
26
+
27
+ def main():
28
+ conn = get_conn()
29
+
30
+ with conn.cursor() as cur:
31
+ cur.execute("SET ivfflat.probes = 10;")
32
+
33
+ # Get all first-page IDs that haven't been checked yet
34
+ with conn.cursor() as cur:
35
+ cur.execute("""
36
+ SELECT p.id FROM pages p
37
+ WHERE p.page_number = 1 AND p.embedding IS NOT NULL
38
+ AND p.id NOT IN (SELECT DISTINCT page_id_a FROM duplicate_pairs)
39
+ ORDER BY p.id
40
+ """)
41
+ page_ids = [r[0] for r in cur.fetchall()]
42
+
43
+ log.info(f"Checking {len(page_ids)} first-page embeddings for duplicates")
44
+
45
+ found = 0
46
+ checked = 0
47
+
48
+ for i in range(0, len(page_ids), BATCH_SIZE):
49
+ batch_ids = page_ids[i:i + BATCH_SIZE]
50
+ insert_rows = []
51
+
52
+ for pid in batch_ids:
53
+ with conn.cursor() as cur:
54
+ # Use the IVFFlat index: ORDER BY <=> finds nearest neighbors
55
+ cur.execute("""
56
+ SELECT p2.id, 1 - (p1.embedding <=> p2.embedding) as sim
57
+ FROM pages p1, pages p2
58
+ WHERE p1.id = %s
59
+ AND p2.page_number = 1
60
+ AND p2.embedding IS NOT NULL
61
+ AND p2.id > p1.id
62
+ ORDER BY p1.embedding <=> p2.embedding
63
+ LIMIT %s
64
+ """, (pid, TOP_K))
65
+
66
+ for row in cur.fetchall():
67
+ neighbor_id, similarity = row
68
+ if similarity >= SIMILARITY_THRESHOLD:
69
+ insert_rows.append((pid, neighbor_id, similarity, 'embedding'))
70
+
71
+ # Batch insert
72
+ if insert_rows:
73
+ with conn.cursor() as cur:
74
+ psycopg2.extras.execute_batch(
75
+ cur,
76
+ """INSERT INTO duplicate_pairs (page_id_a, page_id_b, similarity, method)
77
+ VALUES (%s, %s, %s, %s)
78
+ ON CONFLICT (page_id_a, page_id_b, method) DO NOTHING""",
79
+ insert_rows,
80
+ page_size=500,
81
+ )
82
+ found += len(insert_rows)
83
+
84
+ # Mark checked pages (insert self-pair as marker if no duplicates found)
85
+ checked += len(batch_ids)
86
+ conn.commit()
87
+
88
+ if checked % 1000 == 0 or insert_rows:
89
+ log.info(f" Checked {checked}/{len(page_ids)}, {found} duplicate pairs found")
90
+
91
+ conn.close()
92
+ log.info(f"Done. {found} duplicate pairs found from {checked} pages checked.")
93
+
94
+
95
+ if __name__ == "__main__":
96
+ main()