Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,7 @@
* [Simplex](linear_programming/simplex.py)

## [Machine Learning](machine_learning)
* [Adaboost](machine_learning/adaboost.py)
* [Apriori Algorithm](machine_learning/apriori_algorithm.py)
* [Astar](machine_learning/astar.py)
* [Automatic Differentiation](machine_learning/automatic_differentiation.py)
Expand Down Expand Up @@ -712,14 +713,17 @@
* [Loss Functions](machine_learning/loss_functions.py)
* Lstm
* [Lstm Prediction](machine_learning/lstm/lstm_prediction.py)
* [Mean Shift](machine_learning/mean_shift.py)
* [Mfcc](machine_learning/mfcc.py)
* [Mini Batch Gradient Descent](machine_learning/mini_batch_gradient_descent.py)
* [Multilayer Perceptron Classifier](machine_learning/multilayer_perceptron_classifier.py)
* [Naive Bayes Text Classification](machine_learning/naive_bayes_text_classification.py)
* [Polynomial Regression](machine_learning/polynomial_regression.py)
* [Principle Component Analysis](machine_learning/principle_component_analysis.py)
* [Q Learning](machine_learning/q_learning.py)
* [Random Forest Classifier](machine_learning/random_forest_classifier.py)
* [Random Forest Regressor](machine_learning/random_forest_regressor.py)
* [Rmsprop](machine_learning/rmsprop.py)
* [Scoring Functions](machine_learning/scoring_functions.py)
* [Self Organizing Map](machine_learning/self_organizing_map.py)
* [Sequential Minimum Optimization](machine_learning/sequential_minimum_optimization.py)
Expand All @@ -736,6 +740,7 @@
* [Arc Length](maths/arc_length.py)
* [Area](maths/area.py)
* [Area Under Curve](maths/area_under_curve.py)
* [Autocorrelation](maths/autocorrelation.py)
* [Average Absolute Deviation](maths/average_absolute_deviation.py)
* [Average Mean](maths/average_mean.py)
* [Average Median](maths/average_median.py)
Expand Down Expand Up @@ -781,6 +786,7 @@
* [Fibonacci](maths/fibonacci.py)
* [Find Max](maths/find_max.py)
* [Find Min](maths/find_min.py)
* [First Fundamental Form](maths/first_fundamental_form.py)
* [Floor](maths/floor.py)
* [Gamma](maths/gamma.py)
* [Gaussian](maths/gaussian.py)
Expand Down Expand Up @@ -843,6 +849,7 @@
* [Square Root](maths/numerical_analysis/square_root.py)
* [Weierstrass Method](maths/numerical_analysis/weierstrass_method.py)
* [Odd Sieve](maths/odd_sieve.py)
* [Padovan Sequence](maths/padovan_sequence.py)
* [Pell Number](maths/pell_number.py)
* [Perfect Cube](maths/perfect_cube.py)
* [Perfect Number](maths/perfect_number.py)
Expand Down Expand Up @@ -872,6 +879,8 @@
* [Reverse Factorial Recursive](maths/reverse_factorial_recursive.py)
* [Segmented Sieve](maths/segmented_sieve.py)
* Series
* [Alternate Harmonic Series](maths/series/alternate_harmonic_series.py)
* [Alternating Harmonic Series](maths/series/alternating_harmonic_series.py)
* [Arithmetic](maths/series/arithmetic.py)
* [Geometric](maths/series/geometric.py)
* [Geometric Series](maths/series/geometric_series.py)
Expand Down Expand Up @@ -911,6 +920,7 @@
* [Polygonal Numbers](maths/special_numbers/polygonal_numbers.py)
* [Pronic Number](maths/special_numbers/pronic_number.py)
* [Proth Number](maths/special_numbers/proth_number.py)
* [Spy Number](maths/special_numbers/spy_number.py)
* [Triangular Numbers](maths/special_numbers/triangular_numbers.py)
* [Trimorphic Number](maths/special_numbers/trimorphic_number.py)
* [Ugly Numbers](maths/special_numbers/ugly_numbers.py)
Expand Down
157 changes: 157 additions & 0 deletions machine_learning/adaboost.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
"""
AdaBoost implementation for binary classification using decision stumps.

Reference: https://en.wikipedia.org/wiki/AdaBoost

>>> import numpy as np
>>> features = np.array([[0, 0], [1, 1], [1, 0], [0, 1]])
>>> labels = np.array([0, 1, 1, 0])
>>> clf = AdaBoost(n_estimators=5)
>>> clf.fit(features, labels)
>>> clf.predict(np.array([[0, 0], [1, 1]]))
array([0, 1])
"""

