I. Giới thiệu
Trong bối cảnh thị trường bất động sản ngày càng phát triển mạnh mẽ, việc xác định giá trị thực của một ngôi nhà trở thành vấn đề quan trọng đối với cả người mua lẫn người bán. Tuy nhiên, giá nhà chịu ảnh hưởng của nhiều yếu tố như vị trí địa lý, diện tích, số phòng, tiện ích xung quanh hay xu hướng kinh tế – xã hội, khiến việc định giá thủ công trở nên khó khăn và thiếu chính xác. Do đó, bài toán dự đoán giá nhà ra đời như một ứng dụng thực tiễn của học máy (Machine Learning), nhằm xây dựng mô hình có khả năng ước lượng giá nhà dựa trên các đặc trưng đầu vào một cách tự động, nhanh chóng và đáng tin cậy.
Một trong những mô hình nền tảng và quan trọng nhất trong học máy là hồi quy tuyến tính (Linear Regression), được sử dụng để dự đoán các giá trị liên tục dựa trên mối quan hệ giữa các biến đầu vào và đầu ra. Trong quá trình huấn luyện, mô hình điều chỉnh các tham số để giảm sai số giữa giá trị dự đoán và giá trị thực thông qua các thuật toán tối ưu như Gradient Descent. Tuy nhiên, dữ liệu trong thực tế thường không hoàn toàn tuyến tính mà chứa nhiều mối quan hệ phi tuyến và phức tạp, khiến mô hình tuyến tính đơn thuần không còn phù hợp.
Để mô hình có khả năng biểu diễn tốt hơn, nhóm mở rộng sang hồi quy đa thức (Polynomial Regression), cho phép nắm bắt các mối quan hệ phi tuyến giữa các đặc trưng. Tuy nhiên, việc tăng bậc đa thức có thể dẫn đến hiện tượng “nổ chiều” (curse of dimensionality) và quá khớp (overfitting). Do đó, nhóm áp dụng biểu diễn đặc trưng có kiểm soát (Feature Engineering), bao gồm việc đánh giá có hệ thống giữa hai dạng đặc trưng là PolynomialFeatures đầy đủ và interaction-only, đồng thời lựa chọn bậc đa thức tối ưu bằng cross-validation nhằm cân bằng giữa độ phức tạp và hiệu năng mô hình.
Bên cạnh đó, để cải thiện khả năng khái quát hóa và hạn chế overfitting, nhóm sử dụng các kỹ thuật điều chuẩn (Regularization) như Ridge Regression (L2) và Lasso Regression (L1), đồng thời mở rộng sang Elastic Net, giúp dung hòa giữa việc chọn lọc đặc trưng (L1) và ổn định hóa hệ số (L2). Các siêu tham số như α và $l1$_ratio được tối ưu thông qua Grid Search hoặc Randomized Search trên thang logarit nhằm tìm ra cấu hình tốt nhất. Ngoài ra, nhóm cũng xem xét và so sánh hiệu quả của các mô hình hồi quy robust như HuberRegressor, QuantileRegressor và RANSACRegressor, nhằm đánh giá khả năng chống nhiễu và xử lý ngoại lai của mô hình trong thực tế.
Cuối cùng, việc đánh giá mô hình được thực hiện một cách có hệ thống bằng cross-validation (KFold, RepeatedKFold, và TimeSeriesSplit cho dữ liệu chuỗi thời gian), với các chỉ số như RMSE, MAE, và R², kèm theo khoảng tin cậy ước lượng bằng bootstrap để đảm bảo kết quả đánh giá khách quan và ổn định.
Phần code được triển khai trên Google Colaboratory: Link code
II. Cải tiến 1: Sử dụng mô hình hoá và quy chuẩn hoá (Regularization) nâng cao
Trong một bài toán về hồi quy, việc lựa chọn mô hình và thiết kế chiến lược tối ưu hoá là rất quan trọng, đặc biệt khi dữ liệu thực tế có đa cộng tuyến, ngoại lệ (outliers), hay phân bố không cân bằng. Dưới đây là phần trình bày chi tiết về bốn phương pháp hồi quy nâng cao thường dùng trong thư viện scikit-learn: Elastic Net, HuberRegressor, QuantileRegressor, và RANSACRegressor.
1. Elastic Net — dung hoà chọn biến (L1) và ổn định hoá hệ số (L2)
1.1. Mục tiêu và ý tưởng
Elastic Net là một hồi quy tuyến tính có chuẩn hoá (regularized linear regression) kết hợp hai dạng phạt phổ biến:
-
L1 (Lasso) khuyến khích hệ số bằng 0 → hỗ trợ chọn biến (sparse solution).
-
L2 (Ridge) thu nhỏ hệ số về 0 nhưng không ép về đúng 0 → ổn định khi có đa cộng tuyến.
Elastic Net cố gắng giữ ưu điểm của cả hai: chọn biến khi cần và vẫn ổn định khi các biến có tương quan mạnh.
1.2. Biểu diễn toán học
Cho dữ liệu $(X, y)$ với $X \in R^{n \times p}$, Elastic Net tìm nghiệm $\hat{\beta}$ bằng cách tối thiểu hóa:
$$\hat{\beta} = \underset{\hat{\beta}}{\arg\min} \frac{1}{2n}\|y - X\beta\|_2^2 + \alpha \left( (1 - \rho)\frac{1}{2}\|\beta\|_2^2 + \rho\|\beta\|_1 \right)$$
Trong đó:
- $\alpha$ là mức độ phạt tổng thể (regularization strength)
- $\rho \in [0, 1]$ là $l1$_ratio (tỉ lệ L1 so với L2). Với $\rho = 1$ tương đương với Lasso và $\rho = 0$ tương đương với Ridge
1.3. Tính chất
-
Khi các biến đồng dạng và ít tương quan → L1 giúp loại bớt biến không cần thiết.
-
Khi có đa cộng tuyến → L2 giữ hệ số ổn định (không thay đổi mạnh) và làm cho bài toán tốt hơn về mặt số học.
-
Elastic Net thường chọn nhóm biến có tương quan (grouping effect) — nghĩa là khi một nhóm biến nhiều mối tương quan đều quan trọng, Elastic Net có xu hướng giữ cả nhóm thay vì chọn một biến như Lasso có thể làm.
1.4. Tối ưu hóa siêu tham số ($\alpha$ và $l1$_ratio)
a. Dò tìm $\alpha$ theo thang log
- Vì $\alpha$ có thể dao động nhiều bậc (có thể từ $10^{-6}$ đến $10^{2}$), ta thường thử nghiệm trên thang log
import numpy as np
alphas = np.logspace(-4, 2, 100)
- Lý do: ảnh hưởng của α đến penalty là phi tuyến, nên lưới thang log bao phủ hợp lý các kịch bản.
b. Tối ưu $l1$_ratio bằng GridSearch hoặc RandomizedSearch
-
GridSearchCV: dò toàn diện trên tất cả cặp ($\alpha$, $l1$_ratio}). Ưu điểm: tìm global best trên lưới. Nhược điểm: tốn kém với nhiều giá trị (chi phí $\text{O}$($|\alpha| \times |l1| \times$ CV-folds)).
-
RandomizedSearchCV: chọn ngẫu nhiên một số cấu hình; phù hợp khi không có nhiều tài nguyên. Có thể dùng phân bố log-uniform cho $\alpha$ và uniform cho $l1$_ratio.
c. Tối ưu hoá & chọn siêu tham số
Đoạn code bên dưới là phần code được triển khai trong bài toán dự đoán giá nhà để tìm được $\alpha$ và $l1$_ratio tối ưu cho bài toán
- GridSearchCV:
from sklearn.linear_model import ElasticNetCV
alphas = np.logspace(-4, 2, 100)
model = ElasticNetCV(alphas=alphas, l1_ratio=[.1, .5, .9], cv=5)
model.fit(X_train, y_train)
print("Best alpha:", model.alpha_)
print("Best l1_ratio:", model.l1_ratio_)
Kết quả:
Best alpha: 0.026560877829466867
Best l1_ratio: 0.5
- RandomizedSearch:
from sklearn.model_selection import RandomizedSearchCV
from sklearn.linear_model import ElasticNet
param_distributions = {
"alpha": np.logspace(-4, 2, 100),
"l1_ratio": np.linspace(0, 1, 200)
}
search = RandomizedSearchCV(ElasticNet(), param_distributions,
n_iter=30, cv=5, random_state=0)
search.fit(X_train, y_train)
print("Best alpha:", search.best_params_['alpha'])
print("Best l1_ratio:", search.best_params_['l1_ratio'])
Kết quả:
Best alpha: 0.023101297000831605
Best l1_ratio: 0.3869346733668342
2. HuberRegressor — hồi quy robust dựa trên hàm Huber (M-estimator)
2.1. Mục tiêu và ý tưởng
HuberRegressor là một mô hình tuyến tính dùng Huber loss — một hàm mất mát kết hợp giữa bình phương (quadratic) và tuyến tính (linear). Mục tiêu là giảm nhạy cảm với outlier nhưng vẫn giữ tính hiệu quả khi lỗi nhỏ.
Huber lọc phần “nhiễu lớn” bằng cách làm phẳng ảnh hưởng của sai số lớn: khi sai số nhỏ thì xử lý như MSE, khi sai số vượt ngưỡng thì chuyển sang linear — do đó giảm trọng số của các outlier.
2.2 Biểu diễn toán học
Huber loss cho sai số $r = y - \hat{y}$ được định nghĩa bằng tham số $\epsilon$ (ngưỡng):
$$L_{\text{Huber}}(r) = \begin{cases} \frac{1}{2}r^2 & \text{nếu } |r| \le \varepsilon, \\ \varepsilon|r| - \frac{1}{2}\varepsilon^2 & \text{nếu } |r| > \varepsilon. \end{cases}$$
Hàm này liên tục và có đạo hàm liên tục tại $r = \pm \epsilon$. Tổng loss được tối thiếu hóa cộng với penalty L2 có thể được thêm vào:
$$\min_{\beta} \frac{1}{n} \sum_{i=1}^{n} L_{\text{Huber}}(y_i - x_i^\top \beta) + \alpha\|\beta\|_2^2$$
2.3. Tính chất
-
Huber là một M-estimator (generalized MLE-like estimator) với ảnh hưởng của outlier bị giới hạn (bounded influence).
-
So với Least Absolute Deviations (L1) (median regression), Huber giữ tính ổn định và có đạo hàm tại 0 (tốt cho tối ưu hoá gradient).
-
So với MSE, Huber giảm ảnh hưởng lên nghiệm khi tồn tại một vài outlier lớn.
3. QuantileRegressor — hồi quy theo phân vị (Pinball loss)
3.1. Mục tiêu và ý tưởng
Quantile regression không chỉ học giá trị mong đợi (mean) mà học một phân vị (quantile) cụ thể của phân phối điều kiện $Y|X$. Thay vì MSE, tối ưu hóa pinball(check) loss:
- Nếu muốn ước lượng median → quantile = 0.5.
- Nếu muốn ước lượng 90th percentile → quantile = 0.9.
Ứng dụng: dự báo rủi ro, biên độ, lập kế hoạch cho worst-case hoặc best-case scenarios.
3.2. Biểu diễn toán học
Cho $r \in (0, 1)$ là quantile mong muốn. Pinball loss:
$$L_{\tau}(r) = \begin{cases}
\tau r & \text{nếu } r \ge 0, \\
(\tau - 1)r & \text{nếu } r < 0,
\end{cases}
\quad \text{với } r = y - \hat{y}$$
Tương đương:
$$L_{\tau}(r) = (\tau - \mathbb{I}\{r < 0\}) \cdot r$$
Mục tiêu:
$$\min_{\beta} \frac{1}{n} \sum_{i=1}^{n} L_{\tau}(y_i - x_i^\top \beta) + \alpha\|\beta\|_1 (\text{hoặc } \|\beta\|_2)$$
3.3 Tính chất
- Quan trọng khi phân phối điều kiện của Y không đối xứng hoặc bạn cần rủi ro (tail) chứ không phải trung bình.
- Đối với $\tau=0.5$, pinball loss đưa đến median regression (ít nhạy với outlier theo chiều lớn).
- Không cần giả thiết Gaussian noise; quantile regression mô tả mối quan hệ giữa X và các quantile khác nhau của Y.
4. RANSACRegressor — tách biệt inliers / outliers bằng phương pháp ngẫu nhiên
4.1. Mục tiêu và ý tưởng
RANSAC (RANdom SAmple Consensus) là một phương pháp robust estimation: nó tìm một mô hình phù hợp với phần lớn dữ liệu inliers, đồng thời bỏ qua outliers. Ý tưởng: lặp nhiều lần, với mỗi lần:
- Bước 1: Chọn ngẫu nhiên một mẫu nhỏ (subset) đủ để ước lượng mô hình.
- Bước 2: Huấn luyện mô hình cơ sở trên subset đó.
- Bước 3: Đánh giá toàn bộ dữ liệu: điểm nào nằm trong ngưỡng sai số (residual) thì là inlier.
- Bước 4: Nếu số inlier đủ lớn → cập nhật mô hình bằng việc fit lại trên toàn bộ inlier.
- Bước 5: Giữ mô hình có số inlier lớn nhất qua các vòng lặp.
RANSAC rất hiệu quả khi có tỷ lệ outlier đáng kể.
4.2. Biểu diễn toán học
Để đạt xác suất thành công $p$ (tìm được một mẫu không chứa outlier) khi tỉ lệ inlier là $w$, và mỗi thử chọn $s$ điểm (min_samples), thì cần số vòng lặp tối thiếu $k$ thỏa:
$$1 - p = (1 - w^{s})^{k} \Rightarrow k = \frac{\log(1 - p)}{\log(1 - w^{s})}$$
4.3. Ưu/Nhược điểm
- Ưu: cực kỳ mạnh nếu outlier là nhiều và phân bố vô tổ chức. Tự tách điểm outlier mà không cần tiền xử lý.
- Nhược: không đảm bảo tối ưu toàn cục; cần nhiều vòng lặp nếu tỉ lệ inlier thấp. Kết quả ngẫu nhiên (nên set
random_state). Không phù hợp khi không tồn tại “big consensus” (không có mô hình đại diện cho một phần lớn dữ liệu).
5. Kết quả khi áp dụng các phương pháp mô hình hoá và quy chuẩn hoá như trên:
5.1. Khi không dùng Polynomial Features
| Model | Train_RMSE | Test_RMSE | Train_R2 | Test_R2 |
|---|---|---|---|---|
| Lasso | 20032.668558 | 26362.510069 | 0.933902 | 0.900792 |
| Huber Regression | 27701.075108 | 27862.374124 | 0.873613 | 0.889182 |
| Linear Regression | 20029.220254 | 28137.140208 | 0.933925 | 0.886986 |
| Ridge | 22488.286583 | 28586.775808 | 0.916705 | 0.883345 |
| ElasticNet Grid Search | 27114.742569 | 31861.640502 | 0.878907 | 0.855086 |
| ElasticNet Random Search | 27275.418781 | 31986.326102 | 0.877468 | 0.853950 |
| RANSAC Regression | 31209.965463 | 32365.806469 | 0.839567 | 0.850464 |
| Quantile Regression | 79691.565798 | 84787.117404 | -0.046002 | -0.026201 |
Table 1: Bảng kết quả so sánh hiệu suất của các mô hình trong bài toán dự đoán giá nhà khi không dùng Polynomial Features
5.2. Khi dùng Polynomial Features
| Model | Train_RMSE | Test_RMSE | Train_R2 | Test_R2 |
|---|---|---|---|---|
| Huber Regression | 28158.025815 | 25430.167664 | 0.869409 | 0.907685 |
| Ridge | 16057.215172 | 27767.330095 | 0.957533 | 0.889937 |
| ElasticNet Grid Search | 22356.465430 | 28432.060659 | 0.917678 | 0.884604 |
| ElasticNet Random Search | 22535.006385 | 28493.233153 | 0.916358 | 0.884107 |
| Lasso | 12063.654479 | 34412.919807 | 0.976030 | 0.830950 |
| Linear Regression | 9545.514116 | 55212.465051 | 0.984993 | 0.564842 |
| RANSAC Regression | 44071.566089 | 73263.640379 | 0.680092 | 0.233787 |
| Quantile Regression | 79691.565798 | 84787.117404 | -0.046002 | -0.026201 |
Table 2: Bảng kết quả so sánh hiệu suất của các mô hình trong bài toán dự đoán giá nhà khi dùng Polynomial Features
5.3. Nhận xét chi tiết từ kết quả 2 bảng trên
| Đặc điểm | Không dùng Polynomial Features | Dùng Polynomial Features |
|---|---|---|
| Hiệu năng tốt nhất (Test_R²) | Lasso: 0.901 | Huber Regression: 0.908 |
| Mức RMSE tốt nhất (Test) | Lasso: ~26,362 | Huber Regression: ~25,430 |
| Overfitting | Ở mức vừa phải | Tăng mạnh (đặc biệt với Linear, Lasso, Ridge) |
| Tính ổn định Train/Test | Khá ổn định | Mất cân bằng nhiều mô hình |
| Mô hình tệ nhất | Quantile Regression (R² < 0) | Quantile Regression (R² < 0) |
a. Khi không dùng Polynomial Features
- Lasso Regression đạt Test_R² = 0.901 và Test_RMSE ≈ 26,362, là mô hình hiệu quả nhất.
- Linear và Ridge Regression cũng cho kết quả tốt nhưng Test_R² thấp hơn, cho thấy Lasso giúp giảm overfitting.
- Các mô hình ElasticNet, Huber, RANSAC hoạt động ổn nhưng không vượt trội.
- Quantile Regression có R² âm, không phù hợp với bài toán.
Kết luận:
Với dữ liệu tuyến tính ban đầu, Lasso Regression là lựa chọn tối ưu, cân bằng giữa độ chính xác và khả năng tổng quát hóa.
b. Khi dùng Polynomial Features
- Huber Regression trở thành mô hình tốt nhất, với Test_R² = 0.908 — cao hơn một chút so với Lasso ở bảng trước.
- Ridge và ElasticNet có Train_R² cao (~0.92–0.96) nhưng Test_R² giảm (~0.88–0.89) → dấu hiệu overfitting.
- Lasso và Linear Regression bị overfit mạnh, với Train_R² rất cao (0.97–0.98) nhưng Test_R² giảm sâu (0.83 và 0.56).
- RANSAC và Quantile tiếp tục có hiệu năng thấp.
Kết luận:
Việc thêm Polynomial Features giúp mô hình nắm bắt quan hệ phi tuyến, nhưng cũng làm tăng nguy cơ overfitting.
Huber Regression cân bằng tốt nhất giữa độ phức tạp và ổn định, thích hợp nhất sau khi mở rộng đặc trưng.
c. Tổng kết so sánh
| Tiêu chí | Không Polynomial | Có Polynomial | Nhận xét |
|---|---|---|---|
| Mô hình tốt nhất | Lasso Regression | Huber Regression | Polynomial giúp cải thiện nhẹ hiệu năng |
| Độ khớp (Train R²) | Trung bình 0.88–0.93 | Tăng lên 0.90–0.98 | Overfitting rõ rệt hơn |
| Hiệu năng Test | Ổn định, sai số hợp lý | Chênh lệch lớn giữa Train/Test | Cần regularization mạnh hơn |
| Tổng thể | Cấu trúc tuyến tính phù hợp | Đặc trưng phi tuyến giúp mô hình phức tạp hơn nhưng khó kiểm soát |
III. Cải tiến 2: Áp dụng các kỹ thuật đánh giá mô hình nâng cao
1. Cross-Validation: Phương pháp KFold và RepeatedKFold
Khi làm việc với mô hình học máy, một trong những cách hiệu quả nhất để kiểm tra độ ổn định và khả năng tổng quát của mô hình là sử dụng Cross-Validation.
Trong đó, KFold là một chiến lược rất phổ biến: dữ liệu được chia thành k phần (gọi là folds). Mỗi lần huấn luyện, mô hình sẽ học từ k−1 phần và kiểm tra trên phần còn lại. Quá trình này lặp lại nhiều lần cho đến khi mọi phần dữ liệu đều lần lượt được dùng làm “bài kiểm tra”.
Cách làm này giúp mô hình không chỉ “giỏi” trên một phần dữ liệu cụ thể, mà còn cho thấy mức độ ổn định khi gặp các tập dữ liệu khác nhau.
Trong ví dụ này, chúng ta sẽ sử dụng KFold với 5 phần dữ liệu (5 folds) - tức là mô hình sẽ được huấn luyện và đánh giá 5 lần độc lập, rồi lấy trung bình kết quả để có cái nhìn toàn diện hơn.
1.1. Định nghĩa hàm [evaluate_model_cv] – “Trái tim” của Cross-Validation
Trước khi bắt đầu huấn luyện, chúng ta cần một công cụ có thể lặp lại toàn bộ quy trình đánh giá một cách thống nhất.
Hàm evaluate_model_cv() chính là trung tâm của quá trình này - nó giống như trọng tài công bằng, giúp chấm điểm mô hình qua từng lượt thi đấu.
Hàm sẽ:
- Chia dữ liệu theo chiến lược Cross-Validation (ví dụ KFold hoặc RepeatedKFold),
- Huấn luyện mô hình trên từng phần dữ liệu,
- Tính toán các chỉ số đánh giá (như RMSE, MAE, và R²),
- Cuối cùng tổng hợp lại thành kết quả trung bình và độ lệch chuẩn cho mỗi chỉ số.
Đây là bước nền tảng mà mọi phương pháp Cross-Validation sau đó (KFold, RepeatedKFold, TimeSeriesSplit) đều sẽ dựa vào.
def evaluate_model_cv(model, X, y, cv_strategy, model_name):
"""
Đánh giá mô hình sử dụng cross-validation
Args:
model: Mô hình cần đánh giá
X: Features
y: Target values
cv_strategy: Chiến lược cross-validation
model_name: Tên mô hình
Returns:
dict: Dictionary chứa các metric và thống kê
"""
print(f"\n=== Đánh giá {model_name} ===")
# Tính toán các metric cho từng fold
rmse_scores = []
mae_scores = []
r2_scores = []
for fold, (train_idx, val_idx) in enumerate(cv_strategy.split(X)):
X_train_fold, X_val_fold = X[train_idx], X[val_idx]
y_train_fold, y_val_fold = y[train_idx], y[val_idx]
# Huấn luyện mô hình trên fold này
model.fit(X_train_fold, y_train_fold)
# Dự đoán trên validation set
y_pred_fold = model.predict(X_val_fold)
# Tính các metric
rmse = np.sqrt(mean_squared_error(y_val_fold, y_pred_fold))
mae = mean_absolute_error(y_val_fold, y_pred_fold)
r2 = r2_score(y_val_fold, y_pred_fold)
rmse_scores.append(rmse)
mae_scores.append(mae)
r2_scores.append(r2)
# Tính thống kê tổng hợp
results = {
'model_name': model_name,
'rmse_mean': np.mean(rmse_scores),
'rmse_std': np.std(rmse_scores),
'mae_mean': np.mean(mae_scores),
'mae_std': np.std(mae_scores),
'r2_mean': np.mean(r2_scores),
'r2_std': np.std(r2_scores),
'rmse_scores': rmse_scores,
'mae_scores': mae_scores,
'r2_scores': r2_scores
}
# In kết quả
print(f"RMSE: {results['rmse_mean']:.2f} ± {results['rmse_std']:.2f}")
print(f"MAE: {results['mae_mean']:.2f} ± {results['mae_std']:.2f}")
print(f"R²: {results['r2_mean']:.3f} ± {results['r2_std']:.3f}")
return results
Cách hoạt động của hàm
Mỗi lần chạy, hàm sẽ:
- Chia dữ liệu thành các phần (folds) theo chiến lược CV.
- Huấn luyện mô hình trên tập train của fold đó.
- Dự đoán kết quả trên tập validation.
- Tính các chỉ số sai số:
RMSE – đo độ lệch trung bình bình phương (sai số càng nhỏ, càng tốt).
MAE – sai số tuyệt đối trung bình, dễ hiểu và ít bị ảnh hưởng bởi giá trị ngoại lai.
R² – thể hiện mức độ mô hình giải thích được biến động của dữ liệu (gần 1 là tốt).
- Ghi lại các chỉ số của từng fold, rồi lấy trung bình và độ lệch chuẩn sau cùng.
1.2. Áp dụng Cross-Validation cho Các mô hình
Sau khi đã có “trái tim” là hàm evaluate_model_cv(), bước tiếp theo là đưa các mô hình vào kiểm tra thực tế thông qua các chiến lược chia dữ liệu khác nhau.
Chiến lược KFold và RepeatedKFold
Chúng ta sẽ sử dụng hai phương pháp phổ biến nhất:
- KFold Cross-Validation (5 folds)
Chia dữ liệu thành 5 phần bằng nhau. Mỗi lần, một phần được dùng làm tập kiểm tra, 4 phần còn lại dùng để huấn luyện mô hình.
Sau 5 lần lặp, chúng ta có thể tính trung bình các chỉ số đánh giá như RMSE, MAE, R² để xem mô hình ổn định đến đâu.
- RepeatedKFold (5 folds × 3 repeats)
Là phiên bản nâng cao của KFold, khi quá trình trên được lặp lại 3 lần với các cách chia dữ liệu khác nhau.
Điều này giúp giảm bớt yếu tố ngẫu nhiên và cung cấp kết quả chính xác, ổn định hơn.
Thiết lập mô hình và chiến lược chia dữ liệu
# Định nghĩa các chiến lược cross-validation
kfold = KFold(n_splits=5, shuffle=True, random_state=42)
repeated_kfold = RepeatedKFold(n_splits=5, n_repeats=3, random_state=42)
# Định nghĩa các mô hình để đánh giá
models_cv = {
"Linear Regression": LinearRegression(),
"Ridge": Ridge(alpha=1.0),
"Lasso": Lasso(alpha=0.1),
"ElasticNet Grid Search": ElasticNet(alpha=0.026560877829466867, l1_ratio=0.5),
"ElasticNet Random Search": ElasticNet(alpha=0.023101297000831605, l1_ratio=0.3869346733668342),
"Huber Regression": HuberRegressor(),
"Quantile Regression": QuantileRegressor(),
"RANSAC Regression": RANSACRegressor()
}
print("=== KFOLD CROSS-VALIDATION (5 folds) ===")
cv_results_kfold = []
for name, model in models_cv.items():
result = evaluate_model_cv(model, X_train, y_train, kfold, name)
cv_results_kfold.append(result)
print("\n=== REPEATED KFOLD CROSS-VALIDATION (5 folds, 3 repeats) ===")
cv_results_repeated = []
for name, model in models_cv.items():
result = evaluate_model_cv(model, X_train, y_train, repeated_kfold, name)
cv_results_repeated.append(result)
Hiệu suất của các mô hình trên bài toán dự đoán giá nhà khi áp dụng các phương pháp Cross-Validation:
| Model | CV_Method | RMSE_Mean | RMSE_Std | MAE_Mean | MAE_Std | R2_Mean | R2_Std |
|---|---|---|---|---|---|---|---|
| Huber Regression | KFold | 30882.878 | 5168.550 | 16956.732 | 803.949 | 0.826 | 0.085 |
| ElasticNet Grid Search | KFold | 33008.615 | 3276.242 | 19601.592 | 448.986 | 0.811 | 0.057 |
| ElasticNet Random Search | KFold | 33053.596 | 3222.478 | 19645.854 | 449.379 | 0.810 | 0.056 |
| Ridge | KFold | 34158.890 | 6028.656 | 19451.039 | 820.873 | 0.786 | 0.111 |
| RANSAC Regression | KFold | 35658.342 | 8660.510 | 19931.361 | 1694.761 | 0.756 | 0.155 |
| Linear Regression | KFold | 38064.685 | 9334.615 | 20271.206 | 1650.873 | 0.724 | 0.169 |
| Lasso | KFold | 38456.389 | 8580.173 | 20291.995 | 976.505 | 0.724 | 0.161 |
| Quantile Regression | KFold | 79272.512 | 7747.554 | 55051.430 | 3256.373 | -0.046 | 0.017 |
| Huber Regression | RepeatedKFold | 30564.766 | 6371.663 | 17039.698 | 1284.373 | 0.835 | 0.076 |
| ElasticNet Grid Search | RepeatedKFold | 32215.486 | 4840.445 | 19264.495 | 1387.878 | 0.822 | 0.054 |
| ElasticNet Random Search | RepeatedKFold | 32259.955 | 4804.699 | 19303.480 | 1395.785 | 0.822 | 0.054 |
| Ridge | RepeatedKFold | 33581.337 | 7503.475 | 19344.143 | 1455.502 | 0.799 | 0.100 |
| Lasso | RepeatedKFold | 36924.465 | 10499.384 | 19820.246 | 1763.918 | 0.748 | 0.152 |
| Linear Regression | RepeatedKFold | 37355.458 | 10306.756 | 20030.877 | 1779.722 | 0.743 | 0.152 |
| RANSAC Regression | RepeatedKFold | 39902.785 | 9480.003 | 20734.034 | 2003.654 | 0.719 | 0.127 |
| Quantile Regression | RepeatedKFold | 79383.812 | 7089.219 | 55083.716 | 3123.910 | -0.048 | 0.028 |
Table 3: Bảng kết quả so sánh hiệu suất của các mô hình trong bài toán dự đoán giá nhà khi có kỹ thuật đánh giá mô hình nâng cao Cross-Validation
1.3. Tổng hợp kết quả so sánh
a. Xếp hạng hiệu suất các mô hình và phương pháp
| Hạng | Mô hình | Phương pháp CV | RMSE (Mean) | R² (Mean) | Nhận xét ngắn |
|---|---|---|---|---|---|
| 🥇 | Huber Regression | RepeatedKFold | 30565 | 0.8345 | Hiệu năng cao và ổn định nhất |
| 🥈 | Huber Regression | KFold | 30883 | 0.8263 | Kết quả tốt, ổn định giữa các fold |
| 🥉 | ElasticNet (Grid/Random) | RepeatedKFold | ~32200 | ~0.822 | Hiệu năng khá tốt, ổn định |
| 4️⃣ | Ridge | RepeatedKFold | 33581 | 0.799 | Kém hơn một chút, vẫn chấp nhận được |
| 5️⃣ | Lasso / Linear Regression | RepeatedKFold | ~37,000 | ~0.74–0.75 | Có dấu hiệu overfitting nhẹ |
| 6️⃣ | RANSAC Regression | RepeatedKFold | 39903 | 0.719 | Không ổn định, RMSE cao |
| 🚫 | Quantile Regression | RepeatedKFold | 79384 | -0.048 | Hiệu năng rất kém, không phù hợp |
b. Phân tích chi tiết theo nhóm mô hình
i. Mô hình mạnh (Huber, ElasticNet)
- Huber Regression consistently đạt RMSE thấp nhất (~ 30k) và $R^2$ cao nhất (~0.83–0.84) ở cả hai phương pháp KFold và RepeatedKFold → Đây là mô hình tổng thể tốt nhất, cho thấy khả năng tổng quát hóa cao và ổn định với nhiễu.
- ElasticNet (Grid/Random Search) cho kết quả tương đối gần Huber ($R^2$ ≈ 0.82), chênh lệch RMSE nhỏ và độ lệch chuẩn thấp → ổn định và đáng tin cậy.
ii. Mô hình trung bình (Ridge, Lasso, Linear)
- Ridge Regression có hiệu năng ổn định, $R^2$ ≈ 0.79–0.80, RMSE ≈ 33–34k → phù hợp nếu muốn mô hình đơn giản, ít nhạy cảm với nhiễu.
- Lasso và Linear Regression cho R² giảm mạnh (~ 0.74) và RMSE cao hơn (~37k–38k) → thể hiện dấu hiệu overfitting và độ tổng quát hóa thấp hơn.
iii. Mô hình yếu (RANSAC, Quantile)
- RANSAC Regression có độ lệch chuẩn cao và $R^2$ thấp (~0.72), cho thấy hiệu năng không ổn định giữa các fold, có thể do mô hình phụ thuộc nhiều vào mẫu ngẫu nhiên.
- Quantile Regression tiếp tục cho $R^2$ âm, chứng tỏ mô hình không phù hợp với dữ liệu này, hiệu năng kém nhất trong tất cả.
c. So sánh giữa hai phương pháp Cross-Validation
| Tiêu chí | KFold | RepeatedKFold | Nhận xét |
|---|---|---|---|
| RMSE trung bình | Cao hơn nhẹ (~1–2%) | Thấp hơn, ổn định hơn | RepeatedKFold cho ước lượng đáng tin cậy hơn |
| R² trung bình | 0.72–0.83 | 0.74–0.83 | RepeatedKFold cải thiện nhẹ độ khớp |
| Độ lệch chuẩn (Std) | Thấp hơn | Cao hơn một chút | Do RepeatedKFold lặp lại nhiều lần → phản ánh biến động tốt hơn |
Kết luận: RepeatedKFold cho kết quả ổn định và phản ánh đúng hiệu năng tổng quát hơn so với KFold thông thường.
2. Bootstrap để tính khoảng tin cậy
Sau khi đã đánh giá mô hình bằng Cross-Validation, chúng ta có thể đã biết rằng mô hình hoạt động tốt đến mức nào qua các chỉ số như RMSE, MAE hay R². Nhưng câu hỏi đặt ra là:
“Liệu những con số này có đáng tin cậy không? Nếu chúng ta huấn luyện lại mô hình trên một mẫu dữ liệu hơi khác, kết quả có thay đổi nhiều không?”
2.1. Bootstrap là gì?
Bootstrap là một kỹ thuật thống kê thuộc nhóm resampling (lấy mẫu lại).
Thay vì chỉ huấn luyện mô hình một lần duy nhất, chúng ta sẽ:
- Lấy mẫu ngẫu nhiên có hoàn lại (sampling with replacement) từ dữ liệu gốc để tạo ra nhiều tập dữ liệu Bootstrap.
- Huấn luyện mô hình trên mỗi tập Bootstrap này.
- Tính toán các metric (như RMSE, MAE, R²) trên từng tập.
- Cuối cùng, xây dựng phân phối của các metric đó và tính khoảng tin cậy (confidence interval) cho từng chỉ số.
Nói cách khác, Bootstrap giúp chúng ta biết được mức độ dao động của mô hình - mô hình ổn định đến mức nào, và liệu kết quả của nó có đáng tin hay không.
2.2. Cách thực hiện
Hãy cùng xem hàm bootstrap_evaluation() dưới đây.
Hàm này sẽ chạy mô hình nhiều lần (ví dụ 100 lần), mỗi lần trên một mẫu bootstrap khác nhau, sau đó thống kê lại trung bình, độ lệch chuẩn, và khoảng tin cậy (confidence interval).
def bootstrap_evaluation(model, X, y, n_bootstrap=100, confidence_level=0.95):
"""
Đánh giá mô hình sử dụng bootstrap để tính khoảng tin cậy
Args:
model: Mô hình cần đánh giá
X: Features
y: Target values
n_bootstrap: Số lượng mẫu bootstrap
confidence_level: Mức tin cậy (mặc định 0.95)
Returns:
dict: Dictionary chứa các metric và khoảng tin cậy
"""
n_samples = len(X)
bootstrap_rmse = []
bootstrap_mae = []
bootstrap_r2 = []
print(f"Đang thực hiện {n_bootstrap} mẫu bootstrap...")
for i in range(n_bootstrap):
# Tạo mẫu bootstrap bằng cách resampling với replacement
bootstrap_indices = np.random.choice(n_samples, size=n_samples, replace=True)
X_bootstrap = X[bootstrap_indices]
y_bootstrap = y[bootstrap_indices]
# Huấn luyện mô hình trên mẫu bootstrap
model.fit(X_bootstrap, y_bootstrap)
# Dự đoán trên toàn bộ dữ liệu gốc
y_pred = model.predict(X)
# Tính các metric
rmse = np.sqrt(mean_squared_error(y, y_pred))
mae = mean_absolute_error(y, y_pred)
r2 = r2_score(y, y_pred)
bootstrap_rmse.append(rmse)
bootstrap_mae.append(mae)
bootstrap_r2.append(r2)
# In tiến trình mỗi 20 mẫu
if (i + 1) % 20 == 0:
print(f"Hoàn thành {i + 1}/{n_bootstrap} mẫu bootstrap")
# Tính khoảng tin cậy
alpha = 1 - confidence_level
lower_percentile = (alpha / 2) * 100
upper_percentile = (1 - alpha / 2) * 100
results = {
'rmse_mean': np.mean(bootstrap_rmse),
'rmse_std': np.std(bootstrap_rmse),
'rmse_ci_lower': np.percentile(bootstrap_rmse, lower_percentile),
'rmse_ci_upper': np.percentile(bootstrap_rmse, upper_percentile),
'mae_mean': np.mean(bootstrap_mae),
'mae_std': np.std(bootstrap_mae),
'mae_ci_lower': np.percentile(bootstrap_mae, lower_percentile),
'mae_ci_upper': np.percentile(bootstrap_mae, upper_percentile),
'r2_mean': np.mean(bootstrap_r2),
'r2_std': np.std(bootstrap_r2),
'r2_ci_lower': np.percentile(bootstrap_r2, lower_percentile),
'r2_ci_upper': np.percentile(bootstrap_r2, upper_percentile),
'bootstrap_rmse': bootstrap_rmse,
'bootstrap_mae': bootstrap_mae,
'bootstrap_r2': bootstrap_r2
}
return results
Sau khi đã định nghĩa hàm bootstrap_evaluation(), bây giờ chúng ta sẽ thực hiện quá trình đánh giá Bootstrap cho các mô hình học máy.
Mỗi mô hình sẽ được huấn luyện 100 lần trên các mẫu bootstrap khác nhau,
từ đó tính toán trung bình, độ lệch chuẩn và khoảng tin cậy (95%) cho các chỉ số RMSE, MAE, và R².
# Thực hiện bootstrap evaluation cho các mô hình
print("=== BOOTSTRAP EVALUATION (100 mẫu bootstrap) ===")
bootstrap_results = []
for name, model in models_cv.items():
print(f"\n--- Đánh giá {name} ---")
result = bootstrap_evaluation(model, X_train, y_train, n_bootstrap=100)
result['model_name'] = name
bootstrap_results.append(result)
# In kết quả với khoảng tin cậy
print(f"\nKết quả Bootstrap cho {name}:")
print(f"RMSE: {result['rmse_mean']:.2f} ± {result['rmse_std']:.2f}")
print(f" Khoảng tin cậy 95%: [{result['rmse_ci_lower']:.2f}, {result['rmse_ci_upper']:.2f}]")
print(f"MAE: {result['mae_mean']:.2f} ± {result['mae_std']:.2f}")
print(f" Khoảng tin cậy 95%: [{result['mae_ci_lower']:.2f}, {result['mae_ci_upper']:.2f}]")
print(f"R²: {result['r2_mean']:.3f} ± {result['r2_std']:.3f}")
print(f" Khoảng tin cậy 95%: [{result['r2_ci_lower']:.3f}, {result['r2_ci_upper']:.3f}]")
Hiệu suất của các mô hình dự đoán giá nhà khi áp dụng Bootstrap
| Model | RMSE_Mean | RMSE_Std | RMSE_CI_Lower | RMSE_CI_Upper | MAE_Mean | MAE_Std | MAE_CI_Lower | MAE_CI_Upper | R2_Mean | R2_Std | R2_CI_Lower | R2_CI_Upper |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Ridge | 27266.044575 | 1855.150809 | 24802.534019 | 31450.863501 | 16190.176010 | 698.250584 | 14953.409267 | 17485.832076 | 0.876985 | 0.017063 | 0.837074 | 0.898679 |
| Lasso | 28552.157081 | 3897.605066 | 23698.435723 | 36028.516942 | 15592.303726 | 644.058386 | 14569.703852 | 16947.399271 | 0.863226 | 0.038038 | 0.786185 | 0.907499 |
| ElasticNet Grid Search | 28821.310202 | 670.156811 | 27649.585500 | 30029.836467 | 17636.012970 | 532.213016 | 16764.929796 | 18699.025334 | 0.863111 | 0.006365 | 0.851470 | 0.874082 |
| ElasticNet Random Search | 28868.312177 | 547.811991 | 27885.627617 | 30152.242351 | 17789.177265 | 513.295947 | 16939.310238 | 18851.542637 | 0.862688 | 0.005235 | 0.850256 | 0.871923 |
| Huber Regression | 28994.638796 | 619.833386 | 27766.958443 | 30103.887333 | 14864.873860 | 215.228257 | 14476.437306 | 15224.407535 | 0.861471 | 0.005915 | 0.850737 | 0.873011 |
| Linear Regression | 28709.506760 | 4258.075789 | 23075.776691 | 36329.836464 | 15779.901354 | 727.514596 | 14267.476635 | 17203.749550 | 0.861258 | 0.041685 | 0.782610 | 0.912296 |
| RANSAC Regression | 39008.853963 | 5132.696767 | 33659.472744 | 50712.773799 | 18860.807226 | 1804.478619 | 16410.121217 | 23214.394674 | 0.745030 | 0.074705 | 0.575740 | 0.813386 |
| Quantile Regression | 79670.629121 | 509.391360 | 78801.904504 | 80887.999894 | 55036.708630 | 49.843985 | 54997.128767 | 55165.402740 | -0.045496 | 0.013391 | -0.077646 | -0.022778 |
Table 4: Bảng kết quả so sánh hiệu suất của các mô hình trong bài toán dự đoán giá nhà khi có kỹ thuật đánh giá mô hình nâng cao Bootstrap
2.3. Tổng hợp kết quả so sánh
a. Xếp hạng hiệu suất các mô hình khi dùng phương pháp Bootstrap
| 🏆 Hạng | Mô hình | RMSE Mean | MAE Mean | R² Mean | Nhận xét ngắn gọn |
|---|---|---|---|---|---|
| 🥇 | Ridge Regression | 27,266 | 16,190 | 0.877 | Hiệu năng tốt nhất, ổn định và tổng quát cao |
| 🥈 | Lasso Regression | 28,552 | 15,592 | 0.863 | Hiệu năng cao, nhưng dao động lớn hơn Ridge |
| 🥉 | ElasticNet (Grid/Random) | ~28,800 | ~17,700 | ~0.863 | Ổn định, gần tương đương Lasso |
| 4️⃣ | Huber Regression | 28,995 | 14,865 | 0.861 | R² ổn định, RMSE hơi cao hơn Ridge |
| 5️⃣ | Linear Regression | 28,709 | 15,780 | 0.861 | Hiệu năng khá nhưng độ biến động cao |
| 6️⃣ | RANSAC Regression | 39,009 | 18,861 | 0.745 | Sai số cao, không ổn định |
| 🚫 | Quantile Regression | 79,671 | 55,037 | -0.046 | Hiệu năng rất kém, không phù hợp |
b. Phân tích chi tiết theo nhóm mô hình
i. Mô hình tuyến tính có regularization (Ridge, Lasso, ElasticNet)
- Ridge Regression đạt RMSE thấp nhất (27,266) và $R^2$ cao nhất (0.877), đồng thời có độ lệch chuẩn nhỏ (≈1,855) → ổn định và đáng tin cậy nhất → Khoảng tin cậy hẹp (RMSE 24,803 – 31,451) cho thấy mô hình tổng quát tốt và ít phụ thuộc vào mẫu huấn luyện.
- Lasso Regression cho hiệu năng gần tương đương Ridge ($R^2$ = 0.863) nhưng độ lệch chuẩn cao hơn, nên kém ổn định hơn.
- ElasticNet (Grid & Random) có kết quả gần như trùng nhau, chứng tỏ quá trình tìm siêu tham số ổn định và ít khác biệt giữa hai cách tìm kiếm.
ii. Mô hình robust (Huber, RANSAC)
- Huber Regression đạt RMSE ≈ 28,995, $R^2$ ≈ 0.861, khá gần Ridge nhưng MAE thấp nhất (14,865) → phù hợp khi dữ liệu có nhiễu nhỏ.
- RANSAC Regression có độ lệch chuẩn lớn, khoảng tin cậy rộng (RMSE 33,659 – 50,713) → mô hình không ổn định, nhạy với phân phối mẫu bootstrap.
iii. Mô hình tuyến tính cơ bản và phi tuyến
- Linear Regression có hiệu năng tương đương Huber, nhưng $R^2$ dao động mạnh (±0.04) → nhạy cảm với nhiễu và phân phối mẫu.
- Quantile Regression tiếp tục cho R² âm, RMSE cao (~80k) → không phù hợp với mục tiêu dự báo trung bình của bài toán.
c. Độ tin cậy và biến động mô hình
| Mô hình | Độ ổn định (Std thấp) | Độ tin cậy (CI hẹp) | Ghi chú |
|---|---|---|---|
| Ridge Regression | Tốt | Tốt | Ổn định nhất, sai số nhỏ |
| ElasticNet (Grid/Random) | Tốt | Tốt | Hiệu năng gần tương đương Ridge |
| Huber Regression | Tốt | Trung bình | Ổn định nhưng RMSE hơi cao hơn Ridge |
| Lasso Regression | Trung bình | Trung bình | Dao động cao hơn, có nguy cơ overfitting |
| Linear Regression | Trung bình | Kém | Biến động mạnh, ít ổn định |
| RANSAC / Quantile | Kém | Kém | Hiệu năng thấp, không đáng tin cậy |
2.4. Trực quan hóa kết quả Bootstrap
Sau khi tổng hợp bảng kết quả định lượng, chúng ta có thể đi xa hơn bằng cách trực quan hóa sự thay đổi của các metric trong quá trình Bootstrap.
Biểu đồ này giúp chúng ta nhìn thấy mức độ dao động của từng chỉ số (RMSE, MAE, R²) giữa các lần lấy mẫu, và so sánh trực tiếp giữa các mô hình.
# Visualize bootstrap results
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
# RMSE distribution
for i, result in enumerate(bootstrap_results):
axes[0].hist(result['bootstrap_rmse'], alpha=0.6, label=result['model_name'], bins=20)
axes[0].set_title('Phân phối RMSE từ Bootstrap', fontsize=14)
axes[0].set_xlabel('RMSE')
axes[0].set_ylabel('Tần suất')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# MAE distribution
for i, result in enumerate(bootstrap_results):
axes[1].hist(result['bootstrap_mae'], alpha=0.6, label=result['model_name'], bins=20)
axes[1].set_title('Phân phối MAE từ Bootstrap', fontsize=14)
axes[1].set_xlabel('MAE')
axes[1].set_ylabel('Tần suất')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
# R² distribution
for i, result in enumerate(bootstrap_results):
axes[2].hist(result['bootstrap_r2'], alpha=0.6, label=result['model_name'], bins=20)
axes[2].set_title('Phân phối R² từ Bootstrap', fontsize=14)
axes[2].set_xlabel('R²')
axes[2].set_ylabel('Tần suất')
axes[2].legend()
axes[2].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Kết quả trực quan
Biểu đồ sẽ hiển thị ba phần:
- Biểu đồ trái: Phân phối RMSE của các mô hình.
- Biểu đồ giữa: Phân phối MAE.
- Biểu đồ phải: Phân phối R².
Mỗi màu đại diện cho một mô hình (Linear, Ridge, Lasso,...).
Các histogram (biểu đồ tần suất) cho thấy mức độ biến động của metric khi dữ liệu được lấy mẫu lại nhiều lần.

