ข้ามไปยังเนื้อหา

ลงมือทำ: เก็บ dataset ที่สมดุลบนบอร์ดแล้วแบ่งบน PC

โมดูล 5 — ฝึกโมเดลและนำไปใช้หลายเป้าหมาย · สไลด์: slides.md · ภาพรวมโมดูล · หน้าหลักสูตร

เติมสี่จุดใน s11_dataset.py ให้อ่าน IMU เขียน CSV นับจำนวนต่อคลาส และนำทางให้เก็บคลาสที่ยังน้อย จนได้ /gestures.csv สามคลาสที่สมดุลจากบอร์ด แล้วคัดลอกมาแบ่ง train/val/test บน PC พร้อมพิสูจน์ด้วย np.bincount ว่าทุกกองครบทุกคลาส

เมื่อจบบทเรียนนี้ คุณจะ:

  1. เติมสี่จุดใน practice/s11_dataset.py จนกดปุ่ม label แล้ว /gestures.csv ได้ 200 บรรทัดใหม่ที่ค่าไม่เป็นศูนย์ แถบสมดุลขยับ และคำใบ้ชี้คลาสที่มีน้อยที่สุด
  2. เก็บ dataset จากบอร์ดให้ครบสามคลาสถึงเป้า แล้วบน PC รัน load_csv → make_windows → split → normalize ตามลำดับ และแสดง np.bincount ของทั้งสามกองที่มีครบทุกคลาส
  3. ทดลองเก็บให้ไม่สมดุลโดยตั้งใจ แล้วอธิบายจากตัวเลขได้ว่าทำไม split แบบ stratified พาความไม่สมดุลไปทุกกอง และต้องแก้ตอนเก็บ

ผ่านบทเรียน 5.1 มาแล้ว เข้าใจ class balance, stratified split และกฎ no leakage เตรียม PC ที่มี Python 3 กับ numpy และ dataset_tools.py และถ้าจะเก็บข้อมูลจริงต้องมีบอร์ด

ทั้งไฟล์อ่านเป็นประโยคเดียว: เลือกป้าย → อ่าน IMU เป็นชุด → เขียนลง CSV → นับต่อคลาส → บอกว่าคลาสไหนยังน้อย → ครบเป้าทุกคลาสคือ dataset พร้อม โครงยังเป็นสี่จังหวะเดิม (import → สร้างครั้งเดียว → ลูป → ui.poll) สิ่งใหม่อยู่ใน record() สองจุดแรกยกมาจาก DAQ logger: (1) ax, ay, az, gx, gy, gz = sensors.bmi270.motion() ได้ accel หน่วย m/s² (az ราว 9.81 ตอนวางนิ่ง) กับ gyro หน่วย deg/s (2) f.write("%s,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f\n" % (label, ax, ay, az, gx, gy, gz)) ลำดับคอลัมน์ต้องตรง CHANNELS และป้ายต้องสะกดตรง CLASSES เพราะ load_csv() ใช้ CLASSES.index(label) สะกด Idle ตัวใหญ่ก็ error แล้ว

สองจุดหลังคือสมองของ dataset engineering: (3) counts[label] += BURST หลังเขียนครบชุด แถบสมดุลจึงขยับ และ (4) fewest = min(counts, key=counts.get) ที่วนคีย์ทั้งหมดแล้วคืน ชื่อคลาส ที่ค่าน้อยสุด โปรแกรมจึงบอกได้ว่าควรเก็บอะไรต่อ เก็บที่ 20 ms ต่อครั้ง (50 Hz) ให้ตรงกับโมเดล Motion ถ้าอัตราต่าง ท่าเดียวกันจะยืดหรือหดในหน้าต่าง

เก็บครบแล้วคัดลอก /gestures.csv ออกจากบอร์ด (BENTO IDE file transfer หรือ mpremote) ไปไว้ที่ data/gestures.csv บน PC แล้วเรียก load_csv → make_windows → split → normalize(Xtr, Xva, Xte) split ก่อน normalize เสมอ ปิดด้วยรายงาน np.bincount ของทั้งสามกอง ถ้ากองใดขาดคลาส เช่น test เป็น [20 20 0] แปลว่าเก็บ shaking น้อยเกินไป ต้องกลับไปเก็บเพิ่มบนบอร์ด

s11_dataset_full.py เพิ่มการเตือนสีแดงเมื่อคลาสน้อยสุดต่ำกว่า 70% ของคลาสมากสุด (BALANCE_TOL = 0.30) แสดงอัตราสมดุล min/max ประเมินจำนวนหน้าต่างด้วยสูตรเดียวกับ make_windows() และมีปุ่มล้างไฟล์ ข้อควรรู้: ตัวนับเริ่มที่ศูนย์ทุกครั้งที่รัน แม้ไฟล์เดิมจะมีข้อมูลอยู่แล้ว

