ทีมที่รับเอกสารภาษาไทยจากลูกค้าหรือจากหน่วยงานภายในมักเจอทางเลือกเดิม คือส่งไฟล์ไปให้บริการ OCR บนคลาวด์ เพื่อให้ได้ข้อความมาค้นหา หรือใส่ในระบบงานต่อ วิธีนี้เริ่มได้เร็ว และบริการบางตัวก็อ่านเอกสารได้ดี แต่เอกสารนั้นออกจากองค์กรแล้ว แม้ผู้ให้บริการจะบอกว่าไม่นำข้อมูลไปฝึกโมเดล ก็ยังเหลือเรื่องสัญญา, พ.ร.บ. คุ้มครองข้อมูลส่วนบุคคล, และหน้าเอกสารที่มักมีสำเนาบัตรหรือข้อมูลส่วนบุคคลปนอยู่กับเนื้อหาที่ต้องการอ่าน

บทความนี้เล่าวิธีที่เราใช้ คือรัน OCR ภาษาไทยบนเครื่อง Apple Silicon ภายในองค์กร เปิดเป็น API ให้เครื่องในเครือข่ายเรียกได้ โดยไม่เปิดพอร์ตออกอินเทอร์เน็ต และไม่ส่งไฟล์ไปบริการภายนอก ตัวอย่างคำสั่ง ที่อยู่ IP และชื่อไฟล์ในบทความเป็นค่าสมมติทั้งหมด ให้ปรับตามระบบของคุณเอง

แผนภาพงาน OCR ภาษาไทยบนเครื่อง Apple Silicon: ไฟล์ในองค์กรถูกแปลงเป็นภาพทีละหน้าที่ 200 DPI แล้วส่งเข้า API ที่ผูกเฉพาะที่อยู่ภายใน พร้อมคีย์ โมเดล Text และ Markdown ทำงานบนเครื่องนั้น ผลลัพธ์เก็บในองค์กร เอกสารไม่ถูกส่งไปบริการคลาวด์ และชุดทดสอบใช้เอกสารสาธารณะก่อนใช้เอกสารจริง
ภาพรวม: เอกสารไหลอยู่ในเครือข่ายภายในตลอดทาง โมเดลทำงานบนเครื่อง Apple Silicon ขององค์กร และคนวัดผลด้วยชุดทดสอบสาธารณะก่อนใช้ของจริง (คลิกที่ภาพเพื่อดูขนาดเต็ม)

ทำไมเอกสารไม่ควรออกจากองค์กร

เอกสารในงานจริงไม่ใช่แค่จดหมายทั่วไป หน้าเดียวกันอาจมีตาราง, หัวกระดาษ, แบบฟอร์ม, และข้อมูลส่วนบุคคล ถ้าส่งไฟล์ไปบริการภายนอกเพื่อทดลองคุณภาพ ข้อมูลนั้นก็ออกไปแล้ว แม้จะลบภายหลัง

เหตุผลที่เราเลือกทำบนเครื่องตัวเองมีดังนี้

  • สัญญาและ พ.ร.บ. คุ้มครองข้อมูลส่วนบุคคลมักจำกัดการส่งสำเนาเอกสารออกนอกองค์กร หรือออกนอกประเทศ
  • ชุดทดสอบกับเอกสารจริงต้องแยกกัน ห้ามเอาไฟล์ลูกค้าไปวัดผลบนบริการคลาวด์
  • เครื่อง Apple Silicon ในออฟฟิศมีหน่วยความจำรวม (unified memory) เพียงพอสำหรับโมเดล OCR ขนาดกลาง จึงเริ่มได้โดยยังไม่ต้องตั้งเครื่อง GPU ในห้องเซิร์ฟเวอร์

เป้าหมายไม่ใช่การทำคะแนนสูงสุดบนทุกชุดทดสอบ แต่เป็นการอ่านเอกสารได้ดีพอใช้ โดยเอกสารไม่ออกจากเครื่องที่เราควบคุม

สถาปัตยกรรมที่เอกสารไม่ไหลออก

งานหนึ่งหน้ามีสี่ขั้น และทุกขั้นอยู่ภายในองค์กร

  1. รับไฟล์ PDF หรือภาพจากงานภายใน
  2. วาดแต่ละหน้าเป็นภาพที่ความละเอียดพอให้อ่านสระและวรรณยุกต์ได้ เราใช้ 200 DPI เป็นจุดตั้งต้น
  3. ส่งภาพเข้าโมเดลบนเครื่อง Apple Silicon แล้วได้ข้อความหรือ Markdown กลับมา
  4. เก็บผลบนระบบภายใน แล้ววัดคุณภาพด้วยชุดเอกสารสาธารณะก่อนใช้กับเอกสารจริง

API ของงานนี้ผูกเฉพาะที่อยู่ภายใน เช่น 100.64.0.10 บน tailnet หรือ 192.0.2.10 บนเครือข่ายสำนักงาน ไม่ผูก 0.0.0.0 และไม่เปิดพอร์ตออกอินเทอร์เน็ต ทุกคำขอมี Bearer token ผลลัพธ์เขียนลงไฟล์ในองค์กรเท่านั้น ในโค้ดต้องไม่มีเส้นทางอัปโหลดไปบริการภายนอก

ถ้าต้องการให้เครื่องนอกออฟฟิศเรียกได้โดยไม่เปิดพอร์ตสาธารณะ ใช้เครือข่าย overlay ส่วนตัว แนวทางที่เราเขียนไว้ใน ติดตั้ง k3s + Argo CD + SealedSecrets บน VM เดียว ให้เข้าได้เฉพาะทาง Tailscale ใช้หลักเดียวกัน คือเข้าได้เฉพาะสมาชิกในเครือข่ายที่เราอนุญาต

เลือกโมเดลอย่างไร

เราเริ่มจากโมเดล open-weight ที่อ่านภาษาไทยและภาษาอังกฤษได้ในเครื่องตัวเอง และมีคู่มือติดตั้งที่ตรวจสอบได้ โดยเฉพาะตระกูล OpenThai 2.0 OCR Text กับ OpenThai 2.0 OCR Markdown ของ iApp Technology ซึ่งมีคู่มือการรันบน Windows, Linux และ Mac ที่ How to run OpenThai 2.0 OCR

สรุปข้อต่างที่ทำให้เราเลือกคนละโมเดลตามงานมีดังนี้

  • OCR Text. ไฟล์เล็ก ใบอนุญาต Apache-2.0 รันบน CPU ได้ คืนข้อความตามลำดับการอ่าน พร้อมกรอบบรรทัด ผู้พัฒนาเผยแพร่ตัวเลขประมาณ 0.5 วินาทีต่อหน้าบน Mac รุ่นใหม่ เหมาะกับงานค้นหา คัดลอกข้อความ และป้อนเข้าสู่ระบบอื่นที่ต้องการแค่ตัวอักษร
  • OCR Markdown. โมเดลประมาณ 0.63 พันล้านพารามิเตอร์ น้ำหนักราว 2.5 GB คืน Markdown และเก็บตารางเป็น HTML พร้อมกรอบและชนิดของแต่ละบล็อก เหมาะกับแบบฟอร์มและหน้าที่มีตาราง ผู้พัฒนาเผยแพร่ตัวเลขประมาณ 1.6 วินาทีต่อหน้าบน NVIDIA H100 และประมาณ 15 ถึง 27 วินาทีต่อหน้าบน Mac ผ่าน PyTorch ตามคู่มือทางการ

