fts5-cjk

SQLite FTS5 的中日韓全文檢索 tokenizer。一個 C 檔,不用字典,沒有相依套件。

問題

FTS5 內建三個 tokenizer,對中文都不能用:

cjk 做什麼

中日韓文字連續的一段,切成前後重疊的兩字 token(bigram),跟 Lucene 的 CJKAnalyzer、Elasticsearch 的 cjk analyzer 同一個做法。其他文字原樣交給 unicode61,所以英文照樣有大小寫摺疊、去變音符號,unicode61 的選項全部可用。

.load ./cjk
CREATE VIRTUAL TABLE t USING fts5(body, tokenize='cjk');
INSERT INTO t VALUES ('日月潭紅茶產於南投縣魚池鄉');

SELECT * FROM t WHERE t MATCH '魚池';       -- 有:子字串比對
SELECT * FROM t WHERE t MATCH '南投 紅茶';   -- 有:兩個詞,AND
SELECT * FROM t WHERE t MATCH '南投魚池';    -- 沒有:一個沒空白的查詢字串是一個 phrase,
                                            -- 南投 跟 魚池 必須相鄰
SELECT highlight(t, 0, '[', ']') FROM t WHERE t MATCH '魚池鄉';
-- 日月潭紅茶產於南投縣[魚池鄉]

名稱後面的參數會傳給 unicode61:tokenize='cjk remove_diacritics 2'

編譯

make        # macOS 產生 cjk.dylib,其他平台 cjk.so
make test   # 用 sqlite3 跑 test.sql,跟 test.expected 比對

需要有 FTS5 的 SQLite 標頭檔 sqlite3ext.h。macOS 用 Homebrew 的(brew install sqlite),系統內建的 sqlite3 不能載入 extension。Debian、Ubuntu 裝 libsqlite3-dev。

結果

中文全文檢索

全國法規資料庫 47,281 條條文,三張表只差 tokenize=。數字是查詢命中的條文數。cjk 那欄和 LIKE '%勞工%' 全表掃描的結果完全相同,也就是真正含有這個詞的條文數;前兩欄是預設 tokenizer 從中找到幾條。

查詢unicode61trigramcjk
勞工110711
退休750844
公司4401809
保險4101579
臺灣00501
特別休假077
有期徒刑813161316
勞工 退休00119

unicode61 只有在標點剛好把詞隔開時才中。trigram 四個字的詞正常,兩個字的詞看不到。臺灣那列也包含寫成台灣的條文,cjk 把兩者視為同一個字。用 bench/law.sh 可重現:它會下載資料、重建各表,只要 cjk 和 LIKE 掃描的筆數不同就失敗。

日文

日文維基 40 篇文章、4,011 個段落。查詢字串是日文原文,括號是繁中對照。

查詢unicode61trigramcjk
会社(公司)10243
保険(保險)00217
労働(勞動)00161
東京160560
契約0040
新幹線367379379
労働基準法(勞動基準法)798080
東京 大学(東京 大學)00262

日文沒有空格,unicode61 把整個子句當一個 token,兩字詞全部消失。三字以上的詞 trigram 才追得上。

韓文

韓文維基 40 篇文章、2,095 個段落。

查詢unicode61trigramcjk
회사(公司)4024
보험(保險)12041
서울(首爾)640338
학교(學校)190194
결혼(結婚)13032
대통령(總統)226262
인천공항(仁川機場)41717
서울 대학10108

韓文有空格,所以 unicode61 找得到單獨出現的詞,但黏了助詞的形態(서울은、서울의、서울에서)全部漏掉,而那才是大多數。兩音節的詞 trigram 一樣看不到。

要知道的事

支援的文字

文字狀態
中文,繁體與簡體測過
日文測過;漢字假名混排,不用字典
韓文測過;韓文本來有空格,bigram 主要處理黏在名詞後面的助詞
拉丁、西里爾、希臘、阿拉伯、希伯來、越南文等照原本由 unicode61 處理
泰、寮、高棉、緬、藏文沒處理:也是無空格文字,但母音和聲調符號是獨立 code point,bigram 要跑在 grapheme cluster 上

其他選擇

做法相依
cjk(本專案)字元 bigram,其餘交給 unicode61無,279 行 C
wangfenjin/simple逐字,可選 jieba、拼音C++、jieba 字典
streetwriters/sqlite-better-trigramtrigram 加短詞 fallbackTypeScript
cwt/fts5-icu-tokenizerICU word break iteratorICU 資料
內建 trigram字元 trigram無

為什麼用 bigram 不用字典

字典式 tokenizer 的品質就是字典的品質,繁體中文的字典很薄。bigram 不會漏掉沒見過的詞。它做不到的是把「南投魚池」這種沒空白的查詢切成兩個詞。FTS5 在呼叫 tokenizer 之前就決定了 phrase 結構,所以這件事要在 FTS5 上面做,例如用一個 SQL function 改寫查詢字串。這是下一步。

常見問題

為什麼 SQLite FTS5 不能直接做中文全文檢索?

FTS5 內建的 unicode61 不知道中文詞在哪裡斷,整句變成一個 token;trigram 一個 token 要三個字,兩個字的詞查不到;porter 是英文用的。

怎麼在 SQLite FTS5 做中日韓全文檢索?

載入 fts5-cjk,建表時指定 tokenize='cjk'。連續的中日韓文字會切成前後重疊的兩字 token(bigram),其他文字交給 unicode61。

fts5-cjk 比內建 tokenizer 好在哪?

在 47,281 條台灣法規條文上查「勞工」,unicode61 找到 11 條、trigram 找到 0 條、cjk 找到 711 條,和 LIKE 全表掃描的結果相同。

fts5-cjk 需要字典或額外套件嗎?

不需要。它是一個 C 檔,不用字典,沒有相依套件,授權是 MIT。

授權

MIT。原始碼:github.com/Ray0907/fts5-cjk。