ไฟล์ ไฟล์นี้สอน
examples/s11_dataset_full.py เก็บ dataset IMU ที่สมดุลและพร้อม train ลง CSV (ฉบับเต็ม)

สไลด์ของบทเรียนนี้อ้างถึงไฟล์ที่อยู่ในบทเรียนอื่นหรือใน shared/ ด้วย:

คอมเมนต์ # เติม: อยู่ที่บรรทัด 67 (หา fewest), 84 (อ่าน motion()), 88 (f.write) และ 93 (counts[label] += BURST) ถ้า CSV เป็น 0.0 หมด จุดที่ 84 ยังว่าง ถ้าแถบไม่ขยับ จุดที่ 93 ยังว่าง ถ้าคำใบ้ชี้ idle ตลอด จุดที่ 67 ยังว่าง

ไฟล์ฝึก เรื่อง
practice/s11_dataset.py เก็บ dataset IMU ที่ “สมดุลและพร้อม train” ลง CSV (ฉบับฝึกเติมโค้ด)

เปิดเฉลยหลังจากลองเองแล้วอย่างน้อยหนึ่งรอบ และอ่าน วิธีใช้เฉลย ก่อน

เฉลย คู่กับ
solution/s11_dataset.py practice/s11_dataset.py

คำถามชุดเดียวกันอยู่ใน quiz.yaml สำหรับระบบที่ตรวจอัตโนมัติ

  1. กดปุ่ม label แล้วแถบสมดุลไม่ขยับเลย แต่ไฟล์มีบรรทัดเพิ่ม จุดใดยังว่าง (เลือกหนึ่งข้อ · เป้าหมายข้อ 1)

    • ก) ax, ay, az, gx, gy, gz = sensors.bmi270.motion()
    • ข) f.write(…)
    • ค) counts[label] += BURST
    • ง) fewest = min(counts, key=counts.get)
    เฉลย

    ค — แถบอ่านค่าจาก counts ถ้าไม่บวก BURST เข้าตัวนับ ไฟล์จะโตแต่โปรแกรมไม่รู้ว่าเก็บไปเท่าไร เป็นการเก็บแบบตาบอด

  2. counts = {‘idle’: 600, ‘circle’: 200, ‘shaking’: 400} ค่า min(counts, key=counts.get) คืออะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 1)

    • ก) 200
    • ข) ‘circle’
    • ค) ‘idle’
    • ง) (‘circle’, 200)
    เฉลย

    ข — min วนคีย์ของ dict แต่เทียบด้วย counts.get จึงคืนชื่อคลาสที่ค่าน้อยสุด ไม่ใช่ตัวเลข คำใบ้จึงบอกให้เก็บ circle เพิ่ม

  3. เรียงขั้นบน PC ให้ไม่มีข้อมูลรั่ว (เรียงลำดับ · เป้าหมายข้อ 2)

    • ก) split(X, y)
    • ข) load_csv(“data/gestures.csv”)
    • ค) normalize(Xtr, Xva, Xte)
    • ง) make_windows(s, l)
    เฉลย

    ข → ง → ก → ค — อ่านไฟล์ → ตัดหน้าต่าง → แบ่งกอง → normalize ด้วยสถิติของ train ถ้า normalize ก่อน split สถิติของ test จะรั่วเข้าไป

  4. เก็บ idle มากกว่าคลาสอื่นสามเท่าแล้ว split แบบ stratified ผลคืออะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 3)

    • ก) split ปรับให้ทุกกองสมดุลเอง
    • ข) ทุกกองได้ idle มากกว่าคลาสอื่นราวสามเท่าเหมือนกัน ความไม่สมดุลติดไปทุกกอง
    • ค) test ไม่มี idle เลย
    • ง) โปรแกรม error
    เฉลย

    ข — stratified รักษาสัดส่วนเดิมของแต่ละคลาสในทุกกอง จึงไม่แก้ความไม่สมดุล ต้องแก้ที่ตอนเก็บ

MVP ของชุดบทเรียน 5.1–5.2: dataset จากบอร์ดที่สะอาด สมดุล และแบ่งแล้ว ทุกกอง (train/val/test) มีครบสามคลาสในสัดส่วนใกล้เคียงกัน

  • เติมไฟล์ฝึกครบสี่จุด ลองบน Emulator ก่อน แล้วเก็บของจริงบนบอร์ดจนทั้งสามคลาสถึง TARGET
  • คัดลอก gestures.csv มา PC แล้วรัน split พิมพ์ np.bincount ของทั้งสามกองลงบันทึกการเรียน
  • เก็บอีกไฟล์ที่ idle มากกว่าคลาสอื่นสามเท่าโดยตั้งใจ แล้วเทียบสัดส่วนในสามกอง อธิบายว่าทำไมความไม่สมดุลไม่หายไปเอง
  • อธิบายได้ว่าทำไมต้อง balance ทำไมต้องสามกอง และทำไม normalize ต้อง fit บน train เท่านั้น

