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

Momentum, RMSProp আর Adam — derive করে আর তুলনা করে

Momentum, Nesterov, AdaGrad, RMSProp, Adam and AdamW — Derivations, Bias Correction and Honest Comparisons

Taught in Bangla, this lesson shows why gradient descent zig-zags in narrow valleys and derives heavy-ball and Nesterov momentum, AdaGrad, RMSProp and Adam with its bias correction, working steps by hand. It implements the adaptive optimizers in the page and compares them on Rosenbrock, contrasts L2 penalty with AdamW's decoupled weight decay, and reviews known convergence failures and generalisation debates.

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

  • Heavy-ball momentum-এর সূত্র, তাকে exponentially weighted গড় হিসেবে দেখা, আর quadratic-এ তার গতি (√κ − 1)/(√κ + 1) কোথা থেকে আসে
  • Nesterov-এর look-ahead gradient, আর heavy ball-এর সঙ্গে তার তফাত
  • AdaGrad, RMSProp আর Adam নিজে কয়েক লাইনে লেখা, আর Adam-এর bias correction derive করা
  • L2 regularization বনাম decoupled weight decay (AdamW), সংখ্যা দিয়ে
  • Optimizer-এর ন্যায্য তুলনা: learning-rate-এর পরিসর, একাধিক seed, আর Adam-এর জানা ব্যর্থতা

AI-এর ভিত্তি কোর্সের পর্ব ৩৪-এ gradient descent-এর নিয়ম x ← x − η∇f(x), learning rate আর SGD-র ধারণা এসেছিল, আর সেখানে এক লাইনে বলা হয়েছিল যে momentum, RMSProp আর Adam এরই উন্নত রূপ। এই কোর্সের পর্ব ১১-এ Hessian আর পর্ব ১০-এ quadratic form-এর বাটির আকার দেখেছেন। এবার প্রশ্ন: সরু, লম্বা উপত্যকায় সাধারণ gradient descent কেন এত ধীর, আর এই optimizer-গুলো ঠিক কোন কৌশলে, কতটা আর কখন তাকে হারায়? সব কটি optimizer আমরা NumPy-তে নিজে লিখব, তিন-চার লাইনে। চালানো যায় এমন চারটি ব্লক পাতাতেই চলে (ব্রাউজারের Python, NumPy আর matplotlib); প্রথমবার প্যাকেজ নামতে কয়েক সেকেন্ড লাগে।

১ · সমস্যাটি — সরু উপত্যকায় gradient descent-এর এঁকেবেঁকে চলা

ধরুন পাহাড়ি এলাকার একটি সরু, লম্বা নালা দিয়ে একটি ফুটবল নামছে। নালার দুই পাড় খুব খাড়া, কিন্তু নালা নিজে প্রায় সমতল, ধীরে ধীরে নিচে নামে। যে বল প্রতি মুহূর্তে শুধু পায়ের নিচের ঢাল দেখে, সে এক পাড় থেকে আরেক পাড়ে লাফাতে থাকে, আর নালা ধরে নিচে নামে খুব ধীরে। একটি ভারী বল কিন্তু আগের গতি ধরে রাখে: পাড়ের দিকে ধাক্কাগুলো একবার এদিক একবার ওদিক, তাই কাটাকাটি হয়ে যায়; নালা ধরে নামার টান প্রতিবার একই দিকে, তাই জমতে থাকে। Momentum-এর পুরো ধারণা এটুকুই।

এই পর্বের প্রথম পরীক্ষাগার একটি সরু বাটি: f(x) = ½(x1² + ২৫x2²). এর Hessian হলো diag(১, ২৫), তাই দুটি eigenvalue: সবচেয়ে ছোট μ = ১ (সমতল দিক, x1) আর সবচেয়ে বড় L = ২৫ (x2, খাড়া দিক)। তাদের অনুপাত condition number κ = L/μ = ২৫. পর্ব ১২ (Convexity — কেন কিছু loss সহজ আর কিছু কঠিন)-এ L-কে বলা হচ্ছে smoothness আর μ-কে strong convexity-র মাপ; এখানে quadratic-এ দুটিই সরাসরি eigenvalue.

Gradient descent এই বাটিতে প্রতিটি eigen-দিক আলাদাভাবে চালায়: λ eigenvalue-এর দিকে প্রতি ধাপে দূরত্ব (১ − ηλ) গুণ হয়। খাড়া দিক স্থির রাখতে η < ২/L লাগে; অথচ সমতল দিকের গতি নির্ভর করে ημ-এর ওপর, যা তখন ২/κ-এর চেয়েও ছোট। সেরা স্থির ধাপ η = ২/(L + μ)-এ দুই দিকের গুণক সমান হয়: (κ − ১)/(κ + ১). κ = ২৫-এ তা ২৪/২৬ ≈ ০.৯২৩, অর্থাৎ প্রতি ধাপে ভুল মাত্র ৭.৭% কমে, আর খাড়া দিকে গুণক −০.৯২৩: চিহ্ন প্রতি ধাপে উল্টায়। ওটিই zig-zag. পর্ব ১৩ (Gradient descent-এর পরিবার — batch, SGD, mini-batch আর learning-rate schedule) এই হার, step size-এর সীমা আর feature scaling দিয়ে κ কমানো বিস্তারিত দেখাচ্ছে; এই পর্ব সেখান থেকে শুরু করে প্রশ্ন করে, κ না কমিয়েই কি দ্রুত যাওয়া যায়?

Optimizer-এর পরিবার — দুটি আলাদা কৌশল, তারপর মিলন

Left branch remembers direction; right branch rescales each parameter; Adam combines both

Family tree of gradient-based optimizers Gradient descent Momentum Nesterov AdaGrad RMSProp Adam AdamW x ← x − η∇f Polyak, ১৯৬৪ ১৯৮৩, look-ahead Duchi ও সহকর্মী, ২০১১ Hinton, ২০১২ Kingma ও Ba, ২০১৪ Loshchilov ও Hutter, ২০১৭ দিক মনে রাখে নিজের মাপের ধাপ
ছবি ১.১ — বাঁ শাখা (নীল) অতীতের gradient-এর দিক জমিয়ে রাখে; ডান শাখা (সবুজ) প্রতিটি parameter-এর gradient-এর মাপ মনে রেখে ধাপকে সেই মাপে ভাগ করে। Adam দুটিকে মেলায়, আর AdamW বদলায় weight decay কোথায় বসবে। সাল প্রকাশনার, আর RMSProp কোনো প্রবন্ধে নয়, একটি lecture-এ এসেছিল (অংশ ৪ দেখুন)।

২ · Heavy-ball momentum — সূত্র, derive আর এক ধাপ হাতে

১৯৬৪ সালে সোভিয়েত গণিতবিদ Boris Polyak একটি প্রবন্ধে gradient descent-এ আগের ধাপের একটি অংশ যোগ করার প্রস্তাব দেন; বলটি ভারী হলে জড়তার কারণে যেমন চলে, তাই এর প্রচলিত নাম heavy-ball পদ্ধতি। আধুনিক লেখায় এটি দুই লাইনে:

vt+1 = β vt − η ∇f(xt), xt+1 = xt + vt+1

এখানে v হলো velocity (গতিবেগ, অর্থাৎ শেষ ধাপটি নিজেই), η হলো learning rate, আর β ∈ [০, ১) হলো momentum-এর সহগ, সাধারণত ০.৯. β = ০ হলে এটি সাধারণ gradient descent. v-কে প্রসারিত করলে (v0 = 0 ধরে) দেখা যায় এটি আসলে অতীতের সব gradient-এর একটি ওজনদার যোগফল:

vt+1 = −η [ gt + β gt−1 + β² gt−2 + … ]

যেখানে gt = ∇f(xt). পুরোনো gradient-এর ওজন জ্যামিতিক হারে কমে, একে বলে exponentially weighted (চলমান) গড়। এর দুটি সরাসরি ফল। এক: gradient যদি অনেক ধাপ ধরে একই থাকে (g), তাহলে ধাপ দাঁড়ায় η(১ + β + β² + …)g = ηg/(১ − β). β = ০.৯ হলে কার্যকর ধাপ η-এর ১০ গুণ। দুই: gradient যদি প্রতি ধাপে চিহ্ন বদলায় (g, −g, g, …), তাহলে যোগফল η(১ − β + β² − …)g = ηg/(১ + β), অর্থাৎ ধাপ ছোট হয়। একই সূত্র দুই দিকে দুই রকম আচরণ করে: নালা ধরে জোরে, পাড়ের দিকে আস্তে।

নিচের stepper সরু বাটিতে η = ০.০৭ আর β = ০.৫ নিয়ে প্রথম দুটি ধাপ হাতে করে। η = ০.০৭ ইচ্ছা করে বড় নেওয়া, খাড়া দিকের সীমা ২/L = ০.০৮-এর কাছাকাছি, যাতে সাধারণ GD zig-zag করে।

f(x) = ½(x1² + 25·x2²), ∇f = (x1, 25·x2), η = 0.07, β = 0.5
ধাপ
হিসাব
লক্ষ করুন
  1. শুরু। বিন্দু (৮, ১) থেকে, velocity শূন্য। f-এর মান ৪৪.৫.
  2. প্রথম ধাপ। v0 = 0, তাই v1 = −ηg = (−০.৫৬, −১.৭৫). x2 ১ থেকে −০.৭৫-এ: খাড়া দিকে ধাপ এত বড় যে বিন্দুটি বাটির তলা পেরিয়ে উল্টো পাড়ে।
  3. নতুন gradient. x2 এখন ঋণাত্মক, তাই তার gradient −১৮.৭৫, আগের ২৫-এর উল্টো চিহ্ন। x1-এর gradient ৭.৪৪, আগের মতোই ধনাত্মক।
  4. আগে velocity-কে β দিয়ে গুণ। পুরোনো গতির অর্ধেক থেকে যায়: (−০.২৮, −০.৮৭৫).
  5. তারপর −ηg যোগ। x1-এ −০.২৮ আর −০.৫২০৮ একই দিকে, মিলে −০.৮০০৮ (সাধারণ GD-র ধাপ শুধু −০.৫২). x2-এ −০.৮৭৫ আর +১.৩১২৫ বিপরীত, মিলে মাত্র +০.৪৩৭৫ (GD-র ধাপ +১.৩১).
  6. অবস্থান। Momentum: (৬.৬৩৯, −০.৩১২৫), f = ২৩.২৬. GD: (৬.৯১৯, ০.৫৬২৫), f = ২৭.৮৯. একটি ধাপেই দুটি সুবিধা: সমতল দিকে বেশি এগোনো, খাড়া দিকে কম লাফানো।

Quadratic-এ গতির হিসাব — কেন (√κ − ১)/(√κ + ১)

এক eigen-দিকে, eigenvalue λ, f = (λ/২)x². দুই লাইনের সূত্র থেকে v বাদ দিলে একটি দুই-ধাপের পুনরাবৃত্তি পাওয়া যায়:

xt+1 = (১ + β − ηλ) xt − β xt−1

এর আচরণ ঠিক করে characteristic সমীকরণ z² − (১ + β − ηλ)z + β = ০-এর দুটি মূল; ভুল প্রতি ধাপে মোটামুটি বড় মূলটির মাপ (|z|) গুণ হয়। মূল দুটি জটিল সংখ্যা হলে তাদের গুণফল β, তাই দুটিরই মাপ ঠিক √β, λ যা-ই হোক। Polyak-এর কৌশল: η আর β এমনভাবে বাছা যাতে μ থেকে L পর্যন্ত সব eigenvalue-তে মূল জটিল (বা ঠিক সীমায়) থাকে। হিসাব করলে আসে η = ৪/(√L + √μ)² আর β = ((√κ − ১)/(√κ + ১))², আর তখন প্রতিটি দিকের হার √β:

তিনটি হার, κ = ২৫ আর κ = ১০০-এ

সেরা স্থির ধাপে GD: (κ − ১)/(κ + ১) → ০.৯২৩ আর ০.৯৮০. Tuned heavy ball: (√κ − ১)/(√κ + ১) → ০.৬৬৭ আর ০.৮১৮. ভুল ১০−৬ গুণ কমাতে মোটা দাগে ধাপ লাগে ln(১০৬)/(−ln হার): κ = ১০০-এ GD-র প্রায় ৬৯০, heavy ball-এর প্রায় ৬৯. ধাপের সংখ্যা κ-এর অনুপাত থেকে নেমে √κ-এর অনুপাতে আসে; κ যত বড়, লাভ তত বেশি। একে বলে acceleration.

একই পুনরাবৃত্তি থেকে স্থিতিশীলতার সীমাও আসে: ০ ≤ β < ১ হলে পদ্ধতি স্থির থাকে যদি ηλ < ২(১ + β). β = ০.৯-এ এটি ηλ < ৩.৮, যেখানে GD-র সীমা ηλ < ২. সতর্কতা: এই সুন্দর হার quadratic-এর জন্য প্রমাণিত। সাধারণ strongly convex function-এ heavy ball একই গতির নিশ্চয়তা দেয় না; Lessard, Recht আর Packard-এর ২০১৬ সালের একটি প্রবন্ধে এমন একটি মসৃণ, strongly convex উদাহরণ দেখানো হয়েছে যেখানে এই tuned heavy ball একটি চক্রে আটকে যায়, অভিসারী হয় না।

Zig-zag কীভাবে থামে, তা কয়েক ধাপ ধরে দেখা যাক। একই η = ০.০৭ আর β = ০.৫, একই শুরু।

f = ½(x1² + 25·x2²), শুরু (8, 1), η = 0.07; momentum-এ β = 0.5
t
GD x
GD f
Momentum x
Momentum v
Momentum f
  1. শুরু। একই বিন্দু, একই η; পার্থক্য শুধু β = ০.৫.
  2. t = ১. v শূন্য থেকে শুরু, তাই দুটিই হুবহু একই জায়গায়। x2 উল্টো পাড়ে, −০.৭৫.
  3. t = ২. GD x2-কে +০.৫৬২৫-এ ছুড়ে দেয়: প্রতি ধাপে −০.৭৫ গুণ, অর্থাৎ পাড় থেকে পাড়ে। Momentum-এ পুরোনো velocity (নিচের দিকে) নতুন gradient-এর ধাক্কা (ওপরের দিকে) কাটে, তাই x2 মাত্র −০.৩১২৫. x1-এ momentum ইতিমধ্যে এগিয়ে (৬.৬৩৯ বনাম ৬.৯১৯).
  4. t = ৩. GD-র x2 এখনও দুলছে (−০.৪২১৯). Momentum-এর x1-এর velocity −০.৮৬৫, GD-র ধাপ (প্রায় ০.৪৮)-এর প্রায় দ্বিগুণ: নালা ধরে নামার টান জমেছে।
  5. t = ৪. Momentum-এর f = ১২.২১, GD-র ১৯.১৬. x2 প্রায় শূন্য (০.০৪৩): পাড়ের দোলা প্রায় থেমে গেছে।
  6. সারাংশ। GD-র ধীরতম গুণক ০.৯৩ (সমতল দিক); momentum-এ সমতল দিকে বড় মূল ০.৮২১, খাড়া দিকে জটিল মূলের মাপ √০.৫ ≈ ০.৭০৭. তাই ১০−৬-এ পৌঁছাতে GD-র ২২০ ধাপ, momentum-এর ৮৩ (একটি স্থানীয় run-এ গোনা)।

৩ · Nesterov — যেখানে যাবেন, সেখানকার ঢাল দেখে পা ফেলা

১৯৮৩ সালে Yurii Nesterov একটি ছোট প্রবন্ধে convex আর মসৃণ function-এর জন্য এমন একটি পদ্ধতি দেন, যার ভুল k ধাপে O(১/k²) হারে কমে, যেখানে সাধারণ gradient descent-এর O(১/k). শুধু gradient ব্যবহার করে এমন পদ্ধতির জন্য এটি তাত্ত্বিকভাবে সেরা হার (Nemirovski আর Yudin-এর lower bound-এর সঙ্গে মেলে)। এর একটি জনপ্রিয় রূপ momentum-এর সঙ্গে প্রায় একই, শুধু gradient নেওয়ার বিন্দুটি আলাদা:

vt+1 = β vt − η ∇f(xt + β vt), xt+1 = xt + vt+1

x + βv হলো সেই বিন্দু, যেখানে শুধু momentum-ই আপনাকে নিয়ে যাবে। Heavy ball আগে এখানকার ঢাল দেখে, তারপর momentum-এ ভেসে যায়; Nesterov আগে ভেসে গিয়ে সেখানকার ঢাল দেখে সংশোধন করে। রিকশাচালকের উদাহরণে: সামনের মোড় দেখে আগেই ব্রেক কষা, মোড়ে পৌঁছে নয়। Overshoot শুরু হওয়ার আগেই সংশোধন আসে বলে Nesterov সাধারণত কম দোলে।

Strongly convex আর L-smooth function-এ η = ১/L আর β = (√κ − ১)/(√κ + ১) নিলে Nesterov-এর হার প্রায় (১ − ১/√κ), এবং এটি সব এমন function-এ প্রমাণিত, শুধু quadratic-এ নয়। Quadratic-এ tuned heavy ball তার চেয়ে একটু দ্রুত। নিচের ব্লক তিনটিকে সরু বাটিতে পাশাপাশি চালায়: সেরা স্থির η-এর GD, Polyak-এর tuned heavy ball, আর Nesterov. শুরু (−৯, ১.৫), থামার শর্ত ‖x‖ < ১০−৬.

import numpy as np
import matplotlib.pyplot as plt

A = np.diag([1.0, 25.0])                  # μ = 1, L = 25, তাই κ = 25
grad = lambda x: A @ x
L, mu, x0 = 25.0, 1.0, np.array([-9.0, 1.5])
r = (np.sqrt(L / mu) - 1) / (np.sqrt(L / mu) + 1)    # (√κ − 1)/(√κ + 1)

def gd(x, v):                             # সাধারণ GD, সেরা স্থির η = 2/(L + μ)
    return x - 2 / (L + mu) * grad(x), v
def heavy_ball(x, v):                     # Polyak: β = r², η = 4/(√L + √μ)²
    v = r**2 * v - 4 / (np.sqrt(L) + np.sqrt(mu))**2 * grad(x)
    return x + v, v
def nesterov(x, v):                       # সামনে তাকিয়ে gradient, β = r, η = 1/L
    v = r * v - grad(x + r * v) / L
    return x + v, v

paths = {}
for name, step in [("GD", gd), ("Heavy ball", heavy_ball), ("Nesterov", nesterov)]:
    x, v, P = x0.copy(), np.zeros(2), [x0]
    for t in range(400):
        x, v = step(x, v)
        P.append(x)
    paths[name] = P = np.array(P)
    d = np.linalg.norm(P, axis=1)
    print(f"{name:10s} steps to |x|<1e-6: {np.argmax(d < 1e-6):3d} | |x| after 10 steps: {d[10]:.1e}")

g1, g2 = np.meshgrid(np.linspace(-10, 10, 200), np.linspace(-3, 3, 120))
plt.contour(g1, g2, 0.5 * (g1**2 + 25 * g2**2), levels=15, colors="0.8")
for name, P in paths.items():
    plt.plot(P[:30, 0], P[:30, 1], ".-", label=name)
plt.title("First 30 steps on f = (x1^2 + 25 x2^2) / 2")
plt.xlabel("x1"); plt.ylabel("x2"); plt.legend(); plt.show()

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

GD         steps to |x|<1e-6: 201 | |x| after 10 steps: 4.1e+00
Heavy ball steps to |x|<1e-6:  48 | |x| after 10 steps: 8.2e-01
Nesterov   steps to |x|<1e-6:  85 | |x| after 10 steps: 2.9e+00

তিনটি সংখ্যাই তত্ত্বের সঙ্গে মেলে। GD-র হার (κ − ১)/(κ + ১) = ০.৯২৩; শুরুর দূরত্ব প্রায় ৯.১২ থেকে ১০−৬-এ নামতে ln(৯.১২ × ১০৬)/(−ln ০.৯২৩) ≈ ২০০ ধাপ, আর run-এ লেগেছে ২০১. Tuned heavy ball-এর হার ০.৬৬৭, তাই হিসাব বলে প্রায় ৪০ ধাপ; লেগেছে ৪৮, কারণ সীমার eigenvalue-দুটিতে characteristic সমীকরণের মূল দুটি সমান (double root), তখন ভুল rt-এর সঙ্গে t দিয়েও গুণ হয়, তাই শুরুতে কিছু ধাপ বেশি লাগে। Nesterov-এর হার এখানে ঠিক ১ − ১/√κ = ০.৮, হিসাবে প্রায় ৭২, run-এ ৮৫, একই কারণে। দশ ধাপ পরের দূরত্বেও একই ক্রম: GD ৪.১, Nesterov ২.৯, heavy ball ০.৮২. ছবিতে GD-র পথ x2-অক্ষ বরাবর ওপর-নিচে লাফায় আর x1 বরাবর খুব ধীরে এগোয়; heavy ball-এর পথ বড় বাঁক নিয়ে দোলে কিন্তু দ্রুত ছোট হয়; Nesterov মসৃণভাবে, প্রায় দোলা ছাড়া নামে, কারণ সে সামনের ঢাল দেখে আগেই সংশোধন করে। এখানে η আর β এসেছে জানা μ আর L থেকে; বাস্তবে এগুলো অজানা, তাই খুঁজে নিতে হয়।

বিভিন্ন library-তে momentum-এর সূত্র আলাদা। PyTorch-এর torch.optim.SGD-এর ডকুমেন্টেশন অনুযায়ী সেখানে v ← μv + g আর x ← x − lr·v; অর্থাৎ η velocity-র বাইরে। স্থির learning rate-এ এটি Polyak-এর রূপের সমান, কিন্তু learning rate মাঝপথে বদলালে (schedule) দুটি আলাদা আচরণ করে। scikit-learn-এর MLPClassifier-এ solver="sgd" দিলে default momentum=0.9 আর nesterovs_momentum=True. β বা lr নকল করার আগে সূত্র মিলিয়ে নিন।

৪ · প্রতিটি parameter-এর নিজের ধাপ — AdaGrad আর RMSProp

Momentum κ-এর ক্ষতি কমায়, কিন্তু সব parameter-এ একই η দেয়। বাস্তবে parameter-গুলোর gradient-এর মাপ খুব আলাদা হতে পারে: বাজারঘরের ডেটায় “অর্ডারের টাকা” (হাজারের ঘরে) আর “ডেলিভারির দিন” (একের ঘরে) feature-এর ওজনের gradient-এ হাজার গুণ তফাত, আর কোনো বিরল শব্দের feature-এর gradient প্রায় সবসময় শূন্য। আদর্শ সমাধান Newton-এর মতো Hessian দিয়ে ভাগ করা (পর্ব ১৫), কিন্তু তা দামি। সস্তা বিকল্প: প্রতিটি parameter-এর ধাপকে তার নিজের gradient-এর সাধারণ মাপ দিয়ে ভাগ করা। একে একটি কর্ণ-matrix দিয়ে preconditioning বলা যায়।

AdaGrad (Duchi, Hazan আর Singer, Journal of Machine Learning Research, ২০১১) প্রতিটি parameter i-এর জন্য অতীতের সব gradient-এর বর্গ যোগ করে রাখে:

Gt,i = Gt−1,i + gt,i², xt+1,i = xt,i − η gt,i / (√Gt,i + ε)

ε হলো শূন্য দিয়ে ভাগ ঠেকানোর একটি খুব ছোট সংখ্যা (যেমন ১০−৮)। যে parameter-এর gradient বড়, তার ধাপ ছোট; যে parameter কদাচিৎ gradient পায় (বিরল feature), তার G ছোট থাকে, তাই এলে বড় ধাপ পায়। Sparse ডেটায় (text, click) এটি খুব কাজের। কিন্তু একটি দুর্বলতা স্পষ্ট: G শুধু বাড়ে। Gradient মোটামুটি স্থির g হলে √Gt ≈ |g|√t, তাই ধাপ ≈ η/√t: সময়ের সঙ্গে শূন্যের দিকে যায়, পথ বাকি থাকলেও। দীর্ঘ, non-convex training-এ এটি সমস্যা।

RMSProp এই সমস্যার একটি সরল সমাধান: যোগফলের জায়গায় g²-এর exponentially weighted গড়, যাতে পুরোনো ইতিহাস ভুলে যাওয়া যায়:

Et,i = ρ Et−1,i + (১ − ρ) gt,i², xt+1,i = xt,i − η gt,i / (√Et,i + ε)

ρ সাধারণত ০.৯. নাম এসেছে root mean square থেকে: √E হলো সাম্প্রতিক gradient-এর RMS. Gradient স্থির থাকলে ধাপ ≈ η, কমে না। এর ইতিহাস একটু অদ্ভুত: RMSProp কোনো প্রকাশিত প্রবন্ধে আসেনি; সাধারণত সূত্র হিসেবে উল্লেখ করা হয় Geoffrey Hinton-এর ২০১২ সালের Coursera কোর্স “Neural Networks for Machine Learning”-এর একটি lecture-এর slide (lecture 6e, অর্থাৎ ষষ্ঠ সপ্তাহের পঞ্চম অংশ)। ফলে বিভিন্ন library-তে ε কোথায় বসে (√-এর ভেতরে না বাইরে) বা momentum যোগ হয় কি না, তাতে ছোটখাটো তফাত আছে।

একটি লক্ষণীয় বৈশিষ্ট্য: g/√(g²-এর গড়) অনুপাতটি gradient-এর একককে বাতিল করে। কোনো feature-কে ১০০০ দিয়ে গুণ করলে তার gradient-ও বড় হয়, কিন্তু ভাগফল প্রায় একই থাকে। তাই এই পরিবারের ধাপ মোটামুটি “lr মাপের, gradient-এর চিহ্নের দিকে”, প্রায় sign-এর মতো। এজন্যই এরা feature scaling-এ কম স্পর্শকাতর। কিন্তু এটি Hessian-এর পূর্ণ প্রতিস্থাপন নয়: কর্ণ-matrix দিয়ে ভাগ শুধু অক্ষ বরাবর লম্বা বাটি সারায়; বাটি তির্যক হলে (feature-গুলো সম্পর্কিত হলে) এটি তা ঠিক করতে পারে না।

৫ · Adam — momentum আর RMSProp একসঙ্গে, আর bias correction derive

Diederik Kingma আর Jimmy Ba ২০১৪ সালের ডিসেম্বরে arXiv-এ Adam (adaptive moment estimation) প্রকাশ করেন, আর ২০১৫ সালের ICLR-এ উপস্থাপন করেন। ধারণা: gradient-এর প্রথম moment (গড়, অর্থাৎ momentum-এর দিক) আর দ্বিতীয় moment (g²-এর গড়, অর্থাৎ RMSProp-এর মাপ) দুটিরই exponentially weighted অনুমান রাখা:

mt = β1mt−1 + (১ − β1)gt, vt = β2vt−1 + (১ − β2)gt²

m̂t = mt/(১ − β1t), v̂t = vt/(১ − β2t), xt = xt−1 − α m̂t/(√v̂t + ε)

প্রবন্ধের প্রস্তাবিত default: α (learning rate) = ০.০০১, β1 = ০.৯, β2 = ০.৯৯৯, ε = 10−8. Exponentially weighted গড়ের “স্মৃতির দৈর্ঘ্য” মোটামুটি ১/(১ − β): m মনে রাখে প্রায় শেষ ১০টি gradient, v প্রায় শেষ ১০০০টি।

Bias correction কোথা থেকে আসে

m0 = 0 দিয়ে শুরু করলে সূত্রটি খুলে লেখা যায়: mt = (১ − β1) ∑i=1..t β1t−i gi. ধরা যাক gradient-এর প্রত্যাশিত মান প্রতি ধাপে একই, E[g]. তাহলে

E[mt] = E[g] · (১ − β1) ∑i=1..t β1t−i = E[g] · (১ − β1t)

শেষ ধাপে জ্যামিতিক ধারার যোগফল ব্যবহার হয়েছে। অর্থাৎ শুরুর দিকে m শূন্যের দিকে ঝুঁকে থাকে: t = ১-এ সত্যিকারের মানের মাত্র ১০%. (১ − β1t) দিয়ে ভাগ করলে এই ঝোঁক ঠিক বাতিল হয়। v-এর জন্য একই যুক্তিতে (১ − β2t), যা t = ১-এ মাত্র ০.০০১: v নিজে সত্যিকারের মানের হাজার ভাগের এক ভাগ। সূক্ষ্ম বিষয়টি দুটির অনুপাত। Correction ছাড়া ধাপ হবে α·m/√v, যার প্রত্যাশিত মাপ α·(১ − β1t)/√(১ − β2t); t = ১-এ তা α·০.১/√০.০০১ ≈ ৩.১৬α. অর্থাৎ correction না থাকলে প্রথম ধাপগুলো ছোট নয়, বড় হয়, কারণ v-এর ঝোঁক m-এর চেয়ে অনেক বেশি। Kingma আর Ba-র প্রবন্ধেও এই কথা আছে: β2 এক-এর কাছে হলে correction ছাড়া শুরুর ধাপ অনেক বড় হয়।

f(θ) = θ², g = 2θ, θ0 = 1.0, α = 0.1, β1 = 0.9, β2 = 0.999, ε ≈ 0
t
g
m, v
1 − β1^t, 1 − β2^t
m̂, v̂
ধাপ, θ
  1. শুরু। m আর v দুটিই শূন্য। f = θ², তাই g = ২θ = ২.
  2. t = ১. m = ০.১ × ২ = ০.২, v = ০.০০১ × ৪ = ০.০০৪: দুটিই শূন্যের দিকে ঝুঁকে। ভাগ করলে m̂ = ২, v̂ = ৪, হুবহু g আর g². ধাপ α·m̂/√v̂ = ০.১ × ২/২ = ০.১: প্রথম ধাপ ঠিক α, gradient-এর মাপ যা-ই হোক।
  3. t = ২. নতুন g = ১.৮. m = ০.৯ × ০.২ + ০.১৮ = ০.৩৬; v = ০.৯৯৯ × ০.০০৪ + ০.০০১ × ৩.২৪ = ০.০০৭২৩৬. ভাগক ০.১৯ আর ০.০০১৯৯৯. ধাপ ০.০৯৯৬, প্রায় α.
  4. t = ৩. g = ১.৬০০৮. m̂ = ১.৭৮৬৩ (তিনটি gradient-এর ওজনদার গড়), √v̂ = ১.৮০৭৫. ধাপ আবার প্রায় ০.১: θ = ০.৭০১৬.
  5. তুলনা। একই α = ০.১-এ GD-র ধাপ ০.২θ, gradient-এর সমানুপাতিক, তাই এখানে বড় (০.২, ০.১৬, ০.১২৮)। Adam-এর ধাপ প্রায় স্থির α, কারণ m̂/√v̂ ≈ ১ যখন gradient-এর চিহ্ন স্থির। Correction ছাড়া ধাপ হতো ০.৩১৬, ০.৪২৩, ০.৪৮৯: α-এর তিন থেকে পাঁচ গুণ।

তাই Adam-এর α-কে “প্রতি ধাপে parameter মোটামুটি কতটা সরবে” হিসেবে পড়া যায়। নিচের ব্লক একটি parameter-এর noisy gradient (গড় ০.৫) নিয়ে correction-সহ আর ছাড়া ধাপের মাপ α-এর গুণিতকে ছাপে, ১০০০ ধাপ পর্যন্ত।

import numpy as np

rng = np.random.default_rng(42)
g = 0.5 + 0.1 * rng.normal(size=1000)      # একটি parameter-এর gradient: গড় 0.5, সামান্য noise
b1, b2, lr, eps = 0.9, 0.999, 0.001, 1e-8  # Adam-এর প্রচলিত default
m = v = 0.0
raw_steps = []
print("   t      g       m   m_hat         v   v_hat   raw  corr")
for t in range(1, 1001):
    m = b1 * m + (1 - b1) * g[t - 1]
    v = b2 * v + (1 - b2) * g[t - 1] ** 2
    m_hat, v_hat = m / (1 - b1**t), v / (1 - b2**t)
    raw = m / (np.sqrt(v) + eps)               # bias correction ছাড়া (lr-এর গুণিতক)
    corr = m_hat / (np.sqrt(v_hat) + eps)      # bias correction-সহ
    raw_steps.append(raw)
    if t in (1, 2, 3, 10, 100, 1000):
        print(f"{t:4d} {g[t-1]:6.3f} {m:7.4f} {m_hat:7.4f} {v:9.6f} {v_hat:7.4f} {raw:5.2f} {corr:5.2f}")
k = int(np.argmax(raw_steps))
print(f"largest raw step: {raw_steps[k]:.2f} x lr at t = {k + 1};  corrected step stays near 1 x lr")

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

   t      g       m   m_hat         v   v_hat   raw  corr
   1  0.530  0.0530  0.5305  0.000281  0.2814  3.16  1.00
   2  0.396  0.0873  0.4597  0.000438  0.2191  4.17  0.98
   3  0.575  0.1361  0.5023  0.000768  0.2563  4.91  0.99
  10  0.415  0.3006  0.4615  0.002243  0.2254  6.35  0.97
 100  0.409  0.4556  0.4556  0.023859  0.2506  2.95  0.91
1000  0.582  0.5381  0.5381  0.162590  0.2571  1.33  1.06
largest raw step: 6.61 x lr at t = 14;  corrected step stays near 1 x lr

