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

bootstrap.png

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

tải xuống (2).png

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 SalePrice về 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.

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.

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.