No of Post Views:

23 hits

Machine Learning for Quants Series with Python (Part 9)

Introduction

In Part 8, we explored individual Decision Trees and introduced the concept of ensembles. We learned that while a single decision tree is highly interpretable, it is notoriously prone to overfitting; it memorizes the training data but struggles to generalize to unseen market conditions.

For quantitative analysts, relying on a single model is akin to relying on a single indicator to trade: it’s risky. This brings us to the formal study of Ensemble Learning. The core philosophy of ensemble learning is the “Wisdom of the Crowds.” If you combine the predictions of multiple, independent models (often called weak learners or base learners), the collective prediction is usually far more accurate and robust than any single model.

In this tutorial, we will dive deep into the mechanics of Bagging and Stacking. We will build a multi-model architecture to predict the direction of a financial index (similar to the LUXXX index problem), demonstrating how uncorrelated models can cancel out each other’s errors.

Learning Objectives

By the end of this tutorial, you will be able to:

  • Differentiate between the three main ensemble paradigms: Blending, Bagging, and Stacking.
  • Explain the statistical mechanism of Bootstrap Aggregating (Bagging) for variance reduction.
  • Build a Stacking Classifier using disparate Base Learners (SVM, Naive Bayes, Decision Trees) and a Meta-Model.
  • Implement Out-of-Fold validation to prevent data leakage when training Meta-Models.

Prerequisites

  • Prior Knowledge: Support Vector Machines (SVM), Decision Trees, Naive Bayes, Train/Test Splits.
  • Libraries: scikit-learn, pandas, numpy, matplotlib.

Core Concepts

1. Why Ensembles Work (The Math of Diversification)

In finance, portfolio theory dictates that combining uncorrelated assets reduces overall portfolio variance without sacrificing expected return. Ensemble learning applies the exact same mathematical principle to machine learning algorithms.

If you have three models with a 60% accuracy rate, and their errors are perfectly uncorrelated, taking a majority vote among them will yield an accuracy higher than 60%. The ensemble only fails when most of the models are wrong at the same time.

2. Bagging (Bootstrap Aggregating)

Bagging is designed to reduce variance (overfitting).

  1. Bootstrap: We create multiple subsets of the training data by sampling with replacement. This means a single training example might appear multiple times in one subset and not at all in another.
  2. Aggregating: We train an independent model (usually a Decision Tree) on each subset. To make a final prediction, we aggregate the outputs by taking a simple average (for regression) or a majority vote (for classification).
  3. Random Forest is the most famous bagging algorithm, adding an extra layer of randomness by also sampling a random subset of features at each split.

3. Stacking (Stacked Generalization)

While Bagging uses the same algorithm on different data subsets, Stacking typically uses different algorithms on the same data.

  1. Level-0 (Base Learners): We train a diverse set of models (e.g., a Tree, an SVM, and a Naive Bayes classifier).
  2. Level-1 (Meta-Model): Instead of a simple majority vote, we train a completely new model (often a simple Logistic Regression) to learn how to best combine the predictions of the Base Learners.

Trainer Note: Stacking learns which base models to trust in different scenarios. If the SVM is great at catching volatility spikes, but the Tree is better at trending markets, the Meta-Model learns these nuances.

The Hands-On Practice

Step 1: Data Preparation (Simulating an Index Classification Problem)

We will simulate a dataset representing an index classification problem (e.g., predicting if an index will move up by a certain threshold tomorrow based on technical indicators).

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

from sklearn.datasets import make_classification

from sklearn.model_selection import train_test_split

from sklearn.preprocessing import StandardScaler

 

# 1. Simulate Financial Index Data

# 2000 days of data, 10 technical indicators (features)

X, y = make_classification(n_samples=2000, n_features=10, n_informative=7,

n_redundant=2, random_state=42, weights=[0.5, 0.5])

 

# Scale the data (Crucial for SVM and regularized models)

scaler = StandardScaler()

X_scaled = scaler.fit_transform(X)

 

# Split into Training (80%) and Holdout/Test (20%)

X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state=42)

 

print(f“Training features shape: {X_train.shape}”)

print(f“Testing features shape: {X_test.shape}”)

 

Step 2: Evaluating Base Learners Independently

To prove the value of stacking, we must first see how our models perform individually. We will use a diverse set of learners: a Decision Tree (non-linear, high variance), an SVM (margin-based), and Gaussian Naive Bayes (probabilistic).

from sklearn.tree import DecisionTreeClassifier

from sklearn.svm import SVC

from sklearn.naive_bayes import GaussianNB

from sklearn.metrics import accuracy_score

 

# Initialize Base Learners

# Note: probability=True is required for SVM if we want to extract predicted probabilities later

