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

Matrix-এর জ্যামিতি — projection, rotation আর least squares

The Geometry of Matrices — Linear Maps, Determinants, Orthogonal Projection, Least Squares and QR

Taught in Bangla, this lesson treats matrices as maps: five basic transformations, composition order, determinant as area scaling and orthogonal matrices. It derives orthogonal projection, shows least squares as projecting y onto the column space, works Gram–Schmidt and QR by hand and in NumPy, and compares normal equations, QR and lstsq on conditioning.

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

  • Matrix-কে linear map হিসেবে দেখা: rotation, scaling, shear, reflection আর projection-এর ছবি, আর কলাম দেখে map চেনা
  • পরপর দুটি map মানে matrix গুণ; determinant মানে ক্ষেত্রফল আর দিকের পরিবর্তন; inverse মানে map উল্টে দেওয়া
  • Orthogonal matrix কেন দৈর্ঘ্য আর কোণ অক্ষত রাখে, আর কেন সংখ্যার হিসাবে এটি সবচেয়ে নিরাপদ
  • একটি রেখায় আর একটি subspace-এ orthogonal projection: P = A(AᵀA)−1Aᵀ নিজে বের করা, P² = P আর Pᵀ = P প্রমাণ
  • Least squares-কে projection হিসেবে পড়া, hat matrix আর leverage, তারপর Gram–Schmidt হাতে করে QR দিয়ে least squares সমাধান

AI-এর ভিত্তি কোর্সের পর্ব ১৭-এ NumPy-তে matrix গুণ, solve, lstsq আর condition number শিখেছেন, পর্ব ৩৩-এ norm, dot product আর কোণ, আর পর্ব ৫৮-এ normal equations আর “y-এর ছায়া” হিসেবে linear regression-এর প্রথম ছবি। পর্ব ০৬ (Vector space — span, basis, linear independence আর rank) দিয়েছে column space আর rank-এর ভাষা। এই পর্বের প্রশ্ন: একটি matrix জ্যামিতিকভাবে কী করে, আর least squares সমাধানকারী software ভেতরে কেন QR ব্যবহার করে? চালানো যায় এমন চারটি ব্লক পাতাতেই চলে (ব্রাউজারের Python, NumPy, SciPy আর matplotlib); প্রথমবার প্যাকেজ নামতে কয়েক সেকেন্ড লাগে।

১ · Matrix মানে একটি map — বর্গাকার জাল কীভাবে বাঁকে

একটি রাবারের চাদরে গ্রাফ পেপারের মতো ছক আঁকা। চাদরের এক কোণ টেবিলে পিন দিয়ে আটকানো, আর আপনি চাদরটি টেনে, ঘুরিয়ে বা তির্যক করে আবার পেতে দিলেন, তবে এমনভাবে যে ছকের প্রতিটি রেখা সোজাই থাকে আর সমান্তরাল রেখা সমান্তরালই থাকে। এখন ছকের মাত্র দুটি তীর, “ডানে এক ঘর” আর “ওপরে এক ঘর”, কোথায় গেল জানলেই চাদরের প্রতিটি বিন্দুর নতুন জায়গা বলে দেওয়া যায়। একটি ২×২ matrix ঠিক এই দুটি তথ্য।

একটি function f যদি দুটি নিয়ম মানে, f(u + v) = f(u) + f(v) আর f(cu) = c·f(u) (যেকোনো vector u, v আর সংখ্যা c-এর জন্য), তাকে বলে linear map বা linear transformation. নিয়ম দুটির সরাসরি ফল: origin নড়ে না (f(0) = 0), সোজা রেখা সোজা থাকে, আর সমান ফাঁকের বিন্দু সমান ফাঁকেই থাকে। প্রতিটি linear map-কে একটি matrix দিয়ে লেখা যায়, আর প্রতিটি matrix একটি linear map.

কেন? দুই মাত্রায় যেকোনো vector x = (x1, x2) লেখা যায় x1e1 + x2e2, যেখানে e1 = (1, 0) আর e2 = (0, 1) হলো standard basis. Linearity বলে f(x) = x1f(e1) + x2f(e2). তাই f(e1) আর f(e2)-কে পাশাপাশি কলাম হিসেবে বসালেই matrix A পাওয়া যায়, আর Ax ঠিক এই weighted যোগফল:

Ax = x1 · (A-এর ১ম কলাম) + x2 · (A-এর ২য় কলাম)

AI-এর ভিত্তি কোর্সের পর্ব ১৭-এ Ax পড়েছিলেন “সারি ধরে ধরে”, প্রতিটি সারির সঙ্গে x-এর dot product. এটি “কলাম ধরে” পড়া: একই হিসাব, কিন্তু জ্যামিতির জন্য এটিই বেশি কাজের। নিচের ছবিতে A = [[2, 1], [1, 2]] (সারি ধরে লেখা; NumPy-র মতো)। প্রথম কলাম (2, 1): e1 সেখানে যায়; দ্বিতীয় কলাম (1, 2): e2 সেখানে। একক বর্গটি একটি parallelogram হয়ে যায়, আর বর্গের কোণ (1, 1) যায় (2, 1) + (1, 2) = (3, 3)-এ।

কলামই বলে দেয় map কী করে: A = [[2, 1], [1, 2]]

Grey: unit square with e1, e2. Blue: its image, spanned by the columns (2, 1) and (1, 2). Area grows from 1 to det A = 3

A 2 by 2 matrix maps the unit square to a parallelogram e₁e₂ Ae₁ = (2, 1) Ae₂ = (1, 2) A(1, 1) = (3, 3) ক্ষেত্রফল: 1 → 3 det A = 2·2 − 1·1 = 3

ML-এ এই ছবি সব জায়গায়। একটি design matrix X আর weight w-এর গুণ Xw হলো X-এর কলামগুলোর (feature-গুলোর) weighted যোগফল; neural network-এর একটি layer Wx + b হলো একটি linear map আর তারপর একটি সরণ, যাকে বলে affine map. সরণ b origin সরায় বলে affine map নিজে linear নয়, কিন্তু তার ভেতরের জ্যামিতি পুরোটাই W-এর।

২ · পাঁচটি মৌলিক map — rotation, scaling, shear, reflection, projection

যেকোনো ২×২ matrix কয়েকটি সরল map জোড়া দিয়ে তৈরি হতে পারে (পর্ব ০৯ (SVD — low-rank approximation, pseudo-inverse আর recommender-এর ভিত্তি) দেখাবে যে ঠিক তিনটিই যথেষ্ট: ঘোরানো, টানা, আবার ঘোরানো)। তাই মৌলিক পাঁচটি চিনে রাখা দরকার। প্রতিটির কলাম দেখে বলুন e1 আর e2 কোথায় গেল।

MapMatrixকী করেdet
Rotation θ[[cos θ, −sin θ], [sin θ, cos θ]]origin ঘিরে θ কোণে ঘড়ির কাঁটার উল্টো দিকে ঘোরায়; দৈর্ঘ্য অপরিবর্তিত1
Scaling[[s1, 0], [0, s2]]x-অক্ষ বরাবর s1 গুণ, y-অক্ষ বরাবর s2 গুণ টানেs1s2
Shear[[1, k], [0, 1]]প্রতিটি বিন্দুকে তার উচ্চতার k গুণ ডানে ঠেলে; বর্গ তির্যক হয়, তাসের গাদা হেলানোর মতো1
Reflection (y = x রেখায়)[[0, 1], [1, 0]]আয়নার মতো উল্টায়: x আর y অদলবদল−1
Projection (x-অক্ষে)[[1, 0], [0, 0]]উচ্চতা মুছে ফেলে, সব বিন্দু অক্ষের ওপর ছায়া হয়0

Rotation-এর matrix মুখস্থ না করে কলাম থেকে বের করুন: e1 = (1, 0)-কে θ কোণে ঘোরালে একক বৃত্তের ওপর (cos θ, sin θ); e2 = (0, 1)-কে ঘোরালে (−sin θ, cos θ). θ = 30° হলে cos θ ≈ 0.866, sin θ = 0.5. নিচের ব্লক একক বর্গ আর ২৫টি বিন্দুর একটি জালকে পাঁচটি map-এ পাঠায়, প্রতিটির ছবি আঁকে, আর determinant-এর পাশে ছবির আসল ক্ষেত্রফল (বহুভুজের shoelace সূত্রে মাপা) ছাপে। সবুজ তীর প্রতিটি ছবিতে e1-এর গন্তব্য, অর্থাৎ matrix-এর প্রথম কলাম।

import numpy as np
import matplotlib.pyplot as plt

t = np.pi / 6                                       # 30 ডিগ্রি
maps = {
    "rotate 30°": np.array([[np.cos(t), -np.sin(t)], [np.sin(t), np.cos(t)]]),
    "scale (2, 0.75)": np.array([[2.0, 0.0], [0.0, 0.75]]),
    "shear k = 1": np.array([[1.0, 1.0], [0.0, 1.0]]),
    "reflect in y = x": np.array([[0.0, 1.0], [1.0, 0.0]]),
    "project onto x-axis": np.array([[1.0, 0.0], [0.0, 0.0]]),
}
sq = np.array([[0, 1, 1, 0], [0, 0, 1, 1]])          # একক বর্গের ৪টি কোণ, কলামে
g = np.linspace(0, 1, 5)
grid = np.array([[a, b] for a in g for b in g]).T    # 2 x 25 বিন্দুর জাল

def area(P):                                        # shoelace সূত্রে বহুভুজের ক্ষেত্রফল
    x, y = P
    return 0.5 * abs(x @ np.roll(y, -1) - y @ np.roll(x, -1))

fig, axes = plt.subplots(2, 3, figsize=(7.2, 5.2), layout="constrained")
for ax, (name, A) in zip(axes.flat, maps.items()):
    img, d = A @ sq, np.linalg.det(A)
    ax.fill(*sq, color="0.85", label="unit square")
    ax.fill(*img, color="C0", alpha=0.45, label="image")
    ax.scatter(*(A @ grid), s=7, color="C3")
    ax.arrow(0, 0, *A[:, 0], color="C2", width=0.02, length_includes_head=True)
    ax.set_title(f"{name}\ndet = {d:.2f}", fontsize=10)
    ax.set_aspect("equal"); ax.set_xlim(-0.8, 2.2); ax.set_ylim(-0.6, 1.6)
    side = "উল্টে যায়" if d < 0 else ("চ্যাপ্টা" if np.isclose(d, 0) else "ঠিক থাকে")
    print(f"{name:20s} det = {d:6.3f}  ক্ষেত্রফল = {area(img):.3f}  দিক: {side}")
axes.flat[5].axis("off")
axes.flat[0].legend(fontsize=8, loc="upper left")
plt.show()

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

rotate 30°           det =  1.000  ক্ষেত্রফল = 1.000  দিক: ঠিক থাকে
scale (2, 0.75)      det =  1.500  ক্ষেত্রফল = 1.500  দিক: ঠিক থাকে
shear k = 1          det =  1.000  ক্ষেত্রফল = 1.000  দিক: ঠিক থাকে
reflect in y = x     det = -1.000  ক্ষেত্রফল = 1.000  দিক: উল্টে যায়
project onto x-axis  det =  0.000  ক্ষেত্রফল = 0.000  দিক: চ্যাপ্টা

