Machine Learning › Tree และ boosting
Decision Tree
ประมาณ 30 นาที
เป้าหมายของบท
- อธิบายได้ว่า decision tree เลือกคำถามด้วย Gini impurity อย่างไร
- อ่านกฎของต้นไม้ด้วย export_text และดูจำนวนใบ/ความลึก
- เห็นว่าต้นไม้ไม่จำกัดความลึกจำข้อมูลเทรน และควบคุมด้วย max_depth / min_samples_leaf
ต้นไม้ตัดสินใจคือ “ชุดคำถามใช่/ไม่ใช่”
Decision tree ทำนายโดยถามคำถามต่อกันทีละข้อ เช่น “ลูกค้ามาจากช่องทาง promo ไหม?” → ถ้าใช่ไปทางซ้าย ถ้าไม่ใช่ไปทางขวา ถามต่อจนถึงใบ (leaf) ที่บอกคำตอบ ข้อดีคือ อ่านเป็นกฎได้ ไม่ต้อง scale ข้อมูล และจับความสัมพันธ์ที่ไม่เป็นเส้นตรงได้
from sklearn.tree import DecisionTreeClassifier, export_text
tree = DecisionTreeClassifier(max_depth=2, random_state=42).fit(X_train, y_train)
print(export_text(tree, feature_names=list(X.columns)))
มันเลือกคำถามอย่างไร: Gini impurity
ทุกโหนดมี impurity = ความ “ปน” ของคลาส (Gini = 1 − Σ p²) ค่า 0 คือบริสุทธิ์ (มีคลาสเดียว) ค่าสูงสุด 0.5 (สำหรับ 2 คลาส) คือปนครึ่งๆ ตอนสร้างต้นไม้ มันลองทุกคำถามที่เป็นไปได้ (feature ไหน, ตัดที่ค่าเท่าไร) แล้วเลือกคำถามที่ ทำให้ impurity ของลูกลดลงมากที่สุด จากนั้นทำซ้ำในแต่ละกิ่งจนถึงเงื่อนไขหยุด
ในข้อมูลของเรา โหนดราก (ก่อนถามอะไร) มี Gini ≈ 0.4836 และคำถามแรกที่ดีที่สุดคือ channel_promo <= 0.5 — สอดคล้องกับที่เรารู้ว่า promo คือสัญญาณเดียวที่แรง
ต้นไม้ที่ไม่จำกัดความลึก = จำข้อมูลเทรน
ถ้าไม่ตั้ง max_depth ต้นไม้จะถามต่อไปเรื่อยๆ จนทุกใบบริสุทธิ์ — เท่ากับ จำ ข้อมูลเทรนทีละแถว
| max_depth | จำนวนใบ | train acc | test acc |
|---|---|---|---|
| 2 | 4 | 0.6585 | 0.6493 |
| 5 | 24 | 0.6670 | 0.6366 |
| 8 | 87 | 0.7078 | 0.6056 |
| ไม่จำกัด (ลึก 28) | 664 | 0.9995 | 0.5493 |
train เกือบ 100% แต่ test ต่ำกว่าการเดา “ซื้อซ้ำทุกคน” (0.5901) เสียอีก — นี่คือ overfitting แบบตำราเลย
ควบคุมความซับซ้อน (hyperparameter)
max_depth— จำกัดความลึกสูงสุดmin_samples_leaf— ใบหนึ่งต้องมีตัวอย่างอย่างน้อยกี่แถว (มาก = ต้นไม้เล็กลง)min_samples_split,max_leaf_nodes— ควบคุมแนวเดียวกัน
เลือกค่าด้วย cross-validation ไม่ใช่ดูจาก test — และเมื่อผลเสมอกัน ให้เลือกต้นไม้ที่ เล็กกว่า (ข้อมูลนี้ depth 1 กับ 2 ทำนายเหมือนกันทุกแถว เพราะคำถามข้อที่สองไม่เปลี่ยนคำตอบของใบใดเลย)
ตัวอย่างแก้โจทย์
กด "ขั้นต่อไป" ทีละขั้น ลองรันและแก้โค้ดในแต่ละขั้นได้
อ่านกฎจากต้นไม้
fit ต้นไม้เล็กๆ แล้วดูว่ามันตั้งคำถามอะไร
fit ต้นไม้ max_depth=2 แล้วพิมพ์กฎด้วย export_text — บรรทัดแรกคือคำถามที่ดีที่สุด (channel_promo <= 0.5)
ความลึกกับ overfitting
เทียบ train/test accuracy ตามความลึกของต้นไม้
วนหลายค่าของ max_depth: train สูงขึ้นเรื่อยๆ แต่ test ลดลง (ลึก 8: 0.7078 / 0.6056)
เติมช่องว่าง
แทน ___ ด้วยโค้ดที่ถูกต้อง แล้วกดตรวจคำตอบ
fit ต้นไม้ max_depth=3 และ min_samples_leaf=30 แล้วเก็บจำนวนใบไว้ในตัวแปร n_leaves เติม ___ ให้ครบ
แบบฝึกท้ายบท
บทนี้จะนับว่าเสร็จเมื่อผ่านข้อพื้นฐานและประยุกต์ครบ ข้อท้าทายได้ดาวเพิ่ม
🟢 พื้นฐาน
fit DecisionTreeClassifier(max_depth=3, random_state=42) บน train แล้วเก็บ accuracy บนชุดทดสอบ (ปัด 4 ตำแหน่ง) ไว้ในตัวแปร depth3_test_acc
ต้นไม้ที่ไม่จำกัดความลึก (random_state=42) มีกี่ใบและลึกกี่ชั้น เก็บไว้ในตัวแปร full_leaves, full_depth
accuracy บน train ของต้นไม้ที่ไม่จำกัดความลึก ปัด 4 ตำแหน่ง เก็บไว้ในตัวแปร full_train_acc
ต้นไม้ max_depth=2 (random_state=42) ใช้ feature ไหนเป็นคำถามแรก (ที่โหนดราก) เก็บชื่อคอลัมน์ (string) ไว้ในตัวแปร root_feature
🟡 ประยุกต์
ใช้ StratifiedKFold 5-fold (skf ที่ให้ไว้) วัด accuracy เฉลี่ยของต้นไม้ที่ max_depth = 1..8 (random_state=42) เก็บ depth ที่ CV accuracy สูงสุดไว้ใน best_cv_depth (ถ้าเสมอให้เลือกค่าน้อยกว่า) และ accuracy ของมัน (ปัด 4 ตำแหน่ง) ไว้ใน best_cv_acc
fit ต้นไม้ที่ไม่จำกัดความลึกแต่ตั้ง min_samples_leaf=20 (random_state=42) เก็บจำนวนใบไว้ใน leaves_20 และ accuracy บนชุดทดสอบ (ปัด 4 ตำแหน่ง) ไว้ใน test_acc_20
ช่องว่าง overfitting (train acc − test acc) ของต้นไม้ max_depth=3 และของต้นไม้ไม่จำกัดความลึก ปัด 4 ตำแหน่ง เก็บไว้ในตัวแปร gap_depth3, gap_full
🔴 ท้าทาย
ต้นไม้ max_depth=3 (random_state=42): feature ที่มี feature_importances_ สูงสุดคืออะไร (string) เก็บไว้ใน top_feature และค่าความสำคัญของมัน (ปัด 3 ตำแหน่ง) ไว้ใน top_importance
ต้นไม้ max_depth=3 (random_state=42): แถวในชุดทดสอบตกไปอยู่ในกี่ใบ (leaves_used) และใบที่ใหญ่ที่สุดมีสัดส่วนกี่ส่วนของชุดทดสอบ (ปัด 4 ตำแหน่ง) เก็บไว้ใน biggest_leaf_share
Quiz
สรุป
- ต้นไม้ถามคำถามใช่/ไม่ใช่ต่อกัน เลือกคำถามที่ลด Gini impurity ของลูกมากที่สุด อ่านเป็นกฎได้และไม่ต้อง scale
- ไม่จำกัดความลึก → 664 ใบ train 0.9995 แต่ test 0.5493 (แย่กว่า baseline 0.5901) คือ overfitting
- คุมด้วย max_depth/min_samples_leaf เลือกด้วย CV และเมื่อเสมอให้เลือกต้นไม้ที่เล็กกว่า
คำถามสัมภาษณ์
Decision tree เลือกจุดแยก (split) อย่างไร
ลองทุกคำถามที่เป็นไปได้ (ทุก feature ทุกค่าตัด) แล้วเลือกคำถามที่ทำให้ impurity (เช่น Gini) ของโหนดลูกที่ถ่วงน้ำหนักตามจำนวนแถวลดลงมากที่สุด จากนั้นทำซ้ำในแต่ละกิ่ง (greedy, ทีละขั้น) จนถึงเงื่อนไขหยุด เช่น max_depth, min_samples_leaf หรือโหนดบริสุทธิ์
ทำไม decision tree ถึง overfit ง่ายและแก้อย่างไร
ถ้าปล่อยให้โตเต็มที่ มันจะแบ่งจนทุกใบบริสุทธิ์ คือจำข้อมูลเทรนรวมทั้ง noise แก้โดยจำกัดขนาด (max_depth, min_samples_leaf, max_leaf_nodes), pruning หรือใช้ ensemble อย่าง random forest / boosting และเลือกไฮเปอร์พารามิเตอร์ด้วย cross-validation