Danh sách bài viết

Bài 19: Ma trận và phép nhân ma trận — vì sao quan trọng trong neural network

Bài 19 series Python, Data & Math Foundations. Sau khi đã có vector ở bài 18, bài này mở rộng lên ma trận: định nghĩa, ký hiệu, các phép toán (cộng, nhân scalar, nhân ma trận, transpose), ma trận đặc biệt (identity, đối xứng, đường chéo), inverse. Trọng tâm là phép nhân ma trận và liên hệ trực tiếp với 1 layer dense neural network y = Wx + b, kèm code Python thuần dùng list of lists.

24/05/2026
15 phút đọc
0 lượt xem
1

Mục Tiêu Bài Học

Sau bài học, bạn sẽ:

  • Đọc được ký hiệu \( A \in \mathbb{R}^{m \times n} \) và xác định shape của một ma trận.
  • Thực hiện được phép cộng, nhân scalar, nhân ma trận, transpose bằng tay với ma trận \(2 \times 2\) hoặc \(2 \times 3\).
  • Hiểu điều kiện shape của phép nhân: \(A_{m \times k} \cdot B_{k \times n} = C_{m \times n}\) và lý do \(AB \ne BA\).
  • Liên hệ phép nhân ma trận với forward pass của 1 layer dense neural network: \( \mathbf{y} = W\mathbf{x} + \mathbf{b} \).
  • Viết được hàm matmul(A, B) bằng list of lists (chưa cần NumPy).
2

Ma Trận Là Gì

Một ma trận là bảng số gồm \(m\) hàng và \(n\) cột. Ta viết:

\[ A \in \mathbb{R}^{m \times n} \]

nghĩa là \(A\) là ma trận có \(m\) hàng, \(n\) cột, mỗi phần tử là số thực. Ví dụ một ma trận \(2 \times 3\):

\[ A = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix} \]

Đọc shape là hai nhân ba (hàng trước, cột sau). Quy ước này nhất quán trong NumPy, PyTorch, TensorFlow: A.shape == (2, 3).

Ở bài 18 bạn đã thấy vector là danh sách số có thứ tự. Ma trận đơn giản là danh sách các vector cùng độ dài xếp thành bảng — mỗi hàng (hoặc cột) là một vector.

3

Ký Hiệu Phần Tử Và Shape

Phần tử ở hàng \(i\), cột \(j\) của ma trận \(A\) ký hiệu là \(A_{ij}\). Với ma trận ở phần trước:

  • \(A_{11} = 1\), \(A_{12} = 2\), \(A_{13} = 3\)
  • \(A_{21} = 4\), \(A_{22} = 5\), \(A_{23} = 6\)

Lưu ý: trong sách toán, chỉ số chạy từ 1; trong code Python, chỉ số chạy từ 0. Tức là \(A_{11}\) trong toán chính là A[0][0] trong Python. Khi đọc paper bạn nên ghi nhớ chênh lệch này để không nhầm.

Ngoài ký hiệu \(A_{ij}\), một số tài liệu viết \(a_{ij}\) (chữ thường) cho phần tử và \(A\) (chữ hoa, in đậm trong sách in: \(\mathbf{A}\)) cho cả ma trận. Bài này dùng chữ hoa cho ma trận, chữ thường có chỉ số cho phần tử.

4

Vector Cột Vs Vector Hàng

Vector là trường hợp đặc biệt của ma trận khi một chiều bằng 1.

Vector cột — ma trận \(n \times 1\):

\[ \mathbf{x} = \begin{bmatrix} x_1 \\ x_2 \\ x_3 \end{bmatrix} \in \mathbb{R}^{3 \times 1} \]

Vector hàng — ma trận \(1 \times n\):

\[ \mathbf{x}^T = \begin{bmatrix} x_1 & x_2 & x_3 \end{bmatrix} \in \mathbb{R}^{1 \times 3} \]

Quy ước phổ biến trong toán và sách deep learning (Goodfellow, Bishop): vector mặc định là vector cột. Khi muốn vector hàng, viết \(\mathbf{x}^T\) (transpose). Quy ước này quan trọng vì nó quyết định thứ tự nhân trong công thức \(W\mathbf{x}\) hay \(\mathbf{x}W\).

Trong NumPy, một mảng 1D x.shape == (3,) không phải vector cột hay vector hàng — đó là mảng 1 chiều. Muốn rõ ràng, dùng x.reshape(3, 1) cho cột hoặc x.reshape(1, 3) cho hàng. Bài 32 sẽ đi sâu vào reshape.

5

Phép Cộng Ma Trận

