繁體中文搜尋預處理工具 trad-zh-search 開源了

閱讀偏好
繁體中文搜尋預處理工具 trad-zh-search 開源了

繁體中文搜尋預處理工具 trad-zh-search 開源了

我把在 Meilisearch 上踩了很煩的繁體中文分詞坑,提取成一個搜尋引擎無關的 Python 套件。CKIP 分詞 + bigram 索引 + 可選用領域字典,pip install 就能用。這篇記錄為什麼要做這個工具、它解決了什麼問題、以及我在過程中學到的幾件事。


前情提要

如果你看過我之前寫的 繁體中文全文搜尋引擎實戰筆記系列 ,你就知道我在 Meilisearch 上踩了一大輪中文分詞的坑。

核心問題其實就一句話:主流搜尋引擎的中文分詞都是用 jieba,而 jieba 是用簡體中文訓練的,對繁體中文的分詞品質不好 XD (結束)

這問題已經很多年了,但一直沒有一個好的解決方案。你可以自己在前端做分詞處理、可以自己在後端加欄位、可以自己建字典 …… 但每個專案都在重新造輪子。

所以我把這幾個月的實戰經驗整理了一下,拆成一個獨立的 Python 套件:trad-zh-search,結果好像也是在造輪子 XD,但如果你在用任何搜尋引擎做繁中索引,這個經驗與套件可能對你有用


痛點到底有多痛

先看一組真實對比。同一段文字,jieba 和 CKIP + 自訂字典的分詞結果:

原文jieba 分詞CKIP + 自訂字典
聖靈充滿的經歷聖靈 / 充滿 / 的 / 經歷聖靈充滿 / 的 / 經歷
因信稱義的教義因信 / 稱義的 / 教義因信稱義 / 的 / 教義
台北靈糧堂的主日崇拜台北 / 靈糧堂 / 的 / 主日 / 崇拜台北靈糧堂 / 的 / 主日崇拜
靈糧教牧宣教神學院靈糧 / 教牧 / 宣教 / 神學院靈糧教牧宣教神學院

jieba 把「稱義的」黏在一起(語法錯誤)、「聖靈充滿」拆成兩個詞(術語不認得)、機構全名全部切碎(專有名詞沒有)。

這些不是挑出來的極端案例,而是繁體中文都會遇到的查詢斷字詞問題,jieba 家族也有人幫忙做了繁體套件,但我覺得不太適合我的用途,所以我改用 CKIP 系列,心酸旅程可以參考我之前寫的文章~


解法:三層疊加

研究與測試了一段時間後,我發現繁體中文搜尋的問題不在搜尋引擎,在預處理。做好分詞和索引,任何搜尋引擎都能受益。

最後疊加了三層:

第一層:CKIP 分詞

中研院開發的 ckip-transformers,用 Transformer 模型做繁體中文分詞,品質遠超 jieba。我選了最小的 albert-tiny 模型——只有 50MB,CPU 就能跑,而且跑了一輪 benchmark 後發現和 bert-base(大 10 倍)的分詞結果幾乎一樣

第二層:Bigram 索引

把中文字兩兩組合:「聖靈充滿」→「聖靈」「靈充」「充滿」。

這是一個很笨但很有效的方法——確保任何子字串都能搜到,不管分詞器怎麼切,CKIP 提供精確匹配,bigram 保底不漏。

第三層:領域字典

告訴分詞器哪些詞不要拆開。「台北靈糧堂」是一個完整的機構名,不要切成「台北/靈糧/堂」。

字典從哪來?用 CKIP 的 NER(命名實體辨識)從文章裡自動提取人名、機構名、地名,然後人工微調,我從 8,000+ 篇文章提取出 900+ 個專有名詞。


實戰數據

在 8,000+ 篇繁體中文文章上跑了 80 組 benchmark query:

  • CKIP + bigram:21% 的查詢 Top-1 結果獲得改善(17 好轉,3 變差)
  • albert-tiny vs bert-base:分詞結果完全一致,速度快 4 倍
  • 自訂字典(915 詞):搜「靈糧教牧宣教神學院」直接命中,不再被切碎

改善的幅度不算驚天動地,但這是在已經有 embedding + hybrid search 的基礎上再疊加的效果。光靠 embedding 搜尋已經能解決 80% 的問題,分詞 + bigram 處理的是剩下那 20%——而那 20% 往往是使用者最在意的精確查詢。


trad-zh-search:搜尋引擎無關的預處理工具

把上面三層打包成一個 Python 套件,和搜尋引擎完全解耦:

pip install trad-zh-search

三行就能用:

from trad_zh_search import tokenize

result = tokenize("轉型正義委員會的調查報告")
print(result.bigrams)   # ['轉型', '型正', '正義', '義委', ...]
print(result.tokens)    # CKIP 分詞結果
print(result.used_ckip) # True / False

不用手寫字典

最省心的功能是自動建字典 —— 丟一批文件進去,CKIP NER 自動提取專有名詞:

from trad_zh_search import build_dictionary, save_dictionary

texts = [open(f).read() for f in my_articles]
my_dict = build_dictionary(texts, min_freq=2)
save_dictionary(my_dict, "my_domain.yaml")

NER 提取完再人工過一遍就好,比從零開始寫字典輕鬆太多了。

不綁定搜尋引擎

目前有 Meilisearch adapter,一行就能把分詞結果轉成 Meilisearch 的多欄位格式。未來會加 Elasticsearch、SQLite FTS5、Orama 等 adapter。

核心邏輯和搜尋引擎完全無關——你拿 TokenResult 裡的 tokens 和 bigrams 去餵任何搜尋引擎都行。

不裝 CKIP 也能用

CKIP 底層需要 PyTorch(~700MB),不是每個環境都方便裝,trad-zh-search 設計成 CKIP 為可選依賴——沒裝的話自動退回 bigram-only 模式,降級但可用,仍然比純 jieba 好。


開源 & 字典

這個工具完全開源(MIT),GitHub 在這裡:

👉 https://github.com/notoriouslab/trad-zh-search

首發附帶了一份基督教繁中字典——從教會文章裡提取的 915 個專有名詞(人名、機構名、神學術語),加上 137 組別名映射和 14 組同義詞。這份字典本身就是用 trad-zh-search 的 build_dictionary 產生的,算是自己吃自己的狗食 XD

字典本身也歡迎貢獻——如果你有法律、醫療、教育等領域的繁中專有名詞,歡迎開 PR 加入。


幾個學到的教訓

1. NLP 最佳分詞 ≠ 搜尋最佳分詞

CKIP 的 97.6% F1 是語法正確度指標,但搜尋更重視 recall(不漏結果)。所以我們不是只用 CKIP,而是 CKIP + bigram 疊加——精確匹配靠 CKIP,召回靠 bigram。

2. 自動提取字典有盲點

NER 只認命名實體(人名/機構/地名),不認領域術語。我一開始用 NER 提取了 1,400 個詞,直到抽檢才發現——整本聖經 66 卷書的書卷名一個都沒有。「以西結書」在 CKIP 眼裡被切成「以西/結書」,但 NER 不會把它標記為實體。

教訓:自動提取後一定要跑領域覆蓋率檢查。

3. 字典清理比提取更花時間

NER 從 8,000 篇文章提取了 1,400 個詞,清理到最後剩 915 個。要移除的包括:通用英文名(David、Grace)、一般姓名(非公眾人物)、NER 黏合錯誤(兩個人名黏在一起)。自動提取給你起跑線,但最後一哩路還是要人工稍微過濾一下。

4. 預處理比搜尋引擎重要

投入在分詞和 bigram 預處理上的時間,比調整搜尋引擎參數的 ROI 高很多,做好預處理,換任何搜尋引擎都能直接受益。


技術棧一覽

你的文本 → trad-zh-search → 搜尋引擎(Meilisearch / Elasticsearch / ...)
元件是什麼需要裝嗎
CKIP (ckip-transformers)中研院繁中分詞,Transformer 模型選裝
albert-tinyCKIP 分詞模型,~50MB,CPU 可跑隨 CKIP 自動下載
BigramCJK 字符兩兩組合,保底不漏內建
領域字典YAML 專有名詞表內建基督教字典,也可自建
PyYAMLYAML 讀寫自動安裝

最小安裝只需要 PyYAML,就能用 bigram 模式。完整安裝加上 CKIP 約 750MB(主要是 PyTorch)。


相關連結

如果你也在做繁體中文搜尋,歡迎試用、回報問題、或貢獻字典,然後可以的話,也記得在 github 上給我一個星星 :P

作者 Jacobmei:帶領街口支付對接國際巨頭 Apple,推動台灣金融科技國際化實踐。

← 回文章列表