Classification Applications › Code Implementation
Language AI HandbookAll chapters Classification Applications › Code ImplementationOne section · Sections in this chapter5 / 10 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Let's work through practical implementations of each classification type. We will use the transformers library and scikit-learn to demonstrate both fine-tuned and zero-shot approaches.
Setup and Data Preparation
We start by installing the required libraries and preparing sample datasets.
import subprocess
subprocess.run(
[
"uv",
"pip",
"install",
"transformers",
"torch",
"datasets",
"scikit-learn",
"textblob",
],
capture_output=True,
check=False,
)import random
from collections import Counter
import numpy as np
# Reproducibility
np.random.seed(42)
random.seed(42)
# Sample sentiment dataset (movie reviews style)
sentiment_data = [
(
"This movie was absolutely brilliant. The performances were outstanding.",
"positive",
),
("A masterpiece of modern cinema. I was moved to tears.", "positive"),
("The story captivated me from the first scene to the last.", "positive"),
(
"Wonderful direction and a compelling script. Highly recommended.",
"positive",
),
("I loved every minute of this film. Instant classic.", "positive"),
("Surprisingly good. Exceeded all my expectations.", "positive"),
(
"Beautifully crafted. The cinematography alone is worth the ticket price.",
"positive",
),
("One of the best films I've seen this decade.", "positive"),
("This movie was a complete waste of time. Terrible acting.", "negative"),
(
"Boring, predictable, and painfully slow. Avoid at all costs.",
"negative",
),
("The plot made no sense and the dialogue was cringeworthy.", "negative"),
("I fell asleep halfway through. Nothing happens.", "negative"),
("The worst movie I've seen in years. Deeply disappointing.", "negative"),
("A confusing mess. The director clearly had no vision.", "negative"),
(
"Poorly written, poorly acted, poorly directed. A complete failure.",
"negative",
),
("Two hours of my life I'll never get back.", "negative"),
("It was fine. Nothing special, nothing terrible.", "neutral"),
("Watchable but forgettable. An average entry in the genre.", "neutral"),
("Some good moments but overall not particularly memorable.", "neutral"),
(
"Decent enough for a rainy afternoon. Nothing to get excited about.",
"neutral",
),
]
# Sample intent dataset (banking assistant)
intent_data = [
("What is my current balance?", "check_balance"),
("How much money do I have in my account?", "check_balance"),
("Show me my account balance", "check_balance"),
("Can you tell me how much is in my savings?", "check_balance"),
("Transfer 50 dollars to my sister", "transfer_funds"),
("Send money to John Smith", "transfer_funds"),
("Move 200 from checking to savings", "transfer_funds"),
("I want to wire funds to my friend", "transfer_funds"),
("Pay my electric bill", "pay_bill"),
("Schedule a payment to Comcast", "pay_bill"),
("I need to pay my rent this month", "pay_bill"),
("Set up auto-pay for my credit card", "pay_bill"),
("Someone used my card without permission", "report_fraud"),
("I see a transaction I don't recognize", "report_fraud"),
("There's a suspicious charge on my account", "report_fraud"),
("I think my card was stolen", "report_fraud"),
("Can you recommend a good restaurant nearby?", "out_of_scope"),
("What's the weather like today?", "out_of_scope"),
("Tell me a joke", "out_of_scope"),
("How do I get to downtown?", "out_of_scope"),
]
# Compute label distribution for display
sent_counts = Counter(label for _, label in sentiment_data)Sentiment examples: 20 Intent examples: 20 Sentiment label distribution: negative: 8 neutral: 4 positive: 8
We have a balanced sentiment dataset with positive, negative, and neutral examples and an intent dataset covering four in-scope banking intents plus out-of-scope examples. The balance matters: imbalanced training data produces classifiers biased toward majority classes. Even in this toy dataset, the label distribution signals something important: in real deployments, out-of-scope traffic often represents 30-50% of all queries, which means your OOS detection system is handling a major portion of actual user requests, not a rare edge case.
Sentiment Analysis with a Pre-trained Model
For sentiment analysis, we will use a pre-trained sentiment model from Hugging Face rather than fine-tuning from scratch, since this illustrates how production deployments often work: use an existing model, test it on your domain, fine-tune only if needed.
from transformers import pipeline
# Load a pre-trained sentiment classifier
# distilbert-sst2 is a small, fast model trained on movie review sentiment
sentiment_pipeline = pipeline(
"text-classification",
model="distilbert-base-uncased-finetuned-sst-2-english",
device="mps", # MPS for Apple Silicon; use -1 for CPU-only systems
)# Test the model on our sample sentences
test_sentences = [
"This product is absolutely amazing. Best purchase I've made all year.",
"Terrible quality. Broke after two days of use.",
"It works as described. Nothing more, nothing less.",
"I would never say this product is good.", # Negation challenge
"Oh great, another software update that breaks everything.", # Sarcasm
]
results = sentiment_pipeline(test_sentences)Sentiment predictions: ------------------------------------------------------------ [POSITIVE 1.00] This product is absolutely amazing. Best purchase ... [NEGATIVE 1.00] Terrible quality. Broke after two days of use. [POSITIVE 0.99] It works as described. Nothing more, nothing less. [NEGATIVE 1.00] I would never say this product is good. [POSITIVE 1.00] Oh great, another software update that breaks ever...
The model handles direct positive and negative sentiment well. The negation and sarcasm examples are harder: "I would never say this product is good" expresses neutrality or mild negativity, and "Oh great, another software update..." is sarcastic. These are exactly the cases where lexicon-based approaches fail catastrophically, and even neural models require substantial examples of such constructions to learn them reliably. The SST-2 training data consists of movie review snippets, which contain fewer sarcastic constructions than, say, Twitter data. This illustrates domain shift in practice: the model's training domain shapes which failure modes it exhibits on out-of-domain text.
Feature-Based Sentiment with TF-IDF
To understand what the transformer model is learning, it helps to compare it against a simple TF-IDF baseline. The baseline makes its decision criteria explicit: you can inspect which words contribute most to each class.
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
from sklearn.pipeline import Pipeline
# Prepare data
texts = [text for text, _ in sentiment_data]
labels = [label for _, label in sentiment_data]
# Build TF-IDF + logistic regression pipeline
tfidf_clf = Pipeline(
[
(
"tfidf",
TfidfVectorizer(ngram_range=(1, 2), max_features=5000, min_df=1),
),
("clf", LogisticRegression(max_iter=500, random_state=42)),
]
)
# Cross-validate (small dataset, so use leave-one-out style)
# With only 20 examples, we use 5-fold CV as a rough estimate
cv_scores = cross_val_score(tfidf_clf, texts, labels, cv=4, scoring="accuracy")TF-IDF + LogReg 4-fold CV accuracy: 0.350 +/- 0.166 Individual fold scores: ['0.400', '0.600', '0.200', '0.200']
# Fit on all data and inspect top features per class
tfidf_clf.fit(texts, labels)
vectorizer = tfidf_clf.named_steps["tfidf"]
clf = tfidf_clf.named_steps["clf"]
feature_names = vectorizer.get_feature_names_out()
# Top features per class
n_top = 8Top discriminative n-grams per class:
--------------------------------------------------
NEGATIVE:
poorly +0.280
no +0.195
complete +0.189
halfway through +0.135
happens +0.135
nothing happens +0.135
asleep halfway +0.135
asleep +0.135
NEUTRAL:
nothing +0.346
but +0.277
nothing terrible +0.172
was fine +0.172
it was +0.172
it +0.172
nothing special +0.172
fine +0.172
POSITIVE:
this +0.193
the +0.193
good exceeded +0.140
surprisingly good +0.140
surprisingly +0.140
all my +0.140
my expectations +0.140
expectations +0.140The TF-IDF model reveals the lexical patterns that drive each class: "brilliant," "outstanding," and "masterpiece" for positive; "terrible," "waste," and "cringeworthy" for negative. These are real signals, but they are surface-level. The model has no mechanism to understand that "not good" is negative or that "sick" can be a compliment. You can use this feature inspection as a diagnostic tool: if the top features for a class contain surprising words, that reveals annotation artifacts or domain-specific conventions in your training data.
Zero-Shot Topic Classification
For topic classification, we demonstrate the zero-shot NLI approach using cross-encoder/nli-deberta-v3-small, a compact DeBERTa-based model designed for natural language inference. This requires no labeled training data and works by framing classification as textual entailment.
from transformers import pipeline
# cross-encoder/nli-deberta-v3-small is a compact NLI model for zero-shot classification
# (facebook/bart-large-mnli is a larger alternative but requires ~1.6GB download)
zero_shot_clf = pipeline(
"zero-shot-classification",
model="cross-encoder/nli-deberta-v3-small",
device="mps", # MPS for Apple Silicon; use -1 for CPU-only systems
)
# Topic categories for a news classification system
news_categories = [
"politics",
"sports",
"technology",
"health",
"business",
"science",
]
# Test documents
test_articles = [
"The Senate passed the infrastructure bill with bipartisan support, sending it to the President for signature.",
"The quarterback threw three touchdown passes in the fourth quarter to complete the comeback victory.",
"Researchers have developed a new neural network architecture that reduces training time by 40 percent.",
"A new study links regular exercise to a 30 percent reduction in the risk of cardiovascular disease.",
"The company reported record earnings this quarter, driven by strong demand in emerging markets.",
]# Run zero-shot classification on each article
zero_shot_results = []
for article in test_articles:
result = zero_shot_clf(article, news_categories)
zero_shot_results.append(result)Zero-shot topic classification results: ================================================================= Text: The Senate passed the infrastructure bill with bipartis... Top label: politics (0.633) All labels: politics(0.63), business(0.31), sports(0.02) Text: The quarterback threw three touchdown passes in the fou... Top label: sports (0.995) All labels: sports(1.00), business(0.00), technology(0.00) Text: Researchers have developed a new neural network archite... Top label: science (0.563) All labels: science(0.56), technology(0.32), sports(0.04) Text: A new study links regular exercise to a 30 percent redu... Top label: health (0.474) All labels: health(0.47), science(0.25), sports(0.22) Text: The company reported record earnings this quarter, driv... Top label: business (0.936) All labels: business(0.94), technology(0.02), health(0.02)
Zero-shot classification assigns the correct category to each article without any task-specific training. The confidence scores reflect the model's uncertainty: clear-cut cases like the political article or the sports article receive high confidence scores, while ambiguous cases that span multiple categories receive more distributed scores. Notice that the neural network architecture article might receive partial scores for both "technology" and "science," since the boundary between these categories is blurry from a linguistic perspective.
This is informative in its own right. When zero-shot scores are distributed across multiple labels, that is often a sign that the document is legitimately ambiguous or multi-topic, not a failure of the model. You can use the distribution of scores as a soft multi-label assignment: if both "technology" and "science" receive scores above 0.3, the document may warrant both labels.
Intent Detection with Confidence Thresholding
Intent detection requires special handling of out-of-scope utterances. We implement a threshold-based rejection mechanism.
# Use the same zero-shot NLI approach for intent detection
# Reuse the zero_shot_clf loaded in the previous section
# In production, you would fine-tune a model on labeled intent data
intent_clf = zero_shot_clf
# Define the in-scope intents
in_scope_intents = [
"check_balance",
"transfer_funds",
"pay_bill",
"report_fraud",
]
# Out-of-scope threshold: below this confidence, reject
OOS_THRESHOLD = 0.50
def classify_intent(utterance, intents, threshold):
"""Classify an utterance with OOS detection."""
result = intent_clf(utterance, intents)
top_intent = result["labels"][0]
top_score = result["scores"][0]
if top_score < threshold:
return "out_of_scope", top_score, top_intent
return top_intent, top_score, None
# Test utterances including OOS examples
test_utterances = [
"What's my account balance right now?",
"Send $200 to my roommate",
"Pay my internet bill",
"There's a charge I don't recognize on my Visa",
"What's a good recipe for pasta?", # OOS
"Can you call a taxi for me?", # OOS
"Tell me today's weather forecast", # OOS
]Intent detection with OOS rejection (threshold = 0.50):
=================================================================
[OOS 0.43] What's my account balance right now?
(nearest intent: pay_bill)[transfer_funds 0.61] Send $200 to my roommate [pay_bill 0.93] Pay my internet bill
[OOS 0.34] There's a charge I don't recognize on my Visa
(nearest intent: pay_bill)
[OOS 0.41] What's a good recipe for pasta?
(nearest intent: check_balance)
[OOS 0.42] Can you call a taxi for me?
(nearest intent: check_balance)[check_balance 0.77] Tell me today's weather forecast
The threshold mechanism correctly identifies out-of-scope queries by their lower confidence scores. In-scope utterances receive high confidence scores because the model's NLI training allows it to recognize that "What's my account balance?" entails the "check_balance" intent description. The nearest intent output for OOS cases is diagnostically useful during development: it tells you which in-scope intent the model found most relevant, which can reveal confusions in your intent schema or gaps in your OOS coverage.