โมดูล 3/5 · สัปดาห์ 7–9 · 27 ชม.

ชุดข้อมูลและ labeling

UAT 306 ปัญญาประดิษฐ์สำหรับระบบอากาศยานไร้คนขับ

เวลาเรียนประมาณ 85 นาทีร่าง รอตรวจปรับปรุงล่าสุด 28 กันยายน 2569

บทเรียน

เมื่อเรียนจบโมดูลนี้ ผู้เรียนจะสามารถ

  1. เขียนคู่มือการติด label ที่ทำให้ผู้ติด label ต่างคนได้ผลตรงกัน
  2. วัดความสอดคล้องของผู้ติด label ด้วย Cohen's kappa และ IoU ของกรอบ
  3. แบ่งชุดข้อมูลตามเที่ยวบินโดยรักษาสัดส่วนคลาสให้ใกล้เคียงกัน
  4. ตรวจชุดข้อมูลก่อนฝึกเพื่อหาข้อมูลรั่วและ label ผิด

ความรู้พื้นฐานที่ควรมี: UAT 306 โมดูล 1–2 · UAT 315 โมดูล 4 (ชุดข้อมูลและการประเมิน)

ทำไมต้องรู้

หน่วยความรู้เรื่องเตรียมและแบ่งชุดข้อมูลของคลังความรู้โดรนย้ำว่า โมเดลเรียนได้เท่าที่ข้อมูลและ label สนับสนุน ต้องกำหนดวัตถุที่จะตรวจ ขอบเขตภาพ และสิทธิ์ก่อนรวบรวม และแยกข้อมูลตามกลุ่มที่อาจคล้ายกัน เช่น เที่ยวบินหรือสถานที่ UAT 315 แสดงแล้วว่าการแบ่งแบบสุ่มให้คะแนนดีเกินจริง โมดูลนี้ลงลึกสองเรื่อง คือ label น่าเชื่อถือแค่ไหน และ แบ่งตามเที่ยวบินอย่างไรให้สัดส่วนคลาสใกล้กัน

คู่มือการติด label

ถ้าผู้ติด label สองคนเห็นภาพเดียวกันแล้วติดต่างกัน โมเดลจะเรียนความสับสนนั้นไปด้วย คู่มือที่ดีกำหนด: นิยามของแต่ละคลาสพร้อมภาพตัวอย่างที่ใช่และไม่ใช่ ขนาดเล็กที่สุดที่ต้องติด วิธีวาดกรอบ (ชิดขอบวัตถุ รวมหรือไม่รวมเงา) และสิ่งที่ต้องทำเมื่อไม่แน่ใจ หน่วยความรู้เรื่อง CVAT ของคลังความรู้โดรนสาธิตการติด label ตรวจ และส่งออกในรูปแบบสำหรับฝึก

ตัวอย่างที่ 1 ผู้ติด label สองคนตรงกันแค่ไหน

ผู้ติด label สองคนจำแนกภาพย่อย 100 ภาพเป็น รอยร้าว ไม่มี หรือคราบ และวาดกรอบรอยร้าวสามรายการเดียวกัน (ข้อมูลจำลอง) Cohen’s kappa (Cohen, 1960) ปรับความตรงกันที่เกิดจากความบังเอิญออก

a = ["crack"] * 40 + ["none"] * 50 + ["stain"] * 10
b = ["crack"] * 34 + ["none"] * 6 + ["none"] * 44 + ["crack"] * 4 + ["stain"] * 2 + ["stain"] * 8 + ["none"] * 2
n = len(a)
labels = sorted(set(a) | set(b))
po = sum(x == y for x, y in zip(a, b)) / n
pe = sum((a.count(c) / n) * (b.count(c) / n) for c in labels)
print(f"observed agreement {po:.2f}, chance agreement {pe:.2f}, kappa {(po - pe) / (1 - pe):.2f}")

def iou(p, q):                                  # กรอบ (x1, y1, x2, y2) หน่วยพิกเซล
    ix = max(0, min(p[2], q[2]) - max(p[0], q[0]))
    iy = max(0, min(p[3], q[3]) - max(p[1], q[1]))
    inter = ix * iy
    union = (p[2] - p[0]) * (p[3] - p[1]) + (q[2] - q[0]) * (q[3] - q[1]) - inter
    return inter / union

pairs = [((100, 100, 200, 160), (104, 98, 206, 158)), ((50, 50, 90, 80), (60, 55, 110, 85)),
         ((300, 200, 340, 260), (330, 240, 380, 300))]
for k, (p, q) in enumerate(pairs, 1):
    print(f"box {k}: IoU between annotators {iou(p, q):.2f}")
observed agreement 0.86, chance agreement 0.42, kappa 0.76
box 1: IoU between annotators 0.85
box 2: IoU between annotators 0.38
box 3: IoU between annotators 0.04

