DS

[Tech Blog #14 | Data Science] Cây Quyết Định (Decision Tree): Bản Chất Trực Quan & Bí Quyết Khắc Phục "Bệnh Học Vẹt" (Overfitting)

1 views
[Tech Blog #14 | Data Science] Cây Quyết Định (Decision Tree): Bản Chất Trực Quan & Bí Quyết Khắc Phục "Bệnh Học Vẹt" (Overfitting)

1. Đặt vấn đề: Bạn đưa ra quyết định mỗi ngày như thế nào?

Hãy nhớ lại lần gần nhất bạn quyết định xem: "Chiều nay có nên xách giày đi chạy bộ ngoài công viên hay không?"
Trong đầu bạn sẽ diễn ra một chuỗi câu hỏi Đúng / Sai (Yes / No) nối tiếp nhau:

Trời có mưa không?
 ├── CÓ: Ở nhà xem phim (Dừng lại)
 └── KHÔNG: Nhiệt độ ngoài trời có trên 38°C không?
      ├── CÓ: Quá nóng, ở nhà bật điều hòa (Dừng lại)
      └── KHÔNG: Bạn có cảm thấy tràn đầy năng lượng không?
           ├── CÓ: Đi chạy bộ ngay! 🏃‍♂️
           └── KHÔNG: Ở nhà nghỉ ngơi.

Cách tư duy rẽ nhánh này chính là bản chất 100% của thuật toán Decision Tree (Cây quyết định) trong Machine Learning.
Khác với Logistic Regression (ép dữ liệu qua phương trình toán học phức tạp), Decision Tree chia không gian dữ liệu thành các "chiếc hộp" hình chữ nhật bằng các câu hỏi điều kiện liên tiếp.


2. Giải mã thuật ngữ & Cách máy tính "chọn câu hỏi"

2.1. Cấu trúc giải phẫu của một Decision Tree

  • Root Node (Nút gốc): Câu hỏi đầu tiên, chứa toàn bộ tập dữ liệu ban đầu.
  • Internal / Decision Node (Nút điều kiện rẽ nhánh): Các câu hỏi trung gian tiếp theo.
  • Leaf Node (Nút lá / Nút kết quả): Điểm dừng cuối cùng, đưa ra nhãn dự đoán (ví dụ: "Duyệt vay" hoặc "Từ chối vay"). Không còn câu hỏi nào sau nút lá.
       [ Nút Gốc (Root Node) ]         <--- Câu hỏi quan trọng nhất
              /        \
            Yes         No
            /             \
    [ Nút Nhánh ]      [ Nút Lá 1 ]    <--- Dự đoán: TỪ CHỐI
       /      \
     Yes       No
     /           \
[ Nút Lá 2 ]   [ Nút Lá 3 ]            <--- Dự đoán: DUYỆT VAY

2.2. Máy tính làm sao biết câu hỏi nào là "tốt nhất" để đặt lên đầu?

Hãy tưởng tượng bạn có một rổ gồm 50 quả bóng xanh và 50 quả bóng đỏ (dữ liệu đang rất hỗn loạn / vẩn đục).

  • Một câu hỏi xuất sắc là câu hỏi chia rổ bóng thành: Rổ 1 chứa 48 quả xanh, Rổ 2 chứa 48 quả đỏ (Dữ liệu trở nên cực kỳ tinh khiết - Pure).
  • Một câu hỏi tệ hại là chia ra mỗi rổ vẫn có 25 xanh và 25 đỏ (Hỗn loạn y nguyên).

Mức độ hỗn loạn này được đo lường bằng chỉ số Gini Impurity (hoặc Entropy):

  • Gini = 0.0: Tinh khiết tuyệt đối (100% các phần tử trong nút thuộc về cùng một lớp duy nhất).
  • Gini = 0.5: Hỗn loạn tối đa (Tỷ lệ 50% lớp A và 50% lớp B).

Nguyên tắc học của Decision Tree: Tại mỗi bước, thuật toán sẽ thử nghiệm tất cả các cột và các ngưỡng giá trị khác nhau, sau đó chọn câu hỏi nào giúp giảm độ hỗn loạn nhiều nhất (gọi là tối đa hóa Information Gain).


3. "Căn bệnh hiểm nghèo" của Decision Tree: Overfitting (Học vẹt)

Điểm mạnh nhất của Decision Tree cũng chính là điểm yếu chết người của nó: Nó quá thông minh và kiên trì.

Nếu bạn không can thiệp, cái cây sẽ tiếp tục mọc nhánh mãi cho đến khi mọi nút lá chỉ chứa đúng 1 điểm dữ liệu.

  • Hậu quả: Cây thuộc lòng từng điểm ngoại lệ (Outlier), từng điểm nhiễu của tập Train →\rightarrow Độ chính xác trên tập Train đạt 100%.
  • Nhưng khi đem sang tập dữ liệu thực tế (Test set), mô hình đoán sai be bét vì dữ liệu thực tế không có những điểm dị biệt ngẫu nhiên đó. Hiện tượng này gọi là Overfitting (Quá khớp / Học vẹt).

Liều thuốc đặc trị: Cắt tỉa cây (Pruning & Hyperparameter Tuning)

Để cây không bị "học vẹt", Data Scientist luôn phải thiết lập các rào cản:

  1. max_depth (Độ sâu tối đa): Giới hạn cây chỉ được rẽ nhánh tối đa 3–5 tầng.
  2. min_samples_split: Một nút phải có tối thiểu bao nhiêu mẫu dữ liệu thì mới được phép rẽ nhánh tiếp (vd: ≥10\ge 10 mẫu).
  3. min_samples_leaf: Một nút lá phải chứa tối thiểu bao nhiêu mẫu thì mới được công nhận (vd: ≥5\ge 5 mẫu).

4. Bảng so sánh: Decision Tree vs. Logistic Regression

Tiêu chíDecision Tree (Cây quyết định)Logistic Regression (Hồi quy Logistic)
Tính khả giải thích (Explainability)🟢 Cực kỳ trực quan, có thể vẽ thành lưu đồ (flowchart) cho sếp/khách hàng duyệt.🟡 Cần giải thích thông qua hệ số Log-odds / Odds Ratio.
Mối quan hệ phi tuyến tính🟢 Tự động bắt được các quy luật phi tuyến tính phức tạp mà không cần biến đổi toán học.🔴 Chỉ vẽ được đường ranh giới thẳng (Tuyến tính).
Độ nhạy với Chuẩn hóa (Scaling)🟢 Không cần chuẩn hóa dữ liệu (StandardScaler), bất chấp đơn vị đo là tỷ đồng hay tuổi tác.🔴 Cực kỳ nhạy cảm, bắt buộc phải đưa dữ liệu về cùng thang đo.
Nguy cơ Overfitting🔴 Rất cao nếu không giới hạn độ sâu của cây.🟢 Thấp hơn nhiều, mô hình ổn định hơn.
Ứng dụng tiêu biểuHệ thống luật phê duyệt tín dụng, chẩn đoán y khoa, phân loại khách hàng.Chấm điểm rủi ro xác suất, dự báo tỷ lệ nhấp chuột quảng cáo (CTR).

5. Thực hành thực chiến: Huấn luyện & Chặn đứng Overfitting bằng Scikit-Learn

Dưới đây là đoạn mã Python hoàn chỉnh xây dựng mô hình Phê duyệt khoản vay (Loan Approval), minh họa sự chênh lệch rõ rệt giữa một "cây mọc vô tội vạ" và một "cây được cắt tỉa chuẩn mực":

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier, export_text
from sklearn.metrics import accuracy_score

# 1. Tạo tập dữ liệu mẫu: Thu nhập (triệu/tháng), Điểm tín dụng (300-850), và Tình trạng Duyệt vay
np.random.seed(42)
n_samples = 300

income = np.random.uniform(10, 80, n_samples)
credit_score = np.random.uniform(400, 850, n_samples)

# Quy tắc ngầm: Thu nhập > 25M và Điểm tín dụng > 600 thì duyệt vay, kèm một chút nhiễu
loan_approved = (
    (income > 25) & (credit_score > 600) ^ (np.random.rand(n_samples) < 0.05)
).astype(int)

df = pd.DataFrame({
    "income_million": income,
    "credit_score": credit_score,
    "loan_approved": loan_approved
})

X = df[["income_million", "credit_score"]]
y = df["loan_approved"]

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.25, random_state=42
)

# ==============================================================================
# TRƯỜNG HỢP 1: CÂY KHÔNG KIỂM SOÁT (UNCONSTRAINED - DỄ BỊ OVERFITTING)
# ==============================================================================
unconstrained_tree = DecisionTreeClassifier(random_state=42) # Không giới hạn max_depth
unconstrained_tree.fit(X_train, y_train)

train_acc_1 = accuracy_score(y_train, unconstrained_tree.predict(X_train))
test_acc_1 = accuracy_score(y_test, unconstrained_tree.predict(X_test))

print("--- 1. CÂY KHÔNG GIỚI HẠN (MỌC TỰ DO) ---")
print(f"Độ sâu của cây: {unconstrained_tree.get_depth()}")
print(f"Train Accuracy: {train_acc_1 * 100:.2f}% (Thuộc vẹt gần như tuyệt đối)")
print(f"Test Accuracy : {test_acc_1 * 100:.2f}% (Chênh lệch lớn so với Train)")

# ==============================================================================
# TRƯỜNG HỢP 2: CÂY ĐƯỢC CẮT TỈA CHUẨN MỰC (PRUNED - TỐI ƯU HÓA)
# ==============================================================================
# Giới hạn độ sâu tối đa là 3 tầng và mỗi lá phải có ít nhất 5 mẫu
pruned_tree = DecisionTreeClassifier(
    max_depth=3, 
    min_samples_leaf=5, 
    random_state=42
)
pruned_tree.fit(X_train, y_train)

train_acc_2 = accuracy_score(y_train, pruned_tree.predict(X_train))
test_acc_2 = accuracy_score(y_test, pruned_tree.predict(X_test))

print("\n--- 2. CÂY ĐƯỢC CẮT TỈA (MAX_DEPTH = 3) ---")
print(f"Độ sâu của cây: {pruned_tree.get_depth()}")
print(f"Train Accuracy: {train_acc_2 * 100:.2f}%")
print(f"Test Accuracy : {test_acc_2 * 100:.2f}% (Khoảng cách Train/Test rất nhỏ -> Khái quát hóa tốt!)")

# 3. Trích xuất toàn bộ cây thành văn bản quy tắc trực quan (Rule-based)
print("\n--- CÁC QUY TẮC RẼ NHÁNH LOGIC CỦA CÂY ĐÃ TỐI ƯU ---")
tree_rules = export_text(pruned_tree, feature_names=["income_million", "credit_score"])
print(tree_rules)

6. 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: "Gini Impurity là gì? Khi nào một nút trong cây quyết định đạt Gini bằng 0?"

Gợi ý trả lời:
"Gini Impurity là một thước đo toán học dùng để định lượng mức độ 'vẩn đục' hay 'hỗn tạp' của một nút dữ liệu. Nó phản ánh xác suất bạn chọn ngẫu nhiên một mẫu dữ liệu và gán nhãn sai cho nó dựa trên phân phối nhãn trong nút đó.
Một nút sẽ đạt chỉ số Gini = 0.0 (tinh khiết tuyệt đối) khi 100% các mẫu dữ liệu trong nút đó đều thuộc về cùng một lớp duy nhất (ví dụ: tất cả 50 khách hàng trong nút đó đều bị Từ chối vay). Khi một nút đạt Gini bằng 0, nó sẽ trở thành một nút lá và thuật toán dừng rẽ nhánh tại nút đó."

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

Câu hỏi: "Làm thế nào bạn phát hiện một mô hình Decision Tree đang bị Overfitting và bạn sẽ dùng những kỹ thuật nào để khắc phục?"

Gợi ý trả lời:
*"Dấu hiệu rõ ràng nhất của Overfitting là sự chênh lệch lớn giữa hiệu suất trên tập Train và tập Test: ví dụ Train Accuracy đạt 98-100% nhưng Test Accuracy chỉ đạt 75%.
Để khắc phục, em sẽ áp dụng các kỹ thuật sau:

  1. Cắt tỉa sớm (Pre-pruning / Hyperparameter Tuning): Sử dụng GridSearch hoặc K-Fold Cross-Validation để tìm các giá trị tối ưu cho: max_depth (giới hạn độ sâu 3-5), min_samples_split (số mẫu tối thiểu để tách nhánh), và min_samples_leaf (số mẫu tối thiểu ở nút lá).
  2. Nâng cấp lên Ensemble Learning: Trong các dự án thực tế lớn, thay vì dùng một cây đơn lẻ dễ bị lung lay, em sẽ chuyển sang sử dụng mô hình kết hợp nhiều cây như Random Forest (kết hợp biểu quyết đa số qua Bagging) hoặc XGBoost / LightGBM (sửa sai tuần tự qua Boosting) để tăng tính ổn định và triệt tiêu phương sai cao."*

7. Key Takeaways (Ghi nhớ nhanh)

  1. Cây quyết định mô phỏng tư duy con người: Rất dễ vẽ lại thành lưu đồ (Flowchart) để giải trình với các phòng ban kinh doanh.
  2. Nguyên tắc rẽ nhánh: Tại mỗi bước, cây chọn câu hỏi nào giúp giảm thiểu độ hỗn loạn (Gini Impurity) nhiều nhất.
  3. Không cần chuẩn hóa dữ liệu: Decision Tree không quan tâm đến độ lớn của con số, dữ liệu tiền tỷ hay phần trăm đều hoạt động mượt mà mà không cần StandardScaler.
  4. Luôn luôn kiểm soát độ sâu (max_depth): Đừng bao giờ thả rông cho cây mọc tự do nếu không muốn rơi vào cái bẫy "học vẹt" (Overfitting).
0Claps