Published on

Day 9: Neural Network từ Zero

Authors

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:

  1. Giải thích được neuron là weighted sum + activation.
  2. 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.
  3. Viết được forward pass của MLP 2-layer bằng NumPy với shape contract rõ ràng.
  4. Hiểu binary cross entropy loss và vì sao cần clipping khi tính log.
  5. Theo dõi được backpropagation từng bước từ output layer về hidden layer.
  6. Train được MLP 2-layer trên XOR dataset bằng gradient descent.
  7. 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ượngViệc cần làmOutput
15 phútĐọc TL;DR, mental model và shape contract trong file nàyNắ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à lossHiểu đường đi của data từ X đến y_hat
35 phútĐọc phần backpropagation và gradient descentTự suy ra được shape của từng gradient
45 phútChạy exercise.md với xor_mlp_numpy.pyTrain được XOR, thấy loss giảm và prediction đúng
10 phútLàm checklist và production reviewBiế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 NetworkCách nhìn của Senior SE
Input tensor XRequest payload hoặc batch record đã được chuẩn hóa
Weight WConfig học từ data, không viết tay
Bias bOffset/default học được
ActivationTransform/policy phi tuyến
Forward passRuntime request flow
LossObjective kỹ thuật để tối ưu
MetricAcceptance criteria theo use case
GradientTín hiệu feedback cho từng tham số
OptimizerUpdate strategy
Model artifactBuild 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ệuShapeÝ 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

Deliverable cuối ngày

Bạn nên có 3 output:

  1. Một lần chạy XOR thành công với loss giảm và prediction đúng 4 điểm.
  2. Một bảng so sánh ngắn giữa hidden_dim, learning_rateactivation.
  3. 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 + ba = 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 @ dZ2dW1 = 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:

x1x2y
000
011
101
110

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ệuShapeGhi 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 x quá â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ếuKhi dùng
SigmoidOutput 0-1, hợp binary probabilityVanishing gradient, dễ overflow nếu không clipOutput layer binary
TanhCenter quanh 0, dễ học XORVẫn vanishing gradientHidden layer nhỏ, bài học từ zero
ReLUNhanh, gradient tốt vùng dươngDead neuronDefault hidden layer MLP/CNN
GELUMượt, mạnh trong TransformerTốn compute hơn ReLUTransformer, 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à:

  1. Hidden layer: linear transform + activation.
  2. 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ếnShape với XORShape 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 khi p thấp.
  • Nếu y = 0, model bị phạt khi p cao.
  • 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 khi log.

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ố đó:

GradientCông thứcShape
dZ2(P - Y) / m(batch_size, output_dim)
dW2A1.T @ dZ2(hidden_dim, output_dim)
db2sum(dZ2, axis=0, keepdims=True)(1, output_dim)
dA1dZ2 @ W2.T(batch_size, hidden_dim)
dZ1dA1 * activation_grad(...)(batch_size, hidden_dim)
dW1X.T @ dZ1(input_dim, hidden_dim)
db1sum(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ểmNhược điểmKhi dùng
float64Chính xác hơn, tốt để học/debug numericTốn RAM gấp đôi, thường chậm hơnNotebook học toán, gradient check
float32Gần default deep learning, nhanh và tiết kiệm RAM hơnÍt precision hơnTraining/inference thông thường

Script hỗ trợ --dtype float32--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ọnNên dùng khiKhông nên dùng khiProduction note
NumPy tự viếtHọc concept, kiểm chứng shape/gradientTraining thật, model lớnDễ sai gradient, không có autograd/GPU ecosystem
PyTorch/TensorFlowDeep learning production, GPU, checkpointBài toán classical ML đơn giảnDefault cho neural network thật
Sigmoid outputBinary probabilityHidden layer sâuNên dùng logits loss trong framework
Tanh hiddenModel nhỏ, cần output centeredNetwork sâu dễ vanishing gradientHợp bài XOR
ReLU hiddenDefault MLP/CNNRisk dead neuron, output probabilityNhanh, đơn giản
GELU hiddenTransformer/model lớnMLP nhỏ cần latency thấpMượt nhưng tốn compute
Full batchDataset rất nhỏDataset lớnỔn định nhưng tốn RAM
Mini-batchDataset vừa/lớnBatch quá nhỏ gây noisyDefault training thực tế
float32Training/inference thông thườngCần debug precision caoTiết kiệm RAM và nhanh hơn
float64Học toán, gradient checkProduction latency/memory chặtChí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 @ W nhanh 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.exp overflow 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.
  • --clip-grad-norm optional để 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_dim lớn hơn.
  • Thử activation khác.
  • Kiểm tra gradient shape bằng assert.
  • Kiểm tra loss có nan khô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

  1. Vì sao nhiều linear layer không activation vẫn chỉ tương đương một linear layer?
  2. Sigmoid nên đặt ở hidden layer hay output layer trong bài toán binary classification? Vì sao?
  3. Vì sao BCE cần clipping khi tự tính bằng NumPy?
  4. Shape của dW2 là gì và vì sao?
  5. Learning rate quá cao thể hiện như thế nào trên loss curve?
  6. Vì sao production training nên dùng framework autograd?
  7. 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, dtype và 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_loss nhỏ hơn 0.05.
  • accuracy=1.000.
  • predictions khớp với expected.
  • 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_dimfinal_lossaccuracyNhận xét
2
4
8

Câu hỏi:

  1. hidden_dim=2 có luôn học được với seed này không?
  2. hidden_dim=8 có giảm loss nhanh hơn không?
  3. 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_ratefinal_lossaccuracyLoss behavior
0.01
0.1
0.5
1.5

Câu hỏi:

  1. Learning rate nào học chậm?
  2. Learning rate nào dễ dao động?
  3. 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:

activationlearning_ratefinal_lossaccuracyNhận xét
tanh0.5
relu0.1
sigmoid0.5
gelu0.1

Câu hỏi:

  1. Activation nào ổn định nhất cho XOR với seed này?
  2. Sigmoid hidden layer học chậm hơn không?
  3. ReLU có bị kẹt với một số seed không?
  4. 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:

dtypefinal_lossaccuracyNhận xét
float32
float64

Câu hỏi:

  1. Với XOR, float64 có cải thiện đáng kể không?
  2. Vì sao deep learning production thường ưu tiên float32, float16 hoặc bfloat16 hơn float64?
  3. 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ếnShape
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:

GradientShape
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.