এই পর্বে যা শিখবেন
- Separating hyperplane, চিহ্নসহ দূরত্ব, আর functional ও geometric margin-এর পার্থক্য হাতে হিসাব করা
- সবচেয়ে চওড়া margin কেন যুক্তিসঙ্গত, আর margin থেকে hard-margin primal problem derive করা
- KKT দিয়ে support vector চেনা, আর ৪টি বিন্দুর ছোট সমস্যা হাতে সমাধান
- SciPy-র SLSQP দিয়ে primal নিজে সমাধান করে scikit-learn-এর SVC-র coef_, intercept_ আর support_vectors_-এর সঙ্গে মেলানো
- Scaling-এর প্রভাব, আর perceptron ও logistic regression-এর রেখার সঙ্গে maximum-margin রেখার তুলনা
এই পর্ব দিয়ে মডিউল ৬ শুরু। পর্ব ২৬-এ বলেছিলাম, SVM-এর score সম্ভাবনা নয়, বরং সীমারেখা থেকে একধরনের দূরত্ব; পর্ব ০২-এ hinge loss দেখেছেন, আর পর্ব ১৬-এ KKT শেষ হয়েছিল SVM-এর দিকে ইঙ্গিত দিয়ে। এখানে সেই সুতোগুলো জোড়া লাগে: দূরত্বের জ্যামিতি থেকে একটি optimization সমস্যা, আর তার সমাধানে কোন বিন্দুগুলো আসলে গুরুত্বপূর্ণ। এই পর্বে শুধু সেই ডেটা, যা একটি সরলরেখা দিয়ে পুরোপুরি আলাদা করা যায় (hard margin). চালানো যায় এমন চারটি ব্লক পাতাতেই চলে (ব্রাউজারের Python, scikit-learn 1.8.0); প্রথমবার scikit-learn নামতে প্রায় দশ সেকেন্ড লাগে।
১ · দুই দলের মাঝে একটি রেখা — কোনটি সবচেয়ে ভালো?
ঢাকার একটি চওড়া রাস্তার মাঝে ডিভাইডার বসাতে হবে, দুই পাশে দোকানের সারি। ডিভাইডার একটি দোকানের গা ঘেঁষে বসালে একটু এদিক-ওদিক হলেই ধাক্কা লাগবে। বুদ্ধিমানের কাজ হলো দুই পাশের সবচেয়ে কাছের দোকান থেকে সমান আর যতটা সম্ভব বেশি দূরে বসানো। আর লক্ষ করুন, এই অবস্থান ঠিক করে শুধু সবচেয়ে কাছের কয়েকটি দোকান; পেছনের সারির দোকান সরালেও ডিভাইডার একই জায়গায় থাকে। SVM ঠিক এই চিন্তা।
বাজারঘর কিছু অর্ডারকে প্রতারণা সন্দেহে আটকায়। দুটি feature: অর্ডারের দাম (হাজার টাকা) আর গ্রাহকের আগের সফল অর্ডারের সংখ্যা। প্রতারণার অর্ডার সাধারণত দামি আর নতুন অ্যাকাউন্টের। Label y = +১ মানে প্রতারণা, −১ মানে সাধারণ; SVM-এ ০/১-এর বদলে ±১ নিলে সূত্র সহজ হয়।
দুই মাত্রায় সীমানা একটি রেখা, তিন মাত্রায় একটি তল, আর d মাত্রায় একটি hyperplane; সব ক্ষেত্রে একই সমীকরণ:
w · x + b = 0
এখানে x একটি অর্ডারের feature-vector, w একটি ওজনের vector আর b একটি সংখ্যা (bias). w · x হলো dot product w1x1 + w2x2 + … (AI-এর ভিত্তি কোর্সের পর্ব ৩৩)। নিয়ম: f(x) = w · x + b ধনাত্মক হলে +১, ঋণাত্মক হলে −১. জ্যামিতিতে w রেখার ওপর লম্ব (normal vector): রেখার ওপর যেকোনো দুটি বিন্দু x, x′ নিলে w · (x − x′) = ০. আর w যেদিকে তাক করে, সেদিক ধনাত্মক।
ডেটা যদি কোনো একটি রেখায় পুরো আলাদা করা যায়, তাকে বলে linearly separable. তখন একটি রেখা পেলে তার আশপাশে আরও অসংখ্য রেখা পাওয়া যায়, সবগুলোর train ভুল শূন্য। Train accuracy দিয়ে এদের মধ্যে বাছাই অসম্ভব; দরকার আরেকটি মাপ। সেই মাপ হলো সবচেয়ে কাছের বিন্দু থেকে রেখার দূরত্ব।
২ · চিহ্নসহ দূরত্ব — functional আর geometric margin
পর্ব ০৭-এর projection মনে করুন। বিন্দু x থেকে hyperplane-এর লম্ব দূরত্ব মানে x-কে একক normal w/‖w‖-এর দিকে projection করে রেখা থেকে কতটা সরে আছে। ফল:
চিহ্নসহ দূরত্ব = (w · x + b) / ‖w‖
‖w‖ হলো w-এর দৈর্ঘ্য, √(w1² + w2²), অর্থাৎ L2 norm. চিহ্ন বলে বিন্দুটি কোন পাশে। একে label দিয়ে গুণ করলে দুটি মাপ পাওয়া যায়:
- Functional margin γ̂i = yi(w · xi + b): ধনাত্মক হলে শ্রেণি ঠিক। এটি পর্ব ০২-এর “margin m = y · score”, যার ওপর hinge আর log loss সংজ্ঞায়িত।
- Geometric margin γi = yi(w · xi + b)/‖w‖: আসল দূরত্ব, চিহ্নসহ। পুরো ডেটার geometric margin γ = mini γi, অর্থাৎ সবচেয়ে কাছের বিন্দুর দূরত্ব।
পার্থক্যটি মৌলিক। w আর b দুটিকেই ১০ দিয়ে গুণ করলে রেখা একই থাকে (সমীকরণের দুই দিক ১০ দিয়ে গুণ), কিন্তু functional margin ১০ গুণ হয়ে যায়। Geometric margin বদলায় না। তাই functional margin বাড়ানোকে লক্ষ্য বানানো অর্থহীন: w বড় করলেই হলো। নিচের stepper এটি সংখ্যায় দেখায়।
- রেখা আর দুটি বিন্দু। w = (৩, ৪), b = −১০. P = (৪, ২) প্রতারণা (+১), Q = (১, ১) সাধারণ (−১)।
- Score. P বসালে w · P + b = ১২ + ৮ − ১০ = ১০, ধনাত্মক, তাই রেখার +১ দিকে। এই সংখ্যাই SVM-এর decision function.
- দূরত্বে আনা। ‖w‖ = √২৫ = ৫. P রেখা থেকে ১০/৫ = ২ একক দূরে।
- Q. Score ৩ + ৪ − ১০ = −৩, দূরত্ব −০.৬. চিহ্ন ঋণাত্মক, আর Q-এর label −১, তাই শ্রেণি ঠিক।
- Label দিয়ে গুণ। Functional margin P-এর ১০, Q-এর ৩; geometric margin ২ আর ০.৬. দুটিই ধনাত্মক, তাই দুটি বিন্দুই ঠিক দিকে। রেখার margin সবচেয়ে ছোটটি, ০.৬.
- Scale বদলালে। w আর b দুটিকেই ২ দিয়ে গুণ করলে functional margin দ্বিগুণ (২০ আর ৬), কিন্তু ‖w‖ = ১০ হওয়ায় geometric margin আগের মতোই ২ আর ০.৬. রেখা একই, তাই মাপও এক থাকাই উচিত।
এই পর্বের ডেটা: বাজারঘরের ২০টি কাল্পনিক অর্ডার, seed দিয়ে বানানো, কোনো আসল দোকানের নয়; ১০টি সাধারণ, ১০টি প্রতারণা। চারটি ব্লকই এই module থেকে ডেটা নেয়।
# bazar_fraud.py — বাজারঘরের ২০টি কাল্পনিক অর্ডার (seed দিয়ে বানানো)
import numpy as np
def make_orders(seed=33, n=10):
rng = np.random.default_rng(seed)
value = np.r_[rng.normal(3.0, 0.9, n), rng.normal(6.0, 0.9, n)].round(1) # দাম, হাজার টাকা
prev = np.r_[rng.normal(7.0, 1.6, n), rng.normal(3.0, 1.6, n)].clip(0).round(0) # আগের সফল অর্ডার
X = np.c_[value, prev]
y = np.r_[-np.ones(n), np.ones(n)].astype(int) # -1 = সাধারণ, +1 = প্রতারণা
return X, yপ্রথম ব্লক তিনটি হাতে বাছা রেখা নেয়, তিনটিই সব অর্ডার ঠিক ভাগ করে। প্রতিটির জন্য functional আর geometric margin, সবচেয়ে কাছের অর্ডার, আর ছবিতে রেখার দুই পাশে margin-এর সমান দূরত্বে ফোঁটা-দাগ।
import numpy as np
import matplotlib.pyplot as plt
from bazar_fraud import make_orders
X, y = make_orders()
lines = {"A": ([5, -4], 2.0), "B": ([2, -1], -3.0), "C": ([4, -1], -11.5)}
print("line w b wrong functional geometric closest")
fig, axes = plt.subplots(1, 3, figsize=(11, 3.6), sharey=True)
gx = np.linspace(1, 7.5, 50)
for ax, (name, (w, b)) in zip(axes, lines.items()):
w = np.array(w, float)
f = y * (X @ w + b) # functional margin প্রতিটি বিন্দুর
d = f / np.linalg.norm(w) # geometric: চিহ্নসহ দূরত্ব × y
i = np.argmin(d)
print(f"{name} {str(w.astype(int)):8s} {b:5.1f} {np.sum(d < 0):3d}"
f" {f.min():9.3f} {d.min():9.3f} #{i}")
for k, ls in [(0, "-"), (d.min(), ":"), (-d.min(), ":")]:
ax.plot(gx, (w[0] * gx + b - k * np.linalg.norm(w)) / -w[1], "k", ls=ls)
ax.scatter(*X[y < 0].T, c="tab:blue", marker="o", label="normal (-1)")
ax.scatter(*X[y > 0].T, c="tab:red", marker="^", label="fraud (+1)")
ax.scatter(*X[i], s=160, facecolors="none", edgecolors="k")
ax.set(title=f"line {name}: margin {d.min():.3f}", xlim=(1, 7.5), ylim=(0, 11),
xlabel="order value (thousand taka)")
axes[0].set_ylabel("previous successful orders"); axes[0].legend(fontsize=8)
w, b = np.array([4.0, -1.0]), -11.5
print("C scaled x10: functional", (y * (X @ (10 * w) + 10 * b)).min().round(3),
" geometric", (y * (X @ w + b) / np.linalg.norm(w)).min().round(3))আসল আউটপুট (ব্রাউজারের Python), নিচে তিনটি ছবি:
line w b wrong functional geometric closest
A [ 5 -4] 2.0 0 0.500 0.078 #2
B [ 2 -1] -3.0 0 1.000 0.447 #2
C [ 4 -1] -11.5 0 2.500 0.606 #2
C scaled x10: functional 25.0 geometric 0.606
তিনটি রেখারই ভুল শূন্য, train accuracy দিয়ে আলাদা করা যায় না। কিন্তু রেখা A অর্ডার #২-এর (দাম ৩.৫ হাজার, আগের অর্ডার ৫) গা ঘেঁষে যায়: দূরত্ব মাত্র ০.০৭৮. ওই অর্ডারের মাপে সামান্য ভুল বা একটু আলাদা একটি নতুন অর্ডার এলেই রায় উল্টে যাবে। রেখা C-এর রাস্তা প্রায় আট গুণ চওড়া, ০.৬০৬. শেষ লাইন দেখায় আগের কথাটি: C-এর w আর b দশ গুণ করলে functional margin ২.৫ থেকে ২৫, অথচ geometric margin সেই ০.৬০৬. তিনটি রেখাতেই সবচেয়ে কাছের বিন্দু #২, কাকতালীয়ভাবে। প্রশ্ন হলো: সব সম্ভাব্য রেখার মধ্যে সবচেয়ে বড় geometric margin কোনটির? অংশ ৬-এ দেখবেন উত্তর ০.৬১৩, C-এর চেয়ে সামান্য বেশি।
৩ · কেন সবচেয়ে চওড়া margin
Maximum-margin classifier সব separating hyperplane-এর মধ্যে geometric margin সবচেয়ে বড় যেটির, সেটি বেছে নেয়। কেন এটি যুক্তিসঙ্গত? তিনটি যুক্তি, সহজ থেকে কঠিন:
- মাপের ভুল সহ্য করা। Margin γ হলে প্রতিটি train বিন্দুকে γ-এর কম যেকোনো দিকে সরালেও রায় বদলায় না। নতুন অর্ডার পুরোনোদের কাছাকাছি হলে চওড়া রাস্তা নিরাপদ।
- কম নমনীয় class. পর্ব ০৩-এ দেখেছেন, hypothesis class যত কম নমনীয় (VC dimension যত ছোট), train আর test ভুলের ফাঁক তত কম হওয়ার কথা। Vapnik-এর একটি পরিচিত ফল মোটামুটি এমন: সব ডেটা ব্যাসার্ধ R-এর একটি বলের ভেতরে থাকলে, অন্তত γ margin রাখে এমন hyperplane-গুলোর VC dimension সর্বোচ্চ প্রায় min(R²/γ², d) + ১. অর্থাৎ margin বড় হলে feature অনেক হলেও কার্যকর জটিলতা কম। এটি সীমা, নিশ্চয়তা নয়; বাস্তবে bound প্রায়ই ঢিলা।
- Leave-one-out যুক্তি। অংশ ৫-এ দেখবেন, সমাধান ঠিক করে শুধু support vector. যে বিন্দু support vector নয়, তাকে বাদ দিয়ে আবার শেখালে একই রেখা আসে, তাই সে বাদ পড়েও ঠিক শ্রেণি পায়। ফলে leave-one-out ভুলের হার ≤ (support vector-এর সংখ্যা)/n. আমাদের ২০টি অর্ডারে ৩টি support vector, তাই সীমা ০.১৫; অংশ ৮-এ আসল LOO ভুল মেপে দেখবেন।
ইতিহাস। সবচেয়ে চওড়া ফাঁকের hyperplane-এর ধারণা সোভিয়েত গবেষণায় ১৯৬০-এর দশকে আসে: Vladimir Vapnik আর Alexander Lerner (১৯৬৩, “generalized portrait”), পরে Vapnik আর Alexey Chervonenkis. আজকের SVM-এর পরিচিত রূপ আসে Bernhard Boser, Isabelle Guyon আর Vapnik-এর ১৯৯২ সালের COLT প্রবন্ধ থেকে, যেখানে maximum margin-এর সঙ্গে kernel যুক্ত হয় (পর্ব ২৯, Kernel trick — RBF, polynomial আর Mercer-এর অন্তর্দৃষ্টি), আর Corinna Cortes ও Vapnik-এর ১৯৯৫ সালের “Support-vector networks” প্রবন্ধে soft margin. Support vector নামটিও এই সময়ের।
৪ · Margin থেকে primal problem
সরাসরি লক্ষ্য লিখলে: maxw,b γ, শর্ত yi(w · xi + b)/‖w‖ ≥ γ সব i-তে। এটি অস্বস্তিকর, কারণ ‖w‖ ভাগে আছে। কিন্তু অংশ ২-এর স্বাধীনতা কাজে লাগানো যায়: w আর b-কে যেকোনো ধনাত্মক সংখ্যা দিয়ে গুণ করলে রেখা একই। তাই এমনভাবে scale ঠিক করুন যাতে সবচেয়ে কাছের বিন্দুর functional margin ঠিক ১ হয়:
mini yi(w · xi + b) = 1
একে বলে canonical রূপ। তখন geometric margin γ = ১/‖w‖, আর রাস্তার দুই কিনারা দুটি সমান্তরাল hyperplane: w · x + b = +১ আর w · x + b = −১. অংশ ২-এর stepper-এ রেখাটি ৩ দিয়ে ভাগ করলে Q-এর functional margin ১ হয়, ‖w‖ = ৫/৩, আর ১/‖w‖ = ০.৬, হুবহু Q-এর দূরত্ব। রাস্তার পুরো প্রস্থ কত, তা নিচে ধাপে ধাপে:
- তিনটি সমান্তরাল তল। Canonical রূপে সবচেয়ে কাছের বিন্দুগুলো w · x + b = ±১-এ বসে, সীমারেখা মাঝে ০-তে। তিনটির normal একই w.
- লম্ব বরাবর হাঁটা। (−) কিনারার একটি বিন্দু x₋ থেকে একক normal w/‖w‖-এর দিকে t দূরত্ব হাঁটলে x₊ পাওয়া যায়। লম্ব বরাবর হাঁটা মানেই সবচেয়ে ছোট দূরত্ব।
- বসিয়ে দেখা। w · w = ‖w‖², আর x₋ কিনারায় বলে w · x₋ + b = −১. তাই x₊-এর score −১ + t‖w‖.
- প্রস্থ। x₊ (+) কিনারায় থাকলে score +১, তাই t = ২/‖w‖. সীমারেখা থেকে প্রতিটি কিনারা ১/‖w‖ দূরে, যা geometric margin γ.
- সংখ্যায়। w = (০.৫, ০.৫) হলে ‖w‖ ≈ ০.৭০৭১ আর প্রস্থ ≈ ২.৮২৮. বাজারঘরের ডেটার আসল SVM (অংশ ৬) w ≈ (১.৫৮৭৩, −০.৩৮১০), প্রস্থ ≈ ১.২২৫২.
- Primal. প্রস্থ বাড়ানো মানে ‖w‖ কমানো। বর্গ আর ½ নিলে objective মসৃণ হয় আর gradient ঠিক w. Objective quadratic আর শর্ত রৈখিক: একটি convex quadratic program.
ফলে hard-margin SVM-এর primal problem:
minw, b ½‖w‖² শর্ত: yi(w · xi + b) ≥ 1, i = 1, …, n
এখানে চলক d + ১টি (w-এর d ঘর আর b), শর্ত n-টি। পর্ব ১২-এর ভাষায় objective strictly convex in w, আর শর্তগুলো রৈখিক, তাই ডেটা আলাদা করা গেলে w-এর সমাধান ঠিক একটি, কোনো local minimum-এর ফাঁদ নেই। লক্ষ করুন, b-এর ওপর কোনো শাস্তি নেই। আর ½‖w‖² ঠিক পর্ব ১৭-এর L2 শাস্তির রূপ: চওড়া margin খোঁজা আর ছোট ওজন খোঁজা একই কথা।
Hard-margin SVM-এর জ্যামিতি — চারটি বিন্দুর উদাহরণ
Separating hyperplane, the two margin hyperplanes, the normal vector w and the support vectors
৫ · Support vector আর KKT
পর্ব ১৬-এর পদ্ধতি সরাসরি খাটে। প্রতিটি শর্তের জন্য একটি Lagrange multiplier αi ≥ ০ নিয়ে Lagrangian:
L(w, b, α) = ½‖w‖² − ∑i αi [ yi(w · xi + b) − 1 ]
Convex objective, রৈখিক শর্ত আর আলাদা করা যায় এমন ডেটা, তাই KKT শর্ত সমাধানের জন্য প্রয়োজনীয় আর যথেষ্ট দুটিই। চারটি শর্ত SVM-এর ভাষায়:
- Stationarity: ∂L/∂w = ০ থেকে w = ∑i αiyixi; ∂L/∂b = ০ থেকে ∑i αiyi = ০. অর্থাৎ w হলো train বিন্দুগুলোর ওজনযুক্ত যোগফল।
- Primal feasibility: yi(w · xi + b) ≥ ১, সব বিন্দু রাস্তার বাইরে বা কিনারায়।
- Dual feasibility: αi ≥ ০.
- Complementary slackness: αi[yi(w · xi + b) − ১] = ০. প্রতিটি বিন্দুতে হয় শর্তটি ঠিক সমতায় (active), নয়তো αi = ০.
শেষ শর্তটিই SVM-এর নাম দিয়েছে। যে বিন্দু কিনারার বাইরে, তার αi = ০, তাই w-এর যোগফলে তার কোনো অবদান নেই। যাদের αi > ০, তারা অবশ্যই কিনারায়: এরাই support vector, যারা রেখাটিকে “ধরে রাখে”। (উল্টোটা সবসময় সত্য নয়: কিনারায় বসা কোনো বিন্দুর αi কখনো কখনো ০ হতে পারে; সাধারণত ডেটায় এটি বিরল।) α থেকে b পেতে যেকোনো support vector নিন: ys(w · xs + b) = ১, তাই b = ys − w · xs. এই α-গুলো দিয়ে লেখা dual problem আর তার derivation আসবে পর্ব ২৮-এ (Soft margin — hinge loss, C আর dual problem). এখন ছবি ২৭.১-এর চারটি বিন্দু হাতে সমাধান করুন:
- ডেটা। দুটি প্রতারণা, দুটি সাধারণ। লক্ষ্য w, b, কোন বিন্দু support vector আর margin কত।
- সবচেয়ে কাছের জোড়া। দুই শ্রেণির চারটি জোড়ার মধ্যে A আর C সবচেয়ে কাছে, √৮ ≈ ২.৮৩. (সাধারণ নিয়মে দুই শ্রেণির convex hull-এর সবচেয়ে কাছের দুটি বিন্দু লাগে, যা কোণের বিন্দু না-ও হতে পারে; এখানে A আর C-ই তা, যাচাই করা হয়েছে।)
- লম্ব-সমদ্বিখণ্ডক। সেরা সীমারেখা A আর C-এর ঠিক মাঝ দিয়ে, AC-এর ওপর লম্ব: মধ্যবিন্দু (২, ২), normal (১, ১)-এর দিকে। তাই x₁ + x₂ = ৪, অর্থাৎ w = (k, k), b = −৪k.
- Scale ঠিক করা। Canonical রূপে A-এর functional margin ১: ২k = ১, k = ০.৫. ফল w = (০.৫, ০.৫), b = −২.
- যাচাই। A আর C-এ মান ঠিক ১ (active), B-তে ২, D-তে ১.৫. সব শর্ত মানে, আর support vector A ও C. প্রস্থ ২/‖w‖ = ২.৮৩, ঠিক AC-এর দূরত্ব।
- Multiplier. B আর D কিনারার বাইরে, তাই α = ০. ∑αᵢyᵢ = ০ থেকে αA = αC; w = α(২, ২) = (০.৫, ০.৫) থেকে α = ০.২৫. scikit-learn-এর
dual_coef_αᵢyᵢ দেয়: C-এর −০.২৫, A-এর ০.২৫.
৬ · হাতে-কলমে — SLSQP দিয়ে নিজে সমাধান, তারপর SVC
Primal-টি একটি সাধারণ constrained সমস্যা, তাই SciPy-র minimize(method="SLSQP") সরাসরি পারে (SLSQP: sequential least squares programming, পর্ব ১৬-এ দেখা)। চলক v = (w1, w2, b); "ineq" শর্ত মানে ফাংশনটির প্রতিটি মান ≥ ০. তারপর scikit-learn-এর SVC(kernel="linear", C=1e6). scikit-learn-এ আলাদা hard-margin বিকল্প নেই; SVC সবসময় soft margin সমাধান করে, আর C খুব বড় দিলে আলাদা করা যায় এমন ডেটায় তা hard margin-এর সমান হয় (C-এর অর্থ পর্ব ২৮-এ)।
import numpy as np
from scipy.optimize import minimize
from sklearn.svm import SVC
from bazar_fraud import make_orders
X, y = make_orders()
np.set_printoptions(precision=4, suppress=True)
# চলক v = (w1, w2, b); লক্ষ্য ½‖w‖²; শর্ত yᵢ(w·xᵢ + b) − 1 ≥ 0
cons = {"type": "ineq", "fun": lambda v: y * (X @ v[:2] + v[2]) - 1}
res = minimize(lambda v: 0.5 * v[:2] @ v[:2], x0=np.zeros(3),
method="SLSQP", constraints=[cons], options={"ftol": 1e-12})
w, b = res.x[:2], res.x[2]
g = y * (X @ w + b) # প্রতিটি শর্তের বাঁ দিক
active = np.flatnonzero(g < 1 + 1e-6)
print("SLSQP :", res.success, "w =", w, "b =", round(b, 4))
print(" margin 2/|w| =", round(2 / np.linalg.norm(w), 4), " active:", active)
svc = SVC(kernel="linear", C=1e6).fit(X, y)
print("SVC : w =", svc.coef_[0], "b =", svc.intercept_.round(4))
print(" margin 2/|w| =", round(2 / np.linalg.norm(svc.coef_), 4),
" support_:", svc.support_)
print("support_vectors_:\n", svc.support_vectors_)
alpha_y = svc.dual_coef_[0] # প্রতিটি support vector-এর αᵢyᵢ
print("alpha_i * y_i :", alpha_y, " sum =", round(alpha_y.sum(), 6) + 0.0)
print("sum alpha*y*x :", alpha_y @ svc.support_vectors_, " (= w)")
print("y(w.x+b) per order:\n", g.round(3))আসল আউটপুট (ব্রাউজারের Python):
SLSQP : True w = [ 1.5873 -0.381 ] b = -4.6508
margin 2/|w| = 1.2252 active: [ 2 8 16]
SVC : w = [ 1.5871 -0.381 ] b = [-4.6498]
margin 2/|w| = 1.2254 support_: [ 2 8 16]
support_vectors_:
[[ 3.5 5. ]
[ 4.7 10. ]
[ 5. 6. ]]
alpha_i * y_i : [-0.9895 -0.3426 1.3322] sum = 0.0
sum alpha*y*x : [ 1.5871 -0.381 ] (= w)
y(w.x+b) per order:
[1.54 3.73 1. 2.175 5.54 1.127 1.952 1.603 1. 1.476 1.762 2.397
5.159 1.286 4.429 3.444 1. 4.429 4.524 2.619]
দুই পথ, এক উত্তর। নিজে লেখা SLSQP আর SVC একই তিনটি অর্ডার বেছেছে, #২, #৮ আর #১৬, আর w, b, margin চতুর্থ দশমিকের কাছাকাছি পর্যন্ত মিলেছে। ছোট পার্থক্য আসে SVC-র থামার সহনশীলতা থেকে (ডিফল্ট tol=1e-3). geometric margin ১/‖w‖ ≈ ০.৬১৩, অংশ ২-এর সেরা হাতের রেখা C-এর (০.৬০৬) চেয়ে সামান্য বেশি, যেমন হওয়ার কথা।
KKT মিলিয়ে দেখা। শেষ লাইনের ২০টি সংখ্যার ঠিক তিনটি ১, বাকিগুলো ১-এর বেশি: শুধু support vector-এর শর্ত active. dual_coef_ প্রতিটি support vector-এর αiyi দেয়: দুটি সাধারণ অর্ডারের ঋণাত্মক, প্রতারণার ধনাত্মক, যোগফল ০ (∑αiyi = ০), আর ∑αiyixi হুবহু coef_ (stationarity). লক্ষ করুন, #৮ (দাম ৪.৭ হাজার, আগের অর্ডার ১০) একটি দামি অর্ডার, কিন্তু পুরোনো বিশ্বস্ত গ্রাহকের; রেখার ঢাল ঠিক করতে সে ভূমিকা রাখে। support_ সূচিগুলো শ্রেণির ক্রমে সাজানো থাকে, আগে −১ শ্রেণি।
৭ · Scaling কেন জরুরি — margin একটি দূরত্ব
Margin মাপা হয় সাধারণ Euclidean দূরত্বে, যেখানে প্রতিটি feature-এর এক একক সমান গুরুত্ব পায়। দাম হাজার টাকায় লিখলে “১” মানে এক হাজার টাকা; টাকায় লিখলে “১” মানে এক টাকা, আর একটি আগের অর্ডারের পার্থক্য এক টাকার সমান দূরত্ব পায়। সমস্যাটি AI-এর ভিত্তি কোর্সের পর্ব ৫৭-এর k-NN-এর মতো। সমাধানও একই: standardization, আর leakage এড়াতে StandardScaler Pipeline-এর ভেতরে (AI-এর ভিত্তি কোর্সের পর্ব ৬৪)। নিচের ব্লক একই ২০টি অর্ডারে চারটি SVM শেখায়: দাম হাজার টাকায় আর টাকায়, প্রতিটি scaling ছাড়া আর সহ। w মূল এককে (প্রতি হাজার টাকা, প্রতি আগের অর্ডার) ফিরিয়ে ছাপা, যাতে তুলনা চলে; শেষে দুটি নতুন অর্ডারের রায়।
import numpy as np
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from bazar_fraud import make_orders
X, y = make_orders() # দাম হাজার টাকায়
X_taka = X * [1000, 1] # একই দাম, এবার টাকায়
new = np.array([[4.5, 6], [5.6, 8]]) # দুটি নতুন অর্ডার (হাজার টাকা, আগের অর্ডার)
print("units support w (per thousand taka, per order) new orders")
for name, A, scale in [("thousand taka", X, 1), ("taka", X_taka, 1000)]:
for std in (False, True):
steps = [StandardScaler()] if std else []
m = make_pipeline(*steps, SVC(kernel="linear", C=1e6)).fit(A, y)
svc = m[-1]
w = svc.coef_[0] / (m[0].scale_ if std else 1) * [scale, 1] # মূল এককে ফেরানো
pred = m.predict(new * [scale, 1])
print(f"{name:13s} {'std' if std else 'raw':3s} {str(svc.support_):10s}"
f" [{w[0]:7.3f} {w[1]:7.3f}] {pred}")
print("spread (max - min): value", np.ptp(X_taka[:, 0]), "taka,",
"previous orders", np.ptp(X[:, 1]))আসল আউটপুট (ব্রাউজারের Python):
units support w (per thousand taka, per order) new orders
thousand taka raw [ 2 8 16] [ 1.587 -0.381] [1 1]
thousand taka std [ 2 8 16] [ 1.587 -0.381] [1 1]
taka raw [ 8 13 16] [ 2.222 -0.333] [-1 1]
taka std [ 2 8 16] [ 1.587 -0.381] [1 1]
spread (max - min): value 5300.0 taka, previous orders 8.0
দাম টাকায় আর scaling ছাড়া (taka raw) support vector বদলে #৮, #১৩, #১৬, আর রেখার ঢালও বদলেছে: দামের ওজন ২.২২২, আগের অর্ডারের −০.৩৩৩. কারণ শেষ লাইনে: টাকায় দামের ছড়ানো ৫,৩০০ একক, আগের অর্ডারের মাত্র ৮; দূরত্বের হিসাবে দামই প্রায় সব। ফল: নতুন অর্ডার (৪,৫০০ টাকা, ৬টি আগের অর্ডার) একক বদলানোর কারণেই “প্রতারণা” থেকে “সাধারণ” হয়ে গেছে। কিছুই বাস্তবে বদলায়নি, শুধু লেখার ধরন। StandardScaler দিলে দুই একক থেকেই হুবহু একই support vector আর একই রেখা আসে। এখানে তা scaling ছাড়া হাজার-টাকার রেখার সঙ্গেও মিলেছে, কারণ দুই ক্ষেত্রেই একই তিনটি বিন্দু active, আর দুই মাত্রায় তিনটি active শর্ত একটি রেখা পুরো ঠিক করে দেয়। সাধারণভাবে এমন মিল আশা করবেন না।
সাবধান: scaling ছাড়া বড় সংখ্যার feature-এ SVC-র solver নিজেও ধীর বা অসম্পূর্ণ হতে পারে। এই ব্লকের seed খোঁজার সময় অন্য একটি seed-এ টাকার এককে SVC এমন সমাধানে থেমেছিল, যেখানে সবচেয়ে ছোট y(w · x + b) ছিল ০.৬২৫, ১ নয় (ডেস্কটপে scikit-learn 1.8.0, একটি run). তাই scaling শুধু ন্যায্য দূরত্বের জন্য নয়, solver-এর জন্যও।
৮ · Perceptron, logistic আর maximum margin — কোন বিন্দু সরালে কী হয়
আলাদা করা যায় এমন ডেটায় আরও দুটি পরিচিত পদ্ধতি রেখা টানে। Perceptron (Frank Rosenblatt, ১৯৫৮; AI-এর ভিত্তি কোর্সের পর্ব ৬৮) ভুল বিন্দু পেলেই w ← w + yx করে, আর কোনো ভুল না থাকলে থামে। Albert Novikoff ১৯৬২ সালে দেখান, margin γ আর ডেটার ব্যাসার্ধ R হলে perceptron সর্বোচ্চ (R/γ)² বার ভুল করে থামে (মূলবিন্দু দিয়ে যাওয়া রেখার রূপে)। কিন্তু থামে প্রথম যে রেখা পায় সেখানেই: সেটি কোথায়, তা নির্ভর করে বিন্দুগুলো কোন ক্রমে এসেছে তার ওপর। Logistic regression (AI-এর ভিত্তি কোর্সের পর্ব ৫৯) log loss কমায়, যেখানে প্রতিটি বিন্দুর টান আছে, দূরের বিন্দুরও।
নিচের ব্লক চারটি রেখা তুলনা করে: ১০ লাইনের perceptron দুটি আলাদা ক্রমে (seed ০ আর ১), ডিফল্ট শাস্তির (C = ১) logistic regression, আর SVM. তারপর পরীক্ষা: একটি সাধারণ বিন্দু (#০) বাদ দিয়ে, আর একটি support vector (#১৬) বাদ দিয়ে আবার SVM, আর বাদ পড়া বিন্দুটির রায়।
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from bazar_fraud import make_orders
X, y = make_orders()
def margin(w, b): # সবচেয়ে কাছের বিন্দুর চিহ্নসহ দূরত্ব
return (y * (X @ w + b)).min() / np.linalg.norm(w)
def perceptron(seed): # Rosenblatt: ভুল হলে w += y·x
w, b, rng = np.zeros(2), 0.0, np.random.default_rng(seed)
for _ in range(100):
for i in rng.permutation(len(y)):
if y[i] * (X[i] @ w + b) <= 0:
w, b = w + y[i] * X[i], b + y[i]
return w, b
lr = LogisticRegression().fit(X, y)
svm = SVC(kernel="linear", C=1e6).fit(X, y)
fits = {"perceptron seed 0": perceptron(0), "perceptron seed 1": perceptron(1),
"logistic (C=1)": (lr.coef_[0], lr.intercept_[0]),
"SVM": (svm.coef_[0], svm.intercept_[0])}
gx = np.linspace(1, 7.5, 50)
plt.scatter(*X[y < 0].T, c="tab:blue"); plt.scatter(*X[y > 0].T, c="tab:red", marker="^")
for name, (w, b) in fits.items():
print(f"{name:18s} margin {margin(w, b):.3f}")
plt.plot(gx, -(w[0] * gx + b) / w[1], label=name, lw=3 if name == "SVM" else 1.5)
plt.scatter(*svm.support_vectors_.T, s=170, facecolors="none", edgecolors="k")
plt.ylim(0, 11); plt.legend(fontsize=8); plt.xlabel("order value (thousand taka)")
for drop in (0, 16): # #0 সাধারণ বিন্দু, #16 support vector
keep = np.arange(len(y)) != drop
m = SVC(kernel="linear", C=1e6).fit(X[keep], y[keep])
print(f"drop #{drop:2d}: w = {m.coef_[0].round(3)}, b = {m.intercept_[0]:.3f},"
f" 2/|w| = {2 / np.linalg.norm(m.coef_):.3f}, left-out -> {m.predict(X[[drop]])[0]:+d}")আসল আউটপুট (ব্রাউজারের Python), নিচে চারটি রেখার ছবি:
perceptron seed 0 margin 0.105
perceptron seed 1 margin 0.185
logistic (C=1) margin 0.188
SVM margin 0.613
drop # 0: w = [ 1.587 -0.381], b = -4.650, 2/|w| = 1.225, left-out -> -1
drop #16: w = [ 0.984 -0.82 ], b = -0.344, 2/|w| = 1.562, left-out -> -1
রেখার তুলনা। চারটি রেখাই সব অর্ডার ঠিক ভাগ করে, কিন্তু perceptron-এর margin ০.১০৫ আর ০.১৮৫: একই algorithm, শুধু ক্রম বদলালে আলাদা রেখা। Logistic regression (০.১৮৮) সব বিন্দুর টানে একটি আপস করে; এখানে ডিফল্ট L2 শাস্তিও তাকে টানে। SVM-এর ০.৬১৩ তিনটির তিন গুণের বেশি। একটি সূক্ষ্ম যোগসূত্র: শাস্তিহীন logistic regression-এ gradient descent আলাদা করা যায় এমন ডেটায় ধীরে ধীরে maximum-margin রেখার দিকেই ঘোরে (ভাবনার প্রশ্ন ৩)।
বিন্দু সরালে। #০ (সাধারণ, কিনারা থেকে দূরে) বাদ দিলে w, b আর প্রস্থ হুবহু আগের মতো, আর #০ নিজে ঠিক শ্রেণি (−১) পায়। Support vector #১৬ (৫ হাজার টাকা, আগের অর্ডার ৬) বাদ দিলে রেখা বদলে যায়, প্রস্থ ১.২২৫ থেকে ১.৫৬২, আর নতুন রেখা #১৬-কেই ভুল শ্রেণি (−১) দেয়। ২০টি বিন্দুর প্রতিটি একবার করে বাদ দিয়ে আলাদা run-এ দেখা গেছে, শুধু #১৬-ই ভুল হয়: leave-one-out ভুল ১/২০ = ০.০৫, অংশ ৩-এর সীমা ৩/২০ = ০.১৫-এর ভেতরে।
৯ · ডেটা আলাদা না হলে — hard margin-এর সীমা
Hard margin-এর সবচেয়ে বড় দুর্বলতা: একটি বিন্দুও ভুল দিকে থাকলে শর্তগুলো একসঙ্গে মানা অসম্ভব, primal-এর কোনো সমাধানই নেই। আর একটি ভুল label বা outlier support vector হয়ে পুরো রেখা টেনে নিতে পারে, যেমন #১৬ সরালেই রেখা বদলেছে। বাস্তবের প্রতারণার label প্রায়ই অসম্পূর্ণ (পর্ব ০৫)। নিচে একই ২০টি অর্ডারের সঙ্গে সাধারণদের মাঝে বসা একটি প্রতারণার অর্ডার (৩ হাজার টাকা, ৭টি আগের অর্ডার) যোগ করে primal আর SVC; ডেস্কটপে সত্যিকারের run (Python 3.14.4, SciPy 1.18.1, scikit-learn 1.8.0):
~/bazarghar-ml$ python nonseparable.py
scipy 1.18.1 | success: False | Positive directional derivative for linesearch
w, b = [0. 0. 0.] | smallest y(w.x+b) - 1 = -1.0 (needs >= 0)
sklearn 1.8.0 | SVC C=1e6 train accuracy: 0.9523809523809523 | support vectors: 5
SLSQP ব্যর্থতা জানায় আর শুরুর বিন্দুতেই আটকে থাকে, যেখানে সব শর্ত ভাঙা। SVC কোনো ত্রুটি দেখায় না, কারণ সে আসলে soft margin সমাধান করে: ২১টির একটিকে ভুল দিকে রেখে (train accuracy ৯৫.২%) একটি রেখা দেয়। তাই “SVC চলেছে” মানে “ডেটা আলাদা করা যায়” নয়। সমাধান হলো শর্তে ঢিল দেওয়া: প্রতিটি বিন্দুকে একটি slack ξi দেওয়া আর C দিয়ে তার দাম ঠিক করা। সেটিই পর্ব ২৮ (Soft margin — hinge loss, C আর dual problem). আর রেখা দিয়ে আলাদা না হলে বাঁকা সীমানা: পর্ব ২৯ (Kernel trick).
আরও গভীরে: Hard-margin SVM আর “দুই convex hull-এর সবচেয়ে কাছের দুটি বিন্দু” খোঁজা একে অন্যের dual; Kristin Bennett আর Erin Bredensteiner ২০০০ সালে এই জ্যামিতিক ব্যাখ্যা বিস্তারিত লেখেন। বড় ডেটায় সাধারণ QP solver ধীর, তাই John Platt ১৯৯৮ সালে SMO প্রস্তাব করেন, যা প্রতিবার মাত্র দুটি α বদলায়; scikit-learn-এর SVC যে LIBSVM (Chih-Chung Chang আর Chih-Jen Lin) ব্যবহার করে, তা এই ধারার একটি solver. Margin দিয়ে generalisation-এর সীমার আধুনিক রূপ (fat-shattering dimension, Rademacher complexity) John Shawe-Taylor, Peter Bartlett ও সহকর্মীদের ১৯৯০-এর দশকের শেষের কাজে আসে।
পরীক্ষা ও ইন্টারভিউতে:
- বিন্দু থেকে hyperplane-এর দূরত্ব? |w · x + b|/‖w‖; চিহ্নসহ রাখলে পাশও বলে।
- Margin-এর প্রস্থ কেন ২/‖w‖? Canonical রূপে কিনারা w · x + b = ±১; লম্ব বরাবর এক থেকে অন্যটিতে যেতে t‖w‖ = ২.
- Hard-margin primal? min ½‖w‖², শর্ত yi(w · xi + b) ≥ ১; convex QP.
- Support vector কারা, KKT দিয়ে? Complementary slackness: αi > ০ হলে yi(w · xi + b) = ১; w = ∑αiyixi, ∑αiyi = ০.
- SVM-এ scaling কেন? Margin Euclidean দূরত্ব; বড় এককের feature দূরত্বে আধিপত্য করে, সমাধান একক-নির্ভর হয়।
১০ · AI-এর যুগে — SVM-এর কোড আর গণিত যাচাই
AI assistant SVM-এর কোড সহজে লেখে, আর derivation-ও সুন্দর করে লেখে। কিন্তু margin-এর সংজ্ঞা, scaling আর solver-এর আচরণে ভুল খুব চেনা:
| AI প্রায়ই যা করে | কেন ভুল | কীভাবে ধরবেন |
|---|---|---|
SVC(hard_margin=True) জাতীয় argument বানায় | এমন argument নেই; 1.8.0-এ TypeError | Docs; hard margin মানে আলাদা করা যায় এমন ডেটায় খুব বড় C |
| Margin = ১/‖w‖ আর ২/‖w‖ গুলিয়ে ফেলে | একটি এক পাশের দূরত্ব, অন্যটি পুরো প্রস্থ | ৪টি বিন্দুর উদাহরণে হাতে মিলিয়ে দেখা |
RBF SVC-তে coef_ পড়ে feature-এর গুরুত্ব বলে | শুধু linear kernel-এ আছে; অন্যথায় AttributeError | Kernel দেখা; গুরুত্বের জন্য পর্ব ৫৬-এর পদ্ধতি |
| Scaling বাদ দেয়, বা পুরো ডেটায় scaler fit করে তারপর ভাগ করে | সমাধান একক-নির্ভর; বা test-এর তথ্য ঢুকে যায় | Pipeline-এর ভেতরে StandardScaler |
| Train accuracy ১০০% দেখে বলে “ডেটা আলাদা, model নিখুঁত” | বড় C-তেও SVC soft; আর train নিখুঁত মানে test নিখুঁত নয় | সব শর্ত y(w · x + b) ≥ ১ নিজে যাচাই; তালাবন্ধ test |
decision_function-কে সম্ভাবনা বলে | এটি চিহ্নসহ দূরত্ব-জাতীয় score | দরকার হলে calibration (পর্ব ২৬) |
AI-কে কোডের সঙ্গে যাচাইও চাইতে বলুন:
AI-এর দেওয়া SVM হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১১৯৯০-এর দশকের শেষ থেকে প্রায় এক দশক SVM ছিল machine learning-এর অন্যতম প্রধান পদ্ধতি। কেন, আর পরে তার জায়গা কমল কেন?
SVM-এর উত্থানের পেছনে কয়েকটি গুণ একসঙ্গে কাজ করেছিল। প্রথম, তত্ত্ব। Vapnik-এর statistical learning theory একটি স্পষ্ট গল্প দিত: margin বড় রাখলে feature অনেক হলেও generalisation নিয়ন্ত্রণে থাকে। সেই সময়ের neural network-এর তুলনায় এটি অনেক বেশি “ব্যাখ্যাযোগ্য” মনে হতো। দ্বিতীয়, optimization. Primal আর dual দুটিই convex, তাই local minimum-এর ভয় নেই, একই ডেটায় সবাই একই উত্তর পায়; neural network-এর প্রশিক্ষণ তখন অনেক বেশি কারিগরি অভিজ্ঞতা-নির্ভর ছিল। তৃতীয়, kernel. Boser, Guyon আর Vapnik ১৯৯২ সালে maximum margin-এর সঙ্গে kernel যোগ করায় একই কাঠামোয় বাঁকা সীমানা শেখা গেল (পর্ব ২৯)। চতুর্থ, ফলাফল: হাতের লেখা অঙ্ক চেনা আর লেখা-শ্রেণিবিভাগের মতো সমস্যায় SVM সে সময়ের সেরা ফলের কাছাকাছি বা সমান ফল দেখায়; Thorsten Joachims ১৯৯৮ সালে লেখা-শ্রেণিবিভাগে linear SVM-এর শক্তি দেখান। পঞ্চম, সহজলভ্য সফটওয়্যার: LIBSVM আর SVMlight অনেকে বিনা খরচে ব্যবহার করতে পারতেন।
জায়গা কমার কারণও কয়েকটি, আর এগুলো নিয়ে আলোচনায় মতভেদ আছে। ছবি, শব্দ আর ভাষার মতো কাঁচা ডেটায় ২০১২ সালের পর থেকে deep learning অনেক এগিয়ে যায়, কারণ সেখানে feature নিজেই শেখা হয়, আর kernel হাতে বাছতে হয়। Kernel SVM-এর খরচ নমুনার সংখ্যার সঙ্গে দ্রুত বাড়ে (পর্ব ৩১), তাই লক্ষ লক্ষ নমুনায় অসুবিধা। আর tabular ডেটায় gradient boosting প্রায়ই ভালো করে বলে অনেক তুলনায় দেখা যায় (মডিউল ৭)। তবু linear SVM লেখা-শ্রেণিবিভাগে এখনো একটি শক্ত baseline, আর margin, support vector আর kernel-এর ধারণাগুলো আধুনিক তত্ত্বে বারবার ফিরে আসে।
মূল উপলব্ধি: একটি পদ্ধতির জনপ্রিয়তা নির্ভর করে তত্ত্ব, optimization, সফটওয়্যার আর ডেটার ধরনের মিলনের ওপর; এর কোনোটি বদলালে সেরা পদ্ধতিও বদলায়, কিন্তু ভালো ধারণা টিকে থাকে।
প্র ০২চওড়া margin ভালো generalisation আনে, এই দাবি কতটা শক্ত? তত্ত্ব কী বলে, আর কোথায় সাবধান হতে হয়?
তিন স্তরে দেখুন। প্রথম স্তর অন্তর্দৃষ্টি: চওড়া রাস্তা মাপের ছোট ভুল সহ্য করে। এটি সত্য, কিন্তু শুধু তখন, যখন নতুন বিন্দু পুরোনোদের কাছাকাছি আসে, আর দূরত্বের মাপটি অর্থপূর্ণ। অংশ ৭ দেখিয়েছে, একক বদলালে “কাছাকাছি”-র অর্থই বদলে যায়; তাই margin-এর সব যুক্তি একটি নির্দিষ্ট দূরত্বের মাপের ওপর দাঁড়িয়ে।
দ্বিতীয় স্তর VC আর margin bound. পর্ব ০৩-এ দেখেছেন, d মাত্রায় সব hyperplane-এর VC dimension d + ১, যা feature অনেক হলে বিশাল। Vapnik দেখান, ডেটা ব্যাসার্ধ R-এর বলে থাকলে আর margin অন্তত γ রাখতে হলে কার্যকর জটিলতা মোটামুটি R²/γ²-এর ওপর নির্ভর করে, d-এর ওপর নয়। পরে Shawe-Taylor, Bartlett, Williamson আর Anthony ১৯৯৮ সালে এমন সীমাকে ডেটা-নির্ভর margin-এর জন্য কঠোরভাবে প্রমাণ করেন। কিন্তু এই সীমাগুলোর সংখ্যা প্রায়ই এত ঢিলা যে বাস্তব ভুলের হার অনুমানে কাজে লাগে না; এগুলো প্রবণতা বোঝায়, পূর্বাভাস দেয় না।
তৃতীয় স্তর leave-one-out. এটি সবচেয়ে সরল আর হুবহু: support vector নয় এমন বিন্দু বাদ দিলে সমাধান বদলায় না, তাই LOO ভুল ≤ support vector/n. আমাদের ডেটায় সীমা ০.১৫, আসল ০.০৫. তবে এটিও শুধু LOO-র সীমা; ডেটার বণ্টন বদলালে কিছুই বলে না। আর hard margin-এ একটি ভুল label support vector হয়ে margin সংকুচিত করতে পারে, তাই বাস্তবে soft margin আর C-এর বাছাই (cross-validation-এ) দরকার।
আধুনিক দিক থেকে একটি মজার মিল: বড় neural network-এর generalisation ব্যাখ্যায়ও margin-ভিত্তিক মাপ নিয়ে গবেষণা চলছে, কিন্তু সেখানে বিষয়টি এখনো মীমাংসিত নয়।
মূল উপলব্ধি: margin-এর যুক্তি একটি ভালো inductive bias আর কিছু নির্দিষ্ট সীমা দেয়, কিন্তু সত্যিকারের ভুলের হার জানতে এখনো আলাদা test আর cross-validation-ই ভরসা।
প্র ০৩শাস্তিহীন logistic regression আলাদা করা যায় এমন ডেটায় কখনো থামে না, ওজন বাড়তেই থাকে। তবু বলা হয় তার রেখা শেষে SVM-এর রেখার দিকে যায়। এটি কীভাবে?
পর্ব ১২-এ দেখেছেন, ডেটা আলাদা করা গেলে log loss-এর minimum সসীম জায়গায় নেই: w-কে একটি separating দিকে বড় করতে থাকলে loss শূন্যের দিকে নামে। তাই gradient descent চলতেই থাকে, ‖w‖ মোটামুটি log-এর হারে বাড়ে। প্রশ্ন হলো, w-এর দিক কোথায় যায়?
Daniel Soudry, Elad Hoffer, Mor Shpigel Nacson, Suriya Gunasekar আর Nathan Srebro ২০১৮ সালে (JMLR, “The implicit bias of gradient descent on separable data”) দেখান, এই অবস্থায় w/‖w‖ hard-margin SVM-এর দিকের দিকে এগোয়, তবে খুব ধীরে, মোটামুটি ১/log(t) হারে। অন্তর্দৃষ্টি: ‖w‖ বড় হলে log loss-এর প্রতিটি পদ প্রায় e−margin-এর মতো, আর exponential-এর যোগফলে সবচেয়ে ছোট margin-এর পদটিই আধিপত্য করে। ফলে সবচেয়ে কাছের বিন্দুগুলো, অর্থাৎ support vector, ধীরে ধীরে দিক ঠিক করে দেয়, আর লক্ষ্য দাঁড়ায় “সবচেয়ে ছোট margin যতটা সম্ভব বড় করা”।
এর তিনটি শিক্ষা। এক, “model কোন loss কমাচ্ছে” ছাড়াও “কোন optimizer কোথা থেকে শুরু করে কতক্ষণ চলছে” ফল ঠিক করে; একে implicit regularization বলা হয়। দুই, অংশ ৮-এর logistic রেখা SVM থেকে দূরে, কারণ তাতে ডিফল্ট L2 শাস্তি আছে আর ওজন সসীম; শাস্তি কমিয়ে অনেকক্ষণ চালালে তবেই দিকটি কাছে আসে, তাও ধীরে। তিন, deep learning-এর generalisation ব্যাখ্যার একটি ধারা এখান থেকে অনুপ্রেরণা নেয়, তবে বড় non-convex network-এ ফলগুলো আরও শর্তসাপেক্ষ, আর গবেষণা চলছে।
ব্যবহারিক দিক: logistic regression-এ separation হলে coefficient আর সম্ভাবনা অর্থহীন রকম চরম হয় (পর্ব ১৮), যদিও রেখার দিক ভালো হতে পারে। তাই হয় শাস্তি দিন, নয়তো সরাসরি maximum margin চাইলে SVM ব্যবহার করুন।
মূল উপলব্ধি: আলাদা করা যায় এমন ডেটায় gradient descent নিজেই maximum margin-এর দিকে টানে; SVM সেই লক্ষ্যটি স্পষ্ট করে লিখে একবারে সমাধান করে।
প্র ০৪বাজারঘরের প্রতারণার label আসে দেরিতে আর অসম্পূর্ণভাবে। Hard-margin SVM এই ডেটায় কেন বিপজ্জনক, আর বিকল্প পরিকল্পনা কী?
প্রতারণার label সাধারণত আসে ঘটনার পরে: গ্রাহক অভিযোগ করেন, পেমেন্ট ফেরত যায়, বা courier টাকা পায় না। কিছু প্রতারণা কখনো ধরাই পড়ে না, তাই “সাধারণ” label পাওয়া কিছু অর্ডার আসলে প্রতারণা। আবার সৎ গ্রাহকের ভুল বোঝাবুঝিতেও কখনো অর্ডার প্রতারণা হিসেবে চিহ্নিত হয়। এই label noise নিয়ে পর্ব ০৫-এ বিস্তারিত দেখেছেন।
Hard margin এই noise-এর প্রতি সবচেয়ে সংবেদনশীল। প্রথম, একটি ভুল label ডেটাকে আলাদা করার অযোগ্য করে দিলে সমাধানই থাকে না; অংশ ৯-এর run-এ SLSQP ব্যর্থ। দ্বিতীয়, ভুল label-টি যদি কোনোমতে রেখার সঠিক দিকে থাকে, কিন্তু অন্য শ্রেণির খুব কাছে, তবে সে support vector হয়ে margin সরু করে আর রেখা ঘুরিয়ে দেয়। অংশ ৮-এ দেখেছেন, একটি support vector সরালেই প্রস্থ ১.২২৫ থেকে ১.৫৬২ হয়ে যায়; উল্টো দিকে, একটি ভুল বিন্দু যোগ হলেও একই মাত্রার পরিবর্তন সম্ভব। অর্থাৎ পুরো model কয়েকটি বিন্দুর সততার ওপর দাঁড়িয়ে।
বিকল্প পরিকল্পনা কয়েক স্তরে। Model-এর স্তরে soft margin: প্রতিটি বিন্দুকে কিছু ভুলের অনুমতি, আর C দিয়ে তার দাম, যা cross-validation-এ বাছাই হবে (পর্ব ২৮)। ডেটার স্তরে, যেসব বিন্দু support vector, তাদের label আলাদা করে মানুষ দিয়ে যাচাই করানো; support vector সংখ্যায় কম বলে এটি সস্তা। মাপের স্তরে, label পাকা হওয়ার জন্য একটি অপেক্ষার সময় রাখা, যাতে সাম্প্রতিক অর্ডারের অসম্পূর্ণ label train-এ না ঢোকে। আর সিদ্ধান্তের স্তরে, score-কে সরাসরি আটকানোর নিয়ম না বানিয়ে, পর্যালোচনার অগ্রাধিকার হিসেবে ব্যবহার করা।
মূল উপলব্ধি: hard margin ধরে নেয় প্রতিটি label নিখুঁত; বাস্তবের ডেটায় সেই অনুমান ভাঙে, তাই soft margin আর support vector-এর label যাচাই একসঙ্গে লাগে।
প্র ০৫Standardization কি সবসময় “সঠিক” scaling? দূরত্ব-নির্ভর model-এ feature-এর মাপ বাছাই আসলে কী ধরনের সিদ্ধান্ত?
অংশ ৭ দেখিয়েছে, scaling ছাড়া SVM-এর সমাধান একক-নির্ভর। কিন্তু standardization-ও একটি পছন্দ, প্রকৃতির নিয়ম নয়। এটি প্রতিটি feature-কে এমনভাবে বদলায় যাতে তার standard deviation ১ হয়, অর্থাৎ ধরে নেয় “প্রতিটি feature-এর এক sd পার্থক্য সমান গুরুত্বপূর্ণ”। এটি একটি যুক্তিসঙ্গত শুরু, কারণ এতে একক বা মুদ্রার পছন্দ আর ফল বদলায় না। কিন্তু domain জ্ঞান অন্য কথা বলতে পারে: হয়তো আগের অর্ডারের সংখ্যায় এক sd পার্থক্য দামের এক sd-র চেয়ে প্রতারণার জন্য অনেক বেশি অর্থবহ।
বিকল্পগুলোর নিজস্ব খরচ আছে। Outlier থাকলে (যেমন একটি দুই লাখ টাকার অর্ডার) গড় আর sd টেনে যায়; তখন median আর interquartile range দিয়ে robust scaling ভালো (পর্ব ২৪-এর চিন্তা)। Min-max scaling সীমার বাইরের নতুন মানে ভেঙে পড়ে। দামের মতো ডানদিকে লম্বা লেজের feature-এ আগে log নেওয়া প্রায়ই বেশি অর্থবহ, কারণ ১,০০০ থেকে ২,০০০ টাকার পার্থক্য আর ৫০,০০০ থেকে ৫১,০০০-এর পার্থক্য প্রতারণার জন্য সমান নয়। আবার একই একক, একই অর্থের feature-গুলো (যেমন কয়েকটি সেন্সরের তাপমাত্রা) হয়তো scale না করাই ভালো, কারণ তাদের আপেক্ষিক মাপের মধ্যেই তথ্য আছে।
গভীরভাবে দেখলে, feature-এর scale বাছাই মানে দূরত্বের মাপ বাছাই, আর দূরত্বের মাপ বাছাই মানে model-কে বলা “কোন পার্থক্য গুরুত্বপূর্ণ”। এটি একধরনের prior বা inductive bias. পর্ব ২৯-এর kernel একই সিদ্ধান্তের আরও সাধারণ রূপ: kernel বাছাই মানে মিলের মাপ বাছাই। আর যেকোনো scaling-এর গড় আর sd শিখতে হবে শুধু train ডেটা থেকে, Pipeline-এর ভেতরে (AI-এর ভিত্তি কোর্সের পর্ব ৬৪), নইলে test-এর তথ্য চুপিচুপি ঢুকে যায়। কোন scaling ভালো, তা শেষ পর্যন্ত cross-validation-এ তুলনা করে ঠিক করা উচিত।
মূল উপলব্ধি: scaling একটি modelling সিদ্ধান্ত, শুধু প্রযুক্তিগত ধাপ নয়; standardization ভালো ডিফল্ট, কিন্তু domain জ্ঞান আর cross-validation দিয়ে তাকে প্রশ্ন করা উচিত।
অনুশীলন
হিসাবগুলো খাতায় করুন, তারপর উত্তর মেলান। চাইলে অংশ ৬-এর ব্লকে ডেটা বদলে যাচাই করুন।
-
পূর্ণ numerical: চারটি বিন্দু: প্রতারণা (+১) P1 = (৩, ১), P2 = (৩, −১); সাধারণ (−১) N1 = (১, ০), N2 = (০, ২). (ক) Hard-margin SVM-এর w আর b (খ) কোনগুলো support vector (গ) margin-এর প্রস্থ (ঘ) αi-গুলো বের করুন।
(ক) P1, P2 দুটিই x1 = ৩-এ, N1 x1 = ১-এ। N1 থেকে P1-P2 রেখাংশের সবচেয়ে কাছের বিন্দু (৩, ০), দূরত্ব ২. সীমারেখা মাঝখানে x1 = ২: w = (k, ০), b = −২k. Canonical: P1-এ ৩k − ২k = k = ১. তাই w = (১, ০), b = −২.
(খ) y(w · x + b): P1 = ১, P2 = ১, N1 = −১ × (১ − ২) = ১, N2 = −১ × (০ − ২) = ২. Support vector P1, P2, N1; N2 নয়।
(গ) প্রস্থ ২/‖w‖ = ২; প্রতি পাশে ১.
(ঘ) αN2 = ০. w = α1(৩, ১) + α2(৩, −১) − α3(১, ০) = (১, ০) আর α1 + α2 = α3. দ্বিতীয় ঘর থেকে α1 = α2 = a, তাই α3 = ২a; প্রথম ঘর ৩a + ৩a − ২a = ৪a = ১. α1 = α2 = ০.২৫, α3 = ০.৫. ডেস্কটপে
SVC(kernel="linear", C=1e6)-এরdual_coef_প্রায় [−০.৫, ০.২৫, ০.২৫] দেয়, যা মেলে। -
সমালোচনা ও নকশা: একজন সহকর্মী AI-এর সাহায্যে লিখলেন: “তিনটি feature (দাম টাকায়, আগের অর্ডারের সংখ্যা, hub থেকে দূরত্ব km-এ) সরাসরি
SVC(kernel="linear", C=1e6)-এ দিয়েছি। Train accuracy ১০০%, তাই ডেটা আলাদা করা যায় আর model নিখুঁত। 2/‖w‖ = ০.০০৪, মানে margin খুব সরু, তাই দাম সবচেয়ে গুরুত্বপূর্ণ feature, কারণ তার coef সবচেয়ে ছোট হলেও মান সবচেয়ে বড়।” কী কী সমস্যা, আর আপনি কী করতেন?সমস্যা: (১) Scaling নেই: টাকায় দাম দূরত্বে আধিপত্য করে, তাই সমাধান একক-নির্ভর, আর ০.০০৪ সংখ্যাটি মিশ্র এককে মাপা, কোনো অর্থ নেই। (২) Train accuracy ১০০% মানে test-এ ভালো নয়; আর SVC soft margin সমাধান করে বলে ১০০% না হলেও ত্রুটি দেখাত না। আলাদা করা যায় কি না জানতে সব শর্ত y(w · x + b) ≥ ১ যাচাই করতে হয়। (৩) Unscaled coef দিয়ে গুরুত্ব বলা ভুল; একক বদলালেই coef বদলায়। (৪) Test set বা cross-validation-এর কথা নেই।
নকশা: train/test ভাগ;
make_pipeline(StandardScaler(), SVC(kernel="linear")); C cross-validation-এ বাছাই (পর্ব ২৮); তালাবন্ধ test-এ মাপ; logistic regression baseline-এর সঙ্গে কয়েকটি fold-এ তুলনা; গুরুত্বের জন্য standardized coef বা permutation importance (পর্ব ৫৬)। -
আউটপুট অনুমান করুন: চালানোর আগে লিখে ফেলুন নিচের কোড কী ছাপবে।
import numpy as np from sklearn.svm import SVC X = np.array([[3, 3], [5, 3], [1, 1], [0, 1]]) y = np.array([1, 1, -1, -1]) m = SVC(kernel="linear", C=1e6).fit(X, y) print(m.support_, m.coef_.round(2), m.intercept_.round(2)) print(m.decision_function([[4, 4], [2, 1]]).round(2))[2 0] [[0.5 0.5]] [-2.] [ 2. -0.5]এটি অংশ ৫-এর চারটি বিন্দু: w = (০.৫, ০.৫), b = −২, support vector A (সূচি ০) আর C (সূচি ২)।
support_-এ আগে আসে −১ শ্রেণির C, তাই[2 0]. নতুন বিন্দু (৪, ৪): ০.৫ × ৮ − ২ = ২; (২, ১): ০.৫ × ৩ − ২ = −০.৫, অর্থাৎ সাধারণের দিকে, কিন্তু রাস্তার ভেতরে। ডেস্কটপে scikit-learn 1.8.0-এ মেলানো। -
দূরত্ব আর সীমা, হাতে: (ক) w = (৬, ৮), b = −৫ হলে বিন্দু (১, ১)-এর চিহ্নসহ দূরত্ব কত? Label −১ হলে তার geometric margin কত? (খ) একটি hard-margin SVM-এর w = (২, −১); margin-এর প্রস্থ আর এক পাশের দূরত্ব কত? (গ) ৫০টি বিন্দুর hard-margin SVM-এ ৪টি support vector; leave-one-out ভুলের হার সর্বোচ্চ কত? (ঘ) সব ‖xi‖ ≤ ৪ আর মূলবিন্দু দিয়ে যাওয়া একটি রেখার margin ০.৫ হলে perceptron সর্বোচ্চ কতবার ভুল করবে?
(ক) w · x + b = ৬ + ৮ − ৫ = ৯, ‖w‖ = ১০, দূরত্ব ০.৯. Label −১ হলে geometric margin −০.৯: বিন্দুটি ভুল দিকে।
(খ) ‖w‖ = √৫ ≈ ২.২৩৬. প্রস্থ ২/√৫ ≈ ০.৮৯৪, এক পাশে ১/√৫ ≈ ০.৪৪৭.
(গ) ≤ ৪/৫০ = ০.০৮.
(ঘ) Novikoff-এর সীমা (R/γ)² = (৪/০.৫)² = ৬৪ বার।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমHard-margin SVM derive করার যুক্তির ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- সীমারেখা w · x + b = 0 আর নিয়ম: চিহ্ন দেখে শ্রেণি
- বিন্দুর চিহ্নসহ দূরত্ব (w · x + b)/‖w‖, label দিয়ে গুণ করে geometric margin
- Scale ঠিক করা: সবচেয়ে কাছের বিন্দুতে y(w · x + b) = 1
- দুই কিনারা ±1 থেকে প্রস্থ 2/‖w‖
- Primal: min ½‖w‖², শর্ত yᵢ(w · xᵢ + b) ≥ 1
- KKT-এর complementary slackness থেকে support vector চেনা
রেখা w · x + b = 0, যেখানে w = (3, 4), b = −2. বিন্দু (2, 1)-এর label −1. এই বিন্দুর geometric margin কত?
- ১.৬
- −১.৬
- −৮
- ৮
একটি hard-margin linear SVM-এর সমাধান w = (2, −1), b = 0.5. দুই কিনারার মধ্যে রাস্তার পুরো প্রস্থ প্রায় কত?
- ০.৪৪৭
- ০.৪
- ০.৮৯৪
- ২.২৩৬
Hard-margin SVM-এর KKT শর্ত অনুযায়ী কোন train বিন্দুর Lagrange multiplier αᵢ শূন্যের বেশি হতে পারে?
- শুধু যাদের yᵢ(w · xᵢ + b) = 1, অর্থাৎ কিনারায় বসা বিন্দু
- শুধু রাস্তার ভেতরে থাকা বিন্দু
- সীমারেখা থেকে সবচেয়ে দূরের বিন্দু
- সব বিন্দু, কারণ w সবার ওজনযুক্ত যোগফল
আলাদা করা যায় এমন একটি ডেটাসেটে hard-margin SVM শেখানো হলো। একটি train বিন্দুর yᵢ(w · xᵢ + b) = 2.7. এই বিন্দুটি বাদ দিয়ে আবার শেখালে কী হবে?
- Margin চওড়া হবে
- রেখা ঘুরে যাবে, কিন্তু প্রস্থ একই থাকবে
- সমাধান থাকবে না
- w, b আর margin হুবহু একই থাকবে
একটি separating রেখার w আর b দুটিকেই 10 দিয়ে গুণ করা হলো। কোনটি সত্য?
- Geometric margin ১০ গুণ হয়
- রেখা একই, functional margin ১০ গুণ, geometric margin অপরিবর্তিত
- রেখাটি সমান্তরালভাবে সরে যায়
- সব বিন্দুর শ্রেণি উল্টে যায়
একটি linear SVM-এ দুটি feature: দাম (টাকায়, পরিসর 1,000–50,000) আর আগের অর্ডারের সংখ্যা (0–20). কোনো scaling না করলে সবচেয়ে সম্ভাব্য সমস্যা কী?
- SVM আগের অর্ডারের feature-টি নিজে থেকে বাদ দিয়ে দেবে
- শুধু শেখার গতি কমবে, সমাধান একই থাকবে
- দূরত্বে দামই প্রায় সব, ফলে সমাধান এককের ওপর নির্ভর করবে
- ডেটা আর আলাদা করা যাবে না
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- দূরত্ব: বিন্দু থেকে w · x + b = ০-এর চিহ্নসহ দূরত্ব (w · x + b)/‖w‖; y দিয়ে গুণ করলে geometric margin, ‖w‖ দিয়ে ভাগ না করলে functional margin, যা scale-নির্ভর।
- Primal: canonical রূপে প্রস্থ ২/‖w‖, তাই min ½‖w‖², শর্ত yi(w · xi + b) ≥ ১; convex QP, সমাধান একটিই।
- Support vector: KKT থেকে w = ∑αiyixi, ∑αiyi = ০, আর αi > ০ শুধু কিনারার বিন্দুতে; বাকি বিন্দু সরালে সমাধান বদলায় না, LOO ভুল ≤ support vector/n.
- Tool: SLSQP দিয়ে primal নিজে, বা
SVC(kernel="linear", C=1e6);coef_,intercept_,support_,dual_coef_(= αᵢyᵢ) মিলিয়ে দেখুন। - সাবধান: scaling ছাড়া সমাধান একক-নির্ভর (Pipeline-এ StandardScaler); hard margin একটি ভুল label-এই ভাঙে, আর SVC সেটি নিঃশব্দে soft margin দিয়ে সামলায়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ২৮ · Soft margin — hinge loss, C আর dual problemপরবর্তী পর্বSlack, hinge loss, C-এর ভূমিকা আর dual সমস্যার সমাধান।
- মেশিন লার্নিং · কোর্স হোম৭৭টি পর্ব১৪টি মডিউলের পুরো রোডম্যাপ; পর্ব ২৮ (Soft margin) এখান থেকে খুলবেন।
- পর্ব ১৬ · Constraint-সহ optimizationএই কোর্সLagrange multiplier, KKT আর duality, যার ওপর SVM দাঁড়িয়ে।
- পর্ব ০৩ · শেখার তত্ত্বএই কোর্সVC dimension আর generalisation gap, margin যুক্তির পটভূমি।
- AI-এর ভিত্তি · পর্ব ৬৮ · Neural network-এর ধারণাআগের ভিত্তিPerceptron-এর নিয়ম, যার সঙ্গে maximum margin তুলনা করেছেন।
- AI-এর ভিত্তি · পর্ব ৬৪ · scikit-learn pipelineআগের ভিত্তিStandardScaler-কে leakage ছাড়া model-এর সঙ্গে জোড়া।