Phép cộng ma trận là element-wise: \( (A + B)_{ij} = A_{ij} + B_{ij} \). Điều kiện: \(A\) và \(B\) phải có cùng shape.

\[ \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} + \begin{bmatrix} 5 & 6 \\ 7 & 8 \end{bmatrix} = \begin{bmatrix} 6 & 8 \\ 10 & 12 \end{bmatrix} \]

Nếu shape khác nhau, phép cộng không xác định. Trong NumPy, hai mảng khác shape có thể vẫn cộng được nhờ broadcasting (bài 33) — nhưng đó là quy tắc mở rộng của NumPy, không phải định nghĩa toán.

Tính chất:

  • Giao hoán: \( A + B = B + A \).
  • Kết hợp: \( (A + B) + C = A + (B + C) \).
  • Phần tử trung hoà: ma trận \(0\) (mọi phần tử bằng 0) thoả \(A + 0 = A\).
6

Phép Nhân Với Scalar

Cho scalar \(c \in \mathbb{R}\) và ma trận \(A \in \mathbb{R}^{m \times n}\), phép nhân scalar cũng là element-wise:

\[ (cA)_{ij} = c \cdot A_{ij} \]

Ví dụ:

\[ 3 \cdot \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} = \begin{bmatrix} 3 & 6 \\ 9 & 12 \end{bmatrix} \]

Trong huấn luyện neural network, bước cập nhật trọng số dùng đúng phép này: \( W \leftarrow W - \eta \cdot \nabla W \), với \(\eta\) là learning rate (scalar) và \(\nabla W\) là gradient (ma trận cùng shape \(W\)). Bài 27 sẽ đi sâu vào gradient descent.

7

Phép Nhân Ma Trận (Matrix Multiplication)

Đây là phép toán quan trọng nhất của bài. Cho \(A \in \mathbb{R}^{m \times k}\) và \(B \in \mathbb{R}^{k \times n}\), tích \(C = AB \in \mathbb{R}^{m \times n}\) được định nghĩa:

\[ C_{ij} = \sum_{l=1}^{k} A_{il} \, B_{lj} \]

Điều kiện shape

Số cột của \(A\) phải bằng số hàng của \(B\). Cách nhớ:

\[ \underbrace{A}_{m \times \boxed{k}} \cdot \underbrace{B}_{\boxed{k} \times n} = \underbrace{C}_{m \times n} \]

Hai \(k\) ở giữa phải khớp — sau khi khớp thì "biến mất", còn \(m\) và \(n\) ở ngoài là shape của kết quả.

Ví dụ cụ thể

Cho \(A \in \mathbb{R}^{2 \times 3}\) và \(B \in \mathbb{R}^{3 \times 2}\):

\[ A = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix}, \quad B = \begin{bmatrix} 7 & 8 \\ 9 & 10 \\ 11 & 12 \end{bmatrix} \]

Tính \(C_{11}\) (hàng 1 của \(A\) dot cột 1 của \(B\)):

\[ C_{11} = 1 \cdot 7 + 2 \cdot 9 + 3 \cdot 11 = 7 + 18 + 33 = 58 \]

Tính tương tự các phần tử còn lại:

\[ C = AB = \begin{bmatrix} 58 & 64 \\ 139 & 154 \end{bmatrix} \in \mathbb{R}^{2 \times 2} \]

Ý nghĩa hình học

Mỗi ma trận có thể xem như một phép biến đổi tuyến tính (linear transformation) trên không gian vector. Nhân hai ma trận \(AB\) tương ứng với kết hợp (compose) hai phép biến đổi: áp \(B\) trước, rồi áp \(A\). Vì thế thứ tự quan trọng.

Không giao hoán

Nói chung \(AB \ne BA\). Có hai mức độ:

  • Trường hợp \(A\) là \(m \times k\) và \(B\) là \(k \times n\) với \(m \ne n\): \(BA\) thậm chí không xác định (shape không khớp).
  • Trường hợp cả \(A\) và \(B\) đều vuông cùng cỡ: \(AB\) và \(BA\) cùng tồn tại nhưng giá trị thường khác nhau.

Ví dụ ngược lại:

\[ \begin{bmatrix} 1 & 1 \\ 0 & 1 \end{bmatrix} \begin{bmatrix} 1 & 0 \\ 1 & 1 \end{bmatrix} = \begin{bmatrix} 2 & 1 \\ 1 & 1 \end{bmatrix} \]

\[ \begin{bmatrix} 1 & 0 \\ 1 & 1 \end{bmatrix} \begin{bmatrix} 1 & 1 \\ 0 & 1 \end{bmatrix} = \begin{bmatrix} 1 & 1 \\ 1 & 2 \end{bmatrix} \]

