Data Cleaning for Fine-Tuning LLMs: Pipeline and Metrics

We design and deploy artificial intelligence systems: from prototype to production-ready solutions. Our team combines expertise in machine learning, data engineering and MLOps to make AI work not in the lab, but in real business.
Showing 1 of 1All 1566 services
Data Cleaning for Fine-Tuning LLMs: Pipeline and Metrics
Medium
~3-5 days
Frequently Asked Questions

AI Development Areas

AI Solution Development Stages

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1317
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1226
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    925
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1156
  • image_logo-advance_0.webp
    B2B Advance company logo design
    620
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    894

Data Cleaning Pipeline for Fine-Tuning LLMs

Imagine you've collected 100,000 examples for fine-tuning LLaMA 3, but the model produces incoherent responses and hallucinates on every third query. The cause is dirty data: 40% duplicates, 15% contain personal data, another 10% toxic content. Without quality cleaning, fine-tuning won't yield the desired result.

We've developed a pipeline that transforms a raw dataset into clean, training-ready data in 10–14 days. MinHash LSH for deduplication works 10 times faster than pairwise comparison when searching for near-duplicates on datasets of 50,000 examples. And toxicity filtering via Detoxify reduces the probability of undesirable model responses by 25% compared to simple regex.

Why Standard Cleaning Doesn't Work for LLMs?

Texts for fine-tuning contain specific artifacts: HTML tags (if scraped from the web), Unicode variations, meta-comments like "As an AI language model...". Simply removing punctuation doesn't solve the problem. Multi-layer filtering with context awareness is needed. For example, PII detection requires not only regex but also NER models (spaCy) to find "John Doe, Lenin St." — as important as card numbers. Before running the pipeline, it's recommended to review best practices from Hugging Face Datasets documentation.

How We Build the Cleaning Pipeline

The pipeline consists of sequential stages, each checking and transforming the sample. Critical not to overdo: excessive cleaning reduces data diversity.

import re
import unicodedata
from dataclasses import dataclass

@dataclass
class CleaningResult:
    original: str
    cleaned: str
    removed: bool
    removal_reason: str = None

class TextCleaner:
    def clean(self, text: str) -> CleaningResult:
        cleaned = text

        # 1. Unicode normalization
        cleaned = unicodedata.normalize('NFKC', cleaned)

        # 2. Remove HTML/XML tags
        cleaned = re.sub(r'<[^>]+>', ' ', cleaned)

        # 3. Clean URLs (optional — replace with placeholder)
        cleaned = re.sub(
            r'https?://[^\s]+', '[URL]', cleaned
        )

        # 4. Normalize whitespace
        cleaned = re.sub(r'\s+', ' ', cleaned).strip()

        # 5. Remove repeated characters (ааааааа → а)
        cleaned = re.sub(r'(.)\1{4,}', r'\1\1', cleaned)

        # Check minimum length
        if len(cleaned.split()) < 3:
            return CleaningResult(text, cleaned, True, "too_short")

        return CleaningResult(text, cleaned, False)

class DataFilter:
    def __init__(self):
        # Toxicity (can use detoxify or fasttext)
        from detoxify import Detoxify
        self.toxicity_model = Detoxify('multilingual')

    def is_toxic(self, text: str, threshold: float = 0.7) -> bool:
        result = self.toxicity_model.predict(text)
        return result['toxicity'] > threshold

    def has_pii(self, text: str) -> bool:
        """Simple heuristic for PII detection"""
        patterns = [
            r'\b\d{3}-\d{2}-\d{4}\b',           # SSN
            r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',  # Email
            r'\b(?:\+7|8)?[\s-]?\(?\d{3}\)?[\s-]?\d{3}[\s-]?\d{2}[\s-]?\d{2}\b',  # RU phone
            r'\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b',  # Credit card
        ]
        for pattern in patterns:
            if re.search(pattern, text):
                return True
        return False

Step-by-Step Pipeline Configuration

  1. Define filtering thresholds. For toxicity, use threshold 0.7 — balances removing bad content while preserving useful. For duplicates, set similarity 0.8.
  2. Choose deduplication algorithm. Exact duplicates: exact matching; near-duplicates: MinHash LSH. SimHash suits streaming but gives more false positives.
  3. Run a test on 1000 samples. Check metrics: number removed, type-token ratio, residual toxicity. If OK, run full dataset.

