Vinova tuyển lập trình viên Mobile & Web ở Hà Nội, lương $300-1000

Article: Search CJK 1763

ngocdaothanh.myopenid.com 172
Updated over 2 years ago

Như bài Làm từ điển dựa trên full text search engine đề cập, search các ngôn ngữ theo hệ chữ alphabet như tiếng Anh và tiếng Việt nói chung không có vấn đề vì việc phân tách các từ rất dễ dàng, chỉ cần dựa vào khoảng trắng. Với các ngôn ngữ không có khoảng trắng thì khó hơn.

Bài viết này tìm hiểu 3 giải pháp search CJK (Chinese-Japanese-Korean), nhiệm vụ thường gặp khi làm project cho khách hàng đến từ 3 nước này, cho chương trình Rails dựa trên 3 engine: Sphinx, Lucene, và Ferret thông qua 3 plugin: Thinking Sphinx, acts_as_solr, và acts_as_ferret.

Cụ thể, chúng ta viết thử chương trình Rails từ điển Anh-Nhật cực đơn giản dựa trên dữ liệu EDICT. Dữ liệu này gồm 133422 mục từ, ở dạng plain text chưa nén kích thước khoảng 9 MB, đủ lớn để đánh giá tốc độ và chất lượng search của từng giải pháp.

Khi chọn plugin đối với từng engine, ta theo hướng thực dụng và phù hợp với thực tế làm project nhất là chọn plugin dễ sử dụng nhất.

Mỗi mục từ trong tập tin dữ liệu plain text có dạng như sau:

2進数字 [2しんすうじ] /(n) (comp) binary digit/

Dữ liệu gốc là tập tin plain text theo encoding EUC-JP. Chương trình Rails cần encoding UTF-8 nên ta chuyển nó sang encoding UFT-8 bằng lệnh iconv -f euc-jp -t utf8 <tên tập tin>.

Insert dữ liệu plain text vào SQL DB

Cấu trúc bảng trong SQL DB như sau:

create_table :dict_ja_en do |t|
t.string :entry
t.string :pronunciation
t.text :description
end

Insert dữ liệu vào DB theo kiểu insert từng mục từ một là cách dễ nghĩ ra nhất nhưng lâu nhất, mất khoảng 2 phút:

source.each_line do |l|
next unless l =~ /^(.+)\s\[(.+)\]\s\/(.+)\/$/
DictJaEn.create(:entry => $1, :pronunciation => $2, :description => $3)
end

Nhanh hơn chút bằng cách dùng trực tiếp kết nối với DB server không thông qua ActiveRecord:

con = DictJaEn.connection
source.each_line do |l|
next unless l =~ /^(.+)\s\[(.+)\]\s\/(.+)\/$/
e, p, d = con.quote($1), con.quote($2), con.quote($3)
con.execute("INSERT INTO dict_ja_en(entry, pronunciation, description) VALUES(#{e}, #{p}, #{d})
end

Cách nhanh nhất, chỉ mất khoảng 10 giây, là chỉ dùng một lệnh để insert toàn bộ:

con = DictJaEn.connection
values_array = []
source.each_line do |l|
next unless l =~ /^(.+)\s\[(.+)\]\s\/(.+)\/$/
e, p, d = con.quote($1), con.quote($2), con.quote($3)
values_array << "(#{e}, #{p}, #{d})"
end
values_string = values_array.join(', ')
con.execute("INSERT INTO dict_ja_en(entry, pronunciation, description) VALUES #{values_string}")

Sphinx và Thinking Sphinx

Hiện tại, bí quyết để Sphinx search CJK là chỉ định min_word_len = 1, ngram_len = 1, còn ngram_charscharset_table như hướng dẫn ở trang wiki Unicode Character Set Tables. Tài liệu về các cấu hình trên cho thấy hỗ trợ CJK của Sphinx còn sơ khai vì chỉ tách bừa từng kí tự CJK.

Làm theo hướng dẫn sử dụng của Thinking Sphinx để hoàn thành chương trình và chạy thử, ta nhận xét:

  • Tạo index cho toàn bộ 9 MB dữ liệu chỉ mất khoảng 2 giây.
  • Kết quả search ra hơi nhiều và chứa cả kết quả sai bét. Ví dụ search "たべる" ra 20 kết quả, chứa cả những thứ như 肩を並べる [かたをならべる].

Để cải thiện, ta dùng mẹo sau:

  • Khi index, đặt độ ưu tiên theo thứ tự: entry > pronunciation > description.
  • Sắp xếp kết quả search theo thứ tự: chiều dài entry > pronunciation > entry.

Source code: sphinx.yml, dict_ja_en.rb.

Lucene và acts_as_solr

acts_as_solr dùng Solr dùng Lucene. Làm theo hướng dẫn của acts_as_solr và sửa schema.xml để dùng Lucene dùng CJKAnalyzer như sau:

    <fieldType name="text" class="solr.TextField">
<analyzer type="index" class="org.apache.lucene.analysis.cjk.CJKAnalyzer">
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.EnglishPorterFilterFactory" protected="protwords.txt"/>
<filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
</analyzer>
<analyzer type="query" class="org.apache.lucene.analysis.cjk.CJKAnalyzer">
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.EnglishPorterFilterFactory" protected="protwords.txt"/>
<filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
</analyzer>
</fieldType>

Bật script/console rồi gõ lệnh: DictJaEn.rebuild_solr_index để tạo index (mất hơn 1 tiếng!). Kết quả search rất tốt, tốt nhất trong 3 giải pháp nhưng:

  • Thời gian khởi tạo index hơi bị lâu, cả tiếng đồng hồ cho hơn 100 ngàn mục từ (trong lúc này, muốn biết Solr đang chạy đến đâu, đã index được bao nhiêu mục từ thì vào http://127.0.0.1:8982/solr). Với project ban đầu chẳng có dữ liệu gì, từ từ nhét dữ liệu vào thì đây không phải vấn đề.
  • Phải cài Java. Đa số admin rất ghét cài Java lên server của mình vì nó ngốn nhiều bộ nhớ.

Ferret và acts_as_ferret

Ferret là "hàng nhái" Lucene viết bằng C cho Ruby. Do đó cách dùng Ferret và acts_as_ferret rất giống Lucene và acts_as_solr.

Cài acts_as_ferret và MultiLingualFerretTools như hướng dẫn ở trang wiki của nó là xong. Nhận xét:

  • Tốc độ index cũng chậm như Lucene vì là hàng nhái, cùng một giuộc.
  • Kết quả search tốt hơn của Sphinx nhưng kém Lucene vì MultiLingualFerretTools chỉ đơn giản dùng biểu thức chính qui.

Kết luận

  • Tốc độ: Sphinx >> Lucene ~ Ferret
  • Chất lượng: Lucene > Ferret > Sphinx
  • Sphinx chọc thẳng vào SQL DB nên tốc độ cực cao nhưng tất cả dữ liệu để index phải nằm trong DB (có thể biến đổi dữ liệu qua vài hàm SQL đơn giản). Hiện (tháng 2 2009) Sphinx chỉ hoạt động với MySQL và PostgreSQL.
  • Lucence và Ferret tốc độ chậm hơn nhiều nhưng uyển chuyển hơn vì có thể đút cho nó bất kì nguồn dữ liệu nào. Do đó có thể dùng chúng để tạo index cho dữ liệu không nằm trong SQL DB, như trang web bất kì trên mạng.

Comments

tnd.myopenid.com 17
over 2 years ago

Hiện tại có một lựa chọn mới cho acts_as_solr là Sunspot, chi tiết có thể xem bài viết trên Linux Magazine tại đây http://www.linux-mag.com/id/7341

You must login to be able to comment

Uploaded files

No file uploaded yet

You must login to be able to upload

Nhà tài trợ:

Mọi người đều tự do viết bài, sửa bài của người khác, và bình luận ở trang web này. Bạn muốn chủ động tạo bài mới để chia sẻ kinh nghiệm với mọi người? Xin click link ở dưới.

Create new content