Building a model is the easy part. Evaluating it correctly — and tuning it to perform best on unseen data — is where most practitioners make critical mistakes. Improper evaluation leads to overconfident models that fail in production; poor hyperparameter tuning leaves performance on the table. This guide covers evaluation metrics, cross-validation strategies, the bias-variance tradeoff, calibration, and modern hyperparameter optimisation methods every data scientist must know.
This guide complements our Machine Learning Interview Q&A and Feature Engineering Interview Q&A. For production model evaluation, see our MLOps Interview Q&A.
Classification Evaluation Metrics
The confusion matrix is the foundation: True Positives (TP), True Negatives (TN), False Positives (FP), False Negatives (FN). All classification metrics derive from these four numbers.
Accuracy = (TP + TN) / Total: Misleading on imbalanced datasets — a model predicting “not fraud” always achieves 99.9% accuracy when fraud is 0.1% of data, yet detects zero cases. Use only when classes are balanced and misclassification costs are equal.
Precision = TP / (TP + FP): Of all predicted positives, what fraction are actually positive? Optimise when false positive cost is high (spam filter flagging legitimate email).
Recall = TP / (TP + FN): Of all actual positives, what fraction did we detect? Optimise when missing a positive is costly (cancer screening — missing actual cancer is worse than a false alarm).
F1-Score = 2 * (P * R) / (P + R): Harmonic mean of precision and recall. Cannot be gamed by sacrificing either metric. F-beta generalises: beta > 1 weights recall more; beta < 1 weights precision more.
ROC-AUC: Area under the ROC curve (TPR vs FPR at every threshold). AUC = 0.5 is random; AUC = 1.0 is perfect. Interpretation: probability that the model ranks a random positive higher than a random negative. Threshold-independent — evaluates ranking ability. Robust to class imbalance. Use as primary metric when comparing models without committing to a threshold.
PR-AUC (Precision-Recall AUC): More informative than ROC-AUC on severely imbalanced datasets. ROC-AUC can be misleadingly high when TN >> FP. For rare event detection (<1% positive rate), always report PR-AUC alongside ROC-AUC.
from sklearn.metrics import (classification_report, roc_auc_score,
average_precision_score, confusion_matrix,
ConfusionMatrixDisplay)
import matplotlib.pyplot as plt
# y_pred_proba = model.predict_proba(X_test)[:, 1]
# y_pred = (y_pred_proba >= 0.5).astype(int)
# print(classification_report(y_test, y_pred))
# print(f'ROC-AUC: {roc_auc_score(y_test, y_pred_proba):.4f}')
# print(f'PR-AUC: {average_precision_score(y_test, y_pred_proba):.4f}')
# ConfusionMatrixDisplay(confusion_matrix(y_test, y_pred)).plot()
# plt.title('Confusion Matrix'); plt.tight_layout(); plt.show()
Regression Metrics
MAE: Average absolute error — interpretable, robust to outliers, all errors weighted equally. RMSE: Square root of average squared error — penalises large errors more, use when large errors are costly. R-squared: Proportion of variance explained. R2=1 perfect; R2=0 is no better than predicting the mean; R2<0 is worse than a constant. MAPE: Scale-free percentage error — undefined at y=0, biased for asymmetric distributions. Key rule: Choose your metric before training — it should reflect the actual business cost of errors.
Bias-Variance Tradeoff
Expected prediction error = Bias² + Variance + Irreducible Noise. Bias: Error from incorrect assumptions — a linear model on non-linear data has high bias. It underfits. Training error ≈ validation error, both high. Fix: increase complexity, add features, reduce regularisation. Variance: Sensitivity to training data fluctuations — a deep decision tree memorises training data. It overfits. Training error << validation error (large gap). Fix: more data, reduce complexity, increase regularisation (L1/L2), dropout, ensemble methods. The goal: find the sweet spot where total error is minimised.
Cross-Validation Strategies
K-Fold CV: Split into k folds; train on k-1, validate on 1; rotate k times; average metrics. Standard k=5 or k=10. All data used for both training and validation.
Stratified K-Fold: Maintains class proportions in each fold — critical for imbalanced classification. Always use for classification tasks.
Nested CV: Outer loop for model evaluation, inner loop for hyperparameter tuning. The outer loop gives an unbiased estimate of the tuned model’s performance. Without nested CV, tuning on the validation set and reporting on the same set gives an optimistic (leaked) estimate.
from sklearn.model_selection import StratifiedKFold, cross_val_score, GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import GradientBoostingClassifier
pipe = Pipeline([
('scaler', StandardScaler()),
('model', GradientBoostingClassifier(random_state=42))
])
outer_cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
inner_cv = StratifiedKFold(n_splits=3, shuffle=True, random_state=0)
param_grid = {'model__n_estimators': [100, 200],
'model__max_depth': [3, 5],
'model__learning_rate': [0.05, 0.1]}
gs = GridSearchCV(pipe, param_grid, cv=inner_cv, scoring='roc_auc', n_jobs=-1)
scores = cross_val_score(gs, X, y, cv=outer_cv, scoring='roc_auc', n_jobs=-1)
print(f'Nested CV AUC: {scores.mean():.4f} +/- {scores.std():.4f}')
Hyperparameter Optimisation
Grid Search: Exhaustively evaluates all combinations. Guaranteed to find the best in-grid config. Exponentially expensive — 5 values for each of 5 hyperparameters = 3125 evaluations. Only practical for 1-3 hyperparameters.
Random Search: Samples random combinations from the hyperparameter space. More efficient than grid search — if only 2 of 10 hyperparameters matter, random search explores many values of those 2. Always preferred when there are more than 3 hyperparameters.
Bayesian Optimisation (Optuna): Builds a surrogate model of the objective, uses it to choose the next configuration most likely to improve results (acquisition function: Expected Improvement). Much more sample-efficient — achieves better results with fewer evaluations. Standard for expensive model training.
import optuna
optuna.logging.set_verbosity(optuna.logging.WARNING)
def objective(trial):
import xgboost as xgb
from sklearn.model_selection import cross_val_score
params = {
'n_estimators': trial.suggest_int('n_estimators', 100, 500),
'max_depth': trial.suggest_int('max_depth', 2, 8),
'learning_rate': trial.suggest_float('learning_rate', 1e-3, 0.3, log=True),
'subsample': trial.suggest_float('subsample', 0.6, 1.0),
'colsample_bytree': trial.suggest_float('colsample_bytree', 0.6, 1.0),
}
model = xgb.XGBClassifier(**params, eval_metric='auc',
random_state=42, n_jobs=-1)
auc = cross_val_score(model, X_train, y_train, cv=3, scoring='roc_auc').mean()
return auc
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=60, timeout=300)
print(f'Best AUC: {study.best_value:.4f}')
print(f'Best params: {study.best_params}')
Model Calibration
A well-calibrated model produces probabilities that match observed frequencies — if the model says P(churn)=0.8 for 100 users, approximately 80 should actually churn. Most models are poorly calibrated: SVMs and decision trees are overconfident; Naive Bayes is underconfident; gradient boosting is moderately calibrated; logistic regression is well-calibrated when assumptions hold.
Reliability diagram: Bin predictions by score; plot average predicted probability vs actual positive fraction. Perfect calibration lies on the diagonal y=x.
Calibration methods: Platt scaling — fits logistic regression on model’s raw scores. Isotonic regression — non-parametric monotonic calibration, more flexible but needs more data. Both fit on a held-out calibration set, never on training or test data. CalibratedClassifierCV in scikit-learn wraps any classifier with either method.
When calibration matters: Any time probabilities drive decisions — risk scores for credit, medical diagnosis probability, fraud score for human review queues. Calibration does not affect AUC (which depends only on ranking), but critically affects threshold-based decisions.
Statistical Significance of Model Comparisons
Never compare two models on a single test set difference without statistical testing. A model achieving 0.855 AUC vs 0.851 AUC — is this real or noise? McNemar’s test compares two classifiers on the same test set. Paired t-test or Wilcoxon signed-rank test compares CV scores across folds. Effect size matters alongside p-value: a statistically significant but tiny improvement (0.001 AUC) may not justify deploying a more complex model.
Proper model evaluation and tuning are the difference between a notebook experiment and a production-grade system. Interviewers test not just whether you know these methods but whether you understand the pitfalls — data leakage in cross-validation, optimistic performance from improper nested CV, poor calibration when predictions are used as probabilities. See our MLOps Interview Q&A for how these principles extend to continuously monitored production models.



