MinHash: Jaccard Similarity, LSH, Near-Duplicate Detection

Michael BrenndoerferJanuary 10, 202647 min read

Part of Language AI Handbook

Explains how MinHash compresses documents into compact signatures that estimate Jaccard similarity, enabling near-duplicate detection.

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

MinHash

When you train a language model on web-scale text, you quickly discover that the internet contains vast amounts of duplicate and near-duplicate content. The same news article republished on dozens of sites. Forum posts copied and pasted across communities. Product descriptions cloned from manufacturers to retailers. Blog posts scraped, lightly paraphrased, and reposted under different bylines. Wikipedia content mirrored across hundreds of fansites. Legal boilerplate repeated verbatim across thousands of documents.

If you include all of this duplicated content in your training data, the consequences are concrete and measurable. The model sees certain texts disproportionately often, which distorts the learned token distribution toward the most replicated documents on the web. Models trained on unprocessed Common Crawl data memorize popular web content more readily, producing outputs that closely mirror specific training examples rather than generalizing from patterns. Benchmark contamination becomes a serious concern when test set passages appear verbatim or near-verbatim in training data, inflating reported performance metrics beyond what the model would achieve on previously unseen text.

The naive solution to this problem is exact deduplication: remove files with identical content. This works perfectly for verbatim copies but fails the moment someone changes a single word, adjusts the formatting, or reorders a sentence. A paraphrase that alters 10% of words is still functionally duplicate content from a training-data perspective. Near-duplicate detection requires measuring textual similarity, and measuring similarity between billions of document pairs using brute-force comparison is computationally infeasible.

MinHash is an algorithm that makes near-duplicate detection tractable at scale. It compresses each document into a compact numerical signature, called a MinHash signature, such that the probability of two signatures agreeing on any position equals the Jaccard similarity of the original documents. With these compact signatures, you can estimate similarity without ever comparing the full documents. With an extension called Locality Sensitive Hashing (LSH), you can find all near-duplicate pairs in a large corpus without comparing every pair, reducing what would be a quadratic-time problem to something approaching linear time in practice.

This chapter covers Jaccard similarity as the mathematical foundation, the probabilistic mechanism behind MinHash, how to implement it efficiently from first principles, and how LSH scales the approach to billions of documents. As we discussed in the deduplication chapter, MinHash is one of the dominant techniques used in cleaning training corpora for large language models, appearing in the pipelines that produced C4, RefinedWeb, RedPajama, and many other influential datasets.

Jaccard Similarity

Before building MinHash, you need to understand what similarity measure it estimates: Jaccard similarity. This measure has a long history in ecology and information retrieval, and its properties make it particularly well suited for detecting near-duplicate documents.

Definition and Intuition

The Jaccard similarity between two sets AA and BB measures overlap as a fraction of union:

J(A,B)=∣A∩B∣∣A∪B∣J(A, B) = \frac{|A \cap B|}{|A \cup B|}

where:

  • ∣A∩B∣|A \cap B|: the number of elements appearing in both AA and BB
  • ∣A∪B∣|A \cup B|: the number of distinct elements appearing in either AA or BB

Jaccard similarity ranges from 0 to 1. A score of 0 means the sets share no elements at all. A score of 1 means the sets are identical, containing exactly the same elements. For text documents, you represent each document as a set of overlapping substrings (called shingles or n-grams), then compute Jaccard similarity over those sets.

The intuition behind using Jaccard for documents is straightforward. If two documents are near-duplicates, they share most of their substrings, so the intersection of their shingle sets is nearly as large as the union. If two documents are completely unrelated, they share almost none of their characteristic phrases, so the intersection is tiny relative to the union. The ratio captures exactly how much of the content is shared.

Why Jaccard Rather Than Cosine Similarity?

Cosine similarity is another popular choice for comparing text representations, but it has different properties that make it less ideal for near-duplicate detection. Cosine similarity measures the angle between two vectors in a shared feature space, which makes it insensitive to magnitude differences. Two documents can have high cosine similarity even if one is much longer than the other, as long as their term frequencies are proportionally similar. For duplicate detection, you typically want a measure that penalizes documents that share only a small fraction of their content, and Jaccard does this naturally through the union in the denominator.

Jaccard is also naturally defined over sets, which maps cleanly onto the shingling representation of documents. There is no need to build a vocabulary or construct term-frequency vectors; you just extract substrings and compare sets. This simplicity is computationally important when processing billions of documents.

Worked Example of Jaccard Similarity

Consider these two sentences:

  • Document A: "the cat sat on the mat"
  • Document B: "the cat sat on a mat"

If you extract 3-word shingles (overlapping windows of 3 consecutive words):

Document A generates: {the cat sat, cat sat on, sat on the, on the mat}

Document B generates: {the cat sat, cat sat on, sat on a, on a mat}

The intersection contains 2 shingles: {the cat sat, cat sat on}. The union contains 6 distinct shingles. Therefore:

J(A,B)=26≈0.333J(A, B) = \frac{2}{6} \approx 0.333

Even though these documents differ by only one word ("the" vs "a"), their Jaccard similarity over 3-word shingles is only 0.33. This is because each changed word affects multiple overlapping shingles. Shorter shingles capture changes more coarsely; longer shingles are more sensitive to local edits. Choosing the right shingle size is one of the key design decisions in a MinHash pipeline.

Jaccard Similarity Is a Metric

Jaccard distance, defined as d(A,B)=1−J(A,B)d(A, B) = 1 - J(A, B), is a valid metric: it is symmetric, non-negative, zero only for identical sets, and satisfies the triangle inequality. This metric property has useful consequences. It means you can use Jaccard distance in metric data structures, and it provides theoretical guarantees for clustering algorithms based on Jaccard distance. MinHash, which approximates Jaccard similarity, inherits these properties approximately.

From Sets to Shingles

To apply Jaccard similarity to documents, you first convert each document into a set of overlapping substrings called shingles or k-grams. A shingle of size kk is a contiguous sequence of kk characters or kk words extracted by sliding a window of length kk across the document one step at a time.

Character-Level Shingles

Character-level shingles (for example, 5-character n-grams) offer fine-grained coverage of document content. The sentence "hello world" produces 5-gram character shingles: {hello, ello , llo w, lo wo, o wor, worl, world}. Notice that whitespace is included in the shingles, so word boundaries are implicitly captured.

Character-level shingles are robust to synonym substitution because replacing one word with a synonym changes many character-level shingles but preserves all the others. They are also more robust to minor formatting differences, like added punctuation or changed capitalization (after lowercasing). The main drawback is that they produce many more shingles per document than word-level shingles, increasing memory requirements during signature computation.

