Thực Hành Neural Network
Lab thực hành: xây dựng Neural Network từ đầu bằng NumPy (backprop thủ công) và với TensorFlow/Keras (Sequential, Functional, Subclassing).
Learning Objectives
- ✓Chuẩn bị môi trường, load và trực quan hóa dữ liệu (make_moons, MNIST)
- ✓Implement forward pass, loss và backpropagation thủ công bằng NumPy
- ✓Thành thạo 3 cách xây dựng model trong Keras: Sequential, Functional, Subclassing
- ✓Nắm 2 phương pháp huấn luyện: model.fit() và Custom Training Loop với GradientTape
- ✓Sử dụng TensorBoard để theo dõi quá trình huấn luyện
Chuẩn Bị Môi Trường & Dữ Liệu
Trước khi cài đặt mô hình, cần kiểm tra phiên bản TensorFlow/Keras và GPU khả dụng, sau đó chuẩn bị hai bộ dữ liệu thực hành: make_moons (bài toán phân loại nhị phân phi tuyến, dễ trực quan hóa trên mặt phẳng 2D) và MNIST (phân loại 10 chữ số viết tay, dùng cho bài toán multi-class ở phần sau). Dữ liệu cần được chuẩn hóa — StandardScaler cho make_moons, chia pixel về [0,1] cho MNIST — trước khi đưa vào mô hình.
import tensorflow as tf
from tensorflow import keras
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
print("TensorFlow:", tf.__version__)
print("GPU available:", tf.config.list_physical_devices("GPU"))
X, y = make_moons(n_samples=1000, noise=0.2, random_state=42)
X = StandardScaler().fit_transform(X)
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)NumPy Scratch — Backpropagation Thủ Công
Tự cài đặt một mạng nơ-ron từ đầu bằng NumPy — không dùng framework — là cách tốt nhất để hiểu rõ từng bước toán học phía sau backpropagation: khởi tạo tham số (He initialization cho ReLU), forward pass qua các lớp ReLU và lớp output Sigmoid, tính binary cross-entropy loss, rồi lan truyền ngược để tính gradient cho từng W và b trước khi cập nhật bằng gradient descent. Mỗi dòng code khớp với một phương trình BP (BP1–BP4 ở bài 8).
def init_params(layer_dims):
params = {}
for l in range(1, len(layer_dims)):
params[f"W{l}"] = np.random.randn(layer_dims[l], layer_dims[l-1]) * np.sqrt(2 / layer_dims[l-1])
params[f"b{l}"] = np.zeros((layer_dims[l], 1))
return params
def sigmoid(z): return 1 / (1 + np.exp(-z))
def relu(z): return np.maximum(0, z)
def forward(X, params):
cache, A, L = [], X, len(params) // 2
for l in range(1, L):
W, b = params[f"W{l}"], params[f"b{l}"]
Z = W @ A + b
cache.append((A, W, b, Z))
A = relu(Z)
W, b = params[f"W{L}"], params[f"b{L}"]
Z = W @ A + b
A_out = sigmoid(Z)
cache.append((A, W, b, Z))
return A_out, cache
def compute_loss(A_out, y, eps=1e-8):
m = y.shape[1]
return -(1 / m) * np.sum(y * np.log(A_out + eps) + (1 - y) * np.log(1 - A_out + eps))Ba Cách Xây Dựng Model Trong Keras
Keras cung cấp ba cách xây dựng model với độ linh hoạt tăng dần. Sequential API xếp các lớp theo một chuỗi thẳng, phù hợp cho kiến trúc đơn giản. Functional API cho phép định nghĩa đồ thị tính toán tùy ý (nhiều đầu vào/đầu ra, nhánh rẽ, kết nối tắt), phù hợp cho kiến trúc phức tạp hơn. Subclassing cho phép tự định nghĩa toàn bộ forward pass bên trong phương thức call(), linh hoạt tối đa nhưng cũng khó debug và tối ưu hóa nhất.
# 1. Sequential
model_seq = keras.Sequential([
keras.layers.Dense(16, activation='relu', input_shape=(2,)),
keras.layers.Dense(1, activation='sigmoid'),
])
# 2. Functional API
inputs = keras.Input(shape=(2,))
x = keras.layers.Dense(16, activation='relu')(inputs)
outputs = keras.layers.Dense(1, activation='sigmoid')(x)
model_func = keras.Model(inputs, outputs)
# 3. Subclassing
class MyModel(keras.Model):
def __init__(self):
super().__init__()
self.dense1 = keras.layers.Dense(16, activation='relu')
self.dense2 = keras.layers.Dense(1, activation='sigmoid')
def call(self, x):
return self.dense2(self.dense1(x))
model_sub = MyModel()Hai Phương Pháp Huấn Luyện
Cách đơn giản nhất là gọi model.fit(), Keras tự động lo toàn bộ vòng lặp huấn luyện: forward, tính loss, backward, cập nhật trọng số. Khi cần kiểm soát chi tiết hơn — ví dụ huấn luyện GAN hoặc áp dụng logic tùy chỉnh — dùng Custom Training Loop với tf.GradientTape để tự ghi lại và tính gradient theo từng bước.
# Cách 1: model.fit()
model_seq.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
model_seq.fit(X_train, y_train, validation_data=(X_val, y_val), epochs=20)
# Cách 2: Custom training loop với GradientTape
optimizer = keras.optimizers.Adam()
loss_fn = keras.losses.BinaryCrossentropy()
for epoch in range(20):
with tf.GradientTape() as tape:
y_pred = model_func(X_train, training=True)
loss = loss_fn(y_train, y_pred)
grads = tape.gradient(loss, model_func.trainable_variables)
optimizer.apply_gradients(zip(grads, model_func.trainable_variables))TensorBoard & Thực Nghiệm Hyperparameter
TensorBoard trực quan hóa loss, accuracy và cấu trúc mô hình theo thời gian thực, giúp so sánh trực tiếp các lần thực nghiệm với hyperparameter khác nhau — số lớp, số neuron, learning rate, batch size. Đây là bước cuối cùng để đánh giá và lựa chọn cấu hình mô hình tốt nhất trước khi áp dụng lên bài toán MNIST đa lớp (multi-class) với 10 nhãn đầu ra và hàm mất mát categorical cross-entropy.
import time
log_dir = "logs/fit/" + str(int(time.time()))
tb_callback = keras.callbacks.TensorBoard(log_dir=log_dir, histogram_freq=1)
model_seq.fit(
X_train, y_train,
validation_data=(X_val, y_val),
epochs=20,
callbacks=[tb_callback],
)
# Chạy: tensorboard --logdir logs/fit