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 ต้นไม้เล็กๆ แล้วดูว่ามันตั้งคำถามอะไร

  1. fit ต้นไม้ max_depth=2 แล้วพิมพ์กฎด้วย export_text — บรรทัดแรกคือคำถามที่ดีที่สุด (channel_promo <= 0.5)

    Python

ความลึกกับ overfitting

เทียบ train/test accuracy ตามความลึกของต้นไม้

  1. วนหลายค่าของ max_depth: train สูงขึ้นเรื่อยๆ แต่ test ลดลง (ลึก 8: 0.7078 / 0.6056)

    Python

เติมช่องว่าง

แทน ___ ด้วยโค้ดที่ถูกต้อง แล้วกดตรวจคำตอบ

ยังไม่ได้ทำ

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

1. Gini impurity ที่ 0 ของโหนดหนึ่งหมายความว่าอย่างไร
2. ต้นไม้ที่ไม่จำกัดความลึกได้ train accuracy 0.9995 แต่ test 0.5493 ในบทนี้ บอกอะไร
3. ทำไมต้นไม้เดี่ยวจึงไม่ต้อง scale ข้อมูล
4. ต้นไม้ depth 1 กับ depth 2 ในข้อมูลนี้ให้ accuracy เท่ากันพอดี ควรเลือกอย่างไร

สรุป

  • ต้นไม้ถามคำถามใช่/ไม่ใช่ต่อกัน เลือกคำถามที่ลด 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