এই পর্বে যা শিখবেন
- SVD-এর জ্যামিতিক ছবি: একক বৃত্ত কীভাবে Vᵀ, Σ আর U পেরিয়ে ellipse হয়
- AᵀA আর AAᵀ-এর eigen থেকে SVD বের করা, হাতে একটি 2×2 উদাহরণসহ, আর NumPy দিয়ে যাচাই
- Singular value, rank, spectral norm আর Frobenius norm-এর সম্পর্ক; Eckart–Young উপপাদ্য আর ছবি ও রেটিং matrix-এ তার যাচাই
- Pseudo-inverse, minimum-norm সমাধান আর condition number; ছোট singular value কেন বিপদ
- Sparse লেখায় truncated SVD, আর recommender system-এর matrix factorization কেন “SVD” নামে পরিচিত
AI-এর ভিত্তি কোর্সের পর্ব ১৭-এ np.linalg.solve, lstsq আর condition number-এর প্রথম পরিচয় হয়েছিল, পর্ব ৬৫-এ SVD দিয়ে PCA, আর পর্ব ৬৯-এ truncated SVD দিয়ে LSA. সেখানে SVD ছিল একটি কাজের যন্ত্র। এই পর্বে প্রশ্ন: যন্ত্রটি ভেতরে কী করে, কেন সবসময় কাজ করে, আর কেন ML-এর এত জায়গায় (compression, least squares, text, recommender) ঘুরেফিরে আসে? এই মডিউলের আগের পর্বগুলো (পর্ব ০৬-এ rank, পর্ব ০৭-এ orthogonal matrix আর projection, পর্ব ০৮-এ eigen আর spectral theorem) এখানে একসঙ্গে কাজে লাগবে। চালানো যায় এমন চারটি ব্লক পাতাতেই চলে (ব্রাউজারের Python, NumPy, matplotlib আর scikit-learn); scikit-learn প্রথমবার নামতে প্রায় ১০ সেকেন্ড লাগে।
১ · SVD-এর ছবি — ঘোরানো, টানা, আবার ঘোরানো
দর্জির দোকানে একটি গোল রুটির মতো কাপড়ের টুকরো ভাবুন। দর্জি প্রথমে টুকরোটি টেবিলে একটু ঘুরিয়ে নেন, তারপর দুই দিকে দুই রকম টান দেন (লম্বায় বেশি, চওড়ায় কম), শেষে আবার ঘুরিয়ে জায়গামতো রাখেন। গোল টুকরোটি হয়ে গেল ডিম্বাকৃতি। SVD বলে: যেকোনো matrix ঠিক এই তিনটি কাজ, আর কিছু নয়। জটিল মনে হওয়া যেকোনো রৈখিক রূপান্তর আসলে “ঘোরাও–টানো–ঘোরাও”।
একটি m × n matrix A একটি n-মাত্রার vector x নিয়ে m-মাত্রার Ax দেয়। Singular value decomposition বলে, A-কে সবসময় লেখা যায়
A = U Σ Vᵀ
যেখানে U একটি m × m orthogonal matrix (কলামগুলো পরস্পর লম্ব আর একক দৈর্ঘ্যের, তাই UᵀU = I), V একটি n × n orthogonal matrix, আর Σ একটি m × n “কর্ণ” matrix, যার কর্ণে σ1 ≥ σ2 ≥ … ≥ 0 বসে আর বাকি সব ঘর শূন্য। Vᵀ মানে V-এর transpose. σi-গুলো singular value, V-এর কলাম vi হলো right singular vector (ইনপুটের জগতে বিশেষ দিক), U-এর কলাম ui left singular vector (আউটপুটের জগতে বিশেষ দিক)। Orthogonal matrix দৈর্ঘ্য আর কোণ বদলায় না, তাই সে কেবল ঘোরায় বা আয়নায় উল্টায় (পর্ব ০৭)।
এক লাইনে মূল সম্পর্ক: A vi = σi ui. অর্থাৎ ইনপুটের i-তম বিশেষ দিককে A আউটপুটের i-তম বিশেষ দিকে পাঠায়, দৈর্ঘ্য σi গুণ করে। Eigenvector-এর সঙ্গে তুলনা করুন (পর্ব ০৮): সেখানে Av = λv, একই দিক। SVD দিক বদলাতে দেয় (v থেকে u), বিনিময়ে সবসময় থাকে, যেকোনো আকারের যেকোনো matrix-এর জন্য, আর σ কখনো ঋণাত্মক বা জটিল সংখ্যা হয় না। Eigen-decomposition-এর এই নিশ্চয়তা নেই।
নিচের stepper A = [[3, 0], [4, 5]] নিয়ে একক বৃত্তকে তিন ধাপে ellipse বানায়। এখানে V আর U দুটিই ঘূর্ণন হিসেবে বেছে নেওয়া (অংশ ২-এ দেখবেন NumPy আয়নাসহ অন্য একটি সমান সঠিক রূপ দেয়)।
- শুরু। একক বৃত্ত মানে সব x যাদের দৈর্ঘ্য ১. লাল v1 = (0.71, 0.71) আর নীল v2 = (−0.71, 0.71) হলো ইনপুটের দুটি বিশেষ দিক; কালো বিন্দুটি x = (1, 0), যাকে আমরা শেষ পর্যন্ত অনুসরণ করব।
- Vᵀ ঘোরায়। Vᵀ বৃত্তকে −৪৫° ঘোরায়: বৃত্ত বৃত্তই থাকে, কিন্তু v1 চলে আসে x অক্ষে (e1) আর v2 y অক্ষে (e2). কালো বিন্দু (1, 0) এখন (0.71, −0.71)-এ।
- Σ টানে। Σ = diag(6.71, 2.24) x অক্ষ বরাবর ৬.৭১ গুণ আর y অক্ষ বরাবর ২.২৪ গুণ টানে। বৃত্ত হলো শোয়ানো ellipse, অর্ধ-অক্ষ ঠিক σ1 আর σ2. কালো বিন্দু (4.74, −1.58)-এ।
- U আবার ঘোরায়। U ellipse-কে +৭১.৬° ঘোরায়, যাতে লম্বা অক্ষ u1 = (0.32, 0.95)-এর দিকে যায়। লাল তীর এখন σ1u1 = (2.12, 6.36) = Av1, নীল তীর σ2u2 = (−2.12, 0.71) = Av2. কালো বিন্দু পৌঁছায় (3, 4)-এ।
- সরাসরি A দিয়ে মেলানো। A·(1, 0) = (3, 4) আর A·(0, 1) = (0, 5): দুটিই একই ellipse-এ পড়ে। ক্ষেত্রফল বেড়েছে σ1σ2 = ১৫ গুণ, যা |det A| = |3·5 − 0·4| = ১৫. তিন সরল ধাপ মিলে ঠিক A.
Matrix বর্গাকার না হলে Σ-ও বর্গাকার নয়। ML-এ ডেটা matrix প্রায় সবসময় “লম্বা” (n সারি উদাহরণ, d কলাম feature, n ≫ d), তাই full SVD-এর U-এর অনেক কলাম শূন্য σ-এর সঙ্গে গুণ হয়ে কাজে লাগে না। ব্যবহারিক রূপ thin SVD: শুধু প্রথম min(m, n)-টি কলাম রাখা। NumPy-তে np.linalg.svd(A, full_matrices=False) এটাই দেয়। আর শুধু প্রথম k-টি রাখলে truncated SVD, যা অংশ ৪ থেকে আমাদের মূল বিষয়।
একই 5 × 4 matrix-এর তিন রূপের SVD — আকারগুলো মিলিয়ে দেখুন
Full, thin and truncated (k = 2) SVD of a 5 × 4 matrix
২ · AᵀA আর AAᵀ থেকে SVD — হাতে একটি 2×2
SVD কোথা থেকে আসে, তা দুই লাইনে বের করা যায়। A = UΣVᵀ হলে
AᵀA = VΣᵀUᵀUΣVᵀ = V (ΣᵀΣ) Vᵀ, AAᵀ = U (ΣΣᵀ) Uᵀ
কারণ UᵀU = I. ΣᵀΣ একটি কর্ণ matrix, কর্ণে σi². তাই V-এর কলাম হলো AᵀA-র eigenvector, আর σi² তার eigenvalue; একইভাবে U-এর কলাম AAᵀ-এর eigenvector. উল্টো দিক থেকে এটিই অস্তিত্বের প্রমাণের নকশা: AᵀA প্রতিসম (symmetric), তাই পর্ব ০৮-এর spectral theorem অনুযায়ী তার orthonormal eigenvector আছে, আর xᵀAᵀAx = ‖Ax‖² ≥ 0 বলে eigenvalue কখনো ঋণাত্মক নয়, তাই বর্গমূল নেওয়া যায়। σi = √λi ধরে, σi > 0 হলে ui = Avi/σi সংজ্ঞায়িত করুন। এগুলো একক দৈর্ঘ্যের আর পরস্পর লম্ব, কারণ uiᵀuj = viᵀAᵀAvj/(σiσj) = λjviᵀvj/(σiσj), যা i ≠ j হলে ০ আর i = j হলে ১. প্রয়োজনে আরও লম্ব vector যোগ করে U সম্পূর্ণ করা হয়। ব্যস, যেকোনো বাস্তব matrix-এর SVD আছে।
হাতে একটি পূর্ণ উদাহরণ (পরীক্ষায় প্রায়ই আসে এমন ধরনের)। A = [[3, 0], [4, 5]].
- AᵀA = [[3·3 + 4·4, 3·0 + 4·5], [20, 0·0 + 5·5]] = [[25, 20], [20, 25]].
- Characteristic polynomial: λ² − (trace)λ + det = λ² − 50λ + (625 − 400) = λ² − 50λ + 225 = (λ − 45)(λ − 5). তাই λ1 = ৪৫, λ2 = ৫, আর σ1 = √45 = 3√5 ≈ ৬.৭০৮, σ2 = √5 ≈ ২.২৩৬.
- λ = ৪৫-এর eigenvector: (25 − 45)x + 20y = 0 → y = x, তাই v1 = (1, 1)/√2. λ = ৫-এর: 20x + 20y = 0 → v2 = (−1, 1)/√2.
- u1 = Av1/σ1 = (3, 9)/√2 ÷ √45 = (3, 9)/√90 = (1, 3)/√10 ≈ (0.316, 0.949). u2 = Av2/σ2 = (−3, 1)/√2 ÷ √5 = (−3, 1)/√10.
- যাচাই: σ1σ2 = √225 = ১৫ = |det A|, আর σ1/σ2 = ৩, যা A-এর condition number (অংশ ৭)।
চিহ্নের স্বাধীনতা: ui আর vi দুটিকে একসঙ্গে −১ দিয়ে গুণ করলেও σiuiviᵀ বদলায় না। তাই দুটি library (বা একই library-র দুটি version) আলাদা চিহ্ন দিতে পারে, দুটিই সঠিক। কিন্তু শুধু একটির চিহ্ন উল্টালে ভুল হয়: ui সবসময় Avi/σi থেকে বের করুন, AAᵀ-এর eigenvector আলাদাভাবে নিয়ে জোড়া লাগাবেন না। সমান σ থাকলে দিকগুলো আরও বেশি অনির্দিষ্ট (একটি পুরো তলের যেকোনো orthonormal জোড়া চলে)।
নিচের ব্লক NumPy-র SVD হাতের হিসাবের সঙ্গে মেলায়, AᵀA-র eigen দিয়ে V যাচাই করে, তারপর stepper-এর চার ধাপ ছবিতে আঁকে।
import numpy as np
import matplotlib.pyplot as plt
np.set_printoptions(precision=4, suppress=True)
A = np.array([[3., 0.], [4., 5.]])
U, s, Vt = np.linalg.svd(A)
print("singular values:", s)
print("U =\n", U, "\nVt =\n", Vt)
print("A == U Σ Vt ?", np.allclose(A, U @ np.diag(s) @ Vt))
lam, Q = np.linalg.eigh(A.T @ A) # ছোট থেকে বড় ক্রমে আসে
print("eig(AᵀA):", lam, "→ √ =", np.sqrt(lam[::-1]))
print("V-র কলাম == AᵀA-র eigenvector (চিহ্ন বাদে)?",
np.allclose(np.abs(Vt.T), np.abs(Q[:, ::-1])))
print("u1 == A v1 / σ1 ?", np.allclose(U[:, 0], A @ Vt[0] / s[0]))
print("det(A) =", round(np.linalg.det(A), 4), "| σ1·σ2 =", round(s.prod(), 4))
t = np.linspace(0, 2 * np.pi, 200)
C = np.vstack([np.cos(t), np.sin(t)]) # একক বৃত্তের ২০০টি বিন্দু
stages = [C, Vt @ C, np.diag(s) @ Vt @ C, U @ np.diag(s) @ Vt @ C]
names = ["unit circle", "after Vt (rotate)", "after Σ (stretch)", "after U = A·x"]
fig, axes = plt.subplots(1, 4, figsize=(9, 2.8), layout="constrained")
for ax, P, name in zip(axes, stages, names):
ax.plot(P[0], P[1], "C0")
ax.plot(P[0, 0], P[1, 0], "o", color="C3") # x = (1, 0) বিন্দুটি অনুসরণ
ax.set_xlim(-7, 7); ax.set_ylim(-7, 7); ax.set_aspect("equal")
ax.axhline(0, color="0.8", lw=0.8); ax.axvline(0, color="0.8", lw=0.8)
ax.set_title(name, fontsize=9)
plt.show()আসল আউটপুট (ব্রাউজারের Python, NumPy 2.4.6):
singular values: [6.7082 2.2361]
U =
[[-0.3162 -0.9487]
[-0.9487 0.3162]]
Vt =
[[-0.7071 -0.7071]
[-0.7071 0.7071]]
A == U Σ Vt ? True
eig(AᵀA): [ 5. 45.] → √ = [6.7082 2.2361]
V-র কলাম == AᵀA-র eigenvector (চিহ্ন বাদে)? True
u1 == A v1 / σ1 ? True
det(A) = 15.0 | σ1·σ2 = 15.0
σ = ৬.৭০৮২ আর ২.২৩৬১, হাতের √45 আর √5-এর সঙ্গে হুবহু। NumPy-র u1 = (−0.316, −0.949) আর v1 = (−0.707, −0.707): হাতের উত্তরের ঠিক উল্টো চিহ্ন, দুটিই একসঙ্গে, তাই গুণফল একই। এই চিহ্নে U আর V দুটিরই determinant −১, অর্থাৎ ঘূর্ণনের সঙ্গে একটি আয়না; দুই আয়না মিলে কাটাকাটি হয়। eigh eigenvalue ছোট থেকে বড় ক্রমে দেয় (৫, ৪৫), SVD বড় থেকে ছোট; তুলনার আগে ক্রম উল্টাতে হয়েছে। ছবির চারটি প্যানেলে লাল বিন্দু (1, 0) শেষে (3, 4)-এ পৌঁছায়, stepper-এর মতোই।
৩ · Singular value, rank আর matrix-এর মাপ
A = UΣVᵀ গুণ করে খুলে লিখলে SVD একটি যোগফল হয়ে যায়:
A = σ1u1v1ᵀ + σ2u2v2ᵀ + … + σrurvrᵀ
প্রতিটি uiviᵀ একটি outer product: m × n matrix যার প্রতিটি সারি viᵀ-এর গুণিতক, তাই এর rank ঠিক ১. অর্থাৎ যেকোনো matrix হলো rank-১ “স্তর”-এর ওজন দেওয়া যোগফল, আর ওজনগুলো বড় থেকে ছোট সাজানো। r হলো শূন্য নয় এমন σ-এর সংখ্যা। এখান থেকে কয়েকটি গুরুত্বপূর্ণ সত্য সরাসরি আসে:
- Rank = শূন্য নয় এমন singular value-এর সংখ্যা। Column space-এর orthonormal basis হলো u1…ur, row space-এর v1…vr, আর null space-এর vr+1…vn. পর্ব ০৬-এর চারটি মৌলিক subspace এক SVD-তেই পাওয়া যায়।
- Numerical rank: float-এর হিসাবে “ঠিক শূন্য” প্রায় আসে না।
np.linalg.matrix_rankএকটি tolerance ধরে, ডিফল্টে σmax × max(m, n) × machine epsilon (প্রায় 2.2 × 10−16), আর তার নিচের σ-কে শূন্য গোনে। ডেটায় মাপার ভুল থাকলে তার মাত্রা অনুযায়ী tolerance বড় করাই যুক্তিসঙ্গত। - Spectral norm ‖A‖2 = σ1: একক vector-কে A সর্বোচ্চ যতগুণ লম্বা করতে পারে, অর্থাৎ ellipse-এর লম্বা অর্ধ-অক্ষ।
- Frobenius norm: ‖A‖F² = সব ঘরের বর্গের যোগফল = σ1² + … + σr². কারণ orthogonal matrix দিয়ে গুণ করলে Frobenius norm বদলায় না, তাই ‖A‖F = ‖Σ‖F. σi²-কে তাই অনেকে i-তম স্তরের “শক্তি” (energy) বলেন।
- বর্গাকার A-তে |det A| = σ1σ2…σn, আর A উল্টানো যায় ঠিক তখনই যখন σn > 0.
PCA-র সঙ্গে সম্পর্ক এক লাইনে: ডেটা matrix X-এর প্রতিটি কলাম থেকে গড় বাদ দিয়ে Xc বানালে covariance = XcᵀXc/(n − 1) = V(Σ²/(n − 1))Vᵀ. তাই principal component-এর দিক হলো V-এর কলাম, আর i-তম component-এর variance σi²/(n − 1). পর্ব ০৮ PCA-কে covariance-এর eigen থেকে বের করে; বাস্তব library (যেমন scikit-learn-এর PCA) সরাসরি Xc-এর SVD নেয়, কারণ covariance বানালে condition number বর্গ হয়ে যায় (অংশ ৭)।
৪ · Eckart–Young — সবচেয়ে ভালো low-rank approximation
যোগফলের রূপ দেখে একটি স্বাভাবিক ধারণা আসে: শুধু প্রথম k-টি স্তর রাখলে কেমন হয়?
Ak = σ1u1v1ᵀ + … + σkukvkᵀ = UkΣkVkᵀ
১৯৩৬ সালে Carl Eckart আর Gale Young (Psychometrika-তে) দেখান, এটি কেবল একটি সুবিধাজনক পছন্দ নয়, সবচেয়ে ভালো পছন্দ। Eckart–Young উপপাদ্য: rank সর্বোচ্চ k এমন সব matrix B-এর মধ্যে ‖A − B‖ সবচেয়ে কম হয় B = Ak-এ, Frobenius আর spectral দুই norm-এই, আর ভুলের মান সঠিকভাবে জানা:
‖A − Ak‖F² = σk+1² + … + σr², ‖A − Ak‖2 = σk+1
প্রথম সমতা সহজে দেখা যায়: A − Ak বাদ পড়া স্তরগুলোর যোগফল, যার SVD-তেই σk+1, …, σr, তাই Frobenius² তাদের বর্গের যোগফল। “এর চেয়ে ভালো কিছু নেই” অংশটি গভীরতর। Spectral norm-এর প্রমাণের নকশা: যেকোনো rank-k B-এর null space-এর মাত্রা অন্তত n − k, আর v1, …, vk+1-এর span-এর মাত্রা k + ১; দুটি মিলে n-এর বেশি, তাই তারা একটি একক vector w-তে ছেদ করে। সেখানে Bw = 0, তাই ‖(A − B)w‖ = ‖Aw‖ ≥ σk+1. কোনো B-ই σk+1-এর নিচে নামতে পারে না, আর Ak ঠিক সেখানে পৌঁছায়।
Low-rank approximation-এর অর্থ ML-এ সবচেয়ে স্পষ্ট দেখা যায় রেটিং-এ। বাজারঘরের পাঁচজন কাল্পনিক ক্রেতা চারটি পণ্যকে ১ থেকে ৫ রেটিং দিয়েছেন: P1 চাল, P2 ডাল, P3 হেডফোন, P4 চার্জার। নিচের stepper প্রথম k-টি σ রেখে matrix আবার গড়ে, আর ভুল মেলায় সূত্রের সঙ্গে। সংখ্যাগুলো seed ছাড়াই নির্দিষ্ট (হাতে বানানো matrix), NumPy দিয়ে হিসাব করা।
- আসল matrix আর তার σ. c1, c2, c5 মুদি পণ্য পছন্দ করেন, c3, c4 গ্যাজেট। চারটি σ: ১৩.৩৮, ৭.১২, ১.৪১, ১.১৪. তাদের বর্গের যোগফল ২৩৩, যা সব ঘরের বর্গের যোগফলের সমান (Frobenius-এর সূত্র)।
- k = ১: শুধু “জনপ্রিয়তা”। R1 = σ1u1v1ᵀ-এর প্রতিটি সারি একই প্যাটার্নের গুণিতক: চাল-ডাল একটু বেশি, হেডফোন-চার্জার কম। কারও রুচির পার্থক্য এখানে নেই। ভুলের বর্গ ৫৪.০১, যা ঠিক বাদ পড়া তিনটি σ²-এর যোগফল।
- k = ২: রুচির অক্ষ যোগ হলো। এখন মুদি-প্রেমীদের চাল-ডালে ৪.৫–৫.০, গ্যাজেটে ১.০–১.৫; গ্যাজেট-প্রেমীদের উল্টো। মাত্র দুটি স্তরে শক্তির ৯৮.৬%, ভুলের বর্গ ৩.৩১ = ২.০০ + ১.৩১.
- k = ৩: খুঁটিনাটি। তৃতীয় স্তর ছোট ব্যক্তিগত পার্থক্য আনে (c2-এর চার্জারে ২, c3-এর হেডফোনে ৫)। ভুলের বর্গ বাকি থাকে শুধু σ4² = ১.৩১. k = ৪-এ হুবহু R ফেরে।
- দিকগুলো পড়া। v1-এর সব সংখ্যা একই চিহ্নের: সার্বিক রেটিং-এর মাত্রা। v2 মুদিতে ধনাত্মক আর গ্যাজেটে ঋণাত্মক: একটি “রুচির অক্ষ”। u2-এ c1, c2, c5 ধনাত্মক আর c3, c4 ঋণাত্মক, অর্থাৎ কে কোন দিকে। কেউ এই অক্ষ শিখিয়ে দেয়নি; SVD ডেটা থেকে বের করেছে। এগুলোকেই recommender-এর ভাষায় latent factor বলে (অংশ ৯)।
- Eckart–Young-এর সারণি। প্রতিটি k-তে মাপা ভুলের বর্গ আর বাদ পড়া σ²-এর যোগফল হুবহু সমান। আর উপপাদ্য বলে: rank ২-এর অন্য কোনো matrix, যেভাবেই বানানো হোক, ৩.৩১-এর কম ভুল করতে পারবে না।
৫ · ছবিতে low-rank — rank বনাম ভুল, সূত্রের সঙ্গে মিলিয়ে
AI-এর ভিত্তি কোর্সের পর্ব ১৮-এ দেখেছেন, ধূসর ছবি মানে একটি 2-D array. তাই ছবিতে Eckart–Young চোখে দেখা যায়। scikit-learn তার package-এর ভেতরে দুটি নমুনা ছবি রাখে (load_sample_image; ইন্টারনেট লাগে না); এখানে “china.jpg”, একটি মন্দিরের ছবি, যা Creative Commons BY 2.0 লাইসেন্সে Flickr ব্যবহারকারী danielbuechele-এর। ব্লকটি ছবিকে ধূসর করে, দ্রুততার জন্য প্রতি দ্বিতীয় pixel নেয় (২১৪ × ৩২০), তারপর k = ৫, ২০, ৫০ রেখে ভুল মাপে আর সূত্রের সঙ্গে মেলায়।
import numpy as np
import matplotlib.pyplot as plt
import PIL # load_sample_image ছবি খুলতে Pillow লাগে
from sklearn.datasets import load_sample_image
img = load_sample_image("china.jpg").mean(axis=2)[::2, ::2] # ধূসর, প্রতি দ্বিতীয় pixel
m, n = img.shape
U, s, Vt = np.linalg.svd(img, full_matrices=False)
print("shape:", img.shape, "| rank:", np.linalg.matrix_rank(img))
print("σ1..σ5:", np.round(s[:5], 1))
total = np.sum(s**2) # = ‖img‖_F²
print(" k ‖A−A_k‖²_F বাদ পড়া σ² আপেক্ষিক ভুল% জায়গা%")
fig, axes = plt.subplots(1, 4, figsize=(9, 2.2), layout="constrained")
axes[0].imshow(img, cmap="gray"); axes[0].set_title(f"original (rank {min(m, n)})", fontsize=9)
for ax, k in zip(axes[1:], (5, 20, 50)):
Ak = (U[:, :k] * s[:k]) @ Vt[:k]
err = np.sum((img - Ak) ** 2)
tail = np.sum(s[k:] ** 2)
store = k * (m + n + 1) / (m * n)
print(f"{k:2d} {err:.4e} {tail:.4e} {100*np.sqrt(tail/total):6.2f} {100*store:6.1f}")
ax.imshow(Ak, cmap="gray"); ax.set_title(f"rank {k}", fontsize=9)
for ax in axes: ax.axis("off")
plt.show()
print("spectral norm ‖A−A_20‖₂ =", round(np.linalg.norm(img - (U[:, :20] * s[:20]) @ Vt[:20], 2), 2),
"| σ21 =", round(s[20], 2))আসল আউটপুট (ব্রাউজারের Python):
shape: (214, 320) | rank: 214
σ1..σ5: [41777.5 7683.2 4879. 2897.7 2454.2]
k ‖A−A_k‖²_F বাদ পড়া σ² আপেক্ষিক ভুল% জায়গা%
5 6.4183e+07 6.4183e+07 18.35 3.9
20 3.3498e+07 3.3498e+07 13.25 15.6
50 1.6092e+07 1.6092e+07 9.19 39.1
spectral norm ‖A−A_20‖₂ = 976.73 | σ21 = 976.73
(ছবির JPEG খোলার কাজ Pillow করে; ডেস্কটপে অন্য version-এ শেষ দশমিকে সামান্য পার্থক্য দেখা যেতে পারে।) তিনটি k-তেই মাপা ভুল আর বাদ পড়া σ²-এর যোগফল চার অঙ্ক পর্যন্ত এক, আর ‖A − A20‖2 = σ21 = ৯৭৬.৭৩: Eckart–Young-এর দুই সূত্রই মিলল। σ1 = ৪১,৭৭৭.৫ বাকিদের চেয়ে অনেক বড়, কারণ প্রথম স্তর মূলত ছবির গড় উজ্জ্বলতা। আপেক্ষিক ভুল (‖A − Ak‖F/‖A‖F) k = ২০-এ ১৩.২৫%, আর তখন জায়গা লাগে মূলের মাত্র ১৫.৬%. ছবিতে rank ৫-এ শুধু ঝাপসা আকৃতি, ২০-এ মন্দিরের ছাদ চেনা যায়, ৫০-এ প্রায় মূলের মতো, তবে সূক্ষ্ম রেখায় দাগ থাকে। লক্ষ করুন, ভুল ধীরে কমে: σ-গুলো ধীরে ছোট হয়, কারণ প্রাকৃতিক ছবিতে অনেক সূক্ষ্ম খুঁটিনাটি থাকে।
বাস্তবে ছবি সংকোচন SVD দিয়ে হয় না। JPEG একটি নির্দিষ্ট basis (discrete cosine transform) ব্যবহার করে ৮ × ৮ টুকরোয়, তাই basis পাঠাতে হয় না, আর মানুষের চোখ যে খুঁটিনাটি কম ধরে তা ফেলে। SVD-এর basis প্রতিটি ছবির নিজের, তাই U আর V-ও জমা রাখতে হয়। SVD-এর শক্তি অন্যখানে: যে ডেটায় সত্যিই কয়েকটি গোপন কারণ কাজ করে (রেটিং, শব্দ-নথি, sensor), সেখানে কম rank-এ প্রায় সব তথ্য ধরা পড়ে।
৬ · Pseudo-inverse — least squares আর minimum-norm সমাধান এক সূত্রে
Ax = b সমাধান করতে A−1 লাগে, কিন্তু A বর্গাকার আর পূর্ণ rank না হলে inverse নেই। দুই রকম সমস্যা আসে। লম্বা A (সমীকরণ বেশি, অজানা কম, যেমন linear regression): সাধারণত কোনো x সব সমীকরণ মানে না, তাই চাই এমন x যাতে ‖Ax − b‖ সবচেয়ে কম (least squares). চওড়া A (অজানা বেশি, underdetermined system): অসীম সমাধান, কোনটি নেব? SVD দুটিরই একটি স্বাভাবিক উত্তর দেয়। Σ-এর শূন্য নয় এমন প্রতিটি σi-কে 1/σi দিয়ে বদলে, শূন্যগুলো শূন্যই রেখে, আকার উল্টে (m × n থেকে n × m) পাওয়া যায় Σ⁺. তখন
A⁺ = V Σ⁺ Uᵀ, x⁺ = A⁺b
এটিই Moore–Penrose pseudo-inverse. E. H. Moore ১৯২০ সালের দিকে আর Roger Penrose ১৯৫৫ সালে আলাদাভাবে এর ধারণা দেন; Penrose চারটি শর্ত দিয়ে এটিকে একমাত্র matrix হিসেবে চিহ্নিত করেন: AA⁺A = A, A⁺AA⁺ = A⁺, আর AA⁺ ও A⁺A দুটিই প্রতিসম। x⁺-এর দুটি গুণ একসঙ্গে প্রমাণ করা যায়: (১) সব x-এর মধ্যে x⁺ ‖Ax − b‖ সবচেয়ে কম করে; (২) এমন সব x-এর মধ্যে x⁺-এর নিজের দৈর্ঘ্য ‖x‖ সবচেয়ে কম। দ্বিতীয়টির কারণ: x⁺ পুরোপুরি row space-এ থাকে (v1…vr-এর span), আর যেকোনো অন্য সমাধান = x⁺ + (null space-এর একটি vector), যা x⁺-এর সঙ্গে লম্ব, তাই পিথাগোরাস অনুযায়ী শুধু দৈর্ঘ্য বাড়ায়। এই সমাধানকে বলে minimum-norm solution.
A লম্বা আর পূর্ণ কলাম-rank হলে A⁺ = (AᵀA)−1Aᵀ, যা normal equations-এর সূত্র (AI-এর ভিত্তি কোর্সের পর্ব ৫৮; projection হিসেবে ব্যাখ্যা পর্ব ০৭-এ)। চওড়া আর পূর্ণ সারি-rank হলে A⁺ = Aᵀ(AAᵀ)−1. SVD-এর সূত্র দুটিকেই ধরে, আর rank কম হলেও কাজ করে। সবচেয়ে ছোট উদাহরণে হাতে দেখি: একটি কলামের matrix a = (3, 4)ᵀ.
- সমস্যা। একটি অজানা x, দুটি পর্যবেক্ষণ: 3x = 2 আর 4x = 1. প্রথমটি বলে x = ০.৬৬৭, দ্বিতীয়টি ০.২৫. হুবহু সমাধান নেই, তাই least squares.
- SVD. এক কলামের matrix-এর একটিই singular value: তার দৈর্ঘ্য, σ = ৫. u হলো a-এর দিকের একক vector (0.6, 0.8), আর v শুধু সংখ্যা ১. Σ একটি 2×1 matrix [[5], [0]].
- Σ⁺ আর a⁺. Σ⁺ আকারে উল্টো (1×2): শূন্য নয় এমন σ উল্টে ১/৫, শূন্য শূন্যই। তাই a⁺ = (1/5)uᵀ = [0.12, 0.16], যা aᵀ/‖a‖²-এর সমান।
- সমাধান। x⁺ = a⁺b = ০.২৪ + ০.১৬ = ০.৪০. Normal equation (aᵀa)−1aᵀb = (3·2 + 4·1)/25 = ১০/২৫ একই উত্তর দেয়। দুই অসঙ্গত উত্তরের মাঝামাঝি, বড় সহগের (৪) সমীকরণের দিকে একটু বেশি ঝোঁকা।
- জ্যামিতি। a·x⁺ = (1.2, 1.6) হলো b-এর a-রেখার ওপর লম্ব projection. Residual (0.8, −0.6) a-এর সঙ্গে লম্ব (dot product ০), আর তার দৈর্ঘ্য ১.০; এর চেয়ে কাছে যাওয়া যায় না।
- চওড়া রূপ। উল্টো আকার aᵀ = [3, 4]: একটি সমীকরণ 3x1 + 4x2 = 2, অসীম সমাধান। Pseudo-inverse দেয় (0.24, 0.32), দৈর্ঘ্য ০.৪০. আরেকটি সমাধান (2/3, 0)-এর দৈর্ঘ্য ০.৬৬৭. x⁺ a-এর দিকেই পড়ে, তাই সবচেয়ে ছোট।
৭ · Condition number = σmax/σmin — কেন কিছু সমাধান কাঁপে
A⁺-এর সূত্রে 1/σi আছে। কোনো σi খুব ছোট হলে 1/σi বিশাল, আর b-এর সামান্য পরিবর্তন x⁺-এ বিশাল পরিবর্তন আনে। x⁺ = Σ (uiᵀb/σi) vi লিখলে দেখা যায়: b-কে δb পরিমাণ নাড়ালে ui দিকের অংশ 1/σi গুণ বড় হয়ে x-এ ঢোকে। সবচেয়ে খারাপ দিক umin. তাই আপেক্ষিক পরিবর্তনের সর্বোচ্চ গুণক হলো
κ(A) = σmax / σmin, ‖δx‖/‖x‖ ≤ κ(A) · ‖δb‖/‖b‖ (বর্গাকার, উল্টানো যায় এমন A)
এই condition number AI-এর ভিত্তি কোর্সের পর্ব ১৭-এ np.linalg.cond হিসেবে এসেছিল; এখন দেখছি এর উৎস। ellipse-এর ভাষায় κ হলো লম্বা আর খাটো অক্ষের অনুপাত: বৃত্তের কাছাকাছি হলে ভালো, সুচের মতো সরু হলে ill-conditioned. দুটি পরিণাম মনে রাখুন। এক, κ(AᵀA) = κ(A)², কারণ AᵀA-র singular value σi²; তাই normal equations সরাসরি সমাধান করলে হারানো অঙ্ক দ্বিগুণ হয়, আর lstsq SVD বা QR দিয়ে কাজ করে। দুই, ML-এ ছোট σ সাধারণত আসে প্রায়-একই feature থেকে (collinearity), আর তখন coefficient অস্থির হয়, যদিও অনুমান (Xw) প্রায় ঠিক থাকে।
প্রতিকার দুটি, দুটিই SVD-এর ভাষায় সরল। Truncated pseudo-inverse: σi একটি সীমার নিচে হলে 1/σi-এর বদলে ০ বসানো (np.linalg.pinv-এর rtol, পুরোনো নাম rcond). Ridge: 1/σi-এর বদলে σi/(σi² + λ), যা বড় σ-তে প্রায় 1/σi আর ছোট σ-তে প্রায় ০; একটি মসৃণ ছাঁটাই। নিচের ব্লক তিনটি দাবি যাচাই করে: SVD থেকে বানানো pseudo-inverse np.linalg.pinv-এর সমান, এক সমীকরণ তিন অজানার সমস্যায় এটি minimum-norm সমাধান দেয়, আর প্রায় একই দুটি feature-এ (ডেলিভারির সরলরেখা দূরত্ব আর রাস্তার দূরত্ব, যা প্রায় ১.৩ গুণ) condition number আর ছাঁটাইয়ের প্রভাব।
import numpy as np
np.set_printoptions(precision=4, suppress=True)
def pinv_svd(A, rtol=1e-12): # A⁺ = V Σ⁺ Uᵀ, ছোট σ বাদ
U, s, Vt = np.linalg.svd(A, full_matrices=False)
inv = np.where(s > rtol * s[0], 1 / s, 0.0)
return Vt.T @ (inv[:, None] * U.T)
# ১) এক সমীকরণ, তিন অজানা: x1 + 2·x2 + 2·x3 = 9
A = np.array([[1., 2., 2.]]); b = np.array([9.])
x = pinv_svd(A) @ b
print("pinv-এর সমাধান:", x, "| ‖x‖ =", np.linalg.norm(x))
print("np.linalg.pinv-এর সঙ্গে মেলে?", np.allclose(pinv_svd(A), np.linalg.pinv(A)))
for other in ([9., 0., 0.], [3., 1., 2.]):
print("আরেকটি সমাধান", other, "→ A·x =", A @ other, "‖x‖ =", round(np.linalg.norm(other), 4))
# ২) প্রায় একই দুটি feature: দূরত্ব (km) আর রাস্তার দূরত্ব ≈ 1.3 × km
rng = np.random.default_rng(42)
km = rng.uniform(1, 15, 30)
X = np.column_stack([km, 1.3 * km + rng.normal(0, 0.01, 30)])
y = 10 + 3 * km + rng.normal(0, 1, 30) # ডেলিভারির মিনিট
Xc, yc = X - X.mean(0), y - y.mean()
s = np.linalg.svd(Xc, compute_uv=False)
print("σ:", s, "| σmax/σmin =", round(s[0] / s[-1], 1),
"| np.linalg.cond =", round(np.linalg.cond(Xc), 1))
y2 = yc + rng.normal(0, 0.2, 30) # label-এ সামান্য নড়চড়
for rt in (1e-12, 1e-2):
w1, w2 = pinv_svd(Xc, rt) @ yc, pinv_svd(Xc, rt) @ y2
print(f"rtol={rt:g}: w = {w1} → নড়চড়ের পর {w2}")আসল আউটপুট (ব্রাউজারের Python):
pinv-এর সমাধান: [1. 2. 2.] | ‖x‖ = 3.0
np.linalg.pinv-এর সঙ্গে মেলে? True
আরেকটি সমাধান [9.0, 0.0, 0.0] → A·x = [9.] ‖x‖ = 9.0
আরেকটি সমাধান [3.0, 1.0, 2.0] → A·x = [9.] ‖x‖ = 3.7417
σ: [36.5872 0.0264] | σmax/σmin = 1386.7 | np.linalg.cond = 1386.7
rtol=1e-12: w = [ 22.4617 -15.0066] → নড়চড়ের পর [ 21.7716 -14.4774]
rtol=0.01: w = [1.0985 1.428 ] → নড়চড়ের পর [1.0977 1.4269]
প্রথম অংশ: x1 + 2x2 + 2x3 = ৯-এর তিনটি সমাধানই সমীকরণ মানে, কিন্তু pseudo-inverse-এর (1, 2, 2)-এর দৈর্ঘ্য ৩, বাকিদের ৯ আর ৩.৭৪. (1, 2, 2) হলো (1, 2, 2)ᵀ-এর দিকেই, অর্থাৎ row space-এ; (3, 1, 2) = (1, 2, 2) + (2, −1, 0), আর দ্বিতীয় অংশটি null space-এ। দ্বিতীয় অংশ: দুটি σ ৩৬.৫৯ আর ০.০২৬৪, অনুপাত ১,৩৮৬.৭, np.linalg.cond-এর সঙ্গে হুবহু। পুরো pseudo-inverse w = (২২.৪৬, −১৫.০১) দেয়: এক feature-এ বিশাল ধনাত্মক, অন্যটিতে বিশাল ঋণাত্মক, যা কাটাকাটি হয়ে মোট প্রভাব প্রায় ৩ মিনিট/km রাখে (22.46 − 1.3 × 15.01 ≈ 2.95). label সামান্য নাড়ালেই coefficient ০.৫–০.৭ সরে। ছোট σ ছেঁটে ফেললে (rtol = ০.০১) w = (১.১০, ১.৪৩), মোট প্রভাব 1.10 + 1.3 × 1.43 ≈ ২.৯৬, আর নড়চড়ে প্রায় অপরিবর্তিত। অনুমান দুই ক্ষেত্রেই প্রায় সমান; পার্থক্য coefficient-এর অর্থ আর স্থিরতায়।
আরও গভীরে: SVD হিসাবের মানক পদ্ধতি Gene Golub আর William Kahan-এর ১৯৬৫ সালের bidiagonalization আর Golub–Reinsch-এর ১৯৭০ সালের algorithm থেকে এসেছে; আজ LAPACK-এর gesdd (divide-and-conquer) NumPy-র ডিফল্ট পথ। খরচ m × n matrix-এ মোটামুটি O(mn·min(m, n)). বড় matrix-এ প্রথম কয়েকটি σ-ই দরকার হলে Halko, Martinsson আর Tropp-এর ২০১১ সালের SIAM Review প্রবন্ধের randomized SVD (অংশ ৮) বা Lanczos-ভিত্তিক ARPACK ব্যবহার হয়। আর Ridge-এর filter factor σi²/(σi² + λ)-এর বিশ্লেষণ পর্ব ১৭ (Regularization-এর তত্ত্ব)-এর একটি ভিত্তি।
৮ · Sparse লেখায় truncated SVD — TF-IDF থেকে LSA
AI-এর ভিত্তি কোর্সের পর্ব ৬৭-এ TF-IDF matrix এসেছিল আর পর্ব ৬৯-এ তার SVD নিয়ে LSA (Deerwester ও সহকর্মীরা, ১৯৯০)। সেখানে কাজ হয়েছিল ঘন (dense) array-তে np.linalg.svd দিয়ে। বাস্তব সংগ্রহে লক্ষ লক্ষ নথি আর লক্ষ শব্দ; matrix-এর প্রায় সব ঘর শূন্য, তাই সেটি sparse matrix হিসেবে রাখা হয়। ঘন করলে memory ফেটে যায়, আর পুরো SVD-এর দরকারও নেই, প্রথম কয়েকশো σ-ই যথেষ্ট। scikit-learn-এর TruncatedSVD ঠিক এজন্য: sparse ইনপুট নেয়, ডিফল্টে randomized SVD চালায় (এলোমেলো vector দিয়ে column space-এর একটি ছোট basis খুঁজে তার ওপর ছোট একটি SVD; n_iter=5, n_oversamples=10), আর শুধু k-টি দিক দেয়।
একটি গুরুত্বপূর্ণ পার্থক্য: TruncatedSVD কলামের গড় বাদ দেয় না, PCA দেয়। গড় বাদ দিলে প্রতিটি শূন্য ঘর একটি ঋণাত্মক সংখ্যা হয়ে যায়, sparse আর থাকে না। তাই TruncatedSVD-এর প্রথম দিক প্রায়ই শুধু “গড় নথি”-র দিক, আর এর explained variance-এর ক্রম σ-এর ক্রমের সঙ্গে না-ও মিলতে পারে। নিচের ব্লক বাজারঘরের দশটি কাল্পনিক বাংলা রিভিউয়ে এটি দেখায়।
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.decomposition import TruncatedSVD
reviews = [ # কাল্পনিক বাজারঘর রিভিউ
"ডেলিভারি খুব দ্রুত এসেছে", "ডেলিভারি দেরিতে এসেছে রাইডার ফোন ধরেনি",
"রাইডার ভদ্র ডেলিভারি দ্রুত", "দুই দিন দেরিতে ডেলিভারি পেলাম",
"শাড়ির কাপড় ভালো রং সুন্দর", "কাপড় পাতলা রং উঠে যায়",
"রং ছবির মতো কাপড় নরম", "দাম কম কিন্তু কাপড় ভালো",
"দাম বেশি তবে ডেলিভারি দ্রুত", "রাইডার দেরিতে এসেছে কাপড় ভালো",
]
vec = TfidfVectorizer(token_pattern=r"\S+") # ফাঁকা জায়গায় ভাঙা: বাংলা যুক্তবর্ণ অক্ষত
X = vec.fit_transform(reviews)
print("X:", X.shape, "| শূন্য নয় এমন ঘর:", X.nnz, f"({100 * X.nnz / np.prod(X.shape):.0f}%)")
svd = TruncatedSVD(n_components=2, random_state=42)
Z = svd.fit_transform(X) # প্রতিটি রিভিউ এখন ২টি সংখ্যা
print("σ (TruncatedSVD):", np.round(svd.singular_values_, 4))
print("σ (পুরো SVD) :", np.round(np.linalg.svd(X.toarray(), compute_uv=False)[:2], 4))
print("explained variance ratio:", np.round(svd.explained_variance_ratio_, 3))
terms = vec.get_feature_names_out()
for i, comp in enumerate(svd.components_):
top = np.argsort(comp)[::-1][:4]
print(f"দিক {i + 1}:", ", ".join(f"{terms[j]} {comp[j]:+.2f}" for j in top))
for r, z in zip(reviews, Z):
print(f"{z[0]:+.2f} {z[1]:+.2f} {r}")আসল আউটপুট (ব্রাউজারের Python, scikit-learn 1.8.0):
X: (10, 28) | শূন্য নয় এমন ঘর: 49 (18%)
σ (TruncatedSVD): [1.5 1.2907]
σ (পুরো SVD) : [1.5 1.2907]
explained variance ratio: [0.043 0.196]
দিক 1: ডেলিভারি +0.39, রাইডার +0.38, এসেছে +0.38, দ্রুত +0.32
দিক 2: কাপড় +0.46, রং +0.40, ভালো +0.33, সুন্দর +0.22
+0.59 -0.36 ডেলিভারি খুব দ্রুত এসেছে
+0.67 -0.22 ডেলিভারি দেরিতে এসেছে রাইডার ফোন ধরেনি
+0.59 -0.36 রাইডার ভদ্র ডেলিভারি দ্রুত
+0.36 -0.17 দুই দিন দেরিতে ডেলিভারি পেলাম
+0.32 +0.67 শাড়ির কাপড় ভালো রং সুন্দর
+0.19 +0.54 কাপড় পাতলা রং উঠে যায়
+0.19 +0.54 রং ছবির মতো কাপড় নরম
+0.33 +0.44 দাম কম কিন্তু কাপড় ভালো
+0.42 -0.25 দাম বেশি তবে ডেলিভারি দ্রুত
+0.72 +0.23 রাইডার দেরিতে এসেছে কাপড় ভালো
১০ × ২৮ matrix-এর মাত্র ১৮% ঘর শূন্য নয়। Randomized পদ্ধতির দুটি σ পুরো SVD-এর সঙ্গে চার দশমিক পর্যন্ত এক। প্রথম দিক ডেলিভারি-রাইডারের শব্দে ভারী, দ্বিতীয়টি কাপড়-রঙের; রিভিউগুলো দ্বিতীয় সংখ্যার চিহ্ন দেখে দুই দলে ভাগ হয়ে যায়, আর মিশ্র রিভিউ (“রাইডার দেরিতে এসেছে কাপড় ভালো”) দুই দিকেই ধনাত্মক। এবার অদ্ভুত অংশ: σ1 > σ2, অথচ explained variance ratio প্রথমটির ০.০৪৩, দ্বিতীয়টির ০.১৯৬. কারণ গড় বাদ দেওয়া হয়নি: প্রথম স্থানাঙ্ক সব রিভিউয়ে ধনাত্মক (০.১৯ থেকে ০.৭২), তার বড় অংশ গড়, আর variance মাপে শুধু গড়ের চারপাশের ছড়ানো। PCA-র অভ্যাসে explained variance দেখে k বাছলে এখানে ভুল সিদ্ধান্ত হতে পারে; σ² বা পরের কাজের ফল (যেমন খোঁজের মান) দেখুন। দ্বিতীয় দিকের চতুর্থ শব্দে “সুন্দর” আর “শাড়ির” সমান ওজনের (দুটিই একটিমাত্র রিভিউতে, একসঙ্গে), তাই কোনটি ছাপা হবে তা sort-এর সমতা ভাঙার নিয়মের ওপর নির্ভর করে; ডেস্কটপে আমাদের এক run-এ অন্যটি এসেছিল। দশটি বাক্য খুব ছোট সংগ্রহ: এখানে সংখ্যার চেয়ে আচরণটি শেখার বিষয়।
৯ · Recommender-এর ভিত্তি — matrix factorization
অংশ ৪-এর stepper আসলে একটি ছোট recommender system-এর ভিত্তি দেখিয়েছে। R ≈ UkΣkVkᵀ লিখে P = UkΣk1/2 আর Q = VkΣk1/2 নিলে R ≈ PQᵀ: প্রতিটি ক্রেতা একটি k-মাত্রার vector pu, প্রতিটি পণ্য একটি qi, আর আনুমানিক রেটিং তাদের dot product puᵀqi. এই কাঠামোর নাম matrix factorization, আর এটি collaborative filtering-এর একটি প্রধান পদ্ধতি: অন্য ক্রেতাদের আচরণ থেকে একজনের পছন্দ আন্দাজ।
পাড়ার মুদি দোকানদার কোনো ফর্মুলা না জেনেও জানেন, “যিনি মিনিকেট চাল নেন, তিনি সাধারণত মসুর ডালও নেন।” তিনি মনে মনে প্রতিটি ক্রেতাকে কয়েকটি গুণে মাপেন (দামের প্রতি সংবেদনশীলতা, রান্নার ধরন) আর প্রতিটি পণ্যকেও একই গুণে। matrix factorization এই “গুণ”-গুলো ডেটা থেকে নিজে খুঁজে নেয়, নাম ছাড়াই।
কিন্তু বাস্তবে একটি বড় বাধা: রেটিং matrix-এর বেশিরভাগ ঘর খালি। একজন ক্রেতা হাজার পণ্যের মধ্যে হয়তো দশটিতে রেটিং দেন। খালি ঘরকে ০ ধরলে SVD শেখে “এই ক্রেতা এই পণ্য অপছন্দ করেন”, যা ভুল; আর খালি ঘরের SVD সংজ্ঞায়িতই নয়। তাই recommender-এর “SVD” আসলে SVD নয়, শুধু যে ঘরগুলো জানা সেখানে ভুল কমানো একটি optimization:
minP, Q ∑(u,i) জানা (rui − puᵀqi)² + λ(‖pu‖² + ‖qi‖²)
এখানে rui ক্রেতা u-এর পণ্য i-কে দেওয়া রেটিং, λ regularization-এর শক্তি। এর জনপ্রিয়তা আসে Netflix Prize (২০০৬–২০০৯) থেকে: Simon Funk ছদ্মনামে একজন প্রতিযোগী ২০০৬ সালে ব্লগে stochastic gradient descent দিয়ে এভাবে factor শেখার পদ্ধতি লেখেন, যা “Funk SVD” নামে পরিচিত হয়; পরে Koren, Bell আর Volinsky ২০০৯ সালে IEEE Computer-এ এর bias-সহ রূপ গুছিয়ে লেখেন। বিকল্প পথ ALS (alternating least squares): Q স্থির রেখে P-এর জন্য least squares, তারপর উল্টো, পালাক্রমে; প্রতিটি ধাপ এই পর্বের pseudo-inverse-এর মতো ছোট সমস্যা। নতুন ক্রেতা বা নতুন পণ্যের কোনো রেটিং না থাকলে তার vector শেখার কিছু নেই; এই cold start সমস্যা, implicit feedback (ক্লিক, কেনা) আর মূল্যায়নের পদ্ধতি পর্ব ৫৩ (Recommender system — collaborative filtering আর matrix factorization)-এ আসবে।
পরীক্ষা ও ইন্টারভিউতে: এই ধরনের প্রশ্ন প্রায়ই আসে।
১. 2×2 matrix-এর SVD হাতে? → AᵀA-র eigenvalue থেকে σ = √λ, eigenvector থেকে V, ui = Avi/σi; শেষে σ1σ2 = |det A| দিয়ে যাচাই।
২. SVD আর eigen-decomposition-এর পার্থক্য? → SVD সব matrix-এর আছে, দুটি আলাদা orthonormal basis, σ ≥ ০; eigen শুধু কিছু বর্গাকার matrix-এর, একটি basis, λ ঋণাত্মক বা জটিল হতে পারে। প্রতিসম PSD matrix-এ দুটি এক।
৩. Best rank-k approximation-এর ভুল? → Frobenius² = বাদ পড়া σ²-এর যোগ, spectral = σk+1 (Eckart–Young).
৪. np.linalg.pinv কী দেয়? → VΣ⁺Uᵀ; least squares-এর minimum-norm সমাধান, ছোট σ rtol দিয়ে ছাঁটা।
৫. Recommender-এ সরাসরি SVD কেন নয়? → খালি ঘর; শুধু জানা ঘরে regularized loss কমানো হয় (SGD বা ALS).
১০ · AI-এর যুগে — SVD-র হিসাব আর AI-এর কোড যাচাই
AI assistant linear algebra-র ধাপগুলো সুন্দর করে লেখে, আর ছোট চিহ্নের ভুল চোখে পড়ে না। SVD-তে কয়েকটি ভুল বিশেষভাবে বারবার দেখা যায়।
| AI প্রায়ই যা করে | কেন ভুল | কীভাবে ধরবেন |
|---|---|---|
| U আর V আলাদা করে AAᵀ আর AᵀA-র eigenvector থেকে নিয়ে জোড়া লাগায় | চিহ্ন বা ক্রম না মিললে UΣVᵀ ≠ A | np.allclose(A, U @ np.diag(s) @ Vt) চালান; ui = Avi/σi থেকে নিন |
np.linalg.svd-এর তৃতীয় ফলকে V ধরে নেয় | NumPy Vᵀ দেয় (সারিগুলো vi) | নিজের version-এর docs পড়ুন; shape আর একটি গুণফল দিয়ে যাচাই |
| Recommender-এ খালি রেটিং ০ ধরে SVD চালায়, আর একই ঘরে ভুল মেপে ফল দেয় | খালি = অপছন্দ নয়; train ঘরে মাপা ভুল আশাবাদী | জানা রেটিং আগে ভাগ করুন, তালা দেওয়া test ঘরে মাপুন, গড়-রেটিং baseline-এর সঙ্গে তুলনা |
Normal equations-এ inv(X.T @ X) লেখে | condition number বর্গ হয়, collinear feature-এ ভেঙে পড়ে | lstsq বা pinv; np.linalg.cond(X) দেখুন |
TruncatedSVD-এর explained variance দেখে PCA-র মতো k বাছে, বা pinv-এ পুরোনো/বানানো parameter দেয় | গড় বাদ না দেওয়ায় ক্রম মেলে না; API version-ভেদে বদলায় | σ² দেখুন; help(np.linalg.pinv) পড়ুন |
হিসাবের সঙ্গে যাচাইয়ের কোড আর সীমাবদ্ধতাও চেয়ে নিন:
AI-এর দেওয়া SVD-র হিসাব বা কোড হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১SVD-এর গণিত উনিশ শতকের, অথচ ব্যবহারিক যন্ত্র হলো ১৯৬০-এর দশকে। মাঝের প্রায় একশো বছরে কী বাধা ছিল?
G. W. Stewart-এর ১৯৯৩ সালের SIAM Review প্রবন্ধ “On the early history of the singular value decomposition” অনুযায়ী Eugenio Beltrami (১৮৭৩) আর Camille Jordan (১৮৭৪) বর্গাকার বাস্তব matrix-এর জন্য প্রায় একই সময়ে, আলাদাভাবে এই বিভাজন পান; পরে Sylvester, Schmidt আর Weyl তত্ত্বটি বাড়ান। কিন্তু তখন এটি ছিল bilinear form বোঝার একটি তাত্ত্বিক ফল। হাতে বড় matrix-এর SVD হিসাব করা কার্যত অসম্ভব ছিল।
১৯৩৬ সালে Eckart আর Young মনোবিজ্ঞানের ডেটার জন্য best low-rank approximation প্রমাণ করেন, অর্থাৎ ব্যবহারের প্রশ্ন তখনই উঠেছিল। তবু বাধা ছিল গণনা। স্বাভাবিক পথ মনে হয় AᵀA বানিয়ে তার eigen নেওয়া, কিন্তু অংশ ৭-এ দেখেছি এতে condition number বর্গ হয়: σmin/σmax যদি ১০−৮ হয়, তবে AᵀA-তে ১০−১৬, যা double precision-এ শূন্যের সঙ্গে মিশে যায়। ছোট singular value, যা rank আর pseudo-inverse-এর জন্য সবচেয়ে জরুরি, ঠিক সেগুলোই হারায়।
Golub আর Kahan ১৯৬৫ সালে দেখান, AᵀA না বানিয়েই orthogonal রূপান্তর (Householder) দিয়ে A-কে bidiagonal আকারে আনা যায়, আর ১৯৭০ সালে Golub আর Reinsch তার ওপর একটি স্থির পুনরাবৃত্তিমূলক algorithm দেন। Orthogonal রূপান্তর rounding-এর ভুল বাড়ায় না, তাই পুরো হিসাব A-এর নিজের নির্ভুলতা রক্ষা করে। এরপর LINPACK, পরে LAPACK এই পদ্ধতিকে মানক বানায়, আর আজ NumPy, SciPy, R বা MATLAB সবাই কার্যত এই কাজের উত্তরসূরি ব্যবহার করে।
ML-এর জন্য শিক্ষা পরিষ্কার: PCA বা least squares-এ covariance বা AᵀA বানিয়ে eigen নেওয়ার চেয়ে ডেটার SVD সাধারণত বেশি নির্ভরযোগ্য। scikit-learn-এর PCA এই কারণেই ডেটা matrix-এর SVD নেয়।
মূল উপলব্ধি: একটি গাণিতিক সত্য আর একটি নির্ভরযোগ্য algorithm দুটি আলাদা আবিষ্কার। ML-এ “সূত্রটি ঠিক” যথেষ্ট নয়; “এই float-এ হিসাবটি স্থির কি না” সমান জরুরি।
প্র ০২Netflix Prize-এর “SVD” আসলে SVD নয় কেন, আর সেই প্রতিযোগিতা থেকে recommender-এর চর্চা কী শিখেছে?
২০০৬ সালে Netflix ঘোষণা করে, তাদের নিজস্ব রেটিং-অনুমানের পদ্ধতির চেয়ে RMSE-তে ১০% ভালো করতে পারলে দশ লাখ ডলার পুরস্কার। ডেটায় প্রায় দশ কোটি রেটিং ছিল, কিন্তু ক্রেতা × সিনেমা matrix-এর তুলনায় তা খুবই কম; বেশিরভাগ ঘর খালি। SVD-এর সংজ্ঞায় সব ঘর জানা থাকতে হয়, তাই সরাসরি প্রয়োগ অসম্ভব।
Simon Funk ছদ্মনামে একজন প্রতিযোগী ২০০৬ সালে ব্লগে লেখেন, শুধু জানা রেটিংয়ের ওপর ভুল কমিয়ে stochastic gradient descent দিয়ে ক্রেতা আর সিনেমার factor একটি একটি করে শেখা যায়, regularization-সহ। গঠন SVD-এর মতো (রেটিং ≈ দুই factor-এর dot product), তাই নাম লেগে যায় “Funk SVD”. কিন্তু এতে orthogonality নেই, singular value-এর ক্রম নেই, আর Eckart–Young-এর নিশ্চয়তাও নেই: খালি ঘরসহ low-rank সমস্যা non-convex, আর সাধারণভাবে কঠিন।
পুরস্কার ২০০৯ সালে পায় “BellKor’s Pragmatic Chaos” দল, অনেক model-এর একটি বড় ensemble দিয়ে। Koren, Bell আর Volinsky-এর ২০০৯ সালের প্রবন্ধ দেখায়, ক্রেতা আর সিনেমার bias (কেউ সবসময় বেশি নম্বর দেন, কিছু সিনেমা সবার প্রিয়), সময়ের সঙ্গে রুচির বদল আর implicit তথ্য যোগ করলে matrix factorization অনেক ভালো করে। Netflix পরে তাদের ব্লগে জানায় যে বিজয়ী ensemble-এর পুরোটা তারা production-এ নেয়নি, কারণ বাড়তি নির্ভুলতার তুলনায় প্রকৌশলের খরচ বেশি মনে হয়েছিল, আর ততদিনে ব্যবসাও DVD থেকে streaming-এ সরছিল।
আরেকটি শিক্ষা গোপনীয়তার: Narayanan আর Shmatikov (২০০৮) দেখান, “বেনামি” রেটিং ডেটার অনেক রেকর্ড অন্য উৎসের সঙ্গে মিলিয়ে চিহ্নিত করা সম্ভব, আর দ্বিতীয় প্রতিযোগিতার পরিকল্পনা আইনি অভিযোগের পর বাতিল হয়।
মূল উপলব্ধি: নাম যা-ই হোক, প্রশ্ন করুন: কোন loss, কোন ঘরে, কোন নিশ্চয়তা। আর RMSE-র সামান্য উন্নতি সবসময় ব্যবসার সিদ্ধান্ত বদলায় না।
প্র ০৩Eckart–Young বলে Ak “সবচেয়ে ভালো”। কোন অর্থে সবচেয়ে ভালো, আর কখন সেই অর্থ আপনার কাজের সঙ্গে মেলে না?
“সবচেয়ে ভালো” মানে Frobenius বা spectral norm-এ, এবং সব ঘর সমান গুরুত্বের ধরে। Frobenius ভুল সব ঘরের বর্গ-ভুলের যোগফল, অর্থাৎ একটি least-squares loss. এর কয়েকটি নীরব অনুমান আছে, আর বাস্তবে প্রায়ই সেগুলো ভাঙে।
Outlier: বর্গের কারণে একটি বড় ভুল ঘর অনেক ছোট ভুলের চেয়ে বেশি ওজন পায়। একটি ভুল sensor-এর রিডিং বা বাজারঘরে একজন ক্রেতার ভুল টাইপে দেওয়া ৫০০০ টাকার অর্ডার পুরো প্রথম singular vector-কে নিজের দিকে টেনে নিতে পারে। Robust PCA (low-rank + sparse ভাগ, Candès ও সহকর্মীদের ২০১১ সালের কাজ) এই সমস্যার একটি উত্তর।
খালি ঘর: অংশ ৯-এ দেখেছি, সব ঘর জানা না থাকলে Eckart–Young প্রযোজ্যই নয়।
অ-ঋণাত্মকতা আর ব্যাখ্যা: রেটিং, শব্দের সংখ্যা বা pixel অ-ঋণাত্মক, কিন্তু SVD-এর factor-এ ঋণাত্মক সংখ্যা থাকে, তাই “এই topic-এ এই শব্দ −০.৩” বোঝা কঠিন। NMF (non-negative matrix factorization) ভুলে কিছুটা খারাপ, কিন্তু ব্যাখ্যায় সহজ; পর্ব ৪১ (PCA-র বাইরে — kernel PCA, ICA আর NMF) আর পর্ব ৪৪ (Topic modeling)-এ আসবে।
Scale: কলামগুলোর একক আলাদা হলে (টাকা আর km) বড় এককের কলাম Frobenius ভুলে আধিপত্য করে। তাই SVD বা PCA-র আগে scaling-এর সিদ্ধান্ত আসলে “কোন ভুল কতটা গুরুত্বপূর্ণ” তার সিদ্ধান্ত।
পরের কাজ: শেষ পর্যন্ত আমরা চাই ভালো recommendation বা ভালো classifier, ভালো reconstruction নয়। LSA-তে কম σ-এর দিক কখনো কখনো খোঁজের জন্য দরকারি হতে পারে। তাই k বাছাই করুন পরের কাজের validation-এ, শুধু রাখা শক্তির শতাংশ দেখে নয়।
মূল উপলব্ধি: প্রতিটি “optimal” ফল একটি loss-এর সাপেক্ষে optimal. Loss-টি আপনার আসল লক্ষ্যের সঙ্গে মেলে কি না, সেটি গণিত বলে দেয় না।
প্র ০৪Feature-এর সংখ্যা উদাহরণের চেয়ে বেশি হলে অসীম সমাধান। Gradient descent তার মধ্যে কোনটি বাছে, আর এর সঙ্গে minimum-norm সমাধানের সম্পর্ক কী?
ধরুন linear regression-এ n < d: ২০টি উদাহরণ, ১০০টি feature. Xw = y-এর অসীম সমাধান, সবাই train ভুল শূন্য করে। কোনটি নতুন ডেটায় ভালো? তত্ত্বের একটি সুন্দর ফল: w = 0 থেকে শুরু করে বর্গ-ভুলের ওপর gradient descent চালালে (যথেষ্ট ছোট learning rate-এ) তা minimum-norm সমাধান X⁺y-তে পৌঁছায়।
কারণটি সরল। Gradient হলো Xᵀ(Xw − y), যা সবসময় X-এর row space-এ থাকে। শূন্য থেকে শুরু করলে প্রতিটি ধাপ row space-এর vector যোগ করে, তাই w কখনো null space-এ কোনো অংশ পায় না। Row space-এ একটিমাত্র সমাধান আছে, আর অংশ ৬-এ দেখেছি সেটিই X⁺y, সবচেয়ে ছোট norm-এর। অর্থাৎ কোনো regularization না লিখেও algorithm নিজে একটি পছন্দ করে নেয়। একে বলে implicit regularization বা implicit bias.
এই পর্যবেক্ষণ পর্ব ০৩-এ উল্লেখ করা double descent আর overparameterization-এর আলোচনার একটি ভিত্তি। Belkin ও সহকর্মীরা (২০১৯) আর Hastie ও সহকর্মীদের (২০২২, Annals of Statistics) বিশ্লেষণে দেখা যায়, minimum-norm interpolation-এর test ভুল d ≈ n-এর কাছে বিশাল হতে পারে (তখন X-এর ছোট σ-এ 1/σ বিস্ফোরিত হয়, অংশ ৭), আর d অনেক বড় হলে আবার কমতে পারে। সেই “শিখর”-টি ঠিক condition number-এর গল্প।
সতর্কতা: এই হুবহু ফল linear least squares-এর জন্য। Logistic regression-এ separable ডেটায় gradient descent max-margin দিকের দিকে যায় (Soudry ও সহকর্মীরা, ২০১৮), আর গভীর network-এ implicit bias কী, তা এখনো সক্রিয় গবেষণার বিষয়; সরল “minimum norm” ব্যাখ্যা সেখানে সাধারণভাবে খাটে বলে ধরে নেওয়া যায় না।
মূল উপলব্ধি: অসীম সমাধানের মধ্যে কোনটি পাবেন, তা ঠিক করে আপনার algorithm আর শুরুর বিন্দু। Pseudo-inverse সেই পছন্দের সবচেয়ে পরিষ্কার গাণিতিক রূপ।
প্র ০৫Numerical rank বের করতে tolerance কত ধরবেন? NumPy-র ডিফল্ট কি ডেটা বিশ্লেষণের জন্য সবসময় ঠিক?
np.linalg.matrix_rank-এর ডিফল্ট tolerance σmax × max(m, n) × eps, যেখানে eps ≈ 2.2 × 10−16. এটি একটি প্রশ্নের উত্তর দেয়: “float-এর rounding-এর কারণে যা শূন্য হওয়া উচিত ছিল, তা কোনগুলো?” কিন্তু ডেটা বিশ্লেষণে প্রশ্নটি প্রায়ই অন্য: “মাপার ভুলের কারণে কোন দিকগুলো আসলে তথ্য বহন করে না?”
অংশ ৭-এর উদাহরণে রাস্তার দূরত্ব km-এর ১.৩ গুণ, সঙ্গে ০.০১ মাত্রার noise. ছোট σ ০.০২৬৪, ডিফল্ট tolerance-এর তুলনায় বিশাল, তাই NumPy rank ২ বলে। গাণিতিকভাবে ঠিক; কিন্তু দ্বিতীয় দিকটি কেবল noise. পরিসংখ্যানের দৃষ্টিতে কার্যকর rank ১. তাই বাস্তব tolerance আসা উচিত ডেটার noise-এর মাত্রা থেকে: মোটা হিসাবে, m × n আকারের নিছক noise-এর matrix-এর সবচেয়ে বড় σ প্রায় s(√m + √n), যেখানে s noise-এর standard deviation; এর কাছাকাছি বা ছোট σ বিশ্বাসযোগ্য নয়। এখানে 0.01 × (√30 + √2) ≈ ০.০৬৯, আর ছোট σ ০.০২৬৪ তার নিচে। Gavish আর Donoho (২০১৪, IEEE Transactions on Information Theory) এই প্রশ্নের জন্য একটি সূত্রভিত্তিক “optimal hard threshold” প্রস্তাব করেন, যা নির্দিষ্ট noise মডেলের অনুমানে প্রযোজ্য।
আরেকটি ফাঁদ scaling: একটি কলাম টাকায় আর অন্যটি হাজার টাকায় লিখলে σ-গুলো বদলায়, condition number বদলায়, এমনকি কোন দিক “ছোট” মনে হবে তাও বদলায়। তাই standardization-এর পর সিদ্ধান্ত নেওয়া সাধারণত বেশি অর্থবহ; তবে standardization নিজেও একটি পছন্দ, যা noise-ভরা ছোট কলামকে বড় করে দেখাতে পারে।
“Condition number” শব্দটি Alan Turing ১৯৪৮ সালের একটি প্রবন্ধে ব্যবহার করেন বলে সাধারণভাবে ধরা হয়; তখন থেকেই প্রশ্নটি ছিল: হিসাবের ভুল আর ডেটার ভুল, কোনটি বড়? ব্যবহারিক নিয়ম: σ-এর গ্রাফ (scree plot) আঁকুন, noise-এর আন্দাজ নিন, আর সম্ভব হলে পরের কাজের validation দিয়ে k বাছুন।
মূল উপলব্ধি: Rank গণিতে একটি পূর্ণসংখ্যা, ডেটায় একটি সিদ্ধান্ত। Tolerance বেছে নেওয়া মানে বলা: “এর চেয়ে ছোট পার্থক্য আমি বিশ্বাস করি না।”
অনুশীলন
হিসাবগুলো খাতায় করুন, তারপর উত্তর মেলান। কোড চালানো ঐচ্ছিক।
-
হাতে SVD আর pseudo-inverse: B = [[2, 2], [1, 1]]. (ক) BᵀB আর তার eigenvalue বের করে singular value দিন। (খ) B-এর rank আর condition number কত? (গ) v1, u1 আর B⁺ বের করুন। (ঘ) Bx = (4, 2)-এর minimum-norm সমাধান কী?
(ক) BᵀB = [[2·2 + 1·1, 2·2 + 1·1], [5, 5]] = [[5, 5], [5, 5]]. trace ১০, det ০, তাই λ = ১০ আর ০; σ1 = √10 ≈ ৩.১৬২, σ2 = ০. (খ) শূন্য নয় এমন σ একটি, তাই rank ১; σmin = ০ বলে condition number অসীম (B উল্টানো যায় না)। (গ) λ = ১০-এর eigenvector: −5x + 5y = 0 → v1 = (1, 1)/√2. Bv1 = (4, 2)/√2, ভাগ √10: u1 = (4, 2)/√20 = (2, 1)/√5. B⁺ = v1u1ᵀ/σ1 = (1/√10)·(1/√2)(1/√5)·[[2, 1], [2, 1]] = [[2, 1], [2, 1]]/10 = [[0.2, 0.1], [0.2, 0.1]]. (ঘ) x⁺ = B⁺(4, 2) = (0.8 + 0.2, 0.8 + 0.2) = (1, 1). যাচাই: B(1, 1) = (4, 2). অন্য সমাধান যেমন (2, 0)-এর দৈর্ঘ্য ২, আর (1, 1)-এর √2 ≈ ১.৪১.
-
সমালোচনা ও নকশা: বাজারঘরের একজন সহকর্মী recommender বানালেন: ২০,০০০ ক্রেতা × ৩,০০০ পণ্যের রেটিং matrix-এ (জানা ঘর প্রায় ০.৫%) খালি ঘরগুলো ০ দিয়ে ভরে
np.linalg.svdচালিয়ে k = ৫০ রাখলেন, আর একই matrix-এর জানা ঘরে RMSE মেপে রিপোর্ট দিলেন “RMSE ০.৪, চমৎকার”। কী কী সমস্যা, আর আপনি কীভাবে নতুন করে সাজাবেন?সমস্যা: (১) খালি ঘরে ০ মানে মডেল শেখে “অদেখা = সবচেয়ে অপছন্দ”, আর ৯৯.৫% ঘর খালি বলে factor-গুলো মূলত কে কী দেখেছে তা শেখে, পছন্দ নয়; অনুমান ০-এর দিকে টানা থাকে। (২) ২০,০০০ × ৩,০০০ ঘন matrix (৬ কোটি double, প্রায় ৪৮০ MB) বানিয়ে পুরো SVD নেওয়া অপ্রয়োজনীয়ভাবে ব্যয়বহুল। (৩) RMSE মাপা হয়েছে সেই ঘরেই যা দিয়ে শেখা: train ভুল, আশাবাদী। (৪) কোনো baseline নেই, আর k = ৫০ কেন তা যাচাই হয়নি।
নতুন নকশা: জানা রেটিংগুলো আগে train/validation/test-এ ভাগ করুন (সময় থাকলে সময় অনুযায়ী, যেমন শেষ মাসের রেটিং test). শুধু জানা ঘরে regularized loss কমান (SGD বা ALS), গড় আর ক্রেতা ও পণ্যের bias-সহ। k আর λ validation-এ বাছুন, কয়েকটি seed-এ। Baseline: সার্বিক গড় আর গড় + bias. Test-এ একবার মাপুন। Recommendation-এর ক্ষেত্রে RMSE-র পাশে top-N মাপকাঠি (precision@k বা recall@k)-ও দিন, আর নতুন ক্রেতার cold start-এর জন্য আলাদা পরিকল্পনা রাখুন (পর্ব ৫৩)।
-
আউটপুট অনুমান করুন: চালানোর আগে লিখে ফেলুন নিচের কোড কী ছাপবে।
import numpy as np A = np.array([[0., 2.], [3., 0.], [0., 0.]]) s = np.linalg.svd(A, compute_uv=False) print(s) print(np.linalg.matrix_rank(A), round(np.linalg.cond(A), 2)) print(np.linalg.pinv(A).round(4))[3. 2.] 2 1.5 [[0. 0.3333 0. ] [0.5 0. 0. ]]AᵀA = [[9, 0], [0, 4]], তাই σ = ৩ আর ২, বড় থেকে ছোট ক্রমে। দুটিই শূন্য নয়, rank ২; condition number ৩/২ = ১.৫. A একটি “ঘর বদলানো” matrix: x1-কে ৩ গুণ করে দ্বিতীয় স্থানে আর x2-কে ২ গুণ করে প্রথম স্থানে পাঠায়। Pseudo-inverse উল্টো কাজ করে: দ্বিতীয় উপাদান ৩ দিয়ে ভাগ করে x1, প্রথমটি ২ দিয়ে ভাগ করে x2, আর তৃতীয় উপাদান (যা A কখনো তৈরি করে না) উপেক্ষা করে। আকার 3×2 থেকে 2×3. সংখ্যাগুলো আমাদের লেখার সময়ের NumPy 2.4.6-এ ডেস্কটপে চালিয়ে মেলানো।
-
Eckart–Young-এর numerical: একটি 1000 × 400 matrix-এর singular value-এর বর্গ: প্রথম চারটি ১০০, ৩৬, ৯, ১, আর বাকি সবগুলোর বর্গের মোট যোগফল ৪. (ক) Rank-২ approximation-এ Frobenius ভুলের বর্গ, আপেক্ষিক Frobenius ভুল আর spectral ভুল কত? (খ) রাখা শক্তির শতাংশ? (গ) Rank-২ রূপ জমা রাখতে কতগুলো সংখ্যা লাগে, মূল matrix-এর কত শতাংশ?
মোট ‖A‖F² = 100 + 36 + 9 + 1 + 4 = ১৫০. (ক) ভুলের বর্গ = বাদ পড়া σ²-এর যোগ = 9 + 1 + 4 = ১৪; আপেক্ষিক ভুল √(14/150) = √0.0933 ≈ ০.৩০৬, অর্থাৎ প্রায় ৩০.৬%; spectral ভুল σ3 = √9 = ৩. (খ) রাখা শক্তি (100 + 36)/150 = ৯০.৭%. লক্ষ করুন, শক্তির ৯০.৭% রাখলেও আপেক্ষিক ভুল ৩০%-এর বেশি, কারণ ভুল মাপা হয় বর্গমূলে। (গ) k(m + n + 1) = 2 × (1000 + 400 + 1) = ২,৮০২টি সংখ্যা, মূল ৪,০০,০০০-এর ০.৭%.
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি ছোট matrix A-এর SVD হাতে বের করার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- AᵀA বের করা
- AᵀA-র eigenvalue বের করে বড় থেকে ছোট সাজানো, আর σi = √λi
- প্রতিটি eigenvalue-এর একক eigenvector বের করে V-এর কলাম বানানো
- σi > 0 হলে ui = Avi / σi হিসাব করে U বানানো
- UΣVᵀ গুণ করে A ফেরে কি না যাচাই করা
A = [[3, 0], [4, 5]] matrix-এর singular value দুটি কত?
- ৫ আর ৩
- ৪৫ আর ৫
- √45 ≈ ৬.৭১ আর √5 ≈ ২.২৪
- √50 আর ০
একটি matrix-এর singular value ১০, ৫, ২ আর ১. সবচেয়ে ভালো rank-২ approximation-এর Frobenius ভুলের বর্গ ‖A − A2‖F² কত?
- ৩
- ৫
- ২
- ১২৫
একটি সমীকরণ x1 + 2x2 + 2x3 = 9-এর অসীম সমাধানের মধ্যে Moore–Penrose pseudo-inverse কোনটি দেয়?
- (9, 0, 0)
- (3, 1, 2)
- (1, 1, 1)
- (1, 2, 2)
একটি design matrix X-এর singular value ৪০ আর ০.০৪. Normal equations-এ XᵀX ব্যবহার করলে XᵀX-এর condition number কত হবে?
- ১০৬
- ১,০০০
- প্রায় ৩১.৬
- ১.৬
Sparse TF-IDF matrix-এ PCA-র বদলে scikit-learn-এর TruncatedSVD ব্যবহারের প্রধান কারণ কোনটি?
- TruncatedSVD অ-রৈখিক গঠন ধরতে পারে
- TruncatedSVD-এর explained variance সবসময় PCA-র চেয়ে বেশি
- TruncatedSVD গড় বাদ দেয় না, তাই matrix sparse থাকে আর memory বাঁচে
- TruncatedSVD সবসময় হুবহু একই ফল দেয়, random_state লাগে না
বাস্তব recommender-এ রেটিং matrix-এর সরাসরি SVD না নিয়ে “শুধু জানা ঘরে ভুল কমানো” matrix factorization (যেমন Funk SVD বা ALS) কেন ব্যবহার হয়?
- রেটিং matrix বর্গাকার নয় বলে তার SVD নেই
- বেশিরভাগ রেটিং অজানা, আর খালি ঘর ০ ধরলে ভুল জিনিস শেখা হয়
- SVD কখনো low-rank রূপ দিতে পারে না
- Matrix factorization সবসময় অ-ঋণাত্মক factor দেয়
NumPy-তে U, s, Vt = np.linalg.svd(A) চালানোর পর নিচের কোন কথাটি সঠিক?
Vt-এর সারিগুলো right singular vector, আরsবড় থেকে ছোট সাজানো- Vt-এর কলামগুলো right singular vector
- s একটি পূর্ণ m × n Σ matrix
- U-এর প্রথম কলামের সব সংখ্যা সবসময় ধনাত্মক
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- A = UΣVᵀ সব matrix-এর আছে: ঘোরানো–টানা–ঘোরানো; Avi = σiui, আর একক বৃত্ত হয় ellipse যার অর্ধ-অক্ষ σi.
- V = AᵀA-র eigenvector, σi = √λi, ui = Avi/σi; চিহ্ন জোড়ায় জোড়ায় স্বাধীন। Rank = শূন্য নয় এমন σ-এর সংখ্যা, ‖A‖2 = σ1, ‖A‖F² = Σσi².
- Eckart–Young: Ak সবচেয়ে ভালো rank-k রূপ; ভুলের বর্গ = বাদ পড়া σ²-এর যোগ, spectral ভুল = σk+1.
- A⁺ = VΣ⁺Uᵀ: least squares-এর minimum-norm সমাধান; κ = σmax/σmin, আর κ(AᵀA) = κ². ছোট σ ছাঁটা বা ridge অস্থিরতা কমায়।
- Sparse লেখায় TruncatedSVD (গড় বাদ দেয় না), আর recommender-এর “SVD” আসলে শুধু জানা ঘরে regularized matrix factorization.
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ১০ · Positive definite matrix — quadratic form আর covarianceপরবর্তী পর্বQuadratic form, PD পরীক্ষা, Cholesky আর covariance-এর জ্যামিতি।
- মেশিন লার্নিং · কোর্স হোম৭৭টি পর্ব১৪টি মডিউলের পুরো রোডম্যাপ।
- AI-এর ভিত্তি · পর্ব ১৭ · NumPy-তে linear algebraআগের ভিত্তিsolve বনাম inverse, condition number আর least squares-এর প্রথম পরিচয়।
- AI-এর ভিত্তি · পর্ব ৬৫ · Clustering আর PCAআগের ভিত্তিSVD দিয়ে নিজে PCA, explained variance আর reconstruction.
- AI-এর ভিত্তি · পর্ব ৬৯ · Embedding আর vector searchআগের ভিত্তিTruncated SVD দিয়ে LSA আর বাংলা রিভিউয়ে খোঁজ।
- Data Structures & Algorithmsপাশের ট্র্যাকAlgorithm-এর খরচ আর proof-এর অভ্যাস, যা numerical algorithm পড়তে কাজে লাগে।