SemHash

repository·main·Indexed 21 days ago

https://github.com/minishlab/semhash

A lightweight, multimodal library for fast semantic deduplication, outlier filtering, and representative sample selection. It supports text via Model2Vec and other modalities like images through custom encoders or sentence-transformers, utilizing efficient similarity search with USearch as the default ANN backend.

Tokens
12.9K
Snippets
45
Records
48
Agent score
75%

What's inside semhash

  1. Implement the Encoder protocol for custom modalities

    main

    SemHash allows you to use any modality (like images) by providing a custom encoder that implements the Encoder protocol. An encoder must have an encode(inputs, **kwargs) method that accepts a batch of inputs and returns a numpy array of embeddings.

    Example implementation for images using timm:

    class VisionEncoder:
        """Custom encoder using timm models. Implements the Encoder protocol."""
    
        def __init__(self, model_name: str = "mobilenetv3_small_100.lamb_in1k"):
            self.model = timm.create_model(model_name, pretrained=True, num_classes=0).eval()
            data_config = timm.data.resolve_model_data_config(self.model)
            self.transform = timm.data.create_transform(**data_config, is_training=False)
    
        def encode(self, inputs, batch_size: int = 128):
            """Encode a batch of PIL images into embeddings."""
            import numpy as np
    
            # Convert grayscale to RGB if needed
            rgb_inputs = [img.convert("RGB") if img.mode != "RGB" else img for img in inputs]
    
            # Process in batches to avoid memory issues
            all_embeddings = []
            with torch.no_grad():
                for i in range(0, len(rgb_inputs), batch_size):
                    batch_inputs = rgb_inputs[i : i + batch_size]
                    batch = torch.stack([self.transform(img) for img in batch_inputs])
                    embeddings = self.model(batch).numpy()
                    all_embeddings.append(embeddings)
    
            return np.vstack(all_embeddings)
  2. Perform cross-dataset deduplication and filtering

    main

    Use SemHash to deduplicate a second dataset (e.g., test_texts) against an existing reference dataset (e.g., train_texts). This is useful for eliminating train/test leakage.

    Unlike the self_ methods, these require passing the target records as an argument:

    • deduplicate(records=..., threshold=...): Removes records from the target dataset that are too similar to the reference dataset.
    • filter_outliers(records=..., outlier_percentage=...): Identifies outliers in the target dataset relative to the reference.
    • find_representative(records=..., selection_size=...): Finds representative samples in the target dataset relative to the reference.
    from datasets import load_dataset
    from semhash import SemHash
    
    # Load two datasets to deduplicate
    train_texts = load_dataset("ag_news", split="train")["text"]
    test_texts = load_dataset("ag_news", split="test")["text"]
    
    # Initialize a SemHash instance with the training data
    semhash = SemHash.from_records(records=train_texts)
    
    # Deduplicate the test data against the training data, optionally with a specific threshold
    deduplicated_test_texts = semhash.deduplicate(records=test_texts, threshold=0.9).selected
    
    # Filter outliers from the test data against the training data, optionally with a specific percentage
    filtered_test_texts = semhash.filter_outliers(records=test_texts, outlier_percentage=0.1).selected
    
    # Find representative texts in the test data against the training data, optionally with a specific selection size
    representative_test_texts = semhash.find_representative(records=test_texts, selection_size=10).selected
  3. Deduplicate and filter multi-column datasets

    main

    When working with datasets containing multiple fields (e.g., a dictionary or a row from a HuggingFace dataset), you can specify which columns should be used for the semantic hashing by passing a list of strings to the columns parameter in SemHash.from_records.

    from datasets import load_dataset
    from semhash import SemHash
    
    # Load the dataset
    dataset = load_dataset("squad_v2", split="train")
    
    # Convert the dataset to a list of dictionaries
    records = [dict(row) for row in dataset]
    
    # Initialize SemHash with the columns to deduplicate
    semhash = SemHash.from_records(records=records, columns=["question", "context"])
    
    # Deduplicate the records
    deduplicated_records = semhash.self_deduplicate().selected
    
    # Filter outliers from the records
    filtered_records = semhash.self_filter_outliers().selected
    
    # Find representative samples in the records
    representative_records = semhash.self_find_representative().selected
  4. Deduplicate, filter outliers, and find representative samples across two datasets

    main

    To compare a new set of records against an existing SemHash instance (e.g., deduplicating a test set against a training set), use the non-self_ prefixed methods. These methods take the new records as an argument and return the results for those specific records.

    Common operations include:

    • deduplicate(records=...): Deduplicates the provided records against the records used to initialize the SemHash instance.
    • filter_outliers(records=...): Filters outliers from the provided records based on the existing instance.
    • find_representative(records=...): Finds representative samples within the provided records.
    from datasets import load_dataset
    from semhash import SemHash
    
    # Load two datasets to deduplicate
    train_texts = load_dataset("ag_news", split="train")["text"]
    test_texts = load_dataset("ag_news", split="test")["text"]
    
    # Initialize a SemHash instance with the training data
    semhash = SemHash.from_records(records=train_texts)
    
    # Deduplicate the test data against the training data
    deduplicated_test_texts = semhash.deduplicate(records=test_texts).selected
    
    # Filter outliers from the test data
    filtered_test_texts = semhash.filter_outliers(records=test_texts).selected
    
    # Find representative texts in the test data
    representative_test_texts = semhash.find_representative(records=test_texts).selected
  5. Deduplicate, filter, and sample image datasets

    main

    To process images, you must provide a vision model (e.g., from sentence-transformers).

    1. Load a vision model (e.g., clip-ViT-B-32).
    2. Initialize SemHash using from_records, specifying the column containing the images and passing the model.
    3. Use self_deduplicate(), self_filter_outliers(), or self_find_representative() to process the images.

    Note: This requires pip install sentence-transformers.

    from datasets import load_dataset
    from sentence_transformers import SentenceTransformer
    from semhash import SemHash
    
    # Load an image dataset and vision model
    model = SentenceTransformer('clip-ViT-B-32')
    dataset = load_dataset("uoft-cs/cifar10", split="test")
    
    # Initialize a SemHash instance with the 'img' column
    semhash = SemHash.from_records(list(dataset), columns=["img"], model=model)
    
    # Deduplicate the images
    deduplicated_images = semhash.self_deduplicate().selected
    
    # Filter outliers
    filtered_images = semhash.self_filter_outliers().selected
    
    # Find representative images
    representative_images = semhash.self_find_representative().selected
  6. Run SemHash benchmarks

    main

    You can run performance benchmarks for SemHash using the provided make commands to evaluate text and image deduplication speeds. SemHash is designed for high performance, capable of deduplicating 1.8M text records in approximately 83 seconds on a CPU.

    # Run text benchmarks
    make benchmark-text
    
    # Run image benchmarks
    make benchmark-image
    
    # Run all benchmarks
    make benchmark
  7. Run Text Benchmarks

    main

    To execute the SemHash text deduplication benchmarks, ensure you have the datasets package installed. You can run the benchmarks using the module command or via make.

    Configuration used in benchmarks:

    • CPU-only: No GPU acceleration.
    • ANN backend: USearch (default).
    • Encoder: potion-base-8M.
    • Timing: Includes encoding, index building, and deduplication time.
    # Install dependencies
    pip install datasets
    
    # Run benchmarks
    python -m benchmarks.run_text_benchmarks
    # Or using make
    make benchmark-text
  8. Deduplicate multi-column datasets

    main

    SemHash can deduplicate records based on multiple fields simultaneously (e.g., in a QA dataset where both the question and context must be considered).

    To do this, pass a list of dictionaries to SemHash.from_records and specify the relevant fields in the columns parameter.

    from datasets import load_dataset
    from semhash import SemHash
    
    # Load the dataset
    dataset = load_dataset("squad_v2", split="train")
    
    # Convert the dataset to a list of dictionaries
    records = [dict(row) for row in dataset]
    
    # Initialize SemHash with the columns to deduplicate
    semhash = SemHash.from_records(records=records, columns=["question", "context"])
    
    # Deduplicate the records
    deduplicated_records = semhash.self_deduplicate().selected
  9. Deduplicate, filter, and sample text datasets

    main

    SemHash provides high-level methods for single-dataset operations on text. Text processing works out of the box using fast Model2Vec embeddings.

    To perform these operations on a single dataset, use the self_ prefixed methods:

    • self_deduplicate(): Removes semantic duplicates from the dataset.
    • self_filter_outliers(): Identifies and removes outliers.
    • self_find_representative(): Selects representative samples from the dataset.

    Note: These examples assume you have the datasets library installed (pip install datasets).

    from datasets import load_dataset
    from semhash import SemHash
    
    # Load a dataset to deduplicate
    texts = load_dataset("ag_news", split="train")["text"]
    
    # Initialize a SemHash instance
    semhash = SemHash.from_records(records=texts)
    
    # Deduplicate the texts
    deduplicated_texts = semhash.self_deduplicate().selected
    
    # Filter outliers
    filtered_texts = semhash.self_filter_outliers().selected
    
    # Find representative texts
    representative_texts = semhash.self_find_representative().selected
  10. Run Image Benchmarks

    main

    To execute the SemHash image deduplication benchmarks, ensure you have timm, torch, and datasets installed. You can run the benchmarks using the module command or via make.

    Configuration used in benchmarks:

    • Device: Apple Silicon GPU (MPS).
    • ANN backend: USearch (default).
    • Encoder: mobilenetv3_small_100.lamb_in1k.
    • Batch size: 128 images per batch.
    • Timing: Includes encoding, index building, and deduplication time.

    Customizing Datasets: You can customize the image datasets by editing benchmarks/data.py and modifying the IMAGE_DATASET_DICT constant.

    # Install dependencies
    pip install timm torch datasets
    
    # Run benchmarks
    python -m benchmarks.run_image_benchmarks
    # Or using make
    make benchmark-image
  11. Deduplicate, filter outliers, and find representative samples on a single dataset

    main

    To perform operations on a single dataset, use the self_* methods on a SemHash instance initialized via SemHash.from_records. These methods return an object where you can access the processed results using the .selected attribute.

    Common operations include:

    • self_deduplicate(): Removes duplicate entries within the dataset.
    • self_filter_outliers(): Removes outlier entries from the dataset.
    • self_find_representative(): Identifies and selects representative samples from the dataset.
    from datasets import load_dataset
    from semhash import SemHash
    
    # Load a dataset to deduplicate
    texts = load_dataset("ag_news", split="train")["text"]
    
    # Initialize a SemHash instance
    semhash = SemHash.from_records(records=texts)
    
    # Deduplicate the texts
    duplicated_texts = semhash.self_deduplicate().selected
    
    # Filter outliers
    filtered_texts = semhash.self_filter_outliers().selected
    
    # Find representative texts
    representative_texts = semhash.self_find_representative().selected