Skip to content
ExponaBLOG
PRACTICAL AI & MACHINE LEARNING · DAY 074 / 100

Labeling data efficiently: practical tips and trade-offs

Good labels are worth more than model tweaks.

Expona Admin8 min read

We've all seen it: teams spend weeks tuning model hyperparameters and testing new architectures, only to see marginal gains. Meanwhile, the same team's labeling process is ad-hoc, inconsistent, and undocumented. The hard truth we've learned across production systems is that labeling data efficiently—with clear guidelines, measured agreement, and smart sampling—delivers far more ROI than another round of model tweaks.

In this article, we'll walk through the practical approach we've used to set up labeling processes that scale, produce consistent results, and actually fit within project budgets.

Write clear labeling guidelines

The foundation of any labeling operation is a document that a new team member can read and follow without needing to ask questions. We've found that vague guidelines like "mark if the review is negative" lead to chaos. Instead, we write specifications that define edge cases, show examples, and explain the reasoning.

Here's what a real guideline looks like for sentiment classification in e-commerce reviews:

python
"""
LABELING GUIDELINE: Review Sentiment Classification
Version: 2.1
Last updated: 2024-01-15

CATEGORIES:
1. POSITIVE (label: 1)
   - Customer expresses satisfaction, praise, or intent to repurchase
   - Example: "Great quality, arrived on time, very happy!"
   - Edge case: Backhanded compliments count as POSITIVE if overall tone is favorable
   - Counter-example: "It's fine I guess" → NEUTRAL (not POSITIVE)

2. NEUTRAL (label: 0)
   - Factual description without clear sentiment
   - Mixed sentiment that balances out
   - Example: "The color is different than the picture, but it still works"
   - Counter-example: "Horrible color, but the material is good" → needs judgment

3. NEGATIVE (label: -1)
   - Customer expresses dissatisfaction, complaint, or regret
   - Example: "Broke after one week, waste of money"
   - Edge case: Sarcasm should be treated as NEGATIVE
   - Counter-example: "Fast shipping!" → POSITIVE (not NEGATIVE)

AMBIGUOUS CASES:
- If a review mentions one positive and one negative aspect with equal weight,
  label based on the sentiment of the main complaint
- If the last sentence contradicts the body, weight the last sentence 60%, body 40%
- Star rating conflicts: use text sentiment, ignore stars

PROCESS:
1. Read the entire review twice
2. Identify primary sentiment (what matters most to the customer)
3. Check edge cases list above
4. Record label and confidence (high/medium/low)
"""

Notice how this guideline includes counter-examples and edge cases. We've also learned to version these documents and to gather feedback from labelers about ambiguous cases. Every two weeks, we review problematic examples and update the guidelines. This iterative refinement is where consistency compounds.

Measure agreement between labelers

You cannot improve what you don't measure. We require that every dataset is labeled by at least two people, and we compute inter-rater agreement using Cohen's Kappa for categorical tasks.

Cohen's Kappa accounts for chance agreement and gives you a score between 0 and 1. Here's a practical implementation:

python
from sklearn.metrics import cohen_kappa_score
import pandas as pd

# Load labels from two annotators
labels_annotator_1 = [1, 0, 1, -1, 1, 0, 1, 1]
labels_annotator_2 = [1, 0, 1, -1, 1, 0, 0, 1]

kappa = cohen_kappa_score(labels_annotator_1, labels_annotator_2)
print(f"Cohen's Kappa: {kappa:.3f}")

# Interpretation:
# 0.81-1.00: Almost perfect agreement
# 0.61-0.80: Substantial agreement
# 0.41-0.60: Moderate agreement
# 0.21-0.40: Fair agreement
# < 0.20: Slight or poor agreement

# Identify disagreements
df = pd.DataFrame({
    'annotator_1': labels_annotator_1,
    'annotator_2': labels_annotator_2,
    'text': ['review_1', 'review_2', ...]  # your actual texts
})

disagreements = df[df['annotator_1'] != df['annotator_2']]
print(f"\nDisagreements: {len(disagreements)} / {len(df)}")
print(disagreements[['text', 'annotator_1', 'annotator_2']])

We target Kappa ≥ 0.70 before declaring a labeling cohort "ready." When Kappa is lower, we run a session where annotators review disagreements together, clarify guidelines, and try again. This is time-consuming but absolutely worth it—low agreement early on means low-quality training data that will haunt your model.

In one SaaS project, we had Kappa of 0.55 on intent classification initially. After refining guidelines and doing three rounds of consensus review, we hit 0.78. The resulting model was 12 percentage points better on the test set than our first attempt.

Use model predictions to pre-label

Once you have a small seed dataset of high-quality, human-labeled examples, you can train an initial model and use its predictions to pre-label new data. This is a massive time-saver when you're scaling labeling to thousands of examples.