บริการ OCR บนคลาวด์บางตัวยังได้คะแนนสูงกว่าบนชุดทดสอบสาธารณะ โดยเฉพาะหน้าที่ยาก แต่เอกสารต้องออกจากเครื่อง ทางเลือกที่ host เองได้อีกสายหนึ่ง เช่น Typhoon OCR ควรเอามาเทียบในชุดทดสอบชุดเดียวกัน ไม่ใช่เลือกจากชื่อโมเดลอย่างเดียว

ลายมือยังเป็นจุดอ่อนของโมเดลในกลุ่มนี้ อย่าตั้งเป้าว่าจะแทนคนอ่านลายมือได้ในขั้นแรก งานที่เราใช้โมเดลเหล่านี้ก่อนคือเอกสารพิมพ์ดี แบบฟอร์ม และตาราง

ติดตั้งบนเครื่อง Apple Silicon

เริ่มจากเส้นทางที่ผู้พัฒนาเอกสารไว้ เพราะวัดผลซ้ำได้และไม่ล็อกกับเครื่องมือภายในของใคร ใช้ Python 3.10 ขึ้นไป สร้างสภาพแวดล้อมแยก แล้วดาวน์โหลดน้ำหนักจาก Hugging Face ตามคู่มือ ตัวอย่างด้านล่างเป็นค่าสมมติ ชื่อโฟลเดอร์ปรับได้

mkdir thai-ocr && cd thai-ocr
python3 -m venv .venv
source .venv/bin/activate
pip install huggingface_hub pypdfium2
hf download iapp/OpenThai2.0-OCR-Text --local-dir OpenThai2.0-OCR-Text
hf download iapp/OpenThai2.0-OCR-Markdown --local-dir OpenThai2.0-OCR-Markdown
# ติดตั้งแพ็กเกจอ่านเอกสารตามคู่มือของแต่ละโมเดล
# จากนั้นเรียกสคริปต์ที่มากับคู่มือ เช่น
# python ocr_text.py document.pdf
# python ocr_markdown.py document.pdf

จุดที่สำคัญกว่าคำสั่งติดตั้งคือการแปลงหน้า PDF เป็นภาพก่อนส่งให้โมเดล อย่าส่งไฟล์ PDF ทั้งก้อนแล้วหวังว่าโมเดลจะเปิดได้เองทุกกรณี เราใช้ 200 DPI เป็นค่าตั้งต้น เพราะสระบน สระล่าง และวรรณยุกต์ภาษาไทยหายง่ายเมื่อภาพเล็กเกินไป

import pypdfium2 as pdfium

pdf = pdfium.PdfDocument("document.pdf")
for i in range(len(pdf)):
    image = pdf[i].render(scale=200 / 72).to_pil()
    image.save(f"page-{i + 1:03d}.png")

คู่มือทางการของโมเดล Markdown ระบุว่าบน GPU ของ Mac ให้รันแบบ greedy (temperature 0) เพราะเมื่อสุ่มตัวอย่าง หน้าอาจเพี้ยน ควรทำตามข้อนี้ตั้งแต่ต้น แล้วจับเวลาและความจำบนเครื่องตัวเอง อย่าใช้ตัวเลขจากเครื่องอื่นเป็นคำตอบสุดท้าย

ในทางปฏิบัติ เราทดลอง runtime ที่ใช้หน่วยความจำรวมของ Apple Silicon โดยตรง เช่น ในตระกูล MLX เพิ่มจากเส้นทาง PyTorch ตามคู่มือ แล้วพบว่าหน้าเดียวกันใช้เวลาสั้นลงเหลือประมาณไม่กี่วินาที ถ้าจะเดินทางนี้ ให้วัดคุณภาพของผลลัพธ์ซ้ำกับชุดเดิมก่อนเปลี่ยน เพราะความเร็วที่ได้อาจมาพร้อมพฤติกรรมที่ต่างจากเซิร์ฟเวอร์ vLLM บน NVIDIA ซึ่งผู้พัฒนาใช้วัดคะแนนที่เผยแพร่ อย่าเปลี่ยน runtime แล้วถือว่าคะแนนเดิมยังใช้ได้