পাঁচটি সারিতেই |det| আর মাপা ক্ষেত্রফল হুবহু এক: determinant ঠিক এই কথাই বলে, map একক বর্গের ক্ষেত্রফল কত গুণ করে। Shear বর্গকে তির্যক করেছে, কিন্তু ক্ষেত্রফল ১-ই আছে (ভূমি আর উচ্চতা বদলায়নি); scaling ২ × ০.৭৫ = ১.৫ গুণ করেছে। Reflection-এ ক্ষেত্রফল ১, কিন্তু det −১: চিহ্নটি বলে বর্গটি “উল্টে” গেছে, ঘড়ির কাঁটার উল্টো দিকে ঘোরা কোণগুলোর ক্রম এখন ঘড়ির কাঁটার দিকে। Projection-এ পুরো বর্গ x-অক্ষের একটি রেখাংশে চ্যাপ্টা, ক্ষেত্রফল ০; ছবিতে ২৫টি লাল বিন্দুর মাত্র ৫টি আলাদা জায়গা দেখা যায়, কারণ একই কলামের পাঁচটি বিন্দু একই ছায়ায় পড়ে।

৩ · পরপর দুটি map মানে matrix গুণ — আর ক্রম কেন জরুরি

প্রথমে R প্রয়োগ, তারপর S: ফল S(Rx). Matrix গুণের সংজ্ঞাই এমনভাবে বানানো যাতে (SR)x = S(Rx) হয়; অর্থাৎ matrix গুণ মানে map-এর composition. লেখার ক্রম আর প্রয়োগের ক্রম উল্টো: SR-এ ডানেরটি আগে। কলামের ভাষায়: SR-এর প্রথম কলাম হলো S(R-এর প্রথম কলাম), অর্থাৎ e1 আগে R-এ, তারপর S-এ।

Composition of a rotation and a shear applied to one point 02424 x = (2, 1)
  1. শুরুর বিন্দু। x = (2, 1), অর্থাৎ ২ ঘর ডানে আর ১ ঘর ওপরে: x = 2e1 + 1e2.
  2. আগে rotation, 90°. cos 90° = 0, sin 90° = 1, তাই R = [[0, −1], [1, 0]]: e1 যায় (0, 1)-এ, e2 যায় (−1, 0)-এ। Rx = 2·(0, 1) + 1·(−1, 0) = (−1, 2).
  3. তারপর shear, k = 1. S = [[1, 1], [0, 1]] প্রতিটি বিন্দুকে তার উচ্চতার সমান ডানে ঠেলে। (−1, 2)-এর উচ্চতা ২, তাই S(−1, 2) = (−1 + 2, 2) = (1, 2).
  4. দুটি মিলে একটি matrix. M = SR = [[1·0 + 1·1, 1·(−1) + 1·0], [0·0 + 1·1, 0·(−1) + 1·0]] = [[1, −1], [1, 0]]. Mx = (2 − 1, 2 + 0) = (1, 2): দুই ধাপের ফল এক গুণেই। det M = 1·0 − (−1)·1 = 1 = det S × det R.
  5. উল্টো ক্রম। আগে shear: S(2, 1) = (3, 1); তারপর rotation: R(3, 1) = (−1, 3). অর্থাৎ RS = [[0, −1], [1, 1]] আর RSx = (−1, 3) ≠ (1, 2). Matrix গুণ সাধারণত commutative নয়: AB ≠ BA.
  6. উল্টে ফেরা। SR-কে undo করতে উল্টো ক্রমে উল্টো map: আগে S−1 = [[1, −1], [0, 1]] (বাঁয়ে ঠেলা): (1, 2) → (−1, 2); তারপর R−1 = −90° rotation = [[0, 1], [−1, 0]]: (−1, 2) → (2, 1). তাই (SR)−1 = R−1S−1.

শেষ ধাপের নিয়মটি রোজকার: আগে মোজা, তারপর জুতা পরলে খোলার সময় আগে জুতা, তারপর মোজা। এবার ML-এর একটি গুরুত্বপূর্ণ ফল: দুটি linear layer পরপর বসালে W2(W1x) = (W2W1)x, অর্থাৎ একটিই linear map. একশটি linear layer-ও একটি matrix-এর বেশি কিছু বলতে পারে না। এজন্যই neural network-এ প্রতিটি layer-এর পর ReLU-এর মতো একটি non-linear function বসে; AI-এর ভিত্তি কোর্সের পর্ব ৬৮-এ XOR-এর উদাহরণে এটি দেখেছেন। আরেকটি ব্যবহারিক কথা: বাজারঘরের ডেলিভারি অ্যাপে মানচিত্রের দিক ঘোরানো, জুম আর হেলানো পরপর হলে প্রতিটি বিন্দুতে তিনবার গুণ না করে আগে তিনটি matrix গুণ করে একটি M বানিয়ে নেওয়া সস্তা।

৪ · Determinant — ক্ষেত্রফল কত গুণ, আর দিক উল্টায় কি না

২×২ matrix A = [[a, b], [c, d]]-এর determinant det A = ad − bc. জ্যামিতিক অর্থ: A-এর কলাম দুটি (a, c) আর (b, d) যে parallelogram গড়ে, তার চিহ্নসহ ক্ষেত্রফল। একক বর্গের ক্ষেত্রফল ১, তাই det A বলে A যেকোনো আকৃতির ক্ষেত্রফল কত গুণ করে, কারণ যেকোনো আকৃতিকে ছোট ছোট বর্গে ভাঙা যায় আর linear map প্রতিটি বর্গকে একই অনুপাতে বদলায়। তিন মাত্রায় det হলো একক ঘনকের আয়তন কত গুণ হলো।

ad − bc কেন? একটি ছোট প্রমাণ। ধরুন a ≠ 0. দ্বিতীয় কলাম থেকে প্রথম কলামের (b/a) গুণ বাদ দিলে, অর্থাৎ একটি shear করলে, parallelogram-এর ভূমি একই থাকে আর উচ্চতাও (shear ক্ষেত্রফল বদলায় না, অংশ ২-এর আউটপুট)। নতুন দ্বিতীয় কলাম (0, d − bc/a). এখন parallelogram-এর একটি বাহু খাড়া, দৈর্ঘ্য d − bc/a, আর অন্য বাহুর অনুভূমিক বিস্তার a. ক্ষেত্রফল = a × (d − bc/a) = ad − bc. চিহ্নের অর্থ: প্রথম কলাম থেকে দ্বিতীয় কলামে যেতে ঘড়ির কাঁটার উল্টো দিকে ঘুরতে হলে ধনাত্মক, ঘড়ির কাঁটার দিকে হলে ঋণাত্মক।

তিনটি নিয়ম, প্রতিটির জ্যামিতিক কারণসহ:

  • det(AB) = det A · det B. আগে B ক্ষেত্রফল det B গুণ করে, তারপর A আরও det A গুণ। অংশ ৩-এ det(SR) = ১ × ১ = ১.
  • det A = 0 ⇔ A-এর inverse নেই। ক্ষেত্রফল শূন্য মানে plane একটি রেখায় (বা বিন্দুতে) চ্যাপ্টা হয়েছে; অনেক বিন্দু একই জায়গায় পড়ে, তাই কোনটি থেকে এসেছিল আর বলা যায় না। এমন matrix-কে বলে singular; এর কলামগুলো linearly dependent, আর rank মাত্রার চেয়ে কম (পর্ব ০৬-এর ভাষা)।
  • det(A−1) = 1 / det A. Inverse map-কে undo করে, তাই ক্ষেত্রফলের গুণকও উল্টায়।

২×২-এর inverse: A−1 = (1 / det A) · [[d, −b], [−c, a]]. পাঁচটি map-এর inverse জ্যামিতি দিয়েই বলা যায়: rotation θ-এর inverse rotation −θ; scaling (s1, s2)-এর inverse (1/s1, 1/s2); shear k-এর inverse shear −k; reflection নিজেই নিজের inverse (দুবার আয়নায় উল্টালে আগের মতো); আর projection-এর inverse নেই, কারণ মোছা উচ্চতা ফেরানোর উপায় নেই।

একটি পূর্ণ numerical. A = [[3, 1], [2, 4]]. det A = 3·4 − 1·2 = 10: যেকোনো আকৃতির ক্ষেত্রফল ১০ গুণ, দিক অক্ষত। A−1 = (1/10)·[[4, −1], [−2, 3]] = [[0.4, −0.1], [−0.2, 0.3]]. যাচাই: A·A−1-এর প্রথম সারি (3·0.4 + 1·(−0.2), 3·(−0.1) + 1·0.3) = (1, 0). b = (5, 10) হলে Ax = b-এর সমাধান x = A−1b = (0.4·5 − 0.1·10, −0.2·5 + 0.3·10) = (1, 2); যাচাই: 3·1 + 1·2 = 5, 2·1 + 4·2 = 10.

ছোট determinant মানেই প্রায়-singular নয়। ১০০ মাত্রায় 0.1·I (প্রতিটি অক্ষ ১০ ভাগের ১ ভাগ)-এর det = 0.1100 = ১০−১০০, অথচ matrix-টি নিখুঁত আচরণের: inverse হলো 10·I, আর condition number ঠিক ১. উল্টো দিকে [[1, 1], [1, 1.000001]]-এর det মাত্র ১০−৬ হলেও কলাম দুটি প্রায় একই দিকে, তাই সত্যিই বিপজ্জনক। Determinant মাপের (scale) ওপর নির্ভর করে; “কতটা চ্যাপ্টা” মাপতে condition number বা singular value দেখুন (পর্ব ০৯)। AI-এর ভিত্তি কোর্সের পর্ব ১৭-এ inv-এর বদলে solve কেন, সেটিও এই কারণে।

৫ · Orthogonal matrix — দৈর্ঘ্য আর কোণ অক্ষত

পাঁচটি map-এর মধ্যে rotation আর reflection বিশেষ: এরা আকৃতি বদলায় না, শুধু ঘোরায় বা উল্টায়। এমন matrix Q-এর কলামগুলো একে অপরের সঙ্গে লম্ব আর প্রতিটির দৈর্ঘ্য ১, অর্থাৎ orthonormal. কলামের dot product-গুলো একসঙ্গে লিখলে শর্তটি এক লাইনে আসে:

QᵀQ = I   ⇔   Q−1 = Qᵀ

এখানে Qᵀ হলো Q-এর transpose (সারি আর কলাম অদলবদল), আর I হলো identity matrix. QᵀQ-এর (i, j) ঘর = i-তম কলাম · j-তম কলাম; i = j হলে ১ (দৈর্ঘ্য ১), না হলে ০ (লম্ব)। এমন বর্গ matrix-কে বলে orthogonal matrix. তিনটি ফল, প্রতিটি এক লাইনে প্রমাণ হয়:

  • দৈর্ঘ্য অক্ষত: ‖Qx‖² = (Qx)ᵀ(Qx) = xᵀQᵀQx = xᵀx = ‖x‖².
  • কোণ অক্ষত: (Qx)·(Qy) = xᵀQᵀQy = x·y; দৈর্ঘ্য আর dot product দুটিই না বদলালে cosine-ও বদলায় না।
  • det = ±1: det(QᵀQ) = (det Q)² = det I = 1. +১ হলে rotation, −১ হলে তাতে একটি reflection মেশানো।

সংখ্যার হিসাবের দিক থেকে orthogonal matrix সবচেয়ে নিরাপদ: inverse বের করতে কোনো ভাগ লাগে না (শুধু transpose), আর condition number ঠিক ১, তাই float-এর ছোট ভুল একটুও বড় হয় না। এজন্যই ভালো algorithm চেষ্টা করে কাজের বড় অংশ orthogonal matrix দিয়ে করতে। একটি গুরুত্বপূর্ণ উদাহরণ Householder reflection: একক vector u-এর জন্য H = I − 2uuᵀ. এটি u-এর লম্ব সমতলে আয়না: u-এর দিকের অংশ উল্টে দেয়, বাকি সব অক্ষত। যাচাই করুন: Hᵀ = H, আর H² = I − 4uuᵀ + 4u(uᵀu)uᵀ = I. অংশ ৮-এ দেখবেন QR-এর software এই আয়নাগুলো দিয়েই চলে।

