সরাসরি পর্বে যান
পর্ব ৩৪ · ৭৭-এর মধ্যে · মডিউল ৭

Boosting-এর ধারণা — AdaBoost আর weak learner

The Idea of Boosting — Weak Learners, Decision Stumps, AdaBoost Step by Step, Exponential Loss, Label Noise and SAMME

Taught in Bangla, this lesson works three AdaBoost rounds by hand on six points, then writes AdaBoost from scratch and matches scikit-learn 1.8's AdaBoostClassifier exactly, explaining why its estimator weights equal 2α. It covers the training-error bound, AdaBoost as forward stagewise minimisation of exponential loss, an in-browser label-noise experiment against random forest, multiclass SAMME and removed arguments.

এই পর্বে যা শিখবেন

  • Weak learner আর strong learner-এর পার্থক্য, আর boosting প্রশ্নটির ইতিহাস
  • AdaBoost-এর পুরো রাউন্ড হাতে: ছয়টি বিন্দুতে তিন রাউন্ডে train error শূন্যে নামানো
  • নিজে লেখা AdaBoost আর scikit-learn 1.8-এর AdaBoostClassifier হুবহু মেলানো, আর ২α-এর রহস্য
  • Train error-এর সীমা ∏ ২√(ε(১ − ε)), exponential loss আর forward stagewise additive modelling
  • Label noise-এ AdaBoost কেন ভাঙে, multiclass-এর SAMME, আর আজকের দিনে AdaBoost কোথায় মানায়

আগের দুই পর্বে গাছ আর গাছের দল এসেছে: পর্ব ৩২ (Decision tree গভীরে — CART, impurity, pruning আর missing value) একটি গাছ কীভাবে প্রশ্ন বেছে ডেটা ভাগ করে তা দেখায়, আর পর্ব ৩৩ (Bagging আর random forest — variance কমানো আর OOB error) দেখায় অনেক গভীর গাছের গড় নিয়ে variance কমানো। AI-এর ভিত্তি কোর্সের পর্ব ৬৬-এ boosting-এর প্রথম দেখা পেয়েছেন, একই fold-এ অন্য model-এর সঙ্গে তুলনায়। এই পর্ব উল্টো দিক থেকে আসে: গভীর গাছ নয়, খুব দুর্বল গাছ, কিন্তু একটির পর একটি, প্রত্যেকে আগের ভুল সারাতে। চালানো যায় এমন চারটি ব্লক পাতাতেই চলে (ব্রাউজারের Python, scikit-learn 1.8.0); প্রথমবার scikit-learn নামতে প্রায় দশ সেকেন্ড লাগে।

১ · Boosting-এর ধারণা — দুর্বল শিক্ষকদের একটি দল

ভর্তি পরীক্ষার প্রস্তুতি নিচ্ছে রিনা। প্রথম দিন একটি মডেল টেস্ট দিয়ে সে দেখে কোন অঙ্কগুলো ভুল হয়েছে। দ্বিতীয় দিন সে সব অঙ্কে সমান সময় দেয় না; আগের দিনের ভুলগুলোতে বেশি সময় দেয়। তৃতীয় দিন আবার, যেগুলো তখনো ভুল হচ্ছে সেগুলোতে আরও বেশি। প্রতিদিনের শেখা আলাদাভাবে সামান্য, কিন্তু সব দিনের শিক্ষা মিলিয়ে সে শক্ত হয়ে ওঠে। AdaBoost ঠিক এই কাজ করে, শুধু “বেশি সময়”-এর জায়গায় “বেশি ওজন”।

Ensemble মানে অনেক model-এর মত মিলিয়ে সিদ্ধান্ত। দুটি বড় পরিবার আছে। Bagging আর random forest একই ধরনের গভীর গাছ আলাদা আলাদা bootstrap নমুনায় সমান্তরালে শেখায়, তারপর গড় নেয়: প্রতিটি গাছ নিজে শক্ত কিন্তু অস্থির, গড়ে অস্থিরতা (variance) কমে। Boosting উল্টো পথে চলে: প্রতিটি model নিজে দুর্বল, অনেক bias, কিন্তু তারা ধারাবাহিকভাবে শেখে, আর পরেরটি আগেরগুলোর ভুল দেখে। ফলে মূলত bias কমে (AI-এর ভিত্তি কোর্সের পর্ব ৬১-এ bias আর variance-এর ভাগ দেখেছেন)।

একটি তাত্ত্বিক প্রশ্ন থেকে জন্ম

Boosting এসেছে পর্ব ০৩-এর PAC learning থেকে। Michael Kearns আর Leslie Valiant ১৯৮০-র দশকের শেষে একটি প্রশ্ন তোলেন: যে শেখার পদ্ধতি শুধু মুদ্রা টসের চেয়ে একটু ভালো (weak learner), তাকে কি এমন পদ্ধতিতে বদলানো যায়, যা যত খুশি নির্ভুল (strong learner)? Robert Schapire ১৯৯০ সালে (“The Strength of Weak Learnability”) দেখান, উত্তর হ্যাঁ: দুর্বল শেখাকে বারবার নতুন করে সাজানো ডেটায় চালিয়ে মিলিয়ে নিলেই হয়। Yoav Freund পরে আরও দক্ষ একটি পদ্ধতি দেন। তারপর Freund আর Schapire মিলে ১৯৯৫ সালে (সম্মেলনে, পূর্ণ প্রবন্ধ ১৯৯৭ সালে Journal of Computer and System Sciences-এ) AdaBoost প্রকাশ করেন। “Ada” এসেছে adaptive থেকে: আগে থেকে জানতে হয় না weak learner কতটা ভালো, প্রতিটি রাউন্ডের মাপা ভুল থেকেই ওজন ঠিক হয়। এই কাজের জন্য দুজন ২০০৩ সালে তাত্ত্বিক কম্পিউটার বিজ্ঞানের Gödel Prize পান।

AdaBoost-এর তিন রাউন্ড: ভুলের ওজন বাড়ে, শেষে ওজনযুক্ত ভোট

ছয়টি অর্ডার, x = ডেলিভারির দিন; বৃত্তের আকার = sample weight; লাল রিং = সেই রাউন্ডে ভুল

Three rounds of AdaBoost on six points with growing weights রাউন্ড ১: সব ওজন ১/৬ x ≤ 2.5 → + ε = 0.167, α = 0.80 1 2 3 4 5 6 রাউন্ড ২: x = 5-এর ওজন 0.5 x ≤ 5.5 → + ε = 0.2, α = 0.69 রাউন্ড ৩: x = 3, 4, 5 ভারী x ≤ 4.5 → − ε = 0.1875, α = 0.73 চূড়ান্ত ভোট: sign(0.80 h₁ + 0.69 h₂ + 0.73 h₃) ● = + ● = − ছয়টিই ঠিক
ছবি ৩৪.১ — প্রতিটি stump একা অন্তত একটি বিন্দুতে ভুল করে, কিন্তু ভুলগুলো আলাদা জায়গায়। ওজনযুক্ত ভোটে তিনটি দুর্বল নিয়ম মিলে নিখুঁত। পুরো হিসাব অংশ ৩-এর stepper-এ।

২ · Weak learner আর decision stump

Weak learner-এর আনুষ্ঠানিক অর্থ: train সারিগুলোর ওপর যেকোনো ওজন দেওয়া হোক, সে এমন একটি নিয়ম ফেরত দেয় যার weighted error ε ≤ ½ − γ, যেখানে γ > ০ একটি ছোট “সুবিধা”, যাকে edge বলে। শর্তটি শক্ত: শুধু সমান ওজনে নয়, boosting যেভাবেই ওজন সাজাক, মুদ্রার চেয়ে একটু ভালো থাকতে হবে। বাস্তবে কেউ এটি প্রমাণ করে না; আমরা এমন সরল model নিই যারা সাধারণত এটি মানে।

সবচেয়ে জনপ্রিয় weak learner হলো decision stump: গভীরতা ১-এর decision tree, অর্থাৎ একটিমাত্র প্রশ্ন, “feature j ≤ t?”, আর দুই পাশে দুটি উত্তর। Stump ওজন বুঝতে পারে: sample weight দিলে scikit-learn-এর গাছ impurity হিসাবে প্রতিটি সারিকে তার ওজন অনুযায়ী গোনে। একটি খুঁটিনাটি মনে রাখুন: DecisionTreeClassifier(max_depth=1) সেরা প্রশ্নটি বাছে weighted Gini দিয়ে, weighted error সরাসরি কমিয়ে নয়। AdaBoost-এর তত্ত্বে যেকোনো weak learner চলে, তাই এতে সমস্যা নেই।

এই পর্বের ডেটা: বাজারঘরের কাল্পনিক অর্ডার, seed দিয়ে বানানো। দুটি feature: ডেলিভারিতে কত দিন লেগেছে (১–১০) আর দাম (হাজার টাকায়, ০.৫–৮)। লক্ষ্য: অর্ডারটি ফেরত এসেছে কি না। নিয়মটি তির্যক আর একটু বাঁকা: দেরি আর দাম দুটিই ফেরতের ঝুঁকি বাড়ায়, একসঙ্গে। সঙ্গে ৫% ভুল label. কোনো একটি খাড়া বা আড়াআড়ি রেখা এই সীমা আঁকতে পারে না, তাই একা একটি stump দুর্বল হতে বাধ্য।

# bazar_ret.py — বাজারঘরের কাল্পনিক ফেরত-অর্ডার, দুটি feature (seed দিয়ে বানানো)
import numpy as np

def make_returns(n, seed=34, noise=0.05):
    rng = np.random.default_rng(seed)
    days = rng.uniform(1, 10, n)                 # ডেলিভারিতে কত দিন
    price = rng.uniform(0.5, 8, n)               # দাম, হাজার টাকায়
    score = days + 1.2 * price - 0.08 * (days - 5) ** 2
    y = (score > 9.5).astype(int)                # 1 = ফেরত এসেছে
    flip = rng.random(n) < noise                 # কিছু ভুল label
    y[flip] = 1 - y[flip]
    return np.column_stack([days, price]), y

প্রথম ব্লক ৬০০টি অর্ডারকে ৩৬০টি train আর ২৪০টি test-এ ভাগ করে (stratified, একই seed), তারপর তিনটি stump শেখায়: শুধু দিন, শুধু দাম, আর দুটি থেকে নিজে বেছে নেওয়া। শেষে একটি ছোট পরীক্ষা: প্রথম stump যেগুলো ভুল করেছে, সেগুলোর ওজন তিন গুণ করে আবার শেখালে নিয়ম বদলায় কি?

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from bazar_ret import make_returns

X, y = make_returns(600)
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.4,
                                      random_state=0, stratify=y)
print("train", Xtr.shape, " test", Xte.shape, f" return rate {y.mean():.3f}")
names = ["days", "price"]
for cols in ([0], [1], [0, 1]):
    s = DecisionTreeClassifier(max_depth=1).fit(Xtr[:, cols], ytr)
    f, t = s.tree_.feature[0], s.tree_.threshold[0]
    print(f"stump on {'+'.join(names[c] for c in cols):10s}"
          f" rule: {names[cols[f]]} <= {t:.2f}"
          f"  train {s.score(Xtr[:, cols], ytr):.3f}"
          f"  test {s.score(Xte[:, cols], yte):.3f}")