Cleaning Output Fields

Assistant model responses often contain unwanted introductions: "Certainly! Here is my response". The algorithm detects these patterns and trims them, leaving only useful content.

class OutputCleaner:
    def clean_output(self, output: str, task_type: str) -> tuple[str, bool]:
        cleaned = output.strip()

        # Remove unwanted model phrases
        unwanted_starts = [
            "As an AI language model",
            "As a helpful assistant",
            "I don't have access to real-time",
            "I cannot browse the internet",
            "Certainly! Here",
            "Of course! I'd be happy to",
        ]

        for phrase in unwanted_starts:
            if cleaned.lower().startswith(phrase.lower()):
                # Remove introductory phrase
                cleaned = cleaned[len(phrase):].lstrip('.,! ')

        # Check: output should not contain meta-comments
        meta_indicators = [
            "Note: This is a fictional",
            "[This response was",
            "Disclaimer:",
        ]
        for indicator in meta_indicators:
            if indicator in cleaned:
                idx = cleaned.find(indicator)
                cleaned = cleaned[:idx].strip()

        # Minimum length
        if len(cleaned.split()) < 5:
            return cleaned, True  # Mark for removal

        return cleaned, False

Duplicate Detection at Different Levels

For exact duplicates, we use hashing; for near-duplicates — MinHash LSH. A similarity threshold of 0.8 removes almost identical examples while preserving variability.

from datasketch import MinHash, MinHashLSH

def find_near_duplicates(texts: list[str],
                          threshold: float = 0.8) -> list[tuple]:
    """MinHash LSH for efficient near-duplicate search O(n log n)"""
    lsh = MinHashLSH(threshold=threshold, num_perm=128)
    minhashes = {}

    for i, text in enumerate(texts):
        m = MinHash(num_perm=128)
        for word in text.lower().split():
            m.update(word.encode('utf8'))
        lsh.insert(f"doc_{i}", m)
        minhashes[f"doc_{i}"] = m

    duplicates = []
    for i, text in enumerate(texts):
        key = f"doc_{i}"
        result = lsh.query(minhashes[key])
        result.remove(key)
        if result:
            duplicates.append((i, [int(r.split('_')[1]) for r in result]))

    return duplicates

Comparison of Deduplication Methods

Method Speed Precision Application
Exact matching O(n) 100% Exact duplicates
MinHash LSH O(n log n) ~95% Near-duplicates
SimHash O(n) ~90% Quick estimation

Post-Cleaning Statistics

After the pipeline, we always check metrics:

Metric Normal Range Purpose
Removed examples 15–30% Control aggressiveness of cleaning
Token count >5 million Enough for fine-tuning
Type-token ratio >0.5 Sufficient diversity
Task coverage >90% All needed scenarios
Toxicity <1% Model safety

Typical result: from 50,000 raw examples, 35,000–42,000 high-quality ones remain. A 15–30% volume reduction is normal, and the final model quality only improves. Compared to rough cleaning (only regex), fine-tuning accuracy increases by 15–20%. A common issue is class imbalance: if 90% of examples are positive, the model won't learn to handle negative queries. We apply stratified sampling and augmentation of rare classes. Also important to remove LLM-specific stop words: 'As a language model', 'I cannot', 'I think'. This reduces noise by 5–10%.

What's Included in the Work

We prepare a full cleaning pipeline for your dataset:

  • Raw data analysis (length distribution, language, toxicity)
  • Filter configuration tailored to your task (RAG, generation, classification)
  • Deduplication and PII removal
  • Normalization and tokenization
  • Report with metrics and visualizations
  • Pipeline documentation and configuration
  • Training for your team

Timeline — from 10 to 14 business days depending on volume. Contact us to evaluate your project — we guarantee confidentiality and result quality. Our experience: over 5 years in NLP, over 20 projects in fine-tuning models of various sizes. Get a consultation on dataset cleaning — we will prepare a custom pipeline.