Đại số tuyến tính · bài 2
Ma trận là phép biến đổi
Cột của ma trận là nơi hai vector cơ sở đáp xuống. Kéo hai cột để thấy cả mặt phẳng biến đổi theo, vì sao AB khác BA, và vì sao mạng nơ-ron cần hàm phi tuyến.
Bài toán
Mỗi lớp của mạng nơ-ron nhân vector đầu vào với một ma trận trọng số. Bài về PCA viết “tìm vector riêng của ma trận hiệp phương sai”, bài về tăng cường dữ liệu viết “xoay ảnh một góc ngẫu nhiên”. Nếu nhìn ma trận là một bảng số, nhân ma trận chỉ là quy tắc hàng nhân cột phải học thuộc, và những câu trên không gợi ra hình ảnh nào. Nhìn ma trận là một hành động lên cả không gian thì chúng có nghĩa ngay.
Kéo thử trước
Chấm xanh lá là nơi î = (1, 0) đáp xuống, chấm đỏ là nơi ĵ = (0, 1) đáp xuống: đó chính là hai cột của M. Kéo chúng để thấy cả lưới biến đổi theo. Chấm xám là vector v trước khi biến đổi; mũi tên tím là M·v, ghép từ v₁ bước theo cột 1 rồi v₂ bước theo cột 2 (hai đoạn nét đứt).
Nên thử: bấm “SR” rồi “RS” và nhìn î đáp ở đâu; kéo hai cột về cùng một đường thẳng và nhìn lưới.
Ba điều phép biến đổi tuyến tính giữ nguyên
Đường thẳng vẫn thẳng, gốc toạ độ đứng yên, lưới ô vuông biến thành lưới song song và cách đều. Kéo giãn, quay, trượt, lật, bóp dẹp đều hợp lệ. Uốn cong một đường hay dời gốc toạ độ đi chỗ khác thì không phải phép biến đổi tuyến tính.
Cột của ma trận là nơi î và ĵ đáp xuống
Gọi và : hai mũi tên dài 1, một nằm trên trục ngang, một trên trục dọc. Mọi vector dựng được từ hai mũi tên này, ví dụ .
Cột thứ nhất của ma trận là nơi đáp xuống, cột thứ hai là nơi đáp xuống. Kiểm bằng hai ma trận dùng suốt trang:
| ma trận | î đáp ở | ĵ đáp ở | việc nó làm |
|---|---|---|---|
| R | (0, 1) | (−1, 0) | quay 90° ngược chiều kim đồng hồ |
| S | (1, 0), đứng yên | (1, 1) | trượt ngang |
Vì sao cột lại khớp? Nhân với thì mọi số ở cột 2 đều nhân với 0, chỉ còn lại cột 1. Nhân với thì chỉ còn cột 2.
Hệ quả: biết , đi đâu là biết mọi vector đi đâu, vì mọi vector là tổ hợp của hai mũi tên đó. Gọi , là hai cột của :
Với phép trượt: . Nhân hàng với cột cũng ra đúng số đó: . Đó là nghĩa của chữ “tuyến tính”: phép biến đổi giữ nguyên phép cộng và phép nhân với một số, nên 4 số của ma trận 2×2 mô tả trọn hành động lên vô hạn điểm của mặt phẳng.
Nhân ma trận là làm liên tiếp hai phép biến đổi
nghĩa là làm trước rồi làm , đọc từ phải sang trái như . Theo dõi theo hai thứ tự:
Quay rồi trượt (SR): R·(1, 0) = (0, 1) S·(0, 1) = (1, 1)
Trượt rồi quay (RS): S·(1, 0) = (1, 0) R·(1, 0) = (0, 1)
Cùng hai phép biến đổi, khác thứ tự, khác kết quả. Nhân ra để đối chiếu:
Cột 1 của là , cột 1 của là , đúng nơi đáp sau hai bước. Thứ tự hành động quan trọng, như đi tất rồi đi giày khác hẳn đi giày rồi đi tất. Ngược lại, thì luôn đúng: cả hai đều là “làm , rồi , rồi ”, chỉ khác cách gộp nhóm.
Mỗi ô của tích là một tích vô hướng
Ô ở hàng , cột của bằng tích vô hướng của hàng của với cột của (bài 1):
Ô hàng 1, cột 2 là . Vì sao lại là hàng của với cột của ? làm trước. qua đáp ở cột 2 của , tức . Qua , mỗi toạ độ mới được tính bằng một hàng của : và . Vậy đi hết hai bước thì tới , đúng cột 2 của .
Ghép với phép chuyển vị (đổi hàng thành cột) là ra công thức điểm attention. Cho là bảng truy vấn và là bảng khoá, mỗi hàng là một token. Ô của là tích vô hướng giữa truy vấn và khoá :
Vì sao mạng nơ-ron cần hàm phi tuyến
Giả sử một mạng chỉ gồm các phép nhân ma trận chồng lên nhau. Nhân ma trận có tính kết hợp, nên
Ba lớp gộp lại đúng thành một ma trận. Chạy thử với ma trận ngẫu nhiên, hai cách tính lệch nhau cỡ , tức chỉ là sai số làm tròn. Xếp 100 lớp tuyến tính cũng chỉ mạnh bằng một lớp.
Vì vậy giữa các lớp phải chèn một hàm phi tuyến như ReLU hay GELU. Khi đó nói chung không viết được thành một ma trận nhân với : hàm tuyến tính luôn có , còn có ReLU ở giữa thì đẳng thức đó hỏng (code bên dưới kiểm). Các lớp không còn gộp được, và chiều sâu mới có ích.
Bẫy khi tự viết nhân ma trận
Công thức đúng là C[i][j] += A[i][t] * B[t][j]. Gõ nhầm B[j][t], sai đúng một chỗ, thì code vẫn chạy, vẫn ra ma trận 2×2, không báo lỗi:
đúng A @ B = [[19, 22], [43, 50]]
nhầm B[j][t] = [[17, 23], [39, 53]] ← chính là A @ B.T
Kết quả sai không phải rác: nó đúng bằng tích với ma trận chuyển vị. Đọc lại code bằng mắt rất khó bắt; cách chắc chắn là so với thư viện, assert np.allclose(matmul(A, B), A @ B). Một bẫy cùng họ trong numpy: A * B là nhân từng ô, ra [[5, 12], [21, 32]]; A @ B mới là nhân ma trận.
Hiểu nhầm hay gặp
Các ý dưới đây là lỗi thật đã gặp khi học bài này.
- “Phép trượt S giữ (3, 2) đứng yên rồi dịch sang phải 2.” Cái đứng yên là , vì cột 1 của là . Mỗi điểm bị đẩy sang phải đúng bằng độ cao của nó, nên đi tới .
- “Mọi vector đều biểu diễn được từ hai vector bất kỳ.” Phải là hai vector không cùng phương. và không tạo ra được (bài 3).
- “Phi tuyến là làm cong không gian.” ReLU không làm cong mà gấp không gian theo các nếp thẳng; GELU mới uốn cong. Cộng thêm bias cũng không phải phi tuyến: các phép “nhân ma trận rồi cộng vector” vẫn gộp được thành một phép như vậy.
- “Ma trận dịch được mọi điểm sang một bên.” Nhân ma trận luôn giữ gốc toạ độ đứng yên, nên không làm được phép dịch. Trong , phần dịch là .
- “ là làm trước.” Đọc từ phải sang trái: trước, sau.
Code: tự viết rồi đối chiếu
Tự viết mat_vec và mat_mul bằng Python thuần rồi so với numpy. Mỗi assert là một con số trong trang này.
# Ma trận là phép biến đổi: tự viết nhân ma trận bằng Python thuần rồi đối chiếu với numpy.
# Mọi assert dưới đây là số được nhắc trong bài.
import numpy as np
def mat_vec(M, v):
return [sum(M[i][t] * v[t] for t in range(len(v))) for i in range(len(M))]
def mat_mul(A, B):
return [[sum(A[i][t] * B[t][j] for t in range(len(B))) for j in range(len(B[0]))] for i in range(len(A))]
R = [[0, -1], [1, 0]] # quay 90° ngược chiều kim đồng hồ
S = [[1, 1], [0, 1]] # trượt ngang
# Cột 1 = nơi î = (1, 0) đáp xuống, cột 2 = nơi ĵ = (0, 1) đáp xuống
assert mat_vec(R, [1, 0]) == [0, 1] and mat_vec(R, [0, 1]) == [-1, 0]
assert mat_vec(S, [1, 0]) == [1, 0] and mat_vec(S, [0, 1]) == [1, 1]
assert mat_vec(S, [3, 2]) == [5, 2] # = 3·(1, 0) + 2·(1, 1)
# Đổi thứ tự là đổi kết quả. SR: làm R trước, S sau.
SR, RS = mat_mul(S, R), mat_mul(R, S)
print("SR =", SR, " RS =", RS)
assert SR == [[1, -1], [1, 0]] and RS == [[0, -1], [1, 1]]
assert mat_vec(SR, [1, 0]) == [1, 1] and mat_vec(RS, [1, 0]) == [0, 1]
# Ô (i, j) của tích = tích vô hướng của hàng i bên trái với cột j bên phải
A, B = [[1, 2], [3, 4]], [[5, 6], [7, 8]]
assert mat_mul(A, B) == [[19, 22], [43, 50]] == (np.array(A) @ np.array(B)).tolist()
Q, K = [[1, 0], [0, 1], [1, 1]], [[2, 0], [0, 3], [1, 1]]
K_T = [list(hang) for hang in zip(*K)]
assert mat_mul(Q, K_T) == [[2, 0, 1], [0, 3, 1], [2, 3, 2]] == (np.array(Q) @ np.array(K).T).tolist()
# Ba lớp chỉ có ma trận gộp lại đúng một ma trận
rng = np.random.default_rng(0)
W1, W2, W3 = rng.standard_normal((3, 2, 2))
x = rng.standard_normal(2)
lech = np.abs(W3 @ (W2 @ (W1 @ x)) - (W3 @ W2 @ W1) @ x).max()
print(f"từng lớp so với gộp một ma trận: lệch {lech:.1e}")
assert lech < 1e-12
# Chèn ReLU vào giữa thì hết tuyến tính: hàm tuyến tính luôn có f(-x) = -f(x)
def f(x):
return W2 @ np.maximum(W1 @ x, 0)
assert not np.allclose(f(-x), -f(x))
# Bẫy: viết nhầm B[j][t] thay B[t][j] vẫn chạy, và ra đúng A @ B.T
sai = [[sum(A[i][t] * B[j][t] for t in range(2)) for j in range(2)] for i in range(2)]
assert sai == [[17, 23], [39, 53]] == (np.array(A) @ np.array(B).T).tolist()
# Trong numpy, * là nhân từng ô; @ mới là nhân ma trận
assert (np.array(A) * np.array(B)).tolist() == [[5, 12], [21, 32]]
print("đúng hết")
Nằm ở đâu trong AI
| Chỗ trong AI | Là phép biến đổi gì |
|---|---|
| Một lớp mạng nơ-ron | Biến đổi không gian biểu diễn |
| , trong attention | Đưa token sang không gian truy vấn và không gian khoá, rồi lấy tích vô hướng (bài 1) |
| Ma trận embedding | Ma trận không vuông: từ không gian có số chiều bằng cỡ bộ từ vựng xuống vài trăm chiều |
| Xếp nhiều lớp | Ghép phép biến đổi; phải chèn phi tuyến ở giữa thì chiều sâu mới có ích |
| Tăng cường dữ liệu ảnh | Xoay, lật, trượt ảnh là ma trận 2×2 tác động lên toạ độ điểm ảnh |
Tự kiểm
Câu 1
Ma trận M có cột 1 là (0, 1) và cột 2 là (−1, 0). Nó làm gì với mặt phẳng?
- A. lật đối xứng qua đường y = x
- B. quay 90° ngược chiều kim đồng hồ
- C. kéo giãn gấp đôi theo trục dọc
- D. trượt mọi điểm sang phải
Xem đáp án
B. î = (1, 0) đáp ở (0, 1), tức quay lên trục dọc; ĵ = (0, 1) đáp ở (−1, 0). Cả hai cùng quay 90° ngược chiều kim đồng hồ. Phép lật qua y = x có hai cột là (0, 1) và (1, 0).
Câu 2
R là phép quay 90°, S = [[1, 1], [0, 1]] là phép trượt. Áp lên (1, 0): “quay rồi trượt” (SR) và “trượt rồi quay” (RS) cho ra gì?
- A. cùng một vector, vì nhân ma trận có tính kết hợp
- B. cùng một vector, vì dùng chung hai ma trận
- C. khác nhau chỉ khi hai ma trận không vuông
- D. hai vector khác nhau: (1, 1) và (0, 1)
Xem đáp án
D. Quay đưa (1, 0) lên (0, 1), trượt đẩy nó sang (1, 1). Còn trượt không đụng tới vector nằm ngang, quay xong ra (0, 1). Tính kết hợp nói về cách gộp nhóm, không cho phép đổi thứ tự.
Câu 3
Một mạng có ba lớp, mỗi lớp chỉ nhân với một ma trận, không có hàm kích hoạt. Nó biểu diễn được những gì?
- A. nhiều hơn một lớp, vì có ba bộ trọng số
- B. đúng những gì một lớp nhân ma trận biểu diễn được
- C. mọi hàm liên tục, nếu ma trận đủ lớn
- D. ít hơn một lớp, vì sai số làm tròn cộng dồn
Xem đáp án
B. W₃(W₂(W₁x)) = (W₃W₂W₁)x: ba ma trận gộp thành một. Phải chèn hàm phi tuyến vào giữa thì các lớp mới không gộp được.
Nguồn của trang này
- Khung bài và bảng “nằm ở đâu trong AI”: AI Engineering from Scratch, Matrix Transformations (mục Transformations as matrices, Rotation, Shearing, Composition), giấy phép MIT, © Rohit Ghumare.
- Khung bài và bảng “nằm ở đâu trong AI”: AI Engineering from Scratch, Vectors, Matrices & Operations (mục Element-wise vs matrix multiplication, Connect to neural networks), giấy phép MIT, © Rohit Ghumare.
- Phần giảng, tính tay và chỗ hay nhầm: syllabus AI foundations, môn 2 bài 2.
- Mọi con số trong bài đã chạy lại bằng numpy. Cập nhật 2026-11-05.