# ভুলগুলোর ওজন বাড়ালে stump কী করে?
s = DecisionTreeClassifier(max_depth=1).fit(Xtr, ytr)
wrong = s.predict(Xtr) != ytr
w = np.where(wrong, 3.0, 1.0)
s2 = DecisionTreeClassifier(max_depth=1).fit(Xtr, ytr, sample_weight=w)
print(f"wrong on train: {wrong.sum()} of {len(ytr)}")
print(f"re-weighted stump rule: {names[s2.tree_.feature[0]]}"
      f" <= {s2.tree_.threshold[0]:.2f}")
print("depth-1 tree leaves:", s.get_n_leaves(), " depth:", s.get_depth())

আসল আউটপুট (ব্রাউজারের Python):

train (360, 2)  test (240, 2)  return rate 0.570
stump on days       rule: days <= 4.02  train 0.744  test 0.658
stump on price      rule: price <= 3.15  train 0.769  test 0.738
stump on days+price rule: price <= 3.15  train 0.769  test 0.738
wrong on train: 83 of 360
re-weighted stump rule: days <= 4.11
depth-1 tree leaves: 2  depth: 1

৫৭% অর্ডার ফেরত, তাই “সবই ফেরত” বললেই ০.৫৭. শুধু দামের stump test-এ ০.৭৩৮, শুধু দিনের stump ০.৬৫৮: মুদ্রার চেয়ে ভালো, কিন্তু অনেক দূরে। দুটি feature দিলে stump দাম বেছে নেয়, কারণ train-এ সেটিই ভালো (০.৭৬৯)। শেষ তিন লাইন boosting-এর বীজ: ৩৬০টির মধ্যে ৮৩টি ভুল সারির ওজন তিন গুণ করতেই stump সম্পূর্ণ অন্য প্রশ্ন বেছে নিল, “দিন ≤ ৪.১১?”. অর্থাৎ ওজন বদলালে একই দুর্বল শেখা নতুন তথ্য খুঁজে পায়। অংশ ৫-এ দেখবেন, AdaBoost-এর দ্বিতীয় রাউন্ডও ঠিক এই নিয়মটি বাছে।

৩ · AdaBoost ধাপে ধাপে — একটি পুরো রাউন্ড হাতে

Binary ক্ষেত্রে label y ∈ {−১, +১} ধরুন, আর প্রতিটি weak learner ht(x)-ও −১ বা +১ ফেরত দেয়। n টি train সারি, T টি রাউন্ড। Freund আর Schapire-এর discrete AdaBoost (যাকে AdaBoost.M1-ও বলা হয়):

  1. শুরু: প্রতিটি সারির ওজন wi = ১/n.
  2. প্রতি রাউন্ড t = ১, …, T:
    • ওজন w দিয়ে weak learner ht শেখানো।
    • Weighted error: εt = ∑ wi, শুধু যেসব i-তে ht(xi) ≠ yi (ওজনের যোগফল ১ ধরে)।
    • ভোটের ওজন: αt = ½ ln((১ − εt)/εt). ε ছোট হলে α বড়।
    • নতুন ওজন: wi ← wi · exp(−αt yi ht(xi)). ঠিক হলে y·h = +১, ওজন e−α গুণ; ভুল হলে y·h = −১, ওজন eα গুণ।
    • সবাইকে Zt = নতুন ওজনের যোগফল দিয়ে ভাগ, যাতে যোগফল আবার ১ হয়।
  3. শেষ: F(x) = ∑t αt ht(x), আর অনুমান sign(F(x)).

এখানে ln হলো e-ভিত্তিক স্বাভাবিক লগারিদম। নিচে ছয়টি অর্ডারে পুরো হিসাব, যা ছবি ৩৪.১-এর পেছনের সংখ্যা। x = ডেলিভারির দিন ১ থেকে ৬, label: +, +, −, −, +, − (+ মানে ফেরত)। Stump শুধু “x ≤ t?” প্রশ্ন করতে পারে, t হলো দুটি বিন্দুর মাঝখানে।

α = ½ ln((1 − ε)/ε)w ← w · e^(−α y h) / Z

            
  1. শুরু। ছয়টি অর্ডারের প্রত্যেকের ওজন ১/৬. লক্ষ করুন x = ৫ একটু অদ্ভুত: আশপাশের অর্ডার ফেরত আসেনি, এটি এসেছে। একটি stump একে আলাদা করতে পারবে না।
  2. রাউন্ড ১-এর stump বাছাই। সব সম্ভাব্য t আর দুই দিক পরীক্ষা করলে সেরা “x ≤ ২.৫ হলে +”: শুধু x = ৫ ভুল। ε₁ = ১/৬ ≈ ০.১৬৬৭. অন্য যেকোনো stump অন্তত দুটি বিন্দুতে ভুল করে।
  3. ভোটের ওজন। α₁ = ½ ln ৫ = ০.৮০৪৭. দুই গুণক একে অন্যের উল্টো: ঠিক অর্ডারের ওজন ০.৪৪৭২ গুণ, ভুলটির ২.২৩৬১ গুণ।
  4. Reweighting. গুণের পরে যোগফল Z₁ = ০.৭৪৫৪, যা সূত্র ২√(ε(১ − ε))-এর সঙ্গে মেলে। ভাগ করলে ভুল x = ৫ পায় ০.৫, বাকি পাঁচটি ০.১ করে। একটিমাত্র ভুল এখন মোট ওজনের অর্ধেক।
  5. রাউন্ড ২। নতুন ওজনে h₁-এর weighted error ঠিক ০.৫, মুদ্রার সমান, তাই তাকে আবার বাছা অর্থহীন। সেরা stump “x ≤ ৫.৫ হলে +”: সে ভারী x = ৫-কে ঠিক বলে, দাম দেয় x = ৩, ৪-এ। ε₂ = ০.২, α₂ = ০.৬৯৩১. আবার গুণ আর ভাগ।
  6. দুই রাউন্ডের ভোট। x = ৩, ৪, ৫-এ দুই stump দুই কথা বলে; α₁ একটু বড়, তাই F = −০.১১, তিনটিই − পায়। x = ৫ এখনো ভুল, train error ১/৬. সীমা Z₁Z₂ = ০.৫৯৬, যা ১/৬-এর চেয়ে বড়: সীমা ঠিকই আছে, শুধু ঢিলা।
  7. রাউন্ড ৩। এখন ভারী x = ৩, ৪, ৫. সেরা stump “x ≤ ৪.৫ হলে −”, ভুল শুধু হালকা তিনটিতে: ε₃ = ০.১৮৭৫, α₃ = ০.৭৩৩২. তিন stump-এর যোগে ছয়টিই ঠিক। scikit-learn এই ছয় বিন্দুতে একই তিনটি threshold বাছে (অনুশীলন ৩)।

৪ · α, ε আর ওজনের হিসাব — কেন ε < ½ লাগে

α-এর সূত্রটি কোথা থেকে এল, অংশ ৭-এ প্রমাণ দেখবেন। আগে তার আচরণ বুঝে নিন। ln((১ − ε)/ε) হলো ঠিক হওয়ার odds-এর লগারিদম: stump যত ভরসার, তার কথার দাম তত বেশি।

α = ½ ln((1 − ε)/ε)ভুল × e^α, ঠিক × e^(−α)

            
  1. ভালো stump. দশ ভাগে এক ভাগ ভুল। α = ১.০৯৮৬, আর ভুলের ওজন ঠিক তিন গুণ, ঠিকের এক-তৃতীয়াংশ। scikit-learn-এর estimator_weights_-এ এই সংখ্যার দ্বিগুণ দেখবেন; কারণ অংশ ৫-এ।
  2. সাধারণ weak learner. ৩০% ভুল। α = ০.৪২৩৬, ওজনের গুণক ১.৫২৭৫ আর ০.৬৫৪৭: বদল মৃদু। বাস্তবের বেশিরভাগ রাউন্ড এমন বা আরও দুর্বল।
  3. মুদ্রা টস। ε = ০.৫-এ α = ০, ওজন বদলায় না, Z = ১. পরের রাউন্ড একই ওজনে একই stump পাবে, তাই অগ্রগতি থেমে যায়। এজন্যই weak learner-কে মুদ্রার চেয়ে একটু হলেও ভালো হতে হয়।
  4. মুদ্রার চেয়ে খারাপ। ε = ০.৭ হলে α ঋণাত্মক: stump-এর কথা উল্টে নিলেই ε = ০.৩-এর stump. Binary-তে তাই এমন stump আসলে দরকারি তথ্য দেয়। তবু scikit-learn ε ≥ ০.৫ দেখলে সেই estimator বাদ দিয়ে থামে; stump নিজেই দুই দিক পরীক্ষা করে বলে এটি সাধারণত ঘটে না।
  5. পুরো ছবি। ε ০.৫-এর দিকে গেলে α শূন্যে নামে, ০-এর দিকে গেলে অসীমে ওঠে। নিখুঁত stump (ε = ০) পেলে scikit-learn তাকে ওজন ১ দিয়ে সেখানেই থামে। তাই কাজের পরিসর ০ < ε < ০.৫.

ভুলের ওজন কেন ঠিক অর্ধেক হয়

রাউন্ড শেষে ভুল সারিগুলোর মোট ওজন: ε · eα / Z = ε · √((১ − ε)/ε) / (২√(ε(১ − ε))) = ½. ঠিকগুলোরও ½. ফল ε যা-ই হোক সবসময় এক। অর্থাৎ নতুন ওজনে সদ্য শেখানো stump ঠিক মুদ্রার মতো, আর পরের রাউন্ডকে নতুন কিছু খুঁজতেই হয়। দশটি অর্ডারে দেখুন:

n = 10, তিনটি ভুলα = ½ ln(7/3) = 0.4236

            
  1. শুরু। দশটি অর্ডার, প্রত্যেকে ০.১. Stump তিনটিতে ভুল, তাই ε = ০.৩ আর α = ০.৪২৩৬.
  2. গুণ। প্রতিটি ভুলের ওজন ০.১৫২৮, প্রতিটি ঠিকের ০.০৬৫৫. আশ্চর্য মিল: তিন ভুলের মোট আর সাত ঠিকের মোট দুটিই ০.৪৫৮৩. মোট Z = ০.৯১৬৫.
  3. ভাগ। Z দিয়ে ভাগ করলে প্রতিটি ভুল ০.১৬৬৭ (দেড় গুণের বেশি), প্রতিটি ঠিক ০.০৭১৪. দুই দলের মোট ঠিক ০.৫ আর ০.৫.
  4. পুরোনো stump অচল। নতুন ওজনে আগের stump-এর weighted error ০.৫, মুদ্রার সমান। Boosting তাই নিজে থেকেই বৈচিত্র্য আনে: পরপর দুই রাউন্ডে একই stump কখনো সেরা হয় না।
  5. Train error-এর সীমা। Freund আর Schapire দেখান train error ≤ ∏ Zt = ∏ ২√(εt(১ − εt)). প্রতি রাউন্ডে ε = ০.৩ হলে ১০০ রাউন্ডে সীমা ০.০০০১৬. ৩৬০টি সারিতে ভুল ১/৩৬০-এর গুণিতক, তাই T = ৬৮-এ সীমা ১/৩৬০-এর নিচে নামলেই train error শূন্য। কিন্তু ε = ০.৪৫ হলে ১০০ রাউন্ডেও সীমা ০.৬০৫: দুর্বলতর stump মানে অনেক ধীর অগ্রগতি।

এই সীমাকে আরও সরল রূপে লেখা যায়। γt = ½ − εt হলো মুদ্রার চেয়ে কতটা ভালো, তাহলে ২√(ε(১ − ε)) = √(১ − ৪γ²) ≤ e−২γ², ফলে train error ≤ exp(−২ ∑t γt²). কথায়: প্রতিটি রাউন্ড যদি মুদ্রার চেয়ে অন্তত γ ভালো হয়, train error সূচকীয় হারে (exponentially) শূন্যের দিকে নামে। এটি train error-এর কথা; নতুন অর্ডারে ভুল কেমন হবে, সেটি আলাদা প্রশ্ন, অংশ ৬-এ।

৫ · নিজে লেখা AdaBoost বনাম AdaBoostClassifier

এবার পুরো অ্যালগরিদম নিজে লিখুন, weak learner হিসেবে scikit-learn-এর stump রেখে। Boosting-এর সব অংশ (ওজন, ε, α, reweighting, শেষ ভোট) আমাদের নিজের। তারপর একই ডেটায় AdaBoostClassifier-এর সঙ্গে মেলানো: stump একই কিনা, ভোটের ওজন কেমন, আর staged_score দিয়ে প্রতিটি রাউন্ড শেষে test accuracy.

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import AdaBoostClassifier
from bazar_ret import make_returns
X, y = make_returns(600)
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.4, random_state=0, stratify=y)
ys = 2 * ytr - 1                                  # 0/1 থেকে −1/+1
T = 50
w = np.full(len(ytr), 1 / len(ytr))              # শুরুতে সবাই সমান
stumps, alphas = [], []
for t in range(T):
    h = DecisionTreeClassifier(max_depth=1).fit(Xtr, ytr, sample_weight=w)
    pred = 2 * h.predict(Xtr) - 1
    eps = w[pred != ys].sum()                    # weighted error
    alpha = 0.5 * np.log((1 - eps) / eps)        # এই stump-এর ভোটের ওজন
    w = w * np.exp(-alpha * ys * pred)           # ভুলের ওজন বাড়ে, ঠিকের কমে
    w = w / w.sum()
    stumps.append(h); alphas.append(alpha)
    if t < 4:
        print(f"round {t+1}: feature {h.tree_.feature[0]} <= {h.tree_.threshold[0]:.2f}"
              f"  eps {eps:.4f}  alpha {alpha:.4f}  max w {w.max():.4f}")
def score(Xs, ys01, k):
    F = sum(a * (2 * h.predict(Xs) - 1) for h, a in zip(stumps[:k], alphas[:k]))
    return np.mean((F > 0) == (ys01 == 1))
ada = AdaBoostClassifier(DecisionTreeClassifier(max_depth=1), n_estimators=T,
                         random_state=0).fit(Xtr, ytr)
print("sklearn estimator_weights_[:4]:", np.round(ada.estimator_weights_[:4], 4))
print("2 * own alpha[:4]:             ", np.round(2 * np.array(alphas[:4]), 4))
print("same stumps:", all(a.tree_.threshold[0] == b.tree_.threshold[0]
                          for a, b in zip(stumps, ada.estimators_)))
staged = list(ada.staged_score(Xte, yte))
for k in (1, 5, 50):
    print(f"T={k:2d}  own test {score(Xte, yte, k):.4f}  sklearn {staged[k-1]:.4f}")

আসল আউটপুট (ব্রাউজারের Python):

round 1: feature 1 <= 3.15  eps 0.2306  alpha 0.6026  max w 0.0060
round 2: feature 0 <= 4.11  eps 0.2251  alpha 0.6180  max w 0.0134
round 3: feature 1 <= 5.78  eps 0.2783  alpha 0.4765  max w 0.0240
round 4: feature 0 <= 6.38  eps 0.3494  alpha 0.3109  max w 0.0344
sklearn estimator_weights_[:4]: [1.2052 1.236  0.953  0.6217]
2 * own alpha[:4]:              [1.2052 1.236  0.953  0.6217]
same stumps: True
T= 1  own test 0.7375  sklearn 0.7375
T= 5  own test 0.8167  sklearn 0.8167
T=50  own test 0.8542  sklearn 0.8542

প্রথম রাউন্ড অংশ ২-এর দামের stump (ε = ০.২৩০৬, ১ − ০.৭৬৯-এর সমান, কারণ শুরুর ওজন সমান)। দ্বিতীয় রাউন্ড ঠিক “দিন ≤ ৪.১১”, অংশ ২-এর তিন গুণ ওজনের পরীক্ষার নিয়ম। রাউন্ড এগোলে ε বাড়ছে (০.২৩ থেকে ০.৩৫): সহজ ভুলগুলো ধরা পড়ে গেছে, ওজন জমছে কঠিন সারিতে, আর সবচেয়ে ভারী সারির ওজন ০.০০৬ থেকে ০.০৩৪, প্রায় ছয় গুণ। পঞ্চাশটি stump-এর সবকটি scikit-learn-এর সঙ্গে হুবহু এক, আর তিনটি T-তে test accuracy-ও অঙ্কে অঙ্কে মেলে: একটি stump ০.৭৩৭৫, পাঁচটি ০.৮১৬৭, পঞ্চাশটি ০.৮৫৪২.

২α-এর রহস্য। scikit-learn ভেতরে SAMME চালায় (অংশ ৯), যার ভোটের ওজন ln((১ − ε)/ε) + ln(K − ১), K = শ্রেণির সংখ্যা। K = ২-এ দ্বিতীয় পদ শূন্য, তাই ওজন আমাদের α-এর ঠিক দ্বিগুণ। সে ওজন বদলায় শুধু ভুলগুলোকে e২α দিয়ে গুণ করে, ঠিকগুলো ছোঁয় না; ভাগ করার পরে ফল আমাদের e±α-এর সঙ্গে এক। আর সব α-কে একই সংখ্যায় গুণ করলে sign(F) বদলায় না, তাই অনুমানও এক।

scikit-learn 1.8-এর ডাক আর পুরোনো argument

AdaBoostClassifier(estimator=None, *, n_estimators=50, learning_rate=1.0, random_state=None): এই চারটিই। estimator=None মানে ডিফল্ট DecisionTreeClassifier(max_depth=1), অর্থাৎ stump. পুরোনো টিউটোরিয়াল আর AI-এর কোডে দুটি argument প্রায়ই দেখবেন যা এখন নেই: base_estimator (নাম বদলে estimator হয়েছে) আর algorithm ("SAMME.R" নামের probability-ভিত্তিক রূপটি আগে deprecated হয়েছিল; এখন শুধু SAMME, আর argument-টিই সরানো)। ডেস্কটপে সত্যিকারের run (Python 3.14.4, scikit-learn 1.8.0):

~/bazarghar-ml$ python old_args.py
scikit-learn 1.8.0
TypeError: AdaBoostClassifier.__init__() got an unexpected keyword argument 'algorithm'
TypeError: AdaBoostClassifier.__init__() got an unexpected keyword argument 'algorithm'
TypeError: AdaBoostClassifier.__init__() got an unexpected keyword argument 'base_estimator'

তিনটি চেষ্টা: algorithm="SAMME", algorithm="SAMME.R" আর base_estimator=...; তিনটিই TypeError. পাতার ব্লকে আমরা তাই এগুলো দিইনি, আর ব্লকটি কোনো warning ছাড়াই চলে। staged_score, staged_predict আর staged_decision_function প্রতিটি রাউন্ড শেষের ফল একটি একটি করে দেয়; আলাদা model না বানিয়েই T বাছাইয়ের জন্য এগুলো খুব কাজের।

৬ · রাউন্ড বাড়লে — সীমারেখা আর train/test error

প্রতিটি stump একটি খাড়া বা আড়াআড়ি রেখা। তাদের ওজনযুক্ত যোগফল তাই অক্ষের সমান্তরাল টুকরো দিয়ে গড়া একটি সিঁড়ি। তির্যক সীমা ধরতে অনেক ধাপ লাগে। তৃতীয় ব্লক ৪০০ রাউন্ডের একটি model শেখায়, আর staged_predict দিয়ে ১, ৫ আর ৫০ রাউন্ড পরের সীমারেখা আঁকে; চতুর্থ ছবিতে প্রতিটি রাউন্ড শেষে train আর test error.

import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.ensemble import AdaBoostClassifier
from bazar_ret import make_returns

X, y = make_returns(600)
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.4, random_state=0, stratify=y)
ada = AdaBoostClassifier(n_estimators=400, random_state=0).fit(Xtr, ytr)
tr_err = [1 - s for s in ada.staged_score(Xtr, ytr)]
te_err = [1 - s for s in ada.staged_score(Xte, yte)]
for k in (1, 2, 5, 50, 100, 400):
    print(f"T={k:3d}  train error {tr_err[k-1]:.3f}  test error {te_err[k-1]:.3f}")
best = int(np.argmin(te_err)) + 1
print(f"lowest test error {min(te_err):.3f} at T={best} (test used only to look)")

g1, g2 = np.meshgrid(np.linspace(1, 10, 200), np.linspace(0.5, 8, 200))
grid = np.column_stack([g1.ravel(), g2.ravel()])
stages = list(ada.staged_predict(grid))
fig, axes = plt.subplots(2, 2, figsize=(8.5, 7.5))
for ax, k in zip(axes.flat[:3], (1, 5, 50)):
    ax.contourf(g1, g2, stages[k-1].reshape(g1.shape), alpha=0.25, cmap="coolwarm")
    ax.scatter(Xtr[:, 0], Xtr[:, 1], c=ytr, s=10, cmap="coolwarm", edgecolor="k", lw=0.2)
    ax.set(title=f"after {k} stump(s)", xlabel="delivery days", ylabel="price (k Tk)")
ax = axes.flat[3]
ax.plot(range(1, 401), tr_err, label="train error")
ax.plot(range(1, 401), te_err, label="test error")
ax.set(xscale="log", xlabel="boosting rounds T", ylabel="error", title="Error vs rounds")
ax.legend()
fig.subplots_adjust(hspace=0.35, wspace=0.3)

আসল আউটপুট (ব্রাউজারের Python), নিচে চারটি ছবি:

T=  1  train error 0.231  test error 0.262
T=  2  train error 0.256  test error 0.338
T=  5  train error 0.139  test error 0.183
T= 50  train error 0.097  test error 0.146
T=100  train error 0.094  test error 0.146
T=400  train error 0.058  test error 0.121
lowest test error 0.117 at T=319 (test used only to look)

প্রথম ছবিতে একটি stump: একটি আড়াআড়ি রেখা, দাম ৩.১৫-এর ওপরে সব “ফেরত”। পাঁচটি stump-এ দুই ধাপের সিঁড়ি, পঞ্চাশটিতে তির্যক সীমার কাছাকাছি একটি সিঁড়ি। T = ২-এ error একটু বাড়ে (test ০.২৬২ থেকে ০.৩৩৮): দুটি প্রায় সমান α-এর stump যেখানে দ্বিমত করে, সেখানে বড় α-টি জেতে, আর এমন অর্ধেক-তৈরি ভোট একটি stump-এর চেয়েও খারাপ হতে পারে। তারপর দ্রুত নামে। ৫০ থেকে ১০০ রাউন্ডে test error ০.১৪৬-এ স্থির, অথচ train error নামতেই থাকে (৪০০-তে ০.০৫৮)। ৪০০ রাউন্ডেও test error বাড়েনি, বরং ০.১২১. সবচেয়ে কম test error T = ৩১৯-এ, কিন্তু এটি শুধু দেখার জন্য: test দেখে T বাছলে সেই test আর নিরপেক্ষ থাকে না। T বাছতে হয় cross-validation বা আলাদা validation ভাগে (AI-এর ভিত্তি কোর্সের পর্ব ৬৬)।

AdaBoost কি overfit করে না? ১৯৯০-এর দশকের শেষে অনেক গবেষক অবাক হয়ে দেখেন, train error শূন্য হওয়ার পরেও রাউন্ড বাড়ালে test error প্রায়ই কমতে থাকে, যা “বেশি জটিল মানেই বেশি overfitting” ধারণার সঙ্গে মেলে না। এর একটি ব্যাখ্যা দেন Schapire, Freund, Bartlett আর Lee (১৯৯৮): train error শূন্য হলেও boosting margin বাড়াতে থাকে, অর্থাৎ ভোট কতটা জোরালোভাবে ঠিক। বেশি margin মানে নতুন ডেটায় ভালো থাকার শক্ত সীমা। ব্যাখ্যাটি নিয়ে বিতর্ক আছে (ভাবনার প্রশ্ন ২)। আর এটি সব ডেটায় সত্য নয়: ভুল label বেশি হলে AdaBoost স্পষ্টভাবে overfit করে, অংশ ৮-এ দেখবেন।

৭ · AdaBoost আসলে কী কমায় — exponential loss আর forward stagewise

AdaBoost প্রথমে এসেছিল learning theory থেকে, কোনো loss কমানোর পদ্ধতি হিসেবে নয়। Jerome Friedman, Trevor Hastie আর Robert Tibshirani ২০০০ সালে (“Additive logistic regression: a statistical view of boosting”, Annals of Statistics) দেখান, একই অ্যালগরিদমকে পরিসংখ্যানের ভাষায় পড়া যায়। পর্ব ০২-এ loss-এর আকার দেখেছেন; সেখানে exponential loss-কে AdaBoost-এর পেছনের loss বলা হয়েছিল। এখানে তার প্রমাণ।

মডেল: F(x) = ∑ βt ht(x), একটি additive model. Loss: L(y, F) = exp(−y F(x)). এখানে y F(x)-কে বলা হয় margin m: m > ০ মানে ঠিক, আর m যত বড়, তত জোরালো ঠিক। পদ্ধতি: forward stagewise, অর্থাৎ প্রতি ধাপে আগের সব পদ স্থির রেখে শুধু একটি নতুন পদ (β, h) যোগ করা, ফিরে গিয়ে পুরোনোগুলো আর না বদলে।

ধাপ t-তে Ft−১ জানা। নতুন পদের জন্য মোট loss:

∑i exp(−yi(Ft−১(xi) + β h(xi))) = ∑i wi exp(−β yi h(xi))

যেখানে wi = exp(−yi Ft−১(xi)), যা নতুন পদের ওপর নির্ভর করে না। এই w-ই AdaBoost-এর ওজন: আগের ভোটে যে সারির margin যত ঋণাত্মক, তার ওজন তত বেশি। এখন যোগফলটি দুই ভাগে ভাঙুন (ওজনের যোগফল ১ ধরে):

= e−β (১ − ε) + eβ ε,   যেখানে ε = ভুলগুলোর ওজন

  • সেরা h: যেকোনো β > ০-এর জন্য এই রাশি ε-এর সঙ্গে বাড়ে, তাই সেরা h হলো যে weighted error সবচেয়ে কম রাখে। এটিই weak learner-কে ওজন দিয়ে শেখানো।
  • সেরা β: β অনুযায়ী অন্তরকলজ শূন্য করলে −e−β(১ − ε) + eβε = ০, অর্থাৎ e২β = (১ − ε)/ε, তাই β = ½ ln((১ − ε)/ε), হুবহু α.
  • পরের ওজন: wi · exp(−β yi h(xi)), হুবহু AdaBoost-এর reweighting. আর সেরা β বসালে রাশিটির মান ২√(ε(১ − ε)) = Z: অংশ ৪-এর সীমা আসলে মোট exponential loss-এর গুণফল।

একই গণনা থেকে আরেকটি কথা বেরোয়: সব সম্ভাব্য F-এর মধ্যে গড় exponential loss সবচেয়ে কম হয় F*(x) = ½ ln(p(x)/(১ − p(x))) হলে, যেখানে p(x) = P(y = +১ | x), অর্থাৎ ফেরতের আসল সম্ভাবনা। অর্থাৎ AdaBoost-এর F আসলে log-odds-এর অর্ধেক আন্দাজ করছে, আর তাত্ত্বিকভাবে p = ১/(১ + e−২F). বাস্তবে এই রূপান্তর থেকে পাওয়া সম্ভাবনা প্রায়ই ভালোভাবে calibrated হয় না; পর্ব ২৬-এ boosting-এর সম্ভাবনা মেপে সারানোর পদ্ধতি দেখেছেন।

মূল সংযোগ

AdaBoost = additive model + exponential loss + forward stagewise. তিনটির যেকোনো একটি বদলালে নতুন অ্যালগরিদম: loss-কে log loss করলে Friedman, Hastie আর Tibshirani-এর LogitBoost; আর যেকোনো differentiable loss-কে gradient দিয়ে সামলালে পর্ব ৩৫ (Gradient boosting — residual থেকে functional gradient descent)-এ দেখবেন।

৮ · Label noise আর outlier — AdaBoost-এর দুর্বল জায়গা

Exponential loss ঋণাত্মক margin-এ সূচকীয় হারে বাড়ে। কোনো অর্ডারের label ভুল হলে (ফেরত আসেনি, কিন্তু খাতায় “ফেরত” লেখা), ভালো model সেটিকে “ভুল” বলবেই, ফলে তার margin ঋণাত্মক থাকে আর প্রতি রাউন্ডে ওজন বাড়ে। শেষে boosting তার বেশিরভাগ শক্তি খরচ করে এই অসম্ভব সারিগুলো মুখস্থ করতে। পর্ব ০৫-এ দেখেছেন, বাস্তবের label noise খুব সাধারণ।

চতুর্থ ব্লক পরিষ্কার label-এর ১,০০০টি অর্ডার নেয়; প্রথম ৬০০টি train, বাকি ৪০০টি test, আর test-এর label কখনো বদলানো হয় না। Train-এর ০%, ১০%, ২০% আর ৩০% label উল্টে দিয়ে AdaBoost (১৫০ stump) আর random forest শেখানো হয়; প্রতিটি হার তিনবার আলাদা seed-এ, গড় দেখানো হয়। সঙ্গে শেষ রাউন্ডের ওজন (w ∝ exp(−y F), F = ∑ α h) থেকে উল্টানো সারিগুলো মোট ওজনের কত ভাগ পেয়েছে। ডান দিকের ছবিতে exponential আর log loss-এর আকার।

import numpy as np
import matplotlib.pyplot as plt
from sklearn.ensemble import AdaBoostClassifier, RandomForestClassifier
from bazar_ret import make_returns
X, y = make_returns(1000, noise=0.0)              # পরিষ্কার label
Xtr, ytr, Xte, yte = X[:600], y[:600], X[600:], y[600:]
rates, acc = [0.0, 0.1, 0.2, 0.3], {"ada": [], "rf": []}
for r in rates:
    a, f = [], []
    for seed in range(3):                         # তিনবার আলাদা উল্টানো
        flip = np.random.default_rng(seed).random(600) < r
        yn = np.where(flip, 1 - ytr, ytr)         # শুধু train-এ ভুল label
        ada = AdaBoostClassifier(n_estimators=150, random_state=0).fit(Xtr, yn)
        rf = RandomForestClassifier(n_estimators=40, min_samples_leaf=5,
                                    random_state=0).fit(Xtr, yn)
        a.append(ada.score(Xte, yte)); f.append(rf.score(Xte, yte))
    acc["ada"].append(np.mean(a)); acc["rf"].append(np.mean(f))
    F = sum(al * (2 * h.predict(Xtr) - 1) for h, al in
            zip(ada.estimators_, ada.estimator_weights_)) / 2
    w = np.exp(-(2 * yn - 1) * F); w /= w.sum()   # শেষ রাউন্ডের ওজন
    print(f"flip {r:.0%}: AdaBoost {acc['ada'][-1]:.3f}  forest {acc['rf'][-1]:.3f}"
          f"  | flipped {flip.mean():.1%} of rows hold {w[flip].sum():.1%} of weight")
m = np.linspace(-2, 3, 200)
fig, (a1, a2) = plt.subplots(1, 2, figsize=(9, 3.6))
a1.plot(rates, acc["ada"], "o-", label="AdaBoost (150 stumps)")
a1.plot(rates, acc["rf"], "s-", label="random forest")
a1.set(xlabel="fraction of train labels flipped", ylabel="accuracy on clean test",
       title="Label noise (mean of 3 runs)")
a2.plot(m, np.exp(-m), label="exponential: exp(-m)")
a2.plot(m, np.log2(1 + np.exp(-m)), label="log loss: log2(1+exp(-m))")
a2.plot(m, (m <= 0).astype(float), "k:", label="0-1 loss")
a2.set(xlabel="margin m = y f(x)", ylabel="loss", ylim=(0, 6), title="Loss vs margin")
a1.legend(fontsize=8); a2.legend(fontsize=8); fig.subplots_adjust(wspace=0.3)

আসল আউটপুট (ব্রাউজারের Python, প্রতিটি হার তিনটি seed-এর গড়; ওজনের ভাগ শেষ seed-এর), নিচে দুটি ছবি:

flip 0%: AdaBoost 0.967  forest 0.970  | flipped 0.0% of rows hold 0.0% of weight
flip 10%: AdaBoost 0.887  forest 0.962  | flipped 9.8% of rows hold 33.8% of weight
flip 20%: AdaBoost 0.879  forest 0.922  | flipped 21.0% of rows hold 39.6% of weight
flip 30%: AdaBoost 0.856  forest 0.871  | flipped 32.2% of rows hold 45.2% of weight