ML-এর জন্য এর মানে: feature space একটি orthogonal matrix দিয়ে ঘোরালে সব দূরত্ব, সব dot product আর সব cosine হুবহু একই থাকে। তাই k-NN, k-means বা cosine similarity-ভিত্তিক খোঁজ ঘোরানো ডেটায় একই উত্তর দেয়। উল্টো দিকে, একটি embedding-এর আলাদা আলাদা অক্ষের (যেমন “৫৭ নম্বর মাত্রা”) নিজস্ব কোনো অর্থ ধরে নেওয়া বিপজ্জনক: পুরো space ঘুরিয়ে দিলেও model-এর আচরণ একই থাকত। ভাবনার প্রশ্ন ৫-এ এর একটি ব্যবহার আছে।

৬ · Orthogonal projection — একটি রেখায়, তারপর একটি subspace-এ

আপনি একটি ধানক্ষেতের মাঝে দাঁড়িয়ে, পাশ দিয়ে একটি সোজা পাকা রাস্তা গেছে। রাস্তার কোন বিন্দু আপনার সবচেয়ে কাছে? যেখানে আপনার থেকে রাস্তায় টানা দাগটি রাস্তার সঙ্গে ঠিক লম্ব। একটু আগে বা পরে গেলে সেই দাগ একটি সমকোণী ত্রিভুজের অতিভুজ হয়ে যায়, যা সবসময় লম্বের চেয়ে লম্বা। Orthogonal projection এই “সবচেয়ে কাছের বিন্দু”-টি খোঁজে।

রেখায় projection. রেখাটি origin দিয়ে vector a-এর দিকে। খুঁজছি p = c·a, এমন c যাতে ভুলের অংশ r = b − ca রেখার সঙ্গে লম্ব: aᵀ(b − ca) = 0 ⇒ aᵀb − c·aᵀa = 0 ⇒ c = aᵀb / aᵀa. তাই p = a(aᵀb)/(aᵀa) = [aaᵀ / (aᵀa)] b, আর projection matrix P = aaᵀ / (aᵀa). aaᵀ একটি কলাম আর একটি সারির গুণ, একটি পূর্ণ matrix (একে বলে outer product).

Projecting a vector onto a line a = (4, 2) b = (1, 3) p = (2, 1) r = (−1, 2) (3, 1.5): 2.5√5 ≈ 2.236
  1. প্রশ্ন। b = (1, 3) রেখার বাইরে। রেখাটি origin দিয়ে a = (4, 2)-এর দিকে। রেখার কোন বিন্দু p, b-এর সবচেয়ে কাছে?
  2. দুটি dot product. aᵀb = 4·1 + 2·3 = 10; aᵀa = 4² + 2² = 20.
  3. সহগ আর ছায়া। c = aᵀb / aᵀa = 10/20 = 0.5, তাই p = 0.5·(4, 2) = (2, 1).
  4. লম্ব যাচাই। r = b − p = (1 − 2, 3 − 1) = (−1, 2). aᵀr = 4·(−1) + 2·2 = 0: ভুলের অংশ রেখার সঙ্গে ঠিক লম্ব।
  5. সত্যিই সবচেয়ে কাছে? ‖r‖ = √(1 + 4) = √5 ≈ 2.236. রেখার অন্য একটি বিন্দু 0.75a = (3, 1.5) থেকে b-এর দূরত্ব √(2² + 1.5²) = √6.25 = 2.5, বেশি। পিথাগোরাস: ‖b‖² = 10 = ‖p‖² + ‖r‖² = 5 + 5.
  6. Projection matrix. P = aaᵀ / aᵀa = (1/20)·[[16, 8], [8, 4]] = [[0.8, 0.4], [0.4, 0.2]]. Pb = (0.8 + 1.2, 0.4 + 0.6) = (2, 1). P² = P (একবার ছায়া ফেললে দ্বিতীয়বার কিছু বদলায় না), Pᵀ = P, det P = 0.16 − 0.16 = 0, আর a-এর দৈর্ঘ্য বদলালেও P একই থাকে: P রেখাটির, নির্দিষ্ট vector-এর নয়।

Subspace-এ projection. এবার A একটি m×k matrix, যার k-টি কলাম linearly independent, আর তাদের column space col(A) হলো একটি k-মাত্রার subspace (তিন মাত্রায় k = 2 হলে একটি সমতল)। col(A)-এর প্রতিটি বিন্দু Ax̂ আকারের। একই যুক্তি: ভুলের অংশ r = b − Ax̂ যেন A-এর প্রতিটি কলামের সঙ্গে লম্ব হয়, অর্থাৎ Aᵀ(b − Ax̂) = 0:

AᵀA x̂ = Aᵀb  ⇒  x̂ = (AᵀA)−1Aᵀb  ⇒  p = Ax̂ = A(AᵀA)−1Aᵀ b

P = A(AᵀA)−1Aᵀ

k = 1 হলে A = a, আর এটি ঠিক আগের aaᵀ / aᵀa. AᵀA-এর inverse থাকে ঠিক তখনই, যখন A-এর কলামগুলো independent (পর্ব ০৬: full column rank). এবার দুটি মূল ধর্মের প্রমাণ:

  • P² = P (idempotent): P·P = A(AᵀA)−1(AᵀA)(AᵀA)−1Aᵀ = A(AᵀA)−1Aᵀ = P. মাঝের AᵀA তার inverse-এর সঙ্গে কেটে যায়।
  • Pᵀ = P (symmetric): (AᵀA) symmetric, তাই তার inverse-ও; Pᵀ = A((AᵀA)−1)ᵀAᵀ = P. এই দ্বিতীয় ধর্মটিই projection-কে “orthogonal” করে; শুধু P² = P হলে তির্যক (oblique) projection-ও হতে পারত।
  • সবচেয়ে কাছে: col(A)-এর যেকোনো q-এর জন্য b − q = r + (p − q), আর p − q col(A)-এ বলে r-এর সঙ্গে লম্ব। পিথাগোরাসে ‖b − q‖² = ‖r‖² + ‖p − q‖² ≥ ‖r‖², সমান শুধু q = p হলে।

আরও দুটি দরকারি তথ্য। (১) I − P-ও একটি projection, col(A)-এর লম্ব দিকে: (I − P)b = r. (২) Q-এর কলাম orthonormal হলে QᵀQ = I, তাই সূত্রটি ছোট হয়ে P = QQᵀ: কোনো inverse লাগে না। অংশ ৮-এর QR এই সুবিধাটিই কাজে লাগায়। নিচের ব্লক তিন মাত্রায় একটি সমতলে projection করে প্রতিটি দাবি সংখ্যায় যাচাই করে।

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

A = np.array([[1.0, 3.0],
              [2.0, 3.0],
              [2.0, 0.0]])         # দুটি কলাম: R³-এর ভেতর একটি সমতল
b = np.array([3.0, 0.0, 9.0])     # সমতলের বাইরের একটি vector

P = A @ np.linalg.inv(A.T @ A) @ A.T     # শেখার জন্য সূত্রটি হুবহু
p = P @ b                                # সমতলে b-এর ছায়া
r = b - p                                # ছায়া থেকে b পর্যন্ত লম্ব অংশ
print("P =\n", P)
print("p =", p, "  r =", r)
print("P @ P == P ?", np.allclose(P @ P, P))
print("P.T == P ?", np.allclose(P.T, P))
print("A.T @ r =", A.T @ r, "← দুটি কলামের সঙ্গেই লম্ব")
print("rank(P) =", np.linalg.matrix_rank(P), "  trace(P) =", round(np.trace(P), 6))

rng = np.random.default_rng(42)          # সমতলের আরও ৫টি এলোমেলো বিন্দু
others = A @ rng.normal(size=(2, 5))
dist = np.linalg.norm(b[:, None] - others, axis=0)
print("‖r‖ =", round(np.linalg.norm(r), 3), "  অন্য বিন্দুগুলোর দূরত্ব:", dist)

M = np.eye(3) - P                        # লম্ব দিকের projection
print("(I − P) b =", M @ b, "  (I − P)² == I − P ?", np.allclose(M @ M, M))
print("P সমতলের vector বদলায় না:", np.allclose(P @ A, A))

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

P =
 [[ 0.556  0.444 -0.222]
 [ 0.444  0.556  0.222]
 [-0.222  0.222  0.889]]
p = [-0.333  3.333  7.333]   r = [ 3.333 -3.333  1.667]
P @ P == P ? True
P.T == P ? True
A.T @ r = [0. 0.] ← দুটি কলামের সঙ্গেই লম্ব
rank(P) = 2   trace(P) = 2.0
‖r‖ = 5.0   অন্য বিন্দুগুলোর দূরত্ব: [11.174 11.79   8.171  7.647 16.267]
(I − P) b = [ 3.333 -3.333  1.667]   (I − P)² == I − P ? True
P সমতলের vector বদলায় না: True

P-এর ঘরগুলো নবম ভাগে: 0.556 = 5/9, 0.444 = 4/9, 0.222 = 2/9, 0.889 = 8/9. ছায়া p = (−1/3, 10/3, 22/3), আর লম্ব অংশ r = (10/3, −10/3, 5/3), দৈর্ঘ্য ঠিক ৫. A.T @ r শূন্য: r দুটি কলামের সঙ্গেই লম্ব, সূত্রের ভিত্তি এটাই। P ৩×৩ হলেও rank ২, কারণ সে পুরো space-কে একটি সমতলে চ্যাপ্টা করে; trace(P) = 5/9 + 5/9 + 8/9 = 2 = সমতলের মাত্রা। একটি projection-এর trace সবসময় তার rank-এর সমান, যা অংশ ৭-এ “parameter-এর সংখ্যা” হিসেবে ফিরে আসবে। সমতলের পাঁচটি এলোমেলো বিন্দুর দূরত্ব ৭.৬৫ থেকে ১৬.২৭, সবগুলোই ৫-এর বেশি। শেষ লাইন: সমতলে আগে থেকেই থাকা vector (A-এর কলাম) P-তে বদলায় না। কোডে inv শুধু সূত্রটি চোখে দেখানোর জন্য; বাস্তবে P কখনো এভাবে বানানো হয় না (অংশ ৮)।

৭ · Least squares মানে projection — y-এর ছায়া X-এর column space-এ

AI-এর ভিত্তি কোর্সের পর্ব ৫৮-এ MSE-এর gradient শূন্য বসিয়ে normal equations XᵀXw = Xᵀy পেয়েছিলেন, আর দেখেছিলেন inverse-এর পথে condition number বর্গ হয়ে যায়। এখানে একই সমীকরণ আসে কোনো derivative ছাড়াই, শুধু জ্যামিতি থেকে, আর তা থেকে অনেক কিছু একসঙ্গে বোঝা যায়।

দৃষ্টিভঙ্গি বদলান: n-টি অর্ডারের ডেলিভারি সময় y একটি n-মাত্রার একটি vector. X-এর প্রতিটি কলাম (সব-১-এর কলাম, দূরত্বের কলাম, ওজনের কলাম) একটি করে n-মাত্রার vector. যেকোনো w-এর জন্য অনুমান Xw এই কলামগুলোর weighted যোগফল, অর্থাৎ col(X)-এর একটি বিন্দু। Least squares চায় ‖y − Xw‖ সবচেয়ে ছোট, অর্থাৎ col(X)-এর যে বিন্দু y-এর সবচেয়ে কাছে। অংশ ৬ বলে সেটি y-এর orthogonal projection, আর শর্ত Xᵀ(y − Xŵ) = 0: ঠিক normal equations.

Least squares-এর ছবি: y-এর লম্ব ছায়া ŷ, X-এর কলামগুলোর সমতলে

The data vector y lives in R^n. The fitted vector ŷ = Hy is its orthogonal projection onto col(X); the residual r is perpendicular to every column

Least squares as orthogonal projection onto the column space column space of X x₁ = 1 x₂ = km y (data) ŷ = Xŵ = Hy r = y − ŷ Xᵀr = 0

Projection matrix-টির এখানে একটি নিজস্ব নাম আছে, hat matrix: H = X(XᵀX)−1Xᵀ, কারণ সে y-কে “টুপি পরায়”, ŷ = Hy. অংশ ৬-এর সব ধর্ম সরাসরি regression-এর তথ্যে বদলে যায়:

  • Residual সব কলামের সঙ্গে লম্ব (Xᵀr = 0). X-এ সব-১-এর কলাম থাকলে তার সঙ্গে লম্ব মানে ∑ri = 0: intercept-সহ model-এ residual-এর যোগফল সবসময় শূন্য।
  • ŷ আর r লম্ব, তাই পিথাগোরাস: ‖y‖² = ‖ŷ‖² + ‖r‖². y থেকে গড় বাদ দিয়ে একই কথা লিখলে পরিচিত ভাঙন আসে: মোট বর্গ = ব্যাখ্যা করা বর্গ + residual বর্গ (SST = SSR + SSE), যা থেকে R² = 1 − SSE/SST.
  • trace(H) = p, parameter-এর সংখ্যা (intercept-সহ)। r থাকে col(X)-এর লম্ব, n − p মাত্রার একটি subspace-এ; এজন্যই গোলমালের variance-এর নিরপেক্ষ আন্দাজে n দিয়ে নয়, n − p দিয়ে ভাগ করা হয়।
  • H-এর কর্ণের ঘর hii-কে বলে i-তম বিন্দুর leverage: ŷi তার নিজের yi-এর ওপর কতটা নির্ভর করে। ০ থেকে ১-এর মধ্যে থাকে, আর গড়ে p/n.

পূর্ণ numerical (পর্ব ৫৮-এর তিনটি বিন্দু, এবার projection-এর চোখে). দূরত্ব x = 2, 4, 6 km, সময় y = 20, 26, 38 মিনিট; X = [[1, 2], [1, 4], [1, 6]]. XᵀX = [[3, 12], [12, 56]], det = 168 − 144 = 24, (XᵀX)−1 = (1/24)·[[56, −12], [−12, 3]]. H-এর (i, j) ঘর = (56 − 12xi − 12xj + 3xixj) / 24. বসালে:

H = [[5/6, 1/3, −1/6], [1/3, 1/3, 1/3], [−1/6, 1/3, 5/6]]

যাচাই: Hy-এর প্রথম ঘর (5/6)·20 + (1/3)·26 − (1/6)·38 = 16.667 + 8.667 − 6.333 = 19; মাঝের ঘর (20 + 26 + 38)/3 = 28; শেষ ঘর 37. তাই ŷ = (19, 28, 37), r = (1, −2, 1), আর Xᵀr = (1 − 2 + 1, 2 − 8 + 6) = (0, 0). trace(H) = 5/6 + 1/3 + 5/6 = 2 = parameter-এর সংখ্যা (b আর w). Leverage দুই প্রান্তে ৫/৬, মাঝে ১/৩: প্রান্তের বিন্দু রেখাটিকে বেশি টানে, যেমন লম্বা লাঠির প্রান্ত ধরে ঘোরানো সহজ। মাঝের বিন্দুর অনুমান ঠিক তিনটি y-এর গড়, কারণ x = 4 হলো x-গুলোর গড়, আর least squares রেখা সবসময় (x̄, ȳ) দিয়ে যায়। উচ্চ leverage-এর outlier কীভাবে fit নষ্ট করে আর তা সামলানোর পথ পর্ব ২৪ (Robust আর quantile regression — outlier আর অনুমানের পরিসর)-এ আসবে।

ইতিহাস: least squares প্রথম ছাপা হয় Adrien-Marie Legendre-এর ১৮০৫ সালের ধূমকেতুর কক্ষপথ নিয়ে লেখায়। Carl Friedrich Gauss ১৮০৯ সালে দাবি করেন তিনি ১৭৯৫ সাল থেকেই পদ্ধতিটি ব্যবহার করছেন, আর ১৮০১ সালে হারিয়ে যাওয়া বামন গ্রহ Ceres-এর অবস্থান আগাম বলায় এর ভূমিকা ছিল বলে প্রচলিত; অগ্রাধিকার নিয়ে বিতর্ক ইতিহাসবিদদের মধ্যে আজও আলোচিত। ভাবনার প্রশ্ন ২-এ আরও।

৮ · Gram–Schmidt আর QR — হাতে, তারপর NumPy-তে

অংশ ৬ বলেছিল: কলাম orthonormal হলে projection সহজ, P = QQᵀ. তাহলে X-এর কলাম থেকে একই column space-এর একটি orthonormal basis বানাতে পারলে least squares সহজ হয়ে যায়। Gram–Schmidt প্রক্রিয়া ঠিক এটি করে, আর তার হিসাবের খাতা থেকেই আসে QR decomposition.

নিয়মটি এক বাক্যে: প্রতিটি নতুন কলাম থেকে আগের সব q-এর দিকের ছায়া বাদ দিন, যা রইল তার দৈর্ঘ্য ১ করুন। ছায়া বাদ দেওয়া মানে ঠিক অংশ ৬-এর রেখায় projection-এর r অংশ রাখা।

A = [a₁ a₂], 3×2লক্ষ্য: A = QR

            
  1. শুরু। অংশ ৬-এর ব্লকের একই A: দুটি কলাম, একটি সমতল। a₁ · a₂ = 9, তাই কলাম দুটি লম্ব নয়। একই সমতলের জন্য লম্ব আর একক দৈর্ঘ্যের দুটি দিক চাই।
  2. প্রথম দিক। a₁-এর দৈর্ঘ্য ৩; ভাগ করে q₁ = (1, 2, 2)/3. দৈর্ঘ্যটি R-এর প্রথম কর্ণ r₁₁ = 3.
  3. ছায়া মাপা। a₂-এর q₁ দিকের অংশ r₁₂ = q₁ · a₂ = 3. q₁-এর দৈর্ঘ্য ১ বলে এখানে aᵀa দিয়ে ভাগ লাগে না; ছায়া হলো 3q₁ = (1, 2, 2).
  4. ছায়া বাদ। v₂ = (3, 3, 0) − (1, 2, 2) = (2, 1, −2). যাচাই q₁ · v₂ = 0: v₂ এখন q₁-এর সঙ্গে লম্ব, অথচ এখনো একই সমতলে।
  5. দ্বিতীয় দিক। ‖v₂‖ = 3, তাই q₂ = (2, 1, −2)/3 আর r₂₂ = 3.
  6. খাতাটিই R. a₁ = 3q₁, a₂ = 3q₁ + 3q₂. এই সহগগুলো একটি upper-triangular R-এ বসালে A = QR. R ত্রিভুজাকার, কারণ k-তম কলাম শুধু প্রথম k-টি q ব্যবহার করে।
  7. Least squares. Aw ≈ b-এ A = QR বসিয়ে দুই দিকে Qᵀ গুণ করলে Rw = Qᵀb. Qᵀb = (7, −4). R ত্রিভুজাকার, তাই back substitution: w₂ = −4/3, তারপর w₁ = (7 + 4)/3 = 11/3. Aw = (−1/3, 10/3, 22/3), অংশ ৬-এর ব্লকের p-এর সঙ্গে হুবহু মেলে।

কেন solver এই পথ বেছে নেয়? X = QR বসালে normal equations হয় RᵀQᵀQRw = RᵀQᵀy ⇒ RᵀRw = RᵀQᵀy ⇒ Rw = Qᵀy (R invertible হলে)। XᵀX কখনো বানাতেই হয় না। Q দৈর্ঘ্য বদলায় না, তাই cond(R) = cond(X), অথচ cond(XᵀX) = cond(X)². X-এ ৭টি দশমিক অঙ্ক হারানোর ঝুঁকি থাকলে normal equations-এ ১৪টি, QR-এ ৭টিই। কাজের দাম: n-টি সারি আর p-টি কলামে normal equations মোটামুটি np² গুণ-যোগ নেয়, Householder QR প্রায় তার দ্বিগুণ; বেশিরভাগ ক্ষেত্রে স্থিরতার জন্য এই দামটুকু দেওয়া হয়। নিচের ব্লক বাজারঘরের ৪০টি কাল্পনিক অর্ডারে তিনটি পথ পাশাপাশি চালায়, তারপর প্রায় একই দুটি কলামের একটি কঠিন ক্ষেত্রে।

import numpy as np
from scipy.linalg import solve_triangular

rng = np.random.default_rng(42)
n = 40                                       # বাজারঘরের ৪০টি কাল্পনিক অর্ডার
km = rng.uniform(1, 25, n)                   # দূরত্ব
kg = rng.uniform(0.2, 15, n)                 # ওজন
X = np.column_stack([np.ones(n), km, kg])
y = X @ np.array([12, 2.5, 0.8]) + rng.normal(0, 4, n)   # ডেলিভারির মিনিট

def three_ways(X, y):
    w_ne = np.linalg.solve(X.T @ X, X.T @ y)   # projection-এর সূত্র: XᵀXw = Xᵀy
    w_ls = np.linalg.lstsq(X, y)[0]            # LAPACK-এর SVD-ভিত্তিক least squares
    Q, R = np.linalg.qr(X)                     # reduced QR: Q 40x3, R 3x3
    w_qr = solve_triangular(R, Q.T @ y)        # Rw = Qᵀy, নিচ থেকে ওপরে সমাধান
    return w_ne, w_ls, w_qr, R

w_ne, w_ls, w_qr, R = three_ways(X, y)
for name, w in [("normal", w_ne), ("lstsq", w_ls), ("QR", w_qr)]:
    print(f"{name:6s} w = {np.array2string(w, precision=4)}")
print(f"সর্বোচ্চ পার্থক্য: {max(abs(w_ne - w_ls).max(), abs(w_qr - w_ls).max()):.1e}")
print(f"cond(X) = {np.linalg.cond(X):.1f}  cond(XᵀX) = {np.linalg.cond(X.T @ X):.1f}"
      f"  cond(R) = {np.linalg.cond(R):.1f}")

road = 1.3 * km + rng.normal(0, 1e-6, n)     # রাস্তার দূরত্ব ≈ 1.3 × সোজা দূরত্ব
X2 = np.column_stack([np.ones(n), km, road])
true = np.array([10, 1, 1.5])
y2 = X2 @ true                               # গোলমাল নেই, তাই সঠিক উত্তর জানা
print(f"\ncond(X2) = {np.linalg.cond(X2):.1e}  cond(X2ᵀX2) = {np.linalg.cond(X2.T @ X2):.1e}")
for name, w in zip(["normal", "lstsq", "QR"], three_ways(X2, y2)[:3]):
    err = np.linalg.norm(w - true) / np.linalg.norm(true)
    print(f"{name:6s} w = {np.array2string(w, precision=4)}  আপেক্ষিক ভুল {err:.1e}")

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

normal w = [9.0774 2.5542 0.9562]
lstsq  w = [9.0774 2.5542 0.9562]
QR     w = [9.0774 2.5542 0.9562]
সর্বোচ্চ পার্থক্য: 7.3e-14
cond(X) = 50.4  cond(XᵀX) = 2542.9  cond(R) = 50.4

cond(X2) = 3.8e+07  cond(X2ᵀX2) = 1.4e+15
normal w = [10.      0.6569  1.7639]  আপেক্ষিক ভুল 4.3e-02
lstsq  w = [10.   1.   1.5]  আপেক্ষিক ভুল 5.9e-10
QR     w = [10.   1.   1.5]  আপেক্ষিক ভুল 2.8e-10

ভালো আচরণের ডেটায় তিন পথ চার দশমিক পর্যন্ত এক; পার্থক্য ১০−১৪-এর ঘরে, শুধু float-এর গোলমাল। ৪০টি অর্ডারের ছোট নমুনা বলে coefficient আসল নিয়ম (১২, ২.৫, ০.৮) থেকে কিছুটা দূরে, সেটি গোলমালের দান, পদ্ধতির নয়। দ্বিতীয় লাইনের সংখ্যাগুলো এই পর্বের মূল দাবি: cond(X) = ৫০.৪, cond(R)-ও ৫০.৪ (হুবহু), আর cond(XᵀX) = ২৫৪২.৯ ≈ ৫০.৪². কঠিন ক্ষেত্রে cond(X2) প্রায় ৩.৮ × ১০৭, বর্গ প্রায় ১.৪ × ১০১৫, float64-এর প্রায় ১৬ অঙ্কের সীমার একদম কিনারায়। ফল: normal equations-এর আপেক্ষিক ভুল ৪.৩% (km-এর coefficient ১-এর বদলে ০.৬৬, রাস্তার দূরত্বের ১.৫-এর বদলে ১.৭৬; দুটির ভাগাভাগি এলোমেলো হয়ে গেছে), আর QR ও lstsq-এর ভুল ১০−১০-এর ঘরে, প্রায় দশ কোটি গুণ কম। ভুলের শেষ অঙ্কগুলো CPU আর LAPACK-এর build ভেদে বদলাতে পারে; ধরন বদলায় না।

Gram–Schmidt নিজে লিখে NumPy-র সঙ্গে মেলানো। নিচের function ঠিক stepper-এর নিয়ম মানে। একটি ছোট পরিবর্তনও আছে: classical সংস্করণে ছায়া মাপা হয় মূল কলাম aj থেকে, আর modified সংস্করণে এখন পর্যন্ত ছেঁটে রাখা v থেকে। গাণিতিকভাবে দুটি এক; float-এ নয়। পরীক্ষাটি একটি কঠিন matrix-এ: 1, t, t², …, t⁹-এর কলাম (polynomial feature, যা পর্ব ২৩ (Linear model গভীরে — basis function, interaction আর regularization path)-এ আসবে), যাদের অনেকগুলো প্রায় একই দিকে।

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

def gram_schmidt(A, modified=False):
    """A-এর কলাম থেকে orthonormal কলামের Q আর upper-triangular R"""
    m, k = A.shape
    Q, R = np.zeros((m, k)), np.zeros((k, k))
    for j in range(k):
        v = A[:, j].copy()
        for i in range(j):
            # classical: মূল কলামের ছায়া; modified: এখন পর্যন্ত বাকি অংশের ছায়া
            R[i, j] = Q[:, i] @ (v if modified else A[:, j])
            v -= R[i, j] * Q[:, i]           # q_i দিকের অংশ বাদ
        R[j, j] = np.linalg.norm(v)          # যা রইল তার দৈর্ঘ্য
        Q[:, j] = v / R[j, j]
    return Q, R

A = np.array([[1.0, 3.0], [2.0, 3.0], [2.0, 0.0]])
Q, R = gram_schmidt(A)
print("3·Q =\n", 3 * Q); print("R =\n", R)
Qn, Rn = np.linalg.qr(A)                     # LAPACK-এর Householder QR
print("NumPy R =\n", Rn)
s = np.sign(np.diag(Rn))                     # কলামের চিহ্ন মিলিয়ে নেওয়া
print("চিহ্ন মেলালে এক?", np.allclose(Qn * s, Q), np.allclose(s[:, None] * Rn, R))
print("QᵀQ = I?", np.allclose(Q.T @ Q, np.eye(2)), "  QR = A?", np.allclose(Q @ R, A))

V = np.vander(np.linspace(0, 1, 30), 10, increasing=True)   # 1, t, t², …, t⁹
print(f"\ncond(V) = {np.linalg.cond(V):.1e};  ‖QᵀQ − I‖ (যত ছোট তত ভালো):")
for name, Qx in [("classical GS", gram_schmidt(V)[0]),
                 ("modified GS", gram_schmidt(V, True)[0]),
                 ("np.linalg.qr", np.linalg.qr(V)[0])]:
    print(f"  {name:13s} {np.linalg.norm(Qx.T @ Qx - np.eye(10)):.1e}")

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

3·Q =
 [[ 1.  2.]
 [ 2.  1.]
 [ 2. -2.]]
R =
 [[3. 3.]
 [0. 3.]]
NumPy R =
 [[-3. -3.]
 [ 0. -3.]]
চিহ্ন মেলালে এক? True True
QᵀQ = I? True   QR = A? True

cond(V) = 3.5e+06;  ‖QᵀQ − I‖ (যত ছোট তত ভালো):
  classical GS  5.0e-02
  modified GS   5.4e-11
  np.linalg.qr  1.4e-15

নিজের লেখা Gram–Schmidt stepper-এর হাতের হিসাব হুবহু দিয়েছে: 3Q-এর কলাম (1, 2, 2) আর (2, 1, −2), R = [[3, 3], [0, 3]]. NumPy-র R-এর সব ঘরের চিহ্ন উল্টো: QR অনন্য নয়, Q-এর কোনো কলাম আর R-এর সংশ্লিষ্ট সারি একসঙ্গে −১ দিয়ে গুণ করলে গুণফল একই থাকে। R-এর কর্ণ ধনাত্মক রাখার শর্ত দিলে তবেই অনন্য; NumPy (LAPACK-এর Householder পদ্ধতি) সেই শর্ত মানে না, তাই চিহ্ন মিলিয়ে তুলনা করতে হয়। শেষ অংশে পার্থক্যটি নাটকীয়: cond প্রায় ৩.৫ × ১০৬ এমন matrix-এ classical Gram–Schmidt-এর Q-এর কলামগুলো আর লম্ব নেই (ভুল ৫.০ × ১০−২, কয়েক শতাংশ), modified সংস্করণে ১০−১১-এর ঘরে, আর Householder-ভিত্তিক np.linalg.qr-এ ১০−১৫-এর ঘরে, প্রায় নিখুঁত।

কারণটি জ্যামিতিক। প্রায় একই দিকের দুটি কলামের ছায়া বাদ দিলে যা থাকে তা খুব ছোট, আর classical সংস্করণে আগের q-গুলোর ছোট ছোট ভুল সেই ছোট অংশে বড় অনুপাতে ঢুকে যায়। Modified সংস্করণ প্রতিটি ধাপের পর বাকি অংশ থেকে আবার মাপে, তাই আগের ভুলও সঙ্গে সঙ্গে ছেঁটে যায়। Householder পদ্ধতি একেবারে আলাদা পথে যায়: q-গুলো ধাপে ধাপে বানানোর বদলে X-কে একের পর এক Householder আয়না দিয়ে প্রতিফলিত করে upper-triangular বানায়; প্রতিটি আয়না orthogonal, তাই ভুল বাড়ে না। LAPACK-এর geqrf routine এই পথে চলে, আর np.linalg.qr সেটিই ডাকে। Givens rotation আরেকটি বিকল্প, যা একবারে একটি করে ঘর শূন্য করে; sparse matrix-এ বা একটি একটি করে সারি যোগ হওয়া ডেটায় কাজে লাগে।

৯ · ML-এ কোথায় কোথায় — layer, PCA, leverage আর সঠিক solver

ধারণাML-এ যেখানে আসে
Linear map, compositionNeural network-এর প্রতিটি layer একটি affine map; non-linearity ছাড়া পুরো network একটি matrix (অংশ ৩)। Deep learning-এর বিস্তারিত পরে Deep Learning কোর্সে।
ProjectionPCA মানে ডেটাকে সবচেয়ে বেশি variance-এর কয়েকটি orthonormal দিকের subspace-এ projection: Xk = XVkVkᵀ. দিকগুলো কীভাবে বাছা হয়, তা পর্ব ০৮ (Eigenvalue আর eigenvector — ধারণা থেকে PCA পর্যন্ত) আর পর্ব ০৯-এ।
Hat matrix, leverageRegression-এ প্রভাবশালী বিন্দু খোঁজা; leave-one-out residual এক লাইনে: ri / (1 − hii), আলাদা করে n বার fit না করেই (linear regression-এর জন্য হুবহু)।
Orthogonal matrixদূরত্ব-ভিত্তিক model ঘোরানো ডেটায় অপরিবর্তিত; ছবির data augmentation-এ ঘোরানো আর উল্টানো; deep network-এ orthogonal weight initialisation (একটি প্রচলিত বিকল্প)।
QR, condition numbernp.linalg.lstsq আর scikit-learn-এর LinearRegression XᵀX না বানিয়ে LAPACK-এর SVD-ভিত্তিক routine দিয়ে সমাধান করে; statsmodels-এর OLS ডিফল্টে pseudo-inverse (SVD), আর বিকল্প হিসেবে QR (method="qr") দেয়। Ridge-এর মতো regularization ill-conditioning কমায়, যা পর্ব ১৭ (Regularization-এর তত্ত্ব — L1/L2-এর জ্যামিতি, MAP আর early stopping)-এ।
DeterminantMultivariate Gaussian-এর ঘনত্বে det Σ আসে (পর্ব ১৯ (Multivariate Gaussian — covariance, Mahalanobis দূরত্ব আর শর্তসাপেক্ষ বণ্টন)); সংখ্যার নিরাপত্তায় det-এর বদলে log-det হিসাব করা হয় (np.linalg.slogdet).

আরও গভীরে: Householder ১৯৫৮ সালে Journal of the ACM-এ আয়না দিয়ে triangular বানানোর পদ্ধতি দেন, আর Gene Golub ১৯৬৫ সালে (Numerische Mathematik) একে least squares-এ প্রয়োগ করেন; আধুনিক LAPACK-এর QR এই ধারার উত্তরসূরি। Åke Björck ১৯৬৭ সালে দেখান modified Gram–Schmidt least squares-এ সংখ্যাগতভাবে স্থির। বিস্তারিত বিশ্লেষণের প্রচলিত পাঠ্য Trefethen আর Bau-এর Numerical Linear Algebra (১৯৯৭) আর Golub ও Van Loan-এর Matrix Computations. অনেক বড় আর sparse সমস্যায় QR-ও ব্যয়বহুল; তখন LSQR-এর মতো iterative পদ্ধতি (scipy.sparse.linalg.lsqr) বা gradient descent ব্যবহার হয়।

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

  • [[3, 1], [2, 4]] একক বর্গের ক্ষেত্রফল কত গুণ করে? det = 12 − 2 = 10 গুণ, দিক অক্ষত (det ধনাত্মক)।
  • a = (4, 2)-এর রেখায় b = (1, 3)-এর projection? (aᵀb / aᵀa)a = (10/20)(4, 2) = (2, 1); r = (−1, 2) ⟂ a.
  • Projection matrix চেনার দুটি শর্ত? P² = P আর Pᵀ = P (orthogonal projection-এর জন্য)।
  • Least squares-এ residual কীসের সঙ্গে লম্ব, আর কেন? X-এর প্রতিটি কলামের সঙ্গে (Xᵀr = 0), কারণ ŷ হলো col(X)-এ y-এর সবচেয়ে কাছের বিন্দু।
  • Normal equations-এর বদলে QR কেন? cond(XᵀX) = cond(X)², QR-এ cond(R) = cond(X); তাই অর্ধেক অঙ্ক হারানোর ঝুঁকি।

১০ · AI-এর যুগে — matrix-এর হিসাব আর AI-এর দাবি যাচাই

AI assistant linear algebra-র derivation আত্মবিশ্বাসের সঙ্গে লেখে, আর চিহ্ন ও সূচক নির্ভুল দেখায় বলে ভুল চোখে পড়ে না। কোডেও সে এমন অভ্যাস দেয় যা এই পর্বের জ্যামিতির সরাসরি লঙ্ঘন।

AI প্রায়ই যা করেকেন ভুলকীভাবে ধরবেন
np.linalg.inv(X.T @ X) @ X.T @ y লেখেCondition number বর্গ হয়; collinear feature-এ অঙ্ক হারায় বা singular হয়lstsq বা QR-এর সঙ্গে তুলনা করুন; np.linalg.cond(X) ছাপুন
Matrix গুণের ক্রম উল্টে ফেলে (AB-কে BA)Composition commutative নয়একটি বিন্দু দুই ক্রমে পাঠিয়ে দেখুন, অংশ ৩-এর মতো
“det ছোট, তাই প্রায় singular” বলেDeterminant scale-নির্ভরCondition number বা singular value দেখুন
Projection-এর সূত্রে (AᵀA)−1 বাদ দিয়ে AAᵀ লেখেকলাম orthonormal না হলে AAᵀ projection নয়np.allclose(P @ P, P) চালান
দুই QR-এর Q না মেলায় “bug” ভাবে, বা চিহ্ন উপেক্ষা করে তুলনা করেQR চিহ্ন পর্যন্ত অনন্যR-এর কর্ণের চিহ্ন মিলিয়ে তুলনা করুন
পুরোনো API দেয় (যেমন lstsq-এ rcond=-1, বা পুরোনো statsmodels/scikit-learn parameter)Version বদলেছে; আচরণ ভিন্ন হতে পারেআপনার version-এর অফিসিয়াল docs পড়ুন, চালিয়ে দেখুন

সূত্রের সঙ্গে প্রতিটি চিহ্নের মানে, ছোট হাতের উদাহরণ আর যাচাইয়ের কোড চেয়ে নিন।

আমি linear regression-এর জন্য least squares নিজে বুঝতে চাই। X একটি 500×6 design matrix (প্রথম কলাম সব 1), y দৈর্ঘ্য 500. ১. projection-এর যুক্তি দিয়ে (derivative ছাড়া) normal equations বের করো; প্রতিটি চিহ্নের মানে লেখো। ২. hat matrix H-এর তিনটি ধর্ম (H² = H, Hᵀ = H, trace H = 6) প্রমাণ করো। ৩. QR দিয়ে সমাধানের ধাপ লেখো, আর normal equations-এর তুলনায় condition number-এ কী লাভ, সংখ্যায় বলো। ৪. 3×2-এর একটি ছোট উদাহরণ হাতে করে দেখাও, আর NumPy 2-এর বর্তমান API দিয়ে (seed দিয়ে) একটি যাচাইয়ের কোড লেখো যা lstsq, QR আর normal equations-এর ফল আর cond(X) ছাপে। ৫. তোমার উত্তরের কোন অংশে ভুল হওয়ার সম্ভাবনা বেশি, সেটিও বলো।

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

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

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

প্র ০১Normal equations কাগজে এত সুন্দর, তবু numerical software কেন QR বা SVD দিয়ে least squares সমাধান করে? Normal equations কি কখনো ঠিক পছন্দ?

সমস্যাটি গণিতের নয়, float-এর। কম্পিউটারে প্রতিটি সংখ্যা প্রায় ১৬টি দশমিক অঙ্কের নির্ভুলতায় রাখা হয় (float64), আর condition number বলে ইনপুটের ছোট ভুল উত্তরে কত গুণ বড় হতে পারে। XᵀX বানানোর মুহূর্তেই condition number বর্গ হয়ে যায়: cond(X) = ১০৭ হলে cond(XᵀX) = ১০১৪, আর ১৬ অঙ্কের মধ্যে প্রায় ১৪টিই ঝুঁকিতে। অংশ ৮-এর ব্লকে ঠিক এটি দেখা গেছে: একই ডেটায় normal equations-এর ভুল ৪.৩%, QR-এর ১০−১০-এর ঘরে। তথ্য XᵀX বানানোর সময়ই হারিয়ে যায়, পরে যত ভালো solver-ই ব্যবহার করুন ফেরানো যায় না।

ইতিহাসে এই উপলব্ধি ধাপে ধাপে এসেছে। Alston Householder ১৯৫৮ সালে orthogonal আয়না দিয়ে matrix-কে triangular বানানোর পদ্ধতি প্রকাশ করেন। Gene Golub ১৯৬৫ সালের একটি প্রবন্ধে দেখান কীভাবে এটি দিয়ে XᵀX না বানিয়েই least squares সমাধান করা যায়। পরে Golub ও সহকর্মীরা SVD হিসাবের স্থিতিশীল পদ্ধতিও দেন। আজ NumPy-র lstsq LAPACK-এর SVD-ভিত্তিক routine ডাকে, আর অনেক statistics software QR ব্যবহার করে (যেমন R-এর lm, যা ঐতিহাসিকভাবে LINPACK-এর QR-এর ওপর গড়া)।

তবে normal equations সবসময় ভুল পছন্দ নয়। যদি n অনেক বড় আর p ছোট হয় (যেমন এক কোটি অর্ডার, ২০টি feature) আর feature-গুলো standardise করা ও প্রায় স্বাধীন হয়, তাহলে XᵀX একটি ২০×২০ matrix, যা ডেটার ওপর একবার চোখ বুলিয়ে, এমনকি টুকরো টুকরো করে (streaming) জমানো যায়। পুরো X মেমোরিতে না রেখেই কাজ চলে, আর তারপর Cholesky দিয়ে সমাধান খুব দ্রুত। cond(X) ১০০ হলে বর্গ ১০৪, যা float64-এ নিরাপদ। Distributed system-এ অনেক মেশিনের XᵀX যোগ করা সহজ বলে এই পথ জনপ্রিয়। Ridge যোগ করলে XᵀX + λI-এর condition number আরও কমে।

তাই সিদ্ধান্তটি একটি trade-off: QR প্রায় দ্বিগুণ হিসাব নেয় আর পুরো X লাগে, কিন্তু স্থির; normal equations সস্তা আর streaming-বান্ধব, কিন্তু খারাপ শর্তের (ill-conditioned) ডেটায় বিপজ্জনক। ভালো অভ্যাস হলো cond(X) মেপে সিদ্ধান্ত নেওয়া।

মূল উপলব্ধি: গাণিতিকভাবে সমান দুটি সূত্র কম্পিউটারে সমান নির্ভরযোগ্য নয়। কোন পথে হিসাব হচ্ছে, তা উত্তরের অংশ।

প্র ০২ভুলের বর্গের যোগফলই কেন কমানো হয়? পরম মানের যোগফল বা সবচেয়ে বড় ভুল কমালে কী হতো, আর projection-এর ছবি এখানে কী বলে?

ইতিহাস দিয়ে শুরু করা যাক। Legendre ১৮০৫ সালে least squares প্রকাশ করেন মূলত একটি ব্যবহারিক কারণে: পদ্ধতিটি একটি linear system দেয়, যা হাতে সমাধান করা যায়। Gauss ১৮০৯ সালে এর একটি সম্ভাবনাভিত্তিক যুক্তি দেন: ভুলগুলো normal distribution মানলে বর্গের যোগফল কমানোই সবচেয়ে সম্ভাব্য উত্তর দেয়। পরে Gauss–Markov উপপাদ্য দেখায় যে ভুলের গড় শূন্য, variance সমান আর পরস্পর সম্পর্কহীন হলে, সব linear আর unbiased আন্দাজের মধ্যে least squares-এর variance সবচেয়ে কম। এখানে normal distribution লাগেই না। Gaussian ভুল থেকে least squares কীভাবে maximum likelihood হয়, তা পর্ব ১৮ (Maximum likelihood আর MAP — model fit করার মূলনীতি)-এ।

জ্যামিতির দিক থেকে বর্গের যোগফল হলো ‖y − Xw‖², সাধারণ Euclid দূরত্বের বর্গ। এই দূরত্বেই “সবচেয়ে কাছের বিন্দু” মানে লম্ব ছায়া, পিথাগোরাস খাটে, আর উত্তর একটি linear map (ŷ = Hy). এর মানে অনেক সুবিধা একসঙ্গে: closed form, ভুলের ভাঙন SST = SSR + SSE, leverage, leave-one-out-এর সহজ সূত্র। পরম মানের যোগফল (L1, যা থেকে median regression আসে) বা সবচেয়ে বড় ভুল (L∞) কমালে “সবচেয়ে কাছের বিন্দু” আর লম্ব ছায়া নয়; উত্তর আর একটি matrix গুণে আসে না, linear programming বা iterative পদ্ধতি লাগে।

দামও আছে। বর্গের কারণে একটি বড় ভুল চারগুণ নয়, বর্গ গুণ শাস্তি পায়, তাই outlier রেখাটিকে নিজের দিকে টেনে নেয়, বিশেষ করে উচ্চ leverage-এর বিন্দু হলে। বাজারঘরে একটি অর্ডারের সময় ভুল করে ৯০০ মিনিট লেখা থাকলে least squares-এর রেখা বেশ সরে যাবে, অথচ L1 প্রায় নড়বে না। Huber loss দুটির মাঝামাঝি, ছোট ভুলে বর্গ আর বড় ভুলে পরম মান; পর্ব ২৪-এ এগুলো আসবে।

তাই উত্তরটি “বর্গই সঠিক” নয়, বরং “বর্গ সবচেয়ে সুবিধাজনক জ্যামিতি দেয়, আর নির্দিষ্ট শর্তে সবচেয়ে কার্যকরও”। ডেটা সেই শর্ত মানে কি না, residual plot দেখে যাচাই করতে হয়।

মূল উপলব্ধি: loss বাছাই মানে দূরত্বের মাপ বাছাই। Euclid দূরত্ব projection-এর সুন্দর জ্যামিতি দেয়, কিন্তু outlier-এর প্রতি সংবেদনশীলতার দামে।

প্র ০৩Classical আর modified Gram–Schmidt গাণিতিকভাবে এক, তবু float-এ এত আলাদা কেন? আর পাঠ্যবইয়ে Gram–Schmidt শেখানো হলে software কেন Householder ব্যবহার করে?

নামটি দুজন গণিতবিদের: Jørgen Pedersen Gram ১৮৮৩ সালে আর Erhard Schmidt ১৯০৭ সালে function-এর space-এ পদ্ধতিটি ব্যবহার করেন। ইতিহাসবিদরা উল্লেখ করেন যে Laplace-এর লেখাতেও এর একটি রূপ আগে দেখা যায়; তাই নামটি আবিষ্কারের নিখুঁত ইতিহাস নয়, প্রচলিত পরিচয়।

কাগজে দুই সংস্করণ এক, কারণ qi-গুলো নিখুঁতভাবে লম্ব হলে মূল কলাম থেকে ছায়া মাপা আর ছেঁটে রাখা অংশ থেকে মাপা একই সংখ্যা দেয়। Float-এ q1 আর q2 সামান্য অ-লম্ব হয়, ধরুন ১০−১৬ মাত্রায়। Classical সংস্করণে q3 বানানোর সময় a3-এর q1 আর q2 দিকের ছায়া আলাদাভাবে মূল a3 থেকে মাপা হয়; আগের অ-লম্বতার ভুল সংশোধনের কোনো সুযোগ থাকে না। যদি a3 প্রায় আগের কলামগুলোর span-এ থাকে, তাহলে বাদ দেওয়ার পর বাকি অংশ খুব ছোট, আর ভাগ করার সময় ভুলটি সেই ছোট সংখ্যার অনুপাতে বড় হয়। মোটামুটি নিয়ম: classical সংস্করণে লম্বতার ভুল condition number-এর বর্গের অনুপাতে বাড়ে, modified সংস্করণে condition number-এর অনুপাতে। অংশ ৮-এর ব্লকে cond প্রায় ৩.৫ × ১০৬-এ ভুল ৫.০ × ১০−২ বনাম ৫.৪ × ১০−১১, এই ধরনের সঙ্গে মেলে।

Householder পদ্ধতি q-গুলো তৈরিই করে না; X-কে orthogonal আয়নায় প্রতিফলিত করে R বানায়, আর Q থাকে আয়নাগুলোর গুণফল হিসেবে। Orthogonal matrix দৈর্ঘ্য অক্ষত রাখে, তাই float-এর ভুল প্রতিটি ধাপে বাড়ে না; Q-এর লম্বতা condition number নির্বিশেষে machine precision-এর কাছাকাছি থাকে (ব্লকে ১.৪ × ১০−১৫)। Åke Björck ১৯৬৭ সালে দেখান যে modified Gram–Schmidt-ও least squares সমাধানে ভালো ফল দেয়, যদি সঠিকভাবে ব্যবহার করা হয়, যদিও Q-এর লম্বতা Householder-এর মতো নিখুঁত থাকে না।

তাহলে Gram–Schmidt কেন শেখানো? কারণ এটি জ্যামিতিটি চোখে দেখায়: প্রতিটি ধাপ একটি projection, আর R-এর প্রতিটি ঘরের মানে আছে। আর কিছু ক্ষেত্রে, যেমন Krylov subspace পদ্ধতিতে (Arnoldi, GMRES), modified Gram–Schmidt বা তার পুনরাবৃত্ত রূপ এখনো প্রধান হাতিয়ার, কারণ সেখানে কলামগুলো একটি একটি করে আসে।

মূল উপলব্ধি: বোঝার জন্য সেরা algorithm আর হিসাবের জন্য সেরা algorithm একই না-ও হতে পারে। ধারণাটি Gram–Schmidt থেকে শিখুন, ব্যবহার করুন library-র Householder QR.

প্র ০৪Determinant-এর সংজ্ঞা এত পুরোনো, তবু আধুনিক numerical linear algebra এটিকে প্রায় এড়িয়ে চলে। ML-এ determinant তাহলে কোথায় সত্যিই দরকার, আর কীভাবে নিরাপদে হিসাব করবেন?

Determinant-এর ধারণা matrix-এর চেয়েও পুরোনো। প্রচলিত বিবরণে জাপানের Seki Takakazu ১৬৮৩ সালে আর Leibniz ১৬৯৩ সালের একটি চিঠিতে সমীকরণের সহগ থেকে এমন রাশি ব্যবহার করেন; “matrix” শব্দটি আর matrix-এর বীজগণিত আসে অনেক পরে, উনিশ শতকে Sylvester আর Cayley-র হাতে। দীর্ঘদিন সমীকরণ সমাধানের প্রধান হাতিয়ার ছিল Cramer-এর নিয়ম, যা determinant-এর অনুপাত দিয়ে উত্তর দেয়।

আধুনিক হিসাবে determinant প্রায় অদরকারি, তিনটি কারণে। (১) সংজ্ঞা থেকে সরাসরি হিসাব n! সংখ্যক পদের, ২০×২০-তেই অসম্ভব; বাস্তবে LU decomposition করে কর্ণের গুণফল নেওয়া হয়। (২) মান সহজেই overflow বা underflow হয়: অংশ ৪-এর 0.1·I-এর মতো, ১০০ মাত্রায় ১০−১০০, আর ৪০০ মাত্রায় float64-এর সীমার নিচে, শূন্য দেখায়, অথচ matrix নিখুঁত। (৩) “প্রায় singular” মাপতে determinant ভুল যন্ত্র; condition number বা ছোট singular value ঠিক যন্ত্র। Cramer-এর নিয়মও বড় system-এ ধীর আর অস্থির।

তবু ML-এ determinant কয়েকটি জায়গায় অপরিহার্য, আর প্রতিটিতে তার জ্যামিতিক অর্থই কাজ করে। Multivariate Gaussian-এর ঘনত্বে √det(2πΣ) দিয়ে ভাগ করা হয়: covariance যত বড় আয়তন জুড়ে ছড়ায়, ঘনত্ব তত পাতলা, যাতে মোট সম্ভাবনা ১ থাকে (পর্ব ১৯)। কোনো চলক বদলালে (x থেকে z = f(x)) ঘনত্ব বদলায় Jacobian-এর determinant দিয়ে, কারণ ছোট এক টুকরো আয়তন কত গুণ হলো সেটিই det; normalizing flow নামের generative model এই নিয়মের ওপর দাঁড়িয়ে, আর Jacobian পর্ব ১১ (Matrix calculus — gradient, Jacobian আর Hessian)-এ আসবে। Gaussian process-এর likelihood-এ log det K আসে (পর্ব ৪৬ (Gaussian process — kernel দিয়ে অনিশ্চয়তাসহ অনুমান))।

এসব জায়গায় নিয়ম একটাই: determinant নয়, তার লগারিদম হিসাব করুন, আর সরাসরি নয়, একটি factorisation থেকে। NumPy-র np.linalg.slogdet চিহ্ন আর log|det| আলাদা ফেরত দেয়; covariance-এর মতো positive definite matrix-এ Cholesky factor L-এর কর্ণ থেকে log det = 2∑log Lii, যা পর্ব ১০ (Positive definite matrix — quadratic form আর covariance)-এ আসবে।

মূল উপলব্ধি: determinant একটি জ্যামিতিক অর্থ (আয়তনের গুণক) হিসেবে অমূল্য, কিন্তু একটি সংখ্যা হিসেবে বিপজ্জনক। অর্থটি মাথায় রাখুন, সংখ্যাটি log-এ আর factorisation দিয়ে হিসাব করুন।

প্র ০৫দুটি আলাদা embedding model (ধরুন একটি বাংলা আর একটি ইংরেজি শব্দের) একই অর্থের শব্দকে আলাদা জায়গায় বসায়। Orthogonal matrix দিয়ে কি দুটি space “মিলিয়ে” নেওয়া যায়? এতে কী লাভ আর কী ঝুঁকি?

অংশ ৫ বলেছিল: একটি embedding space পুরোটা ঘুরিয়ে দিলেও সব দূরত্ব আর cosine একই থাকে। তাই দুটি আলাদা প্রশিক্ষণে একই ধরনের কাঠামো শেখা হলেও অক্ষগুলো সম্পূর্ণ আলাদা দিকে পড়তে পারে। প্রশ্নটি তাহলে: একটি orthogonal Q কি খুঁজে পাওয়া যায় যা প্রথম space-কে দ্বিতীয়টির ওপর যতটা সম্ভব বসিয়ে দেয়?

২০১৩ সালে Mikolov ও সহকর্মীরা দেখান যে কয়েক হাজার পরিচিত শব্দ-জোড়া (যেমন অভিধান থেকে) দিয়ে দুই ভাষার word embedding-এর মধ্যে একটি linear map শেখা যায়, যা অচেনা শব্দের অনুবাদও আন্দাজ করতে পারে। পরের কাজে (যেমন Xing ও সহকর্মীদের ২০১৫ সালের কাজ) map-টিকে orthogonal রাখার প্রস্তাব আসে, যুক্তি ছিল: শুধু ঘোরানো হলে প্রতিটি ভাষার নিজের ভেতরের দূরত্ব নষ্ট হয় না। এই “সবচেয়ে ভালো orthogonal map” খোঁজার সমস্যার নাম orthogonal Procrustes, যার সমাধান ১৯৬০-এর দশকে মনোবিজ্ঞানের পরিসংখ্যানে (Schönemann, ১৯৬৬) প্রকাশিত হয়েছিল, আর উত্তরটি আসে SVD থেকে (পর্ব ০৯)।

লাভ স্পষ্ট: কম label-এর ভাষায় (বাংলার জন্য প্রাসঙ্গিক) বেশি ডেটার ভাষার কাঠামো ধার করা যায়, দুই সময়ে প্রশিক্ষিত দুটি model-এর embedding তুলনা করা যায় (শব্দের অর্থ সময়ের সঙ্গে কতটা সরল), বা একটি model-এর নতুন version-এ পুরোনো index-এর সঙ্গে সামঞ্জস্য রাখা যায়।

ঝুঁকিগুলোও জ্যামিতি থেকেই আসে। (১) Orthogonal map ধরে নেয় দুই space-এর আকৃতি একই, শুধু ঘোরানো; দুই ভাষার শব্দের সম্পর্ক অনেক ক্ষেত্রে হুবহু এক নয় (একটি বাংলা শব্দের একাধিক ইংরেজি অর্থ, সাংস্কৃতিক শব্দ), তখন সবচেয়ে ভালো ঘোরানোও অনেক শব্দ ভুল জায়গায় বসায়। গবেষণায় দেখা গেছে দূরের ভাষার জোড়ায় এই “একই আকৃতি”-র অনুমান দুর্বল হতে পারে। (২) প্রায় একই দিকের অক্ষ বা কম জোড়া থাকলে সমাধান অস্থির। (৩) মেলানোর পর মূল্যায়ন যদি একই অভিধানের শব্দে হয়, ফল আশাবাদী; আলাদা test শব্দ রাখতে হয়, পর্ব ০৩-এর শিক্ষা।

মূল উপলব্ধি: orthogonal matrix “আকৃতি অক্ষত” রাখে বলে দুটি space মেলানোর স্বাভাবিক হাতিয়ার; কিন্তু দুই space-এর আকৃতি সত্যিই এক কি না, তা অনুমান, প্রমাণ নয়, আর তা আলাদা ডেটায় যাচাই করতে হয়।

অনুশীলন

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

  1. QR দিয়ে least squares, হাতে: X = [[1, 2], [1, 4], [1, 6]] (প্রথম কলাম intercept, দ্বিতীয়টি km), y = (20, 26, 38). (ক) Gram–Schmidt দিয়ে Q আর R বের করুন। (খ) Qᵀy বের করে Rw = Qᵀy সমাধান করুন। (গ) hat matrix-এর কর্ণের ঘর (leverage) তিনটি কত, আর তাদের যোগফল কেন ২?

    (ক) a1 = (1, 1, 1), ‖a1‖ = √3, q1 = (1, 1, 1)/√3. r12 = q1 · a2 = (2 + 4 + 6)/√3 = 12/√3 = 4√3. v2 = a2 − 4√3·q1 = (2, 4, 6) − (4, 4, 4) = (−2, 0, 2), ‖v2‖ = √8 = 2√2, q2 = (−1, 0, 1)/√2. R = [[√3, 4√3], [0, 2√2]].

    (খ) Qᵀy = ((20 + 26 + 38)/√3, (−20 + 38)/√2) = (84/√3, 18/√2) = (28√3, 9√2). নিচ থেকে: 2√2·w = 9√2 ⇒ w = 4.5; √3·b + 4√3·4.5 = 28√3 ⇒ b = 28 − 18 = 10. রেখা ŷ = 10 + 4.5x, AI-এর ভিত্তি কোর্সের পর্ব ৫৮-এর normal equations-এর উত্তরের সঙ্গে হুবহু এক, কিন্তু কোনো inverse ছাড়াই।

    (গ) H = QQᵀ, তাই hii = (q1-এর i-তম ঘর)² + (q2-এর i-তম ঘর)². h11 = 1/3 + 1/2 = 5/6, h22 = 1/3 + 0 = 1/3, h33 = 1/3 + 1/2 = 5/6. যোগফল ২ = trace(H) = rank = parameter-এর সংখ্যা (b আর w). QQᵀ দিয়ে leverage বের করা (XᵀX)−1-এর পথের চেয়ে সহজ আর নিরাপদ।

  2. সমালোচনা ও নকশা: বাজারঘরের একজন সহকর্মী জেলাভিত্তিক ডেলিভারি সময়ের model বানাতে X-এ একটি intercept কলাম, ৬৪টি জেলার প্রতিটির জন্য একটি one-hot কলাম, “দূরত্ব (km)” আর “দূরত্ব (মিটার)” দুটিই রেখেছেন, আর লিখেছেন w = np.linalg.inv(X.T @ X) @ X.T @ y. কোড একটি উত্তর দিয়েছে, কোনো error ছাড়াই। জ্যামিতির ভাষায় কী কী ভুল, আর আপনি কীভাবে নতুন করে সাজাবেন?

    ভুল ১, কলামগুলো dependent: ৬৪টি one-hot কলামের যোগফল সব-১-এর কলাম, অর্থাৎ intercept কলাম বাকিগুলোর linear combination (dummy trap). আবার মিটার = ১০০০ × km, আরেকটি নির্ভরতা। তাই X-এর কলামগুলো col(X)-এর basis নয়, XᵀX singular, আর (XᵀX)−1 গাণিতিকভাবে নেই। Float-এ inverse “পাওয়া গেছে” মানে rounding-এর গোলমাল থেকে বানানো একটি বিশাল সংখ্যার matrix; w-এর মানগুলো অর্থহীন, যদিও ŷ হয়তো কাছাকাছি আসে (কারণ projection ŷ নিজে অনন্য, শুধু w অনন্য নয়)।

    ভুল ২, পথটি নিজেই অস্থির: collinearity না থাকলেও inverse আর XᵀX condition number বর্গ করে।

    নতুন নকশা: (১) একটি জেলাকে reference রেখে ৬৩টি dummy, অথবা intercept বাদ। (২) দূরত্বের একটি এককই রাখুন। (৩) np.linalg.matrix_rank(X) আর np.linalg.cond(X) ছেপে দেখুন rank = কলামের সংখ্যা কি না (পর্ব ০৬)। (৪) সমাধান lstsq বা QR দিয়ে; scikit-learn-এর OneHotEncoder(drop="first") আর LinearRegression একটি pipeline-এ। (৫) কিছু জেলায় অর্ডার খুব কম হলে সেই coefficient অনিশ্চিত; ridge বিবেচনা করুন। (৬) মান মাপুন আলাদা test set-এ, একটি সরল baseline (যেমন জেলার গড় সময়)-এর সঙ্গে তুলনা করে।

  3. আউটপুট অনুমান করুন: চালানোর আগে লিখে ফেলুন নিচের কোড কী ছাপবে।
    import numpy as np
    R = np.array([[0, -1], [1, 0]])
    F = np.array([[1, 0], [0, -1]])
    x = np.array([2, 1])
    print(R @ F @ x, F @ R @ x)
    print(np.linalg.det(R @ F).round(3), (R.T @ R == np.eye(2)).all())
    print(F @ R)
    [1 2] [-1 -2]
    -1.0 True
    [[ 0 -1]
     [-1  0]]

    R হলো 90° rotation, F হলো x-অক্ষে reflection (y-এর চিহ্ন উল্টায়)। R @ F @ x-এ ডানেরটি আগে: Fx = (2, −1), তারপর R(2, −1) = (1, 2). F @ R @ x-এ আগে Rx = (−1, 2), তারপর F: (−1, −2). ক্রম বদলালে ফল বদলায়। det(RF) = det R × det F = 1 × (−1) = −1: একটি rotation আর একটি reflection মিলে reflection-ই থাকে। RᵀR = I, কারণ R orthogonal; integer আর float-এর তুলনাও এখানে হুবহু সত্য। শেষ লাইনের FR = [[0, −1], [−1, 0]] হলো y = −x রেখায় reflection: e1 যায় (0, −1)-এ, e2 যায় (−1, 0)-এ।

  4. Projection থেকে reflection: a = (1, 1)-এর রেখায় projection matrix P বের করুন, b = (3, 1)-এর projection p আর r বের করুন। তারপর দেখান F = 2P − I একটি reflection (কোন রেখায়?), F orthogonal, আর det F কত। ঐচ্ছিক: NumPy-তে তিনটি দাবি যাচাই করুন।

    aᵀa = 2, aaᵀ = [[1, 1], [1, 1]], তাই P = [[0.5, 0.5], [0.5, 0.5]]. Pb = (2, 2) = p, r = b − p = (1, −1), আর aᵀr = 1 − 1 = 0.

    F = 2P − I = [[0, 1], [1, 0]]: অংশ ২-এর y = x রেখায় reflection. জ্যামিতি: b = p + r, আর Fb = 2p − b = p − r, অর্থাৎ রেখার দিকের অংশ একই, লম্ব অংশ উল্টানো; এটিই আয়না। Fb = (1, 3). FᵀF = F² = 4P² − 4P + I = 4P − 4P + I = I (P² = P ব্যবহার করে), তাই F orthogonal. det F = 0·0 − 1·1 = −1: দিক উল্টায়। অংশ ৫-এর Householder আয়না H = I − 2uuᵀ একই ধাঁচের, তবে সেটি u-এর লম্ব রেখা বা সমতলে আয়না (H = −F, যখন u রেখার দিকে)।

    import numpy as np
    a = np.array([1.0, 1.0]); P = np.outer(a, a) / (a @ a)
    F = 2 * P - np.eye(2)
    print(P @ np.array([3.0, 1.0]), np.allclose(F.T @ F, np.eye(2)), np.linalg.det(F))
    # [2. 2.] True -1.0

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

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

ক্রমQR দিয়ে least squares সমাধানের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Design matrix X বানানো (intercept-এর জন্য সব-১-এর কলামসহ) আর rank পরীক্ষা
  2. X = QR: Q-এর কলাম orthonormal, R upper-triangular
  3. y-কে Q-এর কলামগুলোর ওপর মাপা: Qᵀy
  4. Back substitution দিয়ে Rw = Qᵀy সমাধান, নিচের সারি থেকে ওপরে
  5. Residual r = y − Xw বের করে যাচাই: Xᵀr ≈ 0

২×২ matrix A = [[3, 1], [2, 4]] একটি একক বর্গকে কোন আকৃতিতে পাঠায়, আর তার ক্ষেত্রফল কত?

  • একটি parallelogram, ক্ষেত্রফল ১৪
  • একটি বর্গ, ক্ষেত্রফল ১০
  • একটি parallelogram, ক্ষেত্রফল ১০
  • একটি রেখাংশ, ক্ষেত্রফল ০

একটি বর্গ matrix P-কে কোনো subspace-এ orthogonal projection বলা যায় ঠিক কোন শর্তে?

  • det P = 0
  • P² = P আর Pᵀ = P
  • PᵀP = I
  • শুধু P² = P

Intercept-সহ একটি linear regression-এর least squares fit-এ residual vector r = y − Xŵ সম্পর্কে কোনটি সবসময় সত্য?

  • r = 0
  • r, y-এর সঙ্গে লম্ব
  • r-এর সব উপাদান সমান
  • r, X-এর প্রতিটি কলামের সঙ্গে লম্ব, তাই ∑ri = 0

একটি design matrix X-এর condition number ১০৬. Normal equations-এর XᵀX আর QR-এর R-এর condition number মোটামুটি কত?

  • XᵀX: ১০১২, R: ১০৬
  • দুটিই ১০৬
  • XᵀX: ১০৬, R: ১০১২
  • XᵀX: ১০৩, R: ১০৬

Gram–Schmidt প্রয়োগ করা হলো a1 = (3, 4) আর a2 = (1, 0)-এ (এই ক্রমে)। দ্বিতীয় orthonormal vector q2 কত?

  • (1, 0)
  • (0.8, −0.6)
  • (0.64, −0.48)
  • (0.6, 0.8)

R = 90° rotation [[0, −1], [1, 0]] আর F = x-অক্ষে reflection [[1, 0], [0, −1]]. “আগে R, তারপর F” এই map-এর matrix কোনটি?

  • [[0, 1], [1, 0]]
  • [[0, −1], [1, 0]]
  • [[0, −1], [−1, 0]]
  • [[−1, 0], [0, −1]]

n = ১০০ সারি আর p = ৫টি linearly independent কলামের (intercept-সহ) একটি design matrix X-এর hat matrix H = X(XᵀX)−1Xᵀ-এর trace আর leverage-এর গড় কত?

  • trace ১০০, গড় leverage ১
  • trace ৯৫, গড় leverage ০.৯৫
  • trace ০, গড় leverage ০
  • trace ৫, গড় leverage ০.০৫

শব্দকোষ

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

মনে রাখুন

  1. Matrix = linear map: কলামগুলো basis vector-এর গন্তব্য; Ax মানে কলামগুলোর weighted যোগফল। গুণ মানে পরপর map, ডানেরটি আগে, আর ক্রম বদলালে ফল বদলায়।
  2. det = ক্ষেত্রফল বা আয়তনের গুণক, চিহ্নে দিক; det = 0 মানে চ্যাপ্টা আর inverse নেই। তবে “প্রায় singular” মাপতে condition number.
  3. Orthogonal Q: QᵀQ = I, দৈর্ঘ্য আর কোণ অক্ষত, det = ±১, cond = ১; rotation আর reflection.
  4. Projection: P = A(AᵀA)−1Aᵀ (orthonormal কলামে QQᵀ), P² = P, Pᵀ = P, trace = rank; least squares-এ ŷ = Hy, Xᵀr = 0, leverage = hii.
  5. QR: Gram–Schmidt-এর খাতাই R; Rw = Qᵀy, cond(R) = cond(X) বনাম cond(XᵀX) = cond(X)². Software Householder ব্যবহার করে; classical Gram–Schmidt float-এ লম্বতা হারায়।

পরের পর্বে: এই পর্বের প্রতিটি map কিছু দিকে শুধু টানে বা চাপে, ঘোরায় না। পর্ব ০৮ (Eigenvalue আর eigenvector — ধারণা থেকে PCA পর্যন্ত) সেই বিশেষ দিকগুলো খুঁজবে, ২×২-এ হাতে আর power iteration দিয়ে, আর দেখাবে covariance matrix-এর সেই দিকগুলোই PCA-র অক্ষ।

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

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