วัดภาษาไทยด้วยโปรแกรม: ทำไม .split() กับ crfcut ถึงหลอกคุณ และ "วรรค" ช่วยได้อย่างไร
บทเรียนจากการสร้างระบบวัดความเป็นธรรมชาติของบทความไทย: กับดักการนับคำและตัดประโยคที่พังแบบเงียบๆ และวิธีวัดจังหวะภาษาไทยด้วยหน่วยที่ภาษามีจริง
ถ้าคุณเคยเขียนโค้ดวิเคราะห์ข้อความภาษาอังกฤษ คุณจะติดนิสัยสองอย่างมา: นับคำด้วยการแยกช่องว่าง และนับประโยคด้วยเครื่องหมายจุด พอเจอภาษาไทย นิสัยทั้งสองอย่างจะทรยศคุณทันที และที่ร้ายคือมันทรยศแบบเงียบๆ โค้ดไม่พัง ตัวเลขออกมาสวย แค่ผิดหมด
บทความนี้เล่าจากประสบการณ์จริงของเว็บนี้เอง: เราสร้างระบบวัด “ความเป็นธรรมชาติ” ของบทความไทยที่ AI เขียน เทียบกับข่าวไทยที่นักข่าวจริงเขียน แล้วพบว่าก่อนจะวัดอะไรได้เลย ต้องตอบคำถามพื้นฐานที่สุดให้ถูกก่อน — หน่วยของภาษาไทยคืออะไรกันแน่?
กับดักที่ 1 — .split() นับคำไม่ได้ เพราะไทยไม่เว้นวรรคระหว่างคำ
ภาษาไทยเขียนติดกันทั้งวรรค ไม่มีช่องว่างระหว่างคำ ดังนั้น len(text.split()) ที่ใช้ได้ดีในภาษาอังกฤษ จะนับ “วรรคทั้งวรรค” เป็นหนึ่งคำ
เราเจอเรื่องนี้ด้วยตัวเอง ครั้งแรกที่เทียบความยาวบทความสองภาษา ระบบรายงานว่าบทความไทย “สั้นกว่า” ฉบับอังกฤษมาก ทั้งที่เนื้อหาเดียวกัน เพราะฝั่งอังกฤษนับคำจริง แต่ฝั่งไทยนับวรรค
วิธีที่ถูกคือใช้ตัวตัดคำ (word tokenizer) ของ PyThaiNLP:
from pythainlp import word_tokenize
text = "ภาษาไทยเขียนติดกันทั้งประโยค"
print(len(text.split())) # 1 <- ผิด
print(len(word_tokenize(text))) # 6 <- ["ภาษาไทย", "เขียน", "ติดกัน", ...]
เอนจินเริ่มต้นชื่อ newmm เป็นแบบอิงพจนานุกรม เร็วพอจะรันในโปรดักชันได้สบาย
กับดักที่ 2 — crfcut ตัดประโยคได้ จนกระทั่งเจอคำอังกฤษ
ปัญหาถัดมาลึกกว่านั้น: ภาษาไทยไม่มีเครื่องหมายจบประโยค ไม่มีจุด ไม่มีอะไรบอกว่าประโยคจบตรงไหน PyThaiNLP จึงมีตัวตัดประโยคชื่อ crfcut ซึ่งเป็นโมเดล CRF ที่เรียนรู้จากการเว้นวรรคของคนจริง ฟังดูดี แต่ในทางปฏิบัติมีกับดักซ้อนกันสองชั้น
ชั้นแรกคือ dependency ที่พังเงียบ — crfcut ต้องมีแพ็กเกจ python-crfsuite ติดตั้งอยู่ ถ้าไม่มี PyThaiNLP จะไม่ error แต่ถอยไปตัดด้วยช่องว่างแทนโดยไม่บอกใคร ผลคือได้ “หนึ่งประโยคต่อหนึ่งคำ” แล้วสถิติทั้งกระดานก็เพี้ยนโดยไม่มีใครสังเกต
ชั้นที่สองคือข้อความจริงมีคำอังกฤษปนเสมอ — บทความไอทีภาษาไทยเต็มไปด้วยชื่อผลิตภัณฑ์และศัพท์เทคนิคภาษาอังกฤษ เราวัดพฤติกรรม crfcut กับย่อหน้าข่าวจริง: ข้อความไทยล้วนตัดถูก 3 ใน 3 จุด แต่พอย่อหน้ามีชื่อผลิตภัณฑ์ภาษาอังกฤษแทรกเข้ามา ความแม่นตกเหลือ 1 ใน 4 ลองแก้ด้วยการ mask คำอังกฤษก่อนตัดก็ไม่รอด เพราะคราวนี้มันสร้างขอบเขตประโยคขึ้นเองกลางชื่อสินค้า เช่นหั่น “Gemini 3.6” ขาดจาก “Flash”
บทเรียนที่แพงที่สุดคือมันไม่ได้แค่ผิด แต่ผิดแบบดูดี เศษ segment ที่ crfcut สร้างขึ้นมา เคยทำให้บทความชิ้นหนึ่งของเราได้คะแนน “สัดส่วนประโยคสั้น” 0.472 ซึ่งดีที่สุดในชุดทดสอบ ทั้งที่พอวัดใหม่ด้วยวิธีที่ตรงไปตรงมา ค่าจริงคือ 0.258 — ตัวเลขสวยจากเครื่องมือพัง อันตรายกว่าตัวเลขแย่จากเครื่องมือดีเสมอ
ทางออก — วัดในหน่วยที่ภาษาไทยมีจริง: วรรค
ถอยหลังมาหนึ่งก้าวแล้วถามใหม่: ผู้เขียนภาษาไทยทำเครื่องหมายอะไรไว้ในข้อความบ้าง? คำตอบเดียวที่มีคือ วรรค — ช่องว่างที่ตั้งใจเคาะเพื่อแบ่งจังหวะการอ่าน แทนที่จะฝืนหา “ประโยค” ที่ภาษาไม่ได้กำกับไว้ เราเปลี่ยนไปวัดในหน่วยวรรคแทน แล้วพบว่าไม่ต้องใช้โมเดลอะไรเลย:
import re
# ตัดเฉพาะช่องว่างที่ "ขนาบด้วยอักษรไทยทั้งสองฝั่ง"
THAI = "ก-๛"
CLAUSE_SPLIT = re.compile(rf"(?<=[{THAI}]) (?=[{THAI}])")
def thai_clauses(paragraph: str) -> list[str]:
return [c.strip() for c in CLAUSE_SPLIT.split(paragraph) if c.strip()]
ความฉลาดอยู่ที่ lookbehind กับ lookahead คู่นี้: ช่องว่างใน “Gemini 3.6 Flash” หรือ “$0.30 ต่อล้านโทเค็น” จะไม่ถูกตัด เพราะมีอย่างน้อยหนึ่งฝั่งไม่ใช่อักษรไทย ชื่อผลิตภัณฑ์กับตัวเลขจึงรอดมาทั้งก้อน โดยไม่ต้องเขียน exception list สักบรรทัด
แล้ววัดอะไรจากวรรคได้บ้าง
พอได้หน่วยที่ไว้ใจได้ ก็สร้างตัวชี้วัดง่ายๆ ได้สามตัว:
- สัดส่วนวรรคสั้น — วรรคที่ยาวไม่เกิน 10 คำ (นับด้วย newmm) คิดเป็นกี่ส่วนของทั้งหมด
- ค่าความแปรปรวนของความยาววรรค (CV) — ยิ่งสูง จังหวะยิ่งหลากหลาย
- จำนวนคำต่อวรรคเฉลี่ย
ตัวเลขโดดๆ ไม่มีความหมายจนกว่าจะมีของจริงมาเทียบ เราจึงสร้างคลังอ้างอิงจากข่าวไอทีภาษาไทยที่มนุษย์เขียน 29 ชิ้นจาก 6 สำนักข่าวไทย (เก็บเฉพาะค่าสถิติรวม ไม่เก็บข้อความต้นฉบับ) แล้วผลที่ออกมาก็ชัดเจนอย่างที่เห็นในภาพเปิดบทความ:
| ตัวชี้วัด (ระดับวรรค) | ข่าวไทยที่คนเขียน (มัธยฐาน) | บทความ AI ของเรา |
|---|---|---|
| สัดส่วนวรรคสั้น ≤ 10 คำ | 0.611 (ต่ำสุด 0.404) | 0.158 – 0.400 |
| CV ความยาววรรค | 0.866 | ต่ำกว่าเกณฑ์เกือบทุกชิ้น |
| คำต่อวรรค | 10.7 (สูงสุด 18.65) | ยาวกว่าอย่างชัดเจน |
แถวแรกคือช่องว่างที่ชัดที่สุดที่เราเคยวัดได้: บทความ AI ของเรา 4 ใน 5 ชิ้น มีสัดส่วนวรรคสั้นต่ำกว่าข่าวจริงทุกชิ้นในคลังอ้างอิง แปลเป็นภาษาคนคือ AI เขียนไทยเป็นวรรคยาวเนิบเสมอกันหมด ส่วนนักข่าวจริงสลับสั้นยาวตลอดเวลา สิ่งที่เราเคยเรียกกันลอยๆ ว่า “จังหวะภาษาไทย” ตอนนี้เป็นตัวเลขที่วัดซ้ำได้ ไม่ใช่ความรู้สึก
โบนัส: กติกาเว้นวรรคก็เขียนเป็น regex ได้
การเว้นวรรคภาษาไทยมีแบบแผนอยู่แล้วตามหลักภาษาและแนวทางกองบรรณาธิการ และส่วนใหญ่ตรวจอัตโนมัติได้ด้วย regex ตรงๆ:
RULES = [
("หลังไม้ยมก ๆ ต้องเว้นวรรค", r"ๆ[^\s\)\]\.,:;]"),
("หลังคอมม่าต้องเว้นวรรค", r",(?!\d{3}\b)\S"), # ยกเว้น 1,000
("ห้ามเว้นวรรคในวงเล็บ", r"\(\s|\s\)"),
("เลขกับ % เขียนติดกัน", r"\d\s%"),
]
สี่บรรทัดนี้จับข้อผิดพลาดการพิมพ์ภาษาไทยที่พบบ่อยที่สุดได้ โดยไม่ต้องเรียกโมเดลภาษาแม้แต่ครั้งเดียว
สามบทเรียนที่อยากให้จำ
หนึ่ง — หน่วยของเครื่องมือไม่ใช่หน่วยของภาษา อย่าฝืนวัด “ประโยค” ในภาษาที่ไม่ได้ทำเครื่องหมายประโยค จงวัดวรรคที่ผู้เขียนทำเครื่องหมายไว้จริง
สอง — เครื่องมือที่พังเงียบ อันตรายกว่าเครื่องมือที่พังดัง ทั้ง crfcut ที่ถอยไปตัดช่องว่างเมื่อขาด dependency และเศษ segment ที่ทำให้คะแนนดูดีขึ้นทั้งที่ข้อมูลผิด ระบบวัดที่ดีต้องสงสัยตัวเลขที่สวยเกินเหตุก่อนเสมอ
สาม — เกณฑ์ต้องมาจากการวัด ไม่ใช่การเดา ครั้งแรกเราตั้งเป้าจากตัวอย่างเพียง 5 ชิ้นแล้วได้เป้าผิดทิศไปสองตัว กว่าจะรู้ก็ตอนวัดกับข่าวจริง 29 ชิ้น และต้องอ่านผลอย่างถ่อมตัวด้วย เพราะข่าวจริงเองก็ยังตกบางเกณฑ์ การตกเกณฑ์จึงแปลว่า “แย่กว่าข่าวจริงส่วนใหญ่ในแกนนั้น” ไม่ใช่ “ใช้ไม่ได้”
ทั้งหมดนี้ไม่ใช่ทฤษฎี มันรันอยู่จริงทุกวันในระบบผลิตบทความของเว็บที่คุณกำลังอ่านอยู่ — เป็นไม้บรรทัดที่ตัดสินว่าดราฟต์ไหนผ่าน ดราฟต์ไหนต้องกลับไปแก้ ถ้าคุณกำลังสร้างระบบ NLP ภาษาไทยของตัวเอง หวังว่าแผลของเราจะช่วยให้คุณไม่ต้องเจ็บซ้ำที่เดิม
แหล่งข้อมูล
- PyThaiNLP — ไลบรารีประมวลผลภาษาไทยแบบโอเพนซอร์ส
- PyThaiNLP บน GitHub