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

Gradient descent-এর পরিবার — batch, SGD, mini-batch আর learning-rate schedule

The Gradient Descent Family — Batch, Stochastic and Mini-batch Gradients, Step Sizes, the Condition Number and Learning-rate Schedules

Taught in Bangla, this lesson analyses batch gradient descent on a quadratic bowl direction by direction, deriving the step limit η < 2/L and the role of the condition number. It shows SGD and mini-batch gradients as unbiased but noisy, the constant-step noise floor and Robbins–Monro conditions, compares step, exponential and cosine schedules with warm-up, and checks feature scaling with scikit-learn.

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

  • Quadratic loss-এ batch gradient descent কেন আর কত দ্রুত নামে, আর condition number κ কীভাবে সেই গতি ঠিক করে
  • ধাপের মাপের সীমা η < 2/L, সেরা η, zig-zag পথ, আর line search কেন দরকার হতে পারে
  • SGD আর mini-batch gradient কেন নিরপেক্ষ অনুমান, তার variance batch size-এর সঙ্গে কীভাবে কমে, আর ধ্রুব η-তে কেন একটি “মেঝে” থাকে
  • Step, exponential, cosine আর warm-up schedule নিজে লিখে একই সমস্যায় তুলনা করা
  • Feature scaling-এ κ কমানো, convergence-এর লক্ষণ পড়া, আর scikit-learn-এর SGD দিয়ে ফল যাচাই

AI-এর ভিত্তি কোর্সের পর্ব ৩৪-এ gradient descent-এর নিয়ম, learning rate-এর প্রভাব আর SGD-এর ধারণা এসেছে; পর্ব ৫৮-এ সেটি দিয়ে রেখা fit করেছেন। এই কোর্সের পর্ব ১১-এ gradient আর Hessian matrix আকারে derive করেছেন, আর পর্ব ১০-এ quadratic form-এর বাটির আকার দেখেছেন। এই পর্বের প্রশ্ন: gradient descent ঠিক কত দ্রুত নামে, কখন ভেঙে পড়ে, আর বাস্তবের বড় ডেটায় কোন রূপটি কেন চলে? চালানো যায় এমন চারটি ব্লক পাতাতেই চলে (ব্রাউজারের Python, NumPy, matplotlib আর scikit-learn); প্রথমবার প্যাকেজ নামতে কয়েক সেকেন্ড লাগে, scikit-learn-এ প্রায় দশ সেকেন্ড।

১ · একটি পরিবার, তিনটি সদস্য — প্রতি ধাপে কতটা ডেটা দেখবেন

ঢাকার বাজারে চালের গড় দাম জানতে চান। এক উপায়: শহরের ১,০০০টি দোকানেই খোঁজ নেওয়া। উত্তর নির্ভুল, কিন্তু একবার খোঁজ নিতেই সারাদিন। আরেক উপায়: পাশের একটি দোকানে জিজ্ঞেস করা। মুহূর্তে উত্তর, কিন্তু সেই দোকান হয়তো একটু বেশি দাম রাখে। মাঝের উপায়: এলোমেলোভাবে ১৬টি দোকান। উত্তর প্রায় নির্ভুল, সময়ও কম। Gradient descent-এর তিন রূপ ঠিক এই তিনটি জরিপ: gradient হলো “গড় দাম”, আর প্রতিটি উদাহরণ একটি দোকান।

ML-এর প্রায় সব loss একটি গড়: L(w) = (1/N) ∑i ℓi(w), যেখানে N উদাহরণের সংখ্যা আর ℓi হলো i-তম উদাহরণের নিজের loss. তাই gradient-ও একটি গড়: ∇L(w) = (1/N) ∑i ∇ℓi(w). Gradient descent-এর নিয়ম w ← w − η∇L(w), যেখানে η (ইটা) হলো learning rate বা ধাপের মাপ। পুরো গড় না নিয়ে কয়েকটি উদাহরণের গড় দিয়ে কাজ চালালে পাওয়া যায় পরিবারের বাকি সদস্যদের:

রূপপ্রতি ধাপে উদাহরণ (B)এক epoch-এ ধাপGradient-এর মানকোথায় চলে
Batch GDসব, B = N১নির্ভুলছোট ডেটা, তত্ত্ব, L-BFGS-এর মতো পদ্ধতির ভেতরে
Mini-batch SGDসাধারণত ১৬ থেকে কয়েক হাজারN/Bনিরপেক্ষ, অল্প noiseDeep learning-সহ প্রায় সব বড় training
SGD (খাঁটি)১Nনিরপেক্ষ, অনেক noiseOnline learning, scikit-learn-এর SGDRegressor

এখানে epoch মানে পুরো ডেটা একবার দেখা, আর batch size B মানে প্রতি ধাপে কয়টি উদাহরণ। বাস্তবে “SGD” নামটি প্রায়ই mini-batch রূপকেই বোঝায়; এই পাতায় B আলাদা করে বলা থাকবে। নিচের ছবি ১৬টি উদাহরণের একটি ছোট ডেটায় তিন রূপের পার্থক্য দেখায়।

এক epoch-এ ১৬টি উদাহরণ কীভাবে ভাগ হয়

Batch GD uses all 16 examples per step; mini-batch (B = 4) makes 4 steps per epoch; SGD (B = 1) makes 16 noisy steps

How one epoch of 16 examples is split by batch GD, mini-batch and SGD Batch GD (B = 16) ১ epoch = ১টি ধাপ নির্ভুল, কিন্তু দামি Mini-batch (B = 4) ১ epoch = ৪টি ধাপ অল্প noise SGD (B = 1) ১ epoch = ১৬টি ধাপ সস্তা, অনেক noise
ছবি ১.১ — একই রঙের বর্গগুলো একটি ধাপে একসঙ্গে ব্যবহার হয়। বাস্তবে প্রতি epoch-এর শুরুতে উদাহরণগুলো এলোমেলো করে নেওয়া হয়, তাই কোন উদাহরণ কোন দলে পড়বে তা প্রতি epoch-এ বদলায়।

সবচেয়ে খাড়া ঢালে নামার ধারণাটি সাধারণত Augustin-Louis Cauchy-র ১৮৪৭ সালের একটি ছোট লেখার সঙ্গে যুক্ত করা হয়, যেখানে তিনি সমীকরণের সমাধান খুঁজতে এই পদ্ধতি প্রস্তাব করেন। এলোমেলো, কোলাহলপূর্ণ পরিমাপ দিয়ে ধাপে ধাপে লক্ষ্যে পৌঁছানোর তত্ত্ব আসে ১৯৫১ সালে Herbert Robbins আর Sutton Monro-র stochastic approximation প্রবন্ধে; আধুনিক SGD-এর গাণিতিক শিকড় সেখানেই। বড় ডেটার ML-এ SGD কেন যুক্তিসঙ্গত, তার একটি পরিচিত ব্যাখ্যা দেন Léon Bottou আর Olivier Bousquet (২০০৭-২০০৮)।

২ · Quadratic বাটিতে batch GD — প্রতিটি eigen দিকের আলাদা হিসাব

Gradient descent-এর আচরণ বোঝার সবচেয়ে পরিষ্কার পরীক্ষাগার হলো quadratic loss: f(x) = ½ xᵀAx − bᵀx, যেখানে A প্রতিসম আর positive definite. এটি শুধু খেলনা নয়: least squares-এর loss হুবহু এই আকারের (A = XᵀX/N), আর যেকোনো মসৃণ loss সর্বনিম্ন বিন্দুর কাছে দ্বিতীয় ক্রমের Taylor approximation-এ এমনই দেখায়, যেখানে A হলো Hessian. পর্ব ১১-এর সূত্রে gradient ∇f(x) = Ax − b, আর সর্বনিম্ন বিন্দু x* মেনে চলে Ax* = b.

এবার ভুল ek = xk − x* কীভাবে বদলায় দেখি। এক ধাপে xk+1 = xk − η(Axk − b). দুই দিক থেকে x* বাদ দিয়ে Ax* = b বসালে:

ek+1 = (I − ηA) ek

A-এর eigenvector-গুলো পরস্পর লম্ব (পর্ব ০৮-এর spectral theorem অনুযায়ী)। ভুলকে সেই দিকগুলোতে ভাঙলে প্রতিটি অংশ আলাদাভাবে চলে: λi eigenvalue-এর দিকে ভুল প্রতি ধাপে (1 − ηλi) দিয়ে গুণ হয়। সবচেয়ে ছোট eigenvalue-কে μ (মিউ) আর সবচেয়ে বড়টিকে L বলি; L হলো বাটির সবচেয়ে খাড়া বাঁক, μ সবচেয়ে চ্যাপ্টা। এ থেকে তিনটি সিদ্ধান্ত সরাসরি আসে:

  • স্থিতির শর্ত: সব দিকে |1 − ηλi| < 1 হতে হবে, অর্থাৎ 0 < η < 2/L. η = 2/L পেরোলেই খাড়া দিকের ভুল প্রতি ধাপে বাড়ে, আর loss অসীমের দিকে যায়।
  • গতি: প্রতি ধাপে ভুল অন্তত max(|1 − ημ|, |1 − ηL|) গুণ কমে। η = 1/L নিলে হার 1 − 1/κ, যেখানে κ = L/μ হলো condition number.
  • সেরা ধ্রুব η: দুই প্রান্তের ক্ষতি সমান করলে η = 2/(L + μ), আর তখন হার (κ − 1)/(κ + 1). ভুলকে ε (এপসাইলন) গুণে কমাতে মোটামুটি (κ/2)·ln(1/ε) ধাপ লাগে; η = 1/L-এ তার প্রায় দ্বিগুণ।

এখানে κ মাপে বাটিটি কতটা লম্বাটে। κ = ১ মানে গোল বাটি, প্রতিটি দিকে একই বাঁক, আর gradient সোজা কেন্দ্রের দিকে দেখায়। κ বড় মানে সরু, লম্বা উপত্যকা: একই η খাড়া দিকের জন্য প্রায় খুব বড় আর চ্যাপ্টা দিকের জন্য খুব ছোট। এমন সমস্যাকে ill-conditioned বলে। নিচের stepper একটি ছোট 2-D বাটিতে এক ধাপ হাতে হিসাব করে।

