LeetLLM
My PlanLearnGlossaryTracksPracticeBlog
LeetLLM

Your go-to resource for mastering AI & LLM systems.

Product

  • Learn
  • Glossary
  • Tracks
  • Practice
  • Blog
  • RSS

Legal

  • Terms of Service
  • Privacy Policy

© 2026 LeetLLM. All rights reserved.

All Topics
Your Progress
0%

0 of 196 articles completed

🛠️Computing Foundations0/9
Git, Shell, Linux for AIDocker for Reproducible AIPython for AI EngineeringNumPy and Tensor ShapesCUDA for ML TrainingMPS & Metal for ML on MacData Structures for AISQL and Data ModelingAlgorithms for ML Engineers
📊Math & Statistics0/9
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningBayesian Inference from EvidenceStatistics and UncertaintyDistributions and SamplingHypothesis Tests, Intervals, and pass@k
📚Preparation & Prerequisites0/13
Neural Networks from ScratchCNNs from ScratchTraining & BackpropagationSoftmax, Cross-Entropy & OptimizationRNNs, LSTMs, GRUs, and Sequence ModelingAutoencoders and VAEsThe Transformer Architecture End-to-EndLanguage Modeling & Next TokensFrom GPT to Modern LLMsPrompt Engineering FundamentalsCalling LLM APIs in ProductionFirst AI App End-to-EndThe LLM Lifecycle
🧮ML Algorithms & Evaluation0/15
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageStatistical Learning and GeneralizationConformal Prediction and UncertaintyClustering and PCAProbabilistic Generative ModelsCore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingCausal Inference and CounterfactualsPyTorch Training LoopsDataset Pipelines and Data Quality
📦Production ML Systems0/6
Feature Engineering for Production MLBatch and Streaming Feature PipelinesGradient Boosted Trees in ProductionRanking and Recommendation SystemsForecasting and Anomaly DetectionMonitoring Predictive Models
🧪Core LLM Foundations0/8
The Bitter Lesson & ComputeBPE, WordPiece, and SentencePieceStatic to Contextual EmbeddingsPerplexity & Model EvaluationFile Ingestion for AIChunking StrategiesLLM Benchmarks & LimitationsInstruction Tuning & Chat Templates
🧰Applied LLM Engineering0/25
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsContext EngineeringPrompt Injection DefenseResponsible AI GovernancePrivacy-Preserving Machine LearningData Labeling and Human FeedbackEvaluating AI AgentsProduction RAG PipelinesHybrid Search: Dense + SparseReranking and Cross-Encoders for RAGRAG Evaluation for Reliable AnswersLLM-as-a-Judge EvaluationBias & Fairness in LLMsHallucination Detection & MitigationLLM Observability & MonitoringExperiment Tracking with MLflow and W&BPrompt Optimization with DSPyModel Versioning & DeploymentSemantic Caching & Cost OptimizationLLM Cost Engineering & Token EconomicsModel Gateways, Routing, and FallbacksDesign an Automated Support Agent
🎓Portfolio Capstones0/9
Capstone: Delivery ETA PredictionCapstone: Product RankingCapstone: Demand ForecastingCapstone: Image Damage ClassifierCapstone: Production ML PipelineCapstone: Document QACapstone: Eval DashboardCapstone: Fine-Tuned ClassifierCapstone: Reproducible ML Study
🧠Transformer Deep Dives0/8
Sentence Embeddings & Contrastive LossEmbedding Similarity & QuantizationScaled Dot-Product AttentionVision Transformers and Image EncodersPositional Encoding: RoPE & ALiBiLayer Normalization: Pre-LN vs Post-LNMechanistic InterpretabilityDecoding Strategies: Greedy to Nucleus
🧬Advanced Training & Adaptation0/17
Scaling Laws & Compute-Optimal TrainingPre-training Data at ScaleBuild GPT from Scratch LabJAX for PyTorch ResearchersContinued Pretraining for Domain ShiftSynthetic Data PipelinesSupervised Fine-Tuning PipelineMixed Precision TrainingDistributed Training: FSDP & ZeROLoRA & Parameter-Efficient TuningTraining Run OperationsReward Modeling from Preference DataRLHF & DPO AlignmentConstitutional AI & Red TeamingRLVR & Verifiable RewardsKnowledge Distillation for LLMsModel Merging and Weight Interpolation
🤖Advanced Agents & Retrieval0/16
Vector DB Internals: HNSW & IVFAdvanced RAG: HyDE & Self-RAGGraphRAG & Knowledge GraphsRAG Security & Access ControlStructured Output GenerationReAct & Plan-and-ExecuteGuardrails & Safety FiltersCode Generation & SandboxingComputer-Use / GUI / Browser AgentsHuman-in-the-Loop Agent ArchitectureAI Coding Workflow with AgentsAgent Memory & PersistenceAgent Failure & RecoveryRecursive Language Models (RLM)Multi-Agent OrchestrationCapstone: Production Agent
⚡Inference & Production Scale0/31
Inference: TTFT, TPS & KV CacheMulti-Query & Grouped-Query AttentionKV Cache & PagedAttentionPrefix Caching and Prompt CachingFlashAttention & Memory EfficiencyContinuous Batching & SchedulingScaling LLM InferenceModel Parallelism for LLM InferenceAccelerator Architecture Field GuideCorrect GPU Profiling and BenchmarkingGPU Kernel Performance Engineering LabGPU Parallel Primitives LabGPU Compilers and Kernel LanguagesGEMM From Naive CUDA to Tensor CoresLow-Precision GPU ProgrammingGPU Runtime Execution LabAI-Generated Kernel EngineeringModel Quantization: GPTQ, AWQ & GGUFLocal LLM DeploymentSLM Specialization & Edge DeploymentSpeculative DecodingLong Context Window ManagementMixture of Experts ArchitectureMamba & State Space ModelsReasoning & Test-Time ComputeAdvanced MLOps & DevOps for AIGPU Serving & AutoscalingGPU Failure OperationsDistributed Inference Data PlaneServing Benchmark EngineeringA/B Testing for LLMs
🏗️System Design Capstones0/9
Content Moderation SystemCode Completion SystemMulti-Tenant LLM PlatformLLM-Powered Search EngineVision-Language Models & CLIPMultimodal LLM ArchitectureDiffusion Models: Images & TextReal-Time Voice AI AgentReasoning Agent System Design
🎤AI Lab Interviewing0/4
AI Lab Coding Interview: Python SystemsAI Lab System Design InterviewAI Lab Behavioral InterviewAI Lab Technical Presentation
🔬Project Deep Dives0/17
Deep Dive - vLLMDeep Dive - SkyRLDeep Dive - FlashAttentionDeep Dive - FlashInferDeep Dive - DeepGEMMDeep Dive - NCCLDeep Dive - MegatronDeep Dive - DeepSpeedDeep Dive - RayDeep Dive - MLflowDeep Dive - PyTorchDeep Dive - TransformersDeep Dive - SGLangDeep Dive - slimeDeep Dive - DeepEPDeep Dive - TinkerDeep Dive - Light-PEFT
Back to Topics
LearnML Algorithms & EvaluationValidation and Leakage
📊MediumEvaluation & Benchmarks

Validation and Leakage