পরিষ্কার label-এ দুটি প্রায় সমান (০.৯৬৭ বনাম ০.৯৭০)। মাত্র ১০% label উল্টাতেই AdaBoost ০.৮৮৭-এ পড়ে, forest প্রায় অটল (০.৯৬২)। কারণটি ডান দিকের সংখ্যায়: সারিগুলোর ৯.৮% উল্টানো, অথচ তারা শেষ রাউন্ডে মোট ওজনের ৩৩.৮% দখল করে আছে, নিজের ভাগের তিন গুণের বেশি। ৩০%-এ উল্টানো সারিরা পায় ৪৫.২%, প্রায় অর্ধেক ওজন। Forest-এর প্রতিটি গাছ সব সারিকে সমান গোনে, আর min_samples_leaf=5 একটি ভুল সারিকে নিজের পাতা পেতে দেয় না; এটি অংশত forest-এর পক্ষে সাজানো, তাই তুলনাটি একটি উদাহরণ, সাধারণ রায় নয়। ৩০%-এ দুটিই অনেক পড়ে। এটি তিনটি seed-এর একটি ছোট পরীক্ষা; seed বদলালে সংখ্যা কয়েক শতাংশ বিন্দু নড়ে। ডেস্কটপে একটি আলাদা পরীক্ষায় (৩০০ stump, ১০০ গাছ, পাঁচটি seed, প্রতিটি আলাদাভাবে) ২০%-এ পাঁচবারই AdaBoost forest-এর নিচে ছিল, ১০%-এ পাঁচবারের চারবার।

ডান ছবি: margin m = −২-এ exponential loss e² ≈ ৭.৪, log loss log₂(১ + e²) ≈ ৩.১; m = −৪-এ ৫৪.৬ বনাম ৫.৮. ঋণাত্মক দিকে log loss প্রায় সরলরেখা (রৈখিক বৃদ্ধি), exponential loss খাড়া হয়ে ওঠে। ভুল label মানে স্থায়ী ঋণাত্মক margin, আর exponential loss তাকে সূচকীয় হারে শাস্তি দেয়।

কী করবেন। (১) আগে label পরিষ্কার করুন, বা অন্তত সন্দেহজনক সারি চিহ্নিত করুন (পর্ব ০৫)। (২) learning_rate < ১ (shrinkage) আর validation দিয়ে কম রাউন্ডে থামা ক্ষতি কমায়। (৩) Log loss-ভিত্তিক boosting (LogitBoost, বা পর্ব ৩৫-এর gradient boosting log loss-সহ) ভুল label-কে কম ভারী শাস্তি দেয়, যদিও তা-ও পুরোপুরি নিরাপদ নয়। (৪) Noise বেশি হলে random forest একটি শক্ত, কম ঝামেলার বিকল্প। Philip Long আর Rocco Servedio (২০০৮) তাত্ত্বিকভাবে দেখান, শুধু AdaBoost নয়, convex potential কমানো যেকোনো booster এলোমেলো label noise-এ ব্যর্থ হতে পারে।

৯ · Multiclass SAMME, knob আর আজকের দিনে AdaBoost

দুইয়ের বেশি শ্রেণি হলে (বাজারঘরে ফেরতের কারণ: “নষ্ট”, “ভুল সাইজ”, “মন বদল”, “দেরি”) সমস্যা হলো weak learner-এর শর্ত। K শ্রেণিতে এলোমেলো অনুমানের ভুল ১ − ১/K, যেমন K = ৪-এ ০.৭৫. প্রতিটি রাউন্ডে ε < ½ চাওয়া তাই খুব কড়া। Ji Zhu, Hui Zou, Saharon Rosset আর Trevor Hastie (“Multi-class AdaBoost”, ২০০৯) SAMME দেন: ভোটের ওজন α = ln((১ − ε)/ε) + ln(K − ১), আর শর্ত শুধু ε < ১ − ১/K. K = ৪, ε = ০.৬ হলে α = ln(০.৪/০.৬) + ln ৩ = −০.৪০৫ + ১.০৯৯ = ০.৬৯৩ > ০: এলোমেলোর চেয়ে ভালো, তাই অবদান ধনাত্মক। চূড়ান্ত অনুমান: যে শ্রেণির পক্ষে মোট α সবচেয়ে বেশি। Multiclass-এর অন্য কৌশল (one-vs-rest, softmax) পর্ব ২৫-এ। SAMME একটি multiclass exponential loss-এর forward stagewise রূপ হিসেবেও পড়া যায়, যা binary-র মতোই।

Knobকী করেব্যবহারিক পরামর্শ
n_estimatorsরাউন্ডের সংখ্যা T (ডিফল্ট ৫০)staged_score দিয়ে validation-এ বাছুন; ε = ০ হলে আগেই থামে
learning_rateপ্রতিটি α-কে এই সংখ্যায় গুণ করে (ডিফল্ট ১.০)ছোট মান (০.১–০.৫) মানে বেশি রাউন্ড লাগে, কিন্তু noise-এ সাধারণত কম ক্ষতি
estimatorWeak learner (ডিফল্ট stump)গভীরতা ২–৩ interaction ধরে; খুব গভীর গাছ weak থাকে না, দ্রুত overfit
random_stateEstimator-এর seedStump-এ সমান ভালো split থাকলে কোনটি আসবে তা ঠিক করে

আজকের দিনে AdaBoost কোথায়? সবচেয়ে বিখ্যাত প্রয়োগ Paul Viola আর Michael Jones-এর ২০০১ সালের মুখ শনাক্তকরণ (face detection): হাজার হাজার সরল Haar feature থেকে AdaBoost অল্প কয়েকটি বেছে নেয়, আর পর্যায়ক্রমিক (cascade) সাজানো classifier সে সময়ের সাধারণ কম্পিউটারে প্রায় real-time-এ চলত বলে প্রবন্ধে দাবি করা হয়েছিল। এখন সে কাজ মূলত deep learning-এর (পরের একটি কোর্সে আসবে)। Tabular ডেটায় আজ সাধারণত gradient boosting (পর্ব ৩৫, ৩৬) প্রথম পছন্দ, কারণ সে যেকোনো loss নেয়, regression ভালো সামলায়, আর noise-এ কম ভঙ্গুর। তবু AdaBoost শেখা জরুরি: boosting-এর মূল কৌশল (ভুলে বেশি মন দেওয়া, ধাপে ধাপে যোগ করা) এখানেই সবচেয়ে পরিষ্কার, আর পরীক্ষা ও ইন্টারভিউতে এর এক রাউন্ড হাতে করা একটি চেনা প্রশ্ন। ছোট, পরিষ্কার-label-এর ডেটায় এটি এখনো একটি যুক্তিসঙ্গত baseline. Regression-এর জন্য AdaBoostRegressor আছে (Harris Drucker ১৯৯৭-এর AdaBoost.R2), কিন্তু সেখানেও আজ gradient boosting বেশি চলে।

আরও গভীরে: Margin ব্যাখ্যা (Schapire, Freund, Bartlett, Lee, Annals of Statistics ১৯৯৮) train margin-এর বণ্টন দিয়ে test error-এর সীমা দেয়, যা stump-এর সংখ্যার ওপর নির্ভর করে না। Leo Breiman ১৯৯৯ সালে arc-gv নামে এমন একটি পদ্ধতি দেখান যার সর্বনিম্ন margin বেশি অথচ test error খারাপ, আর Lev Reyzin ও Schapire ২০০৬ সালে দেখান যে সেখানে base learner-এর জটিলতা বেড়ে গিয়েছিল। AdaBoost-কে on-line learning-এর multiplicative weights বা “Hedge” অ্যালগরিদমের একটি প্রয়োগ হিসেবেও পড়া যায়, যেখান থেকে Freund আর Schapire-এর ১৯৯৭-এর প্রবন্ধের শুরু। পুরো তত্ত্ব একসঙ্গে পাবেন Schapire আর Freund-এর বই “Boosting: Foundations and Algorithms” (MIT Press, ২০১২)-এ।

পরীক্ষা ও ইন্টারভিউতে:

  • ε = ০.২৫ হলে α কত? ½ ln ৩ = ০.৫৪৯; ভুলের ওজন √৩ = ১.৭৩২ গুণ, ঠিকের ০.৫৭৭ গুণ।
  • Reweighting-এর পরে আগের ভুলগুলোর মোট ওজন? সবসময় ½, তাই আগের stump নতুন ওজনে মুদ্রার সমান।
  • AdaBoost কোন loss কমায়? exp(−y F(x)), forward stagewise additive model হিসেবে (Friedman, Hastie, Tibshirani ২০০০)।
  • Bagging আর boosting-এর মূল পার্থক্য? Bagging সমান্তরালে গভীর গাছের গড় নিয়ে variance কমায়; boosting ধারাবাহিকভাবে দুর্বল model যোগ করে bias কমায়।
  • AdaBoost কখন খারাপ করে? অনেক ভুল label বা outlier থাকলে: exponential loss তাদের ওজন সূচকীয় হারে বাড়ায়।

১০ · AI-এর যুগে — boosting কোড যাচাই

AI assistant-কে “AdaBoost দিয়ে ফেরত-অর্ডার predict করো” বললে সে দ্রুত কোড দেয়, কিন্তু সেই কোড প্রায়ই পুরোনো version-এর, বা হিসাবে ভুল। পরিচিত ভুলগুলো:

AI প্রায়ই যা করেকেন ভুলকীভাবে ধরবেন
base_estimator= বা algorithm="SAMME.R"1.8.0-এ দুটিই নেই; TypeErrorনিজের version-এর docs আর get_params() দেখুন
হাতে লেখা AdaBoost-এ α = ln((১ − ε)/ε), সঙ্গে e±α reweightingদুটি রীতি মিশে গেছে; ওজন বদল দ্বিগুণ জোরালোছয়টি বিন্দুতে হাতে এক রাউন্ড করে মেলান; ভুলের মোট ওজন ½ হচ্ছে কি না দেখুন
Test set-এ staged_score দেখে n_estimators বাছাTest-এ tuning, ফল আশাবাদীCV বা আলাদা validation ভাগে বাছুন; test শুধু শেষে একবার
“AdaBoost কখনো overfit করে না” দাবিLabel noise-এ স্পষ্ট overfit করেকয়েকটি seed-এ forest বা log loss-এর boosting-এর সঙ্গে তুলনা
predict_proba-কে সরাসরি সম্ভাবনা ধরাBoosting-এর সম্ভাবনা প্রায়ই calibrated নয়Reliability diagram আর আলাদা ভাগে calibration (পর্ব ২৬)

AI-কে শুধু কোড নয়, হিসাব আর যাচাইও চাইতে বলুন:

আমি বাজারঘরের ফেরত-অর্ডার (০/১) নিয়ে কাজ করছি, প্রায় ৫০,০০০ সারি, ১২টি সংখ্যার feature, scikit-learn 1.8.0. Label হাতে বসানো, তাই কিছু ভুল থাকতে পারে। ১. AdaBoostClassifier-এর একটি Pipeline দাও; শুধু 1.8.0-এ থাকা argument ব্যবহার করো (base_estimator বা algorithm দিও না), আর প্রতিটি argument docs-এর কোথায় আছে বলো। ২. n_estimators আর learning_rate বাছো 5-fold stratified CV দিয়ে, staged_score ব্যবহার করে; test set শুধু শেষে একবার। ৩. একই fold-এ RandomForestClassifier আর HistGradientBoostingClassifier রাখো, তিনটি seed-এ গড় আর ছড়ানো দেখাও। ৪. Label noise-এর প্রভাব মাপতে train-এর ১০% label ইচ্ছা করে উল্টে আবার তুলনা করো। ৫. ছয়টি বিন্দুর একটি ছোট উদাহরণে প্রথম রাউন্ডের ε, α আর নতুন ওজন হাতে হিসাব করে কোডের সঙ্গে মিলিয়ে দেখাও। ৬. কোনো warning চাপা দিও না, আর সম্ভাবনা লাগলে calibration আলাদা ভাগে করো।

AI-এর দেওয়া boosting কোড হাতে পেলে — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১“দুর্বল শেখা কি শক্ত শেখার সমান?” — একটি বিমূর্ত তাত্ত্বিক প্রশ্ন কীভাবে ML-এর সবচেয়ে ব্যবহৃত কৌশলগুলোর একটির জন্ম দিল?

১৯৮৪ সালে Leslie Valiant PAC learning-এর কাঠামো দেন: একটি শেখার পদ্ধতি “শেখে”, যদি যথেষ্ট ডেটা পেলে উচ্চ সম্ভাবনায় যত খুশি কম ভুলের নিয়ম দিতে পারে। এর কয়েক বছর পরে Michael Kearns আর Valiant একটি দুর্বলতর ধারণা নিয়ে প্রশ্ন তোলেন: যদি একটি পদ্ধতি শুধু মুদ্রা টসের চেয়ে একটু ভালো করতে পারে, তবে কি সেই সমস্যা “শক্তভাবে” শেখাও সম্ভব? প্রশ্নটি নিছক কৌতূহল ছিল না; cryptography-র সঙ্গে এর যোগ ছিল, কারণ কিছু সমস্যা দুর্বলভাবেও শেখা কঠিন হলে তা দিয়ে নিরাপত্তার প্রমাণ গড়া যায়।

Robert Schapire তাঁর ১৯৯০ সালের প্রবন্ধে গঠনমূলক উত্তর দেন: হ্যাঁ, দুটি ধারণা সমান। তাঁর পদ্ধতি তিনটি দুর্বল শেখাকে চতুরভাবে সাজানো ডেটায় চালিয়ে সংখ্যাগরিষ্ঠ ভোট নেয়, আর এটি পুনরাবৃত্তি করে ভুল যত খুশি কমায়। প্রমাণটি সুন্দর হলেও পদ্ধতিটি ব্যবহারিক ছিল না। Yoav Freund এরপর “boost by majority” দেন, যা দক্ষ কিন্তু আগে থেকে জানতে চায় weak learner কতটা ভালো (γ)। AdaBoost-এর আসল উদ্ভাবন এখানেই: প্রতিটি রাউন্ডের মাপা ε থেকেই α ঠিক হয়, তাই γ জানার দরকার নেই, আর অ্যালগরিদমটি দশ লাইনে লেখা যায়।

এই গল্পে দুটি শিক্ষা আছে। এক, তত্ত্ব আর চর্চা আলাদা জগৎ নয়: একটি বিশুদ্ধ তাত্ত্বিক প্রশ্নের উত্তর থেকে এমন পদ্ধতি এসেছে যা পরের দশকে মুখ শনাক্তকরণে, আর তার উত্তরসূরি gradient boosting আজ tabular ডেটার প্রতিযোগিতায় নিয়মিত দেখা যায়। দুই, প্রমাণের ভেতরের ধারণা (ভুলে বেশি ওজন দেওয়া, যাতে পরের শেখা বাধ্য হয় নতুন কিছু খুঁজতে) প্রমাণের চেয়ে বেশি দিন টেকে। Freund আর Schapire ২০০৩ সালে Gödel Prize পান, যা তাত্ত্বিক কম্পিউটার বিজ্ঞানের বড় পুরস্কারগুলোর একটি; ML-এর একটি ব্যবহারিক অ্যালগরিদমের জন্য এটি বিরল।

একটি সতর্কতা: “weak learner সবসময় পাওয়া যায়” এটি ধরে নেওয়া অনুমান। বাস্তবে যদি কোনো ওজনে stump মুদ্রার চেয়ে ভালো করতে না পারে (যেমন label প্রায় এলোমেলো), তাহলে তত্ত্বের নিশ্চয়তা আর খাটে না, যা অংশ ৮-এ দেখেছেন।

মূল উপলব্ধি: Boosting একটি তাত্ত্বিক সমতার প্রমাণ থেকে এসেছে; তার মূল কৌশল, ভুলের ওপর ওজন বাড়িয়ে নতুন তথ্য খোঁজা, আজকের প্রায় সব boosting পদ্ধতির ভিত্তি।

প্র ০২Train error শূন্য হওয়ার পরেও AdaBoost-এর test error কেন প্রায়ই কমতে থাকে? margin ব্যাখ্যা কতটা টেকে?

Occam-এর ক্ষুর আর VC তত্ত্বের (পর্ব ০৩) সরল শিক্ষা বলে, model যত জটিল, train আর test-এর ফাঁক তত বেশি হতে পারে। T টি stump-এর AdaBoost T বাড়লে জটিল হয়। তবু ১৯৯০-এর দশকের শেষে Harris Drucker, Corinna Cortes, Leo Breiman ও অন্যদের পরীক্ষায় দেখা গেল, অনেক ডেটায় train error শূন্য হওয়ার পরেও শত শত রাউন্ড চালালে test error কমতে থাকে বা অন্তত বাড়ে না। অংশ ৬-এর ছোট ডেটাতেও ৪০০ রাউন্ডে test error বাড়েনি।

Schapire, Freund, Bartlett আর Lee ১৯৯৮ সালে ব্যাখ্যা দেন margin দিয়ে। প্রতিটি train সারির margin = y · ∑αtht(x) / ∑αt, −১ থেকে +১-এর মধ্যে, ভোট কতটা একপক্ষীয় তার মাপ। Train error শূন্য হলেও boosting ছোট margin-এর সারিগুলোতে ওজন দিতে থাকে, ফলে margin-এর বণ্টন ডানে সরে। তাঁরা একটি সীমা প্রমাণ করেন, যেখানে test error নির্ভর করে ছোট margin-এর সারির অনুপাত আর base learner-এর জটিলতার ওপর, কিন্তু রাউন্ডের সংখ্যার ওপর নয়। SVM-এর margin-এর (পর্ব ২৭) সঙ্গে মিলটি স্পষ্ট।

কিন্তু Breiman ১৯৯৯ সালে একটি পাল্টা পরীক্ষা দেন: arc-gv নামের একটি পদ্ধতি সর্বনিম্ন margin আরও বেশি বাড়ায়, অথচ test error খারাপ করে। এটি margin ব্যাখ্যার ওপর বড় প্রশ্ন তোলে। ২০০৬ সালে Lev Reyzin আর Schapire দেখান যে সেই পরীক্ষায় arc-gv-এর গাছগুলো বেশি গভীর ও জটিল হয়ে গিয়েছিল; জটিলতা সমান রাখলে margin-এর বণ্টন আবার যুক্তিসঙ্গত ব্যাখ্যা দেয়। পরে আরও কিছু কাজ margin-এর পুরো বণ্টন (শুধু সর্বনিম্ন নয়) নিয়ে শক্ত সীমা দেয়। অন্যদিকে Friedman, Hastie আর Tibshirani-এর পরিসংখ্যানিক দৃষ্টিতে ব্যাখ্যাটি অন্য: stagewise ফিটিং নিজেই একধরনের ধীর, নিয়ন্ত্রিত শেখা।

বিতর্কটি পুরোপুরি মেটেনি, আর দুটি দৃষ্টিই কিছু অংশ ব্যাখ্যা করে। ব্যবহারিক দিক থেকে নিশ্চিত কথা হলো: “AdaBoost overfit করে না” কোনো নিয়ম নয়। Noise কম হলে এটি প্রায়ই সত্য, noise বেশি হলে মিথ্যা। আধুনিক deep learning-এর “double descent” নিয়ে আলোচনাতেও এই পুরোনো ধাঁধার প্রতিধ্বনি আছে।

মূল উপলব্ধি: Margin তত্ত্ব AdaBoost-এর দেরিতে overfit করার একটি শক্ত কিন্তু বিতর্কিত ব্যাখ্যা; রাউন্ডের সংখ্যা সবসময় validation দিয়েই ঠিক করতে হয়।

প্র ০৩Friedman, Hastie আর Tibshirani-এর “পরিসংখ্যানের চোখে boosting” কী বদলে দিল, আর কেন সেটিও বিতর্কহীন নয়?

২০০০ সালের Annals of Statistics-এর প্রবন্ধটি দেখায়, AdaBoost হলো exponential loss-এর ওপর forward stagewise additive modelling, যা অংশ ৭-এ ধাপে ধাপে দেখেছেন। প্রবন্ধটির সঙ্গে Breiman, Freund ও Schapire সহ কয়েকজনের লিখিত আলোচনা (discussion) প্রকাশিত হয়েছিল, যা পড়লে বোঝা যায় দুই সম্প্রদায় একই অ্যালগরিদমকে কত আলাদা চোখে দেখত। Learning theory-র লোকেরা দেখতেন weak learner আর margin; পরিসংখ্যানবিদরা দেখলেন একটি loss, একটি model আর একটি optimisation পদ্ধতি।

এই দৃষ্টির লাভ বিশাল। প্রথমত, loss বদলানোর দরজা খুলে গেল: log loss নিলে LogitBoost, পরে Friedman-এর ২০০১ সালের gradient boosting যেকোনো differentiable loss নেয়, regression আর ranking সহ। আজকের XGBoost, LightGBM আর CatBoost (পর্ব ৩৬) এই ধারার উত্তরসূরি। দ্বিতীয়ত, F কী আন্দাজ করছে তা বোঝা গেল: log-odds-এর অর্ধেক, তাই সম্ভাবনা বের করার একটি পথ মিলল। তৃতীয়ত, পরিচিত পরিসংখ্যানের কৌশল এল: shrinkage (ছোট learning rate), subsampling আর early stopping, যা regularization-এর মতো কাজ করে (পর্ব ১৭)।

