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

Density estimation — histogram, KDE আর mixture model

Density Estimation — Histograms, Kernel Density Estimation and Mixture Models

Taught in Bangla, this lesson estimates densities from samples with histograms, kernel density estimation and Gaussian mixtures. It derives Freedman–Diaconis and Scott bin widths, computes a KDE by hand and matches NumPy, SciPy and scikit-learn, chooses bandwidth by held-out likelihood instead of Silverman's rule, fixes boundary bias with a log transform and Jacobian, and shows the curse of dimensionality.

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

  • Density estimate কী, আর anomaly score, sampling ও generative classifier-এ এর ব্যবহার
  • Histogram-কে density হিসেবে পড়া, আর bin width-এর Freedman–Diaconis, Scott ও Sturges নিয়ম
  • KDE হাতে আর NumPy-তে, তারপর scipy.stats.gaussian_kde আর KernelDensity দিয়ে যাচাই
  • Bandwidth-এর bias–variance, Silverman-এর নিয়ম, আর held-out log-likelihood দিয়ে GridSearchCV
  • Boundary bias, log রূপান্তর আর Jacobian, KDE থেকে নতুন নমুনা, mixture model আর curse of dimensionality

AI-এর ভিত্তি কোর্সের পর্ব ৩০-এ histogram আর seaborn-এর kdeplot ছবি আঁকার যন্ত্র হিসেবে এসেছে, আর পর্ব ৩৬-এ PDF মানে কী, তা দেখেছেন। এই কোর্সের পর্ব ১৮ একটি জানা আকারের বণ্টনের parameter MLE দিয়ে বের করেছে, আর পর্ব ১৯ অনেক মাত্রার Gaussian density দেখিয়েছে। এই পর্বে প্রশ্নটি উল্টো: বণ্টনের আকার আগে থেকে জানা না থাকলে, শুধু নমুনা থেকে পুরো density কীভাবে অনুমান করবেন, আর সেই অনুমান কতটা ভালো, তা কীভাবে মাপবেন? চালানো যায় এমন চারটি ব্লক পাতাতেই চলে (ব্রাউজারের Python: NumPy, SciPy, matplotlib আর scikit-learn); প্রথমবার প্যাকেজ নামতে কয়েক সেকেন্ড লাগে, scikit-learn-এ প্রায় ১০ সেকেন্ড।

১ · Density estimation কী, আর কোথায় কাজে লাগে

একটি চায়ের দোকানের মালিক হিসাবের খাতায় প্রতিটি কাপ বিক্রির সময় লিখে রাখেন। এক মাস পরে তিনি জানতে চান, দিনের কোন সময়ে ভিড় কতটা ঘন। খাতার প্রতিটি লাইন একটি বিন্দু; বিন্দুগুলো সকাল আটটার আশপাশে আর সন্ধ্যা ছয়টার আশপাশে জমাট। এই জমাট ভাবটিকে একটি মসৃণ বক্ররেখায় বদলানোই density estimation. বক্ররেখা হাতে থাকলে তিনি বলতে পারেন কখন দুজন কর্মচারী লাগবে, আর রাত দুটোর একটি বিক্রি কতটা অস্বাভাবিক।

আনুষ্ঠানিকভাবে: একটি অজানা বণ্টন p থেকে স্বাধীনভাবে আসা নমুনা x1, …, xn দেওয়া আছে। লক্ষ্য একটি function p̂, যা সব জায়গায় অ-ঋণাত্মক, মোট integral ১, আর p-এর যতটা সম্ভব কাছে। একে বলে density estimation. এটি unsupervised learning-এর সবচেয়ে মৌলিক কাজগুলোর একটি: কোনো label নেই, শুধু “ডেটা কোথায় ঘন, কোথায় পাতলা” শেখা। একটি ভালো p̂ হাতে থাকলে অনেক কাজ হয়:

Anomaly score

p̂(x) খুব কম হলে x বিরল। −log p̂(x) একটি স্বাভাবিক “অস্বাভাবিকতার” মাপ; পর্ব ৪৩ (Anomaly detection — isolation forest, LOF আর one-class SVM) এর ওপর দাঁড়াবে।

নতুন নমুনা

p̂ থেকে বানানো ডেলিভারি সময় দিয়ে গুদাম বা রাইডারের সংখ্যা simulate করা যায়, আসল গ্রাহকের তথ্য না ছুঁয়েই।

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

P(সময় > ৪৮ ঘণ্টা) = ৪৮ থেকে অসীম পর্যন্ত p̂-এর integral: প্রতিশ্রুতি ভঙ্গের ঝুঁকি একটি সংখ্যায়।

Generative classifier

প্রতিটি শ্রেণির জন্য p̂(x | y), তারপর Bayes-এর নিয়ম। পর্ব ১৯-এর LDA/QDA এটিই করে Gaussian দিয়ে; KDE দিলে আকারের বাঁধন থাকে না।

দুটি বড় পথ আছে। Parametric: আকার আগে ধরে নিন (Gaussian, gamma, log-normal), তারপর কয়েকটি parameter MLE দিয়ে বের করুন (পর্ব ১৮)। আকার ঠিক হলে অল্প ডেটাতেই নির্ভুল; ভুল হলে যত ডেটাই দিন, ভুল থেকে যায়। Nonparametric: আকার ধরে না নিয়ে ডেটাকেই কথা বলতে দিন: histogram আর KDE. এদের “parameter” আসলে পুরো ডেটাসেট, তাই এরা nonparametric model. মাঝামাঝি পথ mixture model: কয়েকটি সরল আকারের ওজন-দেওয়া যোগফল, যা পর্ব ৩৯ (Gaussian mixture আর EM algorithm — নরম clustering)-এর বিষয়।

এই পর্বের ডেটা। বাজারঘরের ৪০০টি কাল্পনিক ডেলিভারি সময় (ঘণ্টায়), seed ৪২ দিয়ে বানানো। তিন ধরনের অর্ডার: ২৫% ঢাকার express (exponential, গড় ৪ ঘণ্টা), ৪৫% ঢাকার সাধারণ (gamma, গড় ২৪ ঘণ্টা), ৩০% ঢাকার বাইরে (gamma, গড় ৬৩ ঘণ্টা)। ডেটা আমরাই বানিয়েছি, তাই আসল density জানা; বাস্তবে এই সুবিধা থাকে না, কিন্তু শেখার সময় অনুমানকে সত্যের সঙ্গে মেলানো যায়।

২ · Histogram — সবচেয়ে পুরোনো অনুমান আর bin-এর সমস্যা

সংখ্যারেখাকে w চওড়া খোপে (bin) ভাগ করুন। x যে খোপে পড়ে, সেই খোপে nkটি নমুনা থাকলে histogram density হলো:

p̂(x) = nk / (n · w)

n দিয়ে ভাগ করলে অনুপাত, w দিয়ে ভাগ করলে “প্রতি ঘণ্টায়” ঘনত্ব, আর সব খোপের ক্ষেত্রফলের যোগ ১. matplotlib-এর hist(..., density=True) আর NumPy-র np.histogram(..., density=True) ঠিক এটিই করে। দুটি সিদ্ধান্ত লাগে: খোপের চওড়া w আর প্রথম খোপ কোথা থেকে শুরু (origin). চওড়া w বড় হলে প্রতিটি খোপে অনেক নমুনা, গণনা স্থির, কিন্তু খোপের ভেতরের আকার মুছে যায়: bias বেশি। w ছোট হলে আকার ধরা পড়ে, কিন্তু প্রতিটি খোপে দু-চারটি নমুনা, তাই উচ্চতা লাফায়: variance বেশি। এটি bias–variance tradeoff-এর সবচেয়ে চোখে-দেখা রূপ।

হিসাব করলে দেখা যায় bias² প্রায় w²-এর সমানুপাতিক আর variance প্রায় 1/(nw)-এর। দুটির যোগ ন্যূনতম হয় যখন w ∝ n−1/3, আর তখন গড় বর্গ-ভুল (MISE) কমে n−2/3 হারে। সমানুপাতিক ধ্রুবকটি আসল density-র ওপর নির্ভর করে, যা অজানা। তাই তিনটি প্রচলিত নিয়ম:

নিয়মসূত্রধরে নেয়কখন ভালো
Sturges (১৯২৬)bin সংখ্যা ⌈log2 n⌉ + 1প্রায় normal, ছোট nn কয়েকশোর কম; বড় n-এ bin খুব কম দেয়
Scott (১৯৭৯)w = 3.49 · σ̂ · n−1/3normal ডেটায় MISE ন্যূনতমপ্রায় প্রতিসম, outlier কম
Freedman–Diaconis (১৯৮১)w = 2 · IQR · n−1/3σ̂-এর বদলে robust ছড়ানোবাঁকা ডেটা আর outlier থাকলে

এখানে σ̂ নমুনার standard deviation, আর IQR = Q3 − Q1, মাঝের ৫০% মানের বিস্তার। একটি outlier σ̂-কে অনেক টানে, IQR-কে প্রায় নড়ায় না। নিচের ব্লক দুটি নিয়ম হাতে হিসাব করে, NumPy-র np.histogram_bin_edges-এর সঙ্গে মেলায়, আর চার রকম bin-এ histogram আঁকে।

import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng(42)            # বাজারঘরের ৪০০টি ডেলিভারি (ঘণ্টা)
n = 400
g = rng.choice(3, size=n, p=[0.25, 0.45, 0.30])   # express, ঢাকা, ঢাকার বাইরে
x = np.where(g == 0, rng.exponential(4, n),
     np.where(g == 1, rng.gamma(4, 6, n), rng.gamma(9, 7, n)))
q1, q3 = np.percentile(x, [25, 75])
print(f"n={n}  min={x.min():.2f}  median={np.median(x):.1f}  max={x.max():.1f}")
print(f"sd={x.std(ddof=1):.2f}  IQR={q3 - q1:.2f}")
fd = 2 * (q3 - q1) * n ** (-1 / 3)          # Freedman–Diaconis
scott = 3.49 * x.std(ddof=1) * n ** (-1 / 3)
print(f"FD width by hand={fd:.2f}  Scott width by hand={scott:.2f}")
for rule in ("fd", "scott", "sturges"):
    e = np.histogram_bin_edges(x, bins=rule)
    print(f"numpy {rule:8s}: {len(e) - 1:3d} bins, width {e[1] - e[0]:.2f}")

fig, axes = plt.subplots(2, 2, figsize=(7, 5), sharex=True)
for ax, b, name in zip(axes.flat, [4, "fd", "scott", 120],
                       ["4 bins (too wide)", "Freedman-Diaconis",
                        "Scott", "120 bins (too narrow)"]):
    ax.hist(x, bins=b, density=True, color="#93c5fd", edgecolor="#1e3a8a")
    ax.set_title(name, fontsize=10)
for ax in axes[1]:
    ax.set_xlabel("delivery time (hours)")
for ax in axes[:, 0]:
    ax.set_ylabel("density")
fig.subplots_adjust(hspace=0.35, wspace=0.25)
plt.show()

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

n=400  min=0.04  median=22.9  max=129.2
sd=26.70  IQR=36.26
FD width by hand=9.84  Scott width by hand=12.65
numpy fd      :  14 bins, width 9.23
numpy scott   :  11 bins, width 11.74
numpy sturges :  10 bins, width 12.92

প্রথম দুই লাইন: median ২২.৯ ঘণ্টা কিন্তু সর্বোচ্চ ১২৯ ঘণ্টা: ডানে লম্বা লেজ। sd ২৬.৭০ আর IQR ৩৬.২৬. হাতের হিসাব: n−1/3 = ৪০০−1/3 = ০.১৩৫৭, তাই FD = ২ × ৩৬.২৬ × ০.১৩৫৭ = ৯.৮৪ আর Scott = ৩.৪৯ × ২৬.৭০ × ০.১৩৫৭ = ১২.৬৫. NumPy-র চওড়া একটু আলাদা কেন? NumPy প্রথমে bin সংখ্যা = ⌈পরিসর / w⌉ বের করে (১২৯.১৬ / ৯.৮৪ = ১৩.১ → ১৪), তারপর পুরো পরিসর সমান ১৪ ভাগ করে: ৯.২৩. Sturges দেয় ⌈log2 ৪০০⌉ + ১ = ১০টি bin. ছবিতে: ৪টি bin-এ শূন্যের কাছের express-এর চূড়া আর ঢাকার বাইরের দ্বিতীয় কুঁজ দুটিই হারিয়ে যায়। ১২০টি bin-এ প্রতিটি খোপে গড়ে তিন-চারটি নমুনা, তাই উচ্চতা এলোমেলো লাফায়, অনেক খোপ ফাঁকা। FD আর Scott মাঝামাঝি, আর এখানে FD একটু বেশি খুঁটিনাটি দেখায়।

Histogram-এর আরও দুটি দুর্বলতা আছে। প্রথমত, origin অর্ধেক bin সরালেই ছবি বদলে যেতে পারে, অথচ ডেটা একই। দ্বিতীয়ত, p̂ ধাপের মতো: খোপের সীমানায় হঠাৎ লাফ, ভেতরে সমতল। অনেকগুলো সরানো origin-এর histogram-এর গড় নিলে (averaged shifted histogram) দুটি সমস্যাই কমে, আর সরানোর সংখ্যা অসীমের দিকে নিলে যা পাওয়া যায়, তা-ই পরের অংশের KDE (একটি ত্রিভুজ-আকারের kernel-সহ)।

৩ · KDE — প্রতিটি বিন্দুতে একটি ছোট ঢিবি

একটি মাঠে প্রতিটি অর্ডারের সময়ের জায়গায় এক মুঠো বালু ফেলুন। প্রতিটি মুঠো একটি ছোট ঢিবি বানায়; যেখানে অনেক অর্ডার কাছাকাছি, সেখানে ঢিবিগুলো মিলে উঁচু টিলা। শেষে মাঠের উঁচু-নিচু আকৃতিটিই density-র অনুমান। মুঠো কতটা ছড়িয়ে ফেলবেন, সেটিই bandwidth: খুব আঁটসাঁট হলে প্রতিটি অর্ডার আলাদা সুচালো চূড়া, খুব ছড়ানো হলে পুরো মাঠ প্রায় সমতল।

Kernel density estimation (KDE)-এর সূত্র:

p̂h(x) = (1 / (n h)) · ∑i=1n K((x − xi) / h)

এখানে K একটি kernel: শূন্যের চারপাশে প্রতিসম, অ-ঋণাত্মক, মোট integral ১. সবচেয়ে প্রচলিত Gaussian kernel φ(u) = e−u²/2/√(2π). h হলো bandwidth: প্রতিটি ঢিবির চওড়া। K((x − xi)/h)/h হলো xi-কে কেন্দ্র করে h-মাপের একটি ঢিবি, যার ক্ষেত্রফল ১; n দিয়ে ভাগ করলে প্রতিটির ক্ষেত্রফল ১/n, আর যোগফলের ক্ষেত্রফল ঠিক ১. তাই p̂ নিজেই একটি বৈধ density. এটি পর্ব ২৯-এর RBF kernel-এর আত্মীয়: Gaussian KDE-র মান আসলে x আর সব ডেটা বিন্দুর RBF similarity-র গড়, একটি ধ্রুবক দিয়ে মাপা। তবে সেখানে kernel ছিল দুটি বিন্দুর মিল, এখানে মসৃণ করার ছাঁকনি।

চারটি ঢিবির যোগফলই KDE

ডেটা {1, 2, 4, 7}, Gaussian kernel, h = 1; প্রতিটি ঢিবির উচ্চতা ১/n দিয়ে মাপা

A Gaussian KDE as the sum of four kernel bumps −112479 p̂(3) = 0.1345 — KDE (যোগফল) - - একটি ঢিবি ডেলিভারি সময় (ঘণ্টা)
ছবি ৩.১ — প্রতিটি নীল ঢিবি φ(x − xi)/৪, চূড়ার উচ্চতা ০.৩৯৯/৪ ≈ ০.১০. সবুজ রেখা চারটির যোগফল। ১ আর ২ কাছাকাছি, তাই দুটি ঢিবি মিলে উঁচু চূড়া; ৭ একা, তাই সেখানে শুধু নিজের ঢিবি। লাল বিন্দুতে x = ৩-এর মান, যা নিচের stepper হাতে হিসাব করে।
চারটি ঢিবির অবদান আলাদা করে, তারপর যোগ
ধাপ
হিসাব
সংখ্যায়
মানে
  1. দূরত্ব h-এর এককে। x = ৩ থেকে প্রতিটি ডেটা বিন্দুর দূরত্বকে h দিয়ে ভাগ: u = (২, ১, −১, −৪). ৭ আছে চার h দূরে।
  2. ঢিবির উচ্চতা। Gaussian kernel প্রতিসম, তাই φ(১) = φ(−১) = ০.২৪১৯৭. দুই h দূরে φ(২) = ০.০৫৩৯৯, আর চার h দূরে মাত্র ০.০০০১৩.
  3. যোগ। ০.৫৩৮০৭. এর প্রায় ৯০% এসেছে ২ আর ৪ থেকে: KDE আসলে একটি “স্থানীয়” গড়, দূরের বিন্দু প্রায় ভোট দেয় না।
  4. ভাগ। n = ৪, h = ১, তাই p̂(৩) = ০.৫৩৮০৭ / ৪ = ০.১৩৪৫২. এর একক “প্রতি ঘণ্টা”: সম্ভাবনা নয়, ঘনত্ব।
  5. Bandwidth-এর প্রভাব। h = ০.৫-এ সব বিন্দু দুই h বা বেশি দূরে, মান নেমে ০.০৫৪০৬: ঢিবিগুলো সরু হয়ে ৩-এর ওপর ফাঁক রেখে দেয়। h = ২-এ ০.১২৫০১. Kernel একই, শুধু h বদলেছে, তবু উত্তর আড়াই গুণ পর্যন্ত বদলায়।

Kernel-এর আকার কতটা জরুরি? Gaussian ছাড়াও আছে Epanechnikov K(u) = ¾(1 − u²) যখন |u| ≤ 1, tophat (সমতল বাক্স) ইত্যাদি। তাত্ত্বিকভাবে গড় বর্গ-ভুলের মাপে Epanechnikov সেরা, কিন্তু Gaussian-এর দক্ষতা তার প্রায় ৯৫%, আর বাকিগুলোও কাছাকাছি। তাই বাস্তবে kernel-এর চেয়ে h বাছাই অনেক বেশি গুরুত্বপূর্ণ। দুটি লাইব্রেরির আচরণ মনে রাখুন। SciPy-র gaussian_kde(data, bw_method=c)-এ c একটি গুণক: আসল চওড়া = c × ডেটার sd. scikit-learn-এর KernelDensity(bandwidth=h)-এ h সরাসরি চওড়া, আর score_samples density নয়, log-density ফেরত দেয়; np.exp লাগে। ভেতরে এটি KD-tree বা ball tree দিয়ে দূরের বিন্দু বাদ দিয়ে হিসাব দ্রুত করে।

৪ · Bandwidth বাছাই — Silverman-এর নিয়ম বনাম held-out likelihood

Histogram-এর মতোই bias আর variance টানাটানি করে, শুধু হার ভালো। ছোট h-এ bias প্রায় (h²/2)·p″(x) (বাঁক যত তীক্ষ্ণ, ভুল তত বেশি), আর variance প্রায় p(x)·R(K)/(nh), যেখানে R(K) = ∫K². যোগফল ন্যূনতম হয় h ∝ n−1/5-এ, আর তখন MISE কমে n−4/5 হারে, histogram-এর n−2/3-এর চেয়ে দ্রুত। কিন্তু ধ্রুবকে আছে অজানা p″. দুটি সমাধান প্রচলিত।

(১) Normal-reference নিয়ম। ধরে নিন আসল density normal, তাহলে সূত্রটি বন্ধ রূপে আসে: h = ১.০৬ σ n−1/5. Bernard Silverman তাঁর ১৯৮৬ সালের বইয়ে একটি একটু সাবধানী রূপ সুপারিশ করেন, যা বাঁকা বা দুই-চূড়ার ডেটায় কম মসৃণ করে:

h = 0.9 · min(σ̂, IQR / 1.34) · n−1/5

১.৩৪ কেন? Normal বণ্টনে IQR ≈ ১.৩৪৯σ, তাই IQR/১.৩৪ হলো σ-এর একটি robust অনুমান; দুটির ছোটটি নিলে একটি outlier σ̂ ফুলিয়ে h-কে বড় করতে পারে না।

