Decision Trees và Ensembles: từ một phép chia đến nhiều mô hình
Hiểu split, Gini impurity, overfitting và khác biệt giữa bagging với boosting qua một cây một tầng tự tính.
Cây quyết định mô tả một chuỗi câu hỏi để đi đến dự đoán. Bài viết dùng một đặc trưng số và nhãn tự tạo để tính split, rồi giải thích khi nào một cây cần được giới hạn hoặc kết hợp với các cây khác.
Mục tiêu học tập
- Tính impurity và mức giảm impurity của một split.
- Nhận biết overfitting và giới hạn diễn giải feature importance.
- Phân biệt bagging, random forest và gradient boosting.
Dự đoán bằng chuỗi câu hỏi
Một cây có thể hỏi số yêu cầu có nhỏ hơn một ngưỡng không, rồi hỏi thêm một thuộc tính khác. Mỗi lần trả lời đưa mẫu sang một nhánh; lá cuối chứa dự đoán.
Một cây ngắn dễ giải thích đường đi của một mẫu. Nhưng nếu có hàng trăm câu hỏi và nhiều nhánh, việc in được cây không có nghĩa toàn bộ mô hình còn dễ hiểu.
Với đặc trưng số, split thường có dạng . Training tìm đặc trưng j và ngưỡng t làm các nhóm con đồng nhất hơn về target. Lá phân loại có thể trả tần suất lớp; lá hồi quy có thể trả trung bình target.
Ngưỡng được tìm từ train. Dữ liệu thiếu, biến phân loại và đặc trưng chưa từng thấy cần cách xử lý phù hợp với implementation đang dùng.
CART xây dựng các split nhị phân theo tìm kiếm tham lam tại từng nút. Cải thiện cục bộ không bảo đảm cây toàn cục tối ưu. Mỗi vùng lá tạo một dự đoán hằng; nhiều split ghép thành ranh giới phi tuyến nhưng thường song song với các trục đặc trưng.
Một thay đổi nhỏ trong dữ liệu có thể làm split đầu thay đổi và kéo theo nhiều nhánh khác. Đó là động lực để đánh giá độ ổn định và cân nhắc ensemble.
Tính một split bằng Gini
Nếu một nhóm chứa cả hai nhãn gần ngang nhau, khó chọn một nhãn đại diện. Nếu hầu hết cùng một nhãn thì nhóm dễ dự đoán hơn. Ta ưu tiên câu hỏi tạo các nhóm con bớt lẫn lộn.
Nhóm rất nhỏ cũng có thể trông hoàn hảo chỉ vì ít điểm. Vì thế tính độ thuần không đủ; còn phải đặt yêu cầu số mẫu tối thiểu.
Với tỷ lệ lớp , Gini là . Điểm sau split là trung bình có trọng số theo số mẫu của Gini hai nhánh. Mức giảm bằng Gini cha trừ điểm sau split.
Ví dụ mã duyệt các trung điểm giữa giá trị x liên tiếp và chọn điểm nhỏ nhất. Đây là stump một tầng, không phải implementation đầy đủ của cây hoặc random forest.
Impurity được tính trên phân bố nhãn ở train; không phải sai số test hoặc xác suất lỗi vận hành. Với sample weights, dùng tổng trọng số thay số mẫu trong tính tỷ lệ và trọng số nhánh.
Khi x có nhiều giá trị độc nhất, nó có nhiều cơ hội thử ngưỡng hơn. Feature importance dựa trên giảm impurity có thể ưu ái các đặc trưng như vậy; cần kiểm tra ngoài mẫu và xem xét biến tương quan.
Giới hạn cây trước khi kết luận
Một cây có thể nhớ từng điểm bằng cách hỏi quá nhiều. Khi gặp điểm mới hơi khác, những nhánh đã nhớ có thể không còn hữu ích.
Thử giới hạn độ sâu và số mẫu ở lá. So kết quả ngoài tập học trước khi chọn cây lớn nhất chỉ vì nó đoán đúng mọi điểm đã thấy.
Các điều khiển phổ biến gồm max depth, minimum samples per leaf và cost-complexity pruning. Chọn chúng bằng validation hoặc cross-validation; test cuối không tham gia chọn cấu hình.
Theo dõi metric theo lớp và chi phí dự đoán, không chỉ accuracy train. Với nhiều dòng của cùng đối tượng, cần chia theo nhóm để tránh nhìn cùng đối tượng ở cả hai phía.
Cost-complexity pruning cân bằng lỗi của cây con với số lá bằng một hệ số phạt. Đường pruning tạo các cây ứng viên; chọn hệ số ngoài mẫu, không dùng cùng tập để vừa chọn vừa báo kết quả cuối.
Permutation importance phải được tính theo protocol đánh giá rõ ràng. Nó đo tác động của việc xáo một đặc trưng trong mô hình và dữ liệu đang xét, không khẳng định vai trò nhân quả của biến đó.
Bagging và boosting giải quyết gì?
Có thể hỏi nhiều cây rồi gộp câu trả lời. Nếu các cây không mắc lỗi giống hệt nhau, việc gộp có thể ổn định hơn một cây đơn lẻ.
Nhưng nhiều cây không tự sửa nhãn sai hoặc dữ liệu rò rỉ. Một tập điểm test đã lẫn vào train vẫn làm kết quả trông tốt giả tạo dù dùng thuật toán nào.
Bagging học nhiều mô hình trên các mẫu bootstrap rồi gộp dự đoán. Random forest còn tạo khác biệt bằng chọn tập đặc trưng ngẫu nhiên ở split. Gradient boosting thêm mô hình tuần tự để cải thiện mục tiêu hiện tại; với squared loss, residual là hướng minh họa trực quan.
Learning rate, số cây và độ phức tạp cây ảnh hưởng boosting. So ensemble với cây đơn bằng cùng split và metric, đồng thời ghi chi phí suy luận.
Bagging chủ yếu giảm variance khi lỗi giữa các thành viên không quá tương quan. Boosting là tối ưu hàm theo bước; negative gradient của loss quyết định tín hiệu cho bước tiếp theo, không luôn là residual theo nghĩa số học đơn giản.
Out-of-bag evaluation hữu ích cho một số mô hình bootstrap, nhưng không mặc nhiên thay thế phép kiểm tra theo thời gian hoặc nhóm. Đặc trưng tạo từ toàn bộ dữ liệu vẫn có thể gây leakage.
Workflow & kiến trúc
Ứng dụng & giới hạn
- Baseline phi tuyến cho dữ liệu bảng.
- Phân tích quy tắc và kiểm tra ensemble trước khi chọn mô hình triển khai.
Điểm mạnh
Cây nhỏ cho phép theo dõi đường dự đoán; ensemble có thể cải thiện độ ổn định hoặc chất lượng tùy bài toán.
Giới hạn cần nhớ
Cây sâu dễ overfit; ensemble không bảo đảm tốt hơn trên mọi dữ liệu. Impurity importance không phải bằng chứng nhân quả.
Thử trong một project nhỏ
Chạy mã và tính Gini của nhánh có một nhãn đối lập bằng tay. Đổi một nhãn để xem ngưỡng tốt nhất có thay đổi không. Viết protocol so sánh cây đơn với random forest mà không dùng test để tuning.
from collections import Counter
# Synthetic binary labels; this example builds only a one-level stump.
data = [(1.0, 0), (2.0, 0), (3.0, 1), (4.0, 1), (5.0, 1)]
def gini(labels):
counts = Counter(labels)
return 1.0 - sum((n / len(labels)) ** 2 for n in counts.values())
values = sorted({x for x, _ in data})
thresholds = [(a + b) / 2 for a, b in zip(values, values[1:])]
candidates = []
for threshold in thresholds:
left = [y for x, y in data if x <= threshold]
right = [y for x, y in data if x > threshold]
score = (len(left) * gini(left) + len(right) * gini(right)) / len(data)
candidates.append((score, threshold))
score, threshold = min(candidates)
gain = gini([y for _, y in data]) - score
assert threshold == 2.5 and abs(score) < 1e-12
print({"threshold": threshold, "weighted_gini": score, "gain": gain})Python chuẩn; một phép chia trên nhãn tự tạo. Điểm Gini này là điểm fit của ví dụ, không phải accuracy hay kết quả benchmark.
