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

Eigenvalue আর eigenvector — ধারণা থেকে PCA পর্যন্ত

Eigenvalues and Eigenvectors — Geometry, Diagonalisation, the Spectral Theorem, Power Iteration, PCA and Markov Chains

Taught in Bangla, this lesson explains eigenvectors as directions a matrix only stretches, solves 2×2 cases by hand, and covers diagonalisation and the spectral theorem. It writes power iteration in the page, derives PCA from the covariance matrix, finds a Markov chain's stationary distribution, and warns about eig versus eigh, ordering, signs and a NumPy version change shown in a real desktop run.

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

  • Eigenvalue আর eigenvector-এর জ্যামিতিক মানে, আর 2×2 matrix-এর characteristic polynomial হাতে সমাধান
  • Diagonalisation (A = VΛV−1), matrix-এর ঘাত সহজে বের করা, আর কখন diagonalise করা যায় না
  • Symmetric matrix-এর spectral theorem, ছোট প্রমাণসহ, আর ML-এ এটি কোথায় কোথায় লাগে
  • নিজে power iteration লেখা, প্রতি ধাপের convergence মাপা, আর eigenvalue gap-এর প্রভাব
  • PCA-কে covariance-এর eigen-decomposition হিসেবে derive করা, Markov chain-এর stationary distribution, আর eig বনাম eigh-এর সংখ্যাগত সতর্কতা

AI-এর ভিত্তি কোর্সের পর্ব ১৭-এ NumPy দিয়ে matrix গুণ, solve আর eigenvalue-র প্রথম ঝলক দেখেছেন, পর্ব ৬৫-এ SVD দিয়ে PCA চালিয়েছেন, আর পর্ব ৫৩-এ stationary distribution তিন পথে বের করেছেন। এই কোর্সের পর্ব ০৬ (Vector space — span, basis, linear independence আর rank) basis আর null space দিয়েছে, আর পর্ব ০৭ (Matrix-এর জ্যামিতি — projection, rotation আর least squares) matrix-কে একটি জ্যামিতিক রূপান্তর হিসেবে দেখিয়েছে। এই পর্বের প্রশ্ন: একটি matrix কোন দিকগুলোকে ঘোরায় না, আর সেই দিকগুলো জানলে PCA, PageRank বা ক্রেতার দীর্ঘমেয়াদি আচরণ কেন হঠাৎ সরল হয়ে যায়? চালানো যায় এমন চারটি ব্লক পাতাতেই চলে (ব্রাউজারের Python, NumPy আর scikit-learn); প্রথমবার প্যাকেজ নামতে কয়েক সেকেন্ড, আর scikit-learn প্রথমবার প্রায় ১০ সেকেন্ড লাগতে পারে।

১ · Eigenvector কী — যে দিক matrix শুধু টানে, ঘোরায় না

রুটি বেলার কথা ভাবুন। বেলুন যদি সবসময় পূর্ব-পশ্চিমে চালান, রুটি সেদিকে লম্বা হয়। রুটির ওপর আগে আঁকা দাগগুলোর বেশির ভাগ বেলার পরে কাত হয়ে যায়, কিন্তু দুটি দাগ শুধু দৈর্ঘ্য বদলায়: পূর্ব-পশ্চিমের দাগ লম্বা হয়, আর উত্তর-দক্ষিণের দাগ প্রায় আগের মতোই থাকে। এই দুটি “না-ঘোরা দিক” হলো বেলার eigenvector, আর কত গুণ লম্বা হলো সেটিই eigenvalue.

পর্ব ০৭-এ দেখেছেন, একটি বর্গাকার matrix A হলো একটি linear map: প্রতিটি vector x-কে নতুন vector Ax-এ পাঠায়। সাধারণত Ax আর x আলাদা দিকে থাকে। কিন্তু কিছু বিশেষ দিক থাকতে পারে, যেখানে A শুধু টানে বা চাপে (অথবা উল্টে দেয়), ঘোরায় না:

A v = λ v,   যেখানে v ≠ 0

এখানে A একটি n×n matrix, v একটি শূন্য নয় এমন vector যাকে বলে eigenvector, আর λ (ল্যাম্বডা) একটি সংখ্যা, A-এর eigenvalue. λ = ৩ মানে সেই দিকে সবকিছু তিনগুণ লম্বা; λ = ০.৫ মানে অর্ধেক; λ ঋণাত্মক হলে দিক উল্টে যায় কিন্তু একই রেখায় থাকে; λ = ০ হলে সেই দিক পুরো চেপে শূন্যে যায়, অর্থাৎ v পড়েছে A-এর null space-এ (পর্ব ০৬)। “Eigen” জার্মান শব্দ, মোটামুটি অর্থ “নিজস্ব”: matrix-এর নিজস্ব দিক আর নিজস্ব গুণক।

দুটি ছোট কিন্তু জরুরি পর্যবেক্ষণ। প্রথমত, v eigenvector হলে 3v বা −v-ও eigenvector, কারণ A(cv) = cAv = λ(cv). তাই eigenvector আসলে একটি দিক বা রেখা, একটি নির্দিষ্ট তীর নয়। সফটওয়্যার সাধারণত দৈর্ঘ্য ১ করে দেয় (unit vector), কিন্তু চিহ্ন (+ না −) যেকোনোটি হতে পারে; অংশ ৬ আর ৮-এ এটি বারবার ফিরবে। দ্বিতীয়ত, একই λ-এর সব eigenvector আর শূন্য vector মিলে একটি subspace গড়ে, যাকে বলে সেই λ-এর eigenspace.

নিচের stepper-এ A = [[4, 1], [2, 3]] (প্রথম সারি 4, 1; দ্বিতীয় সারি 2, 3). প্রতিটি ধাপে একটি v (ধূসর তীর) আর তার ছবি Av (নীল তীর) দেখুন; লাল আর সবুজ ড্যাশ রেখা দুটি eigen-দিক।

Which vectors stay on their own line under A λ = 5 λ = 2
  1. (1, 0) ঘুরে যায়। প্রথম কলামটিই A(1, 0): (4, 2). x-অক্ষের দিক থেকে ছবি প্রায় ২৬.৬° ঘুরে গেছে। তাই (1, 0) eigenvector নয়।
  2. (0, 1)-ও ঘোরে। A(0, 1) = দ্বিতীয় কলাম (1, 3); y-অক্ষ থেকে প্রায় ১৮.৪° ঘুরেছে। দুই অক্ষের কোনোটিই এই A-এর বিশেষ দিক নয়।
  3. (1, 1) শুধু লম্বা হয়। A(1, 1) = (4 + 1, 2 + 3) = (5, 5) = 5 × (1, 1). ছবি ঠিক একই লাল রেখায়, পাঁচগুণ লম্বা। তাই (1, 1) eigenvector, eigenvalue ৫.
  4. (1, −2)-ও। A(1, −2) = (4 − 2, 2 − 6) = (2, −4) = 2 × (1, −2). সবুজ রেখায় থেকে দ্বিগুণ লম্বা: eigenvalue ২. লক্ষ করুন, লাল আর সবুজ রেখা পরস্পর লম্ব নয়; A symmetric নয় বলে (অংশ ৪)।
  5. যেকোনো vector = eigen-অংশের যোগ। (2, −1) = (1, 1) + (1, −2). A প্রতিটি অংশকে আলাদা করে টানে: 5(1, 1) + 2(1, −2) = (5, 5) + (2, −4) = (7, 1). সরাসরি গুণেও (8 − 1, 4 − 3) = (7, 1). Eigenvector-এর ভাষায় matrix-এর কাজ শুধু “প্রতিটি অক্ষে আলাদা গুণ”: এটিই অংশ ৩-এর diagonalisation.

শেষ ধাপটিই পুরো পর্বের চাবি। একটি জটিল দেখতে matrix, সঠিক অক্ষে তাকালে, শুধু কয়েকটি স্বাধীন গুণ। AI-এর ভিত্তি কোর্সের পর্ব ৩৩-এ যে dot product আর norm শিখেছেন, সেগুলো দিয়েই এখন “দিক ঘুরল কি না” মাপা যায়: u আর Au-এর মধ্যের কোণ শূন্য বা ১৮০° হলে u eigen-দিক।

২ · Characteristic polynomial — 2×2 matrix হাতে সমাধান

Eigenvalue খোঁজার যুক্তি তিন লাইনে। Av = λv-কে লিখুন Av − λIv = 0, অর্থাৎ (A − λI)v = 0, যেখানে I হলো identity matrix. আমরা শূন্য নয় এমন v চাই। পর্ব ০৬-এর ভাষায়, (A − λI)-এর null space-এ শূন্য ছাড়া কিছু থাকতে হবে, অর্থাৎ matrix-টি singular. আর singular মানে determinant শূন্য:

det(A − λI) = 0

বাঁদিকটি λ-এর একটি বহুপদী (polynomial), যার নাম characteristic polynomial; n×n matrix-এ তার ঘাত n. A = [[a, b], [c, d]] হলে

det [[a − λ, b], [c, d − λ]] = (a − λ)(d − λ) − bc = λ² − (a + d)λ + (ad − bc)

এখানে a + d হলো কর্ণের যোগফল, যাকে বলে trace (tr A), আর ad − bc হলো det A. তাই 2×2-এর সূত্র: λ² − (tr A)λ + det A = 0. দ্বিঘাত সমীকরণের মূলের নিয়ম থেকে সঙ্গে সঙ্গে দুটি যাচাই আসে: λ1 + λ2 = tr A আর λ1λ2 = det A. এই দুটি সব n×n matrix-এর জন্যও সত্য (সব eigenvalue-র যোগ = trace, গুণ = determinant).

পূর্ণ সমাধান · A = [[4, 1], [2, 3]]

ধাপ ১, বহুপদী: tr A = 4 + 3 = 7, det A = 4·3 − 1·2 = 10. তাই λ² − 7λ + 10 = 0, অর্থাৎ (λ − 5)(λ − 2) = 0, λ = ৫ আর ২. যাচাই: ৫ + ২ = ৭ ✓, ৫ × ২ = ১০ ✓.

ধাপ ২, λ = ৫-এর eigenvector: A − 5I = [[−1, 1], [2, −2]]. প্রথম সারি বলে −x + y = 0, অর্থাৎ y = x; দ্বিতীয় সারি একই কথা বলে (singular বলেই)। তাই v1 = (1, 1), বা দৈর্ঘ্য ১ করে (0.7071, 0.7071).

ধাপ ৩, λ = ২-এর eigenvector: A − 2I = [[2, 1], [2, 1]]. 2x + y = 0, অর্থাৎ y = −2x. v2 = (1, −2), দৈর্ঘ্য ১ করে (0.4472, −0.8944) বা তার উল্টো চিহ্ন।

ধাপ ৪, যাচাই: A(1, −2) = (2, −4) = 2(1, −2) ✓.

সব 2×2 matrix এত ভদ্র নয়। তিনটি বিশেষ ক্ষেত্র মনে রাখার মতো, কারণ পরীক্ষা আর ইন্টারভিউতে এগুলোই আসে:

৯০° ঘোরানো

R = [[0, −1], [1, 0]]: tr = 0, det = 1, তাই λ² + 1 = 0, λ = ±i (কাল্পনিক)। কোনো বাস্তব দিক ঘোরা থেকে রেহাই পায় না, যা জ্যামিতিতে স্পষ্ট। Non-symmetric বাস্তব matrix-এ জটিল (complex) eigenvalue স্বাভাবিক ঘটনা।

Shear (কাত করা)

[[1, 1], [0, 1]]: λ² − 2λ + 1 = 0, λ = ১ দুবার। কিন্তু A − I = [[0, 1], [0, 0]]-এর null space শুধু (1, 0)-এর রেখা: দুটি eigenvalue, একটি মাত্র দিক। এমন matrix-কে বলে defective.

Identity বা 3I

λ = ৩ দুবার, আর প্রতিটি দিক eigenvector. Eigenspace পুরো সমতল। তখন “কোন eigenvector”, প্রশ্নটির একক উত্তর নেই; অংশ ৮-এ এর সংখ্যাগত মানে দেখবেন।

এবার কম্পিউটারে। নিচের ব্লক একই A-এর characteristic polynomial-এর মূল np.roots দিয়ে আর eigenvalue np.linalg.eig দিয়ে বের করে, eigenvector যাচাই করে, তারপর একক বৃত্তের ২৪টি দিক নিয়ে মাপে A প্রতিটিকে কত ডিগ্রি ঘোরায়, আর ছবি আঁকে।

import numpy as np
import matplotlib.pyplot as plt
np.set_printoptions(precision=4, suppress=True)
A = np.array([[4.0, 1.0],
              [2.0, 3.0]])
tr, det = np.trace(A), np.linalg.det(A)       # λ² − (trace)·λ + det = 0
print(f"trace {tr:.0f}, det {det:.4f}")
print("np.roots  :", np.roots([1, -tr, det]))
w, V = np.linalg.eig(A)                       # কলামগুলো eigenvector
print("eig dtype:", w.dtype, "| max |imag|:", np.abs(np.imag(w)).max())
w, V = np.real(w), np.real(V)                 # imag 0, তাই নিরাপদ
print("np.linalg.eig:", w)
print("V (columns):\n", V)
for lam, v in zip(w, V.T):
    print(f"λ = {lam:.0f}: A@v = {A @ v}  λ·v = {lam * v}")
t = np.linspace(0, 2 * np.pi, 24, endpoint=False)
U = np.vstack([np.cos(t), np.sin(t)])          # ২৪টি unit vector
AU = A @ U
cos = np.sum(U * AU, 0) / np.linalg.norm(AU, axis=0)
turn = np.degrees(np.arccos(np.clip(cos, -1, 1)))     # u আর Au-এর কোণ
print("turn (deg), first 12 directions:", turn[:12].round(1))
fig, ax = plt.subplots(figsize=(5.2, 5.2), layout="constrained")
for (x, y), (p, q) in zip(U.T, AU.T):
    ax.plot([x, p], [y, q], color="0.75", lw=0.8)
ax.plot(*U, "o", color="0.45", ms=3, label="unit vector u")
ax.plot(*AU, "o", color="C0", ms=4, label="image A u")
for lam, v, c in zip(w, V.T, ("C3", "C2")):
    ax.axline((0, 0), v, color=c, ls="--", lw=1)
    ax.annotate("", lam * v, (0, 0), arrowprops=dict(color=c, width=2))
    ax.plot([], [], color=c, label=f"eigen-direction, λ = {lam:.0f}")
ax.set_aspect("equal"); ax.set_xlim(-5.5, 5.5); ax.set_ylim(-5.5, 5.5)
ax.set_title("A stretches eigen-directions, turns the rest")
ax.legend(fontsize=8, loc="lower right")
plt.show()

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

trace 7, det 10.0000
np.roots  : [5. 2.]
eig dtype: float64 | max |imag|: 0.0
np.linalg.eig: [5. 2.]
V (columns):
 [[ 0.7071 -0.4472]
 [ 0.7071  0.8944]]
λ = 5: A@v = [3.5355 3.5355]  λ·v = [3.5355 3.5355]
λ = 2: A@v = [-0.8944  1.7889]  λ·v = [-0.8944  1.7889]
turn (deg), first 12 directions: [26.6 18.3  9.2  0.   8.5 15.4 18.4 13.3  5.4 26.6 34.5 32.8]

দুই পথে একই মূল: np.roots আর eig দুটিই ৫ আর ২, হাতের হিসাবের মতো। এই পাতার NumPy 2.4.6-এ dtype float64, কারণ imag অংশ শূন্য; NumPy 2.5 থেকে এখানে complex128 আসে (অংশ ৮)। V-এর প্রথম কলাম (0.7071, 0.7071) হলো (1, 1)/√2. দ্বিতীয় কলাম (−0.4472, 0.8944) হাতের (1, −2)/√5-এর উল্টো চিহ্ন: একই রেখা, তীর উল্টো দিকে, আর দুটিই সঠিক। পরের দুই লাইনে A@v আর λ·v হুবহু এক। ঘোরার তালিকায় দিকগুলো ১৫° পরপর: চতুর্থটি (৪৫°) ঘোরে ০°, কারণ সেটিই লাল eigen-দিক; অন্যরা ৩৪.৫° পর্যন্ত ঘোরে। সবুজ eigen-দিক (−৬৩.৪°, বা উল্টো দিকে ১১৬.৬°) grid-এ নেই, কিন্তু তার কাছের ১২০°-এর দিক মাত্র ৫.৪° ঘোরে। ছবিতে ধূসর রেখা প্রতিটি u-কে তার Au-এর সঙ্গে জোড়ে: লাল আর সবুজ রেখার ওপরের বিন্দু সোজা বাইরে সরে, বাকিরা কাত হয়ে যায়।

কম্পিউটার কিন্তু বহুপদী সমাধান করে না। np.roots-এর docs বলে, এটি বহুপদীর companion matrix বানিয়ে তার eigenvalue বের করে। অর্থাৎ উল্টো পথ: মূল খুঁজতে eigenvalue, eigenvalue খুঁজতে মূল নয়। কারণ বড় ঘাতের বহুপদীর মূল সহগের সামান্য রাউন্ডিংয়েও অনেক সরে যায় (ভাবনার প্রশ্ন ৩)। Characteristic polynomial হাতের হিসাব আর প্রমাণের হাতিয়ার; সফটওয়্যারে চলে QR algorithm-এর মতো পুনরাবৃত্তিমূলক পদ্ধতি।

৩ · Diagonalisation — eigenvector-এর ভাষায় matrix লেখা

A-এর n-টি eigenvector-কে কলাম করে একটি matrix V বানান, আর eigenvalue-গুলো কর্ণে বসিয়ে Λ (বড় হাতের ল্যাম্বডা)। প্রতিটি কলামে Avi = λivi, তাই একসঙ্গে লিখলে AV = VΛ. V-এর কলামগুলো linearly independent হলে V-এর inverse আছে, আর

A = V Λ V−1

একে বলে diagonalisation বা eigen-decomposition. ডান থেকে বাঁয়ে পড়ুন: V−1x দেয় x-এর স্থানাঙ্ক eigenvector-এর basis-এ (পর্ব ০৬-এর “basis-এ স্থানাঙ্ক”), Λ প্রতিটি স্থানাঙ্ককে নিজের λ দিয়ে গুণ করে, আর V ফল আবার সাধারণ অক্ষে ফিরিয়ে আনে। Stepper-এর শেষ ধাপে ঠিক এটিই হাতে করেছিলেন: (2, −1)-এর eigen-স্থানাঙ্ক (1, 1), গুণের পর (5, 2), ফেরত (7, 1).

ঘাত সস্তা হয়ে যায়। A2 = VΛV−1VΛV−1 = VΛ2V−1, আর সাধারণভাবে Ak = VΛkV−1; Λk মানে শুধু কর্ণের প্রতিটি সংখ্যার k-তম ঘাত। আমাদের A-তে V = [[1, 1], [1, −2]], det V = −3, V−1 = (1/3)[[2, 1], [1, −1]]. গুণ করলে

Ak = (1/3) · [[2·5k + 2k, 5k − 2k], [2·5k − 2·2k, 5k + 2·2k]]

k = ১ বসিয়ে যাচাই: (10 + 2)/3 = 4, (5 − 2)/3 = 1, (10 − 4)/3 = 2, (5 + 4)/3 = 3 ✓. k = ১০-এ 510 = 9,765,625 আর 210 = 1,024, তাই প্রথম ঘর (19,531,250 + 1,024)/3 = 6,510,758; np.linalg.matrix_power(A, 10)-ও ঠিক এই সংখ্যা দেয়। আর একটি জিনিস চোখে পড়ে: k বড় হলে 5k 2k-কে পুরো ছাপিয়ে যায়, তাই Akx প্রায় পুরোপুরি λ = ৫-এর দিকে ঘুরে যায়। এটিই অংশ ৫-এর power iteration আর অংশ ৭-এর Markov chain-এর মূল কারণ।

কখন diagonalise করা যায়? যখন n-টি linearly independent eigenvector আছে। একটি যথেষ্ট শর্ত: n-টি eigenvalue সব আলাদা। দুটির জন্য ছোট প্রমাণ: ধরুন c1v1 + c2v2 = 0. A দিয়ে গুণ করলে c1λ1v1 + c2λ2v2 = 0; প্রথম সমীকরণকে λ2 দিয়ে গুণ করে বিয়োগ করলে c1(λ1 − λ2)v1 = 0. λ1 ≠ λ2 আর v1 ≠ 0, তাই c1 = 0, তারপর c2 = 0. Eigenvalue পুনরাবৃত্ত হলে কখনো হয় (3I), কখনো হয় না (shear). Defective matrix-এর জন্য Jordan form নামে একটি সাধারণীকরণ আছে, কিন্তু সংখ্যাগতভাবে তা অস্থির, আর ML-এ প্রায় দরকার পড়ে না, কারণ আমাদের গুরুত্বপূর্ণ matrix-গুলো বেশির ভাগই symmetric.

Eigen-decomposition আর SVD গুলিয়ে ফেলবেন না। VΛV−1 শুধু বর্গাকার matrix-এ, আর সবসময় থাকে না। SVD (A = UΣVT) যেকোনো আকারের matrix-এ সবসময় থাকে, আর দুই পাশে orthogonal matrix. দুটির সম্পর্ক অংশ ৬-এ একটু, আর পুরোটা পর্ব ০৯ (SVD — low-rank approximation, pseudo-inverse আর recommender-এর ভিত্তি)-এ।

৪ · Symmetric matrix আর spectral theorem — লম্ব অক্ষের নিশ্চয়তা

AT = A হলে matrix-টি symmetric: ঘর (i, j) আর (j, i) সমান। ML-এ সবচেয়ে গুরুত্বপূর্ণ matrix-গুলো এই জাতের: covariance matrix, loss-এর Hessian (পর্ব ১১), kernel বা Gram matrix (পর্ব ২৯), graph-এর Laplacian. এদের জন্য একটি অসাধারণ শক্তিশালী ফল আছে।

Spectral theorem (বাস্তব symmetric matrix)

A বাস্তব আর symmetric হলে: (ক) সব eigenvalue বাস্তব সংখ্যা; (খ) এমন n-টি eigenvector বেছে নেওয়া যায় যারা পরস্পর লম্ব আর দৈর্ঘ্য ১ (orthonormal); তাই (গ) A = QΛQT, যেখানে Q-এর কলাম সেই eigenvector, আর QTQ = I, অর্থাৎ Q একটি orthogonal matrix (Q−1 = QT).

(খ)-এর ছোট প্রমাণ (আলাদা eigenvalue-র ক্ষেত্রে): Av1 = λ1v1, Av2 = λ2v2, λ1 ≠ λ2. তাহলে λ1(v1Tv2) = (Av1)Tv2 = v1TATv2 = v1TAv2 = λ2(v1Tv2). মাঝের ধাপে AT = A ব্যবহার হয়েছে। তাই (λ1 − λ2)v1Tv2 = 0, আর যেহেতু λ1 ≠ λ2, dot product v1Tv2 = 0: দুটি orthogonal. পুনরাবৃত্ত eigenvalue-তে eigenspace-এর ভেতরে Gram–Schmidt (পর্ব ০৭) দিয়ে লম্ব basis বেছে নেওয়া যায়। (ক)-এর যুক্তি “আরও গভীরে” বাক্সে।

Symmetric S = [[2, 1], [1, 2]]: একক বৃত্ত হয়ে যায় উপবৃত্ত, অক্ষ দুটি eigenvector

Unit circle mapped by S becomes an ellipse whose axes are the orthogonal eigenvectors (1,1)/√2 (λ = 3) and (1,−1)/√2 (λ = 1)

Spectral theorem picture: circle to ellipse along orthogonal eigenvectors λ = 3, দৈর্ঘ্য 3 λ = 1, দৈর্ঘ্য 1 একক বৃত্ত S-এর পরে উপবৃত্ত
ছবি ৪.১ — S = [[2, 1], [1, 2]]: tr = ৪, det = ৩, তাই λ² − 4λ + 3 = 0, λ = ৩ আর ১. Eigenvector (1, 1)/√2 আর (1, −1)/√2 পরস্পর লম্ব। একক বৃত্তের সব বিন্দু S-এর পরে একটি উপবৃত্তে যায়, যার অর্ধ-অক্ষ ঠিক eigenvector-এর দিকে আর দৈর্ঘ্য |λ|. অংশ ১-এর non-symmetric A-তে দুটি eigen-দিক লম্ব ছিল না।

Rank-১ টুকরোর যোগ। QΛQT গুণ করে লিখলে A = λ1q1q1T + λ2q2q2T + … + λnqnqnT. প্রতিটি qiqiT হলো qi-এর রেখায় orthogonal projection (পর্ব ০৭)। অর্থাৎ symmetric matrix-এর কাজ: x-কে লম্ব অক্ষগুলোতে ছায়া ফেলা, প্রতিটি ছায়াকে λi দিয়ে গুণ, তারপর যোগ। S-এর জন্য: 3 · ½[[1, 1], [1, 1]] + 1 · ½[[1, −1], [−1, 1]] = [[1.5 + 0.5, 1.5 − 0.5], [1.5 − 0.5, 1.5 + 0.5]] = [[2, 1], [1, 2]] ✓. ছোট λ-এর টুকরো ফেলে দিলে matrix-এর সবচেয়ে ভালো low-rank আন্দাজ মেলে (symmetric positive semidefinite matrix-এ); সাধারণ matrix-এ এর রূপ Eckart–Young, পর্ব ০৯-এ।

সব eigenvalue ধনাত্মক হলে xTAx > 0 সব x ≠ 0-এ; এমন matrix-কে positive definite বলে। Covariance matrix-এর সব eigenvalue ≥ ০ কেন, আর quadratic form xTAx-এর বাটি, জিন বা উপত্যকার আকার, সেটি পর্ব ১০ (Positive definite matrix — quadratic form আর covariance)-এর বিষয়। এখানে শুধু PCA-র জন্য যতটুকু লাগে, ততটুকু।

আরও গভীরে: (ক)-এর যুক্তি: Av = λv-তে v জটিল হতে পারে ধরে নিন, আর v̄ তার conjugate. তাহলে v̄TAv = λ‖v‖², আর A বাস্তব symmetric বলে বাঁদিকটি নিজের conjugate-এর সমান, অর্থাৎ বাস্তব; ‖v‖² > 0, তাই λ বাস্তব। ঐতিহাসিকভাবে symmetric matrix-এর বাস্তব eigenvalue-র ফল সাধারণত Cauchy-র (১৮২৯ সালের কাছাকাছি) কাজের সঙ্গে যুক্ত করা হয়, আর “spectrum” শব্দটি পরে Hilbert-এর integral equation-এর কাজ থেকে আসে। Eigenvector-এর পরিবর্তন কতটা স্থির, তার সীমা দেয় Davis–Kahan উপপাদ্য (১৯৭০): ভুল ∝ perturbation-এর মাপ ÷ eigenvalue-র ফাঁক।

৫ · Power iteration — নিজের হাতে একটি eigen-solver

অংশ ৩-এ দেখেছেন Akx সবচেয়ে বড় eigenvalue-র দিকে ঘুরে যায়। এই পর্যবেক্ষণকে algorithm বানালে পাই power iteration, যা Richard von Mises আর Hilda Pollaczek-Geiringer-এর ১৯২৯ সালের কাজের সঙ্গে সাধারণত যুক্ত করা হয়:

y = A xk,   xk+1 = y / ‖y‖,   λ̂ = xk+1T A xk+1

প্রতি ধাপে গুণ, তারপর দৈর্ঘ্য ১ করা (না করলে সংখ্যা 5k-এর মতো ফুলে overflow হয়)। শেষ রাশিটি Rayleigh quotient: একটি unit x-এর জন্য xTAx, যা x eigenvector হলে ঠিক λ দেয়।

কেন কাজ করে। A diagonalisable হলে শুরুর x0 = c1v1 + c2v2 + … + cnvn. তাহলে

Akx0 = λ1k [ c1v1 + c2(λ2/λ1)kv2 + … + cn(λn/λ1)kvn ]

|λ1| > |λ2| ≥ … হলে বন্ধনীর বাকি পদ প্রতি ধাপে অন্তত |λ2/λ1| গুণ ছোট হয়। তাই দিকের ভুল কমে মোটামুটি |λ2/λ1|k হারে। Symmetric A-তে Rayleigh quotient আরও ভালো: তার ভুল কমে প্রায় |λ2/λ1|2k হারে, কারণ eigenvector-এর কাছে xTAx প্রথম ক্রমে বদলায় না। দুটি শর্ত: (১) শুরুর x0-এ v1-এর অংশ থাকতে হবে (c1 ≠ 0); এলোমেলো x0 নিলে কার্যত সবসময় থাকে। (২) সবচেয়ে বড় |λ| একটিই হতে হবে; |λ1| = |λ2| হলে (যেমন λ = ১ আর −১) x চিরকাল দোলে। 1 − |λ2/λ1|-এর মতো মাপকে সাধারণভাবে বলে eigenvalue gap: ফাঁক যত বড়, convergence তত দ্রুত।

S-এর eigenvalue ৩ আর ১, তাই λ2/λ1 = 1/3; লক্ষ্য v1 = (0.7071, 0.7071)
ধাপ
আগের x
y = S x
‖y‖
নতুন x = y / ‖y‖
Rayleigh xᵀSx
v₁ থেকে কোণ
  1. প্রথম গুণ। S(1, 0) = প্রথম কলাম (2, 1). দৈর্ঘ্য √5 = ২.২৩৬১ দিয়ে ভাগ করে x1 = (0.8944, 0.4472). Rayleigh quotient x1TSx1 = ২.৮, আসল ৩-এর চেয়ে ০.২ কম। লক্ষ্য দিক (৪৫°) থেকে কোণ ১৮.৪৩৫°.
  2. দ্বিতীয় গুণ। S(0.8944, 0.4472) = (2 × 0.8944 + 0.4472, 0.8944 + 2 × 0.4472) = (2.2361, 1.7889). ভাগ করে (0.7809, 0.6247); কোণ ৬.৩৪০°, Rayleigh ২.৯৭৫৬.
  3. তৃতীয় গুণ। নতুন x = (0.7328, 0.6805), কোণ ২.১২১°. প্রতি ধাপে কোণ প্রায় তিন ভাগের এক ভাগ হচ্ছে: ঠিক λ2/λ1 = 1/3.
  4. চতুর্থ গুণ। x = (0.7158, 0.6983), কোণ ০.৭০৭°, Rayleigh ২.৯৯৯৭. ‖y‖ নিজেও ৩-এর দিকে যাচ্ছে (২.৯৯৮২), কারণ eigenvector-এর কাছে ‖Sx‖ ≈ λ1.
  5. হার মিলিয়ে দেখা। শুরুর x0 = (1, 0) = ½(1, 1) + ½(1, −1); k ধাপে দুই অংশের অনুপাত (1/3)k, তাই tan(কোণ) হুবহু ১/৩, ১/৯, ১/২৭, ১/৮১. Rayleigh-এর ভুল প্রতি ধাপে প্রায় ১/৯ গুণ, অর্থাৎ (λ2/λ1)2: symmetric matrix-এ eigenvalue-র আন্দাজ eigenvector-এর চেয়ে দ্বিগুণ দ্রুত নির্ভুল হয়।

এবার ৪×৪ matrix-এ, দুটি ভিন্ন gap নিয়ে। দুটি matrix-এর সবচেয়ে বড় eigenvalue ৫ আর eigenvector-ও এক (একই orthogonal Q দিয়ে বানানো, QR থেকে, যা পর্ব ০৭-এর বিষয়), পার্থক্য শুধু λ2: একটিতে ৪.৫ (অনুপাত ০.৯), অন্যটিতে ১ (অনুপাত ০.২)। শুরু একই এলোমেলো x0, আর শেষে np.linalg.eigh-এর উত্তরের সঙ্গে মেলানো।

import numpy as np

rng = np.random.default_rng(42)
Q, _ = np.linalg.qr(rng.standard_normal((4, 4)))   # একটি এলোমেলো orthogonal matrix

def make(lams):                                     # জানা eigenvalue-এর symmetric matrix
    return Q @ np.diag(lams) @ Q.T

def power_iteration(A, steps, x):
    rows = []
    for k in range(1, steps + 1):
        y = A @ x                                   # ১. গুণ
        x = y / np.linalg.norm(y)                   # ২. দৈর্ঘ্য 1 করা
        lam = x @ A @ x                             # ৩. Rayleigh quotient
        rows.append((k, lam, x))
    return rows

x0 = rng.standard_normal(4)
for lams in ([5, 4.5, 2, 1], [5, 1, 0.5, 0.2]):
    A = make(lams)
    w, V = np.linalg.eigh(A)                        # উত্তর মেলানোর জন্য
    v1 = V[:, -1]                                   # eigh: ছোট থেকে বড়, শেষটি সবচেয়ে বড়
    print(f"eigenvalues {lams}, ratio λ2/λ1 = {lams[1] / lams[0]}")
    for k, lam, x in power_iteration(A, 30, x0):
        if k in (1, 2, 3, 5, 10, 20, 30):
            ang = np.degrees(np.arccos(min(1.0, abs(x @ v1))))
            print(f"  step {k:>2}: λ estimate {lam:.6f} | error {5 - lam:.1e}"
                  f" | angle to v1 {ang:.4f}°")
    print("  eigh top eigenvalue:", round(w[-1], 6))

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

eigenvalues [5, 4.5, 2, 1], ratio λ2/λ1 = 0.9
  step  1: λ estimate 4.775344 | error 2.2e-01 | angle to v1 30.3040°
  step  2: λ estimate 4.888443 | error 1.1e-01 | angle to v1 26.2928°
  step  3: λ estimate 4.916856 | error 8.3e-02 | angle to v1 23.7404°
  step  5: λ estimate 4.943883 | error 5.6e-02 | angle to v1 19.5632°
  step 10: λ estimate 4.978917 | error 2.1e-02 | angle to v1 11.8497°
  step 20: λ estimate 4.997338 | error 2.7e-03 | angle to v1 4.1842°
  step 30: λ estimate 4.999675 | error 3.3e-04 | angle to v1 1.4612°
  eigh top eigenvalue: 5.0
eigenvalues [5, 1, 0.5, 0.2], ratio λ2/λ1 = 0.2
  step  1: λ estimate 4.930570 | error 6.9e-02 | angle to v1 7.4884°
  step  2: λ estimate 4.997573 | error 2.4e-03 | angle to v1 1.4078°
  step  3: λ estimate 4.999906 | error 9.4e-05 | angle to v1 0.2772°
  step  5: λ estimate 5.000000 | error 1.5e-07 | angle to v1 0.0110°
  step 10: λ estimate 5.000000 | error 1.7e-14 | angle to v1 0.0000°
  step 20: λ estimate 5.000000 | error 2.7e-15 | angle to v1 0.0000°
  step 30: λ estimate 5.000000 | error 2.7e-15 | angle to v1 0.0000°
  eigh top eigenvalue: 5.0

অনুপাত ০.৯: ৩০ ধাপ পরেও v1 থেকে কোণ ১.৪৬°, eigenvalue-র ভুল ৩.৩ × ১০−৪. হার মিলিয়ে দেখুন: ধাপ ২০ থেকে ৩০-এ tan(কোণ) কমার কথা 0.910 = ০.৩৪৯ গুণ; tan 4.1842° × 0.349 ≈ tan 1.46° ✓. Rayleigh-এর ভুল ২.৭ × ১০−৩ থেকে ৩.৩ × ১০−৪, প্রায় 0.8110 = ০.১২ গুণ: অনুপাতের বর্গ, যেমন তত্ত্ব বলে। অনুপাত ০.২: একই শুরু থেকে ৫ ধাপেই ভুল ১.৫ × ১০−৭, ১০ ধাপে ১.৭ × ১০−১৪, তারপর ২.৭ × ১০−১৫-এ আটকে থাকে: এটি floating-point-এর রাউন্ডিংয়ের মেঝে, এর নিচে নামা যায় না। দুই matrix-এর λ1 আর v1 একই, তবু কাজের পরিমাণে বিশাল পার্থক্য; ঠিক করে দিল শুধু gap. শেষ লাইনে eigh-এর উত্তর ৫.০, power iteration-এর সঙ্গে মেলে।

বাস্তবে কী চলে। শুদ্ধ power iteration শুধু একটি eigenvector দেয়, আর gap ছোট হলে ধীর। বাস্তব সফটওয়্যার এর উন্নত রূপ ব্যবহার করে: বাকিগুলোর জন্য deflation বা একসঙ্গে কয়েকটি vector (subspace iteration); কোনো নির্দিষ্ট মানের কাছের eigenvalue-র জন্য inverse iteration ((A − σI)−1 দিয়ে গুণ); আর বড় sparse matrix-এ Lanczos পদ্ধতি (১৯৫০), যা SciPy-র eigsh ARPACK-এর মাধ্যমে চালায়। ছোট dense matrix-এর সব eigenvalue-র জন্য LAPACK-এর QR-ভিত্তিক routine (John Francis আর Vera Kublanovskaya, ১৯৬১ সালের কাছাকাছি স্বাধীনভাবে)। নিচে একটি ২০০০×২০০০ covariance matrix-এর সবচেয়ে বড় তিনটি eigenvalue তিন পথে, ডেস্কটপে সত্যিকারের run (Python 3.14.4, NumPy 2.5.4, SciPy 1.18.1; সময় একটি ল্যাপটপের একটি run, সাধারণ ফল নয়):

~/bazarghar-ml$ python eig_check.py
…
numpy eigh (all 2000) : [4.719  4.6754 4.6016]  0.54 s
scipy eigh subset (3) : [4.719  4.6754 4.6016]  0.29 s
eigsh Lanczos (3)     : [4.719  4.6754 4.6016]  0.07 s

তিনটির উত্তর এক; সব ২০০০টি eigenvalue না চাইলে scipy.linalg.eigh(..., subset_by_index=...) বা eigsh অনেক কম কাজ করে। Matrix যত বড় আর sparse, পার্থক্য তত বেশি।

৬ · Covariance-এর eigen থেকে PCA — variance সর্বোচ্চ করার derivation

AI-এর ভিত্তি কোর্সের পর্ব ৬৫-এ PCA চালিয়েছেন SVD দিয়ে, আর explained variance মেপেছেন। সেখানে “সবচেয়ে বেশি variance-এর দিক”-এর উত্তর হিসেবে eigenvector এসেছিল। এখানে প্রশ্নটি উল্টো দিক থেকে: কেন সেই দিক eigenvector-ই হবে?

সেটআপ। X একটি n×d ডেটা matrix (n গ্রাহক, d feature). প্রতিটি কলাম থেকে গড় বাদ দিলে পাই centred Xc. তার covariance matrix

Σ = XcTXc / (n − 1)

একটি d×d symmetric matrix: কর্ণে প্রতিটি feature-এর variance, বাইরে প্রতি জোড়ার covariance. এখন একটি unit দিক u (‖u‖ = ১) নিন, আর প্রতিটি গ্রাহককে সেই দিকে ছায়া ফেলুন: z = Xcu, n-টি সংখ্যা। তাদের variance

Var(z) = zTz / (n − 1) = uTXcTXcu / (n − 1) = uTΣu

অর্থাৎ যেকোনো দিকে ছড়ানো = একটি quadratic form. PCA-র প্রথম অক্ষের সমস্যা তাই: ‖u‖ = ১ শর্তে uTΣu সর্বোচ্চ করুন।

প্রমাণ ১ · spectral theorem দিয়ে, calculus ছাড়া

Σ = QΛQT, λ1 ≥ λ2 ≥ … ≥ λd ≥ ০. যেকোনো unit u-কে eigenvector-এর basis-এ লিখুন: u = a1q1 + … + adqd, যেখানে a1² + … + ad² = ১ (Q orthogonal বলে দৈর্ঘ্য অক্ষুণ্ণ)। তাহলে uTΣu = λ1a1² + λ2a2² + … + λdad² ≤ λ1(a1² + … + ad²) = λ1. সমতা ঠিক তখনই, যখন সব ওজন λ1-এর eigenvector-এ (a1 = ±১)। তাই প্রথম principal component = Σ-এর সবচেয়ে বড় eigenvalue-র eigenvector, আর সেই দিকে variance ঠিক λ1.

প্রমাণ ২, Lagrange দিয়ে (পর্ব ১৬-এর হাতিয়ারের প্রথম ঝলক): L(u, λ) = uTΣu − λ(uTu − 1). u-এর সাপেক্ষে gradient 2Σu − 2λu (quadratic form-এর gradient পর্ব ১১-এ derive হবে); শূন্য বসালে Σu = λu. অর্থাৎ শর্তসাপেক্ষ সর্বোচ্চ বা সর্বনিম্নের প্রতিটি সম্ভাব্য বিন্দুই eigenvector, আর সেখানে uTΣu = λuTu = λ, তাই সবচেয়ে বড়টি বেছে নিন। এখানে λ হলো Lagrange multiplier, যা শেষে eigenvalue হয়ে দাঁড়ায়। দ্বিতীয় PC: u ⟂ q1 শর্ত যোগ করলে a1 = ০, আর একই যুক্তিতে সর্বোচ্চ λ2, q2-এ। এভাবে সব PC পরস্পর লম্ব, আর j-তম PC-র ভাগ explained variance ratio = λj / (λ1 + … + λd); হর হলো tr Σ, মোট variance.

Variance along a rotating unit direction
  1. θ = ০°, u = (1, 0). uTΣu = Σ-এর প্রথম কর্ণ ঘর = ২.৫: শুধু প্রথম feature-এর variance. u = (cos θ, sin θ) বসালে সাধারণ সূত্র uTΣu = 2.5cos²θ + 2.5sin²θ + 2·2·cos θ sin θ = 2.5 + 2 sin 2θ.
  2. θ = ৩০°. u = (0.866, 0.5); 2.5 + 2 sin 60° = 2.5 + 1.732 = ৪.২৩২. দুই feature একসঙ্গে বাড়ে (covariance ২ ধনাত্মক), তাই তির্যক দিকে ছড়ানো বেশি।
  3. θ = ৪৫°: সর্বোচ্চ। sin 90° = 1, তাই ২.৫ + ২ = ৪.৫. এটি Σ-এর সবচেয়ে বড় eigenvalue (tr = ৫, det = ৬.২৫ − ৪ = ২.২৫; λ² − 5λ + 2.25 = 0, λ = ৪.৫ আর ০.৫)। মেঘের লম্বা অক্ষ ঠিক এদিকে।
  4. θ = ৯০°. আবার ২.৫: দ্বিতীয় feature-এর variance. লক্ষ করুন, কোনো একক feature-এর দিক সেরা নয়; সেরা দিক দুটির মিশ্রণ।
  5. θ = ১৩৫°: সর্বনিম্ন। sin 270° = −1, তাই ০.৫ = λ2. এই দিক মেঘের সরু অক্ষ, দ্বিতীয় PC. সর্বোচ্চ আর সর্বনিম্ন দুটিই eigenvector-এ, ঠিক Lagrange-এর ফল যেমন বলে।
  6. যাচাই: Σu = λu. θ = ৪৫°-এ u = (0.7071, 0.7071); Σu = (2.5 × 0.7071 + 2 × 0.7071, একই) = (3.182, 3.182) = 4.5 × u. মোট variance ৫-এর ৪.৫/৫ = ৯০% প্রথম PC-তে। এটি AI-এর ভিত্তি কোর্সের পর্ব ৬৫-এর হাতের উদাহরণের একই Σ.

SVD-এর সঙ্গে সম্পর্ক। Xc = USVT হলে Σ = XcTXc/(n − 1) = V(S²/(n − 1))VT. এটি হুবহু spectral decomposition: SVD-এর ডান singular vector = Σ-এর eigenvector, আর λj = sj²/(n − 1). তাহলে কোনটি ব্যবহার করবেন? Covariance বানালে condition number বর্গ হয়ে যায়, তাই খুব ছোট-বড় মেশানো singular value থাকলে SVD বেশি নির্ভুল। আবার n অনেক বড় আর d ছোট হলে d×d covariance-এর eigh অনেক সস্তা। scikit-learn 1.5 থেকে PCA-তে svd_solver="covariance_eigh" যোগ হয়েছে, আর docs অনুযায়ী "auto" নীতিতে feature ১০০০-এর কম আর sample তার ১০ গুণের বেশি হলে এটিই বাছা হয়। অর্থাৎ নিচের উদাহরণে scikit-learn নিজেও ভেতরে covariance-এর eigen-decomposition চালাচ্ছে।

নিচের ব্লকে বাজারঘরের ৫০০ জন কাল্পনিক গ্রাহক, চারটি feature: মাসে অর্ডার, গড় বাস্কেট (৳), শেষ অর্ডারের পর কত দিন (recency), আর রিভিউ সংখ্যা। চারটিই একটি লুকানো “আগ্রহ” থেকে কিছুটা আসে, তাই পরস্পর সম্পর্কিত। এককগুলো আলাদা বলে আগে standardize করা হয়েছে (AI-এর ভিত্তি কোর্সের পর্ব ৬৫-এর scaling-এর ফাঁদ মনে করুন)। তারপর তিন পথে PCA: নিজের eigh, scikit-learn, আর SVD.

import numpy as np
from sklearn.decomposition import PCA
np.set_printoptions(precision=4, suppress=True)
rng = np.random.default_rng(42)                     # কাল্পনিক বাজারঘর: ৫০০ গ্রাহক
n = 500
engage = rng.normal(0, 1, n)                        # লুকানো "আগ্রহ"
orders = 4 + 1.5 * engage + rng.normal(0, 0.8, n)              # মাসে অর্ডার
basket = 900 + 150 * engage + rng.normal(0, 200, n)            # গড় বাস্কেট (৳)
recency = 20 - 6 * engage + rng.normal(0, 5, n)                # শেষ অর্ডারের পর দিন
reviews = 1 + 0.2 * engage + rng.normal(0, 1, n)               # রিভিউ সংখ্যা
X = np.column_stack([orders, basket, recency, reviews])
Z = (X - X.mean(0)) / X.std(0)                      # standardize (একক মুছে ফেলা)
S = np.cov(Z, rowvar=False)                         # Σ, ভাগ n − 1
lam, V = np.linalg.eigh(S)                          # ছোট থেকে বড়
lam, V = lam[::-1], V[:, ::-1]                      # বড় থেকে ছোট
print("eigenvalues of Σ :", lam)
print("explained ratio  :", lam / lam.sum())
pca = PCA().fit(Z)
print("sklearn variance :", pca.explained_variance_)
_, s, Vt = np.linalg.svd(Z - Z.mean(0), full_matrices=False)
print("SVD s²/(n−1)     :", s**2 / (n - 1))
sign = np.sign(np.sum(V * pca.components_.T, 0))   # চিহ্ন মেলানো
print("same directions (signs aside):",
      np.allclose(V * sign, pca.components_.T), np.allclose(np.abs(Vt), np.abs(V.T)))
print("PC1 weights (orders, basket, recency, reviews):", V[:, 0] * sign[0])
v1 = V[:, 0]
print("v1ᵀΣv1 =", round(v1 @ S @ v1, 4), "| var of scores =", round((Z @ v1).var(ddof=1), 4))
for name, u in [("orders only", np.eye(4)[0]), ("random unit", rng.normal(size=4))]:
    u = u / np.linalg.norm(u)
    print(f"uᵀΣu for {name:<11}: {u @ S @ u:.4f}  (≤ λ1 = {lam[0]:.4f})")

আসল আউটপুট (ব্রাউজারের Python, scikit-learn 1.8.0):

eigenvalues of Σ : [2.2007 0.9335 0.5661 0.3077]
explained ratio  : [0.5491 0.2329 0.1413 0.0768]
sklearn variance : [2.2007 0.9335 0.5661 0.3077]
SVD s²/(n−1)     : [2.2007 0.9335 0.5661 0.3077]
same directions (signs aside): True True
PC1 weights (orders, basket, recency, reviews): [ 0.5934  0.5041 -0.5775  0.2457]
v1ᵀΣv1 = 2.2007 | var of scores = 2.2007
uᵀΣu for orders only: 1.0020  (≤ λ1 = 2.2007)
uᵀΣu for random unit: 0.4485  (≤ λ1 = 2.2007)

তিন পথ, এক উত্তর: নিজের eigh, scikit-learn-এর explained_variance_ আর SVD-এর s²/(n − 1) চার দশমিক পর্যন্ত হুবহু এক (২.২০০৭, ০.৯৩৩৫, ০.৫৬৬১, ০.৩০৭৭), আর দুটি True বলছে দিকগুলোও চিহ্ন বাদে মেলে। প্রথম PC মোট variance-এর ৫৪.৯%. PC1-এর ওজন: অর্ডার +০.৫৯, বাস্কেট +০.৫০, recency −০.৫৮, রিভিউ +০.২৫: বেশি অর্ডার, বড় বাস্কেট আর সাম্প্রতিক কেনাকাটা একসঙ্গে, যাকে “সক্রিয় ক্রেতা”-র অক্ষ বলা যায়। তবে নামটি একটি ব্যাখ্যা; আর এই ডেটা একটি লুকানো কারণ থেকে বানানো বলে একটি প্রধান PC আসাই প্রত্যাশিত। রিভিউর ওজন কম, কারণ কোডে তার সঙ্গে আগ্রহের যোগ দুর্বল (০.২)। সূত্রের যাচাই: v1TΣv1 আর score-এর variance দুটিই ২.২০০৭ = λ1. শুধু অর্ডারের দিকে uTΣu = ১.০০২০, ঠিক ১ নয়, কারণ standardize হয়েছে n দিয়ে ভাগ করে আর np.cov ভাগ করে n − 1 দিয়ে: 500/499 = ১.০০২. এলোমেলো একটি দিকে ০.৪৪৮৫. কোনো দিকই λ1 ছাড়ায় না, যেমন প্রমাণ ১ বলে।

৭ · Markov chain-এর stationary distribution — eigenvalue ১-এর eigenvector

AI-এর ভিত্তি কোর্সের পর্ব ৫৩-এ বাজারঘরের ক্রেতার চারটি অবস্থার (Active, Occasional, Dormant, Churned) একটি Markov chain ছিল, আর transition matrix P-এর stationary distribution π (যেখানে πP = π) তিন পথে বের হয়েছিল, একটি পথ eig(P.T). সেখানে যা “কেন” প্রশ্ন বাকি ছিল, এখানে তার উত্তর।

১. eigenvalue ১ কেন সবসময় আছে? P-এর প্রতিটি সারির যোগ ১. তাই সব-১ vector 1-এর জন্য P1 = 1: ১ হলো P-এর eigenvalue, ডান eigenvector 1. আর det(PT − λI) = det((P − λI)T) = det(P − λI), তাই P আর PT-এর eigenvalue একই; PT-এরও eigenvalue ১, আর তার eigenvector-ই πT (πP = π-কে transpose করলে PTπT = πT).

২. কোনো eigenvalue ১-এর চেয়ে বড় নয় কেন? Px-এর প্রতিটি ঘর x-এর ঘরগুলোর একটি ওজনদার গড় (ওজন অঋণাত্মক, যোগ ১), তাই max|(Px)i| ≤ max|xi|. Px = λx হলে |λ| · max|xi| ≤ max|xi|, অর্থাৎ |λ| ≤ ১.

৩. π একটিই আর ধনাত্মক কেন? এর উত্তর Perron (১৯০৭) আর Frobenius (১৯১২)-এর নামে পরিচিত Perron–Frobenius উপপাদ্য: সব ঘর ধনাত্মক এমন matrix-এ সবচেয়ে বড় eigenvalue একক (পুনরাবৃত্ত নয়), বাকি সবার |λ| তার চেয়ে কঠোরভাবে ছোট, আর তার eigenvector-এর সব ঘর ধনাত্মক নেওয়া যায়। AI-এর ভিত্তি কোর্সের পর্ব ৫৩-এর irreducible আর aperiodic শর্ত ঠিক এই নিশ্চয়তা পাওয়ার শর্ত।

৪. কত দ্রুত? অংশ ৩-এর মতোই: শুরুর বণ্টন x0-কে বাঁ eigenvector-এ ভাঙলে xn = x0Pn = π + (λ2n-এর অনুপাতে কমতে থাকা পদ) + …. তাই π থেকে দূরত্ব মোটামুটি |λ2|n হারে কমে, আর 1 − |λ2| হলো chain-এর spectral gap. বাজারঘরের chain-এ |λ2| = ০.৭২৫১: প্রতি মাসে দূরত্ব প্রায় ২৭% কমে। এভাবে দেখলে x ← xP পুনরাবৃত্তিটি আসলে PT-এর ওপর power iteration, যার “সবচেয়ে বড়” eigenvalue ১. PageRank-এর ক্ষেত্রেও একই কথা (AI-এর ভিত্তি কোর্সের পর্ব ৫৩-এ দেখেছেন): damping factor c (প্রায়ই ০.৮৫) যোগ করলে Google matrix-এর দ্বিতীয় eigenvalue-র মান c-এর বেশি হয় না বলে Haveliwala আর Kamvar ২০০৩ সালে দেখান, তাই কোটি পাতার web graph-এও power iteration নিশ্চিতভাবে এগোয়।

নিচের ব্লকের প্রথম অংশ এই চারটি কথা সংখ্যায় যাচাই করে। দ্বিতীয় অংশ পরের অংশের জন্য সতর্কবার্তা: symmetric-এর জন্য বানানো eigh-কে non-symmetric PT দিলে কী হয়, আর প্রায়-symmetric matrix-এ eig কী করে।

import numpy as np
np.set_printoptions(precision=4, suppress=True)

P = np.array([[0.70, 0.20, 0.08, 0.02],      # Active, Occasional, Dormant, Churned
              [0.30, 0.40, 0.25, 0.05],      # সারি = এই মাস, কলাম = পরের মাস
              [0.10, 0.20, 0.50, 0.20],
              [0.10, 0.05, 0.05, 0.80]])
print("P @ ones =", P @ np.ones(4))           # সারির যোগ 1 → eigenvalue 1
w, V = np.linalg.eig(P.T)                     # πP = π  ⇔  Pᵀπᵀ = πᵀ
order = np.argsort(-np.abs(w))
w, V = np.real(w[order]), np.real(V[:, order])
pi = V[:, 0] / V[:, 0].sum()
print("eigenvalues:", w, "| π:", pi)
x = np.array([1.0, 0, 0, 0])                  # Active থেকে শুরু
for n in range(1, 31):
    x = x @ P
    if n in (5, 10, 20, 30):
        print(f"n={n:>2}: ‖x−π‖₁ = {np.abs(x - pi).sum():.2e} | |λ2|^n = {abs(w[1])**n:.2e}")
print("eigh on non-symmetric Pᵀ (wrong!):", np.linalg.eigh(P.T)[0])

S = np.array([[2.0, 1e-9], [-1e-9, 2.0]])     # প্রায় symmetric, eigenvalue জোড়া
we = np.linalg.eig(S)[0]
print("eig :", we.real, "| imag:", [f"{v:.0e}" for v in we.imag], we.dtype)
print("eigh:", np.linalg.eigh(S)[0], np.linalg.eigh(S)[0].dtype)
C = np.array([[4.0, 2.0], [2.0 + 1e-6, 3.0]])  # রাউন্ডিংয়ে সামান্য অসমান
L = np.tril(C) + np.tril(C, -1).T              # নিচের ত্রিভুজ থেকে বানানো
print("eig − eigh:", f"{np.abs(np.sort(np.linalg.eig(C)[0].real) - np.linalg.eigh(C)[0]).max():.1e}",
      "| eigh(C) == eigh(lower):", np.array_equal(np.linalg.eigh(C)[0], np.linalg.eigh(L)[0]))

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

P @ ones = [1. 1. 1. 1.]
eigenvalues: [1.     0.7251 0.4979 0.177 ] | π: [0.3499 0.1998 0.1827 0.2676]
n= 5: ‖x−π‖₁ = 2.00e-01 | |λ2|^n = 2.00e-01
n=10: ‖x−π‖₁ = 4.33e-02 | |λ2|^n = 4.02e-02
n=20: ‖x−π‖₁ = 1.76e-03 | |λ2|^n = 1.61e-03
n=30: ‖x−π‖₁ = 7.09e-05 | |λ2|^n = 6.48e-05
eigh on non-symmetric Pᵀ (wrong!): [0.1615 0.4676 0.7614 1.0095]
eig : [2. 2.] | imag: ['1e-09', '-1e-09'] complex128
eigh: [2. 2.] float64
eig − eigh: 4.9e-07 | eigh(C) == eigh(lower): True

প্রথম লাইন দেখায় P1 = 1, তাই ১ eigenvalue. চারটি eigenvalue ১, ০.৭২৫১, ০.৪৯৭৯, ০.১৭৭; সবই |λ| ≤ ১, আর π = (0.3499, 0.1998, 0.1827, 0.2676), AI-এর ভিত্তি কোর্সের পর্ব ৫৩-এর সংখ্যার সঙ্গে হুবহু এক। এরপর Active থেকে শুরু করে π থেকে দূরত্ব: ৫ মাসে ০.২০, ১০-এ ০.০৪৩, ২০-এ ০.০০১৮, ৩০-এ ৭ × ১০−৫. পাশের কলাম |λ2|n প্রায় একই সংখ্যা দেয়; অনুপাত প্রায় ১.১-এ স্থির, যা শুরুর বিন্দুর ওপর নির্ভর করা একটি ধ্রুবক। ভুল টুল: non-symmetric PT-এ eigh নিঃশব্দে উত্তর দিল, আর তার একটি “eigenvalue” ১.০০৯৫: অংশের শুরুতে প্রমাণ করা |λ| ≤ ১-এর সঙ্গে অসম্ভব। কোনো error বা warning নেই। প্রায়-symmetric: [[2, ε], [−ε, 2]]-এর eigenvalue ঠিক 2 ± iε, তাই ১০−৯-এর একটি অসমতা পুনরাবৃত্ত eigenvalue-কে complex বানিয়ে দেয়, আর dtype complex128; eigh real ২, ২ দেয়। শেষ লাইনে C-এর ওপরের ত্রিভুজে ২ আর নিচে ২ + ১০−৬: eigh-এর ফল নিচের ত্রিভুজ থেকে বানানো matrix-এর ফলের সঙ্গে bit-বাই-bit এক (True), অর্থাৎ ওপরের ঘরটি সে পড়েইনি।

৮ · সংখ্যার সতর্কতা — eig বনাম eigh, ক্রম, চিহ্ন আর version

হাতে eigenvalue বের করা আর কম্পিউটারে বের করার মধ্যে কয়েকটি ফাঁদ আছে, যেগুলো কোনো error দেয় না, শুধু চুপচাপ ভুল বা অপ্রত্যাশিত উত্তর দেয়।

বিষয়np.linalg.eignp.linalg.eigh
কোন matrix-এর জন্যযেকোনো বর্গাকারশুধু symmetric (বা Hermitian); অন্য কিছু দিলে ভুল উত্তর, কোনো সতর্কবার্তা ছাড়া
কোন ঘর পড়েসব ঘরডিফল্টে শুধু নিচের ত্রিভুজ (UPLO="L")
ফলের ক্রমকোনো নিশ্চিত ক্রম নেইছোট থেকে বড় (ascending)
ফলের dtypeNumPy 2.4-এ মান অনুযায়ী real বা complex; NumPy 2.5 থেকে সবসময় complexসবসময় real
Eigenvectorদৈর্ঘ্য ১, কিন্তু পরস্পর লম্ব হওয়ার নিশ্চয়তা নেইorthonormal, QTQ = I
গতি আর স্থিরতাধীর; non-symmetric-এ eigenvalue খুব সংবেদনশীল হতে পারেদ্রুত; ছোট perturbation-এ eigenvalue-ও ছোট সরে

NumPy 2.5-এর পরিবর্তন। NumPy 2.5.0 (জুন ২০২৬)-এর release notes-এ compatibility note: linalg.eig আর linalg.eigvals এখন সবসময় complex array দেয়, আগে ফলের dtype মানের ওপর নির্ভর করত (gh-30411); notes নিজেই symmetric matrix-এর জন্য eigh/eigvalsh-এর পরামর্শ দেয়। এই পাতার ব্রাউজার-Python-এ NumPy 2.4.6, তাই অংশ ২-এর ব্লকে dtype float64 এসেছে। ডেস্কটপে একই A, সত্যিকারের run:

~/bazarghar-ml$ python eig_check.py
Python 3.14.4 | NumPy 2.5.4 | SciPy 1.18.1
eig(A): [5.+0.j 2.+0.j] complex128
eigvalsh(A @ A.T) dtype: float64
…

পুরোনো কোডে w.dtype == float ধরে নেওয়া থাকলে, বা ফল সরাসরি pandas-এ বা JSON-এ লেখা হলে, NumPy আপগ্রেডের পর তা ভাঙতে পারে। নিরাপদ অভ্যাস: symmetric হলে eigh; না হলে imag অংশ সত্যিই শূন্যের কাছে কি না যাচাই করে তারপর np.real, যেমন অংশ ২ আর ৭-এর কোডে।

Non-symmetric matrix-এর সংবেদনশীলতা। J = [[1, 1], [ε, 1]] নিন। tr = ২, det = 1 − ε, তাই λ = 1 ± √ε. ε = 10−10, প্রায় রাউন্ডিংয়ের মাপের একটি বদল, eigenvalue-কে 10−5 সরিয়ে দেয়: পরিবর্তনের চেয়ে লক্ষ গুণ বড় ফল। Symmetric matrix-এ এমন হয় না: Weyl-এর অসমতা অনুযায়ী E যোগ করলে প্রতিটি eigenvalue সর্বোচ্চ ‖E‖ (spectral norm) সরে। অংশ ৭-এর শেষ লাইনে C-এর অসমতা ১০−৬, আর eig ও eigh-এর পার্থক্য তারও কম।

চিহ্ন আর পুনরাবৃত্ত eigenvalue: v আর −v দুটিই বৈধ উত্তর, তাই দুটি লাইব্রেরি বা দুটি version আলাদা চিহ্ন দিতে পারে; PCA-র loading-এ “recency-র ওজন ঋণাত্মক” এমন ব্যাখ্যা দেওয়ার আগে চিহ্ন একটি নিয়মে বেঁধে নিন (যেমন সবচেয়ে বড় |ওজন| ধনাত্মক)। আর λ1 ≈ λ2 হলে সেই দুটি eigenvector আলাদাভাবে প্রায় অর্থহীন: তাদের span স্থির, কিন্তু span-এর ভেতরের দিক ডেটার সামান্য বদলেই ঘুরে যায় (Davis–Kahan). ভাবনার প্রশ্ন ৫-এ এর ব্যবহারিক প্রভাব।

Covariance নিজেই একটু অসমান হলে। np.cov বা XTX থেকে বানানো matrix সাধারণত symmetric, কিন্তু নিজে লেখা কোডে (যেমন দুটি আলাদা গণনা থেকে উপরের আর নিচের ত্রিভুজ) রাউন্ডিংয়ের অসমতা ঢুকতে পারে। তখন (C + C.T) / 2 দিয়ে symmetric করে নিয়ে eigh চালান, আর ডেটা থেকে PCA দরকার হলে সরাসরি SVD বা scikit-learn ব্যবহার করুন।

৯ · AI-এর যুগে — eigen আর PCA-র কোড ও derivation যাচাই

AI assistant linear algebra-র derivation খুব গুছিয়ে লেখে, আর চিহ্নগুলো নিখুঁত দেখায়। কিন্তু eigen-এর কোডে তার ভুলগুলো প্রায়ই নীরব: কোনো error নেই, শুধু ভুল দিক।

AI প্রায়ই যা করেকেন ভুলকীভাবে ধরবেন
Covariance-এ eig চালিয়ে V[:, 0]-কে PC1 বলেeig-এর ক্রম নিশ্চিত নয়; eigh হলে প্রথমটি সবচেয়ে ছোটeigenvalue ছাপিয়ে দেখুন; বড় থেকে ছোটে সাজান
Stationary distribution-এ eig(P) লেখে, P.T নয়ডান eigenvector সব-১ vector দেয়, π নয়π-কে P দিয়ে গুণ করে πP = π মিলিয়ে দেখুন
Non-symmetric matrix-এ eighনিচের ত্রিভুজ পড়ে ভুল উত্তর দেয়, কোনো সতর্কবার্তা ছাড়াnp.allclose(A, A.T) আগে যাচাই
Standardize না করে PCA, বা পুরো ডেটায় scaler fit করে তারপর splitবড় এককের feature PC1 দখল করে; test ডেটার তথ্য leak হয়Pipeline-এ scaler + PCA, শুধু train-এ fit
PC-র loading-এর চিহ্ন বা “PC1 মানে আগ্রহ”-কে নিশ্চিত সত্য বলেচিহ্ন ইচ্ছামতো; নাম দেওয়া একটি ব্যাখ্যা, প্রমাণ নয়চিহ্ন বেঁধে নিন; bootstrap-এ loading কতটা নড়ে দেখুন
Derivation-এ “uTΣu-এর gradient Σu” লেখেSymmetric Σ-এ gradient 2Σu; সাধারণ A-তে (A + AT)uছোট উদাহরণে numerical gradient দিয়ে মিলিয়ে নিন (পর্ব ১১)

শুধু কোড নয়, যাচাইয়ের পরীক্ষাও চেয়ে নিন; শেষ দুটি লাইন সবচেয়ে জরুরি।

আমার কাছে ১২টি সংখ্যাসূচক feature-এর একটি গ্রাহক টেবিল আছে (n ≈ ২০,০০০)। আমি PCA বুঝতে চাই, শুধু চালাতে নয়। ১. NumPy দিয়ে covariance-এর eigen-decomposition থেকে PCA লেখো: standardize, np.cov, np.linalg.eigh, বড় থেকে ছোটে সাজানো, explained variance ratio. ২. একই ডেটায় scikit-learn PCA আর np.linalg.svd-এর সঙ্গে মেলানোর কোড দাও, চিহ্নের পার্থক্য সামলে। ৩. কেন প্রথম PC = সবচেয়ে বড় eigenvalue-র eigenvector, তা ধাপে ধাপে derive করো, প্রতিটি চিহ্নের মানে লিখে। ৪. আমার NumPy version-এ np.linalg.eig আর eigh-এর ফলের dtype আর ক্রম কী, তা অফিসিয়াল docs-এর লিংকসহ বলো। ৫. scaler কোথায় fit করতে হবে যাতে test ডেটা leak না হয়, তা দেখাও। ৬. তোমার উত্তরের কোন অংশে ভুল হওয়ার সম্ভাবনা বেশি, আর আমি কোন ছোট পরীক্ষায় তা ধরব, সেটিও বলো।

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

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

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

প্র ০১PCA বলে “সবচেয়ে বেশি variance মানে সবচেয়ে বেশি তথ্য”। এই অনুমান কখন ভেঙে পড়ে?

PCA-র ইতিহাস দুটি প্রশ্ন থেকে এসেছে। Karl Pearson ১৯০১ সালে বিন্দুর মেঘের সবচেয়ে কাছের রেখা বা সমতল খুঁজেছিলেন (লম্ব দূরত্বের বর্গের যোগ সর্বনিম্ন), আর Harold Hotelling ১৯৩৩ সালে পরস্পর-সম্পর্কিত পরীক্ষার নম্বরের পেছনের অল্প কয়েকটি “principal component” খুঁজেছিলেন, যেখান থেকে নামটি এসেছে। দুই প্রশ্নের উত্তর একই: covariance-এর বড় eigenvector. কিন্তু দুটিতেই “তথ্য” মানে ছড়ানো, কোনো লক্ষ্য (label) নয়।

প্রথম ভাঙন: এককের ওপর নির্ভরতা। PCA scale-invariant নয়। বাজারঘরের ডেটায় বাস্কেট টাকায় (σ শ’খানেক) আর অর্ডার সংখ্যায় (σ দেড়-দুই) রাখলে PC1 প্রায় পুরোটাই বাস্কেট, কারণ সংখ্যাটি বড়, তথ্য বেশি বলে নয়। Standardize করা মানে correlation matrix-এর eigen নেওয়া, আর সেটিও একটি সিদ্ধান্ত: সব feature-কে সমান গুরুত্ব দেওয়া।

দ্বিতীয় ভাঙন: label-এর সঙ্গে সম্পর্কহীনতা। ধরুন churn আলাদা করে এমন সংকেতটি একটি ছোট variance-এর দিকে, যেমন রিভিউয়ের ছোট পরিবর্তন, আর বড় variance-এর দিক মৌসুমি কেনাকাটা। প্রথম দুটি PC রেখে বাকিগুলো ফেলে দিলে সংকেতটিই হারিয়ে যায়। এজন্য supervised কাজে PCA একটি hyperparameter, cross-validation দিয়ে বিচার করার মতো, সব সময় লাভজনক নয়। Label ব্যবহার করে দিক খোঁজে এমন পদ্ধতি (যেমন LDA বা partial least squares) এখানে ভিন্ন উত্তর দেয়।

তৃতীয় ভাঙন: রৈখিকতা আর outlier. ডেটা যদি একটি বাঁকা পৃষ্ঠে থাকে, সরল অক্ষ তা ধরতে পারে না; kernel PCA, ICA আর manifold পদ্ধতি পর্ব ৪১ আর ৪২-এ। আর variance বর্গ নেয়, তাই কয়েকটি বড় outlier একাই একটি PC বানিয়ে ফেলতে পারে; robust PCA-র রূপ এর জন্যই।

মূল উপলব্ধি: PCA একটি নির্দিষ্ট প্রশ্নের নিখুঁত উত্তর (“কোন লম্ব অক্ষে ছড়ানো সবচেয়ে বেশি”), আর সেই প্রশ্ন আপনার প্রশ্ন কি না, সেটি আলাদা করে যাচাই করতে হয়।

প্র ০২কোটি কোটি পাতার web graph-এ PageRank বের করতে পুরো eigen-decomposition না করে power iteration কেন?

Sergey Brin আর Lawrence Page ১৯৯৮ সালে প্রকাশিত Google-এর প্রথম দিকের প্রবন্ধে PageRank বর্ণনা করেন: একজন random surfer link ধরে ঘোরেন, আর মাঝে মাঝে (সম্ভাবনা 1 − c) যেকোনো পাতায় লাফ দেন। প্রতিটি পাতার গুরুত্ব হলো এই Markov chain-এর stationary distribution, অর্থাৎ একটি বিশাল matrix-এর eigenvalue ১-এর eigenvector.

N পাতার জন্য পুরো eigen-decomposition-এর খরচ মোটামুটি N³-এর অনুপাতে, আর dense N×N matrix রাখার জায়গা N². N যদি কোটির ঘরে হয়, এটি কোনো যন্ত্রেই সম্ভব নয়। কিন্তু আমাদের তো সব eigenvector লাগে না, একটিই লাগে, আর সেটি সবচেয়ে বড় eigenvalue-র। তার ওপর link matrix অতি sparse: প্রতিটি পাতায় গড়ে কয়েকটি থেকে কয়েক ডজন link. তাই x ← xP-এর এক ধাপের খরচ link-এর সংখ্যার অনুপাতে, যা বিতরণ করে অনেক যন্ত্রে চালানো যায়।

কত ধাপ লাগবে, তা ঠিক করে দ্বিতীয় eigenvalue. Damping factor c যোগ করার একটি বড় সুবিধা: Haveliwala আর Kamvar ২০০৩ সালে দেখান যে Google matrix-এর দ্বিতীয় eigenvalue-র মান c-এর বেশি নয়। c = ০.৮৫ হলে প্রতি ধাপে ভুল অন্তত ০.৮৫ গুণ কমে; ৫০ ধাপে 0.8550 ≈ ৩ × ১০−৪, ১০০ ধাপে প্রায় ৯ × ১০−৮. ভুল কত থাকলে যথেষ্ট, সেটি ব্যবহারের ওপর নির্ভর করে; র‍্যাংকিংয়ের ক্রম ঠিক হলেই অনেক সময় চলে। c কমালে convergence দ্রুত, কিন্তু র‍্যাংকিং link-এর গঠন কম প্রতিফলিত করে: সংখ্যাগত সুবিধা আর মডেলের অর্থের মধ্যে একটি trade-off.

এখনকার সার্চ ইঞ্জিন শুধু PageRank-এর ওপর চলে না, অনেক সংকেত মেশায়; কোন কোম্পানি ঠিক কী ব্যবহার করে তা প্রকাশ্য নয়। কিন্তু “বড় sparse matrix-এর কয়েকটি eigenvector, পুনরাবৃত্তিমূলক পদ্ধতিতে” এই ধাঁচ graph-ভিত্তিক recommendation, spectral clustering আর বড় PCA-তে আজও কেন্দ্রীয়।

মূল উপলব্ধি: বড় সমস্যায় প্রশ্নটা “সব eigenvector” নয়, “যে কয়েকটি দরকার, সেগুলো sparse গুণ দিয়ে”। আর কত দ্রুত পাবেন, সেটি ঠিক করে eigenvalue-র ফাঁক।

প্র ০৩পাঠ্যবইয়ে eigenvalue মানে characteristic polynomial-এর মূল। তাহলে সফটওয়্যার কেন বহুপদী সমাধান করে না?

তিনটি কারণ, একটি গাণিতিক আর দুটি সংখ্যাগত। গাণিতিক কারণ: Abel–Ruffini উপপাদ্য (উনিশ শতকের শুরু) অনুযায়ী পাঁচ বা তার বেশি ঘাতের সাধারণ বহুপদীর মূল যোগ, বিয়োগ, গুণ, ভাগ আর বর্গমূল-ঘনমূলের মতো radical দিয়ে কোনো সাধারণ সূত্রে লেখা যায় না। আর যেকোনো বহুপদী কোনো না কোনো matrix-এর (companion matrix) characteristic polynomial, তাই ৫×৫ বা বড় matrix-এর eigenvalue-র জন্যও সাধারণ বন্ধ সূত্র নেই। সব eigen-solver তাই পুনরাবৃত্তিমূলক।

প্রথম সংখ্যাগত কারণ: বহুপদীর মূল সহগের প্রতি ভয়ংকর সংবেদনশীল হতে পারে। James Wilkinson-এর বিখ্যাত উদাহরণে (১৯৫০-এর দশকের শেষ থেকে আলোচিত) (x − 1)(x − 2)…(x − 20) গুণ করে সহগে লিখলে x19-এর সহগে প্রায় 2−23-এর একটি ক্ষুদ্র পরিবর্তনেই কয়েকটি মূল বাস্তব থেকে সরে জটিল সংখ্যা হয়ে যায়। অথচ মূল matrix (কর্ণে ১ থেকে ২০) একদম নিরীহ। অর্থাৎ matrix থেকে বহুপদীতে যাওয়ার ধাপটিই তথ্য নষ্ট করে।

দ্বিতীয় সংখ্যাগত কারণ: n×n determinant বিস্তার করে বহুপদী বানানোই ব্যয়বহুল আর অস্থির। তার বদলে ১৯৬১ সালের কাছাকাছি John Francis আর Vera Kublanovskaya স্বাধীনভাবে QR algorithm দেন: matrix-কে বারবার QR-এ ভেঙে উল্টো ক্রমে গুণ (A ← RQ), যাতে eigenvalue বদলায় না কিন্তু matrix ক্রমে ত্রিভুজাকার হয় আর কর্ণে eigenvalue ফুটে ওঠে। Shift আর Hessenberg রূপান্তরের মতো কৌশলে এটি দ্রুত, আর আজকের LAPACK-এর geev-এর মতো routine এরই উত্তরসূরি। NumPy-র np.roots যে companion matrix-এর eigenvalue দিয়ে মূল বের করে, তা এই উল্টো সম্পর্কেরই প্রমাণ।

তাহলে characteristic polynomial-এর কাজ কী? প্রমাণ (trace = যোগ, det = গুণ), ছোট matrix হাতে, আর তত্ত্বে ধারণা গড়া। পরীক্ষার খাতায় 2×2 বা 3×3-এ এটিই সঠিক হাতিয়ার; কম্পিউটারে নয়।

মূল উপলব্ধি: গণিতে সমতুল্য দুটি পথ সংখ্যায় সমতুল্য নয়। ভালো numerical algorithm সমস্যাটিকে এমন রূপে রাখে যেখানে রাউন্ডিং কম ক্ষতি করে।

প্র ০৪Recurrent network বা যেকোনো পুনরাবৃত্ত গুণে “spectral radius < ১ হলে স্থির” বলা হয়। শুধু eigenvalue দেখে কি নিশ্চিত হওয়া যায়?

পুরোপুরি নয়। Spectral radius ρ(A) হলো সবচেয়ে বড় |λ|. অংশ ৩-এর Ak = VΛkV−1 থেকে বোঝা যায় k → ∞-এ Akx শূন্যে যায় যদি ρ < ১, আর ফুলে ওঠে যদি ρ > ১. Recurrent neural network-এ weight matrix বারবার গুণ হয়, তাই এই চিন্তা থেকে vanishing আর exploding gradient-এর আলোচনা আসে; পুনরাবৃত্ত গুণের এই সমস্যা নিয়ে ১৯৯০-এর দশকের শুরুতে Hochreiter আর Bengio ও সহকর্মীরা বিশ্লেষণ করেন, আর ২০১৩ সালে Pascanu, Mikolov আর Bengio এটিকে আরও স্পষ্ট করেন। বিস্তারিত পরে Deep Learning কোর্সে।

কিন্তু “শেষ পর্যন্ত” আর “পথে” আলাদা। Non-symmetric matrix-এ V-এর কলামগুলো প্রায় সমান্তরাল হতে পারে, তখন V−1-এর মান বিশাল। ফলে ρ < ১ হলেও প্রথম কয়েকটি ধাপে ‖Akx‖ অনেক বড় হতে পারে, তারপর নামে। একে transient growth বলে, আর এমন matrix-কে non-normal বলে (ATA ≠ AAT). উদাহরণ: A = [[0.9, 10], [0, 0.9]]; দুটি eigenvalue ০.৯, কিন্তু A(0, 1) = (10, 0.9): প্রথম ধাপেই দৈর্ঘ্য প্রায় দশগুণ। Lloyd Trefethen আর Mark Embree-র “Spectra and Pseudospectra” বই এই ঘটনার পূর্ণ বিশ্লেষণ, যেখানে eigenvalue-র বদলে pseudospectrum দেখার পরামর্শ দেওয়া হয়।

অংশ ৮-এর J = [[1, 1], [ε, 1]]-এর eigenvalue-র অতি সংবেদনশীলতাও একই রোগের অন্য লক্ষণ। Symmetric (বা আরও সাধারণভাবে normal) matrix-এ এসব হয় না: সেখানে ‖Ak‖ = ρk হুবহু, আর eigenvalue ছোট perturbation-এ ছোটই সরে। এজন্য ML-এর তত্ত্বে symmetric matrix এত প্রিয়।

ব্যবহারিক পরামর্শ: স্থিরতা বিচার করতে eigenvalue-র সঙ্গে singular value-ও দেখুন (‖A‖2 = সবচেয়ে বড় singular value, পর্ব ০৯); ‖A‖2 < ১ হলে প্রতিটি ধাপেই দৈর্ঘ্য কমে, যা ρ < ১-এর চেয়ে শক্ত নিশ্চয়তা।

মূল উপলব্ধি: eigenvalue দীর্ঘমেয়াদি আচরণ বলে; কাছাকাছি ধাপের আচরণ আর সংবেদনশীলতা বুঝতে eigenvector-এর জ্যামিতি আর singular value-ও লাগে।

প্র ০৫বাজারঘরের দুই মাসের ডেটায় PCA চালিয়ে দেখা গেল PC2 আর PC3-এর loading পুরো বদলে গেছে। এটি কি গ্রাহক আচরণের পরিবর্তন, না অন্য কিছু?

প্রথমে সন্দেহ করুন সংখ্যাগত অস্থিরতা। যদি λ2 আর λ3 কাছাকাছি হয় (ধরুন ০.৯৫ আর ০.৯০), তাহলে তাদের eigenvector আলাদাভাবে প্রায় নির্ধারিত নয়। Davis আর Kahan-এর ১৯৭০ সালের উপপাদ্যের মূল কথা: covariance-এ E মাপের পরিবর্তন হলে eigenvector-এর কোণের পরিবর্তন মোটামুটি ‖E‖ ÷ (eigenvalue-র ফাঁক)-এর সমানুপাতিক। ফাঁক ০.০৫ হলে sampling noise-এর ছোট পরিবর্তনেও PC2 আর PC3 নিজেদের span-এর ভেতরে অনেকটা ঘুরে যেতে পারে। কিন্তু দুটি মিলে যে সমতল, সেটি তুলনামূলক স্থির থাকে।

দ্বিতীয় সন্দেহ: চিহ্ন। দুই run-এ একই দিক v আর −v আসতে পারে, তখন সব loading উল্টো দেখায় অথচ কিছুই বদলায়নি। Loading তুলনা করার আগে চিহ্ন একটি নিয়মে বাঁধুন, বা |cos| দিয়ে দিকের মিল মাপুন।

তৃতীয়: preprocessing. দুই মাসে আলাদা গড় আর standard deviation দিয়ে standardize করলে Σ নিজেই বদলায়; ঈদের মাসে বাস্কেটের σ বেড়ে গেলে standardize-এর পরে তার ওজন কমবে। Preprocessing একই রাখুন, বা প্রথম মাসের scaler-ই দ্বিতীয় মাসে প্রয়োগ করুন।

এসব বাদ দেওয়ার পরও বদল থাকলে তবেই আচরণের পরিবর্তনের কথা ভাবুন। যাচাইয়ের একটি সৎ পদ্ধতি: প্রতিটি মাসের ভেতরেই bootstrap (পর্ব ২১) করে loading কতটা নড়ে দেখুন। দুই মাসের পার্থক্য যদি এক মাসের ভেতরের bootstrap ছড়ানোর চেয়ে স্পষ্টভাবে বড় হয়, আর eigenvalue-র ফাঁক যথেষ্ট, তাহলে পরিবর্তনটি সম্ভবত আসল। ডেটার বণ্টন সময়ের সঙ্গে বদলানো নিয়ে বিস্তারিত পর্ব ৫৯-এ।

রিপোর্টে কাছাকাছি eigenvalue-র PC আলাদা আলাদা নাম দিয়ে (“PC2 = দামের সংবেদনশীলতা”) ব্যাখ্যা না করাই নিরাপদ; বরং বলুন “PC2–PC3 সমতলে এই feature-গুলোর প্রভাব”।

মূল উপলব্ধি: একটি eigenvector-এর বিশ্বাসযোগ্যতা নির্ভর করে তার eigenvalue অন্যদের থেকে কতটা আলাদা তার ওপর। ফাঁক না দেখে loading-এর গল্প বলবেন না।

অনুশীলন

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

  1. হাতে 2×2 (numerical): B = [[3, 2], [2, 0]]. (ক) characteristic polynomial আর eigenvalue বের করুন, trace আর determinant দিয়ে যাচাই করুন। (খ) প্রতিটির eigenvector বের করে দেখান যে তারা লম্ব। (গ) diagonalisation ব্যবহার করে B3(3, −1) বের করুন, সরাসরি গুণ না করে।

    (ক) tr B = ৩, det B = 0 − 4 = −৪. λ² − 3λ − 4 = 0, অর্থাৎ (λ − 4)(λ + 1) = 0: λ = ৪ আর −১. যাচাই: ৪ + (−১) = ৩ ✓, ৪ × (−১) = −৪ ✓.

    (খ) λ = ৪: B − 4I = [[−1, 2], [2, −4]], −x + 2y = 0, v1 = (2, 1). λ = −১: B + I = [[4, 2], [2, 1]], 2x + y = 0, v2 = (1, −2). Dot product 2·1 + 1·(−2) = ০, লম্ব ✓ (B symmetric, তাই প্রত্যাশিত)।

    (গ) (3, −1) = (2, 1) + (1, −2), অর্থাৎ দুই eigenvector-এর সহগ ১ আর ১. তাই B3(3, −1) = 4³(2, 1) + (−1)³(1, −2) = (128, 64) + (−1, 2) = (127, 66). সরাসরি যাচাই: B(3, −1) = (7, 6), B(7, 6) = (33, 14), B(33, 14) = (127, 66) ✓. লক্ষ করুন, ঋণাত্মক eigenvalue বিজোড় ঘাতে দিক উল্টায়।

  2. সমালোচনা ও নকশা: বাজারঘরের একজন বিশ্লেষক টাকা, দিন আর সংখ্যায় মাপা ছয়টি কাঁচা feature-এর ওপর এই কোড চালিয়ে রিপোর্ট করলেন “PC1 হলো বাস্কেটের মূল্য, আর recency-র ওজন ঋণাত্মক, তাই নিয়মিত ক্রেতারা বেশি খরচ করেন”: w, V = np.linalg.eig(np.cov(X.T)); pc1 = V[:, 0]. কোডে আর সিদ্ধান্তে কী কী সমস্যা, আর আপনি কীভাবে নতুন করে করবেন?

    কোডের সমস্যা: (১) eig-এর ক্রম নিশ্চিত নয়, তাই V[:, 0] সবচেয়ে বড় eigenvalue-র হবে এমন নিশ্চয়তা নেই। (২) Covariance symmetric, তাই eigh দ্রুততর, real আর orthonormal ফল দেয়; NumPy 2.5 থেকে eig complex array দেয়, পরের কোডে ঝামেলা হতে পারে। (৩) Standardize করা হয়নি: টাকার σ শ’য়ের ঘরে, সংখ্যার σ এককের ঘরে, তাই PC1 প্রায় নিশ্চিতভাবে বাস্কেট, কারণ এককটি বড়।

    সিদ্ধান্তের সমস্যা: (৪) Eigenvector-এর চিহ্ন ইচ্ছামতো; “ঋণাত্মক ওজন” থেকে দিক নিয়ে দাবি করা যায় না, শুধু PC-র ভেতরে feature-গুলোর আপেক্ষিক চিহ্ন অর্থপূর্ণ। (৫) PCA সম্পর্ক দেখায়, কারণ নয়; “নিয়মিত ক্রেতারা বেশি খরচ করেন” একটি correlation-এর বিবৃতি মাত্র।

    নতুন নকশা: train ভাগে StandardScaler + PCA একটি pipeline-এ fit; eigenvalue বড় থেকে ছোটে আর explained variance ratio দেখান; চিহ্ন একটি নিয়মে বাঁধুন; bootstrap দিয়ে loading-এর স্থিরতা দেখান; PC1 আর PC2-এর eigenvalue কাছাকাছি হলে আলাদা নাম না দেওয়া; আর আচরণগত দাবির জন্য সরাসরি দুই variable-এর সম্পর্ক বা পরীক্ষা।

  3. আউটপুট অনুমান করুন: চালানোর আগে লিখে ফেলুন নিচের কোড কী ছাপবে।
    import numpy as np
    A = np.array([[2.0, 0.0], [0.0, 3.0]])
    w, V = np.linalg.eigh(A)
    print(w)
    print(V)
    x = np.array([1.0, 1.0])
    for _ in range(3):
        x = A @ x
    print(x, x / np.linalg.norm(x))
    [2. 3.]
    [[1. 0.]
     [0. 1.]]
    [ 8. 27.] [0.28408833 0.95879811]

    Diagonal matrix-এর eigenvalue কর্ণের সংখ্যাই, আর eigh সেগুলো ছোট থেকে বড় দেয়: ২, ৩. Eigenvector অক্ষ দুটি, তাই V = identity (ডেস্কটপ NumPy 2.5.4-এ চালিয়ে মেলানো; চিহ্ন তাত্ত্বিকভাবে উল্টোও হতে পারত)। তিনবার গুণে x = (2³, 3³) = (8, 27); দৈর্ঘ্য √(64 + 729) = √793 ≈ 28.16, তাই (0.2841, 0.9588). তিন ধাপেই x অনেকটা λ = ৩-এর দিকে (0, 1) ঝুঁকেছে; অনুপাত (2/3)³ = 8/27, ঠিক power iteration-এর হার।

  4. Markov chain হাতে: বাজারঘরের অ্যাপে একজন ক্রেতা প্রতি সপ্তাহে Active (A) বা Inactive (I). P = [[0.9, 0.1], [0.5, 0.5]] (সারি = এই সপ্তাহ)। (ক) P-এর দুটি eigenvalue, (খ) stationary distribution π, (গ) প্রতি সপ্তাহে π থেকে দূরত্ব কত গুণ কমে, আর ভুল ১%-এর নিচে নামতে মোটামুটি কত সপ্তাহ লাগবে, যদি শুরুতে দূরত্ব প্রায় ১ হয়?

    (ক) সারির যোগ ১, তাই একটি eigenvalue ১. trace = ১.৪, তাই অন্যটি ১.৪ − ১ = ০.৪ (যাচাই: det = 0.45 − 0.05 = 0.4 = ১ × ০.৪ ✓)।

    (খ) πP = π: πA = 0.9πA + 0.5πI, অর্থাৎ 0.1πA = 0.5πI, πA = 5πI. যোগ ১ ধরে π = (5/6, 1/6) ≈ (0.833, 0.167).

    (গ) দূরত্ব প্রতি সপ্তাহে |λ2| = ০.৪ গুণ হয়। 0.4n < 0.01 চাইলে n > ln 0.01 / ln 0.4 = 4.605 / 0.916 ≈ ৫.০৩, তাই প্রায় ৬ সপ্তাহ. Spectral gap ০.৬ বড় বলে chain দ্রুত স্থির হয়।

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

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

ক্রমEigen-decomposition দিয়ে PCA করার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Train ডেটার প্রতিটি কলাম centre আর standardize করা
  2. Covariance matrix Σ = XcTXc / (n − 1) বানানো
  3. np.linalg.eigh(Σ) চালানো
  4. Eigenvalue বড় থেকে ছোটে সাজিয়ে eigenvector-এর কলামও একই ক্রমে আনা
  5. প্রথম q-টি eigenvector-এ ডেটা project করে score বের করা, আর explained variance ratio দেখা

Matrix A = [[5, 2], [2, 2]]-এর eigenvalue দুটি কত?

  • ৫ আর ২
  • ৪ আর ৩
  • ৬ আর ১
  • ৩ আর ২

একটি বাস্তব symmetric matrix-এর দুটি আলাদা eigenvalue-র eigenvector সম্পর্কে কোনটি সবসময় সত্য?

  • তাদের দৈর্ঘ্য সমান
  • তারা পরস্পর লম্ব (dot product শূন্য)
  • তারা সমান্তরাল
  • তাদের অন্তত একটি জটিল সংখ্যার

Power iteration একটি symmetric matrix-এ প্রতি ধাপে eigenvector-এর দিকের ভুল মোটামুটি কোন হারে কমায়? (eigenvalue |λ1| > |λ2| ≥ …)

  • 1/λ1 গুণ
  • |λn/λ1| গুণ, যেখানে λn সবচেয়ে ছোট
  • প্রতি ধাপে একটি নির্দিষ্ট পরিমাণ, যেমন ০.০১ করে
  • |λ2/λ1| গুণ

Centred ডেটার covariance matrix Σ, আর একটি unit vector u. ডেটাকে u-এর দিকে project করলে score-এর variance কত, আর কোন u-তে তা সবচেয়ে বড়?

  • uTΣu; Σ-এর সবচেয়ে বড় eigenvalue-র eigenvector-এ, মান λ1
  • ‖Σu‖; সবচেয়ে ছোট eigenvalue-র eigenvector-এ
  • tr Σ; যেকোনো u-তে সমান
  • Σ-এর সবচেয়ে বড় কর্ণ ঘর; সেই feature-এর অক্ষে

একটি Markov chain-এর transition matrix P (প্রতিটি সারির যোগ ১, π P = π)। NumPy দিয়ে stationary distribution π বের করার সঠিক পথ কোনটি?

  • np.linalg.eig(P)-এর eigenvalue ১-এর eigenvector
  • np.linalg.eigh(P.T)-এর সবচেয়ে বড় eigenvector
  • np.linalg.eig(P.T)-এর eigenvalue ১-এর eigenvector, যোগফল ১ করে
  • np.linalg.svd(P)-এর প্রথম singular vector

Non-symmetric একটি matrix-এ np.linalg.eigh চালালে সাধারণত কী হয়?

  • একটি error দেয় যে matrix symmetric নয়
  • নীরবে নিচের ত্রিভুজ থেকে বানানো symmetric matrix-এর eigenvalue দেয়, যা ভুল উত্তর
  • সঠিক complex eigenvalue দেয়
  • স্বয়ংক্রিয়ভাবে eig চালিয়ে সঠিক উত্তর দেয়

শব্দকোষ

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

মনে রাখুন

  1. Av = λv: eigenvector এমন দিক যা A শুধু টানে বা উল্টায়; দিক গুরুত্বপূর্ণ, দৈর্ঘ্য আর চিহ্ন নয়।
  2. 2×2-এ λ² − (tr A)λ + det A = 0; যোগ = trace, গুণ = determinant. সফটওয়্যার বহুপদী নয়, QR-ধাঁচের পুনরাবৃত্তি চালায়।
  3. A = VΛV−1 হলে Ak = VΛkV−1; symmetric হলে A = QΛQT, বাস্তব eigenvalue আর লম্ব eigenvector.
  4. Power iteration সবচেয়ে বড় eigenvector দেয়, ভুল কমে |λ2/λ1|k হারে; Markov chain-এর x ← xP একই জিনিস, eigenvalue ১-এ।
  5. PCA = ‖u‖ = ১ শর্তে uTΣu সর্বোচ্চ = Σ-এর বড় eigenvector; symmetric-এ সবসময় eigh, ক্রম আর চিহ্ন নিজে সামলান।

পরের পর্বে: পর্ব ০৯ (SVD — low-rank approximation, pseudo-inverse আর recommender-এর ভিত্তি) eigen-decomposition-কে যেকোনো আকারের matrix-এ নিয়ে যাবে: ঘোরানো, টানা, আবার ঘোরানো; আর দেখাবে কেন ছোট singular value ফেলে দিলে একটি rating matrix-এর সবচেয়ে ভালো সংক্ষিপ্ত রূপ মেলে।

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

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