তবু ব্যাখ্যাটি সব কিছু ব্যাখ্যা করে না। David Mease আর Abraham Wyner ২০০৮ সালে (JMLR) “Evidence contrary to the statistical view of boosting” নামে প্রবন্ধে কয়েকটি পরীক্ষা দেখান, যেখানে পরিসংখ্যানিক দৃষ্টির স্বাভাবিক পরামর্শ (যেমন ছোট গাছ, early stopping, log loss) ফল খারাপ করেছে। যেমন, তাঁদের সিমুলেশনে বড় গাছ আর বেশি রাউন্ড অনেক সময় ভালো ছিল। লেখকদের উত্তর আর পাল্টা উত্তরও প্রকাশিত হয়। সাধারণ শিক্ষা হলো, একটি তত্ত্ব কোনো অ্যালগরিদমের একটি দিক খুব ভালো আলো ফেলে, কিন্তু সেটিই “আসল” ব্যাখ্যা, এমন দাবি সাবধানে করা উচিত।

আপনার কাজের জন্য অর্থ: AdaBoost-কে loss-এর ভাষায় বোঝা সবচেয়ে কাজের, কারণ তা থেকে দুর্বলতা (noise-এ exponential loss) আর প্রতিকার (অন্য loss) দুটিই পরিষ্কার হয়। কিন্তু কোন setting ভালো, তা ডেটায় cross-validation দিয়েই ঠিক করুন।

মূল উপলব্ধি: পরিসংখ্যানের দৃষ্টি boosting-কে loss আর optimisation-এর কাঠামোয় এনে gradient boosting-এর পথ খুলেছে; তবু কোনো একক তত্ত্ব boosting-এর সব আচরণ ব্যাখ্যা করে না।

প্র ০৪ভুল label-এ AdaBoost কেন এত ভঙ্গুর, আর গবেষকরা এর প্রতিকার হিসেবে কী কী চেষ্টা করেছেন?

Thomas Dietterich ২০০০ সালে (Machine Learning জার্নাল) bagging, boosting আর randomization তুলনা করেন, আর ইচ্ছা করে label-এ noise যোগ করে দেখান: noise কম হলে AdaBoost প্রায়ই সেরা, কিন্তু noise বাড়লে bagging ভালো করে, আর AdaBoost-এর ফল দ্রুত পড়ে। কারণটি অংশ ৮-এ সংখ্যায় দেখেছেন: ভুল label-এর সারির margin স্থায়ীভাবে ঋণাত্মক, আর exp(−m) তাকে সূচকীয় হারে ওজন দেয়। কয়েক রাউন্ডেই কয়েকটি সারি মোট ওজনের বড় অংশ দখল করে, আর পরের stump-গুলো তাদের পেছনে ছোটে।

Philip Long আর Rocco Servedio ২০০৮ সালে (ICML; বিস্তারিত সংস্করণ ২০১০ সালে) আরও কড়া একটি ফল দেন: যেকোনো convex potential কমানো booster (AdaBoost, LogitBoost-সহ) এমন ডেটা দিয়ে হারানো যায়, যেখানে এলোমেলো label noise থাকলে সে প্রায় মুদ্রার মতো ফল দেয়, যদিও noise ছাড়া সমস্যাটি সহজ। এটি সব বাস্তব ডেটার কথা নয়, একটি সবচেয়ে খারাপ ক্ষেত্রের প্রমাণ; কিন্তু এটি দেখায় যে convex loss বদলালেই সমস্যা পুরো মেটে না।

প্রতিকারের চেষ্টা কয়েক দিকে। Freund নিজে BrownBoost দেন (২০০১), যা খুব বেশি ভুল হওয়া সারিকে একসময় “ছেড়ে দেয়”, অর্থাৎ non-convex potential. Gentle AdaBoost আর LogitBoost ওজনের বৃদ্ধি কম খাড়া রাখে। Shrinkage আর early stopping সারিগুলোকে কম মুখস্থ করতে দেয়। আর ডেটার দিক থেকে: label পরিষ্কার করা, একাধিক জনের annotation, বা সন্দেহজনক সারি চিহ্নিত করে বাদ দেওয়া (পর্ব ০৫)। মজার ব্যাপার, AdaBoost-এর এই দুর্বলতাকে কাজেও লাগানো যায়: শেষ রাউন্ডে যে সারিগুলোর ওজন অস্বাভাবিক বেশি, তারা প্রায়ই ভুল label-এর প্রার্থী।

বাজারঘরের মতো ব্যবসায় label প্রায়ই প্রক্রিয়া থেকে আসে: কেউ ফেরত নেয়, কেউ ভুল কারণ বসায়, কেউ ভুলে যায়। তাই AdaBoost বেছে নেওয়ার আগে প্রশ্নটি সৎভাবে করুন: label কতটা বিশ্বাসযোগ্য?

মূল উপলব্ধি: AdaBoost-এর শক্তি (কঠিন সারিতে মন দেওয়া) আর দুর্বলতা (ভুল label-এর পেছনে ছোটা) একই কৌশলের দুই দিক; noise-এর মাত্রা জেনে model আর loss বাছাই করুন।

প্র ০৫Viola–Jones-এর মুখ শনাক্তকরণে AdaBoost “feature বাছাইকারী” হিসেবে কীভাবে কাজ করেছিল, আর সেখান থেকে আজকের ML চর্চার জন্য কী শেখার আছে?

২০০১ সালে Paul Viola আর Michael Jones (CVPR সম্মেলন; পরে ২০০৪ সালে জার্নালে) একটি মুখ শনাক্তকরণ পদ্ধতি দেন, যা সে সময়ের সাধারণ কম্পিউটারে ভিডিওর প্রায় প্রতিটি ফ্রেমে চালানো যায় বলে তাঁরা জানান। এর তিনটি মূল অংশ ছিল। এক, Haar-জাতীয় সরল feature: ছবির দুটি বা তিনটি আয়তক্ষেত্রের উজ্জ্বলতার পার্থক্য, যেমন চোখের এলাকা গালের চেয়ে অন্ধকার। একটি ছোট জানালাতেই এমন feature লাখের বেশি। দুই, integral image, যা যেকোনো আয়তক্ষেত্রের যোগফল কয়েকটি যোগ-বিয়োগে দেয়। তিন, AdaBoost.

এখানে AdaBoost-এর weak learner হলো একটি feature-এর ওপর একটি stump, অর্থাৎ একটিমাত্র সীমা। প্রতি রাউন্ডে সে লাখ feature-এর মধ্য থেকে সেই একটি বাছে, যা বর্তমান ওজনে সবচেয়ে কম weighted error দেয়। ফলে AdaBoost একই সঙ্গে classifier আর feature selection: কয়েকশো রাউন্ডে কয়েকশো feature, লাখের মধ্যে। তারপর classifier-গুলো cascade-এ সাজানো: প্রথম ধাপে খুব ছোট একটি দল বেশিরভাগ “মুখ নয়” জানালা দ্রুত বাদ দেয়, শুধু সন্দেহজনকগুলো পরের ভারী ধাপে যায়। তাঁরা AdaBoost-এর একটি অসম রূপও ব্যবহার করেন, কারণ মুখ বাদ পড়া আর ভুল করে মুখ বলা, দুই ভুলের দাম সমান নয়।

অনেক বছর ধরে ক্যামেরা আর সফটওয়্যারে এই ধরনের detector ব্যাপকভাবে ব্যবহৃত হয়েছে, OpenCV-এর Haar cascade যার পরিচিত উদাহরণ। আজ convolutional neural network-ভিত্তিক detector (পরের একটি কোর্সে) নির্ভুলতায় অনেক এগিয়ে, তাই নতুন কাজে Viola–Jones সাধারণত প্রথম পছন্দ নয়।

শিক্ষা তিনটি। এক, model-এর গতি আর খরচ নকশার অংশ: cascade-এর ধারণা আজও “আগে সস্তা model দিয়ে ছেঁকে, পরে দামি model” পাইপলাইনে দেখা যায় (পর্ব ৩৭-এ ensemble-এর খরচের আলোচনাও দেখবেন)। দুই, stump-ভিত্তিক boosting স্বাভাবিকভাবে feature বাছে, যা ব্যাখ্যা দিতে সাহায্য করে। তিন, ভুলের দাম অসম হলে loss বা ওজন সেভাবে সাজাতে হয়, যা পর্ব ৫০ (Imbalanced ডেটা — resampling, SMOTE, class weight আর threshold)-এ আবার আসবে।

মূল উপলব্ধি: Viola–Jones দেখায়, সঠিক weak learner আর গতির নকশার সঙ্গে AdaBoost একই সঙ্গে feature বাছাই আর দ্রুত শ্রেণিবিভাগ করতে পারে; পদ্ধতি পুরোনো হলেও নকশার ধারণাগুলো টিকে আছে।

অনুশীলন

