এই পর্বে যা শিখবেন
- দ্বিতীয় ক্রমের Taylor মডেল থেকে Newton-এর ধাপ derive করা, হাতে আর NumPy-তে
- Linear, superlinear আর quadratic convergence-এর পার্থক্য, আর Newton কখন, কেন ব্যর্থ হয়
- Armijo backtracking line search হাতে চালানো, আর Wolfe শর্ত কী পাহারা দেয়
- BFGS-এর হালনাগাদ সূত্র আর L-BFGS-এর two-loop recursion, ছোট সংখ্যায় ধাপে ধাপে
- SciPy আর scikit-learn-এ কোন optimizer কখন বাছবেন, আর ফল কীভাবে যাচাই করবেন
AI-এর ভিত্তি কোর্সের পর্ব ৩৪-এ gradient descent আর learning rate দেখেছেন, আর পর্ব ৪৪-এ scipy.optimize.minimize-কে method না বলে দিলে সে BFGS চালায়, সেটিও দেখেছেন। এই কোর্সের পর্ব ১১-এ logistic regression-এর Hessian XᵀSX বের করে Newton-এর কয়েকটি ধাপ চালিয়েছিলাম, আর দেখেছিলাম ৫০ ধাপের gradient descent-এর কাজ Newton চার-পাঁচ ধাপে সারে। এই পর্বের প্রশ্ন তার পরের: Newton কেন এত দ্রুত, কখন সে ভুল দিকে যায়, তাকে কীভাবে নিরাপদ করা হয়, আর Hessian বানানো যখন অসম্ভব তখন L-BFGS কীভাবে প্রায় একই সুবিধা দেয়? Gradient descent-এর নিজের পরিবার (batch, SGD, schedule) পর্ব ১৩ (Gradient descent-এর পরিবার — batch, SGD, mini-batch আর learning-rate schedule)-এর বিষয়, আর momentum ও Adam পর্ব ১৪ (Momentum, RMSProp আর Adam — derive করে আর তুলনা করে)-এর। চালানো যায় এমন চারটি ব্লক পাতাতেই চলে (ব্রাউজারের Python, NumPy, SciPy আর scikit-learn); প্রথমবার প্যাকেজ নামতে কয়েক সেকেন্ড লাগে, scikit-learn-এ প্রায় দশ সেকেন্ড।
১ · ঢাল দেখে হাঁটা বনাম বাঁক দেখে লাফ — কেন second-order
বান্দরবানের একটি পাহাড়ি পথে কুয়াশার মধ্যে নিচের উপত্যকায় নামছেন। পায়ের নিচের মাটি কোন দিকে ঢালু, সেটুকু বোঝা যায়: এটি gradient. শুধু এটুকু জেনে নামলে প্রতিবার ছোট ছোট পা ফেলতে হয়, কারণ জানেন না সামনে ঢাল কত তাড়াতাড়ি সমান হয়ে যাবে। এখন ধরুন একজন অভিজ্ঞ গাইড পায়ের নিচের মাটির বাঁক দেখেও বলতে পারেন, “ঢাল যেভাবে কমছে, তাতে তলা আর প্রায় তিরিশ পা দূরে।” এই বাঁকের তথ্যই দ্বিতীয় derivative, অর্থাৎ Hessian. বাঁক জানলে এক লাফে তলার কাছে যাওয়া যায়; তবে কুয়াশায় গাইডের অনুমান ভুল হলে সেই লাফ খাদেও নিয়ে যেতে পারে। এই পর্ব দুটি দিকই দেখবে।
ML-এ প্রায় সব শেখা একটি loss L(w) কমানো। Gradient descent-এর ধাপ w ← w − η∇L, যেখানে η হলো learning rate. এর দুটি দুর্বলতা আছে। প্রথমত, η নিজে বাছতে হয়: বড় হলে loss লাফিয়ে বাড়ে, ছোট হলে হাজার হাজার ধাপ লাগে। দ্বিতীয়ত, loss-এর বাটি এক দিকে সরু আর অন্য দিকে চওড়া হলে, অর্থাৎ Hessian-এর condition number κ = λmax/λmin বড় হলে, gradient-এর দিক তলার দিকে নয়, খাড়া দেয়ালের দিকে হেলে থাকে; তাই পথ আঁকাবাঁকা হয়। এই আঁকাবাঁকা পথের ছবি আর হার পর্ব ১৩-এ আসবে। Second-order পদ্ধতি দুটি সমস্যাই একসঙ্গে ধরে: Hessian দিয়ে প্রতিটি দিকের ধাপ আলাদা মাপে বাঁকিয়ে নেয়, আর সঠিক বাঁক জানা থাকলে ধাপের স্বাভাবিক দৈর্ঘ্য ১, কোনো η লাগে না।
| পরিবার | প্রতি ধাপে কী লাগে | মেমরি (d parameter) | সর্বনিম্নের কাছে গতি | η বাছতে হয়? |
|---|---|---|---|---|
| First-order (GD, SGD, Adam) | gradient | d-এর কয়েক গুণ | linear (κ বড় হলে ধীর) | হ্যাঁ |
| Newton | gradient + পুরো Hessian, তারপর d × d সমীকরণ | d² | quadratic | না (line search থাকে) |
| Quasi-Newton (BFGS) | gradient; বাঁক শেখে পার্থক্য থেকে | d² | superlinear | না |
| L-BFGS | gradient + শেষ m জোড়া vector | 2md | সাধারণত linear, কিন্তু GD-র চেয়ে অনেক দ্রুত | না |
টেবিলের “গতি” কথাটি একটু পরে নিখুঁতভাবে সংজ্ঞায়িত হবে। আপাতত মনে রাখুন: মেমরি আর প্রতি ধাপের খরচ যত বেশি, ধাপের সংখ্যা তত কম। কোনটি জিতবে তা নির্ভর করে d কত বড়, ডেটা কত noisy, আর loss কতটা মসৃণ, তার ওপর।
২ · Newton-এর ধাপ — Taylor-এর বাটির তলায় লাফ
পর্ব ১১-এ দেখেছেন, x-এর কাছে মসৃণ f-এর দ্বিতীয় ক্রমের Taylor approximation:
f(x + d) ≈ m(d) = f(x) + gᵀd + ½ dᵀHd
এখানে g = ∇f(x) হলো gradient, H = ∇²f(x) হলো Hessian, আর d হলো সরণ। m(d)-কে বলা হয় quadratic model: আসল f-এর জায়গায় একটি বাটি, যা x-এর কাছে f-এর মান, ঢাল আর বাঁক তিনটিই মেলায়। H positive definite হলে বাটিটির একটিই তলা। m-এর d-এর সাপেক্ষে gradient g + Hd; শূন্য বসালে Newton-এর ধাপ:
H d = −g, তারপর x ← x + d (এক চলকে: x ← x − f′(x)/f″(x))
কোডে কখনো H-এর inverse বানানো হয় না; সমীকরণটি সমাধান করা হয় (np.linalg.solve), আর H positive definite জানা থাকলে Cholesky দিয়ে, যা পর্ব ১০-এ দেখেছেন। Quadratic function-এ m আর f হুবহু এক, তাই Newton এক ধাপেই সর্বনিম্নে পৌঁছায়; পর্ব ১১-এর ছবি ৬.১-এ লাল তীর ঠিক এটিই দেখিয়েছিল। অ-quadratic function-এ মডেলটি শুধু স্থানীয়ভাবে ঠিক, তাই ধাপ বারবার নিতে হয়। নিচের stepper একটি অ-quadratic এক-চলকের function-এ প্রথম দুটি ধাপ হাতে করে দেখায়।
- Function আর তার দুই derivative. f″(x) = eˣ সব x-এ ধনাত্মক, তাই f convex আর সর্বনিম্ন একটিই: f′ = 0 যেখানে, অর্থাৎ eˣ = ২, x* = ln ২ ≈ ০.৬৯৩১. শুরুতে x0 = ০, ঢাল −১, বাঁক ১.
- Quadratic model. x0-এ f-এর মান, ঢাল আর বাঁক মিলিয়ে একটি parabola: m(d) = ১ − d + ০.৫d². এর তলা যেখানে ঢাল শূন্য, অর্থাৎ d = ১.
- প্রথম লাফ। d = −f′/f″ = ১, তাই x1 = ১. মডেল ভেবেছিল সেখানে মান ০.৫ হবে, আসলে ০.৭১৮৩; কারণ x0 থেকে ১ একক দূরে parabola আর আসল বাঁক আলাদা হয়ে গেছে। তবু মান ১ থেকে কমেছে।
- দ্বিতীয় লাফ। x1 = ১-এ নতুন করে মডেল: ঢাল ০.৭১৮৩, বাঁক ২.৭১৮৩, ধাপ −০.২৬৪২. x2 = ০.৭৩৫৮, মান ০.৬১৫৫৫, সর্বনিম্ন ০.৬১৩৭১-এর খুব কাছে। তলার যত কাছে, মডেল তত নির্ভুল।
- ভুলের ধারা। ০.৩১ থেকে ০.০৪৩, তারপর ০.০০০৯, তারপর ৪ × ১০−৭, তারপর ৮ × ১০−১৪. প্রতি ধাপে ভুল মোটামুটি আগের ভুলের বর্গের অনুপাতে, তাই সঠিক দশমিক অঙ্ক ১, ৩, ৬, ১৩: প্রায় দ্বিগুণ। একে বলে quadratic convergence.
Newton-এর একটি সুন্দর গুণ হলো affine invariance: চলক x-কে একটি invertible matrix দিয়ে বদলে (যেমন একটি feature-কে টাকা থেকে হাজার টাকায়) নতুন চলকে Newton চালালে ঠিক একই বিন্দুগুলো পাওয়া যায়, শুধু নতুন এককে লেখা। কারণ gradient আর Hessian দুটিই বদলে যায়, আর H−1g-তে সেই বদল কেটে যায়। Gradient descent-এ এমন হয় না; সেখানে feature scaling বদলালে পথ আর গতি দুটিই বদলায়। এজন্য Newton-এর পরিবারের পদ্ধতি ill-conditioned সমস্যায় অনেক কম ভোগে। তবে float-এর rounding আর সংখ্যাগত স্থিতিশীলতার কারণে বাস্তবে scaling তবুও সাহায্য করে।
৩ · Quadratic convergence আর বিশুদ্ধ Newton-এর দুই ব্যর্থতা
গতির ভাষা নিখুঁত করা যাক। ek = ‖xk − x*‖ লিখুন, অর্থাৎ k-তম ধাপে সর্বনিম্ন থেকে দূরত্ব। পুনরাবৃত্তিটি converge করছে ধরে:
| নাম | সংজ্ঞা (বড় k-তে) | উদাহরণে ভুলের ধারা | কোথায় দেখা যায় |
|---|---|---|---|
| Linear | ek+1 ≤ r·ek, ০ < r < ১ | r = ০.৫: ১০−২, ৫ × ১০−৩, ২.৫ × ১০−৩ … | Gradient descent; r প্রায় (κ − ১)/(κ + ১) |
| Superlinear | ek+1/ek → ০ | অনুপাত নিজেই ছোট হতে থাকে | BFGS, ভালো শর্তে |
| Quadratic | ek+1 ≤ M·ek² | ১০−২, ১০−৪, ১০−৮, ১০−১৬ | সর্বনিম্নের কাছে Newton |
Newton-এর quadratic convergence-এর তিনটি শর্ত আছে: সর্বনিম্নে Hessian positive definite, Hessian যথেষ্ট মসৃণভাবে বদলায় (Lipschitz), আর শুরুর বিন্দু সর্বনিম্নের যথেষ্ট কাছে। শেষ শর্তটিই সবচেয়ে বিপজ্জনক, কারণ “যথেষ্ট কাছে” কত, আগে থেকে সাধারণত জানা যায় না। নিচের ব্লক তিনটি function-এ বিশুদ্ধ Newton চালায়: একটিতে সব ঠিক, একটিতে বাঁক ভুল চিহ্নের, আর একটিতে শুরু অনেক দূরে। শেষে একটি সরল প্রতিকার, damped Newton: ধাপ যতক্ষণ f কমায় না, ততক্ষণ তাকে অর্ধেক করা।
import numpy as np
def newton(fp, fpp, x, steps): # 1-D Newton: x ← x − f′(x)/f″(x)
xs = [x]
for _ in range(steps):
x = x - fp(x) / fpp(x)
xs.append(x)
return xs
# ১) মসৃণ, convex: f(x) = eˣ − 2x, সর্বনিম্ন x* = ln 2
xs = newton(lambda x: np.exp(x) - 2, np.exp, 0.0, 5)
print(" k x_k error")
for k, x in enumerate(xs):
print(f"{k:2d} {x:.15f} {abs(x - np.log(2)):.1e}")
# ২) f(x) = x⁴ − 2x², f″ < 0 যখন |x| < 0.577: Newton গর্ত নয়, চূড়া খোঁজে
fp, fpp = lambda x: 4*x**3 - 4*x, lambda x: 12*x**2 - 4
print("x^4-2x^2, x0=0.3:", ", ".join(f"{v:.2g}" for v in newton(fp, fpp, 0.3, 4)))
print(" f''(0) =", fpp(0.0), "-> x = 0 is a local MAXIMUM (minima at +-1)")
# ৩) f(x) = √(1 + x²): Newton-এর ধাপ x ← −x³, |x0| > 1 হলে ছিটকে যায়
fp = lambda x: x / np.sqrt(1 + x**2)
fpp = lambda x: (1 + x**2) ** -1.5
print("sqrt(1+x^2), x0=0.5:", ", ".join(f"{v:.3g}" for v in newton(fp, fpp, 0.5, 4)))
print("sqrt(1+x^2), x0=1.5:", ", ".join(f"{v:.3g}" for v in newton(fp, fpp, 1.5, 4)))
# প্রতিকার: damped Newton, ধাপ t অর্ধেক করতে থাকুন যতক্ষণ f না কমে
f, x = lambda x: np.sqrt(1 + x**2), 1.5
for k in range(6):
d, t = -fp(x) / fpp(x), 1.0
while f(x + t * d) >= f(x):
t /= 2
x = x + t * d
print(f"damped step {k+1}: t = {t:<9} x = {x:.3e}")আসল আউটপুট (ব্রাউজারের Python):
k x_k error
0 0.000000000000000 6.9e-01
1 1.000000000000000 3.1e-01
2 0.735758882342885 4.3e-02
3 0.694042299918915 9.0e-04
4 0.693147581059771 4.0e-07
5 0.693147180560025 8.0e-14
x^4-2x^2, x0=0.3: 0.3, -0.074, 0.00082, -1.1e-09, 0
f''(0) = -4.0 -> x = 0 is a local MAXIMUM (minima at +-1)
sqrt(1+x^2), x0=0.5: 0.5, -0.125, 0.00195, -7.45e-09, 0
sqrt(1+x^2), x0=1.5: 1.5, -3.38, 38.4, -5.68e+04, 1.83e+14
damped step 1: t = 0.5 x = -9.375e-01
damped step 2: t = 1.0 x = 8.240e-01
damped step 3: t = 1.0 x = -5.594e-01
damped step 4: t = 1.0 x = 1.751e-01
damped step 5: t = 1.0 x = -5.366e-03
damped step 6: t = 1.0 x = 1.545e-07
প্রথম অংশ stepper-এর সংখ্যাগুলোই: পাঁচ ধাপে ভুল ৮ × ১০−১৪, float64-এর প্রায় শেষ সীমা। দ্বিতীয় অংশে f(x) = x⁴ − 2x²-এর দুটি সর্বনিম্ন ±১-এ, আর x = ০-তে একটি চূড়া। x0 = ০.৩-এ f″ = ১২(০.০৯) − ৪ = −২.৯২, ঋণাত্মক। Newton শুধু f′ = ০ এমন বিন্দু খোঁজে; সেটি গর্ত, চূড়া না saddle, তা আলাদা করে না। তাই চার ধাপে সে চূড়া x = ০-তে স্থির হয়েছে, আর সেখানেও quadratic গতিতে। বহু চলকে একই কথা: Hessian indefinite হলে Newton-এর দিক saddle-এর দিকে টানে। তৃতীয় অংশে √(1 + x²)-এর সর্বনিম্ন ০-তে, আর f″ সব জায়গায় ধনাত্মক; তবু x0 = ১.৫ থেকে ধারা ১.৫, −৩.৩৮, ৩৮.৪, −৫.৬৮ × ১০৪, ১.৮৩ × ১০১৪: সোজা অসীমে। কারণ এখানে ধাপটি সরলীকরণ করলে x ← −x³; পরম মান ১-এর কম হলে ঘন নিয়ে আরও ছোট হয় (x0 = ০.৫-এর ধারা), বেশি হলে বিস্ফোরণ। দূরে f প্রায় সরলরেখা, বাঁক খুব কম, তাই মডেলের বাটি অনেক চওড়া আর তার তলা অনেক দূরে। শেষ অংশে damped Newton প্রথম ধাপে t = ০.৫ নিয়ে x-কে −০.৯৩৮-এ এনেছে, যেখানে পরম মান ১-এর কম; তারপর পুরো ধাপ (t = ১) চলেছে, আর ছয় ধাপে x প্রায় ১.৫ × ১০−৭. একটি ছোট পাহারাদারই বিস্ফোরণ থামিয়েছে।
খরচের হিসাব — কেন বড় model-এ বিশুদ্ধ Newton অচল
d সংখ্যক parameter-এ Hessian-এ d² ঘর, আর Cholesky দিয়ে সমাধানে প্রায় d³/3 গুণ-যোগ। float64-এ প্রতিটি ঘর ৮ বাইট। d = ১,০০০ হলে Hessian ৮ মেগাবাইট আর সমাধানে প্রায় ৩.৩ × ১০৮ গুণ-যোগ, একটি ল্যাপটপে এক সেকেন্ডেরও কম। d = ১০ লাখ (একটি TF-IDF ফিচারের logistic regression-এ খুব সাধারণ) হলে Hessian ৮ × ১০১২ বাইট, অর্থাৎ ৮ টেরাবাইট: শুধু রাখাই অসম্ভব। তার ওপর Hessian নিজে হিসাব করতে পুরো ডেটার ওপর d² মাপের কাজ। এই দুই বাধা, ভুল বাঁক আর বিশাল খরচ, এই পর্বের বাকি অংশের দুটি সুতো: line search প্রথমটির উত্তর, quasi-Newton দ্বিতীয়টির।
৪ · Line search — Armijo-র “যথেষ্ট কমা”র শর্ত
Damped Newton-এর ধারণা সাধারণ: দিক d ঠিক রেখে ধাপের দৈর্ঘ্য t আলাদা করে বাছা, x ← x + t·d. দিক বাছা আর দূরত্ব বাছা দুটি আলাদা কাজ। দিক যে কোনো পদ্ধতির হতে পারে (gradient-এর উল্টো, Newton, BFGS), শর্ত শুধু একটি: d যেন descent direction হয়, অর্থাৎ gᵀd < ০. তাহলে খুব ছোট t-তে f অবশ্যই কমে। দূরত্ব বাছার কাজটির নাম line search, কারণ x থেকে d-এর দিকে একটি সরলরেখার ওপর এক চলকের function φ(t) = f(x + t·d) দেখা হয়।
রিকশাওয়ালা গলির মোড়ে দাঁড়িয়ে জানেন কোন দিকে যেতে হবে, কিন্তু কতদূর সোজা গিয়ে আবার বাঁক নিতে হবে, তা জানেন না। তিনি প্রথমে পুরো রাস্তা ধরে এগোনোর কথা ভাবেন; সামনে কাদা দেখলে অর্ধেক পথ, তাতেও না হলে তার অর্ধেক। প্রতিবার শুধু দেখেন, “এটুকু গেলে কি অন্তত প্রত্যাশামতো এগোব?” Line search ঠিক এই কাজটি করে, আর “প্রত্যাশামতো”-র গাণিতিক রূপের নাম Armijo-র শর্ত।
শুধু “f কমেছে কি না” দেখা যথেষ্ট নয়: খুব সামান্য কমা বারবার নিলে পদ্ধতি সর্বনিম্নে না পৌঁছে আটকে থাকতে পারে। Larry Armijo ১৯৬৬ সালের একটি প্রবন্ধে যে শর্ত দেন, এখন তাকে বলে Armijo condition বা sufficient decrease:
f(x + t·d) ≤ f(x) + c·t·gᵀd, ০ < c < ১
ডান দিকটি একটি সরলরেখা, শুরুর ঢাল gᵀd-এর c গুণ। c = ১ হলে রেখাটি φ-এর tangent; ছোট c (প্রচলিত মান ১০−৪) মানে “ঢাল যা প্রতিশ্রুতি দেয়, তার সামান্য একটি অংশ কমলেই চলবে”। Backtracking হলো এই শর্ত পরীক্ষার সবচেয়ে সরল কৌশল: t = ১ দিয়ে শুরু, শর্ত না মিললে t ← β·t (প্রায়ই β = ০.৫), যতক্ষণ না মেলে। Newton আর quasi-Newton-এ t = ১ দিয়ে শুরু করা জরুরি, কারণ সর্বনিম্নের কাছে পুরো ধাপই quadratic বা superlinear গতি দেয়; line search তখন চুপচাপ t = ১ মেনে নেয়।
Armijo-র শর্ত ছবিতে — কোন t গ্রহণযোগ্য
φ(t) = f(x + t·d) for f = ½(x² + 10y²), x = (1, 1), d = −∇f = (−1, −10): φ(t) = 5.5 − 101t + 500.5t²
| চেষ্টা | |
| নতুন বিন্দু | |
| f(নতুন) | |
| Armijo সীমা | |
| রায় |
- প্রস্তুতি। সরু বাটি f = ½(x² + ১০y²), যার Hessian diag(১, ১০), condition number ১০. বিন্দু (১, ১)-এ gradient (১, ১০), দিক তার উল্টো। শুরুর ঢাল gᵀd = −(১ + ১০০) = −১০১.
- t = ১. নতুন বিন্দু (০, −৯): y-দিকে বাটির উল্টো দেয়ালে অনেক উঁচুতে। f = ½ × ১০ × ৮১ = ৪০৫, সীমা ৫.৪৮৯৯-এর অনেক ওপরে। বাতিল।
- t = ০.৫. বিন্দু (০.৫, −৪), f = ৮০.১২৫. এখনো বাতিল।
- t = ০.২৫. বিন্দু (০.৭৫, −১.৫), f = ১১.৫৩১. শুরুর ৫.৫-এর চেয়েও বেশি, বাতিল।
- t = ০.১২৫. বিন্দু (০.৮৭৫, −০.২৫), f = ০.৬৯৫৩ ≤ ৫.৪৯৮৭: গৃহীত। পাঁচটি f-হিসাব লাগল (শুরুর মানসহ)। সেরা সম্ভাব্য t ছিল ১০১/১০০১ ≈ ০.১০০৯, যেখানে f = ০.৪০৪৬; backtracking নিখুঁত নয়, কিন্তু সস্তা আর যথেষ্ট ভালো।
Wolfe শর্ত — খুব ছোট ধাপও আটকানো
Armijo খুব বড় ধাপ আটকায়, কিন্তু খুব ছোট ধাপ আটকায় না; backtracking-এ সমস্যা কম, কারণ t বড় থেকে ছোট হয় আর প্রথম গ্রহণযোগ্য মানেই থামে। অন্য কৌশলে (যেমন t বাড়িয়ে খোঁজা) দ্বিতীয় একটি শর্ত লাগে, যাকে বলে curvature condition: φ′(t) ≥ c2·φ′(০), যেখানে c < c2 < ১. মানে নতুন বিন্দুতে ঢাল যেন শুরুর মতো খাড়া নিচের দিকে না থাকে; থাকলে আরও এগোনো উচিত ছিল। দুই শর্ত মিলে Wolfe conditions, Philip Wolfe-এর ১৯৬৯ সালের কাজের নামে। Nocedal আর Wright-এর পাঠ্যবইয়ে Newton ও quasi-Newton-এর জন্য c2 = ০.৯ প্রচলিত বলা হয়েছে। উপরের stepper-এ t = ০.১২৫-এ φ′ = −১০১ + ১০০১ × ০.১২৫ = ২৪.১, যা −৯০.৯-এর চেয়ে বড়, তাই Wolfe-ও মেলে। BFGS-এর জন্য এই শর্ত বিশেষ গুরুত্বপূর্ণ, অংশ ৬-এ কারণ দেখবেন।
আরেকটি দর্শন: trust region. Line search আগে দিক বাছে, তারপর দূরত্ব। Trust region উল্টো: আগে একটি ব্যাসার্ধ Δ ঠিক করে, যার ভেতরে quadratic model-কে বিশ্বাস করা যায়, তারপর সেই বৃত্তের ভেতরে মডেলের সর্বনিম্ন খোঁজে। আসল কমা আর মডেলের প্রতিশ্রুত কমার অনুপাত ভালো হলে Δ বাড়ে, খারাপ হলে কমে। Hessian indefinite হলেও এটি নিরাপদে কাজ করে, কারণ বৃত্তের ভেতরে সর্বনিম্ন সবসময় থাকে। Levenberg (১৯৪৪) আর Marquardt (১৯৬৩)-এর least squares পদ্ধতি, যেখানে H-এর সঙ্গে λI যোগ করা হয়, এই ধারণার পূর্বসূরি। SciPy-র trust-ncg, trust-exact, trust-krylov আর trust-constr এই পরিবারের।
৫ · Rosenbrock-এর কলার মতো উপত্যকা — fixed step, Armijo আর damped Newton
Optimizer পরীক্ষার সবচেয়ে পরিচিত function, Rosenbrock function, Howard Rosenbrock-এর ১৯৬০ সালের প্রবন্ধ থেকে: f(x, y) = (1 − x)² + 100(y − x²)². সর্বনিম্ন (১, ১)-এ, মান ০. সমস্যা হলো তলাটি একটি সরু, বাঁকা, কলার মতো উপত্যকা (y ≈ x²): দেয়াল খাড়া (১০০ গুণক), কিন্তু উপত্যকার মেঝে প্রায় সমতল। সর্বনিম্নে Hessian-এর condition number প্রায় ২,৫০০. প্রচলিত শুরু (−১.২, ১). নিচের ব্লক তিনটি পদ্ধতি চালায়, আর গোনে কতবার f হিসাব করা হলো, কারণ বাস্তবে প্রতিটি f-হিসাব মানে পুরো ডেটার ওপর একবার loss.
import numpy as np
import matplotlib.pyplot as plt
calls = 0
def f(p): # Rosenbrock, সর্বনিম্ন (1, 1)-এ, মান 0
global calls; calls += 1
return (1 - p[0])**2 + 100 * (p[1] - p[0]**2)**2
grad = lambda p: np.array([-2*(1 - p[0]) - 400*p[0]*(p[1] - p[0]**2), 200*(p[1] - p[0]**2)])
hess = lambda p: np.array([[2 - 400*p[1] + 1200*p[0]**2, -400*p[0]], [-400*p[0], 200.0]])
def armijo(p, d, g, t=1.0, c=1e-4, beta=0.5): # backtracking: শর্ত না মেলা পর্যন্ত t অর্ধেক
fp = f(p)
while f(p + t * d) > fp + c * t * (g @ d) and t > 1e-10:
t *= beta
return t
def run(name, direction, use_ls, iters, eta=1e-3):
global calls; calls, p = 0, np.array([-1.2, 1.0]); path = [p]
for _ in range(iters):
g = grad(p); d = direction(p, g)
p = p + (armijo(p, d, g) if use_ls else eta) * d
path.append(p)
print(f"{name:<18} iters {iters:4d} f-evals {calls:5d} f = {f(p):.1e} x = {np.round(p, 4)}")
return np.array(path)
sd = lambda p, g: -g # steepest descent
nt = lambda p, g: -np.linalg.solve(hess(p), g) # Newton-এর দিক
P1 = run("GD, fixed 1e-3", sd, False, 2000)
P2 = run("GD + Armijo", sd, True, 2000)
P3 = run("Newton + Armijo", nt, True, 20)
X, Y = np.meshgrid(np.linspace(-1.5, 1.5, 200), np.linspace(-0.5, 1.6, 200))
plt.figure(figsize=(6.4, 4), layout="constrained")
plt.contour(X, Y, (1 - X)**2 + 100 * (Y - X**2)**2, levels=np.logspace(-1, 3, 14), colors="0.7")
for P, s, lab in [(P1, "C0", "GD fixed step"), (P2, "C1", "GD + Armijo"), (P3, "C3.-", "Newton + Armijo")]:
plt.plot(P[:, 0], P[:, 1], s, lw=1.5, ms=4, label=lab)
plt.plot(1, 1, "k*", ms=12); plt.legend(fontsize=8); plt.title("Rosenbrock: paths from (-1.2, 1)"); plt.show()আসল আউটপুট (ব্রাউজারের Python; নিচে ছবিও আঁকা হয়):
GD, fixed 1e-3 iters 2000 f-evals 0 f = 7.8e-02 x = [0.7207 0.518 ]
GD + Armijo iters 2000 f-evals 21742 f = 1.3e-04 x = [0.9887 0.9774]
Newton + Armijo iters 20 f-evals 47 f = 8.5e-12 x = [1. 1.]
Fixed step η = ১০−৩: ২,০০০ ধাপ পরেও x = (০.৭২১, ০.৫১৮), f = ০.০৭৮; উপত্যকার মেঝেতে নেমেছে, কিন্তু মেঝে ধরে (১, ১)-এর দিকে হামাগুড়ি দিচ্ছে। এটি একটিও f-হিসাব করেনি (শুধু ২,০০০টি gradient), কিন্তু η = ১০−৩ আসলে আগে থেকে চেষ্টা করে বাছা: সর্বনিম্নে Hessian-এর বড় eigenvalue প্রায় ১,০০২, তাই η > ২/১০০২ ≈ ০.০০২ হলে সেখানে gradient descent অস্থির। GD + Armijo একই ২,০০০ ধাপে f = ১.৩ × ১০−৪, প্রায় ৬০০ গুণ ভালো, কারণ প্রতি ধাপে যতটা সম্ভব বড় ধাপ নেয়; ছবিতে কমলা পথ একবার উপত্যকা পেরিয়ে বড় লাফও দিয়েছে। কিন্তু দাম আছে: ২১,৭৪২টি f-হিসাব, ধাপপ্রতি প্রায় ১১টি, কারণ প্রতিবার t = ১ থেকে শুরু করে অনেকবার অর্ধেক করতে হয়। Gradient descent-এর দিক এখানে খারাপ, তাই line search অনেক খাটে। Newton + Armijo মাত্র ২০ ধাপ আর ৪৭টি f-হিসাবে f = ৮.৫ × ১০−১২: দিক ভালো হলে line search প্রায় সবসময় প্রথম চেষ্টাতেই t = ১ মেনে নেয়। লাল পথটি উপত্যকার বাঁক ধরে এগিয়েছে, কারণ Hessian বাঁকটি “দেখে”। ২০ ধাপের মধ্যে প্রথম কয়েকটি ধীর (দূরে মডেল খারাপ), শেষেরগুলো quadratic.
Rosenbrock-এ Newton-এর দিক সবসময় descent নয়। উপত্যকার বাইরে কিছু জায়গায় Hessian indefinite (যেমন y > x² + ০.০০৫ হলে)। তখন −H−1g উপরের দিকেও যেতে পারে, আর backtracking t-কে শূন্যের দিকে ঠেলে দেয়। এজন্য কোডে t > 1e-10 পাহারা রাখা হয়েছে। এই run-এ পথটি সেখানে পড়েনি, তবে বাস্তব কোডে এর প্রতিকার দরকার: gᵀd ≥ ০ হলে gradient-এর দিকে ফিরে যাওয়া, অথবা H-এর সঙ্গে τI যোগ করে positive definite বানানো (Cholesky ব্যর্থ হলে τ বাড়ানো), অথবা trust region.
৬ · Quasi-Newton আর BFGS — gradient-এর পার্থক্য থেকে বাঁক শেখা
Newton-এর দামি অংশ Hessian. কিন্তু প্রতিটি ধাপে আমরা একটি সস্তা তথ্য পাই, যা এতক্ষণ ফেলে দিচ্ছিলাম: পরপর দুটি বিন্দুর gradient. এক চলকে দুটি বিন্দুর ঢালের পার্থক্যকে দূরত্ব দিয়ে ভাগ করলেই বাঁকের আন্দাজ: f″ ≈ (f′(xk+1) − f′(xk)) / (xk+1 − xk). এটি পুরোনো secant পদ্ধতির ভাবনা। বহু চলকে লিখুন
sk = xk+1 − xk (কতটা সরলাম), yk = gk+1 − gk (gradient কতটা বদলাল)
Quadratic function-এ y = Hs হুবহু, অন্যথায় প্রায়। Quasi-Newton পদ্ধতিগুলো inverse Hessian-এর একটি আন্দাজ M রাখে (শুরুতে প্রায়ই M0 = I), দিক নেয় d = −M·g, আর প্রতি ধাপে M-কে এমনভাবে হালনাগাদ করে যাতে সে সর্বশেষ জোড়াটি হুবহু মানে:
Mk+1 yk = sk (secant condition)
এক চলকে এই শর্তই M পুরোপুরি ঠিক করে দেয়। বহু চলকে অসংখ্য M এই শর্ত মানে; কোনটি বাছবেন? BFGS-এর উত্তর: যেটি আগের Mk-এর সবচেয়ে কাছে (একটি নির্দিষ্ট ওজনদার মাপে), প্রতিসম আর positive definite. ফল একটি rank-২ হালনাগাদ, ρ = ১/(yᵀs) লিখে:
Mk+1 = (I − ρ s yᵀ) Mk (I − ρ y sᵀ) + ρ s sᵀ
নামটি চারজনের: Charles Broyden, Roger Fletcher, Donald Goldfarb আর David Shanno, যাঁরা ১৯৭০ সালে আলাদা আলাদা প্রবন্ধে প্রায় একই সূত্রে পৌঁছান। এর আগের DFP পদ্ধতি (William Davidon-এর ১৯৫৯ সালের একটি প্রতিবেদন, পরে Fletcher আর Powell ১৯৬৩ সালে জনপ্রিয় করেন) একই ধারণার আরেকটি রূপ; বাস্তবে BFGS বেশি স্থিতিশীল বলে প্রমাণিত হয়েছে। একটি জরুরি শর্ত: yᵀs > ০ হতে হবে, নইলে ρ ঋণাত্মক আর M positive definite থাকে না। Convex function-এ এটি সবসময় সত্য; non-convex-এ Wolfe-এর curvature শর্ত এটি নিশ্চিত করে, কারণ φ′(t) ≥ c2φ′(০) থেকেই yᵀs > ০ আসে। এজন্যই BFGS-এর সঙ্গে Wolfe line search জোড়া হয়।
নিচের ব্লক পর্ব ১১-এর বাটি f = ½xᵀAx, A = [[২.৫, ১.৫], [১.৫, ২.৫]]-এ BFGS নিজে লিখে চালায়। A কোথাও M-এর হালনাগাদে ঢোকে না; সব বাঁকের তথ্য আসে শুধু gradient-এর পার্থক্য y থেকে। দুইবার: একবার নিখুঁত line search দিয়ে (quadratic-এ সেরা t বের করা যায় শুধু gradient দিয়ে, t = −gᵀd / dᵀ(∇f(x + d) − g)), একবার Armijo দিয়ে। প্রতি ধাপে ছাপা হয় M আসল A−1 থেকে কত দূরে, আর secant শর্ত কতটা মানা হলো।
import numpy as np
np.set_printoptions(precision=4, suppress=True)
A = np.array([[2.5, 1.5], [1.5, 2.5]]) # আসল Hessian; BFGS এটি কখনো দেখে না
f = lambda x: 0.5 * x @ A @ x
grad = lambda x: A @ x
A_inv = np.linalg.inv(A) # শুধু তুলনার জন্য
def bfgs_update(M, s, y): # inverse Hessian-এর আন্দাজ M-এর হালনাগাদ
rho, I = 1.0 / (y @ s), np.eye(len(s))
return (I - rho * np.outer(s, y)) @ M @ (I - rho * np.outer(y, s)) + rho * np.outer(s, s)
def run(exact, steps):
x, M = np.array([2.0, -0.5]), np.eye(2) # M0 = I: প্রথম ধাপটি সাধারণ gradient descent
for k in range(1, steps + 1):
g = grad(x); d = -M @ g
if exact: # quadratic-এ নিখুঁত line search, শুধু gradient দিয়ে
t = -(g @ d) / (d @ (grad(x + d) - g))
else:
t = 1.0 # Armijo backtracking
while f(x + t * d) > f(x) + 1e-4 * t * (g @ d):
t *= 0.5
s = t * d; y = grad(x + s) - g # বাঁকের তথ্য শুধু gradient-এর পার্থক্যে
M = bfgs_update(M, s, y); x = x + s
print(f"step {k}: t = {t:.3f} |M - A^-1| = {np.linalg.norm(M - A_inv):.1e}"
f" |My - s| = {np.linalg.norm(M @ y - s):.0e} f = {f(x):.1e}")
return M
print("exact line search:"); M = run(True, 2)
print("M after 2 steps:\n", M, "\ntrue A^-1:\n", A_inv)
print("Armijo line search:"); run(False, 6)আসল আউটপুট (ব্রাউজারের Python):
exact line search:
step 1: t = 0.281 |M - A^-1| = 1.2e-01 |My - s| = 4e-16 f = 8.4e-01
step 2: t = 0.889 |M - A^-1| = 1.6e-16 |My - s| = 2e-16 f = 1.2e-32
M after 2 steps:
[[ 0.625 -0.375]
[-0.375 0.625]]
true A^-1:
[[ 0.625 -0.375]
[-0.375 0.625]]
Armijo line search:
step 1: t = 0.500 |M - A^-1| = 1.2e-01 |My - s| = 4e-16 f = 2.6e+00
step 2: t = 1.000 |M - A^-1| = 6.7e-02 |My - s| = 7e-16 f = 1.3e-02
step 3: t = 1.000 |M - A^-1| = 7.2e-03 |My - s| = 3e-17 f = 5.1e-05
step 4: t = 1.000 |M - A^-1| = 6.9e-03 |My - s| = 0e+00 f = 1.2e-08
step 5: t = 1.000 |M - A^-1| = 5.8e-03 |My - s| = 1e-20 f = 2.3e-12
step 6: t = 1.000 |M - A^-1| = 3.3e-03 |My - s| = 0e+00 f = 2.7e-17
নিখুঁত line search-এ দুই ধাপেই M হুবহু A−1 (পার্থক্য float-এর rounding মাত্র), আর f প্রায় ১০−৩২, অর্থাৎ সর্বনিম্নে। এটি একটি পরিচিত উপপাদ্যের উদাহরণ: n-মাত্রার quadratic-এ নিখুঁত line search-সহ BFGS সর্বোচ্চ n ধাপে থামে, আর M তখন আসল inverse Hessian. কারণ নিখুঁত line search-এ পরপর ধাপগুলো A-এর সাপেক্ষে “conjugate” হয়, আর প্রতিটি নতুন হালনাগাদ আগের secant শর্তগুলো ভাঙে না। Armijo-তে প্রথম ধাপে t = ০.৫, তারপর প্রতিবার পুরো ধাপ t = ১; f ১০−২, ১০−৫, ১০−৮, ১০−১২, ১০−১৭: প্রতি ধাপে কমার অনুপাত নিজেই ছোট হচ্ছে, অর্থাৎ superlinear. অথচ M তখনো A−1 থেকে ৩ × ১০−৩ দূরে। শিক্ষাটি গুরুত্বপূর্ণ: দ্রুত চলতে পুরো Hessian নির্ভুল জানার দরকার নেই, শুধু যে দিকে হাঁটছেন সেই দিকের বাঁক জানলেই চলে (Dennis আর Moré-এর ১৯৭০-এর দশকের একটি ফলের সারকথা)। প্রতিটি সারিতে |My − s| প্রায় শূন্য, অর্থাৎ secant শর্ত প্রতিবার মানা হয়েছে।
৭ · L-BFGS — শুধু শেষ m জোড়া মনে রাখা
BFGS Hessian হিসাবের খরচ বাঁচায়, কিন্তু মেমরি নয়: M তবুও d × d ঘন matrix, d = ১০ লাখে ৮ টেরাবাইট। Jorge Nocedal ১৯৮০ সালের একটি প্রবন্ধে সমাধান দেন: M জমা না রেখে শুধু শেষ m জোড়া (si, yi) রাখুন, আর প্রতিবার দরকারি গুণফল M·g সরাসরি সেই জোড়াগুলো থেকে হিসাব করুন। এর নাম L-BFGS (L মানে limited memory, অর্থাৎ সীমিত স্মৃতি)। Liu আর Nocedal-এর ১৯৮৯ সালের প্রবন্ধ একে বড় সমস্যায় জনপ্রিয় করে। হিসাবের কৌশলটির নাম two-loop recursion: প্রথম loop নতুন থেকে পুরোনো জোড়ার দিকে চলে, মাঝে একটি সরল শুরুর আন্দাজ γI, তারপর দ্বিতীয় loop পুরোনো থেকে নতুনে। খরচ প্রায় 4md গুণ-যোগ, মেমরি 2md সংখ্যা।
| d (parameter) | Newton বা BFGS: d² সংখ্যা (float64) | L-BFGS, m = 10: 2md সংখ্যা |
|---|---|---|
| ১,০০০ | ৮ মেগাবাইট | ১৬০ কিলোবাইট |
| ১ লাখ | ৮০ গিগাবাইট | ১৬ মেগাবাইট |
| ১০ লাখ | ৮ টেরাবাইট | ১৬০ মেগাবাইট |
নিচের stepper ছোট সংখ্যায় পুরো two-loop দেখায়, m = ২. বাটি f = ½(x² + ১০y²), Hessian diag(১, ১০), বর্তমান বিন্দু (২, ১), gradient g = (২, ১০). স্মৃতিতে দুটি পুরোনো জোড়া: s1 = (১, −১), y1 = (১, −১০) আর নতুনটি s2 = (১, ০), y2 = (১, ০). (Quadratic বলে y = Hs.)
| ধাপ | |
| হিসাব | |
| মানে |
- প্রথম loop, নতুন জোড়া। α2 মাপে g-এর কতটা অংশ সর্বশেষ ধাপ s2-এর দিকে “ব্যাখ্যা” করা যায়। সেই অংশ (α2·y2) বাদ দিলে q = (০, ১০).
- প্রথম loop, পুরোনো জোড়া। একই কাজ s1, y1 দিয়ে: α1 = (১/১১)(০ − ১০) = −০.৯০৯১, আর q = (০, ১০) + ০.৯০৯১ × (১, −১০) = (০.৯০৯১, ০.৯০৯১).
- শুরুর আন্দাজ। M0 = γI, যেখানে γ = s2ᵀy2/y2ᵀy2: সর্বশেষ ধাপের দিকে বাঁকের উল্টো। এখানে সেই দিক x-অক্ষ, বাঁক ১, তাই γ = ১. r = q.
- দ্বিতীয় loop, পুরোনো জোড়া। β1 = (১/১১)(০.৯০৯১ − ৯.০৯১) = −০.৭৪৩৮; r-এ s1-এর দিকে (α1 − β1) = −০.১৬৫৩ গুণ যোগ: r = (০.৭৪৩৮, ১.০৭৪৪).
- দ্বিতীয় loop, নতুন জোড়া। β2 = ০.৭৪৩৮, আর (α2 − β2) = ১.২৫৬২ গুণ s2 যোগ: r = (২, ১.০৭৪৪). এটিই এই দুই জোড়া দিয়ে বানানো BFGS matrix M গুণ g, অথচ কোনো ২ × ২ matrix বানানো হয়নি।
- ফল। দিক −r = (−২, −১.০৭৪৪). এক ধাপে f ৭ থেকে ০.০২৭৭-এ নামে। আসল Newton-এর দিক (−২, −১), যার সঙ্গে কোণ মাত্র ১.৭°; gradient descent-এর দিক (−২, −১০) Newton থেকে ৫২° হেলানো। দুটি vector জোড়ার স্মৃতিই বাঁকের প্রায় পুরো ছবি দিয়েছে।
বাস্তবে m সাধারণত ৩ থেকে ২০; SciPy-র L-BFGS-B-তে এর নাম maxcor, default ১০. m বাড়ালে প্রতি ধাপ একটু দামি হয় আর দিক একটু ভালো হয়, কিন্তু লাভ দ্রুত কমে যায়। L-BFGS-B-র “B” মানে bounds: প্রতিটি চলকের নিচের আর উপরের সীমা (যেমন ওজন ≥ ০) মেনে চলার ক্ষমতা, Byrd, Lu, Nocedal আর Zhu-র ১৯৯৫ সালের কাজ থেকে। লেখার সময়ে SciPy-র সোর্সে লেখা আছে, তারা L-BFGS-B v3.0-র মূল Fortran কোডের একটি C-তে অনূদিত ও পরিবর্তিত রূপ ব্যবহার করে। সীমাসহ optimization-এর তত্ত্ব পর্ব ১৬ (Constraint-সহ optimization — Lagrange multiplier আর KKT)-এর বিষয়।
৮ · SciPy আর scikit-learn-এ — চারটি পদ্ধতি পাশাপাশি
নিজে লেখা optimizer শেখার জন্য ভালো, কাজের জন্য library. scipy.optimize.minimize-এ এই পর্বের প্রতিটি ধারণার একটি তৈরি রূপ আছে:
| method | কী করে | কী দিতে হয় | কখন ভালো |
|---|---|---|---|
BFGS | ঘন M, Wolfe line search | f, ভালো হয় jac | ছোট-মাঝারি d (কয়েক হাজার পর্যন্ত); কোনো method না দিলে সীমা বা শর্তহীন সমস্যায় এটিই চলে |
L-BFGS-B | m জোড়ার স্মৃতি, চলকের সীমা মানে | f, jac; ইচ্ছামতো bounds | বড় d, মসৃণ loss; ML-এর সবচেয়ে সাধারণ পছন্দ |
Newton-CG | Hd = −g-কে conjugate gradient দিয়ে আনুমানিকভাবে সমাধান (truncated Newton) | jac আর hess বা শুধু hessp | Hessian-vector product সস্তা হলে, বড় d-তেও |
trust-constr | Trust region; শর্তও মানতে পারে | jac, ভালো হয় hess | শর্তসহ সমস্যা; শর্তহীনে নিরাপদ, তবে ভারী |
Newton-CG-এর কৌশলটি বিশেষ করে মনে রাখার মতো। Conjugate gradient পদ্ধতি Hd = −g সমাধান করে H-কে কখনো পুরোটা না দেখে, শুধু বারবার “H গুণ একটি vector v” চেয়ে। এই Hessian-vector product Hv বের করতে পুরো Hessian লাগে না: logistic regression-এ Xᵀ(S(Xv)), দুটি matrix-vector গুণ; আর যেকোনো autodiff-যোগ্য model-এ gradient-এর দিকনির্দেশক derivative হিসেবে, খরচ প্রায় একটি gradient-এর কয়েক গুণ (Pearlmutter, ১৯৯৪)। CG-কে কয়েক ধাপ পরে থামিয়ে দিলে একটি “যথেষ্ট ভালো” Newton-এর দিক পাওয়া যায়।
নিচের ব্লক চারটি method দুটি সমস্যায় চালায়: Rosenbrock (SciPy-র নিজস্ব rosen, rosen_der, rosen_hess) আর বাজারঘরের ৫০০টি কাল্পনিক অর্ডারের ফেরত-সম্ভাবনার logistic regression, ছোট L2 শাস্তিসহ, সূত্রে দেওয়া gradient আর Hessian দিয়ে (পর্ব ১১-এর সূত্র, শুধু শাস্তির λI যোগ)। মাপকাঠি x* বের করা হয় ৩০ ধাপ বিশুদ্ধ Newton দিয়ে। শেষে একই logistic objective scikit-learn-এর তিনটি solver দিয়ে।
import numpy as np
from scipy.optimize import minimize, rosen, rosen_der, rosen_hess
from sklearn.linear_model import LogisticRegression
rng = np.random.default_rng(42) # কাল্পনিক বাজারঘর: ৫০০টি অর্ডার, ৫টি feature, ফেরত এল কি না
n, d = 500, 6
X = np.column_stack([np.ones(n), rng.normal(size=(n, d - 1))])
y = (rng.random(n) < 1 / (1 + np.exp(-(X @ rng.normal(size=d))))).astype(float)
sig = lambda z: 1 / (1 + np.exp(-z))
loss = lambda w: np.sum(np.logaddexp(0, X @ w) - y * (X @ w)) + 0.5 * w @ w # L2, λ = 1
grad = lambda w: X.T @ (sig(X @ w) - y) + w
hess = lambda w: X.T @ (X * (sig(X @ w) * (1 - sig(X @ w)))[:, None]) + np.eye(d)
for name, (f, g, H, x) in {"Rosenbrock": (rosen, rosen_der, rosen_hess, np.array([-1.2, 1.0])),
"logistic": (loss, grad, hess, np.zeros(d))}.items():
ref = x.copy()
for _ in range(30): # তুলনার মাপকাঠি: বিশুদ্ধ Newton, ৩০ ধাপ
ref = ref - np.linalg.solve(H(ref), g(ref))
print(f"--- {name}, dimension {x.size}, x* = {np.round(ref, 3)}")
print("method nit nfev njev nhev |x - x*|")
for m in ["BFGS", "L-BFGS-B", "Newton-CG", "trust-constr"]:
kw = {"hess": H} if m in ("Newton-CG", "trust-constr") else {}
r = minimize(f, x, jac=g, method=m, **kw)
print(f"{m:<12} {r.nit:4d} {r.nfev:5d} {r.njev:5d} {getattr(r, 'nhev', 0):5d} {np.linalg.norm(r.x - ref):.1e}")
for s in ["lbfgs", "newton-cg", "newton-cholesky"]: # একই objective, scikit-learn দিয়ে
clf = LogisticRegression(C=1.0, fit_intercept=False, solver=s, tol=1e-8).fit(X, y)
print(f"sklearn {s:<16} n_iter {clf.n_iter_[0]:3d} |w - w*| = {np.linalg.norm(clf.coef_[0] - ref):.1e}")আসল আউটপুট (ব্রাউজারের Python, SciPy আর scikit-learn):
--- Rosenbrock, dimension 2, x* = [1. 1.]
method nit nfev njev nhev |x - x*|
BFGS 32 39 39 0 6.1e-08
L-BFGS-B 36 44 44 0 2.2e-06
Newton-CG 83 105 105 83 3.9e-05
trust-constr 44 44 30 30 3.3e-14
--- logistic, dimension 6, x* = [1.313 0.208 1.189 0.25 1.2 0.693]
method nit nfev njev nhev |x - x*|
BFGS 17 24 24 0 6.2e-08
L-BFGS-B 8 9 9 0 5.1e-06
Newton-CG 8 9 9 8 1.7e-09
trust-constr 22 14 13 13 1.2e-09
sklearn lbfgs n_iter 10 |w - w*| = 3.8e-08
sklearn newton-cg n_iter 6 |w - w*| = 5.6e-11
sklearn newton-cholesky n_iter 5 |w - w*| = 1.0e-07
কলামগুলো: nit ধাপ, nfev/njev/nhev কতবার f, gradient আর Hessian হিসাব হলো (BFGS আর L-BFGS-B কোনো Hessian নেয় না, তাই ০), শেষ কলাম মাপকাঠি থেকে দূরত্ব। Rosenbrock-এ BFGS ৩২ ধাপে ৬ × ১০−৮ পর্যন্ত, L-BFGS-B ৩৬ ধাপে ২ × ১০−৬: এখানে d = ২, তাই স্মৃতি সীমিত রাখার কোনো লাভ নেই। Newton-CG ৮৩ ধাপ নিয়েছে, অংশ ৫-এর হাতে লেখা damped Newton-এর ২০ ধাপের চেয়ে বেশি: তার ভেতরের CG negative curvature পেলে থেমে যায় আর line search সাবধানী, তাই বাঁকা উপত্যকায় ধাপগুলো ছোট হয়েছে। trust-constr সবচেয়ে নির্ভুল (৩ × ১০−১৪), কারণ এর default থামার শর্ত কঠোর; প্রতিটি method-এর নিজস্ব default tolerance আছে, তাই “কে বেশি নির্ভুল” তুলনা করতে হলে tolerance মিলিয়ে নিতে হয়। Logistic-এ (convex, মসৃণ, d = ৬) ছবি বদলে যায়: L-BFGS-B আর Newton-CG ৮ ধাপে, ৯টি f-হিসাবে। একই সমস্যায় “কোন optimizer সেরা” প্রশ্নের উত্তর সমস্যার আকৃতির ওপর নির্ভর করে; একটি benchmark-এর জয়-পরাজয় সাধারণ নিয়ম নয়, আর এই সংখ্যাগুলোও একটি run-এর। scikit-learn-এর তিনটি solver একই উত্তরে পৌঁছেছে (পার্থক্য ১০−৭ বা তার কম), যা নিশ্চিত করে C = 1, fit_intercept=False-এ তার objective হুবহু আমাদের loss: ½‖w‖² + C × log loss-এর যোগফল।
scikit-learn কেন lbfgs দিয়ে শুরু করে
scikit-learn-এর ডকুমেন্টেশন অনুযায়ী LogisticRegression-এর default solver ০.২২ version-এ liblinear থেকে বদলে lbfgs করা হয়েছে, আর docs-এর ভাষায় এটি “a good default solver because it works reasonably well for a wide class of problems”. কারণগুলো এই পর্ব থেকেই বোঝা যায়: L2 শাস্তিসহ log loss মসৃণ আর convex, তাই quasi-Newton-এর জন্য আদর্শ; মেমরি O(md), তাই feature অনেক হলেও চলে; Hessian লাগে না; আর multinomial (বহু শ্রেণির softmax) loss-ও সরাসরি সামলায়। একই docs বলে, নমুনা অনেক আর feature কম হলে (n ≫ d) newton-cholesky ভালো পছন্দ, যা ১.২ version-এ যোগ হয়েছে আর পুরো Hessian বানায়, তাই এর মেমরি d²-এর অনুপাতে বাড়ে। L1 শাস্তি মসৃণ নয় বলে lbfgs তা পারে না; সেখানে liblinear বা saga. ব্যবহারিক সতর্কতা: feature-এর scale খুব আলাদা হলে lbfgs-এর ধাপ বেশি লাগে, আর max_iter (default ১০০)-এর মধ্যে না থামলে scikit-learn একটি ConvergenceWarning দেয়; সমাধান সাধারণত আগে StandardScaler, তারপর প্রয়োজনে max_iter বাড়ানো।
পরিসংখ্যানের library-তে একই logistic regression প্রায়ই অন্য নামে চলে: IRLS (iteratively reweighted least squares). পর্ব ১১-এ দেখেছেন, logistic-এ Newton-এর প্রতিটি ধাপ আসলে S-এর ঘরগুলোকে ওজন ধরে একটি weighted least squares. statsmodels-এর GLM এভাবে fit করে, আর standard error-এর জন্য শেষ Hessian কাজে লাগায়। পুরো exponential family-তে এই ভাবনার সাধারণ রূপ পর্ব ২০ (Exponential family আর GLM — Poisson, binomial আর link function)-এ আসবে।
৯ · কোন পদ্ধতি কখন — সিদ্ধান্তের ছক
| পরিস্থিতি | প্রথম পছন্দ | কেন |
|---|---|---|
| মসৃণ, convex, d কয়েকশ পর্যন্ত, পুরো ডেটা একবারে (GLM, ছোট logistic) | Newton / IRLS, বা newton-cholesky | কয়েক ধাপে মেশিনের নির্ভুলতা; Hessian থেকে standard error-ও |
| মসৃণ, d হাজার থেকে কোটি, পুরো ডেটা মেমরিতে (TF-IDF logistic, CRF, ছোট model) | L-BFGS | Hessian লাগে না, মেমরি 2md, η বাছতে হয় না |
| Hessian-vector product সস্তা, খুব নির্ভুল উত্তর দরকার | Newton-CG / trust-ncg | Newton-এর গতি, পুরো H ছাড়াই |
| সীমা বা শর্ত আছে | L-BFGS-B (শুধু সীমা), trust-constr বা SLSQP | শর্ত মানার কাঠামো তৈরি (পর্ব ১৬) |
| বিশাল ডেটা, mini-batch, non-convex (deep learning) | SGD পরিবার, Adam (পর্ব ১৩–১৪) | Noisy gradient-এ secant-এর y অর্থহীন হয়; প্রতি ধাপ সস্তা হতে হয় |
| মসৃণ নয় (L1, hinge, ReLU-র ভাঁজ) | Coordinate descent, proximal পদ্ধতি, subgradient | Hessian বা secant-এর ভিত্তি মসৃণতা; ভাঁজে ভাঙে |
আরও গভীরে: এই পর্বের প্রায় সব তত্ত্ব (Wolfe শর্তে convergence, BFGS-এর superlinear গতি, two-loop, trust region) এক জায়গায় পাবেন Nocedal আর Wright-এর Numerical Optimization (দ্বিতীয় সংস্করণ, ২০০৬)-এ। L-BFGS-এর একটি “compact” matrix রূপ (Byrd, Nocedal আর Schnabel, ১৯৯৪) L-BFGS-B-র ভিত্তি। Deep learning-এ second-order ধারণা ফিরে আসছে আংশিক রূপে: Martens-এর Hessian-free optimization (২০১০), K-FAC (Martens আর Grosse, ২০১৫) Fisher matrix-কে layer-ভিত্তিক Kronecker গুণফলে আন্দাজ করে, আর Shampoo-র মতো preconditioner; এদের বাস্তব সুবিধা নিয়ে গবেষকদের মধ্যে এখনো তর্ক আছে।
পরীক্ষা ও ইন্টারভিউতে: (১) f(x) = x² − 4x + 7, x0 = ১০ থেকে Newton-এর এক ধাপ? f′ = ১৬, f″ = ২, x1 = ১০ − ৮ = ২: quadratic-এ এক ধাপেই সর্বনিম্ন। (২) Newton-এর দুটি ব্যর্থতা? Hessian positive definite না হলে চূড়া বা saddle-এ যায়; দূর থেকে বিশাল ধাপে ছিটকে যায়; প্রতিকার line search বা trust region. (৩) Armijo-র শর্ত লিখুন। f(x + td) ≤ f(x) + c·t·gᵀd, c প্রায় ১০−৪. (৪) BFGS-এর secant শর্ত? Mk+1yk = sk, আর M positive definite থাকতে yᵀs > ০. (৫) L-BFGS-এর মেমরি? 2md সংখ্যা; d = ১০৬, m = ১০-এ ২ × ১০৭, float64-এ ১৬০ MB.
১০ · AI-এর যুগে — AI-এর লেখা optimizer কোড যাচাই
“এই loss কমানোর কোড লিখে দাও” বললে AI assistant প্রায়ই চলনসই কোড দেয়, কিন্তু optimizer-এর ভুল চুপচাপ থাকে: কোড চলে, একটি সংখ্যা ছাপে, আর সেটি সর্বনিম্ন নয়। নিচের ভুলগুলো বারবার দেখা যায়।
| AI প্রায়ই যা করে | কেন সমস্যা | কীভাবে ধরবেন |
|---|---|---|
| বিশুদ্ধ Newton লেখে, line search বা Hessian-এর চিহ্ন যাচাই ছাড়া | Non-convex loss-এ চূড়া বা saddle-এ যায়; দূর থেকে ছিটকে যায় | প্রতি ধাপে loss কমছে কি না আর gᵀd < ০ কি না দেখুন; eigvalsh দিয়ে Hessian দেখুন |
np.linalg.inv(H) @ g লেখে | ধীর আর সংখ্যাগতভাবে কম স্থিতিশীল | solve বা scipy.linalg.cho_solve ব্যবহার করুন |
বড় d-তে method="BFGS" বা পুরো Hessian সুপারিশ করে | d² মেমরি; ১০ লাখ feature-এ টেরাবাইট | আকার গুনে দেখুন; L-BFGS বা Newton-CG + hessp |
jac না দিয়ে minimize চালায় | Finite difference-এ প্রতি gradient-এ d + ১টি f-হিসাব, আর নির্ভুলতা কম | Gradient সূত্রে দিন, আর পর্ব ১১-এর মতো central difference দিয়ে যাচাই করুন |
result.success না দেখেই result.x ব্যবহার করে | সর্বোচ্চ ধাপ পেরিয়ে থামা সমাধানও x ফেরত দেয় | success, message, শেষ gradient-এর মাপ ছাপুন |
scikit-learn-এ বাদ পড়া বা পুরোনো parameter (যেমন multi_class, ১.৮-এ deprecated penalty) দেয়, বা ConvergenceWarning চাপা দিতে warning বন্ধ করে | নতুন version-এ error বা warning; অসম্পূর্ণ fit লুকিয়ে যায় | নিজের version-এর docs পড়ুন; scale করুন, max_iter বাড়ান, warning চাপা দেবেন না |
AI-কে শুধু কোড নয়, শর্ত আর যাচাইও চাইতে বলুন:
AI-এর দেওয়া optimizer কোড হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১Newton-এর পদ্ধতি “affine invariant” — এর মানে কী, আর gradient descent-এর সঙ্গে feature scaling-এর সম্পর্কে এটি কী শেখায়?
ধরুন বাজারঘরের একটি model-এ দুটি feature: ডেলিভারির দূরত্ব km-এ আর ঝুড়ির দাম টাকায়। দামের সংখ্যাগুলো হাজারের ঘরে, দূরত্ব এক অঙ্কের। loss-এর বাটি তখন দামের ওজনের দিকে অত্যন্ত সরু আর দূরত্বের ওজনের দিকে চওড়া, অর্থাৎ Hessian-এর condition number বিশাল। Gradient descent-এর একটিমাত্র learning rate দুই দিকে একসঙ্গে ঠিক হতে পারে না: সরু দিকে স্থির থাকার মতো ছোট η চওড়া দিকে হামাগুড়ি দেয়। তাই GD-তে standardization প্রায় বাধ্যতামূলক।
Newton-এ এই সমস্যা হয় না। চলক w-কে w = Tz লিখলে (T যেকোনো invertible matrix, যেমন প্রতিটি feature-এর এককের বদল), z-এর gradient Tᵀg আর Hessian TᵀHT. Newton-এর ধাপ −(TᵀHT)−1Tᵀg = −T−1H−1g, যা w-এর ভাষায় ফিরিয়ে নিলে হুবহু পুরোনো ধাপ। অর্থাৎ Newton নিজেই সবচেয়ে ভালো “scaling” খুঁজে নেয়, কারণ Hessian হলো স্থানীয় জ্যামিতির মাপকাঠি। এই দৃষ্টিতে Newton আসলে একটি বিশেষ মাপে (Hessian-এর মাপে) steepest descent; quasi-Newton পদ্ধতির পুরোনো নাম “variable metric method” এখান থেকেই, কারণ প্রতি ধাপে দূরত্বের মাপটি বদলায়।
ইতিহাসে Newton-Raphson পদ্ধতি সমীকরণের মূল খোঁজার জন্য, সতেরো শতকে; optimization-এর রূপ আর তার convergence-এর কঠোর তত্ত্ব পরে এসেছে, যেমন Kantorovich-এর বিংশ শতকের মাঝামাঝির কাজ। তবে খুঁটিনাটি ইতিহাস নিয়ে উৎসভেদে ভিন্নতা আছে।
বাস্তবে দুটি সতর্কতা। BFGS আর L-BFGS আংশিকভাবে এই গুণ পায়, কারণ শুরুর আন্দাজ M0 = I বা γI নিজে scale-নির্ভর; তাই scikit-learn-এর lbfgs-এও feature scale করলে ধাপ কম লাগে। আর float64-এর rounding-এ ill-conditioned H-এর সমাধান অনির্ভুল হতে পারে; গণিতের invariance আর কম্পিউটারের invariance এক জিনিস নয়।
মূল উপলব্ধি: Hessian নিজেই সবচেয়ে ভালো scaling; যে পদ্ধতি বাঁক যত কম জানে, তার জন্য হাতে scaling তত বেশি জরুরি।
প্র ০২১৯৭০ সালে চারজন গবেষক প্রায় একই BFGS সূত্রে আলাদাভাবে পৌঁছালেন — এর পেছনের গল্প আর BFGS কেন DFP-কে ছাপিয়ে গেল?
গল্পের শুরু ১৯৫০-এর দশকের শেষে। পদার্থবিজ্ঞানী William Davidon একটি গবেষণাগারে লম্বা কম্পিউটার হিসাব চালাতেন, আর মেশিন প্রায়ই হিসাব শেষ হওয়ার আগে বিকল হতো। দ্রুততর minimization-এর খোঁজে তিনি ১৯৫৯ সালে একটি প্রতিবেদনে প্রথম “variable metric” পদ্ধতির ধারণা দেন। প্রচলিত বিবরণ অনুযায়ী প্রবন্ধটি তখন জার্নালে প্রকাশ হয়নি, আর বহু বছর পরে ১৯৯১ সালে SIAM Journal on Optimization-এর প্রথম সংখ্যায় ছাপা হয়। Fletcher আর Powell ১৯৬৩ সালে পদ্ধতিটি বিশ্লেষণ ও জনপ্রিয় করেন, তাই নাম DFP.
১৯৭০ সালে Broyden, Fletcher, Goldfarb আর Shanno চারটি আলাদা প্রবন্ধে আরেকটি হালনাগাদে পৌঁছান। চারজন চারটি পথে: কেউ পদ্ধতির একটি পরিবার থেকে, কেউ একটি ওজনদার ন্যূনতম-পরিবর্তনের নীতি থেকে, কেউ DFP-র “dual” রূপ থেকে। একই সময়ে একাধিক জনের একই আবিষ্কার বিজ্ঞানে অস্বাভাবিক নয়: প্রশ্নটি তখন পেকে উঠেছিল, আর সবার হাতে একই উপাদান ছিল (secant শর্ত, rank-২ হালনাগাদ, positive definiteness রক্ষা)।
BFGS কেন জিতল? DFP Hessian-এর inverse-এর আন্দাজ হালনাগাদ করে এমনভাবে, যা BFGS Hessian-এর আন্দাজে করে; দুটি গাণিতিকভাবে “dual”. কিন্তু পরীক্ষায় দেখা গেল, অনির্ভুল line search-এ BFGS অনেক বেশি নির্ভরযোগ্য। Powell আর অন্যদের বিশ্লেষণে এর একটি ব্যাখ্যা আসে: BFGS-এর একটি “self-correcting” গুণ আছে, কোনো দিকে বাঁক ভুল আন্দাজ হলে কয়েক ধাপে তা নিজেই সারিয়ে নেয়, বিশেষ করে বাঁক বেশি আন্দাজ হলে; DFP-তে এই সংশোধন অনেক ধীর হতে পারে।
আজ BFGS আর তার সীমিত-স্মৃতি রূপ L-BFGS মসৃণ optimization-এর মানদণ্ড। SciPy-তে সীমা ও শর্তহীন সমস্যায় default, আর scikit-learn-এর logistic regression-এ default solver-এর ভিত্তি।
মূল উপলব্ধি: গাণিতিকভাবে সমান দুটি পদ্ধতির পার্থক্য প্রায়ই ধরা পড়ে অনির্ভুল বাস্তবতায়: অসম্পূর্ণ line search আর float-এর rounding-এ কোনটি নিজেকে সামলায়।
প্র ০৩Second-order পদ্ধতি এত দ্রুত হলে deep learning-এর training-এ প্রায় সবাই SGD আর Adam কেন চালায়, আর কোথায় second-order ধারণা ফিরে আসছে?
পর্ব ১১-এ একটি অংশ দেখেছেন: আকারের বাধা। কয়েক কোটি parameter-এ পুরো Hessian বা BFGS-এর M রাখা যায় না। কিন্তু L-BFGS তো সেই সমস্যা মেটায়; তবু deep learning-এ তা খুব কম চলে। আসল কারণ দুটি: noise আর loss-এর non-convex আকৃতি।
Noise: deep learning-এ gradient আসে mini-batch থেকে, পুরো ডেটা থেকে নয়। BFGS-এর বাঁকের তথ্য y = gk+1 − gk; দুটি আলাদা mini-batch-এর gradient-এর পার্থক্যে বাঁকের চেয়ে noise বেশি থাকতে পারে, ফলে yᵀs ঋণাত্মক বা অর্থহীন হয়। Line search-ও noisy loss-এ বিভ্রান্ত হয়: একটি batch-এ Armijo মেলে, পরেরটিতে মেলে না। পুরো ডেটা দিয়ে প্রতি ধাপ নিলে খরচ অসম্ভব হয়।
Non-convexity: নিউরাল নেটওয়ার্কের loss-এ Hessian-এর অনেক eigenvalue ঋণাত্মক হতে পারে। বিশুদ্ধ Newton saddle point-এর দিকে টানে; Dauphin ও সহকর্মীদের ২০১৪ সালের একটি প্রবন্ধ যুক্তি দেয় যে উচ্চ মাত্রায় saddle point খারাপ local minimum-এর চেয়ে অনেক বড় বাধা। তাছাড়া deep learning-এ লক্ষ্য শুধু training loss নিখুঁত কমানো নয়, ভালো generalisation; SGD-র noise নিজেই এক ধরনের regularization হিসেবে কাজ করে বলে অনেকে মনে করেন, যদিও এটি সক্রিয় গবেষণার বিষয়।
তবু second-order ভাবনা ফিরে আসছে আংশিক রূপে। Adam প্রতিটি parameter-এর জন্য আলাদা ধাপ নেয়, যাকে কর্ণ-preconditioner হিসেবে দেখা যায় (পর্ব ১৪)। Martens-এর ২০১০ সালের Hessian-free পদ্ধতি Newton-CG-র ধারণা নেটওয়ার্কে আনে। K-FAC Fisher information matrix-কে layer অনুযায়ী Kronecker গুণফলে আন্দাজ করে, আর Shampoo-র মতো পদ্ধতি বড় প্রশিক্ষণে পরীক্ষা হচ্ছে। এদের সুবিধা কাজভেদে ভিন্ন, আর কোনটি সাধারণভাবে সেরা তা নিয়ে ঐকমত্য নেই।
উল্টো দিকে, ছোট model fine-tune করা বা পুরো ডেটা একবারে মেমরিতে আঁটে এমন সমস্যায় (যেমন একটি ছোট নেটওয়ার্কের শেষ layer বা style transfer-এর মতো কাজ) L-BFGS আজও ব্যবহার হয়; PyTorch-এও torch.optim.LBFGS আছে।
মূল উপলব্ধি: second-order পদ্ধতির শক্তি নির্ভর করে নির্ভুল gradient আর মসৃণ, প্রায়-convex জ্যামিতির ওপর; noise আর অগণিত saddle-এ সেই শক্তি দুর্বল হয়, তাই সেখানে সস্তা, শক্তপোক্ত first-order পদ্ধতি জেতে।
প্র ০৪Line search আর trust region — একই সমস্যার দুটি দর্শন। কোনটি কখন ভালো, আর Levenberg-Marquardt এই গল্পে কোথায়?
দুটিই একই প্রশ্নের উত্তর: quadratic model-কে কতদূর বিশ্বাস করব? Line search বলে, আগে দিক ঠিক করি (Newton, BFGS বা gradient), তারপর সেই সরলরেখায় কতদূর যাব তা ঠিক করি। এটি সরল, সস্তা, আর মডেল যখন ভালো তখন প্রায় খরচহীন (প্রথম চেষ্টাতেই t = ১)। কিন্তু দিকটাই যদি খারাপ হয়, যেমন indefinite Hessian-এ উপরের দিকে, তাহলে দূরত্ব কমিয়ে কোনো লাভ নেই; তখন আলাদা করে Hessian সংশোধন করতে হয় (H + τI).
Trust region উল্টো ক্রমে: আগে একটি ব্যাসার্ধ Δ, তারপর সেই সীমার ভেতরে মডেলের সর্বনিম্ন। বৃত্তটি সীমিত বলে Hessian indefinite হলেও সমস্যাটির একটি অর্থপূর্ণ উত্তর থাকে, আর negative curvature-এর দিকটিও কাজে লাগে: সেই দিকে বৃত্তের সীমা পর্যন্ত যাওয়া। আসল কমা আর মডেলের প্রতিশ্রুত কমার অনুপাত ρ দেখে Δ বাড়ানো-কমানো হয়, যেন আবহাওয়ার পূর্বাভাসদাতা নিজের অতীত নির্ভুলতা দেখে পরের পূর্বাভাসে কতটা জোর দেবেন তা ঠিক করেন।
ইতিহাসে এর শিকড় least squares-এ। Kenneth Levenberg ১৯৪৪ সালে আর Donald Marquardt ১৯৬৩ সালে প্রস্তাব করেন Gauss-Newton-এর সমীকরণে JᵀJ-এর সঙ্গে λI যোগ করতে। λ বড় হলে ধাপ ছোট আর gradient descent-এর মতো, λ ছোট হলে Gauss-Newton-এর মতো। পরে দেখা যায় এটি একটি trust region সমস্যার সমাধানের সমতুল্য: প্রতিটি Δ-এর জন্য একটি λ আছে। আজও curve fitting-এ (যেমন SciPy-র curve_fit-এর default পদ্ধতি) এটি চলে।
বড় সমস্যায় trust region-এর ভেতরের উপ-সমস্যা আনুমানিকভাবে সমাধান হয়, Steihaug-এর ১৯৮৩ সালের CG-ভিত্তিক কৌশলে, যা Newton-CG-র ভাবনার খুব কাছে। SciPy-র trust-ncg এভাবে কাজ করে। সাধারণ অভিজ্ঞতা: মসৃণ, convex ML loss-এ line search-ভিত্তিক L-BFGS যথেষ্ট; non-convex, কঠিন জ্যামিতিতে বা Hessian নিয়মিত indefinite হলে trust region বেশি নির্ভরযোগ্য।
মূল উপলব্ধি: line search দিক বিশ্বাস করে দূরত্ব নিয়ে সন্দেহ করে; trust region মডেলের পরিধি নিয়ে সন্দেহ করে, তাই খারাপ বাঁকেও নিরাপদ।
প্র ০৫scikit-learn-এর LogisticRegression-এর default solver liblinear থেকে lbfgs-এ বদলানো থেকে library নকশা আর ব্যবহারকারীর দায়িত্ব নিয়ে কী শেখা যায়?
scikit-learn-এর ডকুমেন্টেশন জানায়, ০.২২ version-এ default solver liblinear থেকে lbfgs করা হয়। liblinear একটি পুরোনো, দ্রুত C++ library; তার পদ্ধতিতে intercept-কে একটি কৃত্রিম feature হিসেবে ধরা হয়, ফলে intercept-ও শাস্তি পায় (তাই scikit-learn-এ intercept_scaling নামে একটি parameter আছে), আর বহু শ্রেণিতে সে সরাসরি multinomial loss কমায় না। lbfgs-এ এই দুটি সমস্যা নেই: intercept আলাদা, আর softmax loss সরাসরি। বদলের পেছনের পুরো আলোচনা প্রকল্পের নিজস্ব ইতিহাসে আছে; এখানে আমরা শুধু docs-এ লেখা তথ্যটুকু নিচ্ছি।
শিক্ষা এক: default একটি নকশার সিদ্ধান্ত, প্রকৃতির নিয়ম নয়। একই কোড দুই version-এ ভিন্ন সংখ্যা দিতে পারে, কারণ optimizer, objective আর থামার শর্ত বদলেছে। পুনরুৎপাদনযোগ্যতার জন্য (পর্ব ০৪) solver, C, tol আর max_iter স্পষ্ট লিখে রাখা, আর library-র version লক করা ভালো অভ্যাস।
শিক্ষা দুই: “ভালো default” মানে সব জায়গায় সেরা নয়। docs নিজেই বলে, n ≫ d হলে newton-cholesky, বড় ডেটায় sag বা saga, L1-এ liblinear বা saga. এই পর্বের run-এ তিনটি solver একই উত্তরে পৌঁছেছে, কারণ objective convex আর মসৃণ; কিন্তু ধাপের সংখ্যা ছিল ১০, ৬ আর ৫. বিশাল ডেটায় এই পার্থক্য ঘণ্টায় গিয়ে দাঁড়ায়।
শিক্ষা তিন: warning-এর দায় ব্যবহারকারীর। lbfgs default হওয়ার পর অনেকে প্রথমবার ConvergenceWarning দেখেন, কারণ default max_iter = ১০০ আর scale না করা feature-এ ধাপ বেশি লাগে। AI assistant প্রায়ই warning চাপা দেওয়ার কোড সুপারিশ করে; সঠিক উত্তর হলো scale করা, তারপর দেখা optimizer আসলে থেমেছে কি না। অসম্পূর্ণ fit-এর coefficient ব্যাখ্যা করা, বিশেষ করে মানুষের ওপর প্রভাব ফেলে এমন সিদ্ধান্তে, গুরুতর ভুল।
মূল উপলব্ধি: optimizer-এর বাছাই model-এর অংশ; সেটি লিখে রাখুন, তার থামার শর্ত যাচাই করুন, আর default-কে সিদ্ধান্ত হিসেবে দেখুন, সত্য হিসেবে নয়।
অনুশীলন
হিসাবগুলো খাতায় করুন, তারপর উত্তর মেলান। চাইলে পাতার ব্লকগুলোতে সংখ্যা বদলে যাচাই করুন।
-
পূর্ণ numerical: f(x) = x² − ln x, x > ০. (ক) x0 = ১ থেকে Newton-এর দুটি ধাপ নিন; প্রতিবার x আর সর্বনিম্ন থেকে ভুল লিখুন। (খ) একই x0 = ১ থেকে gradient descent-এর দিক d = −f′(১) নিয়ে Armijo backtracking চালান (c = ১০−৪, β = ০.৫, t = ১ থেকে শুরু)। কোন t গৃহীত হয়?
f′(x) = 2x − 1/x, f″(x) = 2 + 1/x² (সবসময় ধনাত্মক, তাই convex বা উত্তল)। সর্বনিম্ন f′ = ০ ⇒ x* = ১/√২ ≈ ০.৭০৭১. (ক) x0 = ১: f′ = ১, f″ = ৩, x1 = ১ − ১/৩ = ০.৬৬৬৭, ভুল ০.০৪০৪. x1-এ f′ = ১.৩৩৩৩ − ১.৫ = −০.১৬৬৭, f″ = ২ + ২.২৫ = ৪.২৫, x2 = ০.৬৬৬৭ + ০.০৩৯২ = ০.৭০৫৯, ভুল ০.০০১২. (পরের ধাপে ভুল প্রায় ১.১ × ১০−৬: quadratic.) (খ) f(১) = ১, d = −১, gᵀd = −১. t = ১: x = ০, যেখানে ln x সংজ্ঞায়িত নয় (f অসীম), বাতিল। t = ০.৫: x = ০.৫, f = ০.২৫ + ০.৬৯৩১ = ০.৯৪৩১ ≤ ১ − ১০−৪ × ০.৫ = ০.৯৯৯৯৫, গৃহীত, t = ০.৫. লক্ষ করুন, line search এখানে domain-এর বাইরে যাওয়াও আটকেছে; কোডে f(০) = inf ফেরত দিলে তুলনাটি নিজে থেকেই বাতিল করে।
-
সমালোচনা ও নকশা: বাজারঘরের একজন সহকর্মী বাংলা রিভিউ থেকে TF-IDF বানিয়েছেন, ৮ লাখ feature. একটি AI assistant-এর পরামর্শে তিনি লিখেছেন
minimize(loss, w0, method="BFGS"), jac ছাড়া। কোড শুরুতেই মেমরি ফুরিয়ে বন্ধ হয়। কী কী ভুল, আর আপনি কী নকশা করবেন?মেমরি: BFGS ঘন M রাখে, ৮ লাখ × ৮ লাখ × ৮ বাইট ≈ ৫.১ টেরাবাইট। Gradient: jac না দিলে SciPy finite difference দিয়ে gradient মাপে, প্রতিবার প্রায় ৮ লাখ বাড়তি loss-হিসাব, অসম্ভব ধীর আর কম নির্ভুল। নকশা: (১) সূত্রে gradient দিন (logistic-এ Xᵀ(p − y) + λw, sparse X-এ সস্তা), central difference দিয়ে কয়েকটি এলোমেলো ঘরে যাচাই করুন। (২)
L-BFGS-B(মেমরি m = ১০-এ প্রায় ১২৮ MB) বা সরাসরি scikit-learn-এরLogisticRegression(solver="lbfgs"), অথবা খুব বড় ডেটায়saga. (৩) শাস্তির শক্তি C cross-validation-এ বাছুন, test set ছুঁয়ে নয়। (৪)success,nit, ConvergenceWarning দেখুন; দরকারে max_iter বাড়ান। (৫) একটি সরল baseline (যেমন majority class বা ছোট vocabulary-র model)-এর সঙ্গে তুলনা করুন, কয়েকটি fold-এ। -
আউটপুট অনুমান করুন: f(x) = √(1 + x²)-এ বিশুদ্ধ Newton, ঠিক x0 = ১ থেকে। চালানোর আগে লিখুন কী ছাপবে।
import numpy as np x = 1.0 for k in range(4): x = x - (x / np.sqrt(1 + x**2)) / (1 + x**2) ** -1.5 print(k + 1, x)ডেস্কটপে সত্যিকারের run (Python 3.14, NumPy 2.4.6):
1 -0.9999999999999998 2 0.9999999999999993 3 -0.9999999999999982 4 0.9999999999999944ধাপটি সরলীকরণে x ← x − x(1 + x²) = −x³. x = ১ হলে −১, তারপর ১, তারপর −১: একটি চক্র, সর্বনিম্ন ০-এর দিকে কখনো যায় না। কিন্তু চক্রটি অস্থির: −x³-এর derivative x = ±১-এ পরম মানে ৩, তাই প্রতিটি rounding ভুল প্রতি ধাপে প্রায় ৩ গুণ বাড়ে (২ × ১০−১৬, ৭ × ১০−১৬, ১.৮ × ১০−১৫, ৫.৬ × ১০−১৫)। পরম মান ১-এর সামান্য নিচে থাকায় আরও অনেক ধাপ পরে ধারাটি শেষ পর্যন্ত ০-এর দিকে নামবে; ১-এর সামান্য ওপরে থাকলে অসীমে যেত। ঠিক সীমানার ওপর Newton-এর আচরণ float-এর rounding নির্ধারণ করে।
-
কোডিং: L-BFGS-এর two-loop recursion একটি function হিসেবে লিখুন:
two_loop(g, S, Y), যেখানে S আর Y হলো পুরোনো থেকে নতুন ক্রমে সাজানো s আর y-এর তালিকা। তারপর একই জোড়াগুলো দিয়ে M0 = γI থেকে BFGS-এর পুরো matrix বানিয়ে M·g-এর সঙ্গে মিলিয়ে দেখান।import numpy as np def two_loop(g, S, Y): q, alphas = g.copy(), [] for s, y in reversed(list(zip(S, Y))): # নতুন থেকে পুরোনো a = (s @ q) / (y @ s); q -= a * y; alphas.append(a) r = (S[-1] @ Y[-1]) / (Y[-1] @ Y[-1]) * q # M0 = γI for (s, y), a in zip(zip(S, Y), reversed(alphas)): # পুরোনো থেকে নতুন b = (y @ r) / (y @ s); r += s * (a - b) return r A = np.diag([1.0, 10.0]) S = [np.array([1.0, -1.0]), np.array([1.0, 0.0])]; Y = [A @ s for s in S] g = np.array([2.0, 10.0]) M = (S[-1] @ Y[-1]) / (Y[-1] @ Y[-1]) * np.eye(2) for s, y in zip(S, Y): rho, I = 1 / (y @ s), np.eye(2) M = (I - rho * np.outer(s, y)) @ M @ (I - rho * np.outer(y, s)) + rho * np.outer(s, s) print(two_loop(g, S, Y), M @ g)দুটিই (২, ১.০৭৪৪), অংশ ৭-এর stepper-এর মতো। লক্ষ রাখুন: α-গুলো প্রথম loop-এ নতুন থেকে পুরোনো ক্রমে জমে, তাই দ্বিতীয় loop-এ উল্টে নিতে হয়। এই ক্রম ভুল করা AI-এর লেখা L-BFGS-এর একটি সাধারণ ভুল; পুরো matrix-এর সঙ্গে ছোট উদাহরণে মিলিয়ে নেওয়াই সবচেয়ে সহজ পরীক্ষা।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমBacktracking line search-সহ damped Newton-এর একটি ধাপ সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- এখনকার x-এ gradient g আর Hessian H হিসাব করা
- H d = −g সমাধান করে দিক d বের করা, আর gᵀd < ০ কি না দেখা
- ধাপের দৈর্ঘ্য t = ১ দিয়ে শুরু করা
- Armijo-র শর্ত না মেলা পর্যন্ত t অর্ধেক করা
- x ← x + t·d, তারপর gradient-এর মাপ দেখে থামা বা চালিয়ে যাওয়া
f(x) = x² − 4x + 7. x0 = ১০ থেকে Newton-এর একটি ধাপের পর x1 কত?
- 8.4
- 2
- −6
- 3
x বিন্দু, d একটি descent direction, g = ∇f(x), ০ < c < ১. Armijo-র sufficient decrease শর্ত কোনটি?
- f(x + t·d) < f(x)
- ∇f(x + t·d)ᵀd ≥ c·gᵀd
- f(x + t·d) ≤ f(x) − c·t·gᵀd
- f(x + t·d) ≤ f(x) + c·t·gᵀd
Quasi-Newton পদ্ধতিতে s = xk+1 − xk, y = ∇f(xk+1) − ∇f(xk), আর M হলো inverse Hessian-এর আন্দাজ। হালনাগাদের পর M কোন শর্ত মানে?
- M y = s
- M s = y
- M = I
- M g = 0
d = ১০৬ parameter আর m = ১০ জোড়ার স্মৃতিতে L-BFGS প্রায় কতগুলো সংখ্যা জমা রাখে (জোড়াগুলোর জন্য)?
- ১০১২
- ১০৭
- ২ × ১০৭
- ২০
একটি পদ্ধতিতে সর্বনিম্নের কাছে ভুল ১০−৩ থেকে পরের ধাপে প্রায় ১০−৬, তারপর প্রায় ১০−১২ হলো। এই গতির নাম কী, আর কোন পদ্ধতির বৈশিষ্ট্য?
- Linear, gradient descent-এর
- Quadratic, Newton-এর
- Sublinear, SGD-র
- Cubic, Adam-এর
scikit-learn ১.৮-এ LogisticRegression-এর default solver কোনটি, আর docs অনুযায়ী n ≫ d এমন ডেটায় কোনটি প্রস্তাবিত?
- liblinear; বড় ডেটায় sag
- saga; n ≫ d-তে liblinear
- lbfgs; n ≫ d-তে newton-cholesky
- newton-cg; n ≫ d-তে lbfgs
f(x) = x⁴ − 2x². x0 = ০.৩ থেকে line search ছাড়া বিশুদ্ধ Newton চালালে কী হয়?
- দ্রুত x = ০-তে পৌঁছায়, যা একটি local maximum
- x = ১-এর সর্বনিম্নে পৌঁছায়
- অসীমে ছিটকে যায়
- −০.৩ আর ০.৩-এর মধ্যে চিরকাল দোলে
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Newton: Taylor-এর quadratic model-এর সর্বনিম্নে লাফ, Hd = −g (inverse নয়, solve দিয়ে সমাধান)। সর্বনিম্নের কাছে quadratic convergence, affine invariant.
- ব্যর্থতা: Hessian positive definite না হলে চূড়া বা saddle; দূর থেকে ছিটকে যাওয়া; d² মেমরি আর d³ খরচ।
- Line search: Armijo f(x + td) ≤ f(x) + c·t·gᵀd, t = ১ থেকে অর্ধেক করে; Wolfe-এর curvature শর্ত yᵀs > ০ নিশ্চিত করে। বিকল্প trust region.
- BFGS / L-BFGS: secant শর্ত My = s, rank-২ হালনাগাদ, superlinear গতি; L-BFGS শেষ m জোড়া রাখে, two-loop দিয়ে M·g, মেমরি 2md.
- বাস্তবে: মসৃণ বড় সমস্যায় L-BFGS (scikit-learn-এর default lbfgs), ছোট n ≫ d-তে newton-cholesky বা IRLS, Hessian-vector product সস্তা হলে Newton-CG; সবসময় success, gradient-এর মাপ আর ConvergenceWarning দেখুন।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ১৬ · Constraint-সহ optimization — Lagrange multiplier আর KKTপরবর্তী পর্বLagrange multiplier, KKT, duality আর বাজেট বণ্টনের shadow price.
- মেশিন লার্নিং · কোর্স হোম৭৭টি পর্ব১৪টি মডিউলের পুরো রোডম্যাপ; পর্ব ১৬ (Constraint-সহ optimization) এখান থেকে খুলবেন।
- পর্ব ১১ · Matrix calculus — gradient, Jacobian আর Hessianআগের ভিত্তিHessian, Taylor approximation আর logistic regression-এ Newton-এর প্রথম ধাপগুলো।
- পর্ব ১০ · Positive definite matrixআগের ভিত্তিHessian-এর চিহ্ন, Cholesky আর condition number.
- AI-এর ভিত্তি · পর্ব ৩৪ · Derivative আর gradient descentআগের ভিত্তিGradient descent, learning rate আর gradient checking-এর শুরু।
- AI-এর ভিত্তি · পর্ব ৪৪ · Local searchআগের ভিত্তিscipy.optimize.minimize-এর প্রথম দেখা, আর local বনাম global optimiser.
- Data Structures & Algorithmsপাশের ট্র্যাকComplexity-র হিসাব, যা d², d³ আর 2md-এর খরচ বুঝতে কাজে লাগে।