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

Bayesian optimization — অল্প চেষ্টায় hyperparameter খোঁজা

Bayesian Optimization — Surrogate Models, Acquisition Functions (PI, EI, UCB), a Loop from Scratch, TPE and Successive Halving

Taught in Bangla, this lesson builds Bayesian optimization from a Gaussian process surrogate and the PI, EI and confidence-bound acquisition functions, with EI worked by hand. In-browser runs compare a from-scratch loop with random search over 20 seeds, tune HistGradientBoosting and run HalvingRandomSearchCV; desktop runs add Optuna's TPE and show how CV noise can hide the gains.

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

  • কেন hyperparameter tuning একটি দামি, শব্দযুক্ত black-box সমস্যা, আর grid বা random search কোথায় অপচয় করে
  • Gaussian process surrogate-এর গড় আর অনিশ্চয়তা থেকে PI, EI আর UCB বের করা, EI-এর সূত্রসহ হাতে হিসাব
  • NumPy, SciPy আর scikit-learn দিয়ে পুরো BO loop নিজে লেখা, আর অনেক seed-এ random search-এর সঙ্গে সৎ তুলনা
  • HistGradientBoosting-এর learning rate আর পাতার সংখ্যা tune করা, Optuna-র TPE-এর ধারণা আর ডেস্কটপ run
  • Successive halving আর Hyperband-এর বাজেটের হিসাব, আর noise, category, parallel চেষ্টার মতো বাস্তব ঝামেলা

মডিউল ৯-এ এ পর্যন্ত অনিশ্চয়তা নিয়ে চারটি পর্ব: পর্ব ৪৫ (Bayesian linear regression — posterior আর predictive অনিশ্চয়তা), পর্ব ৪৬ (Gaussian process — kernel দিয়ে অনিশ্চয়তাসহ অনুমান), পর্ব ৪৭ (MCMC — Metropolis-Hastings আর Gibbs sampling) আর পর্ব ৪৮ (Variational inference-এর ধারণা — ELBO আর approximation). এই শেষ পর্বে সেই অনিশ্চয়তা একটি ব্যবহারিক কাজে লাগে: কোন hyperparameter পরের বার চেষ্টা করবেন? পর্ব ৩৮-এ দেখেছেন, random search-এর বাজেট বাড়ালে লাভ দ্রুত কমে, আর Optuna-র একটি ডেস্কটপ run-এর কথা শুনেছেন। এখানে তার ভেতরের যুক্তি নিজে লিখবেন। চারটি ব্লক পাতাতেই চলে (ব্রাউজারের Python, scikit-learn 1.8.0); প্রথমবার scikit-learn নামতে প্রায় দশ সেকেন্ড লাগে। Optuna আর বড় তুলনাগুলো ডেস্কটপের সত্যিকারের run.

১ · দামি black-box function — প্রতিটি চেষ্টার দাম আছে

গ্রামে গভীর নলকূপ বসাতে হবে, কিন্তু কোথায় মিষ্টি পানি সবচেয়ে বেশি তা জানা নেই। প্রতিটি পরীক্ষামূলক খননে কয়েক দিন আর অনেক টাকা। অভিজ্ঞ মিস্ত্রি সারা মাঠে সমান দূরত্বে খোঁড়েন না। আগের তিনটি খননের ফল দেখে তিনি ভাবেন: “এই দুইটির মাঝখানে পানি ভালো হওয়ার কথা” (জানা জায়গা কাজে লাগানো), আবার “পূর্বের মাঠে কেউ খোঁড়েনি, সেখানে চমক থাকতে পারে” (অচেনা জায়গা খোঁজা)। Bayesian optimization ঠিক এই মিস্ত্রির হিসাবকে সংখ্যায় লেখে।

Hyperparameter বাছাই এমন একটি সমস্যা। ধরুন λ হলো hyperparameter-এর একটি সেট (learning rate, পাতার সংখ্যা), আর f(λ) হলো সেই সেটিংয়ে model শিখিয়ে মাপা cross-validation loss. আমরা চাই f(λ) সবচেয়ে ছোট এমন λ. কিন্তু f-এর তিনটি অসুবিধা: এক, দামি: প্রতিটি মান মানে k বার পুরো training. দুই, black-box: কোনো সূত্র বা gradient নেই, তাই পর্ব ১৩-এর gradient পদ্ধতি চলে না। তিন, শব্দযুক্ত: fold-এর ভাগ বা seed বদলালে একই λ-তে f একটু বদলায়।

AI-এর ভিত্তি কোর্সের পর্ব ৬৬-এ grid আর random search দেখেছেন; পর্ব ৩৮-এ তাদের বাজেটের হিসাব। দুটিরই একটি মিল: আগের ফল দেখে পরের চেষ্টা বদলায় না। দশম চেষ্টাও প্রথমটির মতো অন্ধ। যখন প্রতিটি চেষ্টা কয়েক সেকেন্ডের, তখন এটা সমস্যা নয়; যখন এক ঘণ্টার, তখন প্রতিটি অন্ধ চেষ্টা দামি। Bayesian optimization (BO) প্রতিটি নতুন ফল থেকে শেখে। তার loop নিচের ছবিতে।

Bayesian optimization-এর loop

সস্তা ধাপ (নীল) বারবার ঘোরে; দামি ধাপ (কমলা) প্রতি ঘোরায় একবার

Bayesian optimization loop শুরু: ৩–৫টি এলোমেলো বিন্দু প্রতিটির CV loss মাপা ১. Surrogate শেখানো (সস্তা) GP: প্রতিটি x-এ গড় μ(x) আর অনিশ্চয়তা σ(x) ২. Acquisition সর্বোচ্চ (সস্তা) EI, PI বা UCB; হাজার বিন্দুতে খোঁজা যায় ৩. দামি মূল্যায়ন (একবার) সেই বিন্দুতে পুরো k-fold CV loss ৪. ডেটায় যোগ, বাজেট বাকি? হ্যাঁ: ১-এ ফেরা · না: বেরিয়ে যাওয়া ফল: এ পর্যন্ত সেরা বিন্দু (মডেলের অনুমান নয়, মাপা মান)
ছবি ৪৯.১ — BO-র খরচ প্রায় পুরোটাই ধাপ ৩-এ। ধাপ ১ আর ২ কয়েক মিলিসেকেন্ডের, তাই সেখানে যত বুদ্ধি খরচ করা যায়, তত লাভ।

নামের “Bayesian” অংশ আসে surrogate থেকে: f সম্পর্কে একটি prior বিশ্বাস (মসৃণ function), প্রতিটি মাপের পরে তা posterior-এ হালনাগাদ (AI-এর ভিত্তি কোর্সের পর্ব ৫০-এর prior থেকে posterior). শুধু মাপা মান নয়, “কোথায় কতটা জানি না” সেটাও surrogate মনে রাখে, আর পরের সিদ্ধান্তে সেই অজ্ঞতাই কাজে লাগে।

২ · Surrogate — Gaussian process যা জানে আর যা জানে না

পর্ব ৪৬ (Gaussian process — kernel দিয়ে অনিশ্চয়তাসহ অনুমান)-এ Gaussian process (GP) বিস্তারিত এসেছে; এখানে দুই লাইনের সারাংশ যথেষ্ট। GP ধরে নেয় যেকোনো কয়েকটি বিন্দুর f-এর মান একসঙ্গে multivariate Gaussian, যার covariance আসে একটি kernel k(x, x′) থেকে: কাছের দুই বিন্দুর মান প্রায় এক (পর্ব ২৯)। মাপা n টি বিন্দুর পরে নতুন x-এর অনুমান Gaussian-এর শর্তসাপেক্ষ সূত্র থেকে (পর্ব ১৯):

μ(x) = k*T(K + σn2I)−1y,   σ2(x) = k(x, x) − k*T(K + σn2I)−1k*

এখানে K হলো মাপা বিন্দুগুলোর kernel matrix, k* হলো নতুন x আর প্রতিটি মাপা বিন্দুর kernel মান, σn2 হলো মাপের শব্দের variance, আর y মাপা মানগুলো। μ(x)-এর রূপ kernel ridge-এর অনুমানের সঙ্গে হুবহু এক (পর্ব ৩০); GP-র বাড়তি দান σ(x). মাপা বিন্দুর ঠিক ওপরে σ প্রায় শূন্য, দূরে গেলে prior-এর মাপে ফিরে যায়।

BO-তে সবচেয়ে প্রচলিত kernel হলো Matérn (ν = ২.৫), কারণ RBF function-কে অসীমভাবে মসৃণ ধরে, যা loss-এর বাস্তব আকারের জন্য প্রায়ই বেশি আশাবাদী। Kernel-এর length-scale ℓ বলে কত দূরে গেলে দুই বিন্দু প্রায় সম্পর্কহীন। সাধারণত ℓ আর শব্দের মাপ marginal likelihood সর্বোচ্চ করে শেখা হয় (পর্ব ৪৬-এর বিষয়)। এই পর্বের কোডে কিন্তু kernel স্থির রাখা হয়েছে (optimizer=None), দুটি কারণে: ১০টির কম বিন্দুতে ℓ শেখা অস্থির, আর সীমায় ঠেকলে scikit-learn সতর্কবার্তা দেয়। normalize_y=True মাপা মানকে গড় ০, sd ১-এ এনে তারপর GP বসায়, তাই kernel-এর মাপ নিয়ে ভাবতে হয় না। বাস্তবের library (scikit-optimize, BoTorch, Optuna-র GPSampler) ℓ শেখে, সঙ্গে prior বা সীমা দিয়ে।

Surrogate হতে GP-ই লাগবে, এমন নয়। দরকার শুধু প্রতিটি বিন্দুতে একটি অনুমান আর তার অনিশ্চয়তা। SMAC (Frank Hutter, Holger Hoos আর Kevin Leyton-Brown, ২০১১) random forest ব্যবহার করে, গাছগুলোর অনুমানের ছড়ানো থেকে অনিশ্চয়তা নেয়; category আর অনেক মাত্রায় এটি GP-র চেয়ে সহজে চলে। TPE (অংশ ৬) আরও আলাদা পথে যায়।

৩ · Acquisition function — PI, EI আর UCB

Surrogate প্রতিটি x-এ দুটি সংখ্যা দেয়: μ(x) আর σ(x). পরের বিন্দু বাছতে এদের একটি সংখ্যায় মেলাতে হয়। এই পর্বে loss ছোট করা লক্ষ্য, তাই f* = এ পর্যন্ত মাপা সবচেয়ে ছোট মান, আর “উন্নতি” মানে f* থেকে নিচে নামা। Surrogate অনুযায়ী x-এ মান f(x) ~ N(μ, σ2). লিখি z = (f* − μ − ξ)/σ, যেখানে ξ ≥ ০ একটি ছোট সংখ্যা যা “নামমাত্র উন্নতি” বাদ দেয় (কোডে ০.০১)। Φ হলো standard normal-এর CDF, φ তার PDF (AI-এর ভিত্তি কোর্সের পর্ব ৩৬)।

  • Probability of improvement (PI), Harold Kushner ১৯৬৪: PI(x) = P(f(x) < f* − ξ) = Φ(z). উন্নতির সম্ভাবনা, কিন্তু উন্নতি কত বড় তা দেখে না।
  • Expected improvement (EI): উন্নতি I = max(f* − ξ − f(x), ০)-এর প্রত্যাশা। ধারণাটি Jonas Mockus-এর ১৯৭০-এর দশকের কাজে; Donald Jones, Matthias Schonlau আর William Welch ১৯৯৮ সালে তাঁদের EGO পদ্ধতিতে এটিকে GP-র সঙ্গে জনপ্রিয় করেন।
  • Confidence bound: সর্বোচ্চ করার সমস্যায় UCB = μ + κσ; আমাদের ছোট করার সমস্যায় উল্টো রূপ LCB = μ − κσ, আর সবচেয়ে ছোট LCB-র বিন্দু বাছা হয়। Niranjan Srinivas ও সহকর্মীরা (ICML ২০১০) দেখান, κ ধীরে বাড়ালে GP-UCB-এর regret-এর তাত্ত্বিক সীমা আছে।

EI-এর বন্ধ সূত্র। ধরুন a = f* − ξ − μ. লিখি f(x) = μ + σu, যেখানে u ~ N(০, ১). উন্নতি ধনাত্মক হয় যখন u < a/σ = z. তাই EI = ∫−∞z (a − σu) φ(u) du = a·Φ(z) − σ∫−∞z u φ(u) du. শেষ integral-টি −φ(z), কারণ φ′(u) = −uφ(u). সুতরাং

EI(x) = (f* − ξ − μ)·Φ(z) + σ·φ(z) = σ·(z·Φ(z) + φ(z))

প্রথম পদ বড় হয় যখন গড়ই ভালো (exploitation); দ্বিতীয় পদ বড় হয় যখন σ বড় (exploration). σ = ০ হলে EI = max(a, ০). নিচে দুটি candidate দিয়ে হাতে হিসাব; এখানে ξ = ০.

f* = 0.520 (log loss), ξ = 0Φ, φ: scipy.stats.norm

            
  1. দুই candidate. A-র গড় সেরার চেয়ে ০.০১ ভালো আর অনিশ্চয়তা কম। B-র গড় ০.০১ খারাপ, কিন্তু σ চার গুণ। প্রশ্ন: কোনটিতে পরের দামি মূল্যায়ন?
  2. A-র z. গড় সেরার চেয়ে ঠিক এক σ নিচে, তাই z = ১. Standard normal টেবিল থেকে Φ(১) = ০.৮৪১৩, φ(১) = ০.২৪২০.
  3. A-র EI. সূত্রে বসিয়ে ০.০১০ × ১.০৮৩৩ = ০.০১০৮৩. উন্নতি প্রায় নিশ্চিত, কিন্তু ছোট।
  4. B-র z. গড় সেরার চেয়ে খারাপ, তাই z ঋণাত্মক, −০.২৫. Φ(−০.২৫) = ০.৪০১৩: উন্নতির সম্ভাবনা চল্লিশ শতাংশের মতো।
  5. B-র EI. zΦ ঋণাত্মক (−০.১০০৩), কিন্তু φ যোগ হয়ে ০.২৮৬৩; বড় σ দিয়ে গুণ করে ০.০১১৪৫. B-র EI A-র চেয়ে সামান্য বেশি।
  6. দুই নিয়মের দুই রায়। PI শুধু সম্ভাবনা দেখে A বাছে। EI দেখে, B ভালো হলে অনেক ভালো হতে পারে, তাই B বাছে। এ কারণেই PI সেরা বিন্দুর গা-ঘেঁষে আটকে যেতে পারে, আর PI-তে ξ বাড়িয়ে তাকে খোঁজে ঠেলতে হয়।

Confidence bound-এ ভারসাম্যের হাতল সরাসরি κ. তিনটি candidate নিয়ে κ বদলালে কী হয়, দেখুন।

ছোট LCB = পরের চেষ্টাতিনটি candidate

            
  1. তিন candidate. P মাপা বিন্দুর পাশে, গড় সবচেয়ে ভালো, σ খুব ছোট। R এমন জায়গায় যেখানে কেউ মাপেনি: গড় খারাপ, σ বড়। Q মাঝামাঝি।
  2. κ = ০। শুধু গড় দেখা, তাই P. এটি লোভী নিয়ম: যা জানা, তার পাশেই বারবার। কোনো ভালো কিন্তু অচেনা উপত্যকা থাকলে তা কখনো খোঁজা হবে না।
  3. κ = ১। Q-এর LCB ০.৫১০, P-এর ০.৫১১. সামান্য ব্যবধানে Q জেতে: একটু দূরে গিয়ে দেখা।
  4. κ = ২। R-এর বড় σ এখন তার খারাপ গড়কে ঢেকে দেয়: ০.৫৬০ − ০.০৮০ = ০.৪৮০. অচেনা জায়গা খোঁজা শুরু।
  5. κ = ৩। R আরও এগিয়ে। κ যত বড়, তত বেশি শুধু অনিশ্চয়তার পেছনে ছোটা, যা মাপা ভালো জায়গাকে আর মাজে-ঘষে না।
  6. মাপা ফল। অনুশীলন ৪-এর কোড Forrester function-এ ২০টি seed-এ চালানো (ডেস্কটপ): κ = ০ বারোবার, κ = ১ ষোলোবার, κ = ২ পনেরোবার আর κ = ৫ মাত্র তিনবার আসল খাদের ০.১-এর মধ্যে পৌঁছায়। দুই চরমই দুর্বল।

৪ · পুরো loop নিজে লেখা — একটি পরীক্ষার function-এ

আসল model-এ যাওয়ার আগে এমন একটি function নিন, যার উত্তর জানা: Forrester function f(x) = (৬x − ২)2 sin(১২x − ৪), x ∈ [০, ১]. Alexander Forrester, András Sóbester আর Andy Keane-এর ২০০৮ সালের surrogate modelling বইয়ের এই function-এ একটি গভীর খাদ (x ≈ ০.৭৫৭, f ≈ −৬.০২১) আর বাঁয়ে একটি অগভীর ফাঁদ (x ≈ ০.১৪, f ≈ −০.৯৮)। ভাবুন প্রতিটি f(x) মাপ এক ঘণ্টার training. প্রথম ব্লক তিনটি শুরুর বিন্দু (০.০৫, ০.৫, ০.৯৫) থেকে PI, EI আর LCB-এর বাছাই তুলনা করে, তারপর EI দিয়ে আরও সাতটি বিন্দু মাপে, আর ৩, ৬ ও ১০টি মাপের পরের অবস্থা আঁকে। দ্বিতীয় ব্লক এই ব্লকের function ব্যবহার করে, তাই দুটি একই namespace-এ চলে।

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import Matern

def f(x):                                   # "দামি" function: Forrester (2008)
    return (6 * x - 2) ** 2 * np.sin(12 * x - 4)
grid = np.linspace(0, 1, 401)
def surrogate(X, y):                        # kernel স্থির: optimizer=None
    gp = GaussianProcessRegressor(Matern(0.1, nu=2.5), alpha=1e-6,
                                  normalize_y=True, optimizer=None).fit(X[:, None], y)
    return gp.predict(grid[:, None], return_std=True)
def acq(mu, sd, best, xi=0.01, kappa=2.0):  # minimise: ছোট f ভালো
    z = (best - mu - xi) / sd
    return {"PI": norm.cdf(z), "EI": (best - mu - xi) * norm.cdf(z) + sd * norm.pdf(z),
            "LCB": -(mu - kappa * sd)}      # সবগুলোতে বড় মান = বেশি আকর্ষণীয়
X = np.array([0.05, 0.5, 0.95]); y = f(X)
for name, a in acq(*surrogate(X, y), y.min()).items():
    print(f"{name:3s} -> next x = {grid[a.argmax()]:.3f}")
fig, axes = plt.subplots(2, 3, figsize=(10, 5), sharex=True)
for n in range(3, 11):                      # EI দিয়ে এক এক করে নতুন বিন্দু
    mu, sd = surrogate(X, y); ei = acq(mu, sd, y.min())["EI"]
    if n in (3, 6, 10):
        top, bot = axes[:, (3, 6, 10).index(n)]
        top.plot(grid, f(grid), "k:", lw=1, label="true f"); top.plot(grid, mu, label="GP mean")
        top.fill_between(grid, mu - 2 * sd, mu + 2 * sd, alpha=0.25, label="mean +/- 2 sd")
        top.plot(X, y, "ko", ms=4); top.set_title(f"after {n} evaluations")
        bot.plot(grid, ei, color="tab:green"); bot.set_xlabel("x")
    if n < 10:
        X = np.append(X, grid[ei.argmax()]); y = np.append(y, f(X[-1]))
axes[0, 0].legend(fontsize=7); axes[1, 0].set_ylabel("EI")
print("evaluated x:", np.round(X, 3))
print(f"best found f = {y.min():.3f} at x = {X[y.argmin()]:.3f}; true min -6.021 at 0.757")
fig.subplots_adjust(hspace=0.25, wspace=0.3)

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

PI  -> next x = 0.055
EI  -> next x = 0.145
LCB -> next x = 0.193
evaluated x: [0.05  0.5   0.95  0.145 0.245 0.37  0.645 0.708 0.748 0.78 ]
best found f = -5.971 at x = 0.748; true min -6.021 at 0.757

প্রথম তিনটি মাপ: f(০.০৫) ≈ ০.৭৪, f(০.৫) ≈ ০.৯১, f(০.৯৫) ≈ ১২.৩. সেরা f* = ০.৭৪, বাঁ প্রান্তে। এখন তিন নিয়মের তিন রায়। PI বাছে ০.০৫৫, সেরা বিন্দুর ঠিক পাশে: সেখানে সামান্য উন্নতি প্রায় নিশ্চিত, যা আগের stepper-এর A-র গল্প। EI বাছে ০.১৪৫, একটু দূরে, যেখানে গড় নামছে আর অনিশ্চয়তাও আছে। LCB (κ = ২) বাছে ০.১৯৩, আরও দূরে। ছবির প্রথম কলামে নীল ছায়া (গড় ± ২ sd) মাপা বিন্দুতে সরু, মাঝে ফুলে ওঠা।