Word-Level Shingles

Word-level shingles treat words as the atomic units. A 3-word shingle captures consecutive trigrams of words, which are sensitive to phrase-level structure. Word-level shingles are better at capturing semantic phrases like "machine learning model" as a unit, making them more sensitive to reorderings and paraphrasings that preserve individual words but change their arrangement.

The downside of word-level shingles is sensitivity to synonym substitution: replacing "big" with "large" changes a word-level shingle entirely while barely changing the character-level representation. For near-duplicate detection of web documents where paraphrasing is the primary near-duplicate mechanism, character-level shingles typically perform better.

Shingle Size Selection

The choice of kk has a direct effect on sensitivity. Very short shingles (k=2 or k=3 characters) appear so frequently across unrelated documents that even dissimilar texts get high Jaccard similarity by chance. Very long shingles (k=20+ characters) become unique to each document, causing near-duplicates that differ slightly to appear unrelated.

In practice, most deduplication pipelines for LLM training use character-level shingles with kk between 5 and 9. The exact value is usually determined empirically by checking whether known near-duplicate pairs get high Jaccard scores and known unrelated pairs get low scores on a small labeled sample. The Pile, RefinedWeb, and similar datasets used k=5k = 5 character shingles as a standard choice.

Hashing Shingles to Integers

You can represent the shingle set compactly using a hash function. Rather than storing the raw strings (which can be hundreds of bytes for long shingles or many bytes even for short ones), you hash each shingle to a 32-bit or 64-bit integer. This trades a small collision probability for a large memory reduction. With 64-bit hashes, the probability of any two distinct shingles colliding is approximately 1/2641 / 2^{64}, which is negligible in practice.

Hashing shingles to integers also accelerates the MinHash computation, because comparing integers is faster than comparing strings, and the subsequent hash function computations operate directly on integer inputs.

The MinHash Algorithm

Computing exact Jaccard similarity for every document pair in a large corpus is infeasible. If you have nn documents, there are O(n2)O(n^2) pairs. For a corpus with 100 billion documents, you would need to compare roughly 5×10215 \times 10^{21} pairs. Even if each comparison took just one nanosecond, completing all comparisons would take over 150,000 years. No hardware can do this.

MinHash offers an elegant probabilistic approximation. Rather than computing the full shingle sets and comparing them, you compute a compact signature for each document such that signature similarity approximates Jaccard similarity. The idea originates in a 1997 paper by Broder, and it is built on a beautiful probabilistic identity about random permutations.

The Core Insight: Random Permutations

Imagine all possible shingles arranged in some arbitrary order, a permutation of the shingle universe. For a given document, look at the shingles it contains, then find the first shingle in the permutation that appears in the document. This is the minimum element under that permutation, hence the name "MinHash."

The key insight is a clean probabilistic fact. If you draw a uniformly random permutation π\pi of the shingle universe and compute the minimum element for documents AA and BB separately, the probability that both minima are the same equals the Jaccard similarity:

P(min⁡π(A)=min⁡π(B))=∣A∩B∣∣A∪B∣=J(A,B)P\left(\min_{\pi}(A) = \min_{\pi}(B)\right) = \frac{|A \cap B|}{|A \cup B|} = J(A, B)

Why This Probability Identity Holds

The proof is elegant and worth understanding. Consider the union A∪BA \cup B, which is the set of all shingles appearing in either document. Under a random permutation π\pi, exactly one shingle in A∪BA \cup B lands in the minimum position, that is, appears first in π\pi among all shingles in the union. Call this element e∗e^*.

There are two cases:

  1. e∗e^* falls in A∩BA \cap B (it appears in both documents). Then min⁡π(A)=e∗\min_\pi(A) = e^* and min⁡π(B)=e∗\min_\pi(B) = e^*, so the minima agree.
  2. e∗e^* falls in A∖BA \setminus B or B∖AB \setminus A (it appears in only one document). Then one minimum is e∗e^* and the other is some different element, so the minima disagree.

By symmetry of a uniformly random permutation, each element in A∪BA \cup B is equally likely to be e∗e^*. The fraction of elements in A∪BA \cup B that also belong to A∩BA \cap B is precisely ∣A∩B∣/∣A∪B∣|A \cap B| / |A \cup B|, which is the Jaccard similarity. Therefore:

P(min⁡π(A)=min⁡π(B))=∣A∩B∣∣A∪B∣=J(A,B)P\left(\min_{\pi}(A) = \min_{\pi}(B)\right) = \frac{|A \cap B|}{|A \cup B|} = J(A, B)

This is a single-bit estimator: it tells you whether the minima match, but a single comparison gives a binary answer (match or no match) with no granularity. To build a useful estimator, you need many independent trials.

Building the Signature with Multiple Hash Functions

To estimate Jaccard similarity reliably, you generate kk independent random permutations π1,π2,…,πk\pi_1, \pi_2, \ldots, \pi_k and compute kk minima per document. These kk values form the MinHash signature: a vector of kk integers. The fraction of positions where two signatures agree gives an estimate of Jaccard similarity:

J^(A,B)=1k∑i=1k1[min⁡πi(A)=min⁡πi(B)]\hat{J}(A, B) = \frac{1}{k} \sum_{i=1}^{k} \mathbf{1}\left[\min_{\pi_i}(A) = \min_{\pi_i}(B)\right]

where 1[⋅]\mathbf{1}[\cdot] is the indicator function, equal to 1 when the condition holds and 0 otherwise.

The variance of this estimator decreases with kk following the formula for a Bernoulli average:

Var(J^)=J(1−J)k\text{Var}\left(\hat{J}\right) = \frac{J(1-J)}{k}

With k=128k = 128 hash functions, the standard deviation at J=0.5J = 0.5 (the worst case) is 0.25/128≈0.044\sqrt{0.25 / 128} \approx 0.044. With k=256k = 256, it falls to roughly 0.0310.031. The tradeoff is memory: kk 32-bit hash values per document requires 4k4k bytes, so 128-hash signatures cost 512 bytes and 256-hash signatures cost 1024 bytes per document. For a billion documents, this is 512 GB and 1 TB respectively, manageable in a distributed system.

Hash Function Implementation

Generating kk actual random permutations over a large shingle universe is impractical in two ways. First, a permutation over a universe of 2322^{32} elements requires 2322^{32} integer entries, or 16 GB just to store one permutation. Second, applying the permutation to find the minimum requires scanning all elements.

