Machine Learning cheat sheet
A scannable Machine Learning reference: 28 short snippets across 13 topics, each linking back to the lesson it came from.
At a glance
| Topic | What it covers | |
|---|---|---|
| Machine learning in one page | Most business problems are supervised. Decide early whether you are predicting a category (classification) or a number | lesson |
| Evaluation and overfitting | A high mean with a large standard deviation means the result depends on which fold you got — the model is not stable | lesson |
| Pipelines and saving models | If you scale or impute before splitting, statistics from the test set sneak into training and your score is optimistic | lesson |
| Data collection, cleaning and feature engineering | A model is a summary of the rows you fed it. If those rows are a biased sample — only surviving customers, only | lesson |
| Splitting data correctly | With little data, skip the fixed validation set and use cross-validation on the development split. What you must not do | lesson |
| Supervised algorithms in practice | A linear model assumes the outcome is a weighted sum of the features — or, for logistic regression, that the log-odds | lesson |
| Unsupervised learning | There is no ground-truth label to score against, so validate a clustering by whether the segments differ on something | lesson |
| Classification metrics in depth | Report the matrix and the two error counts, not just a scalar. Stakeholders can argue about how many false positives | lesson |
| Regression metrics and residual analysis | Quote the error in the units of the problem, next to the mean target value. 'RMSE 412' means nothing until the reader | lesson |
| Imbalanced data | The class balance is a property of the problem, not a defect in the data. Removing negative rows to 'fix' it throws | lesson |
| Hyperparameter tuning and experiment tracking | Tune two or three parameters, not everything. Each extra dimension multiplies the fits, increases the chance of | lesson |
| Interpretability, bias and fairness | Permutation importance, partial dependence, SHAP, subgroup metrics, where bias enters, and documenting what a model | lesson |
| Deploying and monitoring a model | Store the model version with every prediction. Without it you cannot explain an old decision, reproduce a complaint, or | lesson |
Quick snippets
Machine learning in one page
Features beat algorithms
import pandas as pd
from sklearn.model_selection import train_test_split
df = pd.read_csv("churn.csv")
X = df.drop(columns=["churned", "customer_id"]) # id is not a feature
y = df["churned"]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y) # stratify keeps class balance
Start with a baseline you must beat
from sklearn.dummy import DummyClassifier
from sklearn.metrics import accuracy_score
base = DummyClassifier(strategy="most_frequent").fit(X_train, y_train)
accuracy_score(y_test, base.predict(X_test)) # the number to beatFull lesson: Machine learning in one page →
Evaluation and overfitting
Overfitting, underfitting, just right
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import GradientBoostingClassifier
scores = cross_val_score(
GradientBoostingClassifier(), X_train, y_train,
cv=5, scoring="roc_auc")
print(scores.mean(), scores.std()) # report the spread, not just the mean
Metrics that match the cost
from sklearn.metrics import precision_recall_curve
prec, rec, thresholds = precision_recall_curve(y_test, proba)
# choose the operating point from the business cost, then hard-code it
best = thresholds[(prec > 0.9)].min()Full lesson: Evaluation and overfitting →
Pipelines and saving models
Tuning without fooling yourself
from sklearn.model_selection import GridSearchCV
grid = {"clf__C": [0.01, 0.1, 1, 10],
"clf__class_weight": [None, "balanced"]}
search = GridSearchCV(model, grid, cv=5, scoring="roc_auc", n_jobs=-1)
search.fit(X_train, y_train)
print(search.best_params_, search.best_score_)
Saving the whole pipeline
import joblib
joblib.dump(search.best_estimator_, "churn_v3.joblib")
# later, in the service
model = joblib.load("churn_v3.joblib")
model.predict(new_rows) # preprocessing travels with the modelFull lesson: Pipelines and saving models →
Data collection, cleaning and feature engineering
Collect before you model
import pandas as pd
df = pd.read_csv("events.csv", parse_dates=["created_at"])
df = df.drop_duplicates(subset=["event_id"])
df = df[df["created_at"] < CUTOFF] # never train on rows newer than the label
print(df.shape)
print(df.dtypes.to_string())
print(df.isna().mean().sort_values(ascending=False).head(10))
print(df["label"].value_counts(normalize=True))
From columns to features
df["hour"] = df["created_at"].dt.hour
df["dow"] = df["created_at"].dt.dayofweek
df["days_since_signup"] = (df["created_at"] - df["signup_at"]).dt.days
df["amount_log"] = np.log1p(df["amount"])
df["orders_per_day"] = df["orders"] / df["tenure_days"].clip(lower=1)
# frequency encoding is safe: it uses no label
freq = df["city"].value_counts(normalize=True)
df["city_freq"] = df["city"].map(freq).fillna(0.0)Full lesson: Data collection, cleaning and feature engineering →
Splitting data correctly
Three sets, three jobs
from sklearn.model_selection import train_test_split
# hold out the test set first and lock it away
X_dev, X_test, y_dev, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y)
# then split development data into train and validation inside every experiment
X_train, X_val, y_train, y_val = train_test_split(
X_dev, y_dev, test_size=0.25, random_state=42, stratify=y_dev)
print(X_train.shape, X_val.shape, X_test.shape)
Split by structure, not by row
from sklearn.model_selection import GroupKFold, StratifiedKFold, TimeSeriesSplit
# several rows per customer: no customer may appear on both sides
for tr, va in GroupKFold(n_splits=5).split(X, y, groups=df["customer_id"]):
...
# future-only training, for anything with a time signal
for tr, va in TimeSeriesSplit(n_splits=5, gap=1).split(X):
...
# keep the class ratio stable in every fold
StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
Nested cross-validation
from sklearn.model_selection import GridSearchCV, cross_val_score
inner = GridSearchCV(model, {"clf__C": [0.1, 1, 10]}, cv=3, scoring="roc_auc")
outer_scores = cross_val_score(inner, X_dev, y_dev, cv=5, scoring="roc_auc")
print(outer_scores.mean(), outer_scores.std()) # the number you may quoteFull lesson: Splitting data correctly →
Supervised algorithms in practice
Start linear
from sklearn.linear_model import LinearRegression, LogisticRegression, Ridge
lin = LinearRegression().fit(X_train_scaled, y_train)
log = LogisticRegression(max_iter=1000).fit(X_train_scaled, y_train_cls)
ridge = Ridge(alpha=1.0).fit(X_train_scaled, y_train)
log.predict_proba(X_test_scaled)[:, 1] # probabilities
log.coef_ # effect per feature, given the scaling
log.classes_ # column order for predict_proba
What each family assumes
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
knn = KNeighborsClassifier(n_neighbors=15, weights="distance")
svc = SVC(C=1.0, gamma="scale", probability=False)
# both are distance-based: scale first, and tune k or C on the validation folds
knn.fit(X_train_scaled, y_train).score(X_test_scaled, y_test)
svc.fit(X_train_scaled, y_train).score(X_test_scaled, y_test)Full lesson: Supervised algorithms in practice →
Unsupervised learning
Reducing dimensions
from sklearn.decomposition import PCA, TruncatedSVD
from sklearn.manifold import TSNE
pca = PCA(n_components=0.95, random_state=42).fit(X_scaled) # keep 95% of variance
print(pca.n_components_, pca.explained_variance_ratio_.sum())
X_pca = pca.transform(X_scaled)
# SVD works on sparse text matrices where PCA would densify them
svd = TruncatedSVD(n_components=100, random_state=42)
# t-SNE is for looking at data, not for feeding a model
emb = TSNE(n_components=2, perplexity=30, random_state=42).fit_transform(X_scaled)
Anomaly detection
from sklearn.ensemble import IsolationForest
from sklearn.neighbors import LocalOutlierFactor
iso = IsolationForest(contamination=0.02, random_state=42).fit(X_scaled)
print(iso.predict(X_scaled)[:10]) # -1 = flagged as anomalous
lof = LocalOutlierFactor(n_neighbors=20, contamination=0.02)
lof.fit_predict(X_scaled) # local density relative to neighboursFull lesson: Unsupervised learning →
Classification metrics in depth
Everything starts at the confusion matrix
from sklearn.metrics import classification_report, confusion_matrix
cm = confusion_matrix(y_test, y_pred)
tn, fp, fn, tp = cm.ravel()
precision = tp / (tp + fp) # of the alarms raised, how many were real
recall = tp / (tp + fn) # of the real cases, how many were caught
fpr = fp / (fp + tn) # of the clean cases, how many were wrongly flagged
print(cm, precision, recall, fpr)
print(classification_report(y_test, y_pred, digits=3))
ROC-AUC versus precision-recall
from sklearn.metrics import (average_precision_score,
precision_recall_curve, roc_auc_score, roc_curve)
prob = clf.predict_proba(X_test)[:, 1]
print("roc auc", roc_auc_score(y_test, prob))
print("average precision", average_precision_score(y_test, prob))
fpr, tpr, thr = roc_curve(y_test, prob)
prec, rec, thr_pr = precision_recall_curve(y_test, prob)
Thresholds and calibration
import numpy as np
from sklearn.calibration import CalibratedClassifierCV
# pick the threshold from the cost of each error type
cost_fp, cost_fn = 5, 50
expected = cost_fp * (1 - prob) + cost_fn * prob
threshold = np.quantile(prob, 0.10) # flag the riskiest 10%
# are the probabilities themselves trustworthy?
cal = CalibratedClassifierCV(clf, method="isotonic", cv=5).fit(X_train, y_train)
prob_cal = cal.predict_proba(X_test)[:, 1]
print(prob_cal.mean(), y_test.mean()) # predicted rate vs observed rateFull lesson: Classification metrics in depth →
Regression metrics and residual analysis
Pick the error you actually pay
import numpy as np
from sklearn.metrics import (mean_absolute_error, mean_squared_error,
r2_score, root_mean_squared_error)
mae = mean_absolute_error(y_test, pred)
rmse = root_mean_squared_error(y_test, pred)
r2 = r2_score(y_test, pred)
print(mae, rmse, r2)
print("typical error", f"{mae / np.mean(y_test):.1%}") # relative to the mean target
Residuals tell you what the score cannot
resid = y_test - pred
# structure in the residuals means the model is missing something
df = pd.DataFrame({"pred": pred, "resid": resid, "segment": segments})
print(df.groupby("segment")["resid"].agg(["mean", "std", "count"]))
# a funnel shape means the error grows with the prediction
print(df.assign(bucket=pd.qcut(df["pred"], 5)).groupby("bucket")["resid"].mean())
# the biggest misses are worth reading one by one
print(df.reindex(resid.abs().sort_values(ascending=False).index).head(10))
When the cost is asymmetric
from sklearn.ensemble import GradientBoostingRegressor
# under-stocking is worse than over-stocking: model a high quantile
high = GradientBoostingRegressor(loss="quantile", alpha=0.9, random_state=42)
high.fit(X_train, y_train)
lower = GradientBoostingRegressor(loss="quantile", alpha=0.1, random_state=42)
lower.fit(X_train, y_train)
band = high.predict(X_test) - lower.predict(X_test) # prediction interval widthFull lesson: Regression metrics and residual analysis →
Imbalanced data
Why accuracy lies
import numpy as np
from sklearn.dummy import DummyClassifier
from sklearn.metrics import accuracy_score, average_precision_score
print(np.bincount(y)) # e.g. [9900, 100]
base = DummyClassifier(strategy="most_frequent").fit(X_train, y_train)
print("accuracy", accuracy_score(y_test, base.predict(X_test))) # 0.99
print("average precision", average_precision_score(y_test, np.zeros_like(y_test)))
Choose the operating point
from sklearn.metrics import precision_recall_curve
prob = pipe.predict_proba(X_test)[:, 1]
prec, rec, thr = precision_recall_curve(y_test, prob)
# if the team can review 200 cases a week, take the top 200 by probability
k = 200
order = np.argsort(-prob)[:k]
print("precision at k", y_test.values[order].mean())
print("recall at k", y_test.values[order].sum() / y_test.sum())Full lesson: Imbalanced data →
Hyperparameter tuning and experiment tracking
Spend the budget wisely
from sklearn.ensemble import HistGradientBoostingClassifier
boost = HistGradientBoostingClassifier(
learning_rate=0.05,
max_iter=1000,
early_stopping=True, # stop when validation loss stops improving
validation_fraction=0.15,
n_iter_no_change=25,
random_state=42).fit(X_train, y_train)
print(boost.n_iter_) # rounds actually usedFull lesson: Hyperparameter tuning and experiment tracking →
Interpretability, bias and fairness
Global and local explanations
from sklearn.inspection import permutation_importance
perm = permutation_importance(
boost, X_val, y_val, scoring="average_precision",
n_repeats=10, random_state=42, n_jobs=-1)
order = np.argsort(-perm.importances_mean)
for i in order[:8]:
print(X_val.columns[i], round(perm.importances_mean[i], 4),
round(perm.importances_std[i], 4))Full lesson: Interpretability, bias and fairness →
Deploying and monitoring a model
Batch or service
import joblib
import pandas as pd
model = joblib.load("churn_v3.joblib")
def score_batch(path_in="scores_in.parquet", path_out="scores_out.parquet"):
rows = pd.read_parquet(path_in)
rows["churn_prob"] = model.predict_proba(rows)[:, 1]
rows["scored_at"] = pd.Timestamp.utcnow()
rows["model_version"] = "churn_v3"
rows.to_parquet(path_out, index=False)
return len(rows)
Training and serving must agree
# a single feature path shared by training, backfill and the service
def build_features(raw: pd.DataFrame) -> pd.DataFrame:
out = raw.copy()
out["amount_log"] = np.log1p(out["amount"])
out["days_since_signup"] = (out["event_at"] - out["signup_at"]).dt.days
return out[FEATURE_ORDER] # column order and names fixed in one place
assert list(build_features(sample).columns) == FEATURE_ORDER
Drift, retraining and rollback
import numpy as np
def psi(expected, actual, bins=10):
"""Population stability index between a training column and today's data."""
edges = np.quantile(expected, np.linspace(0, 1, bins + 1))
e = np.histogram(expected, bins=edges)[0] / len(expected)
a = np.histogram(actual, bins=edges)[0] / len(actual)
e, a = np.clip(e, 1e-6, None), np.clip(a, 1e-6, None)
return float(np.sum((a - e) * np.log(a / e)))
# psi under 0.1: stable, 0.1-0.25: watch, above 0.25: investigate
print({c: round(psi(train[c], today[c]), 3) for c in FEATURE_ORDER})Full lesson: Deploying and monitoring a model →
FAQ
Is this Machine Learning cheat sheet free to use?
Where do the examples come from?
How do I go deeper than a cheat sheet?
Related cheat sheets
AI Basics AI Agents Math for AI scikit-learn TensorFlow PyTorch
Last refreshed 2026-09-27.