๐Ÿ“Š AdaBoost

Boosting by reweighting the data, not by fitting gradients
Trees & Ensembles Intermediate
Contents

    XGBoost, LightGBM and CatBoost are all gradient boosting: each tree is fitted to the gradient of a loss. AdaBoost came first, and boosts a different way — by reweighting the data.

    The idea, from 1995, is startlingly simple:

    Train a weak model. Find which points it got wrong. Make those points matter more. Train another. Repeat. Give each model a vote proportional to how good it was.

    $$F(x) = \operatorname{sign}\!\left(\sum_{m=1}^{M}\alpha_m h_m(x)\right)$$

    A weak learner need only beat chance — here, a decision stump: a tree of depth 1, one question. The remarkable result is that a sequence of such stumps, correctly weighted, becomes a strong classifier.

    Everything below is computed by hand for three rounds — every sample weight, every $\alpha$, every reweighting — then checked against scikit-learn.

    Display helpers ready: banner, step, check, expand, show_matrix, show_matmul, show_elementwise.

    1. The Data and the Weak Learner

    The same 12 EEG recordings as the tree walkthroughs. AdaBoost uses labels in $\{-1, +1\}$, because that convention makes $y \cdot h(x)$ positive exactly when the prediction is right — which the $\alpha$ formula relies on.

    12 recordings x 3 features
    
        id      region   line_length    spike_rate           age   y_class    y_reg
    -------------------------------------------------------------------------------
     rec00     frontal          12.0           0.5          24.0         0      0.2
     rec01    temporal          15.0           1.2          31.0         0      0.5
     rec02    temporal          42.0           6.8          58.0         1      6.4
     rec03   occipital          38.0           5.1          47.0         1      4.8
     rec04     frontal          11.0           0.3          19.0         0      0.1
     rec05    temporal          55.0           8.2          63.0         1      9.1
     rec06   occipital          40.0           1.9          35.0         1      3.5
     rec07    temporal          47.0           7.4          52.0         1      7.2
     rec08     frontal          14.0           0.8          28.0         0      0.3
     rec09   occipital          51.0           6.1          60.0         1      6.9
     rec10     frontal          45.0           2.4          41.0         0      1.6
     rec11    temporal          35.0           4.7          30.0         1      4.1
    -------------------------------------------------------------------------------
    
    class balance: 5 negative, 7 positive
    y_reg mean = 3.7250, std = 3.0518
    labels recoded: [-1 -1  1  1 -1  1  1  1 -1  1 -1  1]
    
    the best single stump on UNIFORM weights (w = 1/12 each):
    
       rule : predict -1 if line_length <= 25.00, else +1
       weighted error : 0.083333
       accuracy       : 0.9167
    
    A single stump gets 11 of 12 right. It is 'weak' - better than the 50% a coin would
    manage, and not much more. That is all AdaBoost requires.

    2. The Three Formulas

    Each round does exactly three things:

    1. Weighted error of the new weak learner:

    $$\varepsilon_m = \sum_{i} w_i \,\mathbb{1}[\,h_m(x_i) \ne y_i\,]$$

    2. Its vote, derived by minimising exponential loss:

    $$\alpha_m = \frac{1}{2}\ln\!\left(\frac{1-\varepsilon_m}{\varepsilon_m}\right)$$

    3. Reweight, then renormalise so the weights sum to 1:

    $$w_i \leftarrow w_i \exp\!\big(-\alpha_m y_i h_m(x_i)\big)$$

    Read the $\alpha$ formula — it does something sensible at every value:

    $\varepsilon$ $\alpha$ meaning
    0.01 $+2.30$ nearly perfect → large vote
    0.40 $+0.20$ slightly useful → small vote
    0.50 $0$ a coin → no vote at all
    0.70 $-0.42$ worse than chance → negative vote — believe its opposite
    error eps       alpha     exp(+a)     exp(-a)                             reading
    ----------------------------------------------------------------------------------
           0.01      2.2976      9.9499      0.1005          nearly perfect -> big vote
           0.10      1.0986      3.0000      0.3333                useful -> solid vote
           0.25      0.5493      1.7321      0.5774                useful -> solid vote
           0.40      0.2027      1.2247      0.8165         barely useful -> small vote
           0.49      0.0200      1.0202      0.9802         barely useful -> small vote
           0.50      0.0000      1.0000      1.0000                   a coin -> ignored
           0.51     -0.0200      0.9802      1.0202       worse than chance -> inverted
           0.70     -0.4236      0.6547      1.5275       worse than chance -> inverted
    ----------------------------------------------------------------------------------
    
    the reweighting factor exp(-alpha*y*h):
       correct  (y*h = +1) -> multiply weight by exp(-alpha)  <- shrinks
       wrong    (y*h = -1) -> multiply weight by exp(+alpha)  <- grows
    
       at eps = 0.25, alpha = 0.5493:
          correct points shrink by 0.5774x
          wrong   points grow  by 1.7321x
          ratio: a mistake becomes 3.00x more important than a hit
    
    
    why that alpha? it minimises the exponential loss.
    With Z(a) = sum_i w_i exp(-a*y_i*h_i) = (1-eps)e^-a + eps*e^a,
    setting dZ/da = 0 gives a = 0.5*ln((1-eps)/eps). Checked numerically:
    
       eps = 0.1: grid minimum at a = +1.09860, formula gives +1.09861   MATCH: True
       eps = 0.25: grid minimum at a = +0.54930, formula gives +0.54931   MATCH: True
       eps = 0.4: grid minimum at a = +0.20270, formula gives +0.20273   MATCH: True

    3. Three Rounds, Every Number

    ==============================================================================
      ROUND 1
    ==============================================================================
    
    chosen stump: predict -1 if line_length <= 25.00
    
        id    weight w_i     y    h(x)     y*h   correct?    w if wrong
    ------------------------------------------------------------------
     rec00      0.083333    -1      -1       1        yes      0.000000
     rec01      0.083333    -1      -1       1        yes      0.000000
     rec02      0.083333     1       1       1        yes      0.000000
     rec03      0.083333     1       1       1        yes      0.000000
     rec04      0.083333    -1      -1       1        yes      0.000000
     rec05      0.083333     1       1       1        yes      0.000000
     rec06      0.083333     1       1       1        yes      0.000000
     rec07      0.083333     1       1       1        yes      0.000000
     rec08      0.083333    -1      -1       1        yes      0.000000
     rec09      0.083333     1       1       1        yes      0.000000
     rec10      0.083333    -1       1      -1         NO      0.083333
     rec11      0.083333     1       1       1        yes      0.000000
    ------------------------------------------------------------------
       sum      1.000000
    
       eps = sum of weights on WRONG points
           = 0.0833 = 0.083333
    
       alpha = 0.5 * ln((1 - 0.083333) / 0.083333)
             = 0.5 * ln(11.000000) = 1.198948
    
       reweight: w_i * exp(-alpha * y_i * h_i)
          correct -> x exp(-1.1989) = 0.301511
          wrong   -> x exp(+1.1989) = 3.316625
       normalising constant Z = 0.552771
    
        id        old w      factor    new (raw)    new (norm)     change
    ------------------------------------------------------------------
     rec00     0.083333    0.301511     0.025126      0.045455     0.545x
     rec01     0.083333    0.301511     0.025126      0.045455     0.545x
     rec02     0.083333    0.301511     0.025126      0.045455     0.545x
     rec03     0.083333    0.301511     0.025126      0.045455     0.545x
     rec04     0.083333    0.301511     0.025126      0.045455     0.545x
     rec05     0.083333    0.301511     0.025126      0.045455     0.545x
     rec06     0.083333    0.301511     0.025126      0.045455     0.545x
     rec07     0.083333    0.301511     0.025126      0.045455     0.545x
     rec08     0.083333    0.301511     0.025126      0.045455     0.545x
     rec09     0.083333    0.301511     0.025126      0.045455     0.545x
     rec10     0.083333    3.316625     0.276385      0.500000     6.000x
     rec11     0.083333    0.301511     0.025126      0.045455     0.545x
    ------------------------------------------------------------------
       weights sum to 1.0000000000
    
    ==============================================================================
      ROUND 2
    ==============================================================================
    
    chosen stump: predict -1 if spike_rate <= 3.55
    
        id    weight w_i     y    h(x)     y*h   correct?    w if wrong
    ------------------------------------------------------------------
     rec00      0.045455    -1      -1       1        yes      0.000000
     rec01      0.045455    -1      -1       1        yes      0.000000
     rec02      0.045455     1       1       1        yes      0.000000
     rec03      0.045455     1       1       1        yes      0.000000
     rec04      0.045455    -1      -1       1        yes      0.000000
     rec05      0.045455     1       1       1        yes      0.000000
     rec06      0.045455     1      -1      -1         NO      0.045455
     rec07      0.045455     1       1       1        yes      0.000000
     rec08      0.045455    -1      -1       1        yes      0.000000
     rec09      0.045455     1       1       1        yes      0.000000
     rec10      0.500000    -1      -1       1        yes      0.000000
     rec11      0.045455     1       1       1        yes      0.000000
    ------------------------------------------------------------------
       sum      1.000000
    
       eps = sum of weights on WRONG points
           = 0.0455 = 0.045455
    
       alpha = 0.5 * ln((1 - 0.045455) / 0.045455)
             = 0.5 * ln(21.000000) = 1.522261
    
       reweight: w_i * exp(-alpha * y_i * h_i)
          correct -> x exp(-1.5223) = 0.218218
          wrong   -> x exp(+1.5223) = 4.582576
       normalising constant Z = 0.416598
    
        id        old w      factor    new (raw)    new (norm)     change
    ------------------------------------------------------------------
     rec00     0.045455    0.218218     0.009919      0.023810     0.524x
     rec01     0.045455    0.218218     0.009919      0.023810     0.524x
     rec02     0.045455    0.218218     0.009919      0.023810     0.524x
     rec03     0.045455    0.218218     0.009919      0.023810     0.524x
     rec04     0.045455    0.218218     0.009919      0.023810     0.524x
     rec05     0.045455    0.218218     0.009919      0.023810     0.524x
     rec06     0.045455    4.582576     0.208299      0.500000    11.000x
     rec07     0.045455    0.218218     0.009919      0.023810     0.524x
     rec08     0.045455    0.218218     0.009919      0.023810     0.524x
     rec09     0.045455    0.218218     0.009919      0.023810     0.524x
     rec10     0.500000    0.218218     0.109109      0.261905     0.524x
     rec11     0.045455    0.218218     0.009919      0.023810     0.524x
    ------------------------------------------------------------------
       weights sum to 1.0000000000
    
    ==============================================================================
      ROUND 3
    ==============================================================================
    
    chosen stump: predict +1 if line_length <= 43.50
    
        id    weight w_i     y    h(x)     y*h   correct?    w if wrong
    ------------------------------------------------------------------
     rec00      0.023810    -1       1      -1         NO      0.023810
     rec01      0.023810    -1       1      -1         NO      0.023810
     rec02      0.023810     1       1       1        yes      0.000000
     rec03      0.023810     1       1       1        yes      0.000000
     rec04      0.023810    -1       1      -1         NO      0.023810
     rec05      0.023810     1      -1      -1         NO      0.023810
     rec06      0.500000     1       1       1        yes      0.000000
     rec07      0.023810     1      -1      -1         NO      0.023810
     rec08      0.023810    -1       1      -1         NO      0.023810
     rec09      0.023810     1      -1      -1         NO      0.023810
     rec10      0.261905    -1      -1       1        yes      0.000000
     rec11      0.023810     1       1       1        yes      0.000000
    ------------------------------------------------------------------
       sum      1.000000
    
       eps = sum of weights on WRONG points
           = 0.0238 + 0.0238 + 0.0238 + 0.0238 + 0.0238 + 0.0238 + 0.0238 = 0.166667
    
       alpha = 0.5 * ln((1 - 0.166667) / 0.166667)
             = 0.5 * ln(5.000000) = 0.804719
    
       reweight: w_i * exp(-alpha * y_i * h_i)
          correct -> x exp(-0.8047) = 0.447214
          wrong   -> x exp(+0.8047) = 2.236068
       normalising constant Z = 0.745356
    
        id        old w      factor    new (raw)    new (norm)     change
    ------------------------------------------------------------------
     rec00     0.023810    2.236068     0.053240      0.071429     3.000x
     rec01     0.023810    2.236068     0.053240      0.071429     3.000x
     rec02     0.023810    0.447214     0.010648      0.014286     0.600x
     rec03     0.023810    0.447214     0.010648      0.014286     0.600x
     rec04     0.023810    2.236068     0.053240      0.071429     3.000x
     rec05     0.023810    2.236068     0.053240      0.071429     3.000x
     rec06     0.500000    0.447214     0.223607      0.300000     0.600x
     rec07     0.023810    2.236068     0.053240      0.071429     3.000x
     rec08     0.023810    2.236068     0.053240      0.071429     3.000x
     rec09     0.023810    2.236068     0.053240      0.071429     3.000x
     rec10     0.261905    0.447214     0.117127      0.157143     0.600x
     rec11     0.023810    0.447214     0.010648      0.014286     0.600x
    ------------------------------------------------------------------
       weights sum to 1.0000000000
    
    ==============================================================================
    
    the ensemble so far:
    
       h1: line_length <= 25.00 -> -1   alpha = 1.198948
       h2: spike_rate <= 3.55 -> -1   alpha = 1.522261
       h3: line_length <= 43.50 -> +1   alpha = 0.804719

    What the weights are doing

    The weight vector is AdaBoost's memory. It says "here is what the ensemble still gets wrong", and the next weak learner is fitted to that, not to the raw data.

    Notice what falls out of the reweighting: after each round the new stump's weighted error on the updated weights is exactly 0.5. The reweighting makes the previous learner useless on the new distribution — forcing the next one to find something different.

    round    eps of THIS stump    eps of the PREVIOUS stump on new weights
    ------------------------------------------------------------------------
         1             0.083333                                            
         2             0.045455                                    0.500000
         3             0.166667                                    0.500000
         4             0.157143                                    0.500000
    ------------------------------------------------------------------------
    
    The right column sits at 0.5 - a coin flip. Each round deliberately
    erases the previous learner's advantage, which is why the ensemble keeps
    acquiring new information instead of repeating itself.
    
    after 6 rounds the heaviest samples are: ['rec06', 'rec10', 'rec04']
       weights: [0.305  0.1855 0.0714]   (uniform would be 0.0833)
    
    Those are the recordings that sit closest to the boundary - the same
    ones the SVM walkthrough would call support vectors, arrived at by a
    completely different route.

    4. The Ensemble Prediction

    $$F(x) = \sum_m \alpha_m h_m(x), \qquad \hat{y} = \operatorname{sign} F(x)$$

    The magnitude of $F$ is a confidence: the margin. A point with $y F(x) \gg 0$ is correctly and confidently classified.

    the 6-stump ensemble, evaluated on every recording
    
        id       a1h1       a2h2       a3h3       a4h4       a5h5       a6h6       F(x)   pred     y    margin
    ----------------------------------------------------------------------------------------------------------
     rec00    -1.1989    -1.5223     0.8047    -0.8398    -0.7651     0.7568    -2.7646     -1    -1    2.7646
     rec01    -1.1989    -1.5223     0.8047    -0.8398    -0.7651     0.7568    -2.7646     -1    -1    2.7646
     rec02     1.1989     1.5223     0.8047     0.8398     0.7651     0.7568     5.8876      1     1    5.8876
     rec03     1.1989     1.5223     0.8047     0.8398     0.7651     0.7568     5.8876      1     1    5.8876
     rec04    -1.1989    -1.5223     0.8047    -0.8398    -0.7651     0.7568    -2.7646     -1    -1    2.7646
     rec05     1.1989     1.5223    -0.8047     0.8398     0.7651    -0.7568     2.7646      1     1    2.7646
     rec06     1.1989    -1.5223     0.8047     0.8398    -0.7651     0.7568     1.3129      1     1    1.3129
     rec07     1.1989     1.5223    -0.8047     0.8398     0.7651    -0.7568     2.7646      1     1    2.7646
     rec08    -1.1989    -1.5223     0.8047    -0.8398    -0.7651     0.7568    -2.7646     -1    -1    2.7646
     rec09     1.1989     1.5223    -0.8047     0.8398     0.7651    -0.7568     2.7646      1     1    2.7646
     rec10     1.1989    -1.5223    -0.8047     0.8398    -0.7651    -0.7568    -1.8101     -1    -1    1.8101
     rec11     1.1989     1.5223     0.8047     0.8398     0.7651     0.7568     5.8876      1     1    5.8876
    ----------------------------------------------------------------------------------------------------------
    
    ensemble accuracy: 1.0000   (12/12)
    single best stump : 0.9167
    
    
    how accuracy and margin evolve
    
     rounds   train accuracy    min margin     exp loss
    --------------------------------------------------
          1           0.9167       -1.1989     0.552771
          2           0.9167       -0.3233     0.230283
          3           1.0000        0.4814     0.171643
          4           1.0000        0.2882     0.124934
          5           1.0000        0.5561     0.095570
          6           1.0000        1.3129     0.073500
    --------------------------------------------------
    
    Even after training accuracy reaches its ceiling, the MARGIN keeps
    improving and the exponential loss keeps falling. That is the classic
    AdaBoost observation: test error often continues to fall after training
    error hits zero, because the ensemble is still growing more confident.

    5. Why the Exponential Loss

    AdaBoost is not a heuristic. It is exactly forward stagewise additive modelling under the exponential loss:

    $$\mathcal{L} = \sum_i \exp\big(-y_i F(x_i)\big)$$

    At each round it adds the term $\alpha_m h_m$ that most reduces that loss, holding everything already added fixed — and the $\alpha$ formula above is the exact minimiser. That connection is what places AdaBoost in the same family as gradient boosting: same additive structure, different loss.

    The exponential loss also explains AdaBoost's known weakness. It penalises a misclassified point exponentially in its margin, so a mislabelled example can dominate the weights entirely. AdaBoost is famously sensitive to label noise, and this is why.

    margin    exponential     logistic     ratio
    --------------------------------------------
         0.0         1.0000       1.0000      1.00x
        -0.5         1.6487       1.8946      0.87x
        -1.0         2.7183       3.0685      0.89x
        -2.0         7.3891       5.7970      1.27x
        -3.0        20.0855       8.6597      2.32x
        -4.0        54.5982      11.5420      4.73x
    --------------------------------------------
    
    At a margin of -4 the exponential loss is 54.6 while logistic is 11.5.
    One badly mislabelled point can therefore soak up almost all the weight.
    
    
    label noise: flip one label and watch
    
     round    weight on the flipped point    share of total
    --------------------------------------------------------
         1                       0.250000            25.0%
         2                       0.178571            17.9%
         4                       0.286458            28.6%
         6                       0.289474            28.9%
         8                       0.373679            37.4%
    --------------------------------------------------------
    
    One mislabelled recording ends up carrying 37% of all the weight.
    Every subsequent weak learner is fitted mainly to an error in the data.
    
    This is why gradient boosting with a logistic loss largely replaced
    AdaBoost in practice - same additive idea, a loss that does not
    over-react.

    6. Checking Against scikit-learn

    scikit-learn implements SAMME, the multi-class generalisation, which reduces to the binary formulas above when there are two classes. With estimator=DecisionTreeClassifier(max_depth=1) it uses exactly the stumps used here.

    (As of scikit-learn 1.8 the algorithm argument is gone — SAMME is the only option.)

    round     our alpha    sklearn alpha     ratio
    ----------------------------------------------
         1      1.198948         2.397895    2.000x
         2      1.522261         3.044522    2.000x
         3      0.804719         1.609438    2.000x
         4      0.839821         1.679642    2.000x
         5      0.765094         1.530189    2.000x
         6      0.756778         1.513556    2.000x
    ----------------------------------------------
    
    sklearn's SAMME uses alpha = ln((1-eps)/eps) + ln(K-1), which for K = 2
    classes is ln((1-eps)/eps) - exactly TWICE our 0.5*ln(...).
    The factor of 2 cancels in sign(F), so the predictions are identical:
    
       every ratio is 2: True
    
       our predictions     : [-1 -1  1  1 -1  1  1  1 -1  1 -1  1]
       sklearn predictions : [-1 -1  1  1 -1  1  1  1 -1  1 -1  1]
       MATCH: True
       accuracy ours 1.0000  sklearn 1.0000   MATCH: True
    
    
    stumps chosen, ours vs sklearn
    
     round                                ours                             sklearn
    ------------------------------------------------------------------------------
         1                line_length <= 25.00                  spike_rate <= 3.55
         2                  spike_rate <= 3.55                line_length <= 25.00
         3                line_length <= 43.50                line_length <= 43.50
         4                line_length <= 25.00                  spike_rate <= 3.55
         5                  spike_rate <= 3.55                line_length <= 25.00
         6                line_length <= 43.50                line_length <= 43.50
    ------------------------------------------------------------------------------
    
    
    staged accuracy
    
     rounds       ours    sklearn    match
    ------------------------------------
          1     0.9167     0.9167     True
          2     0.9167     0.9167     True
          3     1.0000     1.0000     True
          4     1.0000     1.0000     True
          5     1.0000     1.0000     True
          6     1.0000     1.0000     True
    ------------------------------------

    7. Summary

    Step Formula
    Weighted error $\varepsilon_m = \sum_i w_i\,\mathbb{1}[h_m(x_i)\ne y_i]$
    Vote $\alpha_m = \tfrac12\ln\frac{1-\varepsilon_m}{\varepsilon_m}$
    Reweight $w_i \leftarrow w_i e^{-\alpha_m y_i h_m(x_i)}$, renormalise
    Predict $\operatorname{sign}\sum_m \alpha_m h_m(x)$
    Implied loss $\sum_i e^{-y_iF(x_i)}$

    The one idea: make the data harder instead of the model bigger. Each round re-describes the problem so that what remains unsolved is what matters, and a sequence of one-question models becomes a strong classifier.

    How it relates to the rest of the series: AdaBoost and gradient boosting are the same additive scheme, $F_m = F_{m-1} + \alpha_m h_m$. AdaBoost reaches it through reweighting under exponential loss; XGBoost reaches it by fitting gradients of an arbitrary loss. Recognising AdaBoost as "gradient boosting with the exponential loss" is what unified the two, and gradient boosting won because any loss became usable.

    Its known weakness, demonstrated above: the exponential loss reacts so strongly to large negative margins that a single mislabelled point can absorb most of the weight. On noisy labels, prefer a logistic loss.

    What is deliberately left out: SAMME.R (which uses probabilities rather than hard votes), AdaBoost for regression, the margin-theory explanation of why test error keeps falling after training error reaches zero, and the use of deeper base learners.