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 178 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/8
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningStatistics 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/11
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageClustering and PCACore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingPyTorch 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/24
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsContext EngineeringPrompt Injection DefenseResponsible AI GovernanceData 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/19
Inference: TTFT, TPS & KV CacheMulti-Query & Grouped-Query AttentionKV Cache & PagedAttentionPrefix Caching and Prompt CachingFlashAttention & Memory EfficiencyContinuous Batching & SchedulingScaling LLM InferenceModel Parallelism for LLM InferenceModel 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 & AutoscalingA/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
LearnProduction ML SystemsGradient Boosted Trees in Production
⚡MediumFine-Tuning & Training

Gradient Boosted Trees in Production

Train a boosted SLA-risk baseline from tabular features, evaluate slices, and package deployment evidence.

14 min read
Learning path
Step 44 of 178 in the full curriculum
Batch and Streaming Feature PipelinesRanking and Recommendation Systems

Personalize this lesson

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

Training-job rows now have input size, heartbeat age, backlog, priority class, and a later label for whether the job missed its SLA (service-level agreement). Those rows still aren't a decision. You need a score for upcoming jobs and a published rule for when to intervene.

A gradient boosted tree is a strong default on this kind of table.[1]Reference 1Why do tree-based models still outperform deep learning on typical tabular data?https://arxiv.org/abs/2207.08815 You already saw the mechanics in Decision Trees, Forests, and Boosting: each new tree fits leftover error. A candidate ships only when it beats a declared baseline on later jobs, survives required slices, and travels with enough evidence to reproduce the call.

Freeze the clock before training

Suppose missed_sla = 1 means a training job missed its promised finish time. Before any fitting, publish a time-ordered split:

SplitCalendar rangePurpose
trainJanuary through Marchfit model
validationAprilselect round count and threshold
testMayfinal evidence after those choices freeze

A random shuffle can put the same disruption into train and test. Calendar order is closer to facing tomorrow's jobs. It still doesn't erase every dependency. If one incident can contribute several rows, document the split unit and keep those rows together whenever splitting them would make holdout look easier than production.

scikit-learn's leakage page says the same thing: don't train on information that wouldn't be available at prediction time.[2]Reference 2Common Pitfalls and Recommended Practices: Data Leakagehttps://scikit-learn.org/stable/common_pitfalls.html#data-leakage The point-in-time replay from Batch and Streaming Feature Pipelines is how this table stays honest. A row created after its decision timestamp is feature leakage and doesn't belong in training.

Diagram showing Jan-Mar rows point-in-time, April freeze rounds + threshold, May scored once, and Required slices.
Jan-Mar rows point-in-time, April freeze rounds + threshold, May scored once, and Required slices.

Build a deterministic fixture that follows that calendar. Labels depend on heartbeat age, backlog, service tier, a large-model flag derived from input size, and a little noise. May also gets a small drift term, so the test month isn't a copy of training.

build-monthly-fixture.py
1import json 2from hashlib import sha256 3 4import numpy as np 5from sklearn.ensemble import GradientBoostingClassifier 6from sklearn.metrics import log_loss, roc_auc_score 7 8rng = np.random.default_rng(11) 9FEATURES = [ 10 "input_gb", 11 "heartbeat_age_hours", 12 "backlog", 13 "priority", 14 "large_model", 15] 16 17def sigmoid(value): 18 return 1 / (1 + np.exp(-value)) 19 20def make_jobs(month, count, drift=0.0): 21 input_gb = rng.integers(40, 1601, count) 22 heartbeat_age = rng.integers(1, 41, count) 23 backlog = rng.integers(0, 31, count) 24 priority = rng.integers(0, 2, count) 25 large_model = (input_gb >= 900).astype(int) 26 logit = ( 27 -5.2 28 + 0.13 * heartbeat_age 29 + 0.07 * backlog 30 + 0.85 * priority 31 + 0.75 * large_model 32 + drift 33 ) 34 missed_sla = rng.binomial(1, sigmoid(logit)) 35 X = np.column_stack([input_gb, heartbeat_age, backlog, priority, large_model]) 36 return {"month": month, "X": X, "y": missed_sla} 37 38train = make_jobs("Jan-Mar", 360) 39valid = make_jobs("April", 140) 40test = make_jobs("May", 140, drift=0.18) 41 42for split in (train, valid, test): 43 print(f"{split['month']}: rows={len(split['y'])} missed_sla={int(split['y'].sum())}")
Output
1Jan-Mar: rows=360 missed_sla=119 2April: rows=140 missed_sla=58 3May: rows=140 missed_sla=55

The fixture is synthetic so you can rerun the whole lab. In a real job, X has to come from that point-in-time contract, not from a table snapshot taken after the decision.

Publish the cheapest baseline

Your first candidate can be a rule: predict an SLA miss when heartbeat_age_hours >= 18. It's weak, and that's the point. The boosted model has to beat something you'd actually run, not get credit for any nonzero score.

Define one cost policy before comparing models:

  • A missed priority job costs 150.
  • A missed standard job costs 60.
  • A false alarm costs 8.

The exact numbers will differ by product. Publishing them matters because model quality is inseparable from the action the score triggers.

score-rule-baseline.py
1def confusion(y, predicted): 2 return { 3 "tp": int(np.sum((y == 1) & (predicted == 1))), 4 "fp": int(np.sum((y == 0) & (predicted == 1))), 5 "fn": int(np.sum((y == 1) & (predicted == 0))), 6 "tn": int(np.sum((y == 0) & (predicted == 0))), 7 } 8 9def cost_stats(split, scores, threshold): 10 predicted = (scores >= threshold).astype(int) 11 priority = split["X"][:, FEATURES.index("priority")] == 1 12 missed = (split["y"] == 1) & (predicted == 0) 13 false_alarm = (split["y"] == 0) & (predicted == 1) 14 return { 15 "threshold": threshold, 16 "cost": int( 17 150 * np.sum(missed & priority) 18 + 60 * np.sum(missed & ~priority) 19 + 8 * np.sum(false_alarm) 20 ), 21 "priority_misses": int(np.sum(missed & priority)), 22 **confusion(split["y"], predicted), 23 } 24 25heartbeat_age_index = FEATURES.index("heartbeat_age_hours") 26rule_valid = (valid["X"][:, heartbeat_age_index] >= 18).astype(int) 27print("rule validation:", cost_stats(valid, rule_valid.astype(float), threshold=0.50))
Output
1rule validation: {'threshold': 0.5, 'cost': 1560, 'priority_misses': 8, 'tp': 48, 'fp': 30, 'fn': 10, 'tn': 52}

The rule already catches 48 of April's 58 SLA misses, at cost 1560. Extra trees have to improve that decision, not just emit a prettier probability.

Why publish the heartbeat-age rule before evaluating the boosted model?

Answer

The cheap rule establishes the decision quality already available without model complexity. The booster must beat that baseline under the same held-out slice and cost policy.

Rebuild one boosting step by hand

A shallow decision tree partitions rows into a few rules. Gradient boosting adds those trees one at a time: each new tree moves predictions toward errors left by the current ensemble. Friedman describes this as stage-wise function approximation using loss gradients.[3]Reference 3Greedy Function Approximation: A Gradient Boosting Machinehttps://projecteuclid.org/journals/annals-of-statistics/volume-29/issue-5/Greedy-Function-Approximation--A-Gradient-Boosting-Machine/10.1214/aos/1013203451.full

For intuition, switch from the binary SLA label to delay hours on three jobs:

LaneActual delayFirst predictionResidual
local standard26-4
regional standard86+2
large-model economy206+14

A small correction tree might add little for local jobs and more for long economy workloads. For squared-error regression, its training target is the residual actual - prediction. A learning rate applies only part of that correction, so one noisy tree can't dominate.

apply-one-residual-correction.py
1actual_delay = np.array([2.0, 8.0, 20.0]) 2prediction = np.array([6.0, 6.0, 6.0]) 3large_model = np.array([0, 0, 1]) 4residual = actual_delay - prediction 5 6correction = np.where( 7 large_model == 1, 8 residual[large_model == 1].mean(), 9 residual[large_model == 0].mean(), 10) 11learning_rate = 0.25 12updated = prediction + learning_rate * correction 13 14print("residuals:", residual.tolist()) 15print("tree correction:", correction.tolist()) 16print("before mae:", round(float(np.mean(np.abs(actual_delay - prediction))), 2)) 17print("after mae:", round(float(np.mean(np.abs(actual_delay - updated))), 2))
Output
1residuals: [-4.0, 2.0, 14.0] 2tree correction: [-1.0, -1.0, 14.0] 3before mae: 6.67 4after mae: 5.5

The tree proposed -1 hour for shorter workloads and 14 hours for the large-model job. Shrinkage kept only a quarter of that move, so the large-model prediction went from 6 to 9.5, not all the way to 20. One round lowers mean absolute error without pretending the first correction is finished.

Friedman writes that update as stage-wise addition with shrinkage. If Fm−1F_{m-1}Fm−1​ is the current score and hmh_mhm​ is the new tree,

Fm(x)=Fm−1(x)+ν hm(x)F_m(x) = F_{m-1}(x) + \nu \, h_m(x)Fm​(x)=Fm−1​(x)+νhm​(x)

where ν\nuν is the learning rate (here 0.250.250.25).[3]Reference 3Greedy Function Approximation: A Gradient Boosting Machinehttps://projecteuclid.org/journals/annals-of-statistics/volume-29/issue-5/Greedy-Function-Approximation--A-Gradient-Boosting-Machine/10.1214/aos/1013203451.full

Classification needs one extra translation. An SLA-risk classifier doesn't fit delay-hour residuals. With log loss, each new tree fits the negative gradient of that loss. At an initial probability of 0.50, that negative binary log-loss gradient is gold - probability. Equivalently, the score gradient ∂L/∂F\partial L/\partial F∂L/∂F is probability - gold.

inspect-log-loss-gradient.py
1gold = np.array([0.0, 1.0, 1.0]) 2probability = np.full(3, 0.50) 3negative_gradient = gold - probability 4 5print("initial probability:", probability.tolist()) 6print("gold - probability:", negative_gradient.tolist())
Output
1initial probability: [0.5, 0.5, 0.5] 2gold - probability: [-0.5, 0.5, 0.5]

Negative values push SLA-risk scores down. Positive values push them up. A real classification booster repeats this across many rows and trees.

When the table gets large or contains high-cardinality categoricals, production teams often choose between three battle-tested boosting systems:

  • XGBoost: Chen and Guestrin built it with a regularized objective (γT+12λ∑wj2\gamma T + \frac{1}{2}\lambda \sum w_j^2γT+21​λ∑wj2​), sparsity-aware split finding that learns default directions for missing values, weighted quantile sketching for approximate splits, and cache-aware column blocks for parallel split finding.[4]Reference 4XGBoost: A Scalable Tree Boosting System.https://www.kdd.org/kdd2016/papers/files/rfp0697-chenAemb.pdf
  • LightGBM: Ke et al. optimized training throughput using histogram-based feature binning, leaf-wise (best-first) tree growth with a max-depth limit, and native integer categorical handling via optimal histogram partitioning without one-hot expansion.[5]Reference 5LightGBM: A Highly Efficient Gradient Boosting Decision Tree.https://papers.nips.cc/paper/2017/hash/6449f44a102fde848669bdd9eb6b76fa-Abstract.html
  • CatBoost: Prokhorenkova et al. implemented symmetric (oblivious) trees for fast inference, ordered boosting to prevent target leakage during target encoding, and automated feature combinations for high-cardinality categoricals.[6]Reference 6CatBoost: unbiased boosting with categorical features.https://arxiv.org/abs/1706.09516

The evidence contract doesn't change with the library. When downstream decision policies depend on well-calibrated probabilities rather than rank order alone, evaluate calibration (reliability curves, Brier score) or apply post-hoc calibration like isotonic regression or Platt scaling on the validation split before freezing the threshold policy.[7]Reference 7Predicting Good Probabilities With Supervised Learning.https://dl.acm.org/doi/10.1145/1102351.1102430

Fit a booster with an explicit stopping rule

This lab uses scikit-learn's GradientBoostingClassifier so the code stays small. It builds an additive model stage by stage and fits each tree to the negative gradient of the chosen loss, here log_loss. A larger job might use XGBoost. You'd still freeze rounds on April, not on training loss.

Fix tree depth and learning rate first. Train a generous maximum number of rounds on January through March, then inspect April loss after each stage.

Don't use sklearn's validation_fraction helper for this split. That flag randomly holds out rows from the training matrix. Here the validation slice is a later month, so you own the loop with staged_predict_proba on April.

fit-round-search.py
1round_search = GradientBoostingClassifier( 2 loss="log_loss", 3 n_estimators=160, 4 learning_rate=0.05, 5 max_depth=2, 6 random_state=11, 7) 8round_search.fit(train["X"], train["y"]) 9print("trained stage cap:", round_search.n_estimators_)
Output
1trained stage cap: 160
choose-early-stop-round.py
1train_losses = [ 2 log_loss(train["y"], probabilities[:, 1], labels=[0, 1]) 3 for probabilities in round_search.staged_predict_proba(train["X"]) 4] 5validation_losses = [ 6 log_loss(valid["y"], probabilities[:, 1], labels=[0, 1]) 7 for probabilities in round_search.staged_predict_proba(valid["X"]) 8] 9best_round = int(np.argmin(validation_losses) + 1) 10 11print("round 1 train / april:", round(train_losses[0], 4), round(validation_losses[0], 4)) 12print("best round:", best_round) 13print("round 50 train / april:", round(train_losses[best_round - 1], 4), round(validation_losses[best_round - 1], 4)) 14print("round 160 train / april:", round(train_losses[-1], 4), round(validation_losses[-1], 4))
Output
1round 1 train / april: 0.6163 0.6805 2best round: 50 3round 50 train / april: 0.3491 0.5643 4round 160 train / april: 0.2684 0.5922

Training loss keeps falling through round 160. April loss bottoms at round 50, then gets worse. Extra trees still helped the training objective. They stopped helping future jobs. That's the overfitting symptom early stopping is for.

April validation log loss versus boosting round, plotted on the same axes as January-March training log loss. Training loss falls from 0.6163 at round 1 through 0.3491 at round 50 to 0.2684 at round 160, while April loss bottoms at 0.5643 at round 50 then rises to 0.5922 at round 160. A dashed marker at round 50 shows the freeze point before May is opened.
January-March log loss keeps dropping after round 50, from 0.3491 to 0.2684. April log loss turns up from 0.5643 to 0.5922, so the production freeze is the April minimum, not the training cap.

Some libraries expose built-in early stopping flags. A production job still needs to record the exact validation slice, monitored metric, patience policy, and selected round count. Here the rule is visible: choose the stage with minimum April log loss, then fit the candidate with that frozen count.

fit-frozen-candidate.py
1candidate = GradientBoostingClassifier( 2 loss="log_loss", 3 n_estimators=best_round, 4 learning_rate=0.05, 5 max_depth=2, 6 random_state=11, 7) 8candidate.fit(train["X"], train["y"]) 9valid_scores = candidate.predict_proba(valid["X"])[:, 1] 10 11print("frozen rounds:", candidate.n_estimators_) 12print("rule validation auc:", round(roc_auc_score(valid["y"], rule_valid), 3)) 13print("boosted validation auc:", round(roc_auc_score(valid["y"], valid_scores), 3))
Output
1frozen rounds: 50 2rule validation auc: 0.731 3boosted validation auc: 0.792

The booster improves April AUC from 0.731 to 0.792. Area under the receiver operating characteristic curve (AUC) summarizes ranking quality across thresholds. It doesn't choose an operations policy.

Validation loss is lowest at round 50 and rises through round 160. Which round count should the frozen candidate use?

Answer

Use round 50, selected on the declared validation slice. Continuing to the training cap would keep trees that improved training fit but worsened held-out loss.

Measure threshold cost, not AUC alone

The classifier outputs an SLA-risk score. Operations still needs a choice: intervene, notify, or leave the job on its normal path. A missed SLA on priority jobs may be more expensive than an unnecessary proactive notification.

Search candidate thresholds on April only. The sort key minimizes declared cost and uses the lower threshold as a tie-breaker.

choose-sla-threshold.py
1thresholds = [0.15, 0.20, 0.25, 0.30, 0.40] 2for threshold in thresholds: 3 stats = cost_stats(valid, valid_scores, threshold) 4 print( 5 f"threshold={threshold:.2f} cost={stats['cost']} " 6 f"priority_misses={stats['priority_misses']}" 7 ) 8 9selected_threshold = min( 10 thresholds, 11 key=lambda threshold: (cost_stats(valid, valid_scores, threshold)["cost"], threshold), 12) 13april_rule = cost_stats(valid, rule_valid.astype(float), threshold=0.50) 14april_boosted = cost_stats(valid, valid_scores, selected_threshold) 15print("selected threshold:", selected_threshold) 16print("april rule cost:", april_rule["cost"]) 17print("april boosted cost:", april_boosted["cost"])
Output
1threshold=0.15 cost=1316 priority_misses=6 2threshold=0.20 cost=1884 priority_misses=10 3threshold=0.25 cost=1912 priority_misses=10 4threshold=0.30 cost=2038 priority_misses=11 5threshold=0.40 cost=2604 priority_misses=14 6selected threshold: 0.15 7april rule cost: 1560 8april boosted cost: 1316

Threshold 0.15 beats the heartbeat-age rule on April (1316 vs 1560) and sits far below the common default of 0.50. Missing a priority SLA is expensive in this policy, so the model accepts more false alarms. If alert fatigue mattered more, the cost table would need another term.

Now freeze the round count, threshold, and cost policy. May can be opened once for final candidate evidence.

score-frozen-test-month.py
1test_scores = candidate.predict_proba(test["X"])[:, 1] 2test_stats = cost_stats(test, test_scores, selected_threshold) 3rule_test = (test["X"][:, heartbeat_age_index] >= 18).astype(int) 4may_rule = cost_stats(test, rule_test.astype(float), threshold=0.50) 5 6print("test auc:", round(roc_auc_score(test["y"], test_scores), 3)) 7print("may rule cost:", may_rule["cost"]) 8print("test policy:", test_stats)
Output
1test auc: 0.882 2may rule cost: 1044 3test policy: {'threshold': 0.15, 'cost': 728, 'priority_misses': 2, 'tp': 50, 'fp': 31, 'fn': 5, 'tn': 54}

May can look better or worse than April. Here the frozen booster still beats the same heartbeat-age rule (728 vs 1044). You don't retune. The job of May is to confirm the frozen policy, not to pick a new one. Aggregate AUC and cost can still hide a serious workload failure.

Gate slices before promotion

Pick required slices before reading test results. A slice gate also needs enough positive cases to mean anything: perfect recall on one SLA-miss job is weak evidence, and an empty slice can't produce recall at all. This lab requires at least 10 SLA-miss jobs per slice, recall of at least 0.90 for priority jobs, and recall of at least 0.85 for large-model jobs.

