এই পর্বে যা শিখবেন
- দুই model-এর গড়ের ভুল হাতে হিসাব করা, আর ভুলের correlation মেপে বোঝা গড় কখন লাভ দেবে
- Hard আর soft voting হাতে, তারপর
VotingClassifierদিয়ে, আর দুটির ফল কেন আলাদা হয় cross_val_predictদিয়ে stacking নিজে লেখা,StackingClassifier-এর সঙ্গে হুবহু মেলানো, আর in-sample stacking-এর ফাঁদ নিজের চোখে দেখা- Blending,
scipy.optimizeদিয়ে simplex-এ weight খোঁজা আর ensemble selection - Tuned একক GBM-এর সঙ্গে ensemble-এর সৎ তুলনা, আর production-এ ensemble-এর আসল খরচ হিসাব করা
এই মডিউলের আগের পর্বগুলোতে ensemble-এর দুটি বড় পরিবার এসেছে। পর্ব ৩৩ (Bagging আর random forest — variance কমানো আর OOB error)-এ অনেক গাছের গড় variance কমায়। পর্ব ৩৪ (Boosting-এর ধারণা — AdaBoost আর weak learner), পর্ব ৩৫ (Gradient boosting — residual থেকে functional gradient descent) আর পর্ব ৩৬ (XGBoost, LightGBM আর CatBoost — পার্থক্য আর হাতে-কলমে)-এ গাছেরা একটির পর একটি আগের ভুল শোধরায়। দুই ক্ষেত্রেই সদস্যরা একই ধরনের model, আর মেলানোর নিয়ম ভেতরে বাঁধা। এই পর্বের প্রশ্ন আলাদা: একেবারে ভিন্ন ধরনের model, যেমন logistic regression, random forest আর boosting, তাদের মত কীভাবে মেলাবেন, আর আদৌ মেলাবেন কি না। পাতার চারটি ব্লক ব্রাউজারের Python-এ চলে (scikit-learn 1.8.0); প্রথমবার scikit-learn নামতে প্রায় দশ সেকেন্ড লাগে। বড় তুলনাটি ডেস্কটপের সত্যিকারের run.
১ · কেন একাধিক model — ভুল যখন আলাদা জায়গায়
কঠিন রোগে অনেকে দ্বিতীয় ডাক্তারের মত নেন। কিন্তু দুজন ডাক্তার যদি একই বই পড়ে, একই হাসপাতালে, একই রিপোর্ট দেখে মত দেন, তাঁরা প্রায় একই জায়গায় ভুল করবেন; দ্বিতীয় মতে লাভ সামান্য। লাভ হয় তখন, যখন দ্বিতীয়জন অন্যভাবে দেখেন: একজন রক্তের রিপোর্ট দেখেন, আরেকজন রোগীর ইতিহাস। আবার দ্বিতীয়জন যদি অনভিজ্ঞ হন, দুজনের মতের গড় নিলে ভালো ডাক্তারের একার মতের চেয়েও খারাপ হতে পারে। Ensemble-এর পুরো গল্প এই তিনটি বাক্যে।
সংখ্যায় দেখি। ধরুন দুটি model একই অর্ডারের ফেরতের সম্ভাবনা অনুমান করে, আর প্রতিটির ভুল e₁ = p̂₁ − p, e₂ = p̂₂ − p (p আসল মান)। ভুলের গড় শূন্য, ছড়ানো σ₁ আর σ₂ (standard deviation), আর দুই ভুলের correlation ρ (−১ থেকে +১). দুই অনুমানের সরল গড়ের ভুল (e₁ + e₂)/2, যার variance:
Var((e₁ + e₂)/2) = (σ₁² + σ₂² + 2ρσ₁σ₂) / 4
σ₁ = σ₂ = σ হলে এটি σ²(1 + ρ)/2. ρ = ১ মানে দুই model হুবহু একই ভুল করে: গড়েও σ², কোনো লাভ নেই। ρ = ০ হলে ভুলের variance অর্ধেক। ρ ঋণাত্মক হলে আরও কম। M টি সমান model-এর বেলায় সূত্রটি ρσ² + (1 − ρ)σ²/M; random forest কেন গাছদের মধ্যে correlation কমাতে চায়, তা পর্ব ৩৩-এ এই সূত্র থেকেই আসছে। নিচের stepper-এ σ₁ = ১ ধরে (অর্থাৎ σ₁²-এর এককে) কয়েকটি পরিস্থিতি:
- হুবহু একই ভুল। ρ = ১ হলে গড়ের variance ১.০০, একক model-এর সমান। একই ডেটায়, একই feature-এ, একই ধরনের model দশবার শেখালে প্রায় এটাই হয়।
- আধা মিল। ρ = ০.৫-এ variance ০.৭৫, এক-চতুর্থাংশ কম। বাস্তবের ভিন্ন model-দের ভুল সাধারণত এই এলাকায় বা তার ওপরে থাকে।
- স্বাধীন ভুল। ρ = ০ হলে variance অর্ধেক। বাস্তবে এটি বিরল, কারণ সব model একই ডেটা থেকে শেখে আর একই কঠিন অর্ডারে হোঁচট খায়।
- উল্টো দিকের ভুল। ρ = −০.৫-এ variance মাত্র ০.২৫. একজন বেশি অনুমান করলে অন্যজন কম করে, গড়ে কাটাকাটি। Negative correlation learning ঠিক এই অবস্থা ইচ্ছা করে তৈরি করতে চায় (অংশ ৭).
- দুর্বল সঙ্গী। σ₂ = ২ (দ্বিগুণ ছড়ানো ভুল), ρ = ০.৫: সরল গড় ১.৭৫, ভালো model-এর একার ১.০০-এর চেয়ে খারাপ। সেরা weight হিসাব করলে w* = ১, অর্থাৎ দুর্বল model-টি বাদ। এ কারণেই weight খোঁজা (অংশ ৫) আর stacking (অংশ ৩) দরকার হয়।
- সাধারণ শর্ত। r = σ₂/σ₁ হলে সরল গড় ভালো model-কে হারায় শুধু যখন ρ < (৩ − r²)/(২r). সমান মানের দুই model-এ প্রায় সবসময়; দেড় গুণ দুর্বল সঙ্গীতে ρ ০.২৫-এর নিচে লাগে; দ্বিগুণ দুর্বলে ρ ঋণাত্মক হতে হবে।
এই পুরো হিসাব squared error-এর জন্য। Classification-এ 0/1 ভুলের ক্ষেত্রেও একই অন্তর্দৃষ্টি খাটে, কিন্তু সূত্র এত পরিষ্কার নয়। তাই বাস্তবে আমরা মাপি: প্রতিটি model-এর test অর্ডারে ভুল (১) বা ঠিক (০) একটি কলামে লিখে কলামগুলোর correlation বের করি। এর নাম error correlation; ensemble-এর সদস্যদের মধ্যে এই ভিন্নতাকে বলে diversity. ভিন্নতা আসে ভিন্ন ধরনের model (রৈখিক বনাম গাছ), ভিন্ন feature, ভিন্ন ডেটা-নমুনা বা ভিন্ন seed থেকে; প্রথম দুটি সাধারণত সবচেয়ে বেশি দেয়।
২ · Hard আর soft voting — সংখ্যাগরিষ্ঠ না গড়
সবচেয়ে সরল মেলানো হলো ভোট। Hard voting-এ প্রতিটি model একটি label দেয় (ফেরত হবে / হবে না), আর যে label বেশি ভোট পায় সেটিই উত্তর, অর্থাৎ majority vote. Soft voting-এ প্রতিটি model একটি সম্ভাবনা দেয়, তাদের গড় (বা weight দেওয়া গড়) ০.৫ পেরোলে “ফেরত”. দুটি পদ্ধতি একই অর্ডারে ভিন্ন উত্তর দিতে পারে। বাজারঘরের দুটি কাল্পনিক অর্ডারে তিন model-এর সম্ভাবনা ধরে নিয়ে হাতে করি:
- তিনটি মত। অর্ডার ক-তে একটি model বেশ নিশ্চিত “হ্যাঁ”, বাকি দুটি কোনোরকমে “না”. অর্ডার খ-তে দুটি কোনোরকমে “হ্যাঁ”, একটি খুব নিশ্চিত “না”.
- Hard vote, ক। সম্ভাবনাকে ০.৫-এ কেটে label: এক হ্যাঁ, দুই না। উত্তর “ফেরত হবে না”. ০.৪৫ আর ০.৪৮ যে প্রায় দোটানায় ছিল, তা হারিয়ে গেল।
- Soft vote, ক। গড় ০.৫১৭, সীমার ওপরে: “ফেরত হবে”. একজনের জোরালো মত দুজনের দুর্বল আপত্তিকে ছাড়িয়ে গেল।
- Hard vote, খ। দুই হ্যাঁ, এক না: “ফেরত হবে”. hgb-এর ০.১০, অর্থাৎ খুব নিশ্চিত “না”, একটি সাধারণ ভোটের সমান গুরুত্ব পেল।
- Soft vote, খ। গড় ০.৩৯০: “ফেরত হবে না”. দুই ক্ষেত্রেই soft vote নিশ্চয়তার মাত্রা গোনে, hard vote শুধু দিক গোনে।
- Weight দেওয়া গড়। অংশ ৫-এর মতো weight (০.৪৪, ০.৩৬, ০.২০) দিলে ক = ০.৫৩১ (ফেরত), খ = ০.৪৪৯ (ফেরত নয়)। এখানে সিদ্ধান্ত বদলায়নি, কিন্তু সীমার কাছের অর্ডারে weight বদলালে বদলাতে পারে।
Soft voting-এর একটি শর্ত: model-দের সম্ভাবনা মোটামুটি একই স্কেলে আর বিশ্বাসযোগ্য হতে হবে। পর্ব ২৬-এ দেখেছেন, random forest-এর সম্ভাবনা মাঝের দিকে চাপা থাকে আর boosting-এর সম্ভাবনা কখনো প্রান্তের দিকে ঠেলা। একটি model খারাপভাবে calibrated হলে soft vote-এ তার প্রভাব ভুল মাপের হয়। Hard voting-এর সুবিধা হলো সম্ভাবনা লাগে না, আর অদ্ভুত স্কেলের model-ও সমান এক ভোট পায়।
এই পর্বের ডেটা: বাজারঘরের কাল্পনিক ২,০০০টি অর্ডার, seed দিয়ে বানানো। ছয়টি feature: দাম (হাজার টাকায়), ডেলিভারির দিন, প্রথম অর্ডার কি না, পোশাক কি না, আগের ফেরতের সংখ্যা আর ছাড়ের শতাংশ। লক্ষ্য: অর্ডারটি ফেরত আসবে কি না (প্রায় ২৮%). নিয়মের ভেতরে রৈখিক অংশ আছে, আবার “প্রথম অর্ডারের পোশাক” বা “দামি পণ্য আর দেরিতে ডেলিভারি”-র মতো interaction আর ছাড়ের ঢেউখেলানো প্রভাবও আছে; ফলে কোনো এক ধরনের model সব ধরতে পারে না। তিনটি base model পুরো পর্বে একই: scaling-সহ logistic regression, random forest (৫০টি গাছ) আর HistGradientBoosting (৫০ ধাপ, ডিফল্ট সেটিং)।
# bazar_returns.py — বাজারঘরের কাল্পনিক অর্ডার-ফেরত (seed দিয়ে বানানো)
import numpy as np
def make_returns(n=2000, seed=37):
rng = np.random.default_rng(seed)
price = rng.lognormal(0.7, 0.6, n) # দাম, হাজার টাকায়
days = rng.integers(1, 8, n) # ডেলিভারির দিন
first = (rng.random(n) < 0.35).astype(int) # প্রথম অর্ডার কি না
cloth = (rng.random(n) < 0.40).astype(int) # পোশাক কি না
past = rng.poisson(0.6, n) # আগের ফেরতের সংখ্যা
disc = rng.uniform(0, 40, n) # ছাড়, শতাংশে
z = (-2.2 + 0.6 * past + 0.03 * disc + 0.25 * (days - 4)
+ 1.6 * cloth * first + 1.2 * (price > 3.5) * (days > 4)
- 0.4 * np.log(price) + 1.1 * np.sin(disc / 6))
y = (rng.random(n) < 1 / (1 + np.exp(-z))).astype(int)
X = np.column_stack([price, days, first, cloth, past, disc])
return X, y
def base_models():
"""তিনটি ভিন্ন ধরনের base model, এই পর্বের সব ব্লকে একই"""
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import (RandomForestClassifier,
HistGradientBoostingClassifier)
return [("logit", make_pipeline(StandardScaler(), LogisticRegression())),
("forest", RandomForestClassifier(n_estimators=50,
min_samples_leaf=2, random_state=0)),
("hgb", HistGradientBoostingClassifier(max_iter=50, random_state=0))]প্রথম ব্লক অর্ধেক অর্ডারে শেখায়, বাকি অর্ধেকে মাপে: প্রতিটি model-এর accuracy আর log loss, ভুলের correlation দুইভাবে, তারপর hard আর soft vote হাতে আর VotingClassifier দিয়ে।
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import log_loss
from sklearn.ensemble import VotingClassifier
from bazar_returns import make_returns, base_models
X, y = make_returns()
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.5, stratify=y,
random_state=0)
print(f"orders {len(y)}, returned {y.mean():.3f}")
P = []
for name, m in base_models():
p = m.fit(Xtr, ytr).predict_proba(Xte)[:, 1]
P.append(p)
print(f"{name:7s} acc {np.mean((p > 0.5) == yte):.3f}"
f" log loss {log_loss(yte, p):.4f}")
P = np.array(P)
wrong = ((P > 0.5) != yte).astype(float)
print("correlation of 0/1 mistakes (logit, forest, hgb):")
print(np.round(np.corrcoef(wrong), 2))
print("correlation of errors y - p:")
print(np.round(np.corrcoef(yte - P), 2))
hard = (P > 0.5).sum(axis=0) >= 2
soft = P.mean(axis=0)
print(f"hard vote by hand acc {np.mean(hard == yte):.3f}")
print(f"soft vote by hand acc {np.mean((soft > 0.5) == yte):.3f}"
f" log loss {log_loss(yte, soft):.4f}")
for v in ["hard", "soft"]:
vc = VotingClassifier(base_models(), voting=v).fit(Xtr, ytr)
print(f"VotingClassifier({v}) acc {vc.score(Xte, yte):.3f}")আসল আউটপুট (ব্রাউজারের Python):
orders 2000, returned 0.282
logit acc 0.748 log loss 0.5280
forest acc 0.756 log loss 0.5126
hgb acc 0.771 log loss 0.5412
correlation of 0/1 mistakes (logit, forest, hgb):
[[1. 0.67 0.54]
[0.67 1. 0.76]
[0.54 0.76 1. ]]
correlation of errors y - p:
[[1. 0.93 0.89]
[0.93 1. 0.96]
[0.89 0.96 1. ]]
hard vote by hand acc 0.767
soft vote by hand acc 0.766 log loss 0.5004
VotingClassifier(hard) acc 0.767
VotingClassifier(soft) acc 0.766
একক model: accuracy-তে hgb সেরা (০.৭৭১), কিন্তু তার log loss সবচেয়ে খারাপ (০.৫৪১২): ডিফল্ট boosting অনেক অর্ডারে অতিরিক্ত নিশ্চিত সম্ভাবনা দেয়। Log loss-এ সেরা forest (০.৫১২৬). ভুলের correlation: 0/1 ভুলে logit আর hgb-এর মধ্যে ০.৫৪, forest আর hgb-এর মধ্যে ০.৭৬; দুই গাছভিত্তিক model বেশি একরকম, logit সবচেয়ে ভিন্ন। কিন্তু y − p-এর correlation ০.৮৯–০.৯৬: প্রতিটি model-ই সেই “অপ্রত্যাশিত” ফেরতগুলো একইভাবে মিস করে, যা কোনো feature থেকে বোঝা যায় না। অর্থাৎ ভুলের বড় অংশ সবার সাধারণ, আর গড় নিয়ে সেটি কমে না। Vote: হাতে করা ফল VotingClassifier-এর সঙ্গে হুবহু মেলে। Accuracy-তে hard (০.৭৬৭) বা soft (০.৭৬৬) কেউই একা hgb-কে ছাড়ায়নি; ১,০০০ অর্ডারে ০.০০৪ মানে চারটি অর্ডার, গোলমালের মধ্যেই। কিন্তু soft vote-এর log loss ০.৫০০৪, প্রতিটি একক model-এর চেয়ে ভালো। Soft vote-এর লাভ প্রায়ই এখানেই দেখা যায়: label-এ নয়, সম্ভাবনার মানে।
৩ · Stacking — একটি model শেখে কাকে কতটা বিশ্বাস করবে
Voting-এ weight আমরা ঠিক করি (সমান, বা হাতে বসানো)। Stacking-এ weight, এমনকি মেলানোর পুরো নিয়মও, ডেটা থেকে শেখা হয়। প্রথম স্তরে কয়েকটি base model; তাদের সম্ভাবনাগুলো হয় নতুন feature; দ্বিতীয় স্তরে একটি meta-model (প্রায়ই সরল logistic regression) সেই feature থেকে আসল label অনুমান করতে শেখে। David Wolpert ১৯৯২ সালে এর নাম দেন “stacked generalization”; Leo Breiman ১৯৯৬ সালে “Stacked regressions” প্রবন্ধে regression-এ এটি পরীক্ষা করে দেখান যে meta-model-এর weight অ-ঋণাত্মক রাখলে ফল বেশি স্থির হয়।
Stacking-এর দুই স্তর: train-এ out-of-fold, test-এ পুরো train-এ শেখা base model
meta-model দেখে শুধু এমন সম্ভাবনা, যা সারিটি না দেখে তৈরি
সবচেয়ে গুরুত্বপূর্ণ প্রশ্ন: meta-model-এর train টেবিলের সম্ভাবনাগুলো কোথা থেকে আসবে? সহজ উত্তর মনে হয়: base model-দের পুরো train-এ শিখিয়ে সেই train-এর ওপরেই অনুমান করানো। এটিই ফাঁদ। random forest বা boosting train সারিগুলো অনেকটা মুখস্থ করে; নিজের দেখা সারিতে তার সম্ভাবনা প্রায় আসল label-এর গায়ে লেগে থাকে। Meta-model তখন ভাবে “forest প্রায় নির্ভুল”, আর তাকে বিশাল weight দেয়। কিন্তু নতুন অর্ডারে forest মোটেও অত নির্ভুল নয়। এটি একধরনের data leakage: AI-এর ভিত্তি কোর্সের পর্ব ৫৬-এ দেখেছেন, যে তথ্য অনুমানের সময় পাওয়া যাবে না, তা শেখার সময় ঢুকলে মূল্যায়ন মিথ্যা হয়। এখানে সেই তথ্য হলো “এই সারির label base model আগেই দেখেছে”.
সমাধান cross-validation-এর কৌশল (AI-এর ভিত্তি কোর্সের পর্ব ৬৬): train-কে K ভাগে ভাগ করুন, প্রতিবার এক ভাগ লুকিয়ে বাকিগুলোতে base model শেখান, আর লুকানো ভাগের অনুমান লিখে রাখুন। K বার শেষে প্রতিটি train সারির একটি করে out-of-fold (OOF) সম্ভাবনা পাওয়া যায়, যা এমন model থেকে এসেছে যে সারিটি দেখেনি। নিচের stepper ব্লক ২-এর আসল run-এর সংখ্যা দিয়ে দুই পথ পাশাপাশি দেখায় (১,০০০টি train অর্ডার, ৫ fold).
- ফাঁস হওয়া পথ। অর্ডার ক আসলে ফেরত এসেছে, যদিও feature দেখে তেমন মনে হয় না। পুরো train-এ শেখা forest আর hgb তাকে দেখেছে, তাই দেয় ০.৭৭ আর ০.৭৮. অর্ডার খ-তেও তারা উত্তরের দিকে ঝুঁকে আছে।
- গড়ে কতটা মুখস্থ। Train সারিতে forest-এর গড় ভুল in-sample ০.২০৩, কিন্তু OOF-এ ০.৩৩৮; hgb ০.২০৪ বনাম ০.৩১৭. Logistic regression-এ প্রায় কোনো পার্থক্য নেই (০.৩৫৪ বনাম ০.৩৫৫), কারণ তার ছয়টি weight মুখস্থ করার জায়গা দেয় না।
- Meta-model ঠকে। In-sample কলাম দেখে meta-model forest-কে ৭.৭৬ weight দেয় আর logit-কে ঋণাত্মক −২.৫৫: “logit যা বলে, তার উল্টোটাই সম্ভবত ঠিক”. Test অর্ডারে এই বিশ্বাস ভুল প্রমাণিত হবে।
- সৎ পথ, এক fold. ২০০টি সারি লুকিয়ে বাকি ৮০০-তে শেখানো, তারপর লুকানো ২০০-তে অনুমান। পাঁচ fold-এ প্রতিটি সারি ঠিক একবার লুকায়।
- OOF টেবিল। একই অর্ডার ক-তে এখন ০.২০, ০.৩২, ০.৩৯: কোনো model-ই তাকে ফেরত বলে চেনেনি। Meta-model এবার base model-দের আসল, নতুন-ডেটার মতো আচরণ দেখে।
- ফল। সৎ meta-model-এর weight তিনটিই ধনাত্মক আর কাছাকাছি; validation ০.৭৫৩, test ০.৭৭২. ফাঁস হওয়া meta-model নিজের টেবিলে CV করে ০.৯৫৬ দেখায়, কিন্তু test-এ ০.৭৬৩. মিথ্যা আশ্বাস আর খারাপ ফল একসঙ্গে।
৪ · Stacking নিজে লেখা, তারপর StackingClassifier
দ্বিতীয় ব্লক তিনটি কাজ করে। এক, cross_val_predict(..., method="predict_proba") দিয়ে তিন base model-এর OOF সম্ভাবনা বানায়, আর একই model পুরো train-এ শিখিয়ে in-sample সম্ভাবনাও বানায়। দুই, দুই টেবিলেই একটি logistic meta-model শেখায়, আর প্রতিটির validation (meta টেবিলে ৫-fold CV) আর test accuracy ছাপে। তিন, একই fold-এ scikit-learn-এর StackingClassifier চালিয়ে দেখে, আমাদের হাতে লেখা stacking-এর সম্ভাবনা হুবহু মেলে কি না।
import numpy as np
from sklearn.base import clone
from sklearn.model_selection import (train_test_split, cross_val_predict,
cross_val_score, StratifiedKFold)
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import StackingClassifier
from bazar_returns import make_returns, base_models
X, y = make_returns()
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.5, stratify=y,
random_state=0)
cv = StratifiedKFold(5, shuffle=True, random_state=1)
base = base_models()
# সৎ পথ: প্রতিটি সারির সম্ভাবনা আসে এমন model থেকে, যে সারিটি দেখেনি
oof = np.column_stack([cross_val_predict(clone(m), Xtr, ytr, cv=cv,
method="predict_proba")[:, 1] for _, m in base])
fitted = [clone(m).fit(Xtr, ytr) for _, m in base]
# ফাঁস হওয়া পথ: একই সারিতে শেখা model-এর নিজের সম্ভাবনা
Zin = np.column_stack([m.predict_proba(Xtr)[:, 1] for m in fitted])
Zte = np.column_stack([m.predict_proba(Xte)[:, 1] for m in fitted])
print("level-1 table", oof.shape, " accuracy of each column on train rows:")
print(" OOF ", np.round(((oof > 0.5) == ytr[:, None]).mean(axis=0), 3))
print(" in-sample", np.round(((Zin > 0.5) == ytr[:, None]).mean(axis=0), 3))
for name, Z in [("honest OOF", oof), ("leaky in-sample", Zin)]:
meta = LogisticRegression().fit(Z, ytr)
val = cross_val_score(LogisticRegression(), Z, ytr, cv=cv).mean()
print(f"{name:15s} weights {np.round(meta.coef_[0], 2)}"
f" validation {val:.3f} test {meta.score(Zte, yte):.3f}")
if name == "honest OOF":
own = meta.predict_proba(Zte)[:, 1]
sk = StackingClassifier(base, final_estimator=LogisticRegression(), cv=cv)
sk.fit(Xtr, ytr)
print(f"StackingClassifier test {sk.score(Xte, yte):.3f}, same probabilities:",
np.allclose(sk.predict_proba(Xte)[:, 1], own))আসল আউটপুট (ব্রাউজারের Python):
level-1 table (1000, 3) accuracy of each column on train rows:
OOF [0.742 0.745 0.738]
in-sample [0.745 0.934 0.913]
honest OOF weights [1.78 1.84 1.34] validation 0.753 test 0.772
leaky in-sample weights [-2.55 7.76 4.77] validation 0.956 test 0.763
StackingClassifier test 0.772, same probabilities: True
প্রথম তিন লাইন পুরো গল্প: OOF সম্ভাবনায় তিন model-এর train-accuracy কাছাকাছি (০.৭৩৮–০.৭৪৫), কিন্তু in-sample-এ forest ০.৯৩৪ আর hgb ০.৯১৩, অথচ logit ০.৭৪৫-ই থাকে। গাছেরা train মুখস্থ করেছে। সৎ meta-model তিনটিকে প্রায় সমান ধনাত্মক weight দেয়; validation ০.৭৫৩, test ০.৭৭২ (এখানে test একটু ভালো এসেছে; ১,০০০ অর্ডারের ভাগে এমন ওঠানামা স্বাভাবিক)। ফাঁস হওয়া meta-model নিজের টেবিলে CV করে ০.৯৫৬ দেখায়, প্রায় ০.২ বেশি, কিন্তু test-এ ০.৭৬৩, সৎ stacking-এর চেয়ে কম। মনে রাখবেন, এখানে meta টেবিলের ওপর CV-ও কোনো সাহায্য করেনি, কারণ ফাঁসটি টেবিল তৈরির সময়েই ঘটেছে। শেষ লাইন: একই cv object আর একই random_state দিলে StackingClassifier-এর সম্ভাবনা আমাদের হাতে লেখা stacking-এর সঙ্গে হুবহু মেলে (True), অর্থাৎ library ঠিক এই কাজটিই করে। লক্ষ করুন, সৎ stacking-এর ০.৭৭২ আর ব্লক ১-এ একই ভাগে একক hgb-এর ০.৭৭১ প্রায় সমান।
StackingClassifier ভেতরে কী করে (scikit-learn 1.8.0-এর docs আর ওপরের মিল থেকে): fit-এ প্রতিটি base model-এর জন্য cross_val_predict চালিয়ে OOF টেবিল বানায় আর তাতে final_estimator শেখায়; তারপর base model-দের পুরো train-এ আবার শেখায় (estimators_), যারা prediction-এর সময় কাজ করে। Binary সমস্যায় predict_proba-র প্রথম কলাম বাদ দেয়, কারণ দুটি কলামের যোগফল ১; আমরাও তাই [:, 1] নিয়েছি। মূল argument:
| Argument | কী করে | সাবধানতা |
|---|---|---|
final_estimator | Meta-model; ডিফল্ট LogisticRegression (Regressor-এ RidgeCV) | সরল রাখুন; জটিল meta-model OOF টেবিলেই overfit করে |
cv | OOF-এর fold; ডিফল্ট ৫-fold (classifier-এ stratified) | প্রতিটি সারিকে ঠিক একবার লুকাতে হয়; TimeSeriesSplit দিলে ValueError |
stack_method | "auto": predict_proba, না থাকলে decision_function, তারপর predict | ভিন্ন স্কেলের score মেশালে meta-model-এ scaling ভাবুন |
passthrough | True হলে আসল feature-ও meta-model-এ যায় | Meta-model তখন বড়; scaling লাগে, overfit-এর ঝুঁকি বাড়ে |
Regression-এর জন্য StackingRegressor একইভাবে কাজ করে, শুধু OOF কলামে সম্ভাবনার বদলে সংখ্যার অনুমান (যেমন ডেলিভারির দিন), আর ডিফল্ট meta-model RidgeCV. এক সতর্কতা: সময়ভিত্তিক ডেটায় সাধারণ K-fold OOF নিজেই ভবিষ্যৎ দেখে ফেলে; cross_val_predict শুধু এমন ভাগ নেয় যেখানে প্রতিটি সারি ঠিক একবার test-এ যায়, তাই TimeSeriesSplit সেখানে সরাসরি চলে না। তখন সময়ের ক্রম মেনে blending (অংশ ৫) সাধারণত নিরাপদ পথ।
বহু-স্তরের stacking-এর খরচ। প্রতিযোগিতায় কখনো দুই-তিন স্তরের stacking দেখা যায়: প্রথম স্তরে বিশটি model, দ্বিতীয় স্তরে কয়েকটি meta-model, তৃতীয় স্তরে তাদের গড়। খরচ গুণে বাড়ে। M টি base model আর K fold হলে প্রথম স্তরেই M × (K + 1) বার fit; প্রতিটি নতুন স্তর আবার nested CV চায়, নইলে ওপরের স্তর নিচের স্তরের ফাঁস দেখে। Prediction-এ প্রতিটি অনুরোধে সব M টি model চলে। আর প্রতিটি স্তরের লাভ সাধারণত আগের চেয়ে কম; অংশ ৬-এর মাপ সেটাই দেখাবে।
৫ · Blending আর weight খোঁজা — holdout-এ সরল পথ
Blending stacking-এর সরল ভাই। K-fold-এর বদলে train-কে একবার দুই ভাগ করা হয়: বড় ভাগে base model শেখে, ছোট holdout ভাগে তাদের অনুমান দিয়ে meta-model শেখে। সুবিধা: সরল, দ্রুত (প্রতিটি base model একবার fit), সময়ের ক্রম মানা সহজ, আর ফাঁসের সুযোগ কম। অসুবিধা: base model পুরো train দেখে না, meta-model শেখে অল্প সারিতে, আর ফল নির্ভর করে একটি ভাগের ভাগ্যের ওপর। নামটি প্রতিযোগিতার চর্চায় এসেছে; Netflix Prize-এর দলগুলো এই অর্থে “blend” শব্দটি বহুল ব্যবহার করত (ভাবনার প্রশ্ন ১).
Meta-model-এর বদলে আরও সংযত বিকল্প: holdout-এ শুধু weight খোঁজা। weight w = (w₁, w₂, w₃) এমনভাবে বাছি যাতে holdout-এ log loss সবচেয়ে কম, শর্ত wⱼ ≥ ০ আর ∑wⱼ = ১. এই শর্তের সেটকে বলে probability simplex. শর্তের কারণে ফল সবসময় সম্ভাবনাগুলোর একটি গড়, তাই অর্থ সহজ আর overfit কম; Breiman-এর ১৯৯৬-এর পর্যবেক্ষণও এই দিকে। SciPy-র minimize-এ SLSQP পদ্ধতি bounds আর সমতা-শর্ত সরাসরি নেয় (constrained optimization-এর বিস্তারিত পর্ব ১৬-এ)।
import numpy as np
from scipy.optimize import minimize
from sklearn.model_selection import train_test_split
from sklearn.metrics import log_loss, accuracy_score
from sklearn.linear_model import LogisticRegression
from bazar_returns import make_returns, base_models
X, y = make_returns()
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.5, stratify=y,
random_state=0)
# blending: train-এর ৭০% দিয়ে base model, বাকি ৩০% holdout
Xfit, Xval, yfit, yval = train_test_split(Xtr, ytr, test_size=0.3,
stratify=ytr, random_state=0)
models = [m.fit(Xfit, yfit) for _, m in base_models()]
Pval = np.column_stack([m.predict_proba(Xval)[:, 1] for m in models])
Pte = np.column_stack([m.predict_proba(Xte)[:, 1] for m in models])
def loss(w):
return log_loss(yval, np.clip(Pval @ w, 1e-6, 1 - 1e-6))
res = minimize(loss, np.full(3, 1 / 3), method="SLSQP", bounds=[(0, 1)] * 3,
constraints={"type": "eq", "fun": lambda w: w.sum() - 1})
w = res.x
print("weights (logit, forest, hgb):", np.round(w, 3), " sum", round(w.sum(), 3))
print(f"holdout log loss: equal {loss(np.full(3, 1 / 3)):.4f},"
f" optimised {res.fun:.4f}")
blend = LogisticRegression().fit(Pval, yval)
rows = [("logit", Pte[:, 0]), ("forest", Pte[:, 1]), ("hgb", Pte[:, 2]),
("equal average", Pte.mean(axis=1)), ("optimised weights", Pte @ w),
("blend (holdout)", blend.predict_proba(Pte)[:, 1])]
for name, p in rows:
print(f"{name:18s} test log loss {log_loss(yte, p):.4f}"
f" acc {accuracy_score(yte, p > 0.5):.3f}")আসল আউটপুট (ব্রাউজারের Python):
weights (logit, forest, hgb): [0.442 0.357 0.201] sum 1.0
holdout log loss: equal 0.5382, optimised 0.5369
logit test log loss 0.5306 acc 0.752
forest test log loss 0.5325 acc 0.737
hgb test log loss 0.5571 acc 0.738
equal average test log loss 0.5151 acc 0.760
optimised weights test log loss 0.5140 acc 0.757
blend (holdout) test log loss 0.5204 acc 0.757
Weight: logit ০.৪৪২, forest ০.৩৫৭, hgb ০.২০১, যোগফল ১; hgb সবচেয়ে কম পায়, কারণ তার অতিরিক্ত নিশ্চিত সম্ভাবনা log loss-এ শাস্তি পায় (একক test log loss ০.৫৫৭১)। Holdout-এ সমান weight থেকে optimise করে লাভ সামান্য: ০.৫৩৮২ থেকে ০.৫৩৬৯. Test-এ: তিনটি ensemble-ই প্রতিটি একক model-এর চেয়ে ভালো (সেরা একক logit ০.৫৩০৬); সমান গড় ০.৫১৫১, optimised weight ০.৫১৪০, আর logistic meta-model-এর blend ০.৫২০৪. Meta-model মাত্র ৩০০টি holdout সারিতে intercept-সহ শর্তহীন weight শেখে, তাই তার variance বেশি; simplex-এর শর্ত এখানে সাহায্য করেছে। Blending-এর দাম: base model এখানে ৭০০ অর্ডারে শিখেছে; forest-এর log loss ০.৫৩২৫, ব্লক ১-এ ১,০০০ অর্ডারে শেখা forest-এর ০.৫১২৬-এর চেয়ে খারাপ। সমান গড় আর optimised weight-এর পার্থক্য (০.০০১১) এতই ছোট যে বাস্তবে সমান গড়ই প্রায়ই যথেষ্ট।
Ensemble selection. Rich Caruana ও সহকর্মীরা (ICML ২০০৪, “Ensemble selection from libraries of models”) একটি লোভী (greedy) পদ্ধতি প্রস্তাব করেন: শত শত model-এর একটি লাইব্রেরি থেকে শুরু করে প্রতি ধাপে সেই model যোগ করুন, যেটি যোগ করলে validation-এর metric সবচেয়ে ভালো হয়; একই model বারবার যোগ করা যায় (with replacement), তাই শেষে প্রতিটি model কতবার এসেছে তা-ই তার weight. এটি স্বয়ংক্রিয়ভাবে simplex-এ থাকে, যেকোনো metric-এ চলে, আর বহু model থেকে অল্প কয়েকটি বেছে নেয়। প্রবন্ধটির মতে hillclimb set-এ overfit ঠেকাতে কয়েকটি সেরা model দিয়ে শুরু করা আর bagging-এর মতো কৌশল সাহায্য করে; কিছু AutoML লাইব্রেরি এর কোনো রূপ ব্যবহার করে বলে জানা যায়। অনুশীলন ৪-এ আপনি এটি নিজে লিখবেন।
৬ · সৎ তুলনা — tuned একক GBM বনাম ensemble
এ পর্যন্ত ensemble-কে তুলনা করেছি ডিফল্ট সেটিং-এর একক model-এর সঙ্গে। এটা অসম লড়াই: ensemble-এ তিনটি model-এর পরিশ্রম, প্রতিপক্ষের কোনো tuning নেই। সৎ প্রশ্ন হলো: একই সময় একটি gradient boosting model-কে ভালোভাবে tune করলে কী হয়? পরিকল্পনা: (১) HistGradientBoosting-এর hyperparameter একটি আলাদা নমুনায় (একই দোকান, অন্য seed) tune করা, যাতে মূল্যায়নের fold-গুলো tuning-এ ব্যবহার না হয়; (২) তারপর একই repeated CV fold-এ tuned HGB, soft vote আর stacking চালানো; (৩) accuracy আর log loss-এর গড় ± standard deviation, সঙ্গে ১,০০০টি অর্ডারের prediction-এর সময়।
Tuning ডেস্কটপে, কারণ ৬০টি সেটিং × ৫ fold ব্রাউজারে মিনিটখানেক আটকে রাখত। সত্যিকারের run (Python 3.14.4, scikit-learn 1.8.0; RandomizedSearchCV, learning rate ০.০১–০.৩, ৫০–৪০০ ধাপ, ৪–৩২ leaf, ন্যূনতম leaf-আকার ১০–৮০, L2 penalty ০.০০১–১০, scoring log loss):
~/bazarghar-ml$ python tune_hgb.py
Python 3.14.4, scikit-learn 1.8.0
60 settings x 5 folds in 53.4 s
{'l2_regularization': 0.5623, 'learning_rate': 0.0208, 'max_iter': 331, 'max_leaf_nodes': 4, 'min_samples_leaf': 52}
best CV log loss 0.5114
default HGB CV log loss 0.5855
Tune করা model খুব সংযত: ছোট learning rate, মাত্র ৪টি leaf-এর ৩৩১টি গাছ, প্রতিটি leaf-এ অন্তত ৫২টি অর্ডার। ডিফল্টের তুলনায় CV log loss ০.৫৮৫৫ থেকে ০.৫১১৪, বড় উন্নতি। অর্থাৎ ব্লক ১-এর ডিফল্ট hgb-র দুর্বল log loss ছিল tuning-এর অভাব, পদ্ধতির নয়। এবার পাতার ব্লক এই সেটিং (গোল করে) নিয়ে একই fold-এ তিন প্রতিযোগী চালায়। এটি কয়েক সেকেন্ড নেয়, কারণ stacking প্রতিটি fold-এর ভেতরে আবার ৩-fold CV চালায়।
import time
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import RepeatedStratifiedKFold, cross_validate
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import (VotingClassifier, StackingClassifier,
HistGradientBoostingClassifier)
from bazar_returns import make_returns, base_models
X, y = make_returns()
tuned = HistGradientBoostingClassifier(learning_rate=0.02, max_iter=330,
max_leaf_nodes=4, min_samples_leaf=52, l2_regularization=0.56,
random_state=0) # আলাদা নমুনায় আগে tune করা
cands = {"tuned HGB": tuned,
"soft vote": VotingClassifier(base_models(), voting="soft"),
"stacking": StackingClassifier(base_models(), LogisticRegression(),
cv=3)}
cv = RepeatedStratifiedKFold(n_splits=3, n_repeats=2, random_state=7)
losses = {}
for name, model in cands.items():
r = cross_validate(model, X, y, cv=cv, scoring=["accuracy", "neg_log_loss"])
acc, ll = r["test_accuracy"], -r["test_neg_log_loss"]
losses[name] = ll
model.fit(X, y)
t0 = time.perf_counter(); model.predict_proba(X[:1000])
ms = (time.perf_counter() - t0) * 1000
print(f"{name:9s} acc {acc.mean():.3f} ± {acc.std():.3f} log loss"
f" {ll.mean():.4f} ± {ll.std():.4f} 1000 rows {ms:5.1f} ms")
d = losses["stacking"] - losses["tuned HGB"]
print("stacking minus tuned HGB, per fold:", np.round(d, 4))
fig, ax = plt.subplots(figsize=(6, 3.4))
for name, ll in losses.items():
ax.plot(range(1, 7), ll, "o-", label=name)
ax.set(xlabel="CV fold (3 folds x 2 repeats)", ylabel="test log loss",
title="Same folds for every model")
ax.legend(fontsize=8)আসল আউটপুট (ব্রাউজারের Python, একটি run; সময় যন্ত্র আর ব্রাউজারভেদে বদলাবে), নিচে ছবি:
tuned HGB acc 0.759 ± 0.006 log loss 0.4975 ± 0.0089 1000 rows 10.7 ms
soft vote acc 0.754 ± 0.012 log loss 0.5083 ± 0.0121 1000 rows 19.5 ms
stacking acc 0.751 ± 0.014 log loss 0.5130 ± 0.0103 1000 rows 20.0 ms
stacking minus tuned HGB, per fold: [0.0086 0.0181 0.0168 0.0124 0.0243 0.0131]
Tuned HGB দুই metric-এই সেরা: accuracy ০.৭৫৯, log loss ০.৪৯৭৫; soft vote ০.৫০৮৩, stacking ০.৫১৩০. শেষ লাইনটি জোড়া তুলনা: প্রতিটি fold-এ stacking-এর log loss বিয়োগ tuned HGB-এর, আর ছয়টিই ধনাত্মক (০.০০৮৬ থেকে ০.০২৪৩)। ছবিতে তিনটি রেখা fold থেকে fold একসঙ্গে ওঠানামা করে: কোন fold কঠিন, সেটাই বড় প্রভাব। তাই আলাদা fold-এ মাপা দুটি গড় তুলনা করা বিভ্রান্তিকর; একই fold আর জোড়া পার্থক্যই সৎ পথ। Latency: ব্রাউজারে ১,০০০ সারির prediction tuned HGB-এ ১০.৭ ms, দুই ensemble-এ প্রায় ২০ ms, প্রায় দ্বিগুণ; একবারের মাপ, তাই গোলমাল আছে। তবে এই তুলনা ensemble-এর প্রতি একটু অন্যায্য: তার সদস্যরা untuned. তাই নিচের ডেস্কটপ run-এ tuned HGB-কেই সদস্য বানানো হয়েছে।
ছয়টি fold কম, তাই ডেস্কটপে বড় run: ৪,০০০টি অর্ডার, ৫ fold × ৫ বার = ২৫টি ভাগ, সব model-এর জন্য একই ভাগ। দুটি নতুন প্রতিযোগী যোগ হলো: tuned HGB-কেই base model বানিয়ে stacking, আর তার সঙ্গে passthrough=True (meta-model-এ scaling সহ)। শেষে পর্ব ২২-এর corrected resampled t-test, কারণ CV fold-গুলো স্বাধীন নয়। সত্যিকারের run (Python 3.14.4, scikit-learn 1.8.0; একটি সাধারণ ল্যাপটপ, একটি run, সাধারণ ফল নয়):
~/bazarghar-ml$ python compare_big.py
Python 3.14.4, scikit-learn 1.8.0
n = 4000, 5 folds x 5 repeats = 25 splits, same splits for all
model acc log loss fit s ms/1000
tuned HGB 0.7561±0.0084 0.5090±0.0111 0.10 3.9
soft vote 0.7566±0.0107 0.5137±0.0116 0.23 7.4
stacking 0.7569±0.0110 0.5142±0.0121 1.06 8.6
stack+tunedHGB 0.7583±0.0092 0.5072±0.0130 1.15 11.2
stack passthru 0.7595±0.0108 0.5069±0.0144 1.14 11.1
log loss soft vote - tuned HGB = +0.0047 corrected t-test p = 0.080
log loss stacking - tuned HGB = +0.0051 corrected t-test p = 0.060
log loss stack+tunedHGB - tuned HGB = -0.0019 corrected t-test p = 0.223
log loss stack passthru - tuned HGB = -0.0021 corrected t-test p = 0.311
পড়ার নিয়ম: log loss কম মানে ভালো; শেষ চার সারি হলো প্রতিটি ensemble-এর log loss বিয়োগ tuned HGB-এর, ধনাত্মক মানে ensemble খারাপ। Untuned সদস্যের soft vote আর stacking দুটিই tuned HGB-এর চেয়ে খারাপ (+০.০০৪৭ আর +০.০০৫১), যদিও p ০.০৬–০.০৮, তাই এ-ও নিশ্চিত নয়। Tuned HGB ভেতরে রাখলে stacking এগিয়ে যায়, কিন্তু মাত্র ০.০০১৯–০.০০২১, যা fold-এর ছড়ানোর (প্রায় ০.০১৩) তুলনায় ছোট, আর p ০.২২ ও ০.৩১. Accuracy-তে পার্থক্য তৃতীয় দশমিকে। দামের দিক পরিষ্কার: প্রতি ভাগে fit ০.১০ থেকে প্রায় ১.১৫ সেকেন্ড (১১ গুণের বেশি), আর ১,০০০ সারির prediction ৩.৯ থেকে প্রায় ১১ ms (প্রায় ৩ গুণ)।
এই ডেটায়, এই আকারে, একটি ভালোভাবে tune করা gradient boosting model-ই প্রায় সব লাভ নিয়ে নেয়। Ensemble-এর বাড়তি লাভ ছোট আর অনিশ্চিত, কিন্তু খরচ নিশ্চিত। এটি একটি কাল্পনিক ডেটার একটি run; অন্য ডেটায়, বিশেষ করে যেখানে model-রা সত্যিই ভিন্ন তথ্য দেখে, ফল ভিন্ন হতে পারে। শিক্ষাটি পদ্ধতিগত: ensemble-কে সবসময় একটি tuned শক্ত baseline-এর সঙ্গে, একই fold-এ, কয়েকবার মেপে তুলনা করুন।
৭ · Production-এ ensemble-এর দাম, আর কখন সত্যিই সাহায্য করে
Notebook-এ ensemble মানে কয়েকটি লাইন। Production-এ, অর্থাৎ বাজারঘরের checkout পাতায় প্রতিটি অর্ডারে ফেরতের ঝুঁকি দেখানোর ব্যবস্থায়, প্রতিটি সদস্য একটি আলাদা দায়:
- Latency: প্রতিটি অনুরোধে সব সদস্য চলে। সমান্তরালে চালালে সবচেয়ে ধীর সদস্যের সময়, পরপর চালালে সবার যোগফল। ওপরের মাপে প্রায় ৩ গুণ। Latency বাজেট কীভাবে ভাবতে হয়, তা System Design কোর্সের latency আর throughput পর্বে।
- রক্ষণাবেক্ষণ: প্রতিটি সদস্যের নিজস্ব preprocessing, library version, retraining আর monitoring. একটি সদস্য আবার শেখালে meta-model-এর weight পুরোনো হয়ে যায়; পুরো stack আবার শেখাতে হয়।
- ব্যাখ্যা আর debugging: একটি ভুল অনুমানের দায় কার, বোঝা কঠিন। গ্রাহক বা নিয়ন্ত্রক “কেন” জানতে চাইলে উত্তর জটিল।
- পুনরুৎপাদন: কোন fold, কোন seed, কোন version-এ OOF টেবিল বানানো, সব লিখে রাখতে হয় (পর্ব ০৪).
তবু ensemble সত্যিই কাজে আসে কিছু পরিস্থিতিতে। নিচের টেবিলটি একটি ব্যবহারিক মানচিত্র, কোনো নিশ্চিত নিয়ম নয়:
| পরিস্থিতি | Ensemble-এর সম্ভাবনা | কারণ |
|---|---|---|
| একই tabular feature-এ কয়েকটি গাছভিত্তিক model | কম | ভুলের correlation বেশি; tuned GBM প্রায় সব নিয়ে নেয় |
| ভিন্ন তথ্য দেখে এমন model: লেখা রিভিউ + অর্ডারের টেবিল | বেশি | ভুল আলাদা জায়গায়; meta-model বোঝে কোনটি কখন ভালো |
| সমান মানের, ভিন্ন ধরনের কয়েকটি model | মাঝারি | Soft vote বা simplex weight সস্তা আর নিরাপদ লাভ দেয় |
| প্রতিযোগিতার leaderboard, যেখানে ০.০০১-ও দামি | বেশি | Latency আর রক্ষণাবেক্ষণের দাম নেই |
| কঠোর latency, ছোট ডিভাইস, ব্যাখ্যার দায় | কম | খরচ লাভের চেয়ে বেশি; distillation (বড় ensemble থেকে ছোট model শেখানো) একটি বিকল্প |
Negative correlation learning (একটি নোট)। অংশ ১-এর stepper বলেছে, ভুলের correlation ঋণাত্মক হলে গড় সবচেয়ে বেশি লাভ দেয়। Yong Liu আর Xin Yao ১৯৯৯ সালে neural network-এর ensemble-এর জন্য এমন একটি পদ্ধতি প্রস্তাব করেন, যেখানে সব সদস্য একসঙ্গে শেখে আর প্রতিটির loss-এ একটি penalty যোগ হয়, যা অন্যদের গড় থেকে একই দিকে সরে যাওয়াকে শাস্তি দেয়। ফলে সদস্যরা ইচ্ছা করে ভিন্ন ভুল করতে শেখে। ধারণাটি সুন্দর, কিন্তু scikit-learn-এ সরাসরি নেই, আর সাধারণ tabular কাজে খুব কম ব্যবহৃত হয়।
আরও গভীরে: Anders Krogh আর Jesper Vedelsby (NIPS ১৯৯৪ সম্মেলন, প্রকাশ ১৯৯৫) squared error-এর জন্য “ambiguity decomposition” দেখান: weight দেওয়া গড়ের ভুল = সদস্যদের গড় ভুল − সদস্যরা গড় থেকে কতটা ছড়ানো (ambiguity). অর্থাৎ সদস্যদের মানের সমান অবস্থায় যত বেশি মতভেদ, তত কম ensemble-এর ভুল। Wolpert-এর ১৯৯২-এর মূল প্রবন্ধে stacking-কে cross-validation-এর সাধারণীকরণ হিসেবে দেখানো হয়েছে; Mark van der Laan ও সহকর্মীরা (২০০৭) “Super Learner” নামে দেখান যে CV-ভিত্তিক stacking বড় নমুনায় লাইব্রেরির সেরা সদস্যের মতো অন্তত ভালো করে, কিছু শর্তসাপেক্ষে।
পরীক্ষা ও ইন্টারভিউতে:
- দুই model-এর গড়ের ভুলের variance? (σ₁² + σ₂² + 2ρσ₁σ₂)/4; σ সমান হলে σ²(1 + ρ)/2.
- Hard আর soft vote-এর পার্থক্য? Hard: label-এর সংখ্যাগরিষ্ঠ; soft: সম্ভাবনার গড়, তাই নিশ্চয়তার মাত্রা গোনে আর calibration-এর ওপর নির্ভর করে।
- Stacking-এ meta-model কেন OOF-এ শেখে? In-sample সম্ভাবনায় মুখস্থ করা model-কে নির্ভুল দেখায়; meta-model ভুল weight শেখে, validation ফুলে ওঠে, test খারাপ হয়।
- Blending বনাম stacking? Blending একটি holdout-এ meta শেখে (সরল, কম ডেটা); stacking K-fold OOF-এ (সব সারি ব্যবহার, K + 1 গুণ fit).
- Ensemble কখন বাদ দেবেন? লাভ fold-এর ছড়ানোর চেয়ে ছোট, আর latency, রক্ষণাবেক্ষণ বা ব্যাখ্যার দাম বড় হলে।
৮ · AI-এর যুগে — ensemble কোড যাচাই
AI assistant-কে শুধু “accuracy বাড়াতে একটি stacking কোড চাই” বললে সে প্রায়ই চটপটে কোড দেয়, যা দেখতে পেশাদার কিন্তু ভেতরে ফাঁস লুকিয়ে থাকে। পরিচিত ভুলগুলো:
| AI প্রায়ই যা করে | কেন ভুল | কীভাবে ধরবেন |
|---|---|---|
Base model পুরো train-এ শিখিয়ে predict_proba(X_train) দিয়ে meta-model | In-sample ফাঁস; validation ফুলে ওঠে | Meta টেবিল cross_val_predict থেকে কি না দেখুন; train আর test-এর মধ্যে বড় ফাঁক |
| Weight বা meta-model test set-এ বাছাই | Test আর নিরপেক্ষ নয় | Test শুধু শেষে একবার; weight holdout বা OOF-এ |
| Ensemble-কে untuned একক model-এর সঙ্গে তুলনা | অসম লড়াই; লাভ বাড়িয়ে দেখায় | একই tuning বাজেটে শক্ত baseline, একই fold, কয়েকটি seed |
| একটি split-এ ০.০০৩ উন্নতি দেখিয়ে “stacking ভালো” | Fold-এর ছড়ানোর চেয়ে ছোট | Repeated CV, mean ± sd, corrected t-test |
পুরোনো বা বানানো argument, যেমন StackingClassifier(meta_classifier=…) | অন্য library-র নাম; scikit-learn-এ final_estimator | নিজের version-এর docs-এ signature মেলান |
| Latency আর রক্ষণাবেক্ষণের কথা না বলা | Production খরচ লুকানো | Prediction-এর সময় মাপুন, সদস্য-সংখ্যা লিখুন |
AI-কে কোডের সঙ্গে প্রমাণও চাইতে বলুন:
AI-এর দেওয়া ensemble কোড হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১Netflix Prize জিতেছিল শত শত model-এর blend, অথচ শোনা যায় Netflix সেই পুরো blend চালু করেনি। এ থেকে ensemble নিয়ে কী শেখার আছে?
Netflix ২০০৬ সালে একটি প্রকাশ্য প্রতিযোগিতা ঘোষণা করে: তাদের নিজস্ব সুপারিশ-ব্যবস্থার (Cinematch) চেয়ে সিনেমার rating অনুমানের RMSE দশ শতাংশ কমাতে পারলে দশ লাখ ডলার পুরস্কার। প্রায় তিন বছর ধরে হাজার হাজার দল অংশ নেয়। ২০০৯ সালে পুরস্কার পায় “BellKor's Pragmatic Chaos”, কয়েকটি দলের মিলিত দল। তাদের সমাধান কোনো একক চমৎকার algorithm ছিল না; ছিল matrix factorization, neighbourhood model, restricted Boltzmann machine আর আরও বহু রূপের শত শত model, যাদের অনুমান একটি blending স্তরে মেলানো হয়েছিল। দ্বিতীয় দলটির নামই ছিল “The Ensemble”, আর প্রতিবেদন অনুযায়ী তাদের test score প্রায় সমান ছিল; ফল নির্ধারিত হয় জমা দেওয়ার সময় দিয়ে।
পরে, ২০১২ সালে Netflix-এর প্রকৌশল ব্লগে লেখা হয়, তারা আগের একটি “Progress Prize”-এর দুটি মূল algorithm (একটি matrix factorization আর RBM-এর একটি রূপ, রৈখিক blend-এ) গ্রহণ করেছিল, কিন্তু চূড়ান্ত বিজয়ী ensemble-এর বাড়তি accuracy production-এ আনার প্রকৌশল-খরচের সমান মনে হয়নি। এর মধ্যে তাদের ব্যবসাও DVD থেকে streaming-এ সরে যাচ্ছিল, ফলে সমস্যাটাই বদলে যাচ্ছিল। ব্লগের ভাষা আর প্রেক্ষাপট নিয়ে আলোচনা আছে, তাই একে “Netflix ensemble প্রত্যাখ্যান করেছিল” এমন সরল গল্প না বানানোই ভালো।
তবু শিক্ষাটি স্পষ্ট আর এই পর্বের মাপের সঙ্গে মেলে। প্রতিযোগিতার leaderboard একটি স্থির test set-এ একটি metric মাপে; সেখানে চতুর্থ দশমিকের উন্নতিও মূল্যবান, আর latency, রক্ষণাবেক্ষণ, ব্যাখ্যা বা ডেটা বদলের কোনো দাম নেই। Production-এ প্রতিটি সদস্য model আলাদা pipeline, retraining, monitoring আর debugging-এর দায়। অংশ ৬-এর run-এ tuned HGB-এর চেয়ে stacking-এর লাভ ছিল log loss-এ প্রায় ০.০০২, আর খরচ ছিল ১১ গুণ fit time ও ৩ গুণ latency; Netflix-এর পরিস্থিতি একই ধরনের, শুধু অনেক বড় মাপে। আবার প্রতিযোগিতাটি অনেক সাধারণ কৌশল জনপ্রিয় করেছে, যেমন matrix factorization-এর রূপগুলো, যা পরে বহু জায়গায় ব্যবহার হয়েছে; অর্থাৎ ensemble থেকে শেখা কৌশল প্রায়ই একক model-কেই উন্নত করে।
মূল উপলব্ধি: leaderboard-এর জয় আর production-এর লাভ এক জিনিস নয়; ensemble-এর বাড়তি accuracy-কে সবসময় তার প্রকৌশল-খরচের পাশে রেখে বিচার করতে হয়।
প্র ০২Wolpert-এর stacking-এ meta-model যেকোনো কিছু হতে পারত, তবু Breiman কেন weight অ-ঋণাত্মক রাখার কথা বলেছিলেন? আর আজ meta-model সরল রাখার পরামর্শ কেন?
David Wolpert-এর ১৯৯২ সালের “Stacked generalization” প্রবন্ধে ধারণাটি খুব সাধারণ: প্রথম স্তরের generalizer-দের এমন অনুমান, যা তারা নিজেদের দেখা ডেটার বাইরে করেছে, সেগুলো দ্বিতীয় স্তরের একটি generalizer-এর ইনপুট। দ্বিতীয় স্তর কী হবে, Wolpert তা বেঁধে দেননি; তাঁর চোখে এটি cross-validation-এর একটি সাধারণ রূপ, যেখানে “সেরা একটি বাছা”-র বদলে “সবার অনুমান থেকে শেখা” হয়।
Leo Breiman ১৯৯৬ সালে “Stacked regressions”-এ regression-এ এটি বাস্তবে পরীক্ষা করেন। তিনি একই ধরনের কয়েকটি model নেন (যেমন ভিন্ন আকারের regression tree বা ভিন্ন সংখ্যক variable-এর linear regression), আর দেখেন যে মেলানোর weight-এ কোনো শর্ত না দিলে least squares প্রায়ই বড় ধনাত্মক আর বড় ঋণাত্মক weight দেয়: একটি model-কে যোগ, প্রায় একই আরেকটিকে বিয়োগ। কারণ base model-দের অনুমান খুব correlated, অর্থাৎ meta-model-এর ইনপুটে প্রবল multicollinearity; এমন অবস্থায় least squares-এর weight অস্থির, আর নতুন ডেটায় খারাপ। weight অ-ঋণাত্মক রাখলে এই দোলাচল বন্ধ হয়, আর তাঁর পরীক্ষায় ফল ভালো হয়েছিল। ব্লক ২-এর ফাঁস হওয়া meta-model-এর logit-এর −২.৫৫ weight ঠিক এই ধরনের অদ্ভুত weight-এর উদাহরণ, যদিও সেখানে মূল কারণ ছিল leakage.
আজকের পরামর্শও একই যুক্তির ধারাবাহিকতা। Meta-model-এর train টেবিলে কলাম কম, কিন্তু কলামগুলো প্রায় একই তথ্য বহন করে, আর সারির সংখ্যা train-এর সমান। জটিল meta-model, যেমন গভীর boosting, এই টেবিলের গোলমালও শিখে ফেলে; তাছাড়া OOF সম্ভাবনা পুরো train-এ আবার শেখা base model-এর সম্ভাবনার থেকে একটু ভিন্ন বণ্টনের (কারণ OOF model কম ডেটায় শেখে), তাই meta-model-কে খুব সূক্ষ্মভাবে খাপ খাওয়ানো বিপজ্জনক। এজন্যই scikit-learn-এর ডিফল্ট final estimator সরল logistic regression বা RidgeCV, আর অনেকে simplex-এ weight (অংশ ৫) পছন্দ করেন।
মূল উপলব্ধি: meta-model-এর ইনপুট প্রায় একই তথ্যের কয়েকটি রূপ; সেখানে অ-ঋণাত্মক weight বা সরল regularized model স্থিরতা দেয়, জটিলতা দেয় overfit.
প্র ০৩Condorcet-এর jury theorem আর Galton-এর মেলার ষাঁড়ের গল্প বলে ভিড়ের মত প্রায়ই ঠিক। তাহলে একই ডেটায় শেখা দশটি model-এর ভোট কেন প্রায়ই হতাশ করে?
Marquis de Condorcet ১৭৮৫ সালে একটি সরল ফল দেন: প্রত্যেক বিচারক যদি স্বাধীনভাবে আর অর্ধেকের বেশি সম্ভাবনায় সঠিক রায় দেন, তাহলে বিচারকের সংখ্যা বাড়ার সঙ্গে সংখ্যাগরিষ্ঠের রায় সঠিক হওয়ার সম্ভাবনা এক-এর দিকে যায়। ১৯০৭ সালে Francis Galton একটি গ্রামীণ মেলায় ষাঁড়ের ওজন আন্দাজের প্রতিযোগিতার কার্ডগুলো বিশ্লেষণ করে Nature-এ লেখেন যে আন্দাজগুলোর মাঝের মান (median) আসল ওজনের খুব কাছে ছিল। এই দুটি গল্প “wisdom of crowds” ধারণার পরিচিত ভিত্তি।
কিন্তু দুটিরই গোপন শর্ত হলো স্বাধীনতা। মেলার মানুষেরা ভিন্ন অভিজ্ঞতা নিয়ে এসেছিলেন: কেউ কসাই, কেউ কৃষক, কেউ শুধু দর্শক; তাদের ভুল নানা দিকে ছড়ানো, তাই গড়ে কাটাকাটি হয়। Condorcet-এর সূত্রেও বিচারকদের ভুল একে অপরের ওপর নির্ভর করে না। একই ডেটায়, একই feature-এ শেখা দশটি model এর ঠিক উল্টো: তারা একই অস্পষ্ট অর্ডারে বিভ্রান্ত হয়, একই ভুল label মুখস্থ করে, একই অনুপস্থিত তথ্যের অভাবে ভোগে। অংশ ১-এর সূত্রে M টি model-এর গড়ের variance ρσ² + (1 − ρ)σ²/M; M যত বড়ই হোক, ρσ²-এর নিচে নামে না। ব্লক ১-এ 0/1 ভুলের correlation ছিল ০.৫৪ থেকে ০.৭৬, তাই তিনটি model-এর ভোট থেকে বড় লাফ আশা করার কারণ ছিল না।
আরও একটি সীমা আছে, যা কোনো ভিড় এড়াতে পারে না: অপরিহার্য গোলমাল, ইংরেজিতে irreducible noise. বাজারঘরের ডেটায় একই রকম দুটি অর্ডারের একটি ফেরত আসে, অন্যটি আসে না, কারণ ক্রেতার মন বদলায়। কোনো model, বা model-এর কোনো ভিড়, এই এলোমেলোতা অনুমান করতে পারে না; accuracy-র একটি ছাদ থাকে। তাই ensemble-এর লাভের জায়গা হলো model-এর variance বা ভিন্ন ধরনের bias কমানো; Bayes error-এর নিচে নামা নয়।
ব্যবহারিক শিক্ষা: ensemble বানানোর আগে ভিন্নতার উৎস খুঁজুন। ভিন্ন model পরিবার, ভিন্ন feature (যেমন রিভিউয়ের লেখা বনাম অর্ডারের সংখ্যা), ভিন্ন ডেটা-উৎস বা ভিন্ন সময়ের ডেটা; আর ভুলের correlation মেপে দেখুন সত্যিই ভিন্নতা এল কি না।
মূল উপলব্ধি: ভিড়ের প্রজ্ঞা আসে স্বাধীন ভুল থেকে; একই ডেটার model-রা সেই স্বাধীনতা হারায়, তাই ভোটের লাভ মাপতে হয় ভুলের correlation দিয়ে, সদস্যের সংখ্যা দিয়ে নয়।
প্র ০৪Machine learning ব্যবস্থার “hidden technical debt” নিয়ে লেখায় ensemble আর একটির ওপর আরেকটি model বসানোকে কেন ঝুঁকি বলা হয়?
D. Sculley ও সহকর্মীরা ২০১৫ সালের NIPS সম্মেলনে “Hidden Technical Debt in Machine Learning Systems” প্রবন্ধে দেখান, ML ব্যবস্থায় model-এর কোডটুকু ছোট অংশ; চারপাশের ডেটা সংগ্রহ, feature, configuration, monitoring আর serving মিলে আসল জটিলতা। তাঁরা একটি নীতির নাম দেন CACE: “Changing Anything Changes Everything”. একটি input feature-এর বণ্টন সামান্য বদলালে, বা একটি hyperparameter বদলালে, পুরো ব্যবস্থার আচরণ এমনভাবে বদলায় যা আগে থেকে বলা কঠিন।
Stacking এই জট আরও ঘন করে। Meta-model শেখে base model-দের নির্দিষ্ট আচরণের ওপর: forest-এর সম্ভাবনা কতটা চাপা, boosting-এর কতটা প্রান্তে। এখন ধরুন একজন প্রকৌশলী শুধু forest-টি উন্নত করলেন, আরও গাছ দিয়ে, আরও ভালো feature দিয়ে। আলাদাভাবে forest ভালো হলো, কিন্তু তার সম্ভাবনার বণ্টন বদলে গেল; meta-model-এর পুরোনো weight আর খাপ খায় না, আর পুরো stack-এর ফল খারাপও হতে পারে। প্রবন্ধটি এমন নির্ভরতাকে “correction cascade” আর “undeclared consumers” ধরনের সমস্যার সঙ্গে আলোচনা করে: একটি model-এর output অন্য model-এর input হলে, প্রথমটি বদলানোর দায় অদৃশ্যভাবে দ্বিতীয়টিতে চলে যায়।
রক্ষণাবেক্ষণের আরও খরচ আছে। প্রতিটি সদস্যের নিজস্ব preprocessing আর library version; একটি library-র নতুন version-এ সংখ্যা সামান্য বদলালে meta-model-এর ইনপুটও বদলায়। Retraining মানে পুরো OOF প্রক্রিয়া আবার, K + 1 গুণ fit. Monitoring-এ প্রতিটি সদস্যের drift আলাদা দেখতে হয়। আর কোনো অর্ডারে ভুল অনুমানের পর “কেন” প্রশ্নের উত্তর খুঁজতে চার-পাঁচটি model-এর ভেতর দেখতে হয়।
এর মানে ensemble নিষিদ্ধ নয়। মানে হলো, সিদ্ধান্তটি প্রকৌশলের: প্রতিটি সদস্যের version আর OOF-এর fold লিখে রাখা (পর্ব ০৪), পুরো stack একসঙ্গে retrain আর পরীক্ষা করা, আর লাভ যদি ছোট হয়, সরলতাকে বেছে নেওয়া।
মূল উপলব্ধি: ensemble model-দের মধ্যে অদৃশ্য নির্ভরতা তৈরি করে; একটি সদস্য বদলালে পুরোটা বদলায়, তাই এর খরচ শুধু CPU-তে নয়, প্রতিটি ভবিষ্যৎ পরিবর্তনে।
প্র ০৫Gradient boosting নিজেই একটি ensemble. তাহলে tuned GBM-এর ওপর আরেক স্তর ensemble কেন প্রায়ই সামান্য লাভ দেয়, আর কোথায় তা সত্যিই বড় লাভ দিতে পারে?
Gradient boosting শত শত গাছের যোগফল; প্রতিটি গাছ আগের ভুল শোধরায়, আর learning rate, গাছের আকার, subsampling আর regularization দিয়ে variance নিয়ন্ত্রণ হয় (পর্ব ৩৫ আর ৩৬-এর বিষয়)। ভালোভাবে tune করা হলে এর bias আর variance দুটিই tabular ডেটায় ইতিমধ্যে কম। Averaging-এর লাভ মূলত variance কমানো (অংশ ১); যে model-এর variance আগেই কম, তার পাশে আরেকটি model গড়ে বসালে কমানোর মতো বাকি থাকে সামান্য। অংশ ৬-এর run-এ ঠিক এটাই দেখা গেল: tuning-এ log loss ০.৫৮৫৫ থেকে ০.৫১১৪, বিশাল লাফ; তার ওপরে stacking-এ মাত্র প্রায় ০.০০২।
দ্বিতীয় কারণ correlation. Random forest আর gradient boosting দুটিই একই axis-aligned গাছ দিয়ে, একই feature থেকে শেখে; তাদের ভুল প্রায়ই একই কঠিন অর্ডারে। Logistic regression ভিন্ন ধরনের, কিন্তু সে দুর্বলও; অংশ ১-এর stepper দেখিয়েছে দুর্বল সঙ্গী তখনই সাহায্য করে, যখন ভুলের correlation খুব কম। তৃতীয় কারণ অপরিহার্য গোলমাল: accuracy-র ছাদ যদি কাছাকাছি হয়, কোনো ensemble সেটি ভাঙতে পারে না।
তাহলে বড় লাভ কোথায়? যেখানে সদস্যরা সত্যিই ভিন্ন তথ্য দেখে। বাজারঘরের উদাহরণে একটি model অর্ডারের টেবিল দেখে, আরেকটি Bangla রিভিউয়ের লেখা, তৃতীয়টি পণ্যের ছবি; তাদের ভুল আলাদা, আর meta-model শিখতে পারে কোন অর্ডারে কোন উৎস বেশি বিশ্বাসযোগ্য। সময়ের সঙ্গে বদলানো ডেটায় ভিন্ন সময়-জানালায় শেখা model-দের মেলানোও কাজে আসতে পারে। আবার ডেটা খুব ছোট হলে, যেখানে একক model-এর variance বড়, সরল গড়ও স্পষ্ট লাভ দেয়। প্রতিযোগিতার শেষ ধাপে, যেখানে খরচের হিসাব নেই, সামান্য লাভও মূল্যবান।
মাপের প্রশ্নও আছে। ০.০০২-এর লাভ সত্যি কি না, তা জানতে পর্ব ২২-এর পরিসংখ্যান লাগে; fold-এর ছড়ানো যদি ০.০১৩ হয়, একক run-এর ০.০০২ প্রায় গোলমাল। বিভিন্ন প্রকাশিত benchmark-এও tabular ডেটায় tuned GBM-কে শক্ত baseline হিসেবে দেখা যায়, যদিও ফল ডেটাভেদে বদলায়; পর্ব ৩৮ (Tabular ডেটায় model বাছাই) এই তুলনা আরও বিস্তারে করবে।
মূল উপলব্ধি: ensemble-এর লাভ আসে কমানো যায় এমন variance আর ভিন্ন ধরনের ভুল থেকে; tuned GBM দুটিই অনেকটা খেয়ে ফেলে, তাই বড় লাভ খুঁজুন সেখানে, যেখানে সদস্যরা ভিন্ন তথ্য দেখে।
অনুশীলন
হিসাবগুলো খাতায় করুন, তারপর উত্তর মেলান। চাইলে অংশ ২, ৪ আর ৫-এর ব্লক বদলে যাচাই করুন।
-
পূর্ণ numerical: দুটি model-এর অনুমানের ভুলের standard deviation σ₁ = ০.৩০ আর σ₂ = ০.৪০, ভুলের correlation ρ = ০.৬. (ক) সরল গড়ের ভুলের standard deviation কত? এটি কি model ১-এর চেয়ে ভালো? (খ) model ১-এ সেরা weight w* আর তখনকার standard deviation কত? (গ) সরল গড় model ১-কে হারাতে ρ কত-র কম হতে হবে?
(ক) Var = (০.০৯ + ০.১৬ + ২ × ০.৬ × ০.৩০ × ০.৪০)/৪ = (০.২৫ + ০.১৪৪)/৪ = ০.৩৯৪/৪ = ০.০৯৮৫; standard deviation √০.০৯৮৫ ≈ ০.৩১৪. এটি model ১-এর ০.৩০-এর চেয়ে খারাপ.
(খ) w* = (σ₂² − ρσ₁σ₂)/(σ₁² + σ₂² − ২ρσ₁σ₂) = (০.১৬ − ০.০৭২)/(০.২৫ − ০.১৪৪) = ০.০৮৮/০.১০৬ ≈ ০.৮৩০. তখন Var = w²σ₁² + (১ − w)²σ₂² + ২w(১ − w)ρσ₁σ₂ ≈ ০.০৮৬৯, standard deviation ≈ ০.২৯৫, model ১-এর চেয়ে সামান্য ভালো।
(গ) r = σ₂/σ₁ = ৪/৩. শর্ত ρ < (৩ − r²)/(২r) = (৩ − ১৬/৯)/(৮/৩) = (১১/৯) × (৩/৮) = ১১/২৪ ≈ ০.৪৫৮. ρ = ০.৬ এর বেশি, তাই সরল গড় হারে; weight দিলে সামান্য জেতে।
-
সমালোচনা ও নকশা: একটি AI assistant এই পরিকল্পনা দিয়েছে: “পুরো ডেটায় StandardScaler চালাব। তারপর train-এ random forest, boosting আর logistic regression শিখিয়ে তাদের
predict_proba(X_train)একটি টেবিলে রাখব, আর তাতে একটি গভীর GradientBoostingClassifier meta-model শেখাব। Meta-model-এর সেরা সেটিং test set-এ দেখে বাছব, আর test accuracy ০.৯৫ হলে রিপোর্টে লিখব stacking একক model-এর চেয়ে ১৮% ভালো।” কী কী সমস্যা, আর সঠিক নকশা কী?সমস্যা: (১) পুরো ডেটায় scaler শেখানো test-এর তথ্য train-এ ঢোকায়; scaler pipeline-এর ভেতরে, শুধু train-এ শিখতে হবে। (২) Meta-model in-sample সম্ভাবনায় শিখছে; forest আর boosting-এর মুখস্থ করা সম্ভাবনা তাকে ঠকাবে (ব্লক ২-এ validation ০.৯৫৬, test ০.৭৬৩)। (৩) গভীর GBM meta-model তিন কলামের প্রায়-একই তথ্যে overfit করবে; সরল logistic বা simplex weight যথেষ্ট। (৪) Test set-এ সেটিং বাছলে test আর নিরপেক্ষ নয়। (৫) “১৮% ভালো” তুলনা কার সঙ্গে, কোন tuning-এ, কতবার মেপে, তা নেই।
সঠিক নকশা: শুরুতে train/test ভাগ, test তালাবন্ধ। প্রতিটি base model একটি Pipeline (scaler যেখানে দরকার)।
StackingClassifier(base, final_estimator=LogisticRegression(), cv=5), যা নিজেই OOF ব্যবহার করে। তুলনার জন্য একটি tuned HistGradientBoosting, একইRepeatedStratifiedKFold-এ; mean ± sd আর corrected t-test; fit time আর latency. শেষে তালাবন্ধ test-এ একবার। -
আউটপুট অনুমান করুন: চালানোর আগে লিখে ফেলুন নিচের কোড কী ছাপবে।
import numpy as np P = np.array([[0.62, 0.45, 0.48], # সারি = অর্ডার, কলাম = model [0.55, 0.52, 0.10], [0.30, 0.70, 0.65]]) hard = ((P > 0.5).sum(axis=1) >= 2).astype(int) soft = P.mean(axis=1) print(hard) print(np.round(soft, 3), (soft > 0.5).astype(int))[0 1 1] [0.517 0.39 0.55 ] [1 0 1]Hard vote: প্রথম অর্ডারে শুধু ০.৬২ সীমার ওপরে, তাই ০; দ্বিতীয়তে ০.৫৫ আর ০.৫২, তাই ১; তৃতীয়তে ০.৭০ আর ০.৬৫, তাই ১. Soft vote-এর গড় ০.৫১৭, ০.৩৯০ আর ০.৫৫০; NumPy গোল করা ০.৩৯ আর ০.৫৫ শেষের শূন্য ছাড়া ছাপে, তাই “0.39 ” আর “0.55 ”. প্রথম দুই অর্ডারে hard আর soft উল্টো উত্তর দেয়, তৃতীয়তে একমত। (ডেস্কটপে NumPy 2.5.4-এ চালিয়ে মেলানো।)
-
কোড লিখুন: Caruana-র ensemble selection নিজে লিখুন: holdout সম্ভাবনার একটি matrix
Pval(সারি অর্ডার, কলাম model) থেকে ২০ ধাপে, প্রতিবার সেই model যোগ করুন (একই model বারবার আসতে পারে), যা যোগ করলে বেছে নেওয়া সবার গড়ের log loss সবচেয়ে কম হয়। শেষে প্রতিটি model কতবার এসেছে / ২০ হলো তার weight. অংশ ৫-এর ব্লকের শেষে যোগ করে চালান, আর SLSQP-এর weight-এর সঙ্গে তুলনা করুন।def ensemble_selection(Pval, yval, rounds=20): chosen = [] for _ in range(rounds): scores = [log_loss(yval, Pval[:, chosen + [j]].mean(axis=1)) for j in range(Pval.shape[1])] chosen.append(int(np.argmin(scores))) return np.bincount(chosen, minlength=Pval.shape[1]) / rounds w_es = ensemble_selection(Pval, yval) print("ensemble selection weights:", w_es) print(f"test log loss {log_loss(yte, Pte @ w_es):.4f}")ডেস্কটপে সত্যিকারের run (scikit-learn 1.8.0, অংশ ৫-এর একই ডেটা আর ভাগ):
ensemble selection weights: [0.45 0.35 0.2 ] test log loss 0.5140Weight (০.৪৫, ০.৩৫, ০.২০), SLSQP-এর (০.৪৪২, ০.৩৫৭, ০.২০১)-এর প্রায় হুবহু, আর test log loss একই ০.৫১৪০. ২০ ধাপে weight-এর ধাপ ০.০৫, তাই এর চেয়ে সূক্ষ্ম মিল সম্ভব নয়। সুবিধা: যেকোনো metric (যেমন AUC) সরাসরি ব্যবহার করা যায়, gradient লাগে না, আর অনেক model থেকে অল্প কয়েকটি বেছে নেয়।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমফাঁস ছাড়া stacking-এর ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- ডেটা train আর test-এ ভাগ করা, test তালাবন্ধ রাখা
- Train-কে K fold-এ ভাগ করে প্রতিবার একটি fold লুকিয়ে base model শেখানো
- লুকানো fold-গুলোর সম্ভাবনা জুড়ে out-of-fold টেবিল বানানো
- Out-of-fold টেবিলে একটি সরল meta-model শেখানো
- পুরো train-এ শেখা base model দিয়ে test-এর সম্ভাবনা বানিয়ে meta-model-এ দেওয়া আর একবার মাপা
দুটি model-এর ভুলের standard deviation সমান (σ), আর ভুলের correlation ০.৬. দুই অনুমানের সরল গড়ের ভুলের variance কত?
- ০.৮σ²
- ০.৫σ²
- ০.৬σ²
- ১.৬σ²
তিনটি model একটি অর্ডারের ফেরতের সম্ভাবনা দেয় ০.৯০, ০.৪০ আর ০.৪০. সীমা ০.৫. Hard vote আর soft vote কী বলবে?
- দুটিই “ফেরত হবে”
- দুটিই “ফেরত হবে না”
- Hard “ফেরত হবে না”, soft “ফেরত হবে”
- Hard “ফেরত হবে”, soft “ফেরত হবে না”
একজন base model (random forest সহ) পুরো train-এ শিখিয়ে সেই train সারিগুলোরই predict_proba দিয়ে meta-model শেখালেন। সাধারণত কী ঘটে?
- কিছুই বদলায় না, কারণ সব সারি একই train থেকে
- Meta-model মুখস্থ করা model-কে অতিরিক্ত weight দেয়; validation ফুলে ওঠে, test খারাপ হয়
- শুধু logistic regression-এর কলাম ভুল হয়
- Test score বাড়ে, কারণ meta-model বেশি আত্মবিশ্বাসী
scikit-learn-এর StackingClassifier-এর fit সম্পর্কে কোনটি সঠিক?
- Base model পুরো train-এ শেখে, আর তাদের in-sample সম্ভাবনায় final estimator শেখে
- Base model শুধু শেষ fold-এ শেখে আর সেগুলোই prediction-এ ব্যবহৃত হয়
- Final estimator test set-এর একটি অংশে শেখে
- cross_val_predict-এর out-of-fold সম্ভাবনায় final estimator শেখে, আর base model-রা পুরো train-এ আবার শেখে
Stacking-এর তুলনায় blending-এর প্রধান দুর্বলতা কোনটি?
- Base model কম ডেটায় শেখে, আর meta-model একটি ছোট holdout-এর ওপর নির্ভর করে
- Blending stacking-এর চেয়ে অনেক ধীর
- Blending-এ সবসময় in-sample leakage হয়
- Blending-এ meta-model শুধু decision tree হতে পারে
একই ২৫টি CV ভাগে stacking-এর log loss একটি tuned একক GBM-এর চেয়ে গড়ে ০.০০২ কম; fold-এর standard deviation প্রায় ০.০১৩, corrected t-test-এ p = ০.৩, আর prediction latency ৩ গুণ। কোন সিদ্ধান্ত সবচেয়ে যুক্তিসঙ্গত?
- Stacking চালু করা, কারণ গড়ে log loss কম
- Test set-এ দুটিই চালিয়ে যেটি ভালো সেটি নেওয়া
- Tuned একক GBM রাখা, যদি না সামান্য উন্নতিরও বড় মূল্য থাকে আর latency গ্রহণযোগ্য হয়
- Stacking-এর seed বদলে এমন run খোঁজা যেখানে p < ০.০৫
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- গড়ের শর্ত: Var(গড়) = (σ₁² + σ₂² + 2ρσ₁σ₂)/4; ভুলের correlation যত কম, লাভ তত বেশি; দুর্বল সঙ্গীর সরল গড় সেরা একক model-কেও হারাতে পারে।
- Voting: hard গোনে label, soft গোনে সম্ভাবনার গড়; soft-এর লাভ প্রায়ই log loss-এ দেখা যায়, আর তা calibration-এর ওপর নির্ভর করে।
- Stacking: meta-model শুধু out-of-fold সম্ভাবনায় শেখে; in-sample-এ শেখালে validation ফুলে ওঠে আর test খারাপ হয়।
StackingClassifierভেতরেcross_val_predictকরে, তারপর base model পুরো train-এ আবার শেখায়। - Blending আর weight: holdout-এ meta বা simplex-এ weight (SLSQP বা ensemble selection); সরল আর দ্রুত, কিন্তু কম ডেটায়।
- সৎ তুলনা: tuned শক্ত baseline, একই fold, repeated CV, mean ± sd, corrected test, আর fit time ও latency; লাভ ছোট হলে সরলতাই জেতে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৩৮ · Tabular ডেটায় model বাছাই — tree, linear না neural, আর tuning-এর বাজেটপরবর্তী পর্বসৎ তুলনার নিয়মে tabular ডেটার জন্য model আর tuning বাজেট বাছাই।
- মেশিন লার্নিং · কোর্স হোম৭৭টি পর্ব১৪টি মডিউলের পুরো রোডম্যাপ; পর্ব ৩৮ (Tabular ডেটায় model বাছাই) এখান থেকে খুলবেন।
- পর্ব ২২ · Model তুলনার পরিসংখ্যানএই কোর্সCorrected resampled t-test আর কেন CV fold স্বাধীন নয়।
- পর্ব ২৬ · Probability calibrationএই কোর্সForest আর boosting-এর সম্ভাবনার আকার, soft vote-এর আগে জরুরি।
- AI-এর ভিত্তি · পর্ব ৫৬ · ডেটা ভাগ আর leakageআগের ভিত্তিIn-sample stacking-এর ফাঁস কোন পরিবারের ভুল, তার ভিত্তি।
- AI-এর ভিত্তি · পর্ব ৬৬ · Cross-validation আর model বাছাইআগের ভিত্তিK-fold, nested CV আর search; out-of-fold-এর মূল কৌশল।
- System Design · Latency আর throughputসম্পর্কিত কোর্সএকাধিক model চালানোর সময় latency কীভাবে মাপবেন আর বাজেট করবেন।