Hướng dẫn chi tiết từng bước tối ưu hoá hyperparameters cho ML models — từ lý thuyết, tính toán thủ công đến thực hành với Python
Sau bài học này, bạn sẽ:
Bạn hãy mô tả sự khác biệt giữa parameter và hyperparameter, và giải thích tại sao việc tuning hyperparameter lại quan trọng trong Machine Learning?
Parameter (tham số mô hình) là các giá trị mà model tự học được từ dữ liệu trong quá trình training.
Ví dụ: Trong Linear Regression , các giá trị , , là parameters — model tự tìm ra chúng bằng cách tối ưu hàm loss.
Hyperparameter (siêu tham số) là các giá trị mà chúng ta phải thiết lập trước khi bắt đầu quá trình training. Model không tự học được các giá trị này.
Ví dụ:
max_depth=5trong Decision Tree,learning_rate=0.01trong Gradient Boosting — đây là những quyết định chúng ta phải đưa ra trước.
| Tiêu chí | Parameter | Hyperparameter |
|---|---|---|
| Ai thiết lập? | Model tự học | Con người thiết lập trước |
| Khi nào xác định? | Trong quá trình training | Trước khi training |
| Ví dụ | Weights, biases | Learning rate, max_depth |
| Thay đổi bằng cách nào? | Gradient descent | Grid search, random search, Bayesian |
| Lưu trữ ở đâu? | Trong model đã train | Trong config/script |
| Ảnh hưởng đến? | Dự đoán trực tiếp |
| Model | Hyperparameters chính | Ý nghĩa |
|---|---|---|
| Decision Tree | max_depth, min_samples_split, criterion | Kiểm soát độ sâu và điều kiện chia nhánh |
| Random Forest | n_estimators, max_depth, max_features | Số cây, độ sâu tối đa, số features mỗi lần chia |
| SVM | C, kernel, gamma | Mức penalty, loại kernel, phạm vi ảnh hưởng |
| XGBoost | n_estimators, , , |
Vấn đề: Cùng một model, cùng dữ liệu — nhưng với hyperparameters khác nhau, kết quả accuracy có thể dao động từ 60% đến 95%.
Khi nào cần tuning?
Luôn bắt đầu với baseline model (hyperparameters mặc định) trước. Chỉ tuning khi baseline đã cho kết quả hợp lý và bạn muốn cải thiện thêm.
Grid Search thử tất cả các tổ hợp có thể của hyperparameters trong một lưới (grid) đã định nghĩa trước.
Khi nào sử dụng Grid Search?
Khi nào KHÔNG nên dùng?
Giả sử chúng ta có model Decision Tree với 2 hyperparameters:
max_depth: [3, 5]min_samples_split: [2, 5]Bước 1: Liệt kê tất cả tổ hợp
Random Search không thử tất cả tổ hợp mà lấy mẫu ngẫu nhiên từ phân phối xác suất của mỗi hyperparameter.
Khi nào sử dụng Random Search?
Tại sao Random Search thường tốt hơn Grid Search?
Theo nghiên cứu của Bergstra & Bengio (2012):
Khi chỉ một vài hyperparameters thực sự quan trọng, Random Search khám phá nhiều giá trị khác nhau cho các hyperparameters quan trọng đó hơn so với Grid Search.
Giả sử XGBoost với 2 hyperparameters, mỗi cái lấy ngẫu nhiên:
max_depth ∈ [3, 10] (uniform integer)learning_rate ∈ [0.01, 0.3] (uniform float)Chọn n_iter=5 (chỉ thử 5 tổ hợp ngẫu nhiên):
| Trial # | max_depth (ngẫu nhiên) | learning_rate (ngẫu nhiên) |
|---|
Bayesian Optimization là phương pháp tìm kiếm có hướng dẫn (guided search) — nó học từ các kết quả trước để quyết định thử tổ hợp nào tiếp theo.
Công thức cốt lõi dựa trên Surrogate Model (mô hình đại diện):
Optuna là framework hyperparameter optimization hiện đại nhất (2019, Preferred Networks), được sử dụng rộng rãi trong cả research và production.
| Tính năng | Grid Search | Random Search | Bayesian (skopt) | Optuna |
|---|---|---|---|---|
| Pruning (dừng sớm trial kém) | ❌ | ❌ | ❌ | ✅ |
| Dashboard trực quan | ❌ | ❌ | ❌ | ✅ |
| Hỗ trợ distributed | ❌ | ❌ |
| Tiêu chí | Grid Search | Random Search | Bayesian Optimization | Optuna (TPE) |
|---|---|---|---|---|
| Ý tưởng | Thử tất cả tổ hợp | Lấy mẫu ngẫu nhiên | Học từ kết quả trước | TPE + Pruning |
| Hiệu quả | ⭐ | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| Tốc độ | Chậm nhất | Nhanh | Trung bình | Nhanh (nhờ pruning) |
Vấn đề: Nếu chỉ dùng 1 lần train/test split, kết quả có thể phụ thuộc vào cách chia dữ liệu → không đáng tin cậy.
Cross-Validation giải quyết bằng cách chia dữ liệu thành phần, luân phiên dùng mỗi phần làm validation.
Dữ liệu: 9 mẫu, chia thành 3 fold, mỗi fold 3 mẫu:
| Fold | Train set | Validation set | Accuracy |
|---|---|---|---|
| 1 | 9 | 3 | 0.85 |
| 2 | 9 | 6 | 0.82 |
Vấn đề: Neural Networks có rất nhiều hyperparameters — kiến trúc mạng (layers, neurons), training (learning rate, batch size), regularization (dropout). Tuning thủ công rất khó.
Keras Tuner là framework chuyên dụng cho Neural Network tuning, tích hợp với TensorFlow/Keras.
| Nhóm | Hyperparameter | Range phổ biến | Ý nghĩa |
|---|---|---|---|
| Kiến trúc | Số hidden layers | 1–6 | Độ phức tạp model |
| Neurons/layer | 16–512 | Dung lượng biểu diễn | |
| Activation function |
| # | Quy tắc | Chi tiết |
|---|---|---|
| 1 | Bắt đầu từ baseline | Chạy model với default parameters trước, ghi nhận accuracy baseline |
| 2 | Random Search trước Grid Search | Random Search hiệu quả hơn cho hầu hết trường hợp (Bergstra & Bengio, 2012) |
| 3 | Dùng log-scale cho learning rate | Giá trị nhỏ (0.001-0.01) thường quan trọng hơn giá trị lớn (0.1-1.0) |
| 4 | Luôn dùng Cross-Validation | Đừng bao giờ tuning trên 1 train/test split duy nhất |
| 5 |
Mục tiêu: Áp dụng tất cả 4 phương pháp đã học → so sánh kết quả và thời gian.
1# ====================================2# SETUP: Chuẩn bị dữ liệu3# ====================================4import numpy as np5import pandas as pd6import time7from sklearn.model_selection import (8 train_test_split, GridSearchCV,9 RandomizedSearchCV, StratifiedKFold, cross_val_score10)11from sklearn.ensemble import RandomForestClassifier12from sklearn.preprocessing import StandardScaler, LabelEncoder13from scipy.stats import randint, uniform14import optuna1516# Load Titanic dataset17url = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"18df = pd.read_csv(url) # Đọc CSV từ URL1920# Tiền xử lý cơ bản21df['Age'].fillna(df['Age'].median(), inplace=True) # Điền tuổi NaN bằng median22df['Embarked'].fillna('S', inplace=True) # Điền cảng lên tàu23df['Sex'] = LabelEncoder().fit_transform(df['Sex']) # Male=1, Female=024df['Embarked'] = LabelEncoder().fit_transform(df['Embarked']) # Mã hoá cảng25df.drop(['Name', 'Ticket', 'Cabin', 'PassengerId'],26 axis=1, inplace=True) # Bỏ cột không cần2728X = df.drop('Survived', axis=1) # Features29y = df['Survived'] # Target30X_train, X_test, y_train, y_test = train_test_split(31 X, y, test_size=0.2, random_state=42, stratify=y32)3334# ====================================35# 1. GRID SEARCH36# ====================================37print("=" * 50)38print("1. GRID SEARCH")39start = time.time()4041grid_params = {42 'n_estimators': [100, 200, 300],43 'max_depth': [5, 10, 15],44 'min_samples_split': [2, 5],45}46# Tổng: 3 × 3 × 2 = 18 tổ hợp × 5 folds = 90 lần train4748grid = GridSearchCV(49 RandomForestClassifier(random_state=42),50 grid_params, cv=5, scoring='accuracy', n_jobs=-151)52grid.fit(X_train, y_train)53grid_time = time.time() - start5455print(f"Best accuracy (CV): {grid.best_score_:.4f}")56print(f"Best params: {grid.best_params_}")57print(f"Test accuracy: {grid.score(X_test, y_test):.4f}")58print(f"Thời gian: {grid_time:.2f}s")5960# ====================================61# 2. RANDOM SEARCH62# ====================================63print("\n" + "=" * 50)64print("2. RANDOM SEARCH")65start = time.time()6667random_params = {68 'n_estimators': randint(50, 500),69 'max_depth': randint(3, 20),70 'min_samples_split': randint(2, 20),71 'max_features': ['sqrt', 'log2', None],72}7374random = RandomizedSearchCV(75 RandomForestClassifier(random_state=42),76 random_params, n_iter=50, cv=5, scoring='accuracy',77 n_jobs=-1, random_state=4278)79random.fit(X_train, y_train)80random_time = time.time() - start8182print(f"Best accuracy (CV): {random.best_score_:.4f}")83print(f"Best params: {random.best_params_}")84print(f"Test accuracy: {random.score(X_test, y_test):.4f}")85print(f"Thời gian: {random_time:.2f}s")8687# ====================================88# 3. OPTUNA89# ====================================90print("\n" + "=" * 50)91print("3. OPTUNA")92start = time.time()9394def optuna_objective(trial):95 params = {96 'n_estimators': trial.suggest_int('n_estimators', 50, 500),97 'max_depth': trial.suggest_int('max_depth', 3, 20),98 'min_samples_split': trial.suggest_int('min_samples_split', 2, 20),99 'max_features': trial.suggest_categorical(100 'max_features', ['sqrt', 'log2', None]101 ),102 }103 model = RandomForestClassifier(**params, random_state=42)104 scores = cross_val_score(model, X_train, y_train, cv=5, scoring='accuracy')105 return scores.mean()106107study = optuna.create_study(direction='maximize')108study.optimize(optuna_objective, n_trials=50, show_progress_bar=True)109optuna_time = time.time() - start110111print(f"Best accuracy (CV): {study.best_value:.4f}")112print(f"Best params: {study.best_params}")113optuna_model = RandomForestClassifier(**study.best_params, random_state=42)114optuna_model.fit(X_train, y_train)115print(f"Test accuracy: {optuna_model.score(X_test, y_test):.4f}")116print(f"Thời gian: {optuna_time:.2f}s")117118# ====================================119# BẢNG SO SÁNH KẾT QUẢ120# ====================================121print("\n" + "=" * 50)122print("BẢNG SO SÁNH")123print(f"{'Phương pháp':<20} {'CV Accuracy':<15} {'Test Accuracy':<15} {'Thời gian':<10}")124print("-" * 60)125print(f"{'Grid Search':<20} {grid.best_score_:<15.4f} {grid.score(X_test, y_test):<15.4f} {grid_time:<10.2f}s")126print(f"{'Random Search':<20} {random.best_score_:<15.4f} {random.score(X_test, y_test):<15.4f} {random_time:<10.2f}s")127print(f"{'Optuna':<20} {study.best_value:<15.4f} {optuna_model.score(X_test, y_test):<15.4f} {optuna_time:<10.2f}s")| Thư viện | Link | Mô tả |
|---|---|---|
| Optuna | optuna.readthedocs.io | Framework HP optimization hiện đại nhất |
| Scikit-learn Model Selection | scikit-learn.org/stable/model_selection.html | GridSearchCV, RandomizedSearchCV, Cross-Validation |
| Keras Tuner | keras.io/keras_tuner | Tuning cho Neural Networks |
| scikit-optimize | scikit-optimize.github.io |
| Tình huống | Giải pháp thay thế |
|---|---|
| Dữ liệu quá ít (dưới 1000 mẫu) | Tập trung thu thập thêm dữ liệu |
| Features chưa tốt | Đầu tư vào feature engineering trước |
| Baseline đã đạt yêu cầu business | Ship model, tuning sau nếu cần |
| Model đơn giản (Logistic Regression) | Default parameters thường đủ tốt |
11. Baseline (default params) → Ghi nhận score22. Feature engineering → Thường cải thiện nhiều hơn tuning33. Random Search (50-100 trials) → Tìm vùng tốt44. Optuna fine-tuning (narrow range) → Tinh chỉnh55. Nested CV → Report unbiased score66. A/B testing → Validate trên production traffic| Nguồn | Link | Ghi chú |
|---|---|---|
| MLflow Documentation | https://mlflow.org/docs/latest/index.html | Tài liệu MLflow cho MLOps |
| Papers With Code | https://paperswithcode.com/ | State-of-the-art ML với code |
| Google ML Best Practices | https://developers.google.com/machine-learning/guides | Best practices từ Google |
| Made With ML | https://madewithml.com/ | MLOps và production ML |
| Surprise Library |
| Cách model học |
max_depthlearning_ratesubsample| Số vòng boosting, độ sâu cây, tốc độ học, tỉ lệ sample |
| Neural Network | layers, neurons, activation, batch_size, epochs | Kiến trúc mạng và quá trình huấn luyện |
| Tổ hợp # | max_depth | min_samples_split |
|---|---|---|
| 1 | 3 | 2 |
| 2 | 3 | 5 |
| 3 | 5 | 2 |
| 4 | 5 | 5 |
Bước 2: Train model với từng tổ hợp, đánh giá bằng cross-validation (CV=3)
| Tổ hợp # | max_depth | min_samples_split | CV Fold 1 | CV Fold 2 | CV Fold 3 | Mean Accuracy |
|---|---|---|---|---|---|---|
| 1 | 3 | 2 | 0.82 | 0.80 | 0.83 | 0.817 |
| 2 | 3 | 5 | 0.81 | 0.79 | 0.82 | 0.807 |
| 3 | 5 | 2 | 0.88 | 0.86 | 0.87 | 0.870 |
| 4 | 5 | 5 | 0.85 | 0.84 | 0.86 | 0.850 |
Bước 3: Chọn tổ hợp có Mean Accuracy cao nhất
Bây giờ mở rộng lên 3 hyperparameters cho Random Forest:
n_estimators: [100, 200, 300]max_depth: [5, 10, 15]max_features: ['sqrt', 'log2']Với CV=5, tổng số lần train model:
| # | n_estimators | max_depth | max_features | Mean CV Accuracy |
|---|---|---|---|---|
| 1 | 100 | 5 | sqrt | 0.823 |
| 2 | 100 | 5 | log2 | 0.819 |
| 3 | 100 | 10 | sqrt | 0.856 |
| 4 | 100 | 10 | log2 | 0.851 |
| 5 | 100 | 15 | sqrt | 0.862 |
| 6 | 100 | 15 | log2 | 0.858 |
| 7 | 200 | 5 | sqrt | 0.831 |
| 8 | 200 | 5 | log2 | 0.827 |
| 9 | 200 | 10 | sqrt | 0.867 |
| 10 | 200 | 10 | log2 | 0.863 |
| 11 | 200 | 15 | sqrt | 0.874 |
| 12 | 200 | 15 | log2 | 0.869 |
| 13 | 300 | 5 | sqrt | 0.833 |
| 14 | 300 | 5 | log2 | 0.829 |
| 15 | 300 | 10 | sqrt | 0.869 |
| 16 | 300 | 10 | log2 | 0.864 |
| 17 | 300 | 15 | sqrt | 0.876 |
| 18 | 300 | 15 | log2 | 0.871 |
Kết luận: Tổ hợp #17 cho kết quả tốt nhất — nhưng mất 90 lần train để tìm ra!
Nếu thêm 1 hyperparameter nữa với 4 giá trị, tổng sẽ là tổ hợp, tức lần train. Đây là lý do Grid Search trở nên không thực tế khi số hyperparameters tăng lên.
Bây giờ chúng ta sẽ code lại chính xác ví dụ tính toán thủ công ở trên bằng Python:
1# Bước 1: Import các thư viện cần thiết2from sklearn.model_selection import GridSearchCV # Module tìm kiếm Grid Search3from sklearn.ensemble import RandomForestClassifier # Model Random Forest4from sklearn.datasets import load_iris # Dataset mẫu để thực hành5from sklearn.model_selection import train_test_split # Chia train/test67# Bước 2: Chuẩn bị dữ liệu8X, y = load_iris(return_X_y=True) # Load dataset Iris (150 mẫu, 4 features)9X_train, X_test, y_train, y_test = train_test_split(10 X, y,11 test_size=0.2, # 20% dữ liệu dành cho test12 random_state=42, # Seed để kết quả reproducible13 stratify=y # Đảm bảo tỷ lệ class cân bằng trong train/test14)1516# Bước 3: Định nghĩa lưới hyperparameters (giống Ví dụ 2)17param_grid = {18 'n_estimators': [100, 200, 300], # Số cây trong rừng19 'max_depth': [5, 10, 15], # Độ sâu tối đa mỗi cây20 'max_features': ['sqrt', 'log2'] # Số features xét khi chia nhánh21}22# Tổng: 3 × 3 × 2 = 18 tổ hợp2324# Bước 4: Khởi tạo model và Grid Search25rf = RandomForestClassifier(random_state=42) # Model cơ bản26grid_search = GridSearchCV(27 estimator=rf, # Model cần tuning28 param_grid=param_grid, # Lưới hyperparameters đã định nghĩa29 cv=5, # 5-Fold Cross-Validation30 scoring='accuracy', # Metric đánh giá: accuracy31 n_jobs=-1, # Dùng tất cả CPU cores để tăng tốc32 verbose=1, # In tiến trình (1=ít, 2=chi tiết)33 return_train_score=True # Lưu cả train score để phát hiện overfitting34)3536# Bước 5: Chạy Grid Search37grid_search.fit(X_train, y_train)38# Scikit-learn sẽ thử tất cả 18 tổ hợp × 5 folds = 90 lần train3940# Bước 6: Xem kết quả41print(f"Tổ hợp tốt nhất: {grid_search.best_params_}")42print(f"Accuracy trung bình (CV): {grid_search.best_score_:.4f}")4344# Bước 7: Đánh giá trên test set45test_accuracy = grid_search.score(X_test, y_test)46print(f"Accuracy trên test set: {test_accuracy:.4f}")Giải thích kết quả: Code trên thực hiện chính xác quy trình mà chúng ta đã tính tay ở Ví dụ 2 — thử tất cả 18 tổ hợp, mỗi tổ hợp đánh giá bằng 5-fold CV, rồi chọn tổ hợp tốt nhất.
| Mean CV Accuracy |
|---|
| 1 | 7 | 0.15 | 0.871 |
| 2 | 4 | 0.08 | 0.845 |
| 3 | 9 | 0.22 | 0.858 |
| 4 | 6 | 0.05 | 0.863 |
| 5 | 8 | 0.12 | 0.878 |
So sánh: Grid Search cần tổ hợp (nếu chia đều mỗi cái 8 và 30 giá trị) → Random Search chỉ cần 5 tổ hợp và vẫn tìm được kết quả tốt!
Mở rộng thành 4 hyperparameters cho XGBoost:
n_estimators ∈ [100, 500]max_depth ∈ [3, 15]learning_rate ∈ [0.01, 0.3]subsample ∈ [0.6, 1.0]Chọn n_iter=8:
| Trial | n_estimators | max_depth | learning_rate | subsample | Mean CV Acc |
|---|---|---|---|---|---|
| 1 | 342 | 8 | 0.07 | 0.85 | 0.881 |
| 2 | 158 | 12 | 0.21 | 0.72 | 0.862 |
| 3 | 467 | 6 | 0.03 | 0.91 | 0.876 |
| 4 | 231 | 10 | 0.15 | 0.68 | 0.870 |
| 5 | 389 | 5 | 0.09 | 0.95 | 0.883 |
| 6 | 275 | 14 | 0.18 | 0.77 | 0.859 |
| 7 | 412 | 7 | 0.05 | 0.88 | 0.889 |
| 8 | 195 | 9 | 0.11 | 0.82 | 0.874 |
Kết luận: Chỉ với 8 trials (thay vì hàng nghìn tổ hợp Grid Search), Random Search tìm được tổ hợp #7 cho accuracy 0.889.
1# Bước 1: Import các thư viện cần thiết2from sklearn.model_selection import RandomizedSearchCV # Module Random Search3from scipy.stats import randint, uniform # Phân phối xác suất cho sampling4from xgboost import XGBClassifier # Model XGBoost56# Bước 2: Định nghĩa phân phối cho mỗi hyperparameter7# (Giống phạm vi trong Ví dụ 2 ở trên)8param_distributions = {9 'n_estimators': randint(100, 500), # Số nguyên ngẫu nhiên trong [100, 500)10 'max_depth': randint(3, 15), # Số nguyên ngẫu nhiên trong [3, 15)11 'learning_rate': uniform(0.01, 0.29), # Số thực ngẫu nhiên trong [0.01, 0.30]12 'subsample': uniform(0.6, 0.4), # Số thực ngẫu nhiên trong [0.6, 1.0]13}1415# Bước 3: Khởi tạo model và Random Search16xgb = XGBClassifier(17 random_state=42,18 eval_metric='logloss' # Metric nội bộ để XGBoost không warning19)2021random_search = RandomizedSearchCV(22 estimator=xgb, # Model cần tuning23 param_distributions=param_distributions, # Phân phối hyperparameters24 n_iter=50, # Thử 50 tổ hợp ngẫu nhiên25 cv=5, # 5-Fold Cross-Validation26 scoring='accuracy', # Metric đánh giá27 n_jobs=-1, # Dùng tất cả CPU28 random_state=42, # Seed ngẫu nhiên cho reproducibility29 verbose=1 # In tiến trình30)3132# Bước 4: Chạy Random Search33random_search.fit(X_train, y_train)3435# Bước 5: Kết quả36print(f"Tổ hợp tốt nhất: {random_search.best_params_}")37print(f"Accuracy (CV): {random_search.best_score_:.4f}")38print(f"Accuracy (Test): {random_search.score(X_test, y_test):.4f}")Giải thích: Thay vì param_grid (danh sách cố định), ta dùng param_distributions (phân phối xác suất). randint(100, 500) nghĩa là lấy ngẫu nhiên số nguyên từ 100 đến 499, uniform(0.01, 0.29) nghĩa là lấy ngẫu nhiên số thực từ 0.01 đến 0.30.
Trong đó:
Thuật toán chọn điểm có cao nhất để thử tiếp.
Khi nào sử dụng Bayesian Optimization?
Giả sử tuning 1 hyperparameter: learning_rate ∈ [0.01, 0.30]
Vòng 1 — Thử ngẫu nhiên 2 điểm ban đầu:
| Trial | learning_rate | Accuracy |
|---|---|---|
| 1 | 0.05 | 0.83 |
| 2 | 0.25 | 0.78 |
Vòng 2 — Surrogate model dự đoán: vùng gần 0.05 có vẻ tốt hơn → thử 0.10:
| Trial | learning_rate | Accuracy |
|---|---|---|
| 3 | 0.10 | 0.87 |
Vòng 3 — Model cập nhật, dự đoán: vùng 0.07–0.12 tiềm năng nhất → thử 0.08:
| Trial | learning_rate | Accuracy |
|---|---|---|
| 4 | 0.08 | 0.89 |
Vòng 4 — Expected Improvement gần 0.08 vẫn cao → thử 0.09:
| Trial | learning_rate | Accuracy |
|---|---|---|
| 5 | 0.09 | 0.88 |
Kết luận: Chỉ với 5 trials, Bayesian Optimization "hội tụ" vào vùng tối ưu . So sánh: Grid Search (chia 30 điểm) cần 30 trials, Random Search cần ~15-20 trials để tìm vùng tương tự.
Tuning 2 hyperparameters: learning_rate ∈ [0.01, 0.3] và max_depth ∈ [3, 15]:
| Trial | learning_rate | max_depth | Accuracy | Ghi chú |
|---|---|---|---|---|
| 1 | 0.10 | 8 | 0.85 | Khởi tạo ngẫu nhiên |
| 2 | 0.25 | 4 | 0.79 | Khởi tạo ngẫu nhiên |
| 3 | 0.05 | 12 | 0.82 | Khởi tạo ngẫu nhiên |
| 4 | 0.08 | 10 | 0.88 | Surrogate → vùng (lr thấp, depth vừa) tiềm năng |
| 5 | 0.06 | 9 | 0.87 | Khám phá lân cận trial #4 |
| 6 | 0.09 | 11 | 0.90 | Hội tụ vào vùng tốt nhất |
| 7 | 0.07 | 10 | 0.89 | Xác nhận vùng tối ưu |
Quan sát:
1# Bước 1: Import thư viện2# scikit-optimize là thư viện Bayesian Optimization cho scikit-learn3from skopt import BayesSearchCV # Bayesian Search tương thích scikit-learn4from skopt.space import Real, Integer, Categorical # Khai báo không gian tìm kiếm56# Bước 2: Định nghĩa không gian tìm kiếm7search_spaces = {8 'n_estimators': Integer(100, 500), # Số nguyên trong [100, 500]9 'max_depth': Integer(3, 15), # Số nguyên trong [3, 15]10 'learning_rate': Real(0.01, 0.3,11 prior='log-uniform'), # Log-uniform: ưu tiên giá trị nhỏ12 'subsample': Real(0.6, 1.0), # Số thực trong [0.6, 1.0]13 'colsample_bytree': Real(0.6, 1.0), # Tỉ lệ features mỗi cây14}15# Lưu ý: prior='log-uniform' phù hợp cho learning_rate vì giá trị thường16# nằm trong khoảng nhỏ (0.001-0.1) hơn là khoảng lớn (0.1-1.0)1718# Bước 3: Khởi tạo Bayesian Search19bayes_search = BayesSearchCV(20 estimator=XGBClassifier(random_state=42, eval_metric='logloss'),21 search_spaces=search_spaces,22 n_iter=50, # 50 trials — mỗi trial "thông minh" hơn trial trước23 cv=5, # 5-Fold Cross-Validation24 scoring='accuracy',25 n_jobs=-1,26 random_state=42,27 verbose=128)2930# Bước 4: Chạy Bayesian Optimization31bayes_search.fit(X_train, y_train)3233# Bước 5: Kết quả34print(f"Tổ hợp tốt nhất: {bayes_search.best_params_}")35print(f"Accuracy (CV): {bayes_search.best_score_:.4f}")36print(f"Accuracy (Test): {bayes_search.score(X_test, y_test):.4f}")| Hạn chế |
| ✅ |
| Define-by-run API | ❌ | ❌ | ❌ | ✅ |
| Tự chọn sampler | ❌ | ❌ | Cố định GP | ✅ (TPE, CMA-ES, ...) |
Optuna sử dụng thuật toán TPE (Tree-structured Parzen Estimator):
Trong đó:
Optuna chọn điểm tiếp theo bằng cách tối đa hóa tỉ số — tức ưu tiên vùng cho kết quả tốt và tránh vùng cho kết quả kém.
Tuning max_depth ∈ [3, 10] cho Decision Tree, 6 trials:
| Trial | max_depth | Accuracy | Phân nhóm (threshold=0.85) |
|---|---|---|---|
| 1 | 5 | 0.82 | Kém (g) |
| 2 | 8 | 0.87 | Tốt (l) |
| 3 | 4 | 0.80 | Kém (g) |
| 4 | 9 | 0.86 | Tốt (l) — TPE cập nhật: vùng [7-10] có tỉ lệ l/g cao |
| 5 | 7 | 0.89 | Tốt (l) — TPE tập trung vùng [7-9] |
| 6 | 8 | 0.88 | Tốt (l) — Xác nhận vùng tối ưu |
Quan sát: Sau trial 4, TPE "hiểu" rằng max_depth cao (7-10) cho kết quả tốt hơn → tập trung thử vùng này.
Pruning là tính năng đặc biệt của Optuna — dừng sớm những trials kém hiệu quả để tiết kiệm thời gian:
| Trial | n_estimators | max_depth | lr | Epoch 10 Acc | Epoch 30 Acc | Epoch 50 Acc | Trạng thái |
|---|---|---|---|---|---|---|---|
| 1 | 200 | 8 | 0.1 | 0.75 | 0.83 | 0.87 | ✅ Hoàn thành |
| 2 | 150 | 4 | 0.3 | 0.60 | — | — | ❌ Pruned tại epoch 10 (quá kém) |
| 3 | 300 | 10 | 0.05 | 0.78 | 0.86 | 0.91 | ✅ Hoàn thành |
| 4 | 100 | 3 | 0.2 | 0.65 | 0.70 | — | ❌ Pruned tại epoch 30 |
| 5 | 250 | 9 | 0.07 | 0.80 | 0.88 | 0.92 | ✅ Hoàn thành |
Kết quả: 5 trials nhưng chỉ cần train 3 trials đầy đủ — tiết kiệm ~40% thời gian so với không pruning!
1# Bước 1: Import thư viện2import optuna # Framework hyperparameter optimization3from sklearn.model_selection import cross_val_score # Cross-validation45# Bước 2: Định nghĩa hàm objective6# Đây là hàm mà Optuna sẽ tối ưu — trả về metric cần maximize/minimize7def objective(trial):8 """9 Hàm mục tiêu cho Optuna.10 - trial: Object quản lý 1 lần thử hyperparameters11 - Mỗi trial.suggest_* tạo ra một giá trị hyperparameter12 """13 # Gợi ý (suggest) giá trị cho mỗi hyperparameter14 params = {15 'n_estimators': trial.suggest_int(16 'n_estimators', 100, 500 # Số nguyên trong [100, 500]17 ),18 'max_depth': trial.suggest_int(19 'max_depth', 3, 15 # Số nguyên trong [3, 15]20 ),21 'learning_rate': trial.suggest_float(22 'learning_rate', 0.01, 0.3,23 log=True # Log-scale: ưu tiên giá trị nhỏ24 ),25 'subsample': trial.suggest_float(26 'subsample', 0.6, 1.0 # Số thực trong [0.6, 1.0]27 ),28 'colsample_bytree': trial.suggest_float(29 'colsample_bytree', 0.6, 1.030 ),31 }3233 # Tạo model với hyperparameters được gợi ý34 model = XGBClassifier(35 **params, # Unpack dictionary thành keyword arguments36 random_state=42,37 eval_metric='logloss'38 )3940 # Đánh giá bằng 5-Fold Cross-Validation41 scores = cross_val_score(42 model, X_train, y_train,43 cv=5, # 5 folds44 scoring='accuracy' # Metric: accuracy45 )4647 return scores.mean() # Trả về accuracy trung bình4849# Bước 3: Tạo Study (quản lý toàn bộ quá trình optimization)50study = optuna.create_study(51 direction='maximize', # Tối đa hoá accuracy52 study_name='xgboost-tuning' # Tên study để dễ quản lý53)5455# Bước 4: Chạy optimization56study.optimize(57 objective, # Hàm mục tiêu58 n_trials=100, # Thử 100 tổ hợp59 show_progress_bar=True # Hiển thị thanh tiến trình60)6162# Bước 5: Xem kết quả63print(f"Tổ hợp tốt nhất: {study.best_params}")64print(f"Accuracy tốt nhất (CV): {study.best_value:.4f}")6566# Bước 6: Trực quan hoá (chạy trong Jupyter Notebook)67fig1 = optuna.visualization.plot_optimization_history(study)68# Biểu đồ lịch sử optimization — cho thấy accuracy cải thiện qua từng trial69fig1.show()7071fig2 = optuna.visualization.plot_param_importances(study)72# Biểu đồ tầm quan trọng — hyperparameter nào ảnh hưởng nhiều nhất73fig2.show()7475fig3 = optuna.visualization.plot_contour(study)76# Biểu đồ đường đồng mức — mối quan hệ giữa các cặp hyperparameters77fig3.show()| Khi nào dùng | Ít params, muốn chắc chắn | Nhiều params, budget vừa | Model train chậm | Mọi trường hợp |
| Thư viện | scikit-learn | scikit-learn | scikit-optimize | optuna |
| Cần domain knowledge | Đặt grid | Đặt distribution | Ít | Ít |
| Parallelization | Dễ | Dễ | Khó | Dễ |
Trong đó: = số giá trị mỗi hyperparameter, = số hyperparameters, = số trials.
1Bước 1: Có bao nhiêu hyperparameters?2 ├── ≤ 3 params, ≤ 5 giá trị/param → Grid Search3 └── > 3 params hoặc continuous range4 ├── Budget hạn chế, model train nhanh → Random Search5 └── Budget thoải mái hoặc model train chậm6 ├── Cần tích hợp đơn giản với scikit-learn → BayesSearchCV7 └── Cần dashboard, pruning, distributed → Optuna ✅Trong thực tế production, Optuna là lựa chọn mặc định phổ biến nhất nhờ API đơn giản, pruning giúp tiết kiệm compute, và dashboard trực quan. Bắt đầu với Optuna nếu bạn không chắc dùng gì.
| 3 |
| 6 |
| 9 |
| 0.88 |
Báo cáo: Accuracy = 0.850 ± 0.024
Vấn đề: K-Fold thường có thể cho ra fold chỉ chứa 1 class → đánh giá sai.
Stratified K-Fold đảm bảo mỗi fold giữ nguyên tỷ lệ class của dataset gốc.
| Ví dụ | Class 0 (70%) | Class 1 (30%) |
|---|---|---|
| Dataset gốc | 70 mẫu | 30 mẫu |
| Fold 1 | 14 mẫu (70%) | 6 mẫu (30%) |
| Fold 2 | 14 mẫu (70%) | 6 mẫu (30%) |
| Fold 3 | 14 mẫu (70%) | 6 mẫu (30%) |
| Fold 4 | 14 mẫu (70%) | 6 mẫu (30%) |
| Fold 5 | 14 mẫu (70%) | 6 mẫu (30%) |
Vấn đề: Khi dùng CV vừa để tuning vừa để đánh giá, kết quả bị optimistic bias — accuracy báo cáo cao hơn thực tế.
Nested CV giải quyết bằng 2 vòng CV lồng nhau:
1┌─── Outer CV (5 folds) ── Đánh giá model ───┐2│ │3│ ┌─── Inner CV (3 folds) ── Tuning ───┐ │4│ │ Fold 1: Train → Val │ │5│ │ Fold 2: Train → Val │ │6│ │ Fold 3: Train → Val │ │7│ │ → Chọn best hyperparameters │ │8│ └─────────────────────────────────────┘ │9│ │10│ Train model (best params) → Test on Outer │11│ → Outer Fold accuracy │12└──────────────────────────────────────────────┘1# Bước 1: Import thư viện2from sklearn.model_selection import (3 StratifiedKFold, # Stratified K-Fold cho class cân bằng4 GridSearchCV, # Grid Search trong inner loop5 cross_val_score # CV cho outer loop6)78# Bước 2: Thiết lập Outer và Inner CV9outer_cv = StratifiedKFold(10 n_splits=5, # 5 folds cho outer (đánh giá cuối cùng)11 shuffle=True,12 random_state=4213)1415inner_cv = StratifiedKFold(16 n_splits=3, # 3 folds cho inner (tuning)17 shuffle=True,18 random_state=4219)2021# Bước 3: Thiết lập Grid Search cho inner loop22param_grid = {23 'n_estimators': [100, 200, 300],24 'max_depth': [5, 10, 15],25}2627grid_search = GridSearchCV(28 estimator=RandomForestClassifier(random_state=42),29 param_grid=param_grid,30 cv=inner_cv, # Inner CV cho tuning31 scoring='accuracy',32 n_jobs=-133)3435# Bước 4: Chạy Nested CV36# cross_val_score sử dụng outer_cv, bên trong mỗi fold chạy grid_search37nested_scores = cross_val_score(38 grid_search, # GridSearch đóng vai trò như "model"39 X, y,40 cv=outer_cv, # Outer CV cho đánh giá41 scoring='accuracy'42)4344# Bước 5: Kết quả unbiased45print(f"Nested CV Accuracy: {nested_scores.mean():.4f} ± {nested_scores.std():.4f}")46# Kết quả này đáng tin cậy hơn regular CV vì tránh optimistic biasGiải thích: cross_val_score dùng outer_cv để chia 5 lần train/test. Trong mỗi lần, grid_search dùng inner_cv để tuning → chọn best params → đánh giá trên outer test fold. Kết quả cuối không bị bias.
| relu, tanh, selu |
| Hàm kích hoạt |
| Training | Learning rate | 1e-4 – 1e-2 | Tốc độ học |
| Batch size | 16–256 | Mẫu/lần cập nhật |
| Optimizer | Adam, SGD, RMSprop | Thuật toán tối ưu |
| Regularization | Dropout rate | 0.0–0.5 | Tỉ lệ neuron bị tắt |
| L2 regularization | 1e-6 – 1e-2 | Phạt weights lớn |
1# Bước 1: Import thư viện2import keras_tuner as kt # Framework tuning cho Keras3from tensorflow import keras # TensorFlow/Keras45# Bước 2: Định nghĩa hàm build_model6# Hàm này nhận object `hp` (HyperParameters) để khai báo không gian tìm kiếm7def build_model(hp):8 """9 Xây dựng model với hyperparameters được tuning.10 hp: Object HyperParameters chứa các phương thức suggest11 """12 model = keras.Sequential() # Model tuần tự (sequential)1314 # Tuning số hidden layers (1 đến 4 layers)15 for i in range(hp.Int('num_layers', min_value=1, max_value=4)):16 model.add(keras.layers.Dense(17 # Tuning số neurons mỗi layer (32 đến 256, bước 32)18 units=hp.Int(f'units_{i}', min_value=32, max_value=256, step=32),19 # Tuning activation function20 activation=hp.Choice('activation', values=['relu', 'tanh', 'selu'])21 ))22 # Tuning dropout rate (0.0 đến 0.5, bước 0.1)23 model.add(keras.layers.Dropout(24 rate=hp.Float('dropout', min_value=0.0, max_value=0.5, step=0.1)25 ))2627 # Output layer (binary classification)28 model.add(keras.layers.Dense(1, activation='sigmoid'))2930 # Tuning learning rate (1e-4 đến 1e-2, log-scale)31 model.compile(32 optimizer=keras.optimizers.Adam(33 learning_rate=hp.Float(34 'learning_rate', min_value=1e-4, max_value=1e-2, sampling='log'35 )36 ),37 loss='binary_crossentropy',38 metrics=['accuracy']39 )4041 return model4243# Bước 3: Tạo Tuner (dùng Hyperband — hiệu quả hơn random)44tuner = kt.Hyperband(45 hypermodel=build_model, # Hàm build model46 objective='val_accuracy', # Metric tối ưu47 max_epochs=50, # Epoch tối đa mỗi trial48 factor=3, # Factor giảm (Hyperband algorithm)49 directory='tuning_results', # Thư mục lưu kết quả50 project_name='neural_net' # Tên project51)5253# Bước 4: Chạy tuning54tuner.search(55 X_train, y_train,56 epochs=50,57 validation_data=(X_val, y_val),58 callbacks=[59 keras.callbacks.EarlyStopping(60 patience=5, # Dừng nếu 5 epoch không cải thiện61 restore_best_weights=True # Khôi phục weights tốt nhất62 )63 ]64)6566# Bước 5: Lấy model và hyperparameters tốt nhất67best_model = tuner.get_best_models(num_models=1)[0]68best_hp = tuner.get_best_hyperparameters(num_trials=1)[0]6970print(f"Số layers: {best_hp.get('num_layers')}")71print(f"Activation: {best_hp.get('activation')}")72print(f"Dropout: {best_hp.get('dropout')}")73print(f"Learning rate: {best_hp.get('learning_rate'):.6f}")| Nested CV cho báo cáo cuối |
| Tránh optimistic bias khi report accuracy |
| 6 | Early stopping | Tiết kiệm compute bằng cách dừng sớm trials kém |
| 7 | Ghi lại experiment | Dùng MLflow hoặc W&B để log tất cả trials |
| 8 | Monitor overfitting | So sánh train/validation score — gap lớn = overfit |
| Lỗi | Hậu quả | Cách khắc phục |
|---|---|---|
| Tuning trên test set | Data leakage, accuracy giả | Dùng separate validation set hoặc CV |
| Grid quá lớn | Chạy mãi không xong | Dùng Random/Bayesian thay vì Grid |
| Không set random_state | Kết quả không reproducible | Luôn set random_state=42 |
| Tune quá nhiều params cùng lúc | Không gian tìm kiếm quá lớn | Tune từng nhóm: kiến trúc → training → regularization |
| Bỏ qua default values | Mất thời gian cho params ít ảnh hưởng | Kiểm tra feature importance của hyperparameters |
| Bayesian Optimization cho scikit-learn |
| XGBoost Parameters | xgboost.readthedocs.io/en/stable/parameter.html | Tài liệu hyperparameters XGBoost |
Trong production, cải thiện 0.5% accuracy nhưng tốn 10x compute thường không đáng. Hãy cân nhắc ROI (Return on Investment) của việc tuning.
| https://surpriselib.com/ |
| Thư viện Recommendation Systems Python |