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

Constraint-সহ optimization — Lagrange multiplier আর KKT

Constrained Optimization — Lagrange Multipliers, KKT Conditions, Duality, Projected Gradient Descent and Shadow Prices

Taught in Bangla, this lesson explains equality constraints with Lagrange multipliers and inequality constraints with the four KKT conditions, solving examples by hand and verifying a solver's answer numerically. It covers weak and strong duality, projected gradient descent with box and simplex projections, and a budget allocation solved with SLSQP and linprog, including shadow prices and where constraints appear in ML.

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

  • সমতার শর্তে Lagrange multiplier: contour আর শর্তের রেখা ছুঁয়ে যাওয়ার জ্যামিতি, হাতে সমাধান, আর λ-এর মানে
  • অসমতার শর্তে KKT-এর চারটি শর্ত, active আর inactive শর্ত, আর ছোট সমস্যায় পুরো হিসাব
  • Lagrangian duality সহজ ভাষায়: weak আর strong duality, Slater-এর শর্ত, আর কেন এটি SVM-এর প্রস্তুতি
  • Projected gradient descent: box আর probability simplex-এ projection, আর L-BFGS-B-এর bounds-এর সঙ্গে তুলনা
  • SciPy-র SLSQP আর linprog দিয়ে একটি বাজেট বণ্টন সমস্যা, আর shadow price পড়া ও যাচাই

AI-এর ভিত্তি কোর্সের পর্ব ৩৪-এ gradient descent শিখেছেন, যেখানে x যেকোনো দিকে যেতে পারত; আর পর্ব ৪৪-এ scipy.optimize-এর কয়েকটি global optimizer দেখেছেন। এই কোর্সের পর্ব ১১-এ gradient আর Hessian matrix আকারে এসেছে, আর পর্ব ০৮-এ PCA-র ভেতরে একবার Lagrange multiplier উঁকি দিয়েছিল। এই পর্বের প্রশ্ন: x যদি স্বাধীন না হয়, যদি বাজেট, সীমা বা “যোগফল ১” জাতীয় শর্ত মানতেই হয়, তাহলে সর্বনিম্ন কোথায় আর কীভাবে চিনবেন? চালানো যায় এমন চারটি ব্লক পাতাতেই চলে (ব্রাউজারের Python, NumPy, SciPy আর matplotlib); প্রথমবার প্যাকেজ নামতে কয়েক সেকেন্ড লাগে।

১ · Constraint কেন — শর্ত মেনে সেরা সমাধান

মাসের শুরুতে বাজারের থলে হাতে আপনি চান সবচেয়ে ভালো খাওয়াদাওয়া, কিন্তু পকেটে আছে মাত্র ৩,০০০ টাকা, আর মাছ বিক্রেতা একজনকে ২ কেজির বেশি ইলিশ দেবেন না। শর্ত না থাকলে উত্তর সহজ: সব কিনে ফেলুন। শর্ত থাকলে প্রশ্নটি বদলে যায়: শেষ ১০০ টাকা কোথায় খরচ করলে সবচেয়ে বেশি লাভ? আর যদি কেউ বলে “আরও ১০০ টাকা দিলাম”, খুশি কতটা বাড়বে? এই দুটি প্রশ্নের গণিতই এই পর্ব; দ্বিতীয় প্রশ্নের উত্তরের নাম Lagrange multiplier.

Optimization-এর সাধারণ রূপ আগের পর্বগুলোতে ছিল “f(x)-কে যত ছোট সম্ভব করুন”, যেখানে f হলো objective function (ML-এ সাধারণত loss). এখন তার সঙ্গে যোগ হলো constraint, অর্থাৎ শর্ত। প্রচলিত লেখার ধরন (standard form):

সর্বনিম্ন করুন f(x), যেখানে gi(x) ≤ 0 (i = 1…m) আর hj(x) = 0 (j = 1…p)

এখানে x ∈ ℝn চলকের vector; প্রতিটি gi একটি inequality constraint (“≤” শর্ত), আর প্রতিটি hj একটি equality constraint. “x ≤ ৫”-কে লেখা হয় g(x) = x − 5 ≤ 0, আর “x ≥ ০”-কে g(x) = −x ≤ 0; তাই সব অসমতা এক দিকে আনা যায়। যে সব x সব শর্ত মানে, তাদের মিলিত সেটই feasible set; সমাধান খোঁজা হয় শুধু তার ভেতরে। “সর্বোচ্চ করুন f” মানে “সর্বনিম্ন করুন −f”, তাই একটি রূপই যথেষ্ট।

ML-এ এ ধরনের শর্ত যতটা মনে হয়, তার চেয়ে অনেক বেশি জায়গায় আছে:

কোথায়শর্তধরন
PCA-র প্রথম দিক (পর্ব ০৮)‖u‖ = 1সমতা
Mixture-এর ওজন, class-এর সম্ভাবনাসব wi ≥ 0, ∑wi = 1 (probability simplex)সমতা + অসমতা
SVM (পর্ব ২৭–২৮)প্রতিটি উদাহরণে yi(wᵀxi + b) ≥ 1অসমতা, n-টি
Lasso-র constrained রূপ (পর্ব ১৭)∑|wi| ≤ tঅসমতা
Non-negative model, NMFসব ওজন ≥ 0box constraint
ব্যবসার সিদ্ধান্তমোট বাজেট, প্রতি চ্যানেলের সীমা, গুদামের ধারণক্ষমতাপ্রায়ই রৈখিক অসমতা

সমস্যার পরিবারগুলোর নামও জেনে রাখুন, কারণ টুল বাছাই এর ওপর নির্ভর করে। f আর সব শর্ত রৈখিক হলে linear programming (LP); f দ্বিঘাত আর শর্ত রৈখিক হলে quadratic programming (QP), যেমন SVM; বাকিগুলো সাধারণ nonlinear programming (NLP). f convex আর feasible set-ও convex হলে সমস্যাটি convex; তখন যেকোনো local সমাধানই global. Convexity-র পুরো আলোচনা পর্ব ১২ (Convexity — কেন কিছু loss সহজ আর কিছু কঠিন)-এর বিষয়; এখানে শুধু ফলটি ব্যবহার করব।

মূল পর্যবেক্ষণ

শর্ত না থাকলে সর্বনিম্ন বিন্দুতে ∇f = 0. শর্ত থাকলে সর্বনিম্ন প্রায়ই সীমানার ওপর, যেখানে f আরও কমতে চায় কিন্তু দেয়াল আটকে রাখে; সেখানে ∇f ≠ 0. তাই নতুন একটি শর্ত লাগে, যা বলে “f যে দিকে কমতে চায়, সে দিকটি ঠিক দেয়ালের বাইরে”। এই পর্বের বাকি সব কিছু এই একটি বাক্যের গাণিতিক রূপ।

২ · সমতার শর্ত আর Lagrange multiplier — রেখাকে ছুঁয়ে যাওয়া contour

সবচেয়ে ছোট উদাহরণ দিয়ে শুরু করা যাক: f(x, y) = x² + 2y²-কে সর্বনিম্ন করুন, শর্ত x + y = 3. শর্তটিকে লিখি h(x, y) = x + y − 3 = 0. শর্ত না থাকলে উত্তর (০, ০), কিন্তু সেটি রেখার ওপর নেই। f-এর contour, অর্থাৎ সমান মানের বক্ররেখাগুলো, এখানে মূলবিন্দু-কেন্দ্রিক উপবৃত্ত। রেখা ধরে হাঁটলে f-এর মান কখনো বাড়ে, কখনো কমে। সর্বনিম্ন সেখানে, যেখানে রেখাটি কোনো একটি উপবৃত্তকে কেটে না গিয়ে শুধু ছুঁয়ে যায়। কেটে গেলে রেখার এক দিকে f আরও ছোট, তাই সেটি সর্বনিম্ন হতে পারে না।

সর্বনিম্ন বিন্দুতে contour রেখাকে ছুঁয়ে যায়, আর ∇f ∥ ∇h

Level curves of x² + 2y² and the constraint line x + y = 3

Tangency of a level curve and the constraint line at the constrained minimum f = 2 f = 6 f = 9 x + y = 3 ∇f = (4, 4) ∇h (2, 1) ∇f = (6, 0) (3, 0)
ছবি ২.১ — f = ৬-এর উপবৃত্ত (মোটা রেখা) রেখাটিকে (২, ১)-এ শুধু ছুঁয়ে যায়; সেখানে ∇f আর ∇h একই দিকে, দুটিই রেখার সঙ্গে লম্ব। (৩, ০)-এ রেখাটি f = ৯-এর উপবৃত্তকে কেটে যায়; ∇f-এর একটি অংশ রেখা বরাবর, তাই রেখা ধরে হাঁটলে f আরও কমানো যায়।

কেন ছোঁয়ার বিন্দুতে দুটি gradient সমান্তরাল? পর্ব ১১-এ দেখেছেন, gradient সবসময় নিজের contour-এর সঙ্গে লম্ব। ∇h লম্ব শর্তের রেখার সঙ্গে, আর ∇f লম্ব f-এর contour-এর সঙ্গে। ছোঁয়ার বিন্দুতে দুটি বক্ররেখার স্পর্শক একই, তাই দুটি লম্বও একই রেখায়। ভিন্নভাবে বললে: রেখা বরাবর একক দিক d-তে f-এর বদলের হার ∇fᵀd. সর্বনিম্নে এটি শূন্য হতেই হবে, নইলে কোনো এক দিকে হাঁটলে f কমত। অর্থাৎ ∇f-এর রেখা বরাবর কোনো অংশ নেই, পুরোটা ∇h-এর দিকে:

∇f(x*) = λ ∇h(x*),   h(x*) = 0

এই সংখ্যা λ-ই Lagrange multiplier. সমীকরণ দুটি একসঙ্গে লেখার একটি কৌশল আছে: Lagrangian নামের নতুন function L(x, λ) = f(x) − λh(x) বানান, আর তার সব চলকে (x আর λ দুটিতেই) ঢাল শূন্য বসান। ∂L/∂x = 0 দেয় ∇f = λ∇h, আর ∂L/∂λ = 0 দেয় h(x) = 0. অর্থাৎ শর্তসহ একটি সমস্যা হয়ে গেল শর্তহীন একটি stationary point খোঁজার সমস্যা, একটি বাড়তি চলক নিয়ে। (সমতার শর্তে λ-এর চিহ্ন যা খুশি হতে পারে, তাই অনেক বই f + λh লেখে; উত্তরে শুধু λ-এর চিহ্ন উল্টায়।)

নিচের stepper রেখা বরাবর চারটি বিন্দুতে দুটি gradient পাশাপাশি রেখে দেখায় কোথায় তারা সমান্তরাল হয়। “রেখা বরাবর অংশ” মানে ∇f-এর সঙ্গে রেখার একক দিক d = (1, −1)/√2-এর dot product; সেটি শূন্য হলেই সর্বনিম্ন।

f(x, y) = x² + 2y², h(x, y) = x + y − 3; ∇f = (2x, 4y), ∇h = (1, 1); রেখার দিক d = (1, −1)/√2
বিন্দু
f
∇f
রেখা বরাবর অংশ ∇fᵀd
রায়
  1. (৩, ০) থেকে শুরু। f = ৯. ∇f = (৬, ০) আনুভূমিক, আর ∇h = (১, ১) তির্যক: দুটি সমান্তরাল নয়। রেখা বরাবর অংশ ৪.২৪ ধনাত্মক, তাই উল্টো দিকে (x কমিয়ে, y বাড়িয়ে) হাঁটলে f কমবে।
  2. (২.৫, ০.৫). f কমে ৬.৭৫. রেখা বরাবর অংশ ২.১২: এখনও ধনাত্মক, কিন্তু আগের চেয়ে ছোট। আমরা উপত্যকার তলার দিকে যাচ্ছি।
  3. (২, ১): ছোঁয়ার বিন্দু। f = ৬, ∇f = (৪, ৪) = ৪ × (১, ১) = ৪∇h. রেখা বরাবর অংশ শূন্য, তাই এখান থেকে কোনো দিকে হাঁটলে f কমে না। হাতে সমাধান: 2x = λ, 4y = λ থেকে x = 2y; শর্তে বসালে 3y = 3, তাই y = ১, x = ২, λ = ৪.
  4. (১, ২): পেরিয়ে গেলে। f আবার ৯, আর রেখা বরাবর অংশ −৪.২৪, চিহ্ন উল্টে গেছে। চিহ্ন ধনাত্মক থেকে ঋণাত্মকে যাওয়ার মাঝেই শূন্যটি ছিল, ঠিক (২, ১)-এ।
  5. λ-এর মানে। শর্তকে x + y = c লিখলে সমাধান x = 2c/3, y = c/3 আর সর্বনিম্ন মান f*(c) = 2c²/3. এর derivative 4c/3, যা c = ৩-এ ঠিক ৪ = λ. অর্থাৎ বাজেট ০.১ বাড়ালে সর্বনিম্ন খরচ প্রায় ০.৪ বাড়ে; আসল বদল ৬.৪০৬৭ − ৬ = ০.৪০৬৭.
λ = শর্তের দাম (sensitivity)

সর্বনিম্ন বিন্দুতে λ বলে শর্তের ডান দিকের সংখ্যা c একটু বদলালে সর্বোত্তম মান কত হারে বদলায়: df*/dc = λ. অর্থনীতিতে একেই বলে shadow price: এক একক বাড়তি সম্পদের প্রান্তিক মূল্য। মনে রাখবেন, এটি একটি derivative, তাই শুধু ছোট বদলে ভরসাযোগ্য; বড় বদলে আবার সমাধান করুন।

একাধিক সমতা থাকলে প্রতিটির জন্য আলাদা λj, আর শর্ত হয় ∇f = ∑λj∇hj. একটি সূক্ষ্ম কথা: এই নিয়ম কাজ করে যদি সমাধানে শর্তগুলোর gradient-গুলো রৈখিকভাবে স্বাধীন হয় (এর নাম LICQ, একটি constraint qualification). এটি না মানলে সর্বনিম্ন বিন্দুতেও কোনো λ না-ও থাকতে পারে; ভাবনার প্রশ্ন ১-এ একটি উদাহরণ আছে।

৩ · বৃত্তের ওপর quadratic — হাতে Lagrange, তারপর SciPy দিয়ে যাচাই

এবার একটি পরিচিত প্রশ্ন: প্রতিসম A = [[৩, ১], [১, ৩]]-এর জন্য f(x) = xᵀAx-কে সর্বোচ্চ করুন, শর্ত ‖x‖² = xᵀx = 1, অর্থাৎ একক বৃত্তের ওপর। পর্ব ১০-এ এর contour দেখেছেন: A positive definite, তাই contour তির্যক উপবৃত্ত। শর্ত h(x) = xᵀx − 1. পর্ব ১১-এর সূত্রে ∇f = 2Ax (A প্রতিসম বলে) আর ∇h = 2x. Lagrange-এর শর্ত:

2Ax = λ · 2x  ⟹  Ax = λx

অর্থাৎ শর্তসহ stationary point মানেই A-এর eigenvector, আর λ তার eigenvalue. সেখানে f = xᵀAx = λxᵀx = λ, তাই সর্বোচ্চ মান সবচেয়ে বড় eigenvalue. A-এর eigenvalue ৪ (দিক (১, ১)/√২) আর ২ (দিক (১, −১)/√২)। এটিই পর্ব ০৮-এর Rayleigh quotient-এর ফল, আর PCA-র প্রথম principal component-এর যুক্তি: variance uᵀΣu সর্বোচ্চ করুন, শর্ত ‖u‖ = 1. নিচের ব্লক একই প্রশ্ন SciPy-র SLSQP দিয়ে সংখ্যায় সমাধান করে, তারপর তিনটি জিনিস মেলায়: eigh-এর উত্তর, দুটি gradient সমান্তরাল কি না, আর λ. শেষে contour-এর ছবি আঁকে।

SLSQP (Sequential Least SQuares Programming) প্রতিটি ধাপে f-এর একটি দ্বিঘাত আর শর্তের একটি রৈখিক আন্দাজ বানিয়ে ছোট একটি QP সমাধান করে; Dieter Kraft-এর ১৯৮৮ সালের Fortran কোড থেকে এসেছে বলে SciPy-র docs-এ উল্লেখ আছে। SciPy-তে minimize সবসময় ছোট করে, তাই সর্বোচ্চের জন্য −f দেওয়া হয়েছে; আর "eq" ধরনের শর্তের function শূন্য হতে হয়।

import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize

A = np.array([[3.0, 1.0], [1.0, 3.0]])
f = lambda v: v @ A @ v                         # এটিকে সর্বোচ্চ করতে চাই
circle = {"type": "eq", "fun": lambda v: v @ v - 1}   # h(x) = xᵀx − 1 = 0

res = minimize(lambda v: -f(v), x0=[1.0, 0.2], method="SLSQP",
               constraints=[circle], options={"ftol": 1e-12})
x = res.x
print("SLSQP x* =", x.round(4), " f(x*) =", round(f(x), 4), " nit =", res.nit)
vals, vecs = np.linalg.eigh(A)
print("eigh: values", vals.round(4), " top vector", vecs[:, -1].round(4))

g_f, g_h = 2 * A @ x, 2 * x                      # ∇f আর ∇h
lam = (g_f @ g_h) / (g_h @ g_h)                  # ∇f = λ∇h থেকে λ
cross = g_f[0] * g_h[1] - g_f[1] * g_h[0]        # সমান্তরাল হলে 0
print("grad f =", g_f.round(4), " grad h =", g_h.round(4))
print(f"lambda = {lam:.4f}   cross(grad f, grad h) = {cross:.1e}")
print("SLSQP multiplier for -f:", res.multipliers.round(4))

t = np.linspace(-1.6, 1.6, 201)
X, Y = np.meshgrid(t, t)
F = 3 * X**2 + 2 * X * Y + 3 * Y**2
fig, ax = plt.subplots(figsize=(5, 5))
cs = ax.contour(X, Y, F, levels=[1, 2, 4, 6, 9], cmap="viridis")
ax.clabel(cs, fontsize=8)
th = np.linspace(0, 2 * np.pi, 300)
ax.plot(np.cos(th), np.sin(th), "k--", label="constraint: x^2 + y^2 = 1")
ax.plot(*x, "ro", label=f"max on circle, f = {f(x):.2f}")
ax.quiver(*x, *(g_f / 8), color="red", angles="xy", scale_units="xy", scale=1)
ax.set(xlabel="x", ylabel="y", aspect="equal",
       title="Level curve f = 4 touches the circle")
ax.legend(loc="lower left", fontsize=8)

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

SLSQP x* = [0.7071 0.7071]  f(x*) = 4.0  nit = 9
eigh: values [2. 4.]  top vector [0.7071 0.7071]
grad f = [5.6569 5.6569]  grad h = [1.4142 1.4142]
lambda = 4.0000   cross(grad f, grad h) = -5.7e-08
SLSQP multiplier for -f: [-4.]

SLSQP ৯টি ধাপে x* = (০.৭০৭১, ০.৭০৭১)-এ পৌঁছেছে, অর্থাৎ (১, ১)/√২, আর f(x*) = ৪. eigh একই কথা বলে: বড় eigenvalue ৪, তার eigenvector একই দিক। দুটি gradient (৫.৬৫৬৯, ৫.৬৫৬৯) আর (১.৪১৪২, ১.৪১৪২) একই দিকে; তাদের 2-D cross product −৫.৭ × ১০−৮, শূন্যের খুব কাছে, যা solver-এর tolerance-এর মাপে স্বাভাবিক। দৈর্ঘ্যের অনুপাত λ = ৪.০০০০, ঠিক eigenvalue. SciPy-র নিজের multiplier −৪, কারণ আমরা −f ছোট করেছি, তাই চিহ্ন উল্টো। ছবিতে f = ৪-এর উপবৃত্ত বৃত্তটিকে লাল বিন্দুতে ছুঁয়ে আছে, আর লাল তীর (∇f) বৃত্তের ব্যাসার্ধ বরাবর বাইরে, অর্থাৎ ∇h-এর দিকে। শুরুর বিন্দু (১, −১)-এর কাছে দিলে solver সমান্তরাল হওয়ার আরেকটি বিন্দু, অর্থাৎ ছোট eigenvalue-র দিক, পেতে পারত; Lagrange-এর শর্ত শুধু stationary point খোঁজে, সর্বোচ্চ না সর্বনিম্ন তা বলে না।

৪ · অসমতার শর্ত আর KKT-এর চারটি শর্ত

বাস্তবের বেশিরভাগ শর্ত সমতা নয়, অসমতা: “বাজেটের বেশি নয়”, “ওজন ঋণাত্মক নয়”। অসমতার শর্তের একটি বিশেষ আচরণ আছে: সমাধানে সে হয় দেয়ালে লেগে আছে (gi(x*) = 0), নয়তো দূরে, ঢিলা (gi(x*) < 0). প্রথম ক্ষেত্রে শর্তটিকে বলে active, দ্বিতীয় ক্ষেত্রে inactive. Inactive শর্ত সমাধানে কোনো ভূমিকা রাখে না: তাকে মুছে দিলেও উত্তর একই থাকে। Active শর্ত সমতার মতো আচরণ করে, তবে একটি পার্থক্যসহ: দেয়াল শুধু এক দিকে ঠেলতে পারে।

ঢাকার একটি গলিতে বল গড়িয়ে নামছে, ঢাল যেদিকে। গলির পাশে দেয়াল। বল যদি দেয়াল থেকে দূরে থেমে যায়, দেয়ালের কোনো ভূমিকা নেই: ঠেলা শূন্য। যদি দেয়ালে আটকে থামে, দেয়াল তাকে বাইরের দিকে ঠেলছে, আর ঠেলাটি ঠিক ততটুকু যতটুকু ঢাল টানছে। দেয়াল কখনো বলকে নিজের দিকে টানতে পারে না, শুধু ঠেলতে পারে। এই তিনটি কথাই KKT: ঠেলা আর টানের ভারসাম্য, ঠেলা কখনো ঋণাত্মক নয়, আর দেয়াল দূরে থাকলে ঠেলা শূন্য।

সাধারণ সমস্যার জন্য Lagrangian L(x, μ, λ) = f(x) + ∑μigi(x) + ∑λjhj(x). এখানে μi অসমতার multiplier, λj সমতার। x* একটি local সর্বনিম্ন হলে (আর একটি constraint qualification মানলে) এমন μ আর λ থাকে, যাতে নিচের চারটি শর্ত একসঙ্গে মেলে। এদের বলে KKT conditions, Karush, Kuhn আর Tucker-এর নামে:

শর্তসূত্রসহজ ভাষায়
১. Stationarity∇f(x*) + ∑μi∇gi(x*) + ∑λj∇hj(x*) = 0ঢালের টান আর দেয়ালগুলোর ঠেলা মিলে শূন্য
২. Primal feasibilitygi(x*) ≤ 0, hj(x*) = 0বিন্দুটি সব শর্ত মানে
৩. Dual feasibilityμi ≥ 0দেয়াল শুধু ঠেলে, টানে না
৪. Complementary slacknessμi · gi(x*) = 0, প্রতিটি i-তেদেয়াল দূরে (gi < 0) হলে ঠেলা শূন্য (μi = 0)

চতুর্থ শর্তটি সবচেয়ে কাজের: প্রতিটি অসমতার জন্য দুটির একটি শূন্য, হয় μi, নয় gi. তাই ছোট সমস্যা হাতে সমাধানের কৌশল হলো active set অনুমান করা: কোন শর্তগুলো দেয়ালে আছে ধরে নিন, সেগুলোকে সমতা বানিয়ে Lagrange-এর মতো সমাধান করুন, তারপর বাকি তিনটি শর্ত (feasibility আর μ ≥ ০) যাচাই করুন। না মিললে অন্য অনুমান। একটি ঋণাত্মক μ বলে “এই দেয়াল আসলে বিন্দুটিকে টেনে রাখছে”, অর্থাৎ শর্তটি ছেড়ে দিলে f আরও কমত না; সেটিকে active তালিকা থেকে বাদ দিন। নিচের stepper একটি দুই-চলকের সমস্যা পুরো হিসাব করে।

g1 = x + y − 3, g2 = x − 1.5, g3 = −yসব g ≤ 0

            
  1. শর্তহীন সর্বনিম্ন আগে। f-এর তলা (৩, ২)-এ। সেখানে g1 আর g2 দুটিই ধনাত্মক, অর্থাৎ শর্ত ভাঙে। তাই সমাধান সীমানায় কোথাও।
  2. অনুমান A. শুধু x + y ≤ ৩ active ধরলে সমাধান হয় (৩, ২)-এর সবচেয়ে কাছের রেখার বিন্দু, (২, ১)। কিন্তু সেখানে x = ২ > ১.৫, দ্বিতীয় শর্ত ভাঙে। Primal feasibility ব্যর্থ, অনুমান বাতিল। (শুধু g2 active ধরলেও (১.৫, ২) আসে, যেখানে g1 = ০.৫ > ০, সেটিও বাতিল।)
  3. অনুমান B. দুটিই active: x = ১.৫ আর x + y = ৩ থেকে (১.৫, ১.৫)। তিনটি শর্তই মানে; g3 = −১.৫, তাই y ≥ ০ দেয়াল থেকে দূরে, inactive.
  4. Stationarity থেকে μ. Inactive বলে μ3 = ০. ∇f + μ1∇g1 + μ2∇g2 = 0-এর y-অংশে শুধু μ1 আছে, তাই μ1 = ১; x-অংশ থেকে μ2 = ৩ − ১ = ২.
  5. বাকি দুটি শর্ত। সব μ ≥ ০, তাই দেয়ালগুলো সত্যিই ঠেলছে। প্রতিটি জোড়ায় একটি শূন্য: active শর্তে g = ০, inactive শর্তে μ = ০.
  6. উপসংহার আর μ-এর মানে। f convex আর শর্তগুলো রৈখিক, তাই KKT মিললেই global সর্বনিম্ন: (১.৫, ১.৫), f* = ২.৫. μ2 = ২ বলে সীমা ০.১ ঢিলা করলে f* প্রায় ০.২ কমবে; আসলে কমে ০.১৮, কারণ μ একটি derivative.

লক্ষ করুন, μ2 = ২ > μ1 = ১: এই বিন্দুতে x ≤ ১.৫ সীমাটি বেশি “দামি”, ঢিলা করলে বেশি লাভ। এই তুলনাই অংশ ৮-এর shadow price-এর মূল কথা। আর KKT-এর একটি সীমাবদ্ধতা মনে রাখুন: সাধারণ (non-convex) সমস্যায় KKT শুধু প্রয়োজনীয় শর্ত; একটি KKT বিন্দু local সর্বনিম্ন, local সর্বোচ্চ বা saddle-ও হতে পারে। Convex সমস্যায় তা যথেষ্টও, তাই সেখানে KKT মিলল মানেই global সমাধান।

৫ · KKT সংখ্যায় যাচাই — একটি solver-এর উত্তর বিশ্বাস না করে পরীক্ষা

বড় সমস্যা হাতে নয়, solver দিয়ে সমাধান হয়। কিন্তু solver-এর “success: True” মানে শুধু তার নিজের থামার নিয়ম মিলেছে। উত্তরটি সত্যিই সমাধান কি না, তা KKT দিয়ে আলাদা করে যাচাই করা যায়, আর এতে active শর্তগুলোর তালিকাও পাওয়া যায়। নিচের ব্লকে তিন চলকের একটি quadratic বাটি f(x) = ½xᵀQx − cᵀx (Q positive definite, তাই convex), সঙ্গে পাঁচটি রৈখিক অসমতা: মোট x1 + x2 + x3 ≤ ১.৫, x1 ≤ ০.২, আর তিনটি x ≥ ০. সব শর্ত একটি matrix আকারে Gx − h ≤ 0, তাই ∇gi হলো G-এর সারি i, আর stationarity হয় ∇f + Gᵀμ = 0.

SciPy 1.16 থেকে SLSQP ফলের সঙ্গে multipliers ফেরত দেয়। ব্লকটি সেগুলো পড়ে চারটি শর্ত যাচাই করে, তারপর শুধু active সারিগুলো নিয়ে least squares-এ μ আবার নিজে বের করে, যাতে solver-এর ওপর নির্ভর না করেও μ মেলানো যায়। SciPy-তে "ineq" মানে function ≥ 0, তাই h − Gx দেওয়া হয়েছে।

import numpy as np
from scipy.optimize import minimize

np.set_printoptions(precision=4, suppress=True)
Q = np.array([[4.0, 1.0, 0.0], [1.0, 3.0, 1.0], [0.0, 1.0, 2.0]])
c = np.array([3.0, 4.0, 3.0])
f = lambda x: 0.5 * x @ Q @ x - c @ x
grad = lambda x: Q @ x - c
print("unconstrained min:", np.linalg.solve(Q, c))

# প্রতিটি constraint g_i(x) = G[i] @ x - h[i] <= 0
G = np.array([[1, 1, 1], [1, 0, 0], [-1, 0, 0], [0, -1, 0], [0, 0, -1.0]])
h = np.array([1.5, 0.2, 0.0, 0.0, 0.0])
names = ["x1+x2+x3 <= 1.5", "x1 <= 0.2", "x1 >= 0", "x2 >= 0", "x3 >= 0"]
cons = [{"type": "ineq", "fun": lambda x: h - G @ x, "jac": lambda x: -G}]
res = minimize(f, np.zeros(3), jac=grad, method="SLSQP",
               constraints=cons, options={"ftol": 1e-12})
x, mu = res.x, res.multipliers
g = G @ x - h
print("x* =", x, " f* =", round(res.fun, 4))
print(f"{'constraint':<17}{'g_i(x*)':>9}{'mu_i':>8}{'mu_i*g_i':>10}  active")
for nm, gi, mi in zip(names, g, mu):
    print(f"{nm:<17}{gi:9.1e}{mi:8.4f}{mi * gi:10.1e}  {abs(gi) < 1e-8}")

print("stationarity |grad f + G.T mu| =", f"{np.linalg.norm(grad(x) + G.T @ mu):.1e}")
print("primal feasible:", bool(np.all(g <= 1e-9)), " dual feasible:", bool(np.all(mu >= -1e-9)))
act = np.abs(g) < 1e-8                 # নিজে আলাদা করে: active সারিতে lstsq
mu_ls = np.linalg.lstsq(G[act].T, -grad(x), rcond=None)[0]
print("mu from lstsq on active rows:", mu_ls)

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

unconstrained min: [0.5556 0.7778 1.1111]
x* = [0.2 0.7 0.6]  f* = -3.465
constraint         g_i(x*)    mu_i  mu_i*g_i  active
x1+x2+x3 <= 1.5   -1.1e-15  1.1000  -1.2e-15  True
x1 <= 0.2         -1.1e-16  0.4000  -4.4e-17  True
x1 >= 0           -2.0e-01  0.0000  -0.0e+00  False
x2 >= 0           -7.0e-01  0.0000  -0.0e+00  False
x3 >= 0           -6.0e-01  0.0000  -0.0e+00  False
stationarity |grad f + G.T mu| = 1.7e-15
primal feasible: True  dual feasible: True
mu from lstsq on active rows: [1.1 0.4]

শর্তহীন সর্বনিম্ন (০.৫৫৬, ০.৭৭৮, ১.১১১)-এর যোগফল প্রায় ২.৪৪ আর x1 = ০.৫৫৬ > ০.২, তাই দুটি শর্ত ভাঙে। সমাধান x* = (০.২, ০.৭, ০.৬): যোগফল ঠিক ১.৫ আর x1 ঠিক ০.২, তাই প্রথম দুটি শর্ত active (g ≈ ১০−১৫, অর্থাৎ float-এর মাপে শূন্য), বাকি তিনটি দেয়াল থেকে দূরে। চারটি KKT শর্ত: stationarity-র residual ১.৭ × ১০−১৫; সব g ≤ ০; সব μ ≥ ০; আর প্রতিটি সারিতে μigi কার্যত শূন্য, কারণ active সারিতে g = ০ আর inactive সারিতে μ = ০. Active সারি দুটি থেকে নিজে least squares-এ বের করা μ = (১.১, ০.৪) solver-এর সংখ্যার সঙ্গে হুবহু মেলে। হাতেও মেলানো যায়: x1 = ০.২ আর x2 + x3 = ১.৩ ধরে দ্বিতীয় আর তৃতীয় সারির stationarity থেকে x2 = ০.৭, x3 = ০.৬, μ1 = ৩ − ০.৭ − ১.২ = ১.১, তারপর প্রথম সারি থেকে μ2 = ০.৪. মানে: মোট সীমা ১.৫ থেকে ০.১ বাড়ালে f* প্রায় ০.১১ কমবে, আর x1-এর সীমা ০.১ বাড়ালে প্রায় ০.০৪ কমবে। “−0.0e+00” শুধু শূন্যের চিহ্নসহ ছাপা, ঋণাত্মক কোনো মান নয়।

৬ · Lagrangian duality — নিচ থেকে সীমা, weak আর strong

Lagrangian-কে আরেকভাবেও দেখা যায়। ধরুন শর্ত মানা বাধ্যতামূলক নয়, কিন্তু ভাঙলে জরিমানা দিতে হয়, প্রতি একক ভাঙায় λ টাকা। প্রতিটি নির্দিষ্ট জরিমানা-হারের জন্য আপনি শর্তহীনভাবে L(x, λ) সর্বনিম্ন করেন। ফলাফল একটি সংখ্যা, যা λ-এর ওপর নির্ভর করে; একে বলে dual function:

q(λ, μ) = minx L(x, λ, μ),   μ ≥ 0

অংশ ২-এর উদাহরণে L = x² + 2y² − λ(x + y − 3). x আর y-তে ঢাল শূন্য বসালে x = λ/2, y = λ/4, আর বসিয়ে q(λ) = 3λ − 3λ²/8. কয়েকটি মান:

λ০২৪৬৮
q(λ) = 3λ − 3λ²/8০৪.৫৬৪.৫০
primal সর্বনিম্ন f*৬৬৬৬৬

দুটি ঘটনা চোখে পড়ে। প্রথমত, প্রতিটি q(λ) ≤ f* = ৬. এটি কাকতালীয় নয়: যেকোনো feasible x-এ শর্তের পদ শূন্য (বা অসমতায় μg ≤ ০), তাই L(x, λ, μ) ≤ f(x); সব x-এর ওপর সর্বনিম্ন নিলে q আরও ছোট। একেই বলে weak duality, আর এটি সব সমস্যায় খাটে, convex হোক বা না হোক। দ্বিতীয়ত, q-এর সর্বোচ্চ মান λ = ৪-এ, ঠিক ৬, অর্থাৎ ফাঁক শূন্য। একে বলে strong duality, আর ফাঁকটির নাম duality gap. q-কে সর্বোচ্চ করার সমস্যাটিই dual problem, আর মূল সমস্যাটি primal.

কখন ফাঁক শূন্য

Convex সমস্যায় (f আর gi convex, hj রৈখিক) যদি এমন অন্তত একটি বিন্দু থাকে যেখানে সব অসমতা কড়াভাবে মানে (gi(x) < 0), তাহলে strong duality খাটে। এটি Slater-এর শর্ত। তখন primal আর dual-এর সমাধান মিলে Lagrangian-এর একটি saddle point তৈরি করে: x-এর দিকে সর্বনিম্ন, λ আর μ-এর দিকে সর্বোচ্চ। Non-convex সমস্যায় সাধারণত ফাঁক থাকে, তবু dual একটি নিশ্চিত নিম্নসীমা দেয়, যা branch-and-bound-এর মতো পদ্ধতিতে কাজে লাগে।

Dual কেন দরকার? তিনটি কারণ। (১) Dual-এর চলক শর্তের সংখ্যার সমান, primal-এর চলক feature-এর সংখ্যার সমান। SVM-এ dual-এর চলক প্রতিটি training উদাহরণের জন্য একটি αi, আর উদাহরণগুলো সেখানে শুধু dot product xiᵀxj হিসেবে আসে। ওই dot product-কে kernel দিয়ে বদলানোই kernel trick. (২) Complementary slackness বলে যে উদাহরণ margin থেকে দূরে, তার αi = ০; শুধু সীমানার উদাহরণগুলো, অর্থাৎ support vector, সমাধান ঠিক করে। (৩) Dual-এর মান সবসময় একটি নিম্নসীমা, তাই primal আর dual-এর মানের পার্থক্য দিয়ে একটি solver জানতে পারে সে সমাধান থেকে কত দূরে; অনেক solver এই ফাঁক দিয়েই থামার নিয়ম বানায়। এই তিনটির পুরো ব্যবহার পর্ব ২৭ (Support Vector Machine — margin, support vector আর hard margin) আর পর্ব ২৮ (Soft margin — hinge loss, C আর dual problem)-এ আসবে।

ইতিহাস: Joseph-Louis Lagrange সমতার শর্তে multiplier-এর পদ্ধতি অষ্টাদশ শতকের শেষভাগে ব্যবহার করেন; ১৭৮৮ সালের Mécanique analytique-এ বলবিদ্যার প্রসঙ্গে এর পরিচিত রূপটি আছে, যদিও আগের লেখাতেও ধারণাটির শুরু খুঁজে পাওয়া যায়। অসমতার শর্তের সংস্করণ Harold Kuhn আর Albert Tucker ১৯৫১ সালে একটি Berkeley symposium-এ প্রকাশ করেন। পরে দেখা যায়, William Karush ১৯৩৯ সালে University of Chicago-র একটি master's thesis-এ প্রায় একই শর্ত লিখেছিলেন, যা প্রকাশিত হয়নি; সেজন্য আজ নামটি KKT. LP-র duality-র ধারণা ১৯৪০-এর দশকের শেষে John von Neumann আর George Dantzig-এর আলোচনা থেকে আসে বলে প্রচলিত বর্ণনা আছে।

৭ · Projected gradient descent আর box constraint — ধাপ নিন, তারপর ফিরে আসুন

Lagrange আর KKT শর্ত লেখার ভাষা; কিন্তু হাজার হাজার চলকের সমস্যায় সমাধানের সবচেয়ে সরল algorithm প্রায়ই এটি: সাধারণ gradient descent-এর একটি ধাপ নিন, আর ধাপের পর বিন্দুটি feasible set-এর বাইরে চলে গেলে সবচেয়ে কাছের বৈধ বিন্দুতে ফিরিয়ে আনুন। এর নাম projected gradient descent:

xk+1 = PC( xk − η ∇f(xk) )

এখানে C হলো feasible set, η ধাপের মাপ (learning rate), আর PC(v) মানে C-এর যে বিন্দু v-এর সবচেয়ে কাছে, অর্থাৎ ‖w − v‖² সর্বনিম্ন করে এমন w ∈ C. C convex হলে এই বিন্দু একটিই। ধারণাটি পুরোনো: ১৯৬০-এর দশকের মাঝামাঝি Goldstein আর Levitin ও Polyak-এর কাজে এর বিশ্লেষণ আছে বলে সাধারণত উল্লেখ করা হয়। কৌশলটির শক্তি নির্ভর করে projection কত সস্তা তার ওপর:

  • Box l ≤ x ≤ u: projection মানে প্রতিটি ঘর আলাদা করে কেটে দেওয়া, np.clip(v, l, u). খরচ n.
  • Ball ‖x‖ ≤ r: v বাইরে থাকলে r·v/‖v‖-এ ছোট করা। Gradient clipping-এর সঙ্গে মিল লক্ষ করুন।
  • Probability simplex {w ≥ 0, ∑w = 1}: সব ঘর থেকে একই সংখ্যা θ বাদ দিয়ে শূন্যে কাটা; θ খুঁজতে sort লাগে, খরচ n log n. নিচের stepper হাতে একবার করে।
  • সাধারণ রৈখিক শর্ত Ax ≤ b: projection নিজেই একটি QP; তখন SLSQP বা interior-point solver ভালো।
লক্ষ্য: w ≥ 0, w1 + w2 + w3 = 1, ‖w − v‖ সর্বনিম্নn = 3

            
  1. সমস্যা। v যোগফলে ১.৪, আর তৃতীয় ঘর ঋণাত্মক, তাই simplex-এর বাইরে। KKT লিখলে দেখা যায় সমাধান wi = max(vi − θ, ০), যেখানে θ হলো “যোগফল ১” শর্তের multiplier; শূন্যে কাটা ঘরগুলো w ≥ ০ শর্তের active দেয়াল।
  2. সাজানো। বড় থেকে ছোট: u = (১.২, ০.৫, −০.৩), ক্রমযোগফল (১.২, ১.৭, ১.৪)।
  3. কতগুলো ঘর বেঁচে থাকবে। j-তম পরীক্ষা জিজ্ঞেস করে “প্রথম j-টি ঘর রাখলে j-তমটি কি ধনাত্মক থাকে?” j = ১ আর ২-এ হ্যাঁ, j = ৩-এ না। তাই ρ = ২: দুটি ঘর থাকবে।
  4. θ আর উত্তর। θ = (১.৭ − ১)/২ = ০.৩৫. সব ঘর থেকে ০.৩৫ বাদ দিয়ে শূন্যে কাটলে w = (০.১৫, ০.৮৫, ০), যোগফল ঠিক ১.
  5. সহজ বিকল্প কেন ভুল। “ঋণাত্মক কেটে যোগফল দিয়ে ভাগ” একটি বৈধ বিন্দু দেয়, (০.২৯৪, ০.৭০৬, ০), কিন্তু v থেকে তার দূরত্ব² ০.৩৭৭, projection-এর ০.৩৩৫-এর চেয়ে বেশি। এটি projection নয়, তাই projected gradient-এর convergence-এর তত্ত্বও এতে খাটে না।

Box শর্তের জন্য SciPy-তে আলাদা একটি পরিচিত টুল আছে: L-BFGS-B, যা পর্ব ১৫ (Second-order পদ্ধতি — Newton, L-BFGS আর line search)-এর L-BFGS-কে bounds মানতে শেখায় (Byrd, Lu, Nocedal আর Zhu, ১৯৯৫)। এটি শুধু box নেয়, সাধারণ শর্ত নয়। নিচের ব্লক অংশ ৫-এর একই বাটিতে দুটি সমস্যা চালায়: box [০, ০.৮]³-এ projected GD বনাম L-BFGS-B, আর simplex-এ projected GD বনাম SLSQP. ধাপের মাপ η = 1/L, যেখানে L হলো Q-এর সবচেয়ে বড় eigenvalue (পর্ব ১৩-এ এর কারণ আসবে)।

import numpy as np
from scipy.optimize import minimize

np.set_printoptions(precision=4, suppress=True)
Q = np.array([[4.0, 1.0, 0.0], [1.0, 3.0, 1.0], [0.0, 1.0, 2.0]])
c = np.array([3.0, 4.0, 3.0])
f = lambda x: 0.5 * x @ Q @ x - c @ x
grad = lambda x: Q @ x - c
eta = 1 / np.linalg.eigvalsh(Q).max()            # ধাপ 1/L

def proj_box(v):                                  # বাক্স [0, 0.8]³: শুধু কেটে দেওয়া
    return np.clip(v, 0.0, 0.8)
def proj_simplex(v):                              # সব ঘর ≥ 0, যোগফল 1
    u = np.sort(v)[::-1]
    css = np.cumsum(u)
    k = np.arange(1, v.size + 1)
    rho = np.nonzero(u - (css - 1) / k > 0)[0][-1]
    theta = (css[rho] - 1) / (rho + 1)
    return np.maximum(v - theta, 0.0)
def pgd(proj, x, tol=1e-10, max_it=500):
    for it in range(1, max_it + 1):
        x_new = proj(x - eta * grad(x))           # gradient ধাপ, তারপর projection
        if np.linalg.norm(x_new - x) < tol:
            return x_new, it
        x = x_new
    return x, max_it
xb, nb = pgd(proj_box, np.zeros(3))
lb = minimize(f, np.zeros(3), jac=grad, method="L-BFGS-B", bounds=[(0, 0.8)] * 3)
print("box     PGD:", xb, f"iters {nb}", "| L-BFGS-B:", lb.x, f"iters {lb.nit}")
xs, ns = pgd(proj_simplex, np.ones(3) / 3)
sl = minimize(f, np.ones(3) / 3, jac=grad, method="SLSQP", bounds=[(0, None)] * 3,
              constraints=[{"type": "eq", "fun": lambda x: x.sum() - 1}],
              options={"ftol": 1e-12})
print("simplex PGD:", xs, f"iters {ns}", "| SLSQP:", sl.x.round(4), f"iters {sl.nit}")
print("projection of (0.5, 1.2, -0.3):", proj_simplex(np.array([0.5, 1.2, -0.3])))

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

box     PGD: [0.55 0.8  0.8 ] iters 13 | L-BFGS-B: [0.55 0.8  0.8 ] iters 3
simplex PGD: [0.1429 0.5714 0.2857] iters 57 | SLSQP: [0.1429 0.5714 0.2857] iters 7
projection of (0.5, 1.2, -0.3): [0.15 0.85 0.  ]

Box-এ দুটি পদ্ধতিই একই উত্তর দিয়েছে: (০.৫৫, ০.৮, ০.৮)। দ্বিতীয় আর তৃতীয় চলক ওপরের সীমায় আটকে আছে (তাদের gradient ঋণাত্মক, অর্থাৎ আরও বাড়তে চায়), আর প্রথমটি ভেতরে: x2 = x3 = ০.৮ বসালে প্রথম সারির ঢাল 4x1 + ০.৮ − ৩ = ০ থেকে x1 = ০.৫৫. Projected GD-র লেগেছে ১৩টি ধাপ (থামার নিয়ম: পরপর দুই বিন্দুর দূরত্ব ১০−১০-এর কম), L-BFGS-B-এর ৩টি; (L-BFGS-B-এর একটি ধাপে line search থাকে, তাই ধাপ এক মাপের নয়।) Simplex-এ উত্তর (০.১৪২৯, ০.৫৭১৪, ০.২৮৫৭) = (১/৭, ৪/৭, ২/৭): এখানে তিনটি ঘরের gradient-ই সমান (−১৩/৭), ঠিক KKT-এর দাবি অনুযায়ী, কারণ সব ঘর ধনাত্মক আর একটিমাত্র শর্ত (যোগফল ১) active. Projected GD এখানে ৫৭টি ধাপ নিয়েছে, SLSQP ৭টি: gradient descent-এর ধীরগতি condition number-এর ওপর নির্ভর করে, projection তা বদলায় না। শেষ লাইন stepper-এর হাতের হিসাব (০.১৫, ০.৮৫, ০) নিশ্চিত করে। এটি একটি ছোট সমস্যার একবারের run, সাধারণ ফল নয়।

৮ · বাজারঘরের বিজ্ঞাপন বাজেট — SLSQP, linprog আর shadow price

বাজারঘর আগামী মাসে বিজ্ঞাপনে মোট ১ লাখ টাকা (১০০ হাজার) খরচ করবে, চারটি চ্যানেলে: সোশ্যাল মিডিয়া, সার্চ বিজ্ঞাপন, ভিডিও আর SMS. প্রতিটি চ্যানেলে খরচের একটি ঊর্ধ্বসীমা আছে (চুক্তি বা দর্শকের সীমা)। এখানের সব সংখ্যা কাল্পনিক, শুধু পদ্ধতি দেখানোর জন্য। প্রতিটি চ্যানেলে ফেরত diminishing returns মানে: প্রথম কয়েক হাজার টাকায় অনেক বিক্রি, পরের টাকায় কম। একে মডেল করা হয়েছে ri(x) = ai log(1 + x/bi) দিয়ে, যার প্রান্তিক ফেরত ri′(x) = ai/(bi + x) খরচ বাড়লে কমে। বাস্তবে এই function-গুলো আসে অতীতের ডেটায় fit করা একটি model থেকে, আর তার নিজস্ব অনিশ্চয়তা আছে।

KKT এখানে একটি সুন্দর ব্যবসায়িক নিয়ম দেয়। বাজেট শর্তের multiplier ν হলে, যে চ্যানেল সীমায় বা শূন্যে আটকে নেই, তার জন্য stationarity বলে ri′(xi) = ν: সব “মুক্ত” চ্যানেলে শেষ হাজার টাকার ফেরত সমান। কোনো চ্যানেলে এটি বেশি হলে সেখানে টাকা সরানো লাভজনক হতো। যে চ্যানেল নিজের সীমায় আটকে আছে, তার প্রান্তিক ফেরত ν-এর চেয়ে বেশি, পার্থক্যটি তার সীমার multiplier. দ্বিতীয় অংশে একই প্রশ্নের একটি LP সংস্করণ: প্রতি হাজার টাকায় স্থির ফেরত, যা linprog (HiGHS solver) দিয়ে সমাধান হয়, আর solver নিজেই প্রতিটি শর্তের dual value, অর্থাৎ shadow price দেয়।

import numpy as np
from scipy.optimize import minimize, linprog

np.set_printoptions(precision=2, suppress=True)
# ক্রম: social, search, video, sms — সব সংখ্যা কাল্পনিক, হাজার টাকায়
a = np.array([120.0, 90.0, 150.0, 50.0])         # ফেরতের উচ্চতা
b = np.array([20.0, 10.0, 60.0, 5.0])            # কত দ্রুত ফেরত কমে
cap = np.array([50.0, 30.0, 60.0, 15.0])         # প্রতি চ্যানেলের সীমা
B = 100.0                                        # মোট বাজেট

rev = lambda x: np.sum(a * np.log1p(x / b))      # কমতে-থাকা ফেরত
d_rev = lambda x: a / (b + x)                    # প্রতি হাজার টাকায় প্রান্তিক ফেরত
budget = {"type": "ineq", "fun": lambda x: B - x.sum(), "jac": lambda x: -np.ones(4)}
res = minimize(lambda x: -rev(x), np.full(4, 25.0), jac=lambda x: -d_rev(x),
               method="SLSQP", bounds=list(zip(np.zeros(4), cap)),
               constraints=[budget], options={"ftol": 1e-12})
x = res.x
print("SLSQP spend :", x, " total", round(x.sum(), 2))
print("marginal    :", d_rev(x), " revenue", round(rev(x), 2))
print("budget multiplier:", res.multipliers.round(4))

r = np.array([2.4, 3.1, 1.6, 2.8])              # LP: প্রতি হাজার টাকায় স্থির ফেরত
lp = linprog(-r, A_ub=np.ones((1, 4)), b_ub=[B],
             bounds=list(zip(np.zeros(4), cap)), method="highs")
print("LP spend    :", lp.x, " revenue", round(-lp.fun, 2))
print("shadow price of budget  :", 0.0 - lp.ineqlin.marginals)
print("shadow price of each cap:", 0.0 - lp.upper.marginals)
lp2 = linprog(-r, A_ub=np.ones((1, 4)), b_ub=[B + 1],
              bounds=list(zip(np.zeros(4), cap)), method="highs")
print("revenue with budget 101 :", round(-lp2.fun, 2))

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

SLSQP spend : [40. 30. 15. 15.]  total 100.0
marginal    : [2.   2.25 2.   2.5 ]  revenue 359.39
budget multiplier: [2.]
LP spend    : [50. 30.  5. 15.]  revenue 263.0
shadow price of budget  : [1.6]
shadow price of each cap: [0.8 1.5 0.  1.2]
revenue with budget 101 : 264.6

কমতে-থাকা ফেরত (SLSQP): পুরো ১০০ হাজার খরচ হয়েছে: সোশ্যালে ৪০, সার্চে ৩০ (সীমা), ভিডিওতে ১৫, SMS-এ ১৫ (সীমা)। প্রান্তিক ফেরত দেখুন: সোশ্যাল আর ভিডিও, যারা কোনো সীমায় আটকে নেই, দুটিরই ঠিক ২.০; আর বাজেটের multiplier-ও ২.০. KKT-এর ব্যবসায়িক রূপ এটিই: মুক্ত চ্যানেলে শেষ হাজার টাকার ফেরত সমান, আর সেটিই বাজেটের দাম। সার্চ (২.২৫) আর SMS (২.৫) নিজেদের সীমায় আটকে, তাদের প্রান্তিক ফেরত বাজেটের দামের চেয়ে বেশি; পার্থক্য (০.২৫ আর ০.৫) তাদের সীমার shadow price, অর্থাৎ সীমা বাড়ানোর সুযোগ থাকলে আগে সেখানে দর-কষাকষি করা লাভজনক। মোট আনুমানিক বাড়তি আয় ৩৫৯.৩৯ হাজার টাকা, এই কাল্পনিক মডেলে। LP সংস্করণ: ফেরত স্থির বলে সমাধান greedy-র মতো: সবচেয়ে লাভজনক সার্চ (৩.১) আর SMS (২.৮) পুরো সীমা পর্যন্ত, তারপর সোশ্যাল (২.৪) পুরো ৫০, বাকি ৫ ভিডিওতে (১.৬)। বাজেটের shadow price ১.৬: বাড়তি হাজার টাকা যাবে ভিডিওতে। আবার চালিয়ে যাচাই: বাজেট ১০১-এ আয় ২৬৩ থেকে ২৬৪.৬, ঠিক ১.৬ বেশি। প্রতিটি সীমার shadow price = চ্যানেলের হার − ১.৬: সোশ্যাল ০.৮, সার্চ ১.৫, SMS ১.২, আর ভিডিওর সীমা inactive বলে ০, complementary slackness অনুযায়ী।

Shadow price-এর তিনটি ফাঁদ: (১) এটি একটি derivative: LP-তে তা একটি নির্দিষ্ট পরিসরেই স্থির থাকে। এখানে বাজেট ১০০ থেকে ১৫৫ পর্যন্ত বাড়ালে বাড়তি টাকা ভিডিওতে যায় আর দাম ১.৬ থাকে; তার পরে ভিডিওর সীমা ৬০-এ পৌঁছালে দাম শূন্য হয়ে যায়, কারণ আর কোথাও খরচের জায়গা নেই। (২) Solver ভেদে চিহ্নের নিয়ম আলাদা: SciPy-র marginals হলো minimize-এর objective-এর derivative, তাই সর্বোচ্চ করার সমস্যায় চিহ্ন উল্টাতে হয়। (৩) Degenerate সমস্যায় (যেমন একটি চ্যানেলের সীমা ঠিক সমাধানের মানের সমান) dual value একটি নয়, অনেকগুলো হতে পারে; তখন solver যেকোনো একটি দেয়। নিশ্চিত হতে সংখ্যাটি বদলে আবার চালান, ঠিক যেমন ১০১ দিয়ে করা হয়েছে।

৯ · ML-এ constraint কোথায় দাঁড়ায় — শাস্তি না শর্ত, আর কোন টুল

ML-এর বেশিরভাগ training loop-এ সরাসরি শর্ত দেখা যায় না, কারণ শর্তকে প্রায়ই শাস্তিতে রূপান্তর করা হয়। Ridge-এর দুটি রূপ ভাবুন: “‖y − Xw‖² সর্বনিম্ন করুন, শর্ত ‖w‖² ≤ t” আর “‖y − Xw‖² + α‖w‖² সর্বনিম্ন করুন”। প্রথমটির KKT লিখলে stationarity হয় ∇‖y − Xw‖² + μ · 2w = 0, যা দ্বিতীয়টির gradient-এর সঙ্গে হুবহু এক যখন α = μ. অর্থাৎ convex সমস্যায় প্রতিটি t-এর জন্য এমন একটি α আছে যার সমাধান একই, আর α হলো শর্তের Lagrange multiplier. তবে t আর α-এর সম্পর্ক ডেটার ওপর নির্ভর করে, আগে থেকে জানা যায় না। AI-এর ভিত্তি কোর্সের পর্ব ৬১-এ L1 আর L2-এর জ্যামিতির ছবিতে যে হীরা আর বৃত্ত দেখেছেন, সেগুলোই এই constrained রূপের feasible set; পুরো তত্ত্ব পর্ব ১৭ (Regularization-এর তত্ত্ব — L1/L2-এর জ্যামিতি, MAP আর early stopping)-এ।

কখন শাস্তি যথেষ্ট নয়, সত্যিকারের শর্ত লাগে? যখন শর্ত ভাঙলে ফলের কোনো মানে থাকে না: সম্ভাবনা ঋণাত্মক হতে পারে না, মজুদ শূন্যের নিচে যেতে পারে না, বাজেট পেরোনো যায় না। Fairness-এর কিছু পদ্ধতিও শর্ত হিসেবে লেখা হয়, যদিও কোন মাপটি ঠিক, তা নিয়ে বিতর্ক আছে।

সমস্যার ধরনSciPy-তে প্রথম পছন্দমন্তব্য
শুধু box (l ≤ x ≤ u)minimize(method="L-BFGS-B", bounds=…)বড় n-এও দ্রুত; সাধারণ শর্ত নেয় না
সরল সেট, বড় nনিজের projected gradientprojection সস্তা হলে (box, ball, simplex)
অল্প চলক, মসৃণ সাধারণ শর্তmethod="SLSQP"multiplier ফেরত দেয়; dense, তাই বড় n-এ ধীর
বড়, sparse, মসৃণ শর্তmethod="trust-constr"interior-point ধাঁচের; multiplier res.v-তে
সব রৈখিকlinprog(method="highs")নির্ভুল সমাধান আর dual value
পূর্ণসংখ্যার চলকmilpNP-hard হতে পারে; ছোট সমস্যায় ভালো

আরও গভীরে: SLSQP-এর মতো sequential quadratic programming প্রতিটি ধাপে Lagrangian-এর Hessian-এর একটি quasi-Newton আন্দাজ আর শর্তের রৈখিক আন্দাজ দিয়ে একটি QP সমাধান করে; local অঞ্চলে এর অভিসরণ Newton-এর মতো দ্রুত। Interior-point পদ্ধতি অসমতাকে −t∑log(−gi) জাতীয় barrier দিয়ে বদলে t ধীরে ধীরে শূন্যে নামায়; LP-তে Karmarkar-এর ১৯৮৪ সালের algorithm এর আধুনিক ঢেউ শুরু করে। Projected gradient-এর সাধারণ রূপ proximal gradient, যা lasso-র soft-thresholding-কে একই কাঠামোয় আনে (পর্ব ১৭)। আরও পড়ার জন্য Boyd ও Vandenberghe-র Convex Optimization (২০০৪) আর Nocedal ও Wright-এর Numerical Optimization প্রচলিত দুটি বই।

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

  • Lagrange multiplier-এর জ্যামিতিক মানে? সর্বনিম্নে ∇f ∥ ∇h, কারণ contour শর্তকে ছুঁয়ে যায়; λ = df*/dc, শর্তের দাম।
  • KKT-এর চারটি শর্ত বলুন। Stationarity, primal feasibility, dual feasibility (μ ≥ ০), complementary slackness (μigi = ০)।
  • Complementary slackness থেকে SVM সম্পর্কে কী জানা যায়? Margin থেকে দূরের উদাহরণে αi = ০; শুধু support vector সমাধান ঠিক করে।
  • Weak আর strong duality-র পার্থক্য? Weak: dual সবসময় primal-এর নিম্নসীমা; strong: ফাঁক শূন্য, convex আর Slater-এর শর্তে।
  • শুধু bounds থাকলে কোন optimizer? L-BFGS-B, বা projection সস্তা হলে projected gradient.

১০ · AI-এর যুগে — AI-এর লেখা optimization কোড আর KKT যাচাই

AI assistant constrained optimization-এর কোড দ্রুত লিখে দেয়, আর বেশিরভাগ সময় কোডটি চলেও। সমস্যা হয় চুপচাপ ভুলে: শর্তের চিহ্ন উল্টো, ভুল solver, বা solver-এর “success” দেখেই উত্তর বিশ্বাস। এই পর্বের KKT যাচাই ঠিক এই ভুলগুলো ধরার যন্ত্র।

AI প্রায়ই যা করেকেন ভুলকীভাবে ধরবেন
SciPy-র "ineq" শর্তকে g(x) ≤ 0 ধরে লেখেSciPy-তে "ineq" মানে fun(x) ≥ 0; শর্ত উল্টে যায়সমাধানে প্রতিটি শর্তের মান ছাপুন আর হাতে মেলান
সাধারণ শর্তসহ সমস্যায় L-BFGS-B বা BFGS-এ constraints দেয়এই method-গুলো সাধারণ শর্ত মানে না; SciPy শুধু একটি warning দেয়docs-এ দেখুন কোন method কোন শর্ত নেয়; warning পড়ুন
res.success দেখেই উত্তর গ্রহণ করেlocal সমাধান বা শিথিল tolerance হতে পারেKKT residual, feasibility আর μ-এর চিহ্ন নিজে পরীক্ষা করুন
Simplex-এ “clip করে ভাগ” দিয়ে projection লেখেএটি সবচেয়ে কাছের বিন্দু নয়দূরত্ব তুলনা করুন, বা sort-ভিত্তিক projection ব্যবহার করুন
Shadow price-কে যেকোনো বড় বদলের জন্য সত্য বলেএটি শুধু একটি পরিসরে বৈধ derivativeশর্তের মান বদলে আবার solve করুন
পুরোনো API দেয় (যেমন linprog(method="simplex"))নতুন SciPy-তে পুরোনো method সরানো হয়েছেনিজের version-এর docs পড়ুন; "highs" ব্যবহার করুন

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

আমি SciPy (version 1.18) দিয়ে একটি সমস্যা সমাধান করতে চাই: চারটি বিজ্ঞাপন চ্যানেলে মোট বাজেট 100, প্রতিটির ঊর্ধ্বসীমা আছে, আর ফেরত a_i·log(1 + x_i/b_i). ১. সমস্যাটি standard form-এ লেখো (minimize, g(x) ≤ 0), আর SciPy-র "ineq" শর্তে কোন চিহ্নে বসবে তা আলাদা করে বলো। ২. কোন method বেছে নিলে, আর কেন L-BFGS-B এখানে যথেষ্ট নয়, এক লাইনে বলো। ৩. সমাধানের পর KKT-এর চারটি শর্ত সংখ্যায় যাচাই করার কোড দাও: stationarity residual, feasibility, multiplier-এর চিহ্ন, complementary slackness. ৪. বাজেটের multiplier-এর ব্যবসায়িক মানে বলো, আর কতটা বদল পর্যন্ত তা বিশ্বাস করা যায়। ৫. তুমি যে SciPy API ব্যবহার করছ, তা কোন version থেকে আছে বলে মনে করো, আর কোথায় ভুল হতে পারে, তাও বলো।

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

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

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

প্র ০১KKT শর্ত কি সবসময় সর্বনিম্ন বিন্দুতে মেলে? Karush-এর ১৯৩৯ সালের কাজ আর constraint qualification-এর গল্প কী?

না, সবসময় নয়। KKT একটি “প্রয়োজনীয় শর্ত”, তবে একটি অতিরিক্ত অনুমানসহ: সমাধানে শর্তগুলোর জ্যামিতি যথেষ্ট ভদ্র হতে হবে। এই অনুমানগুলোকে বলে constraint qualification. সবচেয়ে পরিচিতটি LICQ: active শর্তগুলোর gradient রৈখিকভাবে স্বাধীন। একটি ছোট উদাহরণে দেখা যাক এটি না মানলে কী হয়। সর্বনিম্ন করুন f(x) = x, শর্ত g(x) = x² ≤ ০. Feasible set-এ শুধু একটি বিন্দু, x = ০, তাই সেটিই সর্বনিম্ন। কিন্তু সেখানে ∇f = ১ আর ∇g = 2x = ০. Stationarity চায় ১ + μ · ০ = ০, যা কোনো μ দিয়েই সম্ভব নয়। অর্থাৎ সত্যিকারের সর্বনিম্ন বিন্দুতে KKT ব্যর্থ, কারণ শর্তটির gradient সেখানে শূন্য, দেয়ালটির কোনো “দিক” নেই।

ইতিহাসের দিকটিও শিক্ষণীয়। Harold Kuhn আর Albert Tucker ১৯৫১ সালে Berkeley-র একটি গণিত ও পরিসংখ্যান symposium-এ “Nonlinear Programming” নামে প্রবন্ধটি প্রকাশ করেন, আর বহু বছর শর্তগুলো “Kuhn–Tucker conditions” নামেই পরিচিত ছিল। পরে জানা যায়, William Karush ১৯৩৯ সালে University of Chicago-তে তাঁর master's thesis-এ প্রায় একই ফল লিখেছিলেন, যা তখন প্রকাশিত হয়নি। ১৯৭০-এর দশকে বিষয়টি ব্যাপকভাবে জানাজানি হওয়ার পর নাম হয় KKT. Fritz John-এর ১৯৪৮ সালের একটি প্রবন্ধে আরেকটি রূপ আছে, যেখানে f-এর gradient-এর সামনেও একটি multiplier থাকে; সেই রূপ constraint qualification ছাড়াই খাটে, কিন্তু তখন f-এর multiplier শূন্যও হতে পারে, যা কোনো তথ্য দেয় না। ইতিহাসের খুঁটিনাটি নিয়ে উৎসভেদে কিছু পার্থক্য আছে।

ব্যবহারিক দিক থেকে convex সমস্যায় Slater-এর শর্ত (অন্তত একটি বিন্দু যেখানে সব অসমতা কড়াভাবে মানে) মানলেই KKT প্রয়োজনীয় আর যথেষ্ট দুটোই। ML-এর বেশিরভাগ convex সমস্যা, যেমন SVM, এই শর্ত সহজেই মানে। সমস্যা হয় অদ্ভুত শর্তে, যেমন একই শর্ত দুবার লেখা, বা x² ≤ ০-এর মতো শর্ত যা আসলে একটি সমতা লুকিয়ে রাখে। তখন solver multiplier খুঁজতে গিয়ে অস্থির হতে পারে, আর অনেক বড় বা অর্থহীন μ দেখায়।

মূল উপলব্ধি: KKT একটি শক্তিশালী পরীক্ষা, কিন্তু শর্তহীন নয়; শর্তগুলো সুগঠিত করে লিখুন, অপ্রয়োজনীয় বা পুনরাবৃত্ত শর্ত বাদ দিন, আর অদ্ভুত μ দেখলে আগে শর্তের জ্যামিতি পরীক্ষা করুন।

প্র ০২Shadow price ব্যবসায়িক সিদ্ধান্তে কতটা বিশ্বাসযোগ্য, আর LP-র এই ধারণা কোথা থেকে এসেছে?

Linear programming-এর জন্ম মূলত পরিকল্পনা আর সম্পদ বণ্টনের প্রশ্ন থেকে। সোভিয়েত গণিতবিদ Leonid Kantorovich ১৯৩৯ সালে কারখানার উৎপাদন পরিকল্পনার জন্য রৈখিক মডেল আর এক ধরনের দাম-ব্যবস্থা প্রস্তাব করেন। যুক্তরাষ্ট্রে George Dantzig ১৯৪৭ সালে simplex method বানান, মার্কিন বিমানবাহিনীর পরিকল্পনার সমস্যার প্রেক্ষাপটে। ১৯৭৫ সালে অর্থনীতিতে নোবেল পুরস্কার পান Kantorovich আর Tjalling Koopmans, “সম্পদের সর্বোত্তম বণ্টনের তত্ত্বে অবদানের” জন্য; Dantzig সেই পুরস্কার পাননি, যা নিয়ে পরে অনেকে মন্তব্য করেছেন। Dual variable-এর অর্থনৈতিক ব্যাখ্যা, অর্থাৎ প্রতিটি সীমিত সম্পদের একটি “ছায়া দাম”, এই ঐতিহ্যেরই অংশ।

বিশ্বাসযোগ্যতার প্রশ্নে তিনটি স্তর আলাদা করুন। প্রথম, গাণিতিক স্তর: shadow price একটি derivative. LP-তে সেটি একটি নির্দিষ্ট পরিসরে স্থির, আর পরিসরের সীমায় লাফ দেয়। এই পর্বের বাজেট উদাহরণে বাজেট ১৫৫ পেরোলে ১.৬ শূন্য হয়ে যায়। পরিসরটি নিশ্চিত হতে কয়েকটি মানে আবার solve করে দেখুন। Degenerate সমস্যায় dual value একাধিক হতে পারে, তখন solver-এর দেওয়া সংখ্যাটি একটিমাত্র সম্ভাব্য উত্তর।

দ্বিতীয়, মডেলের স্তর: “প্রতি হাজার টাকায় ২.৪ ফেরত” কথাটি নিজেই অতীত ডেটা থেকে আনুমানিক। সেটি ভুল হলে optimizer নিখুঁতভাবে ভুল উত্তর দেয়। Optimization মডেলের অনুমানগুলোকে আরও জোরালো করে তোলে: যে চ্যানেলের ফেরত বাড়িয়ে অনুমান করা হয়েছে, optimizer ঠিক সেখানেই সব টাকা ঢালে। এজন্য অনুমানের অনিশ্চয়তা ধরে robust optimization বা কয়েকটি দৃশ্যপট নিয়ে সমাধান করা হয়।

তৃতীয়, কার্যকারণের স্তর: বিজ্ঞাপনের ফেরত মাপা একটি কার্যকারণ প্রশ্ন। যে মাসে বিক্রি এমনিতেই বেশি, সে মাসে বিজ্ঞাপনও বেশি দেওয়া হলে ডেটায় ফেরত বেশি দেখাবে। Controlled experiment বা যত্নশীল causal পদ্ধতি ছাড়া ফেরতের সংখ্যাগুলো পক্ষপাতদুষ্ট হতে পারে।

মূল উপলব্ধি: shadow price একটি স্থানীয় আর মডেল-নির্ভর উত্তর; পরিসর যাচাই, মডেলের অনিশ্চয়তা আর কার্যকারণ, তিনটিই না দেখে এর ওপর বড় সিদ্ধান্ত নেওয়া ঠিক নয়।

প্র ০৩ML-এ শর্তের বদলে প্রায় সবসময় শাস্তি (penalty) ব্যবহার করা হয় কেন, আর কখন সেটি ভুল পছন্দ?

প্রধান কারণ গণনার সুবিধা। Gradient descent আর তার পরিবার (SGD, Adam) শর্তহীন সমস্যার জন্য বানানো। শাস্তির রূপে loss + α · R(w) লিখলে একই training loop চলে, শুধু gradient-এ একটি পদ যোগ হয়। Convex সমস্যায় শাস্তি আর শর্ত সমতুল্যও: অংশ ৯-এ দেখেছেন, ‖w‖² ≤ t শর্তের KKT stationarity হুবহু শাস্তির রূপের gradient, যেখানে α হলো শর্তের multiplier. তাই ridge বা lasso-কে যেকোনো রূপে লেখা যায়, আর α বাছাই cross-validation দিয়ে হয়।

কিন্তু সমতুল্যতার একটি দাম আছে: α আর t-এর সম্পর্ক ডেটার ওপর নির্ভর করে, আগে থেকে জানা যায় না। যদি আপনার আসল চাহিদা একটি স্পষ্ট সংখ্যা হয়, যেমন “মোট খরচ ১ লাখের বেশি নয়” বা “প্রতিটি সম্ভাবনা ০ আর ১-এর মধ্যে”, তাহলে শাস্তি দিয়ে সেটি নিশ্চিত করতে α বারবার বদলাতে হয়, আর তবুও সীমা সামান্য ভাঙতে পারে। Non-convex সমস্যায় সমতুল্যতা আরও দুর্বল: কিছু শর্তযুক্ত সমাধান কোনো α দিয়েই শাস্তির রূপে পাওয়া যায় না।

কখন শর্ত ভালো পছন্দ? (১) কঠিন সীমা: সম্ভাবনা, মজুদ, বাজেট, শারীরিক সীমা; এখানে সামান্য ভাঙাও অর্থহীন বা বিপজ্জনক। (২) ব্যাখ্যা: “ওজনের মোট মাপ ৩-এর বেশি নয়” কথাটি মানুষের কাছে α = ০.০৭-এর চেয়ে স্পষ্ট। (৩) Projection সস্তা হলে: simplex বা box-এ projected gradient একটি শাস্তির মতোই সরল, কিন্তু প্রতিটি ধাপে শর্ত নিশ্চিত করে। Deep learning-এও এর উদাহরণ আছে: ২০১৭ সালের Wasserstein GAN-এর প্রথম সংস্করণে critic-এর ওজন একটি box-এ কেটে রাখা হয়েছিল (weight clipping), যদিও পরে লেখকেরা এর সমস্যা দেখে gradient penalty-র মতো বিকল্প প্রস্তাব করেন।

মূল উপলব্ধি: শাস্তি সুবিধাজনক আর convex ক্ষেত্রে শর্তের সমতুল্য; কিন্তু যেখানে সীমাটি নিজেই ব্যবসা বা পদার্থবিদ্যার একটি অলঙ্ঘনীয় নিয়ম, সেখানে সরাসরি শর্তই সৎ পছন্দ।

প্র ০৪SVM শেখাতে dual problem কেন এত গুরুত্বপূর্ণ হয়ে উঠল, আর কখন primal-ই ভালো?

Support vector machine-এর আধুনিক রূপের শুরু হিসেবে সাধারণত Boser, Guyon আর Vapnik-এর ১৯৯২ সালের প্রবন্ধ আর Cortes ও Vapnik-এর ১৯৯৫ সালের soft-margin প্রবন্ধ উল্লেখ করা হয়। Hard-margin SVM-এর primal সমস্যা: ‖w‖²/2 সর্বনিম্ন করুন, শর্ত প্রতিটি উদাহরণে yi(wᵀxi + b) ≥ ১. এটি একটি QP, চলক w (feature-এর সংখ্যার সমান), শর্ত n-টি। এর Lagrangian লিখে w আর b-তে ঢাল শূন্য বসালে w = ∑αiyixi আর ∑αiyi = ০ আসে; বসিয়ে dual সমস্যা হয় α-তে, যেখানে ডেটা আসে শুধু xiᵀxj হিসেবে।

এই শেষ পর্যবেক্ষণটিই dual-কে বিখ্যাত করেছে। xiᵀxj-এর জায়গায় একটি kernel k(xi, xj) বসালে model কার্যত একটি অনেক বড়, এমনকি অসীম মাত্রার feature space-এ রৈখিক সীমানা শেখে, অথচ সেই feature কখনো হিসাব করতে হয় না। Complementary slackness দ্বিতীয় উপহার দেয়: margin-এর বাইরের উদাহরণে αi = ০, তাই চূড়ান্ত model শুধু support vector-এর ওপর নির্ভর করে। এটি sparse, আর কোন উদাহরণগুলো সীমানা ঠিক করছে তা সরাসরি দেখা যায়।

তবে dual-এর একটি বড় খরচ আছে: n × n kernel matrix. n = ১০ লাখ হলে তা ১০১২টি ঘর, যা মেমরিতে রাখাই কঠিন। ১৯৯৮ সালে John Platt-এর SMO algorithm একবারে মাত্র দুটি α বদলে এই সমস্যা অনেকটা সামলায়, আর LIBSVM-এর মতো লাইব্রেরি এই ধারার ওপর দাঁড়িয়ে। কিন্তু রৈখিক SVM-এ, যেখানে feature অনেক আর kernel লাগে না (যেমন TF-IDF টেক্সট), primal-এ সরাসরি সমাধান বা dual coordinate descent প্রায়ই অনেক দ্রুত; LIBLINEAR এই কারণে আলাদা লাইব্রেরি। scikit-learn-এর LinearSVC-তে primal বা dual বাছাইয়ের একটি parameter আছে, যার সঠিক নাম আর default নিজের version-এর docs-এ দেখে নিন।

এর বিস্তারিত পর্ব ২৭ থেকে ৩১-এ।

মূল উপলব্ধি: dual একই সমস্যার আরেকটি দৃশ্য, যেখানে চলক শর্তের সংখ্যায়; কোন দৃশ্য সস্তা, তা ঠিক করে n বনাম feature-এর সংখ্যা আর kernel লাগবে কি না।

প্র ০৫Projected gradient, SLSQP, interior-point আর penalty: একটি নতুন constrained সমস্যায় কোনটি কখন বেছে নেবেন?

প্রথম প্রশ্ন সবসময়: সমস্যাটি convex কি না, আর কত বড়। Convex হলে যেকোনো ভালো solver global সমাধান দেবে, তখন বাছাই মূলত গতি আর সুবিধার প্রশ্ন। Non-convex হলে প্রতিটি পদ্ধতিই local সমাধান দেয়, তখন কয়েকটি শুরুর বিন্দু থেকে চালানো আর ফলের তুলনা জরুরি।

Projected gradient সবচেয়ে সরল, প্রতি ধাপে শুধু gradient আর একটি projection. Box, ball বা simplex-এর মতো সেটে projection সস্তা, তাই লাখো চলকেও চলে, আর mini-batch gradient-এর সঙ্গেও মেলে। দুর্বলতা: ill-conditioned সমস্যায় gradient descent-এর মতোই ধীর (এই পর্বের simplex run-এ ৫০টির বেশি ধাপ, SLSQP-এর কয়েকটির জায়গায়), আর সাধারণ রৈখিক বা অরৈখিক শর্তে projection নিজেই একটি কঠিন সমস্যা।

SQP (যেমন SLSQP) Newton-ধাঁচের: অল্প ধাপে উচ্চ নির্ভুলতা, যেকোনো মসৃণ শর্ত নেয়, multiplier দেয়। Dieter Kraft-এর ১৯৮৮ সালের কোড আজও SciPy-তে এর ভিত্তি বলে docs-এ উল্লেখ আছে, যদিও SciPy 1.16-এর release notes অনুযায়ী Fortran 77 কোডটি C-তে নতুন করে লেখা হয়েছে। দুর্বলতা: dense matrix নিয়ে কাজ করে, তাই কয়েক হাজার চলকের বেশি হলে ধীর আর মেমরি-ক্ষুধার্ত।

Interior-point পদ্ধতি অসমতাকে barrier দিয়ে বদলে ভেতর দিয়ে সমাধানের দিকে হাঁটে। Fiacco আর McCormick-এর ১৯৬০-এর দশকের কাজ থেকে শুরু, আর Karmarkar-এর ১৯৮৪ সালের LP algorithm এর পুনর্জাগরণ ঘটায়। বড়, sparse, অনেক শর্তের সমস্যায় এটি শক্তিশালী; SciPy-র trust-constr এর একটি রূপ ব্যবহার করে, আর HiGHS-এ LP-র জন্য simplex আর interior-point দুটিই আছে।

Penalty বা augmented Lagrangian শর্তকে loss-এ ঢুকিয়ে শর্তহীন optimizer চালায়। Deep learning-এ এটিই প্রায়ই একমাত্র ব্যবহারিক পথ, কারণ সেখানে training loop শর্তহীন। দুর্বলতা: শর্ত শুধু আনুমানিকভাবে মানে, আর শাস্তির ওজন বাছতে হয়।

মূল উপলব্ধি: কোনো একটি পদ্ধতি সবার সেরা নয়; সমস্যার গঠন (convex, আকার, শর্তের ধরন, projection-এর খরচ) দেখে টুল বাছুন, আর যেকোনো টুলের উত্তর KKT দিয়ে যাচাই করুন।

অনুশীলন

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

  1. পূর্ণ numerical (Lagrange): x + 2y = 8 শর্তে f(x, y) = xy সর্বোচ্চ করুন। (ক) Lagrange-এর সমীকরণ লিখে x, y, λ আর f* বের করুন। (খ) শর্তের ডান দিক ৮ থেকে ৯ করলে f* প্রায় কত বাড়বে, λ দিয়ে আন্দাজ করুন, তারপর আসল মানের সঙ্গে মেলান।

    (ক) ∇f = (y, x), ∇h = (১, ২)। ∇f = λ∇h থেকে y = λ আর x = 2λ. শর্তে বসালে 2λ + 2λ = ৮, তাই λ = ২, x = ৪, y = ২, f* = ৮. (খ) সাধারণভাবে x + 2y = c হলে x = c/2, y = c/4, f*(c) = c²/8, আর df*/dc = c/4 = ২ (c = ৮-এ), ঠিক λ. আন্দাজ: f*(৯) ≈ ৮ + ২ × ১ = ১০. আসল: ৮১/৮ = ১০.১২৫. পার্থক্য ০.১২৫ আসে কারণ λ একটি derivative, আর এখানে বদলটি (১ একক) ছোট নয়।

  2. KKT হাতে: সর্বনিম্ন করুন f(x, y) = (x − 1)² + (y − 2)², শর্ত x + y ≤ ২. (ক) শর্তহীন সর্বনিম্ন কি feasible? (খ) শর্তটি active ধরে x*, μ আর f* বের করুন, আর চারটি KKT শর্ত যাচাই করুন। (গ) শর্তটি x + y ≤ ৪ হলে μ কত, আর কেন?

    (ক) শর্তহীন সর্বনিম্ন (১, ২), যেখানে x + y = ৩ > ২, তাই feasible নয়। (খ) x + y = ২ ধরে (১, ২)-এর সবচেয়ে কাছের বিন্দু: (০.৫, ১.৫)। ∇f = (2(০.৫ − ১), 2(১.৫ − ২)) = (−১, −১), ∇g = (১, ১), তাই stationarity থেকে μ = ১. যাচাই: primal: g = ০ ✓; dual: μ = ১ ≥ ০ ✓; complementary: ১ × ০ = ০ ✓; stationarity: (−১, −১) + ১·(১, ১) = 0 ✓. f* = ০.২৫ + ০.২৫ = ০.৫. Convex সমস্যা, তাই এটি global সর্বনিম্ন। (গ) x + y ≤ ৪ হলে শর্তহীন সর্বনিম্ন (১, ২) নিজেই feasible (৩ < ৪), শর্ত inactive, তাই complementary slackness অনুযায়ী μ = ০; দেয়াল দূরে, ঠেলার দরকার নেই।

  3. সমালোচনা ও নকশা: বাজারঘরের একজন সহকর্মী (বা একটি AI assistant) একটি mixture model-এর ওজন w শেখাতে প্রতিটি gradient ধাপের পর w = np.clip(w, 0, None); w = w / w.sum() লিখেছেন। বাজেট সমস্যায় তিনি LP-র shadow price ১.৬ দেখে রিপোর্টে লিখেছেন “বাজেট ৫০ হাজার বাড়ালে আয় ৮০ হাজার বাড়বে”। দুটি সিদ্ধান্তের সমস্যা কী, আর কীভাবে ঠিক করবেন?

    Projection: clip করে ভাগ করলে একটি বৈধ বিন্দু আসে, কিন্তু সবচেয়ে কাছের বিন্দু নয়; v = (০.৫, ১.২, −০.৩)-এ এর দূরত্ব² ০.৩৭৭, আসল projection-এর ০.৩৩৫. ফলে projected gradient-এর convergence-এর নিশ্চয়তা থাকে না, আর ধাপের মাপ বদলালে ফলও অদ্ভুতভাবে বদলাতে পারে। আরও বিপদ: সব ঘর ঋণাত্মক হলে w.sum() শূন্য, ভাগে NaN. ঠিক করার পথ: sort-ভিত্তিক simplex projection, অথবা softmax parameterisation (w = softmax(z), z শর্তহীন), যা নিজেই simplex-এ থাকে। Shadow price: ১.৬ একটি derivative, বাজেট ১৫৫ পর্যন্তই বৈধ (তারপর ভিডিওর সীমা ভরে যায় আর দাম শূন্য)। ১০০ থেকে ১৫০-এ বদলটি এই পরিসরের ভেতরে, তাই LP-র ভেতরে ৮০ ঠিক; কিন্তু রিপোর্টে পরিসরটি লিখতে হবে, আর মনে রাখতে হবে যে “স্থির ফেরত” মডেলটি নিজেই একটি অনুমান। যাচাইয়ের নকশা: (১) বাজেট ১২০, ১৫০, ১৬০ দিয়ে আবার solve করুন; (২) কমতে-থাকা ফেরতের মডেলেও একই প্রশ্ন চালান; (৩) ফেরতের অনুমানের অনিশ্চয়তা ধরে কয়েকটি দৃশ্যপটে ফল দেখান।

  4. আউটপুট অনুমান করুন: অংশ ৭-এর proj_simplex function ধরে নিয়ে, চালানোর আগে লিখে ফেলুন নিচের কোড কী ছাপবে।
    import numpy as np
    np.set_printoptions(precision=4, suppress=True)
    print(np.clip(np.array([1.4, -0.2, 0.7]), 0, 1))
    print(proj_simplex(np.array([0.2, 0.2, 0.2])))
    print(proj_simplex(np.array([2.0, 0.0, -1.0])))
    [1.  0.  0.7]
    [0.3333 0.3333 0.3333]
    [1. 0. 0.]

    প্রথম লাইন: box [০, ১]-এ প্রতিটি ঘর আলাদা করে কাটা, ১.৪ → ১, −০.২ → ০. দ্বিতীয়: যোগফল ০.৬ < ১, তাই θ = (০.৬ − ১)/৩ = −০.১৩৩৩, অর্থাৎ প্রতিটি ঘরে ০.১৩৩৩ যোগ হয়; projection ঘর বাড়াতেও পারে। তৃতীয়: সাজানো (২, ০, −১), j = ১-এ ২ − ১ = ১ > ০, j = ২-এ ০ − (২ − ১)/২ = −০.৫ < ০, তাই ρ = ১, θ = ১, আর w = max((১, −১, −২), ০) = (১, ০, ০)। NumPy শেষ দুই অ্যারেতে দশমিক অঙ্কের সংখ্যা অনুযায়ী ছাপে, তাই “1.” আর “0.7” দেখায়।

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

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

ক্রমঅসমতার শর্তসহ একটি ছোট সমস্যা হাতে সমাধানের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. সব শর্ত gi(x) ≤ 0 আকারে লেখা
  2. শর্তহীন সর্বনিম্ন বের করে দেখা সেটি feasible কি না
  3. কোন শর্তগুলো active তা অনুমান করে সেগুলোকে সমতা ধরা
  4. Stationarity থেকে x আর μ সমাধান করা
  5. Feasibility, μ ≥ 0 আর complementary slackness যাচাই করা; না মিললে অন্য অনুমান

x + y = 3 শর্তে f(x, y) = x² + 2y²-এর সর্বনিম্ন বিন্দু আর Lagrange multiplier কত (∇f = λ∇h রীতিতে)?

  • (1.5, 1.5), λ = 3
  • (2, 1), λ = 4
  • (3, 0), λ = 6
  • (0, 0), λ = 0

একটি সমাধানে অসমতার শর্ত g3(x*) = −1.5 (অর্থাৎ শর্তটি কড়াভাবে মানা হচ্ছে)। KKT অনুযায়ী তার multiplier μ3 কত হতে হবে?

  • যেকোনো ঋণাত্মক সংখ্যা
  • 1.5
  • যেকোনো ধনাত্মক সংখ্যা
  • 0

একটি convex সমস্যার primal সর্বনিম্ন মান f* = 6, আর Slater-এর শর্ত মানে। Dual function q(λ) সম্পর্কে কোন কথাটি সঠিক?

  • প্রতিটি λ-এ q(λ) ≤ 6, আর q-এর সর্বোচ্চ মান 6
  • প্রতিটি λ-এ q(λ) ≥ 6
  • q-এর সর্বোচ্চ মান 6-এর কম, কারণ duality gap সবসময় থাকে
  • q(λ) সব λ-এ সমান

v = (0.5, 1.2, −0.3)-কে probability simplex {w ≥ 0, ∑w = 1}-এ Euclidean projection করলে ফল কী?

  • (0.294, 0.706, 0)
  • (0.5, 1.2, 0)
  • (0.15, 0.85, 0)
  • (1/3, 1/3, 1/3)

একটি LP-তে প্রতি হাজার টাকায় ফেরত চারটি চ্যানেলে 2.4, 3.1, 1.6 আর 2.8; সীমা 50, 30, 60, 15; মোট বাজেট 100. সমাধানে 1.6-এর চ্যানেলে আংশিক খরচ হয়। বাজেটের shadow price কত?

  • 3.1
  • 1.6
  • 2.475
  • 0

কোন পরিস্থিতিতে scipy.optimize.minimize-এর method="L-BFGS-B" একা যথেষ্ট?

  • ওজন অ-ঋণাত্মক আর যোগফল 1
  • বিন্দুটি একক বৃত্তের ভেতরে থাকতে হবে
  • প্রতিটি চলক 0 থেকে 1-এর মধ্যে, আর কোনো শর্ত নেই
  • চারটি চ্যানেলের মোট খরচ 100-এর বেশি নয়

Hard-margin SVM-এর dual-এ complementary slackness থেকে কোন সিদ্ধান্ত আসে?

  • Margin থেকে দূরের উদাহরণের multiplier শূন্য, তাই শুধু support vector-রা w ঠিক করে
  • সব উদাহরণের multiplier ধনাত্মক
  • Kernel ছাড়া dual লেখা যায় না
  • Primal আর dual-এর মান সবসময় আলাদা

শব্দকোষ

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

মনে রাখুন

  1. সমতা: সর্বনিম্নে contour শর্তকে ছুঁয়ে যায়, তাই ∇f = λ∇h; Lagrangian L = f − λh-এর stationary point. λ = df*/dc, শর্তের দাম।
  2. KKT: stationarity ∇f + ∑μi∇gi + ∑λj∇hj = 0, primal feasibility, μ ≥ 0, μigi = 0. Convex সমস্যায় যথেষ্ট, সাধারণভাবে (constraint qualification-সহ) প্রয়োজনীয়।
  3. Duality: q(λ, μ) = minx L সবসময় f*-এর নিম্নসীমা; convex আর Slater-এ ফাঁক শূন্য। SVM-এর dual, kernel আর support vector এখান থেকে আসে।
  4. Projection: x ← PC(x − η∇f); box-এ clip, simplex-এ sort করে θ বাদ দিয়ে শূন্যে কাটা। শুধু bounds হলে L-BFGS-B, সাধারণ মসৃণ শর্তে SLSQP, রৈখিক হলে linprog.
  5. যাচাই: solver-এর “success” যথেষ্ট নয়; শর্তের মান, KKT residual, μ-এর চিহ্ন দেখুন, আর shadow price শর্ত বদলে আবার solve করে মেলান।

পরের পর্বে: এই পর্বে দেখেছেন, ‖w‖² ≤ t শর্ত আর α‖w‖² শাস্তি একই সমাধান দিতে পারে। পর্ব ১৭ (Regularization-এর তত্ত্ব — L1/L2-এর জ্যামিতি, MAP আর early stopping) সেই সম্পর্ককে কেন্দ্র করে দেখাবে কেন L1 শূন্য বানায়, regularization কীভাবে prior-এর সমান, আর early stopping কেন নিজেই একটি লুকানো regularization.

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

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