บทเรียน 5.5 — ลงมือทำ: เติมสคริปต์ฝึกแล้วรันใน Docker

Training II · ฝึกโมเดลใน TensorFlow (Docker) แล้วทดสอบบน PC

โมดูล 5 — ฝึกโมเดลและนำไปใช้หลายเป้าหมาย

ต่อจากบทเรียน 5.4 — ข้างในการฝึก: Keras, Conv1D, gradient descent, int8 และ confusion matrix

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

โครงของไฟล์ s12_train.py — 4 ช่องที่ต้องเติม

ไฟล์ฝึก s12_train.py มีโครงครบทั้งไฟล์แล้ว เหลือ 4 จุด ให้เติม ตรงกับสี่จังหวะพอดี:

ช่อง 1 · fit model.fit(...) ในฟังก์ชัน main ช่อง 2 · repr set representative_dataset ใน to_int8_tflite ช่อง 3 · int8 I/O inference_input_type ใน to_int8_tflite ช่อง 4 · accuracy (preds == yte).mean() ใน eval_int8 ส่วนที่เหลือ (dataset_tools, build_model, quantize/dequantize) ให้ไว้แล้ว — อ่านให้เข้าใจ

ทุกช่องมีคำใบ้ # เติม: พร้อมคำตอบเต็มในคอมเมนต์ ลองพิมพ์เองก่อน ถ้าติดค่อยเปิดเฉลย

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

ไล่โค้ด (1) — ช่องเติม fit

ช่องเติมที่ 1 อยู่ในฟังก์ชัน main() หลัง compile — จังหวะที่โมเดลเรียนรู้จริง:

model.compile(optimizer="adam",
              loss="sparse_categorical_crossentropy", metrics=["accuracy"])

# ----- เติมช่องที่ 1 -----
# เติม: ฝึกโมเดลด้วยชุดฝึก และตรวจกับชุด val ทุก epoch
#       -> model.fit(Xtr, ytr, validation_data=(Xva, yva),
#                    epochs=a.epochs, batch_size=32, verbose=2)
pass

_, acc = model.evaluate(Xte, yte, verbose=0)   # วัด float32 บนชุดทดสอบ
  • แทน pass ด้วย model.fit(...) ตามคำใบ้ — นี่คือบรรทัดที่ใช้เวลานานที่สุด (ฝึก 25 รอบ)
  • ถ้าลืมเติม: โมเดลจะไม่เคยเรียนรู้ evaluate จะได้ accuracy ราวๆ 33% (เดามั่วใน 3 คลาส)
  • verbose=2 = พิมพ์หนึ่งบรรทัดต่อ epoch จะได้เห็น accuracy/val_accuracy ไต่ขึ้น

สังเกตว่า evaluate (บรรทัดถัดมา) จะโชว์ความแม่นจริงก็ต่อเมื่อ fit ทำงานก่อน — ลืมช่องนี้ ตัวเลขทั้งหมดหลังจากนี้จะมั่ว

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

ไล่โค้ด (2+3) — ช่องเติม int8

ช่องเติมที่ 2 และ 3 อยู่ในฟังก์ชัน to_int8_tflite() — สองจังหวะที่แยก "โมเดล PC" ออกจาก "โมเดลลงชิป":

conv = tf.lite.TFLiteConverter.from_keras_model(model)
conv.optimizations = [tf.lite.Optimize.DEFAULT]

# ----- เติมช่องที่ 2 -----
# เติม: -> conv.representative_dataset = representative
pass

conv.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]

# ----- เติมช่องที่ 3 -----
# เติม: -> conv.inference_input_type = tf.int8
#          conv.inference_output_type = tf.int8
pass
  • ช่อง 2: แทน pass ด้วย conv.representative_dataset = representative (calibration — เห็นสไลด์ก่อนหน้า)
  • ช่อง 3: แทน pass ด้วยสองบรรทัด บังคับ input/output เป็น int8
  • ถ้าลืมช่อง 2: conv.convert() หยุดด้วย ValueError เพราะ full-integer ต้องมี representative dataset · ถ้าลืมช่อง 3: ขาเข้า/ขาออกของไฟล์ยังเป็น float32 ไม่ใช่สัญญา int8 ที่ต้องการ และ eval_int8() ที่ป้อน int8 จะรันไม่ผ่าน

สองช่องนี้สั้นมาก แต่คือ หัวใจของทั้ง Pillar 4 — ทำให้โมเดล 11 KB วิ่งบน NPU ได้จริง อ่านคอมเมนต์ในไฟล์ให้ครบก่อนเติม

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

