พื้นฐาน ML
UAT 315 ปัญญาประดิษฐ์ การวิเคราะห์ข้อมูล และการมองเห็นด้วยคอมพิวเตอร์สำหรับระบบอากาศยานไร้คนขับ
บทเรียน
เมื่อเรียนจบโมดูลนี้ ผู้เรียนจะสามารถ
- แยกการเรียนรู้แบบมีผู้สอนกับไม่มีผู้สอน และงานจำแนกประเภทกับงานพยากรณ์ค่า
- แบ่งข้อมูลเป็นชุดฝึก ชุดตรวจสอบ และชุดทดสอบ และอธิบายหน้าที่ของแต่ละชุด
- ฝึกและเปรียบเทียบ logistic regression กับ decision tree ด้วย scikit-learn
- อธิบายการเรียนน้อยไปและการเรียนเกิน และเหตุที่ความแม่นยำอย่างเดียวหลอกได้
ทำไมต้องรู้
การเรียนรู้ของเครื่อง (machine learning, ML) คือการให้คอมพิวเตอร์หากฎจากตัวอย่าง แทนที่คนจะเขียนกฎเองทุกข้อ งานโดรนหลายอย่างเหมาะกับ ML เช่น ทำนายว่ามอเตอร์ใกล้เสียจาก log การสั่น หรือจำแนกพื้นที่น้ำท่วมจากภาพ แต่แบบจำลองที่ได้คะแนนดีบนข้อมูลที่เคยเห็นอาจใช้ไม่ได้เลยกับข้อมูลใหม่ โมดูลนี้ปูหลักการที่ทำให้ผลของ ML เชื่อถือได้
ชนิดของการเรียนรู้
- เรียนแบบมีผู้สอน (supervised learning) มีคำตอบที่ถูกต้อง (ป้าย, label) ของตัวอย่างฝึก แบ่งเป็น จำแนกประเภท (classification) เมื่อคำตอบเป็นกลุ่ม เช่น มอเตอร์ปกติหรือผิดปกติ และ พยากรณ์ค่า (regression) เมื่อคำตอบเป็นตัวเลข เช่น เวลาบินที่เหลือ
- เรียนแบบไม่มีผู้สอน (unsupervised learning) ไม่มีป้าย แบบจำลองหาโครงสร้างเอง เช่น จัดกลุ่มเที่ยวบินที่คล้ายกัน หรือหาเที่ยวบินที่แปลกไปจากกลุ่ม
การฝึกคือการปรับค่าพารามิเตอร์ ของแบบจำลองให้คำทำนายใกล้ป้ายจริงมากที่สุด โดยวัดความต่างด้วย ฟังก์ชันความสูญเสีย (loss)
แบ่งข้อมูลสามชุด
ตัวอย่างนี้ใช้ข้อมูลสังเคราะห์ 400 เที่ยวบิน คุณลักษณะคือการสั่นเฉลี่ย (g) และกระแสขณะลอยตัว (A) ป้ายคือมอเตอร์ผิดปกติหรือไม่ ข้อมูลแบ่งเป็นสามชุดที่มีหน้าที่ต่างกัน
- ชุดฝึก (train) ใช้ปรับพารามิเตอร์
- ชุดตรวจสอบ (validation) ใช้เลือกแบบจำลองและค่าตั้ง เช่น ความลึกของต้นไม้
- ชุดทดสอบ (test) เก็บไว้ประเมิน ครั้งเดียว หลังเลือกทุกอย่างแล้ว
import numpy as np
from sklearn.model_selection import train_test_split
rng = np.random.default_rng(345)
n = 400
faulty = rng.random(n) < 0.2
vibration = np.where(faulty, rng.normal(0.17, 0.05, n), rng.normal(0.12, 0.04, n))
current = np.where(faulty, rng.normal(20, 2.5, n), rng.normal(18, 2.5, n))
X = np.column_stack([vibration, current])
y = faulty.astype(int)
X_rest, X_test, y_rest, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=345)
X_train, X_val, y_train, y_val = train_test_split(X_rest, y_rest, test_size=0.25, stratify=y_rest, random_state=345)
print(f"train {len(y_train)} validation {len(y_val)} test {len(y_test)} faulty share {y.mean():.1%}")
train 240 validation 80 test 80 faulty share 21.8%
stratify=y รักษาสัดส่วนมอเตอร์ผิดปกติให้ใกล้เคียงกันทุกชุด ซึ่งสำคัญเมื่อกลุ่มหนึ่งมีน้อย
แบบจำลองแรกและกับดักของความแม่นยำ
Logistic regression เป็นแบบจำลองจำแนกประเภทพื้นฐาน รวมคุณลักษณะแบบเส้นตรงแล้วแปลงเป็นความน่าจะเป็นด้วยฟังก์ชันซิกมอยด์ ก่อนดูผล ต้องมี ค่าอ้างอิง (baseline) เสมอ
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, recall_score
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
baseline = np.zeros_like(y_val)
print(f"always 'healthy': accuracy {accuracy_score(y_val, baseline):.3f} recall {recall_score(y_val, baseline):.3f}")
model = make_pipeline(StandardScaler(), LogisticRegression()).fit(X_train, y_train)
pred = model.predict(X_val)
print(f"logistic regression: accuracy {accuracy_score(y_val, pred):.3f} recall {recall_score(y_val, pred):.3f}")
pred_03 = (model.predict_proba(X_val)[:, 1] >= 0.3).astype(int)
print(f"threshold 0.3: accuracy {accuracy_score(y_val, pred_03):.3f} recall {recall_score(y_val, pred_03):.3f}")
always 'healthy': accuracy 0.787 recall 0.000
logistic regression: accuracy 0.950 recall 0.765
threshold 0.3: accuracy 0.900 recall 0.824
แบบจำลองที่ตอบว่า “ปกติ” ทุกครั้งได้ความแม่นยำเกือบ 79% ทั้งที่ไม่พบมอเตอร์ผิดปกติเลยสักลำ (recall = 0) นี่คือ กับดักของความแม่นยำ เมื่อกลุ่มหนึ่งมีน้อย logistic regression ดีกว่ามาก และถ้าลดจุดตัดความน่าจะเป็นจาก 0.5 เป็น 0.3 จะพบมอเตอร์ผิดปกติมากขึ้น แลกกับการเตือนผิดเพิ่ม ในงานซ่อมบำรุงที่การพลาดมอเตอร์เสียอาจทำให้โดรนตก การเลือก recall สูงมักคุ้มกว่า make_pipeline ช่วยให้ StandardScaler เรียนสถิติจากชุดฝึกเท่านั้นตามที่เรียนในโมดูล 1
เรียนน้อยไปและเรียนเกิน
- เรียนน้อยไป (underfitting) แบบจำลองง่ายเกินไป ทำได้ไม่ดีทั้งชุดฝึกและชุดตรวจสอบ
- เรียนเกิน (overfitting) แบบจำลองจำรายละเอียดและสัญญาณรบกวนของชุดฝึก ทำได้ดีมากบนชุดฝึกแต่แย่ลงบนข้อมูลใหม่
Decision tree แบ่งข้อมูลด้วยคำถามใช่หรือไม่ทีละขั้น ความลึกของต้นไม้คุมความซับซ้อน
from sklearn.tree import DecisionTreeClassifier
for depth in (1, 2, 3, 4, 6, 10, None):
tree = DecisionTreeClassifier(max_depth=depth, random_state=0).fit(X_train, y_train)
print(f"max_depth {str(depth):>4}: train {accuracy_score(y_train, tree.predict(X_train)):.3f}"
f" validation {accuracy_score(y_val, tree.predict(X_val)):.3f}")
max_depth 1: train 0.867 validation 0.887
max_depth 2: train 0.879 validation 0.863
max_depth 3: train 0.904 validation 0.925
max_depth 4: train 0.929 validation 0.925
max_depth 6: train 0.954 validation 0.912
max_depth 10: train 0.992 validation 0.887
max_depth None: train 1.000 validation 0.887
เมื่อต้นไม้ลึกขึ้น ความแม่นยำบนชุดฝึกเพิ่มจนถึง 1.000 แต่บนชุดตรวจสอบสูงสุดที่ความลึก 3 ถึง 4 แล้วลดลง ความลึกไม่จำกัดคือการเรียนเกินอย่างชัดเจน เราจึงเลือกความลึกจากชุดตรวจสอบ ไม่ใช่จากชุดฝึก
ตัวอย่างที่ 1 ประเมินครั้งสุดท้ายด้วยชุดทดสอบ
หลังเลือกความลึก 3 จากชุดตรวจสอบแล้ว ฝึกใหม่ด้วยชุดฝึกรวมชุดตรวจสอบ และประเมินกับชุดทดสอบเพียงครั้งเดียว
final = DecisionTreeClassifier(max_depth=3, random_state=0).fit(X_rest, y_rest)
test_pred = final.predict(X_test)
print(f"test accuracy {accuracy_score(y_test, test_pred):.3f} test recall {recall_score(y_test, test_pred):.3f}")
test accuracy 0.800 test recall 0.412
ผลบนชุดทดสอบต่ำกว่าบนชุดตรวจสอบมาก ความแม่นยำลดจาก 0.925 เหลือ 0.800 และพบมอเตอร์ผิดปกติเพียง 41% ผลแบบนี้พบบ่อยในงานจริง สาเหตุหนึ่งคือชุดตรวจสอบมีมอเตอร์ผิดปกติเพียง 17 ลำ ตัวเลขจากชุดเล็กจึงแกว่งมาก และการเลือกค่าที่ดีที่สุดจากชุดตรวจสอบก็ทำให้ผลบนชุดนั้นดูดีเกินจริงเล็กน้อย ผลบนชุดทดสอบคือตัวเลขที่ต้องรายงานตามจริง ถ้าเห็นผลนี้แล้วย้อนกลับไปปรับความลึกเพื่อให้ตัวเลขดีขึ้น ชุดทดสอบจะไม่เป็นอิสระอีกต่อไป ทางที่ถูกคือเก็บข้อมูลเพิ่ม ใช้การตรวจสอบไขว้ (cross-validation) และเตรียมชุดทดสอบใหม่สำหรับรอบถัดไป
กิจกรรมในชั้นเรียน
กิจกรรม: ออกแบบงาน ML สำหรับซ่อมบำรุง
- กำหนดปัญหาหนึ่ง เช่น ทำนายว่าแบตเตอรี่ควรปลดระวางหรือไม่ ระบุว่าเป็นงานจำแนกประเภทหรือพยากรณ์ค่า มีผู้สอนหรือไม่ และป้ายมาจากไหน
- อภิปรายว่าผลเสียของการพลาดของจริง (FN) กับการเตือนผิด (FP) ในงานนั้นต่างกันอย่างไร แล้วเลือกตัวชี้วัดหลัก
- ลองเปลี่ยนจุดตัดในโค้ดตัวอย่างเป็น 0.2 และ 0.5 บันทึก accuracy และ recall แล้วเลือกจุดตัดพร้อมเหตุผล
- อธิบายให้เพื่อนฟังว่าทำไมห้ามใช้ชุดทดสอบเลือกความลึกของต้นไม้
ข้อผิดพลาดที่พบบ่อย
ระวัง
- รายงานความแม่นยำอย่างเดียว กับข้อมูลที่กลุ่มหนึ่งมีน้อย
- ไม่มีค่าอ้างอิง จึงไม่รู้ว่าแบบจำลองดีกว่าการเดาแบบง่ายหรือไม่
- เลือกแบบจำลองจากชุดฝึก ซึ่งให้รางวัลแก่การเรียนเกิน
- ใช้ชุดทดสอบหลายครั้ง จนกลายเป็นชุดตรวจสอบอีกชุด
- ใช้จุดตัด 0.5 เสมอ โดยไม่ดูผลเสียของความผิดพลาดแต่ละแบบ
สรุป
- ML แบบมีผู้สอนเรียนจากตัวอย่างที่มีป้าย แบ่งเป็นงานจำแนกประเภทและพยากรณ์ค่า
- ชุดฝึกใช้ปรับพารามิเตอร์ ชุดตรวจสอบใช้เลือกแบบจำลอง และชุดทดสอบใช้ประเมินครั้งเดียว
- ต้องเทียบกับค่าอ้างอิงเสมอ และความแม่นยำหลอกได้เมื่อกลุ่มไม่สมดุล
- ความซับซ้อนที่มากเกินทำให้เรียนเกิน เลือกความซับซ้อนจากผลบนชุดตรวจสอบ
แบบฝึกตรวจความเข้าใจ
- ทำนายเวลาบินที่เหลือเป็นนาทีเป็นงานชนิดใด
- ข้อมูล 1 000 เที่ยว มีมอเตอร์ผิดปกติ 50 เที่ยว แบบจำลองที่ตอบ “ปกติ” ทุกครั้งได้ความแม่นยำเท่าใด
- แบบจำลองได้ความแม่นยำ 1.00 บนชุดฝึก แต่ 0.80 บนชุดตรวจสอบ บอกอะไร
- มีมอเตอร์ผิดปกติจริง 20 ลำ แบบจำลองพบ 15 ลำ recall เท่าใด
- ทำไมจึงต้องใช้
stratifyตอนแบ่งข้อมูลที่กลุ่มหนึ่งมีน้อย
เฉลย
- พยากรณ์ค่า (regression) แบบมีผู้สอน
- ทั้งที่ไม่พบมอเตอร์ผิดปกติเลย
- แบบจำลองเรียนเกิน จำชุดฝึกได้แต่ใช้กับข้อมูลใหม่ได้ไม่ดี
- เพื่อให้สัดส่วนของกลุ่มที่มีน้อยใกล้เคียงกันทุกชุด ไม่เช่นนั้นบางชุดอาจแทบไม่มีตัวอย่างของกลุ่มนั้น
สรุปสูตรสำคัญ
| ฟังก์ชันซิกมอยด์ของ logistic regression | |
| ความแม่นยำ (accuracy) | |
| recall |
แหล่งอ้างอิงหลัก
- Géron, A. (2025). Hands-on machine learning with Scikit-Learn and PyTorch. O'Reilly. link
- Géron, A. (2022). Hands-on machine learning with Scikit-Learn, Keras, and TensorFlow (3rd ed.). O'Reilly.
- Goodfellow, I., Bengio, Y., & Courville, A. (2016). Deep learning. MIT Press. link
- scikit-learn developers. Metrics and scoring: Quantifying the quality of predictions (scikit-learn 1.9). link
- scikit-learn developers. Cross-validation: Evaluating estimator performance (scikit-learn 1.9). link
- Montgomery, D. C., & Runger, G. C. (2018). Applied statistics and probability for engineers (7th ed.). Wiley. link
อ่านเพิ่มเติม
ศึกษาหน่วยความรู้ที่กำหนดล่วงหน้า ดูสื่อประกอบ และทำ quiz ประจำโมดูล
ในชั้นเรียน / ภาคสนาม
บรรยาย อภิปรายกรณีศึกษา และแก้โจทย์ในชั้นเรียน
หลักฐานการเรียนรู้: ผล quiz และแบบฝึกหัดที่ส่ง