ES倒排索引與分詞詳解
倒排索引
- 正排索引:文件id到單詞的關聯關係
- 倒排索引:單詞到文件id的關聯關係
示例:
對以下三個文件去除停用詞後構造倒排索引
image
倒排索引-查詢過程
查詢包含“搜尋引擎”的文件
- 通過倒排索引獲得“搜尋引擎”對應的文件id列表,有1,3
- 通過正排索引查詢1和3的完整內容
- 返回最終結果
倒排索引-組成
- 單詞詞典(Term Dictionary)
- 倒排列表(Posting List)
單詞詞典(Term Dictionary)
單詞詞典的實現一般用B+樹,B+樹構造的視覺化過程網址:
關於B樹和B+樹
image
倒排列表(Posting List)
- 倒排列表記錄了單詞對應的文件集合,有倒排索引項(Posting)組成
- 倒排索引項主要包含如下資訊:
- 文件id用於獲取原始資訊
- 單詞頻率(TF,Term Frequency),記錄該單詞在該文件中出現的次數,用於後續相關性算分
- 位置(Posting),記錄單詞在文件中的分詞位置(多個),用於做詞語搜尋(Phrase Query)
- 偏移(Offset),記錄單詞在文件的開始和結束位置,用於高亮顯示
image
B+樹內部結點存索引,葉子結點存資料,這裡的 單詞詞典就是B+樹索引,倒排列表就是資料,整合在一起後如下所示
note:
B+樹索引中文和英文怎麼比較大小呢?unicode比較還是拼音呢?
image
ES儲存的是一個JSON格式的文件,其中包含多個欄位,每個欄位會有自己的倒排索引
分詞
分詞是將文字轉換成一系列單詞(Term or Token)的過程,也可以叫文字分析,在ES裡面稱為Analysis
image
分詞器
分詞器是ES中專門處理分詞的元件,英文為Analyzer,它的組成如下:
- Character Filters:針對原始文字進行處理,比如去除html標籤
- Tokenizer:將原始文字按照一定規則切分為單詞
- Token Filters:針對Tokenizer處理的單詞進行再加工,比如轉小寫、刪除或增新等處理
分詞器呼叫順序
image
Analyze API
ES提供了一個可以測試分詞的API介面,方便驗證分詞效果,endpoint是_analyze
- 可以直接指定analyzer進行測試
image
- 可以直接指定索引中的欄位進行測試
POST test_index/doc
{
"username": "whirly",
"age":22
}
POST test_index/_analyze
{
"field": "username",
"text": ["hello world"]
}
- 可以自定義分詞器進行測試
POST _analyze
{
"tokenizer": "standard",
"filter": ["lowercase"],
"text": ["Hello World"]
}
預定義的分詞器
ES自帶的分詞器有如下:
- Standard Analyzer
- 預設分詞器
- 按詞切分,支援多語言
- 小寫處理
- Simple Analyzer
- 按照非字母切分
- 小寫處理
- Whitespace Analyzer
- 空白字元作為分隔符
- Stop Analyzer
- 相比Simple Analyzer多了去除請用詞處理
- 停用詞指語氣助詞等修飾性詞語,如the, an, 的, 這等
- Keyword Analyzer
- 不分詞,直接將輸入作為一個單詞輸出
- Pattern Analyzer
- 通過正則表示式自定義分隔符
- 預設是\W+,即非字詞的符號作為分隔符
- Language Analyzer
- 提供了30+種常見語言的分詞器
示例:停用詞分詞器
POST _analyze
{
"analyzer": "stop",
"text": ["The 2 QUICK Brown Foxes jumped over the lazy dog's bone."]
}
結果
{
"tokens": [
{
"token": "quick",
"start_offset": 6,
"end_offset": 11,
"type": "word",
"position": 1
},
{
"token": "brown",
"start_offset": 12,
"end_offset": 17,
"type": "word",
"position": 2
},
{
"token": "foxes",
"start_offset": 18,
"end_offset": 23,
"type": "word",
"position": 3
},
{
"token": "jumped",
"start_offset": 24,
"end_offset": 30,
"type": "word",
"position": 4
},
{
"token": "over",
"start_offset": 31,
"end_offset": 35,
"type": "word",
"position": 5
},
{
"token": "lazy",
"start_offset": 40,
"end_offset": 44,
"type": "word",
"position": 7
},
{
"token": "dog",
"start_offset": 45,
"end_offset": 48,
"type": "word",
"position": 8
},
{
"token": "s",
"start_offset": 49,
"end_offset": 50,
"type": "word",
"position": 9
},
{
"token": "bone",
"start_offset": 51,
"end_offset": 55,
"type": "word",
"position": 10
}
]
}
中文分詞
- 難點
- 中文分詞指的是將一個漢字序列切分為一個一個的單獨的詞。在英文中,單詞之間以空格作為自然分界詞,漢語中詞沒有一個形式上的分界符
- 上下文不同,分詞結果迥異,比如交叉歧義問題
- 常見分詞系統
安裝ik中文分詞外掛
# 在Elasticsearch安裝目錄下執行命令,然後重啟es
bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.3.0/elasticsearch-analysis-ik-6.3.0.zip
# 如果由於網路慢,安裝失敗,可以先下載好zip壓縮包,將下面命令改為實際的路徑,執行,然後重啟es
bin/elasticsearch-plugin install file:///path/to/elasticsearch-analysis-ik-6.3.0.zip
- ik測試 - ik_smart
POST _analyze
{
"analyzer": "ik_smart",
"text": ["公安部:各地校車將享最高路權"]
}
# 結果
{
"tokens": [
{
"token": "公安部",
"start_offset": 0,
"end_offset": 3,
"type": "CN_WORD",
"position": 0
},
{
"token": "各地",
"start_offset": 4,
"end_offset": 6,
"type": "CN_WORD",
"position": 1
},
{
"token": "校車",
"start_offset": 6,
"end_offset": 8,
"type": "CN_WORD",
"position": 2
},
{
"token": "將",
"start_offset": 8,
"end_offset": 9,
"type": "CN_CHAR",
"position": 3
},
{
"token": "享",
"start_offset": 9,
"end_offset": 10,
"type": "CN_CHAR",
"position": 4
},
{
"token": "最高",
"start_offset": 10,
"end_offset": 12,
"type": "CN_WORD",
"position": 5
},
{
"token": "路",
"start_offset": 12,
"end_offset": 13,
"type": "CN_CHAR",
"position": 6
},
{
"token": "權",
"start_offset": 13,
"end_offset": 14,
"type": "CN_CHAR",
"position": 7
}
]
}
- ik測試 - ik_max_word
POST _analyze
{
"analyzer": "ik_max_word",
"text": ["公安部:各地校車將享最高路權"]
}
# 結果
{
"tokens": [
{
"token": "公安部",
"start_offset": 0,
"end_offset": 3,
"type": "CN_WORD",
"position": 0
},
{
"token": "公安",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 1
},
{
"token": "部",
"start_offset": 2,
"end_offset": 3,
"type": "CN_CHAR",
"position": 2
},
{
"token": "各地",
"start_offset": 4,
"end_offset": 6,
"type": "CN_WORD",
"position": 3
},
{
"token": "校車",
"start_offset": 6,
"end_offset": 8,
"type": "CN_WORD",
"position": 4
},
{
"token": "將",
"start_offset": 8,
"end_offset": 9,
"type": "CN_CHAR",
"position": 5
},
{
"token": "享",
"start_offset": 9,
"end_offset": 10,
"type": "CN_CHAR",
"position": 6
},
{
"token": "最高",
"start_offset": 10,
"end_offset": 12,
"type": "CN_WORD",
"position": 7
},
{
"token": "路",
"start_offset": 12,
"end_offset": 13,
"type": "CN_CHAR",
"position": 8
},
{
"token": "權",
"start_offset": 13,
"end_offset": 14,
"type": "CN_CHAR",
"position": 9
}
]
}
- ik兩種分詞模式ik_max_word 和 ik_smart 什麼區別?
-
ik_max_word: 會將文字做最細粒度的拆分,比如會將“中華人民共和國國歌”拆分為“中華人民共和國,中華人民,中華,華人,人民共和國,人民,人,民,共和國,共和,和,國國,國歌”,會窮盡各種可能的組合;
-
ik_smart: 會做最粗粒度的拆分,比如會將“中華人民共和國國歌”拆分為“中華人民共和國,國歌”。
-
自定義分詞
當自帶的分詞無法滿足需求時,可以自定義分詞,通過定義Character Filters、Tokenizer和Token Filters實現
- 在Tokenizer之前對原始文字進行處理,比如增加、刪除或替換字元等
- 自帶的如下:
- HTML Strip Character Filter:去除HTML標籤和轉換HTML實體
- Mapping Character Filter:進行字元替換操作
- Pattern Replace Character Filter:進行正則匹配替換
- 會影響後續tokenizer解析的position和offset資訊
Character Filters測試
POST _analyze
{
"tokenizer": "keyword",
"char_filter": ["html_strip"],
"text": ["<p>I'm so <b>happy</b>!</p>"]
}
# 結果
{
"tokens": [
{
"token": """
I'm so happy!
""",
"start_offset": 0,
"end_offset": 32,
"type": "word",
"position": 0
}
]
}
- 將原始文字按照一定規則切分為單詞(term or token)
- 自帶的如下:
- standard 按照單詞進行分割
- letter 按照非字元類進行分割
- whitespace 按照空格進行分割
- UAX URL Email 按照standard進行分割,但不會分割郵箱和URL
- Ngram 和 Edge NGram 連詞分割
- Path Hierarchy 按照檔案路徑進行分割
Tokenizers 測試
POST _analyze
{
"tokenizer": "path_hierarchy",
"text": ["/path/to/file"]
}
# 結果
{
"tokens": [
{
"token": "/path",
"start_offset": 0,
"end_offset": 5,
"type": "word",
"position": 0
},
{
"token": "/path/to",
"start_offset": 0,
"end_offset": 8,
"type": "word",
"position": 0
},
{
"token": "/path/to/file",
"start_offset": 0,
"end_offset": 13,
"type": "word",
"position": 0
}
]
}
- 對於tokenizer輸出的單詞(term)進行增加、刪除、修改等操作
- 自帶的如下:
- lowercase 將所有term轉為小寫
- stop 刪除停用詞
- Ngram 和 Edge NGram 連詞分割
- Synonym 新增近義詞的term
Token Filters測試
POST _analyze
{
"text": [
"a Hello World!"
],
"tokenizer": "standard",
"filter": [
"stop",
"lowercase",
{
"type": "ngram",
"min_gram": 4,
"max_gram": 4
}
]
}
# 結果
{
"tokens": [
{
"token": "hell",
"start_offset": 2,
"end_offset": 7,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "ello",
"start_offset": 2,
"end_offset": 7,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "worl",
"start_offset": 8,
"end_offset": 13,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "orld",
"start_offset": 8,
"end_offset": 13,
"type": "<ALPHANUM>",
"position": 2
}
]
}
自定義分詞
自定義分詞需要在索引配置中設定 char_filter、tokenizer、filter、analyzer等
自定義分詞示例:
- 分詞器名稱:my_custom\
- 過濾器將token轉為大寫
PUT test_index_1
{
"settings": {
"analysis": {
"analyzer": {
"my_custom_analyzer": {
"type": "custom",
"tokenizer": "standard",
"char_filter": [
"html_strip"
],
"filter": [
"uppercase",
"asciifolding"
]
}
}
}
}
}
自定義分詞器測試
POST test_index_1/_analyze
{
"analyzer": "my_custom_analyzer",
"text": ["<p>I'm so <b>happy</b>!</p>"]
}
# 結果
{
"tokens": [
{
"token": "I'M",
"start_offset": 3,
"end_offset": 11,
"type": "<ALPHANUM>",
"position": 0
},
{
"token": "SO",
"start_offset": 12,
"end_offset": 14,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "HAPPY",
"start_offset": 18,
"end_offset": 27,
"type": "<ALPHANUM>",
"position": 2
}
]
}
分詞使用說明
分詞會在如下兩個時機使用:
- 建立或更新文件時(Index Time),會對相應的文件進行分詞處理
- 查詢時(Search Time),會對查詢語句進行分詞
- 查詢時通過analyzer指定分詞器
- 通過index mapping設定search_analyzer實現
- 一般不需要特別指定查詢時分詞器,直接使用索引分詞器即可,否則會出現無法匹配的情況
分詞使用建議
- 明確欄位是否需要分詞,不需要分詞的欄位就將type設定為keyword,可以節省空間和提高寫效能
- 善用_analyze API,檢視文件的分詞結果
分析與分析器
分析 包含下面的過程:
- 首先,將一塊文字分成適合於倒排索引的獨立的 詞條 ,
- 之後,將這些詞條統一化為標準格式以提高它們的“可搜尋性”,或者 recall
分析器執行上面的工作。 分析器 實際上是將三個功能封裝到了一個包裡:
字元過濾器
首先,字串按順序通過每個 字元過濾器 。他們的任務是在分詞前整理字串。一個字元過濾器可以用來去掉HTML,或者將 &
轉化成 `and`。
分詞器
其次,字串被 分詞器 分為單個的詞條。一個簡單的分詞器遇到空格和標點的時候,可能會將文字拆分成詞條。
Token 過濾器
最後,詞條按順序通過每個 token 過濾器 。這個過程可能會改變詞條(例如,小寫化 Quick
),刪除詞條(例如, 像 a`, `and`, `the
等無用詞),或者增加詞條(例如,像 jump
和 leap
這種同義詞)。
Elasticsearch提供了開箱即用的字元過濾器、分詞器和token 過濾器。 這些可以組合起來形成自定義的分析器以用於不同的目的。我們會在 自定義分析器 章節詳細討論。
內建分析器
但是, Elasticsearch還附帶了可以直接使用的預包裝的分析器。 接下來我們會列出最重要的分析器。為了證明它們的差異,我們看看每個分析器會從下面的字串得到哪些詞條:
"Set the shape to semi-transparent by calling set_trans(5)"
標準分析器
標準分析器是Elasticsearch預設使用的分析器。它是分析各種語言文字最常用的選擇。它根據 Unicode 聯盟 定義的 單詞邊界 劃分文字。刪除絕大部分標點。最後,將詞條小寫。它會產生
set, the, shape, to, semi, transparent, by, calling, set_trans, 5
簡單分析器
簡單分析器在任何不是字母的地方分隔文字,將詞條小寫。它會產生
set, the, shape, to, semi, transparent, by, calling, set, trans
空格分析器
空格分析器在空格的地方劃分文字。它會產生
Set, the, shape, to, semi-transparent, by, calling, set_trans(5)
語言分析器
特定語言分析器可用於 很多語言。它們可以考慮指定語言的特點。例如, 英語
分析器附帶了一組英語無用詞(常用單詞,例如 and
或者 the
,它們對相關性沒有多少影響),它們會被刪除。 由於理解英語語法的規則,這個分詞器可以提取英語單詞的 詞幹 。
英語
分詞器會產生下面的詞條:
set, shape, semi, transpar, call, set_tran, 5
注意看 transparent`、 `calling
和 set_trans
已經變為詞根格式。
什麼時候使用分析器
當我們 索引 一個文件,它的全文域被分析成詞條以用來建立倒排索引。 但是,當我們在全文域 搜尋 的時候,我們需要將查詢字串通過 相同的分析過程 ,以保證我們搜尋的詞條格式與索引中的詞條格式一致。
全文查詢,理解每個域是如何定義的,因此它們可以做 正確的事:
- 當你查詢一個 全文 域時, 會對查詢字串應用相同的分析器,以產生正確的搜尋詞條列表。
- 當你查詢一個 精確值 域時,不會分析查詢字串, 而是搜尋你指定的精確值。
現在你可以理解在 開始章節 的查詢為什麼返回那樣的結果:
date
域包含一個精確值:單獨的詞條 `2014-09-15`。_all
域是一個全文域,所以分詞程序將日期轉化為三個詞條: `2014`, `09`, 和 `15`。
當我們在 _all
域查詢 2014`,它匹配所有的12條推文,因為它們都含有 `2014
:
GET /_search?q=2014 # 12 results
當我們在 _all
域查詢 2014-09-15`,它首先分析查詢字串,產生匹配 `2014`, `09`, 或 `15
中 任意 詞條的查詢。這也會匹配所有12條推文,因為它們都含有 2014
:
GET /_search?q=2014-09-15 # 12 results !
當我們在 date
域查詢 `2014-09-15`,它尋找 精確 日期,只找到一個推文:
GET /_search?q=date:2014-09-15 # 1 result
當我們在 date
域查詢 `2014`,它找不到任何文件,因為沒有文件含有這個精確日誌:
GET /_search?q=date:2014 # 0 results !
測試分析器
有些時候很難理解分詞的過程和實際被儲存到索引中的詞條,特別是你剛接觸 Elasticsearch。為了理解發生了什麼,你可以使用 analyze
API 來看文字是如何被分析的。在訊息體裡,指定分析器和要分析的文字:
GET /_analyze { "analyzer": "standard", "text": "Text to analyze" }
結果中每個元素代表一個單獨的詞條:
{ "tokens": [ { "token": "text", "start_offset": 0, "end_offset": 4, "type": "<ALPHANUM>", "position": 1 }, { "token": "to", "start_offset": 5, "end_offset": 7, "type": "<ALPHANUM>", "position": 2 }, { "token": "analyze", "start_offset": 8, "end_offset": 15, "type": "<ALPHANUM>", "position": 3 } ] }
token
是實際儲存到索引中的詞條。 position
指明詞條在原始文字中出現的位置。 start_offset
和 end_offset
指明字元在原始字串中的位置。
每個分析器的 type
值都不一樣,可以忽略它們。它們在Elasticsearch中的唯一作用在於keep_types
token 過濾器。
analyze
API 是一個有用的工具,它有助於我們理解Elasticsearch索引內部發生了什麼,隨著深入,我們會進一步討論它。
指定分析器
當Elasticsearch在你的文件中檢測到一個新的字串域 ,它會自動設定其為一個全文 字串
域,使用 標準
分析器對它進行分析。
你不希望總是這樣。可能你想使用一個不同的分析器,適用於你的資料使用的語言。有時候你想要一個字串域就是一個字串域--不使用分析,直接索引你傳入的精確值,例如使用者ID或者一個內部的狀態域或標籤。
要做到這一點,我們必須手動指定這些域的對映。
更多內容請訪問網站: http://laijianfeng.org