এই পর্বে যা শিখবেন
- 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; লাল রিং = সেই রাউন্ডে ভুল
২ · 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-ও বলা হয়):
- শুরু: প্রতিটি সারির ওজন wi = ১/n.
- প্রতি রাউন্ড 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 = নতুন ওজনের যোগফল দিয়ে ভাগ, যাতে যোগফল আবার ১ হয়।
- শেষ: F(x) = ∑t αt ht(x), আর অনুমান sign(F(x)).
এখানে ln হলো e-ভিত্তিক স্বাভাবিক লগারিদম। নিচে ছয়টি অর্ডারে পুরো হিসাব, যা ছবি ৩৪.১-এর পেছনের সংখ্যা। x = ডেলিভারির দিন ১ থেকে ৬, label: +, +, −, −, +, − (+ মানে ফেরত)। Stump শুধু “x ≤ t?” প্রশ্ন করতে পারে, t হলো দুটি বিন্দুর মাঝখানে।
- শুরু। ছয়টি অর্ডারের প্রত্যেকের ওজন ১/৬. লক্ষ করুন x = ৫ একটু অদ্ভুত: আশপাশের অর্ডার ফেরত আসেনি, এটি এসেছে। একটি stump একে আলাদা করতে পারবে না।
- রাউন্ড ১-এর stump বাছাই। সব সম্ভাব্য t আর দুই দিক পরীক্ষা করলে সেরা “x ≤ ২.৫ হলে +”: শুধু x = ৫ ভুল। ε₁ = ১/৬ ≈ ০.১৬৬৭. অন্য যেকোনো stump অন্তত দুটি বিন্দুতে ভুল করে।
- ভোটের ওজন। α₁ = ½ ln ৫ = ০.৮০৪৭. দুই গুণক একে অন্যের উল্টো: ঠিক অর্ডারের ওজন ০.৪৪৭২ গুণ, ভুলটির ২.২৩৬১ গুণ।
- Reweighting. গুণের পরে যোগফল Z₁ = ০.৭৪৫৪, যা সূত্র ২√(ε(১ − ε))-এর সঙ্গে মেলে। ভাগ করলে ভুল x = ৫ পায় ০.৫, বাকি পাঁচটি ০.১ করে। একটিমাত্র ভুল এখন মোট ওজনের অর্ধেক।
- রাউন্ড ২। নতুন ওজনে h₁-এর weighted error ঠিক ০.৫, মুদ্রার সমান, তাই তাকে আবার বাছা অর্থহীন। সেরা stump “x ≤ ৫.৫ হলে +”: সে ভারী x = ৫-কে ঠিক বলে, দাম দেয় x = ৩, ৪-এ। ε₂ = ০.২, α₂ = ০.৬৯৩১. আবার গুণ আর ভাগ।
- দুই রাউন্ডের ভোট। x = ৩, ৪, ৫-এ দুই stump দুই কথা বলে; α₁ একটু বড়, তাই F = −০.১১, তিনটিই − পায়। x = ৫ এখনো ভুল, train error ১/৬. সীমা Z₁Z₂ = ০.৫৯৬, যা ১/৬-এর চেয়ে বড়: সীমা ঠিকই আছে, শুধু ঢিলা।
- রাউন্ড ৩। এখন ভারী x = ৩, ৪, ৫. সেরা stump “x ≤ ৪.৫ হলে −”, ভুল শুধু হালকা তিনটিতে: ε₃ = ০.১৮৭৫, α₃ = ০.৭৩৩২. তিন stump-এর যোগে ছয়টিই ঠিক। scikit-learn এই ছয় বিন্দুতে একই তিনটি threshold বাছে (অনুশীলন ৩)।
৪ · α, ε আর ওজনের হিসাব — কেন ε < ½ লাগে
α-এর সূত্রটি কোথা থেকে এল, অংশ ৭-এ প্রমাণ দেখবেন। আগে তার আচরণ বুঝে নিন। ln((১ − ε)/ε) হলো ঠিক হওয়ার odds-এর লগারিদম: stump যত ভরসার, তার কথার দাম তত বেশি।
- ভালো stump. দশ ভাগে এক ভাগ ভুল। α = ১.০৯৮৬, আর ভুলের ওজন ঠিক তিন গুণ, ঠিকের এক-তৃতীয়াংশ। scikit-learn-এর
estimator_weights_-এ এই সংখ্যার দ্বিগুণ দেখবেন; কারণ অংশ ৫-এ। - সাধারণ weak learner. ৩০% ভুল। α = ০.৪২৩৬, ওজনের গুণক ১.৫২৭৫ আর ০.৬৫৪৭: বদল মৃদু। বাস্তবের বেশিরভাগ রাউন্ড এমন বা আরও দুর্বল।
- মুদ্রা টস। ε = ০.৫-এ α = ০, ওজন বদলায় না, Z = ১. পরের রাউন্ড একই ওজনে একই stump পাবে, তাই অগ্রগতি থেমে যায়। এজন্যই weak learner-কে মুদ্রার চেয়ে একটু হলেও ভালো হতে হয়।
- মুদ্রার চেয়ে খারাপ। ε = ০.৭ হলে α ঋণাত্মক: stump-এর কথা উল্টে নিলেই ε = ০.৩-এর stump. Binary-তে তাই এমন stump আসলে দরকারি তথ্য দেয়। তবু scikit-learn ε ≥ ০.৫ দেখলে সেই estimator বাদ দিয়ে থামে; stump নিজেই দুই দিক পরীক্ষা করে বলে এটি সাধারণত ঘটে না।
- পুরো ছবি। ε ০.৫-এর দিকে গেলে α শূন্যে নামে, ০-এর দিকে গেলে অসীমে ওঠে। নিখুঁত stump (ε = ০) পেলে scikit-learn তাকে ওজন ১ দিয়ে সেখানেই থামে। তাই কাজের পরিসর ০ < ε < ০.৫.
ভুলের ওজন কেন ঠিক অর্ধেক হয়
রাউন্ড শেষে ভুল সারিগুলোর মোট ওজন: ε · eα / Z = ε · √((১ − ε)/ε) / (২√(ε(১ − ε))) = ½. ঠিকগুলোরও ½. ফল ε যা-ই হোক সবসময় এক। অর্থাৎ নতুন ওজনে সদ্য শেখানো stump ঠিক মুদ্রার মতো, আর পরের রাউন্ডকে নতুন কিছু খুঁজতেই হয়। দশটি অর্ডারে দেখুন:
- শুরু। দশটি অর্ডার, প্রত্যেকে ০.১. Stump তিনটিতে ভুল, তাই ε = ০.৩ আর α = ০.৪২৩৬.
- গুণ। প্রতিটি ভুলের ওজন ০.১৫২৮, প্রতিটি ঠিকের ০.০৬৫৫. আশ্চর্য মিল: তিন ভুলের মোট আর সাত ঠিকের মোট দুটিই ০.৪৫৮৩. মোট Z = ০.৯১৬৫.
- ভাগ। Z দিয়ে ভাগ করলে প্রতিটি ভুল ০.১৬৬৭ (দেড় গুণের বেশি), প্রতিটি ঠিক ০.০৭১৪. দুই দলের মোট ঠিক ০.৫ আর ০.৫.
- পুরোনো stump অচল। নতুন ওজনে আগের stump-এর weighted error ০.৫, মুদ্রার সমান। Boosting তাই নিজে থেকেই বৈচিত্র্য আনে: পরপর দুই রাউন্ডে একই stump কখনো সেরা হয় না।
- 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-এ সাধারণত কম ক্ষতি |
estimator | Weak learner (ডিফল্ট stump) | গভীরতা ২–৩ interaction ধরে; খুব গভীর গাছ weak থাকে না, দ্রুত overfit |
random_state | Estimator-এর seed | Stump-এ সমান ভালো 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-কে শুধু কোড নয়, হিসাব আর যাচাইও চাইতে বলুন:
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 বাছাই আর দ্রুত শ্রেণিবিভাগ করতে পারে; পদ্ধতি পুরোনো হলেও নকশার ধারণাগুলো টিকে আছে।
অনুশীলন
হিসাবগুলো খাতায় করুন, তারপর উত্তর মেলান। চাইলে অংশ ৫ আর ৬-এর ব্লক বদলে যাচাই করুন।
-
পূর্ণ numerical: আটটি অর্ডার, প্রত্যেকের শুরুর ওজন ০.১২৫. প্রথম stump ঠিক দুটিতে ভুল করে। (ক) ε আর α কত? (খ) ভুল আর ঠিক অর্ডারের ওজন কোন গুণকে বদলায়, আর ভাগের পরে প্রত্যেকের ওজন কত? (গ) scikit-learn-এর
estimator_weights_[0]কত দেখাবে? (ঘ) এক রাউন্ড পরে train error-এর সীমা কত, আর আসল train error কত?(ক) ε = ২ × ০.১২৫ = ০.২৫. α = ½ ln(০.৭৫/০.২৫) = ½ ln ৩ = ০.৫৪৯৩.
(খ) ভুল × eα = √৩ = ১.৭৩২১, ঠিক × e−α = ১/√৩ = ০.৫৭৭৪. গুণের পরে ভুল প্রতিটি ০.২১৬৫, ঠিক প্রতিটি ০.০৭২২; Z = ২ × ০.২১৬৫ + ৬ × ০.০৭২২ = ০.৮৬৬০ = ২√(০.২৫ × ০.৭৫). ভাগের পরে প্রতিটি ভুল ০.২৫ (দুটির মোট ০.৫), প্রতিটি ঠিক ০.০৮৩৩ (ছয়টির মোট ০.৫)।
(গ) SAMME, K = ২: ln ৩ + ln ১ = ১.০৯৮৬, অর্থাৎ ২α.
(ঘ) সীমা Z₁ = ০.৮৬৬. আসল train error একটি stump-এর ভুল, ২/৮ = ০.২৫. সীমাটি সত্য কিন্তু ঢিলা; অনেক রাউন্ডের গুণফলে তবেই তা কাজের হয়।
-
সমালোচনা ও নকশা: একটি 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আরalgorithmscikit-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_estimatorsCV-তেstaged_scoreদিয়ে। একই fold-এ random forest আর log loss-এর gradient boosting (পর্ব ৩৫), কয়েকটি seed. সম্ভাবনা লাগলে আলাদা ভাগেCalibratedClassifierCVআর reliability diagram (পর্ব ২৬)। শেষে test-এ একবার। -
আউটপুট অনুমান করুন: চালানোর আগে লিখে ফেলুন নিচের কোড কী ছাপবে। (সংকেত: এটি অংশ ৩-এর ছয়টি অর্ডার।)
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সেই মানই ফেরত দেয়। -
কোড লিখুন: অংশ ৫-এর ডেটায় (৬০০ অর্ডার, একই 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-এর একটি রাউন্ড আর শেষ অনুমানের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- প্রতিটি train সারির ওজন ১/n দিয়ে শুরু করা
- বর্তমান ওজন দিয়ে একটি weak learner (stump) শেখানো
- ভুল সারিগুলোর ওজন যোগ করে weighted error ε বের করা
- α = ½ ln((১ − ε)/ε) হিসাব করা
- ভুলের ওজন eα আর ঠিকের e−α গুণ করে যোগফল ১-এ ভাগ করা
- সব রাউন্ড শেষে 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-এর অবদান এখনো ধনাত্মক থাকে?
- শুধু ε < ০.৫ হলে
- ε < ০.৭৫ হলে
- শুধু ε < ০.২৫ হলে
- যেকোনো ε < ১ হলে
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Boosting: দুর্বল model ধারাবাহিকভাবে, প্রত্যেকে আগের ভুলে বেশি ওজন দিয়ে; শেষে ওজনযুক্ত ভোট। মূলত bias কমায়, bagging-এর উল্টো।
- এক রাউন্ড: ε = ভুলের ওজন, α = ½ ln((১ − ε)/ε), ভুল × eα, ঠিক × e−α, Z দিয়ে ভাগ; এরপর ভুলের মোট ওজন ঠিক ½. কাজের শর্ত ০ < ε < ০.৫.
- সীমা: train error ≤ ∏ ২√(εt(১ − εt)) ≤ exp(−২∑γt²); কিন্তু শেষের রাউন্ডে ε প্রায় ০.৫-এর কাছে, তাই অগ্রগতি ধীর।
- Loss-এর দৃষ্টি: AdaBoost = additive model + exponential loss + forward stagewise; F আন্দাজ করে log-odds-এর অর্ধেক। scikit-learn 1.8 (SAMME) binary-তে ২α দেখায়,
algorithmআরbase_estimatorনেই। - সীমাবদ্ধতা: ভুল label আর outlier-এ ভঙ্গুর; shrinkage, validation-এ T বাছাই, log loss-এর boosting বা random forest বিবেচনা করুন। Multiclass-এ SAMME, শর্ত ε < ১ − ১/K.
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৩৫ · Gradient boosting — residual থেকে functional gradient descentপরবর্তী পর্বResidual-এ গাছ বসিয়ে function-এর জগতে gradient descent চালানো।
- মেশিন লার্নিং · কোর্স হোম৭৭টি পর্ব১৪টি মডিউলের পুরো রোডম্যাপ; পর্ব ৩৫ (Gradient boosting) এখান থেকে খুলবেন।
- পর্ব ০২ · সমস্যা থেকে ML taskএই কোর্সExponential, log আর 0-1 loss-এর আকার আর surrogate loss.
- পর্ব ০৩ · শেখার তত্ত্বএই কোর্সPAC learning আর VC dimension, যেখান থেকে weak learnability-র প্রশ্ন এসেছে।
- পর্ব ০৫ · Label আর ডেটার মানএই কোর্সLabel noise মাপা আর কমানো, যা AdaBoost-এর জন্য বিশেষ জরুরি।
- পর্ব ২৬ · Probability calibrationএই কোর্সBoosting-এর সম্ভাবনা মাপা আর সারানো।
- AI-এর ভিত্তি · পর্ব ৬৬ · Model selection আর CVআগের ভিত্তিBoosting-এর প্রথম দেখা, আর n_estimators-এর মতো hyperparameter সৎভাবে বাছাই।