18
18
19
19
class AdaBoost :
20
20
def __init__ (self , n_estimators : int = 50 ) -> None :
21
- """Initialize AdaBoost classifier.
21
+ """
22
+ Initialize AdaBoost classifier.
23
+
22
24
Args:
23
- n_estimators: Number of boosting rounds.
25
+ n_estimators: Number of boosting rounds (weak learners) .
24
26
"""
25
27
self .n_estimators : int = n_estimators
26
- self .alphas : list [float ] = [] # Weights for each weak learner
27
- self .models : list [dict [str , Any ]] = [] # List of weak learners (stumps)
28
+ self .alphas : list [float ] = [] # Weights assigned to each weak learner
29
+ self .models : list [dict [str , Any ]] = [] # Stores each decision stump
28
30
29
31
def fit (self , feature_matrix : np .ndarray , target : np .ndarray ) -> None :
30
- """Fit AdaBoost model.
32
+ """
33
+ Train AdaBoost model using decision stumps.
34
+
31
35
Args:
32
- feature_matrix: (n_samples, n_features) feature matrix
33
- target: (n_samples,) labels (0 or 1)
36
+ feature_matrix: 2D array of shape (n_samples, n_features)
37
+ target: 1D array of binary labels (0 or 1)
34
38
"""
35
- n_samples , _n_features = feature_matrix .shape
36
- sample_weights = np .ones (n_samples ) / n_samples # Initialize sample weights
39
+ n_samples , _ = feature_matrix .shape
40
+
41
+ # Initialize uniform sample weights
42
+ sample_weights = np .ones (n_samples ) / n_samples
43
+
44
+ # Reset model state
37
45
self .models = []
38
46
self .alphas = []
39
- y_signed = np .where (target == 0 , - 1 , 1 ) # Convert labels to -1, 1
47
+
48
+ # Convert labels to {-1, 1} for boosting
49
+ y_signed = np .where (target == 0 , - 1 , 1 )
50
+
40
51
for _ in range (self .n_estimators ):
41
- # Train a decision stump with weighted samples
52
+ # Train a weighted decision stump
42
53
stump = self ._build_stump (feature_matrix , y_signed , sample_weights )
43
54
pred = stump ["pred" ]
44
55
err = stump ["error" ]
45
- # Compute alpha (learner weight)
56
+
57
+ # Compute alpha (learner weight) with numerical stability
46
58
alpha = 0.5 * np .log ((1 - err ) / (err + 1e-10 ))
47
- # Update sample weights
59
+
60
+ # Update sample weights to focus on misclassified points
48
61
sample_weights *= np .exp (- alpha * y_signed * pred )
49
62
sample_weights /= np .sum (sample_weights )
63
+
64
+ # Store the stump and its weight
50
65
self .models .append (stump )
51
66
self .alphas .append (alpha )
52
67
53
68
def predict (self , feature_matrix : np .ndarray ) -> np .ndarray :
54
- """Predict class labels for samples in feature_matrix.
69
+ """
70
+ Predict binary class labels for input samples.
71
+
55
72
Args:
56
- feature_matrix: (n_samples, n_features) feature matrix
73
+ feature_matrix: 2D array of shape (n_samples, n_features)
74
+
57
75
Returns:
58
- (n_samples,) predicted labels (0 or 1)
59
- >>> import numpy as np
60
- >>> features = np.array([[0, 0], [1, 1], [1, 0], [0, 1]])
61
- >>> labels = np.array([0, 1, 1, 0])
62
- >>> clf = AdaBoost(n_estimators=5)
63
- >>> clf.fit(features, labels)
64
- >>> clf.predict(np.array([[0, 0], [1, 1]]))
65
- array([0, 1])
76
+ 1D array of predicted labels (0 or 1)
66
77
"""
67
78
clf_preds = np .zeros (feature_matrix .shape [0 ])
79
+
80
+ # Aggregate predictions from all stumps
68
81
for alpha , stump in zip (self .alphas , self .models ):
69
82
pred = self ._stump_predict (
70
- feature_matrix , stump ["feature" ], stump ["threshold" ], stump ["polarity" ]
83
+ feature_matrix ,
84
+ stump ["feature" ],
85
+ stump ["threshold" ],
86
+ stump ["polarity" ],
71
87
)
72
88
clf_preds += alpha * pred
89
+
90
+ # Final prediction: sign of weighted sum
73
91
return np .where (clf_preds >= 0 , 1 , 0 )
74
92
75
93
def _build_stump (
76
- self , feature_matrix : np .ndarray , target_signed : np .ndarray , sample_weights : np .ndarray
94
+ self ,
95
+ feature_matrix : np .ndarray ,
96
+ target_signed : np .ndarray ,
97
+ sample_weights : np .ndarray ,
77
98
) -> dict [str , Any ]:
78
- """Find the best decision stump for current weights."""
79
- _n_samples , n_features = feature_matrix .shape
99
+ """
100
+ Build the best decision stump for current sample weights.
101
+
102
+ Returns:
103
+ Dictionary containing stump parameters and predictions.
104
+ """
105
+ _ , n_features = feature_matrix .shape
80
106
min_error = float ("inf" )
81
107
best_stump : dict [str , Any ] = {}
108
+
109
+ # Iterate over all features and thresholds
82
110
for feature in range (n_features ):
83
111
thresholds = np .unique (feature_matrix [:, feature ])
84
112
for threshold in thresholds :
85
113
for polarity in [1 , - 1 ]:
86
114
pred = self ._stump_predict (feature_matrix , feature , threshold , polarity )
87
115
error = np .sum (sample_weights * (pred != target_signed ))
116
+
117
+ # Keep stump with lowest weighted error
88
118
if error < min_error :
89
119
min_error = error
90
120
best_stump = {
@@ -94,15 +124,28 @@ def _build_stump(
94
124
"error" : error ,
95
125
"pred" : pred .copy (),
96
126
}
127
+
97
128
return best_stump
98
129
99
130
def _stump_predict (
100
- self , feature_matrix : np .ndarray , feature : int , threshold : float , polarity : int
131
+ self ,
132
+ feature_matrix : np .ndarray ,
133
+ feature : int ,
134
+ threshold : float ,
135
+ polarity : int ,
101
136
) -> np .ndarray :
102
- """Predict using a single decision stump."""
137
+ """
138
+ Predict using a single decision stump.
139
+
140
+ Returns:
141
+ 1D array of predictions in {-1, 1}
142
+ """
103
143
pred = np .ones (feature_matrix .shape [0 ])
144
+
145
+ # Apply polarity to threshold comparison
104
146
if polarity == 1 :
105
147
pred [feature_matrix [:, feature ] < threshold ] = - 1
106
148
else :
107
149
pred [feature_matrix [:, feature ] > threshold ] = - 1
108
- return pred
150
+
151
+ return pred
0 commit comments