中文分词

本文是对ElasticSearch中文分词学习的一个知识总结,包括如下章节的内容:

  • 基本概念
  • ik分词器的安装
  • ik中文分词器的使用
  • ik的自定义词典
  • 文档的中文分词使用

参考资料:
1、如果希望先对ElasticSearch组件的基本概念有所了解,可先阅读《ElasticSearch学习笔记》

一、基本概念

当一个文档被存储时,ES会使用分词器从文档中提取出若干词元(token)来支持索引的存储和搜索。
ES内置了很多分词器,但内置的分词器对中文的处理不好。下面通过例子来看内置分词器的处理。在web客户端发起如下的一个REST请求,对英文语句进行分词:

POST http://localhost:9200/_analyze
{  
    "text": "hello world"  
}

操作成功后,响应的内容如下:

{
  "tokens": [
    {
      "token": "hello",
      "start_offset": 0,
      "end_offset": 5,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "world",
      "start_offset": 6,
      "end_offset": 11,
      "type": "<ALPHANUM>",
      "position": 1
    }
  ]
}

上面结果显示 "hello world"语句被分为两个单词,因为英文天生以空格分隔,自然就以空格来分词,这没有任何问题。

下面我们看一个中文的语句例子,请求REST如下:

POST http://localhost:9200/_analyze
{  
    "text": "世界如此之大"  
}

操作成功后,响应的内容如下:

{
  "tokens": [
    {
      "token": "世",
      "start_offset": 0,
      "end_offset": 1,
      "type": "<IDEOGRAPHIC>",
      "position": 0
    },
    {
      "token": "界",
      "start_offset": 1,
      "end_offset": 2,
      "type": "<IDEOGRAPHIC>",
      "position": 1
    },
    {
      "token": "如",
      "start_offset": 2,
      "end_offset": 3,
      "type": "<IDEOGRAPHIC>",
      "position": 2
    },
    {
      "token": "此",
      "start_offset": 3,
      "end_offset": 4,
      "type": "<IDEOGRAPHIC>",
      "position": 3
    },
    {
      "token": "之",
      "start_offset": 4,
      "end_offset": 5,
      "type": "<IDEOGRAPHIC>",
      "position": 4
    },
    {
      "token": "大",
      "start_offset": 5,
      "end_offset": 6,
      "type": "<IDEOGRAPHIC>",
      "position": 5
    }
  ]
}

从结果可以看出,这种分词把每个汉字都独立分开来了,这对中文分词就没有意义了,所以ES默认的分词器对中文处理是有问题的。好在有很多不错的第三方的中文分词器,可以很好地和ES结合起来使用。在ES中,每种分词器(包括内置的、第三方的)都会有个名称。上面默认的操作,其实用的分词器的名称是standard。下面的请求与前面介绍的请求是等价的,如:

POST http://localhost:9200/_analyze
{  
    "analyzer": "standard",
    "text": "世界如此之大"  
}

当我们换一个分词器处理分词时,只需将"analyzer"字段设置相应的分词器名称即可。
ES通过安装插件的方式来支持第三方分词器,对于第三方的中文分词器,比较常用的是中科院ICTCLAS的smartcn和IKAnanlyzer分词器。在本文中,我们介绍IKAnanlyzer分词器(下面简称ik)的使用。

因为是在网上搜索资料来进行ik的安装,碰到了很多坑,折腾很长时间才搞定,主要原因是网上的很多资料都是针对旧版本的ES和IK版本,有很多信息在新版本中已经有变化。下面的内容会介绍那些遇到的坑。

二、ik分词器的安装

ES提供了一个脚本elasticsearch-plugin(windows下为elasticsearch-plugin.bat)来安装插件,脚本位于ES安装目录的bin目录下。elasticsearch-plugin脚本可以有三种命令,靠参数区分:

1、 elasticsearch-plugin install 插件地址
install 参数指定的命令是安装指定的插件到当前ES节点中。

