ย้ายบล็อกไปที่ bact.cc แล้วนะครับ

พ.ร.บ.คอมพิวเตอร์
หยุด ร่างพ.ร.บ.คอมพิวเตอร์
พื้นที่เก็บข้อมูลออนไลน์ ฟรี 2GB จาก Dropbox (sync กับ Windows, Linux, Mac, iPhone, Android ฯลฯ ได้)

2006-12-27

Using dictionary with ICU4J BreakIterator

การสร้างและเรียกใข้พจนานุกรมสำหรับตัดคำ ใน ICU4J

จดวิธีการตัดคำด้วย DictionaryBasedBreakIterator ของ ICU4J และการสร้างพจนานุกรมตัดคำเอง

(เฮ้! นี่คือ “จาวา” ขวัญอ่อน? รักสวยรักงาม? .. ระวังถูกงับมือ! เราเตือนคุณแล้วนะ :P)


การสร้างไฟล์พจนานุกรมสำหรับตัดคำ

ใช้โปรแกรม BuildDictionaryFile สร้างไฟล์พจนานุกรม, วิธีใช้คือ:

BuildDictionaryFile input [encoding] [output] [list]

  • input = ข้อมูลเข้า ไฟล์พจนานุกรม เป็นไฟล์ชนิดข้อความ หนึ่งคำต่อหนึ่งบรรทัด
  • encoding = รหัสตัวอักษรของไฟล์พจนานุกรม เช่น TIS-620, UTF-8 (ถ้าไม่ใส่จะใช้ค่าปริยาย คือ UTF-8)
  • output = ข้อมูลออก ผลลัพธ์ เป็นไฟล์ชนิดไบนารี (จะใช้เป็นอินพุตของคอนสตรัคเตอร์ของคลาส DictionaryBasedBreakIterator ต่อไป)
  • list = ข้อมูลออก รายการคำที่ถูกบรรจุในพจนานุกรม (output) เป็นไฟล์ชนิดข้อความ หนึ่งคำต่อหนึ่งบรรทัด ใช้รหัสตัวอักษร UTF-8 (ไม่ใส่ก็ได้)

เช่น ถ้าไฟล์ข้อความที่เราจะแปลงเป็นพจนานุกรมตัดคำ ชื่อ tdict.txt ก็สั่ง:

# java com.ibm.icu.dev.tools.rbbi.BuildDictionaryFile tdict.txt \
    TIS-620 tdict.dic tdict-list.txt

รอซักพัก เราก็จะได้ไฟล์ tdict.dic ไปใช้ละ

การใช้โปรแกรม BuildDictionaryFile จำเป็นต้องมีไลบรารี ICU4J อยู่ใน classpath (ใช้ออปชั่น -cp ก็ได้)

ซอร์สโค้ด BuildDictionaryFile.java นั้น ไม่ได้เผยแพร่ออกมาพร้อมกับตัว ICU4J แต่หาดาวน์โหลดได้ในอินเทอร์เน็ต (ค้นอินเทอร์เน็ตด้วยคำว่า “BuildDictionaryFile.java” แล้วพยายามหาไฟล์รุ่นที่ใหม่ที่สุด ตัวที่ผมใช้อยู่นี้ อยู่ในแพคเกจ com.ibm.icu.dev.tools.rbbi ถ้ารุ่นเก่ากว่านี้มันจะอยู่ในแพคเกจ com.ibm.tools.rbbi คิดว่าเค้ามีการปรับแพคเกจใหม่นิดหน่อย)
การคอมไพล์ ต้องมีไลบรารี ICU4J อยู่ใน classpath (ใช้คลาส com.ibm.icu.util.CompactByteArray, com.ibm.icu.text.UnicodeSet และ com.ibm.icu.impl.Utility)


การใช้ไฟล์พจนานุกรมในการตัดคำ

คลาสที่เราใช้ในการตัดคำด้วยพจนานุกรม ใน ICU4J คือ คลาส DictionaryBasedBreakIterator ในแพคเกจ com.ibm.icu.text (DictionaryBasedIterator นี้ เป็นซับคลาสของคลาส RuleBasedBreakIterator ซึ่งเป็นอินพลีเมนเตชันของแอบสแตรคคลาส BreakIterator อีกที)

คอนสตรัคเตอร์ของ DictionaryBasedBreakIterator ที่เราจะใช้ ต้องการพารามิเตอร์สองตัว คือ กฎการตัดคำ (String) และ พจนานุกรม (InputStream)

ผมไม่รู้จะหากฎมาจากไหนดี ก็เลยไปเอามาจากที่เค้ามีอยู่แล้วละกัน อย่างนี้

String rules = (RuleBasedBreakIterator.getWordInstance(new Locale ("th"))).toString();

คำสั่งนี้ จะไปเรียกเอาตัวตัดคำด้วยกฎ (สำหรับคำภาษาไทย) มาจากคลาส RuleBasedBreakIterator ซึ่งจริง ๆ ผมก็ไม่รู้แน่ ว่ามันมีอยู่หรือไม่ อย่างไรก็ตาม หากตัว RuleBasedBreakIterator ไม่มีตัวตัดคำสำหรับโลแคล (locale) ใด มันก็จะส่งคืนตัวตัดคำปริยาย (มาตรฐาน) มาให้เรา เอาเป็นว่าไม่ต้องห่วงละกัน (ยังนึกวิธีดี ๆ ไม่ออกน่ะ :P)
Update: 2006.12.28 พี่ป๊อกไขขาน สร้างความกระจ่างเรื่องที่ว่า กฎมันมาจากไหน ดูได้ที่ เสริม ICU4J ในนั้นยังแสดงวิธีการดึงกฎจากไฟล์กฎอีกด้วย (แต่จะสร้างไฟล์กฎยังไงหว่า? .. ต้องศึกษากันต่อไป)