check-required-slices.py
1def slice_gate(split, scores, threshold, name, mask, minimum_recall, minimum_positives): 2 predicted = (scores >= threshold).astype(int) 3 stats = confusion(split["y"][mask], predicted[mask]) 4 positives = stats["tp"] + stats["fn"] 5 measured_recall = stats["tp"] / positives if positives else 0.0 6 enough_support = positives >= minimum_positives 7 passed = enough_support and measured_recall >= minimum_recall 8 result = { 9 "name": name, 10 "rows": int(np.sum(mask)), 11 "positives": positives, 12 "recall": round(measured_recall, 3), 13 "minimum_recall": minimum_recall, 14 "minimum_positives": minimum_positives, 15 "confusion": stats, 16 "passed": passed, 17 } 18 print( 19 f"{name}: positives={positives} recall={measured_recall:.3f} " 20 f"minimum_recall={minimum_recall:.2f} " 21 f"minimum_positives={minimum_positives} pass={passed}" 22 ) 23 return result 24 25priority_index = FEATURES.index("priority") 26large_model_index = FEATURES.index("large_model") 27required_slice_results = [ 28 slice_gate( 29 test, 30 test_scores, 31 selected_threshold, 32 "priority", 33 test["X"][:, priority_index] == 1, 34 minimum_recall=0.90, 35 minimum_positives=10, 36 ), 37 slice_gate( 38 test, 39 test_scores, 40 selected_threshold, 41 "large_model", 42 test["X"][:, large_model_index] == 1, 43 minimum_recall=0.85, 44 minimum_positives=10, 45 ), 46] 47required_slices_pass = all(result["passed"] for result in required_slice_results) 48print("all required slices pass:", required_slices_pass)
Output
1priority: positives=36 recall=0.944 minimum_recall=0.90 minimum_positives=10 pass=True 2large_model: positives=31 recall=0.903 minimum_recall=0.85 minimum_positives=10 pass=True 3all required slices pass: True

Both required slices pass for the frozen candidate. Keep the gate anyway. A future retrain, feature drift, or threshold edit can break one segment while aggregate metrics still look acceptable.

To see the failure symptom, test a careless threshold edit from 0.15 to 0.50.

reproduce-threshold-regression.py
1bad_threshold = 0.50 2bad_slice_results = [ 3 slice_gate( 4 test, 5 test_scores, 6 bad_threshold, 7 "priority", 8 test["X"][:, priority_index] == 1, 9 minimum_recall=0.90, 10 minimum_positives=10, 11 ), 12 slice_gate( 13 test, 14 test_scores, 15 bad_threshold, 16 "large_model", 17 test["X"][:, large_model_index] == 1, 18 minimum_recall=0.85, 19 minimum_positives=10, 20 ), 21] 22print("release blocked:", not all(result["passed"] for result in bad_slice_results))
Output
1priority: positives=36 recall=0.722 minimum_recall=0.90 minimum_positives=10 pass=False 2large_model: positives=31 recall=0.645 minimum_recall=0.85 minimum_positives=10 pass=False 3release blocked: True

Nothing about the fitted trees changed. The threshold edit alone drops priority recall from 0.944 to 0.722. Versioning only the model file would miss the regression.

Why doesn't a higher AUC choose the production intervention threshold?

Answer

AUC measures ordering across thresholds. Operations still needs a threshold chosen against concrete false-alarm and missed-SLA costs, including critical slices such as priority jobs.

Ship a model that can be operated

The candidate should export:

ArtifactWhy it matters
feature_contract.jsonproves column meanings and time boundary
split_manifest.jsonproves evaluation wasn't random or leaky
model.skops or model.onnxversioned fitted model
threshold_policy.jsonturns score into action
slice_metrics.jsonrecords required-slice support and regressions
serving_schema.jsonvalidates incoming row shape

Create a compact receipt for the candidate. The real deployment bundle would persist the estimator with a format you can defend. scikit-learn's current guidance is: use ONNX when you only need scores and don't need the Python object back; use skops.io when you do need the object and want to inspect untrusted types before loading; treat pickle, joblib, and cloudpickle as trusted-source-only because loading them can execute code. None of those Python formats support loading an estimator trained on a different scikit-learn version, so pin Python, scikit-learn, NumPy, SciPy, and the serializer together.[8]Reference 8Model persistencehttps://scikit-learn.org/stable/model_persistence.html