Instead, you simulate permutations using random hash functions. For each of the kk "permutations," define a hash function hih_i that maps shingle integers to a range of integers. The minimum hash value of the document's shingles under hih_i approximates the minimum element under a random permutation πi\pi_i, provided the hash function distributes values approximately uniformly.

A classic approach from the universal hashing literature uses linear functions modulo a prime:

ha,b(x)=((a⋅x+b) mod p) mod Mh_{a,b}(x) = ((a \cdot x + b) \bmod p) \bmod M

where:

  • xx: the integer-hashed shingle value
  • aa: a random integer drawn uniformly from {1,…,p−1}\{1, \ldots, p-1\}
  • bb: a random integer drawn uniformly from {0,…,p−1}\{0, \ldots, p-1\}
  • pp: a large prime number larger than MM (for example, the Mersenne prime 231−12^{31} - 1)
  • MM: the size of the hash space (for example, 2322^{32})

For each of the kk hash functions, you pick different (a,b)(a, b) pairs. The minimum over all shingles in the document under ha,bh_{a,b} gives one component of the MinHash signature.

Modern implementations often use faster non-cryptographic hash functions like MurmurHash3 or xxHash with different seeds for each of the kk positions. The critical requirement is that the hash functions distribute values uniformly and independently, which both MurmurHash and xxHash achieve with high quality. In very large pipelines, a technique called "one permutation MinHash" (or densified one permutation MinHash) uses a single hash of the document into a partitioned range, computing one minimum per partition rather than kk separate hashes, which is significantly faster.

The Minimum Hash Value Is the Key

It may seem surprising that simply taking the minimum hash value encodes useful similarity information. The intuition is that under a hash function with uniform output distribution, the minimum hash value of a large set of shingles is essentially a random sample from the shingle distribution. If two documents share many shingles, there is a high chance that the shingle with the smallest hash value appears in both. If they share few shingles, the minimums are likely to come from their non-overlapping portions and will differ.

More precisely, the minimum hash value is a random representative of the shingle set. Two documents with high Jaccard similarity are likely to pick the same representative because their shingle sets are nearly the same. Two documents with low Jaccard similarity are likely to pick different representatives.

Worked Example

Let's walk through a concrete MinHash computation with a small example that you can follow step by step. This will solidify both the mechanics of the algorithm and the intuition behind why it works.

Suppose we have two documents:

  • Document A: "data science is great"
  • Document B: "data science is wonderful"

We extract 2-word shingles:

  • A's shingles: {data science, science is, is great}
  • B's shingles: {data science, science is, is wonderful}

The intersection is {data science, science is}, with 2 elements. The union has 4 distinct shingles: {data science, science is, is great, is wonderful}. So the exact Jaccard similarity is:

J(A,B)=24=0.5J(A, B) = \frac{2}{4} = 0.5

Now suppose we assign integer IDs to the 4 distinct shingles: data science = 0, science is = 1, is great = 2, is wonderful = 3. We use two hash functions:

h1(x)=(3x+1) mod 5h_1(x) = (3x + 1) \bmod 5 h2(x)=(x+2) mod 5h_2(x) = (x + 2) \bmod 5

Computing hash values for each shingle ID:

  • h1(0)=1h_1(0) = 1, h1(1)=4h_1(1) = 4, h1(2)=2h_1(2) = 2, h1(3)=0h_1(3) = 0
  • h2(0)=2h_2(0) = 2, h2(1)=3h_2(1) = 3, h2(2)=4h_2(2) = 4, h2(3)=0h_2(3) = 0

Document A contains shingle IDs {0,1,2}\{0, 1, 2\}, so its MinHash signature is:

sig(A)=[min⁡(h1(0),h1(1),h1(2)),  min⁡(h2(0),h2(1),h2(2))]\text{sig}(A) = \left[\min(h_1(0), h_1(1), h_1(2)),\; \min(h_2(0), h_2(1), h_2(2))\right] =[min⁡(1,4,2),  min⁡(2,3,4)]=[1,2]= \left[\min(1, 4, 2),\; \min(2, 3, 4)\right] = [1, 2]

Document B contains shingle IDs {0,1,3}\{0, 1, 3\}, so its MinHash signature is:

sig(B)=[min⁡(h1(0),h1(1),h1(3)),  min⁡(h2(0),h2(1),h2(3))]\text{sig}(B) = \left[\min(h_1(0), h_1(1), h_1(3)),\; \min(h_2(0), h_2(1), h_2(3))\right] =[min⁡(1,4,0),  min⁡(2,3,0)]=[0,0]= \left[\min(1, 4, 0),\; \min(2, 3, 0)\right] = [0, 0]

The estimated Jaccard similarity is the fraction of positions where the signatures agree:

J^(A,B)=02=0\hat{J}(A, B) = \frac{0}{2} = 0

Both positions disagree, giving an estimate of 0. The true Jaccard is 0.5. This is a bad estimate, but that's expected: with only 2 hash functions, variance is enormous. The standard deviation at J=0.5J = 0.5 with k=2k = 2 is 0.25/2≈0.35\sqrt{0.25 / 2} \approx 0.35, so getting an estimate anywhere from 0 to 1 is quite plausible.

With k=128k = 128 hash functions, the standard deviation would be 0.25/128≈0.044\sqrt{0.25 / 128} \approx 0.044, and the estimate would almost certainly fall close to 0.5. This variance reduction is why practical MinHash implementations use 128 to 256 hash functions.

MinHash LSH: Finding Near-Duplicates at Scale

Computing MinHash signatures reduces the comparison problem: instead of comparing full documents (potentially thousands of characters), you compare compact kk-dimensional integer vectors. But you still face the O(n2)O(n^2) pair comparison problem. For a billion documents with 128-dimensional signatures, comparing all pairs would require roughly 6.4×10166.4 \times 10^{16} integer comparisons. Even at 10 billion comparisons per second, this would take over 6 million seconds, or about 74 days, on a single machine.

Locality Sensitive Hashing solves this by converting the problem into a bucketing problem. Instead of comparing all pairs, you organize documents into buckets such that similar documents tend to fall into the same bucket. You then only compare documents within the same bucket, dramatically reducing the number of comparisons without losing many true near-duplicate pairs.

The Core LSH Idea

Locality Sensitive Hashing is a general framework for approximate nearest neighbor search. The key requirement is a hash function family that is "locally sensitive," meaning pairs with high similarity hash to the same bucket with high probability, while pairs with low similarity hash to different buckets with high probability.

