NLP Evaluation: Intrinsic vs Extrinsic
Intrinsic Evaluation
Intrinsic evaluation measures model quality in isolation, independent of any downstream application. It evaluates properties of the model itself:
| Task | Intrinsic Metric | What It Measures |
|---|---|---|
| Language Modeling | Perplexity | How well the LM predicts held-out text |
| Word Embeddings | Analogy accuracy (king-man+woman=?) | Geometric properties of the embedding space |
| Word Embeddings | Word similarity correlation | Correlation with human similarity judgments |
| MT | BLEU, chrF, TER | N-gram overlap with reference translations |
| Summarization | ROUGE-1/2/L | N-gram recall against reference summaries |
| ASR | WER, CER | Word/character error rate vs transcript |
| Parsing | UAS, LAS | Unlabeled/labeled attachment score for dependencies |
| NER | Span-level F1 | Strict entity boundary and type matching |
Perplexity for language models:
Lower perplexity = better model. A perplexity of means the model is as confused as if choosing uniformly among alternatives at each step.
Extrinsic Evaluation
Extrinsic evaluation measures how well the model contributes to a downstream task. It is the true test of real-world utility:
| Embedding Model | Intrinsic | Extrinsic (downstream) |
|---|---|---|
| Word2Vec-Google-300d | Word analogy: 78% | Sentiment classification: 88% |
| GloVe-840B-300d | Word analogy: 82% | Sentiment classification: 89% |
| BERT-base | — | Sentiment classification: 93% |
A model may perform better intrinsically but worse extrinsically (or vice versa). Always prefer extrinsic evaluation for production decisions.
NLP Benchmarks
GLUE and SuperGLUE
GLUE (General Language Understanding Evaluation, Wang et al. 2018) aggregates 9 NLU tasks:
| Task | Type | Description |
|---|---|---|
| SST-2 | Sentiment | Binary sentiment on movie reviews |
| MNLI | NLI | Multi-genre natural language inference (3 classes) |
| QQP | Paraphrase | Quora question pair similarity |
| QNLI | QA-NLI | Whether context contains answer to question |
| RTE | NLI | Recognizing textual entailment (2 classes) |
| WNLI | Coreference | Winograd schema NLI |
| CoLA | Grammar | Acceptability of English sentences |
| MRPC | Paraphrase | Microsoft Research Paraphrase Corpus |
| STS-B | Similarity | Semantic textual similarity (regression) |
SuperGLUE (harder tasks) includes BoolQ, CB, COPA, MultiRC, ReCoRD, RTE, WiC, WSC.
Human performance on GLUE ≈ 87; BERT-large ≈ 80; DeBERTa-v3-large ≈ 90 (superhuman on many sub-tasks).
Task-Specific Metrics Reference
| NLP Task | Primary Metric | Secondary Metrics |
|---|---|---|
| Text Classification | Accuracy, F1 | AUC-ROC, Matthews Correlation |
| Multi-label Classification | Micro-F1 | Macro-F1, Hamming Loss |
| NER | Span F1 | Precision, Recall by entity type |
| Dependency Parsing | LAS | UAS, EM |
| Machine Translation | BLEU, COMET | chrF, TER |
| Summarization | ROUGE-L | BERTScore, FactCC (faithfulness) |
| ASR | WER | CER, RTF (real-time factor) |
| QA (Extractive) | EM, F1 | Has-Answer F1 (SQuAD 2.0) |
| Language Modeling | Perplexity | BPC (bits per character) |
| Dialogue | BLEU, METEOR | Human evaluation (coherence, engagement) |
Human Evaluation
For open-ended generation, automated metrics correlate poorly with human preferences. Human evaluation assesses:
- Fluency: Is the output grammatically correct and natural?
- Coherence: Does it make logical sense as a whole?
- Faithfulness: Does it accurately reflect the source/input?
- Relevance: Does it address the prompt/question?
Platforms: Amazon Mechanical Turk, Scale AI, Surge AI. Use inter-annotator agreement (Krippendorff's α, Fleiss's κ) to measure annotation quality.
Deploying NLP Models
Serving Architecture
Client Request (text)
↓
Load Balancer / API Gateway
↓
NLP Inference Server (FastAPI / TorchServe / Triton)
├─ Tokenization
├─ Model Inference (ONNX / TensorRT / PyTorch)
└─ Post-processing (decode labels, threshold)
↓
Response (labels, scores, spans)
Model Optimization for Production
Quantization: reduce weight precision from FP32 → INT8 or FP16:
- 2–4× memory reduction
- 2–3× speedup on CPU
- 0.5–2% accuracy drop (acceptable in most cases)
- Tools: ONNX Runtime, Hugging Face Optimum, BitsAndBytes
Distillation: train a small student model to mimic a large teacher:
- DistilBERT: 40% smaller, 60% faster, 97% of BERT performance
- TinyBERT, MobileBERT: further compressed versions
ONNX Export: convert PyTorch models to ONNX for cross-platform deployment:
torch.onnx.export(model, dummy_input, "model.onnx", opset_version=14)
Batching and caching: batch multiple requests together for higher GPU utilization; cache frequent queries (cosine similarity for semantic cache).
Deployment Options
| Option | Pros | Cons | Best For |
|---|---|---|---|
| HuggingFace Inference API | Zero-ops, instant | Cost, rate limits | Prototypes |
| Replicate / Modal | Serverless, auto-scale | Cold starts | Bursty workloads |
| AWS SageMaker | Managed, scalable | Complex setup | Enterprise |
| Self-hosted (k8s + Triton) | Full control, cheap at scale | Operational complexity | High volume |
| OpenAI/Anthropic API | SOTA quality, simple | Cost, data privacy | LLM features |
A/B Testing
Roll out new models gradually:
- Shadow mode: run new model in parallel, log results, compare offline
- Canary deployment: send 5–10% of traffic to new model, monitor metrics
- Full rollout: increase to 100% if metrics are stable
# ─── 1. Comprehensive NLP Evaluation ─────────────────────────
from sklearn.metrics import (classification_report, confusion_matrix,
roc_auc_score, average_precision_score)
import numpy as np
# Multi-class classification evaluation
y_true = [0, 1, 2, 0, 1, 2, 0, 1, 2, 1]
y_pred = [0, 1, 2, 0, 2, 2, 1, 1, 0, 1]
y_prob = np.array([
[0.8, 0.1, 0.1], [0.1, 0.8, 0.1], [0.1, 0.1, 0.8],
[0.7, 0.2, 0.1], [0.2, 0.3, 0.5], [0.1, 0.1, 0.8],
[0.3, 0.5, 0.2], [0.1, 0.8, 0.1], [0.4, 0.2, 0.4],
[0.1, 0.7, 0.2],
])
print("Classification Report:")
print(classification_report(y_true, y_pred,
target_names=["positive", "negative", "neutral"]))
# AUC-ROC (one-vs-rest)
auc_ovr = roc_auc_score(y_true, y_prob, multi_class='ovr', average='weighted')
print(f"Weighted AUC-ROC (OvR): {auc_ovr:.4f}")
# ─── 2. NER Span-Level F1 ─────────────────────────────────────
# seqeval is the standard library for NER evaluation
# pip install seqeval
from seqeval.metrics import f1_score, precision_score, recall_score, classification_report as seq_report
y_true_ner = [
["B-PER", "I-PER", "O", "B-ORG", "O"],
["O", "B-GPE", "O", "O"],
]
y_pred_ner = [
["B-PER", "I-PER", "O", "B-ORG", "O"], # perfect
["O", "B-GPE", "I-GPE", "O"], # wrong: extra I-GPE
]
print("\nNER Span-level Evaluation:")
print(seq_report(y_true_ner, y_pred_ner))
print(f" F1: {f1_score(y_true_ner, y_pred_ner):.4f}")
print(f" Precision: {precision_score(y_true_ner, y_pred_ner):.4f}")
print(f" Recall: {recall_score(y_true_ner, y_pred_ner):.4f}")
# ─── 3. BLEU, ROUGE, and BERTScore ───────────────────────────
import sacrebleu
from rouge_score import rouge_scorer
# Machine Translation
mt_refs = [["The cat sat on the mat.", "A cat was sitting on the mat."]]
mt_hyps = ["The cat is sitting on the mat."]
bleu = sacrebleu.corpus_bleu(mt_hyps, mt_refs)
chrf = sacrebleu.corpus_chrf(mt_hyps, mt_refs)
print(f"\nMT Evaluation:")
print(f" BLEU: {bleu.score:.2f}, chrF: {chrf.score:.2f}")
# Summarization
rouge = rouge_scorer.RougeScorer(['rouge1', 'rouge2', 'rougeL'], use_stemmer=True)
ref_summary = "AI has transformed many industries including healthcare and finance."
hyp_summary = "Artificial intelligence has changed healthcare, finance, and other sectors."
scores = rouge.score(ref_summary, hyp_summary)
print(f"\nSummarization ROUGE:")
for k, v in scores.items():
print(f" {k}: P={v.precision:.3f} R={v.recall:.3f} F={v.fmeasure:.3f}")
# BERTScore (semantic similarity, better human correlation)
# pip install bert-score
from bert_score import score as bert_score
P, R, F1 = bert_score([hyp_summary], [ref_summary], lang="en", verbose=False)
print(f" BERTScore F1: {F1.mean():.4f}")
# ─── 4. FastAPI NLP Serving ───────────────────────────────────
# Save as: nlp_api.py
# pip install fastapi uvicorn transformers torch
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from transformers import pipeline
import time
app = FastAPI(title="NLP Inference API", version="1.0.0")
# Load models once at startup
sentiment_pipe = pipeline("sentiment-analysis",
model="distilbert-base-uncased-finetuned-sst-2-english")
ner_pipe = pipeline("ner", model="dbmdz/bert-large-cased-finetuned-conll03-english",
aggregation_strategy="simple")
class TextRequest(BaseModel):
text: str
model: str = "sentiment"
class PredictionResponse(BaseModel):
text: str
predictions: list
latency_ms: float
model_version: str = "v1.0"
@app.post("/predict", response_model=PredictionResponse)
async def predict(request: TextRequest):
start = time.time()
if len(request.text) > 10000:
raise HTTPException(400, "Text exceeds maximum length of 10000 characters")
if request.model == "sentiment":
result = sentiment_pipe(request.text[:512])
elif request.model == "ner":
result = ner_pipe(request.text[:512])
else:
raise HTTPException(400, f"Unknown model: {request.model}")
latency = (time.time() - start) * 1000
return PredictionResponse(
text=request.text[:100],
predictions=result,
latency_ms=round(latency, 2),
)
@app.get("/health")
async def health():
return {"status": "ok", "models": ["sentiment", "ner"]}
# Run: uvicorn nlp_api:app --host 0.0.0.0 --port 8000
# ─── 5. Monitoring: Data Drift Detection ──────────────────────
from scipy.stats import ks_2samp
from transformers import AutoTokenizer
def compute_text_stats(texts):
"""Compute distribution features for drift detection."""
lengths = [len(t.split()) for t in texts]
return {
"mean_length": np.mean(lengths),
"std_length": np.std(lengths),
"max_length": np.max(lengths),
"lengths": lengths,
}
# Reference distribution (training data)
train_texts = ["This is great!", "Terrible product.", "Average quality.",
"Works as expected.", "Would recommend.", "Disappointed."] * 100
# Production distribution (potentially drifted)
prod_texts = ["Excelente producto!!!", "Sehr gut.", "Fantastique!", "훌륭해요",
"Amazing 10/10 best ever", "Complete garbage waste of money"] * 100
train_stats = compute_text_stats(train_texts)
prod_stats = compute_text_stats(prod_texts)
# Kolmogorov-Smirnov test for distribution shift
ks_stat, p_value = ks_2samp(train_stats["lengths"], prod_stats["lengths"])
print(f"\nDrift Detection (text length distribution):")
print(f" KS statistic: {ks_stat:.4f}, p-value: {p_value:.4f}")
print(f" Drift detected: {p_value < 0.05}")Production Monitoring
What to Monitor
| Signal | What | Alert When |
|---|---|---|
| Latency | p50/p95/p99 response time | p95 > SLA threshold |
| Throughput | Requests per second | Sudden drops or spikes |
| Error rate | HTTP 4xx/5xx, exceptions | Error rate > 1% |
| Confidence distribution | Model output score histogram | Mean confidence drops significantly |
| Input drift | Length, vocabulary, language distribution | KS-test p < 0.05 |
| Output drift | Label distribution shift | Production labels diverge from training |
| Feedback signal | User corrections, thumbs up/down | Accuracy drops on labeled feedback |
Concept Drift vs Data Drift
- Data drift (covariate shift): input distribution changes — e.g., new product categories appear in customer reviews
- Concept drift: relationship changes — e.g., sentiment about a term shifts due to a news event (a brand name becomes associated with controversy)
Detection methods: statistical tests (KS-test, PSI - Population Stability Index), embedding drift (cosine distance between rolling mean embeddings), performance-based monitoring (accuracy on labeled samples).
Responsible AI for NLP
Bias and Fairness:
- Sentiment models may perform differently across demographic groups
- NER models may miss entities from non-Western cultures
- MT quality varies dramatically across language pairs and domains
- Use CheckList (Ribeiro et al.) for behavioral testing: minimum functionality, invariance, directional
Toxic Content:
- Train or fine-tune toxicity classifiers (Perspective API, Detoxify)
- Implement input/output content filtering in the serving pipeline
Privacy:
- PII detection and redaction before logging: names, emails, phone numbers
- Differential privacy in training (DP-SGD) for sensitive data
- Comply with GDPR/CCPA — allow users to request deletion of their data
Knowledge check
What is the key difference between intrinsic and extrinsic NLP evaluation?
NLP Module Summary
Congratulations on completing the Natural Language Processing module! Here is the full picture:
| Chapter | Core Concepts |
|---|---|
| 1. Preprocessing | Tokenization, normalization, stemming, lemmatization |
| 2. Phonology | Phonemes, MFCC, ASR (Whisper), CTC, TTS, WER |
| 3. Morphology | Morphemes, POS tagging, chunking, WordNet |
| 4. Syntax | CFG, CYK, PCFG, dependency parsing, Universal Dependencies |
| 5. Semantics | Lexical relations, WSD (Lesk/BERT), SRL (PropBank) |
| 6. Discourse & Pragmatics | Coreference (SpanBERT), RST, speech acts, sarcasm |
| 7. Representations | BoW, TF-IDF, Word2Vec, GloVe, FastText, BERT, SBERT |
| 8. Text Classification | Naive Bayes, BERT fine-tuning, zero-shot (BART-MNLI), VADER |
| 9. IE & NER | IOB tagging, CRF, BERT NER, relation extraction, OpenIE |
| 10. Machine Translation | SMT, seq2seq+attention, Transformer, BLEU, NLLB-200 |
| 11. QA & Summarization | SQuAD/BERT, RAG (DPR+FAISS), BART/T5, ROUGE |
| 12. NLP Models | N-grams, LSTM, Transformer, BERT/GPT/T5, LoRA, scaling laws |
| 13. Evaluation & Deployment | GLUE, perplexity, FastAPI serving, drift monitoring, responsible AI |
The modern NLP stack:
- Foundation: Pre-trained Transformer (BERT, LLaMA, GPT-4)
- Adaptation: Fine-tuning or PEFT (LoRA) on task-specific data
- Retrieval: RAG for knowledge-intensive tasks
- Serving: ONNX/TensorRT + FastAPI + batching
- Monitoring: drift detection, confidence tracking, human feedback