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

t-SNE আর UMAP — manifold learning আর তার ফাঁদ

t-SNE and UMAP — Manifolds, Isomap, Perplexity, the Crowding Problem, Trustworthiness and the Traps of Pretty Pictures

Taught in Bangla, this lesson unrolls a swiss roll with PCA, MDS and Isomap, codes the perplexity binary search behind t-SNE, and explains the Student-t kernel and the crowding problem. Planted clusters show that t-SNE and UMAP distort cluster sizes and gaps, checked with trustworthiness in browser runs and a real desktop umap-learn run.

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

  • Manifold hypothesis কী, আর linear পদ্ধতি swiss roll-এ কেন ব্যর্থ হয়
  • Classical MDS আর Isomap-এর geodesic দূরত্ব, আর প্রতিবেশীর graph কখন “শর্টকাট” নেয়
  • t-SNE-র affinity, perplexity-র binary search নিজে লেখা, Student-t আর KL objective
  • t-SNE ছবির পাঁচটি ফাঁদ সংখ্যা দিয়ে ধরা, আর trustworthiness দিয়ে যাচাই
  • UMAP-এর ধারণা আর একটি সত্যিকারের ডেস্কটপ run

আগের পর্বগুলো মাত্রা কমিয়েছে linear ভাবে বা kernel দিয়ে: PCA-র ভিত্তি পর্ব ০৮ আর পর্ব ০৯-এ, PCA-র প্রথম পরিচয় AI-এর ভিত্তি কোর্সের পর্ব ৬৫-এ, আর kernel PCA, ICA, NMF পর্ব ৪১ (PCA-র বাইরে — kernel PCA, ICA আর NMF)-এ। এই পর্বের পদ্ধতিগুলো মূলত ছবি আঁকার জন্য: অনেক মাত্রার ডেটাকে দুই মাত্রায় এনে চোখে দেখা। ছবি সুন্দর হয়, আর ঠিক সেজন্যই বিপজ্জনক। পাতার চারটি ব্লক ব্রাউজারের Python-এ চলে (scikit-learn 1.8.0); প্রথমবার scikit-learn নামতে প্রায় দশ সেকেন্ড লাগে। UMAP ব্রাউজারে চলে না, তাই সেটি ডেস্কটপের সত্যিকারের run.

১ · Manifold hypothesis — অনেক কলাম, কিন্তু আসল স্বাধীনতা কম

ঢাকার একটি বাসের রুট ভাবুন। বাসটির অবস্থান লিখতে অক্ষাংশ, দ্রাঘিমাংশ আর উচ্চতা, তিনটি সংখ্যা লাগে। কিন্তু বাস তো রাস্তা ছেড়ে যায় না; রুটের শুরু থেকে কত কিলোমিটার এসেছে, এই একটি সংখ্যাই যথেষ্ট। তিন মাত্রার জগতে বাসের সব সম্ভাব্য অবস্থান আসলে একটি এক-মাত্রার বাঁকা রেখা। আর দুটি স্টপের “দূরত্ব” মানে রাস্তা ধরে দূরত্ব, পাখির ওড়ার দূরত্ব নয়।

এটিই manifold hypothesis: বাস্তবের উঁচু মাত্রার ডেটা (ছবির পিক্সেল, গ্রাহকের কেনাকাটার vector) সাধারণত পুরো জায়গা জুড়ে থাকে না; তা থাকে একটি কম মাত্রার, বাঁকানো “পাতা”-র কাছে, যার গাণিতিক নাম manifold. একটি ৮×৮ হাতের লেখার ছবি ৬৪ মাত্রার vector, কিন্তু এলোমেলো ৬৪টি পিক্সেল প্রায় কখনো একটি অঙ্কের মতো দেখায় না; আসল অঙ্কগুলো অল্প কয়েকটি স্বাধীন পরিবর্তনে (হেলানো, মোটা, আকার) তৈরি। Manifold learning সেই পাতাটি খুঁজে সমতলে বিছাতে চায়।

কেন linear পদ্ধতি যথেষ্ট নয়? PCA ডেটাকে একটি সমতল তলে ছায়া হিসেবে ফেলে। পাতাটি নিজেই বাঁকানো হলে ছায়ায় পাতার দূরের অংশ একে অপরের ওপর পড়ে। পাখির ওড়ার দূরত্ব (Euclidean distance) আর পাতা ধরে হাঁটার দূরত্ব (geodesic distance) আলাদা হয়ে যায়, আর linear পদ্ধতি শুধু প্রথমটি দেখে।

Swiss roll: সোজা দূরত্ব বনাম পাতা ধরে দূরত্ব

বাঁয়ে ওপর থেকে দেখা পেঁচানো পাতা, ডানে খুলে বিছানো পাতা

Euclidean vs geodesic distance on a swiss roll A B পেঁচানো (3-D) লাল: সোজা ≈ ১৯ সবুজ: পাতা ধরে ≈ ৮৯ খুলে বিছানো (2-D) A B দৈর্ঘ্য t বরাবর উচ্চতা ওপর-নিচে
ছবি ৪২.১ — পেঁচানো অবস্থায় A আর B কাছে (লাল), কিন্তু পাতা ধরে তারা দুই প্রান্তে (সবুজ)। Manifold learning চায় ডানের ছবিটি।

২ · MDS আর Isomap — দূরত্ব রেখে ছবি, আর geodesic দূরত্ব

Multidimensional scaling (MDS) একটি সরল প্রশ্ন করে: সব জোড়ার দূরত্বের একটি টেবিল দিলে, এমন দুই মাত্রার বিন্দু খুঁজুন যাদের মাঝের দূরত্ব টেবিলের যতটা সম্ভব কাছে। জেলা শহরগুলোর সড়ক-দূরত্বের টেবিল থেকে বাংলাদেশের মানচিত্র আঁকার মতো। Classical MDS (Torgerson, ১৯৫২) দূরত্বের বর্গের matrix-কে দুই দিক থেকে centre করে একটি Gram matrix বানায়, তারপর eigen-decomposition; Euclidean দূরত্বে এর ফল PCA-র সমান। Metric MDS আবার “stress” = Σ(dij − ‖yi − yj‖)² কমায় (scikit-learn-এ SMACOF algorithm). কিন্তু টেবিলে যদি সোজা দূরত্ব থাকে, তাহলে swiss roll-এর A আর B কাছেই থাকবে।

Isomap (Tenenbaum, de Silva আর Langford, Science, ২০০০) শুধু দূরত্বটা বদলায়। (১) প্রতিটি বিন্দুকে তার k জন নিকট প্রতিবেশীর সঙ্গে জুড়ে একটি neighbour graph; কাছের দূরত্বে সোজা দূরত্ব আর পাতা ধরে দূরত্ব প্রায় সমান। (২) দূরের জোড়ার দূরত্ব = graph-এ সবচেয়ে ছোট পথ, Dijkstra দিয়ে (DSA কোর্সের Dijkstra পর্ব দেখুন)। (৩) সেই geodesic টেবিলে classical MDS. প্রথম ব্লকে তিনটি পদ্ধতি একই swiss roll-এ। প্রতিটি বিন্দুর রং t, অর্থাৎ পাতা বরাবর তার অবস্থান। ছবি থেকে t সোজা রেখায় অনুমান করা গেলে (linear regression-এর R² বেশি) ধরা যায় পাতাটি খুলেছে।

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_swiss_roll
from sklearn.decomposition import PCA
from sklearn.manifold import Isomap, MDS
from sklearn.linear_model import LinearRegression

X, t = make_swiss_roll(n_samples=600, noise=0.3, random_state=42)
print("X:", X.shape, f" t (position on the roll) {t.min():.1f}..{t.max():.1f}")
models = {"PCA": PCA(n_components=2),
          "MDS": MDS(n_components=2, n_init=1, init="classical_mds",
                     random_state=42),
          "Isomap": Isomap(n_neighbors=10, neighbors_algorithm="kd_tree")}
fig, axes = plt.subplots(1, 3, figsize=(9, 3))
for ax, (name, m) in zip(axes, models.items()):
    Z = m.fit_transform(X)
    r2 = LinearRegression().fit(Z, t).score(Z, t)   # is t a straight line in Z?
    print(f"{name:6s} R^2 of t from the 2-D picture = {r2:.3f}")
    ax.scatter(Z[:, 0], Z[:, 1], c=t, s=6, cmap="viridis")
    ax.set_title(f"{name}  (R^2 = {r2:.2f})", fontsize=9)
    ax.set_xticks([]); ax.set_yticks([])
fig.subplots_adjust(wspace=0.08)

i, j = np.argmin(t), np.argmax(t)              # the two ends of the roll
s = np.linspace(t[i], t[j], 2001)
arc = np.trapezoid(np.sqrt(1 + s**2), s)        # length of the spiral r = t
print(f"ends: straight line {np.linalg.norm(X[i] - X[j]):.1f}, true arc {arc:.1f}")
for n in (400, 600):                            # Isomap's graph distance
    Xn, tn = make_swiss_roll(n_samples=n, noise=0.3, random_state=42)
    g = Isomap(n_neighbors=10, neighbors_algorithm="kd_tree").fit(Xn)
    a, b = np.argmin(tn), np.argmax(tn)
    print(f"n={n}: graph distance between the ends {g.dist_matrix_[a, b]:.1f}")

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

X: (600, 3)  t (position on the roll) 4.8..14.1
PCA    R^2 of t from the 2-D picture = 0.075
MDS    R^2 of t from the 2-D picture = 0.077
Isomap R^2 of t from the 2-D picture = 0.982
ends: straight line 19.0, true arc 89.1
n=400: graph distance between the ends 27.9
n=600: graph distance between the ends 92.8

PCA আর MDS: R² ০.০৭৫ আর ০.০৭৭, প্রায় সমান, কারণ সোজা দূরত্বে classical MDS আর PCA একই জিনিস। ছবিতে রঙের ক্রম পেঁচানোই থাকে। Isomap: R² ০.৯৮২; ছবিতে রং বাঁ থেকে ডানে মসৃণভাবে বদলায়, পাতাটি খুলে গেছে। দূরত্ব: দুই প্রান্তের সোজা দূরত্ব ১৯.০, সর্পিল ধরে আসল দৈর্ঘ্য ৮৯.১. ৬০০ বিন্দুতে graph-এর পথ ৯২.৮, আসলের চেয়ে প্রায় ৪% বেশি, কারণ পথ প্রতিবেশী থেকে প্রতিবেশীতে আঁকাবাঁকা যায়। কিন্তু ৪০০ বিন্দুতে পথ মাত্র ২৭.৯: বিন্দু পাতলা হওয়ায় কোনো বিন্দুর ১০ প্রতিবেশীর একজন পাশের প্যাঁচে পড়েছে, আর graph একটি short-circuit শর্টকাট পেয়েছে। একটি ভুল কিনারাই পুরো geodesic টেবিল নষ্ট করে। Isomap-এর বড় দুর্বলতা এটিই: ঘন নমুনা আর ঠিক k দরকার, আর noise বেশি হলে শর্টকাট সহজে আসে।

একই পরিবারে আরও পদ্ধতি আছে: Locally Linear Embedding (Roweis আর Saul, Science, ২০০০) প্রতিটি বিন্দুকে প্রতিবেশীদের ওজনযুক্ত যোগ হিসেবে লেখে আর সেই ওজন দুই মাত্রায় রাখে; Laplacian eigenmap graph-এর Laplacian-এর eigenvector নেয়। সবগুলোরই মূল ধাপ একটি প্রতিবেশীর graph. পরিষ্কার গাণিতিক পাতায় এগুলো সুন্দর কাজ করে, কিন্তু অনেক দলে ভাগ হওয়া আসল ডেটায় (হাতের লেখা, কোষের জিন-প্রকাশ) প্রায়ই ঘিঞ্জি ছবি দেয়। সেই সমস্যার জন্যই এসেছে t-SNE.

৩ · t-SNE, প্রথম অর্ধেক — affinity আর perplexity

t-SNE (t-distributed Stochastic Neighbor Embedding; Laurens van der Maaten আর Geoffrey Hinton, JMLR, ২০০৮) দূরত্ব রাখতে চায় না; চায় প্রতিবেশিত্ব রাখতে। এটি Hinton আর Roweis-এর আগের SNE (NIPS ২০০২ সম্মেলন)-এর উন্নত রূপ। প্রথম অর্ধেকে প্রতিটি বিন্দু i-এর জন্য একটি সম্ভাবনা: “i যদি নিজের প্রতিবেশী বাছে, j-কে বাছার সম্ভাবনা কত”:

pj|i = exp(−‖xi − xj‖² / 2σi²) / Σk≠i exp(−‖xi − xk‖² / 2σi²)

এখানে xi মূল ডেটার বিন্দু, σi বিন্দু i-এর Gaussian-এর চওড়া। কাছের j বেশি সম্ভাবনা পায়, দূরের j প্রায় শূন্য। এই সম্ভাবনাকে বলে affinity. প্রশ্ন হলো σi কত হবে। ঘন এলাকার বিন্দুতে ছোট σ যথেষ্ট, পাতলা এলাকায় বড় σ লাগে। তাই t-SNE প্রতিটি বিন্দুর জন্য আলাদা σi খোঁজে, যাতে তার সম্ভাবনা-বণ্টনের entropy একটি নির্দিষ্ট মানে আসে:

Perp(Pi) = 2H(Pi), H(Pi) = −Σj pj|i log₂ pj|i

Entropy-র ভিত্তি AI-এর ভিত্তি কোর্সের পর্ব ৩৮-এ। মূল কথা: k জন প্রতিবেশীর মধ্যে সমান ভাগ হলে H = log₂ k, তাই perplexity = k. অর্থাৎ perplexity হলো কার্যকর প্রতিবেশীর সংখ্যা। Perplexity বাড়ালে σ বাড়ে, বণ্টন চওড়া হয়, বেশি প্রতিবেশী গোনা হয়। নিচের stepper-এ একটি বিন্দুর পাঁচ প্রতিবেশী, দূরত্বের বর্গ ১, ২, ৪, ৯ আর ১৬:

d² = 1, 2, 4, 9, 16p ∝ exp(−d²/2σ²)

            
  1. সরু Gaussian. σ = ০.৫-এ প্রায় সব সম্ভাবনা (০.৮৭৯) সবচেয়ে কাছের প্রতিবেশীর। Entropy ০.৫৪৮ bit, perplexity ১.৪৬: বিন্দুটি কার্যত দেড়জনকে প্রতিবেশী মনে করে।
  2. একটু চওড়া। σ = ১-এ সম্ভাবনা প্রথম তিনজনে ছড়াল, perplexity ২.৭২.
  3. আরও চওড়া। σ = ২-এ পাঁচজনই কিছু পায়, perplexity ৪.৩০. দূরের প্রতিবেশীর ভাগ এখনো কম, কারণ Gaussian দূরত্বের বর্গে কমে।
  4. সীমা। σ খুব বড় হলে সবাই ০.২ পায়, H = log₂ ৫, perplexity ঠিক ৫. প্রতিবেশীর সংখ্যার চেয়ে বেশি perplexity হতে পারে না; তাই n বিন্দুর ডেটায় perplexity n-এর চেয়ে অনেক ছোট রাখতে হয়।
  5. Binary search. Perplexity σ-এর সঙ্গে একদিকে বাড়ে, তাই সীমা দুটি (lo, hi) ধরে মাঝখানে পরীক্ষা: perplexity লক্ষ্যের কম হলে lo = মাঝ, বেশি হলে hi = মাঝ. লক্ষ্য ২-এর জন্য σ = ০.৬৯০.
  6. লক্ষ্য ৪। σ = ১.৭৩০. প্রতিটি বিন্দু নিজের σ পায়: ঘন এলাকায় ছোট, পাতলা এলাকায় বড়। এভাবে t-SNE ঘনত্বের পার্থক্য প্রথমেই প্রায় মুছে দেয়, যা পরে একটি ফাঁদ হয়ে ফিরবে (অংশ ৫).

শেষে দুই দিকের সম্ভাবনা মিলিয়ে একটি প্রতিসম সংখ্যা: pij = (pj|i + pi|j) / 2n, যাতে সব জোড়ার যোগফল ১. দূরে একা পড়ে থাকা বিন্দুও এতে অন্তত 1/2n ভাগ পায়। scikit-learn খরচ বাঁচাতে প্রতিটি বিন্দুর শুধু প্রায় ৩ × perplexity জন নিকট প্রতিবেশী নিয়ে হিসাব করে, বাকিদের p ধরে শূন্য।

৪ · t-SNE, দ্বিতীয় অর্ধেক — Student-t, KL আর crowding problem

এবার দুই মাত্রায় বিন্দু yi রাখতে হবে, যাদের মাঝেও একটি সম্ভাবনা qij. মূল SNE এখানেও Gaussian নিত। t-SNE নেয় এক degree of freedom-এর Student-t (Cauchy) বণ্টন:

qij = (1 + ‖yi − yj‖²)−1 / Σk≠l (1 + ‖yk − yl‖²)−1

তারপর y-গুলো এমনভাবে সরানো হয় যাতে KL divergence C = Σ pij log(pij/qij) সবচেয়ে কম হয়; gradient হলো ∂C/∂yi = 4 Σj (pij − qij)(yi − yj)(1 + ‖yi − yj‖²)−1. p > q হলে j-কে কাছে টানে, p < q হলে দূরে ঠেলে। KL অপ্রতিসম: কাছের জোড়াকে (বড় p) দূরে রাখলে বড় শাস্তি, দূরের জোড়াকে (ছোট p) কাছে রাখলে ছোট শাস্তি। তাই t-SNE স্থানীয় গঠন রক্ষায় কঠোর আর বৈশ্বিক গঠনে উদাসীন। Optimisation-এর শুরুতে সব p-কে কিছুক্ষণ ১২ গুণ বাড়ানো হয় (early exaggeration), যাতে দলগুলো আগে আঁটসাঁট হয়ে সরে যাওয়ার জায়গা পায়।

কেন Student-t? দশ মাত্রায় একটি বিন্দুর চারপাশে মাঝারি দূরত্বে যত প্রতিবেশী আঁটে, দুই মাত্রায় তত জায়গা নেই। একে বলে crowding problem. নিচের stepper-এ সংখ্যায়:

Gaussian: exp(−d²/2)Student-t: 1/(1 + d²)

            
  1. উঁচু মাত্রায় জায়গা খোলসে। একটি বলের ভেতরের অর্ধেক ব্যাসার্ধে আয়তনের ভাগ ০.৫মাত্রা: দুই মাত্রায় ০.২৫, দশ মাত্রায় ০.০০০৯৮. দশ মাত্রায় সমানভাবে ছড়ানো প্রতিবেশীদের প্রায় সবাই মাঝারি দূরত্বের খোলসে থাকে।
  2. দুই মাত্রায় জায়গার অভাব। সেই সব মাঝারি প্রতিবেশীকে দুই মাত্রায় মাঝারি দূরত্বে বসানো যায় না। Gaussian q-তে তারা কেন্দ্রের দিকে চাপা পড়ে, আর আলাদা দলগুলো মাঝখানে মিশে যায়। এটিই crowding problem.
  3. দুই kernel পাশাপাশি। দূরত্ব ১ থেকে ৩-এ গেলে Gaussian ৫৫ গুণ কমে, Student-t মাত্র ৫ গুণ। Student-t-এর লেজ মোটা: দূরে গেলেও সম্ভাবনা ধীরে কমে।
  4. একই q, ভিন্ন দূরত্ব। উঁচু মাত্রায় Gaussian-এ d = ৩-এর মান (০.০১১১) পেতে Student-t-এ দূরত্ব লাগে ৯.৪৩. কাছের জোড়া (d = ১ → ০.৮১) প্রায় একই থাকে, কিন্তু মাঝারি জোড়াকে অনেক দূরে বসানো যায়।
  5. পরিণাম। দলের ভেতর আঁটসাঁট, দলের মাঝে চওড়া ফাঁক। এজন্যই t-SNE-র ছবি এত পরিষ্কার। কিন্তু খেয়াল করুন: ফাঁকের মাপ এখন একটি অরৈখিক টানাটানির ফল, আসল দূরত্বের মাপ নয়।

দ্বিতীয় ব্লক প্রথম অর্ধেক নিজে করে: load_digits-এর (scikit-learn-এর সঙ্গে আসা UCI হাতের লেখার ডেটা) ৩০০টি ছবির প্রথমটির জন্য binary search দিয়ে perplexity ৫, ৩০ আর ৫০-এর σ খোঁজে। তারপর scikit-learn-এর TSNE চালিয়ে PCA-র সঙ্গে trustworthiness তুলনা করে। Trustworthiness (Venna আর Kaski, ২০০১) মাপে: ছবিতে যারা কাছের k প্রতিবেশী, তারা আসল ডেটাতেও কাছে কি না। ছবিতে “ভুয়া প্রতিবেশী” এলে শাস্তি, শাস্তি তার আসল র‍্যাংক কত দূরে তার অনুপাতে; ১ মানে নিখুঁত।

import time
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE, trustworthiness

X, y = load_digits(return_X_y=True)
idx = np.random.default_rng(42).choice(len(y), 300, replace=False)
X, y = X[idx], y[idx]                       # 300 images, 64 pixels each
d2 = ((X - X[0]) ** 2).sum(axis=1)[1:]      # squared distances from point 0

def perp_of(sigma):                          # perplexity of p(j|0) for one sigma
    p = np.exp(-(d2 - d2.min()) / (2 * sigma**2)); p /= p.sum()
    H = -(p[p > 0] * np.log2(p[p > 0])).sum()
    return 2 ** H, p
for target in (5, 30, 50):
    lo, hi = 1e-3, 1e3
    for _ in range(60):                      # binary search on sigma
        mid = (lo + hi) / 2
        lo, hi = (mid, hi) if perp_of(mid)[0] < target else (lo, mid)
    perp, p = perp_of(mid)
    print(f"perplexity {target:2d}: sigma {mid:5.2f}, perp {perp:5.2f},"
          f" top-5 p sum {np.sort(p)[-5:].sum():.2f}")

t0 = time.perf_counter()
P15 = PCA(n_components=15).fit_transform(X)  # 64 -> 15 columns first
Z = TSNE(perplexity=30, random_state=42).fit_transform(P15)
print(f"t-SNE on {P15.shape}: {time.perf_counter() - t0:.1f} s")
for name, E in (("PCA", P15[:, :2]), ("t-SNE", Z)):
    print(f"{name:5s} trustworthiness (k=5) {trustworthiness(X, E, n_neighbors=5):.3f}")
fig, ax = plt.subplots(figsize=(5, 4))
sc = ax.scatter(Z[:, 0], Z[:, 1], c=y, cmap="tab10", s=10)
ax.legend(*sc.legend_elements(), fontsize=7, ncol=2, title="digit")
ax.set_title("t-SNE of 300 digits (perplexity 30)", fontsize=9)

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

perplexity  5: sigma  8.37, perp  5.00, top-5 p sum 0.97
perplexity 30: sigma 13.11, perp 30.00, top-5 p sum 0.56
perplexity 50: sigma 14.33, perp 50.00, top-5 p sum 0.44
t-SNE on (300, 15): 2.9 s
PCA   trustworthiness (k=5) 0.820
t-SNE trustworthiness (k=5) 0.991

Binary search: তিনটি লক্ষ্যই ঠিক মিলেছে (৫.০০, ৩০.০০, ৫০.০০)। σ ৮.৩৭ থেকে ১৪.৩৩, কারণ বেশি প্রতিবেশী ধরতে Gaussian চওড়া করতে হয়। Perplexity ৫-এ সবচেয়ে কাছের পাঁচজন সম্ভাবনার ৯৭% পায়; ৫০-এ মাত্র ৪৪%. (কোডে d2 - d2.min() বিয়োগ শুধু সংখ্যা ছোট রাখে; সাধারণীকরণের পর p বদলায় না।) PCA আগে: t-SNE চলেছে PCA-র প্রথম ১৫টি কলামে, যা বড় ডেটায় একটি প্রচলিত অভ্যাস: noise কমে, প্রতিবেশী-খোঁজা দ্রুত হয়। (৬৪ কলামে ব্রাউজারের Python-এ প্রতিবেশী-খোঁজা একটি অপ্রাসঙ্গিক warning ছাপে, এটিও একটি কারণ।) PCA-র ২ মাত্রার ছবি হলো এই ১৫টি কলামের প্রথম দুটি। Trustworthiness মাপা হয়েছে মূল ৬৪ মাত্রার সঙ্গে: PCA ০.৮২০, t-SNE ০.৯৯১. ছবিতে কাছের পাঁচজনকে t-SNE প্রায় নিখুঁতভাবে আসল প্রতিবেশী রাখে; PCA-র ছবিতে অনেক ভুয়া প্রতিবেশী। ছবিতে দশটি অঙ্ক প্রায় আলাদা দলে, PCA-র ছবিতে যা মিশে থাকে। এই হলো t-SNE-র শক্তি, আর পরের অংশ তার দাম।

৫ · ফাঁদ — perplexity, seed, দলের আকার, ফাঁক আর noise-এ দল

ফাঁদগুলো দেখতে এমন ডেটা লাগে, যার আসল উত্তর আমরা জানি। দশ মাত্রায় তিনটি কাল্পনিক দল, প্রতিটিতে ৬৪টি বিন্দু: A খুব আঁটসাঁট (standard deviation ০.৫), B তার ৮ গুণ ছড়ানো (৪.০), C মাঝারি (১.০)। নকশায় কেন্দ্রের দূরত্ব A–B = ২০, B–C = ৬০, A–C = ৮০ (নমুনার এলোমেলোতায় মাপা অনুপাত একটু আলাদা আসবে)। তুলনার জন্য ১৯২টি বিন্দুর একটি গোল Gaussian মেঘ, যাতে কোনো দলই নেই। তারপর চারটি t-SNE: তিন দলে perplexity ২, ৫ আর ৩০, আর noise-এ perplexity ২. এই ব্লক আর পরেরটি একই namespace ভাগ করে।

import time
import numpy as np
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE

rng = np.random.default_rng(42)
centres = np.zeros((3, 10))             # three groups in 10-D
centres[1, 0] = 20                      # B is 20 from A
centres[2, 0] = 80                      # C is 80 from A, 60 from B
spread = [0.5, 4.0, 1.0]                # A tight, B 8 times wider, C medium
X = np.vstack([c + s * rng.standard_normal((64, 10))
               for c, s in zip(centres, spread)])
lab = np.repeat([0, 1, 2], 64)
noise = rng.standard_normal((192, 10))  # one round Gaussian blob, no groups

def tsne(data, perp, seed=0, init="pca"):
    return TSNE(perplexity=perp, init=init, random_state=seed,
                max_iter=500).fit_transform(data)

t0 = time.perf_counter()
runs = {"perplexity 2": tsne(X, 2), "perplexity 5": tsne(X, 5),
        "perplexity 30": tsne(X, 30), "pure noise, perp 2": tsne(noise, 2)}
print(f"4 t-SNE runs of 192 points: {time.perf_counter() - t0:.1f} s")
fig, axes = plt.subplots(1, 4, figsize=(10, 2.8))
for ax, (name, Z) in zip(axes, runs.items()):
    if "noise" in name:
        ax.scatter(Z[:, 0], Z[:, 1], color="gray", s=6)
    else:
        ax.scatter(Z[:, 0], Z[:, 1], c=lab, cmap="brg", s=6)
    ax.set_title(name, fontsize=9); ax.set_xticks([]); ax.set_yticks([])
fig.subplots_adjust(wspace=0.08)

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

4 t-SNE runs of 192 points: 2.5 s

রং: নীল A, লাল B, সবুজ C. Perplexity ২: প্রতিটি আসল দল কয়েকটি ছোট টুকরোয় ভেঙেছে; কেউ না জানলে এখানে আট-দশটি “দল” গুনবেন। Perplexity ৫: তিনটি দল একটি প্রায় সমবাহু ত্রিভুজের তিন কোণে, আর তিনটিই প্রায় একই আকারের। অথচ আসলে C অনেক দূরে আর B অনেক বেশি ছড়ানো। Perplexity ৩০: দলগুলো আঁটসাঁট আর আবার প্রায় একই আকারের; ফাঁকগুলোও কাছাকাছি। শুধু noise: কোনো দল নেই এমন ডেটায় ছবিতে অনেক ছোট ছোট দলা। চোখের দেখা যথেষ্ট নয়; পরের ব্লক সংখ্যা দিয়ে মাপে।