ไล่โค้ด (4) — ช่องเติม accuracy

ช่องเติมที่ 4 อยู่ในฟังก์ชัน eval_int8() หลังลูปรันโมเดลครบทุกหน้าต่าง:

    preds.append(int(o.argmax()))
preds = np.asarray(preds)

# ----- เติมช่องที่ 4 -----
# เติม: คำนวณความแม่นบนชุดทดสอบ (สัดส่วนที่ทายถูก)
#       -> acc = (preds == yte).mean()
acc = 0.0
pass
print("int8 test accuracy: %.3f" % acc)
  • แทน pass ด้วย acc = (preds == yte).mean() — เทียบคำทายกับเฉลย นับสัดส่วนที่ตรง
  • preds == yte ได้ array ของ True/False, .mean() ของ True/False = สัดส่วน True = ความแม่น
  • ถ้าลืมเติม: acc ค้างที่ 0.0 รายงานจะบอก accuracy = 0 ทั้งที่โมเดลอาจทายถูกหมด

นี่คือช่องเดียวกับที่ eval_pc.py ทำ — คุณกำลังเขียนตัววัดผลด้วยมือ เข้าใจว่า "accuracy 1.000" มาจากไหนจริงๆ

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

ลงมือทำ — เติม 4 ช่อง แล้วรันใน Docker

เปิด s12_train.py วางไว้ในโฟลเดอร์ shared/training/ (ข้างๆ dataset_tools.py) แล้วเติมทีละช่อง:

# ฟังก์ชัน เติมด้วย ถ้าลืม
1 main model.fit(Xtr, ytr, validation_data=(Xva, yva), epochs=a.epochs, batch_size=32, verbose=2) accuracy ~33% (เดามั่ว)
2 to_int8_tflite conv.representative_dataset = representative convert() หยุดด้วย ValueError
3 to_int8_tflite conv.inference_input_type = tf.int8 (+output) I/O ยังเป็น float32 — eval_int8 ป้อน int8 ไม่ผ่าน
4 eval_int8 acc = (preds == yte).mean() รายงาน accuracy = 0

ขั้นตอน:

  1. เติมทั้ง 4 ช่อง ตามคำใบ้ # เติม: (ลบ pass แทนด้วยคำสั่งจริง)
  2. docker build -t edgeai-train . (ครั้งเดียว) แล้ว docker run --rm -v "$PWD":/work edgeai-train python s12_train.py
  3. ดูจนได้ float32 test accuracy → int8 test accuracy → confusion matrix + ไฟล์ model_int8.tflite โผล่มา

สี่ช่องนี้คือสี่จังหวะของการฝึกเป๊ะ — เติมครบเมื่อไร คุณมีโมเดลของตัวเองพร้อมกระจายไปสี่เป้าหมายในชุดบทเรียนถัด ๆ ไป

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

ปลายทางของไฟล์นี้ — รันได้จริงบน Emulator

model_int8.tflite ที่เราเพิ่งฝึกไม่ได้จบแค่ในไฟล์ BENTO Edge AI Emulator มีโมเดลท่ามือที่ฝึกด้วย pipeline เดียวกันนี้ติดมาแล้ว (แปลง .tflite เป็น ONNX ครั้งเดียว) เปิดสวิตช์ REAL ของโมเดล Motion ก็เห็นมันทายท่ามือ live บนหน้าจอเบราว์เซอร์ ชุดบทเรียนถัดไป (บทเรียน 5.6–5.7) เราจะดูว่าไฟล์ของเราเองไปถึงเบราว์เซอร์ได้อย่างไร

หน้า BENTO Playground บน Emulator ที่รันโมเดล Motion จริงผ่าน ONNX Runtime Web: คลาส idle แถบความมั่นใจสามคลาส และเวลาอนุมาน 0.30 ms

จอ emulator ที่รันได้จริง — โมเดลท่ามือ int8 ที่ฝึกด้วย pipeline เดียวกับวันนี้ ทำงานในเบราว์เซอร์ผ่าน ONNX Runtime Web

  • บรรทัดบน = คลาสที่ชนะพร้อมความมั่นใจ, แถบสามแถบ = ความมั่นใจของแต่ละคลาส (จาก softmax), บรรทัดล่าง = เวลาอนุมานและ backend ที่ใช้
  • นี่คือ "the one artifact → many targets" ที่จับต้องได้: ไฟล์ .tflite เดียวเป็นต้นทางของทั้งเบราว์เซอร์และบอร์ด
  • วันนี้เราแค่สร้างไฟล์ + ทดสอบบน PC ให้แน่ใจก่อน ชุดบทเรียนถัดไปค่อยเอาขึ้นจอ emulator