ফাঁদ আর মুক্তি: EI প্রথমে বাঁয়ের অগভীর খাদে যায় (০.১৪৫, ০.২৪৫, ০.৩৭)। ছয়টি মাপের পরে সেরা মান মাত্র −০.৯৮; দ্বিতীয় কলামে দেখবেন বাঁ দিক এখন ভালো জানা (ছায়া সরু), আর তাই EI-এর সবচেয়ে উঁচু চূড়া ডানে, ০.৬৫-এর কাছে, যেখানে অনিশ্চয়তা বড়। পরের চারটি মাপ (০.৬৪৫, ০.৭০৮, ০.৭৪৮, ০.৭৮) গভীর খাদে নামে; দশটিতে সেরা −৫.৯৭১, আসল সর্বনিম্নের ০.০৫ কাছে। তৃতীয় কলামে EI প্রায় সর্বত্র শূন্য, শুধু খাদের তলায় সরু চূড়া: model এখন প্রায় নিশ্চিত কোথায় ভালো।

একটি run প্রমাণ নয়; ভাগ্যও হতে পারে। দ্বিতীয় ব্লক ২০টি আলাদা seed-এ এলোমেলো তিনটি শুরুর বিন্দু থেকে BO (EI) আর একই বাজেটের random search চালায়। প্রতি seed-এ দুই পদ্ধতির প্রথম তিনটি বিন্দু হুবহু এক, তাই পার্থক্য আসে শুধু পরের বাছাই থেকে। মাপ হলো ঘাটতি (regret): এ পর্যন্ত পাওয়া সেরা মান বিয়োগ আসল সর্বনিম্ন।

F_MIN = f(0.757249)                         # আসল minimum (-6.0207), শুধু মাপার জন্য
def bo(seed, budget=15):                    # 3টি এলোমেলো শুরু, তারপর EI
    rng = np.random.default_rng(seed)
    X = rng.uniform(0, 1, 3); y = f(X)
    while len(X) < budget:
        mu, sd = surrogate(X, y)
        x_new = grid[acq(mu, sd, y.min())["EI"].argmax()]
        X = np.append(X, x_new); y = np.append(y, f(x_new))
    return np.minimum.accumulate(y) - F_MIN   # gap = এ পর্যন্ত সেরা − আসল minimum
def rand(seed, budget=15):                  # একই বাজেট, একই প্রথম 3টি, শুধু এলোমেলো
    return np.minimum.accumulate(f(np.random.default_rng(seed).uniform(0, 1, budget))) - F_MIN
B = np.array([bo(s) for s in range(20)])    # 20টি seed
R = np.array([rand(s) for s in range(20)])
print("budget   BO gap mean (sd)   random gap mean (sd)   BO better")
for k in (3, 5, 8, 10, 15):
    b, r = B[:, k - 1], R[:, k - 1]
    print(f"{k:4d}     {b.mean():5.2f} ({b.std(ddof=1):4.2f})       "
          f"{r.mean():5.2f} ({r.std(ddof=1):4.2f})          {(b < r).sum()}/20")
print("gap < 0.1 after 15:", (B[:, -1] < 0.1).sum(), "BO,", (R[:, -1] < 0.1).sum(), "random")
fig, ax = plt.subplots(figsize=(6.5, 3.6))
ks = np.arange(1, 16)
for G, lab in [(B, "Bayesian optimisation (EI)"), (R, "random search")]:
    ax.plot(ks, np.median(G, 0), lw=2, label=lab + ", median")
    ax.fill_between(ks, *np.percentile(G, [25, 75], 0), alpha=0.2)
ax.set(yscale="log", ylim=(1e-4, 20), xlabel="number of evaluations",
       ylabel="best f found - true min", title="Same budget, 20 seeds (band = 25-75%)")
ax.legend(fontsize=8)

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

budget   BO gap mean (sd)   random gap mean (sd)   BO better
   3      3.69 (2.20)        3.69 (2.20)          0/20
   5      2.18 (2.39)        2.92 (2.13)          14/20
   8      1.14 (2.02)        1.70 (1.65)          14/20
  10      0.10 (0.19)        1.23 (1.27)          18/20
  15      0.00 (0.00)        0.81 (0.97)          20/20
gap < 0.1 after 15: 20 BO, 3 random

তিনটি মাপে দুই পদ্ধতি হুবহু সমান, যেমন হওয়ার কথা। পাঁচটিতে BO এগোয় (গড় ঘাটতি ২.১৮ বনাম ২.৯২), কিন্তু sd ২-এর ওপরে, অর্থাৎ seed-এর ভাগ্য তখনো বড়। দশটি মাপে পার্থক্য স্পষ্ট: BO-র গড় ঘাটতি ০.১০, random-এর ১.২৩; ২০টির ১৮টি seed-এ BO ভালো। পনেরোটিতে BO সব seed-এ খাদের তলায় (ঘাটতি < ০.১, কুড়িতে কুড়ি), random search মাত্র তিনটিতে। ছবিতে log স্কেলে BO-র median রেখা ছয়টি মাপের পর খাড়া নামে, random-এরটি প্রায় সমতল। কারণ সরল: এক মাত্রায়, মসৃণ function-এ, যেখানে surrogate ঠিক ধারণা দেয়, সেখানে আগের ফল ব্যবহার করা বিশাল সুবিধা। পরের অংশে দেখবেন বাস্তবের শব্দযুক্ত loss-এ ছবিটি এত পরিষ্কার নয়।

৫ · আসল tuning — learning rate × পাতার সংখ্যা

এবার বাজারঘরের churn ডেটা (পর্ব ৩৮-এর একই bazar_churn.py, পাতায় লুকানো ফাইল হিসেবে আছে)। Model: HistGradientBoostingClassifier, দুটি hyperparameter: learning_rate ০.০০৩ থেকে ১, log স্কেলে (০.০০৩ থেকে ০.০৩ আর ০.১ থেকে ১ সমান গুরুত্বের পাল্লা), আর max_leaf_nodes ২ থেকে ৬৪. লক্ষ্য: ৩-fold CV log loss ছোট করা। ব্রাউজারে দ্রুত চালাতে ডেটা ৫০০ সারি, শুধু ছয়টি সংখ্যার কলাম, ২০টি boosting iteration. দুটি hyperparameter-কে [০, ১]² বর্গে আনা হয়েছে, যাতে GP-র একটি length-scale দুই দিকেই চলে। এখানে মাপের শব্দ বাস্তব, তাই kernel-এ WhiteKernel যোগ করা। প্রতিটি seed-এ random search ১২টি এলোমেলো বিন্দু মাপে; BO তার প্রথম চারটি নিয়ে বাকি আটটি EI দিয়ে বাছে (৫০০টি এলোমেলো candidate-এর মধ্যে সেরা EI). ব্লকটি প্রায় তিন সেকেন্ড নেয়।

import numpy as np
import pandas as pd
from scipy.stats import norm
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import Matern, WhiteKernel
from sklearn.model_selection import StratifiedKFold, cross_val_score
from bazar_churn import make_churn
X, y = make_churn(500); X = X.select_dtypes("number")   # ছোট রাখা: 500 সারি, 6টি সংখ্যা
cv = StratifiedKFold(3, shuffle=True, random_state=0)
def params(u):                              # [0,1]² -> learning_rate (log), leaves
    return 10 ** (-2.5 + 2.5 * u[0]), int(round(2 + 62 * u[1]))
def loss(u):                                # একটি "দামি" মূল্যায়ন: 3-fold CV log loss
    lr, leaves = params(u)
    m = HistGradientBoostingClassifier(learning_rate=lr, max_leaf_nodes=leaves, max_iter=20, random_state=0)
    return -cross_val_score(m, X, y, cv=cv, scoring="neg_log_loss").mean()
def ei_next(U, L, cand):                    # noise-সহ GP, kernel স্থির
    gp = GaussianProcessRegressor(Matern(0.3, nu=2.5) + WhiteKernel(0.05),
                                  normalize_y=True, optimizer=None).fit(U, L)
    mu, sd = gp.predict(cand, return_std=True); z = (L.min() - mu) / sd
    return cand[np.argmax((L.min() - mu) * norm.cdf(z) + sd * norm.pdf(z))]
rows = []
for seed in (1, 2, 3):
    rng = np.random.default_rng(seed)
    U = rng.uniform(size=(12, 2)); L = np.array([loss(u) for u in U])   # random: 12টি
    Ub, Lb = U[:4], L[:4]                   # BO: একই প্রথম 4টি, তারপর EI দিয়ে 8টি
    for _ in range(8):
        u = ei_next(Ub, Lb, rng.uniform(size=(500, 2)))
        Ub, Lb = np.vstack([Ub, u]), np.append(Lb, loss(u))
    for name, UU, LL in (("BO", Ub, Lb), ("random", U, L)):
        lr, lv = params(UU[LL.argmin()]); c = np.minimum.accumulate(LL)
        rows.append([seed, name, c[3], c[7], c[11], round(lr, 3), lv])
t = pd.DataFrame(rows, columns=["seed", "method", "best@4", "best@8", "best@12", "lr", "leaves"])
print(t.round(4).to_string(index=False))
print(t.groupby("method")["best@12"].agg(["mean", "std"]).round(4))

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

 seed method  best@4  best@8  best@12    lr  leaves
    1     BO  0.5209  0.5183   0.5101 0.196       3
    1 random  0.5209  0.5106   0.5106 0.075       4
    2     BO  0.5207  0.5207   0.5183 0.074      47
    2 random  0.5207  0.5176   0.5176 0.080      11
    3     BO  0.5232  0.5068   0.5064 0.200       2
    3 random  0.5232  0.5232   0.5232 0.050      12
          mean     std
method                
BO      0.5116  0.0061
random  0.5171  0.0063

তিনটি seed-এর দুটিতে BO ভালো (seed ৩-এ ০.৫০৬৪ বনাম ০.৫২৩২), একটিতে random (seed ২-এ ০.৫১৭৬ বনাম ০.৫১৮৩); seed ১-এ প্রায় সমান। গড়ে BO ০.৫১১৬, random ০.৫১৭১: পার্থক্য ০.০০৫৫, অথচ দুটির seed-ভেদে sd প্রায় ০.০০৬. তিনটি seed থেকে কোনো রায় দেওয়া যায় না। লক্ষ করুন সেরা সেটিংগুলোর ধরন: learning rate প্রায় ০.২ হলে খুব ছোট গাছ (২–৩টি পাতা), আর ০.০৫–০.০৮ হলে পাতার সংখ্যা ৪ থেকে ৪৭, যেখানে loss গাছের আকারে কম সংবেদনশীল। ৫০০ সারির ছোট ডেটায় সরল model-ই ভালো, যা পর্ব ৩৮-এর search-ও পেয়েছিল।

ব্রাউজারে তিনটির বেশি seed চালানো যায় না, তাই একই সমস্যা ডেস্কটপে ২০টি seed-এ চালানো হয়েছে, সঙ্গে Optuna-র TPE (অংশ ৬)। সত্যিকারের run: একটি সাধারণ ল্যাপটপ, Python 3.14.4, scikit-learn 1.8.0, optuna 5.0.0, uv দিয়ে বানানো একটি আলাদা venv-এ। তুলনার জন্য একটি “প্রায়-সত্য” মানও মাপা: ১৫ × ১৫ grid-এর ২২৫টি মূল্যায়নের সেরা। শেষ লাইনে একটি শব্দ-পরীক্ষা: একই সেটিং (learning rate ০.১, ৮টি পাতা) ১০টি আলাদা ৩-fold ভাগে।

~/bazarghar-ml$ uv run python bo2d_seeds.py
Python 3.14.4, scikit-learn 1.8.0, optuna 5.0.0
reference: best of a 15 x 15 grid (225 evaluations) = 0.5018  (20 s)
20 seeds, budget 12, 3-fold CV log loss (lower is better)
method                   best@4          best@8          best@12         gap to grid@12
random                   0.5198 ± 0.0067  0.5166 ± 0.0060  0.5149 ± 0.0062  +0.0131
BO (GP + EI)             0.5198 ± 0.0067  0.5150 ± 0.0059  0.5126 ± 0.0058  +0.0108
TPE, 4 startup           0.5231 ± 0.0100  0.5192 ± 0.0066  0.5155 ± 0.0056  +0.0137
TPE, default 10 startup  0.5231 ± 0.0100  0.5213 ± 0.0100  0.5168 ± 0.0048  +0.0150
BO (GP + EI)             beats random at 12 in 12/20 seeds, ties 1
TPE, 4 startup           beats random at 12 in 9/20 seeds, ties 0
TPE, default 10 startup  beats random at 12 in 8/20 seeds, ties 0
noise check: lr=0.1, leaves=8 on 10 different 3-fold splits: 0.5263 ± 0.0126
total 101 s

তিনটি জিনিস পড়ুন। এক, বাজেট ১২-তে BO গড়ে ০.৫১২৬, random ০.৫১৪৯: BO সামান্য এগিয়ে, ২০টির ১২টি seed-এ। grid-এর সেরার সঙ্গে ঘাটতি ০.০১৩১ থেকে ০.০১০৮-এ নামে। দুই, TPE এখানে random-এর চেয়ে ভালো নয়। ডিফল্টে TPE প্রথম ১০টি trial এলোমেলো চালায় (n_startup_trials=10), তাই ১২-এর বাজেটে তার নিজের বুদ্ধি খাটে মাত্র দুবার; চারে নামালেও এই ছোট বাজেটে লাভ দেখা যায়নি। তিন, সবচেয়ে জরুরি: শুধু CV-র ভাগ বদলালে একই সেটিংয়ের loss-এর sd ০.০১২৬. পদ্ধতিগুলোর পার্থক্য (০.০০২৩) তার পাঁচ ভাগের এক ভাগ। অর্থাৎ এই সমস্যায় “কোন সেটিং সেরা” প্রশ্নের চেয়ে বড় অনিশ্চয়তা হলো “এই সংখ্যাটি কতটা সত্য”। ছোট ডেটা, সস্তা model আর দুটি মাত্রায় BO-র লাভ ছোট; লাভ বাড়ে যখন প্রতিটি মূল্যায়ন দামি, মাত্রা কয়েকটি বেশি, আর loss-এর আকার মসৃণ। এটি একটি কাল্পনিক ডেটার একটি run, সাধারণ সত্য নয়।

৬ · TPE আর Optuna — ঘনত্বের অনুপাত দিয়ে খোঁজ

GP প্রশ্ন করে “এই x-এ f কত হবে?” TPE (James Bergstra, Rémi Bardenet, Yoshua Bengio আর Balázs Kégl, NIPS ২০১১) প্রশ্নটি উল্টে দেয়: “ভালো ফল যেসব x-এ এসেছে, সেগুলো দেখতে কেমন?” এ পর্যন্ত মাপা trial-গুলোকে loss অনুযায়ী সাজিয়ে সেরা একটি ভাগ (Optuna 5.0.0-এর ডিফল্টে প্রায় সেরা ১০%, সর্বোচ্চ ২৫টি) “ভালো” দল, বাকি “খারাপ” দল। দুই দলের x থেকে দুটি ঘনত্ব আন্দাজ করা হয়, l(x) আর g(x), প্রতিটি মাপা বিন্দুর ওপর একটি ছোট Gaussian বসিয়ে (পর্ব ৪০-এর KDE-র মতো, যার পুরোনো নাম Parzen window). তারপর l থেকে কয়েকটি candidate টেনে (ডিফল্টে ২৪টি) যেটির l(x)/g(x) সবচেয়ে বড়, সেটি পরের trial. Bergstra ও সহকর্মীরা দেখান, এই অনুপাত সর্বোচ্চ করা আর EI সর্বোচ্চ করা একটি নির্দিষ্ট অর্থে সমতুল্য।

কেন এত জনপ্রিয়? প্রতিটি hyperparameter-এর ঘনত্ব আলাদা আন্দাজ করা যায়, তাই category, পূর্ণসংখ্যা আর শর্তসাপেক্ষ প্রশ্ন (“optimizer যদি SGD হয় তবেই momentum”) সহজে চলে, আর খরচ trial-এর সংখ্যার সঙ্গে প্রায় রৈখিক, GP-র n³-এর মতো নয়। দুর্বলতা: মূল TPE প্রতিটি hyperparameter আলাদা দেখে, তাদের পারস্পরিক সম্পর্ক দেখে না (Optuna-য় multivariate বিকল্প আছে), আর শুরুর এলোমেলো trial-এ বাজেট খরচ করে। উপরের ডেস্কটপ run-এ Optuna-র অংশটুকু এমন:

def tpe(seed, startup):
    s = optuna.create_study(sampler=optuna.samplers.TPESampler(seed=seed, n_startup_trials=startup))
    s.optimize(lambda t: loss_lr(t.suggest_float("learning_rate", 10 ** -2.5, 1.0, log=True),
                                 t.suggest_int("max_leaf_nodes", 2, 64)), n_trials=12)
    return np.minimum.accumulate([t.value for t in s.trials])

suggest_float(..., log=True) হলো log স্কেলের খোঁজ, যা ব্রাউজারের ব্লকে হাতে 10 ** (...) লিখে করা হয়েছিল। Optuna-র ডিফল্ট sampler TPE; GP-ভিত্তিক GPSampler-ও আছে, তবে তার জন্য PyTorch লাগে। কোন sampler আপনার সমস্যায় ভালো, তা এই পর্বের মতো কয়েকটি seed-এ মেপে দেখাই নিরাপদ।

৭ · Successive halving আর Hyperband — খারাপ চেষ্টা আগেই থামানো

ক্রিকেট দলের বাছাই ক্যাম্পে ৮১ জন আসেন। প্রত্যেককে পুরো মৌসুম খেলানোর সময় নেই। প্রথম দিন সবাইকে দশটি করে বল, সেরা এক-তৃতীয়াংশ পরের দিনে; সেখানে প্রত্যেককে তিন গুণ বেশি বল, আবার এক-তৃতীয়াংশ টিকে থাকেন। কয়েক ধাপে একজন বাকি। ঝুঁকিও স্পষ্ট: যিনি ধীরে ছন্দ পান, তিনি প্রথম দিনেই বাদ পড়তে পারেন।

BO বুদ্ধি খরচ করে কোন বিন্দু মাপবেন তা বাছতে। আরেকটি পথ: প্রতিটি মাপকে সস্তা করা। কম সারি, কম boosting iteration বা কম epoch-এ চালানো মূল্যায়ন কম নির্ভুল, কিন্তু খারাপ সেটিং ছাঁটতে প্রায়ই যথেষ্ট। এই ধারণার নাম multi-fidelity optimization. Successive halving (Zohar Karnin ও সহকর্মীরা ২০১৩; hyperparameter-এ Kevin Jamieson আর Ameet Talwalkar, AISTATS ২০১৬) ঠিক ক্যাম্পের নিয়ম: n টি candidate, প্রতিটিকে r সম্পদ; সেরা 1/η টিকে থাকে, তাদের সম্পদ η গুণ। scikit-learn-এ HalvingRandomSearchCV এখনো experimental, তাই আগে from sklearn.experimental import enable_halving_search_cv লাগে। চতুর্থ ব্লক ১,২০০ সারির ডেটায় ২৭টি এলোমেলো candidate দিয়ে শুরু করে, সম্পদ হলো সারির সংখ্যা, প্রথম ধাপে ৪০, factor ৩.

import numpy as np
import pandas as pd
from scipy.stats import loguniform, randint
from sklearn.experimental import enable_halving_search_cv  # noqa: experimental, আলাদা import লাগে
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.model_selection import HalvingRandomSearchCV, StratifiedKFold
from bazar_churn import make_churn
X, y = make_churn(1200); X = X.select_dtypes("number")
space = {"learning_rate": loguniform(0.003, 1.0), "max_leaf_nodes": randint(2, 65)}
search = HalvingRandomSearchCV(
    HistGradientBoostingClassifier(max_iter=30, random_state=0), space,
    n_candidates=27, factor=3, resource="n_samples", min_resources=40,
    cv=StratifiedKFold(3, shuffle=True, random_state=0),
    scoring="neg_log_loss", random_state=0).fit(X, y)
r = pd.DataFrame(search.cv_results_)
per_round = r.groupby("iter").agg(rows=("n_resources", "first"),
                                  candidates=("params", "size"),
                                  best_loss=("mean_test_score", lambda s: -s.max()))
print(per_round.to_string())
cost = (per_round.rows * per_round.candidates).sum()
print(f"row-fits per fold: halving {cost}, full random search {27 * 1200}")
print("winner:", {k: round(float(v), 3) for k, v in search.best_params_.items()},
      f"| 3-fold log loss on {search.n_resources_[-1]} rows: {-search.best_score_:.4f}")

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

      rows  candidates  best_loss
iter                             
0       40          27   0.632817
1      120           9   0.548803
2      360           3   0.514709
3     1080           1   0.512455
row-fits per fold: halving 4320, full random search 32400
winner: {'learning_rate': 0.038, 'max_leaf_nodes': 8.0} | 3-fold log loss on 1080 rows: 0.5125

চারটি ধাপ: ২৭টি candidate ৪০ সারিতে, ৯টি ১২০-এ, ৩টি ৩৬০-এ, আর শেষে একটি ১,০৮০-এ। প্রথম ধাপের সেরা log loss ০.৬৩৩, কারণ ৪০ সারিতে কেউই ভালো শেখে না; শেষ ধাপে ০.৫১২৫. মোট খরচ (প্রতি fold-এ সারি × fit) ৪,৩২০, যেখানে ২৭টিকেই পুরো ১,২০০ সারিতে চালালে ৩২,৪০০: প্রায় সাড়ে সাত ভাগের এক ভাগ। এই সংখ্যা ডেস্কটপের সঙ্গে হুবহু মেলে না: একই random_state-এ ডেস্কটপের ২৭টি candidate অন্য, সম্ভবত ব্রাউজারের ৩২-bit Python-এ এলোমেলো পূর্ণসংখ্যা টানার পথ আলাদা বলে। বাজেটের হিসাব নিচে ধাপে ধাপে।

n_candidates = 27, factor η = 3min_resources = 40, মোট 1,200 সারি

            
  1. ধাপের সংখ্যা। scikit-learn দুটি সীমা দেখে: সম্পদ ৪০ থেকে ৩ গুণ করে কতবার বাড়ানো যায় (১,২০০-এর মধ্যে), আর ২৭ থেকে ৩ দিয়ে ভাগ করে কতবার একজনে নামা যায়। দুটিই ৪, তাই চারটি ধাপ।
  2. ধাপ ০। সবাইকে সবচেয়ে কম সম্পদ। ২৭ × ৪০ = ১,০৮০. এখানেই সবচেয়ে বেশি candidate, কিন্তু প্রতিটি সস্তা।
  3. ধাপ ১। সেরা এক-তৃতীয়াংশ, ৯টি, প্রত্যেকে তিন গুণ সারি, ১২০. খরচ আবার ১,০৮০.
  4. ধাপ ২। ৩টি candidate, ৩৬০ সারি। খরচ সেই ১,০৮০.
  5. ধাপ ৩। শেষ জন ১,০৮০ সারিতে। খেয়াল করুন, ১,২০০ নয়: ৪০ × ৩ × ৩ × ৩ = ১,০৮০. প্রতি ধাপে মোট খরচ সমান থাকে, এটাই successive halving-এর নকশা।
  6. মোট আর ঝুঁকি। ৪,৩২০ বনাম ৩২,৪০০, প্রায় ১৩%. কিন্তু সাশ্রয়ের দাম আছে: ৪০ সারির র‍্যাঙ্ক যদি ১,২০০ সারির র‍্যাঙ্কের সঙ্গে না মেলে, তবে আসল সেরা candidate প্রথম ধাপেই বাদ পড়তে পারে।

সেই ঝুঁকি কত বড়? ডেস্কটপে সত্যিকারের run (একই ল্যাপটপ আর version). প্রথমটি একই ১,২০০ সারির সমস্যা ৮টি seed-এ: halving-এর বিজয়ীকে পুরো ডেটায় মেপে দেখা হয়েছে, একই ২৭টির মধ্যে সে কততম। দ্বিতীয়টি বড় ডেটায় (২৪,০০০ সারি), যেখানে সম্পদ হলো boosting iteration (১০ → ৩০ → ৯০ → ২৭০), পাশে একই ২৭টি candidate-এর পুরো random search.

~/bazarghar-ml$ uv run python halving_seeds.py
Python 3.14.4, scikit-learn 1.8.0
n = 1200, min_resources = 40, factor 3, 27 candidates, max_iter=30
seed  halving time  winner on all rows  best of the same 27 (all rows)  winner's rank  full-search time
   0        3.2 s   0.5155                   0.4854                          10 of 27          3.6 s
   1        3.0 s   0.5033                   0.4814                           6 of 27          3.6 s
   2        3.3 s   0.5093                   0.4837                           6 of 27          3.8 s
   3        3.2 s   0.5343                   0.4956                          14 of 27          3.8 s
   4        3.3 s   0.5483                   0.4960                          12 of 27          4.1 s
   5        3.3 s   0.5085                   0.4929                           4 of 27          3.8 s
   6        3.3 s   0.5006                   0.4795                           7 of 27          3.5 s
   7        3.2 s   0.4921                   0.4885                           2 of 27          3.6 s

~/bazarghar-ml$ uv run python halving_iter.py
Python 3.14.4, scikit-learn 1.8.0
24,000 rows, 27 candidates; halving resource = max_iter (10 -> 30 -> 90 -> 270)
seed 0: halving  10.3 s, best 0.4721 | full random search (all 27 at 270)  36.2 s, best 0.4606
seed 1: halving  10.2 s, best 0.4757 | full random search (all 27 at 270)  36.2 s, best 0.4608
seed 2: halving  11.6 s, best 0.4928 | full random search (all 27 at 270)  40.6 s, best 0.4608

ছোট ডেটায় halving-এর বিজয়ী ২৭টির মধ্যে ২য় থেকে ১৪তম; আর সময়ও প্রায় বাঁচেনি (৩.২ বনাম ৩.৬ সেকেন্ড), কারণ এত ছোট fit-এ প্রতিটির স্থির খরচই বেশি। বড় ডেটায় iteration-কে সম্পদ ধরলে চিত্র বদলায়: halving তিন গুণের বেশি দ্রুত (প্রায় ১১ বনাম ৩৮ সেকেন্ড), কিন্তু প্রতিবার পুরো search-এর চেয়ে খারাপ বিজয়ী (যেমন ০.৪৭২১ বনাম ০.৪৬০৬)। seed ০ খুঁটিয়ে দেখলে কারণ স্পষ্ট: ১০ iteration-এ শীর্ষ তিনটির learning rate ০.২৮ থেকে ০.৪৭, অথচ ২৭০ iteration-এ পুরো search-এর শীর্ষ পাঁচটির চারটি ০.০১ থেকে ০.০৪. ছোট learning rate দশ ধাপে কিছুই শেখে না, তাই প্রথম ধাপেই বাদ। কম সম্পদের র‍্যাঙ্ক বেশি সম্পদের র‍্যাঙ্ক নয়। Hyperband (Lisha Li ও সহকর্মীরা, JMLR ২০১৮) এই ঝুঁকি ভাগ করে: আলাদা শুরুর বাজেট দিয়ে কয়েকটি halving (bracket) চালায়, যার একটি অল্প candidate-কে প্রথম থেকেই অনেক সম্পদ দেয়। BOHB (Stefan Falkner, Aaron Klein আর Frank Hutter, ICML ২০১৮) Hyperband-এর এলোমেলো candidate-এর জায়গায় TPE-জাতীয় model বসায়। এ ছাড়া early stopping-ভিত্তিক pruning (Optuna-র pruner) একই ধারণার আরেক রূপ।

৮ · বাস্তবের ঝামেলা — noise, category, parallel আর কখন BO নয়

  • শব্দযুক্ত loss: অংশ ৫-এ দেখেছেন, CV-র ভাগ বদলালেই loss ০.০১৩ নড়ে। তখন “এ পর্যন্ত সেরা f*” নিজেই ভাগ্যবান একটি মাপ হতে পারে। প্রতিকার: GP-তে শব্দের পদ (WhiteKernel বা alpha), f* হিসেবে মাপা মানের বদলে surrogate-এর গড় ব্যবহার, আর শেষে বিজয়ীকে নতুন fold-এ আবার মাপা। অনেক candidate থেকে সেরা বাছার আশাবাদ (selection bias) এখানেও আছে; চূড়ান্ত সংখ্যা nested CV বা তালাবন্ধ test থেকে।
  • Category আর শর্তসাপেক্ষ space: GP-র kernel দূরত্ব চায়; “optimizer ∈ {adam, sgd}” বা “kernel = rbf হলে তবেই gamma”-র মতো প্রশ্নে সে অস্বস্তিতে। TPE আর SMAC-এর random forest এখানে স্বাভাবিক।
  • মাত্রা: GP-ভিত্তিক BO সাধারণত কয়েকটি থেকে প্রায় কুড়িটি মাত্রায় ভালো চলে বলে ধরা হয়; তার বেশিতে surrogate দুর্বল হয়, আর acquisition-এর খোঁজও কঠিন। তখন গুরুত্বপূর্ণ কয়েকটি hyperparameter বাছাই বা trust-region পদ্ধতি (যেমন TuRBO, David Eriksson ও সহকর্মীরা, NeurIPS ২০১৯)।
  • একসঙ্গে অনেক মূল্যায়ন: আটটি machine থাকলে একই সময়ে আটটি বিন্দু চাই, কিন্তু EI একটি দেয়। একটি জনপ্রিয় কৌশল “constant liar” (David Ginsbourger ও সহকর্মীরা, ২০১০): চলমান বিন্দুর ফল একটি নকল মান ধরে নিয়ে পরের বিন্দু বাছা। Optuna-র TPE-তেও এমন বিকল্প আছে। Random search আর Hyperband স্বভাবতই parallel.
  • কখন BO নয়: প্রতিটি মূল্যায়ন কয়েক সেকেন্ডের হলে random search-এর সরলতা আর parallelism প্রায়ই জেতে; বাজেট কয়েকশো হলে GP-র n³ খরচও চোখে পড়ে (পর্ব ১০-এর Cholesky). আর প্রথমে ভাবুন, কোন hyperparameter আদৌ গুরুত্বপূর্ণ; ভুল space-এ সবচেয়ে বুদ্ধিমান খোঁজও অপচয়।

আরও গভীরে: Jasper Snoek, Hugo Larochelle আর Ryan Adams-এর “Practical Bayesian Optimization of Machine Learning Algorithms” (NIPS ২০১২) GP-ভিত্তিক BO-কে ML tuning-এ জনপ্রিয় করে; তাঁরা kernel-এর hyperparameter-এর ওপর গড় করা আর সময়ের হিসাবে EI (EI per second) প্রস্তাব করেন। Bobak Shahriari ও সহকর্মীদের সমীক্ষা (Proceedings of the IEEE, ২০১৬) আর Roman Garnett-এর বই Bayesian Optimization (Cambridge University Press, ২০২৩) পূর্ণ তত্ত্ব দেয়। আরেকটি acquisition হলো Thompson sampling (William Thompson-এর ১৯৩৩ সালের ধারণা): posterior থেকে একটি function টেনে তার সর্বনিম্ন বিন্দু মাপা, যা parallel-এ স্বাভাবিকভাবে বৈচিত্র্য আনে।

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

  • Bayesian optimization-এর দুই উপাদান? অনিশ্চয়তাসহ surrogate (সাধারণত GP) আর acquisition function (EI, PI, UCB).
  • f* = ০.৪০, μ = ০.৩৫, σ = ০.১০ হলে EI? z = ০.৫, Φ = ০.৬৯১৫, φ = ০.৩৫২১; EI = ০.১ × (০.৫ × ০.৬৯১৫ + ০.৩৫২১) ≈ ০.০৬৯৮.
  • PI-এর দুর্বলতা? শুধু সম্ভাবনা দেখে, লাভের আকার নয়; তাই সেরা বিন্দুর গা-ঘেঁষে নিশ্চিত ছোট লাভে আটকায়।
  • UCB-তে κ বাড়ালে? অনিশ্চয়তার ওজন বাড়ে, তাই খোঁজ (exploration) বেশি; κ = ০ মানে শুধু গড়, পুরো exploitation.
  • ২৭টি candidate, factor ৩, ৪টি ধাপ: প্রতি ধাপে কয়টি? ২৭, ৯, ৩, ১; প্রতিটি ধাপে সম্পদ তিন গুণ, তাই প্রতি ধাপের মোট খরচ সমান।