publish-candidate-receipt.py
1receipt = { 2 "artifact": "sla-risk-candidate-v1", 3 "feature_contract": "point-in-time-snapshot-v1", 4 "model": { 5 "family": "gradient_boosted_trees", 6 "learning_rate": 0.05, 7 "max_depth": 2, 8 "rounds": best_round, 9 }, 10 "policy": { 11 "sla_risk_threshold": selected_threshold, 12 "costs": { 13 "false_alarm": 8, 14 "standard_miss": 60, 15 "priority_miss": 150, 16 }, 17 }, 18 "split_manifest": {"train": "Jan-Mar", "validation": "April", "test": "May"}, 19 "test_auc": round(roc_auc_score(test["y"], test_scores), 3), 20 "test_policy": test_stats, 21 "required_slice_metrics": required_slice_results, 22 "required_slices_pass": required_slices_pass, 23 "release_status": "candidate_for_shadow" if required_slices_pass else "blocked", 24} 25payload = json.dumps(receipt, sort_keys=True) 26print("release status:", receipt["release_status"]) 27print("frozen rounds and threshold:", best_round, selected_threshold) 28print("receipt sha256:", sha256(payload.encode()).hexdigest()[:12])
Output
1release status: candidate_for_shadow 2frozen rounds and threshold: 50 0.15 3receipt sha256: fd121184d2ff

The receipt binds feature semantics, fitted-tree choices, threshold policy, split dates, measured holdout policy, and support-aware slice evidence. Shadow traffic and production monitoring still come next. Don't mutate the live policy in place when retraining changes those values.

Complete the lesson

Mastery Check

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

1.An SLA-risk classifier has labeled jobs from January through May. To avoid leakage, features must reflect only values known at each job's decision time, model choices must be frozen before final holdout scoring, and linked rows from the same incident shouldn't be split across holdouts. Which split plan provides valid promotion evidence?

Correct answer: Use Jan-Mar for training, April for selection, May for final evidence, and keep linked rows together.

Valid promotion evidence comes from a time-ordered split that imitates facing future jobs. Model choices are made on validation data, May is reserved for final evidence, point-in-time features prevent future information from entering rows, and linked rows stay together when splitting them would make the holdout artificially easy.

2.After training a boosted classifier, validation AUC is higher than the heartbeat-age rule. What comparison still has to be made before treating the booster as useful for interventions?

Correct answer: Score the boosted model and the declared rule on the same later validation data under the same cost policy, then check that the booster improves the intervention decision.

A rule baseline is an operational comparison point. The booster must prove that its added complexity improves the decision policy on the same future-like data and published costs. A higher-looking score, random-row comparison, or inherited threshold doesn't establish that.

3.Suppose a regression toy example has actual delays [2, 8, 20] and first predictions [6, 6, 6], so its squared-error residuals are [-4, 2, 14]. An SLA-risk classifier instead uses binary log loss and starts three rows at probability 0.50 with labels [0, 1, 1]. What correction target direction does the classifier use at that initial stage?

Correct answer: Negative log-loss gradients [-0.5, 0.5, 0.5], so the first row's risk is pushed down and the two SLA-miss rows are pushed up.

Squared-error regression can fit actual minus prediction in the original delay units. Binary log-loss classification fits corrections in the loss-gradient direction. At probability 0.50, gold minus probability is [-0.5, 0.5, 0.5], pushing the negative row down and the positive rows up.

4.A round search trains 160 boosting stages with fixed depth and learning rate. January-March log loss is 0.6163 at round 1, 0.3491 at round 50, and 0.2684 at round 160. April log loss is 0.6805, 0.5643, and 0.5922 at those same rounds. May hasn't been opened yet. What should be recorded for the frozen candidate?

Correct answer: Refit the candidate with 50 rounds and record that April log loss was the monitored early-stopping metric.

The validation month selects the stage count before the test month is used. April loss is lowest at round 50 and worse by round 160, even though training loss is still falling, so the frozen candidate should use 50 rounds. Choosing with May would leak final-test evidence into model selection.

5.A teammate wants to enable sklearn early stopping with validation_fraction=0.2 on the January-March matrix instead of scoring April after each stage. Why is that the wrong stopping split for this lab?

Correct answer: validation_fraction randomly holds out training rows, so it wouldn't use the declared April month as the freeze slice.

