Cleanlab Tutorial: Improving Data Quality for Machine Learning

# Tutorial Cleanlab: Meningkatkan Kualitas Data untuk Machine Learning Kualitas data adalah fondasi dari setiap proyek machine learning yang sukses. Tidak peduli seberapa canggih model yang Anda guna...

By Ruby Abdullah · · tutorial
CleanlabData QualityMachine LearningConfident LearningPython

Cleanlab Tutorial: Improving Data Quality for Machine Learning

Data quality is the foundation of every successful machine learning project. No matter how sophisticated your model is, if the training data contains mislabeled examples or excessive noise, model performance will degrade significantly. Cleanlab is a powerful open-source solution that automatically detects and fixes data quality issues.

In this tutorial, we will learn how to use Cleanlab to find label errors, identify outliers, and build cleaner datasets so your machine learning models can achieve optimal performance.

What Is Cleanlab?

Cleanlab is an open-source Python library that implements the Confident Learning algorithm to detect issues in datasets. The library can find:

  • Label errors: Data with incorrect labels in the training dataset
  • Outliers: Unusual or anomalous data points
  • Near-duplicates: Nearly identical data that could cause data leakage
  • Class overlap: Areas where classes overlap with each other

Cleanlab works with any machine learning model — scikit-learn, PyTorch, TensorFlow, XGBoost, and more. You simply provide the predicted probabilities from your model, and Cleanlab will analyze the results.

Installation

Basic Installation

pip install cleanlab

Installation with Additional Dependencies

pip install "cleanlab[datalab]"

Development Installation

pip install "cleanlab[all]"

Verify Installation

import cleanlab

print(cleanlab.version)

Environment Setup

Ensure you have the following dependencies:

pip install scikit-learn pandas numpy matplotlib seaborn

Basic Usage: Finding Label Errors

Example 1: Simple Classification Dataset

Let's start with a simple example using the Iris dataset:

import numpy as np

import pandas as pd

from sklearn.datasets import loadiris

from sklearn.modelselection import crossvalpredict

from sklearn.linearmodel import LogisticRegression

from cleanlab.filter import findlabelissues

iris = loadiris()

X = iris.data

y = iris.target

np.random.seed(42)

noisyindices = np.random.choice(len(y), size=15, replace=False)

ynoisy = y.copy()

for idx in noisyindices:

possiblelabels = [l for l in range(3) if l != y[idx]]

ynoisy[idx] = np.random.choice(possiblelabels)

print(f"Number of intentionally corrupted labels: {len(noisyindices)}")

print(f"Corrupted indices: {sorted(noisyindices)}")

Getting Predicted Probabilities

Cleanlab requires out-of-sample predicted probabilities. The best approach is using cross-validation:

model = LogisticRegression(maxiter=500, randomstate=42)

predprobs = crossvalpredict(

model, X, ynoisy,

cv=5,

method="predictproba"

)

print(f"predprobs shape: {predprobs.shape}")

print(f"Example probabilities for first sample: {predprobs[0]}")

Finding Label Issues

labelissues = findlabelissues(

labels=ynoisy,

predprobs=predprobs,

returnindicesrankedby="selfconfidence"

)

print(f"\nNumber of label issues found: {len(labelissues)}")

print(f"Detected indices: {sorted(labelissues)}")

detected = set(labelissues)

actual = set(noisyindices)

truepositives = detected & actual

precision = len(truepositives) / len(detected) if detected else 0

recall = len(truepositives) / len(actual) if actual else 0

print(f"\nPrecision: {precision:.2f}")

print(f"Recall: {recall:.2f}")

Example 2: Using the CleanLearning Wrapper

Cleanlab provides a CleanLearning wrapper that automatically detects label issues and trains a model on the cleaned data:

from cleanlab.classification import CleanLearning

cl = CleanLearning(

clf=LogisticRegression(maxiter=500, randomstate=42),

seed=42

)

cl.fit(X, ynoisy)

labelissuesdf = cl.getlabelissues()

print(labelissuesdf.head(10))

print(f"\nAvailable columns: {labelissuesdf.columns.tolist()}")

print(f"Number of label issues: {labelissuesdf['islabelissue'].sum()}")

Comparing Performance

from sklearn.modelselection import crossvalscore

baselinemodel = LogisticRegression(maxiter=500, randomstate=42)

baselinescores = crossvalscore(baselinemodel, X, ynoisy, cv=5)

print(f"Accuracy with noisy data: {baselinescores.mean():.4f} (+/- {baselinescores.std():.4f})")

cleanmask = ~labelissuesdf["islabelissue"].values

Xclean = X[cleanmask]

yclean = ynoisy[cleanmask]

cleanmodel = LogisticRegression(maxiter=500, randomstate=42)

cleanscores = crossvalscore(cleanmodel, Xclean, yclean, cv=5)

print(f"Accuracy with clean data: {cleanscores.mean():.4f} (+/- {cleanscores.std():.4f})")

Using Datalab for Comprehensive Analysis

Datalab is a more comprehensive Cleanlab feature that provides a thorough analysis of your dataset:
from cleanlab import Datalab

lab = Datalab(

data=pd.DataFrame(X, columns=iris.featurenames),

labelname="label",

task="classification"

)

lab.findissues(predprobs=predprobs, features=X)

lab.report()

Exploring Datalab Results

issuesummary = lab.getissuesummary()

print(issuesummary)

labelissues = lab.getissues("label")

print("\nTop 10 data points with lowest label issue score:")

print(labelissues.nsmallest(10, "labelscore"))

outlierissues = lab.getissues("outlier")

print("\nTop 10 outliers:")

print(outlierissues.nsmallest(10, "outlierscore"))

Finding Near-Duplicates

duplicateissues = lab.getissues("nearduplicate")

neardupes = duplicateissues[duplicateissues["isnearduplicateissue"]]

print(f"\nNumber of near-duplicates: {len(neardupes)}")

if len(neardupes) > 0:

for idx in neardupes.index[:5]:

neardupset = duplicateissues.loc[idx, "nearduplicatesets"]

score = duplicateissues.loc[idx, "nearduplicatescore"]

print(f" Index {idx} (score: {score:.4f}): near-duplicate with {neardupset}")

Advanced Usage: Text Datasets

Cleanlab is particularly useful for NLP datasets. Here is an example using a text classification scenario:

import pandas as pd

from sklearn.featureextraction.text import TfidfVectorizer

from sklearn.naivebayes import MultinomialNB

from sklearn.modelselection import crossvalpredict

from cleanlab import Datalab

texts = [

"Stock prices surged sharply today",

"The football team won the championship final",

"Special fried rice recipe for the family",

"Indonesia's economy grew by 5 percent",

"Basketball player set a new scoring record",

"How to make a delicious chocolate cake",

"Central bank raised interest rates",

"Tennis tournament starts next week",

"Tips for cooking tender beef rendang",

"Stock market experienced a correction",

"The badminton match was very exciting",

"Homemade pizza tutorial for beginners",

"Gold investment is increasingly popular",

"New football league season schedule",

"Fresh fruit smoothie recipe",

"Stock prices increased dramatically",

"Swimming athlete broke world record",

"How to cook traditional chicken soup",

"Rupiah strengthened against the dollar",

"Esports competition gaining popularity",

]

labels = [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1]

labels[3] = 1 # Intentionally wrong: business -> sports

labels[11] = 0 # Intentionally wrong: food -> business

labels[16] = 2 # Intentionally wrong: sports -> food

labelnames = {0: "business", 1: "sports", 2: "food"}

df = pd.DataFrame({"text": texts, "label": labels})

vectorizer = TfidfVectorizer(maxfeatures=100)

Xtfidf = vectorizer.fittransform(df["text"]).toarray()

model = MultinomialNB()

predprobs = crossvalpredict(model, Xtfidf, df["label"], cv=3, method="predictproba")

lab = Datalab(data=df, labelname="label", task="classification")

lab.findissues(predprobs=predprobs, features=Xtfidf)

lab.report()

issues = lab.getissues("label")

problematic = issues[issues["islabelissue"]].sortvalues("labelscore")

for idx in problematic.index:

text = df.loc[idx, "text"]

givenlabel = labelnames[df.loc[idx, "label"]]

predicted = labelnames[issues.loc[idx, "predictedlabel"]]

score = issues.loc[idx, "labelscore"]

print(f" [{idx}] '{text}'")

print(f" Label: {givenlabel} -> Predicted: {predicted} (score: {score:.4f})")

Advanced Usage: Integration with Deep Learning

Using Cleanlab with PyTorch

import torch

import torch.nn as nn

from torch.utils.data import DataLoader, TensorDataset

from sklearn.modelselection import StratifiedKFold

from cleanlab.filter import findlabelissues

import numpy as np

class SimpleClassifier(nn.Module):

def init(self, inputdim, numclasses):

super().init()

self.network = nn.Sequential(

nn.Linear(inputdim, 128),

nn.ReLU(),

nn.Dropout(0.3),

nn.Linear(128, 64),

nn.ReLU(),

nn.Dropout(0.3),

nn.Linear(64, numclasses)

)

def forward(self, x):

return self.network(x)

def getcrossvalpredprobs(X, y, nsplits=5, epochs=50):

"""Get out-of-sample predicted probabilities using cross-validation."""

skf = StratifiedKFold(nsplits=nsplits, shuffle=True, randomstate=42)

predprobs = np.zeros((len(y), len(np.unique(y))))

for fold, (trainidx, validx) in enumerate(skf.split(X, y)):

Xtrain = torch.FloatTensor(X[trainidx])

ytrain = torch.LongTensor(y[trainidx])

Xval = torch.FloatTensor(X[validx])

traindataset = TensorDataset(Xtrain, ytrain)

trainloader = DataLoader(traindataset, batchsize=32, shuffle=True)

model = SimpleClassifier(X.shape[1], len(np.unique(y)))

optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

criterion = nn.CrossEntropyLoss()

model.train()

for epoch in range(epochs):

for batchX, batchy in trainloader:

optimizer.zerograd()

outputs = model(batchX)

loss = criterion(outputs, batchy)

loss.backward()

optimizer.step()

model.eval()

with torch.nograd():

valoutputs = model(Xval)

valprobs = torch.softmax(valoutputs, dim=1).numpy()

predprobs[validx] = valprobs

print(f" Fold {fold+1}/{nsplits} complete")

return predprobs

from sklearn.datasets import makeclassification

X, y = makeclassification(

nsamples=1000, nfeatures=20, ninformative=15,

nclasses=5, nclustersperclass=1, randomstate=42

)

noiserate = 0.1

nnoisy = int(len(y) noiserate)

noisyidx = np.random.choice(len(y), size=nnoisy, replace=False)

ynoisy = y.copy()

for idx in noisyidx:

possible = [l for l in range(5) if l != y[idx]]

ynoisy[idx] = np.random.choice(possible)

print("Running cross-validation to get predicted probabilities...")

predprobs = getcrossvalpredprobs(X, ynoisy, nsplits=5, epochs=30)

issues = findlabelissues(

labels=ynoisy,

predprobs=predprobs,

returnindicesrankedby="selfconfidence"

)

print(f"\nFound {len(issues)} label issues")

print(f"Actual noisy labels: {len(noisyidx)}")

overlap = len(set(issues[:nnoisy]) & set(noisyidx))

print(f"True positives (in top-{nnoisy}): {overlap}")

print(f"Recall: {overlap/nnoisy:.2%}")

Using Cleanlab with XGBoost

import xgboost as xgb

from sklearn.modelselection import crossvalpredict

from cleanlab.classification import CleanLearning

from sklearn.datasets import loadbreastcancer

data = loadbreastcancer()

X, y = data.data, data.target

np.random.seed(42)

noiseidx = np.random.choice(len(y), size=30, replace=False)

ynoisy = y.copy()

ynoisy[noiseidx] = 1 - ynoisy[noiseidx]

xgbmodel = xgb.XGBClassifier(

nestimators=100,

maxdepth=3,

learningrate=0.1,

randomstate=42,

evalmetric="logloss"

)

cl = CleanLearning(clf=xgbmodel, seed=42)

cl.fit(X, ynoisy)

labelissues = cl.getlabelissues()

detected = labelissues[labelissues["islabelissue"]].index.tolist()

print(f"Detected {len(detected)} label issues")

print(f"Actually noisy: {len(noiseidx)}")

tp = len(set(detected) & set(noiseidx))

precision = tp / len(detected) if detected else 0

recall = tp / len(noiseidx)

print(f"Precision: {precision:.2%}")

print(f"Recall: {recall:.2%}")

Confident Learning: The Theory Behind Cleanlab

Cleanlab uses the Confident Learning approach introduced by Curtis Northcutt et al. Here are the key concepts:

Confident Joint

The Confident Joint is an estimate of the joint distribution between noisy labels and true labels:

from cleanlab.count import computeconfidentjoint, estimatelatent

confidentjoint = computeconfidentjoint(

labels=ynoisy,

predprobs=predprobs

)

print("Confident Joint Matrix:")

print(confidentjoint)

py, noisematrix, invnoisematrix = estimatelatent(

confidentjoint=confidentjoint,

labels=ynoisy

)

print("\nNoise Matrix (P(noisy label | true label)):")

print(np.round(noisematrix, 3))

print("\nInverse Noise Matrix (P(true label | noisy label)):")

print(np.round(invnoisematrix, 3))

Label Quality Scores

Each data point receives a quality score indicating how confident we are that the label is correct:

from cleanlab.rank import getlabelqualityscores

qualityscores = getlabelqualityscores(

labels=ynoisy,

predprobs=predprobs,

method="selfconfidence"

)

print(f"Mean quality score: {qualityscores.mean():.4f}")

print(f"Min quality score: {qualityscores.min():.4f}")

print(f"Max quality score: {qualityscores.max():.4f}")

import matplotlib.pyplot as plt

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

axes[0].hist(qualityscores, bins=50, edgecolor="black", alpha=0.7)

axes[0].setxlabel("Label Quality Score")

axes[0].setylabel("Frequency")

axes[0].settitle("Distribution of Label Quality Scores")

axes[0].axvline(x=0.5, color="red", linestyle="--", label="Threshold 0.5")

axes[0].legend()

cleanscores = qualityscores[~np.isin(np.arange(len(ynoisy)), noisyidx)]

noisyscores = qualityscores[noisyidx]

axes[1].hist(cleanscores, bins=30, alpha=0.6, label="Clean labels", color="green")

axes[1].hist(noisyscores, bins=30, alpha=0.6, label="Noisy labels", color="red")

axes[1].setxlabel("Label Quality Score")

axes[1].setylabel("Frequency")

axes[1].settitle("Quality Score: Clean vs Noisy Labels")

axes[1].legend()

plt.tightlayout()

plt.savefig("labelqualitydistribution.png", dpi=150)

plt.show()

Multi-Label Classification

Cleanlab also supports multi-label scenarios:

from cleanlab.multilabelclassification.filter import findlabelissues as findmultilabelissues

from sklearn.multiclass import OneVsRestClassifier

from sklearn.linearmodel import LogisticRegression

from sklearn.preprocessing import MultiLabelBinarizer

np.random.seed(42)

nsamples = 500

nfeatures = 20

nclasses = 4

X = np.random.randn(nsamples, nfeatures)

truelabels = []

for i in range(nsamples):

nlabels = np.random.randint(1, 3)

labels = sorted(np.random.choice(nclasses, size=nlabels, replace=False).tolist())

truelabels.append(labels)

mlb = MultiLabelBinarizer()

ybinary = mlb.fittransform(truelabels)

noiseindices = np.random.choice(nsamples, size=50, replace=False)

ynoisybinary = ybinary.copy()

for idx in noiseindices:

flipcol = np.random.randint(nclasses)

ynoisybinary[idx, flipcol] = 1 - ynoisybinary[idx, flipcol]

model = OneVsRestClassifier(LogisticRegression(maxiter=500, randomstate=42))