A = diag(1, 10): μ = 1, L = 10, κ = 10, তাই 2/L = 0.2
ধাপ
x
হিসাব
f(x)
  1. বাটি আর শুরু। A = diag(১, ১০), অর্থাৎ x₂-এর দিকে বাটি দশ গুণ খাড়া। μ = ১, L = ১০, κ = ১০. শুরুর বিন্দু (২, ১)-এ loss ½(১·৪ + ১০·১) = ৭. স্থিতির সীমা 2/L = ০.২.
  2. Gradient. ∇f = Ax = (২, ১০). খাড়া দিকের ঘর পাঁচ গুণ বড়, তাই gradient কেন্দ্রের দিকে নয়, প্রায় পুরোপুরি x₂-এর দিকে দেখায়।
  3. এক ধাপ, η = ০.১৫. নতুন x = (২ − ০.৩, ১ − ১.৫) = (১.৭, −০.৫). x₂ শূন্য পেরিয়ে উল্টো দিকে চলে গেছে, তবু loss ৭ থেকে ২.৬৯৫-এ নেমেছে।
  4. প্রতিটি দিকের গুণক। চ্যাপ্টা দিকে 1 − ηλ = ০.৮৫: ভুল ধীরে, একই দিকে কমে। খাড়া দিকে −০.৫: ভুল অর্ধেক হয়, কিন্তু প্রতি ধাপে চিহ্ন বদলায়। এই চিহ্ন বদলই ছবিতে zig-zag পথ বানায়।
  5. সীমা পেরোলে। η = ০.২৫ > 2/L. খাড়া দিকের গুণক 1 − ২.৫ = −১.৫, তাই x₂ প্রতি ধাপে দেড় গুণ বড় হয়ে চিহ্ন বদলায়: এক ধাপেই loss ১২.৩৭৫, দশ ধাপে x₂ ≈ ৫৭.৭ আর loss প্রায় ১৬,৬২৬. চ্যাপ্টা দিক (গুণক ০.৭৫) তখনও ঠিকঠাক কমছে, কিন্তু তাতে কিছু যায় আসে না।
  6. সেরা ধ্রুব η. η = ২/১১ ≈ ০.১৮১৮-এ দুই দিকের গুণক +০.৮১৮ আর −০.৮১৮: সবচেয়ে খারাপ দিকের হার যত কম হতে পারে, ঠিক (κ − ১)/(κ + ১) = ৯/১১. মজার ব্যাপার: এই শুরু থেকে দশ ধাপ পরে η = ০.১৫ (loss ০.০৭৭৫) এর চেয়ে ভালো করেছে (০.১২৬৫), কারণ “সেরা” η সবচেয়ে খারাপ শুরুর জন্য সেরা, প্রতিটি শুরুর জন্য নয়।

৩ · ধাপের মাপ আর condition number — η < 2/L, zig-zag আর κ-এর অনুপাতে ধাপ

তত্ত্ব বলছে ধাপের সংখ্যা κ-এর অনুপাতে বাড়ে। নিচের ব্লক তা মাপে: একটি 2-D বাটি, μ = ১ আর L = κ, ৩০° ঘোরানো যাতে অক্ষ বরাবর না থাকে। শুরু (−৪, ৩), থামা যখন ‖x − x*‖ < 10−6. প্রতিটি κ-তে দুটি η: 1/L আর 2/(L + μ)। তারপর L = ২০-এর বাটিতে η-কে 2/L = ০.১-এর ঠিক নিচে আর ঠিক ওপরে রেখে দেখে, আর শেষে দুটি বাটিতে প্রথম ৩০ ধাপের পথ আঁকে।

import numpy as np
import matplotlib.pyplot as plt

def bowl(kappa, angle=np.pi / 6):          # f(x) = ½ xᵀAx, eigenvalue μ = 1, L = κ
    c, s = np.cos(angle), np.sin(angle)
    R = np.array([[c, -s], [s, c]])
    return R @ np.diag([1.0, kappa]) @ R.T

def gd(A, eta, steps=20000, tol=1e-6):     # batch GD: x ← x − η∇f(x), ∇f = Ax
    x, path = np.array([-4.0, 3.0]), []         # শুরু x0 = (−4, 3)
    for k in range(steps):
        path.append(x)
        if np.linalg.norm(x) < tol or np.linalg.norm(x) > 1e8:
            break
        x = x - eta * (A @ x)
    return np.array(path), k

print("kappa  iters(eta=1/L)  iters(eta=2/(L+mu))  rate (k-1)/(k+1)")
for kappa in (1, 2, 10, 50, 200):
    A = bowl(kappa)
    k1, k2 = gd(A, 1 / kappa)[1], gd(A, 2 / (kappa + 1))[1]
    print(f"{kappa:5} {k1:15} {k2:20} {(kappa - 1) / (kappa + 1):17.3f}")
A = bowl(20)                                # L = 20, তাই 2/L = 0.1
for eta in (0.099, 0.101):
    print(f"eta = {eta}: |x| after 200 steps = {np.linalg.norm(gd(A, eta, 201)[0][-1]):.3g}")
g = np.linspace(-5, 5, 201); X1, X2 = np.meshgrid(g, g)
fig, axs = plt.subplots(1, 2, figsize=(8, 3.8), layout="constrained")
for ax, kappa in zip(axs, (2, 20)):
    A = bowl(kappa); p = gd(A, 2 / (kappa + 1), steps=30)[0]
    F = 0.5 * (A[0, 0] * X1**2 + 2 * A[0, 1] * X1 * X2 + A[1, 1] * X2**2)
    ax.contour(X1, X2, F, levels=np.geomspace(0.05, 150, 10), cmap="viridis")
    ax.plot(p[:, 0], p[:, 1], "o-", color="C3", ms=3, lw=1.2)
    ax.set_title(f"kappa = {kappa}: first 30 GD steps"); ax.set_aspect("equal")
    ax.set_xlabel("x1"); ax.set_ylabel("x2")
plt.show()

আসল আউটপুট (ব্রাউজারের Python; নিচে ছবিও আঁকা হয়):

kappa  iters(eta=1/L)  iters(eta=2/(L+mu))  rate (k-1)/(k+1)
    1               1                    1             0.000
    2              21                   15             0.333
   10             138                   77             0.818
   50             718                  386             0.961
  200            2891                 1543             0.990
eta = 0.099: |x| after 200 steps = 0.0809
eta = 0.101: |x| after 200 steps = 241

প্রথম টেবিলে κ = ১০ থেকে ২০০-তে ২০ গুণ বাড়লে η = 1/L-এ ধাপ ১৩৮ থেকে ২৮৯১, প্রায় ২১ গুণ; সেরা η-তে ৭৭ থেকে ১৫৪৩, প্রায় ২০ গুণ। অর্থাৎ ধাপের সংখ্যা κ-এর অনুপাতে, আর সেরা η মোটামুটি অর্ধেক ধাপ বাঁচায়, ঠিক তত্ত্বের মতো। κ = ১-এ এক ধাপেই শেষ: গোল বাটিতে η = 1/L সোজা কেন্দ্রে লাফ দেয়। শেষ কলামের হার (κ − ১)/(κ + ১) κ = ২০০-এ ০.৯৯০: প্রতি ধাপে ভুল মাত্র ১% কমে। দ্বিতীয় অংশে η = ০.০৯৯ থেকে ০.১০১, মাত্র ২% বদল, অথচ ফল সম্পূর্ণ উল্টো: ২০০ ধাপে দূরত্ব ০.০৮১-এ নেমেছে বনাম ২৪১-এ উঠেছে। স্থিতির সীমা ধারালো, ধীরে ধীরে খারাপ হওয়ার কোনো সতর্ক সংকেত নেই। ছবিতে বাঁ দিকের প্রায় গোল বাটিতে পথ কয়েক ধাপেই কেন্দ্রে; ডান দিকের κ = ২০-এর বাটিতে পথ সরু উপত্যকার দুই দেয়ালে বাড়ি খেতে খেতে এগোয়, এটিই zig-zag.

Quadratic ছাড়িয়ে: L-smooth আর strongly convex

সব loss quadratic নয়, তবে একই দুটি সংখ্যা সাধারণ ক্ষেত্রেও কাজ করে। কোনো loss-কে L-smooth বলে যদি তার gradient খুব দ্রুত বদলাতে না পারে: ‖∇f(x) − ∇f(y)‖ ≤ L‖x − y‖; দুবার differentiable হলে এর মানে Hessian-এর সব eigenvalue L-এর বেশি নয়। আর μ-strongly convex মানে বাটি সব জায়গায় অন্তত μ পরিমাণ বাঁকা, অর্থাৎ Hessian-এর eigenvalue কখনো μ-এর কম নয়। Convexity-র এই সংজ্ঞা আর ছবি পর্ব ১২ (Convexity — কেন কিছু loss সহজ আর কিছু কঠিন)-এর বিষয়; এখানে শুধু ফল, যা optimization-এর পাঠ্যবইয়ে (যেমন Nesterov-এর বই) প্রমাণসহ আছে:

Loss-এর ধরনη = 1/L-এ যা নিশ্চিতমানে
L-smooth আর μ-strongly convexভুল প্রতি ধাপে একটি ধ্রুব গুণকে কমে (যেমন 1 − μ/L)Linear convergence: প্রতি কয়েক ধাপে এক দশমিক অঙ্ক বেশি সঠিক
L-smooth আর convex (μ = 0 হতে পারে)f(xk) − f* ≤ L‖x0 − x*‖²/(2k)ধীর, 1/k হারে; ভুল অর্ধেক করতে ধাপ দ্বিগুণ
শুধু L-smooth (non-convex, যেমন neural network)k ধাপের মধ্যে সবচেয়ে ছোট ‖∇f‖² ≤ 2L(f(x0) − flow)/kশুধু “ঢাল প্রায় শূন্য” নিশ্চিত; সেটি minimum, saddle না সমতল, বলা যায় না

শেষ সারিতে flow হলো loss-এর যেকোনো নিচের সীমা। তিনটি সারিতেই η-এর মাপ ঠিক করতে L জানা দরকার, অথচ বাস্তবে L প্রায় কখনো জানা থাকে না। এর একটি উত্তর line search: প্রতি ধাপে একটি বড় η দিয়ে শুরু করে, loss যথেষ্ট না কমা পর্যন্ত η অর্ধেক করতে থাকা (Armijo-র backtracking). Batch পদ্ধতিতে এটি খুব কাজের; বিস্তারিত আর Wolfe শর্ত আসবে পর্ব ১৫ (Second-order পদ্ধতি — Newton, L-BFGS আর line search)-এ। SGD-তে line search কঠিন, কারণ প্রতিটি loss-এর মাপও কোলাহলপূর্ণ; সেখানে হাতে বাছা η আর schedule-ই প্রচলিত।

৪ · SGD আর mini-batch — নিরপেক্ষ কিন্তু কোলাহলপূর্ণ gradient

ডেটায় দশ লাখ উদাহরণ থাকলে batch GD-এর প্রতিটি ধাপে দশ লাখটি gradient হিসাব করতে হয়। SGD-এর বুদ্ধি: একটি উদাহরণ i এলোমেলোভাবে (সমান সম্ভাবনায়) বেছে শুধু ∇ℓi(w) দিয়ে ধাপ নেওয়া। কেন এটি যুক্তিসঙ্গত? কারণ এর প্রত্যাশিত মান ঠিক আসল gradient:

Ei[∇ℓi(w)] = (1/N) ∑i ∇ℓi(w) = ∇L(w)

অর্থাৎ এটি একটি unbiased estimator: গড়ে কোনো দিকে হেলে নেই। কিন্তু প্রতিটি অনুমান আলাদা, আর সেই ছড়ানোকে বলে gradient noise. একটি উদাহরণের noise-এর মাপ σ²(w) = (1/N) ∑i ‖∇ℓi(w) − ∇L(w)‖², অর্থাৎ প্রতিটি উদাহরণের gradient আসল gradient থেকে গড়ে কতটা দূরে (বর্গে)। B-টি উদাহরণের গড় নিলে:

Var(mini-batch gradient) = (σ²/B) · (N − B)/(N − 1)

প্রথম অংশ σ²/B পরিসংখ্যানের পরিচিত নিয়ম: স্বাধীন অনুমানের গড়ে variance B গুণ কমে। দ্বিতীয় অংশটি এসেছে কারণ একটি batch-এ একই উদাহরণ দুবার নেওয়া হয় না (without replacement); B = N হলে এটি শূন্য, কারণ তখন অনুমানই আসল gradient. খরচ B-এর অনুপাতে বাড়ে, কিন্তু noise-এর standard deviation কমে মাত্র √B-এর অনুপাতে: batch চার গুণ করলে noise অর্ধেক। এই অসমতাই mini-batch-এর মূল টানাপোড়েন। নিচের stepper চারটি উদাহরণের একটি ছোট ডেটায় সব হিসাব হাতে করে।

loss = ½ · গড়(w·x − y)²w = 1, η = 0.05

            
  1. ছোট ডেটা। চারটি কাল্পনিক উদাহরণ, intercept ছাড়া একটি ওজন w. w = ১-এ সব অনুমান আসলের চেয়ে কম, loss ৪.২৫.
  2. প্রতিটি উদাহরণের gradient. ½(wx − y)²-এর w-এর সাপেক্ষে derivative (wx − y)·x. চারটি মান −১, −২, −১২, −১৬: সবাই একমত যে w বাড়ানো দরকার, কিন্তু কতটা, তাতে বিশাল অমিল।
  3. Batch ধাপ। পুরো gradient চারটির গড় −৭.৭৫. এক ধাপে w = ১.৩৮৭৫, loss ১.৮১০.
  4. SGD ধাপ। একটি উদাহরণ এলোমেলোভাবে ওঠে। দ্বিতীয়টি উঠলে w মাত্র ১.১-এ যায় (loss ৩.৫১২৫), চতুর্থটি উঠলে ১.৮-এ (loss ০.৪৫, batch ধাপের চেয়েও ভালো)। চারটি সম্ভাব্য ফলের গড় ঠিক ১.৩৮৭৫: এটিই “নিরপেক্ষ” কথাটির মানে।
  5. Noise কত। একটি উদাহরণের gradient-এর variance σ² = গড়(gi²) − (গড় g)² = ১০১.২৫ − ৬০.০৬২৫ ≈ ৪১.১৯. Standard deviation প্রায় ৬.৪, অথচ আসল gradient −৭.৭৫: noise প্রায় সংকেতের সমান।
  6. Mini-batch, B = ২. ছয়টি সম্ভাব্য জোড়ার গড় gradient-এরও গড় −৭.৭৫ (এখনও নিরপেক্ষ), কিন্তু variance নেমে ১৩.৭৩, সূত্র (σ²/B)·(N − B)/(N − 1)-এর সঙ্গে হুবহু মেলে। সর্বনিম্ন বিন্দু w* = ৬১/৩০ ≈ ২.০৩৩.

এবার বাজারঘরের একটি বড় কাল্পনিক ডেটা: ১,০২৪টি অর্ডার, তিনটি feature (পণ্যের সংখ্যা, দূরত্ব km, আগের ছয় মাসের কেনাকাটা টাকায়), আর লক্ষ্য অর্ডারের মূল্য (শত টাকায়)। Feature-গুলো standardize করা, intercept-সহ চারটি ওজন, loss ½ · গড় বর্গ ভুল, অর্থাৎ ½ × MSE. ব্লকটি প্রথমে w = 0-তে চার রকম B-এর জন্য ৩০০টি করে mini-batch gradient নিয়ে noise-এর variance মাপে আর সূত্রের সঙ্গে মেলায়। তারপর একই η = ০.০৫-এ দশ epoch চালিয়ে loss আর সেরা loss-এর ব্যবধান (gap) আঁকে। এই ব্লক আর পরের অংশ ৬-এর ব্লক একই namespace ভাগ করে (data-shared), তাই পরেরটি এখানকার ডেটা আর sgd() ব্যবহার করে।

import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng(42)                 # কাল্পনিক বাজারঘর: ১০২৪টি অর্ডার
N = 1024
items, km = rng.integers(1, 11, N), rng.uniform(1, 15, N)
spend = rng.gamma(2.0, 3000, N)                 # আগের ছয় মাসের কেনাকাটা, টাকা
y = 2 + 1.5 * items + 0.3 * km + 0.0004 * spend + rng.normal(0, 1.5, N)  # শত টাকা
Xraw = np.column_stack([items, km, spend]).astype(float)
X = np.column_stack([np.ones(N), (Xraw - Xraw.mean(0)) / Xraw.std(0)])
loss = lambda w: 0.5 * np.mean((X @ w - y) ** 2)
L_star = loss(np.linalg.lstsq(X, y, rcond=None)[0])
grad = lambda w, i: X[i].T @ (X[i] @ w - y[i]) / len(i)   # mini-batch gradient

def sgd(B, epochs, lr, seed=0):                 # lr(epoch, t) → η
    r, w, t, gap = np.random.default_rng(seed), np.zeros(4), 0, [loss(np.zeros(4)) - L_star]
    for ep in range(epochs):
        perm = r.permutation(N)                 # প্রতি epoch-এ নতুন এলোমেলো ক্রম
        for s in range(0, N, B):
            w, t = w - lr(ep, t) * grad(w, perm[s:s + B]), t + 1
        gap.append(loss(w) - L_star)
    return np.array(gap)

w0 = np.zeros(4); G = X * (X @ w0 - y)[:, None]           # প্রতিটি অর্ডারের নিজের gradient
g, s2 = G.mean(0), np.mean(np.sum((G - G.mean(0)) ** 2, 1))
plt.figure(figsize=(6.4, 3.8), layout="constrained")
print("    B  steps/epoch  noise var  theory  final gap (10 ep)")
for B in (1, 16, 128, N):
    gs = np.array([grad(w0, rng.choice(N, B, replace=False)) for _ in range(300)])
    var, th = np.mean(np.sum((gs - g) ** 2, 1)), s2 / B * (N - B) / (N - 1)
    gap = sgd(B, 10, lambda ep, t: 0.05)
    print(f"{B:5} {N // B:12} {var:10.3f} {th:7.3f} {gap[-1]:12.2e}")
    plt.semilogy(gap, "o-", ms=3, label=f"batch size {B}")
plt.xlabel("epoch (passes over the data)"); plt.ylabel("loss - best loss")
plt.title("Same eta = 0.05, different batch sizes"); plt.legend(); plt.show()

আসল আউটপুট (ব্রাউজারের Python; নিচে ছবিও আঁকা হয়):

    B  steps/epoch  noise var  theory  final gap (10 ep)
    1         1024    867.986 867.717     2.27e-01
   16           64     51.819  53.437     4.81e-03
  128            8      5.996   5.937     3.46e-02
 1024            1      0.000   0.000     4.51e+01

মাপা noise আর সূত্রের মান প্রতিটি সারিতে কাছাকাছি (যেমন B = ১৬-এ ৫১.৮ বনাম ৫৩.৪; পার্থক্য ৩০০টি নমুনার নিজস্ব এলোমেলোতা)। B ১ থেকে ১৬ হলে variance প্রায় ১৬ গুণ কমেছে, ১৬ থেকে ১২৮-এ প্রায় ৮ গুণ, আর B = N-এ শূন্য। দশ epoch-এর পরের ব্যবধান আরও মজার: B = ১৬ সবচেয়ে ভালো (৪.৮ × ১০−৩)। B = ১ অনেক বেশি ধাপ পেয়েছে (১০,২৪০টি), কিন্তু noise এত বেশি যে প্রথম epoch-এর পরেই আর নামতে পারেনি, ব্যবধান ০.০১ থেকে ০.২-এর মধ্যে লাফাচ্ছে। B = ১২৮ মসৃণ কিন্তু ধাপ কম (৮০টি), আর batch GD মাত্র ১০টি ধাপে ব্যবধান ৪৫-এ থেমেছে। এখানে খরচ মাপা হয়েছে epoch-এ, অর্থাৎ মোট কতগুলো উদাহরণের gradient হিসাব হলো। GPU-তে বড় batch সমান্তরালে চলে বলে সময়ের হিসাব আলাদা হতে পারে। এটি একটি seed-এর একটি run, তাই সংখ্যাগুলো সাধারণ নিয়ম নয়, ধারণার উদাহরণ।

৫ · ধ্রুব learning rate-এর মেঝে আর Robbins–Monro-র শর্ত

আগের ছবির B = ১-এর রেখা কেন একটি উচ্চতায় আটকে গেল? Batch GD-তে সর্বনিম্ন বিন্দুর কাছে gradient নিজেই শূন্যের দিকে যায়, তাই ধাপও ছোট হয়ে আসে। SGD-তে তা হয় না: ডেটায় noise থাকলে w*-এ পৌঁছেও প্রতিটি উদাহরণের নিজের gradient শূন্য নয়, শুধু তাদের গড় শূন্য। তাই ধ্রুব η-তে প্রতিটি ধাপ w-কে একটু এদিক-ওদিক ঠেলতে থাকে, আর w সর্বনিম্ন বিন্দুর চারপাশে একটি মেঘের মতো ঘোরে। সেই মেঘের আকার মোটামুটি η·σ²-এর অনুপাতে।

অন্ধকারে টর্চ ছাড়া একটি ঘরের ঠিক মাঝখানে দাঁড়াতে চান, আর প্রতিবার পা ফেলার সময় কেউ একটু ধাক্কা দেয়। বড় বড় পা ফেললে দ্রুত মাঝখানের কাছে পৌঁছাবেন, কিন্তু ধাক্কার জন্য মাঝখানের চারপাশে বড় বৃত্তে ঘুরবেন। পা যত ছোট করবেন, বৃত্ত তত ছোট। সবচেয়ে বুদ্ধিমানের কাজ: শুরুতে বড় পা, তারপর ধীরে ধীরে ছোট।

অংশ ৪-এর একই ডেটায় একটি আলাদা local run (B = ১, ২০ epoch, তিনটি seed-এর গড়) এই অনুপাত দেখিয়েছে: শেষের ব্যবধান η = ০.০২-এ ০.০৬৫, ০.০৫-এ ০.২০৬, ০.১-এ ০.৪৫৫, ০.২-এ ১.১৭; η দ্বিগুণ হলে মেঝে মোটামুটি দ্বিগুণ। মেঝে নামানোর দুটি উপায়: B বাড়ানো (σ²/B কমে), অথবা সময়ের সঙ্গে η কমানো। দ্বিতীয়টির তাত্ত্বিক ভিত্তি Robbins আর Monro-র ১৯৫১ সালের শর্ত:

∑t ηt = ∞ আর ∑t ηt² < ∞

প্রথম শর্ত বলে মোট হাঁটার দূরত্ব অসীম হতে হবে, যাতে যত দূরেই শুরু হোক, লক্ষ্যে পৌঁছানো যায়। দ্বিতীয় শর্ত বলে noise-এর মোট প্রভাব সসীম হতে হবে, যাতে শেষে কাঁপুনি থামে। ηt = η0/(1 + t/t0) দুটিই মানে; ধ্রুব η প্রথমটি মানে, দ্বিতীয়টি নয়। এই Robbins–Monro শর্ত তত্ত্বের নিশ্চয়তা দেয়, তবে বাস্তবে 1/t হার প্রায়ই খুব দ্রুত কমে আর training ধীর করে দেয়। তাই ব্যবহারিক learning-rate scheduleগুলো সাধারণত সীমিত সংখ্যক epoch-এর জন্য বানানো, অসীমের জন্য নয়। আরেকটি পুরোনো কৌশল হলো ধ্রুব η রেখে শেষ দিকের w-গুলোর গড় নেওয়া (iterate averaging, Polyak আর Juditsky ১৯৯২; scikit-learn-এ average=True).

Epoch আর এলোমেলো ক্রম: তত্ত্বে প্রতিটি ধাপে উদাহরণ স্বাধীনভাবে, ফেরত দিয়ে (with replacement) তোলা হয়। বাস্তবে প্রায় সবাই প্রতি epoch-এ ডেটা একবার এলোমেলো করে পরপর batch নেয়, যাতে প্রতিটি উদাহরণ ঠিক একবার আসে। এই “random reshuffling” বাস্তবে সাধারণত সমান বা ভালো কাজ করে, আর এর তত্ত্ব তুলনামূলক সাম্প্রতিক গবেষণার বিষয়। একটি জিনিস কখনো করবেন না: ডেটা যে ক্রমে সাজানো (যেমন তারিখ বা label অনুযায়ী), সেই ক্রমেই SGD চালানো। তখন পরপর অনেক ধাপ একই ধরনের উদাহরণ দেখে, আর gradient-এর অনুমান আর নিরপেক্ষ থাকে না।

৬ · Learning-rate schedule — step, exponential, cosine আর warm-up

Schedule মানে ধাপ বা epoch t-এর একটি function η(t). সবচেয়ে প্রচলিত চারটি:

নামসূত্রভাবনা
Step decayη0 · γ⌊t/s⌋, যেমন প্রতি s epoch-এ ১০ ভাগের ১Plateau-তে পৌঁছালে হঠাৎ ছোট পা; ResNet-যুগের image model-এ বহুল প্রচলিত
Exponential decayη0 · γt, যেমন γ = ০.৮ প্রতি epochপ্রতি epoch-এ একই অনুপাতে মসৃণভাবে কমা
Cosine annealingηmin + ½(ηmax − ηmin)(1 + cos(πt/T))শুরুতে প্রায় সমতল, মাঝে দ্রুত, শেষে আবার ধীরে শূন্যের দিকে
Warm-upপ্রথম W ধাপে ηmax · (t + 1)/W, তারপর অন্য scheduleশুরুর অস্থির মুহূর্তে বড় ধাপ এড়ানো

Cosine schedule এসেছে Ilya Loshchilov আর Frank Hutter-এর SGDR প্রবন্ধ থেকে (arXiv ২০১৬, ICLR ২০১৭), যেখানে তারা বারবার “restart” করে cosine চক্র চালান; আজ বেশিরভাগ ক্ষেত্রে restart ছাড়া একটি মাত্র চক্র ব্যবহার হয়। ধীরে ধীরে warm-up জনপ্রিয় হয় মূলত Priya Goyal ও সহকর্মীদের ২০১৭ সালের বড়-batch ImageNet training-এর কাজে, যেখানে batch বড় করার সঙ্গে η-ও আনুপাতিক বাড়ানো হয় (linear scaling rule) আর প্রথম কয়েক epoch-এ η ধীরে ধীরে তোলা হয়। নিচের stepper দশ epoch-এর একটি cosine-with-warm-up schedule epoch ধরে ধরে হিসাব করে।

epoch t = 0 … 9η_min = 0

            
  1. নিয়ম। মোট T = ১০ epoch, প্রথম W = ২ epoch warm-up. তারপর বাকি ৮ epoch-এ cosine অর্ধেক চক্র ঘুরে ηmax থেকে ০-এর দিকে নামে।
  2. Warm-up. Epoch ০-এ η = ০.০৫, epoch ১-এ ০.১. বড় model-এ শুরুর ওজন এলোমেলো থাকে আর gradient অস্বাভাবিক বড় হতে পারে; তখন পুরো η দিয়ে শুরু করলে প্রথম কয়েক ধাপেই loss লাফিয়ে উঠতে পারে।
  3. Cosine শুরু। Epoch ২-এ cos ০ = ১, তাই η = ০.১. Epoch ৩-এ cos(π/৮) ≈ ০.৯২৪, η ≈ ০.০৯৬: শুরুতে খুব ধীরে নামে, তাই বড় পায়ে দ্রুত অগ্রগতি চলতে থাকে।
  4. মাঝপথ। Epoch ৪, ৫, ৬-এ η ০.০৮৫, ০.০৬৯, ০.০৫০. Epoch ৬-এ cos(π/২) = ০, তাই η ঠিক অর্ধেক। এই অংশে নামার গতি সবচেয়ে বেশি।
  5. শেষের দিক। Epoch ৭, ৮, ৯-এ η ০.০৩১, ০.০১৫, ০.০০৪. শেষে খুব ছোট পা, তাই অংশ ৫-এর মেঘ সংকুচিত হয়ে সর্বনিম্নের কাছে বসে যায়।
  6. তিন schedule পাশাপাশি। ১০ epoch-এ η-এর যোগফল cosine-এ ০.৬০, exponential (০.৮t)-এ ০.৪৪৬, প্রতি তিন epoch-এ অর্ধেক করা step-এ ০.৫৩৮. Cosine মাঝের epoch-গুলোতে বেশি “হাঁটে”, তারপর শেষে সবচেয়ে ছোট পা ফেলে।

এবার অংশ ৪-এর একই ডেটায়, B = ১৬ আর ২০ epoch-এ পাঁচটি schedule: ধ্রুব ০.১, ধ্রুব ০.০১, step (epoch ৮ আর ১৬-এ ১০ ভাগের ১), exponential (প্রতি epoch-এ ০.৮ গুণ) আর warm-up-সহ cosine (প্রথম ২ epoch warm-up). এখানে schedule ধাপ ধরে হিসাব হয়, epoch ধরে নয়, তাই cosine মসৃণ। প্রতিটি schedule তিনটি seed-এ চালিয়ে গড় নেওয়া হয়েছে।

import numpy as np
import matplotlib.pyplot as plt

B, E, eta0 = 16, 20, 0.1                        # আগের ব্লকের N, sgd() এখানে ব্যবহার হচ্ছে
spe = N // B; T, W = E * spe, 2 * spe           # মোট ধাপ ১২৮০, warm-up ১২৮ ধাপ (২ epoch)
def cosine_wu(ep, t):
    if t < W:
        return eta0 * (t + 1) / W               # warm-up: সরলরেখায় ০ থেকে eta0
    return eta0 * 0.5 * (1 + np.cos(np.pi * (t - W) / (T - W)))

schedules = {
    "constant 0.1": lambda ep, t: eta0,
    "constant 0.01": lambda ep, t: 0.01,
    "step (x0.1 at ep 8, 16)": lambda ep, t: eta0 * 0.1 ** (ep // 8),
    "exponential (x0.8 per ep)": lambda ep, t: eta0 * 0.8 ** ep,
    "cosine + warm-up": cosine_wu,
}
fig, (a1, a2) = plt.subplots(1, 2, figsize=(9, 3.6), layout="constrained")
t = np.arange(T)
print(f"{'schedule':26} final gap (mean of 3 seeds)   sum of eta")
for name, f in schedules.items():
    gaps = np.array([sgd(B, E, f, seed=s) for s in range(3)])
    etas = np.array([f(k // spe, k) for k in t])
    print(f"{name:26} {gaps[:, -1].mean():12.2e} {etas.sum():22.1f}")
    a1.plot(t / spe, etas, label=name)
    a2.semilogy(gaps.mean(0), label=name)
a1.set(xlabel="epoch", ylabel="learning rate", title="Learning-rate schedules")
a2.set(xlabel="epoch", ylabel="loss - best loss", title="Mini-batch SGD, batch size 16")
a2.legend(fontsize=7); plt.show()

আসল আউটপুট (ব্রাউজারের Python; নিচে দুটি ছবি পাশাপাশি আঁকা হয়):

schedule                   final gap (mean of 3 seeds)   sum of eta
constant 0.1                   1.28e-02                  128.0
constant 0.01                  5.16e-05                   12.8
step (x0.1 at ep 8, 16)        2.38e-05                   56.6
exponential (x0.8 per ep)      5.80e-06                   31.6
cosine + warm-up               1.60e-05                   64.1

ধ্রুব ০.১ মেঝেতে আটকে আছে: ব্যবধান ১.৩ × ১০−২, ডান ছবিতে নীল রেখা প্রথম দুই epoch-এর পর আর নামে না। তিনটি decay schedule একই ০.১ দিয়ে শুরু করেও শেষে ১০−৫-এর ঘরে: মেঝের চেয়ে প্রায় ৫০০ থেকে ২২০০ গুণ নিচে। শেষ কলামের Ση দেখায় তারা ধ্রুব ০.১-এর চেয়ে অনেক কম “হেঁটেছে”, কিন্তু ঠিক সময়ে পা ছোট করেছে। ধ্রুব ০.০১ শেষে মন্দ নয় (৫.২ × ১০−৫), কিন্তু ছবিতে কমলা রেখা প্রথম কয়েক epoch-এ অনেক ধীর; সহজ সমস্যা বলে (κ ≈ ১.০৬) পরে ধরে ফেলেছে। এই run-এ exponential সবচেয়ে নিচে, কিন্তু পার্থক্য ছোট আর একটি ছোট convex সমস্যায় তিন seed-এর গড়; অন্য সমস্যায় ক্রম বদলাতে পারে। Warm-up এখানে কোনো উপকার করেনি, বরং প্রথম epoch-এ cosine রেখাকে একটু পিছিয়ে দিয়েছে। এর উপকার দেখা যায় deep network, খুব বড় batch বা Adam-এর মতো adaptive optimizer-এ, যেখানে শুরুর gradient অবিশ্বস্ত; Adam আসবে পর্ব ১৪ (Momentum, RMSProp আর Adam — derive করে আর তুলনা করে)-এ।

৭ · Feature scaling — κ বদলালে ধাপও বদলায়, আর scikit-learn দিয়ে যাচাই

Least squares-এ loss-এর Hessian XᵀX/N, যার প্রতিটি কর্ণের ঘর একটি কলামের বর্গের গড়। একটি feature টাকায় (হাজার হাজার) আর আরেকটি পণ্যের সংখ্যায় (১ থেকে ১০) মাপা হলে Hessian-এর eigenvalue-গুলো লক্ষ কোটি গুণ আলাদা হতে পারে: κ বিশাল, বাটি অবিশ্বাস্য রকম সরু। Feature scaling, বিশেষ করে standardization (প্রতিটি কলাম থেকে গড় বাদ দিয়ে standard deviation দিয়ে ভাগ), বাটিকে গোলের কাছে নিয়ে আসে। AI-এর ভিত্তি কোর্সের পর্ব ৬৩-এ scaling-এর কৌশল আর পর্ব ৬৪-এ pipeline দিয়ে leakage এড়ানো দেখেছেন; এখানে দেখি optimization-এর দিক থেকে এর দাম।

নিচের ব্লক একই ১,০২৪টি অর্ডারে তিনভাবে মাপা feature নিয়ে (কাঁচা টাকা, কেনাকাটা হাজার টাকায়, standardized) Hessian-এর κ বের করে আর η = 1/L-এ batch GD চালায়, যতক্ষণ না loss-এর ব্যবধান সেরা loss-এর ১০−৬ ভাগের নিচে নামে (সর্বোচ্চ ১০,০০০ ধাপ)। তারপর scikit-learn-এর SGD দুটি পরীক্ষা হিসেবে চালায়: SGDRegressor-কে LinearRegression-এর সঙ্গে, আর “বড় অর্ডার” (মূল্য median-এর বেশি) label-এ SGDClassifier-কে প্রায় শাস্তিহীন LogisticRegression-এর সঙ্গে মেলায়।

import numpy as np
from sklearn.linear_model import LinearRegression, LogisticRegression, SGDClassifier, SGDRegressor
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

rng = np.random.default_rng(42)                 # অংশ ৪-এর একই কাল্পনিক ১০২৪টি অর্ডার
N = 1024
items, km = rng.integers(1, 11, N), rng.uniform(1, 15, N)
spend = rng.gamma(2.0, 3000, N)
y = 2 + 1.5 * items + 0.3 * km + 0.0004 * spend + rng.normal(0, 1.5, N)
Xraw = np.column_stack([items, km, spend]).astype(float)
def kappa_steps(A, max_it=10000):               # batch GD, η = 1/L; থামা: gap < 1e-6 × best
    Z = np.column_stack([np.ones(N), A]); ev = np.linalg.eigvalsh(Z.T @ Z / N)
    r = Z @ np.linalg.lstsq(Z, y, rcond=None)[0] - y
    best, w = 0.5 * r @ r / N, np.zeros(Z.shape[1])
    for k in range(max_it + 1):
        r = Z @ w - y                           # residual, loss আর gradient দুটিতেই লাগে
        if 0.5 * r @ r / N - best < 1e-6 * best or k == max_it: break
        w = w - Z.T @ r / N / ev[-1]
    return ev[-1] / ev[0], k, 0.5 * r @ r / N - best
for name, A in [("raw (taka)", Xraw), ("spend in 1000 taka", Xraw / [1, 1, 1000]),
                ("standardized", (Xraw - Xraw.mean(0)) / Xraw.std(0))]:
    print("{:19} kappa = {:9.3g}  GD steps = {:5}  gap = {:.1e}".format(name, *kappa_steps(A)))

scaled = lambda model: make_pipeline(StandardScaler(), model)
ols = scaled(LinearRegression()).fit(Xraw, y)
sgr = scaled(SGDRegressor(penalty=None, max_iter=2000, tol=1e-6, random_state=42)).fit(Xraw, y)
print("OLS coef  :", np.round(ols[-1].coef_, 3), "\nSGDRegr.  :", np.round(sgr[-1].coef_, 3),
      " epochs:", sgr[-1].n_iter_, " eta0:", sgr[-1].eta0, sgr[-1].learning_rate)
big = (y > np.median(y)).astype(int)            # বড় অর্ডার (১) না ছোট (০)
lr = scaled(LogisticRegression(C=1e10)).fit(Xraw, big)
sgc = scaled(SGDClassifier(loss="log_loss", alpha=1e-5, learning_rate="adaptive", eta0=0.1,
                           max_iter=2000, tol=1e-6, random_state=42)).fit(Xraw, big)
print("Logistic  :", np.round(lr[-1].coef_[0], 2), "\nSGDClass. :", np.round(sgc[-1].coef_[0], 2),
      " epochs:", sgc[-1].n_iter_, " accuracy:", lr.score(Xraw, big), sgc.score(Xraw, big))

আসল আউটপুট (ব্রাউজারের Python, scikit-learn 1.8.0):

raw (taka)          kappa =  5.81e+08  GD steps = 10000  gap = 3.8e+01
spend in 1000 taka  kappa =  1.53e+03  GD steps =  9285  gap = 1.1e-06
standardized        kappa =      1.06  GD steps =     3  gap = 9.3e-08
OLS coef  : [4.286 1.191 1.737] 
SGDRegr.  : [4.288 1.191 1.738]  epochs: 14  eta0: 0.01 invscaling
Logistic  : [5.05 1.43 2.11] 
SGDClass. : [5.06 1.44 2.11]  epochs: 76  accuracy: 0.9150390625 0.9150390625

কাঁচা টাকায় κ ≈ ৫.৮১ × ১০৮: দশ হাজার ধাপ পরেও ব্যবধান ৩৮, অর্থাৎ প্রায় কিছুই শেখেনি, কারণ η = 1/L টাকার কলামের বিশাল বাঁকে বাঁধা আর বাকি দিকগুলোতে ধাপ প্রায় শূন্য। শুধু একটি কলামকে হাজার দিয়ে ভাগ করলে κ নামে ১,৫৩০-এ আর ধাপ লাগে ৯,২৮৫. Standardize করলে κ ≈ ১.০৬, তিন ধাপেই শেষ। মডেল, ডেটা আর অ্যালগরিদম একই; বদলেছে শুধু একক। দ্বিতীয় অংশে SGDRegressor-এর ওজন OLS-এর সঙ্গে তিন দশমিক পর্যন্ত প্রায় মেলে, ১৪ epoch-এ। আউটপুট দেখাচ্ছে তার schedule-এর ডিফল্ট: learning_rate="invscaling", eta0=0.01, অর্থাৎ η = eta0 / tpower_t, ডিফল্ট power_t = ০.২৫. SGDClassifier logistic loss-এ learning_rate="adaptive" দিয়ে logistic regression-এর ওজনের সঙ্গে দুই দশমিকে মেলে, accuracy-ও এক (০.৯১৫)। max_iter=2000 আর tol=1e-6 দেওয়া, তাই tol-এর শর্তেই থেমেছে, ConvergenceWarning আসেনি।

SGDClassifier-এর ডিফল্ট schedule আলাদা: learning_rate="optimal", যেখানে η = 1/(α(t + t0)), আর scikit-learn-এর docs অনুযায়ী t0 বাছা হয় Léon Bottou-র প্রস্তাবিত একটি heuristic দিয়ে। এখানে α হলো regularization-এর শক্তি, তাই খুব ছোট α দিলে প্রথম দিকের η বিশাল হয়। এই পার্থক্য একটি desktop-এ সত্যিকারের run-এ দেখা গেছে (Python 3.14, লেখার সময়ের scikit-learn 1.9.1, একই ডেটা standardize করা, alpha=1e-5):

$ python defaults_check.py
scikit-learn 1.9.1
optimal   coef [9.32 0.49 7.06]  epochs 44  acc 0.885
adaptive  coef [5.06 1.44 2.11]  epochs 76  acc 0.915

ডিফল্ট “optimal” schedule ৪৪ epoch-এ tol-এর শর্তে থেমেছে, কোনো warning দেয়নি, অথচ ওজন logistic regression-এর (৫.০৫, ১.৪৩, ২.১১) থেকে অনেক দূরে আর accuracy কম। “থেমেছে” মানে “পৌঁছেছে” নয়: tol শুধু বলে শেষ কয়েক epoch-এ loss খুব একটা কমেনি। তাই SGD-এর ফল সবসময় একটি নির্ভরযোগ্য পদ্ধতির (closed form, lbfgs) সঙ্গে ছোট ডেটায় একবার মিলিয়ে নিন।

Scaling-এর দুটি সীমা: (১) Standardization শুধু প্রতিটি কলামের মাপ ঠিক করে, যা এক ধরনের কর্ণ-preconditioning. দুটি feature নিজেরা খুব correlated হলে বাটি তির্যকভাবে সরু থাকে, আর তখন κ বড়ই থাকে; সেটি সারাতে লাগে whitening, Newton বা quasi-Newton (পর্ব ১৫)। (২) Scaler-এর গড় আর standard deviation শুধু training ডেটা থেকে শিখতে হবে, তারপর validation আর test-এ একই রূপান্তর; নইলে test-এর তথ্য train-এ চুইয়ে আসে। Pipeline এটি আপনাআপনি করে।

৮ · Convergence-এর লক্ষণ পড়া — কখন কী বদলাবেন

Training চলার সময় loss-এর রেখাই আপনার ডাক্তারের থার্মোমিটার। রেখাটি epoch ধরে আঁকুন, y-অক্ষ log scale-এ, আর পারলে আসল সর্বনিম্ন বা একটি ভালো baseline-এর সঙ্গে ব্যবধান দেখুন। সাধারণ লক্ষণ আর তাদের কারণ:

লক্ষণসম্ভাব্য কারণপ্রথমে যা করবেন
Loss বাড়ছে, বা কয়েক ধাপে inf/NaNη > 2/L, প্রায়ই unscaled feature-এর জন্য L বিশালFeature scale করুন; η ৩ থেকে ১০ গুণ কমিয়ে আবার চালান
Loss কমছে, কিন্তু উঠানামা করে দোলেη সীমার কাছে, বা ill-conditioned বাটিতে zig-zagη একটু কমান; scaling দেখুন; momentum (পর্ব ১৪) বিবেচনা করুন
খুব ধীর, সরলরেখার মতো স্থির পতনη খুব ছোট, বা κ বিশালη বাড়ান, scaling আর condition number দেখুন
দ্রুত নেমে একটি কোলাহলপূর্ণ মেঝেতে আটকেধ্রুব η-তে SGD noiseSchedule দিন, বা batch বাড়ান, বা iterate averaging
Train loss চমৎকার, validation খারাপOptimization নয়, generalization-এর সমস্যাRegularization, বেশি ডেটা, early stopping (পর্ব ১৭)

থামার নিয়ম তিন রকম। Batch পদ্ধতিতে সবচেয়ে পরিষ্কার হলো gradient-এর দৈর্ঘ্য ‖∇L‖ একটি ছোট সীমার নিচে নামা। SGD-তে প্রতিটি gradient কোলাহলপূর্ণ, তাই সাধারণত দেখা হয় কয়েক epoch ধরে loss যথেষ্ট কমছে কি না; scikit-learn ঠিক এটি করে: পরপর n_iter_no_change (ডিফল্ট ৫)টি epoch-এ loss tol-এর চেয়ে বেশি না কমলে থামে। তৃতীয়, আলাদা validation ডেটায় score আর না বাড়লে থামা, যাকে early stopping বলে; এটি optimization-এর চেয়ে regularization-এর হাতিয়ার, পর্ব ১৭ (Regularization-এর তত্ত্ব — L1/L2-এর জ্যামিতি, MAP আর early stopping)-এ বিস্তারিত।

η বাছার একটি ব্যবহারিক কৌশল: ছোট একটি অংশে কয়েক epoch চালিয়ে η-কে ১০−৪, ১০−৩, … এভাবে দশ গুণ করে বাড়ান, আর যে সবচেয়ে বড় η-তে loss এখনও স্থিরভাবে কমে, তার চেয়ে একটু ছোট নিন। Leslie Smith-এর ২০১৭ সালের cyclical learning rate প্রবন্ধের “LR range test” এই ধারণারই একটি রূপ। আর batch size বাছতে মনে রাখুন: noise B-এর বর্গমূলের অনুপাতে কমে, কিন্তু খরচ B-এর অনুপাতে বাড়ে; একটি সীমার পরে batch বাড়ালে প্রতি epoch-এ অগ্রগতি প্রায় বাড়ে না।

আরও গভীরে: Strongly convex সমস্যায় SGD-এর সেরা সম্ভব হার মোটামুটি 1/t, batch GD-এর linear হারের চেয়ে ধীর, তবু প্রতি ধাপ N গুণ সস্তা বলে বড় N-এ জেতে; Bottou আর Bousquet (২০০৭) দেখান, statistical error-এর চেয়ে নিখুঁত optimization অর্থহীন। Finite-sum সমস্যায় SAG, SVRG, SAGA-র মতো variance-reduction পদ্ধতি (২০১২-২০১৪) linear হার ফিরিয়ে আনে; scikit-learn-এর LogisticRegression(solver="saga") এর একটি। পুরো চিত্রের জন্য Bottou, Curtis আর Nocedal-এর ২০১৮ সালের SIAM Review সমীক্ষা “Optimization Methods for Large-Scale Machine Learning” একটি ভালো শুরু।

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

  • Quadratic-এ gradient descent কখন diverge করে? η ≥ 2/L হলে, যেখানে L হলো Hessian-এর সবচেয়ে বড় eigenvalue.
  • Condition number কী, আর GD-তে তার প্রভাব? κ = L/μ; প্রয়োজনীয় ধাপ মোটামুটি κ·ln(1/ε)-এর অনুপাতে, তাই বড় κ মানে ধীর আর zig-zag.
  • SGD-এর gradient কি biased? না, গড়ে আসল gradient; variance σ²/B (without replacement-এ (N − B)/(N − 1) গুণ কম)।
  • ধ্রুব learning rate-এ SGD কেন সর্বনিম্নে থামে না? সর্বনিম্নেও প্রতিটি উদাহরণের gradient শূন্য নয়; η কমাতে হয় বা batch বাড়াতে হয়।
  • GD-এর আগে feature scale কেন? Hessian-এর eigenvalue-র ব্যবধান, অর্থাৎ κ, কমে; একই η-তে অনেক কম ধাপ লাগে।

৯ · AI-এর যুগে — AI-এর লেখা training loop আর optimizer সেটিং যাচাই

AI assistant এক মিনিটে একটি পরিপাটি SGD loop বা scikit-learn-এর সেটিং লিখে দেয়। সমস্যা হলো, optimization-এর ভুল প্রায়ই নীরব: কোড চলে, loss কিছুটা কমে, আর ফল “ঠিকঠাক দেখায়”। নিচের ভুলগুলো বারবার দেখা যায়:

AI প্রায়ই যা করেকেন সমস্যাকীভাবে ধরবেন
“learning rate 0.01 দিন” বলে, feature-এর মাপ না দেখেইনিরাপদ η নির্ভর করে L-এর ওপর, যা feature-এর একক দিয়ে বদলায়Hessian-এর সবচেয়ে বড় eigenvalue বা κ মাপুন; কয়েকটি η-তে loss-এর রেখা আঁকুন
প্রতি epoch-এ ডেটা shuffle করে না, বা তারিখ অনুযায়ী সাজানো ডেটায় SGD চালায়Gradient-এর অনুমান আর নিরপেক্ষ থাকে নাLoop-এ permutation আছে কি না পড়ুন; seed দিয়ে পুনরুৎপাদনযোগ্য রাখুন
Schedule আর η বাছে test set-এর score দেখেTest score আশাবাদী হয়ে যায়আলাদা validation বা cross-validation; test set তালা দেওয়া
scikit-learn-এর পুরোনো বা অস্তিত্বহীন parameter দেয় (যেমন বহু পুরোনো n_iter)নতুন version-এ error, বা অন্য অর্থনিজের version-এর docs পড়ুন; get_params() দেখুন
ConvergenceWarning চাপা দিতে warnings.filterwarnings("ignore") লেখেসমস্যা লুকায়, সারায় নাScaling, max_iter, tol দেখুন; closed form বা lbfgs-এর সঙ্গে মেলান
একটি seed-এর একটি run দেখে বলে “cosine সেরা”SGD-এর ফল seed-এ বদলায়; পার্থক্য প্রায়ই noise-এর মাপের মধ্যেকয়েকটি seed-এ গড় আর ছড়ানো দেখুন; ধ্রুব η-এর baseline রাখুন

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

আমার কাছে একটি linear regression সমস্যা আছে: ৫০,০০০টি সারি, ১২টি numeric feature, যাদের মাপ খুব আলাদা (কিছু টাকায়, কিছু ০ থেকে ১)। ১. NumPy-তে mini-batch SGD লেখো (B = 64), প্রতি epoch-এ shuffle, seed দিয়ে। ২. Feature scaling কোথায় আর কীভাবে করবে বলো, যাতে validation-এর তথ্য train-এ না ঢোকে। ৩. Loss-এর Hessian XᵀX/N থেকে L আর κ হিসাব করে নিরাপদ η-এর সীমা দেখাও। ৪. ধ্রুব η, step আর warm-up-সহ cosine — তিনটি schedule তুলনা করো, ৩টি seed-এ, validation loss দিয়ে। ৫. ফলকে closed-form least squares (np.linalg.lstsq)-এর সঙ্গে মেলানোর কোড দাও। ৬. scikit-learn-এর SGDRegressor-এর যে parameter ব্যবহার করবে, তার নাম আমার version-এর docs-এ আছে কি না, সেটি আমাকে যাচাই করতে বলো।

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

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

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

প্র ০১Optimization-এর তত্ত্বে batch GD-এর হার SGD-এর চেয়ে ভালো, তবু বড় ডেটার ML-এ SGD কেন জিতল?

তত্ত্বে প্রশ্নটি সাধারণত এভাবে মাপা হয়: কত ধাপে ভুল ε-এর নিচে নামে। Strongly convex সমস্যায় batch GD-এর ভুল জ্যামিতিক হারে কমে, প্রতি কয়েক ধাপে এক দশমিক অঙ্ক; SGD-এর সেরা হার মোটামুটি 1/t. এই মাপে batch পরিষ্কার জয়ী। কিন্তু ML-এ আসল খরচ ধাপ নয়, মোট কতবার একটি উদাহরণের gradient হিসাব হলো। Batch-এর প্রতিটি ধাপে N-টি হিসাব; N দশ লাখ হলে একটি ধাপের খরচে SGD দশ লাখ ধাপ নিয়ে ফেলে।

দ্বিতীয় আর গভীরতর যুক্তি দেন Léon Bottou আর Olivier Bousquet তাদের ২০০৭ সালের NIPS প্রবন্ধ “The Tradeoffs of Large Scale Learning”-এ। তারা মোট ভুলকে তিন ভাগে ভাগ করেন: model family-র সীমা (approximation), সীমিত ডেটার কারণে ভুল (estimation), আর optimization অসম্পূর্ণ থাকার ভুল। Training ডেটা নিজেই আসল জগতের একটি নমুনা, তাই training loss-কে দশম দশমিক পর্যন্ত কমানোর কোনো মানে নেই, যদি estimation error তৃতীয় দশমিকেই থাকে। হাতে সময় সীমিত হলে আরও বেশি ডেটা দেখা (SGD) প্রায়ই কম ডেটায় নিখুঁত optimization-এর (batch) চেয়ে ভালো test ফল দেয়।

তৃতীয় কারণ ব্যবহারিক: ডেটায় পুনরাবৃত্তি থাকে। বাজারঘরের লাখো অর্ডারের মধ্যে হাজারো প্রায় একই রকম; প্রথম কয়েক হাজার দেখেই gradient-এর দিক মোটামুটি জানা যায়। অংশ ৪-এর run-এও এক epoch-এর পর B = ১৬ যেখানে পৌঁছেছে, batch GD দশ epoch-এও সেখানে যায়নি। চতুর্থত, SGD stream-এ চলে: নতুন ডেটা এলে আবার পুরো ডেটা পড়তে হয় না।

তবে batch পদ্ধতি হারিয়ে যায়নি। মাঝারি আকারের convex সমস্যায় (যেমন scikit-learn-এর LogisticRegression-এর ডিফল্ট lbfgs) পুরো gradient দিয়ে quasi-Newton পদ্ধতি দ্রুত আর নির্ভরযোগ্য, আর tuning প্রায় লাগে না।

মূল উপলব্ধি: প্রশ্নটি “কত ধাপে” নয়, “কত হিসাবে কতটা ভালো test ফল”; সেই মাপে বড় ডেটায় সস্তা, কোলাহলপূর্ণ ধাপই লাভজনক।

প্র ০২Batch size বড় করলে কি সবসময় ভালো? Linear scaling rule, warm-up আর “sharp minima”-র বিতর্ক কী বলে?

বড় batch-এর আকর্ষণ পরিষ্কার: GPU-তে অনেক উদাহরণ একসঙ্গে চলে, তাই প্রতি সেকেন্ডে বেশি ডেটা দেখা যায়। ২০১৭ সালে Priya Goyal ও Facebook AI Research-এর সহকর্মীরা দেখান, ImageNet-এ ResNet-50 প্রায় ৮,০০০ ছবির batch-এ প্রায় এক ঘণ্টায় train করা যায়, ছোট batch-এর প্রায় সমান accuracy রেখে। তাদের দুটি কৌশল: batch k গুণ করলে η-ও k গুণ (linear scaling rule), আর শুরুর কয়েক epoch-এ η ধীরে ধীরে তোলা (warm-up). যুক্তিটি এই পর্বের সূত্র থেকেই আসে: B গুণ বাড়লে noise কমে, তাই বড় ধাপ নেওয়া নিরাপদ হয়।

কিন্তু সীমা আছে। এই পর্বের quadratic বিশ্লেষণে একটি সীমা স্পষ্ট: noise যত কমুক, η কখনো 2/L পেরোতে পারে না। তাই batch বাড়াতে বাড়াতে একসময় η আর বাড়ানো যায় না, আর প্রতি epoch-এর অগ্রগতি কমতে থাকে। পরে OpenAI-এর McCandlish ও সহকর্মীরা (২০১৮) gradient-এর noise মেপে একটি “critical batch size” অনুমানের পদ্ধতি দেন, যার ওপরে batch বাড়ানোর লাভ দ্রুত কমে।

আরেকটি বিতর্ক generalization নিয়ে। Keskar ও সহকর্মীরা (ICLR ২০১৭) পর্যবেক্ষণ করেন, খুব বড় batch-এ train করা network-এর test ফল কখনো কখনো খারাপ, আর প্রস্তাব করেন এরা loss-এর “sharp” বা খাড়া minimum-এ পৌঁছায়, যেখানে ছোট নড়াচড়াতেই loss অনেক বাড়ে। Dinh ও সহকর্মীরা (২০১৭) পাল্টা দেখান, sharpness-এর সংজ্ঞা parameter-এর মাপ বদলালেই বদলানো যায়, তাই “sharp মানে খারাপ” কথাটি সরলভাবে সত্য নয়। আবার Smith ও সহকর্মীরা (২০১৮) দেখান, η কমানোর বদলে batch বাড়ানো অনেক ক্ষেত্রে প্রায় একই প্রভাব ফেলে। বিষয়টি এখনও পুরোপুরি মীমাংসিত নয়।

মূল উপলব্ধি: বড় batch মানে কম noise আর বেশি সমান্তরালতা, কিন্তু η-এর ওপরের সীমা আর ক্রমহ্রাসমান লাভ থেকেই যায়; batch আর η একসঙ্গে tune করতে হয়।

প্র ০৩Cosine schedule কি সত্যিই অন্য schedule-এর চেয়ে ভালো, নাকি শুধু জনপ্রিয়? কীভাবে নিজে সিদ্ধান্ত নেবেন?

ইতিহাস দিয়ে শুরু করা যাক। ২০১০-এর দশকের শুরুর দিকে image model-এ প্রচলিত ছিল step decay: যেমন ResNet-এর মূল প্রবন্ধে (He ও সহকর্মী, ২০১৬) η = ০.১ দিয়ে শুরু করে error থেমে গেলে ১০ ভাগের ১ করা হয়। এতে দুটি অতিরিক্ত hyperparameter আসে, কখন কাটবেন আর কতটা। ২০১৬ সালে Loshchilov আর Hutter SGDR প্রবন্ধে cosine আকারে মসৃণ পতন আর পর্যায়ক্রমিক restart প্রস্তাব করেন। Cosine-এর আকর্ষণ হলো এর প্রায় কোনো আলাদা hyperparameter নেই: শুধু ηmax আর মোট দৈর্ঘ্য T. পরে Transformer-ভিত্তিক language model-এ warm-up আর cosine-এর জুটি প্রায় মানক হয়ে ওঠে।

কিন্তু “জনপ্রিয়” আর “প্রমাণিতভাবে সেরা” এক নয়। অংশ ৬-এর run-এ তিনটি decay schedule-এর ফল একই মাত্রায় ছিল, আর exponential সামান্য এগিয়ে ছিল; এটি একটি ছোট convex সমস্যার তিন seed-এর গড়, সাধারণ নিয়ম নয়। বড় benchmark-গুলোতেও schedule-এর মধ্যে পার্থক্য প্রায়ই ভালোভাবে tune করা η-এর পার্থক্যের চেয়ে ছোট। Cosine-এর একটি বাস্তব অসুবিধা: T আগে থেকে জানতে হয়; training মাঝপথে বাড়াতে চাইলে পুরো schedule বদলে যায়। এজন্য সাম্প্রতিক কিছু কাজ (যেমন warmup-stable-decay ধরনের schedule, বা schedule-free পদ্ধতির প্রস্তাব) এই নির্ভরতা কমানোর চেষ্টা করছে; এগুলো এখনও গবেষণা আর অভিজ্ঞতার বিষয়।

নিজে সিদ্ধান্ত নেওয়ার একটি সৎ পদ্ধতি: (১) প্রথমে ধ্রুব η-এর একটি baseline, যার η একটি ছোট sweep-এ বাছা; (২) তারপর একই মোট epoch-এ দুই-তিনটি schedule; (৩) প্রতিটি কয়েকটি seed-এ, ফল validation ডেটায়, গড় আর ছড়ানো সহ; (৪) পার্থক্য seed-এর ছড়ানোর চেয়ে বড় হলে তবেই সিদ্ধান্ত। পর্ব ০৪-এর পুনরুৎপাদনযোগ্য experiment-এর নিয়ম এখানে হুবহু খাটে।

মূল উপলব্ধি: যেকোনো যুক্তিসঙ্গত decay ধ্রুব η-এর মেঝে ভাঙে; কোনটি সেরা, তা সমস্যাভেদে বদলায় আর কয়েক seed-এর তুলনা ছাড়া বলা যায় না।

প্র ০৪Decision tree-তে feature scaling লাগে না, অথচ gradient descent-এ এত জরুরি কেন? Scaling কি সব ill-conditioning সারায়?

Decision tree প্রতিটি feature-এ শুধু একটি সীমারেখা খোঁজে, “দূরত্ব ৫ km-এর বেশি কি না”। একটি feature-কে ধনাত্মক সংখ্যা দিয়ে গুণ করলে উদাহরণগুলোর ক্রম বদলায় না, তাই একই ভাগ পাওয়া যায়। Gradient descent-এ বিষয়টি আলাদা: একটি একক η সব দিকে ব্যবহার হয়, আর loss-এর বাঁক প্রতিটি দিকে feature-এর মাপের বর্গের অনুপাতে। টাকার কলামকে ১,০০০ দিয়ে ভাগ করলে সেই দিকের বাঁক ১০ লাখ গুণ কমে; অংশ ৭-এর run-এ κ ৫.৮১ × ১০৮ থেকে ১,৫৩০-এ নেমেছে।

এই পরামর্শ পুরোনো। Yann LeCun, Léon Bottou, Genevieve Orr আর Klaus-Robert Müller-এর ১৯৯৮ সালের “Efficient BackProp” প্রবন্ধে input-কে গড় শূন্য আর একই রকম variance-এ আনা, আর সম্ভব হলে decorrelate করা, ঠিক এই Hessian-এর যুক্তিতে সুপারিশ করা হয়েছিল। পরে deep network-এর ভেতরের layer-এও একই ধারণা প্রয়োগের চেষ্টা থেকে এসেছে batch normalization (Ioffe আর Szegedy, ২০১৫), যদিও এর সাফল্যের সঠিক কারণ নিয়ে পরে বিতর্ক হয়েছে।

কিন্তু standardization সব সারায় না। এটি Hessian-এর কর্ণের ঘরগুলো সমান করে, কর্ণের বাইরের ঘর, অর্থাৎ feature-গুলোর correlation, ছোঁয় না। ধরুন “মোট মূল্য” আর “পণ্যের সংখ্যা” প্রায় একই তথ্য বহন করে; standardize করার পরেও দুটির correlation ধরুন ০.৯৯ থাকলে XᵀX/N-এর eigenvalue হয় প্রায় ১.৯৯ আর ০.০১, κ প্রায় ২০০. বাটিটি তির্যকভাবে সরু। এটি সারাতে লাগে পুরো matrix দিয়ে রূপান্তর (whitening, PCA), Hessian-এর তথ্য ব্যবহারকারী পদ্ধতি (Newton, L-BFGS; পর্ব ১৫), বা অপ্রয়োজনীয় feature বাদ দেওয়া বা regularization (পর্ব ১৭)।

মূল উপলব্ধি: Scaling একটি সস্তা কর্ণ-preconditioner, যা gradient descent-এর সবচেয়ে সাধারণ সমস্যা সারায়; correlated feature-এর তির্যক সরু বাটির জন্য আরও শক্তিশালী হাতিয়ার লাগে।

প্র ০৫scikit-learn “converged” বললে বা ConvergenceWarning না দিলে কি ধরে নেওয়া যায় model ঠিকঠাক শিখেছে?

না, আর অংশ ৭-এর desktop run তার সরাসরি উদাহরণ। SGDClassifier ডিফল্ট “optimal” schedule আর খুব ছোট alpha-তে ৪৪ epoch-এ চুপচাপ থেমেছে, কোনো warning নেই, অথচ ওজনগুলো সঠিক মান থেকে অনেক দূরে (একটি প্রায় দ্বিগুণ, একটি তিন গুণের বেশি, একটি প্রায় এক-তৃতীয়াংশ) আর accuracy তিন শতাংশ বিন্দু কম। কারণ scikit-learn-এর SGD-তে থামার শর্ত হলো: পরপর n_iter_no_changeটি epoch-এ loss tol-এর চেয়ে বেশি না কমা। Loss-এর কমা থেমে যেতে পারে সর্বনিম্নে পৌঁছানোর কারণে, আবার noise-এর মেঝেতে বা ভুল η-তে আটকে যাওয়ার কারণেও। শর্তটি দুটির পার্থক্য করতে পারে না।

উল্টো দিকেও ভুল হয়। ConvergenceWarning মানে max_iter শেষ হয়েছে, কিন্তু শর্ত পূরণ হয়নি; তার মানে এই নয় যে model অকেজো। অনেক সময় ফল ইতিমধ্যে যথেষ্ট ভালো, শুধু tol খুব কড়া। তাই warning-কে উপেক্ষা বা চাপা দেওয়া (filterwarnings) যেমন ভুল, তেমনি warning দেখলেই আতঙ্কিত হওয়াও ভুল। দুটি ক্ষেত্রেই উত্তর হলো স্বাধীন যাচাই।

ব্যবহারিক যাচাইয়ের ক্রম: (১) ছোট ডেটায় একটি নির্ভরযোগ্য solver (closed form, lbfgs)-এর সঙ্গে ওজন আর loss মেলানো; (২) loss-এর রেখা আঁকা, শুধু শেষ সংখ্যা নয়; (৩) batch পদ্ধতিতে gradient-এর দৈর্ঘ্য দেখা; (৪) কয়েকটি seed আর দুই-তিনটি η-তে ফল কতটা বদলায় দেখা; (৫) শেষ বিচার validation ডেটায়, train-এ নয়। আর মনে রাখুন, optimization পুরোপুরি সফল হলেও model খারাপ হতে পারে: ভুল feature, leakage বা overfitting optimization-এর আওতার বাইরে।

এই প্রসঙ্গে পরিসংখ্যানের একটি পুরোনো সতর্কবাণী মনে আসে: একটি iterative পদ্ধতির “converged” বার্তা algorithm-এর নিজের শর্ত পূরণের খবর, আপনার উদ্দেশ্য পূরণের নয়। statsmodels-এর মতো library-ও তাই fit-এর পরে convergence-এর তথ্য আলাদা করে দেখায়।

মূল উপলব্ধি: “থেমেছে” আর “পৌঁছেছে” আলাদা; library-র বার্তা একটি সংকেত মাত্র, যাচাই হলো স্বাধীন solver, loss-এর রেখা আর validation.

অনুশীলন

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

  1. পূর্ণ numerical: f(x) = ½(4x1² + x2²). (ক) L, μ, κ আর স্থিতির সীমা কত? (খ) x0 = (১, ২) থেকে η = ০.২-এ এক ধাপ নিন; নতুন x আর f কত? (গ) সেরা ধ্রুব η আর তখনকার হার কত, আর ভুলকে ১০৬ গুণ কমাতে মোটামুটি কত ধাপ লাগবে? η = 1/L-এ কত?

    (ক) Hessian diag(৪, ১), তাই L = ৪, μ = ১, κ = ৪, আর η < 2/L = ০.৫. (খ) ∇f = (৪x1, x2) = (৪, ২). x1 = (১ − ০.৮, ২ − ০.৪) = (০.২, ১.৬). f ৪ থেকে নেমে ½(৪·০.০৪ + ২.৫৬) = ১.৩৬. দিকভিত্তিক গুণক ১ − ০.২·৪ = ০.২ আর ১ − ০.২·১ = ০.৮. (গ) η = 2/(L + μ) = ০.৪, হার (κ − ১)/(κ + ১) = ০.৬. ০.৬k ≤ ১০−৬ মানে k ≥ ln(১০৬)/ln(১/০.৬) = ১৩.৮২/০.৫১১ ≈ ২৭.০৫, অর্থাৎ ২৮ ধাপ. η = 1/L = ০.২৫-এ হার ০.৭৫, k ≥ ১৩.৮২/০.২৮৮ ≈ ৪৮.০২, অর্থাৎ ৪৯ ধাপ, প্রায় দ্বিগুণ।

  2. সমালোচনা ও নকশা: বাজারঘরের একজন সহকর্মী (বা একটি AI assistant) একটি regression model-এ কাঁচা feature (টাকা, km, সংখ্যা) দিয়ে mini-batch SGD চালিয়েছেন, B = ৩২ আর η = ০.০১. Loss কয়েক ধাপে NaN হয়েছে। তিনি B বাড়িয়ে ৫১২ করেছেন আর linear scaling rule মেনে η = ০.১৬ দিয়েছেন; এবার আরও দ্রুত NaN. শেষে তিনি test set-এর score দেখে η = ০.০০০০১ বেছেছেন। কোথায় কোথায় ভুল, আর আপনি কীভাবে নতুন করে নকশা করবেন?

    মূল কারণ: NaN হওয়া মানে η > 2/L, আর L বিশাল কারণ টাকার কলাম scale করা হয়নি (অংশ ৭-এ এমন ডেটায় κ ≈ ৫.৮ × ১০৮)। Batch বাড়ালে noise কমে, কিন্তু L বদলায় না, তাই η বাড়ানো বিপদ আরও বাড়িয়েছে: linear scaling rule η-এর ওপরের সীমা পেরোতে দেয় না। Test-এ বাছা: test score আর নিরপেক্ষ থাকে না; আর এত ছোট η হয়তো শুধু টাকার দিকটি স্থির রাখে, বাকি দিকগুলো প্রায় শেখেই না। নতুন নকশা: (১) train, validation, test আলাদা; (২) pipeline-এ StandardScaler, শুধু train-এ fit; (৩) XᵀX/N থেকে L মেপে η-এর সীমা জানা, তারপর ছোট sweep; (৪) ধ্রুব η baseline আর একটি decay schedule, কয়েকটি seed-এ, validation loss-এ তুলনা; (৫) closed form-এর সঙ্গে মিলিয়ে নেওয়া; (৬) test শুধু একবার, একেবারে শেষে।

  3. আউটপুট অনুমান করুন: চালানোর আগে লিখে ফেলুন নিচের কোড কী ছাপবে।
    import numpy as np
    eta0, T = 0.2, 4
    for t in range(T + 1):
        print(t, round(eta0 * 0.5 * (1 + np.cos(np.pi * t / T)), 4), eta0 * 0.5 ** (t // 2))
    0 0.2 0.2
    1 0.1707 0.2
    2 0.1 0.1
    3 0.0293 0.1
    4 0.0 0.05

    দ্বিতীয় কলাম cosine: cos ০ = ১ থেকে ০.২; cos(π/৪) ≈ ০.৭০৭১ থেকে ০.১ × ১.৭০৭১ = ০.১৭০৭; cos(π/২) = ০ থেকে ০.১; cos(৩π/৪) ≈ −০.৭০৭১ থেকে ০.০২৯৩; cos π = −১ থেকে ০. তৃতীয় কলাম step decay: প্রতি দুই ধাপে অর্ধেক (t // 2 = ০, ০, ১, ১, ২)। এটি round করা হয়নি, তবু 0.2 * 0.5 আর 0.2 * 0.25 float-এ হুবহু 0.1 আর 0.05 ছাপে। লক্ষ করুন, শেষে cosine শূন্যে পৌঁছায় কিন্তু step তখনও ০.০৫.

  4. Noise আর batch size: একটি সমস্যায় N = ৫০,০০০ আর একটি উদাহরণের gradient-এর noise σ² = ৩৬. (ক) B = ৯ আর B = ৩৬-এ mini-batch gradient-এর variance আর standard deviation কত (without replacement সংশোধন সহ)? (খ) Noise অর্ধেক করতে খরচ কত গুণ বাড়ল? (গ) B = ৩২, η = ০.০৫ থেকে B = ২৫৬-এ গেলে linear scaling rule কোন η বলবে, আর কোন শর্ত পরীক্ষা না করে সেটি বিশ্বাস করা উচিত নয়?

    (ক) B = ৯: σ²/B = ৪, সংশোধন (৫০,০০০ − ৯)/(৪৯,৯৯৯) ≈ ০.৯৯৯৮৪, তাই variance ≈ ৩.৯৯৯, standard deviation ≈ ২. B = ৩৬: σ²/B = ১, সংশোধন ≈ ০.৯৯৯৩, variance ≈ ০.৯৯৯৩, standard deviation ≈ ১. N অনেক বড় বলে সংশোধন প্রায় নগণ্য। (খ) প্রতি ধাপে খরচ B-এর অনুপাতে, তাই ৪ গুণ খরচে noise অর্ধেক। (গ) B ৮ গুণ, তাই নিয়ম বলবে η = ০.০৫ × ৮ = ০.৪. কিন্তু η তখনও 2/L-এর নিচে কি না দেখতে হবে, warm-up দিয়ে শুরু করা ভালো, আর validation loss-এ পুরোনো সেটিংয়ের সঙ্গে তুলনা করতে হবে; নিয়মটি একটি heuristic, নিশ্চয়তা নয়।

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

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

ক্রমMini-batch SGD-এর একটি epoch-এর কাজ সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Training ডেটার ক্রম এলোমেলো করা (shuffle)
  2. এলোমেলো ক্রম থেকে পরের B-টি উদাহরণ নিয়ে একটি mini-batch বানানো
  3. সেই batch-এ গড় gradient হিসাব করা
  4. Schedule থেকে এই ধাপের η নিয়ে w ← w − η·gradient করা
  5. Epoch শেষে validation loss মেপে থামার শর্ত দেখা

f(x) = ½ xᵀAx, যেখানে A-এর eigenvalue ২ আর ৫০. ধ্রুব η-তে batch gradient descent কোন শর্তে প্রতিটি শুরু থেকে সর্বনিম্নে পৌঁছায়?

  • 0 < η < 1
  • 0 < η < 0.04
  • 0 < η ≤ 0.02, তার বেশি নয়
  • যেকোনো ধনাত্মক η, কারণ loss convex

একটি quadratic loss-এর Hessian-এর condition number κ = ১০০. সেরা ধ্রুব η = 2/(L + μ)-এ batch gradient descent-এর প্রতি ধাপের ভুল কমার গুণক কত?

  • ০.০১
  • ০.৯৯
  • প্রায় ০.৯৮০
  • ০.৫

N = ১০,০০০টি উদাহরণের loss-এর গড় থেকে, একই উদাহরণ দুবার না নিয়ে, B = ১০০-এর একটি mini-batch-এর গড় gradient নেওয়া হলো। একটি উদাহরণের gradient-এর variance σ². এই অনুমান সম্পর্কে কোনটি সঠিক?

  • নিরপেক্ষ, variance প্রায় σ²/১০০
  • Biased, কারণ সব উদাহরণ দেখা হয়নি
  • নিরপেক্ষ, variance σ²/১০
  • নিরপেক্ষ আর variance শূন্য

ধ্রুব learning rate-এ SGD-এর training loss দ্রুত নেমে একটি কোলাহলপূর্ণ স্তরে থেমে আছে, আর ডেটায় label-এর noise আছে। ওই স্তর আরও নামানোর জন্য কোনটি সবচেয়ে সরাসরি উপায়?

  • η বাড়ানো
  • শুধু আরও বেশি epoch চালানো
  • Shuffle বন্ধ করা
  • সময়ের সঙ্গে η কমানো, যেমন cosine বা step decay

ηmax = ০.১, ηmin = ০, মোট T = ৮ epoch-এর cosine schedule η(t) = ½ηmax(1 + cos(πt/T)) (warm-up নেই)। Epoch t = ২-এ η কত?

  • ০.০৫
  • প্রায় ০.০৮৫৪
  • ০.১
  • প্রায় ০.০১৪৬

একটি linear regression-এ একটি feature টাকায় (০ থেকে ২০,০০০) আর অন্যগুলো ১ থেকে ১০-এর মধ্যে। Batch gradient descent খুব ধীর। কোন পরিবর্তন সাধারণত সবচেয়ে বড় উন্নতি আনে?

  • η দশ গুণ বাড়ানো
  • Mini-batch-এ চলে যাওয়া
  • Feature standardize করা (scaler শুধু train-এ fit করে)
  • শুধু max_iter বাড়ানো

শব্দকোষ

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

মনে রাখুন

  1. পরিবার: batch (B = N), mini-batch (B ছোট), SGD (B = ১); একই নিয়ম w ← w − η·ĝ, পার্থক্য gradient-এর নির্ভুলতা আর প্রতি ধাপের খরচে।
  2. Quadratic-এ: eigen দিকের ভুল প্রতি ধাপে (1 − ηλ) গুণ; শর্ত η < 2/L; সেরা η = 2/(L + μ), হার (κ − 1)/(κ + 1); ধাপ κ-এর অনুপাতে।
  3. Noise: mini-batch gradient নিরপেক্ষ, variance (σ²/B)(N − B)/(N − 1); ধ্রুব η-তে মোটামুটি η-এর অনুপাতে একটি মেঝে।
  4. Schedule: step, exponential, cosine আর warm-up মেঝে ভাঙে; Robbins–Monro: ∑η = ∞, ∑η² < ∞; কোনটি সেরা তা কয়েক seed-এ যাচাই করে বলুন।
  5. প্রথম ওষুধ scaling: standardize করলে κ নামে, ধাপ কমে; SGD-এর ফল closed form বা lbfgs-এর সঙ্গে মিলিয়ে নিন, “থেমেছে” মানে “পৌঁছেছে” নয়।

পরের পর্বে: অংশ ৩-এর zig-zag-এর একটি সরাসরি প্রতিকার আছে: আগের ধাপের দিক মনে রাখা। পর্ব ১৪ (Momentum, RMSProp আর Adam — derive করে আর তুলনা করে) একই ill-conditioned বাটিতে heavy-ball momentum, Nesterov, RMSProp আর Adam derive করে নিজে লিখবে, আর দেখাবে কোনটি কখন সাহায্য করে আর কখন করে না।

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

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