প্রথম সারিতে m = ০.০৫৩০, অর্থাৎ প্রথম gradient ০.৫৩০-এর মাত্র ১০%; ভাগ করার পর m̂ = ০.৫৩০৫, হুবহু সেই gradient. v-ও ০.০০০২৮১ থেকে v̂ = ০.২৮১৪-এ ওঠে, যা g²-এর সমান। Correction-সহ ধাপ (corr কলাম) প্রথম থেকেই প্রায় ১ × lr, আর পুরো run-এ ০.৯১ থেকে ১.০৬-এর মধ্যে থাকে; ১-এর চেয়ে সামান্য কম হওয়ার কারণ noise: m̂/√v̂ আসলে “গড় ÷ RMS”, যা noise থাকলে ১-এর নিচে। Correction ছাড়া (raw কলাম) প্রথম ধাপ ৩.১৬ × lr, ঠিক অংশ ৫-এর হিসাব ০.১/√০.০০১; তারপর বাড়তে থাকে, t = ১৪-এ সর্বোচ্চ ৬.৬১ × lr, কারণ m দ্রুত (প্রায় ১০ ধাপে) পূর্ণ হয় কিন্তু v-এর লাগে প্রায় ১০০০ ধাপ। ১০০ ধাপ পরেও raw ধাপ ২.৯৫ গুণ, ১০০০ ধাপ পরেও ১.৩৩ গুণ বড়। অর্থাৎ correction বাদ দিলে training-এর শুরুতে কার্যকর learning rate কয়েক গুণ বেড়ে যায়, ঠিক যখন model সবচেয়ে অস্থির।

৬ · Rosenbrock-এ AdaGrad, RMSProp আর Adam — নিজে লিখে তুলনা

সরু বাটি সহজ, কারণ সেটি quadratic. Rosenbrock function f(x, y) = (১ − x)² + ১০০(y − x²)² (Howard Rosenbrock, ১৯৬০) একটি বাঁকা, কলার মতো উপত্যকা: তলায় নামা সহজ, কিন্তু উপত্যকার বাঁক ধরে সর্বনিম্ন বিন্দু (১, ১)-এ পৌঁছানো কঠিন, আর বিভিন্ন জায়গায় বাঁকের মাপ আলাদা, তাই কোনো একটি স্থির κ নেই। Optimizer পরীক্ষার এটি একটি পুরোনো মানদণ্ড; পর্ব ১৫-এ Newton আর L-BFGS-ও এখানেই পরীক্ষা হবে।

নিচের ব্লকে প্রতিটি optimizer একটি function, যা gradient, একটি ছোট state dictionary আর ধাপ-সংখ্যা t নিয়ে শুধু “কত সরতে হবে” ফেরত দেয়। চারটি learning rate-এ (০.০০১ থেকে ১) প্রতিটি ২০০০ ধাপ চলে, শুরু (−১.৫, ২). ছকে থাকে (১, ১) থেকে শেষ দূরত্ব, আর ছবিতে প্রতিটি optimizer-এর নিজের সেরা lr-এর পথ। একটি optimizer-কে শুধু একটি lr-এ চালিয়ে বিচার করা অন্যায্য, তাই পরিসর।

import numpy as np
import matplotlib.pyplot as plt

f = lambda p: (1 - p[0])**2 + 100 * (p[1] - p[0]**2)**2       # Rosenbrock, সর্বনিম্ন (1, 1)
grad = lambda p: np.array([-2 * (1 - p[0]) - 400 * p[0] * (p[1] - p[0]**2), 200 * (p[1] - p[0]**2)])
def gd(g, s, t, lr): return lr * g
def adagrad(g, s, t, lr):                                      # সব অতীত g²-এর যোগফল
    s["G"] = s.get("G", 0) + g**2;              return lr * g / (np.sqrt(s["G"]) + 1e-8)
def rmsprop(g, s, t, lr):                                      # g²-এর চলমান গড়
    s["E"] = 0.9 * s.get("E", 0) + 0.1 * g**2;  return lr * g / (np.sqrt(s["E"]) + 1e-8)
def adam(g, s, t, lr, b1=0.9, b2=0.999):                       # momentum + RMSProp + bias correction
    s["m"] = b1 * s.get("m", 0) + (1 - b1) * g
    s["v"] = b2 * s.get("v", 0) + (1 - b2) * g**2
    return lr * s["m"] / (1 - b1**t) / (np.sqrt(s["v"] / (1 - b2**t)) + 1e-8)

lrs, best = [0.001, 0.01, 0.1, 1.0], {}
print("distance to (1, 1) after 2000 steps, start (-1.5, 2)\nlr      " + "".join(f"{lr:>9}" for lr in lrs))
for name, opt in [("GD", gd), ("AdaGrad", adagrad), ("RMSProp", rmsprop), ("Adam", adam)]:
    row = ""
    for lr in lrs:
        p, s, P = np.array([-1.5, 2.0]), {}, []
        for t in range(1, 2001):
            p = p - opt(grad(p), s, t, lr); P.append(p)
            if f(p) > 1e6: break                                  # ছিটকে গেছে, থামুন
        d = np.linalg.norm(p - 1) if f(p) < 1e6 else np.inf
        row += f"{d:9.1e}" if d < np.inf else " diverged"
        if d < best.get(name, (np.inf,))[0]: best[name] = (d, np.array(P), lr)
    print(f"{name:8s}" + row)

u, w = np.meshgrid(np.linspace(-2, 2, 200), np.linspace(-1, 3, 200))
plt.contour(u, w, np.log10(f((u, w)) + 1e-3), levels=20, colors="0.8")
for name, (d, P, lr) in best.items():
    plt.plot(P[:, 0], P[:, 1], lw=1.2, label=f"{name} (lr={lr})")
plt.plot(1, 1, "k*", ms=12); plt.legend(); plt.xlabel("x"); plt.ylabel("y")
plt.title("Rosenbrock: each optimizer at its best lr in the grid"); plt.show()

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

distance to (1, 1) after 2000 steps, start (-1.5, 2)
lr          0.001     0.01      0.1      1.0
GD        8.2e-01 diverged diverged diverged
AdaGrad   2.7e+00  2.5e+00  1.0e+00  5.7e-01
RMSProp   1.6e+00  1.2e-01  1.2e+00  4.9e-01
Adam      2.4e+00  1.9e-01  1.7e-11  1.3e+00

GD শুধু lr = ০.০০১-এ টিকেছে (দূরত্ব ০.৮২); ০.০১ বা তার বেশিতে ছিটকে গেছে, কারণ শুরুর এলাকায় বাঁক খুব তীক্ষ্ণ (সেখানে Hessian-এর বড় eigenvalue কয়েকশ থেকে হাজারের ঘরে)। AdaGrad সব lr-এ ধীর: তার ধাপ ১/√t হারে কমে, তাই সবচেয়ে বড় lr = ১-এও ২০০০ ধাপে দূরত্ব ০.৫৭. RMSProp-এর সেরা lr = ০.০১ (দূরত্ব ০.১২); বড় lr-এ সে সর্বনিম্নের কাছে স্থির হতে পারে না, কারণ তার ধাপ প্রায় সবসময় lr মাপের, তাই উপত্যকার দুই পাড়ে দোলে। Adam lr = ০.১-এ দূরত্ব ১.৭ × ১০−১১, কার্যত সঠিক উত্তর: দোলার সময় gradient-এর চিহ্ন উল্টায়, তাই m̂ ছোট হয়ে ধাপ নিজে থেকেই কমে যায়। কিন্তু lr = ০.০০১-এ Adam দূরত্ব ২.৪-এ আটকে: তার প্রতিটি ধাপ মোটামুটি lr-এর বেশি নয়, আর ২০০০ × ০.০০১ = ২ একক পথ শুরু থেকে (১, ১) পর্যন্ত বাঁকা উপত্যকা পেরোনোর জন্য যথেষ্ট নয়। lr = ১-এর কলামটি আলাদা করে সন্দেহ করুন: এত বড় ধাপে পথ বিশৃঙ্খল (chaotic), আর একই কোড ডেস্কটপের NumPy-তে চালালে RMSProp আর Adam-এর এই দুটি সংখ্যা অন্য রকম এসেছে (০.৪৯-এর জায়গায় ১.৫, ১.৩-এর জায়গায় ০.৩৬), কারণ rounding-এর ক্ষুদ্র তফাতও সেখানে বড় হয়ে যায়। সতর্কতা: এটি একটি শুরুর বিন্দু, একটি function আর চারটি lr-এর একটি run; এখান থেকে “Adam সেরা” নয়, শুধু “এই ধরনের বাঁকা উপত্যকায় Adam-এর momentum আর নিজের মাপের ধাপ একসঙ্গে কাজে লেগেছে” বলা যায়।

৭ · L2 না weight decay — AdamW আর একটি ন্যায্য তুলনা

AI-এর ভিত্তি কোর্সের পর্ব ৬১-এ L2 regularization এসেছে: loss-এ (λ/২)‖w‖² যোগ, ফলে gradient-এ λw যোগ হয়। সাধারণ gradient descent-এ তখন হালনাগাদ w ← w − η(∇L + λw) = (১ − ηλ)w − η∇L: প্রতি ধাপে ওজন একটি স্থির অনুপাতে শূন্যের দিকে ছোট হয়। এই “ছোট করা”-কেই বলে weight decay, আর SGD-তে দুটি কথা একই জিনিস (পর্ব ৬৮-এ সেখানে এক লাইনে এর ইঙ্গিত ছিল)।

Adam-এ তারা আলাদা হয়ে যায়। Coupled L2 (Adam + L2): g = ∇L + λw-কে Adam-এ দিলে λw-ও m আর v-এর ভেতরে যায়, আর শেষে √v̂ দিয়ে ভাগ হয়। যে ওজনের gradient ঐতিহাসিকভাবে বড় (v̂ বড়), তার শাস্তি কার্যত ছোট; যার gradient ছোট, তার বড়। Decoupled (AdamW): Ilya Loshchilov আর Frank Hutter ২০১৭ সালে arXiv-এ (পরে ICLR ২০১৯-এ) প্রস্তাব দেন, শাস্তিকে Adam-এর হিসাব থেকে বের করে সরাসরি ওজনে বসাতে:

w ← w − η ( m̂/(√v̂ + ε) + λw )

এখানে m̂ আর v̂ শুধু ∇L থেকে আসে। এই AdamW প্রতিটি ওজনকে একই অনুপাতে (ηλ) ছোট করে। তাঁদের image classification পরীক্ষায় AdamW-এর generalization ভালো ছিল; আজ বহু transformer training-এ এটি প্রচলিত পছন্দ। তবে এটি deep network-এর অভিজ্ঞতা; প্রতিটি সমস্যায় স্বয়ংক্রিয় নিশ্চয়তা নয়।

একটি গুরুত্বপূর্ণ ফল: AdamW আর loss + (λ/২)‖w‖²-এর সর্বনিম্ন বিন্দুতে যায় না, কারণ তার স্থির বিন্দুর শর্ত আলাদা। নিচের ব্লক এটি সংখ্যায় দেখায়। কাল্পনিক বাজারঘরের ৩০০টি অর্ডারের ফেরত (logistic regression, চারটি feature, ইচ্ছা করে scale খুব আলাদা: ১, ১, ১০, ০.১), objective J(w) = গড় log loss + (০.০৫/২)‖w‖². Newton-এর ১০ ধাপে সঠিক সর্বনিম্ন বের করে (পর্ব ১১-এর মতো), তারপর Adam + L2 আর AdamW ৩০০০ ধাপ। দ্বিতীয় অংশে GD, momentum (β = ০.৯) আর Adam-কে চারটি learning rate আর পাঁচটি seed-এ ৩০০ ধাপ চালিয়ে J − J*-এর গড়।

import numpy as np
sig = lambda z: 0.5 * (1 + np.tanh(z / 2))              # sigmoid, overflow ছাড়া
def data(seed, n=300):                                  # কাল্পনিক বাজারঘর: অর্ডার ফেরত এল কি না
    rng = np.random.default_rng(seed)
    X = rng.normal(size=(n, 4)) * [1, 1, 10, 0.1]       # feature-গুলোর scale খুব আলাদা
    y = (rng.random(n) < sig(X @ [1.0, -2.0, 0.2, 5.0])).astype(float)
    return X, y
lam, lrs = 0.05, [0.001, 0.01, 0.1, 1.0]
grad = lambda w, X, y: X.T @ (sig(X @ w) - y) / len(y)
J = lambda w, X, y: np.mean(np.logaddexp(0, X @ w) - y * (X @ w)) + lam / 2 * w @ w
def exact(X, y, l=lam, w=np.zeros(4)):                  # Newton-এর ১০ ধাপ: আসল সর্বনিম্ন
    for _ in range(10):
        p = sig(X @ w)
        H = X.T @ (X * (p * (1 - p))[:, None]) / len(y) + l * np.eye(4)
        w = w - np.linalg.solve(H, grad(w, X, y) + l * w)
    return w
def run(opt, X, y, lr, steps=300, decoupled=False):
    w, m, v = np.zeros(4), np.zeros(4), np.zeros(4)
    for t in range(1, steps + 1):
        g = grad(w, X, y) + (0 if decoupled else lam * w)        # coupled: L2 যায় gradient-এ
        if opt == "GD": w = w - lr * g
        elif opt == "Momentum": m = 0.9 * m + g; w = w - lr * m
        else:
            m, v = 0.9 * m + 0.1 * g, 0.999 * v + 0.001 * g**2
            step = m / (1 - 0.9**t) / (np.sqrt(v / (1 - 0.999**t)) + 1e-8)
            w = w - lr * (step + (lam * w if decoupled else 0))   # decoupled: আলাদা করে ছোট করা
    return w
X, y = data(0)
for name, w in [("exact, lam=0.05", exact(X, y)), ("exact, lam=0", exact(X, y, 0.0)),
                ("Adam + L2", run("Adam", X, y, 0.01, 3000)), ("AdamW", run("Adam", X, y, 0.01, 3000, True))]:
    print(f"{name:15s} w = {np.round(w, 3)}  J = {J(w, X, y):.5f}")
print("\nJ - J* after 300 steps, mean of 5 seeds\nlr       " + "".join(f"{lr:>9}" for lr in lrs))
for opt in ["GD", "Momentum", "Adam"]:
    gap = lambda lr: np.mean([J(run(opt, *d, lr), *d) - J(exact(*d), *d) for d in map(data, range(5))])
    print(f"{opt:9s}" + "".join(f"{gap(lr):9.1e}" for lr in lrs))

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

exact, lam=0.05 w = [ 0.688 -1.086  0.106  0.083]  J = 0.46774
exact, lam=0    w = [ 1.181 -1.885  0.141  3.246]  J = 0.78156
Adam + L2       w = [ 0.688 -1.086  0.106  0.083]  J = 0.46774
AdamW           w = [ 1.169 -1.861  0.14   3.181]  J = 0.76830

J - J* after 300 steps, mean of 5 seeds
lr           0.001     0.01      0.1      1.0
GD         1.2e-01  4.0e-02  2.2e-05  3.8e+00
Momentum   4.0e-02  1.6e-05  5.5e-15  6.0e+00
Adam       6.9e-02  6.1e-06  2.3e-15  4.4e-14

প্রথম চার লাইন অংশ ৭-এর মূল কথা। Adam + L2-এর ওজন (০.৬৮৮, −১.০৮৬, ০.১০৬, ০.০৮৩) আর J = ০.৪৬৭৭৪, Newton-এর সঠিক সমাধানের সঙ্গে ছাপা প্রতিটি অঙ্ক পর্যন্ত এক: coupled L2 হুবহু ঘোষিত objective-কে সর্বনিম্ন করেছে। AdamW-এর ওজন (১.১৬৯, −১.৮৬১, ০.১৪, ৩.১৮১), যা λ = ০-এর শাস্তিহীন সমাধান (১.১৮১, −১.৮৮৫, ০.১৪১, ৩.২৪৬)-এর খুব কাছে; তার J = ০.৭৬৮৩০ অনেক বেশি। চতুর্থ ওজনেই তফাত সবচেয়ে বড় (০.০৮৩ বনাম ৩.১৮১): ওই feature-এর scale ০.১, তাই তার ভালো ওজন বড়, আর L2 শাস্তি তাকেই সবচেয়ে জোরে টানে। AdamW-এর decay এই full-batch run-এ প্রায় কাজই করেনি; কেন, তার একটি ব্যাখ্যা ভাবনার প্রশ্ন ০৩-এ। দ্বিতীয় ছক learning-rate-এর প্রতি সংবেদনশীলতা, পাঁচটি seed-এর গড়: GD আর momentum ভালো শুধু lr = ০.১-এ (৩০০ ধাপে momentum ৫.৫ × ১০−১৫ পর্যন্ত পৌঁছেছে, Adam-এর সমান মানের), কিন্তু lr = ১-এ দুটিই খারাপ (৩.৮ আর ৬.০), কারণ scale ১০-এর feature-এর দিকে ধাপ খুব বড়। Adam ০.১ আর ১ দুটিতেই প্রায় নিখুঁত, ০.০১-এও ৬ × ১০−৬. ০.০০১-এ তিনটিই পিছিয়ে: ৩০০ ধাপ যথেষ্ট নয়। অর্থাৎ Adam-এর আসল সুবিধা এখানে গতিতে নয়, স্থিতিশীলতার পরিসরে; আর ইচ্ছা করে scale এলোমেলো রাখা হয়েছে, standardize করলে GD-র পরিসরও অনেক চওড়া হতো (পর্ব ১৩)।

৮ · কোনটি কখন — সৎ তুলনা আর জানা ব্যর্থতা

উপরের run-গুলো দুটি ছোট function আর একটি convex model, পূর্ণ gradient-সহ; এখান থেকে সাধারণ সিদ্ধান্ত টানা ভুল হবে। বাস্তব training-এ gradient noisy (mini-batch থেকে আসে, অর্থাৎ SGD-র পরিবেশ), loss non-convex, আর মাপকাঠি training loss নয়, নতুন ডেটায় ফল। কয়েকটি সতর্ক পর্যবেক্ষণ:

OptimizerState (প্রতি parameter)শক্তিদুর্বলতা
GD / SGDকিছু নাসরল, তত্ত্ব পরিষ্কার, কম memoryκ বড় হলে ধীর; lr-এর পরিসর সরু
Momentum, Nesterov১টি (v)সরু উপত্যকায় √κ-এর লাভ; ভালো tuning-এ খুব শক্তিশালীlr আর β দুটিই tune করতে হয়; বেশি β-তে overshoot
AdaGrad১টি (G)Sparse feature, convex সমস্যাধাপ ১/√t হারে শুকিয়ে যায়
RMSProp১টি (E)নিজের মাপের ধাপ, ধাপ শুকায় নাপ্রকাশিত প্রবন্ধ নেই; স্থির lr-এ সর্বনিম্নের কাছে দুলতে পারে
Adam / AdamW২টি (m, v)lr-এর বড় পরিসরে কাজ করে; scaling-এ কম স্পর্শকাতরMemory দ্বিগুণ; কিছু ক্ষেত্রে অভিসারী নয়; generalization নিয়ে বিতর্ক

Adam সবসময় অভিসারী নয়। Sashank Reddi, Satyen Kale আর Sanjiv Kumar-এর ২০১৮ সালের ICLR প্রবন্ধ “On the Convergence of Adam and Beyond” দেখায় যে Adam-এর মূল প্রবন্ধের অভিসারের প্রমাণে একটি ফাঁক ছিল, আর একটি সরল এক-মাত্রিক online convex উদাহরণ দেয়, যেখানে বেশিরভাগ ধাপে ছোট gradient এক দিকে আর মাঝে মাঝে একটি বড় gradient অন্য দিকে আসে। v-এর স্মৃতি সীমিত বলে বড় gradient-এর তথ্য দ্রুত মুছে যায়, আর Adam ভুল প্রান্তে গিয়ে স্থির হয়। তাঁদের প্রস্তাবিত AMSGrad v̂-কে কখনো কমতে দেয় না (অতীতের সর্বোচ্চ রাখে)। বাস্তবে এই ব্যর্থতা কত ঘন ঘন ঘটে, তা নিয়ে মতভেদ আছে; PyTorch-এর Adam-এ amsgrad=True একটি বিকল্প হিসেবে আছে।

Generalization নিয়ে বিতর্ক। Ashia Wilson ও সহকর্মীদের ২০১৭ সালের NeurIPS প্রবন্ধ “The Marginal Value of Adaptive Gradient Methods in Machine Learning” কয়েকটি পরীক্ষায় দেখায় যে ভালোভাবে tune করা SGD + momentum-এর test ফল adaptive পদ্ধতির চেয়ে ভালো বা সমান ছিল। আবার পরে অনেক বড় benchmark গবেষণা (যেমন Schmidt, Schneider আর Hennig-এর ২০২১ সালের “Descending through a Crowded Valley”) মোটামুটি এই সিদ্ধান্তে আসে যে কোনো একটি optimizer সব কাজে সেরা নয়, আর tuning-এর বাজেট প্রায়ই optimizer বাছাইয়ের চেয়ে বেশি গুরুত্বপূর্ণ। অভিজ্ঞতার কথা হিসেবে বলা যায়: transformer-জাতীয় model-এ AdamW প্রচলিত, কিছু image model-এ SGD + momentum আজও জনপ্রিয়; কিন্তু এগুলো প্রচলন, প্রমাণিত নিয়ম নয়।

ব্যবহারিক রেসিপি

(১) Convex, ছোট-মাঝারি সমস্যায় (logistic, linear model) প্রথমে L-BFGS বা Newton (পর্ব ১৫); learning rate লাগেই না। (২) Neural network বা খুব বড় ডেটায়: AdamW, lr ১০−৪ থেকে ১০−২-এর মধ্যে একটি log-পরিসরে খুঁজুন, সঙ্গে warm-up আর decay schedule (পর্ব ১৩)। (৩) সময় থাকলে SGD + Nesterov momentum-ও একই যত্নে tune করে তুলনা করুন। (৪) প্রতিটি তুলনা একাধিক seed-এ, validation ডেটায়, test set তালা দিয়ে (পর্ব ০৪)। (৫) Feature scaling করুন; Adam কম স্পর্শকাতর হলেও regularization-এর অর্থ scale-এর ওপর নির্ভর করে।

scikit-learn-এ এই optimizer-গুলো সরাসরি দেখা যায় MLPClassifier আর MLPRegressor-এ: default solver="adam", সঙ্গে beta_1=0.9, beta_2=0.999, epsilon=1e-8, আর learning_rate_init=0.001, অর্থাৎ Kingma ও Ba-র প্রস্তাবিত মানগুলোই। কিন্তু alpha সেখানে loss-এ যোগ হওয়া L2 শাস্তি, AdamW-এর decoupled decay নয়। আর SGDClassifier-এ কোনো momentum নেই; সেখানে learning-rate schedule আছে (পর্ব ১৩)। Deep learning framework-এর optimizer আসবে Deep Learning কোর্সে।

আরও গভীরে: Momentum আর acceleration-এর পরিষ্কার জ্যামিতিক ব্যাখ্যার জন্য Gabriel Goh-এর ২০১৭ সালের Distill প্রবন্ধ “Why Momentum Really Works” পড়ার মতো; Nesterov-এর পদ্ধতির পূর্ণ তত্ত্ব তাঁর Introductory Lectures on Convex Optimization (২০০৪) বইয়ে। Optimizer-গুলোকে একই কাঠামোয় দেখার একটি পরিচিত সমীক্ষা Sebastian Ruder-এর ২০১৬ সালের arXiv লেখা “An overview of gradient descent optimization algorithms”. Warm-up কেন Adam-এ কাজে লাগে, তার একটি ব্যাখ্যা দেয় Liu ও সহকর্মীদের ২০২০ সালের RAdam প্রবন্ধ (ICLR): শুরুতে v̂-এর অনুমান খুব কম gradient থেকে আসে, তাই তার variance বেশি।

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

  • Momentum-এর হালনাগাদ লিখুন, আর β = ০.৯-এ স্থির gradient-এ কার্যকর ধাপ কত? v ← βv − ηg, x ← x + v; ধাপ η/(১ − β) = ১০η.
  • Nesterov আর heavy ball-এর তফাত? Nesterov gradient নেয় x + βv-তে (look-ahead); convex-smooth function-এ O(১/k²) প্রমাণিত।
  • Adam-এ bias correction কেন? m0 = v0 = 0 বলে E[mt] = (১ − β1t)E[g]; ভাগ করে ঝোঁক বাতিল। না করলে শুরুর ধাপ বড় হয়, কারণ v বেশি ঝুঁকে থাকে।
  • Adam + L2 আর AdamW-এর তফাত? প্রথমটিতে λw gradient-এ ঢুকে √v̂ দিয়ে ভাগ হয়; AdamW ওজনকে সরাসরি ηλw কমায়।
  • AdaGrad-এর মূল দুর্বলতা আর RMSProp-এর সমাধান? g²-এর যোগফল শুধু বাড়ে, ধাপ শুকায়; RMSProp যোগফলের জায়গায় চলমান গড় নেয়।

৯ · AI-এর যুগে — AI-এর লেখা optimizer কোড আর দাবি যাচাই

“Adam দিয়ে training loop লিখে দাও” বললে AI assistant কয়েক সেকেন্ডে পরিষ্কার কোড দেয়। ঝুঁকি থাকে খুঁটিনাটিতে, যা চালিয়ে না দেখলে ধরা পড়ে না: t শূন্য থেকে শুরু করলে bias correction-এ শূন্য দিয়ে ভাগ, weight decay ভুল জায়গায়, বা একটি run দেখে “Adam সেরা” সিদ্ধান্ত।

AI প্রায়ই যা করেকেন সমস্যাকীভাবে ধরবেন
PyTorch-এর Adam(weight_decay=...)-কে AdamW-এর সমান বলেপ্রথমটি coupled L2, দ্বিতীয়টি decoupled; ফল আলাদানিজের version-এর docs-এ সূত্র পড়ুন; ছোট উদাহরণে দুটি চালিয়ে ওজন মিলিয়ে দেখুন
Bias correction-এ t = ০ থেকে শুরু, বা correction বাদশূন্য দিয়ে ভাগ, বা শুরুর ধাপ কয়েক গুণ বড়প্রথম ধাপ হাতে হিসাব করুন: correction-সহ তা প্রায় ঠিক lr
প্রতিটি optimizer-কে একটিই default lr-এ চালিয়ে তুলনাএক lr-এ এক optimizer সুবিধা পায়Log-পরিসরে lr-এর grid, প্রতিটির নিজের সেরা lr
একটি seed, training loss দেখে বিজয়ী ঘোষণাNoise আর overfitting-কে পার্থক্য ভাবাকয়েকটি seed, validation ডেটা, সরল baseline-এর সঙ্গে তুলনা
Momentum-এর সূত্র এক library থেকে, lr অন্য library থেকেη velocity-র ভেতরে না বাইরে, তাতে কার্যকর ধাপ বদলায়দুটি সূত্র পাশাপাশি লিখে মেলান
“Adam-এর অভিসার প্রমাণিত” বলে দাবিমূল প্রমাণে ফাঁক ছিল; অভিসারী নয় এমন উদাহরণ আছেপ্রাথমিক উৎস (Reddi ও সহকর্মী, ২০১৮) দেখুন

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

আমি NumPy-তে নিজে Adam আর AdamW লিখতে চাই, একটি logistic regression-এ (n = 300, ৪টি feature, seed 0 দিয়ে)। ১. প্রতিটি হালনাগাদের সূত্র লেখো: m, v, m̂, v̂, আর weight decay ঠিক কোথায় বসে। t কত থেকে শুরু, তা স্পষ্ট বলো। ২. দেখাও প্রথম ধাপে (t = 1) bias correction-সহ ধাপের মাপ কত, আর correction ছাড়া কত, β1 = 0.9 আর β2 = 0.999 ধরে। ৩. PyTorch-এর Adam(weight_decay) আর AdamW-এর তফাত এক লাইনে বলো, আর কোন version-এর docs থেকে বলছ তা উল্লেখ করো। ৪. Adam, SGD + momentum আর GD-র ন্যায্য তুলনার পরিকল্পনা দাও: lr-এর log-grid, ৫টি seed, validation ডেটা, test set আলাদা। ৫. কোন জায়গায় তোমার উত্তর ভুল হতে পারে, আর আমি কীভাবে সংখ্যা দিয়ে যাচাই করব, তা বলো।

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

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

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

প্র ০১Heavy ball quadratic-এ এত দ্রুত হলে Nesterov-এর পদ্ধতির দরকার কেন হলো?

Polyak ১৯৬৪ সালে heavy ball-এর গতি প্রমাণ করেছিলেন বিন্দুর কাছাকাছি, যেখানে function প্রায় quadratic; সেখানে হার (√κ − ১)/(√κ + ১), যা এই পর্বের সরু বাটির run-এ সবচেয়ে কম ধাপ (৪৮) দিয়েছে। সমস্যা হলো বিন্দু থেকে দূরে, যেখানে Hessian জায়গাভেদে বদলায়। ২০১৬ সালে Laurent Lessard, Benjamin Recht আর Andrew Packard control theory-র যন্ত্র (integral quadratic constraints) দিয়ে optimizer বিশ্লেষণ করতে গিয়ে একটি মসৃণ, strongly convex, এক-মাত্রিক function দেখান, যেখানে quadratic-এর জন্য tuned heavy ball অভিসারী না হয়ে একটি চক্রে ঘুরতে থাকে। অর্থাৎ quadratic-এর সেরা η আর β সাধারণ convex function-এ নিরাপদ নয়।

Nesterov-এর ১৯৮৩ সালের অবদান ছিল এমন একটি পদ্ধতি, যার গতির প্রমাণ সব convex আর L-smooth function-এ খাটে: ভুল O(১/k²), আর strongly convex হলে প্রতি ধাপে প্রায় (১ − ১/√κ). এটি গুরুত্বপূর্ণ কারণ Nemirovski আর Yudin আগেই (১৯৮৩ সালের বইয়ে) দেখিয়েছিলেন যে শুধু gradient-এর তথ্য ব্যবহার করে এমন কোনো পদ্ধতি সাধারণভাবে এর চেয়ে দ্রুত হতে পারে না। Nesterov সেই সীমায় পৌঁছান, তাই তাঁর পদ্ধতিকে “optimal” বলা হয়।

মজার ব্যাপার, look-ahead-এর অন্তর্দৃষ্টি পরে এসেছে; মূল প্রবন্ধটি ছিল একটি বীজগাণিতিক নির্মাণ, যার জ্যামিতিক অর্থ নিয়ে বহু বছর ধরে নতুন নতুন ব্যাখ্যা প্রকাশিত হয়েছে (differential equation হিসেবে, estimate sequence হিসেবে, ইত্যাদি)। Deep learning-এ এর জনপ্রিয়তা আসে ২০১৩ সালে Sutskever ও সহকর্মীদের প্রবন্ধে, যেখানে এটি momentum-এর একটি ছোট পরিবর্তন হিসেবে লেখা হয়।

বাস্তবে, noisy mini-batch gradient থাকলে acceleration-এর তাত্ত্বিক সুবিধার অনেকটা হারিয়ে যায়, কারণ momentum noise-ও জমায়। তবু Nesterov momentum প্রায়ই স্থিতিশীলতায় সামান্য সুবিধা দেয়, আর তার খরচ heavy ball-এর সমান।

মূল উপলব্ধি: quadratic-এ দ্রুত আর সব convex function-এ প্রমাণিতভাবে দ্রুত এক কথা নয়; Nesterov দ্বিতীয়টি দেয়, heavy ball সাধারণভাবে দেয় না।

প্র ০২এত প্রমাণের পরও “Adam সবচেয়ে ভালো optimizer” কথাটি কেন ভুল?

প্রথমত, “ভালো” কয়েক রকম: কত দ্রুত training loss কমে, কত কম tuning লাগে, আর নতুন ডেটায় ফল কত ভালো। এই পর্বের run-গুলোতে Adam-এর সবচেয়ে বড় সুবিধা ছিল দ্বিতীয়টি: logistic regression-এ lr ০.০১ থেকে ১ পর্যন্ত পুরো পরিসরে কাজ করেছে, যেখানে GD আর momentum ভালো ছিল শুধু lr = ০.১-এর কাছে। কিন্তু momentum তার সেরা lr-এ Adam-এর সমান নির্ভুলতায় পৌঁছেছে। আর Rosenbrock-এ Adam জিতেছে একটি শুরুর বিন্দু আর একটি grid-এ; অন্য শুরুতে ফল বদলাতে পারে।

দ্বিতীয়ত, অভিসার। অংশ ৮-এ দেখেছেন, Reddi, Kale আর Kumar ২০১৮ সালে মূল প্রমাণের ফাঁক ধরেন আর এমন সরল convex উদাহরণ দেন যেখানে Adam ভুল বিন্দুতে স্থির হয়, কারণ v̂-এর সীমিত স্মৃতি বিরল কিন্তু বড়, তথ্যপূর্ণ gradient দ্রুত ভুলে যায়।

তৃতীয়ত, generalization. Wilson ও সহকর্মীরা ২০১৭ সালে একটি ছোট তাত্ত্বিক উদাহরণও দেন, যেখানে adaptive পদ্ধতি এমন সমাধানে যায় যা ভালোভাবে generalize করে না, যদিও SGD সঠিক সমাধানে যায়। পরে AdamW আর ভালো schedule দিয়ে অনেক কাজে এই ফারাক কমেছে বলে বহু গবেষণায় দেখা গেছে, আর ২০২১ সালের benchmark-এ (Schmidt ও সহকর্মী) ১০০টির বেশি optimizer তুলনা করে দেখা যায়, tuning-এর পর বেশ কয়েকটি জনপ্রিয় optimizer কাছাকাছি।

চতুর্থত, খরচ: Adam প্রতি parameter-এ দুটি অতিরিক্ত সংখ্যা রাখে, বড় model-এ যা model-এর আকারের দ্বিগুণ memory. এই কারণে memory বাঁচানোর optimizer নিয়েও কাজ চলছে।

মূল উপলব্ধি: Adam একটি নিরাপদ, কম-tuning-এর শুরু; কিন্তু “সেরা” দাবি করতে হলে একই যত্নে tune করা প্রতিদ্বন্দ্বী, একাধিক seed আর validation ডেটা লাগে।

প্র ০৩Adam-এর ε শুধু “শূন্য দিয়ে ভাগ ঠেকানো”-র জন্য, নাকি এর প্রভাব আরও গভীর?

ε সাধারণত ১০−৮, আর বেশিরভাগ সময় সত্যিই চোখে পড়ে না। কিন্তু দুটি পরিস্থিতিতে এটি ফল ঠিক করে দেয়। প্রথম: যখন gradient নিজেই খুব ছোট, √v̂ ε-এর কাছাকাছি। তখন ধাপ m̂/(√v̂ + ε) আর প্রায় ±α থাকে না, gradient-এর সমানুপাতিক হয়ে যায়; অর্থাৎ Adam ধীরে ধীরে সাধারণ momentum-SGD-র মতো আচরণ করে, যার কার্যকর learning rate α/ε, বিশাল। কিছু গবেষক তাই ε-কে আরেকটি hyperparameter হিসেবে tune করার পরামর্শ দেন; বিভিন্ন library-র default-ও আলাদা (যেমন Keras-এর Adam-এ default epsilon ১০−৭), যা ছোট কিন্তু বাস্তব তফাত তৈরি করে।

দ্বিতীয় পরিস্থিতি এই পর্বের AdamW run-এ দেখা গেছে। AdamW-এর স্থির বিন্দুতে m̂/(√v̂ + ε) = −λw হতে হবে। Full-batch-এ gradient প্রায় স্থির, তাই m̂/√v̂ ≈ g/|g|, যার মাপ প্রায় ১, gradient যত ছোটই হোক। এই সমীকরণ মিলতে পারে কেবল তখন, যখন |g| এত ছোট যে ε ভাগফলকে কমিয়ে দেয়: আমাদের হিসাবে |g| মোটামুটি ১০−৯-এর ঘরে। অর্থাৎ যথেষ্ট লম্বা run-এ AdamW-এর ওজন প্রায় শাস্তিহীন সমাধানে যায়, আর decay-এর প্রভাব কার্যত ε ঠিক করে। আমাদের ৩০০০ ধাপের run সেদিকেই যাচ্ছিল (ওজন শাস্তিহীন সমাধানের খুব কাছে)। Mini-batch training-এ ছবিটি আলাদা: সেখানে v̂-এ gradient-এর noise প্রাধান্য পায়, তাই m̂/√v̂ ছোট থাকে আর weight decay সত্যিকারের টান দেয়। এটি আমাদের নিজস্ব ছোট বিশ্লেষণ, একটি সাধারণ উপপাদ্য নয়; কিন্তু বার্তাটি স্পষ্ট: AdamW-এর λ-এর অর্থ lr, ε, batch আর training-এর দৈর্ঘ্যের ওপর নির্ভর করে।

মূল উপলব্ধি: ε একটি নিরাপত্তা-সংখ্যা হলেও ছোট gradient-এ আর কম precision-এ (float16) সেটি optimizer-এর আচরণ বদলে দেয়; তাই default-কে অদৃশ্য ধরে নেবেন না।

প্র ০৪Weight decay আর L2 regularization কেন একসময় একই ধরা হতো, আর AdamW কী বদলাল?

Neural network-এ weight decay-এর ধারণা পুরোনো; ১৯৮০-র দশকের শেষে Hanson ও Pratt-এর মতো গবেষকেরা এবং ১৯৯২ সালে Krogh আর Hertz-এর একটি পরিচিত প্রবন্ধ দেখান যে প্রতি ধাপে ওজন সামান্য ছোট করলে generalization ভালো হয়। সাধারণ SGD-তে w ← (১ − ηλ)w − η∇L হুবহু loss-এ (λ/২)‖w‖² যোগ করার সমান, তাই দুটি নাম বহু বছর একই অর্থে চলেছে, আর অনেক library “weight_decay” নামে আসলে gradient-এ λw যোগ করত।

Adaptive optimizer আসার পর এই সমতা ভেঙে যায়, কিন্তু অনেক দিন কেউ খেয়াল করেনি। Loshchilov আর Hutter-এর ২০১৭ সালের লেখার মূল পর্যবেক্ষণ: Adam-এ λw-কে gradient-এ ঢোকালে তা √v̂ দিয়ে ভাগ হয়, ফলে যে ওজনের gradient বড় বা অস্থির, সে কম শাস্তি পায়। তাঁরা যুক্তি দেন এটি অনাকাঙ্ক্ষিত, আর decay-কে আলাদা করে প্রস্তাব দেন। তাঁদের পরীক্ষায় AdamW-এর test ফল ভালো হয়, আর সেরা λ আর সেরা lr কম জড়িয়ে থাকে, তাই আলাদা আলাদা খোঁজা সহজ।

কিন্তু এই পর্বের run একটি সূক্ষ্ম বিষয় দেখায়: AdamW আর কোনো সহজ objective-কে সর্বনিম্ন করে না। Adam + L2 হুবহু loss + (λ/২)‖w‖²-এর সমাধানে পৌঁছেছে (J = ০.৪৬৭৭৪, Newton-এর সঙ্গে তিন দশমিক পর্যন্ত একই ওজন); AdamW পৌঁছেছে প্রায় শাস্তিহীন সমাধানে। তাই কেউ যদি “L2-regularised logistic regression” চান, অর্থাৎ পরিসংখ্যানের অর্থে একটি নির্দিষ্ট MAP অনুমান (পর্ব ১৭ আর পর্ব ১৮-এ আসবে), তাহলে AdamW সঠিক যন্ত্র নয়। আবার একটি বড় network-এ, যেখানে লক্ষ্য শুধু ভালো generalization, AdamW-এর আচরণ বাস্তবে প্রায়ই ভালো।

আরেকটি খুঁটিনাটি: PyTorch-এর AdamW-এ decay হয় lr × λ × w, অর্থাৎ lr schedule-এর সঙ্গে decay-ও কমে; মূল প্রবন্ধে একটি আলাদা schedule-গুণকের কথা ছিল। ফলে একই λ-এর অর্থ library ভেদে সামান্য আলাদা হতে পারে।

মূল উপলব্ধি: SGD-তে L2 আর weight decay একই; adaptive optimizer-এ নয়। কোনটি চান, তা নির্ভর করে আপনি একটি নির্দিষ্ট objective চান, নাকি শুধু ভালো generalization.

প্র ০৫Adaptive optimizer-কে “সস্তা Newton” বলা কতটা ঠিক?

Newton-এর ধাপ H−1g: Hessian দিয়ে ভাগ, যাতে প্রতিটি দিকে বাঁকের মাপ অনুযায়ী ধাপ হয় আর κ-এর প্রভাব চলে যায় (পর্ব ১১-এ দেখেছেন, পর্ব ১৫-এ বিস্তারিত)। AdaGrad, RMSProp আর Adam ধাপকে একটি কর্ণ-matrix diag(√v̂) দিয়ে ভাগ করে। তাই একটি জনপ্রিয় ব্যাখ্যা: এরা কর্ণ-Hessian-এর সস্তা অনুমান। এই ব্যাখ্যার কিছু সত্য আছে, কিন্তু তিনটি জায়গায় এটি টানা।

এক, একক: Hessian-এর একক gradient/parameter, আর √v̂-এর একক gradient-এর। ফলে Newton-এর ধাপের একক parameter-এর, কিন্তু Adam-এর ধাপ m̂/√v̂ একক-বিহীন, α দিয়ে গুণ। Adam-এর ধাপ বাঁকের মাপ নয়, gradient-এর মাপ দিয়ে ভাগ। Quadratic-এ বিন্দু থেকে দূরে gradient বড়, তাই ভাগফল প্রায় ±১; এটি Newton-এর চেয়ে sign-এর কাছাকাছি।

দুই, কর্ণ: শুধু কর্ণ নেওয়া মানে অক্ষ বরাবর লম্বা বাটি সারানো যায়, তির্যক বাটি নয়। Feature-গুলো সম্পর্কিত হলে (বাজারঘরের “মোট খরচ” আর “অর্ডারের সংখ্যা”) Hessian-এর বড় অংশ কর্ণের বাইরে থাকে, যা Adam দেখেই না। পূর্ণ matrix-এর সস্তা অনুমান করে এমন পদ্ধতি আছে: K-FAC (Martens আর Grosse, ২০১৫) বা Shampoo (Gupta ও সহকর্মী, ২০১৮), যারা প্রতিটি layer-এর জন্য ছোট ছোট matrix রাখে।

তিন, Fisher বনাম Hessian: v̂ হলো g²-এর গড়, যা empirical Fisher information-এর কর্ণের সঙ্গে সম্পর্কিত। Log-likelihood loss-এ সর্বনিম্ন বিন্দুর কাছে Fisher আর Hessian কাছাকাছি হতে পারে, কিন্তু সাধারণভাবে নয়; Kunstner ও সহকর্মীদের ২০১৯ সালের একটি প্রবন্ধ দেখায় empirical Fisher-কে Hessian-এর বিকল্প ধরলে কোথায় ভুল হয়। তাছাড়া Adam বর্গমূল নেয়, Newton নেয় না; এটিও তত্ত্বের চেয়ে অভিজ্ঞতা থেকে আসা পছন্দ।

মূল উপলব্ধি: Adaptive optimizer প্রতিটি parameter-এর gradient-এর মাপ সমান করে; এটি Newton-এর একটি দূর আত্মীয়, প্রতিস্থাপন নয়। বাঁক সত্যিই জানতে চাইলে second-order পদ্ধতি লাগে।

অনুশীলন

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

  1. পূর্ণ numerical: f(w) = ২w1² + ০.৫w2², বিন্দু w = (১, ২). (ক) η = ০.১-এ GD-র এক ধাপ। (খ) momentum-এ β = ০.৯, η = ০.১, আর আগের velocity v = (−০.২, ০.১) হলে এক ধাপ। (গ) Adam-এর প্রথম ধাপ (m = v = 0 থেকে, α = ০.০১, β1 = ০.৯, β2 = ০.৯৯৯, ε উপেক্ষা)। (ঘ) (গ)-তে bias correction না করলে ধাপ কত?

    Gradient ∇f = (৪w1, w2) = (৪, ২). (ক) w − ηg = (১ − ০.৪, ২ − ০.২) = (০.৬, ১.৮). (খ) v ← ০.৯(−০.২, ০.১) − ০.১(৪, ২) = (−০.১৮ − ০.৪, ০.০৯ − ০.২) = (−০.৫৮, −০.১১); w ← (১ − ০.৫৮, ২ − ০.১১) = (০.৪২, ১.৮৯). প্রথম ঘরে পুরোনো আর নতুন একই দিকে, তাই বড় ধাপ; দ্বিতীয় ঘরে পুরোনো velocity উল্টো দিকে ছিল, তাই ধাপ ছোট। (গ) m = ০.১g = (০.৪, ০.২), v = ০.০০১g² = (০.০১৬, ০.০০৪); m̂ = m/০.১ = (৪, ২), v̂ = v/০.০০১ = (১৬, ৪); ধাপ α·m̂/√v̂ = ০.০১·(৪/৪, ২/২) = (০.০১, ০.০১); w = (০.৯৯, ১.৯৯). Gradient-এ দুই গুণ তফাত থাকলেও দুই ঘর সমান সরেছে। (ঘ) α·m/√v = ০.০১·(০.৪/০.১২৬৫, ০.২/০.০৬৩২) ≈ ০.০১·(৩.১৬, ৩.১৬) = (০.০৩১৬, ০.০৩১৬), প্রায় ৩.১৬ গুণ বড়, কারণ ০.১/√০.০০১ ≈ ৩.১৬.

  2. সমালোচনা ও নকশা: বাজারঘরের একটি দল একটি ছোট neural network-এর জন্য optimizer বাছাইয়ের রিপোর্ট দিয়েছে: “Adam (lr = ০.০০১, default) আর SGD (lr = ০.০১, momentum নেই) একবার করে ২০ epoch চালানো হয়েছে; Adam-এর test accuracy ১.২% বেশি, তাই Adam সেরা। Regularization-এর জন্য Adam-এ weight_decay = ০.০১ দেওয়া হয়েছে, যা AdamW-এর সমান।” এই রিপোর্টের সমস্যাগুলো কী, আর একটি ন্যায্য পরীক্ষার নকশা কেমন হবে?

    সমস্যা: (১) প্রতিটি optimizer একটিই lr-এ: SGD-র জন্য ০.০১ খুব ছোট বা বড় হতে পারে, আর momentum ছাড়া SGD একটি দুর্বল প্রতিদ্বন্দ্বী। (২) একটি seed: ১.২% তফাত seed-এর noise-এর মধ্যেই থাকতে পারে। (৩) Test set দেখে সিদ্ধান্ত: তখন test আর নিরপেক্ষ থাকে না। (৪) PyTorch-এর Adam(weight_decay=...) coupled L2, AdamW-এর decoupled decay নয়; দাবিটি ভুল। (৫) ২০ epoch স্থির: একটি optimizer হয়তো দ্রুত শুরু করে কিন্তু পরে আরেকটি এগিয়ে যায়। নকশা: ডেটা train/validation/test-এ ভাগ, test তালা দেওয়া। Optimizer: AdamW, Adam + L2, SGD + Nesterov momentum. প্রতিটির জন্য lr-এর log-grid (যেমন ১০−৪ থেকে ১ পর্যন্ত ৭টি মান) আর weight decay-এর ছোট grid, একই tuning বাজেট। প্রতিটি সেটিং ৫টি seed-এ; validation-এর গড় আর ছড়ানো (standard deviation) দেখে সেরা বাছাই। শেষে শুধু বাছাই করা সেটিং একবার test-এ। Learning curve আর lr-সংবেদনশীলতার ছক (এই পর্বের অংশ ৭-এর মতো) রিপোর্টে রাখুন।

  3. আউটপুট অনুমান করুন: f(x) = x²/২, তাই gradient = x. চালানোর আগে লিখে ফেলুন নিচের কোড কী ছাপবে, আর একই η-তে সাধারণ GD কী ছাপত।
    x, v = 1.0, 0.0
    for t in range(3):
        v = 0.5 * v - 0.5 * x
        x = x + v
        print(round(x, 4), round(v, 4))
    0.5 -0.5
    0.0 -0.5
    -0.25 -0.25

    t = ০: v = ০ − ০.৫ = −০.৫, x = ০.৫. t = ১: v = ০.৫(−০.৫) − ০.৫(০.৫) = −০.৫, x = ০.০. t = ২: v = ০.৫(−০.৫) − ০ = −০.২৫, x = −০.২৫. Momentum বিন্দুটিকে সর্বনিম্ন ০ পেরিয়ে নিয়ে গেছে: x = ০-তে gradient শূন্য, কিন্তু velocity নয়। একই η = ০.৫-এ সাধারণ GD x ← ০.৫x দিত ০.৫, ০.২৫, ০.১২৫: কখনো পেরোয় না। এই এক-মাত্রিক, সুগঠিত সমস্যায় momentum-এর লাভ নেই; লাভ আসে κ বড় হলে।

  4. Derive করুন: f(x) = (λ/২)x², λ > ০. Heavy-ball-এর দুই লাইনের সূত্র থেকে v বাদ দিয়ে দেখান xt+1 = (১ + β − ηλ)xt − βxt−1. তারপর λ = ১০, β = ০.৯ হলে কোন η পর্যন্ত পদ্ধতি স্থির থাকে, আর সাধারণ GD-র সীমার সঙ্গে তুলনা করুন।

    vt+1 = xt+1 − xt, আর vt = xt − xt−1. সূত্রে বসালে xt+1 − xt = β(xt − xt−1) − ηλxt, অর্থাৎ xt+1 = (১ + β − ηλ)xt − βxt−1. Characteristic সমীকরণ z² − (১ + β − ηλ)z + β = ০. দুই মূলের মাপ ১-এর কম হওয়ার শর্ত (০ ≤ β < ১-এ): ০ < ηλ < ২(১ + β). β = ০.৯, λ = ১০ হলে ηλ < ৩.৮, অর্থাৎ η < ০.৩৮. সাধারণ GD-তে (β = ০) শর্ত ηλ < ২, অর্থাৎ η < ০.২. সীমার কাছে momentum ধীরে দোলে, তাই বড় সীমা মানেই দ্রুত নয়; সঠিক η আর β একসঙ্গে বাছতে হয়।

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

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

ক্রমAdamW-এর একটি ধাপের হিসাব সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. ধাপ-সংখ্যা t এক বাড়ানো আর loss-এর gradient g বের করা (শাস্তি ছাড়া)
  2. m ← β1·m + (1 − β1)·g দিয়ে প্রথম moment হালনাগাদ
  3. v ← β2·v + (1 − β2)·g² দিয়ে দ্বিতীয় moment হালনাগাদ
  4. (1 − β1^t) আর (1 − β2^t) দিয়ে ভাগ করে m̂ আর v̂ বের করা
  5. w ← w − η·(m̂/(√v̂ + ε) + λ·w) দিয়ে ওজন হালনাগাদ

Heavy-ball momentum-এ v ← βv − ηg, x ← x + v. Gradient অনেক ধাপ ধরে স্থির g থাকলে β = ০.৯-এ প্রতি ধাপে x প্রায় কতটা সরে?

  • ηg
  • ০.৯ηg
  • ১০ηg
  • ধাপ ক্রমাগত বাড়তে থাকে, অসীমের দিকে

Adam-এ m0 = v0 = 0, β1 = ০.৯, β2 = ০.৯৯৯. Bias correction বাদ দিলে প্রথম ধাপ (t = ১) correction-সহ ধাপের প্রায় কত গুণ হয়?

  • প্রায় ০.১ গুণ, অর্থাৎ অনেক ছোট
  • প্রায় ৩.১৬ গুণ
  • ঠিক ১ গুণ, কোনো তফাত নেই
  • প্রায় ১০০০ গুণ

Nesterov momentum-কে heavy-ball momentum থেকে কোন বৈশিষ্ট্য আলাদা করে?

  • Nesterov g²-এর চলমান গড়ও রাখে
  • Nesterov-এ β সবসময় ০.৯৯
  • Nesterov Hessian দিয়ে ধাপকে ভাগ করে
  • Gradient নেওয়া হয় look-ahead বিন্দু x + βv-তে

একটি parameter-এর gradient অনেক ধাপ ধরে প্রায় স্থির g. AdaGrad-এর ধাপ সময়ের সঙ্গে কেমন হয়, আর RMSProp কী বদলায়?

  • AdaGrad-এর ধাপ প্রায় η/√t হারে কমে; RMSProp চলমান গড় নিয়ে ধাপ প্রায় η রাখে
  • AdaGrad-এর ধাপ বাড়ে; RMSProp তা থামায়
  • AdaGrad-এর ধাপ স্থির η; RMSProp ধাপ কমায়
  • দুটির ধাপ সবসময় একই

Adam-এ L2 শাস্তি gradient-এ যোগ করা (g = ∇L + λw) আর AdamW-এর decoupled weight decay-এর মূল পার্থক্য কোনটি?

  • AdamW ওজনকে শূন্যের দিকে টানে না
  • AdamW momentum ব্যবহার করে না
  • Adam + L2-এ শাস্তিও √v̂ দিয়ে ভাগ হয়; AdamW প্রতিটি ওজন একই অনুপাতে কমায়
  • দুটি সবসময় হুবহু একই ফল দেয়

একটি quadratic loss-এর condition number κ = ১০০. সেরা স্থির ধাপের gradient descent আর tuned heavy-ball momentum-এর প্রতি ধাপের ভুল কমার গুণক প্রায় কত?

  • GD ০.৯৯, momentum ০.৯০
  • GD ≈ ০.৯৮০, momentum ≈ ০.৮১৮
  • GD ≈ ০.৮১৮, momentum ≈ ০.৯৮০
  • দুটিই ০.৫

শব্দকোষ

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

মনে রাখুন

  1. Momentum: v ← βv − η∇f, x ← x + v; একই দিকের gradient জমে (ধাপ ≈ η/(১ − β)), উল্টানো gradient কাটে। Quadratic-এ tuned হার (√κ − ১)/(√κ + ১), GD-র (κ − ১)/(κ + ১)-এর জায়গায়।
  2. Nesterov: gradient নেয় x + βv-তে; convex-smooth function-এ O(১/k²) প্রমাণিত, heavy ball-এর এমন সাধারণ প্রমাণ নেই।
  3. AdaGrad → RMSProp: ধাপকে √(g²) দিয়ে ভাগ; AdaGrad যোগফল নেয় বলে ধাপ শুকায়, RMSProp চলমান গড় নেয়।
  4. Adam: m আর v-এর চলমান গড়, bias correction (১ − βt) দিয়ে; correction ছাড়া শুরুর ধাপ বড় হয়। AdamW শাস্তিকে আলাদা করে ηλw কমায়, যা L2-এর সমান নয়।
  5. তুলনা: সেরা optimizer বলে কিছু নেই; প্রতিটির নিজের সেরা lr, কয়েকটি seed, validation ডেটা। Adam অভিসারী না হওয়ার জানা উদাহরণ আছে (Reddi ও সহকর্মী, ২০১৮)।

পরের পর্বে: এই পর্বের optimizer-গুলো শুধু gradient দেখে, বাঁক আন্দাজ করে। পর্ব ১৫ (Second-order পদ্ধতি — Newton, L-BFGS আর line search)-এ বাঁককে সরাসরি কাজে লাগানো হবে: Newton-এর quadratic convergence, line search, BFGS আর L-BFGS, আর scipy.optimize-এ Rosenbrock আর logistic regression-এ তুলনা।

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

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