Split access-review requests by time and user, block post-decision fields, fit preprocessing on training rows only, and treat public LLM benchmarks as contamination-prone.

29 min read
Learning path
Step 36 of 196 in the full curriculum
Reinforcement Learning BasicsStatistical Learning and Generalization

Personalize this lesson

Adapt explanations and teaching visuals to your background and preferred voice.

Request REQ-10234 lands on your access gateway at 09:00:00. By 10:22, a manager approves it. Two weeks later, on July 15, a quarterly compliance audit discovers a policy violation and revokes the grant. If you build a model on finished database rows, those later fields look like gold: they predict whether a ticket needed review with near-perfect accuracy. But when the automated router had to make its choice at 09:00:00, none of those downstream events existed yet.

Every machine learning system deployed in production is an engine for making decisions under uncertainty. In empirical risk minimization, our training algorithm optimizes a hypothesis hhh to minimize loss across an available training sample SSS of nnn observations:

R^S(h)=1n∑i=1nL(h(xi),yi)\hat{R}_S(h) = \frac{1}{n}\sum_{i=1}^n L(h(x_i), y_i)R^S​(h)=n1​i=1∑n​L(h(xi​),yi​)

Because the model parameters adapt directly to that exact sample, training error is systematically optimistic. The true metric we care about is the out-of-sample risk (or population risk) across the unseen data-generating distribution D\mathcal{D}D:

R(h)=E(x,y)∼D[L(h(x),y)]R(h) = \mathbb{E}_{(x, y) \sim \mathcal{D}}[L(h(x), y)]R(h)=E(x,y)∼D​[L(h(x),y)]

The difference between what our model scores on training data and how it performs in the wild is the generalization gap, G(h)=R(h)−R^S(h)G(h) = R(h) - \hat{R}_S(h)G(h)=R(h)−R^S​(h).[1]Reference 1The Elements of Statistical Learning.https://hastie.su.domains/ElemStatLearn/

Offline evaluation exists to estimate this out-of-sample risk honestly before code touches live traffic. Data leakage occurs when information from the target, from future timestamps, or from held-out evaluation entities contaminates the training pipeline. Leakage shrinks the measured generalization gap during development to zero, handing you a mirage: a model that looks flawless on paper but falls apart the second it hits production traffic.

Timeline architecture for access request REQ-10234. On the left, decision-time features known at 08:59 (ambiguity score, 90-day exceptions, and user role) are marked ALLOW. In the center sits the 09:00:00 temporal firewall and guardrail router f(X). On the right, post-decision events (manager approval at 10:22, reviewer triage, and July 15 audit outcome) are marked BLOCK, isolating target leakage from the model input.
For `REQ-10234`, only fields available to the router at 09:00:00 may enter the opening-time model. Manager approvals and quarterly audit revocations sit downstream of the decision barrier: they're post-hoc outcomes, not input features.

Start at the decision moment

You're building a production guardrail that predicts, the moment a request opens, whether it must route to mandatory human review. The compliance audit supplies the ground-truth label later, applying the security policy in place when the ticket opened. At prediction time, manager approvals, reviewer triage logs, and grant revocation outcomes don't exist yet.

For REQ-10234, the decision timestamp is 2026-07-01 09:00:00.

FieldWhen it existsValid at opening?Why
ambiguity_scoreBefore openingYesExtracted from submitted request text
prior_policy_exceptions_90dBefore openingYesUser history already recorded in security logs
manager_approval_received_atAfter evidence requestNoThe router's own action can create this event
access_revoked_after_auditAfter resolutionNoReveals the post-resolution outcome
needs_reviewAfter auditLabel onlyGround truth target that the guardrail predicts

Write this feature contract before touching any model code. If a field didn't exist when the action was chosen, it can't enter features, prompt context, retrieval documents, or preprocessing statistics.

Predict the audit before running it: the two 08:59 fields should be allowed, while manager approval and audit revocation must be blocked.

audit-feature-availability.py
1from datetime import datetime 2 3decision_at = datetime.fromisoformat("2026-07-01T09:00:00") 4fields = [ 5 ("ambiguity_score", "2026-07-01T08:59:00"), 6 ("prior_exceptions_90d", "2026-07-01T08:59:00"), 7 ("manager_approval_at", "2026-07-01T10:22:00"), 8 ("access_revoked_at", "2026-07-15T12:00:00"), 9] 10 11for field, observed_at in fields: 12 known = datetime.fromisoformat(observed_at) <= decision_at 13 print(f"{field:22} {'ALLOW' if known else 'BLOCK'}")
Output
1ambiguity_score ALLOW 2prior_exceptions_90d ALLOW 3manager_approval_at BLOCK 4access_revoked_at BLOCK

The two blocked fields help downstream forensic analysis, but they aren't valid inputs for the opening decision. Observation time means when the system could actually read the value from its datastores, not merely when the real-world event happened. If an engineer changes their role in HR at 08:50, but the LDAP cache only syncs at midnight, that role update isn't available to an 09:00 decision.

Timestamp audits catch obvious future fields, but derived features need the same scrutiny. Rebuild them from strict as-of snapshots and inspect their aggregation windows. An offline database join can stamp a record at 08:59 while aggregating log rows that arrived at 11:00.

Labels demand an independent audit. A June request audited in August can't train a model deployed on July 1, even though the request itself is weeks old. Training requires both the request features and the ground-truth label to be available prior to the model-fit cutoff. Held-out validation labels can arrive later, because they score predictions frozen at the decision moment.

Why is manager_approval_received_at invalid even though it appears in every completed historical database record?

Answer

The model makes its routing decision before an approval request is ever sent. Using that timestamp gives the model information caused by downstream workflow actions, turning evaluation into an unrealistic simulation where the model sees the consequences of its own decisions.

Train, validation, and test have different jobs

Knowing which columns are available at decision time doesn't complete your evaluation design. To estimate out-of-sample risk honestly, you need three non-negotiable data roles. Confusing their boundaries is the fastest way to mislead your team about a model's true capability.[1]Reference 1The Elements of Statistical Learning.https://hastie.su.domains/ElemStatLearn/

SplitAllowed useMust not do
TrainFit model weights, tree splits, tokenizers, scalers, and feature selectorsClaim this score estimates deployment performance
ValidationTune hyperparameters, calibrate probability thresholds, select architecturesReuse it as an untouched final estimate
TestEstimate out-of-sample risk once after all design decisions are frozenInspect errors, tweak features or thresholds, and re-report the score

To estimate performance on future access requests, use a chronological train, validation, and test split:

  • January through June: training.
  • July: validation for tuning features and choosing the review threshold.
  • August: locked test, opened exactly once after all choices are frozen.

The small fixture below isolates month assignment. It uses two requests per month: 12 training requests, 2 validation requests, and 2 locked test requests. These small counts demonstrate the partitioning logic, not a production-scale evaluation sample.