Hai kết quả khác nhau — đây là lý do trong neural network, thứ tự \(W\mathbf{x}\) và \(\mathbf{x}W\) cho ra ý nghĩa khác (và thường chỉ một trong hai khớp shape).

Tính chất khác

  • Kết hợp: \( (AB)C = A(BC) \) (khi shape khớp).
  • Phân phối: \( A(B + C) = AB + AC \).
  • Liên kết với scalar: \( c(AB) = (cA)B = A(cB) \).
8

Transpose

Transpose của \(A\), ký hiệu \(A^T\), là ma trận tạo bằng cách đảo hàng và cột: \( (A^T)_{ij} = A_{ji} \). Nếu \(A \in \mathbb{R}^{m \times n}\) thì \(A^T \in \mathbb{R}^{n \times m}\).

\[ A = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix}, \quad A^T = \begin{bmatrix} 1 & 4 \\ 2 & 5 \\ 3 & 6 \end{bmatrix} \]

Tính chất hay dùng:

  • \( (A^T)^T = A \).
  • \( (A + B)^T = A^T + B^T \).
  • \( (AB)^T = B^T A^T \) — chú ý đảo thứ tự.
  • \( (cA)^T = c A^T \).

Trong code, NumPy có A.T hoặc A.transpose(); PyTorch có A.t() cho 2D hoặc A.transpose(dim0, dim1) cho tensor N chiều.

9

Identity Matrix Và Inverse

Ma trận đơn vị (identity matrix) ký hiệu \(I\) (hoặc \(I_n\) khi cần rõ kích thước) là ma trận vuông có đường chéo chính bằng 1, các phần tử còn lại bằng 0.

\[ I_3 = \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix} \]

Tính chất: với mọi \(A\) shape phù hợp, \( AI = IA = A \). \(I\) đóng vai trò "số 1" trong phép nhân ma trận.

Ma trận nghịch đảo (inverse) của ma trận vuông \(A\), ký hiệu \(A^{-1}\), là ma trận thoả:

\[ A A^{-1} = A^{-1} A = I \]

Không phải ma trận nào cũng có inverse. \(A\) phải:

  • Vuông (cùng số hàng và cột).
  • Có định thức (determinant) khác 0 — tương đương các cột (hoặc hàng) độc lập tuyến tính.

Bài này chỉ preview ngắn. Inverse xuất hiện trong nhiều công thức lý thuyết (ví dụ nghiệm closed-form của linear regression \( \hat{\boldsymbol{\beta}} = (X^T X)^{-1} X^T \mathbf{y} \)), nhưng trong deep learning thực tế ta hiếm khi tính \(A^{-1}\) trực tiếp — vừa tốn kém vừa không ổn định số học. Thay vào đó dùng gradient descent (bài 27) hoặc phân rã ma trận chuyên dụng.

10

Ma Trận Đặc Biệt

Một vài loại ma trận xuất hiện thường xuyên trong AI:

Ma trận vuông (square)

Số hàng bằng số cột: \(A \in \mathbb{R}^{n \times n}\). Chỉ ma trận vuông mới có khái niệm inverse, determinant, eigenvalue.

Ma trận đường chéo (diagonal)

Vuông và mọi phần tử ngoài đường chéo bằng 0: \(D_{ij} = 0\) khi \(i \ne j\).

\[ D = \begin{bmatrix} 2 & 0 & 0 \\ 0 & 5 & 0 \\ 0 & 0 & 7 \end{bmatrix} \]

Nhân với ma trận đường chéo là phép scale theo từng chiều. Identity \(I\) là trường hợp đặc biệt với mọi phần tử trên đường chéo bằng 1.

Ma trận đối xứng (symmetric)

Vuông và \(A = A^T\), tức \(A_{ij} = A_{ji}\) với mọi \(i, j\).

\[ S = \begin{bmatrix} 1 & 2 & 3 \\ 2 & 4 & 5 \\ 3 & 5 & 6 \end{bmatrix} \]

Ma trận hiệp phương sai (covariance) và Gram matrix \(X^T X\) trong ML đều đối xứng.

11

Vì Sao Ma Trận Quan Trọng Trong Neural Network

1 layer dense = 1 phép nhân ma trận + cộng vector

Một layer fully-connected (dense) trong neural network thực chất là biến đổi tuyến tính có học được:

\[ \mathbf{y} = W \mathbf{x} + \mathbf{b} \]

Trong đó:

  • \(\mathbf{x} \in \mathbb{R}^{d_{in}}\) — input vector (ví dụ \(d_{in} = 784\) với MNIST 28×28 flatten).
  • \(W \in \mathbb{R}^{d_{out} \times d_{in}}\) — ma trận trọng số (weight matrix).
  • \(\mathbf{b} \in \mathbb{R}^{d_{out}}\) — bias vector.
  • \(\mathbf{y} \in \mathbb{R}^{d_{out}}\) — output trước khi qua hàm kích hoạt.

Sau đó áp một hàm phi tuyến (ReLU, sigmoid, tanh…) lên \(\mathbf{y}\) để layer không bị "sụp" thành một biến đổi tuyến tính duy nhất khi xếp nhiều layer. Toàn bộ kiến trúc MLP, transformer, CNN, RNN đều dựa trên block cơ bản này — chỉ khác cách \(W\) được tổ chức và chia sẻ.

Batch dữ liệu = ma trận, không phải vector rời rạc

Khi train, ta không đẩy từng sample một mà đẩy cả batch \(N\) sample cùng lúc. Mỗi sample là một hàng:

\[ X \in \mathbb{R}^{N \times d_{in}} \]

Forward pass dùng dạng row-vector phổ biến trong framework:

\[ Y = X W^T + \mathbf{b}, \quad Y \in \mathbb{R}^{N \times d_{out}} \]

Trong PyTorch, nn.Linear(d_in, d_out) khai báo \(W\) shape (d_out, d_in) và tính đúng theo công thức trên (xem source torch.nn.functional.linear). Một số sách lý thuyết ưu tiên column-vector và viết \(Y = WX + \mathbf{b}\) với \(X\) là \(d_{in} \times N\). Hai cách chỉ là quy ước transpose — kết quả tương đương.

Tận dụng GPU

Lý do batch quan trọng: GPU có hàng nghìn lõi xử lý song song và thư viện chuyên cho phép nhân ma trận (cuBLAS, cuDNN). Nhân ma trận (N, d_in) × (d_in, d_out) chạy được song song trên cả \(N\) sample. Nếu loop từng sample bằng vòng for trên CPU, ta lãng phí phần cứng và chậm hơn nhiều lần.

Toàn bộ "tri thức" của model là tập hợp các ma trận

Khi nói "model có 7 tỷ tham số" (ví dụ Llama 7B), nghĩa là tổng số phần tử trong tất cả ma trận \(W\) (và vector bias) của model bằng khoảng 7 tỷ. File checkpoint .safetensors hay .bin chính là serialize các ma trận đó. Train model = điều chỉnh giá trị từng phần tử trong các ma trận này dựa trên gradient.

12

Code Python: Matmul Bằng List Thuần

Để hiểu công thức \(C_{ij} = \sum_l A_{il} B_{lj}\), ta cài đặt bằng list of lists — không dùng NumPy. Ba vòng lặp lồng nhau: hai cho hàng-cột của \(C\), một cho tổng theo chỉ số chung.

def shape(M):
    """Trả về (rows, cols) cho ma trận biểu diễn bằng list of lists."""
    rows = len(M)
    cols = len(M[0]) if rows > 0 else 0
    return rows, cols


def matmul(A, B):
    """Nhân ma trận A (m x k) với B (k x n), trả về C (m x n)."""
    m, k_a = shape(A)
    k_b, n = shape(B)
    if k_a != k_b:
        raise ValueError(f"Shape không khớp: A là {m}x{k_a}, B là {k_b}x{n}")

    # Khởi tạo C là ma trận m x n toàn 0
    C = [[0 for _ in range(n)] for _ in range(m)]

    for i in range(m):
        for j in range(n):
            total = 0
            for l in range(k_a):
                total += A[i][l] * B[l][j]
            C[i][j] = total
    return C


def transpose(M):
    """Đảo hàng cột: M (m x n) -> M^T (n x m)."""
    m, n = shape(M)
    return [[M[i][j] for i in range(m)] for j in range(n)]


# Ví dụ chạy thử
A = [[1, 2, 3],
     [4, 5, 6]]
B = [[7, 8],
     [9, 10],
     [11, 12]]

C = matmul(A, B)
print(C)              # [[58, 64], [139, 154]]
print(shape(C))       # (2, 2)
print(transpose(A))   # [[1, 4], [2, 5], [3, 6]]

Cách viết này đúng định nghĩa, dễ debug, nhưng rất chậm với ma trận lớn vì Python interpret từng phép cộng-nhân. Với \(N = 1000\) và \(d_{in} = d_{out} = 1000\), số phép nhân là \(10^9\) — chạy bằng list thuần mất hàng phút, trong khi NumPy gọi BLAS (C tối ưu) chỉ mất phần giây. Module 4 sẽ thay code này bằng A @ B trong NumPy.

Tuy nhiên đoạn code trên không vô dụng: nó cho bạn cảm nhận rõ phép nhân ma trận là gì. Khi đọc tài liệu framework sau này, gặp shape mismatch error bạn sẽ tự hình dung được "à, \(k\) ở giữa không khớp".

13

Bài Tập Củng Cố

Làm trên giấy hoặc trong notebook:

  1. Cho \(A \in \mathbb{R}^{4 \times 7}\) và \(B \in \mathbb{R}^{7 \times 3}\). Tích \(AB\) có shape bao nhiêu? \(BA\) có xác định không, vì sao?
  2. Viết hàm matmul(A, B) bằng list thuần (xem mẫu ở bước 12). Test với \(A = \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix}\) và \(B = \begin{bmatrix} 3 & 4 \\ 5 & 6 \end{bmatrix}\) — kết quả phải bằng chính \(B\).
  3. Viết hàm transpose(M) bằng list thuần. Kiểm tra \( (A^T)^T = A \) với một ma trận \(3 \times 2\) tự chọn.
  4. Cho \(A = \begin{bmatrix} 2 & 1 \\ 1 & 3 \end{bmatrix}\). Tính \(A^T\) bằng tay, sau đó nhận xét \(A\) có đối xứng không.
  5. Tìm shape của các ma trận \(W\) và vector \(\mathbf{b}\) trong một MLP có input 784, một hidden layer 128, output 10. Có bao nhiêu tham số có thể học (đếm phần tử \(W\) và \(\mathbf{b}\) ở mọi layer)?
Đáp án
  1. \(AB \in \mathbb{R}^{4 \times 3}\). \(BA\) không xác định vì số cột của \(B\) là 3, số hàng của \(A\) là 4, không khớp.
  2. Khi \(A = I_2\) thì \(IB = B\) — kết quả phải y nguyên \(B\). Nếu khác, sai ở vòng lặp tổng \(l\).
  3. \(A^T\) đảo hàng cột; áp transpose lần nữa quay về \(A\). Đây là cách verify hàm transpose viết đúng.
  4. \(A^T = \begin{bmatrix} 2 & 1 \\ 1 & 3 \end{bmatrix} = A\) → \(A\) là ma trận đối xứng.
  5. Layer 1: \(W_1\) shape \(128 \times 784\), \(\mathbf{b}_1\) shape \(128\) → \(128 \cdot 784 + 128 = 100\,480\) tham số. Layer 2: \(W_2\) shape \(10 \times 128\), \(\mathbf{b}_2\) shape \(10\) → \(10 \cdot 128 + 10 = 1\,290\) tham số. Tổng: \(101\,770\) tham số.
14

Tổng Kết

  • Ma trận \(A \in \mathbb{R}^{m \times n}\) là bảng số \(m\) hàng \(n\) cột; phần tử ký hiệu \(A_{ij}\).
  • Vector là ma trận đặc biệt: cột \(n \times 1\) hoặc hàng \(1 \times n\).
  • Cộng và nhân scalar là element-wise; cộng yêu cầu cùng shape.
  • Phép nhân ma trận \(C = AB\) yêu cầu số cột \(A\) bằng số hàng \(B\); công thức \(C_{ij} = \sum_l A_{il} B_{lj}\).
  • Nhìn chung \(AB \ne BA\) — thứ tự quan trọng vì nó tương ứng với việc kết hợp hai phép biến đổi tuyến tính.
  • Transpose \(A^T\) đảo hàng cột; chú ý \( (AB)^T = B^T A^T \).
  • Identity \(I\) đóng vai trò "số 1"; inverse \(A^{-1}\) chỉ tồn tại với ma trận vuông không suy biến.
  • Forward pass 1 layer dense là \( \mathbf{y} = W\mathbf{x} + \mathbf{b} \); batch \(N\) sample thành ma trận \(X\) để chạy song song trên GPU.
  • Code list thuần giúp hiểu công thức nhưng chậm; thực tế sẽ dùng NumPy / PyTorch ở module sau.
15

Bài Tiếp Theo

Bài 20 mở rộng từ ma trận lên tensor — cấu trúc N chiều. Vector là tensor 1D, ma trận là tensor 2D, ảnh RGB là tensor 3D, batch ảnh là tensor 4D. Bạn sẽ thấy lý do các framework đặt tên là PyTorch và TensorFlow — tất cả dữ liệu đều đi qua tensor.