৯ · AI-এর যুগে — tuning-এর কোডে AI-কে যাচাই

AI assistant-কে “আমার model Bayesian optimization দিয়ে tune করে দাও” বললে সাধারণত কয়েক লাইনের Optuna বা scikit-optimize কোড আসে, যা চলে, কিন্তু প্রায়ই চেনা ভুলসহ:

AI প্রায়ই যা করেকেন ভুলকীভাবে ধরবেন
Objective-এ test set-এর score ফেরত দেয়tuning test-কে মুখস্থ করে; শেষ সংখ্যা আশাবাদীobjective-এ শুধু train অংশের CV; test শেষে একবার
Learning rate বা regularization সমান স্কেলে খোঁজে০.০০১ আর ০.০১-এর পার্থক্য হারিয়ে যায়log=True বা loguniform
Search-এর সেরা score-কে চূড়ান্ত ফল বলেঅনেক চেষ্টার সেরা; selection bias আর শব্দnested CV বা তালাবন্ধ test; কয়েকটি fold-seed
পুরোনো বা বানানো API (suggest_loguniform, অস্তিত্বহীন sampler বা argument)Optuna 5.0.0-এ এটি FutureWarning দেয় (v3.0.0-এ deprecated, v6.0.0-এ সরানোর কথা); বানানো নাম error দেয়নিজের installed version-এর docs; কোড চালিয়ে দেখা
একটি seed-এ BO বনাম random চালিয়ে বিজয়ী ঘোষণাপার্থক্য প্রায়ই seed আর CV-র শব্দের চেয়ে ছোটঅনেক seed, mean ± sd, একই প্রথম বিন্দু
EI-এর সূত্রে চিহ্ন উল্টায় (ছোট বনাম বড় করা)loop খারাপ দিকে খোঁজে, তবু চলেএক মাত্রার জানা function-এ ঘাটতি মেপে দেখা

AI-কে তৈরি উত্তর নয়, যাচাইযোগ্য পরিকল্পনা আর কোড চাইতে বলুন:

আমি বাজারঘরের churn ডেটায় HistGradientBoostingClassifier tune করতে চাই (scikit-learn 1.8.0, optuna-র যে version install আছে তা নিচে লিখছি)। প্রায় ২০,০০০ সারি, লক্ষ্য log loss ছোট করা, বাজেট ৪০টি মূল্যায়ন। ১. প্রথমে একটি তালাবন্ধ test আলাদা করো; objective শুধু বাকি অংশে 5-fold CV ফেরত দেবে। ২. search space দাও: learning_rate log স্কেলে, max_leaf_nodes, min_samples_leaf, l2_regularization; প্রতিটির পরিসর কেন, এক লাইনে বলো। ৩. একই বাজেটে random search আর TPE তুলনার কোড দাও, ৫টি seed-এ, mean ± sd সহ; n_startup_trials-এর মান আর তার প্রভাব ব্যাখ্যা করো। ৪. একই সেটিং আলাদা fold-seed-এ মাপলে loss কতটা নড়ে, তা মাপার কোডও দাও। ৫. যে প্রতিটি function আর argument ব্যবহার করছ, তা আমার version-এর docs-এ আছে কি না বলো; কোনো warning চাপা দিও না।

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

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

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

প্র ০১সোনার খনির ভূতত্ত্ব থেকে hyperparameter tuning — GP-ভিত্তিক optimization এই পথ কীভাবে পেরোল?

১৯৫০-এর দশকে দক্ষিণ আফ্রিকার খনি প্রকৌশলী Danie Krige কয়েকটি খননের নমুনা থেকে আশেপাশের আকরিকের মান অনুমানের পরিসংখ্যানিক পদ্ধতি নিয়ে কাজ করেন। ১৯৬০-এর দশকে ফরাসি গণিতবিদ Georges Matheron একে তত্ত্বে দাঁড় করান, আর তাঁর সম্মানে নাম হয় kriging. আজকের ভাষায় kriging হলো Gaussian process regression: কাছের নমুনা বেশি ওজন পায়, আর প্রতিটি অনুমানের সঙ্গে একটি অনিশ্চয়তা আসে। এই পর্বের নলকূপের উপমা তাই কাকতালীয় নয়।

অনুমান থেকে খোঁজে যাওয়ার ধারণা আসে আলাদা পথে। Harold Kushner ১৯৬৪ সালে শব্দযুক্ত এক মাত্রার function-এ উন্নতির সম্ভাবনা দিয়ে পরের বিন্দু বাছার প্রস্তাব দেন। Jonas Mockus ১৯৭০-এর দশকে Bayesian দৃষ্টিতে global optimization নিয়ে কাজ করেন, যেখানে প্রত্যাশিত উন্নতির ধারণা আসে। ১৯৯৮ সালে Jones, Schonlau আর Welch “Efficient Global Optimization of Expensive Black-Box Functions” প্রকাশ করেন: kriging surrogate আর EI-এর এই জোড়া (EGO) প্রকৌশল নকশায়, যেমন গাড়ি বা বিমানের অংশের simulation-এ, ব্যাপকভাবে ব্যবহৃত হতে থাকে, কারণ প্রতিটি simulation ঘণ্টার পর ঘণ্টা চলত।

ML-এ প্রবেশ ঘটে মূলত ২০১১–১২ সালে: Bergstra ও সহকর্মীদের TPE আর Snoek, Larochelle ও Adams-এর GP-ভিত্তিক কাজ দেখায় যে neural network-এর মতো দামি model-এর hyperparameter-এ এটি মানুষের হাতে বাছাইয়ের সমান বা ভালো ফল দিতে পারে। তারপর SMAC, Spearmint, Hyperopt, Optuna, BoTorch-এর মতো library আসে। ২০১৮ সালে Yutian Chen ও সহকর্মীরা একটি প্রতিবেদনে জানান, AlphaGo-র কিছু hyperparameter BO দিয়ে tune করা হয়েছিল।

ক্ষেত্র বদলেছে, কিন্তু মূল অবস্থা একই: মূল্যায়ন দামি, সূত্র অজানা, আর হাতে আছে কয়েকটি নমুনা আর একটি অনিশ্চয়তার মডেল।

মূল উপলব্ধি: BO নতুন আবিষ্কার নয়, খনি আর প্রকৌশলের পুরোনো সমস্যার সমাধান; ML শুধু আরেকটি “দামি খনন”-এর ক্ষেত্র।

প্র ০২Bayesian optimization কি সত্যিই random search-এর চেয়ে ভালো? প্রমাণ কী বলে?

উত্তর “নির্ভর করে”, আর কীসের ওপর নির্ভর করে তা বোঝাই আসল শিক্ষা। Bergstra আর Bengio (JMLR ২০১২) দেখান, random search grid-এর চেয়ে ভালো, কারণ গুরুত্বপূর্ণ মাত্রা অল্প। কিন্তু random search একটি শক্ত baseline, দুর্বল প্রতিদ্বন্দ্বী নয়। Hyperband-এর লেখকরা (Li ও সহকর্মীরা, ২০১৮) তাঁদের পরীক্ষায় দেখান, দ্বিগুণ বাজেটের random search অনেক সমস্যায় কয়েকটি BO পদ্ধতির কাছাকাছি ফল দেয়; অর্থাৎ বুদ্ধিমান বাছাইয়ের লাভ কখনো কখনো বাড়তি কয়েকটি চেষ্টার সমান মাত্র।

অন্যদিকে, NeurIPS ২০২০-এর black-box optimization প্রতিযোগিতার বিশ্লেষণে Ryan Turner ও সহকর্মীরা (২০২১) জানান, বাস্তব ML tuning সমস্যায় BO-ভিত্তিক জমাগুলো random search-এর চেয়ে স্পষ্টভাবে এগিয়ে ছিল। দুটি ফল পরস্পরবিরোধী নয়: সমস্যার আকার, বাজেট, মাত্রা আর শব্দ আলাদা।

এই পর্বের মাপ দুই প্রান্তই দেখায়। Forrester function-এ, এক মাত্রায়, মসৃণ, শব্দহীন, দশটি চেষ্টায় BO-র গড় ঘাটতি ০.১০ আর random-এর ১.২৩: বিশাল পার্থক্য। কিন্তু HistGradientBoosting-এর দুটি hyperparameter-এ ৫০০ সারিতে পার্থক্য ০.০০২৩ log loss, যেখানে CV-র ভাগ বদলালেই ০.০১৩ নড়ে। প্রথম ক্ষেত্রে surrogate function-কে ভালো ধরে; দ্বিতীয়টিতে loss-এর তল প্রায় সমতল আর শব্দে ঢাকা, তাই surrogate-এর শেখার কিছু নেই।

ব্যবহারিক নিয়ম: মূল্যায়ন যত দামি, বাজেট যত ছোট আর loss-এর তল যত মসৃণ, BO-র লাভ তত বেশি। মূল্যায়ন সস্তা আর অনেক machine থাকলে random search বা Hyperband সরল আর শক্ত। আর যেকোনো দাবি যাচাই করুন কয়েকটি seed-এ, একই শুরুর বিন্দু থেকে, mean ± sd দিয়ে।

মূল উপলব্ধি: BO-র লাভ সমস্যার বৈশিষ্ট্য; বিজ্ঞাপনের দাবি নয়, আপনার সমস্যায় কয়েকটি seed-এর মাপই প্রমাণ।

প্র ০৩Exploration বনাম exploitation — slot machine থেকে hyperparameter পর্যন্ত একই দ্বিধা কেন বারবার ফিরে আসে?

নতুন শহরে প্রথম সপ্তাহে একটি রেস্তোরাঁয় ভালো খেয়েছেন। এখন প্রতিদিন সেখানেই যাবেন (exploitation), নাকি মাঝে মাঝে নতুন জায়গা চেষ্টা করবেন (exploration)? শুধু পুরোনোয় থাকলে হয়তো শহরের সেরা দোকানটি কখনো পাবেন না; শুধু নতুনে ঘুরলে জানা ভালো খাবারও খাবেন না। গণিতে এটি multi-armed bandit সমস্যা: কয়েকটি slot machine, প্রত্যেকটির অজানা গড় পুরস্কার, সীমিত চেষ্টা।