make-a-locked-time-split.py
1episodes = [ 2 {"request": f"{month}-{index}", "month": month} 3 for month in range(1, 9) 4 for index in range(2) 5] 6 7train = [row for row in episodes if row["month"] <= 6] 8validation = [row for row in episodes if row["month"] == 7] 9test = [row for row in episodes if row["month"] == 8] 10 11assert len(train) == 12 12assert len(validation) == 2 13assert len(test) == 2 14print("train requests =", len(train), "months = 1..6") 15print("valid requests =", len(validation), "months = 7") 16print("test requests =", len(test), "months = 8 (locked)")
Output
1train requests = 12 months = 1..6 2valid requests = 2 months = 7 3test requests = 2 months = 8 (locked)
Diagram showing Jan-Jun requests fit model and transforms, July requests tune threshold, August requests one locked estimate, and September traffic monitor outcomes.
Jan-Jun requests fit model and transforms, July requests tune threshold, August requests one locked estimate, and September traffic monitor outcomes.

January through June fits parameters, July selects thresholds, and August estimates frozen performance. July's ground-truth labels must be available before the August system is locked; otherwise, move the test window later.

The most common real-world failure mode here is test-set hill-climbing. An engineer evaluates candidate model A on August data, gets a disappointing result, tweaks a few features, adjusts the classification threshold, and re-evaluates candidate model B on the same August data. If model B scores higher, they declare victory and ship it.

That reported score is invalid. The moment August error feedback guides a development choice, August stops being an untouched out-of-sample holdout; it becomes an informal validation set. Its score absorbs selection bias, making the system look more accurate than it actually is.[1]Reference 1The Elements of Statistical Learning.https://hastie.su.domains/ElemStatLearn/ If test results force an architecture revision, August joins the historical development corpus, and an untouched September window must serve as the new locked test.

Cross-validation rotates the validation job

A single validation month can be unusually forgiving or unusually harsh. Cross-validation (CV) repeats fitting and evaluation across several systematic partitions of development data, reducing the variance of our performance estimate without opening the locked final test.[2]Reference 2Cross-validation: evaluating estimator performancehttps://scikit-learn.org/stable/modules/cross_validation.html

In standard KKK-fold cross-validation, we partition development data into KKK disjoint subsets (folds) of equal size, V1,V2,…,VKV_1, V_2, \dots, V_KV1​,V2​,…,VK​. For each iteration k∈{1,…,K}k \in \{1, \dots, K\}k∈{1,…,K}, we hold out fold VkV_kVk​ as the validation partition and fit the pipeline on the remaining K−1K-1K−1 folds, denoted S−kS_{-k}S−k​. The overall cross-validation risk estimate is the average validation loss across all KKK folds:

R^CV(h)=1K∑k=1KR^Vk(h−k)\hat{R}_{\text{CV}}(h) = \frac{1}{K} \sum_{k=1}^K \hat{R}_{V_k}(h_{-k})R^CV​(h)=K1​k=1∑K​R^Vk​​(h−k​)

Choosing KKK involves a classic bias-variance trade-off:

  • K=5K = 5K=5 or K=10K = 10K=10: Each fold trains on 80% to 90% of development data. This creates a slightly pessimistic bias (since training on fewer examples slightly understates what the full development set can achieve), but the variance of the estimator remains low.
  • Leave-One-Out (LOO, K=nK = nK=n): Each model fits on n−1n - 1n−1 samples. Bias is practically zero, but computational cost is severe and variance can be surprisingly high: the nnn fitted models are nearly identical because their training sets overlap by n−2n−1\frac{n-2}{n-1}n−1n−2​ samples, inducing strong positive correlation between fold errors.[1]Reference 1The Elements of Statistical Learning.https://hastie.su.domains/ElemStatLearn/

When target classes are imbalanced, random KKK-fold splitting introduces another subtle risk: some folds may randomly receive zero positive examples. In access routing or fraud detection, where policy violations might make up only 2% of traffic, Stratified K-Fold guarantees that every fold VkV_kVk​ preserves the exact class proportions of the overall dataset.

Suppose five earlier-history folds give these F1 scores for our review guardrail: 0.67, 0.75, 0.58, 0.83, and 0.71. F1 combines precision and recall: how many flagged tickets genuinely needed review, and how many violative tickets were caught.

For the first fold, suppose the guardrail catches 4 needed reviews, sends 3 routine requests to review, and misses 1 needed review. That gives 4 true positives, 3 false positives, and 1 false negative:

P=44+3=47,R=44+1=45,F1=2PRP+R=812≈0.67P = \frac{4}{4+3} = \frac{4}{7},\qquad R = \frac{4}{4+1} = \frac{4}{5},\qquad F_1 = \frac{2PR}{P+R} = \frac{8}{12} \approx 0.67P=4+34​=74​,R=4+14​=54​,F1​=P+R2PR​=128​≈0.67

The harmonic mean severely penalizes either low precision or low recall. It treats false positives and false negatives symmetrically, which rarely matches business reality: missing a critical policy violation is usually far more dangerous than sending an extra ticket to triage. Keep raw confusion counts beside F1 when choosing production operating points.

summarize-cross-validation-folds.py
1import statistics 2 3fold_f1 = [0.67, 0.75, 0.58, 0.83, 0.71] 4mean_f1 = statistics.mean(fold_f1) 5std_f1 = statistics.stdev(fold_f1) 6 7assert round(mean_f1, 2) == 0.71 8assert round(std_f1, 2) == 0.09 9print("fold F1 =", fold_f1) 10print(f"mean F1 = {mean_f1:.2f}") 11print(f"std F1 = {std_f1:.2f}")
Output
1fold F1 = [0.67, 0.75, 0.58, 0.83, 0.71] 2mean F1 = 0.71 3std F1 = 0.09

The mean of 0.71 summarizes expected development performance. The sample standard deviation, 0.09, measures dispersion across these five folds. It isn't a standard error of the mean or a confidence interval: because the training sets across folds overlap heavily, the individual fold scores are correlated rather than independent.

Never report the best fold (0.83) as your expected production metric. Investigate the worst fold (0.58) to understand which traffic patterns caused the model to stumble. Note also that the mean of fold F1 scores differs mathematically from the pooled F1 score calculated by aggregating all out-of-fold predictions before evaluating: because F1 is a non-linear ratio, those two summaries will not match.

Standard shuffled cross-validation assumes that rows are exchangeable: swapping rows between folds doesn't violate the underlying distribution. When data has entity grouping or temporal dependencies, that assumption breaks down completely. Stratification balances class counts, but it doesn't prevent a user or future record from crossing the split boundary.[2]Reference 2Cross-validation: evaluating estimator performancehttps://scikit-learn.org/stable/modules/cross_validation.html

Target leakage: when a feature steals the answer key

Suppose access_revoked_after_audit sits in your historical database table. Because an audit happens weeks after a ticket closes, that column virtually mirrors whether human review was required in the first place. Including it in an opening-time classifier yields an astonishing metric and a completely broken production system.

This is target leakage: when an input feature includes information that is a causal descendant or direct proxy of the label you're trying to predict.

In the lab below, eight held-out requests are scored under two configurations. The honest rule uses only opening-time fields: predict review if ambiguity - 0.9 * policy_support > 0. The leaky rule includes the post-close audit field, which in this dataset directly mirrors the label. Predict which score will look impressive before running the comparison.

