[From medium]Elasticsearch 中文同義詞與自動完成

Originally published at https://medium.com/@whccchi on May 2, 2020. 原文網址
最近幫公司實作自動完成的功能,趁機補了一下相關的基礎知識,寫在這邊跟各位分享,這篇文章提供要使用 Elasticsearch 完成中文同義詞與自動完成所需的最小知識。
專有名詞
- document
可以被 index 的最小單位。
- settings
針對某個 index 的 tokenizer、filter、shards、replica…等的設定,可以 update,要先關閉後才能做。
- mappings
定義某個index包含的fields的儲存方式,無法 update,有分靜態和動態,動態是指當有新 field 產生時 Elasticsearch 會嘗試猜測並賦予類型,只支援這幾種類型。
額外要提的是 type:在 7.0 版本之後預設移除,8.0 則不支援,官方說明原因為其設計理念與 Luence 實際儲存資料的方式有衝突:在 RDBMS 的 context 中,table 之間的 column 不會互相影響,而在 Elasticsearch 對 filed 儲存方式是在同個 index 裡面同名稱的 field 吃同樣的 mapping,跟原本想做到的概念相違。
index概念簡介
analyzer概念簡介
analyzer 由三個部分組成由處理順序左到右如圖,只有 text field 才支援 analyzer 設定。
有了 analyzer 的概念後在設定同義詞時會比較明白在設定的到底是什麼。
同義詞
這邊使用 ik 分詞器,比較老牌(出問題比較能找到解)的中文分詞器,搭配自定義的字典檔
以及同義字字庫
就能開始使用,在 analyzer 中是屬於 Tokenizer 的層級。
範例
- 字典檔(custom-dict.txt):讓進去的字不要被切斷,能夠正確被同義字字庫比對。
胃食道逆流
流行性感冒
- 同義字字庫(synonym.txt)
流感,流行性感冒
胃食道逆流=>胃病
- Dockerfile
FROM elasticsearch:7.6.2
RUN ./bin/elasticsearch-plugin install -b [https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.6.2/elasticsearch-analysis-ik-7.6.2.zip](https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.6.2/elasticsearch-analysis-ik-7.6.2.zip)
RUN mkdir -p /usr/share/elasticsearch/config/analysis-ik/custom
COPY ./dict/custom-dict.txt /usr/share/elasticsearch/config/analysis-ik/custom/custom-dict.dic
COPY ./dict/synonym.txt /usr/share/elasticsearch/config/analysis/synonym.txt
USER elasticsearch
利用上面這些範例可快速在本地建立一個具有 ik 分詞功能的 elasticsearch,接著在設定 mapping 與 setting
// PUT /<index>
{
"mappings": {
"properties": {
"field1": {
"analyzer": "ik_syno_max",
"search_analyzer": "ik_syno_smart",
"type": "text"
},
"field2": {
"analyzer": "ik_syno_max",
"search_analyzer": "ik_syno_smart",
"type": "text"
}
}
},
"settings": {
"analysis": {
"analyzer": {
"ik_syno_smart": {
"type": "custom",
"tokenizer": "ik_smart",
"filter": ["my_synonym"]
},
"ik_syno_max": {
"type": "custom",
"tokenizer": "ik_max_word",
"filter": ["my_synonym"]
}
},
"filter": {
"my_synonym": {
"type": "synonym",
"synonyms_path": "analysis/synonym.txt"
}
}
}
}
}
設定成功的話測試結果如下
// POST /<index>/_analyze
// request
{
"text": "流行性感冒",
"analyzer": "ik_syno_max"
}
// response
{
"tokens": [
{
"token": "流感",
"start_offset": 0,
"end_offset": 5,
"type": "SYNONYM", // 同義詞判斷成功
"position": 0
}
]
}
自動完成
官方推薦兩種方式
- Completion Suggester
回應速度很快,但其資料結構因素只能做到prefix completion。
- search-as-you-type (7.2~)
使用 shingle token filter 當作 base,因此可以做到 infix completion,舊的版本可以用 ngram token filter 達到類似效果。
我最後使用 search-as-you-type,因為這個效果比較符合使用情境,設定步驟如下:
建立 mapping
// PUT /<index>
{
"mappings": {
"properties" : {
"completion_field" : {
"type" : "search_as_you_type",
"analyzer": "ik_max_word"
}
}
}
}
建立 data
// PUT /<index>/<id>
// request 1
{
"completion_field": "甲狀腺腫大"
}
// request 2
{
"completion_field": "甲狀腺凸眼症"
}
...
測試結果
// GET /<index>/_search
// request
{
"_source": ["completion_field"],
"query": {
"multi_match": {
"query": "甲狀",
"type": "best_fields", // 按照有對應到的順序計算
"fields": [
"completion_field"
]
}
}
}
// response
{
...
"hits" : {
...
"hits" : [
{
"_index" : "completion_field",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.8630463,
"_source" : {
"completion_field" : "甲狀腺腫大"
}
},
{
"_index" : "completion_field",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.8630463,
"_source" : {
"completion_field" : "甲狀腺凸眼症"
}
}
]
}
}
前端隨便找一套看的順眼的 auto-completion library 套一下就好~這邊延用之前同義字的 analyzer,因為這樣自動完成出來的東西才不會被切的很奇怪。
上面大概說明怎麼快速建立一個具有同義字和自動完成功能的 Elasticsearch node,至於效果好不好得看字庫範圍夠不夠集中且多元,這塊很吃領域知識,這篇文章提供了「有」的部分,要「好」的話還有更多需要做的。