Sales Prediction P5.1 (P1)

  • Dự Đoán Giá Nhà: Từ Dữ Liệu Thô Đến Mô Hình Machine Learning

Distribution of SalePrice

Hình 1: Dự đoán giá nhà bằng AI

1. Giới thiệu tổng quan

Dự án Dự đoán Giá Nhà (House Price Prediction) là một bài toán regression điển hình trong lĩnh vực Machine Learning, sử dụng các kỹ thuật regression tiên tiến để dự đoán giá bán của các căn nhà dựa trên các đặc điểm như diện tích, số phòng, chất lượng, vị trí, và nhiều yếu tố khác.

Tổng quan dự án

Dự án này được chia thành hai phần chính:

  1. Data Preprocessing Pipeline (Phần 1 - Bài viết này):
    - Xử lý và làm sạch dữ liệu thô
    - Feature engineering và kiểm soát độ phức tạp
    - Biến đổi dữ liệu từ 1,460 mẫu với 81 đặc trưng thành 1,078 mẫu train và 360 mẫu test, mỗi mẫu có 1,097 đặc trưng sẵn sàng cho model training

  2. Model Training & Evaluation (Phần 2 - Bài viết tiếp theo):
    - Huấn luyện nhiều mô hình regression khác nhau (Linear, Ridge, Lasso, Elastic Net, Huber, Quantile, etc.)
    - So sánh và đánh giá hiệu suất các mô hình
    - Model interpretation với SHAP values và feature importance
    - Đánh giá nâng cao với cross-validation và bootstrap confidence intervals

Vấn đề cần giải quyết cho phần này

Dataset dự đoán giá nhà thường gặp phải nhiều thách thức:

  • Missing values: Nhiều cột có dữ liệu thiếu, một số cột thiếu đến hơn 50% dữ liệu
  • Outliers: Các giá nhà cực đoan (quá cao hoặc quá thấp) có thể làm méo mô hình dự đoán
  • Mixed data types: Dữ liệu bao gồm cả số (numeric) và chữ (categorical), cần xử lý khác nhau
  • Feature engineering: Cần tạo ra các đặc trưng mới từ dữ liệu có sẵn để cải thiện hiệu suất mô hình
  • Curse of dimensionality: Cần kiểm soát số lượng features để tránh overfitting và "nổ chiều"

Trong các dự án Machine Learning, tiền xử lý dữ liệu (data preprocessing) thường chiếm 60-70% thời gian và công sức của toàn bộ quy trình. Với dữ liệu bất động sản - một loại dữ liệu phức tạp với nhiều biến số và đặc điểm - việc có một pipeline xử lý tự động và đảm bảo chất lượng là vô cùng quan trọng.

Giải pháp: DataQualityPipeline

Để giải quyết những thách thức trên, chúng ta sử dụng DataQualityPipeline - một class Python được thiết kế để tự động hóa toàn bộ quy trình tiền xử lý dữ liệu qua 13 bước xử lý tuần tự:

  1. Validation dữ liệu đầu vào
  2. Phát hiện và loại bỏ outliers
  3. Phân tích và xử lý dữ liệu thiếu
  4. Chia dữ liệu train/test
  5. Phân loại kiểu dữ liệu
  6. Điền giá trị thiếu
  7. Tạo đặc trưng từ kiến thức domain
  8. Mã hóa biến phân loại
  9. Tạo đặc trưng đa thức
  10. Chuẩn hóa đặc trưng
  11. Kết hợp đặc trưng
  12. Kiểm tra chất lượng đầu ra
  13. Tạo báo cáo tổng hợp

Core Module

Module chính của pipeline được triển khai trong DataQualityPipeline class - một class Python chứa toàn bộ logic preprocessing. Class này được thiết kế với các đặc điểm:

  • Tự động hóa: Chỉ cần gọi một hàm fit_transform() để chạy toàn bộ 13 bước
  • Đảm bảo chất lượng: Tự động validate ở nhiều giai đoạn
  • Chống data leakage: Đảm bảo test set hoàn toàn "blind" với train set
  • Logging và báo cáo: Tạo ra báo cáo chi tiết về quá trình xử lý

Cấu trúc bài viết

Bài viết này tập trung vào Phần 1: Data Preprocessing Pipeline, bao gồm:

  • Phần 2: Quy trình xử lý dữ liệu và kiểm soát chất lượng
  • Phần 3: Kỹ thuật tạo đặc trưng và kiểm soát độ phức tạp
  • Phần 4: Kết luận và tổng kết

