Elasticsearch 6.x 倒排索引與分詞

小旋鋒發表於2018-08-19

倒排索引

  • 正排索引:文件id到單詞的關聯關係
  • 倒排索引:單詞到文件id的關聯關係

示例: 對以下三個文件去除停用詞後構造倒排索引

image

倒排索引-查詢過程

查詢包含“搜尋引擎”的文件

  1. 通過倒排索引獲得“搜尋引擎”對應的文件id列表,有1,3
  2. 通過正排索引查詢1和3的完整內容
  3. 返回最終結果

倒排索引-組成

  • 單詞詞典(Term Dictionary)
  • 倒排列表(Posting List)

單詞詞典(Term Dictionary)

單詞詞典的實現一般用B+樹,B+樹構造的視覺化過程網址: B+ Tree Visualization

關於B樹和B+樹

  1. 維基百科-B樹
  2. 維基百科-B+樹
  3. B樹和B+樹的插入、刪除圖文詳解

image

倒排列表(Posting List)

  • 倒排列表記錄了單詞對應的文件集合,有倒排索引項(Posting)組成
  • 倒排索引項主要包含如下資訊:
    1. 文件id用於獲取原始資訊
    2. 單詞頻率(TF,Term Frequency),記錄該單詞在該文件中出現的次數,用於後續相關性算分
    3. 位置(Posting),記錄單詞在文件中的分詞位置(多個),用於做詞語搜尋(Phrase Query)
    4. 偏移(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:實現中英文單詞的切分,可自定義詞庫,支援熱更新分詞詞典
    • jieba:支援分詞和詞性標註,支援繁體分詞,自定義詞典,並行分詞等
    • Hanlp:由一系列模型與演算法組成的Java工具包,目標是普及自然語言處理在生產環境中的應用
    • THUAC:中文分詞和詞性標註

安裝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&apos;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&apos;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
參考文件:

  1. elasticsearch 官方文件
  2. 慕課網 Elastic Stack從入門到實踐

歡迎關注我的微信公眾號

小旋鋒微信公眾號

相關文章