Cheat sheet and protocol card
The 12 steps, in a form to print on a card
python16 lines
import pandas as pd show(pd.DataFrame([ {"#": 1, "Step": "Open the file, look at its shape", "Code": "df.shape, df.head(), df.info()"}, {"#": 2, "Step": "Identify the target", "Code": "df[target].value_counts()"}, {"#": 3, "Step": "Decide the task type", "Code": "number of classes -> classification / regression"}, {"#": 4, "Step": "Count the missing values", "Code": "df.isna().sum()"}, {"#": 5, "Step": "Drop the trap columns", "Code": "IDs, leakage, numbers stored as text"}, {"#": 6, "Step": "SPLIT (before everything)", "Code": "train_test_split(stratify=y)"}, {"#": 7, "Step": "Build a baseline", "Code": "DummyClassifier / DummyRegressor"}, {"#": 8, "Step": "The first model, with a Pipeline", "Code": "Pipeline([preprocessing, model])"}, {"#": 9, "Step": "Choose the right metric", "Code": "F1 / PR-AUC / MAE"}, {"#": 10, "Step": "Cross-validation", "Code": "cross_val_score(cv=5)"}, {"#": 11, "Step": "Error analysis", "Code": "which rows does it get wrong?"}, {"#": 12, "Step": "Save and document", "Code": "joblib + manifest + DATASHEET"}, ]))
| # | Step | Code |
|---|---|---|
| 1 | Open the file, look at its shape | df.shape, df.head(), df.info() |
| 2 | Identify the target | df[target].value_counts() |
| 3 | Decide the task type | number of classes -> classification / regression |
| 4 | Count the missing values | df.isna().sum() |
| 5 | Drop the trap columns | IDs, leakage, numbers stored as text |
| 6 | SPLIT (before everything) | train_test_split(stratify=y) |
| 7 | Build a baseline | DummyClassifier / DummyRegressor |
| 8 | The first model, with a Pipeline | Pipeline([preprocessing, model]) |
| 9 | Choose the right metric | F1 / PR-AUC / MAE |
| 10 | Cross-validation | cross_val_score(cv=5) |
| 11 | Error analysis | which rows does it get wrong? |
| 12 | Save and document | joblib + manifest + DATASHEET |
The first 10 minutes: code to copy and use
python20 lines
import pandas as pd from sklearn.model_selection import train_test_split from sklearn.dummy import DummyClassifier df = pd.read_csv("new.csv") print(df.shape) print(df.dtypes.to_string()) print(df.isna().sum().to_string()) print(df[TARGET].value_counts(normalize=True)) # how many unique values per column: exposes the ID and number-as-text traps for c in df.columns: print(f"{c:<24} unique={df[c].nunique():<6} dtype={df[c].dtype}") y = df[TARGET] X = df.drop(columns=[TARGET]) Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y) baseline = DummyClassifier(strategy="most_frequent").fit(Xtr, ytr) print("BASELINE:", baseline.score(Xte, yte))
The Pipeline template
python17 lines
from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline from sklearn.impute import SimpleImputer from sklearn.preprocessing import StandardScaler, OneHotEncoder NUM = X.select_dtypes("number").columns.tolist() CAT = X.select_dtypes("object").columns.tolist() prep = ColumnTransformer([ ("num", Pipeline([("imp", SimpleImputer(strategy="median")), ("sc", StandardScaler())]), NUM), ("cat", Pipeline([("imp", SimpleImputer(strategy="most_frequent")), ("oh", OneHotEncoder(handle_unknown="ignore"))]), CAT), ]) pipe = Pipeline([("prep", prep), ("model", MODEL)]) pipe.fit(Xtr, ytr)
Choosing a model
python16 lines
show(pd.DataFrame([ {"Situation": "Tabular, little data (<1000)", "First choice": "LogisticRegression / Ridge", "Then": "RandomForest"}, {"Situation": "Tabular, lots of data", "First choice": "LightGBM / XGBoost", "Then": "stacking"}, {"Situation": "Many categorical columns", "First choice": "CatBoost", "Then": "target encoding + boosting"}, {"Situation": "Text", "First choice": "TF-IDF + LogisticRegression", "Then": "a fine-tuned transformer"}, {"Situation": "Images", "First choice": "a pretrained CNN (transfer learning)", "Then": "fine-tuning"}, {"Situation": "A time series", "First choice": "naive baseline + lags + Ridge", "Then": "boosting (on differenced data)"}, {"Situation": "An explanation is required", "First choice": "a linear model / a single tree", "Then": "boosting with SHAP"}, ]))
| Situation | First choice | Then |
|---|---|---|
| Tabular, little data (<1000) | LogisticRegression / Ridge | RandomForest |
| Tabular, lots of data | LightGBM / XGBoost | stacking |
| Many categorical columns | CatBoost | target encoding + boosting |
| Text | TF-IDF + LogisticRegression | a fine-tuned transformer |
| Images | a pretrained CNN (transfer learning) | fine-tuning |
| A time series | naive baseline + lags + Ridge | boosting (on differenced data) |
| An explanation is required | a linear model / a single tree | boosting with SHAP |
Choosing a metric
python9 lines
show(pd.DataFrame([ {"Case": "Classification, balanced", "Metric": "accuracy, ROC-AUC"}, {"Case": "Classification, imbalanced", "Metric": "F1, PR-AUC, recall@precision"}, {"Case": "Asymmetric cost of mistakes", "Metric": "custom: weights on FN and FP"}, {"Case": "Regression with outliers", "Metric": "MAE (not RMSE)"}, {"Case": "Regression where big errors are very bad", "Metric": "RMSE"}, {"Case": "Relative error matters", "Metric": "MAPE (careful near zero)"}, {"Case": "Ranking / ordering", "Metric": "NDCG, MAP"}, ]))
| Case | Metric |
|---|---|
| Classification, balanced | accuracy, ROC-AUC |
| Classification, imbalanced | F1, PR-AUC, recall@precision |
| Asymmetric cost of mistakes | custom: weights on FN and FP |
| Regression with outliers | MAE (not RMSE) |
| Regression where big errors are very bad | RMSE |
| Relative error matters | MAPE (careful near zero) |
| Ranking / ordering | NDCG, MAP |
When the result is bad: a decision tree
python14 lines
show(pd.DataFrame([ {"Sign": "Train low, test low", "Diagnosis": "underfitting", "Action": "a more complex model, more features, less regularisation"}, {"Sign": "Train high, test low", "Diagnosis": "overfitting", "Action": "more data, regularisation, a simpler model"}, {"Sign": "Both very high", "Diagnosis": "LIKELY LEAKAGE", "Action": "look at feature importance, remove the column and measure"}, {"Sign": "CV is scattered (large std)", "Diagnosis": "too little or uneven data", "Action": "more folds, stratification, more data"}, {"Sign": "One class is never found", "Diagnosis": "imbalance", "Action": "class_weight, the threshold, change the metric"}, {"Sign": "Good offline, bad in production", "Diagnosis": "drift or a pipeline difference", "Action": "compare the input distribution, check the Pipeline is identical"}, ]))
| Sign | Diagnosis | Action |
|---|---|---|
| Train low, test low | underfitting | a more complex model, more features, less regularisation |
| Train high, test low | overfitting | more data, regularisation, a simpler model |
| Both very high | LIKELY LEAKAGE | look at feature importance, remove the column and measure |
| CV is scattered (large std) | too little or uneven data | more folds, stratification, more data |
| One class is never found | imbalance | class_weight, the threshold, change the metric |
| Good offline, bad in production | drift or a pipeline difference | compare the input distribution, check the Pipeline is identical |
Quick reminders
python12 lines
show(pd.DataFrame([ {"Topic": "Split", "Reminder": "BEFORE everything. stratify=y for classification"}, {"Topic": "Scaler", "Reminder": "fit on train only. Inside a Pipeline it's not a problem"}, {"Topic": "Time series", "Reminder": "A chronological split, TimeSeriesSplit, differencing"}, {"Topic": "Grouped data", "Reminder": "GroupShuffleSplit: one object stays in one part"}, {"Topic": "Trees", "Reminder": "No scaling needed"}, {"Topic": "KNN, SVM, NN", "Reminder": "Scaling is REQUIRED"}, {"Topic": "random_state", "Reminder": "Set it everywhere, for reproducibility"}, {"Topic": "Neural networks", "Reminder": "At least 3 seeds, and record the worst one too"}, {"Topic": "The test set", "Reminder": "Once. Look a second time and it's validation"}, {"Topic": "Saving", "Reminder": "Pipeline + manifest + a round-trip test"}, ]))
| Topic | Reminder |
|---|---|
| Split | BEFORE everything. stratify=y for classification |
| Scaler | fit on train only. Inside a Pipeline it's not a problem |
| Time series | A chronological split, TimeSeriesSplit, differencing |
| Grouped data | GroupShuffleSplit: one object stays in one part |
| Trees | No scaling needed |
| KNN, SVM, NN | Scaling is REQUIRED |
| random_state | Set it everywhere, for reproducibility |
| Neural networks | At least 3 seeds, and record the worst one too |
| The test set | Once. Look a second time and it's validation |
| Saving | Pipeline + manifest + a round-trip test |
The import bundle
python19 lines
import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.model_selection import (train_test_split, cross_val_score, GridSearchCV, StratifiedKFold, GroupShuffleSplit, TimeSeriesSplit) from sklearn.pipeline import Pipeline from sklearn.compose import ColumnTransformer from sklearn.impute import SimpleImputer from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.dummy import DummyClassifier, DummyRegressor from sklearn.linear_model import LogisticRegression, Ridge, Lasso from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier from sklearn.metrics import (accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, average_precision_score, confusion_matrix, classification_report, mean_absolute_error, r2_score) import joblib
The last word
python13 lines
show(pd.DataFrame([ {"Rule": "1. The baseline first", "Why": "Without a baseline, a result means nothing"}, {"Rule": "2. Split before everything", "Why": "Every step done after it risks leakage"}, {"Rule": "3. A Pipeline is mandatory", "Why": "It stops leakage at the level of structure"}, {"Rule": "4. Every number must be measured", "Why": "A guessed number is a lie"}, {"Rule": "5. If your guess is rejected, write it down", "Why": "That's where the most valuable knowledge is"}, ])) print() print("In this book I guessed wrong several times, and measurement rejected me:") print("the CNN didn't win on centred images, the LSTM came out unstable, TF-IDF") print("beat the transformer, and the column order turned out not to matter.") print() print("Each time I wrote down the MEASUREMENT, not the guess. Do the same.")
Output
In this book I guessed wrong several times, and measurement rejected me: the CNN didn't win on centred images, the LSTM came out unstable, TF-IDF beat the transformer, and the column order turned out not to matter. Each time I wrote down the MEASUREMENT, not the guess. Do the same.
| Rule | Why |
|---|---|
| 1. The baseline first | Without a baseline, a result means nothing |
| 2. Split before everything | Every step done after it risks leakage |
| 3. A Pipeline is mandatory | It stops leakage at the level of structure |
| 4. Every number must be measured | A guessed number is a lie |
| 5. If your guess is rejected, write it down | That's where the most valuable knowledge is |
Questions about this chapter?
The course, homework review and the other five books are in Uzbek. Questions in English are welcome on Telegram.