ML Mastery Lecture Notes
ML Mastery Lecture Notes
Table of Contents
1. Core Foundations & Exploratory Data Analysis
2. Supervised Learning — Regression Deep Dive
3. Supervised Learning — Classification & Real-World Project
4. Model Tuning, Ensemble Methods & Unsupervised Learning
5. Pro Project Tips & Common Mistakes
1
SECTION 1: Core Foundations & Exploratory Data Analysis
(EDA)
1.1 Conceptual Blueprint — AI vs ML vs DL vs Data Science
Nesting relationship:
AI ⊃ ML ⊃ DL
Data Science overlaps AI/ML but also includes analytics, statistics, and business reporting
2
Traditional programming is preferred when: - Rules are deterministic, small in number, and
well understood. - Explainability/auditability is legally mandatory and a black-box model is
unacceptable. - There is no representative dataset available.
3
• MNAR (Missing Not At Random) — missingness related to the unobserved value
itself (e.g., high earners not disclosing income). Requires domain-aware handling;
naive imputation introduces bias.
4. Outlier Detection & Treatment
• IQR Method:
𝐼𝑄𝑅 = 𝑄3 − 𝑄1
Lower Bound = 𝑄1 − 1.5 × 𝐼𝑄𝑅 , Upper Bound = 𝑄3 + 1.5 × 𝐼𝑄𝑅
Any point outside these bounds is a candidate outlier.
Q1 = df['salary'].quantile(0.25)
Q3 = df['salary'].quantile(0.75)
IQR = Q3 - Q1
lower, upper = Q1 - 1.5*IQR, Q3 + 1.5*IQR
df_clean = df[(df['salary'] >= lower) & (df['salary'] <= upper)]
4
- Centers data around mean 0 with unit variance (std = 1). - Does not bound values to a fixed
range. - Robust-ish to outliers relative to Min-Max, but still affected since 𝜇, 𝜎 are outlier-
sensitive.
from [Link] import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = [Link](X_test) # NEVER fit on test data
Golden Rule: Always fit the scaler on the training set only, then transform both train and
test using those fitted parameters. Fitting on the full dataset (before train-test split) leaks
test-set statistics into training — a classic and very common beginner mistake (data leakage).
5
vectors; combining cuisine lists into counts.
• Feature Selection: Choosing a subset of the most relevant existing features to reduce
dimensionality and noise.
– Filter methods: correlation threshold, Chi-Square test, ANOVA F-test — statistical,
model-agnostic.
– Wrapper methods: Recursive Feature Elimination (RFE) — uses a model’s perfor-
mance to iteratively drop features.
– Embedded methods: L1 (Lasso) regularization naturally zeroes out irrelevant fea-
ture coefficients; tree-based feature_importances_.
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression
model = LogisticRegression(max_iter=1000)
selector = RFE(model, n_features_to_select=5)
[Link](X_train_scaled, y_train)
selected_features = X_train.columns[selector.support_]
1 𝑛 1 𝑛 2
𝐽 (𝜃) = ∑(𝑦𝑖 − 𝑦𝑖̂ )2 = ∑ (𝑦𝑖 − (𝜃0 + 𝜃1 𝑥𝑖 ))
𝑛 𝑖=1 𝑛 𝑖=1
6
1
(Some formulations use 2𝑛 for a cleaner gradient derivative — both are valid; the 21 just
cancels the exponent’s 2 during differentiation.)
Reason Explanation
Differentiability |𝑒| has a sharp, non-differentiable corner at
𝑒 = 0, which breaks gradient-based
optimization. 𝑒2 is smooth and differentiable
everywhere — required for calculating
gradients in Gradient Descent.
Penalizes large errors more Squaring amplifies large deviations
disproportionately, pushing the optimizer to
avoid big misses — often desirable since
large errors are usually more costly in
real-world terms.
Convexity MSE as a function of 𝜃 is a convex
(bowl-shaped) function, guaranteeing a
single global minimum reachable by
Gradient Descent — absolute error’s
optimization landscape is less well-behaved
for gradient methods (though MAE is still
convex, it’s non-smooth at 0).
Mathematical tractability Squared error connects directly to variance
and has a closed-form analytical solution
(Normal Equation) via calculus, unlike MAE
which requires linear programming.
𝜕𝐽 2 𝑛
= − ∑(𝑦𝑖 − 𝑦𝑖̂ )
𝜕𝜃0 𝑛 𝑖=1
𝜕𝐽 2 𝑛
= − ∑(𝑦𝑖 − 𝑦𝑖̂ ) ⋅ 𝑥𝑖
𝜕𝜃1 𝑛 𝑖=1
7
Learning Rate (𝛼)
The learning rate controls the step size of each update.
Variant Description
Batch Gradient Descent Uses the entire dataset per update — stable
but slow for large datasets
Stochastic Gradient Descent (SGD) Uses one random sample per update — fast,
noisy convergence path, can escape local
minima
Mini-Batch Gradient Descent Uses a small batch (e.g., 32/64 samples) per
update — the practical default, balances
speed and stability
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = [Link](X_test)
model = LinearRegression()
[Link](X_train_scaled, y_train)
y_pred = [Link](X_test_scaled)
8
print("Intercept:", model.intercept_)
print("Coefficients:", model.coef_)
Evaluation Metrics
n, p = X_test.shape
adj_r2 = 1 - (1 - r2) * (n - 1) / (n - p - 1)
Interpretation caution: A high 𝑅2 on training data with a much lower 𝑅2 on test data
signals overfitting. Adjusted 𝑅2 is preferred over plain 𝑅2 when comparing models with
different numbers of features.
9
• If 𝜎(𝑧) ≥ 0.5 → predict class 1; else predict class 0 (threshold is tunable).
• The decision boundary is the surface where 𝜎(𝑧) = 0.5, i.e., where 𝑧 = 0 — for 2D
data this is a line; in higher dimensions, a hyperplane.
Cost Function — Log-Loss (Binary Cross-Entropy):
1 𝑛
𝐽 (𝜃) = − ∑ [𝑦𝑖 log(𝑦𝑖̂ ) + (1 − 𝑦𝑖 ) log(1 − 𝑦𝑖̂ )]
𝑛 𝑖=1
MSE isn’t used here because plugging the sigmoid into MSE creates a non-convex cost surface
(many local minima). Log-loss remains convex for logistic regression, guaranteeing Gradient
Descent converges to the global minimum. It also heavily penalizes confident-but-wrong pre-
dictions (e.g., predicting 0.99 when the true label is 0).
Pros: Fast, interpretable coefficients (log-odds), works well when classes are linearly sepa-
rable, outputs calibrated probabilities. Cons: Assumes a linear decision boundary; struggles
with complex, non-linear relationships unless features are engineered (polynomial terms) or
combined with kernel tricks.
from sklearn.linear_model import LogisticRegression
model = LogisticRegression(max_iter=1000)
[Link](X_train_scaled, y_train)
y_pred = [Link](X_test_scaled)
y_proba = model.predict_proba(X_test_scaled)[:, 1]
10
Pros: Simple, no training phase, naturally handles multi-class. Cons: Slow at prediction
time (must compute distance to all training points), sensitive to feature scale (scaling is
mandatory), sensitive to irrelevant features and the curse of dimensionality.
from [Link] import KNeighborsClassifier
model = KNeighborsClassifier(n_neighbors=5, metric='minkowski', p=2) # p=2 -> Euclidean
[Link](X_train_scaled, y_train)
Entropy measures impurity/disorder in a node — 0 when a node is pure (all one class), maximal
when classes are evenly mixed.
|𝑆𝑘 |
Information Gain = 𝐸𝑛𝑡𝑟𝑜𝑝𝑦(𝑝𝑎𝑟𝑒𝑛𝑡) − ∑ 𝐸𝑛𝑡𝑟𝑜𝑝𝑦(𝑆𝑘 )
𝑘
|𝑆|
The tree picks, at each split, the feature/threshold that maximizes information gain (i.e., pro-
duces the purest child nodes).
Gini Impurity (alternative to entropy, used by CART/scikit-learn default):
𝑐
𝐺𝑖𝑛𝑖(𝑆) = 1 − ∑ 𝑝𝑖2
𝑖=1
Computationally cheaper than entropy (no log), generally gives similar results.
Overfitting mitigation: - Pruning: Pre-pruning (stop growing early via max_depth,
min_samples_split, min_samples_leaf) vs Post-pruning (grow fully, then trim back branches
that don’t improve validation performance, e.g., cost-complexity pruning ccp_alpha). -
max_depth: caps how deep the tree can grow — deeper trees memorize training data (high
variance).
Pros: Highly interpretable (visualizable), handles non-linear relationships, no scaling re-
quired, handles mixed feature types. Cons: Prone to overfitting if unconstrained, unstable
(small data changes can produce a very different tree), biased toward features with more
levels.
from [Link] import DecisionTreeClassifier
model = DecisionTreeClassifier(criterion='gini', max_depth=5, min_samples_leaf=10, random_state
[Link](X_train, y_train) # no scaling needed
11
3.4 Support Vector Machines (SVM)
Intuition: Finds the hyperplane that best separates classes by maximizing the margin —
the distance between the hyperplane and the nearest data points of each class (the support
vectors).
2
Math/Logic: - Hyperplane: 𝑤⋅𝑥+𝑏 = 0 - Margin width: ‖𝑤‖ — SVM’s objective is to maximize
this, equivalent to minimizing ‖𝑤‖ subject to correctly classifying all points (hard margin) or
allowing some slack for misclassification (soft margin, controlled by hyperparameter 𝐶 ). - 𝐶
(regularization): small 𝐶 → wider margin, tolerates more misclassification (simpler, more
regularized model); large 𝐶 → narrower margin, tries hard to classify every point correctly
(risk of overfitting).
Kernel Trick: When data isn’t linearly separable in its original space, SVM implicitly maps it
into a higher-dimensional space where a linear separator does exist — without explicitly com-
puting the transformation (computational efficiency via the kernel function). - RBF (Radial
2
Basis Function) Kernel: 𝐾(𝑥𝑖 , 𝑥𝑗 ) = 𝑒−𝛾‖𝑥𝑖 −𝑥𝑗 ‖ — creates flexible, non-linear boundaries;
𝛾 controls the influence radius of a single training point (high 𝛾 = tight, complex boundary →
overfitting risk). - Polynomial Kernel: 𝐾(𝑥𝑖 , 𝑥𝑗 ) = (𝑥𝑖 ⋅𝑥𝑗 +𝑐)𝑑 — captures polynomial-order
interactions.
Pros: Effective in high-dimensional spaces, robust to overfitting when margin maximization
is well-regularized, versatile via kernels. Cons: Computationally expensive on large datasets,
less interpretable, requires careful tuning of C and gamma, requires feature scaling.
from [Link] import SVC
model = SVC(kernel='rbf', C=1.0, gamma='scale')
[Link](X_train_scaled, y_train)
12
Pros: Extremely fast to train, works well with high-dimensional data (text), performs well
even with the independence assumption violated, needs relatively little training data. Cons:
The independence assumption can hurt accuracy when features are strongly correlated; prob-
ability estimates can be poorly calibrated even when class predictions are correct.
from sklearn.naive_bayes import GaussianNB
model = GaussianNB()
[Link](X_train, y_train)
Stratified K-Fold preserves class proportions in each fold — essential for imbalanced classi-
fication problems.
Why K-Fold is preferred: every data point gets used for both training and validation exactly
once across the folds, giving a much more reliable estimate of how the model generalizes, and
reducing the risk that results are an artifact of one particular split.
3.7 Project & Deployment Lessons — Heart Disease & Titanic Datasets
End-to-End Workflow
Raw Data
│
▼
Data Cleaning (nulls, duplicates, dtypes)
│
▼
Feature Encoding (categorical → numeric)
│
▼
Train-Test Split
│
▼
Feature Scaling (fit on train, transform both)
13
│
▼
Model Training + K-Fold CV
│
▼
Model Evaluation & Comparison (accuracy, precision, recall, F1, ROC-AUC)
│
▼
Model Selection
│
▼
Model Persistence (pickle/joblib)
│
▼
Deployment (Flask/Streamlit/Django API)
Feature Encoding
models = {
'Logistic Regression': LogisticRegression(max_iter=1000),
'KNN': KNeighborsClassifier(n_neighbors=7),
'Decision Tree': DecisionTreeClassifier(max_depth=5),
'SVM': SVC(probability=True),
'Naive Bayes': GaussianNB()
}
Why KNN/SVM are often chosen for datasets like Heart Disease: these datasets are
moderate-sized, mostly numeric after encoding, and classes are often not perfectly linearly
separable — KNN captures local patterns well, and SVM (RBF kernel) captures non-linear
boundaries robustly, often outperforming plain Logistic Regression while remaining less
prone to overfitting than an unconstrained Decision Tree.
14
Model Persistence
import pickle
# Save
with open('heart_disease_model.pkl', 'wb') as f:
[Link](model, f)
# Also save the scaler! Predictions will be wrong without matching preprocessing.
with open('[Link]', 'wb') as f:
[Link](scaler, f)
# Load
with open('heart_disease_model.pkl', 'rb') as f:
loaded_model = [Link](f)
Alternative with joblib (preferred for large numpy-heavy models, e.g., Random Forests):
import joblib
[Link](model, 'heart_disease_model.joblib')
loaded_model = [Link]('heart_disease_model.joblib')
app = Flask(__name__)
model = [Link](open('heart_disease_model.pkl', 'rb'))
scaler = [Link](open('[Link]', 'rb'))
@[Link]('/predict', methods=['POST'])
def predict():
data = request.get_json()
features = [Link](data['features']).reshape(1, -1)
features_scaled = [Link](features)
prediction = [Link](features_scaled)
probability = model.predict_proba(features_scaled)[0][1]
return jsonify({'prediction': int(prediction[0]), 'probability': float(probability)})
if __name__ == '__main__':
[Link](debug=True)
Deployment architecture concept: the trained .pkl model is a static artifact — it’s loaded
once when the server starts, then reused for every incoming request. The UI (web form,
Streamlit app, or mobile client) sends raw feature values via an HTTP request → the API
applies the same preprocessing (scaler) used in training → feeds it to the loaded model →
returns a prediction as JSON. This separation (train once offline, serve many times online) is
the standard “training pipeline” vs “inference pipeline” split in production ML systems.
15
SECTION 4: Model Tuning, Ensemble Methods & Unsuper-
vised Learning
4.1 Hyperparameter Optimization
Parameters vs Hyperparameters:
Parameters Hyperparameters
Definition Learned automatically from Set manually before training
data during training begins
Examples Coefficients 𝜃 in Linear Learning rate 𝛼, k in KNN,
Regression, split thresholds C/gamma in SVM, max_depth in
in a Decision Tree, support Trees, number of trees in
vectors in SVM Random Forest
Who sets it The optimization algorithm The practitioner (or a search
(e.g., Gradient Descent) algorithm like
GridSearchCV)
GridSearchCV
Exhaustively tries every combination of specified hyperparameter values, using cross-
validation to score each combination, and returns the best-performing set.
from sklearn.model_selection import GridSearchCV
from [Link] import SVC
param_grid = {
'C': [0.1, 1, 10, 100],
'gamma': ['scale', 0.01, 0.1, 1],
'kernel': ['rbf']
}
For KNN:
param_grid_knn = {'n_neighbors': list(range(1, 21)), 'metric': ['euclidean', 'manhattan']}
grid_knn = GridSearchCV(KNeighborsClassifier(), param_grid_knn, cv=5)
grid_knn.fit(X_train_scaled, y_train)
16
random_search = RandomizedSearchCV(SVC(kernel='rbf'), param_dist, n_iter=20, cv=5, random_state
random_search.fit(X_train_scaled, y_train)
Boosting
Core idea: Train models sequentially, where each new model focuses on correcting the er-
rors (misclassified/high-residual points) made by the previous models. Combines many weak
learners (models barely better than random guessing, typically shallow trees/“stumps”) into
one strong learner. This reduces bias.
Algorithm Mechanism
AdaBoost Increases the sample weight of misclassified
points after each round, so the next weak
learner focuses harder on them; final
prediction is a weighted vote of all learners
Gradient Boosting Each new tree is trained to predict the
residual errors (gradient of the loss function)
of the combined ensemble so far,
progressively minimizing loss
17
Algorithm Mechanism
XGBoost An optimized, regularized, parallelized
implementation of Gradient Boosting — adds
L1/L2 regularization on tree weights,
handles missing values natively, and is
significantly faster; industry standard for
tabular data competitions
Bagging vs Boosting — key difference: Bagging trains models independently and in par-
allel (variance reduction, e.g., Random Forest); Boosting trains models sequentially, each
depending on the last (bias reduction, e.g., XGBoost). Boosting typically achieves higher ac-
curacy but is more prone to overfitting and more sensitive to hyperparameters (learning rate,
number of estimators) than bagging.
(Within-Cluster Sum of Squares — total squared distance of points from their assigned cen-
troid.) Plot WCSS against different values of 𝑘; WCSS always decreases as 𝑘 increases, but
look for the “elbow” point where the rate of decrease sharply slows — that’s the point of
diminishing returns for adding more clusters.
from [Link] import KMeans
import [Link] as plt
18
wcss = []
for k in range(1, 11):
km = KMeans(n_clusters=k, init='k-means++', random_state=42, n_init=10)
[Link](X_scaled)
[Link](km.inertia_)
Silhouette Score: A more rigorous complement to the elbow method, measuring how similar
a point is to its own cluster versus other clusters:
𝑏(𝑖) − 𝑎(𝑖)
𝑠(𝑖) =
max(𝑎(𝑖), 𝑏(𝑖))
where 𝑎(𝑖) = average distance to points in the same cluster, 𝑏(𝑖) = average distance to points
in the nearest other cluster. Ranges from -1 (poor clustering) to +1 (well-separated, dense
clusters).
from [Link] import silhouette_score
score = silhouette_score(X_scaled, km.labels_)
[Link]([Link](pca.explained_variance_ratio_))
[Link]('Number of Components'); [Link]('Cumulative Explained Variance')
[Link]()
Why scaling is mandatory before PCA: PCA identifies directions of maximum variance. If
features are on different scales (e.g., income in the thousands vs age in single/double digits),
the feature with the larger numeric range will dominate the variance calculation purely due
19
to units, not genuine importance — distorting the principal components. Standardization
ensures every feature contributes on equal footing.
When to use PCA: reducing training time/memory for high-dimensional data, mitigating mul-
ticollinearity before Linear/Logistic Regression, visualizing high-dimensional data in 2D/3D,
noise reduction. Trade-off: principal components are linear combinations of original features
and lose direct interpretability — a real cost when explainability matters (e.g., regulated do-
mains).
20
# Mistake Why It Hurts Fix
4 Blindly trusting Producing plots and After every major
model output metrics without result, write 1–2
without interpreting what sentences of
sanity-checking / they mean misses plain-language
writing bugs (e.g., leakage interpretation;
conclusions giving suspiciously question any metric
perfect scores) and that looks “too good”
produces reports
with no actionable
insight
5 Overfitting via Model memorizes Use regularization
unconstrained training data noise (L1/L2), constrain
model complexity instead of learning complexity
(deep trees, no generalizable (max_depth,
regularization, too patterns — great min_samples_leaf),
many features train score, poor monitor train vs
relative to data size) real-world validation gap, use
performance cross-validation
21
Scenario Recommended Starting Point Reasoning
Larger tabular dataset, Random Forest (bagging) Strong out-of-the-box
accuracy is the priority, performance, resistant to
interpretability secondary overfitting, provides feature
importances
Large tabular dataset, Gradient Boosting / State-of-the-art on
squeezing out maximum XGBoost (boosting) structured/tabular data,
accuracy, handles missing values,
competition/production- highly tunable
grade
High-dimensional sparse Naive Bayes Extremely fast, performs
data (text, spam) well despite the
independence assumption,
standard baseline for text
Need to reduce PCA (as a preprocessing Preserves variance,
dimensionality / visualize / step, not a classifier) decorrelates features,
remove multicollinearity first speeds up downstream
models
Dataset size < features (p » Regularized Regularization controls
n), risk of overfitting linear Linear/Logistic variance; embedded feature
models Regression (Lasso/Ridge) selection via L1
or tree ensembles
No labels available, K-Means Clustering Standard unsupervised
exploring natural groupings baseline; use Elbow +
Silhouette to pick k
End of Mastery Lecture Notes — Complete Machine Learning Course (4-Part Series).
22