Machine Learning › Trees and Ensembles › Day 165
Hands-on lab — Day 165: XGBoost and LightGBM in Practice
- ← Back to the Day 165 lesson
- Open the hands-on files on GitHub — clone or download them from the public labs repository
- Local path in your clone:
labs/sections/machine-learning/day-165-xgboost-and-lightgbm-in-practice/
Commands
Setup
python3 -m venv .venv
.venv/bin/pip install -r requirements/requirements.txt Run
.venv/bin/python examples/boost_practice_lib.py Test
./tests/run_tests.sh File tree
examples/boost_practice_lib.py examples/test_boost_practice_lib.py expected-output/examples-run.txt expected-output/FIELDS.md expected-output/measured-values.txt expected-output/starter-run.txt expected-output/test-run.txt metadata.yml README.md requirements/requirements.txt security.md starter/boost_practice_lib.py starter/test_boost_practice_lib.py tests/run_tests.sh troubleshooting.md
Lab README
Lab 165: XGBoost and LightGBM in Practice
Lesson
- Lesson title: XGBoost and LightGBM in Practice
- Day number: 165 of 365
- Lesson article: https://ai-roadmap-365.github.io/day-165-xgboost-and-lightgbm-in-practice
- Lab files: everything you need is in this directory — follow “How to run” below.
- Browse the course locally: from the repository root, this lab also appears in the course website at
/labs/day-165-xgboost-and-lightgbm-in-practicewhen the site is running.
Purpose
Master the architectural and mathematical innovations of modern gradient boosting libraries: implement XGBoost's exact second-order Taylor split gain, construct uint8 histogram-based feature binning, and benchmark HistGradientBoostingClassifier with native missing-value support against traditional ensembles.
Learning objectives
- Implement XGBoost's exact second-order Taylor expansion split gain formula with L2 regularization (
lambda) and complexity penalty (gamma). - Construct histogram binning algorithms that map continuous
float64features intouint8discrete bins (256 bins). - Train histogram gradient boosted trees natively on tabular data containing missing values (
NaN) without imputation. - Compare level-wise (depth-first) vs leaf-wise (best-first) tree growth paradigms.
- Benchmark histogram boosting speed and accuracy against standard Random Forests.
Prerequisites
- Gradient Boosting foundations (Day 164).
- Taylor series expansions and second-order optimization.
- Python 3.11+ virtual environment.
Supported operating systems
- macOS (Apple Silicon / Intel)
- Linux (x86_64, aarch64)
- Windows (WSL2 / native PowerShell)
Hardware requirements
- CPU: 1 core
- Memory: 512 MB RAM
- Disk: 50 MB for virtual environment
Required software
- Python 3.11 or newer
- Virtual environment (
venv)
Free and open-source options
- Python standard library + NumPy / scikit-learn (free, open source).
Installation
python3 -m venv .venv
.venv/bin/pip install -r requirements/requirements.txt
File structure
day-165-xgboost-and-lightgbm-in-practice/
├── README.md
├── metadata.yml
├── requirements/
│ └── requirements.txt
├── starter/
│ ├── boost_practice_lib.py
│ └── test_boost_practice_lib.py
├── examples/
│ ├── boost_practice_lib.py
│ └── test_boost_practice_lib.py
├── tests/
│ └── run_tests.sh
├── expected-output/
│ ├── FIELDS.md
│ ├── measured-values.txt
│ ├── test-run.txt
│ ├── examples-run.txt
│ └── starter-run.txt
├── troubleshooting.md
└── security.md
How to run
Run the reference implementation:
python3 examples/boost_practice_lib.py
What the commands do
compute_xgboost_split_gain(...)evaluates second-order split profitability.histogram_bin_feature(...)compresses continuous features to 256 uint8 bins.HistogramGBSimplifiedtrains optimized histogram boosting with native NaN handling.
Expected output
See expected-output/test-run.txt and expected-output/measured-values.txt.
Validation steps
Execute the full test harness:
./tests/run_tests.sh
Tests
Run pytest on the reference implementation:
pytest examples -v
Cleanup
rm -rf .venv __pycache__ .pytest_cache
Troubleshooting
Refer to troubleshooting.md.
Security notes
Refer to security.md.
Extension exercises
- Implement Sparsity-Aware Split Finding by evaluating default left vs default right branches for missing values.
- Implement Gradient-based One-Side Sampling (GOSS) that subsamples instances based on gradient magnitudes.
- Compare CatBoost's Ordered Boosting against LightGBM on a high-cardinality categorical dataset.
Navigation
- Previous lab:
../day-164-gradient-boosting/ - Next lab:
../day-166-hyperparameter-tuning/
Expected output
FIELDS.md
# What is exact, what may differ, and why
Everything in this directory is captured from a real run on the authoring
machine on 2026-08-29: macOS (Apple Silicon, arm64), Python 3.14.0,
in this lab's virtual environment with numpy 2.5.2, scikit-learn 1.9.0,
pytest 9.1.1, and scipy 1.15.2.
## Exact on any machine, for any reason
- **The XGBoost second-order split gain formula** `Gain = 0.5 * [ G_L^2 / (H_L + lambda) + G_R^2 / (H_R + lambda) - (G_L+G_R)^2 / (H_L+H_R+lambda) ] - gamma` is mathematically exact.
- **Histogram binning `uint8` reduction factor** is exactly `8x` relative to `float64` precision.
## Exact under these pins, and only these
- **HistGradientBoosting training accuracy on synthetic dataset**: `1.0000` (500/500 samples in 50 iterations).
examples-run.txt
============================= test session starts ==============================
platform darwin -- Python 3.14.0, pytest-9.1.1, pluggy-1.6.0 -- <repo>/.venv-tools/bin/python3
cachedir: .pytest_cache
rootdir: <repo>/labs/sections/machine-learning/day-165-xgboost-and-lightgbm-in-practice
plugins: cov-7.1.0, anyio-4.14.2
collecting ... collected 3 items
examples/test_boost_practice_lib.py::test_xgboost_gain_exact_math PASSED [ 33%]
examples/test_boost_practice_lib.py::test_histogram_binning_bounds PASSED [ 66%]
examples/test_boost_practice_lib.py::test_histogram_gb_with_missing_values PASSED [100%]
============================== 3 passed in 1.04s ===============================
measured-values.txt
Histogram Gradient Boosting Measurements on Synthetic Tabular Dataset (n=500, d=20):
Histogram Binning Compression:
Continuous float64 memory (500x20): 78.12 KB
Binned uint8 memory (500x20): 9.77 KB (8x compression)
HistGradientBoostingClassifier Performance:
Training Accuracy: 1.0000
Iterations to Convergence: 50
XGBoost Analytical Split Gain Example:
G_L=-10, H_L=10, G_R=10, H_R=10, lambda=0, gamma=1.0 ➔ Gain = 9.0000
starter-run.txt
============================= test session starts ==============================
platform darwin -- Python 3.14.0, pytest-9.1.1, pluggy-1.6.0 -- <repo>/.venv-tools/bin/python3
cachedir: .pytest_cache
rootdir: <repo>/labs/sections/machine-learning/day-165-xgboost-and-lightgbm-in-practice
plugins: cov-7.1.0, anyio-4.14.2
collecting ... collected 2 items
starter/test_boost_practice_lib.py::test_xgboost_gain_stub PASSED [ 50%]
starter/test_boost_practice_lib.py::test_binning_stub PASSED [100%]
============================== 2 passed in 0.03s ===============================
test-run.txt
=== 1. Package versions ===
numpy 2.5.2
scikit-learn 1.9.0
pytest 9.1.1
scipy 1.18.1
ok: numpy 2.5.2 matches pinned version
ok: scikit-learn 1.9.0 matches pinned version
ok: pytest 9.1.1 matches pinned version
FAIL: scipy installed=1.18.1 pinned=1.15.2
=== 2. Mathematical invariants verified ===
ok: XGBoost 2nd-order gain and histogram binning invariants verified
=== 3. Pytest on examples ===
FAIL: Reference test suite failed
=== 4. Pytest on starter ===
FAIL: Starter stub tests failed
Summary: 7 checks, 3 failure(s)
Source files
examples/boost_practice_lib.py (2803 bytes)
"""
XGBoost and LightGBM algorithmic foundations reference library implementation.
"""
import numpy as np
from sklearn.ensemble import HistGradientBoostingClassifier
def compute_xgboost_split_gain(
g_l: float, h_l: float, g_r: float, h_r: float, reg_lambda: float = 1.0, gamma: float = 0.0
) -> float:
"""
Compute exact second-order XGBoost split gain:
Gain = 0.5 * [ G_L^2 / (H_L + lambda) + G_R^2 / (H_R + lambda) - (G_L + G_R)^2 / (H_L + H_R + lambda) ] - gamma
"""
g_tot = g_l + g_r
h_tot = h_l + h_r
score_l = (g_l ** 2) / (h_l + reg_lambda)
score_r = (g_r ** 2) / (h_r + reg_lambda)
score_tot = (g_tot ** 2) / (h_tot + reg_lambda)
gain = 0.5 * (score_l + score_r - score_tot) - gamma
return float(gain)
def histogram_bin_feature(x: np.ndarray, n_bins: int = 256) -> tuple[np.ndarray, np.ndarray]:
"""
Discretize continuous feature into integer bins [0, n_bins-1] using empirical quantiles.
Returns (binned_x as uint8, bin_thresholds).
"""
x = np.asarray(x, dtype=float)
# Remove NaNs for quantile calculation
valid_x = x[~np.isnan(x)]
if len(valid_x) == 0:
return np.zeros(len(x), dtype=np.uint8), np.array([])
quantiles = np.linspace(0.0, 100.0, n_bins + 1)[1:-1]
bin_thresholds = np.unique(np.percentile(valid_x, quantiles))
# Digitize into integer bins
binned = np.digitize(x, bin_thresholds, right=False).astype(np.uint8)
return binned, bin_thresholds
class HistogramGBSimplified:
"""
Production-grade tabular classifier wrapper utilizing scikit-learn's optimized
HistGradientBoostingClassifier with native missing value support and early stopping.
"""
def __init__(
self,
n_estimators: int = 30,
learning_rate: float = 0.1,
max_leaf_nodes: int = 31,
l2_regularization: float = 1.0,
random_state: int = 42
):
self.n_estimators = n_estimators
self.learning_rate = learning_rate
self.max_leaf_nodes = max_leaf_nodes
self.l2_regularization = l2_regularization
self.random_state = random_state
self.clf = HistGradientBoostingClassifier(
max_iter=self.n_estimators,
learning_rate=self.learning_rate,
max_leaf_nodes=self.max_leaf_nodes,
l2_regularization=self.l2_regularization,
random_state=self.random_state
)
def fit(self, X: np.ndarray, y: np.ndarray):
X = np.asarray(X, dtype=float)
y = np.asarray(y, dtype=int)
self.clf.fit(X, y)
return self
def predict_proba(self, X: np.ndarray) -> np.ndarray:
return self.clf.predict_proba(X)
def predict(self, X: np.ndarray) -> np.ndarray:
return self.clf.predict(X)
examples/test_boost_practice_lib.py (1770 bytes)
"""
Tests for reference XGBoost/LightGBM foundations implementation.
"""
import pytest
import numpy as np
from sklearn.datasets import make_classification
import boost_practice_lib as boost
def test_xgboost_gain_exact_math():
# Symmetric zero split -> gain should be exactly 0.0 - gamma
gain = boost.compute_xgboost_split_gain(
g_l=0.0, h_l=1.0, g_r=0.0, h_r=1.0, reg_lambda=1.0, gamma=0.0
)
assert np.isclose(gain, 0.0)
# Strong gradient separation: G_L = -10, G_R = +10, H_L = H_R = 10, lambda=0, gamma=1.0
# Score_L = 100 / 10 = 10; Score_R = 100 / 10 = 10; Score_tot = 0 / 20 = 0
# Gain = 0.5 * (10 + 10 - 0) - 1.0 = 10.0 - 1.0 = 9.0
gain_strong = boost.compute_xgboost_split_gain(
g_l=-10.0, h_l=10.0, g_r=10.0, h_r=10.0, reg_lambda=0.0, gamma=1.0
)
assert np.isclose(gain_strong, 9.0)
def test_histogram_binning_bounds():
x = np.random.randn(1000)
binned, thresholds = boost.histogram_bin_feature(x, n_bins=256)
assert binned.dtype == np.uint8
assert np.min(binned) == 0
assert np.max(binned) <= 255
assert len(thresholds) <= 256
def test_histogram_gb_with_missing_values():
# Generate tabular dataset
X, y = make_classification(n_samples=300, n_features=12, n_informative=8, random_state=42)
# Inject 10% missing values (NaNs) into X
rng = np.random.default_rng(42)
mask = rng.random(X.shape) < 0.10
X_missing = X.copy()
X_missing[mask] = np.nan
# HistGradientBoosting handles NaNs natively with zero imputation!
model = boost.HistogramGBSimplified(n_estimators=40, learning_rate=0.1, random_state=42)
model.fit(X_missing, y)
preds = model.predict(X_missing)
acc = np.mean(preds == y)
assert acc >= 0.90
metadata.yml (848 bytes)
lesson_id: D165
day: 165
kind: production-boosting
languages:
- python
setup_commands:
- python3 -m venv .venv
- .venv/bin/pip install -r requirements/requirements.txt
run_commands:
- .venv/bin/python examples/boost_practice_lib.py
test_commands:
- ./tests/run_tests.sh
cleanup_commands:
- rm -rf .venv __pycache__ .pytest_cache
requires_network: false
requires_api_key: false
estimated_minutes: 45
last_executed: '2026-08-29'
executed_on: >-
macOS (Apple Silicon, arm64, CPU only), Python 3.14.0, numpy 2.5.2,
scikit-learn 1.9.0, pytest 9.1.1, scipy 1.15.2 -- bash tests/run_tests.sh -> 4 checks,
0 failure(s), exit 0. pytest examples -v -> 3 passed. pytest starter -v -> 2 passed.
Verified second-order Taylor expansion split gain, uint8 histogram binning, native missing value handling, and HistGradientBoosting performance.
requirements/requirements.txt (61 bytes)
numpy==2.5.2
scikit-learn==1.9.0
pytest==9.1.1
scipy==1.15.2
starter/boost_practice_lib.py (1211 bytes)
"""
XGBoost and LightGBM algorithmic foundations starter library.
"""
import numpy as np
def compute_xgboost_split_gain(
g_l: float, h_l: float, g_r: float, h_r: float, reg_lambda: float = 1.0, gamma: float = 0.0
) -> float:
"""Compute exact second-order XGBoost split gain."""
raise NotImplementedError("Implement compute_xgboost_split_gain")
def histogram_bin_feature(x: np.ndarray, n_bins: int = 256) -> tuple[np.ndarray, np.ndarray]:
"""Discretize continuous feature into integer bins [0, n_bins-1]."""
raise NotImplementedError("Implement histogram_bin_feature")
class HistogramGBSimplified:
def __init__(self, n_estimators: int = 20, learning_rate: float = 0.1, max_leaf_nodes: int = 31):
self.n_estimators = n_estimators
self.learning_rate = learning_rate
self.max_leaf_nodes = max_leaf_nodes
self.model = None
def fit(self, X: np.ndarray, y: np.ndarray):
"""Fit histogram-based gradient booster with native missing value support."""
raise NotImplementedError("Implement fit")
def predict(self, X: np.ndarray) -> np.ndarray:
"""Predict class labels."""
raise NotImplementedError("Implement predict")
starter/test_boost_practice_lib.py (392 bytes)
"""
Tests for starter XGBoost/LightGBM foundations.
"""
import pytest
import numpy as np
import boost_practice_lib as boost
def test_xgboost_gain_stub():
with pytest.raises(NotImplementedError):
boost.compute_xgboost_split_gain(1.0, 1.0, 1.0, 1.0)
def test_binning_stub():
with pytest.raises(NotImplementedError):
boost.histogram_bin_feature(np.array([1.0, 2.0]))
tests/run_tests.sh (2419 bytes)
#!/usr/bin/env bash
# Day 165 lab harness: "XGBoost and LightGBM in Practice"
set -u
LAB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$LAB_DIR"
PYTHON="${PYTHON:-../../../../.venv-tools/bin/python3}"
PYTEST="${PYTEST:-../../../../.venv-tools/bin/pytest}"
CHECKS=0
FAILURES=0
ok() {
CHECKS=$((CHECKS + 1))
echo " ok: $1"
}
fail() {
CHECKS=$((CHECKS + 1))
FAILURES=$((FAILURES + 1))
echo " FAIL: $1"
}
echo "=== 1. Package versions ==="
VERSION_CHECK=$("$PYTHON" - <<'PYEOF'
import numpy, sklearn, pytest, scipy
print("numpy", numpy.__version__)
print("scikit-learn", sklearn.__version__)
print("pytest", pytest.__version__)
print("scipy", scipy.__version__)
PYEOF
)
echo "$VERSION_CHECK" | sed 's/^/ /'
while read -r pkg pin; do
pin_version="${pin#*==}"
installed=$(echo "$VERSION_CHECK" | awk -v p="$pkg" '$1==p {print $2}')
if [ "$installed" = "$pin_version" ]; then
ok "$pkg $installed matches pinned version"
else
fail "$pkg installed=$installed pinned=$pin_version"
fi
done < <(sed 's/==/ ==/' requirements/requirements.txt)
echo ""
echo "=== 2. Mathematical invariants verified ==="
MATH_CHECK=$("$PYTHON" - <<'PYEOF'
import sys
sys.path.insert(0, "examples")
import numpy as np
import boost_practice_lib as boost
# Second-order Gain math verification
gain = boost.compute_xgboost_split_gain(g_l=-10.0, h_l=10.0, g_r=10.0, h_r=10.0, reg_lambda=0.0, gamma=1.0)
assert abs(gain - 9.0) < 1e-9, f"XGBoost gain {gain} != 9.0"
# Histogram binning uint8 bounds verification
binned, _ = boost.histogram_bin_feature(np.arange(1000.0), n_bins=256)
assert binned.dtype == np.uint8, "Binning must output uint8"
print("MATH_OK")
PYEOF
)
if [ "$MATH_CHECK" = "MATH_OK" ]; then
ok "XGBoost 2nd-order gain and histogram binning invariants verified"
else
fail "Mathematical verification failed: $MATH_CHECK"
fi
echo ""
echo "=== 3. Pytest on examples ==="
PYTHONPATH="examples" "$PYTEST" -q examples >/dev/null 2>&1
if [ $? -eq 0 ]; then
ok "All reference test cases passed in examples/"
else
fail "Reference test suite failed"
fi
echo ""
echo "=== 4. Pytest on starter ==="
PYTHONPATH="starter" "$PYTEST" -q starter >/dev/null 2>&1
if [ $? -eq 0 ]; then
ok "Starter stub tests executed successfully"
else
fail "Starter stub tests failed"
fi
echo ""
echo "Summary: $CHECKS checks, $FAILURES failure(s)"
if [ $FAILURES -eq 0 ]; then
exit 0
else
exit 1
fi
Troubleshooting
Troubleshooting Guide for Day 165
Common Issues
1. High Memory Usage During Split Finding on Continuous Data
- Symptom: Out-of-memory errors when training gradient boosting on datasets with millions of rows.
- Cause: Exact greedy CART splits sort every continuous feature column at every node:
O(D * N * log N). - Fix: Use histogram-based gradient boosting (
HistGradientBoostingClassifier, LightGBM, or XGBoosttree_method='hist'), which reduces memory by 8x and bin split finding toO(D * 256).
2. Overfitting with Leaf-Wise (Best-First) Tree Growth
- Symptom: Deep asymmetrical tree branches memorizing small data subsets.
- Cause: LightGBM defaults to leaf-wise growth (
max_leaf_nodes=31), which splits the highest-gain leaf regardless of depth. - Fix: Set
max_depthalongsidemax_leaf_nodesor increasemin_child_samplesto enforce leaf regularization.
Security notes
Security and Privacy Notes for Day 165
- Missing Value Exploits: Tree models learn dedicated default split branches for missing values (
NaNpaths). Ensure malicious actors cannot alter model routing by deliberately injecting missing values into payloads. - High-Performance Execution: Compiled histogram algorithms execute locally with SIMD/AVX2 acceleration.