model_dt = DecisionTreeClassifier(max_depth=5, random_state=42)

model_svm = SVC(probability=True, random_state=42)

model_nb = GaussianNB()

 

base_models = [(‘Decision Tree’, model_dt),

(‘SVM’, model_svm),

(‘Naive Bayes’, model_nb)]

 

# Train and evaluate each independently

print(“— Base Learner Independent Performance —“)

for name, model in base_models:

model.fit(X_train, y_train)

y_pred = model.predict(X_test)

acc = accuracy_score(y_test, y_pred)

print(f“{name} Accuracy: {acc:.4f}”)

 

Text representation of a table showing base learner independent performance metrics: Decision Tree Accuracy: 0.8475, SVM Accuracy: 0.9350, Naive Bayes Accuracy: 0.7850.

Step 3: Implementing Stacking Classifier

We could build the meta-model manually using cross_val_predict to avoid data leakage (a crucial concept where the meta-model must be trained on out-of-sample predictions from the base models). Fortunately, scikit-learn handles this internal cross-validation seamlessly with StackingClassifier.

from sklearn.ensemble import StackingClassifier

from sklearn.linear_model import LogisticRegression

 

# Define the Level-0 estimators (Base Models)

estimators = [

(‘dt’, DecisionTreeClassifier(max_depth=5, random_state=42)),

(svm, SVC(probability=True, random_state=42)),

(nb, GaussianNB())

]

 

# Define the Level-1 meta-model

# Logistic Regression is ideal as it acts as a weighted voter

meta_model = LogisticRegression()

 

# Initialize the Stacking Classifier

# cv=5 means it uses 5-fold cross-validation to generate the training data for the meta-model

stacking_clf = StackingClassifier(

estimators=estimators,

final_estimator=meta_model,

cv=5

)

 

# Train the Stacking ensemble

print(“n— Training Stacking Ensemble —“)

stacking_clf.fit(X_train, y_train)

 

# Evaluate the ensemble

stack_pred = stacking_clf.predict(X_test)

stack_acc = accuracy_score(y_test, stack_pred)

print(f“Stacking Ensemble Accuracy: {stack_acc:.4f}”)

 

-- Training Stacking Ensemble -- Stacking Ensemble Accuracy: 0.9400

Observation: You should typically see the Stacking ensemble outperform or equal the best individual base learner. Even a 1-2% edge in quantitative finance translates to significant alpha over time.

Step 4: Inspecting the Meta-Model (Who does it trust?)

A fascinating aspect of stacking is seeing how the meta-model weighted the base learners. Let’s look at the coefficients of the Logistic Regression meta-model.

# Extract the trained meta-model

meta_model_trained = stacking_clf.final_estimator_

 

# The coefficients represent the “weight” given to each base learner

weights = meta_model_trained.coef_[0]

 

plt.figure(figsize=(8, 4))

plt.bar([‘Decision Tree’, ‘SVM’, ‘Naive Bayes’], weights, color=[‘blue’, ‘orange’, ‘green’])

plt.title(“Meta-Model Weights (Trust Levels)”)

plt.ylabel(“Logistic Regression Coefficient”)

plt.axhline(0, color=‘black’, linewidth=0.8)

plt.show()

 

Bar chart showing meta-model weights (trust levels) for Decision Tree, SVM, and Naive Bayes with respective logistic regression coefficients. SVM has the highest coefficient, followed by Decision Tree and Naive Bayes.

Check Your Work:

  • Leakage Check: Why did StackingClassifier use cv=5 internally? If the base models trained on the whole training set and then passed those exact predictions to the meta-model, the meta-model would over-rely on the Decision Tree (which naturally overfits training data). Cross-validation ensures the meta-model learns from out-of-sample mistakes.
  • Correlation Test: Ensembles work best when models make different mistakes. Print the predictions of the three base models on the test set and calculate their correlation matrix. If the correlation is 0.99, stacking won’t help much!

Conclusion

In this lesson, we moved beyond single estimators. We saw how Bagging reduces variance by training on bootstrap samples, and we successfully implemented Stacking to combine the diverse theoretical strengths of Trees, SVMs, and Bayesian probability.

By using a Logistic Regression meta-model, we created an algorithmic “portfolio manager” that learned exactly how much capital (weight) to allocate to each of its subordinate analysts (base learners).

In the next part, we will explore the final pillar of ensemble learning: Boosting. We will pull back the curtain on AdaBoost and Gradient Boosting to see how models can mathematically learn from the mistakes of their predecessors.


Leave a Reply

Discover more from SimplifiedZone

Subscribe now to keep reading and get access to the full archive.

Continue reading

Discover more from SimplifiedZone

Subscribe now to keep reading and get access to the full archive.

Continue reading