All writing2026.09.22 · MSBD 5001 · Foundations of Data Analytics · 2 min

MinHash and LSH: Find Near-Duplicates Without Comparing Every Pair

Turn documents into shingles, compress Jaccard similarity into signatures, then use LSH to generate a small candidate set.

Finding duplicate documents is easy until the collection becomes large. With one million documents, an all-pairs comparison needs roughly 500 billion checks. The important idea is therefore not a faster comparison. It is avoiding almost all comparisons.

Step 1: preserve local order with shingles

A k-shingle is a sequence of k tokens. The string abcab has the 2-shingle set {ab, bc, ca}. Unlike a bag of words, shingles retain some local order. Represent each document as a set and compare two sets with Jaccard similarity:

J(A, B) = |A intersection B| / |A union B|

Small k creates accidental matches; very large k misses lightly edited copies. The right value depends on document length and the kind of edits that matter.

Step 2: replace a large set with a signature

For a random permutation of the universe, MinHash records the first member belonging to a set. The key property is:

P[minhash(A) = minhash(B)] = J(A, B)

Repeat this with many hash functions. The fraction of matching signature rows estimates Jaccard similarity. More rows reduce variance but consume more memory and computation.

Step 3: use LSH as a candidate generator

Even short signatures still produce quadratic work if every pair is compared. LSH divides a signature into bands and hashes each band. Two documents become candidates when at least one band matches. More rows per band make matching stricter; more bands improve recall but admit more false positives.

In plain terms: MinHash makes each document small. LSH decides which small representations are worth checking. An exact comparison can verify the final candidates.

This is a reusable system pattern: cheap approximation first, expensive truth check second. Measure candidate reduction, recall on known duplicate pairs, and false-positive cost together; optimizing only one can make the pipeline useless.

Review card

  • Shingling converts sequence similarity into set similarity.
  • Jaccard compares shared elements with the union.
  • MinHash preserves Jaccard similarity in expectation.
  • LSH reduces search, not final verification.
  • Banding trades recall against candidate volume.

找重複文件不難,難的是 collection 變大後仍做得完。一百萬份文件若兩兩比較,大約要檢查五千億組。真正重要的不是把 comparison 再加速一點,而是不要比較其中絕大多數。

第一步:用 shingle 保留局部順序

k-shingle 是連續 k 個 token。字串 abcab 的 2-shingle set 是 {ab, bc, ca}。它比 bag of words 多保留一些局部順序。把文件表示成 set 後,以 Jaccard similarity 比較:

J(A, B) = |A 交集 B| / |A 聯集 B|

k 太小會有大量偶然重合;太大則會漏掉稍微編輯過的 copy。選擇要看文件長度,以及產品認定哪些修改仍算相似。

第二步:用 signature 取代巨大 set

對 universe 做 random permutation,MinHash 記錄某個 set 最先出現的 member。核心性質是:

P[minhash(A) = minhash(B)] = J(A, B)

用多個 hash function 重複,signature 相同 row 的比例就能估計 Jaccard similarity。Row 越多 variance 越小,但 memory 與 computation 也越高。

第三步:LSH 只產生值得驗證的 candidates

即使 signature 很短,全部兩兩比較仍是 quadratic。LSH 把 signature 分成 bands,各自 hash;至少一個 band 相同才成為 candidate。每 band 的 rows 越多,條件越嚴;bands 越多,recall 越高,但 false positives 也會增加。

白話來說: MinHash 把每份文件縮小;LSH 決定哪些縮小版值得真正比較;最後仍可用 exact comparison 驗證。

這是一個很通用的 system pattern:先用便宜 approximation 篩選,再用昂貴 truth check 收尾。實作時要一起量 candidate reduction、已知 duplicate recall 與 false-positive cost,不能只最佳化單一數字。

複習卡

  • Shingling 把 sequence similarity 轉成 set similarity。
  • Jaccard 比較交集相對於聯集的比例。
  • MinHash 在 expectation 上保留 Jaccard similarity。
  • LSH 負責縮小搜尋,不負責最終判定。
  • Banding 是 recall 與 candidate volume 的交換。