ช่องเติมที่ 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ทำงานก่อน — ลืมช่องนี้ ตัวเลขทั้งหมดหลังจากนี้จะมั่ว
ช่องเติมที่ 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
pass ด้วย conv.representative_dataset = representative (calibration — เห็นสไลด์ก่อนหน้า)pass ด้วยสองบรรทัด บังคับ input/output เป็น int8conv.convert() หยุดด้วย ValueError เพราะ full-integer ต้องมี representative dataset · ถ้าลืมช่อง 3: ขาเข้า/ขาออกของไฟล์ยังเป็น float32 ไม่ใช่สัญญา int8 ที่ต้องการ และ eval_int8() ที่ป้อน int8 จะรันไม่ผ่านสองช่องนี้สั้นมาก แต่คือ หัวใจของทั้ง Pillar 4 — ทำให้โมเดล 11 KB วิ่งบน NPU ได้จริง อ่านคอมเมนต์ในไฟล์ให้ครบก่อนเติม
ช่องเติมที่ 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" มาจากไหนจริงๆ
เปิด 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 |
ขั้นตอน:
# เติม: (ลบ pass แทนด้วยคำสั่งจริง)docker build -t edgeai-train . (ครั้งเดียว) แล้ว docker run --rm -v "$PWD":/work edgeai-train python s12_train.pyfloat32 test accuracy → int8 test accuracy → confusion matrix + ไฟล์ model_int8.tflite โผล่มาสี่ช่องนี้คือสี่จังหวะของการฝึกเป๊ะ — เติมครบเมื่อไร คุณมีโมเดลของตัวเองพร้อมกระจายไปสี่เป้าหมายในชุดบทเรียนถัด ๆ ไป
model_int8.tflite ที่เราเพิ่งฝึกไม่ได้จบแค่ในไฟล์ BENTO Edge AI Emulator มีโมเดลท่ามือที่ฝึกด้วย pipeline เดียวกันนี้ติดมาแล้ว (แปลง .tflite เป็น ONNX ครั้งเดียว) เปิดสวิตช์ REAL ของโมเดล Motion ก็เห็นมันทายท่ามือ live บนหน้าจอเบราว์เซอร์ ชุดบทเรียนถัดไป (บทเรียน 5.6–5.7) เราจะดูว่าไฟล์ของเราเองไปถึงเบราว์เซอร์ได้อย่างไร

จอ emulator ที่รันได้จริง — โมเดลท่ามือ int8 ที่ฝึกด้วย pipeline เดียวกับวันนี้ ทำงานในเบราว์เซอร์ผ่าน ONNX Runtime Web
.tflite เดียวเป็นต้นทางของทั้งเบราว์เซอร์และบอร์ดเห็นภาพปลายทางแล้วจะฝึกสนุกขึ้น — ถ้า front-end (normalize + quantize) ตรงกัน ไฟล์ที่ผ่าน
eval_pc.pyจะตัดสินแบบเดียวกันในเบราว์เซอร์ นี่คือ parity ที่ชุดบทเรียนถัดไปจะพิสูจน์
ถ้าอยากเข้าใจคณิตหลังฉากให้ลึกขึ้น สามคลิปนี้อธิบายด้วยภาพเคลื่อนไหวที่เข้าใจง่ายมาก:
วิดีโอ
เอกสาร/ภาพอ้างอิง
วิดีโอ/ภาพภายนอกเป็นของเจ้าของต้นฉบับ ใช้เพื่อการศึกษา อ้างอิงลิงก์ต้นทาง
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 มีไว้ทำอะไร
ถ้าติด ให้ไต่บันไดนี้ทีละขั้น อย่าเพิ่งกระโดดไปดูเฉลย เพราะของจะเข้าหัวตอนที่คุณพยายามเองก่อน:
# เติม: ทั้ง 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)ลองเขียนเองให้สุดก่อนนะ ถ้าติดจริงๆ ค่อยเปิดเฉลยดูทีละช่อง แล้วกลับมาพิมพ์เอง — เดี๋ยวเราค่อย ๆ แกะไปด้วยกัน
การฝึกโมเดลตัวแรกซ่อนแนวคิด Edge AI หลายชั้นที่จะใช้ไปตลอด โมดูล 5 (Training) และ Apps:
ฝั่ง Machine Learning / Training
ฝั่ง วิศวกรรม / เครื่องมือ
.tflite หนึ่งไฟล์ ที่ บทเรียน 5.6–5.9 จะกระจายไปสี่เป้าหมายทั้งหมดนี้ยืนบนสี่จังหวะ
build → fit → convert → eval— พลังของ "train once, run everywhere" คือคุณสร้างชิ้นงานเดียวในชุดบทเรียนนี้ แล้วเอาไปได้ทั้งบอร์ด เบราว์เซอร์ และ Cortex-A
โฟลว์ที่เราทำวันนี้ (เก็บข้อมูล → ฝึกใน container → บีบ int8 → ทดสอบ) ไม่ใช่ของเล่น มันคือ workflow มาตรฐานของทีม Edge AI จริง
โมเดลที่คุณฝึกวันนี้แก้โจทย์เดียวกับโมเดล Motion ที่ติดมากับบอร์ดตั้งแต่ บทเรียน 1.1–1.3 — ต่างกันแค่ตัวนี้ คุณฝึกเอง เข้าใจทุกจังหวะ นี่คือความหมายของการ "ถอยกลับไปสร้างต้นทางเอง"
งานทำเอง (ท้ายบทเรียน):
s12_train.py ให้ครบทั้ง 4 ช่อง รันใน Docker จนได้ model_int8.tflite + รายงานความแม่น--epochs เหลือ 3 แล้วรันใหม่ เทียบ accuracy กับตอน 25 epoch — จดว่าต่างกันแค่ไหน เพราะอะไร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) — ไฟล์เดิม ไม่แก้ เจอกันครับ