Web Character Simplification Chart 0.1 #opendream

ก่อนจะประมวลผลข้อความ เราจำเป็นต้องทำความสะอาดข้อความเสียหน่อย ทั่ว ๆ ไปที่จำเป็นต้องทำ ก็เช่น แปลง new line (\r\n หรือ \n), หรือแปลงให้อยู่ในชุดอักขระ (character set) ที่โปรแกรมใน processing pipeline จะทำงานได้ เช่นแปลง ä เป็น ae หรือแปลง “ ” เป็น ” “, หรือการ normalize ลำดับอักขระ เช่น น.หนู+สระอำ+ไม้โท → น.หนู+ไม้โท+สระอำ, หรือไปถึงขั้นซับซ้อน อย่างแก้ตัวสะกด

(กรณีเป็นงานลักษณะจดหมายเหตุ หรือ archival ก็อาจจำเป็นต้องเก็บตัว raw text ก่อนแปลงเอาไว้ด้วย เพราะการแปลงอาจจะ(และมักจะ)เป็น lossy คือแปลงไปแล้วแปลงกลับมาได้ไม่เหมือนเดิม เช่นตัวอย่างข้างบน ที่แปลง “ ” เป็น ” “)

การทำความสะอาดข้อความ หรือ text cleansing นี้ มีจุดประสงค์หลายอย่าง อย่างหนึ่งก็เพื่อเพิ่มความต้องตรงกัน (integrity) ภายในเนื้อข้อความ ซึ่งกรณีถ้าจะเอาข้อความนี้ไปประมวลผลทางสถิติ เช่นเอาไปฝึกเครื่อง (machine learning) ความต้องตรงกันภายในข้อความนี้ ก็มีผลต่อความแม่นยำของตัวแบบที่จะฝึกได้

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

สำหรับแอปพลิเคชั่นการค้นหาและการทำดัชนี การทำความสะอาดข้อความ รวมไปถึงการทำให้ข้อความมันซับซ้อนน้อยลง (ไม่ว่าจะในทางลำดับอักษร ซึ่งจะมีผลต่อขนาดของ word set หรือการลดขนาดของ character set ด้วยการยุบตัวอักษรบางตัวเข้าด้วยกัน) จะช่วยให้ดัชนีมีขนาดเล็กลงด้วย และถ้าใช้ประกอบกับตารางการแทนอักษร/คำ (replacement table) ก็จะช่วยให้โอกาสการเจอคำที่ค้นหา มีมากขึ้น เนื่องจากลดผลกระทบจากการสะกดไม่ตรงกัน (spelling variations) (จริง ๆ จะใช้ replacement table อย่างเดียวก็ได้ แต่กรณีนั้น search space ก็จะใหญ่ขึ้น มีผลต่อประสิทธิภาพ)

ในตอนที่เขียน JavaScript สำหรับใช้กับ Roti ก็คิดไว้าว่าจะเจอปัญหาลักษณะนี้ ซึ่งจะมีผลต่อการคำนวณหาบล็อกโพสต์ที่ใกล้เคียง (ดูตัวอย่างได้ที่ท้ายโพสต์นี้ ตรงที่เขียนว่า ‘roti thinks you may like these posts…’) เลยให้จาวาสคริปต์ทำความสะอาดข้อความนิดหน่อย ก่อนจะส่งไปเซิร์ฟเวอร์

เนื่องจาก Roti ทำงานกับเอกสารเว็บ ซึ่งมี ‘ปัญหา’ อีกอย่าง คือการใช้ HTML/XML entities แทนตัวอักษร เช่นใช้ & เพื่อแทนอักษร & ดังนั้นก่อนจะทำอะไรกับข้อความ จึงควร decode entities พวกนี้ออกมาก่อน

กรณีของ HTML entities ถ้าใช้ PHP สามารถใช้ฟังก์ชั่น html_entity_decode($text) ได้เลย แต่ในจาวาสคริปต์มันไม่มี (หรือผมไม่รู้ว่ามันมี) ก็เลยใช้แทนที่สตริงแบบบ้าน ๆ ไป

ตารางข้างล่างปรับแก้มาจากตัวจาวาสคริปต์ roticlient.js และใส่รายละเอียดเพิ่มเติมนิดหน่อย ยินดีรับคำแนะนำครับ

possible replacements ในตารางนั้น เป็นเพียงคำแนะนำตั้งต้น การจะเลือกใช้อะไร ขึ้นอยู่กับแอปพลิเคชั่นด้วย เช่น ตัวเลือกที่เหมาะกับการเอาไปทำดัชนี ก็อาจจะไม่เหมือนกับตัวเลือกที่เหมาะกับการเอาไปแสดงผล เช่น zero-width space ถ้าทำดัชนี การแทนด้วย space น่าจะเหมาะ แต่ถ้าจะเอาไปแสดงผล การลบมันทิ้งไปเลย น่าจะเหมาะกว่า

Web Character Simplification Chart 0.1

Throw away extra semantics for a simpler text processing. This is only a suggestion. Apply to your needs accordingly.

