Machine Learning › Tree และ boosting

Gradient Boosting

ประมาณ 35 นาที

เป้าหมายของบท

  • อธิบายกลไกของ gradient boosting (ต้นไม้ตื้นทีละต้น แก้ความผิดพลาดต่อเนื่อง)
  • ปรับ n_estimators / learning_rate / max_depth และดูเส้นโค้งด้วย staged_predict บน validation
  • ใช้ early stopping ในตัวเพื่อเลือกจำนวนรอบ

Boosting: ต้นไม้ตื้นๆ ที่ช่วยกันแก้ความผิดพลาดของต้นก่อนหน้า

Random forest สร้างต้นไม้ลึกๆ พร้อมกัน แล้วเฉลี่ย ส่วน gradient boosting สร้างต้นไม้ตื้นๆ ทีละต้น โดยแต่ละต้นพยายามแก้ส่วนที่โมเดลรวมของต้นก่อนหน้ายังทำนายพลาด:

  1. เริ่มจากคำตอบง่ายๆ (เช่น สัดส่วนคลาสเฉลี่ย)
  2. คำนวณ “ความผิดพลาด” (gradient ของ loss) ของแต่ละแถว
  3. เทรนต้นไม้เล็กๆ ทำนายความผิดพลาดนั้น แล้วบวกเข้าโมเดลโดยย่อขนาดด้วย learning_rate
  4. ทำซ้ำ n_estimators รอบ
from sklearn.ensemble import GradientBoostingClassifier

gb = GradientBoostingClassifier(n_estimators=100, learning_rate=0.1, max_depth=3, random_state=42)
gb.fit(X_train, y_train)

ไฮเปอร์พารามิเตอร์ที่ต้องรู้

  • n_estimators — จำนวนรอบ (มากขึ้น = พอดีข้อมูลเทรนมากขึ้น)
  • learning_rate — ก้าวของแต่ละรอบ; ค่าเล็กต้องใช้รอบมากขึ้นแต่มักนิ่งกว่า (เป็น trade-off คู่กับ n_estimators)
  • max_depth — ความลึกของต้นไม้ย่อย (boosting ใช้ตื้น 1–5)

ต่างจาก forest: ในบูสต์ จำนวนรอบที่มากเกินไปทำให้ overfit ได้ ผลจากข้อมูลของเรา (learning_rate=0.1, depth 3):

จำนวนรอบ test accuracy
1 0.5901
5 0.6437
10 0.6493
25 0.6535
50 0.6451
100 0.6394

ผลบน test ขึ้นไปสูงสุดราว 25 รอบแล้วค่อยๆ ลง ขณะที่ train ยังเพิ่มขึ้น (100 รอบ: train 0.7172; 300 รอบ: train 0.8074 / test 0.6225)

staged_predict และ early stopping

gb.staged_predict(X) คืนคำทำนายหลังแต่ละรอบ ทำให้เห็นเส้นโค้งแบบนี้ได้ในการ fit ครั้งเดียว — แต่ให้ดูบน validation ไม่ใช่ test ทางลัดคือ early stopping ในตัว: ตั้ง n_iter_no_change=10 และ validation_fraction=0.1 แล้วมันจะกันข้อมูลส่วนหนึ่งไว้ตรวจเอง และหยุดเมื่อผลไม่ดีขึ้น 10 รอบติด

gb = GradientBoostingClassifier(n_estimators=500, validation_fraction=0.1, n_iter_no_change=10, random_state=42)
gb.fit(X_train, y_train)
gb.n_estimators_   # 25 ในข้อมูลนี้ (ไม่ใช่ 500)

เมื่อเทียบกับ forest

boosting มักแม่นกว่าเมื่อมีสัญญาณซับซ้อนหลายชั้นพอ แต่จูนยากกว่า ไวต่อ noise/รอบมากเกินไป ในข้อมูลของเรา (CV 5-fold) GB ค่าเริ่มต้นได้ accuracy ≈ 0.6492 และ AUC ≈ 0.6526 ซึ่งสูงกว่า forest (0.6432 / 0.6420) เพียงเล็กน้อย — ยังอยู่ในระดับ “ต่างกันไม่กี่ร้อยส่วน” ไม่ใช่ชัยชนะเด็ดขาด

ตัวอย่างแก้โจทย์

กด "ขั้นต่อไป" ทีละขั้น ลองรันและแก้โค้ดในแต่ละขั้นได้

GB ค่าเริ่มต้นและจำนวนรอบ

ดูว่าผลเปลี่ยนตามจำนวนรอบอย่างไร

  1. fit GB 100 รอบ (lr 0.1, depth 3): train 0.7172 test 0.6394

    Python

early stopping ในตัว

ให้ GB หยุดเองเมื่อ validation ไม่ดีขึ้น

  1. ตั้ง n_estimators=500 แต่เปิด n_iter_no_change=10 — มันหยุดที่ 25 รอบ (n_estimators_) แทนที่จะครบ 500

    Python

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

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

ยังไม่ได้ทำ

fit GB ด้วย learning_rate=0.05 (random_state=42) แล้วเก็บ accuracy บนชุดทดสอบไว้ในตัวแปร lr_acc เติม ___ ให้ครบ

แบบฝึกท้ายบท

บทนี้จะนับว่าเสร็จเมื่อผ่านข้อพื้นฐานและประยุกต์ครบ ข้อท้าทายได้ดาวเพิ่ม

🟢 พื้นฐาน

🟢 พื้นฐานยังไม่ได้ทำ

fit GradientBoostingClassifier(random_state=42) (ค่าเริ่มต้นอื่นๆ) แล้วเก็บ accuracy บน train และ test (ปัด 4 ตำแหน่ง) ไว้ในตัวแปร gb_train_acc, gb_test_acc

🟢 พื้นฐานยังไม่ได้ทำ

GB ที่ตั้ง n_estimators=30 (random_state=42) มีกี่รอบ/ต้นไม้ย่อย (ดูจาก n_estimators_) เก็บไว้ในตัวแปร n_stages

🟢 พื้นฐานยังไม่ได้ทำ

GB ที่ learning_rate=0.05 (random_state=42): accuracy บนชุดทดสอบ ปัด 4 ตำแหน่ง เก็บไว้ในตัวแปร lr05_test_acc

🟢 พื้นฐานยังไม่ได้ทำ

GB ที่ใช้ต้นไม้ตอ (max_depth=1, random_state=42): accuracy บนชุดทดสอบ ปัด 4 ตำแหน่ง เก็บไว้ในตัวแปร stump_test_acc

🟡 ประยุกต์

🟡 ประยุกต์ยังไม่ได้ทำ

fit GB ค่าเริ่มต้น (random_state=42) แล้วใช้ staged_predict บน X_test เก็บ accuracy หลังรอบที่ 1, 10 และ 50 (ปัด 4 ตำแหน่ง) ไว้ในตัวแปร acc_1, acc_10, acc_50

🟡 ประยุกต์ยังไม่ได้ทำ

ใช้ชุด validation (X_tr/X_val/y_tr/y_val ที่แบ่งจาก train ไว้ให้แล้ว) เทรน GB 100 รอบ (random_state=42) แล้วหาจำนวนรอบที่ให้ accuracy บน X_val สูงสุด (ถ้าเสมอเอาน้อยสุด) เก็บไว้ใน best_stage และ accuracy ของมัน (ปัด 4 ตำแหน่ง) ไว้ใน best_val_acc

🟡 ประยุกต์ยังไม่ได้ทำ

GB ที่เปิด early stopping (n_estimators=500, validation_fraction=0.1, n_iter_no_change=10, random_state=42): หยุดที่กี่รอบ เก็บไว้ใน stopped_at และ accuracy บนชุดทดสอบ (ปัด 4 ตำแหน่ง) ไว้ใน es_test_acc

🔴 ท้าทาย

🔴 ท้าทายยังไม่ได้ทำ

CV 5-fold (skf ที่ให้ไว้) ของ ROC-AUC เฉลี่ย เทียบ GB ค่าเริ่มต้น (random_state=42) กับ random forest (100 ต้น, min_samples_leaf=10, random_state=42) ปัด 4 ตำแหน่ง เก็บไว้ในตัวแปร gb_cv_auc, rf_cv_auc

🔴 ท้าทายยังไม่ได้ทำ

GB 100 รอบ (random_state=42) เทียบ learning_rate สูง (0.5) กับต่ำ (0.01): accuracy บน train และ test ของแต่ละแบบ (ปัด 4 ตำแหน่ง) เก็บไว้ใน fast_train, fast_test, slow_train, slow_test

Quiz

1. gradient boosting ต่างจาก random forest ตรงไหนเป็นหลัก
2. learning_rate ใน gradient boosting ทำอะไร
3. ในบทนี้ test accuracy ของ GB สูงสุดราว 25 รอบ (0.6535) แล้วลดที่ 100 รอบ (0.6394) ขณะ train เพิ่ม บ่งบอกว่าอะไร
4. ทำไมการเลือกจำนวนรอบจาก staged_predict บน X_test แล้วรายงานผล X_test จึงไม่ควร

สรุป

  • Gradient boosting เพิ่มต้นไม้ตื้นทีละต้นเพื่อแก้ความผิดพลาดของโมเดลรวม; learning_rate ย่อแต่ละก้าว
  • รอบมากเกินไป overfit ได้ (ข้อมูลนี้ test สูงสุดที่ ~25 รอบ 0.6535 แล้วลดเหลือ 0.6394 ที่ 100 รอบ) เลือกรอบด้วย validation/early stopping ไม่ใช่ test
  • GB ค่าเริ่มต้นได้ CV AUC 0.6526 เทียบ forest 0.6420 — ต่างกันเล็กน้อยเท่านั้น

คำถามสัมภาษณ์

อธิบาย gradient boosting ให้คนที่รู้จักแต่ decision tree

เริ่มจากคำตอบง่ายๆ แล้วสร้างต้นไม้เล็กๆ ทีละต้นเพื่อทำนาย "ส่วนที่ยังพลาด" (gradient ของ loss) ของโมเดลปัจจุบัน แล้วบวกเข้าไปโดยคูณ learning_rate ทำซ้ำหลายรอบ ผลรวมของต้นไม้ตื้นๆ เหล่านี้จึงสร้างขอบเขตการตัดสินใจที่ซับซ้อนได้

จะเลือก n_estimators และ learning_rate อย่างไรไม่ให้ overfit

ทั้งสองแลกกัน (ก้าวเล็ก = ต้องรอบมาก) ตั้ง learning_rate ค่อนข้างเล็กแล้วใช้ early stopping (n_iter_no_change + validation_fraction) หรือ validation set แยกเพื่อหยุดตอน validation หยุดดีขึ้น และยืนยันด้วย cross-validation แล้วแตะ test ครั้งเดียว