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

ต้นไม้ตัดสินใจลึกแค่ไหนถึงจะดี

  1. เทียบ accuracy ของ train กับ test ที่ความลึกต่างๆ — ยิ่งลึก train ยิ่งสูงแต่ test ยิ่งแย่

    Python

cross-validation ที่ถูกวิธี

วัดผลให้เชื่อถือได้ไม่ขึ้นกับการแบ่งข้อมูลครั้งเดียว

  1. 5-fold CV แบบ shuffle ของ LogisticRegression — ได้ 5 คะแนนที่ไม่ต่างกันมาก

    Python

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

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

ยังไม่ได้ทำ

หาค่าเฉลี่ยของ 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

1. สัญญาณของ overfitting คือข้อไหน
2. ข้อดีของ k-fold cross-validation เมื่อเทียบกับการแบ่ง train/test ครั้งเดียว
3. ทำไมต้องใส่ shuffle=True ให้ KFold กับข้อมูลที่แถวเรียงตามเวลา
4. ถ้า max_depth หลายค่าให้คะแนน CV เท่ากัน ควรเลือกอย่างไร

สรุป

  • 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 หรือไม่