倒排索引
- 正排索引:文件id到單詞的關聯關係
- 倒排索引:單詞到文件id的關聯關係
示例: 對以下三個文件去除停用詞後構造倒排索引
倒排索引-查詢過程
查詢包含“搜尋引擎”的文件
- 通過倒排索引獲得“搜尋引擎”對應的文件id列表,有1,3
- 通過正排索引查詢1和3的完整內容
- 返回最終結果
倒排索引-組成
- 單詞詞典(Term Dictionary)
- 倒排列表(Posting List)
單詞詞典(Term Dictionary)
單詞詞典的實現一般用B+樹,B+樹構造的視覺化過程網址: B+ Tree Visualization
關於B樹和B+樹
倒排列表(Posting List)
- 倒排列表記錄了單詞對應的文件集合,有倒排索引項(Posting)組成
- 倒排索引項主要包含如下資訊:
- 文件id用於獲取原始資訊
- 單詞頻率(TF,Term Frequency),記錄該單詞在該文件中出現的次數,用於後續相關性算分
- 位置(Posting),記錄單詞在文件中的分詞位置(多個),用於做詞語搜尋(Phrase Query)
- 偏移(Offset),記錄單詞在文件的開始和結束位置,用於高亮顯示
B+樹內部結點存索引,葉子結點存資料,這裡的 單詞詞典就是B+樹索引,倒排列表就是資料,整合在一起後如下所示
note: B+樹索引中文和英文怎麼比較大小呢?unicode比較還是拼音呢?
ES儲存的是一個JSON格式的文件,其中包含多個欄位,每個欄位會有自己的倒排索引
分詞
分詞是將文字轉換成一系列單詞(Term or Token)的過程,也可以叫文字分析,在ES裡面稱為Analysis
分詞器
分詞器是ES中專門處理分詞的元件,英文為Analyzer,它的組成如下:
- Character Filters:針對原始文字進行處理,比如去除html標籤
- Tokenizer:將原始文字按照一定規則切分為單詞
- Token Filters:針對Tokenizer處理的單詞進行再加工,比如轉小寫、刪除或增新等處理
分詞器呼叫順序
Analyze API
ES提供了一個可以測試分詞的API介面,方便驗證分詞效果,endpoint是_analyze
- 可以直接指定analyzer進行測試
- 可以直接指定索引中的欄位進行測試
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實現
Character 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
}
]
}
複製程式碼
Tokenizers
- 將原始文字按照一定規則切分為單詞(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
}
]
}
複製程式碼
Token Filters
- 對於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,檢視文件的分詞結果
更多內容請訪問我的個人網站: laijianfeng.org
參考文件:
- elasticsearch 官方文件
- 慕課網 Elastic Stack從入門到實踐
歡迎關注我的微信公眾號