predprobsmultilabel = crossvalpredict(model, X, ynoisybinary, cv=5, method="predictproba")

issues = findmultilabelissues(

labels=list(mlb.inversetransform(ynoisybinary)),

predprobs=[predprobsmultilabel[:, i] for i in range(nclasses)],

)

print(f"Found {issues.sum()} label issues in multi-label setting")

Best Practices

1. Use Out-of-Sample Predictions

Always use cross-validation to get predicted probabilities. Never use predictions from a model trained on the entire dataset:

# CORRECT: Cross-validation

predprobs = crossvalpredict(model, X, y, cv=5, method="predictproba")

WRONG: In-sample predictions

model.fit(X, y)

predprobs = model.predictproba(X) # Overfit, results unreliable

2. Use a Sufficiently Good Model

The model used to produce predicted probabilities should be reasonably accurate. A model that is too weak will produce many false positives:

from sklearn.ensemble import GradientBoostingClassifier

model = GradientBoostingClassifier(

nestimators=200,

maxdepth=5,

learningrate=0.1,

randomstate=42

)

predprobs = crossvalpredict(model, X, y, cv=5, method="predictproba")

3. Manually Verify After Detection

Cleanlab provides a ranking, but the final decision is yours. Always manually verify findings, especially for the most suspicious data points:

from cleanlab.filter import findlabelissues

issues = findlabelissues(

labels=y, predprobs=predprobs,

returnindicesrankedby="selfconfidence"

)

print("Top 20 most suspicious data points:")

for rank, idx in enumerate(issues[:20], 1):

print(f" #{rank}: Index {idx}")

print(f" Current label: {y[idx]}")

print(f" Predicted probs: {predprobs[idx]}")

print(f" Suggested label: {np.argmax(predprobs[idx])}")

print()

4. Iterative Cleaning

The data cleaning process should be done iteratively:

def iterativecleaning(X, y, model, maxiterations=3):

"""Perform iterative data cleaning."""

currentX = X.copy()

currenty = y.copy()

totalremoved = 0

for iteration in range(maxiterations):

predprobs = crossvalpredict(

model, currentX, currenty, cv=5, method="predictproba"

)

issues = findlabelissues(

labels=currenty, predprobs=predprobs,

returnindicesrankedby="selfconfidence"

)

if len(issues) == 0:

print(f"Iteration {iteration+1}: No more label issues found")

break

print(f"Iteration {iteration+1}: Found {len(issues)} label issues")

totalremoved += len(issues)

mask = np.ones(len(currenty), dtype=bool)

mask[issues] = False

currentX = currentX[mask]

currenty = currenty[mask]

print(f"\nTotal data removed: {totalremoved}")

print(f"Final dataset size: {len(currenty)}")

return currentX, currenty

5. Choose the Right Filter Method

Cleanlab provides several filter methods. Choose according to your needs:

# Most conservative method (low false positives)

issuesprune = findlabelissues(

labels=y, predprobs=predprobs,

filterby="prunebyclass"

)

Most aggressive method (high recall)

issuesnoise = findlabelissues(

labels=y, predprobs=predprobs,

filterby="prunebynoiserate"

)

Balanced method (default)

issuesboth = findlabelissues(

labels=y, predprobs=predprobs,

filterby="both"

)

Confident learning method

issuescl = findlabelissues(

labels=y, predprobs=predprobs,

filterby="confidentlearning"

)

print(f"prunebyclass: {len(issuesprune)} issues")

print(f"prunebynoiserate: {len(issuesnoise)} issues")

print(f"both: {len(issuesboth)} issues")

print(f"confidentlearning: {len(issuescl)} issues")

Complete Pipeline: End-to-End Data Cleaning

Here is a complete pipeline you can use in real projects:

import numpy as np

import pandas as pd

from sklearn.modelselection import crossvalpredict, crossvalscore

from sklearn.ensemble import GradientBoostingClassifier

from cleanlab import Datalab

from cleanlab.classification import CleanLearning

def dataqualitypipeline(X, y, featurenames=None):

"""Complete pipeline for data quality analysis and improvement."""

if featurenames is None:

