วิศวกรรมชุดข้อมูล: สมดุลคลาส หน้าต่าง และการแบ่ง train/val/test
โมดูล 5 — ฝึกโมเดลและนำไปใช้หลายเป้าหมาย · สไลด์: slides.md · ภาพรวมโมดูล · หน้าหลักสูตร
เริ่มสร้างโมเดลของเราเองจากสิ่งที่มาก่อนการฝึกเสมอ คือ dataset รู้จักท่อเตรียมข้อมูลสี่ขั้น capture, label, window, split เหตุผลของ class balance การแบ่ง train/val/test แบบ stratified และกฎเหล็กห้ามข้อมูลรั่ว (no leakage) โดยรันท่อทั้งเส้นด้วยข้อมูลสังเคราะห์ก่อน
เป้าหมาย
หัวข้อที่มีชื่อว่า “เป้าหมาย”เมื่อจบบทเรียนนี้ คุณจะ:
- เรียงท่อเตรียมข้อมูลสี่ขั้น capture → label → window → split ได้ และบอกได้ว่าขั้นใดทำบนบอร์ด ขั้นใดทำบน PC
- คำนวณสัดส่วนคลาส p_c = n_c / N และอธิบายด้วยตัวอย่างได้ว่าทำไม dataset ที่ไม่สมดุลให้ความแม่นบนกระดาษที่หลอกตา
- คำนวณจำนวนหน้าต่างของแต่ละคลาสในกอง train, val และ test แบบ stratified ด้วย ⌊0.15·n_c⌋ และบอกหน้าที่ของแต่ละกอง
- ระบุได้ว่า normalize แบบใดทำให้ข้อมูลรั่ว และแก้ด้วยกฎ fit บน train แล้ว apply ทุกกอง
ก่อนเริ่ม
หัวข้อที่มีชื่อว่า “ก่อนเริ่ม”ผ่านโมดูล 4 มาแล้ว เข้าใจหน้าต่างเลื่อน WIN กับ HOP จากบทเรียน 4.5 และเคยเก็บ CSV ด้วย DAQ logger ในบทเรียน 2.2
เตรียม PC ที่มี Python 3 กับ numpy แล้วดาวน์โหลด dataset_tools.py ไว้
- อุปกรณ์: คอมพิวเตอร์ของคุณ ไม่ต้องใช้บอร์ด — ใช้ PC ที่มี Python 3 และ numpy บอร์ดหรือ Emulator ใช้ในบทเรียน 5.2
- เรียนมาก่อน: บทเรียน 4.6 — ลงมือทำ: feature vector จากหน้าต่างเลื่อน
ดูของจริงก่อน
หัวข้อที่มีชื่อว่า “ดูของจริงก่อน”รันท่อทั้งเส้นก่อนเข้าใจ: python dataset_tools.py --synthesize --out data/gestures.csv แล้ว python dataset_tools.py --out data/gestures.csv
จะได้ samples (3600, 6) windows (143, 50, 6) class counts [48 48 47] ถามตัวเองว่าตัวเลขสามตัวสุดท้ายบอกอะไร
dataset ของเราคือ CSV ธรรมดา หนึ่งบรรทัดต่อหนึ่ง sample ขึ้นต้นด้วย label ตามด้วยหกค่า ax,ay,az,gx,gy,gz จาก sensors.bmi270.motion()
label คือเฉลยที่มนุษย์ติดให้ เราใช้สามคลาสเดียวกับโมเดล Motion บนบอร์ด (idle, circle, shaking) เพื่อเทียบโมเดลของเรากับของสำเร็จได้ภายหลัง
CSV ดิบยังฝึกไม่ได้ ต้องผ่านสี่ขั้น: capture และ label บนบอร์ดด้วย MicroPython แล้ว window และ split บน PC ด้วย dataset_tools.py
ซึ่งมีห้าฟังก์ชัน load_csv, make_windows, normalize, split และ synthesize ข้อมูลสังเคราะห์ให้ท่อรันได้ก่อนมีข้อมูลจริง
class balance: ถ้า 95% ของข้อมูลเป็น idle โมเดลที่ตอบ idle ตลอดจะแม่น 95% บนกระดาษแต่ใช้งานไม่ได้ เราวัดด้วย $p_c = n_c/N$
และสมดุลเมื่อ $p_c \approx 1/K$ (สามคลาสคือราว 0.33) ทางแก้ที่ตรงที่สุดคือเก็บให้สมดุลตั้งแต่ตอนเก็บ windowing ใช้ WIN = 50 (หนึ่งวินาทีที่ 50 Hz)
และ HOP = 25 ป้ายของหน้าต่างคือป้ายส่วนใหญ่ในนั้น และต้องตัดแบบเดียวกับที่บอร์ดป้อนโมเดลตอนใช้งาน
train / val / test: train ให้โมเดลเรียน val ใช้เช็กระหว่างฝึกและจูน test สอบครั้งเดียวตอนจบ split() แบ่งแบบ stratified
คือแยกทีละคลาส สลับลำดับด้วย rng.permutation แล้วหั่น $n_{c,test} = \lfloor 0.15,n_c \rfloor$, $n_{c,val} = \lfloor 0.15,n_c \rfloor$ ที่เหลือเป็น train
ทุกกองจึงมีครบทุกคลาสในสัดส่วนเดิม ข้อมูลสังเคราะห์ 143 หน้าต่างแบ่งได้ train [34 34 33] val [7 7 7] test [7 7 7]
สุดท้ายคือ no leakage: normalize() คิด mean/std จาก train เท่านั้นแล้วใช้กับทุกกอง ถ้าคิดจากข้อมูลทั้งหมด test จะรั่วเข้า train
ตัวเลขความแม่นจะสวยเกินจริงโดยไม่มี error ใด ๆ เตือน
ตัวอย่างสมบูรณ์
หัวข้อที่มีชื่อว่า “ตัวอย่างสมบูรณ์”สไลด์ของบทเรียนนี้อ้างถึงไฟล์ที่อยู่ในบทเรียนอื่นหรือใน shared/ ด้วย:
- m02-daq/l02-daq-logger-lab/examples/s04_daq_logger.py — เก็บข้อมูล sensor ลงไฟล์ CSV (Data Acquisition)
- m02-daq/l02-daq-logger-lab/practice/s04_daq_logger.py — เก็บข้อมูล sensor ลงไฟล์ CSV (Data Acquisition) (ฉบับฝึกเติมโค้ด)
- m05-training/l02-dataset-lab/practice/s11_dataset.py — เก็บ dataset IMU ที่ “สมดุลและพร้อม train” ลง CSV (ฉบับฝึกเติมโค้ด)
- shared/training/dataset_tools.py — Dataset tools for the IMU gesture classifier (Pillar 4 / Training).
เช็กความเข้าใจ
หัวข้อที่มีชื่อว่า “เช็กความเข้าใจ”คำถามชุดเดียวกันอยู่ใน quiz.yaml สำหรับระบบที่ตรวจอัตโนมัติ
-
เรียงท่อเตรียมข้อมูลจากเซนเซอร์จนพร้อมฝึก (เรียงลำดับ · เป้าหมายข้อ 1)
- ก) window: ตัดเป็นหน้าต่าง 50 sample (บน PC)
- ข) capture: อ่าน IMU ที่ 50 Hz (บนบอร์ด)
- ค) split: แบ่ง train/val/test (บน PC)
- ง) label: ติดป้ายท่าที่ทำ (บนบอร์ด)
เฉลย
ข → ง → ก → ค — capture กับ label เกิดบนบอร์ดขณะเก็บ ส่วน window กับ split ทำบน PC ด้วย dataset_tools.py แล้วจึงส่งต่อให้การฝึก
-
dataset มี idle 950 หน้าต่าง circle 30 และ shaking 20 โมเดลที่ตอบ idle ทุกครั้งได้ความแม่นเท่าไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 2)
- ก) 33%
- ข) 50%
- ค) 95% ทั้งที่ไม่ได้เรียนอะไรเลย
- ง) 100%
เฉลย
ค — p_idle = 950/1000 = 0.95 ความแม่นบนกระดาษจึงสูงแต่หลอกตา สมดุลควรให้ทุกคลาสได้ราว 1/3
-
คลาส shaking มี 40 หน้าต่าง split แบบ stratified (val = test = 0.15) ได้ train, val, test กี่หน้าต่าง (เลือกหนึ่งข้อ · เป้าหมายข้อ 3)
- ก) 28, 6, 6
- ข) 30, 5, 5
- ค) 26, 7, 7
- ง) 40, 0, 0
เฉลย
ก — ⌊0.15 × 40⌋ = 6 ไป test และ 6 ไป val ที่เหลือ 40 − 12 = 28 เป็น train train จึงรับเศษที่เหลือเสมอ
-
ทำไมต้องมี val แยกจาก test (เลือกหนึ่งข้อ · เป้าหมายข้อ 3)
- ก) เพื่อให้ train มีข้อมูลน้อยลง
- ข) เราดู val ซ้ำ ๆ ตอนจูน val จึงปนการตัดสินใจของเราไปแล้ว test ที่ไม่เคยแตะเท่านั้นที่บอกความแม่นจริง
- ค) val กับ test ต่างกันแค่ชื่อ
- ง) เพราะ test ต้องใหญ่กว่า train
เฉลย
ข — val ใช้ระหว่างฝึกจึงเริ่มเอนตามการจูน test เก็บไว้สอบครั้งเดียวตอนจบ ถ้าแอบดู test ระหว่างจูน ตัวเลขจะโกหก
-
โค้ดแบบใดทำให้ข้อมูลรั่ว (เลือกหนึ่งข้อ · เป้าหมายข้อ 4)
- ก) split ก่อน แล้ว normalize(Xtr, Xva, Xte) ที่คิด mean/std จาก Xtr
- ข) normalize X ทั้งก้อนด้วย mean/std ของข้อมูลทั้งหมด แล้วค่อย split
- ค) ใช้ rng.permutation ก่อนหั่นแต่ละคลาส
- ง) ตั้ง seed ของ split ให้คงที่
เฉลย
ข — mean/std ที่คิดรวม test ทำให้สถิติของ test เข้าไปอยู่ในข้อมูลที่ใช้ฝึก ต้อง fit บน train แล้ว apply ทุกกอง
- รัน
dataset_tools.pyแบบ--synthesizeแล้วแบบอ่านไฟล์ จดจำนวน samples, windows และ class counts ลงบันทึกการเรียน - ใน Python เรียก
split()กับหน้าต่างที่ได้ แล้วพิมพ์np.bincountของทั้งสามกอง ตรวจว่าตรงกับการคำนวณด้วยมือ - เขียนสองบรรทัดว่าถ้าเรียก
normalize()ก่อนsplit()จะรั่วตรงไหน
บทเรียน 5.2 เราจะเติม s11_dataset.py เก็บ dataset จริงบนบอร์ดโดยเฝ้าแถบสมดุล แล้วแบ่งบน PC
บทเรียนถัดไป: บทเรียน 5.2 — ลงมือทำ: เก็บ dataset ที่สมดุลบนบอร์ดแล้วแบ่งบน PC
สะท้อนคิด
หัวข้อที่มีชื่อว่า “สะท้อนคิด”- ถ้าเก็บท่าเดินจากหลายคน ทำไมควรแบ่ง train/test ตาม “คน” แทนตาม sample
- คลาสที่หายากในงานจริง เช่นเสียงเครื่องจักรเสีย คุณจะเก็บให้สมดุลได้อย่างไร
คำถามทบทวน
ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย
-
เรียงท่อเตรียมข้อมูลจากเซนเซอร์จนพร้อมฝึก (เป้าหมายข้อ 1)
- window: ตัดเป็นหน้าต่าง 50 sample (บน PC)
- capture: อ่าน IMU ที่ 50 Hz (บนบอร์ด)
- split: แบ่ง train/val/test (บน PC)
- label: ติดป้ายท่าที่ทำ (บนบอร์ด)
ดูเฉลย
ลำดับที่ถูก: B. capture: อ่าน IMU ที่ 50 Hz (บนบอร์ด) → D. label: ติดป้ายท่าที่ทำ (บนบอร์ด) → A. window: ตัดเป็นหน้าต่าง 50 sample (บน PC) → C. split: แบ่ง train/val/test (บน PC)
capture กับ label เกิดบนบอร์ดขณะเก็บ ส่วน window กับ split ทำบน PC ด้วย dataset_tools.py แล้วจึงส่งต่อให้การฝึก
-
dataset มี idle 950 หน้าต่าง circle 30 และ shaking 20 โมเดลที่ตอบ idle ทุกครั้งได้ความแม่นเท่าไร (เป้าหมายข้อ 2)
- 33%
- 50%
- 95% ทั้งที่ไม่ได้เรียนอะไรเลย
- 100%
ดูเฉลย
คำตอบ: C. 95% ทั้งที่ไม่ได้เรียนอะไรเลย
p_idle = 950/1000 = 0.95 ความแม่นบนกระดาษจึงสูงแต่หลอกตา สมดุลควรให้ทุกคลาสได้ราว 1/3
-
คลาส shaking มี 40 หน้าต่าง split แบบ stratified (val = test = 0.15) ได้ train, val, test กี่หน้าต่าง (เป้าหมายข้อ 3)
- 28, 6, 6
- 30, 5, 5
- 26, 7, 7
- 40, 0, 0
ดูเฉลย
คำตอบ: A. 28, 6, 6
⌊0.15 × 40⌋ = 6 ไป test และ 6 ไป val ที่เหลือ 40 − 12 = 28 เป็น train train จึงรับเศษที่เหลือเสมอ
-
ทำไมต้องมี val แยกจาก test (เป้าหมายข้อ 3)
- เพื่อให้ train มีข้อมูลน้อยลง
- เราดู val ซ้ำ ๆ ตอนจูน val จึงปนการตัดสินใจของเราไปแล้ว test ที่ไม่เคยแตะเท่านั้นที่บอกความแม่นจริง
- val กับ test ต่างกันแค่ชื่อ
- เพราะ test ต้องใหญ่กว่า train
ดูเฉลย
คำตอบ: B. เราดู val ซ้ำ ๆ ตอนจูน val จึงปนการตัดสินใจของเราไปแล้ว test ที่ไม่เคยแตะเท่านั้นที่บอกความแม่นจริง
val ใช้ระหว่างฝึกจึงเริ่มเอนตามการจูน test เก็บไว้สอบครั้งเดียวตอนจบ ถ้าแอบดู test ระหว่างจูน ตัวเลขจะโกหก
-
โค้ดแบบใดทำให้ข้อมูลรั่ว (เป้าหมายข้อ 4)
- split ก่อน แล้ว normalize(Xtr, Xva, Xte) ที่คิด mean/std จาก Xtr
- normalize X ทั้งก้อนด้วย mean/std ของข้อมูลทั้งหมด แล้วค่อย split
- ใช้ rng.permutation ก่อนหั่นแต่ละคลาส
- ตั้ง seed ของ split ให้คงที่
ดูเฉลย
คำตอบ: B. normalize X ทั้งก้อนด้วย mean/std ของข้อมูลทั้งหมด แล้วค่อย split
mean/std ที่คิดรวม test ทำให้สถิติของ test เข้าไปอยู่ในข้อมูลที่ใช้ฝึก ต้อง fit บน train แล้ว apply ทุกกอง
อ้างอิงบทเรียนนี้
ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน
"วิศวกรรมชุดข้อมูล: สมดุลคลาส หน้าต่าง และการแบ่ง train/val/test" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0
ข้อความอ้างอิงภาษาอังกฤษ: "Dataset engineering: class balance, windows and the train/val/test split" from TESA Open Knowledge by the Thai Embedded Systems Association (TESA), https://github.com/tesaiot/tesa-qualification-program, licensed under CC BY-NC 4.0
ลิงก์บทเรียน: https://tesaiot.github.io/tesa-qualification-program/courses/edge-ai-developer/m05-training/l01-dataset-engineering/
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0
เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA