Skip to main content
MahiDev — Learn. Build. Become AI-ready.

Lesson 14

ML Workflow

Structure an end-to-end machine learning project from problem definition through data exploration, feature engineering, model selection, evaluation, and iteration.

Intermediate25 min
Six-stage flow: Problem then Data then Features then Model then Evaluate then Deploy

A machine learning project is not a single fit and predict. It is a loop: you define the problem, understand the data, engineer features, train and evaluate a baseline, diagnose errors, improve, and repeat. Structure prevents wasted effort.

Step 1: define the problem

Before writing code, answer:

  • What decision or action will the prediction support?
  • Is it regression, classification, or clustering?
  • What metric matters? (Accuracy? Recall? RMSE?)
  • What is the baseline? (A simple rule? Historical average?)

A clear problem definition stops you from optimising the wrong thing.

Step 2: explore and prepare the data

python
import numpy as np
import pandas as pd
from numpy.random import default_rng
rng = default_rng(42)
n = 300
experience = rng.integers(1, 21, n)
certifications = rng.integers(0, 7, n)
overtime = rng.integers(0, 45, n)
education = rng.choice([10, 12, 14, 16, 18], n, p=[0.05, 0.25, 0.35, 0.25, 0.10])
noise = rng.normal(0, 4500, n)
salary = (20000 + experience * 3200 + (education - 10) * 1500 +
          certifications * 1200 + overtime * 80 + noise).round(-2)
salary = np.clip(salary, 18000, 160000).astype(int)
senior = (salary >= 65650).astype(int)
top = ((experience >= 15) & (certifications >= 5)).astype(int)
employees = pd.DataFrame(dict(
    experience=experience, certifications=certifications,
    overtime=overtime, education=education, salary=salary,
    senior=senior, top=top,
))
# Quick data exploration
print(employees.shape)
print(employees.dtypes)
print(employees[["experience", "certifications", "salary"]].describe().round(1))
print("\nclass balance:", employees["senior"].value_counts().to_dict())

Output:

text
(300, 7)
experience        int64
certifications    int64
overtime          int64
education         int64
salary            int64
senior            int64
top               int64
dtype: object
       experience  certifications    salary
count       300.0           300.0     300.0
mean         10.5             3.0   65239.0
std           5.5             2.1   18735.0
min           1.0             0.0   24000.0
25%           6.0             1.0   51375.0
50%          10.0             3.0   65650.0
75%          15.0             5.0   81000.0
max          20.0             6.0  104900.0

class balance: {0: 150, 1: 150}

Step 3: baseline → improve → evaluate

python
import numpy as np
import pandas as pd
from numpy.random import default_rng
rng = default_rng(42)
n = 300
experience = rng.integers(1, 21, n)
certifications = rng.integers(0, 7, n)
overtime = rng.integers(0, 45, n)
education = rng.choice([10, 12, 14, 16, 18], n, p=[0.05, 0.25, 0.35, 0.25, 0.10])
noise = rng.normal(0, 4500, n)
salary = (20000 + experience * 3200 + (education - 10) * 1500 +
          certifications * 1200 + overtime * 80 + noise).round(-2)
salary = np.clip(salary, 18000, 160000).astype(int)
senior = (salary >= 65650).astype(int)
top = ((experience >= 15) & (certifications >= 5)).astype(int)
employees = pd.DataFrame(dict(
    experience=experience, certifications=certifications,
    overtime=overtime, education=education, salary=salary,
    senior=senior, top=top,
))
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import train_test_split
X = employees[["experience", "certifications", "overtime", "education"]].values
y = employees["senior"].values
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)
baseline = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))
baseline.fit(X_train, y_train)
improved = GradientBoostingClassifier(n_estimators=100, random_state=0).fit(X_train, y_train)
print(f"baseline: {baseline.score(X_test, y_test):.3f}")
print(f"improved: {improved.score(X_test, y_test):.3f}")

Output:

text
baseline: 0.933
improved: 0.883

Always establish a baseline first. It tells you whether a complex model is worth the extra effort.

Step 4: iterate on errors

Look at the rows your model got wrong. Common fixes:

  • Missing important features → add them.
  • Class imbalance → adjust class weights.
  • Overfitting → add regularisation or more data.
  • Underfitting → use a more complex model or engineer features.

Track each experiment. Reproducibility requires fixed random seeds and the same train-test split.

Key takeaways

  • Define the problem and metric before writing any code
  • Explore data shapes, types, and class balance early
  • Train a simple baseline before trying complex models
  • Iterate: diagnose errors, improve features or model, re-evaluate
  • Track experiments with fixed seeds and identical splits

Loading playground…

Check your understanding

ML Workflow Quiz

What is the first step in an ML workflow?

1 / 10