featurenames = [f"feature{i}" for i in range(X.shape[1])]

df = pd.DataFrame(X, columns=featurenames)

df["label"] = y

print("=" 60)

print("STEP 1: Baseline Performance")

print("=" 60)

model = GradientBoostingClassifier(nestimators=100, randomstate=42)

baselinescores = crossvalscore(model, X, y, cv=5, scoring="accuracy")

print(f"Baseline Accuracy: {baselinescores.mean():.4f} (+/- {baselinescores.std():.4f})")

print("\n" + "=" 60)

print("STEP 2: Data Quality Analysis with Datalab")

print("=" 60)

predprobs = crossvalpredict(model, X, y, cv=5, method="predictproba")

lab = Datalab(data=df, labelname="label", task="classification")

lab.findissues(predprobs=predprobs, features=X)

lab.report()

print("\n" + "=" 60)

print("STEP 3: Clean Data and Re-train")

print("=" 60)

cl = CleanLearning(

clf=GradientBoostingClassifier(nestimators=100, randomstate=42),

seed=42

)

cl.fit(X, y)

issuesdf = cl.getlabelissues()

nissues = issuesdf["islabelissue"].sum()

print(f"Label issues found: {nissues} ({nissues/len(y)100:.1f}%)")

cleanmask = ~issuesdf["islabelissue"].values

Xclean = X[cleanmask]

yclean = y[cleanmask]

cleanscores = crossvalscore(model, Xclean, yclean, cv=5, scoring="accuracy")

print(f"Clean Accuracy: {cleanscores.mean():.4f} (+/- {cleanscores.std():.4f})")

print(f"Improvement: {(cleanscores.mean() - baselinescores.mean())100:.2f}%")

return {

"baselineaccuracy": baselinescores.mean(),

"cleanaccuracy": cleanscores.mean(),

"nissues": nissues,

"issuesdf": issuesdf,

"Xclean": Xclean,

"yclean": yclean

}

from sklearn.datasets import loaddigits

digits = loaddigits()

X, y = digits.data, digits.target

np.random.seed(42)

nnoisy = int(0.05 len(y))

noisyidx = np.random.choice(len(y), size=nnoisy, replace=False)

ynoisy = y.copy()

for idx in noisyidx:

ynoisy[idx] = np.random.choice([l for l in range(10) if l != y[idx]])

results = dataqualitypipeline(X, ynoisy, featurenames=[f"pixel_{i}" for i in range(64)])

Conclusion

Cleanlab is an invaluable tool in the modern data scientist's toolkit. By implementing Confident Learning, Cleanlab enables you to:

  • Detect label errors automatically without requiring ground truth
  • Identify outliers and anomalous data points
  • Find near-duplicates that could cause data leakage
  • Improve model performance by cleaning the training dataset
  • Keys to successfully using Cleanlab:

    • Always use out-of-sample predictions (cross-validation)
    • Use a sufficiently good model to generate predicted probabilities
    • Manually verify findings, especially for ambiguous cases
    • Perform cleaning iteratively for optimal results

    With cleaner data, your machine learning models will be more accurate, more robust, and more reliable in production. Remember: garbage in, garbage out — and Cleanlab helps ensure that what goes into your model is not garbage.

    Related Articles

    Complete Comet ML Tutorial: MLOps Platform for Experiment Tracking and Model Management

    Tutorial Lengkap Comet ML: Platform MLOps untuk Experiment Tracking dan Model Management Dalam dunia machine learning mo...

    MLX Tutorial: Apple's Machine Learning Framework for Apple Silicon

    Tutorial MLX: Framework Machine Learning Apple untuk Apple Silicon MLX adalah framework machine learning open-source dar...

    SHAP Tutorial: Explainable AI and Model Interpretability

    SHAP - Panduan Praktis Explainable AI dan Interpretabilitas Model Model machine learning makin sering dipakai untuk meng...

    PyOD Tutorial: Anomaly and Outlier Detection in Python

    Deteksi Anomali di Python dengan PyOD: Panduan Praktis Sebagian besar dataset di dunia nyata mengandung sebagian kecil d...