sklearn's validation_fraction helper samples a random holdout from the fitting matrix. This lab's validation slice is a later calendar month, so the stopping loop has to score April explicitly with staged_predict_proba.

6.April threshold search for the frozen booster reports costs: 0.15 -> 1316, 0.20 -> 1884, 0.25 -> 1912, 0.30 -> 2038, 0.40 -> 2604. The heartbeat-age rule costs 1560 on the same month. The policy minimizes declared cost and breaks ties toward the lower threshold. What should be frozen before opening May?

Correct answer: Record threshold 0.15 in the threshold policy with the cost table and selected rounds, and reserve May for final policy and slice evidence.

The declared objective chooses the lowest April cost, which is threshold 0.15, and that point also beats the published rule. That threshold is part of the operating policy and must be versioned with the selected model choices before May is opened for final evaluation.

7.The frozen candidate has acceptable aggregate test cost. A required large-model slice has 9 SLA-miss jobs, catches all 9, and the gate requires recall at least 0.85 with at least 10 positive cases. What should the gate report?

Correct answer: Fail closed for insufficient positive-case support, even though measured recall is 1.0 on the 9 SLA-miss jobs.

A slice gate requires both enough positive cases and enough recall. Perfect measured recall on too few SLA-miss jobs is weak evidence, so the slice shouldn't pass until the holdout has sufficient positive support.

8.A team saves only model.skops for an SLA-risk model and later needs to reproduce why a candidate was allowed into shadow traffic. Which missing bundle evidence is most important for reproducible operations?

Correct answer: Feature contract, split manifest, threshold policy with costs, required-slice metrics, and serving schema tied to the fitted model.

A fitted booster alone doesn't reproduce the release decision or the actions it triggers. Operations need the feature meanings and time boundary, evaluation split, selected threshold and costs, slice-gate evidence, and serving row shape bound to the model artifact.

8 questions remaining.

Next Step
Continue to Ranking and Recommendation Systems

You can now freeze a tabular risk model only after it beats a baseline, passes required slices, and ships with a versioned threshold. Ranking asks a different prediction: among many documents, which few should appear first, when the displayed order becomes tomorrow's training data.

PreviousBatch and Streaming Feature Pipelines
Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

Why do tree-based models still outperform deep learning on typical tabular data?

Grinsztajn, L., Oyallon, E., & Varoquaux, G. · 2022

https://arxiv.org/abs/2207.08815

Common Pitfalls and Recommended Practices: Data Leakage

Scikit-learn Developers · 2026 · Official documentation

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

Greedy Function Approximation: A Gradient Boosting Machine

Friedman, J. H. · 2001 · The Annals of Statistics

https://projecteuclid.org/journals/annals-of-statistics/volume-29/issue-5/Greedy-Function-Approximation--A-Gradient-Boosting-Machine/10.1214/aos/1013203451.full

XGBoost: A Scalable Tree Boosting System.

Chen, T. & Guestrin, C. · 2016 · KDD 2016

https://www.kdd.org/kdd2016/papers/files/rfp0697-chenAemb.pdf

LightGBM: A Highly Efficient Gradient Boosting Decision Tree.

Ke, G., Meng, Q., Finley, T., Wang, T., Chen, W., Ma, W., Ye, Q., & Liu, T. · 2017 · NeurIPS 2017

https://papers.nips.cc/paper/2017/hash/6449f44a102fde848669bdd9eb6b76fa-Abstract.html

CatBoost: unbiased boosting with categorical features.

Prokhorenkova, L., Gusev, G., Vorobev, A., Dorogush, A. V., & Gulin, A. · 2018 · NeurIPS 2018

https://arxiv.org/abs/1706.09516

Predicting Good Probabilities With Supervised Learning.

Niculescu-Mizil, A. & Caruana, R. · 2005 · ICML 2005

https://dl.acm.org/doi/10.1145/1102351.1102430

Model persistence

scikit-learn developers · 2026 · scikit-learn User Guide

https://scikit-learn.org/stable/model_persistence.html

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.