2、 elasticsearch-plugin list
list参数指定的命令是显示当前ES节点已经安装的插件列表。

3、 elasticsearch-plugin remove 插件名称
remove 参数指定的命令是删除已安装的插件。

使用elasticsearch-plugin install 安装插件时,插件地址既可以是一个远程文件地址(在线安装),也可以是下载到本地的文件,不管是远程文件或本地文件,对于ik插件来说都是一个zip文件。
注意,ik的版本要与ES的版本一致,因为本文ES用的是5.6.9版本,所以我们ik也用的是5.6.9版本。

远程文件安装命令如下:

elasticsearch-plugin  install  
https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v5.6.9/elasticsearch-analysis-ik-5.6.9.zip

因为使用的Linux机器无法直接上外网,所以就考虑采用本地文件安装的方式,这是碰到的第一个坑。从上面地址上下载了elasticsearch-analysis-ik-5.6.9.zip文件到本地,然后执行命令如下(实际下面写法是错误的):

elasticsearch-plugin  install 
/home/hadoop/elasticsearch-analysis-ik-5.6.9.zip

上面命令是在linux系统下执行的,zip文件位于本地的/home/hadoop目录下。但是无论怎么执行,都报错,报/home/hadoop/elasticsearch-analysis-ik-5.6.9.zip不是插件。在网上搜索了半天,也没有找到原因,甚至怀疑是下载的二进制包有问题,直接下载源代码自己编译打包成zip包,结果还是有问题。后来请教了同事,才知道elasticsearch-plugin install后面跟的路径,如果是本地的话,需要带file:///协议头。果然,带上协议头无论是在liunx下还是windows下安装都是成功的。这个事情给的教训是,不要想当然,因为以为本地直接采用本地路径即可,没想到要加file:///协议头;第二,有问题一段时间搞不定时及时向他人请教,省得浪费时间。

正确的linux下的本地安装命令是:

elasticsearch-plugin  install 
file:///home/hadoop/elasticsearch-analysis-ik-5.6.9.zip

正确的windows下本地安装的命令是:

elasticsearch-plugin.bat install 
file:///D:/hadoop/elasticsearch-analysis-ik-5.6.9.zip

安装完毕后,发现在ES的安装目录下的plugins目录下多了一个analysis-ik目录(内容是ik的zip包解压后根目录下的所有文件,一共是5个jar文件和1个properties配置文件),另外ES的安装目录下的config目录下多了一个analysis-ik目录(内容是ik的zip包解压后根目录下的config目录下所有文件,用于放置ik的自定义词库)。

遇到的第二个坑是,网上很多资料说,安装ik插件后,需要在ES的配置文件elasticsearch.yml中加上如下一行内容:

index.analysis.analyzer.ik.type: "ik"

可是,实际情况是加上这句话后,ES启动失败,从报的错误信息也看不到原因。又在网上查了半天,终于在一篇文章中看到对这个问题的解释:“index.analysis.analyzer.ik.type这个在新版本的ES中已经不需要了,添加了启动时反而会报错,ES5.X版本不再通过elasitcsearch.yml配置设置分词规则,而是在创建索引时指定”。

下面再介绍遇到的第三个坑,网上给出使用ik的例子,请求命令如下:

POST http://localhost:9200/_analyze
{  
    "analyzer": "ik",
    "text": "世界如此之大"  
}

但是总是返回错误信息,错误如下:

{
  "error": {
    "root_cause": [
      {
        "type": "remote_transport_exception",
        "reason": "[IefRKZ0][localhost:9300][indices:admin/analyze[s]]"
      }
    ],
    "type": "illegal_argument_exception",
    "reason": "failed to find global analyzer [ik]"
  },
  "status": 400
}