Character Name Character Unicode ASCII range? HTML Entities Possible Replacements
Space     yes    
Non-breaking space   U+00A0 yes   (space)
Zerowidth space   U+200B     (space), <delete>
 
Soft hyphen   U+00AD yes &shy; <delete>
 
Hyphen-Minus U+002D yes    
Hyphen U+2010     – (hyphen-minus)
Minus sign U+2212   &minus;
Figure dash U+2012    
En dash U+2013   &ndash; -, —
Em dash U+2014   &mdash; -, —
Horizontal bar U+2015    
Armenian hyphen ֊ U+058A    
Hebrew magaf ־ U+05BE    
Mongolian todo hyphen U+1806    
Japanese chōonpu U+30FC    
 
Hyphen bullet U+2043     * (star), -, <delete>
Small bullet U+2022     *, -, <delete>
 
Tilde ~ U+007E yes &tilde;  
Swung dash ˜ U+2053     ~ (tilde)
Wave dash U+301C     ~
Wavy dash U+3030     ~

ด้านล่างนี้ เป็นตัวอย่างโค้ดที่แทนที่ตัวอักษรต่าง ๆ จาก roticlient.js


return str.replace(/(&nbsp;|&#8208;|&#8210;|&ndash;|&#8211;|&mdash;|&#8212;|&#8213;|&#8275)/g, ' ')
.replace(/(&shy;|&#xAD;|&#173;|&#x200B;|&#8203)/g,'')
.replace(/[\u200b\u200d\u00ad]/g, '')
.replace(/[!\?;:"'`“”^\*\+\-_\(\)\[\]\{\}#@&~\.,\\\/\|]/g, ' ')
.replace(/\s+/g, ' ');

ติดตามการพัฒนา Roti ได้จาก Opendream’s blog

(โพสต์นี้ให้ @pittaya เนื่องจากเคยถามเอาไว้เมื่อนานเมือกแล้ว)

technorati tags:
,
,

ช่วงช่วง หลินฮุ่ย เคอิโงะ เสื้อแดง เอ็นจีวี นักศึกษา แพนด้า SEO OCR

OCRopus โอเพ่นซอร์สทูลคิตสำหรับงาน OCR รุ่น 0.4 ออกแล้วครับ น่าจะคอมไพล์อะไรต่าง ๆ ได้ง่ายขึ้นบนแพลตฟอร์มที่ไม่ใช่ GNU/Linux ครับ

ดาวน์โหลดได้ทันทีที่เว็บไซต์ OCRopus (โอเพ่นซอร์ส Apache License 2.0)

ที่หน้าเว็บ Course: OCRopus สอนการใช้งานและปรับแต่ง OCRopus มีวิธีเขียน Lua และ C++ เพื่อเรียกใช้ OCRopus ด้วย

แม้ OCRopus จะรองรับการเพิ่มเติมภาษาใหม่ ๆ เข้าไปได้ แต่ก็ต้องลงแรงหน่อย ตอนนี้ยังใช้ไทยไม่ได้โดยทันที

สำหรับคนที่มองหาตัวที่อ่านภาษาไทยได้ และไม่แพงนัก ลองหา ArnThai (อ่านไทย) มาทดสอบดู มีทั้งบนวินโดวส์และลีนุกซ์ (รุ่นบนลีนุกซ์เก่ากว่าหน่อย) ติดต่อเนคเทคได้ ถ้าสนใจนำไปใช้ ที่เคยถาม สามารถตกลงสัญญาอนุญาตได้หลายแบบ

สำหรับตัวที่มีประสิทธิภาพดีกว่านั้นและมีความสามารถเพิ่มเติมอื่น ๆ ที่อ่านไทยได้ คือ ABBYY FindReader อ่าน PDF ได้ ใช้ได้หลายภาษา (ทายอัตโนมัติ) คง document logical structure, formatting, style, ฟอนต์, และตาราง อู้ฟู่หรูหรามาก (ขอบคุณ @sarasinb และ @thai101 สำหรับข้อมูล)

รีวิวซอฟต์แวร์ OCR ต่าง ๆ บนลีนุกซ์ : โดย groundstate, โดย Peter Selinger. บทความ optical cahracter recogntion ที่วิกิพีเดียภาษาอังกฤษ มีลิงก์ไปหาซอฟต์แวร์ต่าง ๆ ด้านล่างสุด.

technorati tags: , , , ,

Gmail’s smarter auto-responses

กูเกิลแล็บส์มาอีกแล้ว คราวนี้เพิ่มพลังจีเมลด้วย “canned responses” หรือระบบตอบจดหมายอัตโนมัติ ที่เราตั้งล่วงหน้าได้ว่า ถ้ามีจดหมายที่มีคำสำคัญนี้ จากคนนั้น ให้ตอบไปว่ายังไง ประหยัดเวลาตอบอีเมลซ้ำ ๆ ซาก ๆ :p

อย่างถ้ามีคำว่า “viagra” มาในเมล เราก็ตั้งให้ตอบกลับไปได้ว่า “Thanks. I do already have good sexes.” อะไรแบบนี้เป็นต้น :p

technorati tags: , ,

swath 0.3.4 Released

โปรแกรมตัดคำ swath ออกรุ่น 0.3.4 แล้ว

Swath 0.3.4 released. Swath (Smart Word Analysis for THai) is a word segmentation for Thai. Swath offers 3 algorithms: Longest Matching, Maximal Matching and Part-of-Speech Bigram. The program supports various file input format such as html, rtf, LaTeX as well as plain text.

Changes from 0.3.1 (the most recent version in Ubuntu repo is 0.3.1)

  • More secure temporary file handling.
  • Fix regression introduced during portability fix in 0.3.2. (Bug report by Pisut Tempatarachoke)
  • Fix bug that prevent ‘-u u,u’ from working. (Bug report by Neutron Soutmun)
  • Minor code and doc improvements.
  • Fix char signedness portability issues.
  • Improved messages and documentation.

Get the latest version from ftp://linux.thai.net/pub/thailinux/software/swath/

More info at http://linux.thai.net/node/117 (posted by thep)

(สะกด swath ยังไงครับ ? swath, Swath, SWATH ?)

technorati tags: , ,

Newline in GATE

ใน GATE, ถ้าเราอยากรู้ว่า เอกสารที่เรากำลังทำงานอยู่เนี่ย มันใช้ อักขระขึ้นบรรทัดใหม่ (newline) แบบไหน ก็เรียกดูได้จากฟีเจอร์ที่ชื่อ “docNewLineType”
โดย docNewLineType นี้ เป็น String มีค่าได้ 4 อย่าง: { “CR”, “LF”, “CRLF”, “LFCR” }

CR คือ Carriage Return — ปัดแคร่(ไปซ้ายสุด) (\r ในหลายภาษาโปรแกรม),
LF คือ Line Feed — เลื่อนบรรทัดใหม่ (\n)

เพื่อขึ้นบรรทัดใหม่ เครือญาติ UNIX อย่าง Linux กับ Mac OS X ใช้ LF ตัวเดียว,
ใน Mac OS (จนถึงรุ่น 9) ใช้ CR ตัวเดียว,
ส่วนเพื่อนรัก Windows ของเรา เป็นเด็กนอย ใช้มันสองตัวเลย CRLF กันเหนียว*, DOS ก็เช่นกัน
(* พูดจริง ๆ แล้ว DOS/Windows ทำตัวเลียนแบบเครื่องพิมพ์ดีดจักรกลได้เหมือนที่สุด คือต้องปัดแคร่ไปซ้ายสุดก่อน จากนั้นจึงค่อยเลื่อนกระดาษไปหนึ่งบรรทัดได้ เป็นการเริ่มบรรทัดใหม่)

นอกเรื่องซะไกล อ่ะ เขียนโค้ดได้อย่างนี้ (ตรงที่เป็นตัวหนา)


int nllen = 1;
String nl = (String) document.getFeatures().get("docNewLineType");
if (nl.equals("CRLF") || nl.equals("LFCR")) {
  nllen = 2;
}

ผมหาเจ้าความยาวของอักขระขึ้นบรรทัดใหม่ (nllen) นี่ไป เพื่อไปชดเชยออฟเซตเวลาประมวลผลเอกสาร
คือ ผมใช้เมทธอด readLine() ในคลาส *Reader ซึ่งมันจะอ่านทีละบรรทัดให้อัตโนมัติ สบายดี
แต่เวลานับตำแหน่งเนี่ย ต้องอย่าลืมนับเจ้าตัวอักขระขึ้นบรรทัดใหม่เข้าไปด้วย ไม่งั้นตำแหน่งมันจะคลาดกัน
ก็เลยต้องรู้ว่า ใช้อักขระขึ้นบรรทัดใหม่แบบไหน จะได้ชดเชยถูก

technorati tags: , ,

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 แต่หาดาวน์โหลดได้ในอินเทอร์เน็ต (update 2011.05.13: ไฟล์ BuildDictionaryFile.java ที่ ICT Trac) (ค้นอินเทอร์เน็ตด้วยคำว่า “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: 

Open Source HTML Parsers in Java

Open Source HTML Parsers in Java, a list by Java-Source.net

NekoHTML, HTML Parser, Java HTML Parser, Jericho HTML Parser, JTidy, TagSoup, HotSax

แถม Nux เหมือนจะทำอะไรได้หลายอย่างสารพัดเกี่ยวกับ XML (เป็น wrapper ของตัวอื่น ๆ ด้วย)

Looking for Structures

keywords: semi-structured text, unstructured text, structure recognition

Parsing Parsing

Natural Language Parsing (course) @ Uni Heidelberg

The Program Transformation Wiki

ANTLR tutorial @ The University of Birmingham (+ many other Java-related tutorials)

Parsing books: by Dick Grune
Modern Compiler Design,
Parsing Techniques – A Practical Guide,
Parsing Techniques – 2nd Edition

Formalism / Tools