future-field-leakage.py
1rows = [ 2 {"ambiguity": 1.4, "policy": 0, "y": 1}, 3 {"ambiguity": -0.2, "policy": 1, "y": 0}, 4 {"ambiguity": 0.8, "policy": 0, "y": 1}, 5 {"ambiguity": 0.1, "policy": 1, "y": 0}, 6 {"ambiguity": 1.1, "policy": 0, "y": 1}, 7 {"ambiguity": -0.5, "policy": 1, "y": 0}, 8 {"ambiguity": 0.3, "policy": 0, "y": 0}, 9 {"ambiguity": 0.6, "policy": 1, "y": 1}, 10] 11 12def accuracy(pred, y): 13 return sum(a == b for a, b in zip(pred, y)) / len(y) 14 15y = [row["y"] for row in rows] 16honest = [int(row["ambiguity"] - 0.9 * row["policy"] > 0) for row in rows] 17leaked = list(y) 18 19print(f"decision-time accuracy={accuracy(honest, y):.2f} preds={honest}") 20print(f"future-field accuracy={accuracy(leaked, y):.2f}") 21assert accuracy(honest, y) == 0.75 22assert accuracy(leaked, y) == 1.0
Output
1decision-time accuracy=0.75 preds=[1, 0, 1, 0, 1, 0, 1, 0] 2future-field accuracy=1.00

The last two rows expose the honest rule's real-world misses: at ambiguity=0.3 it predicts review but the label is 0; at ambiguity=0.6 with policy support it predicts no review but the label is 1. Six of eight honest predictions match, giving an authentic 0.75 accuracy.

The leaked field matches every single label because it literally copies the ground truth. When your offline model scores an improbable 1.00 accuracy or 0.999 AUC, it hasn't achieved artificial superintelligence. You've simply handed the model an answer key that won't exist at 09:00:00 on Monday morning.

Clustered entities change the generalization claim

A standard random row split assumes every observation is independently generated. In production software, data points almost always cluster around entities: users, patient IDs, enterprise organizations, or host machines.

If your product promise is "this model generalizes to brand-new users," but each user's earlier requests appear in the training split while later requests appear in validation, your evaluation is answering the wrong question. The model doesn't need to learn generalizable security principles: it can simply memorize user-specific idiosyncrasies, such as habitual request times, specific cloud resource IDs, or writing style.

The fixture below isolates this failure mode. Eight users each submit two identical requests, and the label is a user-specific attribute. The model is a simple lookup table: if a user appeared in training, repeat their known label; otherwise fall back to 0.

The first split puts one row from every user on each side. The second split holds out entire users using a group boundary.

group-leakage-from-repeat-users.py
1rows = [] 2for user_index in range(8): 3 user_id = f"u{user_index:02d}" 4 label = user_index % 2 5 rows.extend([(user_id, label), (user_id, label)]) 6 7def lookup_accuracy(train_idx, val_idx): 8 memory = {rows[i][0]: rows[i][1] for i in train_idx} 9 correct = 0 10 for i in val_idx: 11 user_id, label = rows[i] 12 pred = memory.get(user_id, 0) 13 correct += pred == label 14 return correct / len(val_idx) 15 16row_train = list(range(0, 16, 2)) 17row_val = list(range(1, 16, 2)) 18group_train = [i for i, (user_id, _) in enumerate(rows) if int(user_id[1:]) < 4] 19group_val = [i for i, (user_id, _) in enumerate(rows) if int(user_id[1:]) >= 4] 20 21row_acc = lookup_accuracy(row_train, row_val) 22group_acc = lookup_accuracy(group_train, group_val) 23assert row_acc == 1.0 24assert group_acc == 0.5 25print(f"paired-row accuracy = {row_acc:.2f}") 26print(f"held-out-user accuracy = {group_acc:.2f}")
Output
1paired-row accuracy = 1.00 2held-out-user accuracy = 0.50

The 1.00 score measures recognition of users already represented in training. The 0.50 score is the honest unseen-user result for this balanced fixture and its fallback rule.

When you promise generalization across new entities, use GroupKFold so every row belonging to an entity stays strictly on one side of the fold boundary. Simply dropping the explicit user_id column isn't enough: high-capacity models easily reconstruct user identity from correlated behavioral signals.[2]Reference 2Cross-validation: evaluating estimator performancehttps://scikit-learn.org/stable/modules/cross_validation.html

Why should every row from one user stay in the same fold when testing generalization to new users?

Answer

Rows from the same user share behavioral patterns, vocabulary, and environment fingerprints. Letting that entity span train and validation allows the model to memorize user-specific identities rather than learning transferable patterns that work on new people.

Preprocessing can learn from validation rows

Data leakage doesn't require an explicit future column. A feature scaler, missing-value imputer, target encoder, principal component analysis (PCA) projection, or feature selector learns parameters during its fit step. If you run .fit() or .fit_transform() on your entire dataset before splitting, validation data has already corrupted your pipeline.[3]Reference 3Common Pitfalls and Recommended Practices: Data Leakagehttps://scikit-learn.org/stable/common_pitfalls.html#data-leakage

Comparison of preprocessing leakage versus clean pipeline evaluation. On the left (leaky), feature selection is fitted on all eight rows before splitting; validation labels inflate column L separation score to 0.75, causing the pipeline to select L and report a fraudulent 1.00 validation accuracy. On the right (clean), the dataset is split first and the selector is fitted strictly on the four training rows; column T wins honestly with separation 1.00, and applying it to validation reveals true chance accuracy of 0.50.
Fitting feature selection across all eight rows lets validation labels boost column L's separation score, locking the pipeline into L and creating a fraudulent 1.00 validation score. Fitting strictly on training rows picks T honestly, exposing the model's true 0.50 chance accuracy.

Consider eight rows and two candidate features: T and L. Column T perfectly mirrors training labels and is noisy on validation. Column L is weak on training and copies validation labels.

Our selector picks the column with the larger absolute difference between class means: Δ=∣μ1−μ0∣\Delta = |\mu_1 - \mu_0|Δ=∣μ1​−μ0​∣. On training rows, T achieves Δ=1.00\Delta = 1.00Δ=1.00 while L achieves Δ=0.50\Delta = 0.50Δ=0.50. But across all eight rows, validation labels join the calculation, inflating L's separation to 0.750.750.75 while dragging T down to 0.500.500.50.

Both paths fit a nearest-mean classifier on training rows only. Only the feature selector's input changes: all eight rows, or training rows alone. Predict which column each path chooses before running the script.

preprocessing-leakage-two-columns.py
1T = [1, 1, 0, 0, 1, 1, 0, 0] 2L = [1, 0, 0, 0, 1, 0, 1, 0] 3y = [1, 1, 0, 0, 1, 0, 1, 0] 4train, val = range(4), range(4, 8) 5 6def mean_gap(col, idx): 7 ones = [col[i] for i in idx if y[i] == 1] 8 zeros = [col[i] for i in idx if y[i] == 0] 9 return abs(sum(ones) / len(ones) - sum(zeros) / len(zeros)) 10 11def nearest_mean_accuracy(col, train_idx, val_idx): 12 ones = [col[i] for i in train_idx if y[i] == 1] 13 zeros = [col[i] for i in train_idx if y[i] == 0] 14 mean_one = sum(ones) / len(ones) 15 mean_zero = sum(zeros) / len(zeros) 16 correct = 0 17 for i in val_idx: 18 pred = 1 if abs(col[i] - mean_one) < abs(col[i] - mean_zero) else 0 19 correct += pred == y[i] 20 return correct / len(val_idx) 21 22print(f"train gap T={mean_gap(T, train):.2f} L={mean_gap(L, train):.2f}") 23print(f"all gap T={mean_gap(T, range(8)):.2f} L={mean_gap(L, range(8)):.2f}") 24columns = {"T": T, "L": L} 25leaky_name = max(columns, key=lambda name: mean_gap(columns[name], range(8))) 26safe_name = max(columns, key=lambda name: mean_gap(columns[name], train)) 27leaky_acc = nearest_mean_accuracy(columns[leaky_name], train, val) 28safe_acc = nearest_mean_accuracy(columns[safe_name], train, val) 29print(f"select {leaky_name} on all rows val acc={leaky_acc:.2f}") 30print(f"select {safe_name} on train val acc={safe_acc:.2f}") 31assert (mean_gap(T, train), mean_gap(L, train)) == (1.0, 0.5) 32assert (mean_gap(T, range(8)), mean_gap(L, range(8))) == (0.5, 0.75) 33assert leaky_acc == 1.0 34assert safe_acc == 0.5
Output
1train gap T=1.00 L=0.50 2all gap T=0.50 L=0.75 3select L on all rows val acc=1.00 4select T on train val acc=0.50

On training rows alone, T wins decisively (1.00 vs 0.50). Across all eight rows, L wins (0.75 vs 0.50) because validation labels entered the selection contest. The train-fitted classifier on L then scores 1.00 on those same validation rows. Select on training rows only, and validation accuracy falls to genuine chance (0.50).

In a famous scikit-learn experiment with 10,000 completely random features and random noise labels, selecting the top 100 features before splitting yields 76% validation accuracy on pure white noise! Selecting features strictly inside each training split produces the true 50% chance baseline.[3]Reference 3Common Pitfalls and Recommended Practices: Data Leakagehttps://scikit-learn.org/stable/common_pitfalls.html#data-leakage

The production cure for preprocessing leakage is architectural: encapsulate every transform inside a Pipeline:

leakage-safe-pipeline.py
1from sklearn.feature_selection import SelectKBest, f_classif 2from sklearn.linear_model import LogisticRegression 3from sklearn.model_selection import cross_val_score 4from sklearn.pipeline import Pipeline 5 6pipeline = Pipeline([ 7 ("selector", SelectKBest(score_func=f_classif, k=1)), 8 ("classifier", LogisticRegression()), 9]) 10# cross_val_score fits selector and classifier on training folds only 11scores = cross_val_score(pipeline, X, y, cv=5)

When passed to cross_val_score, the Pipeline ensures that selector.fit() and classifier.fit() execute strictly on the training fold, while transform() and predict() execute out-of-sample on validation rows. A pipeline won't fix a bad time split or repair a leaked database field, but it completely eliminates transform leakage.

Where should a scaler, imputer, or target encoder be fitted during cross-validation?

Answer

Fit it strictly on each fold's training partition, then apply that fitted transform to the fold's validation partition. Fitting once across all rows leaks validation statistics into the model hypothesis space before training even begins.

Temporal validation: walk-forward, purging, and embargo windows

Access routing faces seasonal traffic, policy shifts, infrastructure migrations, and evolving user habits. A model fitted on August can't predict June in any honest simulation of future performance. Rolling-origin evaluation (walk-forward time-series cross-validation) advances the training cutoff chronologically, ensuring that every validation fold evaluates requests that occurred strictly after its training data.[4]Reference 4Forecasting: Principles and Practice, Third Edition.https://otexts.com/fpp3/

Expanding historical windows mirror production retraining: you retrain periodically on accumulated historical data. In our access routing timeline, we keep development folds strictly within January through June: train on months 1-2 and validate on 3-4; then expand training to months 1-4 and validate on 5-6. July is the dedicated threshold-selection month, while August remains locked.

Four-track walk-forward validation architecture spanning January through August. Fold 1 trains on Jan-Feb, applies a purged embargo buffer, and validates on Mar-Apr while May-Aug remain untouched. Fold 2 trains on Jan-Apr and validates on May-Jun. The third track trains on Jan-Jun and uses July exclusively for threshold calibration. The final track freezes the entire system and evaluates once on August, maintaining complete out-of-sample isolation.
Four-track walk-forward architecture. Each fold expands historical training data and validates chronologically. Purged embargo buffers prevent overlapping labels and autoregressive feature bleed across the split. August's final holdout is never touched until code, weights, and thresholds are locked.

In sequential production systems, chronological ordering alone isn't enough to prevent leakage. Two subtle mechanisms frequently corrupt temporal splits:

  1. Purging overlapping label horizons: In reality, outcome labels rarely arrive instantaneously. An access ticket opened on June 25 might take 14 days for a security audit to resolve, meaning its label arrives on July 9. If you train on data up to June 30 and validate on July, that late-June training row's label was shaped by events occurring inside the July validation window. Purging removes any training instance whose label resolution horizon extends into the validation window.
  2. Embargo windows for autoregressive features: Features frequently rely on backward-looking rolling aggregates, such as a user's 7-day request count or 30-day policy exception velocity. On July 1 at 00:05, a request's 7-day velocity is computed from raw events that occurred in late June, which the model already processed during training. Because of autoregression and shared underlying log events, data points immediately following the split boundary are not truly independent. An embargo window places a protective buffer (such as 7 days of unused data) immediately after the training cutoff to eliminate autoregressive feature bleed.
walk-forward-validation-windows.py
1months = [month for month in range(1, 7) for _ in range(2)] 2folds = [(1, 2, 3, 4), (1, 4, 5, 6)] 3 4for fold, (train_lo, train_hi, val_lo, val_hi) in enumerate(folds, start=1): 5 train = [m for m in months if train_lo <= m <= train_hi] 6 validation = [m for m in months if val_lo <= m <= val_hi] 7 assert max(train) < min(validation) 8 assert max(validation) <= 6 # July selects; August stays locked. 9 print( 10 f"fold {fold}: train <= {max(train)} " 11 f"validate = {min(validation)}..{max(validation)}" 12 )
Output
1fold 1: train <= 2 validate = 3..4 2fold 2: train <= 4 validate = 5..6

Each fold keeps validation chronologically after training. Refit the complete pipeline, including all encoders and scalers, independently from scratch inside each fold.

Time and group boundaries combined

A future validation month contains both returning and genuinely new users. If your business cares about both, evaluate and report them as distinct slices.

Repeated users aren't automatically leakage: prior user history is legitimate context for a returning-user prediction if that history existed before the decision moment. But a claim about performance on new users requires both a temporal boundary and a group boundary: no user in that validation slice can have prior history in training.

The fixture below freezes training at July 1. Request r2 opened in June, but its label arrives in August, so it can't enter July training. July traffic contains returning user u1 and new user u4.

audit-time-groups-and-label-availability.py
1from datetime import datetime 2 3# request, user, opening time, time its label becomes available 4rows = [ 5 ("r1", "u1", "2026-01-03", "2026-01-10"), 6 ("r2", "u2", "2026-06-20", "2026-08-02"), 7 ("r3", "u3", "2026-06-10", "2026-06-20"), 8 ("r4", "u1", "2026-07-03", "2026-07-10"), 9 ("r5", "u4", "2026-07-05", "2026-07-20"), 10] 11rows = [(r, u, datetime.fromisoformat(t), datetime.fromisoformat(l)) 12 for r, u, t, l in rows] 13fit_at = datetime(2026, 7, 1) 14end_at = datetime(2026, 8, 1) 15history = [row for row in rows if row[2] < fit_at] 16train = [row for row in history if row[3] <= fit_at] 17seen_users = {row[1] for row in history} 18july = [row for row in rows if fit_at <= row[2] < end_at] 19new_users = [row for row in july if row[1] not in seen_users] 20returning_users = [row for row in july if row[1] in seen_users] 21 22assert {row[1] for row in train}.isdisjoint(row[1] for row in new_users) 23print("training requests:", [row[0] for row in train]) 24print("returning-user July requests:", [row[0] for row in returning_users]) 25print("new-user July requests:", [row[0] for row in new_users])
Output
1training requests: ['r1', 'r3'] 2returning-user July requests: ['r4'] 3new-user July requests: ['r5']

User u2 is marked as previously seen even though its label isn't ready for training. Newness depends on past activity, not on whether an earlier ticket made it into the training split.

Nested selection and validation overuse

Validation data lets you choose features, tune regularizers, and calibrate decision thresholds. But unlimited search comes with a hidden cost: if you evaluate 5,000 hyperparameter combinations against the same validation split, pure sample noise will eventually win the contest.

Reporting that winning validation score as your expected production performance is another class of leakage: selection bias from validation overuse. Nested cross-validation prevents this by separating model selection from model evaluation:[1]Reference 1The Elements of Statistical Learning.https://hastie.su.domains/ElemStatLearn/

  1. Outer loop: Partitions data into KKK outer folds (or walk-forward temporal blocks) that never participate in tuning or model selection.
  2. Inner loop: For each outer fold, searches hyperparameters, features, and thresholds using internal cross-validation on the remaining development folds only.
  3. Outer evaluation: Fits the winning inner configuration on the development partition and scores it once on the untouched outer fold.

The average score across the outer folds estimates the generalized error of your entire modeling procedure, not of a specific parameter set chosen after inspecting outer results. If computing nested CV across large models is too expensive, preserve at least one locked, untouched temporal window at the end of your timeline. Treat heavy validation searches as exploratory, never as published claims.

Threshold calibration: validation chooses, test reports

A probabilistic classifier doesn't output binary decisions; it outputs a score or calibrated probability P(review∣x)∈[0,1]P(\text{review} \mid x) \in [0, 1]P(review∣x)∈[0,1]. To act in production, you must set an operating threshold θ\thetaθ such that y^=1\hat{y} = 1y^​=1 when P≥θP \ge \thetaP≥θ.

Choosing this threshold from your test set is target leakage: test labels directly shape the decision boundary.

In the lab below, July validation data evaluates three candidate thresholds: 0.300.300.30, 0.500.500.50, and 0.700.700.70. Only after selection is locked does August report one final test score. F1 for threshold θ\thetaθ uses predictions p≥θp \ge \thetap≥θ. Predict which threshold July selects before opening August.

choose-threshold-before-opening-test.py
1thresholds = [0.30, 0.50, 0.70] 2validation_probability = [0.82, 0.62, 0.58, 0.42, 0.31, 0.12] 3validation_label = [1, 1, 1, 0, 0, 0] 4test_probability = [0.77, 0.49, 0.39, 0.52, 0.66, 0.14] 5test_label = [1, 1, 0, 0, 1, 0] 6 7def f1_score(y, pred): 8 tp = sum(a == 1 and b == 1 for a, b in zip(y, pred)) 9 fp = sum(a == 0 and b == 1 for a, b in zip(y, pred)) 10 fn = sum(a == 1 and b == 0 for a, b in zip(y, pred)) 11 precision = tp / (tp + fp) if tp + fp else 0.0 12 recall = tp / (tp + fn) if tp + fn else 0.0 13 return 0.0 if precision + recall == 0 else 2 * precision * recall / (precision + recall) 14 15validation_f1 = { 16 threshold: f1_score( 17 validation_label, [int(p >= threshold) for p in validation_probability] 18 ) 19 for threshold in thresholds 20} 21chosen = max(validation_f1, key=validation_f1.get) 22test_f1 = f1_score(test_label, [int(p >= chosen) for p in test_probability]) 23 24for threshold, score in validation_f1.items(): 25 print(f"threshold={threshold:.2f} valid_f1={score:.2f}") 26print(f"chosen threshold={chosen:.2f} test_f1={test_f1:.2f}") 27assert validation_f1[0.50] == 1.0 28assert round(test_f1, 2) == 0.67
Output
1threshold=0.30 valid_f1=0.75 2threshold=0.50 valid_f1=1.00 3threshold=0.70 valid_f1=0.50 4chosen threshold=0.50 test_f1=0.67

Threshold 0.50 classifies all six July validation requests perfectly, scoring 1.00. Applying that frozen threshold to August yields two true positives, one false positive, and one false negative, producing an F1 of 4 / (4 + 1 + 1) = 0.67.

Six test rows are far too small for statistical certainty, but the drop illustrates an essential truth: your validation metric was an optimized search score; the locked test metric is your honest out-of-sample estimate. If you had reported July's 1.00 to stakeholders, you would have promised a mirage. If you tweak the threshold after seeing August's errors, you destroy the test set's integrity. Log the chosen threshold, feature contract, and locked test score together.

Modern relevance: benchmark contamination in large language models

The fundamental laws of data leakage don't disappear when tabular rows become prompt tokens, retrieval documents, or public evaluation benchmarks. Large language models (LLMs) face identical boundary failures across every layer of their architecture:

SystemLeakage pathHonest boundary
Retrieval-augmented generation (RAG)Chunks from one source document span both tuning sets and evaluation queriesAssign whole source documents before chunking
Fine-tuning & instruction tuningEvaluation questions, worked traces, or ground-truth answers enter SFT corporaEnforce dataset provenance and hash-based quarantine
Public benchmark evaluationBenchmark test problems (MMLU, GSM8K, HumanEval) exist in pretraining web scrapesDecontaminate pretraining corpora via n-gram filtering and test on fresh dynamic benchmarks

For an enterprise access-policy assistant, determine exactly what you're evaluating. If your claim is generalization to new policy manuals, keep each document and its derived queries together. If your claim is answering new questions about an existing, static knowledge base, retrieving from shared documents is intended production behavior, not leakage. Leaking occurs when evaluation queries or reference answers contaminate prompt tuning or embedding models.

The fixture below verifies document-level isolation. Alternating chunks shares every source document across train and test, whereas partitioning complete source pages first shares zero documents.

split-documents-before-chunking.py
1documents = { 2 "policy_access": [ 3 "temporary admin roles need review", 4 "manager approval may support access grant", 5 ], 6 "policy_incidents": [ 7 "sev2 incidents need incident commander approval", 8 "service-owner approval precedes production access", 9 ], 10 "policy_admins": [ 11 "privileged role appeal needs audit", 12 "audit stores decision reason", 13 ], 14} 15chunks = [(document, chunk) for document, texts in documents.items() for chunk in texts] 16 17bad_train = chunks[::2] 18bad_test = chunks[1::2] 19bad_overlap = sorted( 20 {document for document, _ in bad_train} & {document for document, _ in bad_test} 21) 22 23safe_train_documents = {"policy_access", "policy_incidents"} 24safe_test_documents = {"policy_admins"} 25safe_train = [(doc, text) for doc, texts in documents.items() 26 if doc in safe_train_documents for text in texts] 27safe_test = [(doc, text) for doc, texts in documents.items() 28 if doc in safe_test_documents for text in texts] 29safe_overlap = sorted({doc for doc, _ in safe_train} & {doc for doc, _ in safe_test}) 30 31print("split chunks first shared documents =", bad_overlap) 32print("split documents first shared documents =", safe_overlap) 33assert bad_overlap == ["policy_access", "policy_admins", "policy_incidents"] 34assert safe_overlap == []
Output
1split chunks first shared documents = ['policy_access', 'policy_admins', 'policy_incidents'] 2split documents first shared documents = []

At foundation scale, pretraining corpora swallow trillions of tokens from Common Crawl, GitHub, and books. Those web scrapes frequently contain the exact test splits of standard benchmarks: MMLU, HumanEval, GSM8K, and MATH. A model that achieves 90% on coding benchmarks may simply be recalling memorized repository files rather than demonstrating generalizable program synthesis.

Teams detect and mitigate benchmark contamination using three complementary strategies:

  1. Exact N-gram overlap filtering: An n-gram is a sequence of nnn consecutive tokens or words. GPT-3 analyzed contamination by checking word overlaps up to 13 words between training data and benchmark tasks.[5]Reference 5Language Models are Few-Shot Learners.https://arxiv.org/abs/2005.14165 Meta's Llama 3 technical report used 8-gram overlap filtering, discovering that 85% of HellaSwag was contaminated in the pretraining mix, which artificially inflated the 8B model's score by an estimated 14.8 points.[6]Reference 6The Llama 3 Herd of Models.https://arxiv.org/abs/2407.21783
  2. MinHash and Locality-Sensitive Hashing (LSH): Exact n-grams miss minor rewording. MinHash algorithms find near-duplicate documents and paraphrased questions efficiently across multi-terabyte datasets.
  3. Perplexity and loss anomaly auditing: If an LLM exhibits an abrupt drop in cross-entropy loss (or perplexity) on benchmark items compared to structurally similar held-out text, it has likely memorized that exact sequence during pretraining.

To stay ahead of web-scrape contamination, modern benchmarks like LiveBench[7]Reference 7LiveBench: A Challenging, Contamination-Limited LLM Benchmarkhttps://arxiv.org/abs/2406.19314 and LiveCodeBench[8]Reference 8LiveCodeBench: Holistic and Contamination Free Evaluation of Large Language Models for Codehttps://arxiv.org/abs/2403.07974 continuously harvest new contest problems and news sources published strictly after model release dates.

The local smoke test below searches for exact 5-gram overlap between training and evaluation prompts:

ngram-contamination-smoke-test.py
1training_prompts = [ 2 "route privileged access request with required review to human queue", 3 "approve access grant after clear manager approval arrives", 4] 5evaluation_prompts = [ 6 "route privileged access request with required review to human queue", 7 "send an ambiguous admin request to a reviewer", 8] 9 10def ngrams(text, size=5): 11 words = text.lower().split() 12 return { 13 " ".join(words[start : start + size]) 14 for start in range(len(words) - size + 1) 15 } 16 17training_grams = set().union(*(ngrams(prompt) for prompt in training_prompts)) 18for prompt in evaluation_prompts: 19 overlaps = ngrams(prompt) & training_grams 20 status = "FLAG" if overlaps else "NO EXACT MATCH" 21 print(f"{status}: {prompt}")
Output
1FLAG: route privileged access request with required review to human queue 2NO EXACT MATCH: send an ambiguous admin request to a reviewer

Exact matching flags verbatim copy-paste. It can't catch paraphrases, translations, reworded questions, or syntactically altered code. Conversely, boilerplate phrases like "please review the attached log" can trigger false positives. Use n-gram filters as an initial sieve, paired with semantic embeddings and provenance tracking.

Historical logs don't reveal unchosen actions

Our labs evaluate a supervised guardrail: given request context available at opening, predict whether compliance review was required. That setup doesn't estimate the business return of a brand-new sequential decision policy.

Historical server logs tell you what happened under the action the legacy router took. They can't tell you what would have happened if the router took a different action for that same request. Those missing results are counterfactual outcomes.

In reinforcement learning, off-policy evaluation (OPE) estimates the value of a new policy using logs generated by a historical policy. OPE requires strong assumptions beyond temporal splitting, most notably common support (action coverage): the historical policy must have chosen the new policy's actions with non-zero probability in similar contexts. Logging action propensities (the probability with which the logging policy chose each option) is essential, but it can't recover outcomes for actions that were never explored.[9]Reference 9Reinforcement Learning: An Introductionhttp://incompleteideas.net/book/the-book-2nd.html

A simulated environment can supply synthetic counterfactual outcomes, but the simulator itself requires validation. To measure true policy impact, pair offline supervised audits with staged canary deployments and randomized online experiments.

Write the production evaluation contract

Before publishing any benchmark or shipping an access guardrail, document your boundaries in a concrete contract:

access-review-evaluation-contract.txt
1Decision moment: request opening, before evidence request or reviewer action 2Allowed features: ambiguity_score, policy_support, prior_policy_exceptions_90d 3Forbidden features: manager_approval_received_at, reviewer_action, access_revoked_after_audit 4Train: January-June requests 5Training labels: available by the model-fit cutoff, not merely attached later 6Validation: July requests; tune thresholds, encoders, and feature selectors here 7Test: August requests; open once after all modeling choices are frozen 8Label timing: July labels must be ready before freezing the August system 9Entity promise: report future returning-user and genuinely new-user slices separately 10Preprocessing: every fit step uses training-fold rows only inside a Pipeline 11Feature snapshot: as-of request opening; aggregation windows terminate before decision time 12Metrics: review-required F1, missed-review count, and false-alarm triage load

A benchmark metric is only as honest as the contract that generated it.

Stress-test the contract

Test your evaluation harness by intentionally breaking one assumption at a time. Predict the failure symptom before comparing it with the analysis below:

  1. Add reviewer_action to the feature availability audit and assign an observation time. Explain why it's label-adjacent target leakage.
  2. Modify group-leakage-from-repeat-users.py so the product promise is performance on returning users rather than new users. Does row-wise splitting answer that question, and what time boundary remains critical?
  3. Add a neutral column N = [0, 1, 1, 0, 0, 1, 1, 0] to the feature selector's dictionary. Calculate its class-mean gaps and predict whether either selected column changes.
  4. Extend the document-chunk lab with a paraphrased evaluation chunk. Why does the exact n-gram matcher miss it, and how would you detect it?
  5. Write an evaluation contract for the reinforcement learning router. Mark which outcomes are directly observed and which are counterfactual.

What each mutation exposes

  1. reviewer_action is logged only after human triage begins. It belongs in forensic outcome analysis, never in opening-time feature vectors.
  2. A row-wise split can evaluate returning users if prior history was legitimately available at inference time. You must still enforce a strict temporal boundary so later requests don't leak backward into earlier training instances.
  3. Feature N has class mean 0.5 for both label classes across all rows. Its separation gap is exactly zero: column T still wins honest training selection, and column L still wins leaky global selection.
  4. Paraphrases preserve semantic intent while altering exact token sequences. Catch them using MinHash near-duplicate clustering, embedding cosine similarity, or manual audit of high-risk test pairs.
  5. In an RL contract, historical rewards are observed only for the actions chosen by the production logging policy. Rewards for alternative actions proposed by the new candidate policy are counterfactual and require importance-sampled off-policy estimators.
Complete the lesson

Mastery Check

Answer every question, then check your score. Score 75% or higher to mark this lesson complete.

1.An access guardrail scores REQ-10234 at 09:00. Ambiguity and prior exceptions are known then; manager approval and audit outcomes arrive later. Which feature contract is valid?

Correct answer: Allow ambiguity_score and prior_policy_exceptions_90d; use needs_review only as the label; block manager_approval_received_at and access_revoked_after_audit.

The feature boundary is the decision moment, not what appears in the completed record. Opening-time features may use information already known, while later events, outcomes, and the label itself must not enter features or learned preprocessing.

2.Requests span months 1 through 8. July selects the review threshold and August is the locked test. Which development-fold design preserves both time order and those reserved roles?

Correct answer: Train on months 1-2 and validate on 3-4, then train on 1-4 and validate on 5-6; keep August outside development.

Every development validation window follows its training window and remains inside January-June. Using July-August as another development fold would spend the reserved threshold-selection and test windows. Time ordering alone doesn't preserve a locked test if model choices still use it.

3.Five earlier-history folds for a review guardrail have F1 scores 0.67, 0.75, 0.58, 0.83, and 0.71. The team wants to pick a model before opening the locked August test. What is the right use of this result?

Correct answer: Treat the mean F1 of about 0.71 as development evidence, note the 0.09 fold spread, and inspect the weak fold before high-cost automation.

The mean summarizes development-fold performance. The standard deviation describes variation, not a confidence interval or standard error; overlapping training sets can correlate fold scores. Keep the weak fold and the locked-test boundary intact.

4.A team promises new-user performance. A user-id lookup scores 1.00 when one of each user's two rows is placed on each side, but 0.50 when whole users are held out. Which statement is defensible?

Correct answer: Use the user-group evaluation for the new-user claim; the overlapping-row score measures known-user memorization.

A split must match the deployment promise. If the claim is new-user performance, the same user cannot appear on both sides of the evaluation boundary, because the model may memorize user-specific patterns rather than generalize.

5.Eight rows have columns T and L. T copies the four training labels and is mixed on validation. L is weak on training and copies the four validation labels. Mean-gap selection on all eight rows keeps L and a train-fitted nearest-mean rule then scores 1.00 on validation. What conclusion follows?

Correct answer: Validation labels influenced which column survived; select on training rows only, then apply that frozen column to validation.

Feature selection is a learned preprocessing step. If it is fitted before the split, held-out labels can influence which columns are selected, creating an inflated score even though the final classifier never directly sees test labels.

6.July validation labels give F1 scores of 0.75, 1.00, and 0.50 for review thresholds 0.30, 0.50, and 0.70. August is the locked test month. Which procedure preserves a valid final estimate?

Correct answer: Freeze threshold 0.50 from July, then report August F1 once without retuning on August errors.

Validation data is allowed to shape the decision rule, including threshold choice. The locked test set is for one final estimate after those choices are frozen. If August labels influence the threshold or feature revisions, August has become development data and a later untouched window is needed for the final score.

7.An access-policy RAG corpus has three source pages, each split into two chunks. The team alternates chunks into tuning and test sets, so every source page has one chunk in each set. To avoid document-level leakage and test whether the system generalizes beyond sources used during tuning, what should the team change?

Correct answer: Assign whole source pages to tuning or test before chunking, and keep provenance so no page crosses the boundary.

For RAG evaluation, the relevant boundary can be the source document, not the individual chunk. If chunks from the same page appear in tuning and test, the system may look as if it generalizes while relying on near-copy material from documents already used during development.

8.An LLM evaluation set has one prompt with exact five-word overlap with a training prompt and another paraphrase with no shared five-word sequence. How should the contamination check be used?

Correct answer: Flag the exact overlap for investigation, but do not treat the paraphrase as certified clean; use provenance, semantic near-duplicate search, and manual review.

Exact n-gram overlap is a cheap smoke test, not a proof of clean evaluation. It can catch copied wording, but paraphrases, translations, and memorized solution patterns may evade exact matching, so provenance and semantic or manual review are still needed.

9.Historical router logs contain each request's opening context, the action the old router took, and the reward observed after resolution. A new RL policy would choose a different action for some August requests. What limitation remains even with a chronological train/test split?

Correct answer: The split blocks future leakage, but rewards for unchosen actions are still counterfactual and need stronger evaluation.

A time split doesn't reveal outcomes under actions the old policy never took. Off-policy estimation needs action coverage and other assumptions; propensities alone can't recover actions with zero historical probability. Simulator validation or staged online evidence may be needed.

10.A model is frozen on July 1. One request opened June 20, but its audit label becomes available August 2. How should a faithful July evaluation use this row?

Correct answer: Exclude its label from July training; an old request isn't trainable before the required outcome is available.

The request-time cutoff and label-availability cutoff answer different questions. The July model can't use an August label. Preserve the real timestamps and omit that labeled training example until its outcome is available.

10 questions remaining.

Next Step
Continue to Statistical Learning and Generalization

Honest splits prevent information from crossing the evaluation boundary. Next you'll examine why finite samples still produce uncertainty, how model complexity changes generalization, and which evidence a held-out score can actually support.

PreviousReinforcement Learning Basics
Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

The Elements of Statistical Learning.

Hastie, T., Tibshirani, R., Friedman, J. · 2009

https://hastie.su.domains/ElemStatLearn/

Cross-validation: evaluating estimator performance

scikit-learn Developers · 2026

https://scikit-learn.org/stable/modules/cross_validation.html

Common Pitfalls and Recommended Practices: Data Leakage

Scikit-learn Developers · 2026 · Official documentation

https://scikit-learn.org/stable/common_pitfalls.html#data-leakage

Forecasting: Principles and Practice, Third Edition.

Hyndman, R. J. & Athanasopoulos, G. · 2021

https://otexts.com/fpp3/

Language Models are Few-Shot Learners.

Brown, T., et al. · 2020 · NeurIPS 2020

https://arxiv.org/abs/2005.14165

The Llama 3 Herd of Models.

Dubey, A., et al. · 2024 · arXiv preprint

https://arxiv.org/abs/2407.21783

LiveBench: A Challenging, Contamination-Limited LLM Benchmark

White, C., et al. · 2024

https://arxiv.org/abs/2406.19314

LiveCodeBench: Holistic and Contamination Free Evaluation of Large Language Models for Code

Jain, N., et al. · 2024

https://arxiv.org/abs/2403.07974

Reinforcement Learning: An Introduction

Sutton, R. S. and Barto, A. G. · 2018 · MIT Press

http://incompleteideas.net/book/the-book-2nd.html

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.