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

แกะแอป Edge AI: ทะเบียนโมเดล verdict และ action

โมดูล 1 — เริ่มต้น: รันของจริงแล้วแกะดูข้างใน · สไลด์: slides.md · ภาพรวมโมดูล · หน้าหลักสูตร

แกะแอป Edge AI ที่โฟกัสโมเดลเดียวเป็นสามจังหวะ select → result → action เลือกโมเดลจากชื่อแทนเลข เข้าใจที่มาของ conf จาก softmax และ argmax แล้วเพิ่ม action ที่ยิงครั้งเดียวเมื่อเจอคลาสเป้าหมายที่มั่นใจพอ

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

  1. เขียน find_model() ที่ค้นโมเดลจากคำในชื่อผ่าน edge_ai.models() แล้วส่ง index ของ dict ที่ได้ไปให้ select() และอธิบายว่าทำไมทนกว่าการ select ด้วยเลขตายตัว
  2. คำนวณ softmax ของคะแนนดิบชุดเล็ก ๆ และบอกได้ว่า label มาจาก argmax ส่วน conf มาจาก max ของ scores ชุดเดียวกัน
  3. เขียนเงื่อนไข action ที่ต้องผ่านทั้ง label ตรงเป้าหมายและ conf ≥ CONF_FLOOR พร้อมธง fired ที่ทำให้ action ยิงครั้งเดียวต่อการเจอ (edge-trigger)
  4. เปรียบเทียบการ poll ด้วย result() กับ on_result(cb) ได้ว่าใครเป็นผู้ขับ ต้องเช็ก seq หรือไม่ และเหมาะกับงานแบบไหน

ผ่านชุดบทเรียน 1.1–1.5 มาแล้ว จำสี่คำสั่ง models, select, result, stop และโครงสี่จังหวะได้ เปิด 13_edge_ai_motion.py ไว้ใน BENTO IDE และถ้ามี ให้เปิด 12_edge_ai_menu.py เทียบด้วย

รัน 13_edge_ai_motion.py ก่อนแตะโค้ด แล้วเขย่าบอร์ด วาดวงกลม หรือวางนิ่ง คลาสที่ชนะ แถบคะแนน และ latency เปลี่ยนสด แอปนี้คือฝาแฝดโมเดลเดียวของเมนู 12_edge_ai_menu.py ลองรันเทียบกันแล้วสังเกตว่า “โมเดลเดียว” ต่างจาก “เมนู” ตรงไหน

แอป Edge AI ที่โฟกัสโมเดลเดียวอ่านเป็น สามจังหวะ: (1) select เลือกโมเดลจากทะเบียน (2) result อ่านคำตัดสินแล้ววาดจอ (3) action สั่งการเมื่อคำตัดสินเข้าเงื่อนไข ชุดบทเรียน 1.1–1.3 หยุดที่จังหวะ 2 บทเรียนนี้เติมจังหวะ 3 ซึ่งทำให้ Edge AI “ใช้งานได้จริง” งานจริงส่วนใหญ่ไม่ให้ผู้ใช้เลือกโมเดลเอง นาฬิกาตรวจการล้มรันโมเดลเดียวตลอดแล้วลงมือเมื่อเจอ

ทะเบียนจาก edge_ai.models() เป็นแหล่งความจริงของแอป แทนที่จะ select(0) ด้วยเลขที่อาจสลับได้เมื่อเฟิร์มแวร์เปลี่ยน เราเขียน find_model(keyword) ค้นจากชื่อแล้วคืน dict ทั้งก้อน ได้ทั้ง index ไปสั่ง select และ labels ไว้ตั้งคลาสเป้าหมาย เปลี่ยน keyword คำเดียวก็ remix เป็นแอปของโมเดลอื่น

scores มาจาก softmax ที่บีบคะแนนดิบ (logits) $z_i$ ให้เป็นความน่าจะเป็นที่รวมกันได้ 1: $\text{softmax}(z)_i = e^{z_i} / \sum_j e^{z_j}$ จากนั้น label คือ argmax (ตำแหน่งของคะแนนสูงสุด) และ conf คือ max (ค่าคะแนนสูงสุด) เช่น scores = [0.03, 0.05, 0.92] ได้ label = shaking และ conf = 0.92 เส้น CONF_FLOOR จึงมีความหมายเพราะคะแนนถูก normalize แล้ว

