Download sdg/preprocessing/dedupe/test_semantic.py from fzzhang/svd-code: direct link, hf CLI and curl.
- Browser
- Download file 6.32 kB
-
https://huggingface.co/fzzhang/svd-code/resolve/main/sdg/preprocessing/dedupe/test_semantic.py
- Command line
-
hf download hf://fzzhang/svd-code/sdg/preprocessing/dedupe/test_semantic.py
-
curl -L -o test_semantic.py https://huggingface.co/fzzhang/svd-code/resolve/main/sdg/preprocessing/dedupe/test_semantic.py
6.32 kB
| """Tests for SemanticDeduplicator. | |
| Avoids downloading any embedding model by exercising `dedup_from_embeddings` | |
| directly with handcrafted L2-normalized vectors. Skipped if faiss is not | |
| installed. | |
| """ | |
| from __future__ import annotations | |
| import pytest | |
| pytest.importorskip("faiss") | |
| import numpy as np | |
| from sdg.preprocessing.dedupe.semantic import SemanticDeduplicator | |
| def _normalize(v: np.ndarray) -> np.ndarray: | |
| norms = np.linalg.norm(v, axis=1, keepdims=True) | |
| return (v / np.clip(norms, 1e-12, None)).astype(np.float32) | |
| def _vec(*components: float) -> np.ndarray: | |
| return np.array(components, dtype=np.float32) | |
| # ββ Boundary cases βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_empty_returns_empty(): | |
| dedup = SemanticDeduplicator() | |
| assert dedup.dedup_from_embeddings(np.zeros((0, 4), dtype=np.float32)) == [] | |
| def test_single_returns_single(): | |
| dedup = SemanticDeduplicator() | |
| emb = _normalize(np.array([[1.0, 0.0, 0.0, 0.0]])) | |
| assert dedup.dedup_from_embeddings(emb) == [0] | |
| # ββ Identity / orthogonality βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_identical_embeddings_collapse(): | |
| dedup = SemanticDeduplicator(threshold=0.9) | |
| emb = _normalize(np.stack([_vec(1, 0, 0), _vec(1, 0, 0)])) | |
| keep = dedup.dedup_from_embeddings(emb) | |
| assert len(keep) == 1 | |
| def test_orthogonal_embeddings_both_kept(): | |
| dedup = SemanticDeduplicator(threshold=0.5) | |
| emb = _normalize(np.stack([_vec(1, 0, 0), _vec(0, 1, 0)])) | |
| keep = dedup.dedup_from_embeddings(emb) | |
| assert keep == [0, 1] | |
| # ββ Threshold behavior βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_above_threshold_collapses(): | |
| # cos(a, b) β 0.95 (small angle) | |
| a = _vec(1.0, 0.0) | |
| b = _vec(np.cos(np.radians(18)), np.sin(np.radians(18))) # ~0.951 | |
| emb = _normalize(np.stack([a, b])) | |
| dedup = SemanticDeduplicator(threshold=0.92) | |
| keep = dedup.dedup_from_embeddings(emb) | |
| assert len(keep) == 1 | |
| def test_below_threshold_both_kept(): | |
| # cos(a, b) β 0.866 (30 degrees) | |
| a = _vec(1.0, 0.0) | |
| b = _vec(np.cos(np.radians(30)), np.sin(np.radians(30))) | |
| emb = _normalize(np.stack([a, b])) | |
| dedup = SemanticDeduplicator(threshold=0.92) | |
| keep = dedup.dedup_from_embeddings(emb) | |
| assert keep == [0, 1] | |
| # ββ Cluster of three βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_cluster_of_three_via_chain_above_threshold(): | |
| a = _vec(1.0, 0.0) | |
| b = _vec(np.cos(np.radians(10)), np.sin(np.radians(10))) | |
| c = _vec(np.cos(np.radians(20)), np.sin(np.radians(20))) | |
| emb = _normalize(np.stack([a, b, c])) | |
| dedup = SemanticDeduplicator(threshold=0.95) | |
| keep = dedup.dedup_from_embeddings(emb) | |
| # All three angles within ~20 degrees -> all pairwise cos > 0.93, | |
| # so they form one cluster -> one kept. | |
| assert len(keep) == 1 | |
| # ββ Representative selection βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_key_fn_selects_representative(): | |
| emb = _normalize(np.stack([_vec(1, 0), _vec(1, 0), _vec(1, 0)])) | |
| response_lengths = [10, 500, 50] | |
| key_fn = lambda i: -response_lengths[i] | |
| dedup = SemanticDeduplicator(threshold=0.99) | |
| keep = dedup.dedup_from_embeddings(emb, key_fn=key_fn) | |
| assert keep == [1] | |
| # ββ HNSW path ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_hnsw_path_runs_above_threshold_size(): | |
| """Force the HNSW code path with a tiny hnsw_threshold. | |
| Uses 384-dim random gaussians (matching our default embed model). At that | |
| dimensionality, random pairs have expected cosine ~0 with std ~1/sqrt(384), | |
| so a threshold of 0.95 is deep in the tail and nothing should collapse. | |
| """ | |
| rng = np.random.default_rng(42) | |
| raw = rng.standard_normal((200, 384)).astype(np.float32) | |
| emb = _normalize(raw) | |
| dedup = SemanticDeduplicator(threshold=0.95, hnsw_threshold=10) | |
| keep = dedup.dedup_from_embeddings(emb) | |
| assert len(keep) == 200 | |
| def test_hnsw_path_collapses_planted_duplicates(): | |
| """HNSW should still find planted near-duplicates among many distractors.""" | |
| rng = np.random.default_rng(123) | |
| distractors = rng.standard_normal((180, 384)).astype(np.float32) | |
| # Plant 20 copies of one vector | |
| seed_vec = rng.standard_normal((1, 384)).astype(np.float32) | |
| planted = np.repeat(seed_vec, 20, axis=0) | |
| raw = np.concatenate([distractors, planted], axis=0) | |
| emb = _normalize(raw) | |
| dedup = SemanticDeduplicator(threshold=0.95, hnsw_threshold=10, topk=25) | |
| keep = dedup.dedup_from_embeddings(emb) | |
| # 180 distractors + 1 representative of the planted cluster = 181 | |
| assert len(keep) == 181 | |
| # ββ Validation βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_invalid_threshold_raises(): | |
| with pytest.raises(ValueError): | |
| SemanticDeduplicator(threshold=0.0) | |
| def test_invalid_topk_raises(): | |
| with pytest.raises(ValueError): | |
| SemanticDeduplicator(topk=0) | |
| # ββ Device resolution ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_device_explicit_passthrough(): | |
| assert SemanticDeduplicator(device="cpu").resolve_device() == "cpu" | |
| def test_device_auto_returns_known_string(): | |
| """auto must resolve to one of mps/cuda/cpu (depending on host).""" | |
| resolved = SemanticDeduplicator(device="auto").resolve_device() | |
| assert resolved in {"mps", "cuda", "cpu"} | |