~2-3 giờ · cần xong bài 4 trước
Tới giờ mọi ma trận bạn gặp đều vuông: vào
R²raR², vàoR³raR³. Bài này bỏ ràng buộc đó — và ngay lập tức bạn có embedding,nn.Embedding,Q = X·W^Qtrong attention, và LoRA. Tất cả chỉ là một ma trậnm × nvớim ≠ n.
🧭 Làm theo thứ tự này
- Xem trước: 3Blue1Brown tập 8 Nonsquare matrices as transformations between dimensions. Xem kỹ, đừng lướt. Giữ câu hỏi: chiếu từ 100.000 chiều từ vựng xuống 768 chiều thì mất gì, giữ gì?
- Làm ô ⚡ bên dưới: chạy code với ma trận nén
Pvà ma trận nângE. - Đọc §1-6. (§4b và §4c là phần mở rộng về bình phương tối thiểu, để sau.)
- Code §7: tự cài phép nhân ma trận
m × nvà hạng bằng khử Gauss. Tự viết xong mới mở code mẫu trong bài. - Làm 3 bài tay ở §10 (30 phút, trên giấy).
- Tự kiểm §9.
Xong bài khi: nói được vì sao nén chiều thì bắt buộc mất thông tin.
⚡ 30 phút đầu — làm trước, đọc sau
import numpy as np
P = np.array([[1.,0.,0.],[0.,1.,0.]]) # 2×3 — NÉN R³ xuống R²
E = np.array([[1.,0.],[0.,1.],[1.,1.]]) # 3×2 — NÂNG R² lên R³
print(P @ np.array([1.,2.,5.])) # đích: [1. 2.]
print(P @ np.array([1.,2.,99.])) # đích: [1. 2.] ← GIỐNG HỆT dòng trên
print(E @ np.array([3.,2.])) # đích: [3. 2. 5.]
print(P @ E) # đích: ma trận đơn vị 2×2
print(E @ P) # đích: [[1,0,0],[0,1,0],[1,1,0]] ← KHÔNG phải I₃
Hai dòng đầu ra cùng một kết quả dù đầu vào khác nhau → bạn vừa nhìn thấy mất thông tin. Hai dòng cuối ra khác nhau → nâng-rồi-nén hồi được, nén-rồi-nâng thì không.
Đó là toàn bộ bài này. Phần còn lại giải thích vì sao, và vì sao nó chính là embedding.
1. m × n nghĩa là gì — và đọc ngược mới đúng
Một ma trận m × n có m hàng, n cột. Nhưng ý nghĩa của nó là:
Ma trận
m × nlà một phép biến đổi từRⁿ(vào) sangRᵐ(ra). Số cột = chiều đầu vào. Số hàng = chiều đầu ra.
Đây là BẪY số 1 và gần như ai cũng vấp: m × n đọc là "m nhân n" nhưng chiều đi là n → m, ngược thứ tự chữ.
Cách nhớ không bao giờ sai — dùng lại đúng ý bài 2 (cột = nơi vector cơ sở đáp xuống):
P = [ 1 0 0 ] 3 cột → có 3 vector cơ sở ở đầu vào → vào từ R³
[ 0 1 0 ] 2 hàng → mỗi cột là một điểm 2 chiều → ra ở R²
Mỗi cột vẫn là "nơi một vector cơ sở đáp xuống", y hệt bài 2. Chỉ khác một điều: chỗ đáp xuống nằm ở không gian khác.
Ẩn dụ backend: ma trận là một hàm có chữ ký (n) => (m). Ma trận vuông là hàm (n) => (n) — cùng kiểu vào ra nên ghép chuỗi thoải mái. Ma trận không vuông đổi kiểu, nên phải khớp chữ ký khi ghép:
A (m×k) · B (k×n) ✓ hợp lệ, ra (m×n) — số cột của A khớp số hàng của B
A (2×3) · B (2×3) ✗ lỗi chiều
Quy tắc "chiều trong phải khớp" không phải luật tuỳ tiện. Nó chỉ là: đầu ra của hàm này phải đúng kiểu với đầu vào của hàm kia. Y như compose hai function trong TypeScript.
2. Nén chiều (m < n) — và vì sao MẤT thông tin là điều bắt buộc
Ma trận nén đơn giản nhất, sẽ dùng suốt bài:
P = [ 1 0 0 ] 2×3 → R³ ⟶ R²
[ 0 1 0 ]
Đọc bằng cột (bài 2):
vector cơ sở của R³ |
đáp xuống | = cột nào của P |
|---|---|---|
î = (1,0,0) |
(1, 0) |
cột 1 ✓ |
ĵ = (0,1,0) |
(0, 1) |
cột 2 ✓ |
k̂ = (0,0,1) |
(0, 0) |
cột 3 — cột 0 |
Cột thứ ba bằng vector 0. Nghĩa là: trục z bị nghiền thẳng về gốc toạ độ. Nó biến mất.
Ví dụ tính tay
P · (1, 2, 5) = ( 1·1 + 0·2 + 0·5 , 0·1 + 1·2 + 0·5 ) = (1, 2)
P · (1, 2, 99) = ( 1·1 + 0·2 + 0·99, 0·1 + 1·2 + 0·99) = (1, 2)
P · (1, 2, −7) = (1, 2)
Ba điểm khác hẳn nhau trong R³ đáp xuống đúng một điểm (1,2) trong R². Thật ra cả một đường thẳng đứng vô hạn (1, 2, t) đều dồn về đó.
Vì sao không undo được? Vì hàm không đơn ánh. Nhìn bằng phép trừ:
(1,2,99) − (1,2,5) = (0, 0, 94) ← một vector KHÁC 0
P · (0, 0, 94) = (0, 0) ← nhưng ảnh của nó BẰNG 0
Tập tất cả vector bị nghiền về 0 gọi là không gian null (null space) của P — đúng khái niệm của
bài 3 §6, giờ áp cho ma trận không vuông. Ở đây nó là trục z, 1 chiều.
Định lý hạng — số khuyết, cách phát biểu ngắn nhất
hạng(P) + số chiều null space = số chiều ĐẦU VÀO
2 + 1 = 3 ✓
Nối về bài 3: hạng là số chiều mà ảnh thực sự chiếm. Với ma trận m × n:
hạng ≤ min(m, n) — luôn luôn, không có ngoại lệ.
Vì hạng bị chặn bởi cả chiều vào lẫn chiều ra. Khi m < n, hạng ≤ m < n, nên null space bắt buộc có ít nhất n − m chiều — nghĩa là bắt buộc có thông tin bị nghiền mất.
Đây là điểm tận cùng của mục này: nén chiều mất thông tin không phải vì ma trận được chọn tồi. Nó là hệ quả số học. Bạn đang cố nhét
nchiều thông tin vàomô, vớim < n— pigeonhole. Không có ma trận nào thoát được.Ẩn dụ backend: đây là hàm băm.
md5không "kém"; nó buộc phải va chạm vì đầu ra 128 bit còn đầu vào không giới hạn. Không tồn tạimd5.undo().
BỎ QUA ĐƯỢC: cách tính cơ sở của null space bằng tay cho ma trận lớn. Bạn cần hiểu null space là gì; scipy.linalg.null_space sẽ tính hộ mãi mãi.
3. Nâng chiều (m > n) — không mất gì, nhưng cũng không phủ hết
Chiều ngược lại:
E = [ 1 0 ] 3×2 → R² ⟶ R³
[ 0 1 ]
[ 1 1 ]
Đọc bằng cột: î = (1,0) đáp xuống (1,0,1); ĵ = (0,1) đáp xuống (0,1,1). Chỉ có 2 cột vì đầu vào chỉ có 2 vector cơ sở.
Ví dụ tính tay
E · (1, 0) = (1, 0, 1)
E · (0, 1) = (0, 1, 1)
E · (3, 2) = (3, 2, 5) ← 3·(1,0,1) + 2·(0,1,1)
E · (1, 2) = (1, 2, 3)
Nhìn kỹ mọi kết quả: toạ độ thứ ba luôn bằng tổng hai toạ độ đầu. Tổng quát:
E · (a, b) = (a, b, a + b) → z = x + y → x + y − z = 0
Đó là phương trình một mặt phẳng đi qua gốc toạ độ trong R³ (pháp tuyến (1, 1, −1)).
Ảnh của
Ekhông phải cảR³. Nó chỉ là một mặt phẳng — một tấm giấy 2 chiều lơ lửng trong không gian 3 chiều.
Kiểm bằng số, chọn ba điểm:
điểm trong R³ |
x + y − z |
có nằm trong ảnh của E không |
|---|---|---|
(1, 2, 3) |
0 |
có — chính là E·(1,2) |
(1, 2, 4) |
−1 |
không — không (a,b) nào cho ra nó |
(0, 0, 1) |
−1 |
không |
hạng(E) = 2 = min(3, 2). Đã kịch trần. Không ma trận 3×2 nào có hạng 3 được — vì đầu vào chỉ có 2 chiều để mà căng ra.
Nâng chiều không tạo ra thông tin. Nó chỉ đặt thông tin 2 chiều vào một không gian rộng hơn. Giống việc lưu một ảnh 2 chiều vào cột
BLOBdung lượng gấp trăm lần: chỗ chứa to hơn, nội dung vẫn thế.
Đây cũng là câu trả lời cho một hiểu lầm phổ biến: "cứ tăng số chiều là mô hình mạnh hơn". Sai. Tăng chiều bằng một phép nhân ma trận không thêm được gì — vì ảnh vẫn kẹt trong một không gian con n chiều. Muốn "lấp đầy" không gian mới thì phải có phi tuyến (đúng lý do đã chứng minh ở bài 2 §4).
4. Ghép hai chiều lại: cái nào undo được?
Giờ ghép P và E. Nhớ quy ước bài 2: đọc từ phải sang trái.
P · E — nâng lên trước, rồi nén xuống (R² → R³ → R²)
P · E = [ 1 0 0 ] · [ 1 0 ] = [ 1 0 ] = I₂
[ 0 1 0 ] [ 0 1 ] [ 0 1 ]
[ 1 1 ]
Bằng đúng ma trận đơn vị. Đưa (3,2) vào: E biến thành (3,2,5), P cắt trục z đi, còn lại (3,2). Khôi phục nguyên vẹn.
E · P — nén xuống trước, rồi nâng lên (R³ → R² → R³)
E · P = [ 1 0 ] [ 1 0 0 ] [ 1 0 0 ]
[ 0 1 ] · [ 0 1 0 ] = [ 0 1 0 ] ≠ I₃
[ 1 1 ] [ 1 1 0 ]
Kiểm bằng số:
(E·P) · (1, 2, 5) = (1, 2, 3)
(E·P) · (1, 2, 99) = (1, 2, 3) ← z = 5 và z = 99 đều thành 3
det(E·P) = 0, hạng(E·P) = 2. Không nghịch đảo được, vĩnh viễn.
| làm gì | kết quả | undo được? | |
|---|---|---|---|
P·E |
nâng R²→R³ rồi nén về R² |
I₂ |
được — không có gì để mất |
E·P |
nén R³→R² rồi nâng lên R³ |
≠ I₃, det = 0 |
không — trục z đã chết ở bước 1 |
Kết luận đắt nhất của bài: thông tin mất ở bước nén, và không bước nào sau đó lấy lại được. Ma trận nâng chiều không phải "nghịch đảo" của ma trận nén — nó chỉ là một phép đặt-vào-không-gian-lớn-hơn.
Ẩn dụ backend:
gzip→gunzipkhôi phục được vì nén không mất.jpegchất lượng 40% thì không — bạn resize ảnh lên 4K vẫn không lấy lại được chi tiết đã mất.E·Plà cái thứ hai.
Đây cũng là lý do một autoencoder phải học: nó không thể khôi phục hoàn hảo, nó chỉ có thể học cách vứt đi thứ ít quan trọng nhất.
4b. ⭐ AᵀA có ĐÚNG không gian null của A — và đó là toàn bộ điều kiện của phương trình chuẩn
(bổ sung 19/8 — nguồn: Strang tr.182-187, §3.3)
E·P ≠ I₃ nghĩa là: khi A không vuông, hệ A x = b thường vô nghiệm — b đơn giản không nằm
trong ảnh của A (§3). Vậy làm gì? Strang đưa ra một thao tác thô bạo đến mức dễ bỏ qua:
Cứ nhân cả hai vế của phương trình VÔ NGHIỆM
A x = bvớiAᵀ.AᵀA x̂ = Aᵀb ← phương trình CHUẨN (normal equations)
AᵀA là ma trận vuông n×n, nên nếu nó khả nghịch thì x̂ = (AᵀA)⁻¹Aᵀb. Đây chính là công thức
mà môn 3 bài 5 dùng làm baseline cho hồi quy
tuyến tính — giờ bạn biết nó từ đâu ra: nhân hai vế với Aᵀ, không có gì hơn.
Câu hỏi thật là: khi nào AᵀA khả nghịch? Sách hay ghi "khi các cột của A độc lập" như một giả
thiết kỹ thuật tuỳ tiện. Nó không tuỳ tiện — nó suy ra từ một dòng:
xᵀAᵀAx = (Ax)ᵀ(Ax) = ‖Ax‖²
Nên AᵀAx = 0 ⟹ xᵀAᵀAx = 0 ⟹ ‖Ax‖² = 0 ⟹ Ax = 0. Chiều ngược lại hiển nhiên. Kết luận:
AᵀAcó ĐÚNG cùng không gian null vớiA. Không "gần bằng", không "chứa" — bằng nhau. Kéo theorank(AᵀA) = rank(A), vàAᵀAkhả nghịch ⟺ cột củaAđộc lập. Vừa cần vừa đủ.
Kiểm bằng số — A là 6×3 với cột3 = cột1 + cột2, nên x = (1, 1, −1) nằm trong null space:
import numpy as np
from scipy.linalg import null_space
rng = np.random.default_rng(0)
A = rng.normal(size=(6, 2))
A = np.column_stack([A, A[:, 0] + A[:, 1]]) # cột3 = cột1 + cột2 → cột phụ thuộc
x = np.array([1., 1., -1.])
print(f"‖Ax‖² = {np.sum((A @ x) ** 2):.2e}") # 0.00e+00
print(f"xᵀAᵀAx = {x @ (A.T @ A) @ x:.2e}") # -8.88e-16 ← cùng số 0, khác đường tính
assert np.linalg.matrix_rank(A) == np.linalg.matrix_rank(A.T @ A) == 2
assert null_space(A).shape[1] == null_space(A.T @ A).shape[1] == 1
(Con số -8.88e-16 thay vì 0.0 chằn là dấu phẩy động, không phải toán sai: ‖Ax‖² cộng bình
phương nên không âm được, còn xᵀAᵀAx đi qua ma trận AᵀA nên có chỗ trừ. Cùng một đại lượng,
hai đường tính, hai kiểu sai số làm tròn.)
Vì sao khó tự nghĩ ra
Vì "phương trình chuẩn" được dạy như một công thức phải nhớ, và "cột độc lập" như một dòng chữ
nhỏ. Không ai chỉ ra rằng hai vế đó là một sự thật. Không thấy đẳng thức null space thì bạn
không biết điều kiện đó là cần, là đủ, hay chỉ là cho chắc — và khi AᵀA suy biến thật, bạn sẽ
đi tìm lỗi ở chỗ khác thay vì đi tìm cột thừa trong đặc trưng của mình (đúng §2 bài 3, chỉ đổi tên).
🪤 BẪY — Strang trình bày phương trình chuẩn mà KHÔNG cảnh báo chỗ này
AᵀA bình phương số điều kiện: κ(AᵀA) = κ(A)². Nghĩa là bạn tự tay biến một bài toán hơi khó
thành một bài toán khó gấp bội, chỉ bằng phép nhân trông vô hại kia.
U, _ = np.linalg.qr(rng.normal(size=(50, 3)))
V, _ = np.linalg.qr(rng.normal(size=(3, 3)))
print(" κ(A) đặt trước | κ(A) đo được | κ(AᵀA) đo được | κ(AᵀA)/κ(A)²")
for k in [1e2, 1e3, 1e6]:
B = U @ np.diag([1.0, 1 / np.sqrt(k), 1 / k]) @ V.T # dựng A có κ đúng bằng k
kA, kB = np.linalg.cond(B), np.linalg.cond(B.T @ B)
print(f" {k:>13.0e} | {kA:>16.6e} | {kB:>17.6e} | {kB / kA**2:.6f}")
assert abs(kB / kA ** 2 - 1.0) < 1e-3
κ(A) đặt trước |
κ(A) đo được |
κ(AᵀA) đo được |
tỉ số |
|---|---|---|---|
| 1e+02 | 1.000000e+02 | 1.000000e+04 | 1.000000 |
| 1e+03 | 1.000000e+03 | 1.000000e+06 | 1.000000 |
| 1e+06 | 1.000000e+06 | 9.999852e+11 | 0.999985 |
Với float64 (~16 chữ số), κ(A) = 1e6 là hoàn toàn xử lý được; κ(AᵀA) = 1e12 thì ăn mất
12 trong 16 chữ số. Đó là lý do trong thực tế người ta không lập AᵀA mà giải qua QR
(hoặc SVD — bài 9). np.linalg.lstsq làm đúng chuyện đó; np.linalg.solve(A.T@A, A.T@b) thì không.
Tôi đã grep toàn cuốn Strang: không có một lần nào cảnh báo về việc bình phương số điều kiện, trong khi phương trình chuẩn được trình bày như phương pháp chính. Sách không sai — nó chỉ không nói. Cơ chế số học nằm ở bài 6 môn 3, và đây đúng loại chỗ mà quy tắc 1 cảnh báo: lỗi giết bạn là chi tiết thiết lập.
4c. ⭐ Bình phương tối thiểu không "nới lỏng mô hình" — nó THAY dữ liệu
(bổ sung 19/8 — nguồn: Strang tr.171 (§3.2), tr.187 (§3.3))
§4b cho công cụ đại số. Mục này cho hình ảnh, và hình ảnh mới là thứ ở lại.
(a) Bài toán khoảng cách TỰ ĐỘNG sinh ra tính trực giao
a và b là hai vector bất kỳ, chẳng vuông góc gì với nhau. Nhưng vừa hỏi
"điểm nào trên đường thẳng chứa a gần b nhất?" là trực giao xuất hiện ngay lập tức:
đoạn nối b tới điểm gần nhất p bắt buộc phải vuông góc với đường thẳng đó. Nếu không vuông
góc, nghiêng nó đi một chút sẽ ngắn hơn — nên nó chưa phải điểm gần nhất.
Strang: "the distance problem automatically brings in orthogonality".
Đó là lý do phép chiếu (bài 1 §2) và bình phương tối thiểu không phải hai chủ đề. Chúng là một bài toán: hỏi "gần nhất" thì được trực giao miễn phí, và trực giao chính là
Aᵀe = 0— phương trình chuẩn ở §4b, đọc ngược.
(b) p chứ không phải b mới là thứ mô hình học được
Ba điểm đo (0,1), (1,3), (2,2). Chúng không thẳng hàng, nên A x = b vô nghiệm:
A = [ 1 0 ] b = ( 1, 3, 2 ) ← cột 1 là hằng số, cột 2 là x
[ 1 1 ]
[ 1 2 ]
Cách nói quen thuộc — "ta tối thiểu hoá tổng bình phương sai số" — là ngôn ngữ giải tích và nó không cho biết chuyện gì đang xảy ra. Strang nói bằng ngôn ngữ khác:
"Least squares replaces points b that are not on a line by points p that are!"
LSQ không nới lỏng gì cả. Nó thay
bbằngp— bộ ba điểm thẳng hàng gầnbnhất — rồi giảiA x̂ = pchính xác, không sai số nào.
import numpy as np
A = np.array([[1., 0.], [1., 1.], [1., 2.]])
b = np.array([1., 3., 2.]) # ba điểm KHÔNG thẳng hàng
xhat = np.linalg.lstsq(A, b, rcond=None)[0] # [1.5 0.5]
p = A @ xhat # [1.5 2. 2.5] ← THẲNG HÀNG
e = b - p # [-0.5 1. -0.5]
print(xhat, p, e, f"‖e‖ = {np.linalg.norm(e):.6f}") # ‖e‖ = 1.224745
print("Aᵀe =", A.T @ e) # [4.44e-16 2.22e-16] ≈ (0, 0)
assert np.allclose(A.T @ e, 0, atol=1e-12) # e TRỰC GIAO với MỌI cột của A
assert np.allclose(np.diff(p, 2), 0) # p thẳng hàng: sai phân cấp 2 = 0
x2 = np.linalg.lstsq(A, p, rcond=None)[0]
assert np.allclose(x2, xhat) and np.linalg.norm(A @ x2 - p) < 1e-12 # A x̂ = p CHÍNH XÁC
Aᵀe = 0 là toàn bộ nội dung: phần dư e trực giao với mọi cột của A, tức e nằm trong
left null space — không gian null của Aᵀ. Nó là phần của b mà A không thể chạm tới,
và vì thế cũng là phần mô hình không thể học.
Cách nhìn "thay dữ liệu" trả lời ngay một câu hỏi mà cách nói giải tích không trả lời được: mô hình học được cái gì? Không phải
b. Làp— hình chiếu củablên không gian cột. Mọi thứ ngoài không gian cột bị vứt trước khi việc học bắt đầu. Đó là nền để hiểu bias của mô hình ở môn 6 bài 3: bias không phải "học chưa đủ lâu", nó là khoảng cách từbtới không gian cột, và không vòng lặp huấn luyện nào rút ngắn được nó.
Vì sao khó tự nghĩ ra: cách nói quen thuộc "tối thiểu hoá tổng bình phương sai số" là ngôn ngữ
giải tích — nó mô tả một phép tối ưu và dừng ở đó. Nó không nói e sẽ nằm ở đâu, không nói vì
sao Aᵀe = 0, không nói mô hình thực sự nhìn thấy dữ liệu nào. Bạn có thể dùng LSQ mười năm bằng
ngôn ngữ đó mà không bao giờ có lý do nghĩ tới hình chiếu, vì công thức không bắt bạn nghĩ tới.
🪤 BẪY — sai số được bình phương là khoảng cách DỌC, không phải khoảng cách vuông góc
Đây là chỗ trực giác đánh lừa: e ⟂ cột của A không có nghĩa là ta đo khoảng cách vuông góc
tới đường thẳng trên mặt phẳng dữ liệu. Trực giao xảy ra trong ℝ³ (không gian của các vector b),
còn cái được bình phương là sai số dọc yᵢ − (m·xᵢ + c).
Nhầm hai cái là nhầm regression thành PCA. Cùng ba điểm, hai tiêu chí, hai đường khác nhau:
xs, ys = np.array([0., 1., 2.]), b
m_reg, c_reg = xhat[1], xhat[0] # tối thiểu sai số DỌC
M = np.column_stack([xs, ys]); M = M - M.mean(axis=0)
d = np.linalg.svd(M)[2][0] # hướng phương sai lớn nhất = PCA
m_tls = d[1] / d[0]; c_tls = ys.mean() - m_tls * xs.mean()
sse_doc = lambda m, c: np.sum((ys - m * xs - c) ** 2)
sse_vuong = lambda m, c: np.sum((ys - m * xs - c) ** 2 / (1 + m * m))
assert abs(m_reg - 0.5) < 1e-12 and abs(m_tls - 1.0) < 1e-12
assert sse_doc(m_reg, c_reg) < sse_doc(m_tls, c_tls) # 1.5 < 2.0
assert sse_vuong(m_tls, c_tls) < sse_vuong(m_reg, c_reg) # 1.0 < 1.2
| đường thẳng | sai số DỌC | sai số VUÔNG GÓC | |
|---|---|---|---|
| Regression (LSQ) | y = 0.5x + 1.5 |
1.5000 ⭐ | 1.2000 |
| TLS / PCA | y = 1.0x + 1.0 |
2.0000 | 1.0000 ⭐ |
Hệ số góc 0.5 so với 1.0 — gấp đôi nhau, trên đúng ba điểm đó. Mỗi đường thắng ở đúng tiêu chí của nó và thua ở tiêu chí kia. Không có đường nào "đúng hơn"; có hai câu hỏi khác nhau:
- "biết
x, đoány" → sai số dọc → regression - "tìm trục chính của đám mây điểm" → sai số vuông góc → PCA (bài 9, môn 6 bài 8)
Ai vẽ "đường thẳng khớp nhất" trong đầu bằng hình ảnh khoảng cách vuông góc sẽ dùng nhầm công cụ mà không bao giờ biết — vì cả hai đều chạy, đều cho một đường, đều trông hợp lý.
4d. ⭐ Khi m < n: vô số nghiệm — chọn nghiệm NGẮN NHẤT, và đó chính là giả nghịch đảo (bổ sung từ VMLS — 2026-10)
(nguồn: VMLS tr.342 và tr.350-351, §16.1.1 và §16.3 (bài toán chuẩn nhỏ nhất); tr.332-333, §15.5.2 (đồng nhất thức "kernel trick"))
§4b-4c lo ca A cao (quá nhiều phương trình, vô nghiệm). Ca ngược lại — A thấp-béo như P ở ô ⚡ — thì Ax = b có vô số nghiệm,
vì cộng thêm bất kỳ vector null (§2) vẫn là nghiệm. Chọn cái nào? Sách chọn cái ngắn nhất, và công thức rơi ra không cần giải tích:
x̂ = Aᵀ(AAᵀ)⁻¹ b = A†b ← AAᵀ là m×m nhỏ; khả nghịch khi các HÀNG của A độc lập
x̂ = Aᵀz nằm trong không gian hàng nên vuông góc với mọi n ∈ null(A): x̂ᵀn = zᵀ(An) = 0. Mọi nghiệm khác x = x̂ + n có
‖x‖² = ‖x̂‖² + ‖n‖² — Pythagoras của bài 1, nên x̂ ngắn nhất.
import numpy as np, time
from scipy.linalg import null_space
P = np.array([[1., 0, 0], [0, 1, 0]]); Pd = P.T@np.linalg.inv(P@P.T)
print(Pd@P, Pd@[1., 2.]) # diag(1,1,0) · (1,2,0): "giải nén" tốt nhất ném trục z, đúng §2
g = np.random.default_rng(0); A, b = g.normal(size=(3, 8)), g.normal(size=3)
xh = A.T@np.linalg.solve(A@A.T, b); assert np.allclose(xh, np.linalg.pinv(A)@b) and np.allclose(A@xh, b)
n_ = null_space(A)@g.normal(size=5) # một vector null bất kỳ
assert np.allclose(A@(xh + n_), b) and np.isclose((xh + n_)@(xh + n_), xh@xh + n_@n_) and abs(xh@n_) < 1e-12
def gd(x, lr=.02, it=20000):
for _ in range(it): x = x - lr*2*A.T@(A@x - b)
return x
x0 = g.normal(size=8) # GD từ 0 dừng ở x̂; từ x0 mang nguyên thành phần null của x0
assert np.allclose(gd(np.zeros(8)), xh) and np.allclose(gd(x0), xh + (np.eye(8) - np.linalg.pinv(A)@A)@x0)
for lam in (1e-6, 1e-9, 1e-12): # ridge λ→0 về A†b — hai cách tính cho HAI độ chính xác
pri = np.linalg.solve(A.T@A + lam*np.eye(8), A.T@b); dua = A.T@np.linalg.solve(A@A.T + lam*np.eye(3), b)
print(f"λ={lam:g} p×p: {abs(pri - xh).max():.1e} m×m: {abs(dua - xh).max():.1e}")
X, y = g.normal(size=(50, 2000)), g.normal(size=50) # 50 mẫu, 2000 đặc trưng
t = time.perf_counter(); w1 = np.linalg.solve(X.T@X + np.eye(2000), X.T@y); t1 = time.perf_counter() - t
t = time.perf_counter(); w2 = X.T@np.linalg.solve(X@X.T + np.eye(50), y); t2 = time.perf_counter() - t
assert np.allclose(w1, w2) and t1 > 10*t2; print(f"{t1*1e3:.1f} ms vs {t2*1e3:.2f} ms") # máy này, 3 lần chạy: 39-48 ms vs 0.17-0.19 ms
λ=1e-06 p×p: 1.6e-07 m×m: 1.6e-07
λ=1e-09 p×p: 2.8e-07 m×m: 1.6e-10 ← p×p chạm sàn làm tròn rồi TỆ DẦN (λ=1e-12: 2.7e-04); m×m vẫn hội tụ (1.6e-13)
Ba hệ quả. (1) P†P = chiếu lên không gian hàng: nén-rồi-nâng tốt nhất vẫn mất trục z (§2) — E·P ≠ I ở §4 không sửa được bằng ma trận nào. (2) GD khởi tạo 0 tự chọn nghiệm này:
gradient 2Aᵀ(Ax − b) luôn nằm trong không gian hàng nên bước đi không bao giờ có thành phần null — cơ chế đằng sau câu "pinv chọn nghiệm chuẩn nhỏ nhất" ở
môn 6 bài 3 §7. (3) Đồng nhất thức (AᵀA + λI)⁻¹Aᵀ = Aᵀ(AAᵀ + λI)⁻¹ đổi một nghịch đảo n×n thành m×m: 50 mẫu × 2000 đặc trưng,
khoảng 40-48 ms so với 0.2 ms (hơn 200 lần) — quy tắc "(lớn)×(nhỏ)²"; với embedding hàng nghìn chiều và vài trăm mẫu, đây là đường tính ridge rẻ hơn (môn 6 bài 4).
Phạm vi: "ngắn nhất" là một prior, không phải sự thật — nếu giá trị thật có thành phần trong null space (A không nhìn thấy nó), sai số ≥ chính thành phần đó, bao nhiêu dữ liệu b cũng không cứu.
5. Đây CHÍNH LÀ embedding
Giờ đổi tên các trục và không cần thêm toán nào nữa.
Một mô hình ngôn ngữ có bộ từ vựng V = 100.000 từ. Biểu diễn thô nhất của một từ là one-hot: vector 100.000 chiều, đúng một ô bằng 1, còn lại bằng 0.
"mèo" = (0, 0, ..., 0, 1, 0, ..., 0) ← 100.000 chiều, số 1 ở đúng vị trí của từ này
One-hot có một tính chất chết người: mọi cặp từ đều vuông góc. cos("mèo", "hổ") = 0, giống hệt cos("mèo", "cái bàn") = 0. Theo bài 1, vuông góc nghĩa là không chia sẻ thông tin gì với nhau. Vậy trong không gian one-hot, mọi từ đều xa lạ với mọi từ như nhau. Vô dụng cho ngữ nghĩa.
Embedding = một ma trận không vuông chiếu R¹⁰⁰⁰⁰⁰ → R⁷⁶⁸. Hết. Đúng thứ ở §2, chỉ khác con số.
Mô phỏng bằng số đẹp: 5 từ, nén R⁵ → R²
Bảng embedding W — hàng thứ i là embedding của từ thứ i:
chiều 1 chiều 2
mèo [ 3 , 4 ]
chó [ 4 , 3 ]
hổ [ 6 , 8 ]
xe [ −4 , 3 ]
bàn [ 0 , −5 ]
BẪY số 2 — quy ước hàng/cột. Ở §2-§4 ta viết
P · v(ma trận trái, vector cột). Thư viện thật (PyTorch, numpy) viếtx @ W(vector hàng trái, ma trận phải) để xử lý cả batch cùng lúc. Hai cách là chuyển vị của nhau: ma trận2×5theo kiểu toán = ma trận5×2theo kiểu thư viện. Đừng cố nhớ — cứ nhìn.shapevà hỏi "chiều nào là vào, chiều nào là ra". Ở đâyWlà5×2: vào 5, ra 2.
Nhân one-hot của "hổ" (vị trí 2) với W:
(0, 0, 1, 0, 0) · W = 0·(3,4) + 0·(4,3) + 1·(6,8) + 0·(−4,3) + 0·(0,−5) = (6, 8)
Nối lại bài 1 — tính cosine giữa các từ sau khi nén:
| cặp | tích vô hướng | cosine | đọc là |
|---|---|---|---|
| mèo, hổ | 50 | 1.00 | trùng hướng hoàn toàn |
| mèo, chó | 24 | 0.96 | rất gần nghĩa |
| mèo, xe | 0 | 0.00 | không liên quan |
| mèo, bàn | −20 | −0.80 | ngược hướng |
| chó, xe | −7 | −0.28 | hơi ngược hướng |
(Ba số 24 / 0 / −7 chính là ba số bạn đã tính tay ở bài 1 — cùng bộ vector, giờ có tên gọi mới.)
Đây là điều one-hot không bao giờ làm được. Trong R⁵ one-hot mọi cosine đều bằng 0. Sau khi nén xuống R², các từ có quan hệ với nhau. Đó là thứ ta được.
Còn mất gì?
hạng(W) = 2. Số chiều bị nghiền về 0 là 5 − 2 = 3. Tìm một vector trong null space bằng số đẹp:
hổ − 2·mèo = (6, 8) − 2·(3, 4) = (0, 0) ← khác 0 ở đầu vào, bằng 0 ở đầu ra
5·chó + 5·xe + 6·bàn = 5(4,3) + 5(−4,3) + 6(0,−5) = (0, 0)
Cụ thể hơn, cos(mèo, hổ) = 1.00 chính xác bằng 1: sau khi nén, hai từ này cùng hướng tuyệt đối. Một hệ retrieval dùng cosine sẽ không phân biệt nổi chúng. Với 2 chiều thì đó là chuyện phải xảy ra — không đủ chỗ.
Mất thông tin trong embedding không phải bug. Nó là cái giá bắt buộc để có được quan hệ ngữ nghĩa. Câu hỏi kỹ thuật duy nhất là: mất cái gì — và toàn bộ việc huấn luyện embedding chính là đi tìm ma trận nén nào vứt đi thứ ít quan trọng nhất.
Chật đến mức nào? Số thật
Lấy n = 2000 vector đơn vị ngẫu nhiên, đo max |cosine| giữa mọi cặp (2000 điểm ngẫu nhiên trong d chiều):
số chiều d |
max │cos│ giữa 2 vector bất kỳ |
│cos│ trung bình |
|---|---|---|
| 2 | 1.0000 | 0.6363 |
| 8 | 0.9892 | 0.2909 |
| 64 | 0.5814 | 0.1001 |
| 768 | 0.1747 | 0.0288 |
Đọc bảng này: ở 2 chiều, có hai từ trùng hướng hoàn toàn — không thể phân biệt. Ở 768 chiều, cặp giống nhau nhất cũng chỉ đạt 0.17, và trung bình 0.03 — gần như mọi cặp đều vuông góc.
Đây là câu trả lời định lượng cho "vì sao 768 chứ không phải 8". Chiều càng cao, càng nhiều hướng gần vuông góc để xếp các nghĩa khác nhau vào mà không chồng lên nhau. Cột
│cos│trung bình tiến về 0 chính là "lời nguyền số chiều" (bài 1, câu tự kiểm 4) — nhưng nhìn từ mặt tốt của nó.
Với V = 100.000 và d = 768, pigeonhole cho ngay:
số từ = 100.000
số từ độc lập tuyến tính tối đa trong R⁷⁶⁸ = 768
số từ BẮT BUỘC phụ thuộc tuyến tính = 99.232
số chiều null space của phép chiếu ≥ 99.232
99.232 chiều thông tin bị nghiền phẳng. Và mô hình vẫn hoạt động — vì thông tin bị vứt phần lớn là thứ mô hình không cần.
6. Vì sao nn.Embedding là BẢNG TRA, không phải phép nhân
Đây là hệ quả kỹ thuật, và nó rơi thẳng ra từ ví dụ trên.
Nhân một vector one-hot với ma trận W:
(0, 0, 1, 0, 0) · W = 0·hàng₀ + 0·hàng₁ + 1·hàng₂ + 0·hàng₃ + 0·hàng₄ = hàng₂
Nhân one-hot với ma trận = lấy ra một hàng. Toàn bộ phép nhân là
V−1lần nhân với 0 rồi cộng vào, cộng đúng một hàng thật.
Đếm chi phí thật, với V = 100.000, d = 768, batch = 32 token:
| số phép nhân-cộng | |
|---|---|
| nhân ma trận đầy đủ | 2.457.600.000 |
| số hạng thực sự khác 0 | 24.576 |
| tỉ lệ lãng phí | 100.000 lần |
| phần trăm phép tính vô ích | 99,999 % |
Đo thời gian thật trên cùng một máy (numpy, float32):
matmul one-hot : ~6,6 ms (lần chạy đầu 40,8 ms — cache lạnh)
lookup W[ids] : ~0,019 ms (lần chạy đầu 0,037 ms — cũng cache lạnh)
→ nhanh hơn : ~350 lần (6,6 / 0,019 ≈ 347 · lần đầu: 40,8 / 0,037 ≈ 1100)
kết quả giống hệt nhau
Ẩn dụ SQL cho đúng bản chất:
-- cách "nhân ma trận": quét toàn bảng rồi nhân mọi hàng với 0 hoặc 1
SELECT SUM(vec * CASE WHEN id = :word_id THEN 1 ELSE 0 END) FROM embeddings;
-- cách lookup: đi thẳng bằng khoá chính
SELECT vec FROM embeddings WHERE id = :word_id;
Cùng kết quả. Một cái O(V·d), một cái O(d). Không framework nào lại đi làm cái đầu.
Vì thế
nn.Embeddingkhông có phép nhân ma trận nào bên trong. Nó là một tensor(V, d)cộng một phép index. Nhưng về mặt toán nó vẫn đúng là một ma trận không vuôngV → d, và mọi thứ ở §2 (hạng, null space, mất thông tin) vẫn áp dụng nguyên vẹn.
Hệ quả gradient, để trọn vẹn: khi backprop, chỉ những hàng được tra mới nhận gradient. Với batch = 32 token trên bảng V = 100.000 hàng, nhiều nhất 32 hàng được đụng tới — 99,968 % bảng đứng yên ở bước đó. Đó là lý do embedding thường dùng gradient thưa (sparse).
BỎ QUA ĐƯỢC: chi tiết cài đặt sparse=True và các optimizer hỗ trợ nó. Chỉ cần biết vì sao gradient thưa.
7. Code — tự cài, KHÔNG dùng hàm có sẵn
import numpy as np
# ---------- (1) nhân ma trận m×n với vector n chiều — tự cài ----------
def apply_map(M, v):
"""M: (m, n) · v: (n,) -> ra vector (m,). Không dùng @."""
m, n = M.shape
assert v.shape == (n,), f"chiều vào phải là {n}, bạn đưa {v.shape[0]}"
out = np.zeros(m)
for i in range(m):
for j in range(n):
out[i] += M[i, j] * v[j]
return out
# ---------- (2) hạng bằng khử Gauss — tự cài ----------
def rank_gauss(M, tol=1e-9):
"""Đếm số hàng khác 0 sau khi khử. Đây là ĐỊNH NGHĨA vận hành của hạng."""
A = M.astype(float).copy()
rows, cols = A.shape
r = 0
for c in range(cols):
if r >= rows:
break
p = r + int(np.argmax(np.abs(A[r:, c]))) # chọn trụ lớn nhất
if abs(A[p, c]) < tol:
continue
A[[r, p]] = A[[p, r]] # đổi chỗ hai hàng
A[r] = A[r] / A[r, c]
for k in range(rows):
if k != r:
A[k] = A[k] - A[k, c] * A[r]
r += 1
return r
P = np.array([[1., 0., 0.],
[0., 1., 0.]]) # nén: R^3 -> R^2
E = np.array([[1., 0.],
[0., 1.],
[1., 1.]]) # nâng: R^2 -> R^3
# (a) nén làm MẤT thông tin: hai điểm khác nhau, một ảnh
a = apply_map(P, np.array([1., 2., 5.]))
b = apply_map(P, np.array([1., 2., 99.]))
assert np.allclose(a, [1., 2.]) and np.allclose(b, [1., 2.])
assert np.allclose(apply_map(P, np.array([0., 0., 94.])), [0., 0.]) # hiệu ↦ 0
print("nén: (1,2,5) và (1,2,99) cùng đáp xuống", a, "✓ mất thông tin")
# (b) nâng KHÔNG phủ hết R^3: ảnh nằm trọn trên mặt phẳng x + y − z = 0
for w in [np.array([1., 0.]), np.array([0., 1.]), np.array([3., 2.]), np.array([1., 2.])]:
x, y, z = apply_map(E, w)
assert abs(x + y - z) < 1e-12
print("nâng: mọi ảnh của E thoả x+y−z=0 ✓ chỉ là một MẶT PHẲNG, không phủ hết R^3")
# (c) hạng tối đa = min(m, n) — tự cài phải khớp numpy
for M in [P, E, np.random.default_rng(7).standard_normal((5, 2))]:
assert rank_gauss(M) == np.linalg.matrix_rank(M) == min(M.shape)
print("rank_gauss khớp numpy, và = min(m,n) ✓")
# (d) nâng-rồi-nén khôi phục được; nén-rồi-nâng thì KHÔNG
assert np.allclose(P @ E, np.eye(2)) # R^2 -> R^3 -> R^2 : undo được
assert not np.allclose(E @ P, np.eye(3)) # R^3 -> R^2 -> R^3 : hỏng
assert abs(np.linalg.det(E @ P)) < 1e-12 # det = 0 → không nghịch đảo được
print("P@E =\n", P @ E, "\nE@P =\n", E @ P)
# ---------- (3) embedding = TRA BẢNG, không phải phép nhân ----------
words = ["mèo", "chó", "hổ", "xe", "bàn"]
W = np.array([[3., 4.], [4., 3.], [6., 8.], [-4., 3.], [0., -5.]]) # hàng i = từ i
def embed_matmul(ids, W):
"""Cách 'đúng sách': dựng one-hot rồi nhân ma trận."""
OH = np.zeros((len(ids), W.shape[0]))
for r, c in enumerate(ids):
OH[r, c] = 1.0
return OH @ W
def embed_lookup(ids, W):
"""Cách thư viện thật làm: chỉ đọc hàng."""
return np.stack([W[i] for i in ids])
ids = [0, 3, 1]
assert np.allclose(embed_matmul(ids, W), embed_lookup(ids, W))
assert np.allclose(embed_lookup(ids, W), W[ids]) # numpy fancy-index
print("one-hot @ W == W[ids] ✓ embedding layer chỉ là bảng tra")
# (e) nén 5 chiều → 2 chiều thì một tổ hợp KHÁC 0 của các từ lại về vector 0
z = np.array([-2., 0., 1., 0., 0.]) # hổ − 2·mèo
assert np.allclose(z @ W, [0., 0.])
assert np.linalg.matrix_rank(W) == 2
print("hổ − 2·mèo ↦", z @ W, " · rank(W)=2 · nullity = 5−2 = 3 ✓")
def cosine(u, v):
return float(np.sum(u * v) / np.sqrt(np.sum(u * u) * np.sum(v * v)))
assert abs(cosine(W[0], W[2]) - 1.0) < 1e-12 # mèo vs hổ: TRÙNG hướng
assert abs(cosine(W[0], W[1]) - 0.96) < 1e-12
assert abs(cosine(W[0], W[3]) - 0.0) < 1e-12
print("cos(mèo,hổ)=1.0 cos(mèo,chó)=0.96 cos(mèo,xe)=0.0 ✓ 2 chiều là quá chật")
Kết quả phải khớp §2-§5. Nếu rank_gauss lệch np.linalg.matrix_rank, nguyên nhân gần như luôn là ngưỡng tol hoặc quên trừ hàng trên trụ — không phải hiểu sai toán. Ghi vào nhật ký lỗi.
Bài tập thêm cho khối này: đo thời gian embed_matmul với V = 100.000, d = 768, batch = 32 rồi so với W[ids]. Trên máy tôi ra ~6,6 ms vs ~0,019 ms. Máy bạn ra số khác, tỉ lệ mới là thứ đáng nhìn.
8. Nối vào AI
| Khái niệm trong bài | Chỗ dùng trong AI | Chiều thật |
|---|---|---|
Ma trận nén m < n |
Embedding layer — từ vựng → không gian dày đặc | 100.000 → 768 |
| Nhân one-hot = tra hàng | nn.Embedding là tensor (V, d) + index, không có matmul |
tiết kiệm ~350 lần |
| Ma trận nén | Q = X·W^Q, K = X·W^K trong attention |
768 → 64 mỗi head |
Hạng ≤ min(m,n) |
hạng(W^Q) ≤ 64 → mỗi head chỉ "nhìn" được một không gian con 64 chiều |
nullity ≥ 704 |
| Nén rồi nâng | LoRA: ΔW = B·A, A nén d→r, B nâng r→d |
hạng(B·A) ≤ r |
| Nén rồi nâng | Autoencoder / bottleneck: ép qua chỗ hẹp để buộc học cái cốt lõi | tuỳ kiến trúc |
| Ảnh không phủ hết | Vì sao xếp lớp tuyến tính vô ích nếu thiếu phi tuyến (bài 2 §4) | — |
Attention, số thật: d_model = 768, 12 head, mỗi head d_k = 64, và 12 × 64 = 768 — vừa khít.
X (10 × 768) · W^Q (768 × 64) → (10 × 64). Mỗi head cố ý nén 12 lần: nó không được phép nhìn hết, nên buộc phải chuyên môn hoá vào một khía cạnh. Rồi QKᵀ mới đo góc (bài 1). Nén trước, đo góc sau — đúng thứ tự đó.
LoRA, số thật với d = 4096:
hạng r |
tham số full d×d |
tham số LoRA 2·d·r |
tỉ lệ | tiết kiệm |
|---|---|---|---|---|
| 1 | 16.777.216 | 8.192 | 0,049 % | 2048 lần |
| 4 | 16.777.216 | 32.768 | 0,195 % | 512 lần |
| 8 | 16.777.216 | 65.536 | 0,391 % | 256 lần |
| 16 | 16.777.216 | 131.072 | 0,781 % | 128 lần |
LoRA hoạt động được chính vì hạng của tích bị chặn: hạng(B·A) ≤ r = 8. Ta cố tình chấp nhận một cập nhật hạng thấp để đổi lấy 256 lần ít tham số. Toàn bộ ý tưởng là §2 + §3 ghép lại.
9. Tự kiểm — trả lời được mới sang bài 6
- Ma trận
768 × 100000biến đổi từ không gian nào sang không gian nào? Còn100000 × 768? Nói rõ cột là chiều vào hay chiều ra. - Vì sao ma trận nén (
m < n) bắt buộc làm mất thông tin? Trả lời bằnghạng ≤ min(m,n)và định lý hạng–số khuyết, không bằng ví dụ số. E · (a,b) = (a, b, a+b). Vì sao ảnh củaEchỉ là một mặt phẳng chứ không phải cảR³? Viết phương trình mặt phẳng đó và chỉ ra một điểm không thuộc ảnh.P·E = I₂nhưngE·P ≠ I₃. Giải thích bằng hành động, không bằng số. Điều này nói gì về việc "nghịch đảo" một ma trận không vuông?- Vì sao nhân một vector one-hot với ma trận luôn cho ra đúng một hàng của ma trận đó? Chứng minh trong 2 dòng, dùng đúng ý "cột = nơi vector cơ sở đáp xuống" của bài 2.
- Với
V = 100.000từ vàd = 768: có tối đa bao nhiêu từ độc lập tuyến tính trong không gian embedding? Bao nhiêu từ buộc phải phụ thuộc? Điều đó nghĩa là gì với hai từ cócosine = 1.0? - One-hot cho
cos = 0với mọi cặp từ. Sau khi nén xuống 768 chiều thìcoskhác 0. Thông tin "mèo gần chó" đó từ đâu ra, khi phép nén chỉ làm mất chứ không thêm? - Bảng ở §5 cho thấy
max│cos│giảm từ1.0000(2 chiều) xuống0.1747(768 chiều). Vì sao chiều càng cao càng dễ tách các nghĩa? Và vì sao không ai dùng thẳngd = 100.000?
Câu 7 là câu khó nhất và là câu tận cùng của bài. Gợi ý: ma trận nén không tự nhiên mà có — nó được học. Thông tin ngữ nghĩa không đến từ phép chiếu, nó đến từ dữ liệu đã nhào nặn ra ma trận đó. Phép chiếu chỉ là cái khuôn; dữ liệu mới là thứ đổ vào khuôn. Trả lời được câu này là bạn đã tách được "cấu trúc đại số" khỏi "nội dung học được" — ranh giới mà rất nhiều người dùng ML cả năm vẫn chưa tách.
Câu 4 và 6 là hai câu đáng nhất còn lại. Câu 6 bạn đã có sẵn con số 99.232 ở §5.
10. Bài tập về nhà trước bài 6
3Blue1Brown — Essence of Linear Algebra:
| Tập | Vì sao |
|---|---|
| 8 — Nonsquare matrices as transformations between dimensions | ⭐ Chính là cả bài này bằng hình động: phép biến đổi giữa hai không gian khác số chiều. Xem kỹ, đừng xem lướt |
| 7 — Inverse matrices, column space and null space | Xem lại nếu §2 (null space) còn mờ. Column space chính là "ảnh" ở §3 |
Giữ sẵn một câu hỏi khi xem tập 8: chiếu từ 100.000 chiều từ vựng xuống 768 chiều thì mất gì, giữ gì? Bạn đã có câu trả lời ở §5 — xem để thấy nó.
Làm tay, không gõ code (30 phút, trên giấy):
- Cho
F = [[1,0,1],[0,1,1]](2×3). Tìm một vector khác 0 bị nghiền về(0,0). (Gợi ý: thử(1,1,−1).) Hạng củaFlà bao nhiêu? Số chiều null space? - Cho
G = [[2,0],[0,2],[2,2]](3×2). Ảnh củaGlà mặt phẳng nào? So vớiEở §3 — giống hay khác? Điều đó nói gì về việc nhân cả ma trận với một số? - Tự nghĩ ra một ma trận
3×5có hạng 2 (không phải 3). Giải thích tại sao nó không đạt đượcmin(3,5) = 3.
Bài 6 sẽ là: bài này cho thấy ma trận không vuông làm mất thông tin không lấy lại được.
Vậy còn ma trận vuông và khả nghịch — nó chẳng mất gì cả, chỉ viết lại cùng một vector bằng
một bộ trục khác. Cùng một mũi tên, hai người mô tả bằng hai hệ toạ độ, ra hai bộ số khác nhau
mà vẫn là một vector. Câu hỏi cần trả lời được: "vector (3,2)" thật ra là câu nói thiếu vế —
thiếu vế nào?