Autocomplete and misspells/spelling corrections with Sphinx
Configuration
Default configuration file /etc/sphinx/sphinx.conf. Có 3 main blocks: index, searchd, source.
Block source chứa
loại source (type of source) ở đây là mysql, thông tin kết nối username, password
tới MySQL server. Column đầu tiên của SQL query là unique ID. SQL query sẽ chạy
mỗi khi index và dump data vào Sphinx index file.
Ví dụ
source movielens
{
type = mysql
sql_host = localhost
sql_user = root
sql_pass = hArjRlutHk
sql_db = movielens
sql_port = 3306 #
optional, default is 3306
# Indexer query
# document_id MUST be the very
first field
# document_id MUST be positive
(non-zero, non-negative)
# document_id MUST fit into 32
bits
# document_id MUST be unique
sql_query = \
SELECT id, title,
UNIX_TIMESTAMP(release_date) AS release_date, \
length(title) AS
title_length FROM movies
sql_attr_uint = title_length
sql_attr_timestamp = release_date
sql_field_string = title
# Document info query
# ONLY used by search
utility to display document information
# MUST be able to fetch document info by its id, therefore
# MUST contain '$id' macro
sql_query_info = SELECT * FROM documents WHERE id=$id
}
Block index chứa
thông tin về source và đường dẫn chứa data. Thông tin charset_type nên là utf-8.
index movielens
{
source = movielens
# mkdir -p
/var/lib/sphinx/data or mkdir -p /var/data/sphinx
# chown -R sphinx:sphinx
/var/lib/sphinx/data
path = /var/lib/sphinx/data/movielens
docinfo = extern
charset_type = utf-8
}
Block searchd chứa
thông tin về port và 1 số biến khác để chạy Sphinx daemon.
searchd
{
listen = 127.0.0.1:9312
listen = 9306:mysql41
log = /var/log/sphinx/searchd.log
query_log = /var/log/sphinx/query.log
read_timeout = 5
max_children = 30
pid_file = /var/run/sphinx/searchd.pid
max_matches = 1000
seamless_rotate = 1
preopen_indexes = 1
unlink_old = 1
workers = threads # for RT to work
binlog_path = /var/lib/sphinx
}
Sau khi chỉnh sửa configuration thực hiện index bằng
command. Nếu không connect được MySQL do lỗi không tìm thấy sock ví dụ
/var/lib/mysql/mysql.sock, kiểm tra sock hiện tại của MySQL và sock báo lỗi có
tồn tại hay không?
# Can't connect to local MySQL server through socket
'/var/lib/mysql/mysql.sock'
# mkdir /var/lib/mysql
# ln -s /tmp/mysql.sock /var/lib/mysql/mysql.sock
# Run indexer for the first time not using --rotate
indexer --config /etc/sphinx/sphinx.conf --all
# Sphinx 2.0.8-id64-release (r3831)
# Copyright (c) 2001-2012, Andrew Aksyonoff
# Copyright (c) 2008-2012, Sphinx Technologies Inc
(http://sphinxsearch.com)
#
# using config file '/etc/sphinx/sphinx.conf'...
Chạy indexer lần đầu tiên không có option '--rotate' để
tránh báo lỗi
WARNING:
index 'movielens': preload: failed to open movielens.sph: Permission denied;
NOT SERVING
Để thực hiện schedule việc index thực hiện tạo cronjob.
crontab -e
# Content
@hourly /usr/bin/indexer --rotate --config /etc/sphinx/sphinx.conf
--all
Start daemon
/etc/init.d/searchd start
# Starting searchd: Sphinx 2.0.8-id64-release (r3831)
# Copyright (c) 2001-2012, Andrew Aksyonoff
# Copyright (c) 2008-2012, Sphinx Technologies Inc
(http://sphinxsearch.com)
#
# using config file '/etc/sphinx/sphinx.conf'...
# listening on 127.0.0.1:9312
# listening on all interfaces, port=9306
# precaching index 'movielens'
# precaching index 'testrt'
# precached 2 indexes in 0.003 sec
Thực hiện test configuration
mysql -h0 -P 9306
# Welcome to the MySQL monitor.
Commands end with ; or \g.
# Your MySQL connection id is 1
# Server version: 2.0.8-id64-release (r3831)
#
# Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights
reserved.
#
# Oracle is a registered trademark of Oracle Corporation and/or its
# affiliates. Other names may be trademarks of their respective
# owners.
#
# Type 'help;' or '\h' for help. Type '\c' to clear the current input
statement.
mysql> SELECT * FROM movielens WHERE
-> MATCH('story') LIMIT 0,3; SHOW META;
+------+--------+--------------------------------+--------------+--------------+
| id | weight | title | release_date |
title_length |
+------+--------+--------------------------------+--------------+--------------+
| 1 | 1663 | Toy Story (1995) | 788893200 | 16 |
| 308 | 1663 | FairyTale: A True Story (1997) | 852051600 | 30 |
| 478 | 1663 | Philadelphia Story, The (1940) | 0 | 30 |
+------+--------+--------------------------------+--------------+--------------+
3 rows in set (0.00 sec)
Autocomplete
Note:
§
Nếu không có document_id dùng crc32(title) thay
cho document_id
§
SELECT query có title_length
§
Index để min_prefix_len = 1 để index khi có
space
§
Allocate đủ memory và thực hiện mlock = 1 tránh
attack, access DB
§
Limit số lượng query, không cần dùng ranker
trong query chỉ cần order theo title_length à option max_matches=10,
ranker=none
§
Thực hiện match start of string anchor "^input*"
Misspells/Spelling corrections (suggestion)
Tham khảo bài viết http://www.ivinco.com/blog/sphinx-in-action-did-you-mean
Blog (tiếng Nga) http://habrahabr.ru/post/61807
mô tả kỹ thuật thực hiện trong một ví dụ trong thư mục misc/suggest/ của Sphinx source.
Ý tưởng cơ bản như sau so sánh sự khác nhau giữa các từ
trong search query và các từ trong 1 từ điển. Các bước thực hiện như sau:
§
Xác định từ điển, có thể là từ điển thật hay từ
titles, keywords của sản phẩm. Có thể phát sinh từ điển từ index có sẵn bằng
cách sử dụng command line 'indexer
–buildstops … –buildfreqs'
§
Có thể điều chỉnh mỗi từ trong từ điển bằng các
tách thành các ký tự hoặc nhóm ký tự: bigram hay trigram (tokenize n-gram). Ví
dụ tách string 'mysql' thành 'm y s q l _m my ys sq ql l_ __m mys ysq
sql ql_ l__'. Trong ví dụ này underscore gạch dưới được dùng để phân tách
chỉ ra giữa chữ đầu hay cuối một word với những chữ nằm giữa từ.
§
Thực hiện index dictionary và words đã modified.
Có thể thêm các index attributes như word length hay bất cứ gì giúp sort kết quả.
Ví dụ như tần số xuất hiện từ word frequency dùng '--buildfreqs' indexer option hay ratings của sản phẩm.
§
Thực hiện việc tách string tương tự đối với
search query
§
Cuối cùng thực hiện search bằng cú pháp ".."/1
(như quorum matching operator)
Tạo dictionary từ index
indexer
movielens --buildstops /home/gponster/dict.txt 100000 --buildfreqs
Sử dụng script trong Sphinx source /misc/suggest để tokenize
trigram và tạo script SQL
CREATE TABLE suggest (
id INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL,
keyword VARCHAR (255) NOT NULL,
unigrams VARCHAR (255) NOT
NULL,
bigrams VARCHAR (255) NOT
NULL,
trigrams VARCHAR (255) NOT
NULL,
freq INTEGER NOT NULL,
UNIQUE(keyword)
);
SQL script
INSERT INTO suggest VALUES
...
(0, 'deal', '__d _de dea eal
al_ l__', 32431)
(0, 'created', '__c _cr cre rea
eat ate ted ed_ d__', 32429)
(0, 'light', '__l _li lig igh
ght ht_ t__', 32275)
(0, 'needed', '__n _ne nee eed
ede ded ed_ d__', 32252)
(0, 'mood', '__m _mo moo ood
od_ d__', 32185)
(0, 'death', '__d _de dea eat
ath th_ h__', 32140)
(0, 'behind', '__b _be beh ehi
hin ind nd_ d__', 32136)
(0, 'usually', '__u _us usu sua
ual all lly ly_ y__', 32113)
(0, 'action', '__a _ac act cti
tio ion on_ n__', 32053)
(0, 'line', '__l _li lin ine
ne_ e__', 32052)
(0, 'pissed', '__p _pi pis iss
sse sed ed_ d__', 32043)
(0, 'bye', '__b _by bye ye_
e__', 32012)
...
Config index suggest
source suggest
{
type = mysql
sql_host = localhost
sql_user = root
sql_pass =
sql_db = movielens
sql_port = 3306 #
optional, default is 3306
sql_query_pre = SET NAMES utf8
sql_query = SELECT id, trigrams, freq,
LENGTH(keyword) AS len, keyword FROM suggest
sql_attr_uint = freq
sql_attr_uint = len
sql_attr_string = keyword
}
index suggest
{
source =
suggest
path = /var/lib/sphinx/data/suggest
docinfo = extern
charset_type = utf-8
}
Để tăng chất lượng sugguest có thể dùng ranker=wordcount
(SPH_RANK_WORDCOUNT trong API) hay ranker=proximity (SPH_RANK_PROXIMITY) để
tính toán weight dựa trên xấp xỉ hay số từ khớp nhau (proximity or the number
of matching words) không phải dựa trên thống kê bằng thuật toán BM25.
Ví dụ câu query cho misspell stery
"stery"
=> {"__s", "_st", "ste", "ter",
"ery", "ry_", "y__"}
Thực hiện Sphinx search
Chiều dài correct string là 5 ± 2, thứ tự sắp xếp được tính bằng @weight + 2 - abs(5 - len)
mysql -h0 -P 9306
mysql> SELECT keyword, len, freq, @weight + 2 - abs(5 - len) AS
final
FROM suggest WHERE match('@trigrams "__s _st ste ter ery ry_ y__
"/1') AND len >= 3 AND len <= 7 ORDER BY final DESC, freq DESC LIMIT
10 option ranker=wordcount;
+------+--------+------+------+---------+-------+
| id | weight | freq |
len | keyword | final |
+------+--------+------+------+---------+-------+
| 69 | 4 |
8 | 5 | story |
6 |
| 288 | 5 |
3 | 7 | mystery | 5 |
| 567 | 3 |
2 | 5 | steal |
5 |
| 2155 | 3 | 1 |
5 | every | 5 |
| 1829 | 3 | 1 |
5 | steps | 5 |
| 957 | 3 |
1 | 5 | steel |
5 |
| 1593 | 3 | 1 |
5 | stein | 5 |
| 686 | 2 |
2 | 5 | stone |
4 |
| 626 | 2 |
2 | 5 | stand |
4 |
| 380 | 2 |
2 | 5 | glory |
4 |
+------+--------+------+------+---------+-------+
10 rows in set (0.00 sec)
Có thể thấy hai kết quả story và mystery là khả quan nhất với
weight là 4 và 5 sau khi điều chỉnh theo len là 6 và 5.
Có thể sort theo weight của full-text search hoặc sắp xếp
theo độ khác nhau giữa misspell và suggestion (ít khác biệt hơn thì tốt hơn).
Nên giới hạn chiều dài (nhỏ nhất và lớn nhất) để cho ra kết
quá không quá dài và không quá ngắn. Thông thường chiều dài của query search bị
sai chênh lệch với chiều dài của string đúng chỉ khoảng từ 2-3 ký tự.
Để tăng thêm độ chính xác nên thực hiện thêm các bước tính
toán tại ứng dụng (in application) sau khi có kết quả từ Sphinx search. Cách
đơn giản nhất là tính toán khoảng cách Levenshtein của 10 từ đầu tiên mà Sphinx
suggest.
Note:
N-gram là kỹ thuật
tokenize một string thành các substring, thông qua việc chia đều string đã có
thành các substring đều nhau, có độ dài là N. Về cơ bản thì N thường nằm từ
1-3, với các tên gọi tương ứng là unigram (n=1), bigram (n=2), trigram(n=3). Ví
dụ đơn giản là chúng ta có string 'good morning', được phân tích thành bigram
như sau:
"good
morning" => {"go", "oo", "od", "d
", " m", "mo", "or", "rn",
"ni", "in", "ng"}
Một kỹ thuật tokenize khác là morphological analysis (không
sử dụng ở đây phân tích string dựa trên cấu trúc từ)
Khoảng cách
Levenshtein (source wiki)
Khoảng cách Levenshtein thể hiện khoảng cách khác biệt giữa
2 chuỗi kí tự. Khoảng cách Levenshtein giữa chuỗi S và chuỗi T là số bước ít nhất
biến chuỗi S thành chuỗi T thông qua 3 phép biến đổi là
§
Xoá 1 kí tự
§
Thêm 1 kí tự
§
Thay kí tự này bằng kí tự khác
Ví dụ: Khoảng cách Levenshtein giữa 2 chuỗi
"kitten" và "sitting" là 3, vì phải dùng ít nhất 3 lần biến
đổi.
§
kitten -> sitten (thay "k" bằng
"s")
§
sitten -> sittin (thay "e" bằng
"i")
§
sittin -> sitting (thêm kí tự "g")
Misspells/Spelling corrections (suggestion)
Về cơ bản Sphinx
không có sẵn built-in suggester (cả term suggester lẫn phrase suggester). Đối với
misspells, việc suggest được thực hiện thông qua search với kỹ thuật n-gram bởi query không có index (query
viết sai). Kết quả cho thấy trigram
(n = 3) cho kết quả kiểm tra từ tốt nhất (unigram
không có nhiều ý nghĩa, bigram (hay
bigram kết hợp trigram) sẽ ra nhiều kết quả không mong muốn khi misspell nhất
là khi sai chính tả của tiếng Việt không dấu sẽ ra nhiều kết quả của tiếng
Anh).
Tham khảo bài viết http://www.ivinco.com/blog/sphinx-in-action-did-you-mean
Blog (tiếng Nga) http://habrahabr.ru/post/61807
mô tả kỹ thuật thực hiện của ví dụ trong thư mục misc/suggest/ của Sphinx source.
Ý tưởng cơ bản như sau so sánh sự khác nhau giữa các từ
trong search query và các từ trong 1 từ điển. Các bước thực hiện như sau:
§
Xác định từ điển, có thể là từ điển thật hay từ
titles, keywords của sản phẩm. Có thể phát sinh từ điển từ index có sẵn bằng
cách sử dụng command line 'indexer –buildstops
… –buildfreqs'
§
Có thể điều chỉnh mỗi từ trong từ điển bằng các
tách thành các ký tự hoặc nhóm ký tự: unigram, bigram hay trigram (tokenize
n-gram). Ví dụ tách string (unigram + bigram + trigram) 'mysql' thành 'm y s q l _m
my ys sq ql l_ __m mys ysq sql ql_ l__'. Trong ví dụ này underscore gạch dưới
được dùng để phân tách chỉ ra giữa chữ đầu hay cuối một word với những chữ nằm
giữa từ.
§
Thực hiện index dictionary và words đã modified.
Có thể thêm các index attributes như word length hay bất cứ gì giúp sort kết quả.
Ví dụ như tần số xuất hiện từ word frequency dùng '--buildfreqs' indexer option hay ratings của sản phẩm.
§
Thực hiện việc tách string tương tự đối với
search query
§
Cuối cùng
thực hiện search bằng cú pháp ".."/1 (như quorum matching operator)
Tạo dictionary từ index
indexer
movies --buildstops ~/dict.txt 100000 --buildfreqs
Sử dụng script trong Sphinx source /misc/suggest để tokenize trigram và tạo script SQL
CREATE TABLE suggest (
id INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL,
keyword VARCHAR (255) NOT NULL,
trigrams VARCHAR (255) NOT
NULL,
freq INTEGER NOT NULL,
UNIQUE(keyword)
);
SQL script
INSERT INTO suggest VALUES
...
(0, 'deal', '__d _de dea eal
al_ l__', 32431)
(0, 'created', '__c _cr cre rea
eat ate ted ed_ d__', 32429)
(0, 'light', '__l _li lig igh
ght ht_ t__', 32275)
(0, 'needed', '__n _ne nee eed
ede ded ed_ d__', 32252)
(0, 'mood', '__m _mo moo ood
od_ d__', 32185)
(0, 'death', '__d _de dea eat
ath th_ h__', 32140)
(0, 'behind', '__b _be beh ehi
hin ind nd_ d__', 32136)
(0, 'usually', '__u _us usu sua
ual all lly ly_ y__', 32113)
(0, 'action', '__a _ac act cti
tio ion on_ n__', 32053)
(0, 'line', '__l _li lin ine
ne_ e__', 32052)
(0, 'pissed', '__p _pi pis iss
sse sed ed_ d__', 32043)
(0, 'bye', '__b _by bye ye_
e__', 32012)
...
Config index suggest
source suggest
{
type = mysql
sql_host = localhost
sql_user = root
sql_pass =
sql_db = movies
sql_port = 3306 #
optional, default is 3306
sql_query_pre = SET NAMES utf8
sql_query = SELECT id, trigrams, freq,
LENGTH(keyword) AS len, keyword FROM suggest
sql_attr_uint = freq
sql_attr_uint = len
sql_attr_string = keyword
}
index suggest
{
source = suggest
path = /var/lib/sphinx/data/suggest
docinfo = extern
charset_type = utf-8
}
Để tăng chất lượng sugguest có thể dùng ranker=wordcount (SPH_RANK_WORDCOUNT trong API) hay ranker=proximity
(SPH_RANK_PROXIMITY) để tính toán
weight dựa trên xấp xỉ hay số từ khớp nhau (proximity or the number of matching
words) không phải dựa trên thống kê bằng thuật toán BM25.
Ví dụ câu query cho misspell stery (expected story)
"stery"
=> {"__s", "_st", "ste", "ter",
"ery", "ry_", "y__"}
Thực hiện Sphinx search với chiều dài correct string là 5 ± 2, thứ tự sắp xếp được tính bằng @weight + 2 - abs(5 - len)
mysql -h0 -P 9306
mysql> SELECT keyword, len, freq, @weight + 2 - abs(5 - len) AS
final
FROM suggest WHERE match('@trigrams "__s _st ste ter ery ry_
y__"/1') AND len >= 3 AND len <= 7 ORDER BY final DESC, freq DESC
LIMIT 10 option ranker=wordcount;
+------+--------+------+------+---------+-------+
| id | weight | freq |
len | keyword | final |
+------+--------+------+------+---------+-------+
| 69 | 4 |
8 | 5 | story |
6 |
| 288 | 5 |
3 | 7 | mystery | 5 |
| 567 | 3 |
2 | 5 | steal |
5 |
| 2155 | 3 | 1 |
5 | every | 5 |
| 1829 | 3 | 1 |
5 | steps | 5 |
| 957 | 3 |
1 | 5 | steel |
5 |
| 1593 | 3 | 1 |
5 | stein | 5 |
| 686 | 2 |
2 | 5 | stone |
4 |
| 626 | 2 |
2 | 5 | stand |
4 |
| 380 | 2 |
2 | 5 | glory |
4 |
+------+--------+------+------+---------+-------+
10 rows in set (0.00 sec)
Có thể thấy hai kết quả story và mystery là khả quan nhất với
weight là 4 và 5 sau khi điều chỉnh theo len là 6 và 5.
Có thể sort theo weight của full-text search hoặc sắp xếp
theo độ khác nhau giữa misspell và suggestion (ít khác biệt hơn thì tốt hơn).
Nên giới hạn chiều dài (nhỏ nhất và lớn nhất) để cho ra kết
quá không quá dài và không quá ngắn. Thông thường chiều dài của query search bị
sai chênh lệch với chiều dài của string đúng chỉ khoảng từ 2-3 ký tự.
Để tăng thêm độ chính xác nên thực hiện thêm các bước tính
toán tại ứng dụng (in application)
sau khi có kết quả từ Sphinx search. Cách đơn giản nhất là tính toán khoảng
cách Levenshtein (hỗ trợ sẵn trong
PHP levenshtein)
của 10 từ đầu tiên mà Sphinx suggest.
N-gram là kỹ thuật
tokenize (split) một string thành các substring, thông qua việc chia đều string
đã có thành các substring đều nhau, có độ dài là N. Về cơ bản thì N thường nằm
từ 1-3, với các tên gọi tương ứng là unigram (n = 1), bigram (n = 2), trigram(n
= 3). Ví dụ đơn string 'good morning', được phân tích thành bigram như sau:
"good
morning" => {"go", "oo", "od", "d
", " m", "mo", "or", "rn",
"ni", "in", "ng"}
Một kỹ thuật tokenize khác là morphological analysis (không
sử dụng ở đây phân tích string dựa trên cấu trúc từ)
Khoảng cách
Levenshtein (source wiki)
Khoảng cách Levenshtein thể hiện khoảng cách khác biệt giữa
2 chuỗi kí tự. Khoảng cách Levenshtein giữa chuỗi S và chuỗi T là số bước ít nhất
biến chuỗi S thành chuỗi T thông qua 3 phép biến đổi là
§
Xoá 1 kí tự
§
Thêm 1 kí tự
§
Thay kí tự này bằng kí tự khác
Ví dụ: Khoảng cách Levenshtein giữa 2 chuỗi
"kitten" và "sitting" là 3, vì phải dùng ít nhất 3 lần biến
đổi.
§
kitten -> sitten (thay "k" bằng
"s")
§
sitten -> sittin (thay "e" bằng
"i")
§
sittin -> sitting (thêm kí tự "g")
Cài đặt
Có thể dùng phương
pháp tương tự như trên đối với phrase suggestion.
§
Không thể dùng buildstop của Sphinx đơn thuần
như trong ví dụ vì buildstop dùng whitespace tokenizer à cần phrase suggester
§
Function trigram của ví dụ trên chưa thực hiện
ASCII folding (bỏ dấu tiếng Việt) à search không dấu không ra
§
Dùng tokenizer tiếng Việt để tăng độ chính xác
Chuẩn bị danh sách
tên diễn viên, đạo diễn:
àlex pastor
ác lôi
ái châu
álex gonzález
álvaro cervantes
álvaro guevara
ángela molina
…
Tương tự đối với danh sách title:
dont go breaking my heart 2
incisive great teacher
500 days of summer
009 no 1 the end of the beginning
1 nenokkadine
10 rules for sleeping around
10 things i hate about you
…
gia đình tinh võ
những cô gái chân dài
nữ tướng cướp
cô nàng đáng yêu
phố wall
một thời để nhớ
quán quân siêu đẳng
…
Đối với title tiếng Việt có thể tăng độ chính xác bằng cách
dùng tool vnTokenizer
để tokenize thêm cho tiếng Việt.
vnTokenizer.sh -i ./title-vn-lowercase.txt -o title-vn-output.txt
sed 's/ /\r\n/g' <./title-vn-output.txt | sed 's/_/ /g' | sed
'/^.\{,7\}$/d' | sed '/^[0-9\/.+-]\+$/d' >result.txt
Kết quả sau khi sử dụng vnTokenizer.
à
á
á phiện
ác
ác chiến
ác mộng
ác nghiệt
…
Lọc bỏ những line là stopwords và các line có len < 7. Thực
hiện insert vào DB, trong trường hợp này coi như freq = 1 với các phrase.
gia đình
nữ tướng
quán quân
siêu đẳng
hoàng hôn
oan hồn
làm nên
tĩnh lặng
cửa hàng
áo cưới
công chúa
arabela
nụ cười
…
Sử dụng script ngrams.py để tạo SQL script
python
ngrams.py -c trigrams -i result.txt -sql
Kết quả
DROP TABLE IF EXISTS suggest;
CREATE TABLE suggest (
id INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL,
keyword VARCHAR(255) NOT NULL,
trigrams VARCHAR(255) NOT NULL,
freq INTEGER NOT NULL,
UNIQUE(keyword)
);
INSERT INTO suggest VALUES
(0, 'gia đình', '__g _gi gia ia_ a__ __d _di din inh nh_ h__ ', 1),
(0, 'nữ tướng', '__n _nu nu_ u__ __t _tu tuo uon ong ng_ g__ ', 1),
(0, 'quán quân', '__q _qu qua uan an_ n__ __q _qu qua uan an_ n__ ',
1),
(0, 'siêu đẳng', '__s _si sie ieu eu_ u__ __d _da dan ang ng_ g__ ',
1),
(0, 'hoàng hôn', '__h _ho hoa oan ang ng_ g__ __h _ho hon on_ n__ ',
1),
(0, 'oan hồn', '__o _oa oan an_ n__ __h _ho hon on_ n__ ', 1),
(0, 'làm nên', '__l _la lam am_ m__ __n _ne nen en_ n__ ', 1),
(0, 'tĩnh lặng', '__t _ti tin inh nh_ h__ __l _la lan ang ng_ g__ ',
1),
(0, 'cửa hàng', '__c _cu cua ua_ a__ __h _ha han ang ng_ g__ ', 1),
(0, 'áo cưới', '__a _ao ao_ o__ __c _cu cuo uoi oi_ i__ ', 1),
(0, 'công chúa', '__c _co con ong ng_ g__ __c _ch chu hua ua_ a__ ',
1),
…
#!/usr/bin/python # -*- coding: utf-8 -*- # Notepad++ Settings->Preferences->Tab Settings->"Replace by space" # import os import sys import struct import getopt import unidecode import re import codecs import nltk def confirm(prompt, resp=False): """prompts for yes or no response from the user. Returns True for yes and False for no. 'resp' should be set to the default value assumed by the caller when user simply types ENTER. >>> confirm(prompt='Create Directory?', resp=True) Create Directory? [y]|n: True >>> confirm(prompt='Create Directory?', resp=False) Create Directory? [n]|y: False >>> confirm(prompt='Create Directory?', resp=False) Create Directory? [n]|y: y True """ if prompt is None: raise Exception('Not valid prompt') if resp: prompt = '%s %s/%s: ' % (prompt, 'Y', 'n') else: prompt = '%s %s/%s: ' % (prompt, 'N', 'y') while True: ans = raw_input(prompt) print '' if not ans: return resp if ans not in ['y', 'Y', 'n', 'N']: print 'please enter y or n.' continue if ans == 'y' or ans == 'Y': return True if ans == 'n' or ans == 'N': return False def debug(msg, level='info'): print '%s : %s' %(level, msg) def check(test, msg): test or exit('Error: \n'+msg) def ascii_folding(s): normal = unidecode.unidecode(s) return normal def ngram(input, minGram = 2, maxGram = 3): tokens = re.split('\s+', input) result = '' if minGram == 2: for token in tokens: if len(token) == 0: continue # bigram text = '_' + ascii_folding(token) + '_' for i in range(len(text) - 1): result += text[i:i + 2] + ' ' if maxGram == 3: for token in tokens: if len(token) == 0: continue # trigram text = '__' + ascii_folding(token) + '__' for i in range(len(text) - 2): result += text[i:i + 3] + ' ' return result def process(infile, outfile, command='trigrams', sql = False): debug('Reading ' + infile) m = 0 lines = codecs.open(infile, 'r', encoding='utf-8').readlines() fo = codecs.open(outfile, 'w', 'utf-8') if sql: fo.write('DROP TABLE IF EXISTS suggest;' + '\n' + 'CREATE TABLE suggest (' + '\n' + '\tid\tINTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL,' + '\n' + '\tkeyword\tVARCHAR(255) NOT NULL,' + '\n' + '\t' + command + '\tVARCHAR(255) NOT NULL,' + '\n' + '\tfreq\tINTEGER NOT NULL,' + '\n' + '\tUNIQUE(keyword)' + '\n' + ');' + '\n') for line in lines: line = line.splitlines()[0] out = '' if command == 'bigrams': out = ngram(line, 2, 2) elif command == 'trigrams': out = ngram(line, 3, 3) elif command == 'bi_tri_grams': out = ngram(line, 2, 3) if len(out) > 0: if sql: if m == 0: fo.write('INSERT INTO suggest VALUES\n') else: fo.write(',\n') fo.write("(0, '" + line + "', '" + out + "', 1)") m = m + 1 if m % 1000 == 0: fo.write(';\n') m = 0 else: fo.write(out + '\n') fo.close() if __name__ == "__main__": infile = '' outfile = '' command = 'trigram' sql = False try: opts, args = getopt.getopt(sys.argv[1:], "hi:o:c:sql", ["help", "infile=", "outfile=", "command="]) except getopt.GetoptError: print 'ngram.py -c <command> -i <infile> -o <outfile>' sys.exit(2) for opt, arg in opts: if opt == '-h': print 'ngram.py -c <command> -i <infile> -o <outfile>' sys.exit() elif opt in ("-i", "--infile"): infile = arg elif opt in ("-o", "--outfile"): outfile = arg elif opt in ("-c", "--command"): command = arg elif opt in ("-sql"): sql = True check(command == 'trigrams' or command == 'bigrams' or command == 'bi_tri_grams', 'Comand must be bigrams|trigrams|bi_tri_grams') check(os.path.exists(infile), 'Input file not exists') if len(outfile) == 0: fn, fext = os.path.splitext(infile) outfile = fn + '-o' + fext if os.path.exists(outfile) and not confirm('Output file exists, overwrite?'): sys.exit(0) debug('Output file: ' + outfile) process(infile, outfile, command, sql) sys.exit(0)
No comments:
Post a Comment