Analysis
Analysis
: 文本分析是把全文本转换一系列单词(term/token)的过程,也叫分词。Analysis是通过Analyzer来实现的。
当一个文档被索引时,每个Field都可能会创建一个倒排索引(Mapping可以设置不索引该Field)。
倒排索引的过程就是将文档通过Analyzer分成一个一个的Term,每一个Term都指向包含这个Term的文档集合。
当查询query时,Elasticsearch会根据搜索类型决定是否对query进行analyze,然后和倒排索引中的term进行相关性查询,匹配相应的文档。
Analyzer组成
分析器(analyzer)都由三种构件块组成的:character filters
, tokenizers
, token filters
。
1) character filter 字符过滤器
在一段文本进行分词之前,先进行预处理,比如说最常见的就是,过滤html标签(<span>hello<span> --> <hello>
2) tokenizers 分词器
英文分词可以根据空格将单词分开,中文分词比较复杂,可以采用机器学习算法来分词。
3) Token filters Token过滤器
将切分的单词进行加工。大小写转换(例将“Quick”转为小写),去掉词(例如停用词像“a”、“and”、“the”等等),或者增加词(例如同义词像“jump”和“leap”)。
三者顺序
:Character Filters--->Tokenizer--->Token Filter
三者个数
:analyzer = CharFilters(0个或多个) + Tokenizer(恰好一个) + TokenFilters(0个或多个)
内置分词器
官网地址
https://www.elastic.co/guide/en/elasticsearch/reference/current/analyzer.html
内置分词器
https://www.elastic.co/guide/en/elasticsearch/reference/7.x/analysis-tokenizers.html
Standard Analyzer
- 默认分词器
- 按词分类
POST _analyze
{
"analyzer": "standard",
"text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}
The above sentence would produce the following terms:
[ The, 2, QUICK, Brown, Foxes, jumped, over, the, lazy, dog's, bone ]
Configuration
The standard
tokenizer accepts the following parameters:
max_token_length |
The maximum token length. If a token is seen that exceeds this length then it is split at max_token_length intervals. Defaults to 255 . |
---|---|
最大令牌长度。如果看到令牌超过此长度,则将其max_token_length 间隔分割。默认为255 。 |
我们将standard
令牌生成器 配置max_token_length
为5。
PUT my-index-000001
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "my_tokenizer"
}
},
"tokenizer": {
"my_tokenizer": {
"type": "standard",
"max_token_length": 5
}
}
}
}
}
POST my-index-000001/_analyze
{
"analyzer": "my_analyzer",
"text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}
The above example produces the following terms:
[ The, 2, QUICK, Brown, Foxes, jumpe, d, over, the, lazy, dog's, bone ]
Simple Analyzer
- 按照非字母切分,非字母则会被去除
- 英文大写转小写
GET _analyze
{
"analyzer": "simple",
"text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}
输出:
[the,quick,brown,foxes,jumped,over,the,lazy,dog,s,bone]
Stop Analyzer
- 停用词过滤(the,a, is)
- 按照非字母切分,非字母则会被去除
GET _analyze
{
"analyzer": "stop",
"text": "The 2 a is 3 QUICK Brown-Foxes jumped over the lazy dog's bone."
}
输出:
[quick,brown,foxes,jumped,over,lazy,dog,s,bone]
Whitespace Analyzer
- 按空格切分
- 英文不区分大小写
- 中文不分词
#stop
GET _analyze
{
"analyzer": "whitespace",
"text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}
输出:
[The,2,QUICK,Brown-Foxes,jumped,over,the,lazy,dog's,bone.]
Keyword Analyzer
- 不分词,当成一整个 term 输出
#keyword
GET _analyze
{
"analyzer": "keyword",
"text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}
输出:
[The 2 QUICK Brown-Foxes jumped over the lazy dog's bone.]
Patter Analyzer**
- 通过正则表达式进行分词
- 默认是 \W+(非字母进行分隔)
GET _analyze
{
"analyzer": "pattern",
"text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}
输出:
[the,2,quick,brown,foxes,jumped,over,the,lazy,dog,s,bone]
Configuration
The pattern
tokenizer accepts the following parameters:
pattern |
A Java regular expression, defaults to \W+ . |
---|---|
flags |
Java正则表达式标志。标志应该用管道分隔,例如"CASE_INSENSITIVE" |
group |
要提取哪个捕获组作为标记。默认为-1 (分割)。 |
我们将pattern tokenizer程序配置为在遇到逗号时将文本分成令牌:
DELETE my-index-000001
PUT my-index-000001
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "my_tokenizer"
}
},
"tokenizer": {
"my_tokenizer": {
"type": "pattern",
"pattern": ","
}
}
}
}
}
POST my-index-000001/_analyze
{
"analyzer": "my_analyzer",
"text": "comma,separated,values"
}
上面的示例产生以下结果:
[ comma, separated, values ]
Language Analyzer
- 停用词过滤(the,a, is)
支持语言:arabic, armenian, basque, bengali, bulgarian, catalan, czech, dutch, english, finnish, french, galician, german, hindi, hungarian, indonesian, irish, italian, latvian, lithuanian, norwegian, portuguese, romanian, russian, sorani, spanish, swedish, turkish.
#english
GET _analyze
{
"analyzer": "english",
"text": "The 2 a is QUICK Brown-Foxes jumped over the lazy dog's bone."
}
输出:
[2,quick,brown,fox,jump,over,the,lazy,dog,bone]
多分词器
delete /st
PUT /st
{
"mappings": {
"properties": {
"uid": {
"type": "keyword"
},
"addr": {
"type": "text",
"analyzer": "standard",
"search_analyzer": "standard",
"fields": {
"standardname": {
"type": "text",
"analyzer": "ik_max_word"
},
"simplename": {
"type": "text",
"analyzer": "simple"
},
"patternname": {
"type": "text",
"analyzer": "pattern"
}
}
}
}
}
}
注意:声明了分词器,插入数据的时候才会根据分词建立倒排序索引。搜索的时候才能命中。
中文分词要比英文分词难,英文都以空格分隔,中文理解通常需要上下文理解才能有正确的理解,比如 [苹果,不大好吃]和
[苹果,不大,好吃],这两句意思就不一样。
中文分词
中文的分词器现在比较推荐的就是 IK分词器
,当然也有些其它的比如 smartCN、HanLP。
hanlp
> ansj
>结巴
>ik
>smart chinese analysis
;
这里只讲如何使用IK做为中文分词。
IK分词器的版本要你安装ES的版本一致。
开源分词器 Ik 的github:
https://github.com/medcl/elasticsearch-analysis-ik
https://github.com/medcl/elasticsearch-analysis-ik/releases
我下载的是7.0.0,地址为
https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v7.0.0
Docker安装IK中文分词器
Elasticsearch-analysis-ik是一款中文的分词插件,支持自定义词库,也有默认的词库。
下载IK分词器:
elasticsearch-analysis-ik-7.0.0.zip
进入解压后的包中看一下,主目录下包含一些插件依赖的外部jar包和一个config文件,
config文件里面的内容是分词器分词时读取文件的主要目录,我们大概说说里面的各文件内容,
IKAnalyzer.cfg.xml:用来配置自定义词库
main.dic:ik原生内置的中文词库,总共有27万多条,只要是这些单词,都会被分在一起
quantifier.dic:放了一些单位相关的词
suffix.dic:放了一些后缀
surname.dic:中国的姓氏
stopword.dic:英文停用词
ik原生最重要的两个配置文件
main.dic:包含了原生的中文词语,会按照这个里面的词语去分词
stopword.dic:包含了英文的停用词
进入容器:
docker exec -it elk /bin/bash
姿势1:(速度超慢)
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.0.0/elasticsearch-analysis-ik-7.0.0.zip
姿势2:(手动安装)
将 elasticsearch-analysis-ik-7.0.0.zip上传至服务器。(版本不同,则目录可能为/usr/share/elasticsearch/plugins/ik)
解压到ik目录,并将解压后的文件传入docker。(也可以在里面解压 yum install -y unzip zip)
docker cp ik elk:/opt/elasticsearch/plugins
进入容器后
docker exec -it elk /bin/bash
cd /opt/elasticsearch/plugins
发现已经有了插件。
退出容器:exit
重启
docker restart elk
IK有两种颗粒度的拆分:
ik_smart
: 会做最粗粒度的拆分
ik_max_word
: 会将文本做最细粒度的拆分
GET _analyze
{
"analyzer": "ik_smart",
"text": "我爱北京天安门"
}
GET _analyze
{
"analyzer": "ik_max_word",
"text": "我爱北京天安门"
}
会拆解成【我,爱,北京,天安门】
自定义字典的分词器
姿势1:
进入docker
docker exec -it elk /bin/bash
cd /opt/elasticsearch/plugins/ik/config
如果docker没有vi
apt-get update
apt-get install vim
编辑文件: vi 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">han.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
han.dic
老韩
信老韩
<entry key="ext_dict">han.dic</entry>
退出 exit
重新启动 docker restart elk
GET _analyze
{
"analyzer": "ik_max_word",
"text":"信老韩得永生"
}
{
"tokens" : [
{
"token" : "信老韩",
"start_offset" : 0,
"end_offset" : 3,
"type" : "CN_WORD",
"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
}
]
}
把老韩改成老潘,则无法得到永生。
姿势2
在外部更改main.dic,然后将main.dic直接拷贝入docker
docker cp main.dic elk:/opt/elasticsearch/plugins/ik/config
查询中使用分词器
有三种方式可以指定查询分析器:
URL指定
7版本分词,使用postman。
请求地址: http://192.168.2.100:9200/_analyze
请求类型: POST
BODY===>RAW===>JSON(AppliactionJSON)
参数:
{
"analyzer":"standard",
"text":"hello love you"
}
得到结果,换分词器。
{
"analyzer":"ik_smart",
"text":"我爱北京天安门"
}
{
"tokens": [
{
"token": "我",
"start_offset": 0,
"end_offset": 1,
"type": "CN_CHAR",
"position": 0
},
{
"token": "爱",
"start_offset": 1,
"end_offset": 2,
"type": "CN_CHAR",
"position": 1
},
{
"token": "北京",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 2
},
{
"token": "天安门",
"start_offset": 4,
"end_offset": 7,
"type": "CN_WORD",
"position": 3
}
]
}
Request Body指定
增加记录
PUT /st/_doc/7
{
"uname":"高3鹏",
"sex":"male",
"age":12,
"bith":"1998-09-09",
"addr":"大门"
}
PUT /st/_doc/6
{
"uname":"高4鹏",
"sex":"male",
"age":12,
"bith":"1998-09-09",
"addr":"我爱北京天安门"
}
PUT /st/_doc/8
{
"uname":"高5鹏",
"sex":"male",
"age":12,
"bith":"1998-09-09",
"addr":"天天向上"
}
查询
get /st/_search
{
"query":{
"match": {
"addr": {
"query" : "天安门",
"analyzer" : "ik_max_word"
}
}
}
}
则只会命中【大门,我爱北京天安门】,而不会命中【天天向上】,原因是
GET _analyze
{
"analyzer": "ik_max_word",
"text": "天安门"
}
的分词结果【天安门,天安,门】不会包含【天,安】
mapping指定
delete /st
PUT /st
{
"mappings": {
"properties": {
"uid": {
"type": "keyword"
},
"addr": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
}
}
}
}
GET /st/_analyze
{
"field": "addr",
"text": "天安门"
}
PUT /st/_doc/7
{
"uname":"高3鹏",
"sex":"male",
"age":12,
"bith":"1998-09-09",
"addr":"大门"
}
PUT /st/_doc/6
{
"uname":"高4鹏",
"sex":"male",
"age":12,
"bith":"1998-09-09",
"addr":"我爱北京天安门"
}
PUT /st/_doc/8
{
"uname":"高5鹏",
"sex":"male",
"age":12,
"bith":"1998-09-09",
"addr":"天天向上"
}
get /st/_search
{
"query":{
"match": {
"addr": {
"query" : "天安门"
}
}
}
}
则和
get /st/_search
{
"query":{
"match": {
"addr": {
"query" : "天安门",
"analyzer" : "ik_max_word"
}
}
}
}
完全一个效果
更改默认的分词器
ES默认的分词器为standard, 想要改变这个, 可以设置成自定义的analyzer.
想要改变成配置好的ik分词器, 在config/elasticsearch.yml文件中添加如下配置即可:
index.analysis.analyzer.default.type:ik
前提当然是你已经安装了ik分词。
Since elasticsearch 5.x index level settings can NOT be set on the nodes
configuration like the elasticsearch.yaml, in system properties or command line
arguments.In order to upgrade all indices the settings must be updated via the
/${index}/_settings API. Unless all settings are dynamic all indices must be closed
in order to apply the upgradeIndices created in the future should use index templates
to set default values.
Please ensure all required values are updated on all indices by executing:
curl -XPUT 'http://localhost:9200/_all/_settings?preserve_existing=true' -d '{
"index.analysis.analyzer.default.type" : "ik_max_word"
}'
5.x 版本后,不能写在配置文件了,使用restApi来配置
delete st
PUT /st
{
"settings" : {
"index" : {
"analysis.analyzer.default.type": "ik_max_word"
}
}
}
GET /st/_analyze
{
"field": "addr",
"text": "天安门"
}
PUT /st/_doc/7
{
"uname":"高3鹏",
"sex":"male",
"age":12,
"bith":"1998-09-09",
"addr":"大门"
}
PUT /st/_doc/6
{
"uname":"高4鹏",
"sex":"male",
"age":12,
"bith":"1998-09-09",
"addr":"我爱北京天安门"
}
PUT /st/_doc/8
{
"uname":"高5鹏",
"sex":"male",
"age":12,
"bith":"1998-09-09",
"addr":"天天向上"
}
get /st/_search
{
"query":{
"match": {
"addr": {
"query" : "天安门"
}
}
}
}
ik分词器热更新
目前该插件支持热更新 IK 分词,通过上文在 IK 配置文件中提到的如下配置 vi IKAnalyzer.cfg.xml
一定注意编码字符集
<!--用户可以在这里配置远程扩展字典 -->
<entry key="remote_ext_dict">location</entry>
<!--用户可以在这里配置远程扩展停止词字典-->
<entry key="remote_ext_stopwords">location</entry>
docker cp IKAnalyzer.cfg.xml elk:/opt/elasticsearch/plugins/ik/config
其中 location
是指一个 url,比如 http://yoursite.com/getCustomDict
,该请求只需满足以下两点即可完成分词热更新。
- 该 http 请求需要返回两个头部(header),一个是
Last-Modified
,一个是ETag
,这两者都是字符串类型,只要有一个发生变化,该插件就会去抓取新的分词进而更新词库。 - 该 http 请求返回的内容格式是一行一个分词,换行符用
\n
即可。
满足上面两点要求就可以实现热更新分词了,不需要重启 ES 实例。
可以将需自动更新的热词放在一个 UTF-8 编码的 .txt 文件里,放在 nginx 或其他简易 http server 下,当 .txt 文件修改时,http server 会在客户端请求该文件时自动返回相应的 Last-Modified 和 ETag。可以另外做一个工具来从业务系统提取相关词汇,并更新这个 .txt 文件。
热更新文件
tomcat中发布文件 han.dic
老韩
爱老虎
爱老韩
人定胜天
胜天
地址为
http://172.16.4.102:8080/han.dic
配置
编辑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">http://172.16.4.102:8080/han.dic</entry>
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
拷贝
在docker内部编辑或者拷贝文件到docker
docker cp IKAnalyzer.cfg.xml elk:/opt/elasticsearch/plugins/ik/config
测试
因为/st已经设置了默认的分词器
PUT /st
{
"settings" : {
"index" : {
"analysis.analyzer.default.type": "ik_max_word"
}
}
}
GET /st/_analyze
{
"field": "addr",
"text": "老韩爱潘峰"
}
#分词为【老韩,爱,潘,峰】
GET /st/_analyze
{
"field": "addr",
"text": "我人定胜天"
}
#分词为【我,人定胜天,人定,胜天】
GET /st/_analyze
{
"field": "addr",
"text": "爱老韩爱老虎"
}
#分词为【爱老韩,老韩,爱老虎,老虎】
再测试
GET /st/_analyze
{
"field": "addr",
"text": "明强吃东座"
}
#则分词为【明,强,吃,东,座】
修改han.dic为
老韩
爱老虎
爱老韩
人定胜天
胜天
明强
吃东座
则访问
GET /st/_analyze
{
"field": "addr",
"text": "明强吃东座"
}
#则分词为【明强,吃东座】