错误信息还是很明确的,找不到分词器ik。就以为是配置文件哪里配错了,折腾试了半天没找到原因。后来查找资料才知道在新的ik版本中,分词器的名称变了(不再叫ik),新版本的ik提供了两个分词器,分别是ik_max_word 和ik_smart,用任何一个替换ik,就没问题了。
三、ik中文分词器的使用
上面提到,ik提供了两个分词器,分别是ik_max_word 和ik_smart,下面我们分别测试下。
先测试ik_max_word,输入命令如下:

POST http://localhost:9200/_analyze
{
"analyzer": "ik_max_word",
"text": "世界如此之大"
}
响应结果如下:

{
"tokens": [
{
"token": "世界",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 0
},
{
"token": "如此之",
"start_offset": 2,
"end_offset": 5,
"type": "CN_WORD",
"position": 1
},
{
"token": "如此",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 2
},
{
"token": "之大",
"start_offset": 4,
"end_offset": 6,
"type": "CN_WORD",
"position": 3
}
]
}
再测试ik_smart,输入命令如下:

POST http://localhost:9200/_analyze
{
"analyzer": "ik_smart",
"text": "世界如此之大"
}
响应结果如下:

{
"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
},
{
"token": "之大",
"start_offset": 4,
"end_offset": 6,
"type": "CN_WORD",
"position": 2
}
]
}
比较两个分词器对同一句中文的分词结果,ik_max_word比ik_smart得到的中文词更多(从两者的英文名含义就可看出来),但这样也带来一个问题,使用ik_max_word会占用更多的存储空间。

四、ik的自定义词典
有时,可能ik自身提供的分词词典无法满足特定的一些需求(如专用名词等),ik提供了自定义词典的功能,也就是用户可以自己定义一些词汇,这样ik就会把它们当作词典中的内容来处理。

举个例子,对于上面例子中的“世界如此之大”这个中文语句,ik词库中不会有“界如此”这样一个单词,假设“界如此”就是一个专用名词,我们希望ik能识别出来。这时就可自定义ik的词典。具体方法是:

1、新建扩展名为dic的文本文件,文件中写入想增加的词条,每个词条单独一行,如文件名是test.dic,文件内容如下:

界如此
高潜
上面例子中有两个自定义词条。

2、将上面的dic文件保存到ES安装目录的config目录下的analysis-ik目录(安装ik插件时产生的目录)下,可以建立子目录,放在子目录下。比如文件的路径如:
** config/analysis-ik/mydic/test.dic**

3、修改ik的配置文件IKAnalyzer.cfg.xml(位于config/analysis-ik目录下),在配置文件中增加如下条目:

<entry key="ext_dict">mydict/test.dic</entry>
这样就将自定义的字典文件加到ik的字典中了。

4、重启ES让生效。

这时我们发起如下的REST请求:

POST http://localhost:9200/_analyze
{
"analyzer": "ik_max_word",
"text": "世界如此之大"
}
响应结果如下:

{
"tokens": [
{
"token": "世界",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 0
},
{
"token": "界如此",
"start_offset": 1,
"end_offset": 4,
"type": "CN_WORD",
"position": 1
},
{
"token": "如此之",
"start_offset": 2,
"end_offset": 5,
"type": "CN_WORD",
"position": 2
},
{
"token": "如此",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 3
},
{
"token": "之大",
"start_offset": 4,
"end_offset": 6,
"type": "CN_WORD",
"position": 4
}
]
}
可以看出,自定义的“界如此”词条被分词出来了。不过如果我们将analyzer改为ik_smart却发现“界如此”词条没能被识别出来。

五、文档的中文分词使用
前面的介绍只是简单举例介绍了ik的使用,下面我们来通过一个更完整的例子介绍分词。ES的分词在创建索引(index)后,可以通过REST命令来设置,这样后续插入到该索引的数据都会被相应的分词器进行处理。

为了比较ik的ik_smart和ik_max_word这两个分词器及默认的分词器standard,我们创建3个索引来分别使用这3个分词器。

1、创建索引

PUT http://192.168.226.132:9200/index_ik_s
PUT http://192.168.226.132:9200/index_ik_m
PUT http://192.168.226.132:9200/index_stan
2、设置分析器

POST http://localhost:9200/index_ik_s/resource/_mapping
{
"properties": {
"content": {
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart"
}
}
}
POST http://localhost:9200/index_ik_m/resource/_mapping
{
"properties": {
"content": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
}
}
}
POST http://localhost:9200/index_stan/resource/_mapping
{
"properties": {
"content": {
"type": "text",
"analyzer": "standard",
"search_analyzer": "standard"
}
}
}
3、插入数据
为了批量插入,我们使用了linxu的curl命令来执行REST操作。

curl -XPOST http://localhost:9200/index_ik_s/resource/1 -d'
{"content":"上海牛人科技有限公司"}'

curl -XPOST http://localhost:9200/index_ik_s/resource/2 -d'
{"content":"上海牛人科技有限公司,牛人聚集的地方"}'

curl -XPOST http://localhost:9200/index_ik_s/resource/3 -d'
{"content":"最牛的人所在的地方,上海牛人科技有限公司"}'

curl -XPOST http://localhost:9200/index_ik_s/resource/4 -d'
{"content":" This is an English test case"}'

curl -XPOST http://localhost:9200/index_ik_s/resource/5 -d'
{"content":" English Test Case"}'
curl -XPOST http://localhost:9200/index_ik_m/resource/1 -d'
{"content":"上海牛人科技有限公司"}'

curl -XPOST http://localhost:9200/index_ik_m/resource/2 -d'
{"content":"上海牛人科技有限公司,牛人聚集的地方"}'

curl -XPOST http://localhost:9200/index_ik_m/resource/3 -d'
{"content":"最牛的人所在的地方,上海牛人科技有限公司"}'

curl -XPOST http://localhost:9200/index_ik_m/resource/4 -d'
{"content":" This is an English test case"}'

curl -XPOST http://localhost:9200/index_ik_m/resource/5 -d'
{"content":" English Test Case"}'
curl -XPOST http://localhost:9200/index_stan/resource/1 -d'
{"content":"上海牛人科技有限公司"}'

curl -XPOST http://localhost:9200/index_stan/resource/2 -d'
{"content":"上海牛人科技有限公司,牛人聚集的地方"}'

curl -XPOST http://localhost:9200/index_stan/resource/3 -d'
{"content":"最牛的人所在的地方,上海牛人科技有限公司"}'

curl -XPOST http://localhost:9200/index_stan/resource/4 -d'
{"content":" This is an English test case"}'

curl -XPOST http://localhost:9200/index_stan/resource/5 -d'
{"content":" English Test Case"}'
4、测试验证和对比
先测下他们对中文标准单词的支持,查询“科技”,3种索引效果都一样的,都能胜任。请求命令如下:

POST http://localhost:9200/_search
{
"query": {
"query_string": {
"query": "科技",
"fields": ["content"]
}
}
}
再测试下非标准的搜索,搜索“海牛”,ik_smart搜索不出结果,因为在smart的反向索引中分词是“上海”、“牛人”。ik_max_word搜索得到结果,因为在maxword的反向索引中“上海”、“牛人”、“海牛”这些都有分词。Stardard可以搜索的到结果,因为startard的反向索引中每个字都拆分成一个分词“海”、“牛”,在搜索时又将每个字都拆成一个检索条件,所以查询得到。

案例中也给了英文的例子,测试后发现,ik_smart和ik_max_word对英文的分词不比standard差多少。

对比后我们的结论是:ik_smart既能满足英文的要求,又更智能更轻量,占用存储最小,所以首推ik_smart;standard对英语支持是最好的,但是对中文是简单暴力每个字建一个反向索引,浪费存储空间而且效果很差;ik_max_word比ik_smart对中文的支持更全面,但是存储上的开销实在太大,不建议使用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容