ชุดบทเรียนถัดไป (บทเรียน 5.3–5.5) เราจะเอา dataset นี้ไปฝึกเป็นโมเดลจริงด้วย TensorFlow ใน Docker แล้วส่งออกเป็น .tflite แบบ int8

บทเรียนถัดไป: บทเรียน 5.3 — ฝึกโมเดลใน Docker: หนึ่งชิ้นงาน สี่เป้าหมาย

  • dataset ของคุณมีหน้าต่างรวมกี่หน้าต่าง คุณคิดว่าพอสำหรับโมเดลเล็ก ๆ หรือไม่ และจะรู้ได้อย่างไร
  • ถ้าต้องเพิ่มคลาสที่สี่ เช่น tap คุณต้องแก้ไฟล์ใดบ้างทั้งฝั่งบอร์ดและฝั่ง PC

คำถามทบทวน

ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย

  1. กดปุ่ม label แล้วแถบสมดุลไม่ขยับเลย แต่ไฟล์มีบรรทัดเพิ่ม จุดใดยังว่าง (เป้าหมายข้อ 1)

    1. ax, ay, az, gx, gy, gz = sensors.bmi270.motion()
    2. f.write(...)
    3. counts[label] += BURST
    4. fewest = min(counts, key=counts.get)
    ดูเฉลย

    คำตอบ: C. counts[label] += BURST

    แถบอ่านค่าจาก counts ถ้าไม่บวก BURST เข้าตัวนับ ไฟล์จะโตแต่โปรแกรมไม่รู้ว่าเก็บไปเท่าไร เป็นการเก็บแบบตาบอด

  2. counts = {'idle': 600, 'circle': 200, 'shaking': 400} ค่า min(counts, key=counts.get) คืออะไร (เป้าหมายข้อ 1)

    1. 200
    2. 'circle'
    3. 'idle'
    4. ('circle', 200)
    ดูเฉลย

    คำตอบ: B. 'circle'

    min วนคีย์ของ dict แต่เทียบด้วย counts.get จึงคืนชื่อคลาสที่ค่าน้อยสุด ไม่ใช่ตัวเลข คำใบ้จึงบอกให้เก็บ circle เพิ่ม

  3. เรียงขั้นบน PC ให้ไม่มีข้อมูลรั่ว (เป้าหมายข้อ 2)

    1. split(X, y)
    2. load_csv("data/gestures.csv")
    3. normalize(Xtr, Xva, Xte)
    4. make_windows(s, l)
    ดูเฉลย

    ลำดับที่ถูก: B. load_csv("data/gestures.csv") → D. make_windows(s, l) → A. split(X, y) → C. normalize(Xtr, Xva, Xte)

    อ่านไฟล์ → ตัดหน้าต่าง → แบ่งกอง → normalize ด้วยสถิติของ train ถ้า normalize ก่อน split สถิติของ test จะรั่วเข้าไป

  4. เก็บ idle มากกว่าคลาสอื่นสามเท่าแล้ว split แบบ stratified ผลคืออะไร (เป้าหมายข้อ 3)

    1. split ปรับให้ทุกกองสมดุลเอง
    2. ทุกกองได้ idle มากกว่าคลาสอื่นราวสามเท่าเหมือนกัน ความไม่สมดุลติดไปทุกกอง
    3. test ไม่มี idle เลย
    4. โปรแกรม error
    ดูเฉลย

    คำตอบ: B. ทุกกองได้ idle มากกว่าคลาสอื่นราวสามเท่าเหมือนกัน ความไม่สมดุลติดไปทุกกอง

    stratified รักษาสัดส่วนเดิมของแต่ละคลาสในทุกกอง จึงไม่แก้ความไม่สมดุล ต้องแก้ที่ตอนเก็บ

อ้างอิงบทเรียนนี้

ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน

"ลงมือทำ: เก็บ dataset ที่สมดุลบนบอร์ดแล้วแบ่งบน PC" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0

ข้อความอ้างอิงภาษาอังกฤษ: "Hands-on: capture a balanced dataset on the board, split it on the PC" 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/l02-dataset-lab/

วิธีอ้างอิง TESA ฉบับเต็ม

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0

เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA