ส่วนบนสุดของไฟล์คือ "แผงตั้งค่า" ที่ทำให้รีทาร์เก็ตง่าย เปลี่ยนที่นี่ที่เดียวก็ได้แอปโมเดลอื่น:
TARGET_KEYWORDS = ("cough",) # คีย์เวิร์ดหาชื่อโมเดล
TARGET_CLASS = "cough" # ชื่อคลาสที่ถือว่า "ตรวจเจอ"
TARGET_SENSOR = edge_ai.SENSOR_MIC
model = find_model(TARGET_KEYWORDS, TARGET_SENSOR)
labels = model['labels'] # เช่น ['unlabelled', 'cough']
TARGET_KEYWORDS บอก find_model() ว่าจะหาโมเดลชื่ออะไร · TARGET_CLASS บอกว่าคลาสไหนคือ "เจอ"sirensออกแบบให้ "จุดที่ต้องแก้ตอนรีทาร์เก็ต" รวมอยู่บนหัวไฟล์ทั้งหมด เป็นนิสัยที่ดีมาก คนอ่านโค้ดต่อจะรู้ทันทีว่าปรับอะไรได้ตรงไหน โดยไม่ต้องไล่ทั้งไฟล์
ช่องเติมที่ 1: ก่อนเข้าลูป สั่งให้โมเดลเป้าหมายเริ่มรัน (ครั้งเดียว ไม่มีปุ่ม Load):
try:
# เติม: เริ่มรันโมเดลเป้าหมายตัวเดียว ด้วย edge_ai.select(model['index'])
pass
lcd.console('<span class=ok> เริ่มฟัง %s…</span>' % model['name'])
while True:
...
pass ด้วย edge_ai.select(model['index']) — ใช้ model['index'] ที่ find_model() หามาให้select() อยู่ ก่อนลูป ไม่ใช่ในปุ่ม เพราะแอปโฟกัสรู้อยู่แล้วว่าจะรันอะไรtry เพราะ select() โยน OSError ได้ ถ้า M55 ไม่ยืนยันการสลับถ้าลืมเติมช่องนี้: แอปเปิดได้ การ์ดขึ้นครบ แต่
result()จะคืนNoneตลอด เพราะไม่มีโมเดลไหนถูกสั่งให้รันเลย — ตัวนับจะไม่มีวันขยับ
ช่องเติมที่ 2 และ 3: หัวใจของลูป อ่านผลแล้วเอาคลาสที่ชนะขึ้นจอ:
# เติม: อ่านผลอนุมานล่าสุดมาเก็บใน r -> r = edge_ai.result()
r = None
pass
if r and r['seq'] != last_seq:
last_seq = r['seq']
# เติม: แสดงคลาสที่ชนะบนจอ ด้วย verdict.text(r['label'] or '-')
pass
conf.text("conf: %.0f %%" % (r['conf'] * 100))
lat.text("latency: %.1f ms" % r['latency_ms'])
pass ด้วย r = edge_ai.result() (ลบ r = None ทิ้ง หรือปล่อยไว้ก็ได้ เพราะจะถูกทับ)pass ด้วย verdict.text(r['label'] or '-')seq ก่อนเสมอ วาดจอเฉพาะตอนมีผลใหม่ — เหมือน บทเรียน 1.1–1.3 เป๊ะโครงส่วนนี้เหมือน บทเรียน 1.1–1.3 มาก จงใจให้เหมือน เพราะเราต้องการให้คุณ จำ pattern อ่านผลให้ขึ้นใจ ก่อนจะต่อ action ที่เป็นของใหม่จริงในช่องถัดไป
ช่องเติมที่ 4: ของใหม่ของชุดบทเรียนนี้ ต่อ verdict เข้ากับ action (การนับ):
# เติม: แทน False ด้วยเงื่อนไขตรวจเจอจริง
# r['label'] == TARGET_CLASS and r['conf'] >= edge_ai.CONF_FLOOR
is_target = False
if is_target and not was_target:
hits += 1
hits_lbl.text(str(hits))
lcd.console('<span class=ok> เจอ %s ครั้งที่ %d</span>' % (TARGET_CLASS, hits))
was_target = is_target
False ด้วย r['label'] == TARGET_CLASS and r['conf'] >= edge_ai.CONF_FLOORnot was_target คือกุญแจของ "ขอบขาขึ้น" — นับหนึ่งต่อการเจอหนึ่งครั้งลองผิดดูก็ได้เพื่อเรียนรู้: ถ้าเติมแค่
r['label'] == TARGET_CLASS(ตัด conf ทิ้ง) แล้วส่งเสียงมั่วๆ คุณจะเห็นตัวนับเด้งทั้งที่ไม่ได้ไอ — นั่นคือ false positive ที่CONF_FLOORมีไว้กัน
เปิด s15_apps.py มี # เติม: วางไว้ 4 จุด (สามจุดหลักของ edge_ai + หนึ่ง action):
| # | จุด | เติมด้วย | ถ้าลืม |
|---|---|---|---|
| 1 | ก่อนลูป | edge_ai.select(model['index']) |
แอปเปิดได้ แต่ result เป็น None ตลอด |
| 2 | ในลูป | r = edge_ai.result() |
จอไม่ขึ้นคลาสเลย |
| 3 | มีผลใหม่ | verdict.text(r['label'] or '-') |
แถบขยับ แต่ตัวใหญ่ไม่เปลี่ยน |
| 4 | ตรวจเจอ | r['label']==TARGET_CLASS and r['conf']>=edge_ai.CONF_FLOOR |
ตัวนับไม่ขยับ (หรือเด้งมั่ว) |
ขั้นตอน:
# เติม: ทีละจุด แทน pass/False ตามคำใบ้cough ชนะ + ตัวนับเพิ่ม ถ้าตัวนับเด้งมั่ว กลับไปเช็กเงื่อนไข conf ในช่อง 4สามช่องแรกคือ pattern เดิมจาก บทเรียน 1.1–1.3 (select/result/verdict) — ช่องที่ 4 คือก้าวใหม่จริงของคุณ: การเปลี่ยน "ผล" ให้กลายเป็น "การกระทำ"
MVP วันนี้ต้องมีแอป อย่างน้อยสองตัว ข่าวดีคือรีทาร์เก็ตใช้เวลาไม่ถึงนาที เปลี่ยนแค่สองบรรทัดบนหัวไฟล์แล้วเซฟเป็นไฟล์ใหม่:
# s15_alarm_app.py — แค่เปลี่ยนสองบรรทัดนี้
TARGET_KEYWORDS = ("alarm",)
TARGET_CLASS = "alarm"
# s15_siren_app.py — โมเดลชื่อ Siren แต่คลาสสะกด sirens (มี s)
TARGET_KEYWORDS = ("siren",)
TARGET_CLASS = "sirens"
find_model() หาโมเดลใหม่ให้เอง, แถบคลาสปรับตาม labels ที่ได้มาTARGET_CLASS ต้องสะกดตรงกับ label จริง (Siren → sirens มี s) เช็กด้วย edge_ai.models() ใน REPL ก่อนs15_cough_app.py / s15_alarm_app.py / s15_siren_app.py — นี่คือ "แอปต่อโมเดล" ที่หลักสูตรตั้งเป้าไว้นี่คือผลตอบแทนของการออกแบบให้ตัวแปรอยู่บนหัวไฟล์: โมเดลหนึ่งแม่แบบ กลายเป็นสามแอปได้ในไม่กี่นาที — คุณเพิ่งสร้าง "ตระกูลแอป" ของตัวเอง
อยากเข้าใจ argmax / softmax / edge AI ให้ลึกขึ้น ลองดูจากช่องที่อธิบายเห็นภาพ:
วิดีโอ (ภาษาอังกฤษ อธิบายเข้าใจง่าย)
ภาพ / เอกสารอ้างอิง
วิดีโอ/ภาพภายนอกเป็นของเจ้าของต้นฉบับ ใช้เพื่อการศึกษา อ้างอิงลิงก์ต้นทาง
MVP ของบทเรียน 6.1–6.2 (เกณฑ์ผ่านของชุดบทเรียน): คุณสร้าง แอปโฟกัสต่อโมเดล ที่ UI สะอาด — เล็งโมเดลด้วย find_model(), โชว์ verdict + แถบทุกคลาส + latency, และมี action หนึ่งอย่าง (ตัวนับ) ที่ทำงานจริงเมื่อคลาสเป้าหมายข้าม CONF_FLOOR
CONF_FLOOR ก่อนนับ และทำไมต้องนับเฉพาะ "ขอบขาขึ้น""แอปที่สร้างเอง" ไม่ใช่แค่ "รันแล้วมีตัวเลข" — คุณต้องบอกได้ว่า action ของคุณเชื่อ verdict ตอนไหน และกันนับเฟ้ออย่างไร นั่นคือหัวใจของ Apps
ถ้าติด ให้ไต่บันไดนี้ทีละขั้น อย่าเพิ่งกระโดดไปดูเฉลย เพราะของจะเข้าหัวตอนที่คุณพยายามเองก่อน:
# เติม: ทั้ง 4 จุดในไฟล์ฝึก + ตารางช่องเติมหน้าที่แล้วs15_apps.py มีโครงครบทั้งไฟล์ เหลือแค่ 4 จุดให้เติมs15_apps.py เติมครบพร้อมคอมเมนต์อธิบายทุกช่อง (อ่านให้เข้าใจ ปิดไฟล์ แล้วพิมพ์เอง)s15_apps_full.py ฉบับขัดเรียบร้อย เพิ่มสีตาม CONF_FLOOR, debounce เบาๆ (นับเมื่อเจอติดกันสองผล), เวลา "เจอครั้งล่าสุด" และปุ่ม Resetลองเขียนเองให้สุดก่อนนะ ถ้าติดจริงๆ ค่อยเปิดเฉลยดูทีละช่อง แล้วกลับมาพิมพ์เอง — เดี๋ยวเราค่อย ๆ แกะไปด้วยกัน
การสร้างแอปโฟกัสตัวแรกดึงทุกอย่างจากชุดบทเรียนก่อนหน้ามารวมกัน แล้วเพิ่ม "การกระทำ" เข้าไป:
ฝั่ง Edge AI / Apps
find_model() แทนเมนู (ต่อจาก บทเรียน 1.1–1.3 และ 1.6–1.7)label/conf ให้เป็นการกระทำ (การนับ) — เปิด โมดูล 6 (Apps)ฝั่ง MicroPython / โครงโปรแกรม
finally: stopfinally: edge_ai.stop() คืนเครื่องสู่สถานะที่รู้แน่ (เหมือนทุกบทเรียน)ทั้งหมดนี้ยังยืนบนคำสั่งเดิมแค่ไม่กี่ตัว — สิ่งที่เพิ่มคือ "วิธีคิด" เรื่องการเปลี่ยนผลเป็นการกระทำ ซึ่งจะขยายต่อในสองชุดบทเรียนถัดไป
แอปโฟกัสที่เราสร้างวันนี้ ไม่ใช่ของสมมติ ทุกตัวมีสินค้าจริงที่ทำงานด้วยโครงเดียวกัน: โฟกัสโมเดลเดียว + นับ/เตือนเมื่อเจอ
โมเดลเดียวกัน เปลี่ยนคีย์เวิร์ดกลายเป็นสินค้าคนละตัว — นี่คือพลังของ "แอปต่อโมเดล" ที่ออกแบบมาดี แม่แบบหนึ่งอัน ต่อยอดได้ทั้งตระกูล
งานทำเอง (ท้ายบทเรียน):
s15_apps.py ให้ครบทั้ง 4 ช่อง รันได้จริง — แอป Cough นับได้ (Emulator หรือบอร์ด)and r['conf'] >= CONF_FLOOR ออกชั่วคราว ส่งเสียงมั่วๆ แล้วจดว่าตัวนับเด้งต่างจากเดิมยังไง (แล้วใส่กลับ)ใบ้ข้อ 3 — เมื่อไม่มีเกณฑ์ conf ทุกครั้งที่คลาสเป้าหมาย "แค่ชนะ" (แม้คะแนนสูสี) ก็จะถูกนับ คุณจะเห็น false positive พุ่งขึ้นทันที นั่นคือเหตุผลที่แอปจริงต้องมีเส้นแบ่ง
วันนี้เราได้: แยกเมนูออกจากแอปโฟกัส · เล็งโมเดลด้วย find_model() · อ่าน scores/latency_ms ให้ครบ · ต่อ verdict เข้ากับ action แรก (การนับ) ด้วยเกณฑ์ CONF_FLOOR และขอบขาขึ้น · รีทาร์เก็ตเป็นตระกูลแอป cough/alarm/siren
ชุดบทเรียนถัดไป (บทเรียน 6.3–6.4) เราจะทำให้ action แรงขึ้น: จาก "นับ" เป็นจุด RGB / เล่นเสียง / เขียน log พร้อม debounce เต็มรูปแบบและการจัดการ false positive อย่างจริงจัง — เจอกันครับ