ความตรงกัน 86% ดูสูง แต่เมื่อหักส่วนที่ตรงกันโดยบังเอิญแล้ว kappa เหลือราว 0.76 (kappa เท่ากับ 1 คือตรงกันทุกภาพ และเท่ากับ 0 คือตรงกันไม่ต่างจากความบังเอิญ) ส่วนกรอบรายการที่ 2 และ 3 มี IoU ต่ำ แปลว่าผู้ติด label เข้าใจขอบเขตของรอยร้าวต่างกัน ต้องทบทวนคู่มือเรื่องการวาดกรอบก่อนติด label ภาพที่เหลือ

กรอบสามคู่ คู่แรกกรอบสีฟ้าและสีส้มเกือบทับกันพอดี IoU 0.85 คู่ที่สองเหลื่อมกันบางส่วน IoU 0.38 คู่ที่สามทับกันเพียงมุมเล็ก IoU 0.04
ภาพที่ 1 IoU ของกรอบจากผู้ติด label สองคน

แบ่งตามเที่ยวบินโดยรักษาสัดส่วนคลาส

ภาพจากเที่ยวบินเดียวกันคล้ายกันมาก ต้องอยู่ชุดเดียวกันทั้งเที่ยว (group split) แต่ถ้าแบ่งตามเที่ยวบินอย่างเดียว บางชุดอาจได้รอยร้าวน้อยหรือมากผิดปกติ scikit-learn มี StratifiedGroupKFold ที่รักษาทั้งกลุ่มและสัดส่วนคลาส ตัวอย่างนี้เขียนหลักการง่าย ๆ เองเพื่อให้เห็นกลไก

ตัวอย่างที่ 2 แบ่ง 12 เที่ยวบินเป็นชุดฝึก ตรวจสอบ และทดสอบ

แต่ละเที่ยวบินมีจำนวนภาพและสัดส่วนภาพที่มีรอยร้าวต่างกัน (ข้อมูลจำลอง) เรียงเที่ยวบินตามสัดส่วนรอยร้าว แล้วแจกเข้าชุดตามรูปแบบวนซ้ำ ฝึก ฝึก ตรวจสอบ ฝึก ฝึก ทดสอบ

flights = {"F01": (118, 0.138), "F02": (135, 0.245), "F03": (137, 0.160), "F04": (66, 0.107),
           "F05": (110, 0.060), "F06": (124, 0.220), "F07": (77, 0.228), "F08": (103, 0.130),
           "F09": (131, 0.201), "F10": (94, 0.246), "F11": (134, 0.243), "F12": (89, 0.234)}   # ภาพ, สัดส่วนรอยร้าว
pattern = ["train", "train", "val", "train", "train", "test"]
split = {"train": [], "val": [], "test": []}
for i, f in enumerate(sorted(flights, key=lambda k: flights[k][1])):
    split[pattern[i % len(pattern)]].append(f)

total = sum(n for n, _ in flights.values())
for name, fs in split.items():
    imgs = sum(flights[f][0] for f in fs)
    cracks = sum(flights[f][0] * flights[f][1] for f in fs)
    print(f"{name:<5} flights {', '.join(fs):<32} images {imgs:>4} ({imgs / total:.0%}), crack rate {cracks / imgs:.3f}")
assert not (set(split["train"]) & set(split["test"]))
train flights F05, F04, F01, F03, F06, F07, F11, F02 images  901 (68%), crack rate 0.180
val   flights F08, F12                         images  192 (15%), crack rate 0.178
test  flights F09, F10                         images  225 (17%), crack rate 0.220

ชุดฝึกกับชุดตรวจสอบได้สัดส่วนรอยร้าวใกล้กันมาก ชุดทดสอบสูงกว่าเล็กน้อย และไม่มีเที่ยวบินใดอยู่ทั้งชุดฝึกและชุดทดสอบ เมื่อมีเที่ยวบินน้อย การแบ่งทำได้หยาบ ทั้งสัดส่วนภาพและสัดส่วนคลาสจึงไม่ตรงเป้าพอดี ในงานจริงควรเก็บข้อมูลจากหลายเที่ยวบินและหลายสถานที่ และระบุในเอกสารชุดข้อมูลว่าแบ่งอย่างไร

สามแถบแนวนอน ชุดฝึก ชุดตรวจสอบ ชุดทดสอบ แต่ละแถบแบ่งเป็นช่องตามเที่ยวบิน ความกว้างตามจำนวนภาพ ท้ายแต่ละแถบเขียนสัดส่วนรอยร้าว 0.180 0.178 และ 0.220
ภาพที่ 2 การแบ่งตามเที่ยวบินที่รักษาสัดส่วนคลาส

ตรวจชุดข้อมูลก่อนฝึก

