datasketch

repository·master·Indexed 25 days ago

https://github.com/ekzhu/datasketch

A library providing probabilistic data structures and efficient indexes for processing and searching very large datasets. It includes implementations of MinHash, HyperLogLog, and HyperLogLog++, as well as indexing tools like Locality Sensitive Hashing (LSH) and Hierarchical Navigable Small World (HNSW) to estimate similarity and cardinality with high speed and low memory overhead.

Tokens
19.3K
Snippets
41
Records
126
Agent score
84%

What's inside datasketch

  1. Overview of datasketch data sketches and indexes

    master

    datasketch provides probabilistic data structures (sketches) for processing large datasets and indexes for sub-linear query times.

    Data Sketches

    • MinHash: Estimate Jaccard similarity and cardinality.
    • Weighted MinHash: Estimate weighted Jaccard similarity.
    • HyperLogLog: Estimate cardinality.
    • HyperLogLog++: Estimate cardinality.

    Indexes

    • MinHash LSH: For MinHash and Weighted MinHash. Supports Jaccard Threshold queries.
    • LSHBloom: For MinHash and Weighted MinHash. Supports Jaccard Threshold queries.
    • MinHash LSH Forest: For MinHash and Weighted MinHash. Supports Jaccard Top-K queries.
    • MinHash LSH Ensemble: For MinHash. Supports Containment Threshold queries.
    • HNSW: For any sketch. Supports Custom Metric Top-K queries.
  2. Improve MinHashLSHForest accuracy via post-processing

    master

    To improve accuracy, use a technique called "post-processing":

    1. Query the forest with a multiple of your desired k (e.g., 2*k). This widens the net to catch true neighbors that a tighter query might miss.
    2. Retrieve the actual sets (or their MinHashes) associated with the returned keys.
    3. Compute the exact (or approximate) Jaccard similarity between the query set and the retrieved candidates.
    4. Re-rank the candidates and take the top k.
  3. Use MinHashLSHEnsemble for containment search

    master

    Use datasketch.MinHashLSHEnsemble to find sets in a collection that have a high degree of containment relative to a query set. Unlike Jaccard similarity, which can be biased by large set sizes, containment measures the fraction of the query set $Q$ contained in a set $X$ ($Containment = \frac{|Q \cap X|}{|Q|}$).

    To use the ensemble:

    1. Create MinHash objects for your sets.
    2. Initialize MinHashLSHEnsemble with a threshold, num_perm, and num_part.
    3. Index your data using an iterable of (key, minhash, size) tuples.
    4. Query the index using a MinHash object and the size of the query set.
    from datasketch import MinHashLSHEnsemble, MinHash
    
    set1 = set(["cat", "dog", "fish", "cow"])
    set2 = set(["cat", "dog", "fish", "cow", "pig", "elephant", "lion", "tiger",
                 "wolf", "bird", "human"])
    set3 = set(["cat", "dog", "car", "van", "train", "plane", "ship", "submarine",
                 "rocket", "bike", "scooter", "motorcyle", "SUV", "jet", "horse"])
    
    # Create MinHash objects
    m1 = MinHash(num_perm=128)
    m2 = MinHash(num_perm=128)
    m3 = MinHash(num_perm=128)
    for d in set1:
        m1.update(d.encode('utf8'))
    for d in set2:
        m2.update(d.encode('utf8'))
    for d in set3:
        m3.update(d.encode('utf8'))
    
    # Create an LSH Ensemble index with threshold and number of partition settings.
    lshensemble = MinHashLSHEnsemble(threshold=0.8, num_perm=128, 
        num_part=32)
    
    # Index takes an iterable of (key, minhash, size)
    lshensemble.index([("m2", m2, len(set2)), ("m3", m3, len(set3))])
    
    # Check for membership using the key
    print("m2" in lshensemble)
    print("m3" in lshensemble)
    
    # Using m1 as the query, get an result iterator 
    print("Sets with containment > 0.8:")
    for key in lshensemble.query(m1, len(set1)):
        print(key)
  4. Generate Weighted MinHash from vectors

    master

    To estimate weighted Jaccard similarity for multisets or vectors, use WeightedMinHashGenerator. You must provide the dimension of the vectors as the first argument. You can optionally specify sample_size (the number of samples) and a seed for reproducibility. Increasing sample_size improves accuracy but decreases performance.

    from datasketch import WeightedMinHashGenerator
    
    # Initialize with dimension 1000, default sample_size 256 and seed 1
    wmg = WeightedMinHashGenerator(1000)
    
    # Initialize with custom sample_size and seed
    wmg = WeightedMinHashGenerator(1000, sample_size=512, seed=12)
  5. Use insertion sessions for bulk MinHash insertion

    master

    When inserting a large number of MinHashes, use lsh.insertion_session() as a context manager. This reduces network overhead by batching calls.

    Warning: Querying the LSH object while an insertion session is open may result in inconsistent results.

    data_list = [("m1", m1), ("m2", m2), ("m3", m3)]
    
    with lsh.insertion_session() as session:
       for key, minhash in data_list:
          session.insert(key, minhash)
  6. Estimate cardinality with HyperLogLog

    master

    Use the HyperLogLog class to estimate the number of distinct values in a dataset using a single pass and fixed memory. You can update the sketch with encoded data and retrieve the estimate using .count().

    Note that you can control accuracy by adjusting the parameter p. A higher p provides better accuracy but increases memory usage exponentially. There is no speed penalty for using a higher p value.

    from datasketch import HyperLogLog
    
    data1 = ['hyperloglog', 'is', 'a', 'probabilistic', 'data', 'structure', 'for',
    'estimating', 'the', 'cardinality', 'of', 'dataset', 'dataset', 'a']
    
    h = HyperLogLog(p=12)  # p=12 provides better accuracy than default p=8
    for d in data1:
      h.update(d.encode('utf8'))
    print("Estimated cardinality is", h.count())
  7. Migrate MinHash sketches from versions before 2.0.0

    master

    Version 2.0.0 introduced new default hash values. To maintain compatibility with sketches or LSH indexes created in version 1.x:

    1. Loading existing objects: Pickled MinHash, LeanMinHash, and bBitMinHash objects from 1.x will load correctly and are automatically marked with scheme="legacy".
    2. Manual construction: If constructing a sketch from raw values (e.g., MinHash(hashvalues=...)), you must explicitly pass scheme="legacy" to avoid silent mislabeling.
    3. LSH Indexes: LSH indexes (like MinHashLSH) learn the scheme from the first inserted MinHash. If you are using an external storage like Redis, you may need to rebuild the index to ensure it uses the new default scheme.
    4. Recommended approach: To fully adopt the new 2.0.0 defaults, recompute sketches from the original data and rebuild any persisted LSH indexes.
  8. Use MinHashLSHBloom for duplicate detection

    master

    LSHBloom is a space-efficient alternative to MinHashLSH. While MinHashLSH returns candidate duplicates, MinHashLSHBloom returns a binary signal (0 or 1) indicating whether a query set has a Jaccard similarity $\ge T$ with any other set in the collection.

    To use it, you must provide an estimate of the dataset size (n) and the acceptable false positive overhead per Bloom filter (fp) during initialization.

    from datasketch import MinHash, MinHashLSHBloom
    
    set1 = set(['minhash', 'is', 'a', 'probabilistic', 'data', 'structure', 'for',
                'estimating', 'the', 'similarity', 'between', 'datasets'])
    set2 = set(['minhash', 'is', 'a', 'probability', 'data', 'structure', 'for',
                'estimating', 'the', 'similarity', 'between', 'documents'])
    set3 = set(['minhash', 'is', 'probability', 'data', 'structure', 'for',
                'estimating', 'the', 'similarity', 'between', 'documents'])
    
    m1 = MinHash(num_perm=128)
    m2 = MinHash(num_perm=128)
    m3 = MinHash(num_perm=128)
    for d in set1:
        m1.update(d.encode('utf8'))
    for d in set2:
        m2.update(d.encode('utf8'))
    for d in set3:
        m3.update(d.encode('utf8'))
    
    # Create LSHBloom index
    # threshold: Jaccard similarity threshold
    # num_perm: number of permutations
    # n: estimated number of sets in the dataset
    # fp: allowable false positive rate per Bloom filter
    lsh = MinHashLSHBloom(threshold=0.5, num_perm=128, n=100, fp=0.001)
    lsh.insert("m2", m2)
    lsh.insert("m3", m3)
    
    is_duplicate = lsh.query(m1)
    print("Is Duplicate: ", is_duplicate)
  9. Enable GPU acceleration for MinHash.update_batch

    master

    You can optionally run part of MinHash.update_batch on a CUDA GPU using CuPy. Note that hashing and permutation generation still occur on the CPU; only permutation application and min-reduction are offloaded to the GPU.

    Configuration: Use the gpu_mode argument in the MinHash constructor:

    • 'disable' (default): Always use CPU.
    • 'detect': Use GPU if available, otherwise fallback to CPU.
    • 'always': Require GPU; raises RuntimeError if no CUDA device is available.

    Installation: CuPy is an optional dependency. Install the version matching your CUDA version (e.g., pip install cupy-cuda12x).

    Tip: If you are in a cloud environment with an NVIDIA driver but no local CUDA Toolkit, install with the CTK extra to avoid RuntimeError: Failed to find CUDA headers: pip install "cupy-cuda12x[ctk]".

    # Force CPU only
    m = MinHash(num_perm=256, gpu_mode="disable")
    m.update_batch(data)
    
    # Require GPU (raises RuntimeError if no CUDA device)
    m = MinHash(num_perm=256, gpu_mode="always")
    m.update_batch(data)