เปิด API ให้ทีมใช้โดยไม่เปิดออกอินเทอร์เน็ต

สคริปต์ในเครื่องผู้ดูแลระบบใช้เริ่มต้นได้ แต่ทีมอื่นจะเรียกจากงานของตัวเองไม่สะดวก เราจึงหุ้มโมเดลด้วย HTTP API เล็ก ๆ แล้วตั้งกติกาให้ชัดตั้งแต่ต้น

  • ผูกเฉพาะที่อยู่ภายใน ไม่ผูก 0.0.0.0
  • บังคับ Bearer token ทุกคำขอ คีย์เก็บในไฟล์โหมด 0600 นอก Git
  • ไม่เขียนเนื้อหาเอกสารลงในบันทึกล็อก
  • จำกัดจำนวนงานพร้อมกันและหน่วยความจำ เพราะโมเดล Markdown ใช้ทรัพยากรมากกว่าโมเดล Text มาก
  • ให้ระบบเริ่มเองเมื่อเครื่องบูต เช่น LaunchAgent บน macOS
# ตัวอย่างการฟังเฉพาะที่อยู่ภายใน ไม่ใช่ทุกอินเทอร์เฟซ
# uvicorn app:app --host 100.64.0.10 --port 8080

# /Library/LaunchAgents/co.example.thai-ocr.plist
# ชี้ไปที่โปรแกรมในโฟลเดอร์งาน และอ่านคีย์จากไฟล์ 0600
# เช่น /usr/local/etc/thai-ocr/api-key
curl -sS \
  -H "Authorization: Bearer $OCR_TOKEN" \
  -F "file=@page-001.png" \
  http://100.64.0.10:8080/ocr

ตรวจจากอีกเครื่องในเครือข่ายเดียวกันว่าเรียกได้ และตรวจจากภายนอกว่าเรียกไม่ได้ ดูด้วยว่าโปรเซสฟังอยู่ที่ที่อยู่ภายในจริง ไม่ใช่ทุกอินเทอร์เฟซ จุดตรวจสุขภาพของ API ควรตอบได้โดยไม่ต้องรับไฟล์เอกสาร อย่าถือว่า "ใช้แต่ในออฟฟิศ" แล้วละคีย์ เพราะเครื่องในเครือข่ายสำนักงานมีมากกว่าที่เราคิด และพอร์ตที่เผลอเปิดออกอินเทอร์เน็ตจะทำให้เอกสารไหลออกได้ในคำขอเดียว

วัดผลก่อนใช้กับเอกสารจริง

อย่าเริ่มจากไฟล์ลูกค้า เริ่มจากชุดเอกสารภาษาไทยสาธารณะชุดเดียวกัน แล้ววัดทั้งคุณภาพและความเร็ว เกณฑ์ที่เราใช้มีอย่างน้อยสี่อย่าง

  • ความผิดพลาดระดับตัวอักษรของหน้าพิมพ์ดี
  • โครงสร้างตาราง หัวตารางที่รวมเซลล์ต้องไม่แตก
  • เวลาต่อหน้า และหน่วยความจำขณะอ่าน
  • พฤติกรรมกับหน้าที่ยาก โดยเฉพาะลายมือ ซึ่งเรายอมรับว่าโมเดลยังอ่อน

ในการทดสอบของเราด้วยชุดเอกสารสาธารณะ บริการคลาวด์บางตัวได้คะแนนรวมสูงกว่า โมเดลที่ host เองทำได้ใกล้เคียงในงานพิมพ์ดีและหน้าที่มีตาราง ส่วนลายมือยังทิ้งช่วงชัดเจน โมเดล Text เร็วและเบา แต่เสียโครงสร้างตาราง โมเดล Markdown ช้ากว่าและใช้ความจำมากกว่า แต่ตารางใช้ต่อได้ และอ่านหน้าสองคอลัมน์ตามลำดับคอลัมน์ได้ดีกว่าในงานที่เราลอง หน้าที่มีทั้งภาษาไทยและภาษาอังกฤษในบรรทัดเดียวกันต้องอยู่ในชุดทดสอบด้วย เพราะงานจริงในไทยมักเป็นแบบนั้น

ตั้งเกณฑ์ก่อนเริ่ม เช่น หน้าพิมพ์ดีต้องอยู่ในระดับที่คนตรวจแล้วใช้ต่อได้ และตารางตัวอย่างต้องไม่สลับแถว ถ้ายังไม่ผ่าน ให้ปรับความละเอียดของภาพ, วิธีตัดหน้า, หรือตัวโมเดล ก่อนจะเปิดให้งานจริงเรียก แนวคิดเดียวกับที่เราใช้ใน ให้ LLM ที่ host เองทำงานกะดึกแบบ shadow mode คือวัดผลกับงานที่คนทำอยู่แล้วก่อนไว้ใจ

เมื่อคุณภาพผ่านชุดสาธารณะแล้ว ค่อยทำชุดตัวอย่างภายในที่ปิดข้อมูลอ่อนไหว ถ้านโยบายขององค์กรอนุญาต ชุดนี้ใช้ไล่ปัญหาเฉพาะแบบฟอร์มของเรา แต่ชุดนี้ต้องไม่ถูกส่งออกไปบริการภายนอก

กับดักที่เจอ

  • ภาพความละเอียดต่ำ ทำให้สระและวรรณยุกต์หาย แล้วไปโทษโมเดล
  • ส่งทั้งไฟล์ PDF โดยไม่วาดเป็นภาพทีละหน้า
  • ผูก 0.0.0.0 หรือเปิดพอร์ตสาธารณะ ทั้งที่ตั้งใจให้ใช้เฉพาะในออฟฟิศ
  • ไม่ใส่ API key เพราะคิดว่าเครือข่ายภายในปลอดภัยพอ
  • เขียนเนื้อหาเอกสารลงในล็อก เพื่อไล่จุดบกพร่อง แล้วล็อกนั้นกลายเป็นสำเนาเอกสาร
  • วัดผลด้วยเอกสารลูกค้าบนบริการคลาวด์
  • ไม่จำกัดหน่วยความจำและคิวงาน แล้วงานอื่นบนเครื่องเดียวกันสะดุด
  • ตั้ง temperature ไม่เป็น 0 บน GPU ของ Mac แล้วหน้าออกมาเพี้ยน
  • คาดหวังว่าลายมือจะดีเท่าหน้าพิมพ์ดีตั้งแต่รอบแรก

สรุป

OCR ภาษาไทยทำบนเครื่องตัวเองได้ในระดับที่ใช้กับงานพิมพ์ดีและตารางได้ โดยไม่ต้องส่งเอกสารออกนอกองค์กร เริ่มจากโมเดล open-weight ที่มีคู่มือชัด, แปลงหน้าเป็นภาพที่ละเอียดพอ, เปิด API เฉพาะที่อยู่ภายในพร้อมคีย์, ห้ามลอกเอกสารลงในล็อก, แล้ววัดผลด้วยชุดสาธารณะก่อนใช้ของจริง

เลือกโมเดล Text เมื่อต้องการข้อความเร็ว ๆ เลือกโมเดล Markdown เมื่อตารางสำคัญ และยอมรับตั้งแต่ต้นว่าลายมือยังต้องให้คนดู ถ้าคุณภาพผ่านเกณฑ์ที่ตั้งไว้ เอกสารจะอยู่บนเครื่องที่เราควบคุมตลอดทาง ถ้ายังไม่ผ่าน ปิด API นั้นได้โดยไม่กระทบบริการภายนอก เพราะตั้งแต่ต้นเราไม่ได้พึ่งบริการเหล่านั้นกับไฟล์ในงานนี้