Elasticsearch系列---使用中文分詞器

前言

前面的案例使用standard、english分詞器,是英文原生的分詞器,對中文分詞支持不太好。中文作爲全球最優美、最複雜的語言,目前中文分詞器較多,ik-analyzer、結巴中文分詞、THULAC、NLPIR和阿里的aliws都是非常優秀的,我們以ik-analyzer作爲講解的重點,其它分詞器可以舉一反三。

概要

本篇主要介紹中文分詞器ik-analyzer的安裝使用、自定義詞庫以及熱更新方案。

分詞器插件安裝

我們Elasticsearch 6.3.1版本爲例,集成IK分詞器,其他的分詞器過程也類似,在ES的bin目錄下執行插件安裝命令即可:
./elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.3.1/elasticsearch-analysis-ik-6.3.1.zip

其中install後面的那個的地址是 elasticsearch-analysis-ik 的github release對應ES版本的下載地址。

插件的版本最好與Elasticsearch版本保持一致,如果Elasticsearch爲別的版本,下載對應版本的ik-analyzer插件即可。

安裝成功後,ES啓動日誌就能看到如下信息:
[2019-11-27T12:17:15,255][INFO ][o.e.p.PluginsService] [node-1] loaded plugin [analysis-ik]

IK分詞器

基礎知識

IK分詞器包含兩種analyzer,一般用ik_max_word
ik_max_word:會將文本做最細粒度的拆分
ik_smart:會做最粗粒度的拆分

測試分詞效果

# ik_max_word分詞測試
GET /_analyze
{
  "text": "您好祖國",
  "analyzer": "ik_smart"
}

# 響應如下:
{
  "tokens": [
    {
      "token": "您好",
      "start_offset": 0,
      "end_offset": 2,
      "type": "CN_WORD",
      "position": 0
    },
    {
      "token": "祖國",
      "start_offset": 2,
      "end_offset": 4,
      "type": "CN_WORD",
      "position": 1
    }
  ]
}
# ik_max_word分詞測試
GET /_analyze
{
  "text": "我和我的祖國",
  "analyzer": "ik_max_word"
}

# 響應如下:
{
  "tokens": [
    {
      "token": "我",
      "start_offset": 0,
      "end_offset": 1,
      "type": "CN_CHAR",
      "position": 0
    },
    {
      "token": "和我",
      "start_offset": 1,
      "end_offset": 3,
      "type": "CN_WORD",
      "position": 1
    },
    {
      "token": "的",
      "start_offset": 3,
      "end_offset": 4,
      "type": "CN_CHAR",
      "position": 2
    },
    {
      "token": "祖國",
      "start_offset": 4,
      "end_offset": 6,
      "type": "CN_WORD",
      "position": 3
    }
  ]
}

配置文件

ik插件安裝完成後,可以在elasticsearch-6.3.1/config/analysis-ik看到ik的配置文件IKAnalyzer.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
        <comment>IK Analyzer 擴展配置</comment>
        <!--用戶可以在這裏配置自己的擴展字典 -->
        <entry key="ext_dict"></entry>
         <!--用戶可以在這裏配置自己的擴展停止詞字典-->
        <entry key="ext_stopwords"></entry>
        <!--用戶可以在這裏配置遠程擴展字典 -->
        <!-- <entry key="remote_ext_dict">words_location</entry> -->
        <!--用戶可以在這裏配置遠程擴展停止詞字典-->
        <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

該目錄下帶有許多文件,含義如下:

  • main.dic ik 原生內置的中文詞庫,裏面有275909條現成的詞語
  • quantifier.dic 量詞和單位名稱,如個,斤,克,米之類的
  • suffix.dic 常見後綴詞,如江,村,省,市,局等
  • surname.dic 中國姓氏
  • stopword.dic 停用詞,目前默認的是寫的幾個英文單詞,如and, a, the等
  • preposition.dic 副詞、語氣助詞,連接詞等無實際含義的詞語,如卻,也,是,否則之類的

6.3.1版本的IK分詞器還提供了額外的詞庫補充文件,extra開頭的那幾個就是,如extra_main.dic,共收錄398716條現有的詞語,默認沒有使用,有需要可以在配置文件IKAnalyzer.cfg.xml上添加,其他類似。

最重要的是main.dic和stopword.dic。stopword(停用詞),分詞時會直接被幹掉,不會建立在倒排索引中。

自定義詞庫

  1. 創建自定義詞庫文件mydic.dic,並在IKAnalyzer.cfg.xml的ext_dict屬性里加上該文件名,可以在mydic.dic文件裏補充自己的詞彙,如網絡流行詞:跪族籃孩。

添加前的分詞效果:

GET /forum/_analyze
{
  "text": "跪族籃孩",
  "analyzer": "ik_max_word"
}

響應結果:
{
  "tokens": [
    {
      "token": "跪",
      "start_offset": 0,
      "end_offset": 1,
      "type": "CN_WORD",
      "position": 0
    },
    {
      "token": "族",
      "start_offset": 1,
      "end_offset": 2,
      "type": "CN_CHAR",
      "position": 1
    },
    {
      "token": "籃",
      "start_offset": 2,
      "end_offset": 3,
      "type": "CN_WORD",
      "position": 2
    },
    {
      "token": "孩",
      "start_offset": 3,
      "end_offset": 4,
      "type": "CN_CHAR",
      "position": 3
    }
  ]
}

添加詞庫後:

{
  "tokens": [
    {
      "token": "跪族籃孩",
      "start_offset": 0,
      "end_offset": 4,
      "type": "CN_WORD",
      "position": 0
    },
    {
      "token": "跪",
      "start_offset": 0,
      "end_offset": 1,
      "type": "CN_WORD",
      "position": 1
    },
    {
      "token": "族",
      "start_offset": 1,
      "end_offset": 2,
      "type": "CN_CHAR",
      "position": 2
    },
    {
      "token": "籃",
      "start_offset": 2,
      "end_offset": 3,
      "type": "CN_WORD",
      "position": 3
    },
    {
      "token": "孩",
      "start_offset": 3,
      "end_offset": 4,
      "type": "CN_CHAR",
      "position": 4
    }
  ]
}

能看到完整的“跪族籃孩”,能看到完整的語詞出現。

2)自己建立停用詞庫,如了,的,哈,啥,這幾個字不想去建立索引
在配置文件IKAnalyzer.cfg.xml下ext_stopwords標籤添加:extra_stopword.dic,並加幾個詞,修改後同樣要重啓es。

例:加一個"啥"字在ext_stopword中
修改前:

GET /forum/_analyze
{
  "text": "啥都好",
  "analyzer": "ik_max_word"
}

響應結果:
{
  "tokens": [
    {
      "token": "啥",
      "start_offset": 0,
      "end_offset": 1,
      "type": "CN_WORD",
      "position": 0
    },
    {
      "token": "都好",
      "start_offset": 1,
      "end_offset": 3,
      "type": "CN_WORD",
      "position": 1
    }
  ]
}

添加停用詞後

{
  "tokens": [
    {
      "token": "都好",
      "start_offset": 1,
      "end_offset": 3,
      "type": "CN_WORD",
      "position": 0
    }
  ]
}

那個啥字直接沒有了,結果符合預期。

熱更新方案

上面自定義詞庫有一個致命問題:必須要重啓ES,新增的詞庫才能生效。

研發、測試環境自己玩玩無所謂,多半是自己使用,節點又少,重啓就重啓,關係不大。但想想生產環境能隨便讓你重啓嗎?動輒幾百個ES實例,重啓的事就別想了,另外找辦法。

由此引出現在的熱更新需求,讓ES不停機能立即加載新增的詞庫。

熱更新的方案

  1. 基於id分詞器原生支持的更新方案,部署一個web服務器,提供一個http接口,通過modified和try兩個http響應頭,來提供詞語的熱更新操作。
  2. 修改ik分詞器源碼,然後手動支持從mysql中每隔一定時間,自動加載新的詞庫。

推薦方案二,方案一雖是官方提供的,但操作起來比較麻煩,還需要部署http服務器。

方案步驟
1)下載源碼
git clone https://github.com/medcl/elasticsearch-analysis-ik
git checkout tags/v6.3.1
該工程是Maven項目工程,將代碼導入IDEA或Eclipse。
2)修改點

org.wltea.analyzer.dic.Dictionary

主要思路是在這個類的initial()方法內增加一個入口,反覆去調用reLoadMainDict()方法,此方法如下:

public void reLoadMainDict() {
           logger.info("重新加載詞典...");
           // 新開一個實例加載詞典,減少加載過程對當前詞典使用的影響
           Dictionary tmpDict = new Dictionary(configuration);
           tmpDict.configuration = getSingleton().configuration;
           tmpDict.loadMainDict();
           tmpDict.loadStopWordDict();
           _MainDict = tmpDict._MainDict;
           _StopWords = tmpDict._StopWords;
           logger.info("重新加載詞典完畢...");
     }

這個方法就是重新加載詞庫的,然後修改loadMainDict()和loadStopWordDict()方法,在這兩個方法最後加上讀取數據庫獲取最新的數據記錄的邏輯即可。數據庫的表結構自己定義兩張表,滿足數據庫表設計規範即可。

3)IDE上mvn package打包
可以直接用target/releases/目錄下的elasticsearch-analysis-ik-6.3.1.zip
4)解壓zip包,加上jdbc的配置,該修改的修改,重啓ES,看日誌
5)在數據庫里加幾個字段,在線嘗試是否生效。

方案延伸

該方案使用數據庫輪詢的方法,簡單有效,但比較浪費資源,畢竟生產上修改詞庫的動作是按需求發生的,可以考慮由定時輪詢改成MQ消息通知,這樣就可以做到按需更新,而不用浪費太多的資源做詞典更新。

小結

本篇對中文分詞器IK作了簡單的講解,市面上流行的中文分詞器很多,如果我們遇到有中文分詞的需求,貨比三家是永遠不過時的道理,調研可能要花費一些時間,但能挑到適合自己項目的分詞器,還是划算的。

專注Java高併發、分佈式架構,更多技術乾貨分享與心得,請關注公衆號:Java架構社區
可以掃左邊二維碼添加好友,邀請你加入Java架構社區微信羣共同探討技術
Java架構社區

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章