- Published on
Day 9: Neural Network từ Zero
- Authors

- Name
- Trần Mạnh Thắng
- @TranManhThang96
Mục tiêu của ngày học
Sau bài này, bạn cần làm được 7 việc:
- Giải thích được neuron là
weighted sum + activation. - Phân biệt được Sigmoid, Tanh, ReLU và GELU theo mục đích sử dụng, điểm mạnh, điểm yếu và rủi ro gradient.
- Viết được forward pass của MLP 2-layer bằng NumPy với shape contract rõ ràng.
- Hiểu binary cross entropy loss và vì sao cần clipping khi tính
log. - Theo dõi được backpropagation từng bước từ output layer về hidden layer.
- Train được MLP 2-layer trên XOR dataset bằng gradient descent.
- Trả lời được khi nào code NumPy tự viết chỉ để học, khi nào concept có thể đưa vào production và cần điều kiện gì.
Cách học đề xuất trong 2.5 giờ
| Thời lượng | Việc cần làm | Output |
|---|---|---|
| 15 phút | Đọc TL;DR, mental model và shape contract trong file này | Nắm được network là một function có tham số học từ data |
| 45 phút | Đọc document.md phần neuron, activation, forward pass và loss | Hiểu đường đi của data từ X đến y_hat |
| 35 phút | Đọc phần backpropagation và gradient descent | Tự suy ra được shape của từng gradient |
| 45 phút | Chạy exercise.md với xor_mlp_numpy.py | Train được XOR, thấy loss giảm và prediction đúng |
| 10 phút | Làm checklist và production review | Biết giới hạn của NumPy implementation |
TL;DR
Neural network là một hàm có nhiều layer. Mỗi layer nhận input dạng ma trận, nhân với weight, cộng bias, rồi đi qua activation:
Z = X @ W + b
A = activation(Z)
Nếu không có activation, nhiều linear layer gộp lại vẫn chỉ là một linear function. Activation tạo non-linearity, nhờ vậy MLP có thể học bài toán XOR mà Logistic Regression tuyến tính không học được.
Training là vòng lặp:
forward pass -> loss -> backward pass -> gradient descent update
Trong production, bạn gần như không tự viết backprop bằng NumPy cho training thật. Bạn dùng PyTorch, TensorFlow hoặc framework tương đương để có autograd, GPU, checkpoint, mixed precision, distributed training, profiler và ecosystem deployment. Nhưng hiểu NumPy implementation giúp bạn debug shape, loss curve, exploding/vanishing gradient, learning rate và train-serving skew tốt hơn.
Mental model cho Senior SE
| Neural Network | Cách nhìn của Senior SE |
|---|---|
Input tensor X | Request payload hoặc batch record đã được chuẩn hóa |
Weight W | Config học từ data, không viết tay |
Bias b | Offset/default học được |
| Activation | Transform/policy phi tuyến |
| Forward pass | Runtime request flow |
| Loss | Objective kỹ thuật để tối ưu |
| Metric | Acceptance criteria theo use case |
| Gradient | Tín hiệu feedback cho từng tham số |
| Optimizer | Update strategy |
| Model artifact | Build artifact cần version, test, rollback |
Điểm khác backend truyền thống: model không chỉ pass/fail theo rule cố định. Model trả score xác suất, và policy layer mới quyết định action cuối cùng theo threshold, risk, cost và business constraint.
Shape contract tối thiểu
Trong bài này ta dùng binary classification với batch input:
| Ký hiệu | Shape | Ý nghĩa |
|---|---|---|
X | (batch_size, input_dim) | Batch input |
W1 | (input_dim, hidden_dim) | Weight hidden layer |
b1 | (1, hidden_dim) | Bias hidden layer, broadcast theo batch |
Z1 | (batch_size, hidden_dim) | Weighted sum hidden layer |
A1 | (batch_size, hidden_dim) | Activation hidden layer |
W2 | (hidden_dim, output_dim) | Weight output layer |
b2 | (1, output_dim) | Bias output layer |
Z2 | (batch_size, output_dim) | Logit output |
P | (batch_size, output_dim) | Probability sau Sigmoid |
Y | (batch_size, output_dim) | Label |
NumPy dùng toán tử @ cho matrix multiplication. Bias có shape (1, hidden_dim) hoặc (1, output_dim) để NumPy broadcasting cộng bias vào từng dòng của batch. Đây là contract cần giữ nghiêm; sai shape là nguồn bug phổ biến nhất khi chuyển từ ý tưởng toán học sang code.
Bản đồ nội dung
- Học phần chính: document.md
- Bài thực hành: exercise.md
- Script chạy được: xor_mlp_numpy.py
Deliverable cuối ngày
Bạn nên có 3 output:
- Một lần chạy XOR thành công với loss giảm và prediction đúng 4 điểm.
- Một bảng so sánh ngắn giữa
hidden_dim,learning_ratevàactivation. - Một đoạn production review trả lời: có nên tự viết MLP bằng NumPy cho production không, nếu không thì dùng framework nào và cần guardrail gì.
Dùng được trong production không? Nếu có thì cần điều kiện gì?
Concept trong bài dùng được trong production. Code NumPy trong bài chủ yếu dùng để học và kiểm chứng toán học.
Không nên dùng code NumPy tự viết cho production training vì:
- Không có autograd, gradient dễ sai và khó review khi model lớn hơn.
- Không tận dụng tốt GPU, mixed precision, distributed training và optimizer hiện đại.
- Thiếu checkpointing, profiler, model export, experiment tracking và ecosystem deployment.
- Dễ thiếu test cho numerical stability, data drift và train-serving skew.
Có thể dùng một implementation NumPy nhỏ trong production inference chỉ khi scope rất hẹp, model rất nhỏ, không cần GPU, đã freeze weights và có đầy đủ điều kiện:
- Shape contract, dtype và feature order được validate.
- Preprocessing được version cùng model artifact.
- Có golden tests cho input/output, threshold và edge cases.
- Có benchmark p50/p95/p99 latency trên hardware thật.
- Có monitoring cho latency, error rate, prediction distribution, data quality và drift.
- Có rollback plan, artifact versioning và owner rõ ràng.
Best solution theo context:
- Học concept, debug toán học: dùng NumPy như bài này.
- Training neural network thật: dùng PyTorch hoặc TensorFlow.
- Tabular data ít hoặc vừa, cần explainability: bắt đầu bằng Logistic Regression, Random Forest hoặc XGBoost trước khi dùng MLP.
- Inference production: ưu tiên artifact chuẩn của framework, có serving stack, monitoring và rollback.
Checklist hoàn thành
- Tôi giải thích được
z = X @ W + bvàa = activation(z). - Tôi biết khi nào dùng Sigmoid, Tanh, ReLU và GELU.
- Tôi viết được shape của
W1,b1,A1,W2,b2,P. - Tôi hiểu vì sao BCE cần clipping probability khi tự tính bằng NumPy.
- Tôi suy ra được
dW2 = A1.T @ dZ2vàdW1 = X.T @ dZ1. - Tôi train được XOR bằng NumPy MLP 2-layer.
- Tôi visualize hoặc ít nhất log được loss giảm theo epoch.
- Tôi nêu được trade-off giữa NumPy tự viết và framework autograd.
- Tôi trả lời được điều kiện để concept neural network được dùng trong production.
Tài liệu
1. Vì sao cần Neural Network?
Các model tuyến tính như Linear Regression hoặc Logistic Regression học được quan hệ dạng:
score = w1*x1 + w2*x2 + ... + wn*xn + b
Chúng mạnh, nhanh, dễ explain và thường là baseline tốt. Nhưng chúng gặp giới hạn khi pattern là phi tuyến. XOR là ví dụ kinh điển:
| x1 | x2 | y |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
Không có một đường thẳng nào tách được class 0 và 1 trong không gian 2 chiều. MLP với hidden layer có activation phi tuyến có thể biến đổi không gian input trước, rồi output layer phân loại trên representation mới.
Mental model thực dụng:
raw features -> learned representation -> prediction score -> business policy
Neural network hữu ích khi feature interaction phức tạp, pattern phi tuyến rõ, dữ liệu đủ lớn và cost/latency chấp nhận được. Với tabular data ít, classical ML vẫn thường là lựa chọn đầu tiên.
2. Neuron = Weighted Sum + Activation
Một neuron nhận nhiều feature đầu vào:
z = w1*x1 + w2*x2 + ... + wn*xn + b
a = activation(z)
Trong đó:
x: feature input.w: weight học từ data.b: bias học từ data.z: weighted sum, còn gọi là pre-activation.a: output sau activation.
Ví dụ:
x = [0.8, 0.2]
w = [2.0, -1.0]
b = -0.5
z = 0.8*2.0 + 0.2*(-1.0) - 0.5 = 0.9
Nếu dùng ReLU:
a = max(0, 0.9) = 0.9
Nếu dùng Sigmoid:
a = 1 / (1 + exp(-0.9)) ≈ 0.711
Neuron riêng lẻ rất đơn giản. Sức mạnh đến từ việc ghép nhiều neuron thành layer, rồi ghép nhiều layer thành network.
3. Matrix form và shape contract
Trong code production-like, ta không loop từng record rồi từng neuron. Ta xử lý theo batch bằng matrix multiplication:
Z = X @ W + b
A = activation(Z)
Shape:
| Ký hiệu | Shape | Ghi chú |
|---|---|---|
X | (batch_size, input_dim) | Mỗi dòng là một sample |
W | (input_dim, output_dim) | Mỗi cột tương ứng một neuron output |
b | (1, output_dim) | Broadcast theo batch_size |
Z | (batch_size, output_dim) | Pre-activation |
A | (batch_size, output_dim) | Post-activation |
NumPy hỗ trợ toán tử @ cho matrix multiplication, tương đương np.matmul trong trường hợp 2D phổ biến. NumPy broadcasting cho phép cộng b shape (1, output_dim) vào Z shape (batch_size, output_dim) khi chiều tương ứng bằng nhau hoặc một chiều là 1.
Shape contract là API contract của model. Nếu X bị đảo thành (input_dim, batch_size), code có thể crash ngay hoặc tệ hơn là chạy ra kết quả sai. Vì vậy script thực hành có _check_2d và kiểm tra số chiều trước khi forward.
4. Activation functions
Nếu chỉ ghép linear layer:
Y = (X @ W1 + b1) @ W2 + b2
Ta có thể viết lại thành một linear layer khác:
Y = X @ (W1 @ W2) + (b1 @ W2 + b2)
Nhiều layer nhưng không có activation vẫn không học được quan hệ phi tuyến. Activation phá tính tuyến tính này.
Sigmoid
sigmoid(x) = 1 / (1 + exp(-x))
Đặc điểm:
- Output nằm trong
(0, 1). - Phù hợp ở output layer cho binary classification.
- Có thể hiểu như probability nếu model được train và calibrate tốt.
- Dễ bị vanishing gradient khi
xquá âm hoặc quá dương. - Khi tự tính bằng NumPy, nên clip input để tránh overflow trong
exp.
Derivative nếu đã có output s:
sigmoid'(x) = s * (1 - s)
Tanh
tanh(x) = (exp(x) - exp(-x)) / (exp(x) + exp(-x))
Đặc điểm:
- Output nằm trong
(-1, 1). - Center quanh 0, thường dễ tối ưu hơn Sigmoid ở hidden layer nhỏ.
- Vẫn bị vanishing gradient khi input có độ lớn cao.
- Tốt cho bài học XOR vì đạo hàm đơn giản và model nhỏ.
Derivative nếu đã có output t:
tanh'(x) = 1 - t^2
ReLU
relu(x) = max(0, x)
Đặc điểm:
- Nhanh, đơn giản, phổ biến cho hidden layer.
- Giảm vanishing gradient ở vùng dương.
- Có thể gặp dead neuron nếu neuron luôn nhận input âm, gradient bằng 0.
- Thường là default hợp lý cho MLP/CNN cổ điển.
Derivative:
relu'(x) = 1 nếu x > 0, ngược lại 0
GELU
GELU thường gặp trong Transformer như BERT/GPT-style models. Công thức gần đúng phổ biến:
gelu(x) ≈ 0.5*x*(1 + tanh(sqrt(2/pi)*(x + 0.044715*x^3)))
Đặc điểm:
- Mượt hơn ReLU vì không cắt cứng tại 0.
- Cho phép một phần giá trị âm đi qua theo xác suất/độ lớn.
- Tốn compute hơn ReLU.
- Thường là lựa chọn tốt trong Transformer, không phải luôn cần cho MLP nhỏ.
Bảng trade-off activation
| Activation | Điểm mạnh | Điểm yếu | Khi dùng |
|---|---|---|---|
| Sigmoid | Output 0-1, hợp binary probability | Vanishing gradient, dễ overflow nếu không clip | Output layer binary |
| Tanh | Center quanh 0, dễ học XOR | Vẫn vanishing gradient | Hidden layer nhỏ, bài học từ zero |
| ReLU | Nhanh, gradient tốt vùng dương | Dead neuron | Default hidden layer MLP/CNN |
| GELU | Mượt, mạnh trong Transformer | Tốn compute hơn ReLU | Transformer, model lớn |
Best solution theo context của bài: dùng Tanh hoặc ReLU ở hidden layer để học XOR; dùng Sigmoid ở output layer để trả probability.
5. Forward pass của MLP 2-layer
MLP 2-layer trong bài này nghĩa là:
- Hidden layer: linear transform + activation.
- Output layer: linear transform + Sigmoid.
Với batch X:
Z1 = X @ W1 + b1
A1 = activation(Z1)
Z2 = A1 @ W2 + b2
P = sigmoid(Z2)
Shape đầy đủ:
| Biến | Shape với XOR | Shape tổng quát |
|---|---|---|
X | (4, 2) | (batch_size, input_dim) |
Y | (4, 1) | (batch_size, output_dim) |
W1 | (2, hidden_dim) | (input_dim, hidden_dim) |
b1 | (1, hidden_dim) | (1, hidden_dim) |
Z1 | (4, hidden_dim) | (batch_size, hidden_dim) |
A1 | (4, hidden_dim) | (batch_size, hidden_dim) |
W2 | (hidden_dim, 1) | (hidden_dim, output_dim) |
b2 | (1, 1) | (1, output_dim) |
P | (4, 1) | (batch_size, output_dim) |
Trong serving, forward pass là phần bạn chạy nhiều nhất. Vì vậy code phải rõ shape, vectorized và không có Python loop theo sample nếu không cần.
6. Loss function: Binary Cross Entropy
Với binary classification, model output probability p và label y thuộc {0, 1}. Binary Cross Entropy:
BCE(y, p) = - y*log(p) - (1-y)*log(1-p)
Mean loss trên batch:
loss = mean(BCE(Y, P))
Ý nghĩa:
- Nếu
y = 1, model bị phạt khipthấp. - Nếu
y = 0, model bị phạt khipcao. - Dự đoán càng tự tin nhưng sai, loss càng lớn.
Vấn đề số học:
log(0)là vô hạn âm.- Sigmoid có thể trả giá trị rất gần 0 hoặc 1.
- Khi tự tính bằng NumPy, cần
np.clip(P, eps, 1 - eps)trước khilog.
Trong framework production, thường dùng loss nhận logits trực tiếp như BCEWithLogitsLoss để ổn định số học hơn. Trong bài này ta dùng Sigmoid + BCE đã clip để dễ nhìn thấy forward pass.
7. Backpropagation step by step
Backpropagation là cách áp dụng chain rule để tính gradient của loss theo từng tham số.
Forward:
Z1 = X @ W1 + b1
A1 = activation(Z1)
Z2 = A1 @ W2 + b2
P = sigmoid(Z2)
L = BCE(Y, P)
Backward đi ngược từ loss:
dZ2 = (P - Y) / m
dW2 = A1.T @ dZ2
db2 = sum(dZ2, axis=0, keepdims=True)
dA1 = dZ2 @ W2.T
dZ1 = dA1 * activation_grad(Z1 hoặc A1)
dW1 = X.T @ dZ1
db1 = sum(dZ1, axis=0, keepdims=True)
Trong đó m = batch_size.
Vì sao dZ2 = (P - Y) / m?
Khi kết hợp Sigmoid output và BCE loss, đạo hàm rút gọn rất đẹp:
dL/dZ2 = P - Y
Nếu loss là mean trên batch, chia thêm cho m.
Đây là lý do nhiều implementation binary classification dùng logits/probability rất cẩn thận: nếu tách sai công thức hoặc quên scale batch, learning rate sẽ khó tuning.
Shape của gradient
Gradient của một tham số phải có cùng shape với tham số đó:
| Gradient | Công thức | Shape |
|---|---|---|
dZ2 | (P - Y) / m | (batch_size, output_dim) |
dW2 | A1.T @ dZ2 | (hidden_dim, output_dim) |
db2 | sum(dZ2, axis=0, keepdims=True) | (1, output_dim) |
dA1 | dZ2 @ W2.T | (batch_size, hidden_dim) |
dZ1 | dA1 * activation_grad(...) | (batch_size, hidden_dim) |
dW1 | X.T @ dZ1 | (input_dim, hidden_dim) |
db1 | sum(dZ1, axis=0, keepdims=True) | (1, hidden_dim) |
Nếu dW1.shape != W1.shape, backward đang sai. Đây là check đơn giản nhưng cực kỳ hữu ích.
8. Gradient descent
Sau khi có gradient, cập nhật tham số:
W = W - learning_rate * dW
b = b - learning_rate * db
Learning rate là hyperparameter nhạy:
- Quá lớn: loss dao động, tăng mạnh hoặc ra
nan. - Quá nhỏ: loss giảm rất chậm.
- Vừa đủ: loss giảm ổn định và prediction cải thiện.
Trong bài này dùng full-batch gradient descent vì XOR chỉ có 4 điểm. Với dataset lớn, mini-batch gần như là default vì:
- Không cần load toàn bộ data vào memory cho mỗi update.
- Tăng throughput tốt hơn trên GPU/accelerator.
- Noise từ mini-batch đôi khi giúp generalization.
9. Initialization, seed và dtype
Seed
Neural network bắt đầu từ weight random. Nếu không set seed, mỗi lần chạy có thể ra loss curve khác nhau. NumPy khuyến nghị dùng np.random.default_rng(seed) thay vì API random global cũ vì:
- Không phụ thuộc global random state.
- Dễ truyền RNG vào function/class.
- Reproducibility rõ hơn.
Initialization
Nếu tất cả weight bằng 0, các neuron trong cùng một layer nhận gradient giống nhau và học giống nhau. Cần random initialization để phá symmetry.
Scale initialization nên phụ thuộc fan_in:
- Tanh/Sigmoid: thường dùng scale gần Xavier, ví dụ
sqrt(1 / fan_in). - ReLU/GELU: thường dùng scale gần He, ví dụ
sqrt(2 / fan_in).
Trong bài, script chọn scale theo activation để model học XOR ổn định hơn.
dtype
Trade-off:
| dtype | Ưu điểm | Nhược điểm | Khi dùng |
|---|---|---|---|
float64 | Chính xác hơn, tốt để học/debug numeric | Tốn RAM gấp đôi, thường chậm hơn | Notebook học toán, gradient check |
float32 | Gần default deep learning, nhanh và tiết kiệm RAM hơn | Ít precision hơn | Training/inference thông thường |
Script hỗ trợ --dtype float32 và --dtype float64. Với XOR, cả hai đều đủ.
10. NumPy implementation design
Code thực hành dùng các nguyên tắc sau:
- Vectorized
@thay vì loop từng sample. - Bias shape
(1, dim)để broadcasting rõ ràng. np.random.default_rng(seed)để reproducible.- Shape checks cho input, label và output.
- Sigmoid clip input trước
exp. - BCE clip probability trước
log. - Logging loss theo
log_every. - Optional matplotlib để visualize loss, nhưng script vẫn chạy nếu không cài matplotlib.
Đây là code gần production theo nghĩa engineering hygiene, không phải production training stack. Nó có contract, validation và observability tối thiểu, nhưng không thay thế autograd framework.
11. XOR dataset
XOR nhỏ nhưng hữu ích vì:
- Dễ kiểm tra bằng mắt.
- Bắt buộc model học non-linearity.
- Nếu model linear không học được, bạn thấy rõ giới hạn của baseline tuyến tính.
- Nếu MLP học được, bạn thấy hidden layer đang tạo representation mới.
Dataset:
X = np.array(
[
[0.0, 0.0],
[0.0, 1.0],
[1.0, 0.0],
[1.0, 1.0],
],
dtype=np.float32,
)
Y = np.array([[0.0], [1.0], [1.0], [0.0]], dtype=np.float32)
Với hidden_dim=4, activation=tanh, learning_rate=0.5, model thường học được XOR sau vài nghìn epoch.
12. Trade-off quan trọng
| Lựa chọn | Nên dùng khi | Không nên dùng khi | Production note |
|---|---|---|---|
| NumPy tự viết | Học concept, kiểm chứng shape/gradient | Training thật, model lớn | Dễ sai gradient, không có autograd/GPU ecosystem |
| PyTorch/TensorFlow | Deep learning production, GPU, checkpoint | Bài toán classical ML đơn giản | Default cho neural network thật |
| Sigmoid output | Binary probability | Hidden layer sâu | Nên dùng logits loss trong framework |
| Tanh hidden | Model nhỏ, cần output centered | Network sâu dễ vanishing gradient | Hợp bài XOR |
| ReLU hidden | Default MLP/CNN | Risk dead neuron, output probability | Nhanh, đơn giản |
| GELU hidden | Transformer/model lớn | MLP nhỏ cần latency thấp | Mượt nhưng tốn compute |
| Full batch | Dataset rất nhỏ | Dataset lớn | Ổn định nhưng tốn RAM |
| Mini-batch | Dataset vừa/lớn | Batch quá nhỏ gây noisy | Default training thực tế |
float32 | Training/inference thông thường | Cần debug precision cao | Tiết kiệm RAM và nhanh hơn |
float64 | Học toán, gradient check | Production latency/memory chặt | Chính xác hơn nhưng đắt hơn |
13. Performance considerations
Vectorization
Matrix multiplication là workload chính. NumPy gọi các thư viện tối ưu thấp hơn như BLAS tùy môi trường cài đặt. Vì vậy:
X @ Wnhanh hơn loop Python theo sample và neuron.- Batch processing tận dụng CPU cache và vectorized kernel tốt hơn.
- Loop Python chỉ nên dùng cho control flow hoặc logging, không dùng cho toán lõi nếu có thể vectorize.
Memory
Memory activation xấp xỉ:
batch_size * hidden_dim * dtype_size
Ví dụ batch_size=4096, hidden_dim=1024, float32=4 bytes:
4096 * 1024 * 4 ≈ 16 MB
Đó mới chỉ là một activation. Training cần lưu nhiều activation và gradient hơn inference.
Latency
Với MLP nhỏ, inference CPU có thể rất nhanh. Nhưng production không đo bằng cảm giác:
- Benchmark trên input thật.
- Đo p50, p95, p99 latency.
- Đo throughput theo batch size.
- Đo memory peak.
- Đo cold start nếu model load theo request hoặc serverless.
Numerical stability
Các lỗi phổ biến:
np.expoverflow với input quá lớn.log(0)khi BCE nhận probability đúng 0 hoặc 1.- Loss thành
nan. - Gradient quá lớn làm weight nổ.
Guardrail trong bài:
- Clip Sigmoid input.
- Clip BCE probability.
- Có
--clip-grad-normoptional để quan sát gradient clipping. - Log loss định kỳ.
14. Production architecture mapping
Một neural network production không chỉ là file weight.
training data version
|
feature/preprocessing code version
|
training config + seed + package versions
|
model artifact + metrics + threshold
|
serving API + monitoring + rollback
Những thứ cần version cùng nhau:
- Feature order và feature schema.
- Preprocessing/scaling/encoding.
- Model architecture.
- Weights.
- Threshold/policy.
- Training dataset snapshot hoặc data version.
- Package/runtime version.
Monitoring tối thiểu:
- Request count, latency p50/p95/p99, error rate.
- Prediction distribution.
- Input null rate, out-of-range rate, schema violation.
- Segment-level metric nếu có delayed label.
- Drift signal.
- Cost và resource utilization.
Testing tối thiểu:
- Unit test activation/loss shape.
- Golden test cho một vài input cố định.
- Contract test cho feature order và dtype.
- Regression test cho metric trên validation set.
- Load test cho latency/memory.
- Rollback drill cho artifact cũ.
15. Dùng được trong production không?
Có, nhưng cần tách rõ "concept" và "code trong bài".
Concept neural network dùng production rất rộng rãi: classification, ranking, recommendation, NLP, computer vision, fraud detection, personalization. Điều kiện là có data đủ tốt, baseline để so sánh, metric rõ, serving stack đáng tin cậy và monitoring sau deploy.
Code NumPy tự viết trong bài không nên dùng cho production training. Nó phù hợp để học, debug và giải thích. Nếu muốn production training:
- Dùng PyTorch/TensorFlow/JAX hoặc framework tương đương.
- Dùng autograd thay vì tự viết gradient.
- Dùng data pipeline, checkpoint, experiment tracking và model registry.
- Dùng validation/test set, threshold tuning và model evaluation theo segment.
- Có CI/CD cho model artifact và rollback.
Trường hợp rất hẹp có thể dùng NumPy inference:
- Model nhỏ, frozen, không cần GPU.
- Latency và memory đã benchmark đạt SLA.
- Feature contract rất ổn định.
- Có monitoring, golden tests và rollback.
- Owner hiểu rõ giới hạn của implementation.
Nếu bạn đang build sản phẩm thật, best solution thường là dùng NumPy để học concept hôm nay, rồi chuyển sang PyTorch ở Day 10 cho implementation thực tế hơn.
16. Debugging checklist
Khi loss không giảm:
- Kiểm tra
X.shape,Y.shape,W1.shape,W2.shape. - Kiểm tra label có đúng shape
(batch_size, 1)không. - Kiểm tra learning rate có quá lớn hoặc quá nhỏ không.
- Log min/max của probability, xem model có saturate 0/1 không.
- Thử seed khác vì initialization có thể kẹt với model nhỏ.
- Thử
hidden_dimlớn hơn. - Thử activation khác.
- Kiểm tra gradient shape bằng assert.
- Kiểm tra loss có
nankhông.
Khi train đúng nhưng inference sai:
- Kiểm tra feature order.
- Kiểm tra dtype và preprocessing.
- Kiểm tra threshold.
- Kiểm tra model weights có load đúng version không.
- Kiểm tra train-serving skew.
17. Câu hỏi tự kiểm tra
- Vì sao nhiều linear layer không activation vẫn chỉ tương đương một linear layer?
- Sigmoid nên đặt ở hidden layer hay output layer trong bài toán binary classification? Vì sao?
- Vì sao BCE cần clipping khi tự tính bằng NumPy?
- Shape của
dW2là gì và vì sao? - Learning rate quá cao thể hiện như thế nào trên loss curve?
- Vì sao production training nên dùng framework autograd?
- Khi nào một MLP nhỏ có thể thua Logistic Regression hoặc XGBoost trong tabular data?
Bài tập
Mục tiêu thực hành
Sau phần này, bạn cần:
- Chạy được MLP 2-layer bằng NumPy, không dùng framework deep learning.
- Thấy loss giảm theo epoch.
- Predict đúng XOR dataset.
- Thay đổi
hidden_dim,learning_rate,activation,dtypevà quan sát trade-off. - Biết cách đọc shape contract và debug khi model không học.
Chuẩn bị môi trường
Yêu cầu tối thiểu:
python3 -m pip install numpy
Nếu muốn visualize loss:
python3 -m pip install matplotlib
Script chính:
python3 lessions/day-09-neural-network-tu-zero/xor_mlp_numpy.py --help
Nếu bạn đã active virtualenv và python trỏ đúng interpreter có NumPy, có thể thay python3 bằng python.
Bài 1: Chạy baseline XOR
Chạy:
python3 lessions/day-09-neural-network-tu-zero/xor_mlp_numpy.py \
--hidden-dim 4 \
--activation tanh \
--learning-rate 0.5 \
--epochs 8000 \
--seed 42 \
--assert-xor
Kỳ vọng:
final_lossnhỏ hơn0.05.accuracy=1.000.predictionskhớp vớiexpected.- Probability gần 0 cho
[0,0],[1,1]và gần 1 cho[0,1],[1,0].
Ví dụ output có thể khác nhẹ theo NumPy/runtime, nhưng pattern phải giống:
final_loss=0.0...
accuracy=1.000
predictions=
[[0]
[1]
[1]
[0]]
expected=
[[0]
[1]
[1]
[0]]
Bài 2: Visualize loss
Nếu đã cài matplotlib:
python3 lessions/day-09-neural-network-tu-zero/xor_mlp_numpy.py \
--hidden-dim 4 \
--activation tanh \
--learning-rate 0.5 \
--epochs 8000 \
--seed 42 \
--plot \
--plot-path /tmp/day09-xor-loss.png
Quan sát:
- Loss có giảm đều không?
- Có plateau không?
- Có đoạn dao động mạnh không?
- Nếu tăng learning rate, đường loss thay đổi thế nào?
Trong production, loss curve là observability tối thiểu của training. Nếu không log loss/metric, bạn không biết model đang học hay chỉ đang chạy.
Bài 3: So sánh hidden size
Chạy 3 cấu hình:
python3 lessions/day-09-neural-network-tu-zero/xor_mlp_numpy.py --hidden-dim 2 --activation tanh --learning-rate 0.5 --epochs 8000 --seed 42
python3 lessions/day-09-neural-network-tu-zero/xor_mlp_numpy.py --hidden-dim 4 --activation tanh --learning-rate 0.5 --epochs 8000 --seed 42
python3 lessions/day-09-neural-network-tu-zero/xor_mlp_numpy.py --hidden-dim 8 --activation tanh --learning-rate 0.5 --epochs 8000 --seed 42
Ghi lại:
| hidden_dim | final_loss | accuracy | Nhận xét |
|---|---|---|---|
| 2 | |||
| 4 | |||
| 8 |
Câu hỏi:
hidden_dim=2có luôn học được với seed này không?hidden_dim=8có giảm loss nhanh hơn không?- Model lớn hơn có luôn tốt hơn trong production không?
Gợi ý production: hidden size lớn hơn tăng capacity nhưng cũng tăng memory, latency, risk overfitting và cost.
Bài 4: So sánh learning rate
Chạy:
python3 lessions/day-09-neural-network-tu-zero/xor_mlp_numpy.py --learning-rate 0.01 --epochs 8000 --seed 42
python3 lessions/day-09-neural-network-tu-zero/xor_mlp_numpy.py --learning-rate 0.1 --epochs 8000 --seed 42
python3 lessions/day-09-neural-network-tu-zero/xor_mlp_numpy.py --learning-rate 0.5 --epochs 8000 --seed 42
python3 lessions/day-09-neural-network-tu-zero/xor_mlp_numpy.py --learning-rate 1.5 --epochs 8000 --seed 42
Ghi lại:
| learning_rate | final_loss | accuracy | Loss behavior |
|---|---|---|---|
| 0.01 | |||
| 0.1 | |||
| 0.5 | |||
| 1.5 |
Câu hỏi:
- Learning rate nào học chậm?
- Learning rate nào dễ dao động?
- Khi loss thành
nan, bạn sẽ debug theo thứ tự nào?
Best solution theo context: bắt đầu từ learning rate vừa phải, log loss, sau đó tune. Với framework thật, dùng optimizer như Adam và scheduler khi bài toán phức tạp hơn.
Bài 5: So sánh activation
Chạy:
python3 lessions/day-09-neural-network-tu-zero/xor_mlp_numpy.py --activation tanh --learning-rate 0.5 --epochs 8000 --seed 42
python3 lessions/day-09-neural-network-tu-zero/xor_mlp_numpy.py --activation relu --learning-rate 0.1 --epochs 8000 --seed 42
python3 lessions/day-09-neural-network-tu-zero/xor_mlp_numpy.py --activation sigmoid --learning-rate 0.5 --epochs 8000 --seed 42
python3 lessions/day-09-neural-network-tu-zero/xor_mlp_numpy.py --activation gelu --learning-rate 0.1 --epochs 8000 --seed 42
Ghi lại:
| activation | learning_rate | final_loss | accuracy | Nhận xét |
|---|---|---|---|---|
| tanh | 0.5 | |||
| relu | 0.1 | |||
| sigmoid | 0.5 | |||
| gelu | 0.1 |
Câu hỏi:
- Activation nào ổn định nhất cho XOR với seed này?
- Sigmoid hidden layer học chậm hơn không?
- ReLU có bị kẹt với một số seed không?
- GELU có đáng dùng cho bài toán nhỏ này không?
Trade-off: activation không có lựa chọn tốt tuyệt đối. ReLU nhanh và phổ biến; GELU hợp Transformer; Sigmoid hợp output probability; Tanh dễ minh họa bài học nhỏ.
Bài 6: Dtype và numerical stability
So sánh:
python3 lessions/day-09-neural-network-tu-zero/xor_mlp_numpy.py --dtype float32 --epochs 8000 --seed 42
python3 lessions/day-09-neural-network-tu-zero/xor_mlp_numpy.py --dtype float64 --epochs 8000 --seed 42
Ghi lại:
| dtype | final_loss | accuracy | Nhận xét |
|---|---|---|---|
| float32 | |||
| float64 |
Câu hỏi:
- Với XOR,
float64có cải thiện đáng kể không? - Vì sao deep learning production thường ưu tiên
float32,float16hoặcbfloat16hơnfloat64? - Vì sao BCE trong script phải clip probability trước khi gọi
log?
Bài 7: Thêm noise vào input
Chạy:
python3 lessions/day-09-neural-network-tu-zero/xor_mlp_numpy.py --noise-std 0.05 --epochs 8000 --seed 42
python3 lessions/day-09-neural-network-tu-zero/xor_mlp_numpy.py --noise-std 0.20 --epochs 8000 --seed 42
Quan sát:
- Probability có còn tự tin không?
- Prediction có còn đúng 4 điểm không?
- Loss có tăng không?
Production lesson: dữ liệu thật hiếm khi sạch như XOR. Nếu feature noisy hoặc distribution thay đổi, cần validation set, monitoring drift và retraining strategy.
Bài 8: Đọc shape contract trong code
Mở xor_mlp_numpy.py, tìm các dòng:
Z1 = X @ self.W1 + self.b1
A1 = activation_forward(self.config.activation, Z1)
Z2 = A1 @ self.W2 + self.b2
P = sigmoid(Z2)
Tự điền shape:
| Biến | Shape |
|---|---|
X | |
W1 | |
b1 | |
Z1 | |
A1 | |
W2 | |
b2 | |
P |
Sau đó đọc backward:
dZ2 = (P - Y) / batch_size
dW2 = cache["A1"].T @ dZ2
db2 = np.sum(dZ2, axis=0, keepdims=True)
dA1 = dZ2 @ self.W2.T
dZ1 = dA1 * activation_backward(...)
dW1 = cache["X"].T @ dZ1
db1 = np.sum(dZ1, axis=0, keepdims=True)
Tự điền shape:
| Gradient | Shape |
|---|---|
dZ2 | |
dW2 | |
db2 | |
dA1 | |
dZ1 | |
dW1 | |
db1 |
Rule kiểm tra nhanh: gradient của tham số nào phải có đúng shape của tham số đó.
Bài 9: Production review
Viết một đoạn 10-15 dòng trả lời:
Nếu team muốn dùng MLP cho một bài toán churn/fraud/ticket classification, tôi sẽ:
1. Bắt đầu bằng baseline nào?
2. Khi nào tôi mới chọn neural network?
3. Tôi có dùng code NumPy tự viết này không?
4. Nếu không, tôi dùng framework nào và vì sao?
5. Artifact cần version những gì?
6. Monitoring production gồm những metric nào?
7. Rollback/fallback ra sao?
Câu trả lời kỳ vọng:
- Baseline trước: Logistic Regression, tree-based model hoặc XGBoost cho tabular.
- Chọn neural network khi pattern phi tuyến phức tạp, data đủ và baseline không đạt.
- Không dùng NumPy tự viết cho production training.
- Dùng PyTorch/TensorFlow để có autograd, checkpoint, GPU, ecosystem.
- Version preprocessing, feature order, model architecture, weights, threshold, dataset và package.
- Monitor latency, error rate, prediction distribution, data quality, drift và delayed labels.
- Có fallback baseline hoặc model artifact trước đó.
Checklist hoàn thành
- Baseline XOR chạy thành công với
--assert-xor. - Tôi có loss curve hoặc log loss giảm theo epoch.
- Tôi đã so sánh ít nhất 3
hidden_dim. - Tôi đã so sánh ít nhất 3
learning_rate. - Tôi đã chạy ít nhất 2 activation khác nhau.
- Tôi giải thích được shape của forward và backward.
- Tôi giải thích được vì sao cần clipping cho Sigmoid/BCE.
- Tôi viết được production review ngắn.