เห็นภาพปลายทางแล้วจะฝึกสนุกขึ้น — ถ้า front-end (normalize + quantize) ตรงกัน ไฟล์ที่ผ่าน eval_pc.py จะตัดสินแบบเดียวกันในเบราว์เซอร์ นี่คือ parity ที่ชุดบทเรียนถัดไปจะพิสูจน์

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

แหล่งเรียนรู้เพิ่มเติม

ถ้าอยากเข้าใจคณิตหลังฉากให้ลึกขึ้น สามคลิปนี้อธิบายด้วยภาพเคลื่อนไหวที่เข้าใจง่ายมาก:

วิดีโอ

เอกสาร/ภาพอ้างอิง

วิดีโอ/ภาพภายนอกเป็นของเจ้าของต้นฉบับ ใช้เพื่อการศึกษา อ้างอิงลิงก์ต้นทาง

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

ชัยชนะที่เห็นได้ + MVP ของชุดบทเรียนนี้

ชัยชนะที่เห็นได้ของชุดบทเรียนนี้ · โมเดลที่คุณฝึกเอง ทายท่ามือของชุดทดสอบถูก พร้อมไฟล์ model_int8.tflite ในมือ

MVP ของบทเรียน 5.3–5.5 (เกณฑ์ผ่านของชุดบทเรียน): คุณฝึกโมเดล Keras ใน Docker ได้สำเร็จ แล้วได้ รายงานความแม่น ออกมา — float32 accuracy, int8 accuracy, และ confusion matrix บนชุดทดสอบที่โมเดลไม่เคยเห็น

  • ได้ไฟล์ model_int8.tflite (int8 full-integer) + model_int8.tflite.norm.npz (mean/std)
  • อธิบายได้ว่าโค้ดเรียก fit / representative_dataset / inference_input_type / accuracy ตรงไหน ทำอะไร

"ฝึกได้" ไม่ใช่แค่ "เห็นตัวเลขวิ่ง" — คุณต้องบอกได้ว่าทำไม int8 accuracy ควรใกล้ float32 และ representative dataset มีไว้ทำอะไร

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

บันไดช่วยเหลือ — ใบ้ → เริ่มจากโครง → เฉลย → ฉบับเต็ม

ถ้าติด ให้ไต่บันไดนี้ทีละขั้น อย่าเพิ่งกระโดดไปดูเฉลย เพราะของจะเข้าหัวตอนที่คุณพยายามเองก่อน:

  • ใบ้ — คำใบ้อยู่ในคอมเมนต์ # เติม: ทั้ง 4 จุดในไฟล์ฝึก + ตารางหน้าที่แล้ว บอกว่าแต่ละช่องเติมอะไร
  • เริ่มจากโครง — s12_train.py มีโครงครบทั้งไฟล์แล้ว เหลือแค่ 4 จุดให้เติม (dataset/model/quantize ให้ไว้แล้ว)
  • เฉลย — s12_train.py เติมครบพร้อมคอมเมนต์อธิบายทุกช่อง (อ่านให้เข้าใจ ปิดไฟล์ แล้วพิมพ์เอง)
  • ฉบับเต็ม — s12_train_full.py ฉบับขัดเรียบร้อย เพิ่ม synthesize fallback + model.summary() + เกต MVP (เทียบ float32 vs int8 แล้วบอก PASS/NOT YET)

ลองเขียนเองให้สุดก่อนนะ ถ้าติดจริงๆ ค่อยเปิดเฉลยดูทีละช่อง แล้วกลับมาพิมพ์เอง — เดี๋ยวเราค่อย ๆ แกะไปด้วยกัน

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

เชื่อมโยงรากฐาน — วันนี้เราแตะอะไรไปบ้าง

การฝึกโมเดลตัวแรกซ่อนแนวคิด Edge AI หลายชั้นที่จะใช้ไปตลอด โมดูล 5 (Training) และ Apps:

ฝั่ง Machine Learning / Training

  • โครงโมเดลเล็ก — Conv1D + pooling + dense ที่เล็กพอลง NPU (op ที่ Ethos-U55 เร่งได้)
  • train / val / test — ฝึกด้วย train, จับ overfit ด้วย val, วัดผลจริงด้วย test (ชุดที่ไม่เคยเห็น)
  • int8 quantization + calibration — บีบโมเดลด้วย representative dataset ให้ลงชิปได้โดยไม่เสียความแม่นมาก
  • confusion matrix — อ่านว่าโมเดลสับสนคู่ไหน ไม่ใช่แค่ตัวเลข accuracy เดียว

ฝั่ง วิศวกรรม / เครื่องมือ

  • Docker — สภาพแวดล้อมทำซ้ำได้ รันเหมือนกันทุก OS
  • the one artifact — .tflite หนึ่งไฟล์ ที่ บทเรียน 5.6–5.9 จะกระจายไปสี่เป้าหมาย
  • normalization เป็นส่วนหนึ่งของโมเดล — เซฟ mean/std ไว้ ไม่งั้นเกิดจุดพังเงียบ

ทั้งหมดนี้ยืนบนสี่จังหวะ build → fit → convert → eval — พลังของ "train once, run everywhere" คือคุณสร้างชิ้นงานเดียวในชุดบทเรียนนี้ แล้วเอาไปได้ทั้งบอร์ด เบราว์เซอร์ และ Cortex-A

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

ใช้จริงที่ไหน — training pipeline ในโลกจริง

โฟลว์ที่เราทำวันนี้ (เก็บข้อมูล → ฝึกใน container → บีบ int8 → ทดสอบ) ไม่ใช่ของเล่น มันคือ workflow มาตรฐานของทีม Edge AI จริง

Reproducible training (Docker/CI) ทีมฝึกโมเดลในกล่องเดียวกัน ผลตรงกันเป๊ะ รันบน CI ได้ ฝึกใหม่อัตโนมัติเมื่อข้อมูลเพิ่ม เหมือน MLOps pipeline จริงในบริษัท int8 quantization (ทุกงาน on-device) คำสั่งเสียง · กล้อง AI · wearable ตรวจการล้ม ทุกตัวบีบ int8 เพื่อลง MCU/NPU เหมือนเรา representative dataset = ขั้นบังคับในงานจริง held-out test = ป้องกันหลอกตัวเอง วัดผลบนชุดที่โมเดลไม่เคยเห็น ค่าที่เชื่อได้ confusion matrix บอกว่าเก็บข้อมูลเพิ่มตรงไหน the one artifact → many targets .tflite เดียว ไป MCU/Web/Cortex-A คือ 5.6–5.9 ของเรา (Web แล้วบอร์ด)

โมเดลที่คุณฝึกวันนี้แก้โจทย์เดียวกับโมเดล Motion ที่ติดมากับบอร์ดตั้งแต่ บทเรียน 1.1–1.3 — ต่างกันแค่ตัวนี้ คุณฝึกเอง เข้าใจทุกจังหวะ นี่คือความหมายของการ "ถอยกลับไปสร้างต้นทางเอง"

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

งานทำเอง + สรุปบทเรียน

งานทำเอง (ท้ายบทเรียน):

  1. เติม s12_train.py ให้ครบทั้ง 4 ช่อง รันใน Docker จนได้ model_int8.tflite + รายงานความแม่น
  2. ลองลด --epochs เหลือ 3 แล้วรันใหม่ เทียบ accuracy กับตอน 25 epoch — จดว่าต่างกันแค่ไหน เพราะอะไร
  3. ลองให้ representative() ป้อนหน้าต่างศูนย์ล้วน (np.zeros_like(X_repr[i:i + 1])) แทนหน้าต่างจริง แล้วรันใหม่ ดูว่า int8 accuracy เปลี่ยนไหม อธิบายว่าทำไม

ใบ้ข้อ 3 — converter วัดช่วงค่า activation จากตัวอย่างที่ป้อน ถ้าตัวอย่างไม่เหมือนข้อมูลจริง สเกล int8 จะไม่พอดี ความแม่นบน int8 มักตกจาก float32 ให้เห็น (ส่วนการลบบรรทัดช่อง 2 ทิ้งไปเลย convert() จะหยุดด้วย ValueError)

วันนี้เราได้: เข้าใจว่าทำไมฝึกใน Docker · เดินครบสี่จังหวะ build → fit → convert → eval · รู้ว่า int8 + representative dataset สำคัญยังไง · ฝึกโมเดลของตัวเองแล้วอ่าน accuracy + confusion matrix เป็น

ชุดบทเรียนถัดไป (บทเรียน 5.6–5.7) เราจะเอาไฟล์ model_int8.tflite เดียวกันนี้ไป รันในเบราว์เซอร์ (BENTO Edge AI Emulator) แล้วเล่าเรื่องการเอาไปรันบน Cortex-A (Raspberry Pi/Jetson) — ไฟล์เดิม ไม่แก้ เจอกันครับ

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0