🔬 Bài 26: Vector và Ma trận — Ngôn ngữ của Machine Learning
Sau bài này bạn sẽ:
- Giải thích được vector là gì bằng ngôn ngữ đời thường và bằng NumPy.
- Làm thành thạo ba phép toán: cộng vector, nhân với một số, tích vô hướng.
- Đọc được
shapecủa một mảng và biết khi nào hai mảng nhân được với nhau. - Nói được vì sao câu “dữ liệu là ma trận mẫu × đặc trưng” lại quan trọng đến vậy.
Ở Chặng 6, bạn đã biết lấy dữ liệu, làm sạch nó và vẽ nó ra biểu đồ. Nhưng máy tính không nhìn biểu đồ — nó chỉ làm toán. Chặng 7 sẽ dệt vào đúng lượng toán tối thiểu để bước vào Machine Learning, và viên gạch đầu tiên chính là vector với ma trận: cách máy biểu diễn mọi thứ, từ điểm số của một học sinh đến một tấm ảnh hay một câu văn.
Hình 1 — Cộng vector chính là nối đuôi hai mũi tên: đi hết chỉ dẫn a, rồi đi tiếp chỉ dẫn b.
1. Vector — một chỉ dẫn di chuyển
Bạn đứng ở góc sân bóng. Ai đó nói: “đi 3 bước sang phải, 1 bước lên trên”. Cặp số (3, 1) đó chính là một vector.
Chú ý: vector không phải một điểm, nó là một chỉ dẫn di chuyển — có hướng (đi về phía nào) và độ dài (đi bao xa). Cùng một chỉ dẫn ấy, bạn xuất phát từ đâu cũng được.
Vector 2 số thì vẽ được trên giấy. Nhưng vector hoàn toàn có thể có 3 số, 10 số, hay 768 số — lúc đó ta không vẽ nổi nữa, chỉ tính được thôi. Điều tuyệt vời là mọi quy tắc tính vẫn y hệt. Bạn học một lần trên giấy, dùng được cho cả nghìn chiều.
Trong NumPy, vector là một mảng 1 chiều. Thuộc tính .shape cho biết mảng có mấy chiều và mỗi chiều dài bao nhiêu. Với vector, .shape luôn có dạng (n,) — dấu phẩy đơn độc ở cuối là dấu hiệu “chỉ có một chiều”.
# Vector trong NumPy la mang 1 chieu import numpy as np a = np.array([3, 1]) print(a) # [3 1] print(a.shape) # (2,) -> 1 chieu, 2 so print(a[0]) # 3 -> phan tu dau tien # Mot hoc sinh mo ta bang 4 dac trung hs = np.array([8.5, 7.0, 9.0, 6.5]) print(hs.shape) # (4,) print(hs.dtype) # float64
Khác với list của Python, mọi phần tử trong một mảng NumPy phải cùng kiểu. Đổi lại, phép toán trên nó nhanh hơn hàng chục lần vì được thực hiện ở tầng C, không phải qua vòng lặp Python.
Trong ML, mỗi mẫu dữ liệu — một học sinh, một bệnh nhân, một mẫu đất, một tấm ảnh — đều được mã hóa thành một vector đặc trưng (feature vector). Ảnh xám 28×28 pixel trở thành vector 784 số. Một câu văn qua mô hình ngôn ngữ trở thành vector embedding 768 số. Một mẫu đất có thể là vector của pH, độ ẩm, N, P, K.
Đây là bước “số hóa” quyết định chất lượng cả nghiên cứu: chọn đặc trưng nào để đưa vào vector thường ảnh hưởng đến kết quả nhiều hơn cả việc chọn thuật toán. Và khi mọi loại dữ liệu đã cùng nằm dưới dạng vector, một bộ công cụ toán học duy nhất dùng được cho mọi ngành — đó chính là lý do ML lan được từ y học sang nông nghiệp sang vật lý.
2. Ba phép toán bạn sẽ dùng suốt đời
- Cộng hai vector: đi hết chỉ dẫn a rồi đi tiếp chỉ dẫn b. Kết quả là mũi tên nối thẳng từ điểm xuất phát tới điểm cuối (xem Hình 1).
- Nhân với một số: giữ nguyên hướng, chỉ kéo dài hoặc co ngắn mũi tên. Nhân với 2 là dài gấp đôi; nhân với 0,5 là ngắn đi một nửa; nhân với −1 là quay ngược 180 độ.
- Tích vô hướng (dot product): lấy hai mũi tên, trả về một con số duy nhất trả lời câu hỏi “hai mũi tên này có cùng hướng không?”. Cùng hướng → số dương lớn. Vuông góc → đúng bằng 0. Ngược hướng → số âm.
Hai phép đầu cho ra vector. Phép thứ ba cho ra một con số — nhớ kỹ điều này, đó là chỗ người mới học hay nhầm nhất.
NumPy làm mọi phép từng phần tử một (element-wise). Riêng tích vô hướng là tổng của các tích từng cặp: a · b = a₀b₀ + a₁b₁ + ... + aₙbₙ.
import numpy as np a = np.array([3, 1]) b = np.array([1, 2]) print(a + b) # [4 3] cong tung phan tu print(a - b) # [2 -1] print(2 * a) # [6 2] nhan vo huong print(a * b) # [3 2] CAN THAN: tung phan tu! print(np.dot(a, b)) # 5 tich vo huong = 3*1 + 1*2 print(a @ b) # 5 cach viet ngan gon print(np.linalg.norm(a)) # 3.162 do dai = can(3^2 + 1^2)
Hai vector muốn cộng hoặc nhân với nhau thì phải cùng độ dài. Nếu không, NumPy báo lỗi ngay — đó là một cơ chế bảo vệ tốt, đừng sợ nó.
Công thức y = w · x + b — một tích vô hướng cộng thêm hằng số — chính là toàn bộ hồi quy tuyến tính, đồng thời là một neuron trong mạng nơ-ron, và cũng là lõi của SVM. Trong đó x là vector đặc trưng (dữ liệu bạn đưa vào) còn w là vector trọng số (thứ mà mô hình phải tự tìm ra). Nói cách khác: toàn bộ việc “học” trong ML là đi tìm w. Bài 27 đến 29 sẽ dạy bạn cách tìm nó.
Tích vô hướng còn là nền của cosine similarity — thước đo “hai văn bản giống nhau bao nhiêu” dùng trong công cụ tìm kiếm ngữ nghĩa và hệ gợi ý. Khi bạn đọc paper và gặp ký hiệu wᵀx hay ⟨w, x⟩, đừng hoảng: đó đúng là np.dot(w, x) bạn vừa viết.
💡 Mẹo tránh bẫy kinh điển: trong NumPy, a * b là nhân từng phần tử, không phải tích vô hướng. Muốn tích vô hướng, dùng a @ b hoặc np.dot(a, b). Rất nhiều lỗi ML âm thầm bắt nguồn từ chỗ này — chương trình vẫn chạy, chỉ có kết quả là sai.
3. Ma trận — khi dữ liệu xếp thành bảng
Một học sinh là một vector. Vậy cả lớp thì sao? Xếp chồng các vector lại, ta được một bảng số — đó chính là ma trận. Mỗi hàng là một học sinh; mỗi cột là một môn học.
Bây giờ giả sử điểm tổng kết tính theo công thức: Toán chiếm 60%, Văn chiếm 40%. Bộ tỉ lệ (0,6 ; 0,4) lại là một vector — ta gọi là vector trọng số.
Nhân ma trận với vector trọng số nghĩa là: áp cùng một công thức tính điểm cho toàn bộ học sinh, chỉ trong một phép tính duy nhất. Không vòng lặp, không thủ công. Đó là toàn bộ vẻ đẹp của ma trận.
Ma trận là mảng 2 chiều, .shape có dạng (số hàng, số cột). Quy tắc nhân duy nhất bạn cần thuộc: (m, n) @ (n,) → (m,). Số cột của ma trận phải khớp độ dài của vector; kết quả dài bằng số hàng.
import numpy as np # 3 hoc sinh (hang) x 2 mon (cot): Toan, Van X = np.array([[8, 6], [5, 9], [7, 7]]) print(X.shape) # (3, 2) -> 3 mau, 2 dac trung print(X[0]) # [8 6] -> hoc sinh dau tien print(X[:, 1]) # [6 9 7] -> cot diem Van w = np.array([0.6, 0.4]) # Toan 60%, Van 40% print(X @ w) # [7.2 6.6 7. ] -> 3 diem tong ket print(X.T.shape) # (2, 3) -> chuyen vi: doi hang thanh cot
Quy ước X.shape = (n_samples, n_features) — hàng là mẫu, cột là đặc trưng — là chuẩn bắt buộc của scikit-learn và của gần như mọi thư viện ML. Toàn bộ Chặng 8 sẽ dựa trên nó. Nếu bạn đưa vào một ma trận bị xoay ngược (đặc trưng thành hàng), thư viện vẫn chạy nhưng mô hình học ra thứ vô nghĩa — một lỗi rất khó phát hiện.
Xa hơn: một mạng nơ-ron sâu về bản chất chỉ là chồng nhiều phép nhân ma trận, xen kẽ giữa chúng là các hàm phi tuyến. GPU nhanh cho deep learning không phải vì nó “thông minh”, mà vì nó nhân được hàng nghìn hàng ma trận song song cùng lúc. Khi đọc paper và gặp Wx + b hay XW, bạn đang nhìn đúng phép tính vừa viết ở trên — chỉ khác kích thước.
Hình 2 — Mỗi hàng của bảng dữ liệu được “nén” thành một con số duy nhất. Số cột của ma trận phải khớp độ dài của vector, nếu không NumPy sẽ báo lỗi.
⚠️ Lỗi bạn chắc chắn sẽ gặp: ValueError: matmul: Input operand 1 has a mismatch... hoặc shapes (3,2) and (3,) not aligned. Cách sửa luôn giống nhau: in X.shape và w.shape ra xem, rồi kiểm tra số cột của cái đứng trước có bằng số hàng (hay độ dài) của cái đứng sau không. Nếu chưa khớp, thường bạn cần X.T. Đừng đoán — hãy in .shape ra, đó là thói quen của người làm dữ liệu chuyên nghiệp.
📝 Bảng ghi nhớ
| Cú pháp | Ý nghĩa | Ví dụ nhanh |
|---|---|---|
np.array([3, 1]) | Tạo vector (mảng 1 chiều) | a = np.array([3, 1]) |
a.shape | Kích thước. (2,) là vector 2 số; (3, 2) là ma trận 3 hàng 2 cột | X.shape → (3, 2) |
a + b | Cộng từng phần tử (hai vector phải cùng độ dài) | [3,1] + [1,2] = [4,3] |
2 * a | Nhân vô hướng: giãn hoặc co mũi tên, giữ nguyên hướng | 2 * [3,1] = [6,2] |
a * b | Nhân từng phần tử — không phải tích vô hướng | [3,1] * [1,2] = [3,2] |
a @ b hoặc np.dot(a, b) | Tích vô hướng → cho ra một số | 3·1 + 1·2 = 5 |
np.linalg.norm(a) | Độ dài (chuẩn) của vector | norm([3,4]) = 5.0 |
X @ w | Nhân ma trận với vector: áp một công thức cho mọi hàng | (3,2) @ (2,) → (3,) |
X.T | Chuyển vị: đổi hàng thành cột | (3,2) → (2,3) |
np.zeros((2, 3)) | Ma trận toàn số 0, hay dùng để khởi tạo trọng số | shape (2, 3) |
✎ Kiểm tra nhanh (5 câu)
Câu 1. Mảng np.array([4, -2, 7]) có shape là gì?
shape là (3,). Dấu phẩy đơn độc ở cuối là dấu hiệu “chỉ có một chiều”. Còn (1, 3) và (3, 1) là ma trận 2 chiều — khác hẳn.Câu 2. Với a = np.array([2, 3]) và b = np.array([4, 1]), giá trị của a @ b là?
2×4 + 3×1 = 8 + 3 = 11. Kết quả là một số, không phải vector. Đáp án [8, 3] là kết quả của a * b — phép nhân từng phần tử.Câu 3. Trong NumPy, a * b với hai vector cùng độ dài sẽ cho ra:
* trong NumPy luôn là phép nhân từng phần tử, cho ra vector cùng độ dài. Muốn tích vô hướng phải dùng @ hoặc np.dot. Đây là bẫy phổ biến nhất: chương trình vẫn chạy bình thường, chỉ có kết quả là sai.Câu 4. Trong scikit-learn, X.shape bằng (100, 5) nghĩa là dữ liệu có:
(n_samples, n_features): hàng là mẫu, cột là đặc trưng. Vậy đây là 100 mẫu, mỗi mẫu mô tả bằng 5 con số. Nếu bạn đưa vào ma trận bị xoay ngược, thư viện vẫn chạy nhưng mô hình học ra thứ vô nghĩa.Câu 5. X có shape (3, 2), w có shape (2,). Kết quả X @ w có shape là?
(m, n) @ (n,) → (m,). Số cột của X là 2, khớp với độ dài của w là 2 — nên nhân được. Kết quả dài bằng số hàng, tức là (3,): mỗi học sinh một điểm tổng kết.💻 Thực hành: vector, ma trận và mũi tên
▶ Cách dùng: bấm Khởi động Python một lần (lần đầu tải NumPy và Matplotlib nên mất khoảng 10–20 giây), đợi báo “sẵn sàng”, rồi bấm Chạy code. Bạn có thể sửa trực tiếp trong ô đen và chạy lại bao nhiêu lần tùy thích.
🔌 Python chưa khởi động.
Kết quả sẽ hiện ở đây.
💡 Thử thách (làm được cả ba là bạn đã nắm chắc bài):
- Đổi
w = np.array([0.5, 0.5])rồi chạy lại. Điểm của ai tăng, của ai giảm? Vì sao? - Tính
np.dot(a, np.array([-1, 3])). Kết quả bằng 0 — theo Lớp A thì điều đó nói lên hai mũi tên có quan hệ gì? Thử vẽ thêm vector[-1, 3]vào hình để kiểm chứng. - Thêm một học sinh thứ tư vào
X(một hàng nữa) rồi chạy lại. Shape củaX @ wthay đổi thế nào? Bạn có phải sửawkhông?