Lesson 4 · Intro to Machine Learning
Your First Model
Hands-on — train, evaluate, and iterate on a real machine learning model using scikit-learn.
Let's Build Something Real
Theory is necessary. Practice is what makes it stick. In this lesson, we'll train a real classifier to predict whether a tumour is malignant or benign — a classic ML benchmark from the UCI Machine Learning Repository, built into scikit-learn.
The Dataset
We'll use the Breast Cancer Wisconsin dataset: 569 samples, 30 features, 2 classes.
from sklearn.datasets import load_breast_cancer
import pandas as pd
data = load_breast_cancer()
X = pd.DataFrame(data.data, columns=data.feature_names)
y = pd.Series(data.target, name="diagnosis") # 0=malignant, 1=benign
print(f"Samples: {len(X)}")
print(f"Features: {X.shape[1]}")
print(f"Class balance: {y.value_counts().to_dict()}")
# Samples: 569
# Features: 30
# Class balance: {1: 357, 0: 212}
Class imbalance (357 benign vs 212 malignant) is worth noting. Accuracy alone can be misleading — a model that always predicts "benign" would be 62.7% accurate but completely useless. We'll use precision, recall, and F1 score.
Step 1: Split the Data
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2, # 20% for testing
random_state=42, # reproducibility
stratify=y, # maintain class balance in both splits
)
print(f"Training samples: {len(X_train)}")
print(f"Test samples: {len(X_test)}")
Step 2: Preprocess — Scale the Features
Many ML algorithms (especially those using distances or gradients) are sensitive to feature scales. The 30 features in our dataset have very different ranges — mean radius is typically 6–28, while mean area is 143–2501.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # Fit only on training data!
X_test_scaled = scaler.transform(X_test) # Apply same scaling to test
Always fit the scaler ontraining data only, then transform both sets. Fitting on test data (or all data) leaks information about the test set into training — a common and subtle mistake.
Step 3: Train a Model
Let's start simple with Logistic Regression, then compare to a more powerful approach.
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, confusion_matrix
# Train
lr_model = LogisticRegression(max_iter=1000, random_state=42)
lr_model.fit(X_train_scaled, y_train)
# Evaluate
lr_preds = lr_model.predict(X_test_scaled)
print(classification_report(y_test, lr_preds, target_names=["Malignant", "Benign"]))
Typical output:
precision recall f1-score support
Malignant 0.97 0.93 0.95 43
Benign 0.96 0.99 0.97 71
accuracy 0.96 114
96% accuracy with just logistic regression! But we can do better.
Step 4: Try a More Powerful Model
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
import numpy as np
rf_model = RandomForestClassifier(n_estimators=100, random_state=42)
rf_model.fit(X_train_scaled, y_train)
# 5-fold cross-validation for a more reliable estimate
cv_scores = cross_val_score(rf_model, X_train_scaled, y_train, cv=5, scoring="f1")
print(f"CV F1: {cv_scores.mean():.3f} ± {cv_scores.std():.3f}")
# CV F1: 0.973 ± 0.012
rf_preds = rf_model.predict(X_test_scaled)
print(classification_report(y_test, rf_preds, target_names=["Malignant", "Benign"]))
Step 5: Understand What the Model Learned
One of the advantages of Random Forests is interpretability — we can see which features mattered most.
import matplotlib.pyplot as plt
importances = pd.Series(
rf_model.feature_importances_,
index=data.feature_names
).sort_values(ascending=False)
print("Top 5 most important features:")
print(importances.head())
# worst concave points 0.147
# worst perimeter 0.132
# worst radius 0.115
# mean concave points 0.103
# worst area 0.089
Step 6: Model Comparison
| Model | Accuracy | F1 (Malignant) | F1 (Benign) |
|---|---|---|---|
| Logistic Regression | 96.5% | 0.95 | 0.97 |
| Random Forest | 97.4% | 0.96 | 0.98 |
| SVM (RBF kernel) | 97.4% | 0.97 | 0.98 |
For this problem, all three models perform similarly. In practice, try multiple models and pick based on the metric that matters most for your use case.
For medical diagnosis,recall on the malignant class (catching all actual cancers) is more important than overall accuracy. Missing a malignant case is far worse than a false alarm. Always choose metrics that reflect your real-world costs.
Step 7: Save and Load Your Model
import joblib
# Save
joblib.dump(rf_model, "breast_cancer_classifier.pkl")
joblib.dump(scaler, "scaler.pkl")
# Load and use
loaded_model = joblib.load("breast_cancer_classifier.pkl")
loaded_scaler = joblib.load("scaler.pkl")
new_patient = X_test.iloc[[0]]
scaled = loaded_scaler.transform(new_patient)
diagnosis = loaded_model.predict(scaled)
print("Benign" if diagnosis[0] == 1 else "Malignant")
🎉 Course Complete!
You've now covered the complete ML workflow:
- ✅ Understand the problem — what are we predicting? What type of task?
- ✅ Prepare the data — split, scale, handle imbalance
- ✅ Train a model — start simple, then go complex
- ✅ Evaluate correctly — use the right metrics for the problem
- ✅ Interpret the model — understand what it learned
- ✅ Deploy — save and load for production use
The next step is to apply this workflow to your own data. Real ML work is mostly data wrangling and iteration — the modelling part often takes less than 20% of the time. Welcome to the field.