William Thompson ১৯৩৩ সালে চিকিৎসার দুটি পদ্ধতি তুলনার প্রেক্ষাপটে একটি সমাধান দেন: প্রতিটির সাফল্যের সম্ভাবনা নিয়ে posterior রাখুন, প্রতিবার posterior থেকে একটি নমুনা টেনে যেটির নমুনা ভালো, সেটি বাছুন। এটিই আজকের Thompson sampling. পরে Peter Auer ও সহকর্মীরা (২০০২) UCB নিয়ম বিশ্লেষণ করেন: গড়ের সঙ্গে একটি অনিশ্চয়তার বোনাস যোগ করে আশাবাদী বাছাই, যা কম চেষ্টা পাওয়া বিকল্পকে বেশি সুযোগ দেয়।

Bayesian optimization হলো অসীম সংখ্যক slot machine-এর bandit, যেখানে পাশাপাশি machine-এর পুরস্কার সম্পর্কিত, আর সেই সম্পর্ক GP-র kernel ধরে। Srinivas ও সহকর্মীরা (২০১০) GP-UCB-এর জন্য দেখান, κ-কে চেষ্টার সংখ্যার সঙ্গে ধীরে (κ²-কে মোটামুটি লগারিদমের হারে) বাড়ালে মোট regret কতটা ধীরে বাড়ে তার সীমা দেওয়া যায়। ব্যবহারে অবশ্য অনেকেই κ ২-এর কাছাকাছি স্থির রাখেন, তাত্ত্বিক মান প্রায়ই খুব বেশি খোঁজে।

এই পর্বের LCB-এর মাপ ঠিক এই দ্বিধা দেখায়: κ = ০ (শুধু গড়) ২০-এর মধ্যে ১২ বার খাদ খুঁজে পায়, বাকি সময় বাঁয়ের ফাঁদে আটকায়; κ = ৫ এত বেশি ঘোরে যে মাত্র ৩ বার খাদের তলায় পৌঁছায়। একই দ্বিধা reinforcement learning-এ, recommender system-এ নতুন পণ্য দেখানোয়, আর A/B test-এ ফিরে আসে।

মূল উপলব্ধি: সীমিত চেষ্টায় শেখা মানেই কিছু চেষ্টা অজানার জন্য রাখা; অনিশ্চয়তার মাপ থাকলে সেই ভাগটি নীতিগতভাবে ঠিক করা যায়।

প্র ০৪GP-ভিত্তিক BO কেন অনেক মাত্রা, category আর শর্তসাপেক্ষ hyperparameter-এ হোঁচট খায়, আর বিকল্পগুলো কী করে?

GP-র শক্তি তার kernel: “কাছের দুই বিন্দুর মান কাছাকাছি”। কিন্তু অনেক মাত্রায় “কাছে” শব্দটি দুর্বল হয়। কুড়ি মাত্রার একক ঘনকে কয়েক ডজন বিন্দু প্রায় ফাঁকা জায়গায় ছড়িয়ে থাকে, প্রায় সব নতুন বিন্দু সব মাপা বিন্দু থেকে দূরে, তাই σ প্রায় সর্বত্র বড় আর EI প্রায় সর্বত্র একই রকম। প্রতিটি মাত্রার জন্য আলাদা length-scale (ARD) শিখতে আবার অনেক বিন্দু লাগে। acquisition-কে সর্বোচ্চ করাও কঠিন: এই পর্বে এক মাত্রায় ৪০১টি বিন্দুর grid যথেষ্ট ছিল, কুড়ি মাত্রায় তা অসম্ভব।

Category আরেকটি সমস্যা। “optimizer = adam” আর “optimizer = sgd”-এর মধ্যে দূরত্ব কত? one-hot করে দূরত্ব বসানো যায়, কিন্তু kernel তখন কৃত্রিম জ্যামিতি ধরে। শর্তসাপেক্ষ space আরও কঠিন: “model = SVM হলে C আর gamma, model = forest হলে গাছের সংখ্যা”; একটি বিন্দুতে কিছু মাত্রার অস্তিত্বই নেই।

বিকল্পগুলো এখান থেকেই জন্মায়। SMAC random forest-কে surrogate করে, যা category আর শর্ত স্বাভাবিকভাবে নেয়। TPE প্রতিটি hyperparameter-এর ঘনত্ব আলাদা আর গাছের মতো শর্ত মেনে আন্দাজ করে, যা থেকে তার নামের “tree-structured”. TuRBO (Eriksson ও সহকর্মীরা, ২০১৯) পুরো space-এ একটি GP-র বদলে সেরা বিন্দুর চারপাশে ছোট একটি “trust region”-এ স্থানীয় GP চালায়। আরেকটি পথ মাত্রা কমানো: অনেক সমস্যায় কয়েকটি hyperparameter-ই আসল (Bergstra আর Bengio-র কম “কার্যকর মাত্রা”), তাই আগে sensitivity দেখে space ছোট করা।

ব্যবহারিক অর্থ: দুই থেকে দশটি সংখ্যার hyperparameter আর দামি মূল্যায়নে GP-BO ভালো পছন্দ; অনেক category বা শর্তে TPE বা SMAC; খুব বড় space-এ প্রথমে space ছোট করা।

মূল উপলব্ধি: surrogate-এর ধারণা যেখানে সমস্যার গঠনের সঙ্গে মেলে, BO সেখানেই জেতে; মাত্রা আর category সেই মিল ভেঙে দেয়।

প্র ০৫কম ডেটা বা কম epoch-এর মূল্যায়ন দিয়ে ছাঁটাই কতটা নিরাপদ? Learning curve কাটাকাটি করলে কী হয়?

Successive halving একটি অনুমানের ওপর দাঁড়িয়ে: কম সম্পদে যে ভালো, বেশি সম্পদেও সে মোটামুটি ভালো। প্রায়ই অনুমানটি চলে, কারণ খুব খারাপ সেটিং (অতি বড় learning rate-এ diverge করা, অতি ছোট model) শুরুতেই চেনা যায়। কিন্তু learning curve কাটাকাটি করলে অনুমান ভাঙে। ছোট learning rate শুরুতে ধীরে নামে, পরে সবচেয়ে নিচে পৌঁছায়; বড় learning rate শুরুতে দ্রুত নামে, পরে থেমে যায়। দশ iteration-এ মাপলে দ্বিতীয়টি জেতে, তিনশো iteration-এ প্রথমটি।

এই পর্বের ডেস্কটপ run ঠিক এটাই দেখায়। ২৪,০০০ সারিতে iteration-কে সম্পদ ধরে halving তিন গুণের বেশি দ্রুত, কিন্তু তিনটি seed-এ তার বিজয়ীর log loss ০.৪৭২১, ০.৪৭৫৭, ০.৪৯২৮, যেখানে একই ২৭টি candidate পুরো ২৭০ iteration-এ চালালে সেরা প্রায় ০.৪৬১. ছোট ডেটার run-এ halving-এর বিজয়ী ২৭টির মধ্যে ২য় থেকে ১৪তম। সাশ্রয় সত্যি, ঝুঁকিও সত্যি।

গবেষণা কয়েকভাবে এর উত্তর দেয়। Hyperband (Li ও সহকর্মীরা, ২০১৮) একাধিক bracket চালায়, যার কোনোটি অল্প candidate-কে শুরু থেকেই বেশি সম্পদ দেয়, তাই কম সম্পদের র‍্যাঙ্ক ভুল হলেও সব ডিম এক ঝুড়িতে থাকে না। ASHA (Li ও সহকর্মীরা, MLSys ২০২০) halving-কে অসমলয় (asynchronous) করে অনেক machine-এ চালায়। BOHB কম আর বেশি সম্পদের তথ্য একসঙ্গে model-এ নেয়। আরেকটি পথ হলো learning curve-এর আকার অনুমান করে ভবিষ্যৎ মান আন্দাজ।

ব্যবহারে: প্রথম ধাপের সম্পদ এত কম রাখবেন না যে সব candidate-ই সমান খারাপ দেখায় (৪০ সারিতে সবার log loss ০.৬-এর ওপরে ছিল); learning rate আর iteration একসঙ্গে tune করলে iteration-কে সম্পদ করার আগে ভাবুন; আর শেষ বিজয়ীকে পুরো সম্পদে, নতুন fold-এ আবার মাপুন।

মূল উপলব্ধি: সস্তা মাপ খারাপকে ছাঁটতে ভালো, সেরাকে চিনতে দুর্বল; multi-fidelity পদ্ধতির নকশা এই দুইয়ের মধ্যে ভারসাম্যের খেলা।

অনুশীলন

