Bốn bài trước đã xây ba mảnh ghép: vector và matrix multiplication (bài 2), gradient và chain rule (bài 3), softmax và cross-entropy (bài 4). Mỗi mảnh đứng riêng đều có vẻ trừu tượng. Bài này ghép chúng lại.
Kết quả của việc ghép đó là neural network — cụ thể là perceptron (1 neuron, 1957) và MLP (nhiều layer, vẫn là backbone của mọi LLM hiện đại). Và để chứng minh rằng đây không phải lý thuyết hàn lâm: bài này kết thúc bằng 60 dòng NumPy thuần train được bài toán XOR, không cần PyTorch, không cần GPU.
Sau bài này, bạn có mental model đủ vững để đọc paper Transformer mà không bị mắc ở phần “Feed Forward Network”. Đó là mục tiêu duy nhất cần đạt được trước khi sang Part 2.
Mental model tổng quát
perceptron: input → [ w·x + b → activation ] → output
MLP: input
└→ hidden layer 1 (nhiều neurons)
└→ hidden layer 2 (nhiều neurons)
└→ output layer → dự đoán
training: forward → loss → backward → update
└──────────────────────────────────┘
lặp N lần
Ba khái niệm cốt lõi: neuron đơn, stack nhiều layer, và vòng lặp học. Mọi thứ phức tạp hơn — LSTM, Transformer, GPT-4 — đều là biến thể của ba điều này.
Phần 1: Perceptron — neuron đơn giản nhất (1957)
Frank Rosenblatt công bố perceptron năm 1957 tại Cornell Aeronautical Laboratory. Báo chí New York Times thời điểm đó gọi nó là “bước đầu của máy móc tự suy nghĩ”. Kỳ vọng lớn, nhưng về cơ bản nó là một phép tính cộng có trọng số rồi qua một hàm phi tuyến.
Công thức:
output = activation(w · x + b)
Trong đó:
x— vector input (ví dụ:[pixel_1, pixel_2, ..., pixel_784]với ảnh 28×28)w— vector weights, cùng kích thước vớixb— bias, một số thực đơnw · x— dot product:w[0]*x[0] + w[1]*x[1] + ... + w[n]*x[n](linear algebra bài 2)activation— hàm phi tuyến (thảo luận ngay dưới đây)
Weights là gì: “tầm quan trọng” của mỗi input. Nếu w[3] lớn, pixel thứ 3 ảnh hưởng mạnh đến output. Training là quá trình điều chỉnh weights để output đúng.
Bias là gì: ngưỡng kích hoạt. Nếu không có bias, neuron chỉ kích hoạt khi input đủ lớn — bias dịch chuyển ngưỡng đó, cho phép model linh hoạt hơn.
Activation function — tại sao cần
Nếu bỏ activation, output = w · x + b — một phép tính tuyến tính thuần. Stack bao nhiêu layer tuyến tính cũng vẫn là tuyến tính. Bài toán thực tế (nhận dạng ảnh, dự đoán text) là phi tuyến. Activation function đưa phi tuyến tính vào.
| Tên | Công thức | Khi dùng |
|---|---|---|
| Sigmoid | 1 / (1 + e^-x) | Cũ, binary classification output |
| Tanh | (e^x - e^-x) / (e^x + e^-x) | Cũ, hidden layers, output [-1, 1] |
| ReLU | max(0, x) | Default hidden layer, rẻ và hoạt động tốt |
| GeLU | xấp xỉ x * sigmoid(1.702 * x) | LLM hiện đại (GPT, Llama) |
ReLU đơn giản đến mức buồn cười — âm thì bằng 0, dương thì giữ nguyên. Nhưng đây là activation được dùng nhiều nhất trong thực tế từ ~2012 đến nay vì nó không bị vanishing gradient như sigmoid/tanh (gradient sigmoid ≈ 0 ở đầu và cuối — backprop đi ngược qua nhiều layer thì gradient biến mất). GeLU là phiên bản smooth hơn ReLU, được dùng trong hầu hết LLM hiện đại.
Giới hạn của perceptron đơn
Perceptron đơn là một đường thẳng phân chia không gian input. Nó chỉ giải được bài toán linearly separable — tức là có thể vẽ một đường thẳng tách hai lớp ra. AND, OR thì được. XOR thì không.
Phần 2: XOR problem — tại sao cần nhiều layer
XOR là bài toán kinh điển chứng minh giới hạn của perceptron đơn.
| Input A | Input B | XOR output |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
Nhìn vào 4 điểm trên mặt phẳng 2D:
B
|
1 | (0,1)=1 (1,1)=0
|
0 | (0,0)=0 (1,0)=1
+-------------------
0 1 A
Class 1 ở góc trên-trái và dưới-phải. Class 0 ở góc trên-phải và dưới-trái. Không có đường thẳng nào tách được hai class này.
Perceptron đơn = một đường thẳng quyết định. Vẽ bao nhiêu cách cũng không tách được XOR.
Giải pháp: stack perceptrons.
Thêm một hidden layer. Hidden layer học cách “biến đổi” không gian input sang không gian mới, nơi bài toán trở thành linearly separable. Output layer kết hợp các biểu diễn trung gian đó.
Đây là lý do deep learning gọi là “deep” — nhiều layer là nhiều bước biến đổi không gian. Mỗi layer học một level abstraction cao hơn: layer đầu học cạnh thô, layer giữa học hình dạng, layer cuối học khái niệm.
Phần 3: MLP — Multi-Layer Perceptron
MLP là perceptron được stack. Architecture:
input layer → hidden layer(s) → output layer
Mỗi layer là: ma trận nhân + cộng bias + activation.
h1 = relu(X @ W1 + b1) # input → hidden
h2 = relu(h1 @ W2 + b2) # hidden → hidden
output = sigmoid(h2 @ W3 + b3) # hidden → output
Dấu @ là matrix multiplication (bài 2). X là ma trận input có shape [batch_size, n_features]. W1 là ma trận weights shape [n_features, n_hidden]. Kết quả h1 có shape [batch_size, n_hidden].
Shape tracking — dev cần chú ý điều này
Lỗi phổ biến nhất khi code neural network là nhầm shape. Theo dõi shape qua từng bước:
X: [4, 2] # 4 samples, 2 features
W1: [2, 4] # 2 inputs → 4 hidden units
b1: [1, 4] # broadcast theo batch
h1: [4, 4] # X @ W1 = [4,2] @ [2,4] = [4,4]
W2: [4, 1] # 4 hidden → 1 output
b2: [1, 1]
out: [4, 1] # h1 @ W2 = [4,4] @ [4,1] = [4,1]
Quy tắc matrix multiplication: [A, B] @ [B, C] = [A, C]. Chiều trong phải khớp. Nếu bị ValueError: matmul: shapes mismatch thì đây là nơi đầu tiên nhìn.
Universal approximation theorem
MLP đủ rộng (đủ nhiều neurons ở hidden layer) có thể xấp xỉ bất kỳ hàm liên tục nào với sai số tùy ý nhỏ. Đây là lý thuyết toán học được chứng minh — không phải claim thực nghiệm. Nó không nói gì về cần bao nhiêu neurons, bao nhiêu data để train, hay bao lâu. Nhưng nó nói: về mặt lý thuyết, MLP đủ mạnh để học bất kỳ pattern nào. Phần còn lại là engineering.
Phần 4: Training loop — gradient descent đầy đủ
Có kiến trúc rồi. Nhưng weights ban đầu là random — model chưa biết gì. Training là quá trình điều chỉnh weights theo hướng giảm loss.
Một vòng lặp training đầy đủ:
1. Forward pass: input → output (dự đoán)
2. Compute loss: so sánh output với ground truth
3. Backward pass: tính gradient của loss theo mỗi weight (chain rule bài 3)
4. Update: w = w - lr * gradient
5. Lặp lại
Forward pass chạy data qua network từ đầu đến cuối, thu được output.
Loss đo độ sai lệch giữa output và ground truth. Với binary output dùng MSE (mean((y - y_hat)^2)) hoặc binary cross-entropy. Với multi-class dùng cross-entropy (bài 4).
Backward pass dùng chain rule để tính dL/dW — gradient của loss theo mỗi weight. Đây là backpropagation. Không cần tính bằng tay — nhưng hiểu nguyên lý: gradient cho biết weight nào cần tăng, giảm, và bao nhiêu.
Update rule: w = w - lr * gradient. Learning rate lr kiểm soát bước nhảy mỗi lần. Quá lớn: dao động, không hội tụ. Quá nhỏ: học rất chậm. Thường bắt đầu thử 0.01, 0.1, 0.5.
Hyperparameters quan trọng:
lr(learning rate): ảnh hưởng lớn nhất đến tốc độ hội tụepochs: số lần chạy qua toàn bộ datasetbatch_size: số samples xử lý mỗi lần trước khi update weights
Monitoring: print loss mỗi vài trăm epochs. Nếu loss không giảm → lr quá nhỏ hoặc architecture sai. Nếu loss dao động/tăng → lr quá lớn.
Phần 5: Hands-on — code MLP train XOR từ zero
Code dưới đây tự implement forward pass, backward pass, và training loop. Không import gì ngoài NumPy. Copy paste chạy thẳng.
import numpy as np
np.random.seed(42)
# XOR dataset
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([[0], [1], [1], [0]])
# Network: 2 inputs -> 4 hidden units -> 1 output
W1 = np.random.randn(2, 4) * 0.5
b1 = np.zeros((1, 4))
W2 = np.random.randn(4, 1) * 0.5
b2 = np.zeros((1, 1))
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def sigmoid_deriv(x):
# x ở đây là output đã qua sigmoid, không phải pre-activation
return x * (1 - x)
lr = 0.5
for epoch in range(5000):
# Forward pass
h = sigmoid(X @ W1 + b1) # [4,2] @ [2,4] = [4,4]
out = sigmoid(h @ W2 + b2) # [4,4] @ [4,1] = [4,1]
# Loss: Mean Squared Error
loss = np.mean((y - out) ** 2)
# Backward pass (chain rule thủ công)
# Gradient của loss theo out: d(MSE)/d(out) = 2*(out-y)/n
# nhân với sigmoid_deriv để qua activation
d_out = (out - y) * sigmoid_deriv(out) # [4,1]
d_W2 = h.T @ d_out # [4,4].T @ [4,1] = [4,1]
d_b2 = d_out.sum(axis=0, keepdims=True) # [1,1]
# Backprop qua W2 vào hidden layer
d_h = d_out @ W2.T * sigmoid_deriv(h) # [4,1] @ [1,4] = [4,4]
d_W1 = X.T @ d_h # [2,4].T @ [4,4] = [2,4]
d_b1 = d_h.sum(axis=0, keepdims=True) # [1,4]
# Gradient descent update
W1 -= lr * d_W1
b1 -= lr * d_b1
W2 -= lr * d_W2
b2 -= lr * d_b2
if epoch % 500 == 0:
print(f"Epoch {epoch:4d}: loss = {loss:.4f}")
print("\nFinal predictions:")
print(out.round(2))
print("\nExpected:")
print(y)
Output khi chạy:
Epoch 0: loss = 0.2589
Epoch 500: loss = 0.2454
Epoch 1000: loss = 0.1821
Epoch 1500: loss = 0.0532
Epoch 2000: loss = 0.0152
Epoch 2500: loss = 0.0073
Epoch 3000: loss = 0.0044
Epoch 3500: loss = 0.0030
Epoch 4000: loss = 0.0022
Epoch 4500: loss = 0.0017
Final predictions:
[[0.04]
[0.96]
[0.96]
[0.05]]
Expected:
[[0]
[1]
[1]
[0]]
Loss giảm từ ~0.26 xuống ~0.001. Predictions ≈ [0, 1, 1, 0] — đúng với XOR.
Giải thích từng block
Initialization: np.random.randn(...) * 0.5 — random nhỏ gần 0. Không init bằng 0 vì tất cả neurons sẽ học giống hệt nhau (symmetry problem). Không init quá lớn vì sigmoid saturate, gradient biến mất.
Forward pass: hai lần matrix multiply + sigmoid. Không có vòng lặp, NumPy xử lý song song toàn bộ batch 4 samples cùng lúc.
Loss: MSE đơn giản hơn cross-entropy cho bài demo. Với classification thực tế nên dùng binary cross-entropy.
Backward pass: đây là phần trọng tâm. d_out là gradient của loss theo output của layer cuối — chain rule ghép d(MSE)/d(out) với d(out)/d(pre_activation) (tức sigmoid_deriv). d_W2 là gradient theo weight layer 2, tính bằng h.T @ d_out. Sau đó backprop tiếp qua layer 1 theo cùng pattern.
Update: gradient descent cơ bản nhất — trừ trực tiếp. PyTorch optimizer.step() làm đúng điều này, cộng thêm momentum, weight decay, và learning rate schedule.
Thử nghiệm thêm
Sau khi code chạy được, thử:
- Tăng hidden units từ 4 lên 8, 16 — loss hội tụ nhanh hơn không?
- Đổi
sigmoidở hidden layer sangrelu(nhớ sửa cả backward) — thấy gì khác? - Giảm
lrxuống0.01— cần bao nhiêu epoch để hội tụ? - Tăng
lrlên2.0— loss có dao động không?
Phần 6: Từ MLP đến Transformer — bridge đến Part 3
Transformer không phải kiến trúc hoàn toàn mới. Nó là MLP được mở rộng với một cơ chế bổ sung gọi là attention.
Mỗi Transformer block gồm:
input
└→ Multi-Head Attention + residual connection
└→ Layer Norm
└→ Feed Forward Network (MLP) + residual connection
└→ Layer Norm
└→ output
Feed Forward Network (FFN) trong Transformer block chính là MLP hai layer:
# FFN trong GPT-2, Llama, và hầu hết LLM
h = gelu(x @ W1 + b1) # expand: d_model → 4 * d_model
out = h @ W2 + b2 # collapse: 4 * d_model → d_model
Paper Transformer gốc (Vaswani 2017) dùng d_model = 512, FFN mở rộng lên 2048 — gấp 4 lần. Llama-3-8B có d_model = 4096, FFN expand lên 14336 — xấp xỉ 3.5 lần. Pattern này gọi là “bottleneck inverse” hay “wide FFN”.
Trong GPT: mỗi block = attention + FFN, stack 32-96 lần tùy model size. GPT-2 small stack 12 lần. Llama-3-70B stack 80 lần.
Hiểu MLP = hiểu 50% kiến trúc Transformer. Phần còn lại là attention (bài 9-11). Residual connection và Layer Norm là kỹ thuật stability — giải thích ở bài 12.
Cheatsheet
| Khái niệm | Ý nghĩa ngắn gọn |
|---|---|
| Neuron | activation(w · x + b) — một đơn vị tính toán |
Weights w | ”Tầm quan trọng” của mỗi input — được học qua training |
Bias b | Ngưỡng kích hoạt — được học qua training |
| Activation function | Đưa phi tuyến tính vào (ReLU, GeLU, sigmoid, tanh) |
| Layer | Nhiều neurons xử lý song song — biểu diễn dưới dạng matrix |
| MLP | Nhiều layer stack — mỗi layer học một level abstraction |
| Forward pass | Input → output, tính prediction |
| Backward pass | Gradient từ loss về mỗi weight, dùng chain rule |
| Training loop | Forward + compute loss + backward + update, lặp N lần |
| Learning rate | Bước nhảy mỗi lần update weight — hyperparameter quan trọng nhất |
Sáu điều phải nhớ nếu không nhớ gì khác:
- Neuron = dot product + activation, không có gì kỳ bí hơn
- Activation function là lý do tại sao network học được pattern phi tuyến
- Stack nhiều layer = nhiều bước biến đổi không gian = học được pattern phức tạp hơn
- Training = lặp đi lặp lại: dự đoán → đo sai → tính gradient → cập nhật
- Gradient = hướng cần điều chỉnh weight để giảm loss
- FFN trong Transformer = MLP hai layer, expand 4x rồi collapse
Lời kết
Part 1 Foundation kết thúc ở đây. Năm bài, từ mental model tổng quát (bài 1) đến linear algebra (bài 2), calculus (bài 3), probability (bài 4), và giờ là neural network code được (bài 5). Đây là nền đủ vững để đọc paper Transformer và không bị lạc ở phần toán.
Part 2 bắt đầu với tokenization (bài 6) — một trong những topic underrated nhất của LLM. Phần lớn người học bỏ qua vì nghĩ nó chỉ là “biến text thành số”. Thực tế: tokenization ảnh hưởng đến chất lượng model, cost inference, và lý do tiếng Việt tốn token gấp đôi tiếng Anh. Bài 7 sẽ build BPE tokenizer từ đầu — 200 dòng Python, không dùng thư viện.
Để tiêu hóa bài này trước khi đọc tiếp:
- Chạy code XOR ở trên, thay đổi hidden units và learning rate để thấy tác động
- Xem Karpathy “The spelled-out intro to neural networks and backpropagation: building micrograd” trên YouTube (1.5 giờ) — sau bài này bạn hiểu 100% những gì ông giải thích
- Challenge: tự code MLP classify MNIST (28×28 ảnh chữ số viết tay) bằng NumPy thuần, không PyTorch — khoảng 100-150 dòng nếu làm gọn