পরের ব্লক প্রতিটি দলের কেন্দ্র আর ছড়ানো (কেন্দ্র থেকে গড় দূরত্ব) মাপে, আসল দশ মাত্রায় আর প্রতিটি ছবিতে। t-SNE-র এককের কোনো মানে নেই, তাই সব ফাঁককে A–B ফাঁক দিয়ে, আর সব ছড়ানোকে A-এর ছড়ানো দিয়ে ভাগ করা হয়েছে। সঙ্গে দুটি random শুরু (seed ০ আর ১), আর noise-এর জোড়া-দূরত্বের তুলনা।

from scipy.spatial.distance import pdist

def summary(P, lab):
    """centre gaps (A-B, B-C, A-C) and average spread of each group"""
    m = [P[lab == k].mean(axis=0) for k in range(3)]
    gaps = [np.linalg.norm(m[a] - m[b]) for a, b in ((0, 1), (1, 2), (0, 2))]
    spr = [np.linalg.norm(P[lab == k] - m[k], axis=1).mean() for k in range(3)]
    return np.array(gaps), np.array(spr)

def show(name, P):
    g, s = summary(P, lab)
    print(f"{name:22s} gaps {np.round(g / g[0], 2)}  spreads {np.round(s / s[0], 2)}")

print("(gaps divided by the A-B gap, spreads divided by A's spread)")
show("original 10-D", X)
for name in ("perplexity 5", "perplexity 30"):
    show("t-SNE " + name, runs[name])
for seed in (0, 1):
    show(f"perp 5, random init {seed}", tsne(X, 5, seed=seed, init="random"))

for name, P in (("pure noise", noise), ("its t-SNE", runs["pure noise, perp 2"])):
    d = pdist(P)
    print(f"{name:10s}: nearest/farthest pair distance ratio "
          f"{d.min() / d.max():.3f}, CV of distances {d.std() / d.mean():.2f}")

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

(gaps divided by the A-B gap, spreads divided by A's spread)
original 10-D          gaps [1.   2.88 3.87]  spreads [1.   8.12 2.14]
t-SNE perplexity 5     gaps [1.   0.94 0.93]  spreads [1.   0.99 0.99]
t-SNE perplexity 30    gaps [1.   1.22 1.3 ]  spreads [1.   1.05 1.  ]
perp 5, random init 0  gaps [1.   0.96 0.92]  spreads [1.   1.05 1.01]
perp 5, random init 1  gaps [1.   0.97 0.98]  spreads [1.   1.01 0.96]
pure noise: nearest/farthest pair distance ratio 0.117, CV of distances 0.22
its t-SNE : nearest/farthest pair distance ratio 0.002, CV of distances 0.47

দলের আকার: আসলে B-এর ছড়ানো A-এর ৮.১২ গুণ, C-এর ২.১৪ গুণ। প্রতিটি ছবিতে তিনটির ছড়ানো প্রায় সমান (০.৯৬–১.০৫)। কারণটি অংশ ৩-এর stepper-এ: প্রতিটি বিন্দু নিজের σ পায়, ফলে প্রতিটি দলের “প্রতিবেশী-বৃত্ত” একই সংখ্যক বিন্দু ধরে, আর ঘনত্বের পার্থক্য মুছে যায়। দলের ফাঁক: মাপা আসল অনুপাত ১ : ২.৮৮ : ৩.৮৭. Perplexity ৫-এ ১ : ০.৯৪ : ০.৯৩, প্রায় সমবাহু; perplexity ৩০-এ ১ : ১.২২ : ১.৩০, অর্থাৎ A–C আসলে A–B-র প্রায় চার গুণ, ছবিতে মাত্র ১.৩ গুণ। Seed: এই সরল, পরিষ্কার আলাদা তিন দলে random শুরুর দুই seed প্রায় একই অনুপাত দিল (০.৯৬/০.৯২ আর ০.৯৭/০.৯৮); seed-এর প্রভাব ডেটার ওপর নির্ভর করে, আর অংশ ৭-এর UMAP run-এ তা অনেক বড়। স্থির ফল মানে সঠিক ফল নয়: দুই seed একমত হয়ে একই ভুল অনুপাত দেখাচ্ছে। Noise: আসল দশ মাত্রার noise-এ দূরত্বগুলো বেশ সমান (সবচেয়ে কাছের জোড়া সবচেয়ে দূরেরটির ০.১১৭ ভাগ, variation-এর সহগ ০.২২)। ছবিতে কিছু জোড়া প্রায় গায়ে গায়ে (০.০০২) আর দূরত্বের বৈচিত্র্য দ্বিগুণ (০.৪৭): এই অসমতাই চোখে “দল” হয়ে দেখা দেয়।

scikit-learn ১.২ সংস্করণ থেকে TSNE-এর ডিফল্ট init="pca" আর learning_rate="auto". PCA দিয়ে শুরু করলে বৈশ্বিক বিন্যাস একটু ভালো থাকে আর seed-এ কম বদলায়, কিন্তু ওপরের perplexity ৫-এর সমবাহু ত্রিভুজ PCA শুরু দিয়েই এসেছে। শুরু ভালো হলে ফাঁদ কমে, মোছে না। পুরোনো n_iter argument সরানো হয়েছে; এখন max_iter.

৬ · t-SNE ছবি পড়া — কী বলা যায়, কী যায় না

এখন নিয়মগুলো একসঙ্গে। একজন বিশ্লেষক বাজারঘরের কাল্পনিক গ্রাহকদের কেনাকাটার vector-এর t-SNE ছবি নিয়ে মিটিং-এ এসেছেন। তাঁর প্রতিটি দাবি পরখ করুন:

দাবি আর রায়t-SNE ছবি

            
  1. স্থানীয় প্রতিবেশিত্ব। t-SNE ঠিক এটিই রক্ষা করতে বানানো, আর trustworthiness ০.৯৯-এর মতো হলে দাবিটি টেকে। তবু কয়েকটি জোড়ার আসল দূরত্ব দেখে নিশ্চিত হোন।
  2. দলের আকার। টেকে না। প্রতিটি বিন্দুর নিজস্ব σ ঘনত্বের পার্থক্য মুছে দেয়; অংশ ৫-এ ৮.১২ গুণ ছড়ানো দল ছবিতে সমান আকারের।
  3. দলের মাঝের দূরত্ব। টেকে না। Student-t-এর টানাটানি আর KL-এর উদাসীনতা বৈশ্বিক দূরত্ব রাখে না। দূরত্বের প্রশ্নে মূল ডেটায় কেন্দ্রের দূরত্ব মাপুন।
  4. দলের সংখ্যা। এখনো প্রমাণ নয়। Perplexity ২-এ তিনটি দল আট-দশ টুকরো হয়েছিল। কয়েকটি perplexity আর seed-এ একই দল ফিরে আসে কি না দেখুন, আর মূল ডেটায় clustering চালিয়ে মিলিয়ে নিন (পর্ব ৩৯-এর Gaussian mixture বা AI-এর ভিত্তির DBSCAN).
  5. ছোট দলা। সাবধান। নিছক noise-ও কম perplexity-তে দলা বানায়। একই আকারের এলোমেলো ডেটার ছবি পাশে রাখা একটি সস্তা নিয়ন্ত্রণ।
  6. অক্ষের অর্থ। টেকে না। ছবি ঘোরালে বা উল্টালে সব দূরত্ব একই, তাই KL-ও একই। PCA-র অক্ষের মতো কোনো feature-এর মিশ্রণ এখানে নেই।

Feature হিসেবে t-SNE-র ফল। scikit-learn-এর TSNE-এ fit_transform আছে, transform নেই: নতুন একটি বিন্দু বসাতে পুরো ছবি আবার বানাতে হয়। তাই কেউ train আর test একসঙ্গে t-SNE করে তার দুই কলামে classifier শেখালে test-এর বিন্দুগুলো train-এর ছবির আকারে প্রভাব ফেলেছে: এটি data leakage (AI-এর ভিত্তি কোর্সের পর্ব ৫৬)। আবার t-SNE ছবিতে দল খুঁজে সেই দলকেই “আবিষ্কৃত segment” বলে রিপোর্ট করা চক্রাকার যুক্তি। নিরাপদ নিয়ম: t-SNE ছবি অনুসন্ধানের জন্য; model-এর feature লাগলে PCA বা train-এ শেখা অন্য পদ্ধতি pipeline-এর ভেতরে।

Trustworthiness-এর সীমা। এটি শুধু k জন কাছের প্রতিবেশী দেখে, তাই ছোট k-তে বৈশ্বিক বিকৃতি ধরে না। অংশ ৫-এর সমবাহু ত্রিভুজেও স্থানীয় প্রতিবেশী ঠিক থাকে। বড় k-তেও মাপুন (অনুশীলন ৪), আর দূরত্বের প্রশ্নে সরাসরি মূল ডেটায় যান।

৭ · UMAP — fuzzy neighbour graph, আর ডেস্কটপে সত্যিকারের run

UMAP (Uniform Manifold Approximation and Projection; Leland McInnes, John Healy আর James Melville, arXiv ২০১৮) তত্ত্বে লেখা হয়েছে topology-র ভাষায়, কিন্তু কাজের ধাপগুলো সহজে বলা যায়, মোটামুটি এভাবে: (১) প্রতিটি বিন্দুর n_neighbors জন নিকট প্রতিবেশী, দ্রুত আনুমানিক পদ্ধতিতে (NN-descent). (২) প্রতিটি কিনারার একটি ওজন, ০ থেকে ১: সবচেয়ে কাছের প্রতিবেশী পায় ১, বাকিরা দূরত্ব অনুযায়ী কম; প্রতিটি বিন্দুর নিজস্ব মাপকাঠি, t-SNE-র σi-এর মতো। এই ওজনের graph-কে বলে fuzzy neighbour graph. (৩) দুই দিকের ওজন মেলানো হয় a + b − ab নিয়মে। (৪) দুই মাত্রায় একটি Student-t-এর মতো বক্ররেখা 1/(1 + a·d2b), যার আকার min_dist ঠিক করে, আর cross-entropy কমানো হয় stochastic gradient দিয়ে, দূরের জোড়া এলোমেলো নমুনা নিয়ে (negative sampling).

ব্যবহারিক পার্থক্য: UMAP সাধারণত বড় ডেটায় দ্রুত, transform দিয়ে নতুন বিন্দু বসাতে পারে, আর ডিফল্ট শুরু spectral (graph Laplacian থেকে), তাই বৈশ্বিক বিন্যাস প্রায়ই একটু ভালো থাকে। কিন্তু “UMAP বৈশ্বিক গঠন রাখে” কথাটি সাবধানে বলতে হয়: Dmitry Kobak আর George Linderman (Nature Biotechnology, ২০২১) দেখান যে এই পার্থক্যের বড় অংশ শুরুর পদ্ধতি থেকে আসে; একই শুরু দিলে t-SNE-ও কাছাকাছি করে। UMAP numba-নির্ভর, তাই ব্রাউজারের Python-এ চলে না। ডেস্কটপে uv দিয়ে একটি project (uv-এর পরিচয় AI-এর ভিত্তি কোর্সের পর্ব ০৭-এ):

~$ uv init bazarghar-manifold
~/bazarghar-manifold$ uv add numpy scipy scikit-learn==1.8.0 matplotlib umap-learn
…
 + numba==0.68.1
 + pynndescent==0.7.0
 + scikit-learn==1.8.0
 + umap-learn==0.5.12
…

স্ক্রিপ্টটি পুরো ১,৭৯৭টি digit-এ t-SNE আর UMAP চালিয়ে সময় আর trustworthiness মাপে (numba প্রথমবার compile করে, তাই আগে একটি ছোট warm-up run), তারপর অংশ ৫-এর একই তিন দলে n_neighbors ৫, ১৫ আর ৫০, প্রতিটিতে দুটি seed. সত্যিকারের run (একটি সাধারণ ল্যাপটপ, একবার; সময় সাধারণ ফল নয়):

~/bazarghar-manifold$ uv run python umap_run.py
…/umap/umap_.py:1952: UserWarning: n_jobs value 1 overridden to 1 by setting random_state. Use no seed for parallelism.
  (একই warning আরও ৭ বার, প্রতিটি seed দেওয়া run-এ)
Python 3.14.4, umap-learn 0.5.12, scikit-learn 1.8.0, numba 0.68.1
t-SNE (1797, 64) -> (1797, 2) in 2.2 s, trustworthiness(k=5) 0.995
UMAP  (1797, 64) -> (1797, 2) in 3.8 s, trustworthiness(k=5) 0.989
original      gaps [1.   2.88 3.87] spreads [1.   8.12 2.14]
UMAP nn= 5 s=0 gaps [1.   0.99 0.95] spreads [1.   1.   0.99]
UMAP nn= 5 s=1 gaps [1.   1.48 1.95] spreads [1.   1.07 0.97]
UMAP nn=15 s=0 gaps [1.   1.05 0.7 ] spreads [1.   1.02 1.03]
UMAP nn=15 s=1 gaps [1.   0.95 1.43] spreads [1.   1.04 1.05]
UMAP nn=50 s=0 gaps [1.   1.26 1.69] spreads [1.   0.97 0.95]
UMAP nn=50 s=1 gaps [1.   1.89 1.21] spreads [1.   0.97 1.01]

Warning: seed দিলে UMAP একটি thread-এ চলে, যাতে ফল পুনরুৎপাদনযোগ্য হয়; গতি আর পুনরুৎপাদনের এই বিনিময় সে নিজেই জানায়। সময়: এই ছোট ডেটায় UMAP (৩.৮ s) t-SNE-র (২.২ s) চেয়ে দ্রুত নয়; UMAP-এর গতির সুবিধা সাধারণত লাখ বিন্দুর ডেটায় দেখা যায়, আর এখানে একবারের মাপ। Trustworthiness: দুটিই প্রায় সমান (০.৯৯৫ আর ০.৯৮৯)। ফাঁদ: ছড়ানো এখানেও প্রায় সমান (০.৯৫–১.০৭, আসলে ৮.১২)। ফাঁকের অনুপাত n_neighbors আর seed-এ লাফায়, t-SNE-র চেয়ে অনেক বেশি: ১৫ প্রতিবেশীতে seed ০-এ ১ : ১.০৫ : ০.৭, যেখানে সবচেয়ে দূরের জোড়া A–C ছবিতে সবচেয়ে কাছে; seed ১-এ ১ : ০.৯৫ : ১.৪৩. ৫০ প্রতিবেশীতে seed ১-এ B–C (১.৮৯) A–C-র (১.২১) চেয়ে বড়, আসল ক্রমের উল্টো। আসল ১ : ২.৮৮ : ৩.৮৭ কোনো run-এই আসেনি। UMAP-এর ছবিও একই নিয়মে পড়তে হয়।

পদ্ধতিযা রক্ষা করেনতুন বিন্দুকখন
PCAসর্বোচ্চ variance, linearহ্যাঁপ্রথম ধাপ, feature, noise কমানো
Classical / metric MDSজোড়ার দূরত্বনা (সরাসরি)শুধু দূরত্বের টেবিল আছে এমন ডেটা
Isomapgraph ধরে geodesic দূরত্বহ্যাঁএকটি মসৃণ, ঘন নমুনার পাতা
t-SNEকাছের প্রতিবেশিত্বনাদল দেখার ছবি, কয়েক হাজার বিন্দু
UMAPকাছের প্রতিবেশিত্ব, কিছুটা বিন্যাসহ্যাঁবড় ডেটার ছবি, দ্রুত অনুসন্ধান

আরও গভীরে: Laurens van der Maaten (JMLR, ২০১৪) Barnes–Hut গাছ দিয়ে t-SNE-র খরচ O(n²) থেকে O(n log n)-এ নামান; scikit-learn-এর ডিফল্ট method="barnes_hut" এটিই। পরে FIt-SNE (Linderman ও সহকর্মী, ২০১৯) আর openTSNE লাখ বিন্দুতে দ্রুত। Wattenberg, Viégas আর Johnson-এর Distill প্রবন্ধ “How to Use t-SNE Effectively” (২০১৬) ইন্টারঅ্যাকটিভভাবে এই পর্বের ফাঁদগুলো দেখায়। Manifold hypothesis-কে পরীক্ষাযোগ্য প্রশ্ন হিসেবে Fefferman, Mitter আর Narayanan (২০১৬) গাণিতিকভাবে বিশ্লেষণ করেছেন।

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

  • Perplexity কী? 2H(Pi), প্রতিটি বিন্দুর কার্যকর প্রতিবেশীর সংখ্যা; σi binary search দিয়ে এমনভাবে বাছা হয় যাতে এটি নির্দিষ্ট মান হয়।
  • Low-dimensional space-এ Student-t কেন? মোটা লেজ মাঝারি দূরের জোড়াকে বেশি দূরে বসাতে দেয়, ফলে crowding problem কমে।
  • t-SNE-র কোন জিনিস বিশ্বাস করবেন না? দলের আকার, দলের মাঝের দূরত্ব, অক্ষ, আর একটিমাত্র perplexity/seed-এর দল-সংখ্যা।
  • Isomap কীভাবে geodesic দূরত্ব পায়? k-NN graph-এ সবচেয়ে ছোট পথ, তারপর classical MDS; পাতলা নমুনায় শর্টকাট বিপদ।
  • t-SNE-র ফল feature হিসেবে? transform নেই, train আর test একসঙ্গে বসালে leakage; feature লাগলে pipeline-এ train-এ শেখা পদ্ধতি।

৮ · AI-এর যুগে — t-SNE/UMAP কোড আর সিদ্ধান্ত যাচাই

AI assistant-কে “আমার গ্রাহকদের segment দেখাও” বললে সে প্রায়ই একটি সুন্দর t-SNE বা UMAP ছবি আর আত্মবিশ্বাসী ব্যাখ্যা দেয়। পরিচিত ভুলগুলো:

AI প্রায়ই যা করেকেন ভুলকীভাবে ধরবেন
TSNE(n_iter=1000)scikit-learn-এ সরানো argument; এখন max_iterনিজের version-এর docs আর চালিয়ে দেখা
tsne.transform(X_test)TSNE-এ transform নেইError পড়ুন; নতুন বিন্দু লাগলে অন্য পদ্ধতি
“দল C সবচেয়ে আলাদা, কারণ ছবিতে দূরে”দলের মাঝের দূরত্ব অর্থহীনমূল ডেটায় কেন্দ্রের দূরত্ব মাপুন
একটি perplexity, একটি seed-এ দল গোনাছবি দুটিতেই বদলায়কয়েকটি perplexity আর seed, noise-এর ছবি পাশে
Train + test একসঙ্গে t-SNE করে classifierLeakage আর চক্রাকার মূল্যায়নSplit আগে; feature-এর জন্য pipeline-এ PCA
Scaling ছাড়া ভিন্ন এককের কলামবড় এককের কলাম দূরত্ব দখল করেআগে scaling, তারপর দূরত্ব

AI-কে ছবির সঙ্গে যাচাইও চাইতে বলুন:

আমার কাছে বাজারঘরের প্রায় ৫,০০০ গ্রাহকের কেনাকাটার vector আছে (২০টি পণ্য-বিভাগে খরচ), scikit-learn 1.8.0 আর umap-learn 0.5.12 ব্যবহার করছি। আমি অনুসন্ধানের জন্য একটি ছবি চাই, সিদ্ধান্তের জন্য নয়। ১. আগে log আর StandardScaler করো, তারপর PCA দিয়ে ৫০ মাত্রা, তারপর t-SNE আর UMAP. ২. t-SNE perplexity ৫, ৩০, ১০০ আর UMAP n_neighbors ৫, ১৫, ৫০, প্রতিটিতে দুটি seed; সব ছবি একটি grid-এ দাও। ৩. প্রতিটির trustworthiness দাও, k = ৫ আর k = ৫০-এ। ৪. একই আকারের এলোমেলো ডেটার একটি t-SNE ছবিও পাশে রাখো। ৫. দলের আকার বা দলের মাঝের দূরত্ব নিয়ে কোনো দাবি করবে না; দূরত্বের প্রশ্ন মূল ডেটায় কেন্দ্রের দূরত্ব দিয়ে উত্তর দাও। ৬. যে argument ব্যবহার করছ তা এই version-এর docs-এ আছে কি না বলো, আর কোনো warning চাপা দিও না।

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

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

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

প্র ০১Single-cell জীববিজ্ঞানে t-SNE আর UMAP ছবি প্রায় প্রতিটি প্রবন্ধে থাকে। তবু কিছু গবেষক এই ছবিগুলোর তীব্র সমালোচনা করেছেন। বিতর্কটা কী, আর তা থেকে কী শেখার আছে?

Single-cell RNA sequencing-এ প্রতিটি কোষের হাজার হাজার জিনের প্রকাশ মাপা হয়; একটি পরীক্ষায় লাখ কোষ। ২০১০-এর দশকের মাঝামাঝি থেকে এই ডেটার t-SNE ছবি, পরে UMAP ছবি, ক্ষেত্রটির প্রায় প্রতীক হয়ে ওঠে: রঙিন দ্বীপ, প্রতিটি দ্বীপ একটি কোষ-ধরন। ছবিগুলো দ্রুত অনুসন্ধানে সত্যিই কাজে এসেছে, নতুন বা বিরল কোষ-দল খুঁজতে।

সমস্যা হলো ব্যাখ্যায়। প্রবন্ধে প্রায়ই লেখা হয়, দুটি দ্বীপ কাছে তাই কোষ দুটি সম্পর্কিত, বা একটি দ্বীপ বড় তাই সেখানে বৈচিত্র্য বেশি। Tara Chari আর Lior Pachter ২০২৩ সালে PLOS Computational Biology-তে “The specious art of single-cell genomics” প্রবন্ধে দেখান যে এই দুই মাত্রার ছবি মূল দূরত্ব ব্যাপকভাবে বিকৃত করে, আর ইচ্ছামতো আকৃতিতেও (তাঁদের উদাহরণে একটি হাতির আকারে) একই রকম “ভালো” ছবি বানানো যায়। অন্য গবেষকেরা পাল্টা যুক্তি দেন যে ছবিগুলো কখনোই দূরত্ব মাপার জন্য ছিল না, স্থানীয় প্রতিবেশিত্ব দেখার জন্য ছিল, আর সেই কাজে সেগুলো কার্যকর। বিতর্কটি এখনো চলছে, তাই দুই পক্ষকেই গুরুত্ব দিয়ে পড়া ভালো।

এই পর্বের সংখ্যা দুই পক্ষের কথাই সমর্থন করে। Digits-এ trustworthiness ০.৯৯১, অর্থাৎ স্থানীয় প্রতিবেশী প্রায় নিখুঁত। আবার তিন দলের পরীক্ষায় ৮.১২ গুণ ছড়ানো দল সমান আকারের দেখিয়েছে আর ১ : ২.৮৮ : ৩.৮৭ ফাঁক হয়েছে ১ : ০.৯৪ : ০.৯৩. কোন প্রশ্নের উত্তর ছবি দিতে পারে আর কোনটি পারে না, সেই সীমা স্পষ্ট করে লেখাই সমাধান। পরিমাণগত দাবি মূল উঁচু মাত্রার ডেটায় পরীক্ষা করতে হয়: clustering, differential expression, বা কেন্দ্রের দূরত্ব।

মূল উপলব্ধি: manifold ছবি অনুসন্ধানের হাতিয়ার, প্রমাণের নয়; ছবিতে দেখা প্রতিটি পরিমাণগত দাবি মূল ডেটায় আলাদা করে যাচাই করতে হয়।

প্র ০২SNE থেকে t-SNE-তে বদল ছিল মাত্র একটি kernel বদলানো। এই ছোট বদল কেন এত বড় প্রভাব ফেলল, আর এর দাম কী?

Geoffrey Hinton আর Sam Roweis-এর Stochastic Neighbor Embedding (NIPS ২০০২ সম্মেলন) প্রতিবেশিত্বকে সম্ভাবনা হিসেবে লেখার ধারণা আনে, আর দুই দিকেই Gaussian ব্যবহার করে। ধারণাটি সুন্দর হলেও বাস্তব ডেটায় ছবি প্রায়ই মাঝখানে একটি ঘিঞ্জি পিণ্ড হয়ে যেত, আর optimisation-ও কঠিন ছিল। ২০০৮ সালে van der Maaten আর Hinton দুটি বদল আনেন: প্রতিসম pij, যাতে gradient সরল হয়, আর নিচু মাত্রায় এক degree of freedom-এর Student-t.

কেন কাজ করে, তা অংশ ৪-এর stepper-এ দেখেছেন। উঁচু মাত্রায় একটি বিন্দুর প্রায় সব প্রতিবেশী মাঝারি দূরত্বের একটি খোলসে থাকে, কারণ আয়তন ব্যাসার্ধের মাত্রা-ঘাতে বাড়ে। দুই মাত্রায় সেই খোলসের জায়গা নেই। Gaussian q দিয়ে মেলাতে গেলে মাঝারি প্রতিবেশীরা কাছে চলে আসে, আর আলাদা দলগুলো একে অপরের ওপর পড়ে। Student-t-এর মোটা লেজে একই q মান অনেক বেশি দূরত্বে পাওয়া যায়: Gaussian-এ দূরত্ব ৩-এর সমান মান Student-t-এ ৯.৪৩-এ। ফলে মাঝারি দূরের বিন্দুরা বাইরে সরে যেতে পারে, দলের মাঝে ফাঁকা জায়গা তৈরি হয়। Gradient-এর (1 + d²)−1 গুণকও দূরের জোড়ার ধাক্কা সীমিত রাখে, তাই optimisation স্থির হয়।

দাম হলো ঠিক এই ফাঁকটাই। ফাঁকের মাপ এখন একটি অরৈখিক টানাটানির ফল, যা আসল দূরত্বের সঙ্গে আনুপাতিক নয়। পরে Kobak ও সহকর্মীরা (২০১৯) দেখান, degree of freedom ১-এর চেয়ে আরও ভারী লেজ দিলে দল আরও ভাঙে আর সূক্ষ্ম গঠন বেরোয়, হালকা লেজে ছবি SNE-র দিকে যায়। অর্থাৎ লেজের ভার নিজেই একটি “কতটা আলাদা দেখাবে” নিয়ন্ত্রক, আর সেই নিয়ন্ত্রণ ডেটা থেকে আসে না, আমাদের বাছাই থেকে আসে।

মূল উপলব্ধি: t-SNE-র পরিষ্কার দল একটি নকশাগত সিদ্ধান্তের ফল; যে kernel crowding সমাধান করে, সেটিই দলের মাঝের দূরত্বকে অর্থহীন করে।

প্র ০৩Isomap ২০০০ সালে Science-এ প্রকাশের সময় খুব সাড়া ফেলেছিল। আজ বাস্তব ডেটায় এটি তুলনামূলক কম ব্যবহৃত হয় কেন?

Joshua Tenenbaum, Vin de Silva আর John Langford-এর Isomap আর একই সংখ্যায় প্রকাশিত Sam Roweis আর Lawrence Saul-এর Locally Linear Embedding nonlinear মাত্রা কমানোর একটি নতুন পথ খুলে দেয়। ধারণাটি পরিষ্কার: স্থানীয়ভাবে সোজা দূরত্ব বিশ্বাসযোগ্য, তাই ছোট ধাপ জুড়ে geodesic দূরত্ব অনুমান করা, তারপর পরিচিত MDS. মুখের ছবির ডেটায় তাঁরা দেখান যে দুই মাত্রার অক্ষ মুখ ঘোরানো আর আলোর দিকের মতো অর্থপূর্ণ পরিবর্তন ধরে। শর্ত মানলে geodesic দূরত্বের অনুমান ঠিক মানে পৌঁছায়, এমন তাত্ত্বিক গ্যারান্টিও ছিল।

সমস্যা সেই শর্তে। এই পর্বের প্রথম ব্লকেই দেখেছেন: একই swiss roll-এ ৬০০ বিন্দুতে graph-এর পথ ৯২.৮, আসল ৮৯.১-এর কাছে, কিন্তু ৪০০ বিন্দুতে ২৭.৯. একটিমাত্র ভুল কিনারা পাশের প্যাঁচে শর্টকাট দিলেই সব দূরের জোড়ার দূরত্ব ভেঙে পড়ে। Noise বাড়লে, পাতা পাতলা হলে, বা k একটু বড় হলে এটি ঘটে। তাছাড়া Isomap ধরে নেয় একটি একক, সংযুক্ত, গর্তহীন পাতা; ডেটা কয়েকটি আলাদা দলে থাকলে graph ভেঙে যায়, আর গর্ত থাকলে geodesic দূরত্ব ঘুরপথে যায়। সব জোড়ার shortest path আর ঘন matrix-এর eigen-decomposition বড় n-এ ব্যয়বহুলও।

বাস্তবের অনেক ডেটা, যেমন অনেক রকম অঙ্ক বা অনেক রকম কোষ, একটি মসৃণ পাতা নয়, বরং অনেক ছোট পাতার জটলা। সেখানে বৈশ্বিক দূরত্ব রাখার চেষ্টা ছেড়ে শুধু প্রতিবেশিত্ব রাখা t-SNE বেশি কাজের ছবি দেয়। তবু Isomap-এর ধারণা হারায়নি: k-NN graph থেকে দূরত্ব বা গঠন বের করার কৌশল UMAP-সহ পরের প্রায় সব পদ্ধতির ভেতরে আছে।

মূল উপলব্ধি: একটি পদ্ধতির শক্তি তার অনুমানের মতোই শক্ত; পাতা মসৃণ আর ঘন না হলে geodesic ধারণাটি নিজেই ভঙ্গুর হয়ে পড়ে।

প্র ০৪বাজারঘরের একটি দল t-SNE ছবি দেখে গ্রাহকদের পাঁচটি segment ঘোষণা করে প্রচারণার বাজেট ভাগ করতে চায়। আপনি কীভাবে সিদ্ধান্তটি নিরাপদ করবেন?

প্রথমে মানতে হবে ছবিটি কী বলছে আর কী বলছে না। ছবিতে পাঁচটি দ্বীপ মানে, এই perplexity আর এই seed-এ, কাছের প্রতিবেশীরা পাঁচটি গুচ্ছে জড়ো হয়েছে। এটি দলের সংখ্যা, আকার বা দলের মাঝের দূরত্বের প্রমাণ নয়। অংশ ৫-এ perplexity ২ তিনটি আসল দলকে আট-দশ টুকরো করেছিল, আর noise থেকেও দলা এসেছিল। বাজেট ভাগের মতো সিদ্ধান্তে এই অনিশ্চয়তা সরাসরি টাকার ঝুঁকি।

নিরাপদ পথ কয়েকটি ধাপে। (১) স্থিতিশীলতা: perplexity ১০, ৩০, ১০০ আর কয়েকটি seed-এ একই পাঁচটি দল ফেরে কি না; noise-এর ছবি পাশে রেখে তুলনা। (২) মূল ডেটায় clustering: scaling করা কেনাকাটার vector-এ Gaussian mixture বা hierarchical clustering চালিয়ে BIC বা silhouette দিয়ে দলের সংখ্যা দেখা (পর্ব ৩৯-এ BIC দেখেছেন); bootstrap দিয়ে দলের স্থিতিশীলতা (পর্ব ২১)। (৩) ব্যাখ্যা: প্রতিটি দলের গড় খরচ কোন বিভাগে আলাদা, তা মূল ডেটার টেবিলে। ছবির অবস্থান থেকে নয়। (৪) ব্যবসায়িক যাচাই: segment দিয়ে ভিন্ন প্রচারণা ছোট পরীক্ষায় (A/B) চালিয়ে দেখা, segment-বিহীন প্রচারণার চেয়ে সত্যিই লাভ হয় কি না।

সঙ্গে নৈতিক আর ব্যবহারিক দিক: segment-এ যেন সংবেদনশীল তথ্য পরোক্ষে না ঢোকে, আর দলের সংজ্ঞা লিখে রাখা হয় যাতে পরের মাসে একই নিয়মে আবার বানানো যায়। t-SNE-র transform নেই, তাই নতুন গ্রাহককে কোন দলে ফেলবেন সেই নিয়মও মূল ডেটার clustering থেকে আসতে হবে।

মূল উপলব্ধি: ছবি প্রশ্ন তোলে, সিদ্ধান্তের উত্তর আসে মূল ডেটার পরিমাপ, স্থিতিশীলতা যাচাই আর ছোট বাস্তব পরীক্ষা থেকে।

প্র ০৫UMAP প্রকাশের পর বলা হতো এটি t-SNE-র চেয়ে বৈশ্বিক গঠন ভালো রাখে। পরে এই দাবি নিয়ে প্রশ্ন উঠল কেন, আর এ থেকে benchmark পড়া নিয়ে কী শেখার আছে?

McInnes, Healy আর Melville ২০১৮ সালে UMAP প্রকাশ করেন, তত্ত্ব হিসেবে Riemannian geometry আর fuzzy simplicial set-এর ভাষায়। বাস্তবে এটি দ্রুত ছিল, নতুন বিন্দু বসাতে পারত, আর অনেক উদাহরণে দলগুলোর আপেক্ষিক বিন্যাস t-SNE-র চেয়ে অর্থপূর্ণ দেখাত। ফলে খুব দ্রুত, বিশেষ করে single-cell জগতে, এটি ডিফল্ট হয়ে ওঠে, আর “UMAP বৈশ্বিক গঠন রাখে” কথাটি প্রায় প্রবাদ হয়ে যায়।

Dmitry Kobak আর George Linderman ২০২১ সালে Nature Biotechnology-তে দেখান যে এই পার্থক্যের বড় অংশ algorithm থেকে নয়, শুরুর অবস্থান থেকে আসে। UMAP-এর ডিফল্ট শুরু spectral, অর্থাৎ graph থেকে একটি বৈশ্বিক বিন্যাস নিয়ে শুরু; পুরোনো t-SNE implementation-গুলোর ডিফল্ট ছিল random. দুটিকে একই অর্থপূর্ণ শুরু দিলে বৈশ্বিক গঠনে তারা কাছাকাছি, আর UMAP-কে random শুরু দিলে সেও বিন্যাস হারায়। এর পর অনেক t-SNE implementation, scikit-learn-সহ (১.২ সংস্করণ থেকে), ডিফল্ট শুরু PCA করেছে। তবে UMAP-এর তাত্ত্বিক ভিত্তি তার বাস্তব আচরণ কতটা ব্যাখ্যা করে, তা নিয়ে আলোচনা এখনো চলছে; কিছু বিশ্লেষণ বলে loss-এর বাস্তব optimisation তত্ত্বের লক্ষ্য থেকে বেশ সরে যায়।

এই পর্বের ডেস্কটপ run একই সুরে কথা বলে: UMAP-এ শুধু seed বদলে একই ১৫ প্রতিবেশীতে ফাঁকের অনুপাত ১ : ১.০৫ : ০.৭ থেকে ১ : ০.৯৫ : ১.৪৩ হয়েছে, আর ছড়ানো সবসময় প্রায় সমান। Benchmark-এর শিক্ষা হলো: দুটি পদ্ধতির তুলনায় ডিফল্ট সেটিং আলাদা হলে পার্থক্য পদ্ধতির নয়, সেটিং-এর হতে পারে। তাই একই শুরু, একই প্রতিবেশী-সংখ্যা, কয়েকটি seed আর একটি পরিমাপযোগ্য মাপকাঠি (trustworthiness, কেন্দ্রের দূরত্বের rank correlation) নিয়ে তুলনা করতে হয়।

মূল উপলব্ধি: “পদ্ধতি X ভালো” দাবির আগে দেখুন তুলনাটি ন্যায্য কি না; ডিফল্ট সেটিং-এর পার্থক্য প্রায়ই পদ্ধতির কৃতিত্ব হিসেবে চালিয়ে দেওয়া হয়।

অনুশীলন

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

  1. পূর্ণ numerical: একটি বিন্দুর তিন প্রতিবেশীর জন্য exp(−d²/2σ²)-এর মান ১, ১/২ আর ১/৪ (একটি নির্দিষ্ট σ-তে)। (ক) pj|i তিনটি কত? (খ) Entropy (bit-এ) আর perplexity কত? (গ) দুই মাত্রায় দুটি জোড়ার দূরত্ব ১ আর ৩ হলে Gaussian exp(−d²/2) আর Student-t 1/(1 + d²)-এর অনুপাত (কাছের ÷ দূরের) কত?

    (ক) যোগফল ১ + ০.৫ + ০.২৫ = ১.৭৫; p = ৪/৭, ২/৭, ১/৭ ≈ ০.৫৭১, ০.২৮৬, ০.১৪৩.

    (খ) H = (৪/৭)log₂(৭/৪) + (২/৭)log₂(৭/২) + (১/৭)log₂ ৭ = ০.৫৭১ × ০.৮০৭ + ০.২৮৬ × ১.৮০৭ + ০.১৪৩ × ২.৮০৭ ≈ ০.৪৬১ + ০.৫১৬ + ০.৪০১ = ১.৩৭৯ bit; perplexity = ২১.৩৭৯ ≈ ২.৬০. তিনজন প্রতিবেশী, কিন্তু অসম ভাগে কার্যকর সংখ্যা আড়াইয়ের মতো।

    (গ) Gaussian: ০.৬০৬৫ ÷ ০.০১১১ ≈ ৫৫; Student-t: ০.৫ ÷ ০.১ = ৫. Student-t-এ দূরের জোড়া তুলনামূলক অনেক বেশি সম্ভাবনা রাখে, তাই তাকে দূরে বসিয়েও KL-এ বড় শাস্তি হয় না।

  2. সমালোচনা ও নকশা: একটি AI assistant-এর রিপোর্ট: “বাজারঘরের ৩,০০০ গ্রাহকের পুরো ডেটায় (train + test) t-SNE perplexity ৫ চালিয়েছি, seed ০. ছবিতে চারটি segment. Segment D সবার থেকে দূরে, তাই সবচেয়ে আলাদা; segment A দ্বিগুণ ছড়ানো, তাই বৈচিত্র্যময়। t-SNE-র দুই কলাম দিয়ে churn classifier শিখিয়েছি, test accuracy ০.৯১.” কী কী সমস্যা, আর সঠিক নকশা কী?

    সমস্যা: (১) একটি perplexity আর একটি seed-এ দল গোনা; ছোট perplexity দল ভেঙে ভুয়া segment বানাতে পারে। (২) “D দূরে, তাই সবচেয়ে আলাদা” আর “A দ্বিগুণ ছড়ানো”: দলের মাঝের দূরত্ব আর দলের আকার t-SNE-তে অর্থহীন। (৩) Train আর test একসঙ্গে t-SNE: test বিন্দু ছবির আকার ঠিক করেছে, leakage; t-SNE-র transform নেই, তাই নতুন গ্রাহকে এই classifier চালানোই যায় না। (৪) Scaling-এর উল্লেখ নেই। (৫) Baseline-এর তুলনা নেই।

    সঠিক নকশা: শুরুতে split. Segment-এর জন্য মূল scaled ডেটায় clustering (যেমন Gaussian mixture, BIC দিয়ে সংখ্যা), bootstrap-এ স্থিতিশীলতা, আর দলের বর্ণনা মূল কলামের গড় দিয়ে। t-SNE শুধু ছবি, কয়েকটি perplexity আর seed-এ, trustworthiness সহ। Churn model-এর feature: মূল feature, বা pipeline-এর ভেতরে train-এ শেখা PCA; একটি সরল baseline-এর সঙ্গে cross-validation-এ তুলনা।

  3. আউটপুট অনুমান করুন: চালানোর আগে লিখে ফেলুন নিচের কোড কী ছাপবে।
    import numpy as np
    p = np.full(8, 1/8)
    H = -(p * np.log2(p)).sum()
    print(H, 2 ** H)
    d = np.array([1.0, 3.0])
    print(np.round(np.exp(-d**2 / 2), 4), np.round(1 / (1 + d**2), 4))
    3.0 8.0
    [0.6065 0.0111] [0.5 0.1]

    আটজনে সমান ভাগ: H = log₂ ৮ = ৩ bit, perplexity ৮, অর্থাৎ কার্যকর প্রতিবেশী ঠিক আটজন। দ্বিতীয় লাইনে দূরত্ব ১ আর ৩-এ Gaussian ০.৬০৬৫ আর ০.০১১১, Student-t ০.৫ আর ০.১; NumPy শেষের শূন্য ছাড়া ছাপে। (ডেস্কটপে NumPy 2.5.4-এ চালিয়ে মেলানো।)

  4. কোড লিখুন: অংশ ৪-এর ব্লকের ৩০০টি digit নিয়ে perplexity ৫, ১৫, ৩০ আর ৫০-এ t-SNE চালান, প্রতিটিতে trustworthiness k = ৫ আর k = ৩০-এ ছাপুন। কোন perplexity কোন k-তে ভালো, আর কেন?
    for perp in (5, 15, 30, 50):
        Z = TSNE(perplexity=perp, random_state=42).fit_transform(X)
        t5 = trustworthiness(X, Z, n_neighbors=5)
        t30 = trustworthiness(X, Z, n_neighbors=30)
        print(f"perplexity {perp:2d}: trust k=5 {t5:.3f}, k=30 {t30:.3f}")

    ডেস্কটপে সত্যিকারের run (scikit-learn 1.8.0, একই ৩০০টি ছবি):

    perplexity  5: trust k=5 0.990, k=30 0.917
    perplexity 15: trust k=5 0.992, k=30 0.931
    perplexity 30: trust k=5 0.992, k=30 0.938
    perplexity 50: trust k=5 0.987, k=30 0.933

    k = ৫-এ সবাই প্রায় সমান (০.৯৮৭–০.৯৯২); একেবারে কাছের প্রতিবেশী যেকোনো যুক্তিসঙ্গত perplexity রক্ষা করে। k = ৩০-এ perplexity ৫ সবচেয়ে খারাপ (০.৯১৭), কারণ সে শুধু পাঁচজন প্রতিবেশীর কথা ভাবে আর মাঝারি দূরত্বের গঠন ভাঙতে দেয়; ৩০ সবচেয়ে ভালো (০.৯৩৮)। ৫০-এ সামান্য কমেছে। পার্থক্যগুলো ছোট আর একটি run-এর, তাই কয়েকটি seed-এ গড় নিলে বেশি নিশ্চিত হওয়া যায়।

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

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

ক্রমt-SNE-র ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. মূল ডেটায় প্রতিটি বিন্দুর নিকট প্রতিবেশী আর দূরত্ব বের করা
  2. প্রতিটি বিন্দুর জন্য binary search দিয়ে σ খোঁজা, যাতে perplexity লক্ষ্যমানে আসে
  3. pj|i আর pi|j মিলিয়ে প্রতিসম pij বানানো
  4. দুই মাত্রায় শুরুর অবস্থান ঠিক করা (যেমন PCA দিয়ে)
  5. Early exaggeration-সহ কিছু ধাপ, তারপর Student-t q দিয়ে KL কমিয়ে বিন্দু সরানো

t-SNE-তে perplexity ৩০ রাখার সবচেয়ে সঠিক ব্যাখ্যা কোনটি?

  • ছবিতে প্রায় ৩০টি দল দেখা যাবে
  • প্রতিটি বিন্দু কার্যত প্রায় ৩০ জন প্রতিবেশী বিবেচনা করে
  • Optimisation ৩০ ধাপ চলে
  • সব বিন্দুর Gaussian-এর σ = ৩০

একটি বিন্দুর প্রতিবেশী-সম্ভাবনা p = (০.৫, ০.২৫, ০.২৫)। তার perplexity কত?

  • ৩.০০
  • ১.৫০
  • প্রায় ২.৮৩
  • ২.০০

t-SNE নিচু মাত্রায় Gaussian-এর বদলে Student-t (এক degree of freedom) ব্যবহার করে কেন?

  • মোটা লেজ মাঝারি দূরের জোড়াকে দূরে বসাতে দেয়, ফলে crowding problem কমে
  • হিসাব অনেক দ্রুত হয় বলে
  • যাতে দলের মাঝের দূরত্ব আসল দূরত্বের সমানুপাতিক হয়
  • যাতে প্রতিটি বিন্দুর σ binary search ছাড়াই পাওয়া যায়

একটি t-SNE ছবিতে দল B, দল A-এর চেয়ে দ্বিগুণ চওড়া দেখায়। মূল ডেটা সম্পর্কে কী বলা যায়?

  • B-এর variance A-এর প্রায় দ্বিগুণ
  • B আসলে A-এর চেয়ে আঁটসাঁট
  • B-তে A-এর দ্বিগুণ বিন্দু
  • ছবি থেকে কিছুই বলা যায় না; মূল ডেটায় ছড়ানো মাপতে হবে

কেউ train আর test সারি একসঙ্গে নিয়ে scikit-learn-এর TSNE চালিয়ে, তার দুই কলামে classifier শিখিয়ে test accuracy রিপোর্ট করলেন। মূল সমস্যা কী?

  • দুই কলামে classifier শেখানো যায় না
  • শুধু seed ঠিক করা হয়নি
  • Test সারি embedding তৈরিতে অংশ নিয়েছে (leakage), আর নতুন ডেটায় transform নেই
  • Perplexity খুব কম রাখা হয়েছে

কোনো দল নেই এমন এলোমেলো Gaussian ডেটায় perplexity ২-এ t-SNE অনেক ছোট দলা দেখাল। সবচেয়ে ভালো প্রতিক্রিয়া কোনটি?

  • কয়েকটি perplexity আর seed-এ চালানো, আর দলের দাবি মূল ডেটায় যাচাই করা
  • দলাগুলো গুনে প্রতিটিকে আলাদা segment ঘোষণা করা
  • শুধু max_iter বাড়িয়ে দলাগুলো মুছে ফেলা
  • Trustworthiness বেশি হলে দলগুলো আসল ধরে নেওয়া

শব্দকোষ

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

মনে রাখুন

  1. Manifold: উঁচু মাত্রার ডেটা প্রায়ই একটি বাঁকানো কম মাত্রার পাতায়; PCA আর MDS সোজা দূরত্ব দেখে, Isomap প্রতিবেশীর graph ধরে geodesic দূরত্ব, কিন্তু পাতলা নমুনায় শর্টকাট নেয়।
  2. Perplexity: 2H, কার্যকর প্রতিবেশীর সংখ্যা; প্রতিটি বিন্দুর σ binary search দিয়ে, তাই ঘনত্বের পার্থক্য শুরুতেই মুছে যায়।
  3. Student-t আর KL: মোটা লেজ crowding সামলায় আর দলের মাঝে ফাঁক বানায়; KL স্থানীয় গঠনে কঠোর, বৈশ্বিক গঠনে উদাসীন।
  4. ফাঁদ: দলের আকার, দলের মাঝের দূরত্ব, অক্ষ আর একটি perplexity বা seed-এর দল-সংখ্যা বিশ্বাস করবেন না; noise-ও দলা বানায়।
  5. যাচাই: ছোট আর বড় k-তে trustworthiness, কয়েকটি সেটিং আর seed, মূল ডেটায় পরিমাপ; t-SNE-র ফল feature নয়। UMAP দ্রুত আর transform দেয়, কিন্তু একই নিয়মে পড়তে হয়।

পরের পর্বে: পর্ব ৪৩ (Anomaly detection — isolation forest, LOF আর one-class SVM)-এ প্রশ্ন বদলাবে: দল খোঁজা নয়, দলের বাইরে পড়ে থাকা অস্বাভাবিক বিন্দু খোঁজা। প্রতিবেশীর দূরত্ব আর স্থানীয় ঘনত্বের যে ধারণা এই পর্বে এসেছে, LOF ঠিক সেটিই কাজে লাগায়।

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

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