হিসাবগুলো খাতায় করুন, তারপর উত্তর মেলান। চাইলে পাতার ব্লক বদলে যাচাই করুন।

  1. পূর্ণ numerical: একটি ছোট করার সমস্যায় এ পর্যন্ত সেরা মান f* = ০.৪০, ξ = ০. দুটি candidate-এ GP-র অনুমান: C (μ = ০.৩৫, σ = ০.১০) আর D (μ = ০.৪৫, σ = ০.২৫)। Φ(০.৫) = ০.৬৯১৫, φ(০.৫) = ০.৩৫২১, Φ(−০.২) = ০.৪২০৭, φ(−০.২) = ০.৩৯১০. (ক) প্রতিটির PI আর EI. (খ) κ = ২-এ LCB. (গ) তিন নিয়ম কোনটি বাছে?

    (ক) C: z = (০.৪০ − ০.৩৫)/০.১০ = ০.৫; PI = ০.৬৯১৫; EI = ০.১০ × (০.৫ × ০.৬৯১৫ + ০.৩৫২১) = ০.১০ × ০.৬৯৭৯ ≈ ০.০৬৯৮. D: z = (০.৪০ − ০.৪৫)/০.২৫ = −০.২; PI = ০.৪২০৭; EI = ০.২৫ × (−০.২ × ০.৪২০৭ + ০.৩৯১০) = ০.২৫ × ০.৩০৬৯ ≈ ০.০৭৬৭.

    (খ) C: ০.৩৫ − ২ × ০.১০ = ০.১৫. D: ০.৪৫ − ২ × ০.২৫ = −০.০৫.

    (গ) PI বাছে C (০.৬৯ > ০.৪২)। EI বাছে D (০.০৭৬৭ > ০.০৬৯৮), কারণ D-এর বড় σ বড় লাভের সম্ভাবনা আনে। LCB-ও D বাছে (−০.০৫ < ০.১৫)। গড়ে ভালো C-কে শুধু PI পছন্দ করে।

  2. সমালোচনা ও নকশা: একজন সহকর্মী লিখেছেন: “৩০টি hyperparameter (১২টি category, কিছু শর্তসাপেক্ষ) GP-ভিত্তিক BO দিয়ে tune করেছি। Objective test set-এর accuracy. ৮টি GPU-তে একই সময়ে ৮টি job, প্রতিটি একই EI-এর সর্বোচ্চ বিন্দু নেয়। learning rate ০.০০০১ থেকে ১ সমান স্কেলে। ৫০টি trial-এর সেরা accuracy ০.৯১৩ রিপোর্ট করেছি।” কী কী সমস্যা, আর ভালো নকশা কী?

    সমস্যা: (১) Objective-এ test set: test আর তালাবন্ধ নেই, ০.৯১৩ আশাবাদী। (২) ৩০ মাত্রা, category আর শর্তে GP দুর্বল। (৩) আটটি job একই বিন্দু নিলে আটবার একই মূল্যায়ন: সাতটি অপচয়। (৪) learning rate সমান স্কেলে খুঁজলে প্রায় সব চেষ্টা ০.১-এর ওপরে পড়ে, ছোট মান প্রায় খোঁজাই হয় না। (৫) ৫০টির সেরাকে রিপোর্ট করা selection bias. (৬) কোনো baseline বা seed-ভেদে তুলনা নেই।

    ভালো নকশা: আগে তালাবন্ধ test আলাদা; objective-এ train অংশের CV. গুরুত্বপূর্ণ কয়েকটি hyperparameter বাছাই করে space ছোট; learning rate log স্কেলে। Category আর শর্তের জন্য TPE বা SMAC. Parallel-এর জন্য constant liar বা TPE-র parallel বিকল্প, বা Hyperband/ASHA. একই বাজেটে random search baseline, কয়েকটি seed. শেষে বিজয়ী nested CV বা test-এ একবার।

  3. আউটপুট অনুমান করুন: চালানোর আগে লিখে ফেলুন নিচের কোড কী ছাপবে।
    from scipy.stats import norm
    best = 0.50
    for mu, sd in [(0.49, 0.001), (0.49, 0.05), (0.55, 0.05)]:
        z = (best - mu) / sd
        print(round(norm.cdf(z), 3), round((best - mu) * norm.cdf(z) + sd * norm.pdf(z), 4))
    1.0 0.01
    0.579 0.0253
    0.159 0.0042

    প্রথমটিতে z = ১০: উন্নতি প্রায় নিশ্চিত (PI = ১.০), কিন্তু EI মাত্র ০.০১, অর্থাৎ গড়ের লাভটুকুই। দ্বিতীয়টিতে একই গড়, বড় σ: z = ০.২, PI কমে ০.৫৭৯, কিন্তু EI বেড়ে ০.০২৫৩ = ০.০১ × ০.৫৭৯৩ + ০.০৫ × ০.৩৯১০. তৃতীয়টির গড় খারাপ (z = −১), তবু EI শূন্য নয়, ০.০০৪২. PI প্রথমটি বাছবে, EI দ্বিতীয়টি। (ডেস্কটপে scipy 1.18.1-এ চালিয়ে মেলানো।)

  4. কোড লিখুন: অংশ ৪-এর Forrester function-এ EI-এর বদলে LCB = μ − κσ দিয়ে BO চালান (৩টি এলোমেলো শুরু, মোট ১০টি মূল্যায়ন, kernel Matern(0.1, nu=2.5) স্থির)। κ = ০, ১, ২, ৫-এর প্রতিটিতে ২০টি seed-এ গড় ঘাটতি আর কতবার ঘাটতি ০.১-এর কম, তা বের করুন।
    import numpy as np
    from sklearn.gaussian_process import GaussianProcessRegressor
    from sklearn.gaussian_process.kernels import Matern
    
    f = lambda x: (6 * x - 2) ** 2 * np.sin(12 * x - 4)
    grid = np.linspace(0, 1, 401)
    def run(kappa, seed, budget=10):
        rng = np.random.default_rng(seed)
        X = rng.uniform(0, 1, 3); y = f(X)
        while len(X) < budget:
            gp = GaussianProcessRegressor(Matern(0.1, nu=2.5), alpha=1e-6,
                                          normalize_y=True, optimizer=None).fit(X[:, None], y)
            mu, sd = gp.predict(grid[:, None], return_std=True)
            x = grid[np.argmin(mu - kappa * sd)]          # LCB: ছোটটি বাছা
            X = np.append(X, x); y = np.append(y, f(x))
        return y.min() + 6.0207
    for kappa in (0, 1, 2, 5):
        gaps = np.array([run(kappa, s) for s in range(20)])
        print(f"kappa = {kappa}: mean gap {gaps.mean():.2f}, found the global dip (gap < 0.1) in {(gaps < 0.1).sum()}/20 seeds")

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

    kappa = 0: mean gap 2.18, found the global dip (gap < 0.1) in 12/20 seeds
    kappa = 1: mean gap 0.58, found the global dip (gap < 0.1) in 16/20 seeds
    kappa = 2: mean gap 0.12, found the global dip (gap < 0.1) in 15/20 seeds
    kappa = 5: mean gap 0.40, found the global dip (gap < 0.1) in 3/20 seeds

    κ = ০ গড় দেখেই বাছে, তাই ২০-এর ৮টি seed-এ বাঁয়ের অগভীর ফাঁদে আটকে থাকে (গড় ঘাটতি ২.১৮)। κ = ১ আর ২ সবচেয়ে ভালো। κ = ৫ খাদ প্রায় খুঁজে পায় (গড় ঘাটতি ০.৪০), কিন্তু তলায় না নেমে অনিশ্চিত জায়গা ঘুরে বেড়ায়, তাই মাত্র ৩ বার ০.১-এর মধ্যে। দুই চরমই দুর্বল; মাঝারি κ নির্ভরযোগ্য।

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

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

ক্রমBayesian optimization-এর ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Search space ঠিক করা (যেমন learning rate log স্কেলে)
  2. কয়েকটি এলোমেলো বিন্দুতে দামি মূল্যায়ন
  3. মাপা বিন্দুতে surrogate (GP) শেখানো
  4. Acquisition function সর্বোচ্চ এমন বিন্দু খোঁজা
  5. সেই বিন্দুতে দামি মূল্যায়ন করে ডেটায় যোগ
  6. বাজেট শেষে সেরা বিন্দু তালাবন্ধ test-এ একবার মাপা

একটি loss ছোট করার সমস্যায় এ পর্যন্ত সেরা মান f* = ০.৫০. একটি বিন্দুতে surrogate-এর অনুমান μ = ০.৪০, σ = ০.১০ (ξ = ০)। Φ(১) = ০.৮৪১৩, φ(১) = ০.২৪২০ হলে expected improvement কত?

  • ০.১০০০
  • প্রায় ০.১০৮৩
  • ০.৮৪১৩
  • ০.০২৪২

Probability of improvement (PI) দিয়ে পরের বিন্দু বাছলে প্রায়ই কী সমস্যা দেখা যায়?

  • PI-এর মান অসীম হয়ে যায়
  • PI অনিশ্চয়তা একেবারে উপেক্ষা করে
  • সেরা বিন্দুর গা-ঘেঁষে নিশ্চিত কিন্তু খুব ছোট উন্নতির দিকে ঝোঁকে
  • শুধু random forest surrogate-এর সঙ্গে চলে

ছোট করার সমস্যায় confidence bound LCB = μ − κσ ব্যবহার করা হচ্ছে। κ ০ থেকে বাড়িয়ে ৩ করলে বাছাই কোন দিকে সরে?

  • বেশি অনিশ্চিত, কম খোঁজা জায়গার দিকে (exploration)
  • শুধু সবচেয়ে ভালো গড়ের দিকে (exploitation)
  • কোনো পরিবর্তন হয় না
  • বাছাই সম্পূর্ণ এলোমেলো হয়ে যায়

HalvingRandomSearchCV-তে ২৭টি candidate, factor = ৩, প্রথম ধাপে ৪০ সারি, আর ধাপ চারটি। শেষ ধাপে কয়টি candidate আর প্রতিটি কত সারিতে?

  • ৩টি, ৩৬০ সারিতে
  • ১টি, ৩২০ সারিতে
  • ১টি, ৩,২৪০ সারিতে
  • ১টি, ১,০৮০ সারিতে

একই hyperparameter সেটিং ১০টি আলাদা ৩-fold ভাগে মাপলে log loss-এর sd ০.০১৩. দুটি tuning পদ্ধতির গড় সেরা মানের পার্থক্য ০.০০২. সবচেয়ে যুক্তিসঙ্গত সিদ্ধান্ত কোনটি?

  • ছোট মানের পদ্ধতিটি স্পষ্টভাবে ভালো
  • পার্থক্য শব্দের ভেতরে; আরও seed আর আবার মাপা ছাড়া কিছু বলা যায় না
  • CV অর্থহীন, train loss দিয়ে বাছাই করা উচিত
  • Test set-এ দুটি পদ্ধতি চালিয়ে যেটি ভালো সেটি নেওয়া

Optuna-র TPE sampler পরের trial কীভাবে বাছে?

  • একটি Gaussian process দিয়ে প্রতিটি বিন্দুর loss অনুমান করে
  • সবসময় সম্পূর্ণ এলোমেলোভাবে
  • ভালো আর খারাপ trial-এর ঘনত্বের অনুপাত l(x)/g(x) সর্বোচ্চ এমন বিন্দু
  • একটি নির্দিষ্ট grid-এর ক্রমে

শব্দকোষ

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

মনে রাখুন

  1. অবস্থা: CV loss একটি দামি, শব্দযুক্ত black-box function; grid আর random search আগের ফল দেখে না, BO দেখে।
  2. দুই উপাদান: surrogate (GP: গড় μ আর অনিশ্চয়তা σ) আর acquisition (PI, EI, LCB/UCB); সস্তা ধাপে বুদ্ধি, দামি ধাপ একবার।
  3. EI = σ(zΦ(z) + φ(z)), z = (f* − ξ − μ)/σ; PI শুধু সম্ভাবনা, EI লাভের আকারও; LCB-তে κ খোঁজের হাতল।
  4. সৎ তুলনা: জানা function-এ BO বিশাল এগিয়ে; বাস্তব ছোট tuning-এ পার্থক্য CV-র শব্দের চেয়েও ছোট হতে পারে। অনেক seed, একই শুরু, mean ± sd.
  5. আরও উপায়: TPE ঘনত্বের অনুপাতে বাছে, category আর শর্তে সুবিধা; successive halving আর Hyperband সস্তা মাপে ছাঁটে, কিন্তু কম সম্পদের র‍্যাঙ্ক সবসময় সত্যি নয়।

পরের পর্বে: মডিউল ৯ এখানে শেষ। পর্ব ৫০ (Imbalanced ডেটা — resampling, SMOTE, class weight আর threshold) দিয়ে মডিউল ১০ শুরু: যখন churn বা প্রতারণার মতো ঘটনা হাজারে কয়েকটি, তখন accuracy কেন মিথ্যা বলে আর কী করবেন।

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

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