হিসাবগুলো খাতায় করুন, তারপর উত্তর মেলান। চাইলে অংশ ৫ আর ৬-এর ব্লক বদলে যাচাই করুন।

  1. পূর্ণ numerical: আটটি অর্ডার, প্রত্যেকের শুরুর ওজন ০.১২৫. প্রথম stump ঠিক দুটিতে ভুল করে। (ক) ε আর α কত? (খ) ভুল আর ঠিক অর্ডারের ওজন কোন গুণকে বদলায়, আর ভাগের পরে প্রত্যেকের ওজন কত? (গ) scikit-learn-এর estimator_weights_[0] কত দেখাবে? (ঘ) এক রাউন্ড পরে train error-এর সীমা কত, আর আসল train error কত?

    (ক) ε = ২ × ০.১২৫ = ০.২৫. α = ½ ln(০.৭৫/০.২৫) = ½ ln ৩ = ০.৫৪৯৩.

    (খ) ভুল × eα = √৩ = ১.৭৩২১, ঠিক × e−α = ১/√৩ = ০.৫৭৭৪. গুণের পরে ভুল প্রতিটি ০.২১৬৫, ঠিক প্রতিটি ০.০৭২২; Z = ২ × ০.২১৬৫ + ৬ × ০.০৭২২ = ০.৮৬৬০ = ২√(০.২৫ × ০.৭৫). ভাগের পরে প্রতিটি ভুল ০.২৫ (দুটির মোট ০.৫), প্রতিটি ঠিক ০.০৮৩৩ (ছয়টির মোট ০.৫)।

    (গ) SAMME, K = ২: ln ৩ + ln ১ = ১.০৯৮৬, অর্থাৎ ২α.

    (ঘ) সীমা Z₁ = ০.৮৬৬. আসল train error একটি stump-এর ভুল, ২/৮ = ০.২৫. সীমাটি সত্য কিন্তু ঢিলা; অনেক রাউন্ডের গুণফলে তবেই তা কাজের হয়।

  2. সমালোচনা ও নকশা: একটি AI assistant এই কোড আর পরিকল্পনা দিয়েছে: “বাজারঘরের ফেরতের কারণের label কর্মীরা হাতে বসান, প্রায় ১৫% ভুল হতে পারে। AdaBoostClassifier(base_estimator=DecisionTreeClassifier(max_depth=8), algorithm="SAMME.R", n_estimators=2000) শেখাব। AdaBoost কখনো overfit করে না, তাই বেশি রাউন্ডই ভালো। Test set-এ staged_score দেখে সেরা T বেছে সেই accuracy রিপোর্ট করব, আর predict_proba দিয়ে ঝুঁকির threshold ঠিক করব।” কী কী সমস্যা, আর আপনার নকশা কী?

    সমস্যা: (১) base_estimator আর algorithm scikit-learn 1.8.0-এ নেই, কোড TypeError দেবে। (২) গভীরতা ৮-এর গাছ weak learner নয়; প্রথম কয়েক রাউন্ডেই train ডেটা প্রায় মুখস্থ, আর ভুল label-এর সারিতে দ্রুত overfit. (৩) “কখনো overfit করে না” ভুল; ১৫% label noise-এ AdaBoost-এর exponential loss ভুল সারির ওজন সূচকীয় হারে বাড়ায় (অংশ ৮)। (৪) Test-এ T বাছাই করে সেই test-এর accuracy রিপোর্ট করা আশাবাদী, test আর নিরপেক্ষ নয়। (৫) predict_proba সাধারণত calibrated নয়; threshold-এর আগে calibration দরকার।

    নকশা: আগে label-এর মান যাচাই (নমুনা দুজনকে দিয়ে আবার বসিয়ে মিল দেখা, পর্ব ০৫)। Train/validation/test ভাগ, test তালাবন্ধ। AdaBoost-এর জন্য stump বা গভীরতা ২, learning_rate ০.১–০.৫, n_estimators CV-তে staged_score দিয়ে। একই fold-এ random forest আর log loss-এর gradient boosting (পর্ব ৩৫), কয়েকটি seed. সম্ভাবনা লাগলে আলাদা ভাগে CalibratedClassifierCV আর reliability diagram (পর্ব ২৬)। শেষে test-এ একবার।

  3. আউটপুট অনুমান করুন: চালানোর আগে লিখে ফেলুন নিচের কোড কী ছাপবে। (সংকেত: এটি অংশ ৩-এর ছয়টি অর্ডার।)
    import numpy as np
    from sklearn.ensemble import AdaBoostClassifier
    X = np.arange(1, 7).reshape(-1, 1)
    y = np.array([1, 1, -1, -1, 1, -1])
    ada = AdaBoostClassifier(n_estimators=3, random_state=0).fit(X, y)
    print(np.array([e.tree_.threshold[0] for e in ada.estimators_]))
    print(np.round(ada.estimator_errors_, 4))
    print(np.round(ada.estimator_weights_, 4))
    print(ada.predict(X))

    ডেস্কটপে সত্যিকারের run (Python 3.14.4, scikit-learn 1.8.0):

    [2.5 5.5 4.5]
    [0.1667 0.2    0.1875]
    [1.6094 1.3863 1.4663]
    [ 1  1 -1 -1  1 -1]

    Threshold তিনটি হাতের হিসাবের সঙ্গে হুবহু: ২.৫, ৫.৫, ৪.৫. Error-ও একই: ১/৬, ০.২, ০.১৮৭৫. ওজনগুলো ২α: ln ৫ = ১.৬০৯৪, ln ৪ = ১.৩৮৬৩, ln(০.৮১২৫/০.১৮৭৫) = ১.৪৬৬৩. শেষ লাইনে ছয়টি অনুমানই label-এর সমান, অর্থাৎ তিন রাউন্ডে train error শূন্য। label −১/+১ দেওয়া হয়েছে, তাই predict সেই মানই ফেরত দেয়।

  4. কোড লিখুন: অংশ ৫-এর ডেটায় (৬০০ অর্ডার, একই train/test ভাগ) ২০০ রাউন্ডের AdaBoostClassifier শেখান। estimator_errors_ থেকে প্রতিটি T-তে দুটি সীমা হিসাব করুন: ∏ ২√(ε(১ − ε)) আর exp(−২∑(½ − ε)²). T = ১, ৫, ২০, ৫০, ২০০-এ staged_score-এর train error-এর পাশে ছাপুন। সীমা কি সবসময় train error-এর ওপরে? শেষের দিকে ε কেমন?
    import numpy as np
    from sklearn.model_selection import train_test_split
    from sklearn.ensemble import AdaBoostClassifier
    from bazar_ret import make_returns
    
    X, y = make_returns(600)
    Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.4, random_state=0, stratify=y)
    ada = AdaBoostClassifier(n_estimators=200, random_state=0).fit(Xtr, ytr)
    eps = ada.estimator_errors_
    bound = np.cumprod(2 * np.sqrt(eps * (1 - eps)))
    gamma_bound = np.exp(-2 * np.cumsum((0.5 - eps) ** 2))
    train_err = [1 - s for s in ada.staged_score(Xtr, ytr)]
    for T in (1, 5, 20, 50, 200):
        print(f"T={T:3d}  train error {train_err[T-1]:.3f}  prod Z {bound[T-1]:.3f}"
              f"  exp bound {gamma_bound[T-1]:.3f}  eps_T {eps[T-1]:.3f}")

    ডেস্কটপে সত্যিকারের run (Python 3.14.4, scikit-learn 1.8.0):

    T=  1  train error 0.231  prod Z 0.842  exp bound 0.865  eps_T 0.231
    T=  5  train error 0.139  prod Z 0.599  exp bound 0.642  eps_T 0.459
    T= 20  train error 0.114  prod Z 0.546  exp bound 0.585  eps_T 0.479
    T= 50  train error 0.097  prod Z 0.499  exp bound 0.535  eps_T 0.474
    T=200  train error 0.072  prod Z 0.395  exp bound 0.424  eps_T 0.449

    হ্যাঁ, সব T-তে train error ≤ ∏Z ≤ exp সীমা, যেমন তত্ত্ব বলে। কিন্তু সীমা ধীরে নামে: ২০০ রাউন্ডে ০.৩৯৫. কারণ শেষ কলামে: পাঁচ রাউন্ডের পরেই ε ০.৪৫–০.৪৮, অর্থাৎ ওজন যখন কঠিন আর ভুল-label-এর সারিতে জমে, তখন stump মুদ্রার চেয়ে খুব সামান্য ভালো। ৫% label noise থাকায় train error শূন্যে নামা এখানে সম্ভবও নয়, কারণ একই জায়গায় দুই রকম label-এর অর্ডার আছে।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমDiscrete AdaBoost-এর একটি রাউন্ড আর শেষ অনুমানের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. প্রতিটি train সারির ওজন ১/n দিয়ে শুরু করা
  2. বর্তমান ওজন দিয়ে একটি weak learner (stump) শেখানো
  3. ভুল সারিগুলোর ওজন যোগ করে weighted error ε বের করা
  4. α = ½ ln((১ − ε)/ε) হিসাব করা
  5. ভুলের ওজন eα আর ঠিকের e−α গুণ করে যোগফল ১-এ ভাগ করা
  6. সব রাউন্ড শেষে sign(∑ αt ht(x)) দিয়ে অনুমান

Discrete AdaBoost-এ একটি stump-এর weighted error ε = ০.২৫. তার ভোটের ওজন α = ½ ln((১ − ε)/ε) কত?

  • ০.৫৪৯
  • ১.০৯৯
  • ০.৭৫০
  • −০.৫৪৯

AdaBoost-এর একটি রাউন্ডে weighted error ε = ০.১. Reweighting আর normalization-এর পরে, সেই রাউন্ডে ভুল হওয়া সারিগুলোর মোট ওজন কত?

  • ০.১
  • ০.৩
  • ০.৫
  • ০.৯

Binary AdaBoost-এ একটি weak learner-এর weighted error ঠিক ε = ০.৫. কী ঘটে?

  • তার α খুব বড় হয়, কারণ সে অর্ধেক সারি ঠিক বলে
  • α = ০, ওজন বদলায় না, রাউন্ডটি কোনো কাজে আসে না
  • α ঋণাত্মক হয়, তাই তার কথা উল্টে নেওয়া হয়
  • ভুল সারিগুলোর ওজন দ্বিগুণ হয়

Additive model F(x) = ∑ βtht(x)-কে forward stagewise পদ্ধতিতে কোন loss কমালে হুবহু AdaBoost-এর α আর reweighting পাওয়া যায়?

  • Log loss: log(১ + exp(−yF))
  • Hinge loss: max(০, ১ − yF)
  • Squared error: (y − F)²
  • Exponential loss: exp(−yF)

Train-এর ২০% label এলোমেলোভাবে উল্টে দিলে AdaBoost-এর test accuracy সাধারণত random forest-এর চেয়ে বেশি পড়ে। প্রধান কারণ কী?

  • Stump খুব গভীর গাছ, তাই একা মুখস্থ করে
  • AdaBoost ভুল সারিগুলোর ওজন কমিয়ে দেয়
  • Exponential loss ঋণাত্মক margin-এর সারিকে সূচকীয় হারে বেশি ওজন দেয়
  • AdaBoost bootstrap নমুনা নেয় না

SAMME-তে K টি শ্রেণি থাকলে ভোটের ওজন α = ln((১ − ε)/ε) + ln(K − ১). K = ৪ হলে কোন ε-তে weak learner-এর অবদান এখনো ধনাত্মক থাকে?

  • শুধু ε < ০.৫ হলে
  • ε < ০.৭৫ হলে
  • শুধু ε < ০.২৫ হলে
  • যেকোনো ε < ১ হলে

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. Boosting: দুর্বল model ধারাবাহিকভাবে, প্রত্যেকে আগের ভুলে বেশি ওজন দিয়ে; শেষে ওজনযুক্ত ভোট। মূলত bias কমায়, bagging-এর উল্টো।
  2. এক রাউন্ড: ε = ভুলের ওজন, α = ½ ln((১ − ε)/ε), ভুল × eα, ঠিক × e−α, Z দিয়ে ভাগ; এরপর ভুলের মোট ওজন ঠিক ½. কাজের শর্ত ০ < ε < ০.৫.
  3. সীমা: train error ≤ ∏ ২√(εt(১ − εt)) ≤ exp(−২∑γt²); কিন্তু শেষের রাউন্ডে ε প্রায় ০.৫-এর কাছে, তাই অগ্রগতি ধীর।
  4. Loss-এর দৃষ্টি: AdaBoost = additive model + exponential loss + forward stagewise; F আন্দাজ করে log-odds-এর অর্ধেক। scikit-learn 1.8 (SAMME) binary-তে ২α দেখায়, algorithm আর base_estimator নেই।
  5. সীমাবদ্ধতা: ভুল label আর outlier-এ ভঙ্গুর; shrinkage, validation-এ T বাছাই, log loss-এর boosting বা random forest বিবেচনা করুন। Multiclass-এ SAMME, শর্ত ε < ১ − ১/K.

পরের পর্বে: পর্ব ৩৫ (Gradient boosting — residual থেকে functional gradient descent) আসছে। এই পর্বে দেখেছেন AdaBoost একটি নির্দিষ্ট loss-এর ধাপে ধাপে কমানো; সেখানে যেকোনো loss-এর জন্য একই কাজ হবে gradient দিয়ে, গাছ বসবে residual-এর ওপর।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান