~3 giờ · cần xong bài 5 trước
Đích của bài này: hiểu rằng hệ toạ độ là thứ bạn CHỌN, không phải thứ có sẵn — và giải thích được bằng lời vì sao công thức
A' = P⁻¹APphải "kẹp" đúng như vậy, chứ không học thuộc nó.
🧭 Làm theo thứ tự này
- Xem trước: 3Blue1Brown tập 13 Change of basis. Xem lại 5 phút giữa tập 4 (
P⁻¹APlà ghép ba phép biến đổi). - Làm ô ⚡ bên dưới.
- Đọc §1-4 (§4 đọc từ phải sang trái), rồi §5-6.
- Làm lại §6 (kiểm hai đường đi) trên giấy với một vector do bạn tự chọn, không mở file. Không làm được thì đọc lại §4, đừng đọc §5.
- Code: tự cài
change_basis(A, P)(tínhP⁻¹AP). Tự viết xong mới mở code mẫu trong bài. Rồi tự kiểm.
Xong bài khi: làm được bước 4 mà không nhìn file.
⚡ 30 phút đầu — làm trước, đọc sau
import numpy as np
P = np.array([[2., -1.],
[1., 1.]]) # cơ sở mới: b1 = (2,1), b2 = (-1,1)
R = np.array([[0., -1.],
[1., 0.]]) # quay 90° trong hệ CHUẨN
# tự viết inv2(M) cho ma trận 2×2 bằng công thức tay — KHÔNG dùng np.linalg.inv
# đích, phải ra đúng 4 dòng sau:
# P @ (1, 1) = (1, 2)
# inv2(P) @ (3, 0) = (1, -1)
# inv2(P) @ R @ P = [[ 1/3, -2/3],
# [ 5/3, -1/3]]
# (inv2(P) @ R @ P) @ (3, 0) = (1, 5)
Ra đúng bốn dòng → đọc tiếp §1-8 để hiểu vì sao thứ tự P⁻¹ · A · P không thể đảo.
Ra sai ở dòng 3 → gần như chắc chắn bạn đang tính P A P⁻¹. §4 và §9 nói thẳng về cái bẫy đó.
1. Hệ toạ độ là một LỰA CHỌN
Từ bài 1 tới giờ, mỗi lần viết v = (3, 0) bạn đã ngầm đồng ý một chuyện: 3 là "ba bước theo hướng
î = (1,0)", 0 là "không bước nào theo hướng ĵ = (0,1)".
Nhưng î và ĵ không phải là sự thật của vũ trụ. Chúng chỉ là hai cây thước mà mọi người mặc định
dùng chung. Mũi tên vẫn nằm đúng chỗ đó dù bạn đo bằng thước nào.
Mũi tên là thật. Cặp số là bản ghi chép. Đổi cơ sở không đụng gì tới mũi tên — nó chỉ đổi cách ghi chép.
Ẩn dụ backend chính xác đến từng chi tiết:
| Thế giới backend | Thế giới vector |
|---|---|
Một thời điểm (một instant thật) |
Một mũi tên trong mặt phẳng |
2026-08-15T14:00+07:00 |
Toạ độ trong hệ A |
2026-08-15T07:00Z |
Toạ độ của cùng thời điểm đó trong hệ B |
| Timezone | Cơ sở |
localToUtc() / utcToLocal() |
P / P⁻¹ |
Hai chuỗi ký tự khác nhau, cùng một khoảnh khắc. Bug timezone kinh điển của backend — "sao giờ lệch 7 tiếng?" — chính là bug quên mình đang ở hệ toạ độ nào. Bài này là bug đó, viết bằng ma trận.
2. Cơ sở mới, và ma trận P
Chọn hai vector làm thước mới. Ta dùng chúng suốt bài:
b₁ = ( 2, 1) b₂ = (−1, 1)
Hai số này vẫn được viết trong hệ chuẩn — phải vậy, nếu không thì không ai đọc được chúng.
Xếp chúng thành hai cột:
P = [ 2 −1 ]
[ 1 1 ]
P làm gì? Nhớ lại bài 2: cột của ma trận = nơi vector cơ sở đáp xuống. Cột 1 của P là (2,1),
nghĩa là P bắn î tới b₁; cột 2 bắn ĵ tới b₂. Nên:
Pnhận vào toạ độ viết trong hệ MỚI, trả ra toạ độ viết trong hệ CHUẨN. Nó làlocalToUtc().
Kiểm bằng tay. Một vector có toạ độ (1, 1) trong hệ mới — tức là "một bước theo b₁, một bước theo b₂":
P · (1,1) = 1·(2,1) + 1·(−1,1) = (2−1, 1+1) = (1, 2)
Cùng một mũi tên. Trong hệ mới nó tên là (1,1), trong hệ chuẩn nó tên là (1,2).
⚠️ BẪY thứ nhất, và là bẫy đắt nhất bài này.
Pcó tên là "ma trận đổi sang cơ sở mới", nhưng nó dịch từ hệ mới VỀ hệ chuẩn, không phải ngược lại. Nhiều sách gọi thẳng nó là change-of-basis matrix rồi để người đọc tự đoán chiều. Cách nhớ không bao giờ sai: cột củaPđược viết bằng ngôn ngữ hệ chuẩn, nên đầu ra củaPcũng bằng ngôn ngữ hệ chuẩn.
Điều kiện để b₁, b₂ là một cơ sở: det(P) ≠ 0. Ở đây det(P) = 2·1 − (−1)·1 = 3. Nếu det = 0
thì hai cột nằm trên cùng một đường thẳng, chúng không căng nổi mặt phẳng (chữ span ở bài 3), và
"hệ toạ độ" đó không mô tả được mọi vector. Cơ sở suy biến = thước bị gãy.
3. Đi ngược: P⁻¹
Muốn hỏi "vector (3,0) của hệ chuẩn thì tên là gì trong hệ mới?" — đi ngược chiều P:
P⁻¹ = (1/3) · [ 1 1 ] = [ 1/3 1/3 ]
[ −1 2 ] [ −1/3 2/3 ]
(Công thức nghịch đảo 2×2: đổi chỗ hai phần tử đường chéo, đổi dấu hai phần tử còn lại, chia cho det = 3.)
P⁻¹ · (3,0) = (1/3)·(1·3 + 1·0, −1·3 + 2·0) = (1/3)·(3, −3) = (1, −1)
Kiểm ngược lại ngay, luôn luôn làm bước này:
1·b₁ + (−1)·b₂ = (2,1) − (−1,1) = (3, 0) ✓
Bảng đối chiếu — cùng những mũi tên, hai bản ghi chép:
| Mũi tên | Toạ độ hệ CHUẨN | Toạ độ hệ MỚI (b₁, b₂) |
|---|---|---|
b₁ |
(2, 1) |
(1, 0) |
b₂ |
(−1, 1) |
(0, 1) |
| — | (1, 2) |
(1, 1) |
| — | (3, 0) |
(1, −1) |
| — | (6, 3) |
(3, 0) |
Đọc hàng thứ nhất và thứ hai cho kỹ: trong hệ của chính nó,
b₁mang toạ độ(1,0). Mỗi cơ sở đều tự thấy mình vuông vắn và đơn vị. Không hệ nào "nghiêng" theo cách nhìn của chính nó — "nghiêng" chỉ là điều một hệ nói về hệ kia.
4. A' = P⁻¹AP — ba hành động, đọc từ phải sang trái
Đây là ý trung tâm của cả bài. Nếu chỉ giữ lại một thứ, giữ đoạn này.
Tình huống. Bạn có một phép biến đổi A — nhưng A được viết bằng ngôn ngữ hệ chuẩn. Nó chỉ biết
nhận vào toạ độ chuẩn và trả ra toạ độ chuẩn. Còn bạn thì đang sống trong hệ b₁, b₂: mọi vector bạn cầm
trên tay đều là toạ độ hệ mới.
Bạn cần một ma trận A' làm việc trọn vẹn bên trong hệ mới: vào toạ độ mới, ra toạ độ mới.
Ghép ba bước, nhớ quy tắc bài 2 — nhân ma trận đọc từ phải sang trái:
A' = P⁻¹ · A · P
③ ② ①
| Bước | Ma trận | Làm gì | Backend |
|---|---|---|---|
| ① | P |
Toạ độ của tôi (hệ mới) → toạ độ hệ chuẩn | localToUtc(t) |
| ② | A |
Làm phép biến đổi, ở cái hệ mà A biết nói |
addOneHour(utc) |
| ③ | P⁻¹ |
Kết quả (hệ chuẩn) → dịch ngược về hệ của tôi | utcToLocal(result) |
Nói thành lời, đây là câu bạn phải trả lời được ở §Tự kiểm: "
A'không phải một phép biến đổi mới. Nó là đúng phépAcũ, chỉ được bọc thêm hai lớp phiên dịch: dịch sang hệ màAbiết nói → làm việc ở đó → dịch ngược về hệ của tôi."
Mọi backend dev đã viết đoạn code này hàng trăm lần:
const toLocal = (t: Instant) => ... // P⁻¹
const toUtc = (t: Local) => ... // P
const addHourLocal = (t: Local) => toLocal(addHourUtc(toUtc(t)))
// ③ ② ①
addHourLocal không phải phép cộng giờ khác. Nó là đúng phép cộng giờ đó, nhìn từ chỗ khác.
A' = P⁻¹AP là dòng code trên, viết bằng ma trận.
Vì sao thứ tự không thể đảo
Xét từng chiều dữ liệu, như xét kiểu trong TypeScript:
A' ăn vào toạ độ MỚI
→ phải gặp P đầu tiên (P mới nhận được toạ độ mới)
A chỉ ăn được toạ độ CHUẨN
→ nên P phải chạy TRƯỚC A
A nhả ra toạ độ CHUẨN
→ phải có P⁻¹ ở CUỐI để dịch về
Ghép lại chỉ còn đúng một khả năng: P⁻¹ A P. Không phải quy ước, mà là ràng buộc kiểu.
Cách đọc thứ hai — cột của A' là gì
Có một cách hiểu công thức mà không cần nhìn nó như "công thức". Nhớ bài 2: cột thứ j của một ma trận
= nơi vector cơ sở thứ j đáp xuống, viết trong hệ đang dùng. Áp thẳng vào hệ mới:
cột 1 của A' = toạ độ (trong hệ mới) của A·b₁
cột 2 của A' = toạ độ (trong hệ mới) của A·b₂
Kiểm ở §5 sẽ thấy đúng từng số. Định nghĩa này không nhắc tới P⁻¹AP — công thức chỉ là hệ quả.
5. Ví dụ tính tay đầy đủ: phép quay 90° nhìn từ hệ nghiêng
R là phép quay 90° ngược chiều kim đồng hồ, viết trong hệ chuẩn (bài 2):
R = [ 0 −1 ]
[ 1 0 ]
Bước 1 — tính R·P. Dùng luật cột: nhân R với từng cột của P.
R·b₁ = R·( 2, 1) = (−1·1, 1·2) = (−1, 2)
R·b₂ = R·(−1, 1) = (−1·1, 1·(−1)) = (−1, −1)
R·P = [ −1 −1 ]
[ 2 −1 ]
Bước 2 — nhân P⁻¹ vào bên trái. Cũng theo cột:
cột 1: P⁻¹·(−1, 2) = (1/3)·(−1+2, 1+4) = (1/3)·(1, 5) = ( 1/3, 5/3)
cột 2: P⁻¹·(−1,−1) = (1/3)·(−1−1, 1−2) = (1/3)·(−2,−1) = (−2/3, −1/3)
R' = P⁻¹ R P = [ 1/3 −2/3 ] = (1/3) · [ 1 −2 ]
[ 5/3 −1/3 ] [ 5 −1 ]
Đọc lại hai cột đó theo §4: cột 1 là (1/3, 5/3) — đó chính là toạ độ hệ mới của R·b₁ = (−1,2).
Cột 2 là (−2/3, −1/3) — toạ độ hệ mới của R·b₂ = (−1,−1). Đúng như đã hứa.
Nhìn
R'mà bảo "đây là phép quay 90°" thì không ai tin — nó xấu, đầy phân số, không có tí đối xứng nào. Nhưng nó là phép quay 90°. Chỉ là ta đang mô tả cú xoay đó bằng hai cây thước nghiêng và không cùng độ dài. Y hệt việc07:00Zkhông "trông giống" hai giờ chiều.
6. Kiểm hai đường đi — bước không được bỏ
Đây là bài test tích phân của cả bài: biến đổi một vector bằng hai con đường, phải ra cùng một kết quả.
Lấy vector có toạ độ (3, 0) trong hệ mới.
Đường 1 — đi vòng qua hệ chuẩn:
① P · (3,0) = 3·b₁ + 0·b₂ = ( 6, 3) ← ra hệ chuẩn
② R · (6,3) = (−3, 6) ← quay ở hệ chuẩn
③ P⁻¹ · (−3,6) = (1/3)·(−3+6, 3+12)
= (1/3)·(3, 15) = (1, 5) ← về lại hệ mới
Đường 2 — quay thẳng trong hệ mới:
R' · (3,0) = 3·(1/3, 5/3) = (1, 5)
Khớp. Và số nguyên, làm được trên giấy trong 30 giây.
Chạy thêm ba vector nữa (đã kiểm bằng máy, mọi dòng đều khớp):
| Toạ độ hệ mới | P·c (hệ chuẩn) |
R·(P·c) |
Đường 1: P⁻¹R P c |
Đường 2: R'·c |
|---|---|---|---|---|
(3, 0) |
(6, 3) |
(−3, 6) |
(1, 5) |
(1, 5) |
(1, 1) |
(1, 2) |
(−2, 1) |
(−1/3, 4/3) |
(−1/3, 4/3) |
(0, 3) |
(−3, 3) |
(−3, −3) |
(−2, −1) |
(−2, −1) |
(2, −1) |
(5, 1) |
(−1, 5) |
(4/3, 11/3) |
(4/3, 11/3) |
Nếu chỉ một hàng lệch, bạn đã đảo chiều
Pở đâu đó. Đây là cách tự bắt bug duy nhất đáng tin trong bài này — như viết một test so hai đường tính khác nhau thay vì tin vào một đường.
7. Cái gì KHÔNG đổi khi đổi cơ sở
R và R' trông chẳng giống nhau tí nào. Nhưng chúng là cùng một hành động, nên phải có những
đại lượng sống sót qua phép đổi hệ:
| Đại lượng | R |
R' |
|
|---|---|---|---|
trace (tổng đường chéo) |
0 + 0 = 0 |
1/3 + (−1/3) = 0 |
bằng nhau |
det |
0·0 − (−1)·1 = 1 |
(1/3)(−1/3) − (−2/3)(5/3) = −1/9 + 10/9 = 1 |
bằng nhau |
hạng (bài 3) |
2 |
2 |
bằng nhau |
det = 1 nghĩa là phép biến đổi không đổi diện tích — đúng, quay thì không làm hình to nhỏ đi.
Đó là tính chất của cú xoay, không phải của bảng số, nên nó không quan tâm bạn ghi chép bằng hệ nào.
Trả nợ câu hỏi bài 3 để lại: HẠNG có đổi khi đổi cơ sở không?
Cuối bài 3 có một câu treo lại: "hạng có đổi không khi đổi cơ sở? Nếu có thì 'số chiều giữ lại được' là một đại lượng vô nghĩa." Trả lời: không đổi, và lý do đã nằm sẵn trong bài 3 §9.
Bài 3 §9 nói rank(AB) ≤ min(rank A, rank B) — ghép phép biến đổi thì hạng không bao giờ tăng.
Áp hai lần:
hạng(P⁻¹AP) ≤ hạng(A) vì nhân thêm P, P⁻¹ chỉ có thể làm mất chiều
hạng(A) = hạng(P·(P⁻¹AP)·P⁻¹) ≤ hạng(P⁻¹AP) vì đi ngược lại cũng vậy
Hai bất đẳng thức ngược chiều nhau ⟹ bằng nhau. Mấu chốt: P khả nghịch (det ≠ 0, điều kiện
để nó là cơ sở ở §2), nên nó không có không gian null, nên nó không nghiền chiều nào về 0. Phiên dịch
không làm mất thông tin; nó chỉ đổi ngôn ngữ.
Quy tắc gộp: nhân với ma trận khả nghịch ở bất kỳ bên nào cũng không đổi hạng. Vì thế "số chiều giữ lại được" là một đại lượng có nghĩa thật — nó thuộc về hành động, không thuộc về cách ghi chép. Bài 9 sẽ cho nó cách đo tốt nhất: hạng = số giá trị suy biến khác 0.
Đại lượng nào không đổi qua
P⁻¹(·)Pthì đại lượng đó thuộc về HÀNH ĐỘNG, không thuộc về CÁCH GHI.trace,det, hạng, và (bài 7) tập eigenvalue — cả bốn đều bất biến. Khi thấy một công thức ML dùngtracehaydetcủa một ma trận, hãy hiểu: tác giả cố tình chọn một đại lượng không phụ thuộc hệ toạ độ.
Ngược lại: các phần tử riêng lẻ A[i][j] không bất biến. Nói "trọng số ở hàng 3 cột 7 lớn nên chiều
đó quan trọng" là một phát biểu về cách ghi chép, không phải về mô hình.
8. Vì sao đáng bỏ công đổi cơ sở
Tới đây bạn có quyền hỏi: đổi hệ để làm gì, khi R' còn xấu hơn R?
Vì chiều ngược lại mới là chiều dùng thật. Chọn hệ cho khéo thì phép biến đổi trở nên tầm thường.
Ví dụ. Trong hệ b₁, b₂, tôi muốn một phép cực đơn giản: kéo giãn theo b₁ gấp 3, giữ nguyên b₂.
D' = [ 3 0 ] ← trong hệ mới. Đơn giản đến mức không cần suy nghĩ.
[ 0 1 ]
Cũng phép đó, viết trong hệ chuẩn (A = P D' P⁻¹ — lần này P nằm ngoài, vì ta đi ngược chiều):
A = (1/3) · [ 7 4 ] = [ 7/3 4/3 ]
[ 2 5 ] [ 2/3 5/3 ]
Kiểm (đã chạy máy): A·b₁ = (6,3) = 3·b₁ ✓ và A·b₂ = (−1,1) = b₂ ✓. trace(A) = 4, det(A) = 3.
[[7/3, 4/3], [2/3, 5/3]]trông như một đống số ngẫu nhiên.diag(3, 1)thì ai cũng đọc được ngay. Cùng một phép biến đổi. Toàn bộ giá trị của bài này nằm ở khoảng cách giữa hai cách viết đó.
Và đây là lúc nó trả tiền — độ phức tạp. Cần A¹⁰? Trong hệ chuẩn: 9 phép nhân ma trận. Trong hệ chéo:
A¹⁰ = P · D'¹⁰ · P⁻¹ vì (P D P⁻¹)¹⁰ = P D (P⁻¹P) D (P⁻¹P) ... D P⁻¹ = P D¹⁰ P⁻¹
D'¹⁰ = diag(3¹⁰, 1), và nâng luỹ thừa một số vô hướng thì rẻ như không. Số cụ thể, đã chạy máy với
A = [[2,1],[1,2]] và cơ sở (1,1), (1,−1):
A¹⁰ trực tiếp = [[29525, 29524], [29524, 29525]]
P·diag(3¹⁰,1)·P⁻¹ = [[29525, 29524], [29524, 29525]] lệch tối đa = 0.0
3¹⁰ = 59049 (và 29525 = (59049+1)/2, 29524 = (59049−1)/2)
Đây đúng là mẫu đổi biểu diễn để đổi Big-O mà backend dev nào cũng biết: dữ liệu không đổi, ta chỉ đổi cách lưu — dựng index, đổi sang cấu trúc khác — và một truy vấn
O(n)thànhO(log n). Đổi cơ sở là "dựng index" cho một phép biến đổi tuyến tính.
Câu hỏi còn lại: cơ sở nào làm cho A thành đường chéo? Đó là toàn bộ bài 7.
9. Hai chỗ BẪY và một chỗ được BỎ QUA
BẪY 1 — nhầm chiều P. Đã nói ở §2, nhắc lại vì nó chiếm ~90% lỗi của bài này. Số thật để đối chiếu:
P⁻¹ R P = [ 1/3 −2/3 ] P R P⁻¹ = [ 1/3 −5/3 ] ← KHÁC NHAU
[ 5/3 −1/3 ] [ 2/3 −1/3 ]
Hai ma trận này không bằng nhau. Cả hai đều "đúng" — nhưng chúng trả lời hai câu hỏi khác nhau:
| Bạn muốn | Dùng |
|---|---|
A viết trong hệ chuẩn → viết lại trong hệ mới |
P⁻¹ A P |
A' viết trong hệ mới → viết lại trong hệ chuẩn |
P A' P⁻¹ |
Cách tự kiểm không cần nhớ: hỏi "ma trận này ăn vào toạ độ loại nào?" rồi soi xem ma trận ở ngoài cùng bên phải có nhận được loại đó không.
BẪY 2 — tưởng A' là một phép biến đổi khác. Không. A và A' gọi là đồng dạng (similar).
Chúng là cùng một hành động lên cùng những mũi tên. Chỉ ngôn ngữ mô tả khác nhau.
BỎ QUA được: cách gọi tên trong sách vở. Có sách viết [T]_B, có sách viết _B[T]_B, có sách gọi
P là "ma trận chuyển từ B sang chuẩn", sách khác gọi ngược lại. Đừng học ký hiệu. Học luật chiều
dữ liệu: cột của P viết bằng ngôn ngữ nào thì đầu ra của P bằng ngôn ngữ đó.
Trường hợp đặc biệt đáng nhớ: cơ sở trực chuẩn
Nếu cơ sở mới gồm các vector dài đúng 1 và vuông góc nhau (nhớ a·b = 0 ở bài 1), gọi ma trận đó là Q:
Q = [ 1/√2 −1/√2 ] (b₁ = (1,1)/√2, b₂ = (−1,1)/√2)
[ 1/√2 1/√2 ]
thì QᵀQ = I, tức Q⁻¹ = Qᵀ (đã kiểm máy: lệch tối đa 1.1e-16). Nghịch đảo miễn phí — chỉ là
chuyển vị. Không phải giải hệ, không chia cho det, không lo sai số khi det gần 0.
Trong tính toán số, "nghịch đảo" là phép đắt và dễ mất chính xác. Cơ sở trực chuẩn biến nó thành một phép hoán vị chỉ số. Đó là lý do PCA, SVD, QR và attention head đều cố bám lấy ma trận trực giao — giống hệt lý do backend thích idempotent: không phải vì đẹp, mà vì rẻ và an toàn.
Bonus đáng suy nghĩ: với Q ở trên, Qᵀ R Q = R — phép quay 90° nhìn từ một hệ đã bị xoay thì vẫn
đúng là phép quay 90°. Vì xoay giao hoán với xoay. Tự hỏi: điều đó nói gì về ý nghĩa của "trục" trong
một không gian embedding?
9b. ⭐ Mỗi toạ độ là một tích vô hướng, nhưng với cơ sở ĐỐI NGẪU chứ không phải với chính cơ sở (bổ sung từ VMLS — 2026-10)
(nguồn: VMLS tr.205, "Dual basis"; khai triển trực chuẩn: VMLS tr.96, công thức (5.5); hàm tuyến tính ra một số: VMLS tr.31)
Bài 1 dạy: tích vô hướng là cái bóng, tức "bao nhiêu của a nằm theo hướng b". Nên khi gặp cơ sở b₁, b₂, thói
quen tự nhiên là: muốn biết v dùng bao nhiêu b₁, lấy b₁·v. Thói quen đó sai nếu cơ sở không trực giao.
Thử với đúng cơ sở của §2 (b₁ = (2,1), b₂ = (−1,1)) và v = (3,0):
đọc bằng bᵢ·v : (b₁·v, b₂·v) = (6, −3) ← SAI
đúng (§3, P⁻¹v) : (1, −1) ← 1·b₁ − 1·b₂ = (2,1) − (−1,1) = (3,0) ✓
Cái cứu được thói quen đó là đọc P⁻¹v theo hàng. Gọi dᵢ là hàng i của P⁻¹, thì cᵢ = dᵢ·v: mỗi toạ độ
vẫn là một tích vô hướng, chỉ là với dᵢ chứ không phải bᵢ. Bộ d₁, d₂ gọi là cơ sở đối ngẫu (dual basis):
P⁻¹ = (1/3)·[ 1 1 ] d₁ = (1/3, 1/3) d₂ = (−1/3, 2/3)
[ −1 2 ]
c₁ = d₁·v = 1/3·3 + 1/3·0 = 1 c₂ = d₂·v = −1/3·3 + 2/3·0 = −1
Vì P⁻¹P = I nên dᵢ·bⱼ = 1 khi i = j và 0 khi khác. Đọc thành hình: d₁·b₂ = −1/3 + 1/3 = 0, tức d₁
vuông góc b₂; d₂·b₁ = −2/3 + 2/3 = 0, tức d₂ vuông góc b₁. Để biết v dùng bao nhiêu b₁, phải đo
v theo hướng vuông góc với mọi trục còn lại, rồi chuẩn hoá cho d₁·b₁ = 1. Lý do: b₁·b₂ = −1 ≠ 0, nên một
phần của b₂ lấn sang hướng b₁, và đo thẳng bằng b₁·v đếm luôn phần lấn đó. Gốc rễ sâu hơn: mỗi cᵢ(v) là
một hàm tuyến tính ra một số, và mọi hàm như vậy đều có dạng "tích vô hướng với một vector cố định" (VMLS tr.31).
Vector cố định đó chính là dᵢ. Đây là nội dung cụ thể của chữ duality ở tập 9 3Blue1Brown mà bài 1 bảo xem lướt.
Trường hợp trực chuẩn (§9): P⁻¹ = Pᵀ nên dᵢ = bᵢ, và thói quen của bài 1 hoá ra đúng. Với Q ở §9:
Qᵀv = (3/√2, −3/√2) = (2.1213, −2.1213). Đây là khai triển x = Σ(bᵢᵀx)bᵢ. Cơ sở trực giao nhưng chưa chuẩn hoá
thì dᵢ = bᵢ/‖bᵢ‖² (VMLS tr.205: a₁ = (1,1), a₂ = (1,−1), x = (−5,1) ra toạ độ (−2,−3)).
import numpy as np
P = np.array([[2., -1.], [1., 1.]]) # b₁ = (2,1), b₂ = (−1,1), như §2
D = np.linalg.inv(P) # hàng i của D là dᵢ
v = np.array([3., 0.])
assert np.allclose(D @ v, [1, -1]) # toạ độ đúng, khớp §3
assert np.allclose(P.T @ v, [6, -3]) # đọc bằng bᵢ·v: sai
assert np.allclose(D @ P, np.eye(2)) # dᵢ·bⱼ = δᵢⱼ (d₁ ⟂ b₂, d₂ ⟂ b₁)
assert np.allclose(np.linalg.inv([[1., 1.], [1., -1.]]) @ [-5., 1.], [-2, -3]) # ví dụ của sách
rng = np.random.default_rng(0) # 8 chiều, cơ sở ngẫu nhiên (không trực giao)
Pn = rng.normal(size=(8, 8)); c = rng.normal(size=8); x = Pn @ c
print(np.linalg.norm(np.linalg.inv(Pn) @ x - c)) # 2.5e-15 (đối ngẫu: khôi phục đúng c)
print(np.linalg.norm(Pn.T @ x - c)) # 28.74 (bᵢ·x: sai)
Qn, _ = np.linalg.qr(Pn) # cơ sở trực chuẩn
print(np.linalg.norm(Qn.T @ (Qn @ c) - c)) # 1.5e-15 (trực chuẩn: bᵢ·x là đủ)
Nối vào AI. Mỗi lần "đọc thành phần của một embedding theo một bộ hướng" (hướng khái niệm tìm bằng probe, trục
PCA, vector trung bình của từng lớp), hãy hỏi bộ hướng đó có trực chuẩn không. Trục PCA/SVD thì có, nên tích vô
hướng là đủ. Bộ hướng tự định nghĩa thường lệch nhau, và V @ x cho toạ độ sai: phải dùng nghịch đảo
(hoặc bình phương tối thiểu khi bộ hướng ít hơn số chiều, bài 5).
Code — tự cài, KHÔNG dùng hàm có sẵn
import numpy as np
# ---------- 1. Tự cài, không dùng np.linalg.inv ----------
def inv2(M):
"""Nghịch đảo ma trận 2×2 bằng công thức tay. det=0 -> không phải cơ sở."""
(a, b), (c, d) = M
det = a * d - b * c
assert abs(det) > 1e-12, f"det = {det}: hai cột phụ thuộc, KHÔNG phải cơ sở"
return np.array([[d, -b], [-c, a]]) / det
def to_std(P, coords):
"""toạ độ trong hệ MỚI -> toạ độ trong hệ CHUẨN."""
return P @ coords
def to_new(P, v):
"""toạ độ trong hệ CHUẨN -> toạ độ trong hệ MỚI."""
return inv2(P) @ v
def change_basis(A, P):
"""A viết trong hệ chuẩn -> chính phép đó viết trong hệ mới."""
return inv2(P) @ A @ P
# ---------- 2. Dữ liệu của bài ----------
b1, b2 = np.array([2., 1.]), np.array([-1., 1.])
P = np.column_stack([b1, b2]) # [[2,-1],[1,1]]
R = np.array([[0., -1.], [1., 0.]]) # quay 90°
# (a) inv2 phải khớp numpy
assert np.allclose(inv2(P), np.linalg.inv(P))
print("inv2 khớp numpy ✓")
# (b) hai chiều dịch phải nghịch nhau
assert np.allclose(to_std(P, [1., 1.]), [1., 2.])
assert np.allclose(to_new(P, [3., 0.]), [1., -1.])
assert np.allclose(to_new(P, to_std(P, [4., -7.])), [4., -7.])
print("to_std / to_new nghịch nhau ✓")
# (c) R' = P^-1 R P
Rp = change_basis(R, P)
print("R' =\n", Rp)
assert np.allclose(Rp, np.array([[1/3, -2/3], [5/3, -1/3]]))
# (d) HAI ĐƯỜNG ĐI PHẢI KHỚP — đây là trái tim của bài
for c in [[3., 0.], [1., 1.], [0., 3.], [2., -1.]]:
duong_1 = to_new(P, R @ to_std(P, c)) # dịch ra chuẩn -> quay -> dịch về
duong_2 = Rp @ np.array(c) # quay thẳng trong hệ mới
assert np.allclose(duong_1, duong_2), c
print("hai đường đi khớp ✓")
# (e) đại lượng BẤT BIẾN qua đổi cơ sở
assert abs(np.trace(R) - np.trace(Rp)) < 1e-12
assert abs(np.linalg.det(R) - np.linalg.det(Rp)) < 1e-12
print(f"trace: {np.trace(R):.0f} = {np.trace(Rp):.0f} ✓ "
f"det: {np.linalg.det(R):.0f} = {np.linalg.det(Rp):.0f} ✓")
# (f) BẪY: P A P^-1 KHÁC P^-1 A P
assert not np.allclose(P @ R @ inv2(P), Rp)
print("P R P^-1 ≠ P^-1 R P ✓ (đúng chiều mới ra đúng số)")
# (g) cơ sở TRỰC CHUẨN: nghịch đảo = chuyển vị, khỏi phải giải hệ
s = 1 / np.sqrt(2)
Q = np.array([[s, -s], [s, s]])
assert np.allclose(Q.T @ Q, np.eye(2))
assert np.allclose(np.linalg.inv(Q), Q.T)
print("cơ sở trực chuẩn: Q^-1 = Q^T ✓")
# (h) TEASER bài 7: có cơ sở làm phép biến đổi thành ĐƯỜNG CHÉO
A = np.array([[2., 1.], [1., 2.]])
Pe = np.array([[1., 1.], [1., -1.]])
D = change_basis(A, Pe)
print("A trong hệ (1,1),(1,-1) =\n", D)
assert np.allclose(D, np.diag([3., 1.]))
assert np.allclose(np.linalg.matrix_power(A, 10),
Pe @ np.diag([3.**10, 1.]) @ inv2(Pe))
print("A^10 tính qua hệ chéo khớp numpy ✓ -> đó là bài 7")
Output phải ra đúng:
inv2 khớp numpy ✓
to_std / to_new nghịch nhau ✓
R' =
[[ 0.33333333 -0.66666667]
[ 1.66666667 -0.33333333]]
hai đường đi khớp ✓
trace: 0 = 0 ✓ det: 1 = 1 ✓
P R P^-1 ≠ P^-1 R P ✓ (đúng chiều mới ra đúng số)
cơ sở trực chuẩn: Q^-1 = Q^T ✓
A trong hệ (1,1),(1,-1) =
[[3. 0.]
[0. 1.]]
A^10 tính qua hệ chéo khớp numpy ✓ -> đó là bài 7
Ghi vào nhật ký lỗi: khi assert (d) gãy mà assert (a) vẫn xanh, bug không nằm ở phép nghịch
đảo — nó nằm ở chiều dịch. Sửa chiều, đừng sửa inv2.
Bài tập thêm (5 phút): chạy change_basis(R, np.array([[1.,2.],[2.,4.]])). Hai cột (1,2) và (2,4) nằm
trên cùng một đường thẳng nên det = 0, và inv2 ném AssertionError với đúng thông báo bạn tự viết.
Giờ xoá dòng assert abs(det) > 1e-12 rồi chạy lại: numpy không ném gì cả — nó chỉ in
RuntimeWarning: divide by zero rồi trả về ma trận toàn nan. Đó chính là lý do dòng assert tồn tại:
một "cơ sở" gãy mà im lặng nguy hiểm hơn nhiều so với một "cơ sở" gãy mà gào lên.
Nối vào AI
| Khái niệm của bài này | Chỗ nó xuất hiện trong AI |
|---|---|
| Cùng một vector, nhiều hệ toạ độ | Embedding chỉ xác định sai kém một phép quay. Quay toàn bộ không gian bằng Q trực giao thì mọi tích vô hướng giữ nguyên — nên mọi cosine similarity không đổi (kiểm máy: 100 vector 768 chiều, quay bằng một Q trực giao ngẫu nhiên, cosine lệch tối đa cỡ 3e-15, tức bằng nhau trong sai số máy). Hệ quả: hỏi "chiều thứ 42 của embedding nghĩa là gì" là câu hỏi sai; chiều không có nghĩa, chỉ quan hệ giữa các vector mới có |
P⁻¹ A P |
Chéo hoá / eigendecomposition (bài 7). Nền của power iteration trong PageRank của Google: lặp nhân ma trận chính là để đưa mọi thứ về trục trội nhất |
| Chọn cơ sở làm ma trận đơn giản | PCA (bài 9 §9, và sâu hơn ở môn 6): tìm cơ sở trực chuẩn mà phương sai dồn vào vài trục đầu, rồi vứt phần còn lại. Nén = đổi hệ toạ độ rồi cắt bớt |
Q⁻¹ = Qᵀ cho cơ sở trực chuẩn |
SVD, QR, khởi tạo trực giao cho RNN, và lý do các thư viện tránh gọi inv() |
| Chiếu sang một cơ sở khác rồi mới làm việc | Attention: W^Q, W^K đưa token sang "không gian truy vấn"/"không gian khoá" — bài 2 đã nói, giờ bạn thấy đó chính là đổi cách mô tả để phép so khớp dễ hơn, rồi QKᵀ mới đo góc (bài 1) |
| Cơ sở ít chiều hơn | LoRA: thay vì cập nhật toàn bộ W, chỉ cập nhật ΔW = BA với hạng r nhỏ — tức chỉ cho phép thay đổi bên trong một cơ sở r chiều đã chọn |
Đại lượng bất biến (trace, det) |
Bất kỳ công thức ML nào dùng trace/det (entropy Gauss, KL giữa hai Gauss, log-det) đều đang cố ý chọn thứ không phụ thuộc hệ toạ độ |
Điểm nối xuyên ba bài: bài 1 cho tích vô hướng (đo góc), bài 2 cho ma trận = hành động, bài 3 cho cơ sở & span. Bài 6 nói: hành động là thật, còn bảng số chỉ là ảnh chụp của hành động từ một góc máy. Bài 7 hỏi tiếp: có góc máy nào đẹp nhất không?
Tự kiểm — trả lời được mới sang bài 7
Pcó hai cộtb₁, b₂. Hai cột đó được viết trong hệ toạ độ nào? Từ đó suy raPdịch từ hệ nào sang hệ nào — và tự chứng minh câu trả lời chứ đừng nhớ.- Giải thích
A' = P⁻¹APbằng ba câu hành động, không viết một ký hiệu nào. Sau đó nói lại bằng ẩn dụ timezone. - Vì sao không thể là
P A P⁻¹? Lập luận bằng "ma trận này ăn vào toạ độ loại nào", giống lập luận kiểu dữ liệu. - Trong hệ của chính nó,
b₁có toạ độ(1,0). Điều đó có nghĩa mọi cơ sở đều tự thấy mình là "hệ chuẩn". Vậy chữ "chuẩn" trong "hệ chuẩn" mang thông tin toán học gì? (Gợi ý: không mang gì cả.) RvàR' = P⁻¹RPkhác nhau từng phần tử, nhưngtrace,detvà hạng đều bằng nhau. Vì sao phải như vậy? Trả lời bằng cách phân biệt cái gì thuộc hành động và cái gì thuộc cách ghi chép. Riêng với hạng, chứng minh lại trong 2 dòng bằngrank(AB) ≤ min(rank A, rank B)của bài 3 §9 — và chỉ ra chỗ nào dùng tới việcPkhả nghịch.- Nếu cơ sở mới là trực chuẩn thì
P⁻¹ = Pᵀ. Nêu hai lợi ích thực tế: một về tốc độ, một về sai số số học. - Một hệ retrieval quay toàn bộ kho embedding bằng một ma trận trực giao
Q(mọi vectorv → Qv), rồi cũng quay cả vector truy vấn. Thứ hạng kết quả trả về đổi thế nào? Vì sao? Suy ra: một chiều đơn lẻ của embedding có mang ý nghĩa riêng không? - Muốn
A' = P⁻¹APra ma trận đường chéo, thì hai cột củaPphải là những vector đặc biệt thế nào? Diễn đạt bằng câu "khiAtác động lên chúng thì...".
Câu 7 là câu sâu nhất — nó xoá bỏ cả một loại hiểu lầm phổ biến ("chiều 42 mã hoá cảm xúc"). Câu 8 khó nhì, và nếu bạn trả lời được thì bạn đã tự phát minh ra eigenvector trước khi học nó.
Bài tập về nhà trước bài 7
3Blue1Brown — Essence of Linear Algebra:
| Tập | Vì sao |
|---|---|
| 13 — Change of basis | ⭐ Chính là bài này bằng hình động. Xem xong, quay lại đọc §4 một lần nữa |
| 3 — Linear transformations and matrices | Xem lại. Câu "cột = nơi vector cơ sở đáp xuống" giờ đọc khác hẳn, vì bạn đã biết "đáp xuống, đo bằng thước nào" |
| 4 — Matrix multiplication as composition | Xem lại đúng 5 phút giữa tập: P⁻¹AP là ghép ba phép biến đổi, không có gì khác |
Xem xong tập 13, làm lại §6 (kiểm hai đường đi) trên giấy với một vector do bạn tự chọn — không mở file này, không mở máy. Làm được là xong bài 6. Không làm được thì đọc lại §4, đừng đọc §5.
Bài 7 sẽ là: eigenvector và eigenvalue. Teaser, và bạn tự trả lời trước khi xem:
A = [ 2 1 ] A · (1, 1) = (3, 3) = 3 · (1, 1)
[ 1 2 ] A · (1,−1) = (1,−1) = 1 · (1,−1)
Hai vector đó không đổi hướng qua A, chỉ bị co giãn. Lấy chúng làm cơ sở — P = [[1,1],[1,−1]] —
thì P⁻¹AP = diag(3, 1), một ma trận đường chéo hoàn hảo (đã kiểm ở phần Code, mục (h)).
Câu hỏi của bài 7: liệu lúc nào cũng tìm được cơ sở như thế? Tìm bằng cách nào? Và vì sao đúng câu hỏi đó lại là thứ cho Google xếp hạng được cả web bằng một phép nhân ma trận lặp lại?