Machine Learning › พื้นฐาน ML และ scikit-learn
Overfitting และ cross-validation
ประมาณ 35 นาที
เป้าหมายของบท
- ระบุ overfitting ได้จากช่องว่างระหว่างคะแนน train กับ test
- ใช้ k-fold cross-validation (พร้อม shuffle) วัดผลอย่างเชื่อถือได้
- เลือก hyperparameter ด้วย CV ไม่ใช่ด้วยชุด test
overfitting คืออะไร
โมเดลที่ซับซ้อนเกินไปอาจ “จำ” ข้อมูล train (รวมถึงสัญญาณรบกวน) จนได้คะแนนสูงมากบน train แต่แย่บนข้อมูลใหม่ — เรียกว่า overfitting สัญญาณคือ ช่องว่างระหว่างคะแนน train กับ test ที่กว้าง
decision tree ที่ไม่จำกัดความลึก (max_depth=None) เป็นตัวอย่างชัดที่สุด: แตกกิ่งจนทายข้อมูล train ถูกเกือบ 100%
from sklearn.tree import DecisionTreeClassifier
tree = DecisionTreeClassifier(max_depth=None, random_state=42).fit(X_train, y_train)
tree.score(X_train, y_train), tree.score(X_test, y_test)
การจำกัดความซับซ้อน (เช่น max_depth) ลด overfitting — แต่ถ้าจำกัดมากเกินไปจะกลายเป็น underfitting (โมเดลง่ายเกินจนจับแบบแผนไม่ได้)
ทำไมต้อง cross-validation
การแบ่ง train/test ครั้งเดียวให้ตัวเลขที่ขึ้นกับว่าบังเอิญแบ่งแบบไหน และถ้าเลือกโมเดลจากผลบนชุด test หลายรอบ ชุด test ก็ “รั่ว” เข้ามาเป็นส่วนของการตัดสินใจ k-fold cross-validation แบ่งข้อมูลเป็น k ส่วน สลับกันเป็นชุดทดสอบทีละส่วน แล้วเฉลี่ยผล — ได้ทั้งค่าเฉลี่ยและความผันผวน (std) ของคะแนน
from sklearn.model_selection import KFold, cross_val_score
kf = KFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=kf)
scores.mean(), scores.std()
ระวัง: ต้อง shuffle
ถ้าแถวข้อมูลเรียงตามเวลา (ที่นี่เรียงตาม customer_id ซึ่งไล่ตามวันสมัคร) การแบ่ง fold แบบไม่สลับ (cv=5 เฉยๆ) จะได้ fold ที่หน้าตาต่างกันมาก
คะแนนแต่ละ fold จึงแกว่งรุนแรงและเชื่อถือไม่ได้ — ใช้ KFold(shuffle=True, random_state=...) หรือ StratifiedKFold(shuffle=True, ...) เสมอ (ยกเว้นงาน time series จริงที่ต้องใช้วิธีเฉพาะ)
เลือก hyperparameter ด้วย CV
ลองหลายค่า (เช่น max_depth 1–10) แล้วเลือกจากค่าเฉลี่ย CV ไม่ใช่จากชุด test — ถ้าหลายค่าให้คะแนนเท่ากัน ให้เลือกโมเดลที่ ง่ายกว่า (depth น้อยกว่า)
ตัวอย่างแก้โจทย์
กด "ขั้นต่อไป" ทีละขั้น ลองรันและแก้โค้ดในแต่ละขั้นได้
ความลึกของ tree กับ overfitting
ต้นไม้ตัดสินใจลึกแค่ไหนถึงจะดี
เทียบ accuracy ของ train กับ test ที่ความลึกต่างๆ — ยิ่งลึก train ยิ่งสูงแต่ test ยิ่งแย่
cross-validation ที่ถูกวิธี
วัดผลให้เชื่อถือได้ไม่ขึ้นกับการแบ่งข้อมูลครั้งเดียว
5-fold CV แบบ shuffle ของ LogisticRegression — ได้ 5 คะแนนที่ไม่ต่างกันมาก
เติมช่องว่าง
แทน ___ ด้วยโค้ดที่ถูกต้อง แล้วกดตรวจคำตอบ
หาค่าเฉลี่ยของ 5-fold CV accuracy ของ LogisticRegression เก็บไว้ในตัวแปร cv_mean เติม ___ ให้ครบ
แบบฝึกท้ายบท
บทนี้จะนับว่าเสร็จเมื่อผ่านข้อพื้นฐานและประยุกต์ครบ ข้อท้าทายได้ดาวเพิ่ม
🟢 พื้นฐาน
DecisionTreeClassifier(random_state=42) ที่ไม่จำกัดความลึก: accuracy บนชุด train ปัด 4 ตำแหน่ง เก็บไว้ในตัวแปร full_train_acc
ต้นไม้ตัวเดิม: accuracy บนชุดทดสอบ ปัด 4 ตำแหน่ง เก็บไว้ในตัวแปร full_test_acc
ช่องว่าง overfitting = train accuracy − test accuracy ของต้นไม้ที่ไม่จำกัดความลึก ปัด 4 ตำแหน่ง เก็บไว้ในตัวแปร gap_full
DecisionTreeClassifier(max_depth=3, random_state=42): accuracy บนชุดทดสอบ ปัด 4 ตำแหน่ง เก็บไว้ในตัวแปร depth3_test_acc
🟡 ประยุกต์
ค่าเฉลี่ย 5-fold CV accuracy (KFold shuffle, random_state=42, ใช้ X, y ทั้งชุด) ของ DecisionTreeClassifier(max_depth=3, random_state=42) ปัด 4 ตำแหน่ง เก็บไว้ในตัวแปร depth3_cv_mean
ค่าเฉลี่ย CV accuracy ของต้นไม้ max_depth=10 (ตั้งค่าเดียวกับ e5) ปัด 4 ตำแหน่ง เก็บไว้ในตัวแปร depth10_cv_mean
ลอง max_depth ตั้งแต่ 1 ถึง 10 ด้วย CV แบบเดียวกัน แล้วหา depth ที่ให้ค่าเฉลี่ย CV สูงสุด (ถ้าเท่ากันเลือก depth ที่น้อยกว่า) เก็บไว้ในตัวแปร best_depth
🔴 ท้าทาย
ค่าเฉลี่ย 5-fold CV accuracy ของ LogisticRegression(max_iter=2000) (KFold shuffle เดิม) ปัด 4 ตำแหน่ง เก็บไว้ในตัวแปร logreg_cv_mean
เทียบ std ของคะแนน ROC-AUC 5-fold ของ LogisticRegression ระหว่าง cv=5 แบบไม่ shuffle กับ StratifiedKFold(5, shuffle=True, random_state=42): แบบไม่ shuffle มี std สูงกว่าไหม (True/False) เก็บไว้ในตัวแปร unshuffled_is_noisier
Quiz
สรุป
- overfitting = train สูง test ต่ำ (tree ไม่จำกัดความลึก train ~1.0 test ~0.55) — จำกัดความซับซ้อนเพื่อลดช่องว่าง
- ใช้ KFold(shuffle=True, random_state=...) กับข้อมูลที่แถวเรียงตามเวลา ไม่งั้นคะแนนแต่ละ fold แกว่งรุนแรง
- เลือก hyperparameter ด้วยค่าเฉลี่ย CV ถ้าเท่ากันเลือกโมเดลที่ง่ายกว่า (ในข้อมูลนี้ tree ตื้นๆ ชนะ tree ลึก)
คำถามสัมภาษณ์
อธิบายว่าทำไมการเลือกโมเดลจากคะแนนบนชุด test หลายรอบจึงเป็นปัญหา
เพราะทุกครั้งที่เลือกจากผลบนชุด test ข้อมูลชุดนั้นถูกใช้ตัดสินใจไปแล้ว คะแนนสุดท้ายจึงดีเกินจริงและไม่ใช่ตัวแทนของข้อมูลใหม่ (test leakage) วิธีที่ถูกคือเลือกโมเดล/hyperparameter ด้วย cross-validation บนชุด train แล้วใช้ชุด test วัดผลรอบสุดท้ายเพียงครั้งเดียว
เจอโมเดลที่ train accuracy 99% แต่ test 55% จะทำอย่างไร
วินิจฉัยว่าเป็น overfitting แล้วลดความซับซ้อนของโมเดล (เช่น จำกัด max_depth, เพิ่ม regularization), เพิ่มข้อมูล หรือลดจำนวน feature จากนั้นวัดซ้ำด้วย cross-validation และเทียบกับ baseline ว่าโมเดลที่ง่ายขึ้นยังชนะ baseline หรือไม่