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

বড় ডেটায় kernel — Nyström, random features আর কখন SVM নয়

Kernels at Scale — Gram Matrix Cost, Linear SVM Solvers, Nyström Approximation, Random Fourier Features and When Not to Use an SVM

Taught in Bangla, this lesson tackles the n-squared cost of kernels: Gram matrix memory, measured SVC fit-time growth, linear SVMs for sparse text, Nyström approximation by hand and in code, random Fourier features against RBFSampler, and approximate kernels plus linear models versus exact SVC. Real in-browser runs and desktop runs up to a million rows guide when not to use SVM.

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

  • Gram matrix-এর memory হাতে হিসাব করা, আর SVC-এর fit time n-এর সঙ্গে কোন হারে বাড়ে তা মেপে বের করা
  • LinearSVC আর SGDClassifier কেন বড় ও sparse ডেটায় চলে, আর তাদের সীমা
  • Nyström approximation হাতে চার বিন্দুতে, তারপর NumPy/SciPy দিয়ে নিজে লেখা ও Nystroem-এর সঙ্গে মেলানো
  • Random Fourier features নিজে লেখা, RBFSampler-এর সঙ্গে মেলানো, আর feature-সংখ্যার সঙ্গে ভুল কীভাবে কমে তা দেখা
  • Exact SVC, Nyström + linear model আর random features-এর accuracy ও সময় তুলনা, আর কোন পরিস্থিতিতে SVM বাদ দেবেন তা ঠিক করা

এই মডিউলে আগের চারটি পর্বে SVM-এর পুরো ভিত তৈরি হচ্ছে: পর্ব ২৭ (Support Vector Machine — margin, support vector আর hard margin), পর্ব ২৮ (Soft margin — hinge loss, C আর dual problem), পর্ব ২৯ (Kernel trick — RBF, polynomial আর Mercer-এর অন্তর্দৃষ্টি) আর পর্ব ৩০ (SVR আর kernel ridge regression — kernel দিয়ে সংখ্যার অনুমান)। সেখানে kernel trick-এর জাদু দেখেছেন: dual শুধু dot product চায়, তাই xi·xj-এর জায়গায় k(xi, xj) বসালেই অ-রৈখিক সীমা। এই পর্ব সেই জাদুর দাম নিয়ে। দামটা হলো প্রতিটি জোড়ার মিল; n বাড়লে জোড়া বাড়ে n² হারে। তারপর দেখবেন দুটি কৌশল, যা kernel-এর সুবিধা প্রায় রেখে দাম কমায়, আর কখন SVM-কে সরিয়ে অন্য model নেওয়াই বুদ্ধিমানের কাজ। চালানো যায় এমন চারটি ব্লক পাতাতেই চলে (ব্রাউজারের Python, scikit-learn 1.8.0); প্রথমবার scikit-learn নামতে প্রায় দশ সেকেন্ড লাগে। বড় n-এর মাপ এসেছে ডেস্কটপের সত্যিকারের run থেকে।

১ · Kernel-এর দাম — প্রতিটি জোড়ার মিল

একটি বিয়েবাড়িতে ৫০ জন অতিথি। প্রত্যেকে প্রত্যেকের সঙ্গে হাত মেলালে মোট ১,২২৫টি করমর্দন। অতিথি ৫০০ হলে করমর্দন প্রায় সোয়া লাখ, দশ গুণ নয়, প্রায় একশো গুণ। Kernel পদ্ধতি ঠিক এই কাজ করে: প্রতিটি অর্ডারকে প্রতিটি অর্ডারের সঙ্গে মিলিয়ে একটি মিলের সংখ্যা লেখে। অতিথি অল্প হলে চমৎকার আপ্যায়ন, হাজার হাজার হলে হল ভেঙে পড়ে।

n টি সারির জন্য kernel k দিয়ে Gram matrix K হলো n×n, যার (i, j) ঘরে k(xi, xj). পর্ব ১০-এ (Positive definite matrix) দেখেছেন, বৈধ kernel-এর K সবসময় positive semidefinite. এখানে আমাদের প্রশ্ন সরল: এই matrix রাখতে কত জায়গা, আর তা দিয়ে শেখাতে কত সময়?

Memory: প্রতিটি float64 সংখ্যা ৮ বাইট, তাই পুরো K রাখতে ৮n² বাইট. Symmetric বলে অর্ধেক রাখলেও ক্রম একই, n². নিচের হিসাব খাতায় করার মতো:

memory = 8 × n × n বাইট1 GB = 10⁹ বাইট

            
  1. এক হাজার সারি। ৮ × ১০⁶ বাইট = ৮ MB. এই আকারে kernel নিয়ে কোনো চিন্তা নেই।
  2. দশ হাজার। n দশ গুণ হলো, memory হলো একশো গুণ: ৮০০ MB. এখনো চলে, কিন্তু টান পড়ছে।
  3. পঞ্চাশ হাজার। ২০ GB, সাধারণ ১৬ GB RAM-এর ল্যাপটপের বাইরে। বাজারঘরের মাসের অর্ডার এর চেয়ে বেশি হতেই পারে।
  4. এক লাখ আর দশ লাখ। ৮০ GB আর ৮ TB. পুরো K রাখার চিন্তাই এখানে বাদ।
  5. বিকল্পের আকার। প্রতিটি সারির জন্য m = ৩০০টি feature রাখলে n × m × ৮ বাইট: এক লাখে ২৪০ MB, দশ লাখে ২.৪ GB. এটিই অংশ ৪ আর ৬-এর কৌশলের মূল লাভ।

সময়: scikit-learn-এর SVC ভেতরে libsvm চালায় (Chih-Chung Chang আর Chih-Jen Lin-এর library). সে পুরো K বানায় না; dual problem-কে SMO (sequential minimal optimization, John Platt ১৯৯৮) ধরনের পদ্ধতিতে প্রতিবার দুটি α বদলে সমাধান করে, আর দরকারি kernel সারিগুলো একটি cache-এ রাখে (cache_size, ডিফল্ট ২০০ MB). তাতে memory বাঁচে, কিন্তু সময় বাঁচে না: যত বেশি সারি support vector হয়, তত বেশি kernel সারি বারবার হিসাব হয়। scikit-learn-এর নিজের docs বলে, SVC-এর fit time সারির সংখ্যার সঙ্গে অন্তত দ্বিঘাত (quadratic) হারে বাড়ে আর কয়েক দশ হাজার সারির পরে অবাস্তব হতে পারে। সাধারণভাবে বলা হয়, SMO-জাতীয় solver-এর খরচ ডেটা আর C-এর ওপর নির্ভর করে মোটামুটি n² থেকে n³-এর মধ্যে; এটি অভিজ্ঞতার কথা, কোনো নিশ্চিত সূত্র নয়।

Prediction-ও সস্তা নয়: একটি নতুন অর্ডারের score f(x) = ∑ αiyik(xi, x) + b, যোগফল সব support vector-এর ওপর। Support vector-এর সংখ্যা ডেটার সঙ্গে বাড়ে (ভুল label বা গোলমাল বেশি হলে প্রায় সেই অনুপাতে), তাই বড় ডেটায় model বড়, আর প্রতিটি prediction ধীর।

২ · মেপে দেখা — n দ্বিগুণ হলে fit time কত গুণ

কথা নয়, মাপ। এই পর্বের ডেটা: বাজারঘরের কাল্পনিক অর্ডার, seed দিয়ে বানানো, কোনো আসল দোকানের নয়। তিনটি feature, সবগুলো z-score: অর্ডারের টাকা, অ্যাকাউন্টের বয়স আর ঠিকানা বদলের প্রবণতা। লক্ষ্য: অর্ডারটি “সন্দেহজনক” ফ্ল্যাগ পাবে কি না। নিয়মটি অ-রৈখিক: স্বাভাবিক অর্ডারের কেন্দ্র থেকে যত দূরে, তত সন্দেহ; সঙ্গে ৫% ভুল label. প্রায় ২৭% অর্ডার ফ্ল্যাগ পায়। কোনো সরলরেখা এমন বৃত্তাকার সীমা আঁকতে পারে না, তাই এটি kernel-এর জন্য উপযুক্ত ডেটা।

# bazar_fraud.py — বাজারঘরের কাল্পনিক সন্দেহজনক অর্ডার (seed দিয়ে বানানো)
import numpy as np

def make_fraud(n, seed=31):
    rng = np.random.default_rng(seed)
    amount = rng.normal(0, 1, n)                     # অর্ডারের টাকা (z-score)
    age = rng.normal(0, 1, n)                        # অ্যাকাউন্টের বয়স (z-score)
    moves = rng.normal(0, 1, n)                      # ঠিকানা বদল (z-score)
    odd = np.hypot(amount - 0.3, age + 0.2) + 0.4 * moves   # অস্বাভাবিকতা
    y = (odd > 1.8).astype(int)
    flip = rng.random(n) < 0.05                      # ৫% ভুল label
    y[flip] = 1 - y[flip]
    return np.column_stack([amount, age, moves]), y

প্রথম ব্লক n = ২৫০ থেকে ৪,০০০ পর্যন্ত প্রতিবার দ্বিগুণ করে RBF SVC শেখায়, সময় আর support vector গোনে, তারপর log–log স্কেলে একটি সরলরেখা বসিয়ে বৃদ্ধির ঘাত (exponent) বের করে। log(সময়) = k · log(n) + ধ্রুবক হলে সময় ∝ nk; ঢাল k-ই উত্তর। ছোট n-এ সময় এত কম যে গোলমাল বেশি, তাই ঢাল মাপা হয়েছে শেষ তিনটি বিন্দুতে।

import time
import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from bazar_fraud import make_fraud

sizes = np.array([250, 500, 1000, 2000, 4000])
times, nsv = [], []
for n in sizes:
    X, y = make_fraud(n)
    t0 = time.perf_counter()
    svc = SVC(kernel="rbf", C=1.0, gamma=0.5).fit(X, y)
    times.append(time.perf_counter() - t0)
    nsv.append(svc.n_support_.sum())
    print(f"n = {n:4d}  fit {times[-1]:6.3f} s  support vectors {nsv[-1]:4d}"
          f"  full Gram {8 * n * n / 1e6:6.1f} MB")
k = np.polyfit(np.log(sizes[2:]), np.log(times[2:]), 1)[0]
print(f"fitted growth (n = 1000..4000): time ~ n^{k:.2f}")
big = np.array([1e4, 1e5, 1e6])
print("full Gram at n =", ", ".join(f"{int(m):,}: {8 * m * m / 1e9:,.1f} GB" for m in big))

fig, (a1, a2) = plt.subplots(1, 2, figsize=(9, 3.8))
a1.loglog(sizes, times, "o-", label="measured SVC fit")
a1.loglog(sizes, times[-1] * (sizes / sizes[-1]) ** 2, "k--", lw=1, label="slope 2")
a1.set(xlabel="n (orders)", ylabel="fit time (s)", title="Exact RBF SVC")
a1.legend(fontsize=8)
grid = np.logspace(3, 6, 30)
a2.loglog(grid, 8 * grid ** 2 / 1e9, label="n x n float64 Gram")
a2.axhline(16, color="r", ls=":", label="16 GB laptop RAM")
a2.set(xlabel="n (orders)", ylabel="memory (GB)", title="Gram matrix memory")
a2.legend(fontsize=8)

আসল আউটপুট (ব্রাউজারের Python, একটি run; সময় প্রতিবার আর প্রতিটি যন্ত্রে আলাদা হবে), নিচে দুটি ছবি:

n =  250  fit  0.014 s  support vectors  106  full Gram    0.5 MB
n =  500  fit  0.014 s  support vectors  194  full Gram    2.0 MB
n = 1000  fit  0.046 s  support vectors  339  full Gram    8.0 MB
n = 2000  fit  0.160 s  support vectors  603  full Gram   32.0 MB
n = 4000  fit  0.516 s  support vectors 1020  full Gram  128.0 MB
fitted growth (n = 1000..4000): time ~ n^1.74
full Gram at n = 10,000: 0.8 GB, 100,000: 80.0 GB, 1,000,000: 8,000.0 GB

n ১,০০০ থেকে ২,০০০ হলে সময় প্রায় ৩.৫ গুণ (০.০৪৬ থেকে ০.১৬০ সেকেন্ড), ২,০০০ থেকে ৪,০০০ হলে প্রায় ৩.২ গুণ। দ্বিগুণ ডেটায় দ্বিগুণ নয়, তিন গুণের বেশি সময়: ঢাল প্রায় ১.৭৪, অর্থাৎ সময় ∝ n১.৭৪-এর কাছাকাছি। ছোট n-এ নির্দিষ্ট খরচ (ডেটা কপি, শুরু করা) বেশি বলে ঢাল একটু কম দেখায়; ডেস্কটপের বড় n-এ নিচে দেখবেন তা প্রায় ২-এ পৌঁছায়। Support vector-এর সংখ্যাও n-এর সঙ্গে বাড়ছে, যদিও অনুপাত ৪২% থেকে ২৫%-এ নামছে: ভুল label আর সীমার কাছের অর্ডারগুলো সবাই support vector হয়। ডান দিকের ছবিতে memory-র রেখা ঢাল ২-এ সোজা উঠে প্রায় n = ৪৫,০০০-এ ১৬ GB-এর লাল রেখা পার হয়। আপনার run-এ সময় একটু আলাদা হবে, কিন্তু আকৃতি একই থাকবে।

বড় n-এ কী হয়, তা ব্রাউজারে মাপা যায় না, ব্রাউজার আটকে যাবে। তাই একই ডেটা-নিয়মে ডেস্কটপে সত্যিকারের run (একটি সাধারণ ল্যাপটপ, Python 3.14.4, scikit-learn 1.9.1, NumPy 2.5.4; একটি run, সাধারণ ফল নয়)। প্রতিটি n-এ exact SVC আর অংশ ৭-এর Nyström + LinearSVC pipeline, আর ২০,০০০ অর্ডারের একটি আলাদা test-এ accuracy:

~/bazarghar-ml$ python scale.py
Python 3.14.4, scikit-learn 1.9.1
      n   model              fit (s)   #SV   test acc
   4,000  exact SVC             0.06    1024  0.9351
   4,000  Nystroem+LinearSVC    0.22       -  0.9381
  16,000  exact SVC             0.92    3459  0.9439
  16,000  Nystroem+LinearSVC    1.69       -  0.9431
  64,000  exact SVC            13.60   13361  0.9449
  64,000  Nystroem+LinearSVC   11.48       -  0.9462
 128,000  exact SVC            54.52   26268  0.9460
 128,000  Nystroem+LinearSVC   27.44       -  0.9458
1,000,000  Nystroem+LinearSVC  394.59       -  0.9477

Exact SVC: n চার গুণ (৪,০০০ থেকে ১৬,০০০) হলে সময় প্রায় ১৫ গুণ; ৬৪,০০০ থেকে ১,২৮,০০০ (দ্বিগুণ) হলে ১৩.৬ থেকে ৫৪.৫ সেকেন্ড, ঠিক চার গুণ, অর্থাৎ ঢাল প্রায় ২. Support vector ২৬,২৬৮, n-এর প্রায় ২০%; এরা সবাই model-এ জমা থাকে। একই ঢালে দশ লাখ সারিতে exact SVC লাগত মোটামুটি ৫৪.৫ × (১০,০০,০০০/১,২৮,০০০)² ≈ ৩,৩০০ সেকেন্ড, প্রায় এক ঘণ্টা; এটি হিসাব, চালানো হয়নি। Nyström + LinearSVC ছোট n-এ বরং ধীর (৪,০০০-এ ০.২২ বনাম ০.০৬), ১৬,০০০ আর ৬৪,০০০-এর মাঝে দুটি সমান হয়, ১,২৮,০০০-এ প্রায় অর্ধেক সময় নেয়। Accuracy সবখানে ০.৯৪–০.৯৫: ৫% label উল্টানো, তাই ০.৯৫-এর কাছে ছাদ। কিন্তু দশ লাখে Nyström + LinearSVC-ও ৩৯৫ সেকেন্ড নিল; কেন, তা নিচের run দেখায়।

দশ লাখ সারিতে সময় কোথায় যায়, তা আলাদা করে মাপা (একই ল্যাপটপ, একটি run): scaling আর Nyström feature এক ধাপে, তারপর একই feature-এ দুটি linear model.

~/bazarghar-ml$ python scale_1m.py
scale + Nystroem features:    3.23 s  Z (1000000, 300), 2.4 GB
SGDClassifier hinge  fit    7.61 s  test acc 0.9425
LinearSVC            fit  402.89 s  test acc 0.9477

Nyström feature বানাতে মাত্র ৩.২ সেকেন্ড, যদিও Z নিজেই ২.৪ GB (প্রথম stepper-এর হিসাবের সঙ্গে হুবহু মেলে)। সময় খেয়েছে LinearSVC: ৪০৩ সেকেন্ড। একই feature-এ SGDClassifier(loss="hinge") শিখেছে ৭.৬ সেকেন্ডে, accuracy ০.৯৪২৫ বনাম ০.৯৪৭৭. অর্থাৎ বড় আর dense feature-এ liblinear-এর নিখুঁত সমাধানের চেয়ে SGD-এর “যথেষ্ট ভালো” সমাধান অনেক সস্তা; দামটি হলো আধা শতাংশ বিন্দুর মতো accuracy আর alpha ও epoch বাছাইয়ের ঝামেলা। এক ঘণ্টার exact SVC-এর জায়গায় প্রায় ১১ সেকেন্ড, একই ডেটা-নিয়মে।

৩ · Kernel ছাড়া SVM — LinearSVC আর SGDClassifier

Kernel-এর খরচ আসে জোড়া থেকে। Kernel না থাকলে, অর্থাৎ সীমাটি x-এর ওপর সরাসরি একটি hyperplane w·x + b হলে, জোড়ার দরকারই নেই। তখন dual-এর বদলে primal সমস্যাটি সরাসরি সমাধান করা যায় (পর্ব ২৮-এ তার রূপ দেখেছেন):

minw, b ½‖w‖² + C ∑i max(0, 1 − yi(w·xi + b))

এখানে yi হলো −১ বা +১, আর max(0, ১ − margin) হলো hinge loss (পর্ব ০২)। w-তে d টি সংখ্যা, d হলো feature-সংখ্যা। প্রতিটি পূর্ণ পাস ডেটার ওপর একবার যায়, খরচ মোটামুটি n × d, আর ডেটা sparse হলে শুধু অশূন্য ঘরের সংখ্যার সমানুপাতিক। এই খরচ n-এর সঙ্গে রৈখিক।

LinearSVC (liblinear)

Rong-En Fan, Kai-Wei Chang, Cho-Jui Hsieh, Xiang-Rui Wang আর Chih-Jen Lin-এর liblinear (JMLR, ২০০৮)। Coordinate descent জাতীয় পদ্ধতিতে primal বা dual সমাধান; dual="auto" ডিফল্ট (scikit-learn 1.5 থেকে) সারি আর feature-এর সংখ্যা দেখে বেছে নেয়। ডিফল্ট loss "squared_hinge", আর intercept-ও regularize হয়, তাই SVC(kernel="linear")-এর হুবহু এক উত্তর নয়।

SGDClassifier(loss="hinge")

প্রতিবার একটি সারি দেখে w একটু সরায়: stochastic gradient descent, যা পর্ব ১৩-এ দেখেছেন। Regularization-এর শক্তি alpha দিয়ে (C-এর উল্টো ধরনের)। partial_fit দিয়ে ডেটা টুকরো টুকরো খাওয়ানো যায়, তাই RAM-এর চেয়ে বড় ডেটাতেও চলে। দাম: learning rate আর epoch-এর ওপর ফল নির্ভর করে, scale করা ডেটা লাগে।

কোথায় linear SVM সবচেয়ে ভালো কাজ করে? লেখার ডেটায়। Thorsten Joachims ১৯৯৮ সালে (ECML) দেখান, অনেক শব্দের feature-এ লেখার শ্রেণিবিভাগে SVM সে সময়ের অন্য পদ্ধতির চেয়ে ভালো, আর linear kernel-ই প্রায় যথেষ্ট। কারণ: হাজার হাজার মাত্রায় ডেটা প্রায়ই প্রায়-রৈখিকভাবে আলাদা করা যায়, kernel যোগ করে লাভ কম। বাজারঘরের পণ্য-বর্ণনার একটি কাল্পনিক উদাহরণ, পাঁচটি বিভাগ (পোশাক, ইলেকট্রনিক্স, মুদি, বই, প্রসাধনী), TF-IDF শব্দ আর শব্দজোড়া (AI-এর ভিত্তি কোর্সের পর্ব ৬৭-এ TF-IDF আর বাংলা tokenization). বাংলা স্বরচিহ্ন regex-এর \w-এ পড়ে না বলে token ভাঙা হয়েছে ফাঁকা জায়গা দিয়ে (token_pattern=r"\S+"). ডেস্কটপে সত্যিকারের run (একটি run):

~/bazarghar-ml$ python text_svm.py
Python 3.14.4, scikit-learn 1.9.1
train X: 200,000 x 3,269, nonzeros 2,808,934 (0.42963% filled), 22 MB data
a dense 200,000 x 200,000 float64 Gram matrix would need 320 GB
LinearSVC            fit   5.41 s   test accuracy 0.9922
SGDClassifier hinge  fit   1.01 s   test accuracy 0.9923

দুই লাখ বর্ণনার TF-IDF matrix-এ মাত্র ০.৪৩% ঘর অশূন্য, ডেটা মাত্র ২২ MB; অথচ একই সারিসংখ্যায় পুরো Gram matrix ৩২০ GB হতো। LinearSVC ৫.৪ সেকেন্ডে, SGDClassifier ১.০ সেকেন্ডে শেখে, দুটিরই test accuracy ০.৯৯২. সতর্কতা: এই কাল্পনিক বর্ণনা খুব সহজ, প্রতিটি বিভাগের নিজস্ব শব্দ আছে, আর শব্দভান্ডারও ছোট (৩,২৬৯টি কলাম); আসল বর্ণনায় কলাম লাখের ঘরে আর accuracy অনেক কম হবে। কিন্তু মূল কথা টেকে: sparse লেখায় linear SVM-এর খরচ অশূন্য ঘরের সংখ্যার সমানুপাতিক, n²-এর নয়।

সীমা। Linear SVM অ-রৈখিক সীমা আঁকতে পারে না। অংশ ২-এর বৃত্তাকার ফ্ল্যাগে শুধু linear model প্রায় “সবাইকে স্বাভাবিক বলা”-র কাছাকাছি থাকে (অংশ ৭-এ দেখবেন)। তখন দুটি পথ: হাতে feature বানানো (পর্ব ২৩-এর basis expansion), অথবা kernel-এর feature-কে আনুমানিকভাবে বানিয়ে নেওয়া, যার নাম kernel approximation. পরের তিন অংশ দ্বিতীয় পথের।

৪ · Nyström — কয়েকটি landmark দিয়ে পুরো kernel matrix

ঢাকার প্রতিটি এলাকা থেকে প্রতিটি এলাকার রিকশাভাড়ার তালিকা বানাতে হাজার হাজার জোড়া লাগে। একজন অভিজ্ঞ চালক অন্যভাবে ভাবেন: তিনি কয়েকটি চেনা মোড় জানেন, যেমন ফার্মগেট, শাহবাগ, মোহাম্মদপুর। নতুন যেকোনো জায়গার ভাড়া তিনি আন্দাজ করেন “এটি কোন কোন মোড়ের কাছে” দেখে। Nyström পদ্ধতি এই চালকের কৌশল: কয়েকটি landmark বিন্দুর সঙ্গে সবার মিল জানলেই বাকি জোড়াগুলোর মিল আন্দাজ করা যায়।

n টি বিন্দু থেকে m টি (m ≪ n) landmark বাছুন, সাধারণত এলোমেলোভাবে। তারপর দুটি ছোট matrix:

  • C (n×m): প্রতিটি বিন্দুর সঙ্গে প্রতিটি landmark-এর kernel মান, Cij = k(xi, lj).
  • W (m×m): landmark-দের নিজেদের Gram matrix, Wjk = k(lj, lk); এটি C-এরই কয়েকটি সারি।

K ≈ K̃ = C W⁺ Cᵀ

W⁺ হলো W-এর Moore–Penrose pseudo-inverse (পর্ব ০৯); W উল্টানো গেলে W⁺ = W⁻¹. ফলটি rank m-এর একটি low-rank approximation. এর নাম এসেছে Evert Nyström-এর ১৯৩০ সালের integral equation সমাধানের পদ্ধতি থেকে; kernel machine-এ এটি আনেন Christopher Williams আর Matthias Seeger (NIPS ২০০০ সম্মেলন, প্রকাশ ২০০১)।

Nyström: বড় বর্গের বদলে তিনটি সরু টুকরো

K (n × n) ≈ C (n × m) · W⁺ (m × m) · Cᵀ (m × n)

Nyström low-rank factorisation of a kernel matrix K n × n পুরো Gram ≈ C n × m W⁺ m × m Cᵀ m × n C: landmark-দের সঙ্গে মিল W: landmark-দের নিজেদের মধ্যে মিল n = 1,00,000, m = 300 হলে: K: 8 × n² বাইট = 80 GB C: 8 × n × m বাইট = 240 MB W: 8 × m² বাইট = 0.72 MB
ছবি ৩১.১ — Nyström কখনো পুরো K বানায় না। শুধু C আর ছোট W, যাদের খরচ n-এর সঙ্গে রৈখিক।

কেন কাজ করে? RBF kernel-এর Gram matrix-এর eigenvalue সাধারণত দ্রুত ছোট হয়ে যায়: কয়েকটি বড় দিকেই প্রায় সব তথ্য, বাকিরা প্রায় শূন্য। তাই একটি ছোট rank-এর matrix K-কে ভালোভাবে ধরতে পারে। Eckart–Young (পর্ব ০৯) বলে সেরা rank-m approximation আসে K-এর সবচেয়ে বড় m টি eigenvalue থেকে; কিন্তু তা পেতে পুরো K লাগে। Nyström সেই সেরার একটি সস্তা আন্দাজ, যা K-এর মাত্র m টি কলাম দেখে।

নিচে চারটি বিন্দুতে পুরো হিসাব, যাতে সূত্রটি হাতে ধরা যায়। এক-মাত্রার বিন্দু x = ০, ১, ২, ৩, RBF kernel k(a, b) = exp(−γ(a − b)²), γ = ০.৫; landmark হলো x = ১ আর x = ২.

k(a, b) = exp(−0.5 (a − b)²)landmark: x = 1, 2

            
  1. পুরো K. চার বিন্দুর ষোলোটি মিল। পাশাপাশি বিন্দুর মিল ০.৬০৬৫, দুই ঘর দূরে ০.১৩৫৩, তিন ঘর দূরে মাত্র ০.০১১১. বাস্তবে এই matrix আমরা বানাতে চাই না; এখানে শুধু তুলনার জন্য।
  2. C আর W. শুধু দুটি landmark কলাম হিসাব হলো: ৪ × ২ = ৮টি kernel মান। W আলাদা হিসাব লাগে না, C-এর landmark সারি দুটি নিলেই হয়।
  3. W-এর উল্টো। ২×২ matrix-এর সূত্রে: determinant ১ − ০.৬০৬৫² = ০.৬৩২১, কর্ণ অদলবদল, বাকি দুটির চিহ্ন উল্টো, তারপর determinant দিয়ে ভাগ। W উল্টানো যায়, তাই W⁺ = W⁻¹.
  4. একটি ঘর: K̃(০, ০). c₀ হলো C-এর প্রথম সারি। quadratic form-টি তিনটি পদে ভাঙলে ০.৪৫৩৪. আসল মান ১, অর্থাৎ x = ০ বিন্দুটি landmark-দের থেকে দূরে বলে তার “নিজের সঙ্গে মিল”-এর অর্ধেকের বেশি হারিয়ে গেছে।
  5. আরেকটি ঘর: K̃(০, ৩). ফল −০.১১০৮, অথচ আসল মান ০.০১১১ আর RBF kernel কখনো ঋণাত্মক নয়। আনুমানিক matrix-এর একটি ঘর এমন অদ্ভুত হতেই পারে; গোটা matrix-এর ভুলই আসল মাপ।
  6. পুরো K̃. Landmark-এর সারি আর কলাম হুবহু মিলেছে, কারণ সেখানে C W⁻¹ W = C. ভুল শুধু x = ০ আর x = ৩-এর কোণগুলোতে, যারা landmark নয়।
  7. ভুলের মাপ। আপেক্ষিক Frobenius ভুল ০.৩১৬; একই rank-এর সেরা সম্ভব ০.২১০. আর একটি নিশ্চিত গুণ: K − K̃ হলো একটি Schur complement, তাই PSD; ফলে কর্ণের মান কখনো বাড়ে না। Landmark বেশি হলে বা ভালো জায়গায় হলে ভুল কমে।

K̃ থেকে feature — linear model-এর জন্য

K̃ বানিয়ে লাভ নেই, সেটিও n×n. আসল কৌশল হলো প্রতিটি বিন্দুর জন্য m টি সংখ্যার একটি feature:

φ(x) = W−½ c(x),   যেখানে c(x) = (k(x, l1), …, k(x, lm))

তাহলে φ(x)·φ(y) = c(x)ᵀ W⁻¹ c(y) = K̃(x, y). অর্থাৎ φ-এর ওপর সাধারণ dot product-ই Nyström kernel. এখন যেকোনো linear model (LinearSVC, logistic regression, ridge) এই n×m feature-এ চালান: kernel-এর অ-রৈখিকতা আছে, জোড়ার খরচ নেই। scikit-learn-এর Nystroem ঠিক এই কাজ করে: fit-এ এলোমেলো m টি সারি বেছে W-এর SVD থেকে W−½ (normalization_) রাখে, খুব ছোট singular value-কে ১০⁻¹²-এ আটকে; transform-এ C × W−½ ফেরত দেয়। খরচ: C বানাতে n·m·d, W-এর SVD m³, গুণ n·m²; memory n·m.

৫ · Nyström হাতে-কলমে — নিজে লেখা, Nystroem আর সেরা সম্ভব

দ্বিতীয় ব্লক ১,০০০টি অর্ডারের পুরো Gram matrix বানায় (এই আকারে তা সস্তা, শুধু তুলনার জন্য), তারপর m বাড়িয়ে তিনটি আপেক্ষিক Frobenius ভুল মাপে: নিজে লেখা Nyström (scipy.linalg.pinv দিয়ে), scikit-learn-এর Nystroem (Z Zᵀ দিয়ে K̃), আর Eckart–Young-এর সেরা rank-m, যা K-এর eigenvalue থেকে আসে। পরের ব্লক এই ব্লকের ফল ব্যবহার করে, তাই দুটি একই namespace-এ চলে।

import numpy as np
from scipy.linalg import eigh, pinv
from sklearn.kernel_approximation import Nystroem
from sklearn.metrics.pairwise import rbf_kernel
from bazar_fraud import make_fraud

X, y = make_fraud(1000, seed=2)
gamma = 0.5
K = rbf_kernel(X, gamma=gamma)                   # পুরো 1000 × 1000 Gram
normK = np.linalg.norm(K)                        # Frobenius norm
lam = eigh(K, eigvals_only=True)[::-1]           # eigenvalue, বড় থেকে ছোট
rng = np.random.default_rng(0)

def nystrom_own(m):
    idx = rng.choice(len(X), m, replace=False)   # m টি landmark
    C = rbf_kernel(X, X[idx], gamma=gamma)       # n × m
    W = C[idx]                                   # m × m
    return C @ pinv(W) @ C.T                     # K ≈ C W⁺ Cᵀ

ms = [5, 10, 25, 50, 100, 200, 400]
err_ny = []
print("  m   own Nystrom  sklearn  best rank-m (SVD)")
for m in ms:
    e_own = np.linalg.norm(K - nystrom_own(m)) / normK
    Z = Nystroem(gamma=gamma, n_components=m, random_state=0).fit_transform(X)
    e_sk = np.linalg.norm(K - Z @ Z.T) / normK
    e_best = np.sqrt(np.sum(lam[m:] ** 2)) / normK   # Eckart–Young সীমা
    err_ny.append(e_sk)
    print(f"{m:4d}   {e_own:.4f}      {e_sk:.4f}   {e_best:.4f}")
print("feature matrix Z:", Z.shape, f"{Z.nbytes / 1e6:.1f} MB vs K {K.nbytes / 1e6:.1f} MB")

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

  m   own Nystrom  sklearn  best rank-m (SVD)
   5   0.4635      0.5645   0.2986
  10   0.3870      0.4926   0.1589
  25   0.1821      0.1676   0.0590
  50   0.0598      0.0668   0.0191
 100   0.0266      0.0215   0.0032
 200   0.0099      0.0088   0.0001
 400   0.0036      0.0026   0.0000
feature matrix Z: (1000, 400) 3.2 MB vs K 8.0 MB

তিনটি কলাম একই গল্প বলে। নিজে লেখা Nyström আর Nystroem প্রায় সমান; পার্থক্য শুধু এলোমেলো landmark-এর ভাগ্যে (m = ৫ আর ১০-এ scikit-learn-এর বাছাই খারাপ পড়েছে)। m = ১০০-তেই আপেক্ষিক ভুল প্রায় ২%, m = ৪০০-তে ০.২৬%. শেষ কলাম হলো সেরা সম্ভব, যা পুরো K-এর eigendecomposition চায় (খরচ n³): m = ১০০-তে ০.৩২%. অর্থাৎ এলোমেলো landmark সেরার চেয়ে কয়েক গুণ খারাপ, কিন্তু খরচে অনেক সস্তা, আর ভুল দ্রুত কমছে কারণ এই K-এর eigenvalue দ্রুত ছোট হয়। শেষ লাইন: n = ১,০০০-এ Z ৩.২ MB আর K ৮ MB, লাভ সামান্য। কিন্তু Z বাড়ে n-এর সঙ্গে আর K বাড়ে n²-এর সঙ্গে: n = ১ লাখে একই m-এ Z ৩২০ MB, K ৮০ GB.

৬ · Random Fourier features — cos দিয়ে kernel-এর নমুনা

Nyström ডেটা থেকে landmark নেয়। Ali Rahimi আর Benjamin Recht ২০০৭ সালে (NIPS, “Random Features for Large-Scale Kernel Machines”) সম্পূর্ণ অন্য পথ দেখান: ডেটা না দেখেই এলোমেলো feature বানানো, যাদের dot product গড়ে kernel-এর সমান। এই প্রবন্ধ ২০১৭ সালে NIPS-এর “test of time” পুরস্কার পায় বলে জানা যায়।

ভিত্তি হলো Bochner-এর উপপাদ্য: k(x, y) যদি শুধু x − y-এর ওপর নির্ভর করে (shift-invariant) আর বৈধ kernel হয়, তবে সেটি একটি সম্ভাবনা বণ্টন p(ω)-এর Fourier রূপ, অর্থাৎ k(x, y) = Eω[cos(ωᵀ(x − y))]. RBF kernel exp(−γ‖x − y‖²)-এর জন্য p(ω) হলো N(0, 2γI), একটি Gaussian. প্রত্যাশা (expectation)-কে D টি নমুনার গড় দিয়ে আন্দাজ করলেই random Fourier features:

z(x) = √(2/D) · cos(ωᵀx + b),   ω ~ N(0, 2γI),   b ~ U(0, 2π)

এখানে D টি ω (প্রতিটি d-মাত্রার) আর D টি b একবার এলোমেলোভাবে টানা হয়, সব সারির জন্য একই। b-এর ভূমিকা: দুটি cos-এর গুণফলকে cos(ωᵀ(x − y))-এ আনা, কারণ b-এর ওপর গড় নিলে cos(ωᵀ(x + y) + 2b) পদটি শূন্য হয়ে যায়। তাই E[z(x)·z(y)] = k(x, y), আর গড়টি D টি স্বাধীন পদের, ফলে ভুলের standard deviation কমে প্রায় 1/√D হারে, যেমন যেকোনো Monte Carlo অনুমানে। ভুল অর্ধেক করতে D চার গুণ।

scikit-learn-এর RBFSampler হুবহু এই সূত্র (source-এ random_weights_ = √(2γ) × standard normal, random_offset_ = U(0, 2π), শেষে √(2/D) দিয়ে গুণ)। সাবধান: তার ডিফল্ট gamma=1.0, যেখানে SVC-এর ডিফল্ট gamma="scale"; একই γ নিজে বসান। তৃতীয় ব্লক প্রথমে একটি জোড়ার kernel D বাড়িয়ে আন্দাজ করে, তারপর পুরো Gram matrix-এর ভুল মেপে আগের ব্লকের Nyström-এর সঙ্গে একটি ছবিতে রাখে।

import matplotlib.pyplot as plt
from sklearn.kernel_approximation import RBFSampler

def rff_own(X, D, seed=0):
    r = np.random.default_rng(seed)
    w = r.normal(0, np.sqrt(2 * gamma), size=(X.shape[1], D))   # ω ~ N(0, 2γI)
    b = r.uniform(0, 2 * np.pi, D)                               # b ~ U(0, 2π)
    return np.sqrt(2 / D) * np.cos(X @ w + b)                    # n × D

x, z = X[0:1], X[5:6]
print(f"one pair: exact k = {rbf_kernel(x, z, gamma=gamma)[0, 0]:.4f}")
for D in [10, 100, 1000, 10000]:
    F = rff_own(np.vstack([x, z]), D)
    print(f"  D = {D:5d}: z(x).z(y) = {F[0] @ F[1]:.4f}")

err_rff, err_rbs = [], []
for D in ms:
    F = rff_own(X, D)
    err_rff.append(np.linalg.norm(K - F @ F.T) / normK)
    Z = RBFSampler(gamma=gamma, n_components=D, random_state=0).fit_transform(X)
    err_rbs.append(np.linalg.norm(K - Z @ Z.T) / normK)
print("  D   own RFF  RBFSampler  (Nystroem, same m)")
for D, a, b, c in zip(ms, err_rff, err_rbs, err_ny):
    print(f"{D:4d}   {a:.4f}   {b:.4f}      {c:.4f}")

fig, ax = plt.subplots(figsize=(6.5, 4))
ax.loglog(ms, err_ny, "o-", label="Nystroem (sklearn)")
ax.loglog(ms, err_rff, "s-", label="random Fourier (own)")
ax.loglog(ms, err_rbs, "^--", label="RBFSampler (sklearn)")
ax.loglog(ms, err_rff[0] * np.sqrt(ms[0] / np.array(ms)), "k:", label="1/sqrt(D) slope")
ax.set(xlabel="number of components (m or D)", ylabel="relative Frobenius error",
       title="Approximating a 1000 x 1000 RBF Gram matrix")
ax.legend(fontsize=8)

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

one pair: exact k = 0.5353
  D =    10: z(x).z(y) = 0.1721
  D =   100: z(x).z(y) = 0.4380
  D =  1000: z(x).z(y) = 0.5609
  D = 10000: z(x).z(y) = 0.5326
  D   own RFF  RBFSampler  (Nystroem, same m)
   5   1.2347   1.4729      0.5645
  10   0.8171   1.0489      0.4926
  25   0.7994   0.6469      0.1676
  50   0.3917   0.4554      0.0668
 100   0.3011   0.3507      0.0215
 200   0.2509   0.2403      0.0088
 400   0.1894   0.1938      0.0026

একটি জোড়ার আসল kernel ০.৫৩৫৩. D = ১০-এ আন্দাজ ০.১৭২, অনেক দূরে; D = ১০০-তে ০.৪৩৮; D = ১,০০০-এ ০.৫৬১; D = ১০,০০০-এ ০.৫৩৩. আন্দাজ নিরপেক্ষ, কিন্তু গোলমাল ধীরে কমে। পুরো matrix-এর টেবিলে তুলনা স্পষ্ট: ৪০০টি component-এ random features-এর ভুল প্রায় ০.১৯, Nyström-এর ০.০০২৬, প্রায় ৭০ গুণ কম। D চার গুণ (১০০ থেকে ৪০০) করলে নিজের RFF-এর ভুল ০.৩০ থেকে ০.১৯, RBFSampler-এর ০.৩৫ থেকে ০.১৯: অর্ধেকের কাছাকাছি, 1/√D নিয়মের মতো, যদিও একটি run-এ সংখ্যা নড়ে। ছবিতে Nyström-এর রেখা অনেক খাড়া। কারণ Nyström ডেটার নিজের কলাম ব্যবহার করে আর eigenvalue দ্রুত কমার সুবিধা পায়; random features ডেটা দেখে না। Tianbao Yang ও সহকর্মীরা (NIPS ২০১২) এই পার্থক্যের তাত্ত্বিক ব্যাখ্যা দেন। তবে Gram matrix-এর ভুলই শেষ কথা নয়; classifier-এর accuracy কী বলে, তা পরের অংশে।

দিকNyströmRandom Fourier features
Feature কোথা থেকেডেটার m টি সারি (landmark)ডেটা-নিরপেক্ষ এলোমেলো ω, b
কোন kernelযেকোনো PSD kernel (polynomial-ও)shift-invariant (RBF, Laplacian ইত্যাদি)
ভুল কমার হারeigenvalue দ্রুত কমলে অনেক দ্রুতপ্রায় 1/√D, ডেটার গঠন কাজে লাগায় না
Fit খরচn·m·d + m³ + n·m²n·D·d (শুধু একটি গুণ)
Model-এ কী থাকেm টি আসল সারি (গোপনীয়তার প্রশ্ন)শুধু এলোমেলো সংখ্যা
Streaming বা ভাগ করা ডেটাআগে landmark বাছতে হয়সহজ: seed জানলেই সবাই একই feature

৭ · আনুমানিক kernel + linear model বনাম exact SVC

এখন আসল পরীক্ষা: approximation দিয়ে শেখানো classifier কি exact SVC-এর মতো ভালো? চতুর্থ ব্লক ৪,০০০টি অর্ডারে পাঁচটি model শেখায়, প্রতিটির আগে StandardScaler একই pipeline-এ (AI-এর ভিত্তি কোর্সের পর্ব ৬৪: scaler শুধু train-এ শেখে)। Kernel দূরত্বভিত্তিক, তাই scaling না করলে বড় এককের feature-ই সব ঠিক করে দেয়। তারপর একটি আলাদা, তালাবন্ধ ৪,০০০ অর্ডারের test-এ accuracy, fit আর predict-এর সময়। γ = ০.৫ আর C = ১ ইচ্ছা করে সবার জন্য এক রাখা হয়েছে, যাতে তুলনাটি শুধু approximation-এর; বাস্তবে এগুলো cross-validation দিয়ে বাছবেন (AI-এর ভিত্তি কোর্সের পর্ব ৬৬)।

import time
import numpy as np
from sklearn.kernel_approximation import Nystroem, RBFSampler
from sklearn.linear_model import SGDClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC, LinearSVC
from bazar_fraud import make_fraud

X, y = make_fraud(4000)                          # train: ৪,০০০ অর্ডার
Xte, yte = make_fraud(4000, seed=7)              # তালাবন্ধ test
g, D = 0.5, 300
models = {
    "linear SVM only": LinearSVC(C=1.0),
    "exact RBF SVC": SVC(kernel="rbf", C=1.0, gamma=g),
    "Nystroem+LinearSVC": make_pipeline(Nystroem(gamma=g, n_components=D, random_state=0),
                                        LinearSVC(C=1.0)),
    "Nystroem+SGD hinge": make_pipeline(Nystroem(gamma=g, n_components=D, random_state=0),
                                        SGDClassifier(loss="hinge", alpha=1e-4, random_state=0)),
    "RBFSampler+LinearSVC": make_pipeline(RBFSampler(gamma=g, n_components=D, random_state=0),
                                          LinearSVC(C=1.0)),
}
print(f"base rate (flagged) = {yte.mean():.3f}")
print("model                  fit s  predict s  test acc")
for name, m in models.items():
    pipe = make_pipeline(StandardScaler(), m)
    t0 = time.perf_counter(); pipe.fit(X, y); t1 = time.perf_counter()
    acc = pipe.score(Xte, yte); t2 = time.perf_counter()
    print(f"{name:21s} {t1 - t0:6.3f}  {t2 - t1:8.3f}   {acc:.4f}")
svc = models["exact RBF SVC"]
print(f"exact SVC keeps {svc.n_support_.sum()} support vectors; "
      f"Nystroem keeps {D} landmarks")

আসল আউটপুট (ব্রাউজারের Python, একটি run; সময় যন্ত্রভেদে আলাদা):

base rate (flagged) = 0.268
model                  fit s  predict s  test acc
linear SVM only        0.009     0.002   0.7750
exact RBF SVC          0.527     0.482   0.9345
Nystroem+LinearSVC     0.697     0.334   0.9360
Nystroem+SGD hinge     0.481     0.332   0.9277
RBFSampler+LinearSVC   0.290     0.046   0.9355
exact SVC keeps 1024 support vectors; Nystroem keeps 300 landmarks

Accuracy. ফ্ল্যাগের হার ০.২৬৮, তাই “সবাই স্বাভাবিক” বললেই ০.৭৩২. শুধু linear SVM ০.৭৭৫, প্রায় কোনো কাজের নয়: বৃত্তাকার সীমা সরলরেখায় ধরা যায় না। Exact RBF SVC ০.৯৩৪৫, আর তিনটি approximation ০.৯২৮ থেকে ০.৯৩৬: কার্যত সমান। মনে রাখুন ৫% label উল্টানো, তাই প্রায় ৯৫%-এর বেশি কোনো model পাবে না। মজার বিষয়: RBFSampler-এর Gram ভুল Nyström-এর চেয়ে অনেক বেশি ছিল, তবু accuracy প্রায় এক। Classifier-এর দরকার শুধু ভালো সীমা, matrix-এর প্রতিটি ঘর নয়, আর regularization গোলমাল কিছুটা গিলে ফেলে; তবে এটি এই ডেটার একটি run, সাধারণ নিয়ম নয়।

সময়: ৪,০০০ সারিতে exact SVC-এর fit (০.৫৩ সেকেন্ড) Nyström + LinearSVC-এর (০.৭০) চেয়ে কম! ছোট ডেটায় approximation-এর লাভ নেই, উল্টো বাড়তি খরচ। লাভ prediction-এ: RBFSampler-এর predict ০.০৪৬ সেকেন্ড, exact SVC-এর ০.৪৮, কারণ SVC প্রতিটি নতুন অর্ডারকে ১,০২৪টি support vector-এর সঙ্গে মেলায়। আর fit-এর লাভ আসে বড় n-এ, যা অংশ ২-এর ডেস্কটপ টেবিলে দেখেছেন।

৮ · কখন SVM নয় — সিদ্ধান্তের মানচিত্র

SVM একটি চমৎকার যন্ত্র, কিন্তু সব কাজের নয়। নিচের টেবিল কয়েকটি সাধারণ পরিস্থিতি আর যুক্তিসঙ্গত প্রথম পছন্দ দেখায়; এগুলো অভিজ্ঞতার নিয়ম, প্রমাণিত সূত্র নয়, আর শেষ কথা সবসময় আপনার ডেটায় cross-validation.

পরিস্থিতিExact kernel SVMপ্রথমে যা চেষ্টা করবেন
কয়েক হাজার থেকে কয়েক দশ হাজার সারি, অল্প সংখ্যার feature, অ-রৈখিক সীমাভালো পছন্দRBF SVC, C আর γ CV দিয়ে; পাশে boosting
লাখ বা কোটি সারিঅবাস্তবNyström বা RBFSampler + linear model; বা boosting
Sparse লেখা, অনেক শব্দসাধারণত দরকার নেইLinearSVC বা SGDClassifier, পাশে logistic regression
অনেক categorical আর missing মানের tabular ডেটাদুর্বল (one-hot, scaling, imputation লাগে)Gradient boosting (মডিউল ৭)
সৎ সম্ভাবনা লাগে (খরচের threshold, বাজেট)Score সম্ভাবনা নয়Logistic regression বা boosting, তারপর calibration যাচাই
মিলিসেকেন্ডে prediction, ছোট modelহাজার হাজার support vectorLinear model বা ছোট গাছের ensemble
শুধু “স্বাভাবিক” ডেটা আছে, অস্বাভাবিক খুঁজতে হবেOne-class SVM একটি বিকল্পপর্ব ৪৩ (Anomaly detection — isolation forest, LOF আর one-class SVM)

সম্ভাবনা নিয়ে আলাদা কথা। পর্ব ২৬-এ দেখেছেন, SVM-এর decision function সম্ভাবনা নয়। SVC(probability=True) দিলে libsvm ভেতরে ৫-fold cross-validation চালিয়ে Platt scaling শেখে; scikit-learn-এর docs সতর্ক করে যে এতে fit ধীর হয় আর predict_proba কখনো predict-এর সঙ্গে না-ও মিলতে পারে। বড় ডেটায় এটি প্রায় পাঁচ গুণ বাড়তি fit. সম্ভাবনা লাগলে CalibratedClassifierCV আলাদা ভাগে, অথবা এমন model যার log loss সরাসরি শেখা।

Tabular ডেটা। বাজারঘরের অর্ডারের টেবিলে জেলা, courier, পেমেন্টের ধরন, পণ্যের বিভাগ: অনেক categorical feature, ভিন্ন একক, missing মান। Kernel SVM-এর জন্য সবকিছু one-hot আর scale করতে হয়, আর RBF-এর একটি γ সব feature-এর দূরত্বকে একভাবে ধরে। গাছভিত্তিক gradient boosting এসব প্রায় নিজেই সামলায়, আর অনেক প্রকাশিত তুলনা আর প্রতিযোগিতার অভিজ্ঞতায় মাঝারি থেকে বড় tabular ডেটায় সে প্রায়ই সবচেয়ে ভালো ফল দেয়; তবে এটি সব ডেটায় সত্য নয়। বিস্তারিত পর্ব ৩৬ (XGBoost, LightGBM আর CatBoost — পার্থক্য আর হাতে-কলমে) আর পর্ব ৩৮ (Tabular ডেটায় model বাছাই — tree, linear না neural, আর tuning-এর বাজেট)-এ।

নিচে বাজারঘরের তিনটি কাল্পনিক পরিস্থিতিতে ধাপে ধাপে সিদ্ধান্ত। প্রতিটিতে প্রথমে আকার আর memory হিসাব, তারপর প্রয়োজন, তারপর পছন্দ।

প্রশ্ন: আকার কত, কী লাগবে, কোন model আগেসব সংখ্যা কাল্পনিক

            
  1. তিনটি কাজ। একই দোকানের তিনটি আলাদা প্রশ্ন: আকার, feature-এর ধরন আর আউটপুটের প্রয়োজন তিনটিতেই আলাদা। তাই উত্তরও আলাদা হবে।
  2. ক: প্রতারণার ঝুঁকি। পুরো Gram matrix ৭২ TB, exact SVC আলোচনার বাইরে। Categorical feature আর সম্ভাবনার দরকার boosting-এর দিকে ঠেলে; logistic regression সরল baseline. Nyström + linear model সম্ভব, কিন্তু categorical আর সম্ভাবনা দুটি সমস্যাই থেকে যায়।
  3. খ: পণ্যের বিভাগ। Sparse TF-IDF, কয়েক লাখ কলাম। এখানে linear SVM দ্রুত আর শক্ত baseline; ৪০টি বিভাগ LinearSVC one-vs-rest দিয়ে সামলায় (পর্ব ২৫). RBF kernel যোগ করলে খরচ অনেক, লাভ সাধারণত সামান্য।
  4. গ: ফেরত-দাবি। মাত্র ৩,০০০ সারি, Gram ৭২ MB. অ-রৈখিক সীমা আর শুধু ক্রম দরকার, তাই exact RBF SVC একদম ঠিক জায়গা। C আর γ GridSearchCV দিয়ে, আর একই fold-এ boosting-এর সঙ্গে তুলনা।
  5. সারাংশ। SVM বাদ দেওয়ার কারণগুলো মূলত চারটি: বিশাল n, সম্ভাবনার দরকার, categorical tabular ডেটা আর prediction-এর গতি। কোনোটি না থাকলে SVM এখনো শক্ত প্রার্থী।

আরও গভীরে: Landmark এলোমেলোভাবে না বেছে k-means কেন্দ্র নিলে (Kai Zhang, Ivor Tsang আর James Kwok, ICML ২০০৮) বা leverage score অনুযায়ী বাছলে (Ahmed Alaoui আর Michael Mahoney, ২০১৫; Cameron Musco আর Christopher Musco, ২০১৭) একই m-এ ভুল প্রায়ই কমে। Tianbao Yang ও সহকর্মীরা NIPS ২০১২-তে তাত্ত্বিক আর পরীক্ষামূলকভাবে দেখান, eigenvalue-এর মধ্যে বড় ফাঁক থাকলে Nyström random features-এর চেয়ে অনেক দ্রুত ভালো হয়। আর FALKON (Alessandro Rudi, Luigi Carratino, Lorenzo Rosasco, ২০১৭) Nyström-কে preconditioned conjugate gradient-এর সঙ্গে মিলিয়ে কোটি সারিতে kernel ridge regression চালানোর পথ দেখায়।

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

  • n = ৫০,০০০-এ float64 Gram matrix কত বড়? ৮ × ৫০,০০০² = ২ × ১০¹⁰ বাইট = ২০ GB.
  • Nyström-এর সূত্র আর খরচ? K ≈ C W⁺ Cᵀ; C হলো n×m, W হলো m×m; সময় প্রায় n·m² + m³, memory n·m.
  • RBF-এর random Fourier feature? ω ~ N(0, 2γI), b ~ U(0, 2π), z(x) = √(2/D) cos(ωᵀx + b); ভুল প্রায় 1/√D হারে কমে।
  • Landmark বিন্দুর সারিতে Nyström কি নিখুঁত? হ্যাঁ, landmark-এর সারি আর কলাম হুবহু মেলে; ভুল শুধু বাকি অংশে, আর K − K̃ PSD.
  • লাখ লাখ লেখার নথিতে কোন SVM? LinearSVC বা SGDClassifier(loss="hinge"), sparse TF-IDF-এর ওপর; kernel SVC নয়।

৯ · AI-এর যুগে — বড় ডেটার kernel কোড যাচাই

AI assistant-কে “fraud detection-এর জন্য SVM লিখে দাও” বললে প্রায়ই সে SVC() লিখে দেয়, ডেটা কত বড় না জেনেই। কোডটি ছোট নমুনায় চলে, তারপর আসল ডেটায় ঘণ্টার পর ঘণ্টা আটকে থাকে। পরিচিত ভুলগুলো:

AI প্রায়ই যা করেকেন ভুলকীভাবে ধরবেন
লাখ সারিতে SVC(kernel="rbf") বা probability=TrueFit অন্তত n² হারে; probability আরও ৫-foldn দ্বিগুণ করে সময় মাপুন; ঢাল দেখে বড় n-এ অনুমান
RBFSampler() বা Nystroem() ডিফল্ট γ-তে, SVC-এর সঙ্গে তুলনাডিফল্ট γ আলাদা (১.০, ১/d, "scale"); তুলনা অসমসব model-এ একই γ লিখে দিন, বা CV দিয়ে আলাদা বাছুন
Scaling ছাড়া kernel approximationদূরত্বে বড় এককের feature-ই প্রাধান্য পায়StandardScaler একই pipeline-এর প্রথমে
“Nyström exact kernel দেয়” বা “RFF সবসময় ভালো” দাবিদুটিই আনুমানিক; কোনটি ভালো, তা ডেটার ওপর নির্ভর করেছোট নমুনায় পুরো K-এর সঙ্গে আপেক্ষিক ভুল মাপুন
Approximation-এর accuracy train-এ বা একটি split-এ দেখিয়ে সিদ্ধান্তএকটি seed-এর ভাগ্য; তালাবন্ধ test নেইকয়েকটি seed আর CV fold; শেষে একবার test

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

আমার কাছে বাজারঘরের প্রায় ২০ লাখ অর্ডার আছে, ১৫টি সংখ্যার feature, লক্ষ্য সন্দেহজনক কি না (scikit-learn 1.8.0). সীমাটি অ-রৈখিক বলে মনে হয়। ১. কোড লেখার আগে exact RBF SVC-এর Gram matrix-এর memory আর fit time-এর মোটামুটি হিসাব দাও, কোন অনুমান ধরে তা লিখে। ২. Nystroem + LinearSVC আর RBFSampler + LinearSVC-এর Pipeline দাও, StandardScaler সবার আগে; gamma, C আর n_components নিজে লিখে দাও, ডিফল্টের ওপর ছেড়ো না। ৩. ২০,০০০ সারির নমুনায় exact SVC-এর সঙ্গে তুলনা করো: একই CV fold, একই gamma, কয়েকটি seed; accuracy, fit আর predict-এর সময় একটি টেবিলে। ৪. পাশে HistGradientBoostingClassifier আর logistic regression baseline রাখো। ৫. সম্ভাবনা লাগলে probability=True ব্যবহার কোরো না; আলাদা ভাগে CalibratedClassifierCV দাও আর reliability diagram দেখাও। ৬. যে class আর argument ব্যবহার করছ, সেগুলো 1.8.0-এর docs-এ কোথায় আছে বলো; কোনো warning চাপা দিও না, আর test set শুধু শেষে একবার ব্যবহার করো।

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

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

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

প্র ০১Random features-এর প্রবন্ধ দশ বছর পরে পুরস্কার পেল, আর পুরস্কারের বক্তৃতায় পুরো ক্ষেত্রকে “alchemy” বলা হলো। এই ঘটনা থেকে ML চর্চা নিয়ে কী শেখার আছে?

Ali Rahimi আর Benjamin Recht-এর ২০০৭ সালের প্রবন্ধের মূল কথা ছিল সরল: kernel machine-এর পুরো Gram matrix না বানিয়ে, এলোমেলো cos feature-এর ওপর একটি linear model শেখালেই প্রায় একই ফল। পরের বছর তাঁরা “random kitchen sinks” নামে আরও এক ধাপ এগোন: শুধু kernel আন্দাজ নয়, এলোমেলো feature-এর ওপর সরাসরি ঝুঁকির সীমাও দেন। ২০১৭ সালে NIPS সম্মেলনে এই কাজ “test of time” পুরস্কার পায় বলে ব্যাপকভাবে জানা যায়।

পুরস্কারের বক্তৃতায় Rahimi যা বললেন, তা নিয়েই বেশি আলোচনা হয়। তাঁর অভিযোগ ছিল, deep learning-এর অনেক চর্চা “alchemy”-র মতো হয়ে গেছে: কৌশল কাজ করে, কিন্তু কেন করে তা কেউ ঠিক জানে না, আর পরীক্ষাগুলো প্রায়ই কারণ খোঁজার বদলে শুধু benchmark-এর সংখ্যা বাড়ায়। Yann LeCun প্রকাশ্যে আপত্তি জানান: তাঁর মতে প্রকৌশল প্রায়ই তত্ত্বের আগে চলে, আর তত্ত্ব না থাকা মানেই কাজটি অবৈজ্ঞানিক নয়। এই বিতর্কের কোনো এক পক্ষ পুরোপুরি জেতেনি; দুই পক্ষেই যুক্তি আছে।

এই পর্বের সঙ্গে সম্পর্ক সরাসরি। Random Fourier features এমন একটি পদ্ধতি, যার তত্ত্ব পরিষ্কার: প্রত্যাশা kernel-এর সমান, ভুল প্রায় 1/√D হারে কমে, আর আপনি ছোট নমুনায় তা মেপে দেখতে পারেন, যেমন অংশ ৬-এর ব্লকে দেখেছেন। একই সঙ্গে প্রবন্ধটি দেখিয়েছিল যে খুব সরল একটি baseline অনেক জটিল পদ্ধতির সঙ্গে পাল্লা দিতে পারে।

ব্যবহারিক শিক্ষা তিনটি। এক, যেকোনো নতুন পদ্ধতির পাশে একটি সরল, ভালোভাবে বোঝা baseline রাখুন। দুই, ablation করুন: কোন অংশ বাদ দিলে ফল কতটা পড়ে, তা না জানলে উন্নতির কারণ জানা যায় না (পর্ব ০৪-এর পুনরুৎপাদনযোগ্য experiment). তিন, আনুমানিক পদ্ধতির ভুল ছোট নমুনায় সরাসরি মাপুন, শুধু শেষ accuracy দেখে সন্তুষ্ট হবেন না।

মূল উপলব্ধি: সরল, বোঝা যায় এমন পদ্ধতি আর তার মাপা ভুল একটি শক্ত ভিত্তি; জটিল পদ্ধতির উন্নতি দাবি করার আগে সেই ভিত্তির সঙ্গে সৎ তুলনা দরকার।

প্র ০২২০০০-এর দশকে SVM ছিল machine learning-এর তারকা। পরের দশকে neural network আর boosting কেন অনেক জায়গায় তার জায়গা নিল? Scale কতটা বড় কারণ?

Corinna Cortes আর Vladimir Vapnik ১৯৯৫ সালে soft-margin SVM প্রকাশ করেন; তার আগে Bernhard Boser, Isabelle Guyon আর Vapnik ১৯৯২ সালে kernel trick-কে maximum margin-এর সঙ্গে মিলিয়েছিলেন। এর পরের প্রায় পনেরো বছর SVM ছিল অনেক প্রয়োগের প্রথম পছন্দ: convex সমস্যা, তাই local minimum-এর ভয় নেই; margin-এর তত্ত্ব; আর লেখা, হাতের লেখা অঙ্ক বা জৈবতথ্যের মতো ক্ষেত্রে শক্ত ফল। Joachims-এর ১৯৯৮ সালের কাজ লেখার শ্রেণিবিভাগে SVM-কে প্রায় মানদণ্ড বানিয়ে দেয়।

ছবি বদলাতে শুরু করে ডেটার আকার বাড়ার সঙ্গে। এই পর্বের অংশ ১ আর ২-এর হিসাবই মূল কারণগুলোর একটি: exact kernel SVM-এর সময় আর prediction-এর খরচ ডেটার সঙ্গে দ্রুত বাড়ে, অথচ neural network mini-batch SGD দিয়ে কোটি নমুনায় শেখে, আর তার খরচ প্রায় রৈখিক। ২০১২ সালে AlexNet ImageNet প্রতিযোগিতায় বড় ব্যবধানে জেতে; সেখানে লাখ লাখ ছবি আর GPU একসঙ্গে কাজ করেছিল। তবে scale একমাত্র কারণ নয়। ছবি, শব্দ আর ভাষায় network নিজেই feature শেখে, যেখানে kernel একটি আগে থেকে ঠিক করা মিলের মাপ; ভালো kernel বানানোই কঠিন ছিল।

Tabular ডেটায় প্রতিযোগী এসেছে অন্য দিক থেকে: gradient boosting-এর দ্রুত বাস্তবায়ন (XGBoost ২০১৪-১৬, পরে LightGBM, CatBoost). এরা categorical আর missing মান সহজে সামলায়, scaling চায় না, আর অনেক প্রতিযোগিতায় ভালো ফল দিয়েছে। Manuel Fernández-Delgado ও সহকর্মীদের ২০১৪ সালের বড় তুলনায় (JMLR, শতাধিক classifier, অনেক ছোট ডেটাসেট) random forest আর Gaussian kernel SVM দুটিই শীর্ষের কাছে ছিল; অর্থাৎ ছোট ডেটায় SVM পিছিয়ে পড়েনি। পরে এই তুলনার পদ্ধতি নিয়েও সমালোচনা হয়েছে, তাই একে চূড়ান্ত রায় না ভাবাই ভালো।

তাই সৎ উত্তর হলো: SVM হারিয়ে যায়নি, তার জায়গা ছোট হয়েছে। মাঝারি আকারের, ভালোভাবে তৈরি সংখ্যার feature-এ, বা লেখার linear baseline হিসেবে সে এখনো শক্ত।

মূল উপলব্ধি: একটি পদ্ধতির জনপ্রিয়তা নির্ভর করে ডেটার আকার, hardware আর feature কে বানায় তার ওপর; scale SVM-এর জায়গা ছোট করেছে, কিন্তু মুছে দেয়নি।

প্র ০৩Nyström-এ landmark কীভাবে বাছবেন: এলোমেলো, k-means কেন্দ্র, না leverage score? প্রতিটির দাম আর লাভ কী?

সবচেয়ে সরল উপায় এলোমেলো বাছাই, যা scikit-learn-এর Nystroem করে। খরচ প্রায় শূন্য, আর বিশ্লেষণ সহজ। কিন্তু দুর্বলতাও স্পষ্ট: ডেটার কোনো ছোট কিন্তু গুরুত্বপূর্ণ অংশ, যেমন বাজারঘরের বিরল প্রতারণার গুচ্ছ, থেকে হয়তো একটিও landmark আসবে না। তখন সেই অংশের বিন্দুগুলোর K̃ খারাপ হয়, অংশ ৪-এর x = ০ বিন্দুর মতো: নিজের সঙ্গে মিল ১-এর বদলে ০.৪৫. ঠিক সেখানেই classifier-এর সবচেয়ে বেশি দরকার ছিল।

Kai Zhang, Ivor Tsang আর James Kwok ২০০৮ সালে দেখান, k-means-এর কেন্দ্রকে landmark নিলে approximation-এর ভুল প্রায়ই কমে, কারণ কেন্দ্রগুলো ডেটার ঘন অঞ্চলগুলো সমানভাবে ঢাকে। দাম: k-means চালাতে বাড়তি সময়, আর কেন্দ্রগুলো আসল সারি নয় (যা গোপনীয়তার জন্য কখনো সুবিধাও)। একটি সমস্যা থেকে যায়: k-means ঘন অঞ্চলকে বেশি গুরুত্ব দেয়, বিরল অঞ্চলকে নয়।

তত্ত্বের দিক থেকে সবচেয়ে শক্ত উপায় leverage score অনুযায়ী বাছাই। Ahmed Alaoui আর Michael Mahoney ২০১৫ সালে (NIPS) ridge leverage score-এর ওপর ভিত্তি করে বাছাইয়ের গ্যারান্টি দেন, আর Cameron Musco ও Christopher Musco ২০১৭ সালে এই score পুরো K না বানিয়েই ধাপে ধাপে অনুমানের পদ্ধতি দেন। মূল ভাবনা: যে বিন্দু অন্যদের দিয়ে ভালো ব্যাখ্যা হয় না, তাকে বাছার সম্ভাবনা বেশি। দাম হলো জটিলতা আর বাস্তবায়নের ঝামেলা।

ব্যবহারিক পরামর্শ: প্রথমে এলোমেলো, কয়েকটি seed-এ, আর m বাড়িয়ে দেখুন accuracy কোথায় থামে। Seed বদলালে ফল বেশি নড়লে, বা বিরল শ্রেণির recall খারাপ হলে, তখন k-means বা শ্রেণিভিত্তিক বাছাই চেষ্টা করুন। আর সবসময় ছোট নমুনায় পুরো K-এর সঙ্গে আপেক্ষিক ভুল মেপে নিন, অংশ ৫-এর মতো।

মূল উপলব্ধি: landmark বাছাই একটি trade-off: এলোমেলো সস্তা আর নিরপেক্ষ, k-means ঘন অঞ্চল ভালো ঢাকে, leverage score তত্ত্বে সেরা; বিরল কিন্তু গুরুত্বপূর্ণ অঞ্চল আলাদা মনোযোগ চায়।

প্র ০৪Random Fourier features আসলে একটি দুই স্তরের neural network, যার প্রথম স্তর শেখানো হয় না। Kernel পদ্ধতি আর neural network-এর এই সম্পর্ক কতটা গভীর?

z(x) = √(2/D) cos(ωᵀx + b) সূত্রটি আবার দেখুন। ω হলো ওজন, b হলো bias, cos হলো activation function, আর তার ওপর linear model হলো আউটপুট স্তর। পার্থক্য একটাই: প্রথম স্তরের ওজন এলোমেলোভাবে বসিয়ে স্থির রাখা হয়, শুধু শেষ স্তর শেখে। এই ধরনের ভাবনা পুরোনো; Radford Neal ১৯৯০-এর দশকে দেখান, একটি লুকানো স্তরের network-এর প্রস্থ অসীম করলে, এলোমেলো ওজনে, তার আউটপুট একটি Gaussian process হয়ে যায়, যার একটি নির্দিষ্ট kernel আছে। Gaussian process নিয়ে বিস্তারিত পর্ব ৪৬-এ আসবে।

২০১৮ সালে Arthur Jacot, Franck Gabriel আর Clément Hongler “neural tangent kernel” (NTK) প্রস্তাব করেন: খুব চওড়া network-কে gradient descent দিয়ে শেখালে, কিছু নির্দিষ্ট শর্তে, তার আচরণ একটি নির্দিষ্ট kernel দিয়ে kernel regression-এর মতো হয়। এতে বোঝা গেল কেন অতি-চওড়া network-এর আচরণ বিশ্লেষণ করা যায়। কিন্তু বাস্তবের সীমিত প্রস্থের network প্রায়ই NTK-এর চেয়ে ভালো করে, কারণ তারা feature শেখে, যা স্থির kernel পারে না। তাই “neural network মানেই kernel machine” বলা অতিরঞ্জন; সম্পর্কটি নির্দিষ্ট অবস্থায় সত্য।

এর একটি ব্যবহারিক দিকও আছে। Siyuan Ma আর Mikhail Belkin-এর EigenPro, আর FALKON-এর মতো কাজ দেখিয়েছে, GPU আর ভালো preconditioner দিয়ে kernel পদ্ধতিকেও কয়েক লাখ থেকে কোটি নমুনায় চালানো যায়। অর্থাৎ kernel আর network-এর লড়াই শুধু তত্ত্বের নয়, hardware আর software-এর বিনিয়োগেরও।

বাজারঘরের দৃষ্টিতে শিক্ষাটি এমন: RBFSampler + LinearSVC হলো একটি সস্তা, স্থির-স্তরের network. ফল যথেষ্ট ভালো না হলে প্রথম স্তরও শেখানো, অর্থাৎ একটি ছোট neural network, পরের স্বাভাবিক ধাপ; deep learning কোর্সে তা বিস্তারিত আসবে।

মূল উপলব্ধি: random features kernel আর neural network-এর মাঝখানের সেতু; শেখানো না-যাওয়া প্রথম স্তর kernel-এর আন্দাজ, আর সেই স্তর শেখানোই network-কে kernel-এর বাইরে নিয়ে যায়।

প্র ০৫একটি শেখানো SVC model ফাইলে কী থাকে? বাজারঘর যদি গ্রাহকের ডেটা মুছে ফেলার অনুরোধ পায়, তবে SVC, Nyström আর random features-এর মধ্যে পার্থক্য কী?

Linear model-এ থাকে শুধু w আর b. কিন্তু kernel SVC-এর prediction f(x) = ∑ αiyik(xi, x) + b চায় প্রতিটি support vector xi-কে। তাই scikit-learn-এর SVC object-এর support_vectors_-এ train ডেটার আসল সারিগুলোই হুবহু রাখা থাকে। অংশ ৭-এর ছোট উদাহরণেই চার হাজারের মধ্যে প্রায় এক হাজার সারি। Model ফাইলটি কাউকে দিলে, বা কোনো server-এ রাখলে, সেই গ্রাহকদের অর্ডারের তথ্যও সঙ্গে যায়।

Nyström-এর অবস্থাও কাছাকাছি: Nystroem object-এর components_-এ m টি landmark সারি আসল রূপে থাকে। অন্যদিকে RBFSampler-এ থাকে শুধু এলোমেলো ω আর b, যারা ডেটা দেখেনি; তার ওপরের linear model-এ শুধু ওজন। এটি গোপনীয়তার দিক থেকে স্পষ্ট সুবিধা। তবে সাবধান: শুধু ওজন থাকলেই model নিরাপদ নয়। শেখানো model থেকে কোনো নির্দিষ্ট সারি train-এ ছিল কি না আন্দাজের (membership inference) গবেষণা দেখিয়েছে, ওজনও তথ্য ফাঁস করতে পারে।

মুছে ফেলার অনুরোধে পার্থক্যটি বাস্তব। একজন গ্রাহকের সারি SVC-এর support vector হলে, শুধু ডাটাবেস থেকে মুছলে হবে না, model ফাইলেও সে আছে; model আবার শেখাতে হবে। এমনকি support vector না হলেও, সারিটি শেখার ফলকে প্রভাবিত করেছে কি না, সেই প্রশ্ন থাকে। “Machine unlearning” নামে একটি পুরো গবেষণার ক্ষেত্র এই প্রশ্ন নিয়ে কাজ করছে। বিভিন্ন দেশের তথ্য-সুরক্ষা আইন এ বিষয়ে কী চায়, তা আইনের ভাষা আর ব্যাখ্যার ওপর নির্ভর করে; এখানে কোনো আইনি পরামর্শ নয়, শুধু প্রযুক্তিগত দিকটি।

ব্যবহারিক নিয়ম: model ফাইলকে ডেটার মতোই সংবেদনশীল ধরুন; কোন model-এ আসল সারি জমা থাকে তা নথিতে লিখুন (পর্ব ০৫-এর ডেটার মান আর versioning); আর আবার শেখানোর পাইপলাইন সহজ রাখুন, যাতে মুছে ফেলার অনুরোধে model নতুন করে বানানো যায়।

মূল উপলব্ধি: kernel SVC আর Nyström model-এর ভেতরে train ডেটার আসল সারি থাকে; গোপনীয়তা আর মুছে ফেলার দায়িত্ব শুধু ডাটাবেসের নয়, model ফাইলেরও।

অনুশীলন

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

  1. পূর্ণ numerical: বাজারঘরের ২,০০,০০০টি অর্ডার, ২০টি feature. (ক) float64-এ পুরো Gram matrix কত GB? (খ) m = ৫০০ landmark-এ Nyström-এর C আর W কত জায়গা নেয়? (গ) পুরো K-এর তুলনায় কত গুণ কম kernel মান হিসাব করতে হয়? (ঘ) D = ৫০০টি random Fourier feature-এ Gram matrix-এর আপেক্ষিক ভুল ০.২০ এলে, ভুল ০.০৫-এ নামাতে মোটামুটি কত D লাগবে, আর তখন feature matrix কত বড়?

    (ক) ৮ × (২ × ১০⁵)² = ৮ × ৪ × ১০¹⁰ = ৩.২ × ১০¹¹ বাইট = ৩২০ GB.

    (খ) C: ৮ × ২ × ১০⁵ × ৫০০ = ৮ × ১০⁸ বাইট = ৮০০ MB. W: ৮ × ৫০০² = ২ MB.

    (গ) পুরো K-এ n² = ৪ × ১০¹⁰ মান, C-তে n·m = ১০⁸ মান; অনুপাত n/m = ৪০০ গুণ কম।

    (ঘ) ভুল প্রায় 1/√D হারে কমে। ভুল চার ভাগের এক ভাগ করতে √D চার গুণ, অর্থাৎ D ষোলো গুণ: ৫০০ × ১৬ = ৮,০০০. Feature matrix: ৮ × ২ × ১০⁵ × ৮,০০০ = ১.২৮ × ১০¹⁰ বাইট = ১২.৮ GB. এটি দেখায় কেন খুব নির্ভুল kernel আন্দাজের জন্য random features ব্যয়বহুল; অংশ ৬-এর ছবিতে Nyström একই ভুলে অনেক কম component চেয়েছে।

  2. সমালোচনা ও নকশা: একটি AI assistant এই পরিকল্পনা দিয়েছে: “বাজারঘরের ৫০ লাখ অর্ডারে SVC(kernel="rbf", probability=True) শেখাব, scaling লাগবে না কারণ RBF নিজেই দূরত্ব মাপে। ধীর হলে RBFSampler() ডিফল্ট দিয়ে বদলে নেব, আর train accuracy ৯৯% হলে model তৈরি। জেলা আর courier-এর নাম LabelEncoder দিয়ে সংখ্যা বানিয়ে দেব।” কী কী সমস্যা, আর সঠিক নকশা কী?

    সমস্যা: (১) ৫০ লাখ সারিতে Gram matrix ২০০ TB-এর হিসাব; exact SVC-এর fit অন্তত n² হারে বাড়ে, অবাস্তব। (২) probability=True ভেতরে আরও ৫-fold CV চালায়, fit আরও ধীর, আর predict_proba predict-এর সঙ্গে না-ও মিলতে পারে। (৩) RBF দূরত্ব মাপে বলেই scaling জরুরি; বড় এককের feature সব ঠিক করে দেয়। (৪) RBFSampler()-এর ডিফল্ট γ = ১.০, যা আগের SVC-এর γ-এর সঙ্গে মেলার কোনো কারণ নেই। (৫) Train accuracy দিয়ে কিছু বলা যায় না; তালাবন্ধ test আর CV লাগে। (৬) LabelEncoder জেলাগুলোকে ১, ২, ৩ বানায়, যেন তাদের মধ্যে ক্রম আর দূরত্ব আছে; kernel-এর দূরত্বে এটি অর্থহীন।

    সঠিক নকশা: আগে একটি সরল baseline (logistic regression, one-hot সহ) আর gradient boosting (categorical সরাসরি সামলায়)। অ-রৈখিক kernel চাইলে Pipeline(ColumnTransformer(one-hot + StandardScaler), Nystroem(gamma=…, n_components=…), LinearSVC বা SGDClassifier), γ আর C একটি নমুনায় CV দিয়ে বাছা। সম্ভাবনা লাগলে আলাদা ভাগে CalibratedClassifierCV, আর reliability diagram. শেষে তালাবন্ধ test-এ একবার।

  3. আউটপুট অনুমান করুন: চালানোর আগে লিখে ফেলুন নিচের কোড কী ছাপবে। (সংকেত: m = n হলে কী হয়?)
    import numpy as np
    from sklearn.kernel_approximation import Nystroem
    X = np.array([[0.0], [1.0], [2.0]])
    Z = Nystroem(gamma=0.5, n_components=3, random_state=0).fit_transform(X)
    print(Z.shape)
    print(np.round(Z @ Z.T, 3))
    (3, 3)
    [[1.    0.607 0.135]
     [0.607 1.    0.607]
     [0.135 0.607 1.   ]]

    তিনটি বিন্দু, তিনটি landmark: প্রতিটি বিন্দুই landmark. তখন C = W = K, আর K উল্টানো যায়, তাই K̃ = K K⁻¹ K = K, হুবহু। মানগুলো exp(−০.৫ × ১) = ০.৬০৭ আর exp(−০.৫ × ৪) = ০.১৩৫. Z-এর আকার (৩, ৩), কারণ n_components = ৩. (ডেস্কটপে scikit-learn 1.9.1-এ চালিয়ে মেলানো।)

  4. কোড লিখুন: RBF kernel (γ = ০.৫) আর দুটি বিন্দু x = (০.৩, −০.২, ১.০), y = (১.০, ০.৪, ০.১) নিন। নিজে লেখা random Fourier features দিয়ে k(x, y) আন্দাজ করুন, D = ২৫, ১০০, ৪০০, ১৬০০-এর প্রতিটিতে ২০০টি আলাদা seed-এ। প্রতিটি D-তে আন্দাজের গড় আর standard deviation ছাপুন, আর দেখান যে sd × √D প্রায় ধ্রুবক।
    import numpy as np
    gamma = 0.5
    x, y = np.array([0.3, -0.2, 1.0]), np.array([1.0, 0.4, 0.1])
    print(f"exact k = {np.exp(-gamma * np.sum((x - y) ** 2)):.4f}")
    for D in [25, 100, 400, 1600]:
        est = []
        for s in range(200):
            r = np.random.default_rng(s)
            w = r.normal(0, np.sqrt(2 * gamma), size=(3, D))
            b = r.uniform(0, 2 * np.pi, D)
            zx = np.sqrt(2 / D) * np.cos(x @ w + b)
            zy = np.sqrt(2 / D) * np.cos(y @ w + b)
            est.append(zx @ zy)
        est = np.array(est)
        print(f"D = {D:4d}  mean {est.mean():.4f}  sd {est.std():.4f}"
              f"  sd*sqrt(D) {est.std() * np.sqrt(D):.3f}")

    ডেস্কটপে সত্যিকারের run (scikit-learn লাগে না, NumPy 2.5.4):

    exact k = 0.4360
    D =   25  mean 0.4106  sd 0.1869  sd*sqrt(D) 0.935
    D =  100  mean 0.4305  sd 0.0907  sd*sqrt(D) 0.907
    D =  400  mean 0.4385  sd 0.0506  sd*sqrt(D) 1.012
    D = 1600  mean 0.4352  sd 0.0239  sd*sqrt(D) 0.954

    গড় সব D-তে আসল ০.৪৩৬০-এর কাছে (প্রত্যাশা kernel-এর সমান, তাই আন্দাজ নিরপেক্ষ)। sd প্রতিবার D চার গুণ হলে প্রায় অর্ধেক হয়, আর sd × √D প্রায় ০.৯–১.০-এ স্থির: Monte Carlo-র 1/√D নিয়ম। ২০০টি seed-এর sd-ও একটি আন্দাজ, তাই সংখ্যাগুলো একটু নড়ে।

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

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

ক্রমবড় ডেটায় Nyström + linear SVM দিয়ে একটি classifier বানানোর ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. ডেটা train আর test-এ ভাগ করা, test তালাবন্ধ রাখা
  2. শুধু train-এ StandardScaler শেখানো
  3. Train থেকে m টি landmark বেছে C আর W হিসাব করা
  4. φ(x) = W−½ c(x) দিয়ে প্রতিটি সারির m টি feature বানানো
  5. সেই feature-এ LinearSVC শেখানো, γ, C আর m cross-validation দিয়ে বাছা
  6. তালাবন্ধ test-এ একবার accuracy আর সময় মাপা

৫০,০০০টি সারির পুরো RBF Gram matrix float64-এ রাখতে কত memory লাগে?

  • ২০০ MB
  • ২০ GB
  • ১০ GB
  • ২০০ GB

n টি সারি আর m টি landmark (m ≪ n) নিয়ে Nyström feature বানানোর memory আর সময়ের মোটামুটি খরচ কী?

  • memory n², সময় n³
  • memory m², সময় m³
  • memory n·m, সময় প্রায় n·m² + m³
  • memory n·m, সময় n²·m

RBF kernel k(x, y) = exp(−γ‖x − y‖²)-এর random Fourier features-এ ω কোন বণ্টন থেকে টানা হয়?

  • N(0, 2γI)
  • N(0, γI)
  • U(0, 2π)
  • N(0, I), γ-এর ওপর নির্ভর করে না

একটি RBF Gram matrix K-এর Nyström আন্দাজ K̃ = C W⁻¹ Cᵀ (W উল্টানো যায়)। কোন কথাটি সবসময় সত্য?

  • K̃-এর প্রতিটি ঘর K-এর সমান
  • K̃-এর সব ঘর অঋণাত্মক
  • K̃-এর কর্ণের মান K-এর কর্ণের চেয়ে বড় বা সমান
  • Landmark-এর সারি আর কলাম হুবহু মেলে, আর K − K̃ PSD

D = ২০০টি random Fourier feature-এ একটি kernel মানের আন্দাজের standard deviation ০.০৮. মোটামুটি ০.০২ পেতে কত D লাগবে?

  • ৮০০
  • ৩,২০০
  • ১২,৮০০
  • D বাড়িয়ে sd কমানো যায় না

বাজারঘরের ১০ লাখ পণ্য-বর্ণনা, TF-IDF-এ পাঁচ লাখ sparse কলাম, ৩০টি বিভাগ। কোন SVM পদ্ধতি প্রথমে চেষ্টা করা যুক্তিসঙ্গত?

  • SVC(kernel="rbf"), C আর γ GridSearchCV দিয়ে
  • RBFSampler(n_components=20000) + LinearSVC
  • LinearSVC বা SGDClassifier(loss="hinge"), sparse TF-IDF-এর ওপর
  • SVC(probability=True), যাতে সম্ভাবনাও পাওয়া যায়

৩,০০০টি ফেরত-দাবি, ৬টি সংখ্যার feature, অ-রৈখিক সীমা, আর শুধু কোন দাবি আগে দেখবেন তার ক্রম দরকার। কোনটি সবচেয়ে যুক্তিসঙ্গত শুরু?

  • StandardScaler + RBF SVC, C আর γ CV দিয়ে, পাশে boosting
  • Nystroem(n_components=50) + SGDClassifier, কারণ kernel সবসময় ধীর
  • শুধু LinearSVC, কারণ এটি সবচেয়ে দ্রুত
  • Scaling ছাড়া SVC(kernel="rbf"), কারণ RBF নিজেই দূরত্ব মাপে

শব্দকোষ

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

মনে রাখুন

  1. দাম: পুরো Gram matrix ৮n² বাইট (১ লাখে ৮০ GB); SVC-এর fit অন্তত প্রায় n² হারে বাড়ে, আর prediction সব support vector-এর ওপর যোগফল।
  2. Linear SVM: LinearSVC (liblinear) আর SGDClassifier(loss="hinge") primal-এ, খরচ ডেটার আকারের সমানুপাতিক; sparse লেখায় শক্ত baseline.
  3. Nyström: K ≈ C W⁺ Cᵀ, φ(x) = W−½ c(x); landmark-এর সারি হুবহু, K − K̃ PSD; memory n·m, সময় n·m² + m³.
  4. Random Fourier features: ω ~ N(0, 2γI), b ~ U(0, 2π), z = √(2/D) cos(ωᵀx + b); নিরপেক্ষ আন্দাজ, ভুল প্রায় 1/√D; একই component-এ Nyström সাধারণত বেশি নির্ভুল।
  5. কখন SVM নয়: বিশাল n, সৎ সম্ভাবনা, categorical tabular ডেটা, দ্রুত prediction বা ছোট model; ছোট-মাঝারি অ-রৈখিক সংখ্যার ডেটায় RBF SVC এখনো শক্ত।

পরের পর্বে: মডিউল ৬ এখানে শেষ। পর্ব ৩২ (Decision tree গভীরে — CART, impurity, pruning আর missing value) দিয়ে মডিউল ৭ শুরু: এই পর্বে যে tabular ডেটার জন্য boosting-এর কথা বারবার এসেছে, তার ভিত্তি, একটি একক গাছ কীভাবে প্রশ্ন বেছে ডেটা ভাগ করে।

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

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