For MinHash, the natural LSH family is to use each component of the signature as a hash function. If two documents have the same value at position ii of their signatures, they hash to the same bucket under hash function ii. By our earlier analysis, two documents share a signature value at position ii with probability equal to their Jaccard similarity.

A single hash function gives very coarse bucketing. With large enough buckets, everything ends up in the same bucket; with small enough buckets, nothing shares a bucket. The banding technique refines this into a more useful filter.

The Band Technique

The standard MinHash LSH approach divides the kk-length signature into bb bands of rr rows each, so k=b×rk = b \times r. For each band, you hash the rr values in that band (treated as a unit, for example, a tuple of rr integers) into a bucket. Two documents become candidate pairs if and only if they share a bucket in at least one band.

The probability that two documents with true Jaccard similarity ss share all rr values in a given band is srs^r. The probability that they do not share all values in that band is 1−sr1 - s^r. The probability that they fail to match in all bb bands is (1−sr)b(1 - s^r)^b. Therefore, the probability that they share at least one band (and thus become candidates) is:

P(candidate∣J=s)=1−(1−sr)bP(\text{candidate} \mid J = s) = 1 - (1 - s^r)^b

where:

  • ss: the true Jaccard similarity between the pair
  • rr: the number of rows per band
  • bb: the number of bands

This function is an S-curve in ss: it transitions sharply from near 0 (low-similarity pairs are almost never candidates) to near 1 (high-similarity pairs are almost always candidates). The location of the transition is controlled by rr and bb.

The Threshold and S-Curve Shape

The midpoint of the S-curve transition (where the probability equals 0.5) provides a useful threshold approximation. Setting 1−(1−tr)b=0.51 - (1 - t^r)^b = 0.5 and solving approximately:

t≈(1b)1/rt \approx \left(\frac{1}{b}\right)^{1/r}

For example, with b=20b = 20 bands and r=5r = 5 rows (a 100-dimensional signature), the threshold is approximately (1/20)1/5≈0.55(1/20)^{1/5} \approx 0.55. Pairs with Jaccard similarity well above 0.55 are detected as candidates with very high probability; pairs well below 0.55 almost never become candidates.

The S-curve sharpness increases as rr increases. With large rr, the condition that all rr rows match is stringent, producing a sharp transition. With small rr, the transition is more gradual because matching 2 out of 2 rows is less discriminating than matching 5 out of 5.

This tunability is one of MinHash LSH's great practical advantages. By choosing bb and rr, you can set the detection threshold precisely:

  • For aggressive deduplication at similarity 0.7, use bb and rr such that the S-curve midpoint falls near 0.7.
  • For conservative deduplication targeting only near-perfect duplicates at similarity 0.9, use parameters that push the threshold higher.
  • For a fixed budget of kk hash functions, you trade off sharpness versus threshold by varying how you split kk into b×rb \times r.

LSH as a Two-Stage Pipeline

LSH does not give you the exact similarity of candidate pairs. It identifies a manageable set of candidate pairs that you then verify by computing exact (or more accurate estimated) Jaccard similarity from the full signatures or from the actual shingle sets. This two-stage architecture, coarse LSH filtering followed by fine verification, makes near-duplicate detection tractable at scale.

The first stage runs in linear time per document (computing the signature and performing bb band hash lookups) plus time proportional to the number of candidate pairs (checking each bucket). The second stage runs in time proportional to the number of candidate pairs, which is much smaller than n2n^2 for realistic similarity distributions.

In production deduplication pipelines for LLM training, the two-stage approach looks like this. All documents are processed in parallel to compute MinHash signatures. Band hashing is performed in a distributed shuffle that groups documents by band bucket. Each bucket is then checked: if it contains more than one document, those documents are emitted as candidate pairs. The candidate pairs are then verified and deduplicated, keeping one representative from each near-duplicate cluster.

False Positives and False Negatives

The banding technique introduces two types of errors. False positives occur when two documents that are not near-duplicates happen to share a band bucket, leading you to compare them unnecessarily. False negatives occur when two true near-duplicates fail to share any band bucket, causing them to be missed.

The S-curve shape directly characterizes these errors. At similarity ss, the probability of a false negative is (1−sr)b(1 - s^r)^b: the pair falls below the detection curve. For ss well above the threshold, this probability is very small. At similarity ss, the probability of a false positive (unnecessary comparison) is the same formula applied at the wrong point: 1−(1−sr)b1 - (1 - s^r)^b for pairs that should not be candidates. For ss well below the threshold, this is also very small.

Near the threshold, both errors occur with non-trivial probability. This is an inherent statistical tradeoff: no threshold-based filter can simultaneously have zero false positives and zero false negatives unless the similarity distribution has a hard gap around the threshold.

For LLM training data deduplication, some false negatives are acceptable. The goal is not perfect deduplication but substantial reduction in near-duplicate content. Missing a few near-duplicate pairs is less costly than the enormous computational burden of verifying all pairs.

Implementation

This section walks through a complete MinHash LSH implementation from scratch, starting with shingle extraction and working up to candidate pair detection and verification.

Setting Up

We begin by importing required libraries. No external packages beyond NumPy are required for the core implementation.

Shingle Extraction

The first step converts each document into a set of character-level shingles. We normalize whitespace and lowercase the text before shingling to ensure consistent matching across minor formatting differences.

In[5]:
Code
def get_shingles(text: str, k: int = 5) -> set[int]:
    """Extract character-level k-shingles from text, returned as hashed integers."""
    text = re.sub(r"\s+", " ", text.lower().strip())
    shingles = set()
    for i in range(len(text) - k + 1):
        shingle = text[i : i + k]
        # Hash shingle to integer for efficiency
        h = int(hashlib.md5(shingle.encode()).hexdigest(), 16) % (2**32)
        shingles.add(h)
    return shingles

The function slides a window of width kk over the normalized text, hashing each shingle to a 32-bit integer. Using MD5 here is acceptable for hashing purposes (not security), and in production you would use a faster non-cryptographic hash like xxHash.

Out[6]:
Console
Document A shingle count: 28
Document B shingle count: 28
Document C shingle count: 52

Exact Jaccard(A, B): 0.6970
Exact Jaccard(A, C): 0.0000
Exact Jaccard(B, C): 0.0000

Documents A and B are near-duplicates (one word differs), so they share most of their 5-character shingles and receive a high Jaccard similarity score. Document C is completely unrelated to both, giving near-zero overlap with either. This confirms that the 5-gram character shingling correctly separates similar from dissimilar documents.

MinHash Signature Generation