Sau khi hoàn thành preprocessing, dữ liệu sẽ được chuyển sang Phần 2 của dự án (Model Training & Evaluation) để huấn luyện và đánh giá các mô hình machine learning.

2. Quy Trình Xử Lý Dữ Liệu Và Kiểm Soát Chất Lượng

Đây là phần quan trọng nhất của pipeline, đảm bảo dữ liệu đầu vào được làm sạch, xử lý đúng cách và không có vấn đề về chất lượng trước khi đưa vào model training.

2.1 Validation Dữ Liệu Đầu Vào

Bước đầu tiên trong pipeline là kiểm tra tính hợp lệ của dữ liệu đầu vào. Bước này đảm bảo rằng:

  • Dữ liệu là một pandas DataFrame hợp lệ
  • DataFrame không rỗng
  • Cột target (ở đây là SalePrice) có tồn tại trong dataset

Việc validation sớm giúp phát hiện lỗi ngay từ đầu, tiết kiệm thời gian xử lý và tránh các lỗi khó debug sau này.

def _validate_input(self, df: pd.DataFrame) -> None:
    """Validate input dataframe."""
    assert isinstance(df, pd.DataFrame), "Input must be pandas DataFrame"
    assert len(df) > 0, "DataFrame is empty"
    assert self.target_col in df.columns, f"Target column '{self.target_col}' not found"

2.2 Phát Hiện Và Xử Lý Outliers

Outliers (giá trị ngoại lai) là những điểm dữ liệu có giá trị bất thường so với phần còn lại. Trong dự đoán giá nhà, outliers có thể là những căn nhà có giá cực kỳ cao hoặc cực kỳ thấp - những điểm này có thể làm méo mô hình dự đoán, đặc biệt là các mô hình linear.

Phương pháp Z-Score

Pipeline sử dụng phương pháp Z-score để phát hiện outliers trên target variable (SalePrice). Z-score đo lường số lần độ lệch chuẩn mà một giá trị cách xa giá trị trung bình.

Công thức Z-score:

$$z = \frac{x - \mu}{\sigma}$$

Trong đó:
- $z$: Z-score (số lần độ lệch chuẩn mà giá trị cách xa trung bình)
- $x$: Giá trị cần kiểm tra (trong trường hợp này là giá nhà SalePrice)
- $\mu$: Giá trị trung bình (mean) của toàn bộ dữ liệu
- $\sigma$: Độ lệch chuẩn (standard deviation) của toàn bộ dữ liệu

Giá trị có $|z| > 3$ được coi là outliers (tức là cách xa trung bình hơn 3 lần độ lệch chuẩn).

Tại sao chọn Z-Score?

Phương pháp Z-score được chọn vì:

  1. Ổn định và đáng tin cậy: Đã được chứng minh qua nhiều ứng dụng thực tế
  2. Phù hợp với phân phối normal: Giá nhà thường có phân phối gần normal, Z-score hoạt động tốt
  3. Dễ hiểu và interpret: Giá trị z-score có ý nghĩa rõ ràng
  4. Hiệu quả tính toán: Nhanh, không tốn nhiều tài nguyên

Kết quả thực tế

Trên dataset của chúng ta, phương pháp Z-score đã phát hiện và loại bỏ 22 outliers (chiếm khoảng 1.5% dữ liệu). Điều này giúp:

  • Loại bỏ các điểm dữ liệu có thể làm méo mô hình
  • Cải thiện độ chính xác của các mô hình linear (Ridge, Lasso, Linear Regression)
  • Đảm bảo phân phối dữ liệu cân bằng hơn

Distribution of SalePrice

Hình 2: Phân phối giá nhà (SalePrice) với đường mean được đánh dấu. Các giá trị có z-score > 3 được coi là outliers.

def _detect_outliers(self, df: pd.DataFrame) -> pd.DataFrame:
    """Detect and remove outliers using z-score method."""
    initial_size = len(df)

    # Tính z-score cho target variable
    z_scores = np.abs(stats.zscore(df[self.target_col]))

    # Đánh dấu outliers (z-score > 3)
    outliers_mask = z_scores > self.outlier_threshold

    # Đếm và loại bỏ outliers
    self.outliers_removed = outliers_mask.sum()
    if self.outliers_removed > 0:
        df_clean = df[~outliers_mask].copy()
        return df_clean

    return df.copy()