Hình 1: Biểu đồ trực quan hóa sự thay đổi của các metric trong quá trình Bootstrap
Cách đọc biểu đồ
- Đỉnh của biểu đồ (vị trí tập trung nhất) là giá trị trung bình của metric.
- Biểu đồ càng hẹp và nhọn → mô hình càng ổn định (ít thay đổi giữa các lần bootstrap).
- Biểu đồ càng phẳng và rộng → mô hình càng dao động nhiều, độ tin cậy thấp hơn.
3. TimeSeriesSplit cho dữ liệu có tính thời gian
Không phải mọi bộ dữ liệu đều có thể chia ngẫu nhiên. Khi chúng ta làm việc với dữ liệu có yếu tố thời gian – như giá nhà theo năm, giá cổ phiếu theo tháng, hoặc nhiệt độ theo ngày – thì thứ tự thời gian rất quan trọng. Nếu chúng ta chia dữ liệu ngẫu nhiên (như KFold hay Bootstrap), mô hình có thể “nhìn thấy tương lai” trong quá trình huấn luyện, gây ra hiện tượng data leakage (rò rỉ dữ liệu).
Để khắc phục điều đó, chúng ta dùng TimeSeriesSplit – một kỹ thuật chia dữ liệu được thiết kế riêng cho chuỗi thời gian.
3.1. TimeSeriesSplit là gì?
TimeSeriesSplit chia dữ liệu theo thứ tự thời gian tự nhiên, đảm bảo rằng:
- Mô hình chỉ học từ dữ liệu quá khứ để dự đoán tương lai.
- Dữ liệu không bị xáo trộn (shuffle = False), giúp giữ nguyên trình tự thời gian.
- Phù hợp với các bài toán có yếu tố thời gian như: dự đoán giá nhà, dự đoán doanh số, dự báo thời tiết…
Ví dụ: trong bài toán House Price, chúng ta có thể dùng YearBuilt làm biến đại diện cho thời gian (proxy for time), và áp dụng TimeSeriesSplit để mô hình dự đoán giá nhà theo xu hướng xây dựng qua từng năm.
Cách hoạt động
Giả sử chúng ta có dữ liệu gồm 10 năm (2010–2019), và chúng ta dùng n_splits=5.
Thì TimeSeriesSplit sẽ chia dữ liệu như sau:
| Lượt chia | Tập huấn luyện | Tập kiểm tra |
|---|---|---|
| 1 | 2010–2014 | 2015 |
| 2 | 2010–2015 | 2016 |
| 3 | 2010–2016 | 2017 |
| 4 | 2010–2017 | 2018 |
| 5 | 2010–2018 | 2019 |
→ Dữ liệu huấn luyện luôn chỉ nằm trước dữ liệu kiểm tra, không có trường hợp “học trước – nhìn sau”.
3.2. Chuẩn bị dữ liệu cho TimeSeriesSplit
Điểm quan trọng nhất khi dùng TimeSeriesSplit là: dữ liệu phải được sắp xếp theo trình tự thời gian.
Vì mô hình sẽ luôn “học quá khứ → dự đoán tương lai”, nên chúng ta không thể đưa dữ liệu vào một cách lộn xộn như khi dùng KFold.
Trong bài toán dự đoán giá nhà, chúng ta coi YearBuilt (năm xây dựng của căn nhà) như một proxy cho thời gian. Ý tưởng: nhà xây càng về sau thì thị trường, vật liệu, thiết kế, khu vực xung quanh… đều thay đổi, nên giá và đặc điểm nhà cũng thay đổi theo thời gian.
Vậy nên trước khi áp dụng TimeSeriesSplit, chúng ta cần:
- Sắp xếp dữ liệu theo YearBuilt để mô phỏng dòng thời gian.
- Tách SalePrice (giá bán) ra làm biến mục tiêu y_sorted.
- Biến đổi và làm sạch dữ liệu đầu vào (X_sorted) giống đúng quy trình tiền xử lý mà chúng ta áp dụng trước đó:
- xử lý giá trị thiếu,
- mã hóa biến phân loại (one-hot encode),
- chuẩn hóa các cột số (scale MinMax),
- ghép tất cả lại thành ma trận đầu vào cuối cùng.
Toàn bộ bước chuẩn bị đó được thực hiện bằng đoạn code sau:
# Chuẩn bị dữ liệu cho TimeSeriesSplit
# Sử dụng YearBuilt để sắp xếp dữ liệu theo thời gian
house_df_sorted = house_df.sort_values('YearBuilt').reset_index(drop=True)
# Tạo lại features cho dữ liệu đã sắp xếp
# Lưu ý: Các cột ["Id","Alley","PoolQC","Fence","MiscFeature"] đã bị xóa trong cell 11
# nên không cần xóa lại nữa
house_df_sorted_processed = house_df_sorted.copy()
# Tách features và target
y_sorted = house_df_sorted_processed['SalePrice'].values
X_sorted_df = house_df_sorted_processed.drop(['SalePrice'], axis=1)
# Xử lý categorical và numerical features
num_cols_sorted = [col for col in X_sorted_df.columns if X_sorted_df[col].dtype in ["float64", "int64"]]
cat_cols_sorted = [col for col in X_sorted_df.columns if X_sorted_df[col].dtype not in ["float64", "int64"]]
# Fill missing values
X_sorted_df[cat_cols_sorted] = X_sorted_df[cat_cols_sorted].fillna("none")
# One-hot encode categorical columns
encoder_sorted = OneHotEncoder(handle_unknown="ignore", sparse_output=False)
encoder_sorted.fit(X_sorted_df[cat_cols_sorted])
encoded_cols_sorted = list(encoder_sorted.get_feature_names_out(cat_cols_sorted))
X_sorted_df[encoded_cols_sorted] = encoder_sorted.transform(X_sorted_df[cat_cols_sorted])
# Impute numerical features
imputer_sorted = SimpleImputer()
X_sorted_df[num_cols_sorted] = imputer_sorted.fit_transform(X_sorted_df[num_cols_sorted])
# Scale numerical features
scaler_sorted = MinMaxScaler()
X_sorted_num_features = scaler_sorted.fit_transform(X_sorted_df[num_cols_sorted])
# Combine features
X_sorted = np.hstack([X_sorted_num_features, X_sorted_df[encoded_cols_sorted].values])
print(f"Dữ liệu đã sắp xếp theo YearBuilt:")
print(f"Kích thước: {X_sorted.shape}")
print(f"Năm xây dựng từ {house_df_sorted['YearBuilt'].min()} đến {house_df_sorted['YearBuilt'].max()}")
Dữ liệu đã sắp xếp theo YearBuilt:
Kích thước: (1460, 286)
Năm xây dựng từ 1872 đến 2010
Giải thích các bước:
- house_df.sort_values('YearBuilt')
→ Sắp xếp toàn bộ các căn nhà theo thứ tự năm xây dựng từ cũ đến mới. Đây chính là “dòng thời gian”. - y_sorted
→ Chính là giá bán thật (SalePrice) tương ứng với từng căn nhà, sau khi đã được sắp xếp theo thời gian. - X_sorted_df
→ Tập đặc trưng đầu vào (đặc điểm căn nhà) dùng để dự đoán giá, đã bỏ cột SalePrice. - num_cols_sorted / cat_cols_sorted
→ Chia cột số và cột phân loại để xử lý đúng cách (điểm rất hay: chúng ta giữ pipeline preprocessing thống nhất như phần trước, nên kết quả TimeSeriesSplit sẽ công bằng). - One-hot encoding với OneHotEncoder
→ Biến các cột dạng chữ (ví dụ “RoofStyle = Gable”, “Neighborhood = CollgCr”) thành cột số 0/1 để mô hình hiểu. - SimpleImputer + MinMaxScaler
→ Điền giá trị thiếu rồi scale tất cả về cùng thang đo, giúp mô hình tuyến tính như Linear/Ridge/Lasso học tốt hơn. - np.hstack([...])
→ Ghép toàn bộ feature số đã scale + feature phân loại đã encode lại thành ma trận X_sorted, là input cuối cùng đưa vào model. - Cuối cùng, đoạn print(...) là để báo lại cho chúng ta biết:
- Có bao nhiêu mẫu sau khi sắp xếp,
- Khoảng năm xây dựng trải dài từ năm nào tới năm nào (như kiểu “dữ liệu có từ 1872 đến 2010”).
3.3. Trực quan hóa TimeSeriesSplit
Sau khi đã thực hiện TimeSeriesSplit để đánh giá mô hình, chúng ta có thể trực quan hóa cách mà dữ liệu được chia. Điều này giúp chúng ta kiểm tra xem việc chia dữ liệu có tuân thủ thứ tự thời gian và không gây rò rỉ dữ liệu (data leakage) hay không.
Đoạn code vẽ biểu đồ TimeSeriesSplit
# Định nghĩa TimeSeriesSplit
ts_split = TimeSeriesSplit(n_splits=5)
print("=== TIMESERIESSPLIT CROSS-VALIDATION ===")
print("Lưu ý: TimeSeriesSplit đảm bảo không có data leakage bằng cách chỉ sử dụng dữ liệu quá khứ để dự đoán tương lai")
# Visualize TimeSeriesSplit
fig, ax = plt.subplots(figsize=(12, 6))
for i, (train_idx, test_idx) in enumerate(ts_split.split(X_sorted)):
train_years = house_df_sorted.iloc[train_idx]['YearBuilt']
test_years = house_df_sorted.iloc[test_idx]['YearBuilt']
ax.scatter(train_years, [i] * len(train_years), alpha=0.6, label=f'Train Fold {i+1}', s=20)
ax.scatter(test_years, [i] * len(test_years), alpha=0.8, label=f'Test Fold {i+1}', s=20)
ax.set_xlabel('Year Built')
ax.set_ylabel('Fold')
ax.set_title('TimeSeriesSplit: Phân chia Train/Test theo thời gian')
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Kết quả trực quan
Biểu đồ hiển thị các điểm tương ứng với năm xây dựng (YearBuilt) trên trục X và số lần chia (fold) trên trục Y.
Mỗi vòng chia (fold) có hai nhóm điểm:
- Train Fold (quá khứ): phần dữ liệu mà mô hình được huấn luyện.
- Test Fold (tương lai): phần dữ liệu dùng để kiểm tra dự đoán.
Lưu ý: TimeSeriesSplit đảm bảo không có data leakage bằng cách chỉ sử dụng dữ liệu quá khứ để dự đoán tương lai

Hình 2: Biểu đồ trực quan hóa TimeSeriesSplit
Khi nhìn vào biểu đồ, chúng ta sẽ thấy rằng:
- Dữ liệu train luôn nằm trước dữ liệu test theo thời gian → đúng tinh thần "học từ quá khứ để đoán tương lai”.
- Không có sự xáo trộn thứ tự thời gian → đảm bảo không có data leakage.
3.4. Tổng hợp kết quả đánh giá với TimeSeriesSplit
Ở các bước trước, chúng ta đã:
- Chuẩn bị dữ liệu theo trục thời gian (YearBuilt),
- Sử dụng TimeSeriesSplit để đảm bảo mô hình chỉ học từ quá khứ để dự đoán tương lai,
- Và trực quan hóa cách các phần train/test được chia theo thời gian.
Bây giờ, chúng ta sẽ đo lường hiệu suất thực tế của từng mô hình (Linear Regression, Ridge, Lasso,...) dưới chiến lược TimeSeriesSplit, rồi gom lại thành một bảng để so sánh.
Mục tiêu ở đây giống như khi chúng ta đánh giá mô hình bằng KFold và RepeatedKFold:
- Tính các chỉ số lỗi như RMSE và MAE,
- Tính độ giải thích dữ liệu R²,
- Ghi nhận cả trung bình và độ lệch chuẩn giữa các lần chia thời gian.
a. Xếp hạng hiệu suất các mô hình khi dùng phương pháp TimeSeriesSplit
| Model | CV_Method | RMSE_Mean | RMSE_Std | MAE_Mean | MAE_Std | R2_Mean | R2_Std |
|---|---|---|---|---|---|---|---|
| ElasticNet Grid Search | TimeSeriesSplit | 36206.041 | 10189.576 | 22231.480 | 4586.081 | 0.663 | 0.059 |
| ElasticNet Random Search | TimeSeriesSplit | 36302.829 | 10236.840 | 22306.296 | 4648.021 | 0.662 | 0.058 |
| Ridge | TimeSeriesSplit | 36598.225 | 11609.889 | 21930.309 | 3566.330 | 0.644 | 0.110 |
| Huber Regression | TimeSeriesSplit | 37295.396 | 9903.249 | 22767.038 | 3675.212 | 0.637 | 0.078 |
| Linear Regression | TimeSeriesSplit | 44971.750 | 15249.643 | 28157.702 | 9222.666 | 0.330 | 0.557 |
| Lasso | TimeSeriesSplit | 45300.338 | 15077.355 | 28041.442 | 9021.820 | 0.315 | 0.580 |
| Quantile Regression | TimeSeriesSplit | 85651.698 | 36617.775 | 62028.515 | 29519.229 | -0.717 | 0.506 |
b. Phân tích chi tiết theo nhóm mô hình
i. Hiệu suất tổng thể (RMSE, MAE, R²):
- ElasticNet Grid Search và ElasticNet Random Search đạt kết quả tốt nhất, với:
- RMSE ≈ 36,200 → sai số trung bình gốc nhỏ nhất.
- MAE ≈ 22,200 → sai số tuyệt đối trung bình thấp nhất.
- R² ≈ 0.66 → mô hình giải thích được khoảng 66% biến thiên của dữ liệu, khá tốt trong bối cảnh chuỗi thời gian.
-
Ridge Regression đứng thứ ba, với R² ≈ 0.644 — gần tương đương ElasticNet, cho thấy Ridge hoạt động ổn định khi có nhiều đặc trưng tuyến tính.
-
Huber Regression cũng cho kết quả khá tốt (R² ≈ 0.637), thể hiện khả năng chống nhiễu, nhưng không vượt trội hơn ElasticNet hay Ridge.
-
Linear Regression và Lasso có R² rất thấp (~0.33 và 0.31), chứng tỏ dễ bị overfitting hoặc underfitting trong bài toán này.
→ Lasso có thể đã loại bỏ quá nhiều đặc trưng. -
Quantile Regression cho kết quả rất kém (R² âm, -0.717) → mô hình không phù hợp cho bài toán dự báo chuỗi thời gian này.
ii. Độ ổn định (RMSE_Std và R²_Std):
- ElasticNet (cả Grid & Random) có R²_Std nhỏ nhất (~0.058–0.059) → ổn định nhất giữa các lần chia TimeSeriesSplit.
- Ridge và Huber có độ lệch chuẩn cao hơn một chút → kết quả dao động nhiều hơn theo thời gian.
- Linear, Lasso, Quantile có R²_Std rất lớn (>0.5) → mô hình không ổn định, hiệu suất thay đổi mạnh giữa các giai đoạn.
iii. So sánh ElasticNet Grid vs Random Search:
- Kết quả gần như tương đương, chênh lệch chỉ vài phần nghìn ở RMSE và R².
- Điều này cho thấy Random Search hiệu quả không kém Grid Search, nhưng tiết kiệm thời gian đáng kể.
c. Hiệu suất và nhận xét các mô hình khi sử dụng TimeSeriesSplit
| Xếp hạng | Mô hình | Hiệu suất | Nhận xét |
|---|---|---|---|
| 🥇 1 | ElasticNet (Grid/Random) | R² ~0.66, RMSE ~36k | Cân bằng giữa bias và variance, ổn định nhất |
| 🥈 2 | Ridge Regression | R² ~0.64 | Hiệu quả, đơn giản, nhưng kém hơn ElasticNet chút ít |
| 🥉 3 | Huber Regression | R² ~0.64 | Ổn định, tốt khi có ngoại lệ |
| 4 | Linear Regression | R² ~0.33 | Kém, chưa xử lý tốt mối quan hệ phức tạp |
| 5 | Lasso Regression | R² ~0.31 | Loại bỏ quá nhiều đặc trưng |
| 🚫 6 | Quantile Regression | R² < 0 | Không phù hợp cho dự báo chuỗi thời gian |
III. Cải tiến 3: Tạo các đặc trưng tổng hợp và thời gian mới
1. Giới thiệu về Feature Engineering
Feature Engineering (Kỹ thuật tạo đặc trưng) là một nghệ thuật và khoa học của việc trích xuất và tạo ra các biến (đặc trưng) mới từ dữ liệu thô để cải thiện hiệu suất mô hình. Nếu dữ liệu thô là các nguyên liệu, thì feature engineering giống như việc một đầu bếp sơ chế và kết hợp chúng để tạo ra một món ăn có hương vị (tín hiệu) đậm đà nhất.
Mục tiêu chính là:
- Khuếch đại tín hiệu: Làm nổi bật các mối quan hệ quan trọng với biến mục tiêu.
- Giảm nhiễu: Loại bỏ thông tin không liên quan.
- Đáp ứng giả định mô hình: Nhiều mô hình (như Hồi quy Tuyến tính) hoạt động tốt nhất khi dữ liệu tuân theo các giả định nhất định (ví dụ: phân phối chuẩn, quan hệ tuyến tính).
2. Phân tích Thử nghiệm 1: Đặc trưng Đa thức Tự động (Polynomial Features)
Trước khi sử dụng Feature Engineering, chúng ta đã thử một phương pháp tự động sử dụng PolynomialFeatures(degree=2, interaction_only=True) để tạo ra các đặc trưng tương tác (ví dụ: OverallQual * GrLivArea).
- Kết quả: Phương pháp này chỉ cải thiện mô hình một chút (Huber R² tăng từ 0.901 lên 0.907) nhưng lại gây ra hiện tượng overfitting (quá khớp) nghiêm trọng cho mô hình Hồi quy Tuyến tính cơ bản (Test R² giảm mạnh xuống 0.56).
- Kết luận: Việc tạo đặc trưng tự động, "mù quáng" đã vô tình tạo ra quá nhiều nhiễu và các vấn đề về đa cộng tuyến (multicollinearity).
3. Phân tích Thử nghiệm 2: Cải tiến Chuyên sâu (Áp dụng kỹ thuật Feature Engineering)
3.1. Biến đổi Biến Mục tiêu (Log Transform)
Đây là thay đổi quan trọng nhất. Phân tích ở Mục 2 cho thấy SalePrice có phân phối lệch phải (right-skewed).
- Kỹ thuật: Áp dụng phép biến đổi logarit (np.log1p) cho biến mục tiêu y.
- Lợi ích:
- Chuẩn hóa Phân phối: Đưa
SalePricevề phân phối gần chuẩn hơn. - Ổn định Phương sai: Giúp các mô hình tuyến tính (Ridge, Lasso) hoạt động hiệu quả hơn, vì chúng giả định mối quan hệ tuyến tính và phần dư có phương sai không đổi.
- Giảm ảnh hưởng của Outliers: Các giá nhà quá cao được "kéo" về gần hơn với phần còn lại của dữ liệu.
- Chuẩn hóa Phân phối: Đưa
3.2. Tạo Đặc trưng Tổng hợp (Synthetic Features)
Thay vì tạo ra hàng trăm đặc trưng tương tác, chúng ta tập trung vào hai đặc trưng tổng hợp có ý nghĩa cao:
- TotalSF (Tổng Diện tích):
- Công thức: GrLivArea + TotalBsmtSF + 1stFlrSF + 2ndFlrSF
- Ý nghĩa: Đây là một đặc trưng tổng hợp (synthetic feature) cực kỳ mạnh mẽ. Thay vì để mô hình tự tìm ra mối liên hệ của 4 cột diện tích riêng lẻ, chúng ta gộp chúng lại thành một biến duy nhất thể hiện "tổng không gian sống", một yếu tố dự đoán giá trị hơn nhiều.
Quality_SF_Index(Chỉ số Chất lượng - Diện tích):- Công thức:
OverallQual * TotalSF - Ý nghĩa: Đây là một đặc trưng tương tác (interaction feature) thông minh. Nó nắm bắt được mối quan hệ phi tuyến tính: giá trị của một mét vuông đất không giống nhau ở mọi ngôi nhà. Một mét vuông trong ngôi nhà chất lượng 10 (
OverallQual=10) có giá trị hơn nhiều so với một mét vuông trong ngôi nhà chất lượng 3. Đặc trưng này đã mã hóa trực tiếp kiến thức nghiệp vụ này cho mô hình.
- Công thức:
4. So sánh hiệu suất của các mô hình khi dùng Polynomial Features và Feature Engineering
| Model | Test_RMSE (Polynomial) | Test_RMSE (Feature Eng.) | Test_R2 (Polynomial) | Test_R2 (Feature Eng.) |
|---|---|---|---|---|
| RANSAC Regression | 22,478 | 1,221,312 | 0.9276 | -211.9245 |
| Lasso | 22,898 (α=0.0005) / 84,462 (default) | 34,413 | 0.9249 (α=0.0005) / -0.0223 (default) | 0.8310 |
| Huber Regression | 23,026 | 25,430 | 0.9240 | 0.9077 |
| Linear Regression | 23,486 | 55,212 | 0.9210 | 0.5648 |
| Ridge | 24,108 (default) / 26,775 (α=10) | 27,767 | 0.9167 (default) / 0.8973 (α=10) | 0.8899 |
| ElasticNet Grid Search | 26,909 | 28,432 | 0.8962 | 0.8846 |
| ElasticNet Random Search | 27,504 | 28,493 | 0.8916 | 0.8841 |
| Quantile Regression | 84,872 | 84,787 | -0.0323 | -0.0262 |
Table 4: Bảng so sánh hiệu suất của các mô hình khi dùng Polynomial Features và Feature Engineering
4.1. Phân tích so sánh tổng hợp
a. Mức độ chính xác (Test_RMSE ↓, Test_R2 ↑)
-
Khi dùng Polynomial Features, hầu hết mô hình đều có RMSE thấp hơn và R² cao hơn → hiệu suất vượt trội.
-
Các mô hình hưởng lợi rõ nhất:
- Linear Regression: R² tăng từ 0.56 → 0.92, RMSE giảm hơn 50% → cho thấy mối quan hệ phi tuyến mạnh.
- Ridge, ElasticNet, Huber: cải thiện vừa phải, chứng tỏ các đặc trưng phi tuyến giúp tăng khả năng khái quát.
- Quantile Regression không cải thiện đáng kể (vẫn rất yếu).
b. Mức độ ổn định
-
Feature Engineering mang lại kết quả ổn định hơn với các mô hình tuyến tính nhẹ (ElasticNet, Ridge), nhưng thấp hơn về độ chính xác.
-
Polynomial Features có vẻ “fit” rất tốt dữ liệu → có nguy cơ overfitting nhẹ, đặc biệt nếu bậc đa thức cao.
c. Trường hợp ngoại lệ
-
RANSAC Regression cực kỳ bất ổn trong Feature Engineering (R² = -211) → có thể do đặc trưng bị nhiễu hoặc scale không phù hợp.
-
Trong khi đó, với Polynomial Features, RANSAC lại là mô hình tốt nhất (R² ≈ 0.93).
4.2. Kết luận tổng hợp
| Nhận xét | Polynomial Features | Feature Engineering |
|---|---|---|
| Hiệu suất trung bình (R²) | ~0.91 | ~0.85 |
| Sai số trung bình (RMSE) | ~25,000 | ~35,000 |
| Mô hình tốt nhất | RANSAC / Lasso (α=0.0005) | Huber Regression |
| Ổn định nhất | ElasticNet / Ridge | Ridge / Huber |
| Gợi ý | Phù hợp nếu có regularization tốt | Phù hợp khi muốn mô hình đơn giản, ít rủi ro overfit |
V. Đánh giá hiệu suất các mô hình khi kết hợp cả 3 cải tiến: Sử dụng các kỹ thuật điều chuẩn, các kỹ thuật đánh giá nâng cao và kỹ thuật lựa chọn đặc trưng
1. Bảng tổng hợp kết quả theo từng mô hình
| Model | RMSE_Mean (No FE) | R2_Mean (No FE) | RMSE_Mean (FE) | R2_Mean (FE) | Nhận xét tổng quan |
|---|---|---|---|---|---|
| Ridge Regression | 27,266 (Bootstrap) → 34,158 (KFold) → 36,598 (TimeSeriesSplit) | 0.877 → 0.786 → 0.644 | ≈ 27,767 | ≈ 0.890 | Feature Engineering giúp Ridge giảm RMSE nhẹ và tăng R² đáng kể; mô hình hoạt động tốt và ổn định khi có đặc trưng mở rộng. |
| Lasso Regression | 28,552 (Bootstrap) → 38,456 (KFold) → 45,300 (TimeSeriesSplit) | 0.863 → 0.724 → 0.315 | ≈ 34,413 | ≈ 0.831 | Với FE, hiệu suất Lasso cải thiện mạnh; chứng tỏ các đặc trưng được chọn lọc tốt giúp mô hình tuyến tính đơn giản hóa và chính xác hơn. |
| ElasticNet (Grid Search) | 28,821 (Bootstrap) → 33,009 (KFold) → 36,206 (TimeSeriesSplit) | 0.863 → 0.811 → 0.663 | ≈ 28,432 | ≈ 0.885 | ElasticNet hưởng lợi rõ rệt từ FE: giảm sai số và tăng R²; cho thấy khả năng cân bằng giữa regularization L1-L2 rất hiệu quả khi đặc trưng được tối ưu. |
| ElasticNet (Random Search) | 28,868 (Bootstrap) → 33,054 (KFold) → 36,303 (TimeSeriesSplit) | 0.863 → 0.810 → 0.662 | ≈ 28,493 | ≈ 0.884 | Hiệu quả tương tự Grid Search, chứng minh rằng Random Search đủ tốt trong tuning hyperparameter; kết hợp FE cho kết quả tối ưu và ổn định. |
| Huber Regression | 28,995 (Bootstrap) → 30,883 (KFold) → 37,295 (TimeSeriesSplit) | 0.861 → 0.826 → 0.637 | ≈ 25,430 | ≈ 0.908 | Rõ ràng Huber Regression cải thiện mạnh với FE: RMSE giảm ~15%, R² tăng đáng kể → mô hình này rất phù hợp cho dữ liệu có nhiễu hoặc ngoại lệ. |
| Linear Regression | 28,709 (Bootstrap) → 38,065 (KFold) → 44,972 (TimeSeriesSplit) | 0.861 → 0.724 → 0.330 | ≈ 55,212 | ≈ 0.565 | Linear Regression bị giảm hiệu suất khi thêm FE, có thể do hiện tượng multicollinearity hoặc FE không tuyến tính không phù hợp với mô hình tuyến tính thuần túy. |
| RANSAC Regression | 39,009 (Bootstrap) → 35,658 (KFold) → 39,903 (RepeatedKFold) | 0.745 → 0.756 → 0.719 | ≈ 1,221,312 | ≈ -211.925 | Kết quả với FE cực kỳ kém (overfitting hoặc lỗi scale), cho thấy RANSAC không phù hợp khi dữ liệu có đặc trưng phức tạp. |
| Quantile Regression | 79,671 (Bootstrap) → 79,273 (KFold) → 85,652 (TimeSeriesSplit) | -0.045 → -0.046 → -0.717 | ≈ 84,787 | ≈ -0.026 | Gần như không thay đổi giữa hai trường hợp; đây không phải mô hình phù hợp cho dự báo chuỗi thời gian hay bài toán hồi quy này. |
2. Phân tích xu hướng hiệu suất
2.1. Hiệu quả của Feature Engineering
- Tác động tích cực rõ ràng lên các mô hình có regularization (Ridge, Lasso, ElasticNet, Huber).
Các mô hình này có khả năng tận dụng thêm đặc trưng mà không bị overfit mạnh. - Tác động tiêu cực hoặc không rõ ràng với các mô hình Linear và RANSAC.
Linear Regression dễ bị mất ổn định khi thêm nhiều đặc trưng có tương quan cao; RANSAC thì phản ứng nhạy cảm với scale và outlier. - Quantile Regression giữ nguyên xu hướng kém trong mọi trường hợp, cho thấy nó không phù hợp với bản chất của dữ liệu (nhiều biến liên tục, có xu hướng tuyến tính).
2.2 Ảnh hưởng của phương pháp đánh giá (CV Method)
- Bootstrap thường cho R² cao nhất và RMSE thấp nhất, chứng tỏ độ ổn định tốt hơn do tái lấy mẫu nhiều lần.
- KFold và RepeatedKFold cung cấp cái nhìn thực tế hơn, nhưng có sai số dao động lớn hơn.
- TimeSeriesSplit cho kết quả kém nhất ở hầu hết mô hình — điều này dễ hiểu vì dữ liệu chuỗi thời gian hạn chế khả năng xáo trộn và có thể chứa tính xu hướng rõ rệt. Điều này làm giảm khả năng mô hình học được đầy đủ các mối quan hệ phức tạp giữa các giai đoạn.
2.3. So sánh mức độ ổn định (R2_Std)
- Nhóm Bootstrap có R2_Std rất nhỏ (~0.005–0.04), cho thấy mức ổn định cao và mô hình tin cậy hơn.
- Nhóm TimeSeriesSplit có R2_Std lớn nhất (0.05–0.5), nghĩa là hiệu suất dao động mạnh giữa các giai đoạn, đặc biệt với Linear và Lasso.
- Các mô hình có R2_Std thấp nhất: ElasticNet (Bootstrap) và Huber (Bootstrap) → minh chứng cho độ vững vàng của các phương pháp regularization khi dữ liệu thay đổi.
3. Tổng hợp và Xếp hạng mô hình
| Xếp hạng | Mô hình | Không dùng FE | Dùng FE | Nhận xét |
|---|---|---|---|---|
| 🥇 1 | Huber Regression | R² ≈ 0.86 | R² ≈ 0.91 | Mô hình mạnh mẽ, ổn định, thích hợp cho dữ liệu nhiễu, cải thiện đáng kể khi có FE. |
| 🥈 2 | Ridge Regression | R² ≈ 0.88 | R² ≈ 0.89 | Cân bằng bias–variance tốt, hiệu quả khi thêm đặc trưng. |
| 🥉 3 | ElasticNet (Grid/Random) | R² ≈ 0.86 | R² ≈ 0.88 | Linh hoạt, hiệu suất cao và ổn định nhất giữa các phương pháp. |
| 4 | Lasso Regression | R² ≈ 0.86 → 0.31 (TSS) | R² ≈ 0.83 | Cải thiện rõ khi có FE, nhưng kém ổn định ở TimeSeriesSplit. |
| 5 | Linear Regression | R² ≈ 0.86 → 0.33 | R² ≈ 0.56 | Không tận dụng tốt FE, dễ bị nhiễu. |
| 6 | RANSAC Regression | R² ≈ 0.75 | R² ≈ -211 | Hoạt động tốt hơn khi không có FE; rất nhạy cảm với cấu trúc dữ liệu. |
| 🚫 7 | Quantile Regression | R² ≈ -0.05 → -0.7 | R² ≈ -0.03 | Không phù hợp với loại dữ liệu và bài toán. |
V. Kết luận: Từ Mô hình đến Giải pháp
Hành trình của dự án này, từ một mô hình hồi quy tuyến tính cơ sở đến một hệ thống dự đoán được tinh chỉnh phức tạp, đã khẳng định rằng hiệu suất vượt trội không nằm ở một thuật toán duy nhất, mà là kết quả của sự cộng hưởng giữa ba trụ cột cải tiến: Kỹ thuật Tạo Đặc trưng (Feature Engineering) có kiểm soát, Mô hình hóa (Modeling) nâng cao, và Đánh giá (Evaluation) toàn diện.
1. Đóng góp của Kỹ thuật Đặc trưng có Kiểm soát
Nỗ lực ban đầu khi áp dụng PolynomialFeatures đã cung cấp một bài học đắt giá: sự tự động hóa "mù quáng" có thể dẫn đến thảm họa. Mặc dù các đặc trưng tương tác (interaction-only) giúp mô hình Robust (như Huber) cải thiện nhẹ (Test $R^2$ đạt 0.908), nó lại khiến các mô hình tuyến tính tiêu chuẩn (Linear Regression) rơi vào tình trạng overfitting (quá khớp) nghiêm trọng ($R^2$ giảm còn 0.56) do "lời nguyền của số chiều".
Việc đánh giá có hệ thống này đã khẳng định sự cần thiết của việc kiểm soát quá trình sinh đặc trưng. Điều này đã tạo tiền đề cho thành công vượt trội, nơi chúng tôi chuyển sang các đặc trưng tổng hợp (TotalSF) và tương tác (Quality_SF_Index) dựa trên kiến thức nghiệp vụ (domain knowledge). Kết quả là $R^2$ đã tăng vọt lên đến 0.928 (với RANSAC), chứng minh rằng các đặc trưng được tạo ra một cách thông minh có khả năng "khuếch đại tín hiệu" hiệu quả hơn nhiều so với việc tạo ra chúng một cách cơ học.
2. Đóng góp của Mô hình hóa và Quy chuẩn hóa Nâng cao
Việc mở rộng kho vũ khí mô hình là một cải tiến then chốt thứ hai:
-
Elastic Net (với $\alpha$ trên thang log và tối ưu $l1\_ratio$) đã chứng minh giá trị của mình. Kết quả cho thấy, trong kịch bản "stress test" với TimeSeriesSplit, Elastic Net là mô hình ổn định nhất (R² ≈ 0.66), vượt xa Lasso (R² ≈ 0.31) nhờ khả năng dung hòa giữa việc chọn biến (L1) và ổn định hệ số (L2) khi đối mặt với dữ liệu có tính xu hướng thời gian.
-
Hồi quy Robust (Huber, RANSAC) là khám phá quan trọng nhất. Trong khi các mô hình tiêu chuẩn hoạt động tốt trên dữ liệu đã được làm sạch, Huber và RANSAC lại thống trị khi được kết hợp với Feature Engineering chuyên sâu. Điều này chứng tỏ rằng ngay cả sau khi biến đổi log, dữ liệu vẫn chứa các điểm ngoại lệ (outliers) mà các hàm mất mát tiêu chuẩn (như MSE) không thể xử lý hiệu quả. QuantileRegressor (với MAE/Pinball loss) thất bại (R² âm), cho thấy việc so sánh và loại trừ các mô hình không phù hợp cũng là một phần quan trọng của quá trình.
3. Đóng góp của Chiến lược Đánh giá Mô hình Toàn diện
Cải tiến thứ ba nằm ở việc áp dụng một bộ chiến lược xác thực (validation) đa dạng, giúp chúng ta nhìn thấy bức tranh toàn cảnh về hiệu suất mô hình:
-
KFold/RepeatedKFold cung cấp một ước tính đáng tin cậy về hiệu suất tổng quát. Kết quả cho thấy Huber (R² ≈ 0.835) và ElasticNet (R² ≈ 0.822) là những lựa chọn ổn định nhất khi dữ liệu được xáo trộn ngẫu nhiên.
-
Bootstrap không chỉ xác nhận kết quả của KFold mà còn cung cấp khoảng tin cậy (confidence interval). Các mô hình như Ridge và Huber cho thấy khoảng tin cậy hẹp (ví dụ: Ridge $R^2$ ≈ 0.877 $\pm$ 0.017), mang lại sự tự tin cao về tính ổn định của chúng.
-
TimeSeriesSplit hoạt động như một bài kiểm tra "thực tế" khắc nghiệt nhất, phơi bày điểm yếu của các mô hình khi phải dự đoán "tương lai" (nhà xây sau) từ "quá khứ" (nhà xây trước). Kết quả R² thấp hơn (cao nhất là 0.66) là một lời nhắc nhở quan trọng về sự nguy hiểm của hiện tượng concept drift (thay đổi xu hướng dữ liệu theo thời gian).
4. Tổng kết
Cuối cùng, dự án này đã chứng minh rằng một đường ống (pipeline) học máy thành công không chỉ dựa vào một mô hình tốt, mà phải là sự kết hợp của: (1) Kỹ thuật tạo đặc trưng thông minh và có kiểm soát, (2) Việc lựa chọn mô hình hồi quy (đặc biệt là các mô hình Robust và Regularized) phù hợp với đặc tính của dữ liệu, và (3) Một chiến lược đánh giá nghiêm ngặt, đa chiều (bao gồm cả Bootstrap và TimeSeriesSplit) để đảm bảo kết quả không chỉ chính xác mà còn ổn định và đáng tin cậy.
Chưa có bình luận nào. Hãy là người đầu tiên!