หน่วยความรู้เรื่องตรวจชุดข้อมูลก่อนเทรน AI ของคลังความรู้โดรนฝึกหาข้อมูลรั่วจากเที่ยวบินเดียวกัน ภาพซ้ำ และข้อมูลไม่ครบด้วย CSV และ Python รายการตรวจขั้นต่ำคือ ภาพซ้ำหรือเกือบซ้ำข้ามชุด กรอบที่อยู่นอกภาพหรือมีขนาดศูนย์ ภาพที่ไม่มี label แต่ควรมี สัดส่วนคลาสของแต่ละชุด และสิทธิ์การใช้ภาพ

ปฏิบัติการประจำโมดูล

ปฏิบัติการ: ชุดข้อมูลที่เชื่อถือได้

  1. เขียนคู่มือการติด label หนึ่งหน้าสำหรับรอยร้าว พร้อมภาพตัวอย่างที่ใช่และไม่ใช่
  2. ให้เพื่อนสองคนติด label ภาพชุดเดียวกัน 50 ภาพใน CVAT คำนวณ kappa และ IoU ด้วยตัวอย่างที่ 1
  3. แก้คู่มือตรงที่ไม่ตรงกัน แล้วติด label ใหม่ วัดซ้ำ
  4. แบ่งชุดข้อมูลตามเที่ยวบินด้วยตัวอย่างที่ 2 หรือ StratifiedGroupKFold แล้วรายงานสัดส่วนคลาส
  5. ตรวจชุดข้อมูลตามรายการขั้นต่ำ และเขียนเอกสารชุดข้อมูล

ข้อผิดพลาดที่พบบ่อย

ระวัง

  • ติด label โดยไม่มีคู่มือ แล้วหวังว่าโมเดลจะเรียนเอง
  • รายงานความตรงกันดิบ โดยไม่หักความบังเอิญ
  • แบ่งแบบสุ่มระดับภาพ ทั้งที่ภาพจากเที่ยวบินเดียวกันคล้ายกัน
  • แบ่งตามเที่ยวบินโดยไม่ดูสัดส่วนคลาส
  • ไม่บันทึกสิทธิ์การใช้ภาพ

สรุป

  • คู่มือการติด label ที่ชัดทำให้ label สอดคล้อง ซึ่งเป็นเพดานของความแม่นของโมเดล
  • Cohen’s kappa หักความตรงกันโดยบังเอิญ IoU วัดความตรงกันของกรอบ
  • แบ่งตามเที่ยวบินหรือสถานที่ และรักษาสัดส่วนคลาสให้ใกล้กันในทุกชุด
  • ตรวจชุดข้อมูลก่อนฝึกทุกครั้งและบันทึกไว้ในเอกสารชุดข้อมูล

แบบฝึกตรวจความเข้าใจ

  1. = 0.9 และ = 0.5 ค่า kappa เท่าใด
  2. กรอบ (0,0,10,10) กับ (5,0,15,10) มี IoU เท่าใด
  3. ทำไมความตรงกัน 90% อาจยังไม่ดีพอ
  4. ทำไมต้องแบ่งตามเที่ยวบิน
  5. kappa เท่ากับ 0 หมายความว่าอะไร
เฉลย
  1. ทับกัน 50 พื้นที่รวม 150 ได้
  2. ถ้าคลาสหนึ่งพบมาก ความตรงกันสูงอาจเกิดจากความบังเอิญ ต้องดู kappa
  3. ภาพจากเที่ยวบินเดียวกันคล้ายกันมาก ถ้าแยกไปอยู่ทั้งชุดฝึกและชุดทดสอบ คะแนนจะดีเกินจริง
  4. ผู้ติด label ตรงกันไม่ต่างจากความบังเอิญ

สรุปสูตรสำคัญ

Cohen's kappa
IoU ของสองกรอบ

แหล่งอ้างอิงหลัก

  1. Cohen, J. (1960). A coefficient of agreement for nominal scales. Educational and Psychological Measurement, 20(1), 37–46. link
  2. scikit-learn developers. Cross-validation: Evaluating estimator performance (scikit-learn 1.9). link
  3. Géron, A. (2022). Hands-on machine learning with Scikit-Learn, Keras, and TensorFlow (3rd ed.). O'Reilly.

อ่านเพิ่มเติม

ศึกษาหน่วยความรู้ที่กำหนดล่วงหน้า ดูสื่อประกอบ และทำ quiz ประจำโมดูล

ในชั้นเรียน / ภาคสนาม

ปฏิบัติการในห้องแล็บหรือภาคสนามตามใบงาน พร้อม checklist ความปลอดภัย

หลักฐานการเรียนรู้: ใบงานที่ผ่านการตรวจและผล quiz

แบบทดสอบประจำโมดูล

แบบทดสอบนี้ใช้ตรวจความเข้าใจ (formative) ไม่ใช่การสอบเก็บคะแนน

โดเมนความรู้: ปัญญาประดิษฐ์และคอมพิวเตอร์วิทัศน์