Session 09🧪

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.

Kiểm tra môi trường & load dữ liệuPython
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).

Forward, loss & backward pass — NumPyPython
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.

Sequential, Functional & SubclassingPython
# 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.

model.fit() vs Custom Training LoopPython
# 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.

TensorBoard callbackPython
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