The workflow looks like this:

  1. Label 500-1000 examples by hand (high quality)
  2. Train a baseline model on these examples
  3. Run inference on your unlabeled data
  4. Present model predictions to annotators as a starting point
  5. Annotators correct errors (much faster than labeling from scratch)

Here's a practical pipeline:

python
import pickle
from sklearn.linear_model import LogisticRegression
from sklearn.feature_extraction.text import TfidfVectorizer

# Load seed data
seed_texts = [...]  # 500 human-labeled examples
seed_labels = [...]

# Train initial model
vectorizer = TfidfVectorizer(max_features=5000, ngram_range=(1, 2))
X_seed = vectorizer.fit_transform(seed_texts)
model = LogisticRegression(max_iter=1000)
model.fit(X_seed, seed_labels)

# Save for production use
with open('model_v1.pkl', 'wb') as f:
    pickle.dump(model, f)
with open('vectorizer_v1.pkl', 'wb') as f:
    pickle.dump(vectorizer, f)

# Pre-label new data
new_texts = [...]  # 5000 unlabeled examples
X_new = vectorizer.transform(new_texts)
pre_labels = model.predict(X_new)
pre_probs = model.predict_proba(X_new).max(axis=1)

# Export for annotation tool with confidence scores
for i, (text, label, prob) in enumerate(zip(new_texts, pre_labels, pre_probs)):
    print(f"ID: {i}, Text: {text[:50]}..., Predicted: {label}, Confidence: {prob:.2f}")

The key insight here is that annotators move much faster when they're correcting rather than creating from scratch. In our ride-hailing platform, pre-labeling cut annotation time per example from 2 minutes to 30 seconds. Even with the overhead of training and inference, we saved 60% overall labor.

Focus labeling on uncertain examples

Not all unlabeled examples deserve equal attention. Examples near the decision boundary—where your model has low confidence—contain more information for learning than examples the model is already confident about.

This is called active learning, and it's one of the highest-ROI techniques we use. Instead of random sampling, we label examples where the model is uncertain.

python
import numpy as np

# After pre-labeling with confidence scores
confidences = model.predict_proba(X_new).max(axis=1)

# Strategy 1: Margin sampling (most uncertain)
# Margin = difference between top two class probabilities
margins = np.abs(model.predict_proba(X_new)[:, 0] - model.predict_proba(X_new)[:, 1])
uncertain_indices = np.argsort(margins)[:500]  # 500 most uncertain

# Strategy 2: Entropy sampling
from scipy.stats import entropy
entropies = [entropy(probs) for probs in model.predict_proba(X_new)]
uncertain_indices = np.argsort(entropies)[-500:]  # 500 highest entropy

# Strategy 3: Mix of uncertainty and coverage
# Balance uncertainty with diversity (e.g., different word lengths, topics)
uncertain_but_diverse = sample_diverse_subset(
    indices=uncertain_indices,
    texts=new_texts,
    k=500
)

# Export only uncertain examples for labeling
for idx in uncertain_but_diverse:
    text = new_texts[idx]
    prob = confidences[idx]
    print(f"Label this (confidence: {prob:.2f}): {text}")

We've seen this reduce labeling volume by 40-50% while maintaining or improving model performance. The intuition is simple: labeling examples the model gets wrong or is unsure about teaches the model more than labeling examples it already handles well.

Common mistakes

  • Skipping the guidelines document. "Just label like you think is right" leads to chaos. Spend a day writing good guidelines; it pays back tenfold.
  • Ignoring disagreement. If two annotators disagree, don't average their labels or pick one arbitrarily. Investigate why, update guidelines, and re-label.
  • Pre-labeling too early. Training a model on 50 examples and using it to pre-label is often worse than just labeling those 50 directly. Wait until you have at least 300-500 high-quality examples.
  • Active learning without quality control. Sampling uncertain examples works, but you still need agreement checks on the newly labeled data. Uncertainty doesn't mean the examples are easy to label consistently.
  • Not versioning your labels. When guidelines change, you need to re-label affected examples. Without version control, you lose track of what changed and why.

Key takeaways

  • Write explicit labeling guidelines with edge cases and counter-examples; update them iteratively based on annotator feedback.
  • Measure inter-rater agreement using Cohen's Kappa; target ≥ 0.70 before declaring a labeling cohort ready for training.
  • Use pre-labeling with a small seed model to reduce annotation time by 40-60% on new data, but only after you have solid seed examples.
  • Apply active learning (uncertainty sampling) to label only the examples your model is uncertain about, reducing labeling volume while improving learning.
  • Labeling data efficiently is a process, not a one-time event. Invest in tooling, documentation, and feedback loops; the ROI far exceeds model tuning.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles