DS

[Tech Blog #34 | Data Science] Xử Lý Biến Định Tính (Categorical Encoding): One-Hot vs. Ordinal vs. Target Encoding Thực Chiến

0 views
[Tech Blog #34 | Data Science] Xử Lý Biến Định Tính (Categorical Encoding): One-Hot vs. Ordinal vs. Target Encoding Thực Chiến

1. Đặt vấn đề: Khi máy tính "mù chữ" trước các cột dạng văn bản

Trong các tập dữ liệu thực tế (bảo hiểm, ngân hàng, e-commerce), hơn 60% các cột thông tin là dữ liệu dạng chữ (Categorical Data):

  • Tỉnh/Thành phố: ["Hà Nội", "Đà Nẵng", "TP.HCM"]
  • Trình độ học vấn: ["Cấp 3", "Đại học", "Thạc sĩ"]
  • Hệ điều hành: ["iOS", "Android", "Windows"]

Tuy nhiên, các thuật toán Machine Learning (từ Hồi quy, SVM cho đến Mạng nơ-ron) thực chất chỉ là các cỗ máy tính toán ma trận số học. Chúng hoàn toàn không biết chữ "Hà Nội" hay "TP.HCM" có nghĩa là gì.

Một sai lầm "chí mạng" mà nhiều người mới bắt đầu thường mắc phải:
Dùng một hàm gán số ngẫu nhiên:
Haˋ Nội=1,Đaˋ Na˘˜ng=2,TP.HCM=3\text{Hà Nội} = 1, \quad \text{Đà Nẵng} = 2, \quad \text{TP.HCM} = 3

Khi bạn nạp con số này vào Linear hoặc Logistic Regression, mô hình sẽ ngầm hiểu một cách máy móc rằng:

TP.HCM(3)>Haˋ Nội(1)\text{TP.HCM} (3) > \text{Hà Nội} (1) và Đaˋ Na˘˜ng(2)=Haˋ Nội+TP.HCM2\text{Đà Nẵng} (2) = \frac{\text{Hà Nội} + \text{TP.HCM}}{2}!

Một mối quan hệ toán học giả tạo đã bị áp đặt lên các địa danh vốn bình đẳng với nhau. Để chuyển đổi chữ thành số một cách thông minh và không bóp méo ngữ nghĩa, chúng ta cần đến Categorical Encoding.


2. Giải mã 3 kỹ thuật mã hóa biến định tính kinh điển

                              DỮ LIỆU DẠNG CHỮ (CATEGORICAL)
                                            │
                     ┌──────────────────────┴──────────────────────┐
                     ▼                                             ▼
        [CÓ THỨ BẬC TỰ NHIÊN?]                        [KHÔNG CÓ THỨ BẬC?]
       (Thấp < Trung bình < Cao)                      (Hà Nội, Đà Nẵng, TP.HCM)
                     │                                             │
                     ▼                                             ▼
            Ordinal Encoding                          Số lượng giá trị (Cardinality)?
                                                                   │
                                            ┌──────────────────────┴──────────────────────┐
                                            ▼ (Ít giá trị: < 10)                          ▼ (Nhiều giá trị: > 50)
                                    One-Hot Encoding                              Target Encoding

2.1. Ordinal Encoding (Mã hóa thứ bậc)

  • Áp dụng: Dành riêng cho các biến có quan hệ thứ tự tự nhiên hơn kém rõ ràng.
  • Cách làm: Gán số nguyên tăng dần theo đúng trật tự:
    • Học vấn: Cấp 3 →1\rightarrow 1, Đại học →2\rightarrow 2, Thạc sĩ →3\rightarrow 3.
    • Mức độ hài lòng: Không hài lòng →1\rightarrow 1, Bình thường →2\rightarrow 2, Rất hài lòng →3\rightarrow 3.

2.2. One-Hot Encoding (Mã hóa nhị phân 0-1)

  • Áp dụng: Dành cho các biến danh xưng không có thứ tự (Nominal) và có số lượng giá trị phân biệt nhỏ (Low-Cardinality, thường <10−15< 10-15 giá trị).
  • Cách làm: Biến mỗi giá trị thành một cột mới chứa số 1 (nếu đúng) hoặc 0 (nếu sai):
Khách hàngThành phốis_HaNoiis_DaNangis_TPHCM
AHà Nội100
BĐà Nẵng010
CTP.HCM001

Cạm bẫy Dummy Variable Trap (Bẫy đa cộng tuyến):
Nếu bạn biết một người không ở Hà Nội (0) và cũng không ở Đà Nẵng (0), chắc chắn 100% người đó ở TP.HCM! Cột thứ 3 hoàn toàn bị dư thừa (Redundant).
Trong các mô hình tuyến tính, việc giữ cả 3 cột sẽ gây ra hiện tượng Đa cộng tuyến hoàn hảo (Multicollinearity) làm sai lệch hệ số trọng số. Quy tắc chuẩn: Nếu có KK giá trị, chỉ giữ lại K−1K - 1 cột (dùng tham số drop='first').

2.3. Target Encoding (Mean Encoding - Vũ khí cho High-Cardinality)

  • Áp dụng: Dành cho các cột có hàng trăm hoặc hàng nghìn giá trị khác nhau (High-Cardinality), ví dụ: Mã bưu chính (Zip code), Model điện thoại, Ngành hàng con. Nếu dùng One-Hot sẽ làm bùng nổ hàng nghìn cột khiến mô hình bị nghẽn (Lời nguyền số chiều - Curse of Dimensionality).
  • Cách làm: Thay thế tên của từng danh mục bằng giá trị trung bình của biến mục tiêu yy trong danh mục đó.
    • Ví dụ: Trong bài toán phát hiện gian lận nợ xấu (y=1y = 1 là bùng nợ): Cư dân Quận 1 có tỷ lệ bùng nợ trung bình là 3%→3\% \rightarrow Thay chữ "Quận 1" bằng con số 0.03. Cư dân Huyện Bình Chánh có tỷ lệ bùng nợ 12%→12\% \rightarrow Thay bằng 0.12.

3. Bảng so sánh 3 kỹ thuật Encoding

Tiêu chíOrdinal EncodingOne-Hot Encoding (OHE)Target / Mean Encoding
Loại dữ liệu áp dụngBiến có thứ tự (Ordinal).Biến danh xưng ít giá trị (<10−15< 10-15).Biến danh xưng nhiều giá trị (>50> 50).
Tác động lên số chiều dữ liệuGiữ nguyên 1 cột.Phình to thành KK hoặc K−1K-1 cột mới.Giữ nguyên 1 cột duy nhất.
Rủi ro toán họcÉp quan hệ thứ tự giả tạo nếu áp dụng sai cho biến danh xưng.Lời nguyền số chiều; Đa cộng tuyến (Dummy Variable Trap).Rất dễ bị Data Leakage và Overfitting nếu không dùng kỹ thuật làm mịn (Smoothing).
Thuật toán phù hợp nhấtCây quyết định, Random Forest, XGBoost.Hồi quy tuyến tính, Logistic, Mạng nơ-ron.Mô hình cây trên dữ liệu cực lớn (CatBoost, LightGBM).

4. Thực hành thực chiến: Triển khai chuẩn chỉ bằng Scikit-Learn trong Python

Đoạn mã Python dưới đây sử dụng công cụ ColumnTransformer của Scikit-Learn để áp dụng đồng thời cả 3 kỹ thuật mã hóa cho từng loại cột khác nhau một cách chuyên nghiệp, khép kín và chống rò rỉ dữ liệu:

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder, TargetEncoder

# 1. Tạo tập dữ liệu thực tế gồm 3 loại cột định tính
data = {
    # Cột thứ bậc: Trình độ học vấn
    "education": ["High School", "Bachelor", "Master", "PhD", "Bachelor", "High School", "Master", "Bachelor"],
    # Cột danh xưng ít giá trị: Kênh tiếp thị
    "channel": ["Facebook", "Google", "Direct", "Facebook", "Direct", "Google", "Facebook", "Google"],
    # Cột danh xưng nhiều giá trị: Mã thiết bị (High Cardinality)
    "device_model": ["iPhone13", "GalaxyS22", "iPhone15", "Xiaomi12", "iPhone13", "GalaxyS22", "OppoReno", "iPhone15"],
    # Biến mục tiêu: Khách có mua hàng VIP hay không (0 hoặc 1)
    "is_vip": [0, 1, 1, 0, 1, 0, 0, 1]
}

df = pd.DataFrame(data)
X = df[["education", "channel", "device_model"]]
y = df["is_vip"]

# 2. Chia tập Train/Test TRƯỚC KHI mã hóa (Ngăn ngừa rò rỉ dữ liệu - Data Leakage)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)

# 3. Định nghĩa thứ tự tường minh cho Ordinal Encoding
education_order = [["High School", "Bachelor", "Master", "PhD"]]

# 4. Xây dựng ColumnTransformer phân luồng xử lý riêng cho từng cột
preprocessor = ColumnTransformer(
    transformers=[
        # Luồng 1: Ordinal Encoding cho cột education
        ("ord", OrdinalEncoder(categories=education_order), ["education"]),
        
        # Luồng 2: One-Hot Encoding cho cột channel (bỏ cột đầu tiên để chống đa cộng tuyến)
        ("ohe", OneHotEncoder(drop="first", sparse_output=False), ["channel"]),
        
        # Luồng 3: Target Encoding cho cột device_model (tích hợp kỹ thuật làm mịn smooth)
        ("target", TargetEncoder(smooth="auto", cv=3, random_state=42), ["device_model"])
    ],
    remainder="passthrough" # Giữ lại các cột số khác nếu có
)

# 5. Huấn luyện bộ mã hóa CHỈ TRÊN TẬP TRAIN và biến đổi tập Train
X_train_encoded = preprocessor.fit_transform(X_train, y_train)

# Lấy tên các cột mới sau khi biến đổi
feature_names = preprocessor.get_feature_names_out()
df_train_encoded = pd.DataFrame(X_train_encoded, columns=feature_names)

print("--- 1. BẢNG DỮ LIỆU GỐC TRƯỚC KHI MÃ HÓA ---")
print(X_train)

print("\n--- 2. BẢNG DỮ LIỆU ĐÃ MÃ HÓA THÀNH SỐ HỌC CHUẨN MỰC ---")
print(df_train_encoded.round(3))

# 6. Biến đổi tập Test độc lập (Chỉ dùng transform, tuyệt đối không fit lại!)
X_test_encoded = preprocessor.transform(X_test)
print("\n✅ Tập Test đã được chuyển đổi an toàn mà không bị Data Leakage!")

5. Câu hỏi phỏng vấn thực tế (Interview Q&A)

Câu 1 (Mức độ: Entry / Junior)

Câu hỏi: "Dummy Variable Trap (Bẫy đa cộng tuyến) trong One-Hot Encoding là gì? Nó gây hại như thế nào và cách khắc phục trong thực tế?"

Gợi ý trả lời:
"Dummy Variable Trap là hiện tượng xảy ra khi các cột mới tạo ra từ One-Hot Encoding có sự tương quan tuyến tính hoàn hảo với nhau (Perfect Multicollinearity).
Ví dụ: Cột Giới tính có 2 giá trị là Nam và Nữ. Nếu ta tạo ra 2 cột is_male và is_female, thì ta luôn có phương trình: is_female=1−is_male\text{is\_female} = 1 - \text{is\_male}. Hai cột này phụ thuộc hoàn toàn vào nhau.
Tác hại: Trong các mô hình tuyến tính (Linear / Logistic Regression), ma trận dữ liệu sẽ bị suy biến (không khả nghịch), khiến việc tính toán nghiệm ma trận thất bại hoặc ước lượng hệ số trọng số ww bị sai lệch nghiêm trọng và không ổn định.
Cách khắc phục: Luôn loại bỏ đi 1 cột trong nhóm mã hóa One-Hot (nếu có KK giá trị thì chỉ giữ lại K−1K - 1 cột, ví dụ dùng drop='first' trong Scikit-Learn). Dòng nào có toàn số 0 ở các cột còn lại sẽ ngầm định đại diện cho giá trị bị bỏ đi đó."

Câu 2 (Mức độ: Junior / Mid-level)

Câu hỏi: "Target Encoding có thể dẫn đến hiện tượng Data Leakage (Rò rỉ dữ liệu) như thế nào? Làm sao bạn xử lý triệt để vấn đề này khi huấn luyện mô hình?"

Gợi ý trả lời:
*"Target Encoding sử dụng trực tiếp nhãn mục tiêu yy để tính toán giá trị đại diện cho các đặc trưng XX.
Nguy cơ rò rỉ: Nếu chúng ta tính giá trị trung bình của yy trên TOÀN BỘ tập dữ liệu trước khi chia Train/Test, hoặc tính trực tiếp trên chính dòng dữ liệu đang xét, thông tin của nhãn mục tiêu sẽ bị rò rỉ ngược vào đặc trưng đầu vào. Mô hình sẽ học vẹt (Overfitting) rất nặng: ví dụ một mã danh mục chỉ xuất hiện đúng 1 lần duy nhất với y=1y = 1 sẽ bị gán điểm 1.0, khiến mô hình tin tưởng tuyệt đối vào danh mục đó một cách mù quáng.
Cách khắc phục chuẩn:

  1. Luôn thực hiện train_test_split trước; chỉ tính toán Target Encoding trên tập Train rồi áp dụng ánh xạ đó sang tập Test.
  2. Sử dụng kỹ thuật K-Fold Out-of-Fold Target Encoding (đã được tích hợp sẵn trong sklearn.preprocessing.TargetEncoder): Khi tính giá trị cho một dòng dữ liệu, ta chỉ lấy trung bình của các dòng khác trong các fold còn lại, loại trừ chính dòng đó.
  3. Áp dụng kỹ thuật Làm mịn (Smoothing / Regularization) để kéo các danh mục có quá ít mẫu về giá trị trung bình chung của toàn bộ tập dữ liệu."*

6. Key Takeaways (Ghi nhớ nhanh)

  1. Không gán số 1, 2, 3 bừa bãi cho biến danh xưng: Tránh để mô hình hiểu sai về quan hệ hơn kém số học giả tạo.
  2. Quy tắc phân luồng Encoding:
    • Có thứ bậc rõ ràng →\rightarrow Dùng Ordinal Encoding.
    • Không thứ tự, ít giá trị (<15< 15) →\rightarrow Dùng One-Hot Encoding (kèm drop='first').
    • Nhiều giá trị (>50> 50) →\rightarrow Dùng Target Encoding (kèm cơ chế làm mịn).
  3. Phòng chống Dummy Variable Trap: Luôn giữ lại K−1K - 1 cột nhị phân khi huấn luyện các mô hình tuyến tính.
  4. Quy tắc bất di bất dịch: Luôn chia train_test_split trước khi thực hiện bất kỳ phép biến đổi Feature Encoding nào để triệt tiêu nguy cơ rò rỉ dữ liệu (Data Leakage).
0Claps