action ต้องยิงเมื่อ ใช่คลาสเป้าหมาย และ มั่นใจพอ (r['conf'] >= edge_ai.CONF_FLOOR) และยิง ครั้งเดียวต่อการเจอ ด้วยธง fired (edge-triggered) ไม่ใช่ยิงทุกเฟรมตลอดที่ยังเจอ (level-triggered) แยก “การตัดสินใจ” (เงื่อนไข hit) ออกจาก “การลงมือ” (fire_action() ที่บี๊บด้วย ui.tone และขึ้นแบนเนอร์) จะ remix action ได้โดยไม่แตะ logic ตรวจจับ อีกทางหนึ่งคือ edge_ai.on_result(cb) ที่ให้เฟิร์มแวร์เรียกฟังก์ชันของเราเมื่อมีคำตัดสิน (ทันทีที่คลาสเปลี่ยน และทวนคลาสเดิมราววินาทีละครั้ง) จึงไม่ต้องเช็ก seq เอง แต่บน BENTO Emulator callback จะทำงานก็ต่อเมื่อโปรแกรมเรียก edge_ai.result() หรือ edge_ai.active()

11_edge_ai_motion.py ทำงานเดียวกับ 13 แต่ใช้โมดูลรุ่นเก่า (deepcraft) ได้แค่คลาสที่ชนะ ส่วน 13 ใช้ edge_ai จึงได้คะแนนทุกคลาสและ latency เปิดสองไฟล์เทียบกันเพื่อเห็นว่าทะเบียนโมเดลให้อะไรเพิ่ม

ไฟล์ ไฟล์นี้สอน
examples/11_edge_ai_motion.py Edge AI: โมเดล AI จำแนกการเคลื่อนไหว (รันบนบอร์ด ไม่ต้องต่อเน็ต)
examples/13_edge_ai_motion.py Edge AI: Motion (IMU) ด้วย API ใหม่ edge_ai — เห็นคะแนนทุกคลาส + latency

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

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

  1. ทำไม find_model(“Motion”) ถึงดีกว่า edge_ai.select(0) ในแอปที่จะใช้กับหลายบอร์ด (เลือกหนึ่งข้อ · เป้าหมายข้อ 1)

    • ก) เพราะ select() รับเลขไม่ได้
    • ข) เพราะลำดับโมเดลอาจต่างกันตามเฟิร์มแวร์หรือบอร์ด การค้นจากชื่อจึงได้โมเดลที่ต้องการเสมอ และได้ labels มาใช้ต่อด้วย
    • ค) เพราะ find_model() รันเร็วกว่าบน NPU
    • ง) เพราะชื่อโมเดลไม่เคยเปลี่ยน แต่ index เปลี่ยนทุกครั้งที่บูต
    เฉลย

    ข — บน Dev Kit Cough อยู่ที่ 3 แต่บน Emulator อยู่ที่ 2 เพราะไม่มี Push การถามทะเบียนด้วยชื่อทำให้โค้ดทนต่อความต่างนี้

  2. คะแนนดิบ z = [0, 0, 0] ของสามคลาส ผ่าน softmax แล้วได้อะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 2)

    • ก) [0, 0, 0]
    • ข) [1, 1, 1]
    • ค) [0.33, 0.33, 0.33] โดยประมาณ conf จึงต่ำกว่า CONF_FLOOR
    • ง) [1, 0, 0]
    เฉลย

    ค — e⁰ = 1 ทุกคลาส หารด้วยผลรวม 3 ได้ราว 0.33 เท่ากันหมด argmax ยังเลือกผู้ชนะได้หนึ่งตัว แต่ conf 0.33 ต่ำกว่า 0.50 จึงไม่ควรเชื่อ

  3. scores = [0.10, 0.70, 0.20] ของ labels [‘idle’, ‘circle’, ‘shaking’] ค่า top, label และ conf คืออะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 2)

    • ก) top = 1, label = circle, conf = 0.70
    • ข) top = 0.70, label = circle, conf = 1
    • ค) top = 2, label = shaking, conf = 0.20
    • ง) top = 1, label = idle, conf = 0.10
    เฉลย

    ก — argmax ให้ตำแหน่ง 1 (top) ซึ่งตรงกับชื่อ circle ส่วน max ให้ค่า 0.70 คือ conf

  4. เขย่าบอร์ดค้างสามวินาที โมเดลตอบ shaking ทุกเฟรม ถ้าไม่มีธง fired จะเกิดอะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 3)

    • ก) action ยิงครั้งเดียวเหมือนเดิม
    • ข) action ยิงทุกเฟรมที่ยังเจอ บี๊บรัวจนน่ารำคาญ (level-triggered)
    • ค) action ไม่ยิงเลย
    • ง) โมเดลหยุดทำงาน
    เฉลย

    ข — ธง fired ทำให้ยิงตอนขอบขาขึ้นครั้งเดียว แล้วรีเซ็ตเมื่อออกจากคลาสเป้าหมาย นี่คือ edge-trigger แบบเดียวกับการกดปุ่มหนึ่งครั้งได้ event หนึ่งครั้ง

  5. ข้อใดถูกต้องเกี่ยวกับ on_result(cb) เทียบกับการ poll ด้วย result() (เลือกทุกข้อที่ถูก) (เลือกได้หลายข้อ · เป้าหมายข้อ 4)

    • ก) เฟิร์มแวร์เป็นผู้เรียก cb ให้ เราจึงไม่ต้องเช็ก seq เอง
    • ข) cb ถูกเรียกทันทีที่คลาสเปลี่ยน และอาจถูกเรียกซ้ำด้วยคลาสเดิมราววินาทีละครั้ง จึงยังต้องมีธง fired
    • ค) การ poll เห็นทุกจังหวะในลูปเดียว เหมาะกับตอนเริ่มเรียน
    • ง) on_result ทำให้ไม่ต้องเรียก edge_ai.stop() ตอนจบ
    เฉลย

    ก, ข, ค — ทั้งสองวิธีถูกต้อง ต่างกันที่ใครถาม ตอนจบยังต้องถอน callback ด้วย on_result(None) แล้ว stop() เสมอ

  • รัน 13_edge_ai_motion.py แล้วจดลงบันทึกการเรียนว่าแต่ละท่าให้คลาสอะไรและ conf ประมาณเท่าไร
  • ใน REPL ลองเขียน find_model("Cough") แล้วพิมพ์ index กับ labels ที่ได้ เทียบกับบอร์ดหรือ Emulator ของคุณ
  • คิดเลข softmax ของ z = [2.0, 3.1, 1.2] ด้วยเครื่องคิดเลขแล้วบอกว่าคลาสไหนชนะด้วย conf เท่าไร

บทเรียน 1.7 เราจะเติมไฟล์ s03_anatomy_edgeai.py จนแอปบี๊บและขึ้นแบนเนอร์เองเมื่อเจอคลาสเป้าหมาย แล้ว remix ให้เป็นของเรา

บทเรียนถัดไป: บทเรียน 1.7 — ลงมือทำ: จาก verdict สู่ action บนบอร์ด

  • ในบ้านหรือที่ทำงานของคุณ มีงานไหนที่ “รู้ว่าเจออะไร” ยังไม่พอ ต้อง “ลงมือทำต่อ” ด้วย และ action นั้นควรยิงครั้งเดียวหรือยิงซ้ำได้
  • ถ้า action ของคุณคือโทรแจ้งเหตุฉุกเฉิน คุณจะตั้ง CONF_FLOOR สูงหรือต่ำ เพราะอะไร

คำถามทบทวน

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

  1. ทำไม find_model("Motion") ถึงดีกว่า edge_ai.select(0) ในแอปที่จะใช้กับหลายบอร์ด (เป้าหมายข้อ 1)

    1. เพราะ select() รับเลขไม่ได้
    2. เพราะลำดับโมเดลอาจต่างกันตามเฟิร์มแวร์หรือบอร์ด การค้นจากชื่อจึงได้โมเดลที่ต้องการเสมอ และได้ labels มาใช้ต่อด้วย
    3. เพราะ find_model() รันเร็วกว่าบน NPU
    4. เพราะชื่อโมเดลไม่เคยเปลี่ยน แต่ index เปลี่ยนทุกครั้งที่บูต
    ดูเฉลย

    คำตอบ: B. เพราะลำดับโมเดลอาจต่างกันตามเฟิร์มแวร์หรือบอร์ด การค้นจากชื่อจึงได้โมเดลที่ต้องการเสมอ และได้ labels มาใช้ต่อด้วย

    บน Dev Kit Cough อยู่ที่ 3 แต่บน Emulator อยู่ที่ 2 เพราะไม่มี Push การถามทะเบียนด้วยชื่อทำให้โค้ดทนต่อความต่างนี้

  2. คะแนนดิบ z = [0, 0, 0] ของสามคลาส ผ่าน softmax แล้วได้อะไร (เป้าหมายข้อ 2)

    1. [0, 0, 0]
    2. [1, 1, 1]
    3. [0.33, 0.33, 0.33] โดยประมาณ conf จึงต่ำกว่า CONF_FLOOR
    4. [1, 0, 0]
    ดูเฉลย

    คำตอบ: C. [0.33, 0.33, 0.33] โดยประมาณ conf จึงต่ำกว่า CONF_FLOOR

    e⁰ = 1 ทุกคลาส หารด้วยผลรวม 3 ได้ราว 0.33 เท่ากันหมด argmax ยังเลือกผู้ชนะได้หนึ่งตัว แต่ conf 0.33 ต่ำกว่า 0.50 จึงไม่ควรเชื่อ

  3. scores = [0.10, 0.70, 0.20] ของ labels ['idle', 'circle', 'shaking'] ค่า top, label และ conf คืออะไร (เป้าหมายข้อ 2)

    1. top = 1, label = circle, conf = 0.70
    2. top = 0.70, label = circle, conf = 1
    3. top = 2, label = shaking, conf = 0.20
    4. top = 1, label = idle, conf = 0.10
    ดูเฉลย

    คำตอบ: A. top = 1, label = circle, conf = 0.70

    argmax ให้ตำแหน่ง 1 (top) ซึ่งตรงกับชื่อ circle ส่วน max ให้ค่า 0.70 คือ conf

  4. เขย่าบอร์ดค้างสามวินาที โมเดลตอบ shaking ทุกเฟรม ถ้าไม่มีธง fired จะเกิดอะไร (เป้าหมายข้อ 3)

    1. action ยิงครั้งเดียวเหมือนเดิม
    2. action ยิงทุกเฟรมที่ยังเจอ บี๊บรัวจนน่ารำคาญ (level-triggered)
    3. action ไม่ยิงเลย
    4. โมเดลหยุดทำงาน
    ดูเฉลย

    คำตอบ: B. action ยิงทุกเฟรมที่ยังเจอ บี๊บรัวจนน่ารำคาญ (level-triggered)

    ธง fired ทำให้ยิงตอนขอบขาขึ้นครั้งเดียว แล้วรีเซ็ตเมื่อออกจากคลาสเป้าหมาย นี่คือ edge-trigger แบบเดียวกับการกดปุ่มหนึ่งครั้งได้ event หนึ่งครั้ง

  5. ข้อใดถูกต้องเกี่ยวกับ on_result(cb) เทียบกับการ poll ด้วย result() (เลือกทุกข้อที่ถูก) (เป้าหมายข้อ 4)

    1. เฟิร์มแวร์เป็นผู้เรียก cb ให้ เราจึงไม่ต้องเช็ก seq เอง
    2. cb ถูกเรียกทันทีที่คลาสเปลี่ยน และอาจถูกเรียกซ้ำด้วยคลาสเดิมราววินาทีละครั้ง จึงยังต้องมีธง fired
    3. การ poll เห็นทุกจังหวะในลูปเดียว เหมาะกับตอนเริ่มเรียน
    4. on_result ทำให้ไม่ต้องเรียก edge_ai.stop() ตอนจบ
    ดูเฉลย

    คำตอบ: A. เฟิร์มแวร์เป็นผู้เรียก cb ให้ เราจึงไม่ต้องเช็ก seq เอง · B. cb ถูกเรียกทันทีที่คลาสเปลี่ยน และอาจถูกเรียกซ้ำด้วยคลาสเดิมราววินาทีละครั้ง จึงยังต้องมีธง fired · C. การ poll เห็นทุกจังหวะในลูปเดียว เหมาะกับตอนเริ่มเรียน

    ทั้งสองวิธีถูกต้อง ต่างกันที่ใครถาม ตอนจบยังต้องถอน callback ด้วย on_result(None) แล้ว stop() เสมอ

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

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

"แกะแอป Edge AI: ทะเบียนโมเดล verdict และ action" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0

ข้อความอ้างอิงภาษาอังกฤษ: "Taking an edge AI app apart: the registry, the verdict and the action" 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/m01-onboarding/l06-edge-ai-app-anatomy/

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

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

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