หลังจากได้ตัวตัดคำด้วยกฎมาแล้ว เราก็ดึงกฎการตัดออกมาซะ ด้วยเมทธอด toString() (ง่าย ๆ อย่างนี้แหละ .. แต่หาอยู่นาน - -")

จากนั้น เราก็เตรียมพจนานุกรม ก็ไม่มีอะไรมาก แค่ชี้ไปหาไฟล์ที่เราสร้างมาจากโปรแกรม BuildDictionaryFile ตะกี้

InputStream dict = new FileInputStream("tdict.dic");

เท่านี้ล่ะ

จากนั้นเราก็สามารถสร้างอินสแตนซ์ของ DictionaryBasedBreakIterator ได้แล้ว

BreakIterator iter = DictionaryBasedBreakIterator(rules, dict);

การใช้งานก็เหมือนกับ BreakIterator ปกติ (ซึ่งก็คล้ายกับ iterator ทั่วไป) โดยเริ่มแรกเราต้องส่งข้อความที่จะตัดไปให้ตัว BreakIterator เสียก่อน ด้วยเมธทอด setText() จากนั้นเราก็ให้ iterator มันวนไปเรื่อย ๆ ด้วยเมทธอด next() จนกว่าจะจบข้อความ

โค้ดด้านล่างจะทำการพิมพ์คำที่ตัดได้ ตามด้วยตัวอักษร "|" ทีละคำ ๆ วนไปจนกว่าจะจบข้อความ


String text = "ข้อความที่จะตัดในนี้";

iter.setText(text); // iter คือ BreakIterator มาจากข้างบน

int start = iter.first();
for (int end = iter.next();
      end != BreakIterator.DONE;
      start = end, end = iter.next()) {
  System.out.print(text.substring(start,end));
  System.out.print("|");
}

เอ แล้วเราทำ serialization ตัวตัดคำ+พจนานุกรมที่สร้างขึ้นมาแล้วได้รึเปล่านะ ?


ข้อจำกัดของพจนานุกรมสำหรับตัดคำใน ICU4J อย่างหนึ่งที่พบ ก็คือ ขนาดของพจนานุกรมไม่สามารถใหญ่กว่า 32,767 คำได้ คาดว่า ในซอร์สโค้ดอาจจะใช้ตัวแปรชนิด short (Java short ใช้ 16 บิต) ในการอ้างอิงตำแหน่งคำ จึงสามารถอ้างอิงตำแหน่งได้สูงสุดเท่ากับตัวเลขที่ใหญ่ที่สุดที่ (signed) short เก็บได้ ซึ่งก็คือ 32,767 นั่นเอง
(หากต้องการให้พจนานุกรมใหญ่กว่านี้ อาจลองแก้ไข คลาส BuildDictionaryFile และ DictionaryBasedBreakIterator แต่ก็อาจพบผลข้างเคียงได้ ในคลาสที่เรียกใช้หรือสืบทอดคลาสเหล่านี้)

ICU4J API References

technorati tags: , , , , ,

6 comments:

polawat phetra said...

สงสัยว่า
เนื้อหาที่อยู่ใน String rules;
หน้าตาเป็นอย่างไร
เพราะที่ bact' เรียก
RuleBasedBreakIterator.getWordInstance
ลองไล่ code ดูแล้ว สุดท้ายมันจะไปใช้ข้อมูลจากเจ้าตัวนี้
http://dev.icu-project.org/cgi-bin/viewcvs.cgi/icu4j/src/com/ibm/icu/impl/data/BreakIteratorRules_th.java?view=markup
ซึ่งควรจะ return กลับมาเป็น instance ชอง DictionaryBasedBreakIterator

ไว้พรุ่งนี้ไปบริษัทฯแล้วจะลอง debug ดู

Mk said...

โชคดีมากที่ ICU มันเขียนด้วย C

polawat phetra said...

ผมเขียนเสริมส่วนที่ว่า rule มันมาจากไหน
http://pphetra.blogspot.com/2006/12/icu4j.html

bact' said...

mk: ถ้าจำไม่ผิด มี ICU4J ก่อนจะมี ICU4C

พี่ป๊อก: วาว วาว :D

Cool said...

รบกวนขอ Link BuildDictionaryFile.java ได้มั้ยครับ
ผมหาไม่เจอ

bact' said...

cool:

ผมไม่มีเก็บไว้เลยครับ เครื่องเก่าฮาร์ดดิสก์มันเดี้ยงหายไปหมดเลย
ยังไงลองกูเกิ้ลหาแฟ้มชื่อ "BuildDictionaryFile.java" ดูนะครับ
น่าจะพอมีเก็บอยู่ตามโปรเจกต์เก่า ๆ

หรือลองดาวน์โหลด ICU4J รุ่นเก่า ๆ มาใช้