Machine Learning Workflow
A machine learning project is more than a call to fit. It's a sequence: define the problem, get and explore the data, prepare it, pick a baseline, try models, tune, evaluate once on the test set, and then deploy and monitor.
Your Path Through This Lesson
0 of 5 done
- List the stages of a complete machine learning project.
- Build one end-to-end project with a pipeline.
- Avoid the most common mistakes, especially data leakage.
The Stages
| # | Stage | Key questions |
|---|---|---|
| 1 | Define the problem | What are we predicting? Classification or regression? What metric matters? |
| 2 | Get the data | Where does it come from? Is it enough? Is it representative? |
| 3 | Split | Set aside a test set first, before looking closely |
| 4 | Explore | Distributions, missing values, outliers, correlations |
| 5 | Prepare | Fill missing values, encode categories, scale |
| 6 | Baseline | What score does a trivial model get? |
| 7 | Try models | Compare a few with cross-validation |
| 8 | Tune | GridSearchCV on the best candidates |
| 9 | Evaluate | Score the final model once on the test set |
| 10 | Deploy and monitor | Save the model, serve predictions, watch for drift |
The loop isn't strictly one-way: exploring often sends you back to collect more data, and errors in evaluation send you back to preparation.
A Complete Example
The breast-cancer dataset: predict whether a tumour is malignant (0) or benign (1).
1–3. Define, Load, Split
Missing a malignant tumour is worse than a false alarm, so we'll watch recall for malignant as well as accuracy.
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
data = load_breast_cancer(as_frame=True)
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
print("train:", X_train.shape, " test:", X_test.shape)
print("class balance in train:", np.bincount(y_train))train: (455, 30) test: (114, 30)
class balance in train: [170 285]4. Explore (Training Data Only)
print("missing values:", int(X_train.isna().sum().sum()))
print(X_train[["mean radius", "mean area", "mean smoothness"]].describe().loc[["mean", "std", "min", "max"]].round(2))missing values: 0
mean radius mean area mean smoothness
mean 14.07 648.54 0.10
std 3.50 344.94 0.01
min 6.98 143.50 0.06
max 28.11 2499.00 0.14No missing values, but very different scales (area in hundreds, smoothness around 0.1), so distance-based and linear models will need scaling.
5–6. Baseline
from sklearn.dummy import DummyClassifier
from sklearn.model_selection import cross_val_score
baseline = DummyClassifier(strategy="most_frequent")
print(f"baseline CV accuracy: {cross_val_score(baseline, X_train, y_train, cv=5).mean():.3f}")baseline CV accuracy: 0.626Always predicting "benign" already scores about 63%. Any real model must beat that clearly.
7. Compare Models
Each candidate is a pipeline, so scaling is fitted inside each cross-validation fold:
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
candidates = {
"logistic regression": make_pipeline(StandardScaler(), LogisticRegression(max_iter=5000)),
"KNN": make_pipeline(StandardScaler(), KNeighborsClassifier()),
"SVM (RBF)": make_pipeline(StandardScaler(), SVC()),
"random forest": RandomForestClassifier(random_state=0),
}
for name, model in candidates.items():
scores = cross_val_score(model, X_train, y_train, cv=5)
print(f"{name:>19}: {scores.mean():.3f} ± {scores.std():.3f}")logistic regression: 0.980 ± 0.013
KNN: 0.967 ± 0.021
SVM (RBF): 0.971 ± 0.018
random forest: 0.956 ± 0.0168. Tune the Best Candidates
from sklearn.model_selection import GridSearchCV
pipe = make_pipeline(StandardScaler(), LogisticRegression(max_iter=5000))
search = GridSearchCV(pipe, {"logisticregression__C": [0.01, 0.1, 1, 10, 100]}, cv=5).fit(X_train, y_train)
print("best C:", search.best_params_["logisticregression__C"])
print(f"best CV accuracy: {search.best_score_:.3f}")best C: 0.1
best CV accuracy: 0.9809. Evaluate Once on the Test Set
from sklearn.metrics import classification_report, confusion_matrix
final = search.best_estimator_
pred = final.predict(X_test)
print(confusion_matrix(y_test, pred))
print(classification_report(y_test, pred, target_names=["malignant", "benign"], digits=3))[[40 2]
[ 1 71]]
precision recall f1-score support
malignant 0.976 0.952 0.964 42
benign 0.973 0.986 0.979 72
accuracy 0.974 114
macro avg 0.974 0.969 0.972 114
weighted avg 0.974 0.974 0.974 114The confusion matrix shows exactly which mistakes remain. If missed malignant tumours are too many, lower the decision threshold for "benign" rather than retuning against the test set.
10. Save the Model
import joblib
joblib.dump(final, "cancer_model.joblib") # save the whole pipeline
model = joblib.load("cancer_model.joblib") # later, in the app
model.predict(new_patients)Because the scaler lives inside the pipeline, the saved file contains everything needed to go from raw measurements to a prediction.
Common Mistakes
| Mistake | Why it hurts | Fix |
|---|---|---|
| Data leakage: scaling, filling or selecting features on all data before splitting | Test information sneaks into training; scores look better than reality | Split first; put every preparation step in a pipeline |
| Tuning against the test set | The test score is no longer an honest estimate | Tune with cross-validation; touch the test set once |
| No baseline | You can't tell whether 90% is good | Always compare with DummyClassifier / DummyRegressor |
| Wrong metric | Accuracy hides failures on rare classes | Choose the metric from the problem (recall, F1, MAE, …) |
| Ignoring drift | The world changes after deployment | Monitor live inputs and accuracy; retrain regularly |
- Add
XGBClassifierand a tuned random forest to the comparison. Does either beat logistic regression? - Compute the recall for malignant tumours when you predict malignant whenever
final.predict_proba(X_test)[:, 0] > 0.3.
Split first, explore and prepare inside pipelines, beat a baseline, compare and tune with cross-validation, and use the test set only once at the end.
Summary
- Problem → data → split → explore → prepare → baseline → models → tune → test once → deploy and monitor.
- Pipelines keep preparation inside cross-validation and make saving the model easy.
- Beat a baseline, choose the right metric, and never tune on the test set.
- You've finished the Machine Learning course. Next, the Deep Learning course takes these ideas into neural networks.