দুটি ছড়ানোর মাপ, তার ছোটটি, তারপর n-এর গুণক
ধাপ
হিসাব
সংখ্যায়
মানে
  1. গড়। যোগফল ৯৬, n = ৮, গড় ১২ ঘণ্টা।
  2. Standard deviation. (x − ১২)²-এর যোগ ৮১ + ৩৬ + ১৬ + ৪ + ০ + ৪ + ৩৬ + ১৬৯ = ৩৪৬. n − ১ = ৭ দিয়ে ভাগ ৪৯.৪৩, বর্গমূল ৭.০৩১. একা ২৫ থেকেই ১৬৯ এসেছে।
  3. Quartile. NumPy-র ডিফল্ট (linear) পদ্ধতিতে Q1-এর অবস্থান ০.২৫ × ৭ = ১.৭৫, অর্থাৎ ৬ আর ৮-এর মাঝে: ৭.৫. Q3-এর অবস্থান ৫.২৫: ১৪ আর ১৮-এর মাঝে, ১৫. IQR = ৭.৫. অন্য quartile পদ্ধতিতে সংখ্যা একটু বদলাতে পারে; পরীক্ষায় যে পদ্ধতি বলা থাকে, সেটি মানুন।
  4. Robust σ. ৭.৫ / ১.৩৪ = ৫.৫৯৭. Normal ডেটায় এটি sd-এর কাছাকাছি হতো; এখানে কম, কারণ লেজটি sd-কে ফুলিয়েছে।
  5. ছোটটি। min(৭.০৩১, ৫.৫৯৭) = ৫.৫৯৭. লেজ বা দুই চূড়া থাকলে এই min-ই h-কে অতিরিক্ত বড় হতে দেয় না।
  6. n-এর গুণক। ৮−1/5 = ০.৬৫৯৮. n−1/5 খুব ধীরে কমে: h অর্ধেক করতে ডেটা ২৫ = ৩২ গুণ লাগে।
  7. ফল। h = ০.৯ × ৫.৫৯৭ × ০.৬৫৯৮ = ৩.৩২৩ ঘণ্টা। সরল ১.০৬σn−1/5 সূত্র দিত ৪.৯১৭, প্রায় দেড় গুণ চওড়া। SciPy-র bw_method="silverman" আসলে এই দ্বিতীয় রূপের কাছাকাছি (৪.৯১৩), min-ওয়ালা রূপ নয়।

(২) Cross-validated likelihood. আকার ধরে না নিয়ে সরাসরি জিজ্ঞেস করুন: কোন h দিলে নতুন ডেটা সবচেয়ে সম্ভাব্য দেখায়? ডেটাকে কয়েক ভাগে (fold) ভাগ করে, প্রতিবার বাকি ভাগে KDE বানিয়ে বাদ রাখা ভাগের log-likelihood ∑ log p̂(xj) যোগ করুন। এটিই held-out log-likelihood. Training ডেটার নিজের likelihood কেন চলবে না, তা নিচের stepper দেখায়।

একই KDE, দুই রকম বিচার
ধাপ
Training log-lik
Held-out log-lik
রায়
  1. প্রশ্ন। চারটি বিন্দুতে KDE বানানো হবে; দুটি বিন্দু (৩ আর ৬) আলাদা রাখা। দুই রকম স্কোর: training বিন্দুগুলোর নিজের log-density-র যোগ, আর আলাদা রাখা বিন্দুর।
  2. h = ১. Training −৮.০৩, held-out −৪.৬১ (p̂(৩) = ০.১৩৪৫, যা প্রথম stepper-এর সংখ্যা)। এখনও কোনো বিচার করা যায় না।
  3. h = ০.১. প্রতিটি training বিন্দুর ওপর তার নিজের ঢিবির চূড়া φ(০)/(৪ × ০.১) = ০.৯৯৭, তাই training log-lik প্রায় শূন্য (−০.০১১), অনেক “ভালো”। কিন্তু ৩ আর ৬ সব ঢিবি থেকে অনেক h দূরে: p̂ প্রায় e−৪৯, held-out −৯৯.৩.
  4. h = ০.০১. নিজের ঢিবির চূড়া ৯.৯৭, log ২.৩০, চারটির যোগ +৯.২০. Log-density ধনাত্মকও হতে পারে, কারণ density ১-এর বেশি হতে পারে। Held-out বিন্দুর density এত ছোট যে float64-এ শূন্য হয়ে যায়। h → ০ হলে training স্কোর সীমাহীন বাড়ে: প্রতিটি বিন্দুতে একটি অসীম সুচ। এটি ডেটা মুখস্থ করা, অর্থাৎ overfitting.
  5. সমাধান। ০.০৫ থেকে ৬ পর্যন্ত h খুঁজলে held-out স্কোর সর্বোচ্চ (−৪.৪৮৪) হয় h ≈ ১.৩৮-এ। এক বা কয়েকটি বিন্দু বাদ রেখে বিচার করলে নিজের ঢিবির সুবিধা থাকে না, তাই খুব ছোট h শাস্তি পায়। বাস্তবে k-fold বা leave-one-out দিয়ে এটি সব বিন্দুতে ঘুরিয়ে করা হয়।

scikit-learn-এ কাজটি সহজ: KernelDensity.score(X) ফেরত দেয় X-এর মোট log-likelihood, আর GridSearchCV estimator-এর score-কেই ডিফল্ট মাপ ধরে। তাই GridSearchCV(KernelDensity(), {"bandwidth": ...}) কোনো label ছাড়াই cross-validation দিয়ে held-out log-likelihood সর্বোচ্চ করে। (AI-এর ভিত্তি কোর্সের পর্ব ৬৬-এর grid search, শুধু মাপটি এখানে likelihood.) নিচের ব্লক নিজের KDE তিনটি বিন্দুতে SciPy আর scikit-learn-এর সঙ্গে মেলায়, Silverman আর cross-validation-এর h তুলনা করে, আর চারটি h-এ বক্ররেখা আঁকে।

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import gaussian_kde, iqr
from sklearn.neighbors import KernelDensity
from sklearn.model_selection import GridSearchCV

rng, n = np.random.default_rng(42), 400    # আগের ব্লকের একই ৪০০টি ডেলিভারি
g = rng.choice(3, size=n, p=[0.25, 0.45, 0.30])
x = np.where(g == 0, rng.exponential(4, n),
     np.where(g == 1, rng.gamma(4, 6, n), rng.gamma(9, 7, n)))
def kde(t, data, h):                       # নিজের KDE: প্রতিটি বিন্দুতে একটি Gaussian ঢিবি
    u = (t[:, None] - data[None, :]) / h
    return np.exp(-0.5 * u**2).sum(axis=1) / (len(data) * h * np.sqrt(2 * np.pi))

s = x.std(ddof=1)
h_silv = 0.9 * min(s, iqr(x) / 1.34) * n ** (-1 / 5)   # Silverman-এর নিয়ম
t = np.array([2.0, 24.0, 60.0])
sp = gaussian_kde(x, bw_method=h_silv / s)(t)          # scipy নেয় h/sd
sk = np.exp(KernelDensity(bandwidth=h_silv).fit(x[:, None]).score_samples(t[:, None]))
print(f"Silverman h = {h_silv:.2f} hours")
for row in zip(t, kde(t, x, h_silv), sp, sk):
    print("t={:4.0f}  mine={:.5f}  scipy={:.5f}  sklearn={:.5f}".format(*row))
grid = GridSearchCV(KernelDensity(), {"bandwidth": np.linspace(1, 15, 29)}, cv=5)
grid.fit(x[:, None])                       # score = held-out fold-এর মোট log-likelihood
h_cv = grid.best_params_["bandwidth"]
print(f"GridSearchCV h = {h_cv:.1f}")
sk_rule = KernelDensity(bandwidth="silverman").fit(x[:, None]).bandwidth_
print(f"sklearn bandwidth='silverman' gives h = {sk_rule:.3f}  (not times sd!)")
tt = np.linspace(-10, 140, 400)
fig, ax = plt.subplots(figsize=(7, 3.6), layout="constrained")
ax.hist(x, bins="fd", density=True, color="#e5e7eb")
for h, lab in [(1, "too small"), (h_cv, "CV"), (h_silv, "Silverman"), (20, "too large")]:
    ax.plot(tt, kde(tt, x, h), label=f"h = {h:.1f} ({lab})")
ax.set_xlabel("delivery time (hours)"); ax.set_ylabel("density")
ax.set_title("Gaussian KDE with four bandwidths"); ax.legend(); plt.show()

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

Silverman h = 7.25 hours
t=   2  mine=0.01575  scipy=0.01575  sklearn=0.01575
t=  24  mine=0.01609  scipy=0.01609  sklearn=0.01609
t=  60  mine=0.00539  scipy=0.00539  sklearn=0.00539
GridSearchCV h = 2.5
sklearn bandwidth='silverman' gives h = 0.320  (not times sd!)

মিল: একই h = ৭.২৫-এ নিজের সূত্র, SciPy (bw_method = h/sd) আর scikit-learn পাঁচ দশমিক পর্যন্ত এক। দুই h-এর অমিল: Silverman বলছে ৭.২৫ ঘণ্টা, cross-validation বলছে ২.৫. কারণ ছবিতেই: শূন্যের কাছে express অর্ডারের একটি সরু, খাড়া চূড়া। Silverman-এর নিয়ম ধরে নেয় ডেটা একটি normal-এর মতো, তাই এই চূড়াকে চেপে চ্যাপ্টা করে (সবুজ রেখা)। Held-out likelihood দেখে যে ওই চূড়ার আশপাশের নতুন অর্ডারগুলো চওড়া h-এ কম সম্ভাব্য দেখায়, তাই সরু h বাছে (কমলা রেখা); বিনিময়ে লেজে একটু ঢেউ থাকে। h = ১ প্রায় প্রতিটি অর্ডারে আলাদা চূড়া দেয়, আর h = ২০ তিন ধরনের অর্ডারকে একটি কুঁজে মিশিয়ে ফেলে। শেষ লাইনটি একটি ফাঁদ: bandwidth="silverman" স্ট্রিং দিলে scikit-learn শুধু n-এর গুণকটি (০.৩২০) bandwidth হিসেবে বসায়, ডেটার sd দিয়ে গুণ করে না; ঘণ্টার এককে এটি মারাত্মক রকম সরু। ডেটা standardize করে নিলে তবেই সংখ্যাটি অর্থবহ। নিজের version-এর docs আর bandwidth_ দেখে নিন।

সতর্কতা: GridSearchCV-এর জন্য ডেটার ক্রম যদি কোনো নিয়মে সাজানো থাকে (যেমন তারিখ বা অর্ডারের ধরন অনুযায়ী), তাহলে ডিফল্ট KFold এলোমেলো না করেই ভাগ করে, আর একেকটি fold-এ একেক ধরনের অর্ডার পড়ে। এখানে ডেটা এলোমেলো ক্রমে বানানো; বাস্তবে KFold(5, shuffle=True, random_state=42) দিন। আর h-এর grid লগারিদমিক রাখুন (যেমন np.logspace), কারণ ভালো h কয়েক গুণ পর্যন্ত বদলাতে পারে।

৫ · ধনাত্মক ডেটার সীমানা — boundary bias, log রূপান্তর আর নতুন নমুনা

ডেলিভারি সময় কখনো ঋণাত্মক নয়। কিন্তু Gaussian ঢিবি দুই দিকে ছড়ায়: ০.৩ ঘণ্টার একটি express অর্ডারের ঢিবির (h = ২.৫) প্রায় অর্ধেক পড়ে শূন্যের নিচে, যেখানে কোনো ডেলিভারি সম্ভব নয়। ফলে দুটি ক্ষতি: শূন্যের নিচে অর্থহীন সম্ভাবনা, আর শূন্যের ঠিক ওপরে ঘনত্ব কম। আসল density সীমানায় মসৃণভাবে ধনাত্মক হলে, সীমানার ঠিক ওপরে সাধারণ KDE প্রায় অর্ধেক মান দেয়, কারণ সেখানে ঢিবি শুধু এক দিক থেকে আসে। এটিই boundary bias.

সমাধান: log জগতে কাজ। y = log x নিন। y-এর কোনো সীমানা নেই, তাই y-তে সাধারণ KDE বানান, তারপর ফিরে আসুন। কিন্তু শুধু p̂Y(log x) বসালে ভুল হবে: density একটি “প্রতি একক” মাপ, আর log একককে টানে-চাপে। চলক বদলের নিয়ম (পর্ব ১১-এর Jacobian-এর এক মাত্রার রূপ):

p̂X(x) = p̂Y(log x) · |dy/dx| = p̂Y(log x) / x

১/x গুণকটি না দিলে বক্ররেখার ক্ষেত্রফল ১ থাকে না। এর আরও কয়েকটি বিকল্প আছে: reflection (প্রতিটি বিন্দুর −xi-এও ঢিবি বসিয়ে শূন্যের নিচের অংশ ভাঁজ করে ওপরে আনা), বিশেষ boundary kernel, বা gamma-আকারের kernel. Log পথটি সবচেয়ে সরল, আর ডানে লম্বা লেজওয়ালা ডেটার জন্য বাড়তি সুবিধা দেয়: log জগতে লেজ ছোট হয়, তাই একটি h সব জায়গায় ভালো খাটে।

নতুন নমুনা। KDE থেকে নমুনা নেওয়া খুব সহজ: এলোমেলোভাবে একটি ডেটা বিন্দু xI বাছুন, তারপর তার সঙ্গে kernel-এর একটি noise যোগ করুন: x* = xI + h·ε, যেখানে Gaussian kernel-এ ε ~ N(০, ১). এটি আসলে পর্ব ২১-এর bootstrap, একটু “মসৃণ” করে (smoothed bootstrap). scikit-learn-এ KernelDensity.sample() এটি করে, তবে শুধু Gaussian আর tophat kernel-এ। Log জগতে নমুনা নিয়ে exp করলে সব নমুনা নিশ্চিতভাবে ধনাত্মক।

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import expon, gamma, norm, iqr
from sklearn.neighbors import KernelDensity
rng, n = np.random.default_rng(42), 400    # একই ৪০০টি ডেলিভারি
g = rng.choice(3, size=n, p=[0.25, 0.45, 0.30])
x = np.where(g == 0, rng.exponential(4, n),
     np.where(g == 1, rng.gamma(4, 6, n), rng.gamma(9, 7, n)))
def true_pdf(t):                           # ডেটা আমরাই বানিয়েছি, তাই আসল density জানা
    return (0.25 * expon.pdf(t, scale=4) + 0.45 * gamma.pdf(t, 4, scale=6)
            + 0.30 * gamma.pdf(t, 9, scale=7))
h = 2.5                                    # আগের ব্লকের cross-validated h
plain = KernelDensity(bandwidth=h).fit(x[:, None])
print(f"plain KDE mass below 0 hours: {norm.cdf(-x / h).mean():.3f}")
y = np.log(x)                              # log জগতে KDE, তারপর ফেরত
hy = 0.9 * min(y.std(ddof=1), iqr(y) / 1.34) * n ** (-1 / 5)
logk = KernelDensity(bandwidth=hy).fit(y[:, None])
def log_kde_pdf(t):                        # f_X(t) = f_Y(log t) / t   (Jacobian)
    return np.exp(logk.score_samples(np.log(t)[:, None])) / t
t = np.array([0.5, 2.0, 24.0])
print(f"Silverman h on log scale = {hy:.3f}")
for row in zip(t, true_pdf(t), np.exp(plain.score_samples(t[:, None])), log_kde_pdf(t)):
    print("t={:4.1f}  true={:.4f}  plain={:.4f}  log-KDE={:.4f}".format(*row))
grid = np.geomspace(1e-4, 2000, 5000)      # log-ফাঁকে বিন্দু
print(f"log-KDE integrates to {np.trapezoid(log_kde_pdf(grid), grid):.3f}")
s1 = plain.sample(1000, random_state=42).ravel()          # নতুন নমুনা
s2 = np.exp(logk.sample(1000, random_state=42).ravel())
print(f"new samples < 0: plain {(s1 < 0).sum()}, log-KDE {(s2 < 0).sum()}")
tt = np.linspace(0.05, 40, 400)
fig, ax = plt.subplots(figsize=(7, 3.6), layout="constrained")
ax.plot(tt, true_pdf(tt), "k--", label="true density")
ax.plot(tt, np.exp(plain.score_samples(tt[:, None])), label=f"plain KDE, h={h}")
ax.plot(tt, log_kde_pdf(tt), label="KDE on log scale, back-transformed")
ax.set_xlabel("delivery time (hours)"); ax.set_ylabel("density")
ax.set_title("Boundary bias near zero"); ax.legend(); plt.show()

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

plain KDE mass below 0 hours: 0.046
Silverman h on log scale = 0.325
t= 0.5  true=0.0552  plain=0.0228  log-KDE=0.0526
t= 2.0  true=0.0382  plain=0.0264  log-KDE=0.0372
t=24.0  true=0.0155  plain=0.0189  log-KDE=0.0152
log-KDE integrates to 1.000
new samples < 0: plain 38, log-KDE 0

প্রথম লাইন: h = ২.৫-এর সাধারণ KDE মোট সম্ভাবনার ৪.৬% শূন্যের নিচে ফেলে। হিসাবটি বন্ধ রূপে: প্রতিটি ঢিবির শূন্যের নিচের অংশ Φ(−xi/h), যেখানে Φ হলো standard normal-এর cumulative probability, আর তার গড়। তুলনার সারণি: t = ০.৫ ঘণ্টায় আসল density ০.০৫৫২, সাধারণ KDE ০.০২২৮ (অর্ধেকেরও কম), log-KDE ০.০৫২৬. t = ২-এ একই ছবি। t = ২৪-এ, সীমানা থেকে দূরে, দুটিই আসলের কাছে, তবে log-KDE আরও কাছে। Integral ১.০০০: Jacobian ১/x থাকায় ফেরত আনা বক্ররেখা বৈধ density; ১/x বাদ দিলে এটি ১ হতো না। নমুনা: ১,০০০টি নতুন নমুনার মধ্যে সাধারণ KDE ৩৮টি ঋণাত্মক ডেলিভারি সময় বানিয়েছে (প্রত্যাশিত ৪.৬%-এর কাছাকাছি), log-KDE একটিও নয়। ছবিতে একটি সৎ সতর্কতা: ০.৫ ঘণ্টার নিচে log-KDE খাড়া উঠে যায় আর ঢেউ খায়। খুব ছোট x-এ ১/x গুণকটি বিশাল, আর log জগতে সেখানে ডেটা খুব কম (০.০৪ ঘণ্টার মতো মান হাতে গোনা), তাই অল্প গোলমালও বহু গুণ বড় হয়ে দেখা দেয়। কোনো সমাধানই বিনামূল্যে নয়: সীমানার একেবারে কাছে সব পদ্ধতিরই variance বেশি।

৬ · অনেক মাত্রায় — multivariate KDE, mixture model আর curse of dimensionality

KDE সহজেই d মাত্রায় যায়: প্রতিটি বিন্দুতে একটি d মাত্রার ঢিবি, p̂(x) = (1/(n hd))∑K(‖x − xi‖/h). scikit-learn সব মাত্রায় একই h নেয়, তাই আগে feature-গুলো একই মাপে আনুন (standardize বা log); নইলে km আর মিনিটের মতো আলাদা এককে একটি h অর্থহীন। SciPy-র gaussian_kde ডেটার covariance matrix দিয়ে ঢিবির আকার নেয়, তাই হেলানো মেঘের সঙ্গে নিজে থেকেই খাপ খায়।

Parametric বিকল্প: mixture. একটি Gaussian mixture model density-কে K-টি Gaussian-এর ওজন-দেওয়া যোগ ধরে: p(x) = ∑k πk N(x | μk, Σk), ∑πk = ১. KDE-কে চরম mixture ভাবা যায়: n-টি উপাদান, প্রতিটির ওজন ১/n, গড় একেকটি ডেটা বিন্দু, covariance h²I. GMM সেখানে কয়েকটি উপাদানে পুরো মেঘ ধরে: parameter-এর সংখ্যা ডেটার সঙ্গে বাড়ে না, মূল্যায়ন দ্রুত (n-টি ঢিবির বদলে K-টি), আর আকার ঠিক হলে অল্প ডেটায় ভালো। EM দিয়ে fit করা আর BIC দিয়ে K বাছাই পর্ব ৩৯-এর বিষয়; এখানে আমরা GMM-কে শুধু density estimate হিসেবে দেখব।

Curse of dimensionality. KDE-র সেরা MISE হার d মাত্রায় n−4/(4+d). d = ১-এ n−0.8, d = ১০-এ n−0.29: একই নির্ভুলতা পেতে ডেটা লাগে বহু গুণ বেশি। কারণটি সহজ: একটি বিন্দুর “কাছে” যথেষ্ট প্রতিবেশী না থাকলে স্থানীয় গড় সম্ভব নয়, আর মাত্রা বাড়লে প্রতিবেশী উধাও হয় (AI-এর ভিত্তি কোর্সের পর্ব ৩৩-এ এর প্রথম দেখা)। নিচের ব্লক প্রথমে বাজারঘরের দুই মাত্রার ডেটায় (log সময়, log দূরত্ব) KDE আর বিভিন্ন K-এর GMM-কে আলাদা test ডেটার গড় log-likelihood দিয়ে তুলনা করে, তারপর দেখায় মাত্রা বাড়লে ব্যাসার্ধ ১-এর ভেতরে কত অংশ বিন্দু থাকে।

import numpy as np
from scipy.stats import chi2
from sklearn.neighbors import KernelDensity
from sklearn.mixture import GaussianMixture
from sklearn.model_selection import GridSearchCV

def orders(n, seed):                       # (log সময়, log দূরত্ব), তিন ধরনের ডেলিভারি
    rng = np.random.default_rng(seed)
    g = rng.choice(3, size=n, p=[0.25, 0.45, 0.30])
    mt, md = np.array([1.0, 3.1, 4.1])[g], np.array([1.2, 2.1, 5.0])[g]
    t = mt + rng.normal(0, 0.45, n)
    return np.column_stack([t, md + 0.6 * (t - mt) + rng.normal(0, 0.35, n)])
train, test = orders(600, 1), orders(2000, 2)

kde = GridSearchCV(KernelDensity(), {"bandwidth": np.linspace(0.05, 0.6, 23)}, cv=5)
kde.fit(train)
h = kde.best_params_["bandwidth"]
print(f"2-D KDE  h={h:.3f}  held-out mean log-lik = {kde.score(test) / len(test):.3f}")
for k in (1, 2, 3, 4, 6):
    gm = GaussianMixture(k, init_params="k-means++", n_init=3, random_state=0).fit(train)
    print(f"GMM k={k}  held-out mean log-lik = {gm.score(test):.3f}  BIC = {gm.bic(train):7.1f}")

rng = np.random.default_rng(0)             # মাত্রা বাড়লে "কাছের" বিন্দু কত?
print(" d  share within radius 1 (sim)  theory")
for d in (1, 2, 5, 10, 20):
    z = rng.standard_normal((20000, d))
    inside = (np.linalg.norm(z, axis=1) <= 1).mean()
    print(f"{d:2d}  {inside:12.5f}  {chi2.cdf(1, d):12.2e}")

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

2-D KDE  h=0.175  held-out mean log-lik = -2.102
GMM k=1  held-out mean log-lik = -2.981  BIC =  3594.0
GMM k=2  held-out mean log-lik = -2.236  BIC =  2821.6
GMM k=3  held-out mean log-lik = -2.056  BIC =  2626.0
GMM k=4  held-out mean log-lik = -2.059  BIC =  2660.9
GMM k=6  held-out mean log-lik = -2.064  BIC =  2732.6
 d  share within radius 1 (sim)  theory
 1       0.68425      6.83e-01
 2       0.39570      3.93e-01
 5       0.03730      3.74e-02
10       0.00015      1.72e-04
20       0.00000      1.71e-10

একই মাপকাঠি: সব model একই ৬০০টি training সারিতে শেখা, আর একই ২,০০০টি নতুন সারিতে গড় log-density দিয়ে বিচার; সংখ্যা যত বড় (শূন্যের কাছে), তত ভালো। Cross-validated KDE পায় −২.১০২. একটি Gaussian (k = ১) −২.৯৮১, অনেক খারাপ: তিন ধরনের অর্ডারকে একটি উপবৃত্তে চাপানো যায় না। k = ৩-এ −২.০৫৬, KDE-র চেয়েও ভালো, আর BIC-ও সবচেয়ে কম (২৬২৬.০)। k = ৪ বা ৬ বাড়তি নমনীয়তা পায় কিন্তু নতুন ডেটায় একটুও উন্নতি নেই। সৎ সতর্কতা: এখানে GMM-এর জয় প্রায় সাজানো, কারণ ডেটা আমরা তিনটি Gaussian মিশিয়েই বানিয়েছি। আসল ডেটায় আকার না মিললে KDE জিততে পারে; তাই দুটিই চালিয়ে held-out likelihood দেখাই নিয়ম। (পাতার ব্রাউজার-Python-এ KMeans একটি অপ্রাসঙ্গিক সতর্কবার্তা ছাপে, তাই init_params="k-means++" দেওয়া হয়েছে।) মাত্রার সারণি: standard normal মেঘের কেন্দ্র থেকে ব্যাসার্ধ ১-এর ভেতরে d = ১-এ ৬৮%, d = ২-এ ৩৯%, d = ৫-এ ৩.৭%, d = ১০-এ ০.০১৫% (২০,০০০-এর মধ্যে ৩টি), d = ২০-এ শূন্য। তাত্ত্বিক মান χ²d-এর cdf, যা পর্ব ১৯-এ এসেছে। মেঘের সবচেয়ে ঘন জায়গাতেও প্রতিবেশী নেই; KDE তখন প্রায় খালি জায়গায় অনুমান করে।

বেশি মাত্রায় তাই সাধারণ কৌশল: কম মাত্রায় নামানো (PCA, পর্ব ০৮), feature-গুলো স্বাধীন ধরে এক মাত্রার density-র গুণফল (Naive Bayes, AI-এর ভিত্তি কোর্সের পর্ব ৫১), বা mixture-এর মতো কাঠামোবদ্ধ model. ছবি, শব্দ বা লেখার মতো হাজার মাত্রার ডেটায় density শেখে normalizing flow, VAE বা diffusion-এর মতো deep generative model, যা পরে deep learning আর generative AI কোর্সে আসবে।

৭ · অনুমান কতটা ভালো — মূল্যায়ন আর পদ্ধতি বাছাই

Classification-এ accuracy আছে; density-তে মূল মাপ held-out গড় log-likelihood: (১/m)∑ log p̂(xj), যেখানে xj শেখার সময় দেখা হয়নি। এটি পর্ব ১৮-এর MLE-র নীতিকে নতুন ডেটায় খাটায়, আর AI-এর ভিত্তি কোর্সের পর্ব ৩৮-এর ভাষায় এটি আসল বণ্টন আর p̂-এর cross-entropy-র ঋণাত্মক অনুমান: যত বড়, KL divergence তত কম। তিনটি নিয়ম মানুন:

  • একই জগতে তুলনা। Log সময়ের ওপর বানানো density-র log-likelihood আর ঘণ্টার ওপর বানানোটির log-likelihood সরাসরি তুলনীয় নয়। ঘণ্টার জগতে আনতে প্রতিটি বিন্দুতে −log x যোগ করুন (Jacobian).
  • একটি শূন্য সব নষ্ট করে। কোনো test বিন্দুতে p̂ = ০ হলে log = −∞. Tophat বা Epanechnikov kernel-এর সীমিত ঢিবি, বা ফাঁকা bin-ওয়ালা histogram-এ এটি সহজেই হয়; Gaussian kernel-এ হয় না।
  • সংখ্যার সঙ্গে চোখ। Log-likelihood-এর ছোট পার্থক্য seed বদলালে উল্টে যেতে পারে; কয়েকটি seed বা fold-এ গড় আর ছড়ানো দেখুন (পর্ব ২১)। আর ছবি আঁকুন: শূন্যের নিচে ভর, লেজে ঢেউ, অর্থহীন চূড়া সংখ্যায় ধরা পড়ে না।
পদ্ধতিশক্তিদুর্বলতাকখন
Parametric (Gaussian, gamma, log-normal)অল্প parameter, অল্প ডেটায় নির্ভুল, ব্যাখ্যা সহজআকার ভুল হলে ভুল থেকে যায়বিষয়জ্ঞান আকার বলে দেয়
Histogramদ্রুত, বোঝা সহজ, বিশাল ডেটায়ও চলেbin আর origin-নির্ভর, ধাপের মতো, ফাঁকা bin-এ শূন্যEDA, এক-দুই মাত্রা, খুব বড় n
KDEমসৃণ, আকার ধরে না, নমুনা নেওয়া সহজh-নির্ভর, সীমানায় bias, মূল্যায়ন O(n), বেশি মাত্রায় অচলএক থেকে কয়েক মাত্রা, মাঝারি n
Gaussian mixtureনমনীয় অথচ সংক্ষিপ্ত, দ্রুত মূল্যায়ন, cluster-ও দেয়K আর শুরু বাছাই, local optimum, singular covarianceকয়েক থেকে কয়েক ডজন মাত্রা, একাধিক দল

আরও গভীরে: KDE-র ধারণা Murray Rosenblatt (১৯৫৬) আর Emanuel Parzen (১৯৬২)-এর প্রবন্ধ থেকে আসে, তাই pattern recognition-এ একে “Parzen window”-ও বলে। Bandwidth বাছাইয়ের সবচেয়ে প্রশংসিত “plug-in” পদ্ধতি Sheather আর Jones (১৯৯১)-এর, যা p″-এর অনুমান ডেটা থেকে নিয়ে সূত্রে বসায়। বড় n-এ হিসাবের গতির জন্য binning-এর পর FFT, বা ঢিবির বাইরের বিন্দু বাদ দেওয়া tree পদ্ধতি ব্যবহার হয় (scikit-learn-এর atol/rtol এই ছাড়ের পরিমাণ ঠিক করে)। বিন্দু ভেদে আলাদা h (adaptive বা variable bandwidth KDE) লেজ আর চূড়া দুটিই ভালো ধরতে পারে।

পরীক্ষা ও ইন্টারভিউতে: এই ধরনের প্রশ্ন বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর পরীক্ষায় প্রায়ই আসে।

  • ডেটা {1, 2, 4, 7}, Gaussian kernel, h = 1 হলে p̂(3)? (φ(2) + φ(1) + φ(−1) + φ(−4))/4 = ০.৫৩৮১/৪ = ০.১৩৪৫.
  • Bandwidth কেন training likelihood দিয়ে বাছা যায় না? h → ০ হলে প্রতিটি বিন্দুর নিজের ঢিবি অসীম উঁচু হয়, training likelihood সীমাহীন বাড়ে; held-out বা leave-one-out লাগে।
  • Histogram আর KDE-র সেরা MISE হার? Histogram n−2/3 (w ∝ n−1/3), KDE n−4/5 (h ∝ n−1/5); d মাত্রায় KDE n−4/(4+d).
  • Freedman–Diaconis কেন Scott-এর চেয়ে robust? σ̂-এর বদলে IQR, যা outlier-এ প্রায় নড়ে না।
  • y = log x-এ density pY জানা থাকলে pX(x)? pY(log x)/x.

৮ · AI-এর যুগে — density estimation-এর কোড যাচাই

AI assistant KDE-র কোড দ্রুত লেখে, কিন্তু ভুলগুলো চুপচাপ থাকে: কোড চলে, ছবি সুন্দর, অথচ সংখ্যা ভুল। নিচের ভুলগুলো বারবার দেখা যায়:

AI প্রায়ই যা করেকেন ভুলকীভাবে ধরবেন
score_samples-এর ফলকে density বলে চালায়এটি log-density; ঋণাত্মক সংখ্যা দেখে “সম্ভাবনা” ভাবা ভুলdocs পড়ুন, np.exp করে integral ≈ ১ কি না দেখুন
SciPy-র bw_method-এ ঘণ্টায় h বসায়এটি sd-এর গুণক, চওড়া নয়kde.factor আর np.sqrt(kde.covariance) ছেপে দেখুন
bandwidth="silverman" দিয়ে ধরে নেয় সব ঠিকscikit-learn শুধু গুণকটি বসায়, sd দিয়ে গুণ করে নাbandwidth_ ছাপুন; আগে standardize করুন
Training ডেটার likelihood দিয়ে h বা K বাছেছোট h বা বড় K সবসময় “জেতে”আলাদা fold বা test ডেটার log-likelihood
Log জগতের KDE ফেরত আনতে Jacobian ভুলে যায়Integral ১ থাকে না, তুলনা ভুলসংখ্যাগতভাবে integral করে দেখুন
বানানো parameter দেয় (যেমন KernelDensity(bw_method=...))ওই নাম SciPy-র, scikit-learn-এর নয়নিজের version-এর docs, আর চালিয়ে দেখা
Anomaly-র সীমা পুরো ডেটার KDE থেকে বানিয়ে একই ডেটায় মূল্যায়ন করেপ্রতিটি বিন্দু নিজের ঢিবি থেকে বাড়তি density পায় (leakage)আগে ভাগ, পরে fit; বা leave-one-out density