Next, we implement the MinHash signature computation. We use kk independent hash functions, each parameterized by a distinct random (a,b)(a, b) pair drawn from a Mersenne prime field.

In[7]:
Code
class MinHash:
    """MinHash signature generator using random linear hash functions."""

    _PRIME = (1 << 31) - 1  # Mersenne prime 2^31 - 1
    _MAX_HASH = 2**32

    def __init__(self, num_hashes: int = 128, seed: int = 42):
        self.num_hashes = num_hashes
        rng = random.Random(seed)
        # Generate random coefficients for each hash function
        self.a = [rng.randint(1, self._PRIME - 1) for _ in range(num_hashes)]
        self.b = [rng.randint(0, self._PRIME - 1) for _ in range(num_hashes)]

    def signature(self, shingles: set[int]) -> np.ndarray:
        """Compute MinHash signature: minimum hash value under each hash function."""
        sig = np.full(self.num_hashes, self._MAX_HASH, dtype=np.uint64)
        for shingle in shingles:
            for i in range(self.num_hashes):
                h = (self.a[i] * shingle + self.b[i]) % self._PRIME
                if h < sig[i]:
                    sig[i] = h
        return sig

    def similarity(self, sig1: np.ndarray, sig2: np.ndarray) -> float:
        """Estimate Jaccard similarity from two signatures."""
        return float(np.mean(sig1 == sig2))

The signature method initializes each position to the maximum possible hash value (serving as infinity), then iterates over all shingles in the document. For each shingle, it computes the hash value under each of the kk linear hash functions and updates the minimum if the new value is smaller. After processing all shingles, the vector of kk minimums is the MinHash signature.

Out[8]:
Console
MinHash similarity estimates (128 hashes):
  A vs B (expected ~high):  0.7734  |  exact: 0.6970
  A vs C (expected ~low):   0.0000  |  exact: 0.0000
  B vs C (expected ~low):   0.0000  |  exact: 0.0000

The MinHash estimates closely track the exact Jaccard values. Documents A and B, which differ by a single word, receive a high estimated similarity close to the exact value. Document C, which is unrelated, receives near-zero estimates for both A vs C and B vs C. With 128 hash functions, the estimates are quite accurate.

LSH Banding

Now we implement the banding technique that makes near-duplicate detection scalable. We divide the signature into bb bands of rr rows each, and bucket documents by their band values.

In[9]:
Code
class LSH:
    """Locality Sensitive Hashing for MinHash signatures using the band technique."""

    def __init__(self, num_bands: int, rows_per_band: int):
        self.num_bands = num_bands
        self.rows_per_band = rows_per_band
        self.num_hashes = num_bands * rows_per_band
        # Each band maintains its own hash table mapping band-hash -> list of doc ids
        self.buckets: list[dict[int, list]] = [
            defaultdict(list) for _ in range(num_bands)
        ]

    def add(self, doc_id: int, signature: np.ndarray) -> None:
        """Add a document signature to the LSH index."""
        for band_idx in range(self.num_bands):
            start = band_idx * self.rows_per_band
            end = start + self.rows_per_band
            band_values = tuple(signature[start:end])
            # Hash the band tuple to a bucket key
            bucket_key = hash(band_values)
            self.buckets[band_idx][bucket_key].append(doc_id)

    def candidate_pairs(self) -> set[tuple]:
        """Return all (doc_i, doc_j) pairs that share at least one bucket."""
        candidates = set()
        for band in self.buckets:
            for bucket_docs in band.values():
                if len(bucket_docs) > 1:
                    for pair in combinations(bucket_docs, 2):
                        candidates.add(tuple(sorted(pair)))
        return candidates

The add method computes bb band hashes for each document and inserts the document ID into the corresponding bucket in each band. The candidate_pairs method iterates over all buckets and emits all pairs of documents that share a bucket in any band.

Out[10]:
Console
Candidate pairs detected: {(0, 1)}
Approximate LSH threshold: 0.707

Pair verification:
  Documents (0, 1): estimated similarity = 0.7734

LSH correctly identifies documents A and B as candidates and correctly ignores the pairing with document C. The configuration of 16 bands times 8 rows gives a threshold near 0.72. Documents A and B, with similarity well above this threshold, are reliably detected as candidates. Document C, with near-zero similarity to the others, is correctly excluded.

Scaling to a Larger Corpus

Let's simulate the approach on a larger corpus to observe how MinHash LSH scales. We generate a collection of documents where some are near-duplicates, then measure the candidate reduction.

In[11]:
Code
def generate_corpus(
    num_docs: int = 1000, dup_fraction: float = 0.2, seed: int = 0
) -> list[str]:
    """Generate a corpus with a known fraction of near-duplicate pairs."""
    rng = random.Random(seed)
    base_texts = [
        "machine learning models learn patterns from data through iterative optimization",
        "natural language processing enables computers to understand human text",
        "neural networks are computational models inspired by biological neurons",
        "transformer architectures use self attention to process sequences efficiently",
        "pretraining on large corpora gives models broad language understanding",
    ]

    words_pool = [
        "the",
        "a",
        "large",
        "small",
        "deep",
        "modern",
        "efficient",
        "powerful",
        "scalable",
        "robust",
        "accurate",
        "fast",
        "simple",
    ]

    corpus = []
    for i in range(num_docs):
        base = rng.choice(base_texts)
        words = base.split()
        if i < int(num_docs * dup_fraction):
            # Near-duplicate: swap one word
            idx = rng.randint(0, len(words) - 1)
            words[idx] = rng.choice(words_pool)
        else:
            # Unique: replace several words
            for _ in range(rng.randint(3, 6)):
                idx = rng.randint(0, len(words) - 1)
                words[idx] = rng.choice(words_pool)
        corpus.append(" ".join(words))
    return corpus


corpus = generate_corpus(num_docs=1000)
In[12]:
Code
# Compute MinHash signatures for all documents
mh = MinHash(num_hashes=128, seed=42)
lsh_large = LSH(num_bands=16, rows_per_band=8)

t0 = time.time()
all_sigs = []
for doc_id, text in enumerate(corpus):
    shingles = get_shingles(text, k=5)
    sig = mh.signature(shingles)
    all_sigs.append(sig)
    lsh_large.add(doc_id, sig)

signature_time = time.time() - t0

# Find candidate pairs
t1 = time.time()
candidates_large = lsh_large.candidate_pairs()
lsh_time = time.time() - t1
Out[13]:
Console
Corpus size: 1000 documents
Total possible pairs: 499,500
Candidate pairs found: 2,260
Candidate reduction factor: 221.0x

Timing:
  Signature computation: 1.556s
  LSH candidate search:  0.001s

LSH reduces the number of pairs to compare by orders of magnitude. Rather than checking all nearly 500,000 possible pairs, you only compare the small fraction that share at least one LSH bucket. This reduction factor grows rapidly with corpus size, making LSH the key ingredient that allows MinHash to scale to billions of documents.

Visualizations

Let's visualize two key properties of MinHash: how the similarity estimate accuracy improves with more hash functions, and the S-curve behavior of LSH banding.

Out[14]:
Visualization
Line plot showing decreasing MinHash estimation error as number of hash functions increases from 8 to 512 for four Jaccard values.
Mean absolute error of MinHash Jaccard similarity estimates as a function of the number of hash functions, for four different true Jaccard values. Error decreases with more hash functions following the theoretical 1/sqrt(k) rate. At 128 hash functions the mean error falls below 0.04 for all similarity levels, making MinHash accurate enough for practical deduplication decisions.
Out[15]:
Visualization
S-curve plot of LSH detection probability versus Jaccard similarity for three band configurations.
LSH candidate detection probability as a function of Jaccard similarity for three band configurations with different total hash budgets. Each S-shaped curve transitions sharply around a threshold determined by the band parameters, with vertical dotted lines marking each approximate threshold. Higher r (rows per band) produces steeper transitions; higher b (number of bands) shifts the threshold lower.
S-curve plot comparing four band or row splits for 128 total hash functions.
LSH probability curves for a fixed 128-hash budget, split into four different band configurations. Fewer bands with more rows per band (b=8, r=16) produce a steep S-curve at a high threshold, while many bands with fewer rows (b=64, r=2) detect lower-similarity pairs with a shallower transition. This tradeoff lets practitioners tune detection sensitivity.
Out[16]:
Visualization
Heatmap matrix of MinHash pairwise similarity scores for 10 documents, revealing two clusters of near-duplicates.
MinHash pairwise similarity estimates for a 10-document corpus containing two groups of near-duplicates. Documents 1 through 5 are variations of a base sentence about machine learning; documents 6 through 10 are variations of a base sentence about neural networks. High estimated similarity (green) appears within each group, while cross-group pairs show near-zero similarity (red), showing how MinHash LSH exploits these clusters to find near-duplicates efficiently.

The heatmap reveals the two clusters of near-duplicates clearly. Documents 1 through 5 are paraphrases of the first base text; documents 6 through 10 are paraphrases of the second. Within each cluster, MinHash estimates high similarity (green). Across clusters, similarity is near zero (red). This block structure is exactly what MinHash LSH exploits: the LSH bands quickly identify the within-cluster pairs as candidates while ignoring the cross-cluster pairs.

Accuracy Analysis: How Many Hash Functions Do You Need?

The estimation variance of MinHash follows directly from the properties of Bernoulli trials. Each of the kk positions in the signature independently agrees with probability J(A,B)J(A, B). The estimated similarity J^\hat{J} is a sample mean of kk independent Bernoulli trials with success probability JJ. By standard statistics:

Var(J^)=J(1−J)k\text{Var}\left(\hat{J}\right) = \frac{J(1-J)}{k}

and the standard deviation is:

SD(J^)=J(1−J)k\text{SD}\left(\hat{J}\right) = \sqrt{\frac{J(1-J)}{k}}

where:

  • JJ: the true Jaccard similarity
  • kk: the number of hash functions
  • J^\hat{J}: the MinHash estimate

The variance is maximized when J=0.5J = 0.5, giving Var=0.25/k\text{Var} = 0.25/k. At the extremes (J=0J = 0 or J=1J = 1), the variance is zero because the answer is known exactly with no uncertainty. For deduplication, the most error-prone regime is when you are near the threshold, which is typically around 0.7 to 0.9 for LLM training pipelines. At J=0.8J = 0.8, the variance is 0.8×0.2/k=0.16/k0.8 \times 0.2 / k = 0.16 / k, somewhat lower than the worst case.

For k=128k = 128 and J=0.5J = 0.5, the standard deviation is 0.25/128≈0.044\sqrt{0.25 / 128} \approx 0.044. A 95% confidence interval spans roughly ±1.96×0.044≈±0.086\pm 1.96 \times 0.044 \approx \pm 0.086. For deduplication decisions, you typically only care whether similarity exceeds a threshold (say, 0.8), not the exact value. An error of 0.09 around the threshold means pairs close to the threshold are uncertain, but pairs well above or well below are reliably classified.

In[17]:
Code
# Analyze the accuracy of MinHash estimation as a function of k
def minhash_std(j: float, k: int) -> float:
    """Theoretical standard deviation of MinHash similarity estimate."""
    return np.sqrt(j * (1 - j) / k)


k_values = [32, 64, 128, 256, 512]
jaccard_test = 0.5  # Worst-case variance at J=0.5
Out[18]:
Console
MinHash estimation standard deviation at J=0.5:
     k     Std Dev     95% CI width
------------------------------------
    32      0.0884           0.3465
    64      0.0625           0.2450
   128      0.0442           0.1732
   256      0.0312           0.1225
   512      0.0221           0.0866

At k=128k = 128 hash functions, the 95% confidence interval width is roughly 0.17. At k=256k = 256, it narrows to about 0.12. In practice, 128 hash functions is the most common choice: it provides sufficient accuracy for deduplication decisions while keeping memory requirements manageable (512 bytes per document).

Confidence Intervals and Threshold Decisions

The practical question is not "how accurate is the estimate?" but "how likely are we to make the wrong deduplication decision?" If your threshold is τ=0.8\tau = 0.8 and two documents have true similarity J=0.85J = 0.85, what is the probability that the MinHash estimate falls below 0.8, causing a false negative?

With k=128k = 128 and J=0.85J = 0.85, the standard deviation is 0.85×0.15/128≈0.031\sqrt{0.85 \times 0.15 / 128} \approx 0.031. The threshold is (0.8−0.85)/0.031≈−1.6(0.8 - 0.85) / 0.031 \approx -1.6 standard deviations below the true value. The probability of a false negative is approximately P(Z<−1.6)≈0.055P(Z < -1.6) \approx 0.055, or about 5.5%. Increasing kk to 256 reduces this to about 1.5%.

This analysis explains why practitioners choose 128 to 256 hash functions rather than fewer: the marginal cost in memory and computation is small, but the reduction in deduplication errors is meaningful.

MinHash in LLM Training Pipelines

MinHash LSH is the dominant deduplication method for web-scale LLM training corpora. Its practical dominance comes from a combination of properties that make it uniquely suited to this application: linear time signature computation, compact storage, tunable precision-recall tradeoffs, and easy parallelization.

Historical Applications

The landmark C4 dataset, used to train the T5 family of models from Google, applied MinHash deduplication to Common Crawl data. The authors found that deduplicated data significantly improved downstream task performance compared to training on the raw corpus. This result has been replicated consistently: removing near-duplicate content improves model quality by reducing memorization of repeated text and improving the diversity of patterns learned.

The Pile, a large open training corpus assembled by EleutherAI, used MinHash LSH to remove near-duplicate documents within each of its 22 constituent datasets. The RedPajama dataset, an open reproduction of the LLaMA training data, applied MinHash deduplication at multiple stages of processing. RefinedWeb, which underpins the Falcon models, ran aggressive MinHash deduplication as a central component of its data processing pipeline, retaining approximately 65% of the documents after deduplication and other filtering steps.

How Web-Scale Pipelines Work

At web scale (trillions of tokens across billions of documents), the MinHash LSH pipeline is distributed across a large compute cluster. The processing proceeds in stages.

In the first stage, documents are partitioned across worker nodes. Each worker computes MinHash signatures for its shard of the corpus independently, since signature computation is embarrassingly parallel. Workers output (document ID, signature) pairs.

In the second stage, the signatures are processed to perform LSH bucketing. This requires a distributed shuffle: for each band, all documents whose rr signature values in that band are identical must be grouped together. In Spark or MapReduce frameworks, this is a natural reduce-by-key operation where the key is the (band index, band hash value) pair and the value is the document ID. After the shuffle, each reducer receives all document IDs that share a given band hash, which become candidate pairs.

In the third stage, candidate pairs are verified. For each candidate pair, the exact MinHash similarity is computed from the full signatures. Pairs exceeding the threshold are confirmed as near-duplicates.

In the fourth stage, clusters are formed. Near-duplicate detection identifies pairs, not clusters. If document A is a near-duplicate of B, and B is a near-duplicate of C, but A and C do not exceed the threshold directly, all three should be deduplicated together. Connected component analysis on the candidate graph identifies maximal clusters. One document is kept from each cluster, typically the one appearing earliest in the data or the one from the highest-quality source.

Choosing Parameters for LLM Deduplication

The typical configuration for LLM training data deduplication uses:

  • Shingle type: character-level 5-grams, normalized and lowercased
  • Number of hash functions: k=128k = 128 or k=256k = 256
  • Similarity threshold: 0.70 to 0.90, depending on aggressiveness
  • Band configuration: bb and rr chosen so the S-curve midpoint matches the desired threshold

For a threshold of 0.8 with k=128k = 128, a common configuration is b=16b = 16 bands of r=8r = 8 rows, giving a threshold near (1/16)1/8≈0.72(1/16)^{1/8} \approx 0.72. A more aggressive threshold of 0.65 might use b=25b = 25 bands of r=5r = 5 rows.

The Impact on Model Quality

Research consistently shows that deduplication improves model quality in measurable ways. Models trained on deduplicated data exhibit lower memorization of training examples, meaning they are less likely to reproduce verbatim text from training data when prompted. This is both a quality improvement (the model generalizes better) and a safety improvement (the model is less likely to inadvertently reproduce copyrighted or sensitive text).

Deduplication also improves benchmark integrity. When test benchmarks are contaminated by near-duplicate examples from the training set, reported scores are inflated relative to true generalization ability. Removing near-duplicates reduces this contamination. The GPT-3 paper reported that near-duplicate test examples in training data led to modestly inflated benchmark scores, and subsequent models have been more careful about this.

Beyond near-duplicate removal, MinHash is a lightweight pre-filter for more expensive deduplication checks. You first run MinHash to find approximate duplicate clusters, then verify the most suspicious pairs with exact string matching or more expensive similarity methods. This two-stage approach dramatically reduces the cost of exact deduplication without sacrificing recall.

Limitations and Practical Considerations

MinHash is a remarkably effective tool, but understanding its limitations prevents misapplication and guides parameter selection.

Sensitivity to Document Length

Very short documents (tweets, headings, short snippets of fewer than 100 characters) have few shingles, which inflates Jaccard estimates due to small set size effects. A document with only 10 unique shingles is likely to share a few with almost any other short document by chance, leading to spuriously high Jaccard estimates. A common remedy is to require a minimum document length before applying MinHash, discarding very short documents from the deduplication pipeline or applying a separate deduplication method for short texts. Most LLM training pipelines filter out documents below a word count threshold (often 50 to 100 words) before applying MinHash.

Shingle Choice Affects Sensitivity

Character-level shingles of size 5 are sensitive to most paraphrasing but may incorrectly flag documents that simply share common boilerplate text, like legal disclaimers, terms of service sections, or navigation menus in otherwise distinct web pages. Word-level shingles are more robust to character-level noise but fail to detect documents that rewrite content with synonyms. Neither choice is universally correct; the right shingling strategy depends on the type of near-duplicates you want to catch. In practice, most LLM pipelines use character-level 5-grams and accept that some boilerplate similarity is tolerable.

A more sophisticated approach uses weighted MinHash, which assigns higher weight to rare shingles and lower weight to common ones, reducing the influence of boilerplate phrases on the similarity estimate. This is analogous to the IDF weighting used in TF-IDF representations.

LSH False Negatives

The banding technique trades recall for efficiency. Near-duplicate pairs with similarity near or just below the threshold may be missed entirely (false negatives). The probability of a false negative at similarity ss is (1−sr)b(1 - s^r)^b. For pairs significantly above the threshold, this probability is negligible. For pairs at or below the threshold, it can be substantial.

For deduplication of training data, some false negatives are acceptable. The goal is to reduce redundancy substantially, not eliminate it perfectly. If 95% of near-duplicate pairs are detected and removed, the training data is dramatically cleaner than the original, even if a few pairs slip through. The computational savings from missing that 5% are significant.

Cluster Transitivity and Graph Construction

MinHash identifies pairs, not clusters. Two documents A and B may each be near-duplicates of a central document C without being near-duplicates of each other (if A and C share many shingles, C and B share many shingles, but A and B have little direct overlap). Naive pair-based deduplication would retain A and B while removing only duplicates of C, leaving two near-duplicate documents in the corpus.

The correct approach is connected component analysis. Build a graph where nodes are documents and edges connect near-duplicate pairs. Each connected component is a cluster of mutually near-duplicate documents (by transitivity). Keep one representative per component, discarding the rest. This is standard practice in production pipelines but adds a post-processing step.

Implementation Complexity at Scale

On billions of documents, the LSH bucketing step requires careful engineering. The hash tables can exceed available memory on a single machine. A corpus of one billion documents with b=16b = 16 bands creates up to 16×10916 \times 10^9 (document, band) entries for the hash tables. Storing these in memory requires specialized distributed hash table implementations or disk-backed storage.

Production systems partition the signature matrix across nodes and perform the band-hash bucketing in a distributed shuffle. Each node handles a subset of bands, and the shuffle redistributes (document, band hash) pairs so that all documents with the same band hash arrive at the same node. This is an efficient use of distributed compute resources but requires careful partitioning to avoid hot spots where one bucket contains a disproportionate share of documents.

Memory bandwidth is another bottleneck. Reading kk integers per document from a corpus of billions of documents involves terabytes of memory reads. Careful layout of the signature matrix in memory (row-major order for sequential access) and batched processing of documents significantly improve throughput.

MinHash Does Not Detect Semantic Similarity

MinHash detects near-duplicate surface text, not semantic similarity. Two documents can express identical ideas in completely different words and receive a Jaccard similarity of near zero. Conversely, two documents that are formally near-duplicates (high Jaccard similarity) might contain importantly different information if the differing words carry key content.

For LLM training data curation, this limitation is usually acceptable. The primary concern is surface redundancy, which distorts the training distribution and causes memorization. Semantic deduplication (removing documents that express the same ideas differently) is a separate and harder problem, typically approached using embedding-based similarity methods rather than MinHash.

Despite these limitations, MinHash LSH remains the practical gold standard for near-duplicate detection at scale. Its linear-time signature computation, compact storage, tunable parameters, and probabilistic guarantees make it uniquely suited to the data volumes required for LLM pretraining. No alternative approach comes close to its combination of efficiency and reliability at billion-document scale.

Summary

MinHash compresses each document into a compact signature that preserves Jaccard similarity: the probability that two signatures agree at any position equals the true Jaccard similarity of the underlying shingle sets. This probabilistic identity allows you to estimate document similarity without comparing the full texts, reducing per-pair comparison cost from the document size to the signature size.

Key takeaways from this chapter:

  • Jaccard similarity measures set overlap as the ratio of intersection to union, ranging from 0 to 1. It is a natural similarity measure for shingled documents and is robust to length differences between documents.
  • Shingling converts documents into sets of overlapping substrings. Character-level 5-grams are the standard choice for LLM training data deduplication because they are robust to minor paraphrasing and synonym substitution.
  • The MinHash identity states that for a random permutation π\pi, the probability that the minimum elements of two sets agree is exactly the Jaccard similarity: P(min⁡π(A)=min⁡π(B))=J(A,B)P(\min_\pi(A) = \min_\pi(B)) = J(A, B).
  • MinHash signatures of length kk are computed by applying kk independent hash functions and taking the minimum hash value per function. The fraction of matching positions estimates Jaccard similarity with standard deviation J(1−J)/k\sqrt{J(1-J)/k}.
  • LSH banding divides the signature into bb bands of rr rows, bucketing documents by their band values. Documents sharing any bucket become candidate pairs with probability 1−(1−sr)b1 - (1 - s^r)^b, creating a tunable S-curve threshold at approximately t≈(1/b)1/rt \approx (1/b)^{1/r}.
  • Practical tradeoffs include shingle size (character vs. word level), number of hash functions (kk), and LSH threshold (bb, rr). These parameters balance precision, recall, and computational cost for the specific deduplication task.
  • At LLM training scale, MinHash LSH is applied to billions of documents in distributed pipelines, reducing near-duplicate content that would otherwise distort the learned distribution, inflate benchmark scores, and increase memorization of training text.

The next chapter explores quality filtering techniques that complement deduplication by removing documents that are syntactically valid but semantically poor, completing the picture of how raw web crawl data is transformed into a high-quality training corpus.

Quiz

Ready to test your understanding? Take this quick quiz to reinforce what you've learned about MinHash and Jaccard similarity.

MinHash and LSH Quiz

Question 1 of 80 of 8 completed
Given sets A = {1, 2, 3, 4} and B = {3, 4, 5, 6}, what is the Jaccard similarity J(A, B)?

Comments

No comments yet. Be the first to share your thoughts!

Reference

Citation details

Cite or share this article.

BIBTEXAcademic
@misc{brenndoerfer2026minhashjaccard, author = {Michael Brenndoerfer}, title = {MinHash: Jaccard Similarity, LSH, Near-Duplicate Detection}, year = {2026}, url = {https://mbrenndoerfer.com/writing/minhash-algorithm-jaccard-similarity-lsh-deduplication}, organization = {mbrenndoerfer.com}, note = {Accessed: 2026-09-30} }
APAAcademic
Michael Brenndoerfer (2026). MinHash: Jaccard Similarity, LSH, Near-Duplicate Detection. Retrieved from https://mbrenndoerfer.com/writing/minhash-algorithm-jaccard-similarity-lsh-deduplication
MLAAcademic
Michael Brenndoerfer. "MinHash: Jaccard Similarity, LSH, Near-Duplicate Detection." 2026. Web. September 30, 2026. <https://mbrenndoerfer.com/writing/minhash-algorithm-jaccard-similarity-lsh-deduplication>.
CHICAGOAcademic
Michael Brenndoerfer. "MinHash: Jaccard Similarity, LSH, Near-Duplicate Detection." Accessed September 30, 2026. https://mbrenndoerfer.com/writing/minhash-algorithm-jaccard-similarity-lsh-deduplication.
HARVARDAcademic
Michael Brenndoerfer (2026) 'MinHash: Jaccard Similarity, LSH, Near-Duplicate Detection'. Available at: https://mbrenndoerfer.com/writing/minhash-algorithm-jaccard-similarity-lsh-deduplication (Accessed: September 30, 2026).
SimpleBasic
Michael Brenndoerfer (2026). MinHash: Jaccard Similarity, LSH, Near-Duplicate Detection. https://mbrenndoerfer.com/writing/minhash-algorithm-jaccard-similarity-lsh-deduplication

About the author

Continue with the full handbook

This chapter is part of Language AI Handbook. Use the handbook page to browse the complete table of contents and continue reading in sequence.

Explore Language AI Handbook
Newsletter

Stay up to date

Get articles, book updates, and news delivered to your inbox.

No spam, unsubscribe anytime.

or

Join the community

Sign in to remove popups, track your reading progress, and join the discussion.