from typing import Any

import numpy as np


class AdaBoost:
def __init__(self, n_estimators: int = 50) -> None:
"""
Initialize AdaBoost classifier.

Args:
n_estimators: Number of boosting rounds (weak learners).
"""
self.n_estimators: int = n_estimators
self.alphas: list[float] = [] # Weights assigned to each weak learner
self.models: list[dict[str, Any]] = [] # Stores each decision stump

def fit(self, feature_matrix: np.ndarray, target: np.ndarray) -> None:
"""
Train AdaBoost model using decision stumps.

Args:
feature_matrix: 2D array of shape (n_samples, n_features)
target: 1D array of binary labels (0 or 1)
"""
n_samples, _ = feature_matrix.shape

# Initialize uniform sample weights
sample_weights = np.ones(n_samples) / n_samples

# Reset model state
self.models = []
self.alphas = []

# Convert labels to {-1, 1} for boosting
y_signed = np.where(target == 0, -1, 1)

for _ in range(self.n_estimators):
# Train a weighted decision stump
stump = self._build_stump(feature_matrix, y_signed, sample_weights)
pred = stump["pred"]
err = stump["error"]

# Compute alpha (learner weight) with numerical stability
alpha = 0.5 * np.log((1 - err) / (err + 1e-10))

# Update sample weights to focus on misclassified points
sample_weights *= np.exp(-alpha * y_signed * pred)
sample_weights /= np.sum(sample_weights)

# Store the stump and its weight
self.models.append(stump)
self.alphas.append(alpha)

def predict(self, feature_matrix: np.ndarray) -> np.ndarray:
"""
Predict binary class labels for input samples.

Args:
feature_matrix: 2D array of shape (n_samples, n_features)

Returns:
1D array of predicted labels (0 or 1)
"""
clf_preds = np.zeros(feature_matrix.shape[0])

# Aggregate predictions from all stumps
for alpha, stump in zip(self.alphas, self.models):
pred = self._stump_predict(
feature_matrix,
stump["feature"],
stump["threshold"],
stump["polarity"],
)
clf_preds += alpha * pred

# Final prediction: sign of weighted sum
return np.where(clf_preds >= 0, 1, 0)

def _build_stump(
self,
feature_matrix: np.ndarray,
target_signed: np.ndarray,
sample_weights: np.ndarray,
) -> dict[str, Any]:
"""
Build the best decision stump for current sample weights.

Returns:
Dictionary containing stump parameters and predictions.
"""
_, n_features = feature_matrix.shape
min_error = float("inf")
best_stump: dict[str, Any] = {}

# Iterate over all features and thresholds
for feature in range(n_features):
thresholds = np.unique(feature_matrix[:, feature])
for threshold in thresholds:
for polarity in [1, -1]:
pred = self._stump_predict(
feature_matrix,
feature,
threshold,
polarity,
)
error = np.sum(sample_weights * (pred != target_signed))

# Keep stump with lowest weighted error
if error < min_error:
min_error = error
best_stump = {
"feature": feature,
"threshold": threshold,
"polarity": polarity,
"error": error,
"pred": pred.copy(),
}

return best_stump

def _stump_predict(
self,
feature_matrix: np.ndarray,
feature: int,
threshold: float,
polarity: int,
) -> np.ndarray:
"""
Predict using a single decision stump.

Returns:
1D array of predictions in {-1, 1}
"""
pred = np.ones(feature_matrix.shape[0])

# Apply polarity to threshold comparison
if polarity == 1:
pred[feature_matrix[:, feature] < threshold] = -1
else:
pred[feature_matrix[:, feature] > threshold] = -1

return pred
Loading