উত্তরের সঙ্গে যাচাইয়ের কোড চেয়ে নিন; নিজে চালানো অংশটিই আসল পরীক্ষা।

আমার কাছে একটি অনলাইন দোকানের ৫,০০০টি ডেলিভারি সময় (ঘণ্টা, সব ধনাত্মক, ডানে লম্বা লেজ) আছে। আমি density অনুমান করে P(সময় > 48 ঘণ্টা) আর নতুন নমুনা চাই। ১. scikit-learn-এর KernelDensity দিয়ে কোড লেখো, যেখানে bandwidth আলাদা fold-এর log-likelihood দিয়ে GridSearchCV-তে বাছা হয় (shuffle-সহ KFold, logspace grid). ২. শূন্যের কাছে boundary bias এড়াতে log রূপান্তর করো, আর ফেরত আনার সময় Jacobian কোথায় বসছে, তা মন্তব্যে লেখো। ৩. ফলাফলের density-র integral ১ কি না, আর শূন্যের নিচে কোনো নমুনা আসছে কি না, যাচাইয়ের লাইন যোগ করো। ৪. score_samples কী ফেরত দেয়, আর তোমার দেওয়া প্রতিটি parameter আমার scikit-learn version-এ আছে কি না, docs-এর কোন পাতায় দেখব — বলো। ৫. তোমার উত্তরে কোথায় ভুল হওয়ার সম্ভাবনা বেশি, সেটিও বলো।

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

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

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

প্র ০১১৮৯৪ সালে Karl Pearson কাঁকড়ার মাপ থেকে দুটি normal-এর mixture বের করেছিলেন, আর ১৯৫০-৬০-এর দশকে Rosenblatt ও Parzen আকারহীন KDE দিলেন। Parametric আর nonparametric এই দুই ধারার টানাপোড়েন আজ কোথায় দাঁড়িয়ে?

Pearson-এর হাতে ছিল নেপলসের কাঁকড়ার কপালের চওড়ার মাপ, যা W. F. R. Weldon সংগ্রহ করেছিলেন। Histogram-টি একটু বাঁকা ছিল, আর প্রশ্ন উঠেছিল এখানে দুটি উপ-জনগোষ্ঠী মিশে আছে কি না। Pearson দুটি normal-এর mixture ধরে পাঁচটি parameter বের করতে method of moments ব্যবহার করেন, যার জন্য একটি নবম ঘাতের বহুপদী সমীকরণ হাতে সমাধান করতে হয়েছিল বলে বর্ণনা পাওয়া যায়। ধারণাটি আধুনিক: অল্প কয়েকটি parameter, আর একটি গল্প (“দুটি দল আছে”)। কিন্তু হিসাবটি এত কঠিন ছিল যে mixture ব্যবহার বহু দশক সীমিত ছিল, যতদিন না ১৯৭৭ সালে Dempster, Laird আর Rubin EM algorithm-কে সাধারণ রূপ দেন (পর্ব ৩৯-এর বিষয়)।

অন্য দিকে, Fix আর Hodges (১৯৫১)-এর nearest-neighbour কাজের পর Rosenblatt (১৯৫৬) আর Parzen (১৯৬২) দেখান যে আকার না ধরেও এমন অনুমান বানানো যায়, যা n বাড়লে যেকোনো মসৃণ density-র দিকে যায়। এর দাম: অনেক বেশি ডেটা, প্রতিটি মূল্যায়নে সব ডেটা লাগে, আর মাত্রা বাড়লে হার দ্রুত খারাপ হয়।

আজকের অনুশীলন দুই ধারাকে মিলিয়ে চলে। কম মাত্রায় আর অনুসন্ধানের সময় KDE: আকার সম্পর্কে কোনো ধারণা চাপায় না, তাই চমক ধরা পড়ে, যেমন এই পর্বের express অর্ডারের চূড়া। কাঠামো জানা থাকলে বা মাত্রা বেশি হলে parametric বা mixture: সংক্ষিপ্ত, দ্রুত, ব্যাখ্যাযোগ্য। আর দুটির মাঝে আছে semi-parametric পথ: log রূপান্তরের পর KDE (এই পর্বের অংশ ৫), বা একটি parametric অনুমানকে nonparametric সংশোধন দিয়ে ঠিক করা। মজার ব্যাপার, KDE নিজেই n উপাদানের একটি mixture, আর অনেক উপাদানের GMM প্রায় KDE-র মতো আচরণ করে; দুই ধারার সীমানা তাই ঝাপসা।

বাছাইয়ের প্রশ্নটি মূলত bias বনাম variance আর ডেটার পরিমাণ বনাম বিষয়জ্ঞানের। ভুল আকার ধরলে parametric model-এর bias কখনো যায় না; কোনো আকার না ধরলে nonparametric model-এর variance মেটাতে প্রচুর ডেটা লাগে। Held-out log-likelihood এই তর্কের নিরপেক্ষ বিচারক।

মূল উপলব্ধি: Parametric আর nonparametric প্রতিদ্বন্দ্বী নয়, একটি বর্ণালির দুই প্রান্ত; ডেটার পরিমাণ, মাত্রা আর বিষয়জ্ঞান ঠিক করে আপনি বর্ণালির কোথায় দাঁড়াবেন।

প্র ০২KDE-র training likelihood h → ০-এ অসীম হয়, আর Gaussian mixture-এর likelihood-ও একটি উপাদান একটি বিন্দুতে চুপসে গেলে অসীম হয়। এই দুই “অসীম” কি একই রোগ? চিকিৎসা কী?

হ্যাঁ, মূলে একই রোগ। Density-র মান ১-এর বেশি হতে পারে, আর কোনো সীমা নেই: একটি খুব সরু ঢিবি তার কেন্দ্রে যত খুশি উঁচু হতে পারে, ক্ষেত্রফল ১ রেখেই। Likelihood হলো ডেটা বিন্দুগুলোতে density-র গুণফল। তাই একটি model যদি কোনো ডেটা বিন্দুর ঠিক ওপরে একটি অসীম সরু চূড়া বসাতে পারে, তাহলে সেই বিন্দুর অবদান অসীম, আর বাকি বিন্দু যতক্ষণ শূন্যে না নামে, মোট likelihood-ও অসীম।

KDE-তে প্রতিটি training বিন্দুর ওপর তার নিজের ঢিবি আছে, তাই h ছোট করলেই সবার চূড়া একসঙ্গে উঁচু হয়। এই পর্বের stepper-এ h = ০.০১-এ training log-likelihood +৯.২ আর বাড়তেই থাকে। Gaussian mixture-এ একটি উপাদানের গড় একটি বিন্দুর ওপর বসে variance শূন্যের দিকে নিলে একই ঘটনা; অন্য উপাদানগুলো বাকি বিন্দুদের ধরে রাখে, তাই মোট likelihood অসীম। একে likelihood-এর degenerate বা singular সমাধান বলে; বিশুদ্ধ MLE এখানে সংজ্ঞাগতভাবে খারাপ প্রশ্ন।

চিকিৎসার তিনটি পরিবার আছে। প্রথম, মূল্যায়ন আলাদা ডেটায়: held-out বা leave-one-out likelihood-এ নিজের ঢিবির সুবিধা থাকে না, তাই সরু h শাস্তি পায়। KDE-তে এটিই প্রচলিত পথ। দ্বিতীয়, নিচের সীমা বা regularization: GMM-এ scikit-learn প্রতিটি covariance-এর কর্ণে একটি ছোট reg_covar যোগ করে, যাতে variance কখনো শূন্যে না নামে; এটি পর্ব ১৭-এর ridge-এর আত্মীয়। তৃতীয়, prior: Bayesian পথে variance-এর ওপর একটি prior দিলে MAP অনুমান (পর্ব ১৮) আর অসীমে যায় না; Bayesian GMM এই পথে চলে।

একটি বড় শিক্ষা এখানে আছে। নমনীয় model-এর training স্কোর প্রায় সবসময় অতিরিক্ত আশাবাদী, আর যথেষ্ট নমনীয় হলে সীমাহীন। Classification-এ এটি training accuracy ১০০%; density-তে এটি অসীম likelihood. দুই ক্ষেত্রেই সমাধান একই নীতিতে: যে ডেটা শেখায় ব্যবহার হয়নি, তার ওপর বিচার।

মূল উপলব্ধি: Density অসীম উঁচু হতে পারে বলে likelihood-এ overfitting সরাসরি অসীম হয়ে দেখা দেয়; আলাদা ডেটায় মূল্যায়ন, covariance-এর নিচের সীমা বা prior দিয়ে তা থামাতে হয়।

প্র ০৩Richard Bellman ১৯৫০-এর দশকে “curse of dimensionality” কথাটি চালু করেন dynamic programming প্রসঙ্গে। Density estimation-এ এই অভিশাপ ঠিক কীভাবে কাজ করে, আর মানুষ কীভাবে তা এড়ায়?

Bellman-এর সমস্যা ছিল হিসাবের: প্রতিটি অবস্থা-চলককে দশটি ভাগে ভাগ করলে d চলকে ১০dটি খোপ, যা d বাড়লে কোনো কম্পিউটারে ধরে না। Density estimation-এ একই সূচকীয় বৃদ্ধি ডেটার পরিমাণে আসে। Histogram-এ প্রতিটি অক্ষে দশটি bin মানে d = ১০-এ দশ বিলিয়ন খোপ; লাখ খানেক নমুনায় প্রায় সব খোপ ফাঁকা।

KDE খোপ বানায় না, কিন্তু একই সমস্যায় পড়ে অন্য পথে। এটি একটি স্থানীয় গড়: একটি বিন্দুর density বলতে তার আশপাশের বিন্দু গোনা। এই পর্বের অংশ ৬-এর সারণি দেখিয়েছে, standard normal মেঘে কেন্দ্রের ব্যাসার্ধ ১-এর ভেতরে d = ২-এ ৩৯% বিন্দু, d = ১০-এ ০.০১৫%. “আশপাশ” ধরে রাখতে h বাড়ালে স্থানীয়তা হারায়, আর bias বাড়ে; h ছোট রাখলে প্রতিবেশী থাকে না, variance বিস্ফোরিত হয়। তত্ত্বে KDE-র সেরা হার n−4/(4+d), অর্থাৎ d = ১০-এ এক মাত্রার সমান নির্ভুলতা পেতে ডেটা লাগে কল্পনাতীত বেশি। Silverman তাঁর ১৯৮৬ সালের বইয়ে normal ডেটার উদাহরণে একটি সারণি দেন যে নির্দিষ্ট নির্ভুলতার জন্য প্রয়োজনীয় নমুনা মাত্রার সঙ্গে কত দ্রুত বাড়ে; সংখ্যাগুলো উৎস দেখে নেওয়াই ভালো, তবে বার্তাটি স্পষ্ট: দশ মাত্রায় এমনকি লাখের ঘরের নমুনাও যথেষ্ট নাও হতে পারে।

বাস্তবে মানুষ অভিশাপ এড়ায় কাঠামো ধরে নিয়ে। স্বাধীনতা: Naive Bayes প্রতিটি feature-এর এক মাত্রার density গুণ করে; ভুল অনুমান, কিন্তু variance নাটকীয়ভাবে কমে। কম মাত্রার কাঠামো: ডেটা প্রায়ই অনেক কম মাত্রার একটি তলের কাছে থাকে (manifold hypothesis, পর্ব ৪২ (t-SNE আর UMAP — manifold learning আর তার ফাঁদ)-এর বিষয়), তাই আগে PCA বা অন্য পদ্ধতিতে নামানো যায়। Parametric বা mixture: parameter-এর সংখ্যা মাত্রার বহুপদী, সূচকীয় নয়। শর্তসাপেক্ষ ভাঙন: p(x1, …, xd) = p(x1)p(x2 | x1)…, যা autoregressive model আর Bayesian network-এর ভিত্তি। ছবি বা লেখার মতো খুব বেশি মাত্রায় deep generative model এই কৌশলগুলোকেই বড় আকারে কাজে লাগায়।

মূল উপলব্ধি: মাত্রা বাড়লে প্রতিবেশী উধাও হয়, তাই আকারহীন স্থানীয় অনুমান অচল হয়ে পড়ে; বেশি মাত্রায় density শেখা মানেই কোনো না কোনো কাঠামো ধরে নেওয়া।

প্র ০৪বাজারঘর চায় কম density-র অর্ডারকে “সন্দেহজনক” চিহ্নিত করতে। “Density কম মানেই অস্বাভাবিক” — এই যুক্তির কোথায় কোথায় ফাঁক, আর কীভাবে নিরাপদে ব্যবহার করবেন?

যুক্তিটির মূল ভালো: স্বাভাবিক আচরণ যেখানে ঘন, সেখানে density বেশি, তাই −log p̂(x) একটি প্রাকৃতিক anomaly score. কিন্তু পাঁচটি ফাঁক আছে। প্রথম, এককের ফাঁদ: density এককের ওপর নির্ভর করে। ঘণ্টা থেকে মিনিটে গেলে সব density ৬০ দিয়ে ভাগ হয়, ক্রম একই থাকে; কিন্তু log রূপান্তর করলে Jacobian ১/x প্রতিটি বিন্দুকে আলাদা মাত্রায় বদলায়, আর “সবচেয়ে কম density”-র তালিকা বদলে যেতে পারে। কোন জগতে density মাপছেন, সেটিই সিদ্ধান্তের অংশ।

দ্বিতীয়, বিরল কিন্তু বৈধ: ঢাকার বাইরের দূরের উপজেলার ১২০ ঘণ্টার ডেলিভারি বিরল, কিন্তু জালিয়াতি নয়। Density বলে “অস্বাভাবিক”, ব্যবসা বলে “স্বাভাবিক কিন্তু কম ঘটে”। তৃতীয়, দূষিত প্রশিক্ষণ: পুরোনো ডেটায় জালিয়াতি থাকলে KDE তাদের ঘিরেও ঢিবি বসায়; একই ধরনের জালিয়াতি বারবার হলে সেখানে density বেশি হয়ে যায়, আর ঠিক যাদের খুঁজছেন, তারা “স্বাভাবিক” দেখায়। চতুর্থ, নিজের ঢিবির সুবিধা: যে ডেটায় KDE বানিয়েছেন, তাতেই score দিলে প্রতিটি বিন্দু নিজের ঢিবি থেকে অন্তত φ(০)/(nh) পায়; একা থাকা outlier-ও তাই কৃত্রিমভাবে উঁচু density পায়। Leave-one-out density বা আলাদা fit-ডেটা লাগে। পঞ্চম, মাত্রা: অনেক feature-এ সব বিন্দুরই density কম (অংশ ৬), তাই score-এর পার্থক্য গোলমালে ডুবে যায়।

নিরাপদ ব্যবহারের নিয়ম: অল্প, অর্থপূর্ণ feature বাছুন; একটি পরিষ্কার সময়ের ডেটায় fit করুন; সীমা ঠিক করুন একটি নির্দিষ্ট চিহ্নিত-হার বা খরচের হিসাব থেকে, “density < কোনো ম্যাজিক সংখ্যা” থেকে নয়; আর কয়েকটি লেবেল-করা উদাহরণে precision মেপে দেখুন। পর্ব ৪৩ (Anomaly detection — isolation forest, LOF আর one-class SVM) দেখাবে, কোন পদ্ধতি density পুরোটা না শিখেই বিরলতা ধরে, আর তাদের সঙ্গে KDE-কে তুলনা কীভাবে হয়।

মূল উপলব্ধি: কম density একটি সংকেত, রায় নয়; এককের পছন্দ, প্রশিক্ষণ ডেটার বিশুদ্ধতা আর মূল্যায়নের পদ্ধতি না দেখে এর ওপর সিদ্ধান্ত নেওয়া বিপজ্জনক।

প্র ০৫প্রতিটি শ্রেণির জন্য KDE দিয়ে p(x | y) বানিয়ে Bayes-এর নিয়মে classifier বানানো যায়। এমন generative classifier কখন logistic regression-এর মতো discriminative model-এর চেয়ে ভালো, আর কখন খারাপ?

পদ্ধতিটি সরল: প্রতিটি শ্রেণি y-এর training সারি আলাদা করে KDE বানান, অগ্রাধিকার P(y) নিন শ্রেণির অনুপাত থেকে, তারপর P(y | x) ∝ p̂(x | y)·P(y). পর্ব ১৯-এর LDA/QDA ঠিক এটিই করে Gaussian দিয়ে; AI-এর ভিত্তি কোর্সের Naive Bayes করে feature-গুলো স্বাধীন ধরে। KDE বসালে শ্রেণির ভেতরের আকার নিয়ে কোনো অনুমান থাকে না: দুই চূড়া, বাঁকা লেজ, সবই ধরা পড়ে।

কখন ভালো: কম মাত্রা (এক থেকে কয়েক), প্রতিটি শ্রেণিতে যথেষ্ট সারি, আর শ্রেণির আকার জটিল। তখন সীমারেখা যেকোনো আকারের হতে পারে। আরও সুবিধা: model সম্পূর্ণ সম্ভাব্যতামূলক, তাই নতুন x কোনো শ্রেণিরই মতো না হলে (সব p̂(x | y) কম) তা ধরা যায়, যা discriminative model পারে না; আর অগ্রাধিকার বদলালে (যেমন উৎসবের মৌসুমে এক ধরনের অর্ডার বেশি) শুধু P(y) বদলে নতুন করে শেখা ছাড়াই মানিয়ে নেওয়া যায়।

কখন খারাপ: মাত্রা বাড়লে (অংশ ৬), প্রতিটি শ্রেণির density-তে curse of dimensionality লাগে, অথচ শ্রেণিবিভাগের জন্য শুধু সীমানার কাছের আচরণ জানলেই চলত। Vladimir Vapnik-এর প্রায়ই উদ্ধৃত পরামর্শ এই অর্থে: একটি সহজ সমস্যা সমাধানের পথে একটি কঠিনতর সমস্যা (পুরো density) সমাধান করতে যাবেন না। Andrew Ng আর Michael Jordan (২০০১)-এর পরিচিত তুলনা দেখায়, Naive Bayes অল্প ডেটায় দ্রুত তার সর্বোচ্চ ক্ষমতায় পৌঁছায়, আর logistic regression বেশি ডেটায় প্রায়ই তাকে ছাড়িয়ে যায়; যদিও প্রতিটি ডেটাসেটে এটি নিশ্চিত নিয়ম নয়। এছাড়া KDE classifier-এর প্রতিটি অনুমানে সব training সারি লাগে, তাই বড় ডেটায় ধীর, আর সম্ভাবনার মান bandwidth-এর ওপর নির্ভর করে বলে calibration (পর্ব ২৬) আলাদা করে যাচাই করতে হয়।

ব্যবহারিক পথ: প্রতিটি শ্রেণির bandwidth আলাদাভাবে held-out likelihood দিয়ে বাছুন, তারপর পুরো classifier-কে classification-এর মাপে (log loss, accuracy) cross-validate করুন, আর একটি logistic regression baseline-এর সঙ্গে তুলনা করুন।

মূল উপলব্ধি: KDE-ভিত্তিক generative classifier কম মাত্রায় নমনীয় আর ব্যাখ্যাযোগ্য, অচেনা ইনপুটও ধরে; বেশি মাত্রায় সে যতটা দরকার তার চেয়ে কঠিন সমস্যা সমাধান করতে যায়।

অনুশীলন

হিসাবগুলো খাতায় করুন, তারপর উত্তর মেলান। একটির জন্য কোড চালানো ঐচ্ছিক।

  1. হাতের হিসাব: চারটি ডেলিভারি সময় {2, 3, 5, 10} ঘণ্টা। (ক) Gaussian kernel আর h = 2 দিয়ে p̂(4) বের করুন (φ(0.5) = 0.35207, φ(1) = 0.24197, φ(3) = 0.00443). (খ) Silverman-এর নিয়মে h কত (NumPy-র linear quartile)? (গ) ১,০০০টি ডেলিভারিতে IQR = ২০ ঘণ্টা হলে Freedman–Diaconis bin width কত?

    (ক) u = (৪ − xi)/২ = (১, ০.৫, −০.৫, −৩). φ-এর যোগ = ০.২৪১৯৭ + ০.৩৫২০৭ + ০.৩৫২০৭ + ০.০০৪৪৩ = ০.৯৫০৫৩. ভাগ n·h = ৮: p̂(৪) = ০.১১৮৮২. (খ) গড় ৫, বর্গ-বিচ্যুতির যোগ ৯ + ৪ + ০ + ২৫ = ৩৮, sd = √(৩৮/৩) = ৩.৫৫৯. Q1-এর অবস্থান ০.৭৫: ২ + ০.৭৫ × ১ = ২.৭৫; Q3-এর অবস্থান ২.২৫: ৫ + ০.২৫ × ৫ = ৬.২৫; IQR = ৩.৫, /১.৩৪ = ২.৬১২. ছোটটি ২.৬১২. ৪−1/5 = ০.৭৫৭৯. h = ০.৯ × ২.৬১২ × ০.৭৫৭৯ = ১.৭৮২ ঘণ্টা। (গ) ১০০০−1/3 = ০.১, তাই w = ২ × ২০ × ০.১ = ৪ ঘণ্টা।

  2. সমালোচনা ও নকশা: বাজারঘরের একজন বিশ্লেষক ৪৮ ঘণ্টার বেশি দেরির সম্ভাবনা বের করতে গত মাসের সব ডেলিভারি সময়ে KernelDensity(bandwidth=0.05) বসালেন, কারণ “training log-likelihood এতেই সবচেয়ে বেশি”। তারপর একই ডেটায় −log density দিয়ে সবচেয়ে “অস্বাভাবিক” ১%-কে তদন্তে পাঠালেন, আর ঘণ্টার এককে সব হিসাব করলেন। কোথায় সমস্যা, আর কীভাবে নতুন করে সাজাবেন?

    সমস্যা: (১) Training likelihood দিয়ে h বাছা অর্থহীন: h যত ছোট, তত “ভালো”; ০.০৫ ঘণ্টা মানে প্রতিটি অর্ডারে আলাদা সুচ, তাই P(> ৪৮) মূলত ৪৮-এর বেশি পুরোনো অর্ডারের অনুপাত, মসৃণ অনুমান নয়। (২) ধনাত্মক ডেটায় শূন্যের নিচে ভর আর সীমানায় bias দেখা হয়নি। (৩) একই ডেটায় fit আর score: প্রতিটি অর্ডার নিজের ঢিবি থেকে অন্তত φ(০)/(nh) পায়, আর h ছোট হলে এটি বিশাল; একা থাকা অর্ডারও উঁচু density পায়, ফলে তালিকা অর্থহীন। (৪) এক মাসের ডেটায় উৎসব বা বন্যার মতো ঘটনা মিশে থাকতে পারে। (৫) “১%” একটি ম্যাজিক সংখ্যা; তদন্তের খরচ আর ক্ষমতার সঙ্গে মেলানো হয়নি।

    নতুন নকশা: log রূপান্তর; shuffle-সহ ৫-fold GridSearchCV-তে logspace grid থেকে held-out log-likelihood দিয়ে h; Jacobian দিয়ে ঘণ্টায় ফেরত এনে সংখ্যাগত integral দিয়ে P(> ৪৮); তুলনার জন্য অভিজ্ঞতালব্ধ অনুপাত আর একটি gamma বা log-normal fit; anomaly score-এর জন্য আলাদা fit-ডেটা বা leave-one-out density, আর কয়েকটি তদন্ত-করা অর্ডারে precision মেপে সীমা ঠিক করা।

  3. আউটপুট অনুমান করুন: চালানোর আগে লিখে ফেলুন নিচের কোড কী ছাপবে।
    import numpy as np
    x = np.array([1., 2., 2., 3., 9.])
    counts, edges = np.histogram(x, bins=4)
    print(edges)
    print(counts)
    print(np.histogram(x, bins=4, density=True)[0])
    [1. 3. 5. 7. 9.]
    [3 1 0 1]
    [0.3 0.1 0.  0.1]

    পরিসর ১ থেকে ৯, চারটি সমান bin, চওড়া ২. প্রথম তিনটি bin বাঁ দিক বন্ধ, ডান দিক খোলা: [১, ৩)-এ ১, ২, ২ (তিনটি); [৩, ৫)-এ ৩; [৫, ৭) ফাঁকা। শেষ bin [৭, ৯] দুই দিকেই বন্ধ, তাই ৯ সেখানে পড়ে। Density = গণনা/(n·w) = গণনা/১০: ০.৩, ০.১, ০, ০.১; ক্ষেত্রফলের যোগ (০.৩ + ০.১ + ০ + ০.১) × ২ = ১. লক্ষ করুন, ফাঁকা bin-এ density শূন্য: সেখানে একটি নতুন বিন্দু এলে তার log-likelihood −∞ হবে।

  4. কোড লিখুন: Epanechnikov kernel K(u) = ¾(1 − u²), |u| ≤ 1 (বাইরে ০) দিয়ে নিজের KDE function লিখুন, আর ডেটা {2, 3, 5, 10}, h = 3-এ x = 4 আর x = 8-এ মান বের করে scikit-learn-এর KernelDensity(kernel="epanechnikov")-এর সঙ্গে মেলান। একই ডেটায় x = 20-এ মান কত, আর তা log-likelihood-এর জন্য কী সমস্যা তৈরি করে?
    import numpy as np
    from sklearn.neighbors import KernelDensity
    d = np.array([2., 3., 5., 10.])
    def epa(t, data, h):
        u = (t[:, None] - data[None, :]) / h
        return (0.75 * np.clip(1 - u**2, 0, None)).sum(1) / (len(data) * h)
    t = np.array([4.0, 8.0])
    kd = KernelDensity(kernel="epanechnikov", bandwidth=3.0).fit(d[:, None])
    print(epa(t, d, 3.0).round(5), np.exp(kd.score_samples(t[:, None])).round(5))

    দুটি ফলই [0.14583 0.03472] (ডেস্কটপে চালানো)। হাতে: x = ৪-এ u = (০.৬৬৭, ০.৩৩৩, −০.৩৩৩, −২); শেষটি সীমার বাইরে, তাই ০. বাকি তিনটির ০.৭৫(১ − u²) = ০.৪১৬৭, ০.৬৬৬৭, ০.৬৬৬৭, যোগ ১.৭৫, ভাগ ১২: ০.১৪৫৮৩. x = ৮-এ শুধু ১০ (u = −০.৬৬৭) অবদান রাখে: ০.৪১৬৭/১২ = ০.০৩৪৭২. x = ২০ সব বিন্দু থেকে ৩-এর বেশি দূরে, তাই p̂ = ০ আর log = −∞: এমন একটি test বিন্দুই পুরো held-out log-likelihood নষ্ট করে। Gaussian kernel-এ মান খুব ছোট হলেও শূন্য হয় না।

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

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

ক্রমধনাত্মক ডেলিভারি সময়ের density KDE দিয়ে অনুমান করার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. ডেটাকে training আর test অংশে ভাগ
  2. সময়কে log জগতে নেওয়া, যাতে শূন্যের সীমানা না থাকে
  3. Training অংশে cross-validation দিয়ে held-out log-likelihood সর্বোচ্চ করে bandwidth বাছাই
  4. বাছা bandwidth-এ পুরো training অংশে KDE বানানো
  5. Jacobian ১/x দিয়ে ঘণ্টার জগতে ফেরত আনা, তারপর test অংশের log-likelihood মাপা

দুটি ডেটা বিন্দু 0 আর 2, Gaussian kernel, h = 1. x = 1-এ KDE-র মান কত? (φ(1) = 0.242)

  • 0.484
  • 0.399
  • 0.242
  • 0.121

KDE-র bandwidth h বাছাইয়ে training ডেটার নিজের log-likelihood সর্বোচ্চ করা ভুল কেন?

  • কারণ training log-likelihood হিসাব করা খুব ধীর
  • কারণ h → 0 হলে training log-likelihood সীমাহীন বাড়ে, তাই সে সবসময় সবচেয়ে ছোট h বাছে
  • কারণ এটি সবসময় সবচেয়ে বড় h বাছে
  • কারণ log-likelihood সবসময় ঋণাত্মক, তাই তুলনা করা যায় না

১,০০০টি নমুনা, IQR = 20 আর sd = 30. Freedman–Diaconis নিয়মে histogram-এর bin width কত?

  • 10.47
  • 40
  • 2
  • 4

সব মান ধনাত্মক এমন ডেটায় (যেমন ডেলিভারি সময়) সাধারণ Gaussian KDE শূন্যের ঠিক ওপরে কী করে, যদি আসল density সেখানে স্পষ্টভাবে ধনাত্মক হয়?

  • Density কম দেখায়, আর কিছু সম্ভাবনা শূন্যের নিচে পড়ে
  • Density বেশি দেখায়, কারণ শূন্যে ঢিবিগুলো জমা হয়
  • কোনো প্রভাব নেই, কারণ KDE ডেটার পরিসর মেনে চলে
  • মোট integral 1-এর বেশি হয়ে যায়

y = log x-এর জগতে একটি KDE বানানো হলো, আর x = 2-এ p̂Y(log 2) = 0.4. ঘণ্টার জগতে p̂X(2) কত?

  • 0.4
  • 0.2
  • 0.8
  • 0.4 × log 2 ≈ 0.277

একই training ডেটায় শেখা দুটি density model-কে একই নতুন ডেটায় গড় log-likelihood দিয়ে মাপা হলো: KDE −2.10, তিন উপাদানের Gaussian mixture −2.06. কোন সিদ্ধান্তটি সবচেয়ে যুক্তিসংগত?

  • KDE ভালো, কারণ তার সংখ্যার মান বড় (2.10 > 2.06)
  • KDE সবসময় ভালো, কারণ সে কোনো আকার ধরে নেয় না
  • এই ডেটায় mixture একটু ভালো, তবে পার্থক্য ছোট বলে কয়েকটি seed বা fold-এ যাচাই দরকার
  • দুটি model-এর log-likelihood কখনো তুলনা করা যায় না

শব্দকোষ

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

মনে রাখুন

  1. Density estimate দেয় anomaly score, নতুন নমুনা, সম্ভাবনার প্রশ্নের উত্তর আর generative classifier-এর p(x | y); parametric, nonparametric আর mixture তিনটি পথ।
  2. Histogram: p̂ = nk/(n·w); w ∝ n−1/3. FD = 2·IQR·n−1/3 (robust), Scott = 3.49·σ̂·n−1/3.
  3. KDE: p̂ = (1/nh)∑K((x − xi)/h); h ∝ n−1/5. Silverman = 0.9·min(σ̂, IQR/1.34)·n−1/5 শুরু হিসেবে, চূড়ান্ত h held-out log-likelihood দিয়ে।
  4. সীমানা: ধনাত্মক ডেটায় log জগতে KDE, ফেরত আনতে p̂X(x) = p̂Y(log x)/x; নমুনা = একটি ডেটা বিন্দু + h·noise.
  5. মাত্রা বাড়লে প্রতিবেশী উধাও (হার n−4/(4+d)); তখন mixture বা কাঠামোবদ্ধ model, আর সব তুলনা একই জগতে held-out log-likelihood দিয়ে।

পরের পর্বে: Density শেখার বদলে ডেটার ভেতরের কাঠামো খোঁজা। পর্ব ৪১ (PCA-র বাইরে — kernel PCA, ICA আর NMF) দেখাবে বাঁকা কাঠামো কীভাবে kernel PCA ধরে, মিশে যাওয়া সংকেত ICA কীভাবে আলাদা করে, আর বাজারঘরের গ্রাহক × পণ্য-শ্রেণির ক্রয় matrix-কে NMF কীভাবে অংশে ভাঙে।

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

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