Machine learning courses spend most of their time on algorithms, but in a real job the model is the smallest part of the project. A typical team spends far more effort understanding the problem, cleaning data, engineering features, and fighting bugs in deployment than it ever spends choosing between random forests and gradient boosters. The difference between a project that ships and one that dies in a notebook is usually the workflow, not the math. This lesson walks the full loop — problem, data, features, model, evaluation, deployment, and monitoring — so you can see where the real risks hide and why modelling is only the middle act. If you learn nothing else from this course, learn that a perfect algorithm on the wrong problem is still wrong.
flowchart LR P["Problem"] --> D["Data"] D --> F["Features"] F --> M["Model"] M --> E["Evaluate"] E --> De["Deploy"] De --> Mo["Monitor"] Mo --> P style P fill:#b45309,color:#fff style M fill:#166534,color:#fff style Mo fill:#7c3aed,color:#fff
Problem and data: the foundation
Every project starts with a question that is fuzzier than it looks. Predicting customer churn sounds simple until you define what churn means — is it no purchase for 30 days, or cancellation, or a refund? The definition decides which data you collect and whether the model even answers the business question. Once the problem is sharp, you gather raw data and perform exploratory data analysis (EDA): plotting distributions, counting missing values, and looking for impossible entries like negative ages or dates in the future. A model can only learn what the data contains, and dirty data teaches dirty lessons. Many experienced practitioners say that 80 percent of project time is data work, and they are not exaggerating.
def data_quality_report(records):
total = len(records)
missing_age = sum(1 for r in records if r.get('age') is None)
invalid_age = sum(1 for r in records if r.get('age') is not None and r['age'] < 0)
duplicates = len(records) - len({tuple(sorted(r.items())) for r in records})
print(f"Total records: {total}")
print(f"Missing age: {missing_age}")
print(f"Invalid age: {invalid_age}")
print(f"Duplicates: {duplicates}")
data_quality_report([
{'age': 25, 'spent': 100},
{'age': None, 'spent': 50},
{'age': -3, 'spent': 200},
{'age': 25, 'spent': 100},
])
Features and model: where the math lives
Feature engineering is the art of turning raw data into numbers the model can actually use. A timestamp becomes hour-of-day and day-of-week. A free-text review becomes a bag of word counts. Geographic coordinates become distance from the nearest store. The best features are the ones that capture the underlying cause of the pattern you are trying to predict. After features come the model and training loop — the part every course emphasizes. You split data into training and validation sets, pick an algorithm, and run the optimizer. But remember: a sophisticated model on weak features will almost always lose to a simple model on strong features. The model is important, but it is not a substitute for understanding your data.
flowchart TD Train["Training set<br/>learn the pattern"] --> Model["Model"] Test["Test set<br/>never seen during training"] --> Model Model -->|"predict"| Test Test -->|"compare"| Score["Score"] style Train fill:#166534,color:#fff style Test fill:#b45309,color:#fff style Score fill:#7c3aed,color:#fff
import random
random.seed(0)
def train_test_split(data, train_fraction=0.8):
shuffled = list(data)
random.shuffle(shuffled)
split = int(len(shuffled) * train_fraction)
return shuffled[:split], shuffled[split:]
train, test = train_test_split(list(range(10)))
print("train:", train)
print("test:", test)
Evaluation, deployment, and monitoring: the long tail
Evaluation means measuring the model on data it has never seen, using metrics that match the business goal. Accuracy is the famous one, but it can hide disasters when the classes are imbalanced — a fraud detector that always predicts not fraud is 99 percent accurate and completely useless. Deployment turns the model from a notebook into a system that serves predictions to real users. That means building an API, handling latency, versioning the model, and planning for rollback when it breaks. Monitoring watches the model after launch, because the world changes. A recommendation system trained on summer vacation data will suggest beach gear in December unless you detect that the input distribution has shifted — a problem called concept drift. The loop never ends; it only slows down.
def accuracy(predictions, labels):
correct = sum(1 for p, l in zip(predictions, labels) if p == l)
return correct / len(labels)
def concept_drift_warning(old_counts, new_counts, threshold=0.1):
total_old = sum(old_counts.values())
total_new = sum(new_counts.values())
for category in old_counts:
p_old = old_counts[category] / total_old
p_new = new_counts.get(category, 0) / total_new
if abs(p_old - p_new) > threshold:
return True
return False
print("Accuracy:", accuracy([1, 0, 1, 1], [1, 0, 0, 1]))
print("Drift?", concept_drift_warning(
{'A': 80, 'B': 20},
{'A': 50, 'B': 50},
))
flowchart LR Deploy["Deployed model"] -->|"serves predictions"| Users["Users"] Users -->|"generate new data"| Data["Data pipeline"] Data -->|"feeds back"| Deploy Data -->|"drift detected?"| Alert["Alert engineer"] style Deploy fill:#166534,color:#fff style Alert fill:#b91c1c,color:#fff
What does this simple train-test split print?
def train_test_split(data):
mid = len(data) // 2
return data[:mid], data[mid:]
train, test = train_test_split([10, 20, 30, 40])
print(train)
print(test)
The mid-point of 4 elements is index 2.
Slicing [:2] gives the first two items; [2:] gives the rest.
Write accuracy(predictions, labels) that returns the fraction of correct predictions. Both arguments are equal-length lists.
def accuracy(predictions, labels):
# fraction of matching elements
pass
Count how many positions have matching elements.
Divide by the total number of predictions.
def accuracy(predictions, labels):
correct = sum(1 for p, l in zip(predictions, labels) if p == l)
return correct / len(labels)
This train_model function accidentally leaks test data into training by using the combined dataset to compute a mean. Fix it so only train_data is used to compute the mean.
def train_model(train_data, test_data):
combined = train_data + test_data
mean = sum(combined) / len(combined)
adjusted = [x - mean for x in train_data]
return adjusted
Remove the combined variable.
Compute the mean using only train_data.
def train_model(train_data, test_data):
mean = sum(train_data) / len(train_data)
adjusted = [x - mean for x in train_data]
return adjusted
Write feature_engineer(records) that takes a list of dicts with keys 'width' and 'height', and returns a new list of dicts that also includes 'area' (width * height) and 'is_square' (True if width == height). Do not modify the original dicts.
def feature_engineer(records):
# Return new list with 'area' and 'is_square' added
pass
Copy each dict with dict(r) before adding keys.
Compute area and is_square from the original values.
def feature_engineer(records):
result = []
for r in records:
new = dict(r)
new['area'] = r['width'] * r['height']
new['is_square'] = r['width'] == r['height']
result.append(new)
return result
Write workflow_stage(description) that returns the ML workflow stage described: 'problem', 'data', 'features', 'model', 'evaluation', 'deployment', or 'monitoring'. Use these keywords: 'label' or 'define' -> 'problem'; 'missing' or 'clean' -> 'data'; 'area' or 'engineer' -> 'features'; 'train' or 'fit' -> 'model'; 'accuracy' or 'score' -> 'evaluation'; 'serve' or 'API' -> 'deployment'; 'drift' or 'degrade' -> 'monitoring'. Otherwise return 'unknown'.
def workflow_stage(description):
# Map description keywords to workflow stages
pass
Convert the description to lowercase before checking.
Check each stage in order and return the first match.
def workflow_stage(description):
text = description.lower()
if 'label' in text or 'define' in text:
return 'problem'
if 'missing' in text or 'clean' in text:
return 'data'
if 'area' in text or 'engineer' in text:
return 'features'
if 'train' in text or 'fit' in text:
return 'model'
if 'accuracy' in text or 'score' in text:
return 'evaluation'
if 'serve' in text or 'api' in text:
return 'deployment'
if 'drift' in text or 'degrade' in text:
return 'monitoring'
return 'unknown'
The loop is never linear
In textbooks the workflow is drawn as a straight line, but in practice you bounce backwards constantly. Evaluation reveals that your features are weak, so you return to feature engineering. Deployment shows the model is too slow, so you simplify it and retrain. Monitoring detects drift, so you collect fresh data and start again. Each iteration teaches you something about the problem that you could not have known at the start. The teams that succeed are not the ones with the best initial plan; they are the ones that build feedback loops fast enough to discover their mistakes before the project runs out of budget.
Recap
- The ML workflow is a loop: problem -> data -> features -> model -> evaluation -> deployment -> monitoring.
- Problem framing and data quality consume more time and determine more success than model choice.
- Feature engineering turns raw data into informative numbers; strong features beat fancy models.
- Data leakage — letting test information into training — produces inflated scores that collapse in production. Split early and touch the test set once.
- Monitoring catches concept drift after deployment, because the real world changes while your model stays frozen.
- Modelling is the middle act. The beginning and the end decide whether the project lives or dies.