So sánh với các phương pháp khác

Có nhiều phương pháp khác để xử lý outliers như IQR (Interquartile Range) hay winsorization (thay vì xóa, cắt giá trị về percentile). Tuy nhiên, với dataset giá nhà này, Z-score là lựa chọn phù hợp vì:

  • IQR phù hợp hơn với phân phối không đối xứng, nhưng giá nhà có phân phối gần normal
  • Winsorization có thể làm mất thông tin quan trọng về giá trị cực đoan thực sự
  • Z-score đơn giản, hiệu quả và phù hợp với đặc điểm dữ liệu

2.3 Phân Tích Và Xử Lý Dữ Liệu Thiếu

Dữ liệu thiếu (missing values) là một vấn đề phổ biến trong các dataset thực tế. Pipeline xử lý vấn đề này theo hai chiến lược khác nhau tùy vào mức độ thiếu của từng cột.

Phân tích mức độ thiếu

Đầu tiên, pipeline phân tích phần trăm dữ liệu thiếu của từng cột:

missing_pct = (df.isnull().sum() / len(df))

Chiến lược: Xóa cột thiếu quá nhiều

Nếu một cột có hơn 50% dữ liệu thiếu, cột đó sẽ bị loại bỏ. Lý do:

  1. Không đáng tin cậy: Cột thiếu >50% không còn đại diện tốt cho thông tin
  2. Điền vào sẽ tạo noise: Điền >50% dữ liệu sẽ tạo ra nhiều giá trị giả, làm nhiễu mô hình
  3. Tốt hơn là loại bỏ: Trong nhiều trường hợp, bỏ cột thiếu quá nhiều tốt hơn là cố điền vào

Kết quả

Trên dataset, pipeline đã loại bỏ 6 cột có mức độ thiếu cao:
- Alley: Thiếu 93.8%
- MasVnrType: Thiếu 59.5%
- PoolQC: Thiếu 99.5%
- Fence: Thiếu 80.8%
- MiscFeature: Thiếu 96.3%
- Id: Cột định danh, không có giá trị dự đoán

Việc loại bỏ các cột này giúp:
- Giảm noise trong dữ liệu
- Tập trung vào các features quan trọng hơn
- Tăng tốc độ xử lý

Missing Values by Feature

Hình 3: Bar chart thể hiện số lượng missing values của các cột. Các cột có >50% missing (như PoolQC, MiscFeature, Alley) đã được loại bỏ.

Validation và Logging

Pipeline tự động log lại tất cả các cột bị xóa cùng với phần trăm thiếu của chúng, giúp người dùng theo dõi và hiểu được quyết định xử lý.

def _drop_high_missing_columns(self, df: pd.DataFrame) -> pd.DataFrame:
    """Drop columns with high missing percentage."""
    missing_pct = (df.isnull().sum() / len(df))
    high_missing = missing_pct[missing_pct > self.missing_threshold]

    self.dropped_cols = high_missing.index.tolist()

    # Cũng xóa cột ID (không có giá trị dự đoán)
    if 'Id' in df.columns:
        self.dropped_cols.append('Id')

    if self.dropped_cols:
        df_clean = df.drop(self.dropped_cols, axis=1)
        return df_clean

    return df

2.4 Chia Dữ Liệu Train/Test

Việc chia dữ liệu thành tập train và test là bước quan trọng để đánh giá hiệu suất mô hình. Tuy nhiên, thứ tự của bước này rất quan trọng.

Tại sao phải chia TRƯỚC?

Pipeline chia dữ liệu NGAY SAU KHI loại bỏ outliers và các cột thiếu nhiều, nhưng TRƯỚC KHI fit bất kỳ transformer nào (imputer, encoder, scaler). Lý do:

  • Chống data leakage: Nếu fit transformers trên toàn bộ data, test set sẽ "nhìn thấy" thông tin từ train set
  • Đánh giá chính xác: Test set phải hoàn toàn "blind" với train set

Stratified Split

Pipeline sử dụng stratified split dựa trên quartile của target variable để đảm bảo phân phối giá nhà đồng đều giữa train và test set.

# Tạo quartile cho stratification
stratify_bins = pd.qcut(df[self.target_col], q=4, duplicates='drop')

train_df, test_df = train_test_split(
    df,
    test_size=0.25,  # 25% test, 75% train
    random_state=42,
    stratify=stratify_bins
)

Kết quả

Sau khi chia:
- Train set: 1,078 samples (75%)
- Test set: 360 samples (25%)
- Giá nhà trung bình tương đồng:
- Train set: $175,129 (trung bình giá nhà trong tập train)
- Test set: $177,987 (trung bình giá nhà trong tập test)
- Chênh lệch: Chỉ khoảng 1.6% - rất nhỏ

Phân phối giá nhà tương đồng giữa train và test cho thấy stratified split đã hoạt động tốt, không có sự mất cân bằng nghiêm trọng giữa hai tập dữ liệu.

2.5 Điền Giá Trị Thiếu

Sau khi đã loại bỏ các cột thiếu quá nhiều và chia train/test, các cột còn lại vẫn có thể có một số giá trị thiếu. Pipeline xử lý chúng với các chiến lược khác nhau tùy vào loại dữ liệu.

Chiến lược cho Categorical Variables

Với các biến phân loại (text), pipeline điền giá trị thiếu bằng chuỗi "none":

train_df[self.cat_cols] = train_df[self.cat_cols].fillna("none")
test_df[self.cat_cols] = test_df[self.cat_cols].fillna("none")

Lý do chọn "none":
- Đơn giản và rõ ràng
- Giữ nguyên tính chất categorical
- Không ảnh hưởng đến phân phối của các giá trị khác

Chiến lược cho Numerical Variables: SimpleImputer với Mean

Với các biến số, pipeline sử dụng SimpleImputer với chiến lược mean (điền bằng giá trị trung bình):

self.imputer = SimpleImputer(strategy='mean')

# Train: fit + transform (học mean từ train)
train_df[self.num_cols] = self.imputer.fit_transform(train_df[self.num_cols])

# Test: CHỈ transform (dùng mean từ train)
test_df[self.num_cols] = self.imputer.transform(test_df[self.num_cols])

Tại sao chọn Mean?

SimpleImputer với mean được chọn vì:

  1. Đơn giản và hiệu quả: Không tốn nhiều tài nguyên tính toán
  2. Ổn định: Mean là một thống kê ổn định, không bị ảnh hưởng nhiều bởi outliers (sau khi đã loại bỏ)
  3. Phù hợp với dataset: Với nhiều biến số trong dataset bất động sản, mean là lựa chọn hợp lý
  4. Tránh overfitting: Mean là một giá trị trung tâm, không quá phức tạp
  5. Dễ interpret: Giá trị mean có ý nghĩa rõ ràng

Chống Data Leakage

Điểm quan trọng: Pipeline CHỈ fit imputer trên train set, sau đó dùng mean đã học để transform test set. Điều này đảm bảo:

  • Test set không "nhìn thấy" thông tin từ train set
  • Đánh giá mô hình chính xác hơn
  • Mô phỏng điều kiện thực tế (test set như dữ liệu mới)

Validation

Sau khi điền, pipeline tự động kiểm tra:

assert train_df.isnull().sum().sum() == 0, "Train still has missing values!"
assert test_df.isnull().sum().sum() == 0, "Test still has missing values!"

Đảm bảo không còn giá trị thiếu nào trong cả train và test set.

So sánh với các phương pháp khác

Có nhiều phương pháp điền giá trị thiếu phức tạp hơn như:

  • KNNImputer: Điền dựa trên K nearest neighbors
  • IterativeImputer: Điền dựa trên các biến khác bằng iterative regression

Tuy nhiên, với dataset này, SimpleImputer mean là lựa chọn tốt vì:
- Đủ hiệu quả cho hầu hết trường hợp
- Không tạo ra dependency phức tạp giữa các biến
- Phù hợp với quy mô dataset (không quá lớn)
- Dễ maintain và debug

3. Kỹ Thuật Tạo Đặc Trưng Và Kiểm Soát Độ Phức Tạp

Sau khi dữ liệu đã được làm sạch, bước tiếp theo là tạo ra các đặc trưng mới để cải thiện khả năng dự đoán của mô hình. Phần này sẽ tập trung vào các kỹ thuật feature engineering và cách kiểm soát độ phức tạp để tránh "curse of dimensionality".

3.1 Tạo Đặc Trưng Từ Kiến Thức Domain

Feature engineering dựa trên kiến thức domain là một cách hiệu quả để tạo ra các đặc trưng có ý nghĩa. Pipeline tạo ra 5 đặc trưng mới dựa trên hiểu biết về bất động sản:

TotalSF - Tổng Diện Tích

TotalSF = TotalBsmtSF + 1stFlrSF + 2ndFlrSF

Tổng diện tích sàn là một chỉ số quan trọng cho giá nhà. Thay vì để model học mối quan hệ giữa 3 biến riêng lẻ, ta tạo một biến tổng hợp có ý nghĩa rõ ràng.

TotalBath - Tổng Số Phòng Tắm

TotalBath = FullBath + 0.5 × HalfBath

Phòng tắm đầy đủ được tính là 1, phòng tắm nửa được tính là 0.5. Đây là một cách normalize các loại phòng tắm khác nhau.

HouseAge - Tuổi Ngôi Nhà

HouseAge = 2024 - YearBuilt

Tuổi ngôi nhà thường có mối quan hệ nghịch với giá nhà (nhà càng cũ, giá càng thấp). Tạo biến tuổi giúp model dễ học mối quan hệ này.

IsRemodeled - Đã Cải Tạo

IsRemodeled = (YearRemodAdd != YearBuilt) ? 1 : 0

Một căn nhà đã được cải tạo thường có giá cao hơn. Biến binary này capture được điều đó.

QualityArea - Tương Tác Chất Lượng × Diện Tích

QualityArea = OverallQual × GrLivArea

Đây là một tương tác quan trọng: một căn nhà vừa có chất lượng cao vừa có diện tích lớn sẽ có giá cao hơn nhiều so với tổng đơn giản của hai yếu tố.

3.2 Mã Hóa Biến Phân Loại

Các mô hình machine learning chỉ làm việc với số, không hiểu text. Vì vậy, các biến categorical (như Neighborhood, HouseStyle) cần được chuyển thành số.

One-Hot Encoding

Pipeline sử dụng OneHotEncoder để chuyển các biến categorical thành các biến binary (0/1):

self.encoder = OneHotEncoder(handle_unknown="ignore", sparse_output=False)
self.encoder.fit(train_df[self.cat_cols])

# Transform cả train và test
train_df[self.encoded_cols] = self.encoder.transform(train_df[self.cat_cols])
test_df[self.encoded_cols] = self.encoder.transform(test_df[self.cat_cols])

Ví dụ:

Neighborhood: ['A', 'B', 'A', 'C']
→ 
Neighborhood_A: [1, 0, 1, 0]
Neighborhood_B: [0, 1, 0, 0]
Neighborhood_C: [0, 0, 0, 1]

Kết quả

  • Input: 38 cột categorical
  • Output: 236 cột binary (one-hot encoded)
  • Tăng gấp 6 lần số lượng features từ categorical

Chống Data Leakage

Encoder được fit trên train set để học tất cả các categories có thể. Nếu test set có category mới (không có trong train), nó sẽ được xử lý bằng handle_unknown="ignore".

3.3 Tạo Đặc Trưng Đa Thức Có Kiểm Soát

Đây là phần quan trọng nhất của feature engineering - tạo ra các tương tác giữa các features để capture mối quan hệ phi tuyến, nhưng với sự kiểm soát chặt chẽ để tránh "nổ chiều" (curse of dimensionality).

PolynomialFeatures là gì?

PolynomialFeatures tạo ra các features mới từ các features gốc bằng cách:
- Giữ nguyên các features gốc
- Tạo các tương tác giữa các features ($x_1 \times x_2$)
- Tạo các lũy thừa ($x_1^2$, $x_2^2$) - nếu không dùng interaction_only

Interaction-Only Mode: Kiểm Soát "Nổ Chiều"

Pipeline sử dụng PolynomialFeatures với interaction_only=True:

self.poly_transformer = PolynomialFeatures(
    degree=2,                    # Bậc 2
    interaction_only=True,       # CHỈ tương tác, KHÔNG có x²
    include_bias=False          # Không có cột constant
)

Interaction-only có nghĩa là:
- ✅ Có tương tác giữa 2 features: $x_1 \times x_2$
- ❌ KHÔNG có lũy thừa: $x_1^2$, $x_2^2$

So Sánh: Interaction-Only vs Full Polynomial

Với Interaction-Only (interaction_only=True)

Với 41 numeric features (sau domain engineering), degree 2:

Số features được tạo:
- Features gốc: 41
- Tương tác: $\binom{41}{2} = \frac{41 \times 40}{2} = 820$
- Trong đó: $\binom{n}{k}$ là số cách chọn $k$ phần tử từ $n$ phần tử (tổ hợp)
- $n = 41$: Số features gốc
- $k = 2$: Mỗi tương tác được tạo từ 2 features
- Công thức: $\binom{n}{k} = \frac{n!}{k!(n-k)!} = \frac{n \times (n-1)}{2}$ khi $k=2$
- Tổng: 861 features

Với Full Polynomial (interaction_only=False)

Với cùng 41 features, degree 2:

Số features được tạo:
- Features gốc: 41
- Lũy thừa: 41 (mỗi feature bình phương)
- Tương tác: 820
- Tổng: 902 features

Với degree 3, số features có thể lên đến hàng nghìn, thậm chí hàng chục nghìn!

Tại Sao Chọn Interaction-Only?

Lựa chọn interaction_only=True là một quyết định thông minh vì:

1. Tránh Curse of Dimensionality

"Curse of dimensionality" xảy ra khi số features quá lớn so với số samples. Với 1,078 samples train:
- 861 features (interaction-only): Tỷ lệ hợp lý (~1.25 samples/feature)
- Hàng nghìn features (full polynomial): Quá nhiều, dễ overfit

2. Capture Tương Tác Quan Trọng

Tương tác giữa các features thường quan trọng hơn lũy thừa. Ví dụ:
- $OverallQual \times GrLivArea$ (chất lượng × diện tích) → Quan trọng
- $OverallQual^2$ (chất lượng bình phương) → Ít quan trọng hơn

3. Phù Hợp Với Regularized Models

Pipeline được thiết kế để làm việc với các regularized models như Ridge, Lasso, Elastic Net:
- Các models này phạt các features không quan trọng
- Với 861 features, regularization hoạt động tốt
- Với hàng nghìn features, regularization có thể không đủ mạnh

4. Tránh Overfitting Từ Lũy Thừa

Lũy thừa ($x^2$, $x^3$) có thể tạo ra các patterns cục bộ, dẫn đến overfitting. Interaction-only tập trung vào mối quan hệ giữa các biến, ổn định hơn.

Ví Dụ Minh Họa

Giả sử có 3 features: $Area$, $Quality$, $Age$

Với interaction_only=True (degree=2):

Features: [Area, Quality, Age, Area×Quality, Area×Age, Quality×Age]
→ 6 features tổng cộng

Với interaction_only=False (degree=2):

Features: [Area, Quality, Age, Area², Quality², Age², Area×Quality, Area×Age, Quality×Age]
→ 9 features tổng cộng

Với 41 features, sự khác biệt càng rõ ràng hơn.

Kết Quả Thực Tế

Trên dataset:
- Input: 41 numeric features (sau domain engineering)
- Output: 861 polynomial features
- Tăng gấp 21 lần, nhưng vẫn kiểm soát được

Feature Importance

Hình 4: Tầm quan trọng của các features sau khi engineering. Biểu đồ này cho thấy các polynomial features và domain features đóng vai trò quan trọng trong việc dự đoán giá nhà.

Tại Sao poly_degree=2?

Lựa chọn bậc 2 (degree=2) là sự cân bằng tốt:
- Degree 1: Không có tương tác, bỏ lỡ mối quan hệ phi tuyến
- Degree 2: Capture tương tác cơ bản, đủ cho hầu hết trường hợp
- Degree 3+: Quá phức tạp, tăng nguy cơ overfitting và nổ chiều

Lưu ý về Cross-Validation

Trong pipeline hiện tại, poly_degreeinteraction_only được set cố định. Trong thực tế, có thể sử dụng cross-validation để tìm bậc đa thức tối ưu:

# Ví dụ (không có trong code hiện tại)
from sklearn.model_selection import GridSearchCV

param_grid = {
    'poly_degree': [1, 2, 3],
    'interaction_only': [True, False]
}
# Tìm combination tốt nhất bằng CV

Tuy nhiên, với dataset này, degree=2interaction_only=True đã cho kết quả tốt và kiểm soát được độ phức tạp.

Chống Data Leakage

PolynomialFeatures được fit trên train set:

train_poly = self.poly_transformer.fit_transform(train_df[self.num_cols])
test_poly = self.poly_transformer.transform(test_df[self.num_cols])

Đảm bảo cấu trúc polynomial được học từ train, không bị ảnh hưởng bởi test set.

3.4 Chuẩn Hóa Đặc Trưng

Sau khi tạo polynomial features, các giá trị có thể rất lớn và ở scales khác nhau. Chuẩn hóa là bước quan trọng, đặc biệt với regularized models.

MinMaxScaler

Pipeline sử dụng MinMaxScaler để chuẩn hóa tất cả features về khoảng $[0, 1]$:

self.scaler = MinMaxScaler()
train_poly_scaled = self.scaler.fit_transform(train_poly)
test_poly_scaled = self.scaler.transform(test_poly)

Công thức:

$$x_{scaled} = \frac{x - x_{min}}{x_{max} - x_{min}}$$

Trong đó:
- $x$: Giá trị gốc cần chuẩn hóa
- $x_{scaled}$: Giá trị đã được chuẩn hóa (nằm trong khoảng $[0, 1]$)
- $x_{min}$: Giá trị nhỏ nhất trong tập dữ liệu (được tính từ train set)
- $x_{max}$: Giá trị lớn nhất trong tập dữ liệu (được tính từ train set)

Kết quả: Tất cả giá trị sau chuẩn hóa sẽ nằm trong khoảng $[0, 1]$, giúp các features có cùng scale và dễ dàng so sánh.

Tại Sao Cần Chuẩn Hóa?

  1. Regularized Models Nhạy Cảm: Ridge, Lasso đánh phạt dựa trên magnitude của coefficients. Features có scale lớn sẽ bị phạt nhiều hơn
  2. Gradient Descent: Chuẩn hóa giúp optimization hội tụ nhanh hơn
  3. So Sánh Features: Tất cả features cùng scale, dễ so sánh và interpret

Chống Data Leakage

Scaler được fit trên train set (học min/max từ train), sau đó dùng để transform test set. Đảm bảo test set không ảnh hưởng đến normalization.

3.5 Kết Hợp Đặc Trưng

Sau khi xử lý riêng biệt, tất cả features được kết hợp lại:

X_train = np.hstack([
    train_poly_scaled,                    # 861 polynomial features
    train_df[self.encoded_cols].values    # 236 one-hot encoded features
])

Kết Quả Cuối Cùng

  • Polynomial features: 861
  • One-hot encoded features: 236
  • Total: 1,097 features

Mỗi mẫu (sample) trong train/test set có 1,097 đặc trưng sau preprocessing.

3.6 Kiểm Tra Chất Lượng Đầu Ra

Trước khi kết thúc, pipeline thực hiện 5 kiểm tra chất lượng:

  1. Feature dimensions: Train và test có cùng số features?
  2. No NaN: Không còn giá trị thiếu?
  3. No Inf: Không có giá trị vô cực?
  4. Sample size: Số samples khớp với target?
  5. Feature variance: Features có variance (tránh constant features)?

Tất cả kiểm tra phải pass, nếu không pipeline sẽ raise error.

3.7 Báo Cáo Chất Lượng

Cuối cùng, pipeline tạo báo cáo tổng hợp bao gồm:
- Số outliers đã loại bỏ
- Số cột đã xóa
- Số features gốc và features sau engineering
- Thống kê train/test set
- Các metrics về chất lượng features

Báo cáo này giúp người dùng hiểu rõ quá trình xử lý và kết quả cuối cùng.

4. Kết Luận

Tóm Tắt Pipeline

Qua bài viết này, chúng ta đã tìm hiểu về DataQualityPipeline - một hệ thống tự động hóa toàn bộ quy trình tiền xử lý dữ liệu qua 13 bước xử lý tuần tự:

Quy Trình Xử Lý Dữ Liệu

Pipeline bắt đầu với việc làm sạch dữ liệu thông qua:
- Phát hiện và loại bỏ outliers bằng phương pháp Z-score
- Phân tích và loại bỏ các cột thiếu quá nhiều dữ liệu (>50%)
- Điền giá trị thiếu một cách thông minh (SimpleImputer với mean cho numerical, "none" cho categorical)

Các bước này đảm bảo dữ liệu sạch, đáng tin cậy và không có các giá trị bất thường có thể làm méo mô hình.

Kỹ Thuật Tạo Đặc Trưng Có Kiểm Soát

Phần quan trọng nhất là tạo đặc trưng với sự kiểm soát chặt chẽ:
- Tạo 5 đặc trưng domain dựa trên kiến thức bất động sản
- Mã hóa 38 biến categorical thành 236 biến binary
- Tạo 861 polynomial features với interaction-only mode để tránh "curse of dimensionality"

Việc sử dụng interaction_only=True là một quyết định quan trọng:
- Kiểm soát số lượng features: 41 → 861 (thay vì hàng nghìn)
- Capture tương tác quan trọng: Tương tác giữa các features thường quan trọng hơn lũy thừa
- Phù hợp với regularized models: Dễ kiểm soát overfitting
- Tránh nổ chiều: Tỷ lệ samples/features hợp lý (~1.25)

Kết Quả

Từ 1,460 mẫu với 81 đặc trưng ban đầu, pipeline đã tạo ra:
- 1,078 mẫu train360 mẫu test
- 1,097 đặc trưng đã được xử lý (861 polynomial + 236 encoded)
- Dữ liệu sạch: Không còn missing values, outliers, hay giá trị bất thường
- Sẵn sàng cho model training: Dữ liệu đã được normalize, encoded và validated

Model Comparison

Hình 5: So sánh hiệu suất các mô hình sau khi sử dụng dữ liệu đã được preprocessing. Preprocessing tốt tạo nền tảng cho các mô hình đạt hiệu suất cao.

Tầm Quan Trọng Của Preprocessing

Preprocessing chiếm 60-70% thời gian và công sức trong một dự án Machine Learning. Một pipeline tốt sẽ:

  1. Tiết kiệm thời gian: Tự động hóa các bước xử lý, không cần làm thủ công
  2. Đảm bảo chất lượng: Validation ở nhiều giai đoạn, đảm bảo không có lỗi
  3. Chống data leakage: Đảm bảo test set hoàn toàn "blind", đánh giá chính xác
  4. Tái sử dụng: Có thể áp dụng cho nhiều dataset khác
  5. Cải thiện hiệu suất: Feature engineering tốt giúp model học tốt hơn

Những Điểm Nổi Bật

Hai điểm nổi bật nhất của pipeline này là:

1. Xử Lý Dữ Liệu Và Đảm Bảo Chất Lượng

  • Phương pháp ổn định: Z-score cho outliers, SimpleImputer cho missing values
  • Validation kỹ lưỡng: Kiểm tra ở nhiều giai đoạn
  • Chống data leakage: Tất cả transformers được fit trên train set
  • Logging chi tiết: Theo dõi được toàn bộ quá trình xử lý

2. Tạo Đặc Trưng Có Kiểm Soát

  • Interaction-only mode: Kiểm soát số lượng features, tránh curse of dimensionality
  • Cân bằng tốt: 861 features - đủ để capture mối quan hệ phức tạp nhưng không quá nhiều
  • Phù hợp với models: Regularized models (Ridge, Lasso) hoạt động tốt với số features này
  • Feature engineering thông minh: Kết hợp domain knowledge và polynomial features

Sẵn Sàng Cho Model Training

Sau khi qua toàn bộ pipeline preprocessing, dữ liệu đã:
- ✅ Sạch và không có lỗi
- ✅ Được encode và normalize đúng cách
- ✅ Có số lượng features hợp lý (1,097)
- ✅ Được chia train/test đúng cách
- ✅ Sẵn sàng để đưa vào các mô hình Machine Learning

Residual Analysis

Hình 6: Phân tích residual của mô hình sau khi training. Dữ liệu đã được preprocessing tốt giúp mô hình có residual phân phối chuẩn và ổn định.

SHAP Summary

Hình 7: SHAP values summary cho thấy tầm quan trọng của các features. Các features được engineering (như polynomial features) đóng vai trò quan trọng trong việc dự đoán.

Advanced Evaluation

Hình 8: Đánh giá nâng cao với cross-validation và bootstrap confidence intervals. Preprocessing tốt đảm bảo mô hình có hiệu suất ổn định và đáng tin cậy.

Pipeline này tạo nền tảng vững chắc cho việc huấn luyện các mô hình dự đoán giá nhà, đảm bảo mô hình có thể học được các patterns quan trọng và đưa ra dự đoán chính xác.


Kết thúc phần Preprocessing Pipeline. Dữ liệu đã sẵn sàng cho Model Training - phần tiếp theo của dự án. Mời bạn xem tiếp phần 2 "Sales Prediction P5.1 (P2)".