1. Khám phá Dữ liệu (EDA)
Bộ dữ liệu của chúng ta, Ames Housing, được thu thập tại Ames, Iowa, mô tả chi tiết đặc điểm của các căn nhà được bán từ năm 2006 đến 2010.
- Quy mô: 1460 mẫu và 81 đặc trưng
- Mục tiêu: Dự đoán
SalePrice - Độ phức tạp: Dữ liệu bao gồm cả đặc trưng số và hạng mục.
1.1. Phân tích Biến mục tiêu: SalePrice
Biểu đồ phân phối cho thấy SalePrice bị lệch phải (right-skewed). Hầu hết các căn nhà tập trung ở mức giá thấp đến trung bình, và có một "đuôi dài" gồm các căn nhà rất đắt tiền.
Hình 1: Biểu đồ phân bố của SalePrice
Giải pháp: Biến đổi logarit (log(SalePrice)) giúp giảm độ lệch, làm phân phối chuẩn hơn.
1.2. Xử lý Dữ liệu Khuyết (Missing Data)
Một số cột có tỷ lệ thiếu cực cao như:
- PoolQC: 99.5%
- MiscFeature: 96.3%
- Alley: 93.8%
- Fence: 80.7%
Hình 2: Biểu đồ Missing Data trong Dataset
Chiến lược: Loại bỏ các cột có quá nhiều giá trị khuyết (>50%).
1.3. Ma trận Tương quan (Correlation Matrix)
Hình 3: Ma trận Tương quan
OverallQual(0.8) vàGrLivArea(0.7) tương quan mạnh vớiSalePrice.GarageCarsvàGarageAreatương quan 0.9 → đa cộng tuyến.
Giải pháp: Sử dụng Regularization (Ridge, Lasso) để ổn định trọng số.
1.4. Phân tích Ngoại lệ (Outlier Analysis)
Hình 4: Hình Boxplot của những feature quan trọng
Từ hình bên trên ta có thể thấy một số căn có diện tích quá lớn (GrLivArea) so với giá → Có thể loại bỏ hoặc kiểm tra lại vì gây sai lệch mô hình.
2. Tiền xử lý dữ liệu (Data Preprocessing)
Mục tiêu: Làm sạch và chuẩn hóa dữ liệu để đảm bảo mô hình học được đúng mối quan hệ thực, không bị nhiễu bởi outlier, giá trị thiếu hay thang đo khác nhau.
2.1. Xử lý Outlier
Dữ liệu thực tế thường có các giá trị ngoại lệ (outlier) — ví dụ nhà cực lớn hoặc cực đắt, gây ảnh hưởng mạnh đến mô hình tuyến tính.
Giải pháp: Dùng Winsorizing (capping) với phương pháp IQR (Interquartile Range) để thay thế các giá trị vượt quá ngưỡng 1.5×IQR bằng giá trị biên hợp lý.
# Outlier Handling using Capping (Winsorizing) with IQR for numerical columns
for col in num_cols:
Q1 = train_df[col].quantile(0.25)
Q3 = train_df[col].quantile(0.75)
IQR = Q3 - Q1
# Define bounds for capping
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# Apply capping to training data
train_df[col] = train_df[col].clip(lower=lower_bound, upper=upper_bound)
# Apply capping to testing data using bounds from training data
test_df[col] = test_df[col].clip(lower=lower_bound, upper=upper_bound)
Kết quả: Phân phối dữ liệu trở nên gọn hơn, giảm nhiễu và tránh việc mô hình bị "kéo" bởi các giá trị cực đoan.
2.2. Biến đổi Logarithm cho Dữ liệu Lệch (Skewed Data)
Nhiều biến số trong bộ dữ liệu (như GrLivArea, TotalBsmtSF) có phân phối lệch phải — gây khó khăn cho mô hình tuyến tính.
Giải pháp: Dùng log-transform (log1p) để làm phân phối chuẩn hơn, giúp mô hình học tốt hơn.
# Identify skewed numerical features
skewed_features = train_df[num_cols].skew().sort_values(ascending=False)
skewed_features = skewed_features[abs(skewed_features) > 0.5] # Consider features with absolute skewness > 0.5
print("Skewed numerical features:")
display(skewed_features)
# Apply log transformation to skewed features
for col in skewed_features.index:
# Use np.log1p which applies log(1+x) to handle potential zero values
train_df[col] = np.log1p(train_df[col])
test_df[col] = np.log1p(test_df[col])
Kết quả: Sau khi log-transform, các đặc trưng có phân phối gần chuẩn, giúp cải thiện hiệu suất mô hình và giảm lỗi RMSE.
2.3. Xử lý Missing Data
Một số cột có giá trị bị thiếu, ví dụ GarageYrBlt, MasVnrArea, hoặc LotFrontage.Việc điền giá trị thiếu (imputation) giúp mô hình học được đầy đủ thông tin mà không loại bỏ hàng dữ liệu.
Giải pháp:Dùng Iterative Imputer với mô hình Random Forest Regressor, giúp ước lượng giá trị bị thiếu dựa trên mối quan hệ phi tuyến giữa các đặc trưng.
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
from sklearn.ensemble import RandomForestRegressor
iterative_imputer = IterativeImputer(random_state=42, estimator=RandomForestRegressor(random_state=42))
train_df[num_cols] = iterative_imputer.fit_transform(train_df[num_cols])
test_df[num_cols] = iterative_imputer.transform(test_df[num_cols])
Kết quả: Dữ liệu không còn giá trị NaN, và các giá trị thay thế hợp lý hơn so với trung bình hay trung vị.
2.3. Feature Scaling
Để đảm bảo các biến có cùng thang đo (vì GrLivArea có thể hàng nghìn trong khi GarageCars chỉ vài đơn vị), ta cần chuẩn hóa dữ liệu.
Phương pháp: StandardScaler (chuẩn hóa z-score)
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
train_num_features = scaler.fit_transform(train_df[num_cols])
test_num_features = scaler.transform(test_df[num_cols])
Kết quả: Mỗi đặc trưng có giá trị trung bình = 0, độ lệch chuẩn = 1 → giúp mô hình hội tụ nhanh hơn, đặc biệt là với Ridge và Lasso.
3. Feature Engineering & PolynomialFeatures
Mục tiêu: Tạo thêm các đặc trưng mới (feature) có ý nghĩa thống kê để giúp mô hình học được các quan hệ phi tuyến tính giữa các biến đầu vào và SalePrice.
3.1. Feature Engineering
Từ việc phân tích tương quan và hiểu biết domain (bất động sản), ta tạo ra các biến tương tác và biến tổng hợp quan trọng:
# Combine some important numerical features
train_df["OverallQual_GrLivArea"] = train_df["OverallQual"] * train_df["GrLivArea"]
test_df["OverallQual_GrLivArea"] = test_df["OverallQual"] * test_df["GrLivArea"]
train_df["GarageCars_GarageArea"] = train_df["GarageCars"] * train_df["GarageArea"]
test_df["GarageCars_GarageArea"] = test_df["GarageCars"] * test_df["GarageArea"]
train_df["TotalBsmtSF_1stFlrSF"] = train_df["TotalBsmtSF"] * train_df["1stFlrSF"]
test_df["TotalBsmtSF_1stFlrSF"] = test_df["TotalBsmtSF"] * test_df["1stFlrSF"]
train_df["TotalSF"] = train_df["TotalBsmtSF"] + train_df["1stFlrSF"] + train_df["2ndFlrSF"]
test_df["TotalSF"] = test_df["TotalBsmtSF"] + test_df["1stFlrSF"] + test_df["2ndFlrSF"]
train_df["HouseAge"] = train_df["YrSold"] - train_df["YearBuilt"]
test_df["HouseAge"] = test_df["YrSold"] - test_df["YearBuilt"]
train_df["RemodAge"] = train_df["YrSold"] - train_df["YearRemodAdd"]
test_df["RemodAge"] = test_df["YrSold"] - test_df["YearRemodAdd"]
Giải thích:
-
OverallQual_GrLivArea: kết hợp chất lượng và diện tích → biểu diễn “mức sống” tổng thể. -
TotalSF: tổng diện tích có thể sử dụng (basement + tầng 1 + tầng 2). -
HouseAge: độ tuổi ngôi nhà tại thời điểm bán. -
RemodAge: số năm kể từ lần tu sửa gần nhất.
Kết quả: Những đặc trưng mới này giúp mô hình nắm bắt được tương tác phi tuyến giữa các yếu tố vật lý và giá bán.
3.2. PolynomialFeatures
Sau khi thêm các đặc trưng cơ bản, ta mở rộng không gian đặc trưng bằng đa thức bậc 2 (Polynomial Features).
Điều này giúp mô hình tuyến tính có thể học được các mối quan hệ bậc cao (ví dụ: diện tích tăng gấp đôi không đồng nghĩa với giá gấp đôi).
from sklearn.preprocessing import PolynomialFeatures
poly_features = PolynomialFeatures(
degree=2, interaction_only=True, include_bias=False
)
train_poly_features = poly_features.fit_transform(train_df[num_cols])
test_poly_features = poly_features.transform(test_df[num_cols])
Kết quả:
Số đặc trưng tăng mạnh (từ ~35 → vài trăm), biểu diễn được các quan hệ tương tác như:
-
GrLivArea * OverallQual
-
GarageArea * YearBuilt
-
TotalBsmtSF * 1stFlrSF
Điều này làm mô hình mạnh hơn, nhưng cũng cần regularization (Ridge, Lasso) để tránh overfitting.
3.3. Kết quả Mô hình Sau Feature Engineering
Ở đây mình dùng những mô hình Machine Learning đơn giản như Linear Regression, Lasso, Ridge.
from sklearn.linear_model import LinearRegression, Ridge, Lasso
models = {
"LinearRegression": LinearRegression(),
"Ridge": Ridge(),
"Lasso": Lasso()
}
Kết quả của những mô hình Machine Learning sau quá trình feature engineering.
| Model | Train_RMSE | Test_RMSE | Train_R2 | Test_R2 |
|---|---|---|---|---|
| Ridge | 17759.525682 | 27989.099849 | 0.948052 | 0.888172 |
| Lasso | 16470.224921 | 29594.118050 | 0.955321 | 0.874979 |
| LinearRegression | 14402.603230 | 31277.627194 | 0.965834 | 0.860350 |
Nhận xét:
-
Cả ba mô hình đều có Train $R^2$ > 0.94, chứng tỏ học tốt trên tập huấn luyện.
-
Ridge đạt Test $R^2$ cao nhất (0.888) → tổng quát tốt nhất trong nhóm tuyến tính.
-
Feature Engineering và Polynomial Features giúp mô hình học được mối quan hệ phi tuyến, cải thiện độ chính xác rõ rệt so với baseline.
4. Mô hình Nâng cao
4.1. Random Forest Regressor
Random Forest là mô hình ensemble của nhiều Decision Tree, mỗi cây học từ một mẫu con của dữ liệu (bootstrap sampling).
Kết quả dự đoán cuối cùng là trung bình của tất cả cây → giúp giảm phương sai (variance) và tránh overfitting.
Cấu hình sử dụng:
from sklearn.ensemble import RandomForestRegressor
RandomForestRegressor(n_estimators=100, random_state=42))
Ưu điểm:
- Ổn định với nhiễu.
- Xử lý tốt cả biến liên tục và rời rạc.
- Cho phép trích xuất feature importance.
4.2. Gradient Boosting Regressor
Khác với Random Forest, Gradient Boosting xây dựng các cây nối tiếp nhau, trong đó mỗi cây mới học để sửa lỗi của cây trước.
Nhờ đó, mô hình đạt hiệu suất cao hơn nhưng dễ overfit nếu không regularize đúng.
Cấu hình sử dụng:
from sklearn.ensemble import GradientBoostingRegressor
GradientBoostingRegressor(
n_estimators=100,
learning_rate=0.1,
max_depth=3,
random_state=42
)RandomForestRegressor(n_estimators=100, random_state=42))
4.3. Kết quả so sánh
| Model | Train_RMSE | Test_RMSE | Train_$R^2$ | Test_$R^2$ |
|---|---|---|---|---|
| RandomForestRegressor | 11,750 | 26,980 | 0.970 | 0.899 |
| GradientBoostingRegressor | 11,626 | 24,682 | 0.974 | 0.913 |
Gradient Boosting vượt trội hơn với Test $R^2 = 0.913$ — trở thành mô hình tốt nhất tính đến thời điểm này.
5. Random Forest cho feature selection
Sau khi thử nghiệm các mô hình nâng cao, bước tiếp theo là xác định đặc trưng nào quan trọng nhất.
5.1. Feature Importance
Dựa trên trọng số của Random Forest, top 10 đặc trưng ảnh hưởng mạnh nhất tới SalePrice là:
| Thứ hạng | Đặc trưng | Importance |
|---|---|---|
| 1 | OverallQual_GrLivArea | 0.072 |
| 2 | TotalSF | 0.065 |
| 3 | GrLivArea | 0.058 |
| 4 | GarageCars_GarageArea | 0.054 |
| 5 | TotalBsmtSF_1stFlrSF | 0.049 |
| 6 | OverallQual | 0.045 |
| 7 | GarageArea | 0.043 |
| 8 | YearBuilt | 0.040 |
| 9 | 1stFlrSF | 0.039 |
| 10 | FullBath | 0.036 |
Nhận xét:
Những đặc trưng về chất lượng tổng thể (OverallQual), diện tích sử dụng (GrLivArea, TotalSF), và garage vẫn chiếm ưu thế.
Các đặc trưng mở rộng như OverallQual_GrLivArea (biến tương tác) chứng tỏ hiệu quả của Feature Engineering.
5.2. Feature Selection
Ta chọn Top 50 đặc trưng quan trọng nhất, huấn luyện lại mô hình trên tập này — giúp giảm chiều dữ liệu và cải thiện tốc độ.
6. Ensemble Learning
Để tối ưu hơn nữa, ta kết hợp nhiều mô hình khác nhau thành Ensemble — một chiến lược mạnh mẽ trong Machine Learning.
6.1. Voting Regressor
Kết hợp kết quả trung bình của:
-
Gradient Boosting
-
Random Forest
-
Lasso Regression
ensemble = VotingRegressor([
("GradientBoostingRegressor", GradientBoostingRegressor(n_estimators=100, random_state=42)),
("RandomForestRegressor", RandomForestRegressor(n_estimators=100, random_state=42)),
("Lasso", Lasso(alpha=10)),
])
6.2. Stacking Regressor
Stacking kết hợp nhiều mô hình "base learners" (XGB, LGBM) và một meta learner (Lasso) ở tầng trên cùng:
stack = StackingRegressor(
estimators=[
("xgb", XGBRegressor(random_state=42)),
("lgb", LGBMRegressor(random_state=42))
],
final_estimator=Lasso(alpha=10),
passthrough=True
)
6.3. Kết quả Tổng hợp
| Model | Train_RMSE | Test_RMSE | Train_$R^2$ | Test_$R^2$ |
|---|---|---|---|---|
| Lasso | 17,234 | 29,432 | 0.950 | 0.876 |
| Ridge | 17,759 | 27,989 | 0.948 | 0.888 |
| Random Forest | 11,750 | 26,980 | 0.970 | 0.899 |
| Gradient Boosting | 11,626 | 24,682 | 0.974 | 0.913 |
| Voting Ensemble | 10,980 | 23,910 | 0.978 | 0.919 |
| Stacking Regressor | 10,402 | 23,400 | 0.982 | 0.922 |
Kết luận:
-
Stacking cho kết quả tốt nhất với $R^2 = 0.922$, $RMSE \approx 23,400$.
-
Ensemble không chỉ giảm sai số mà còn giúp mô hình ổn định và tổng quát hóa tốt hơn.
7. Giải thích Mô hình (XAI)
Để hiểu vì sao mô hình dự đoán như vậy, ta áp dụng Explainable AI gồm hai kỹ thuật phổ biến: SHAP và LIME.
7.1. SHAP (SHapley Additive Explanations)
SHAP cho phép xem mức độ đóng góp của từng đặc trưng tới giá trị dự đoán cụ thể.
import shap
def shap_ensemble_analysis(ensemble_model, X_train, X_test, feature_names, max_samples=100):
X_train = pd.DataFrame(X_train, columns=feature_names)
X_test = pd.DataFrame(X_test, columns=feature_names)
# Create background dataset (sample from training data)
data = X_train.sample(n=min(100, len(X_train)), random_state=42)
# Initialize SHAP KernelExplainer
explainer = shap.KernelExplainer(ensemble_model.predict, data)
# Calculate SHAP values for test set sample
test_sample = X_test.head(max_samples)
shap_values = explainer.shap_values(test_sample)
return explainer, shap_values, test_sample
Nhóm mình chạy code ở bên trên dùng Ensemble Model.
# Get the best performing model (Ensemble model)
best_model = advanced_models_selected["Ensemble"]
# Get the feature names for the selected features
feature_names_selected = [feature_names[i] for i in selected_indices]
# Perform SHAP analysis using the provided function
explainer, shap_values, test_sample = shap_ensemble_analysis(best_model, X_train_selected, X_test_selected, feature_names_selected)
# Generate SHAP summary plot
print("SHAP Summary Plot:")
shap.summary_plot(shap_values, test_sample, feature_names=feature_names_selected)
plt.show()
# Generate a SHAP force plot for the first test sample
print("\nSHAP Force Plot for the first test sample:")
shap.initjs() # Initialize JS for force plot
shap.force_plot(explainer.expected_value, shap_values[0,:], features=test_sample.iloc[0,:], feature_names=feature_names_selected)
Kết quả như ở dưới.
Hình 5: Kết qủa của Shap
Biểu đồ SHAP Summary cho thấy các đặc trưng ảnh hưởng mạnh nhất:
-
OverallQual -
GrLivArea -
TotalSF -
GarageCars -
YearBuilt
Dựa trên hình kết quả của Shap ta có thể hiểu được. Ví dụ, với một căn nhà có giá dự đoán $230,000:
| Feature | Đóng góp (ΔSalePrice) | Ảnh hưởng |
|---|---|---|
| OverallQual | +$20,000 | Tăng giá |
| GrLivArea | +$15,000 | Tăng giá |
| GarageCars | +$8,000 | Tăng giá |
| HouseAge | -$5,000 | Giảm giá |
7.2. LIME (Local Interpretable Model-Agnostic Explanations)
LIME giúp giải thích từng dự đoán cá nhân, bằng cách mô phỏng mô hình phức tạp bằng mô hình tuyến tính cục bộ quanh điểm cần giải thích.
from lime import lime_tabular
model_to_explain = advanced_models_selected["Ensemble"]
# Create a LIME explainer
explainer = lime_tabular.LimeTabularExplainer(
training_data=X_train_selected,
mode='regression',
feature_names=feature_names_selected,
random_state=42
)
# Choose an instance from the test set to explain
instance_to_explain = X_test_selected[0]
# Explain the instance's prediction
explanation = explainer.explain_instance(
data_row=instance_to_explain,
predict_fn=model_to_explain.predict,
num_features=10
)
# Visualize the explanation
print("LIME Explanation for the first test instance:")
explanation.as_pyplot_figure()
plt.show()
# Print the explanation as text
print("\nLIME Explanation (Text):")
print(explanation.as_list())
Hình 6: Kết qủa của LIME vởi 1 sample
8. Tổng Hợp
| Giai đoạn | Kỹ thuật chính | Cải thiện |
|---|---|---|
| EDA | Phân tích SalePrice, loại missing, xử lý outlier | Làm sạch dữ liệu |
| Preprocessing | Chuẩn hóa, log-transform, imputation | Phân phối chuẩn |
| Feature Engineering | Tạo biến tương tác, polynomial | Bổ sung phi tuyến tính |
| Modeling | Ridge, Lasso, Gradient Boosting | $R^2 = 0.91$ |
| Feature Selection | Random Forest Importance | Giảm chiều dữ liệu |
| Ensemble | Voting + Stacking | $R^2 = 0.922$ |
| XAI | SHAP + LIME | Giải thích mô hình |
Kết quả cuối cùng:
$R^2 = 0.922$, $RMSE = 23,400$
→ Một mô hình mạnh, cân bằng giữa độ chính xác và khả năng giải thích.
Tài liệu tham khảo
[1] AIO Vietnam 2025 – Project 5.1 Guidelines
[2] Kaggle: House Prices - Advanced Regression Techniques
[3] Lundberg & Lee (2017), “A Unified Approach to Interpreting Model Predictions”
[4] Ribeiro et al. (2016), “Why Should I Trust You?”: Explaining Predictions of Any Classifier
Chưa có bình luận nào. Hãy là người đầu tiên!