Elasticsearch 是一个非常强大的搜索引擎。它目前被广泛地使用于各个 IT 公司。Elasticsearch 是由 Elastic 公司创建。它的代码位于 GitHub - elastic/elasticsearch: Free and Open, Distributed, RESTful Search Engine。目前,Elasticsearch 是一个免费及开放(free and open)的项目。同时,Elastic 公司也拥有 Logstash 及 Kibana 开源项目。这个三个项目组合在一起,就形成了 ELK 软件栈。他们三个共同形成了一个强大的生态圈。简单地说,Logstash 负责数据的采集,处理(丰富数据,数据转换等),Kibana 负责数据展示,分析,管理,监督及应用。Elasticsearch 处于最核心的位置,它可以帮我们对数据进行快速地搜索及分析。
我们生活中的数据总体分为两种:结构化数据和非结构化数据。
非结构化数据又一种叫法叫全文数据。
按照数据的分类,搜索也分为两种:
对非结构化数据也即对全文数据的搜索主要有两种方法:
一种是顺序扫描法(Serial Scanning):所谓顺序扫描,比如要找内容包含某一个字符串的文件,就是一个文档一个文档的看,对于每一个文档,从头看到尾,如果此文档包含此字符串,则此文档为我们要找的下一个文件,直到扫描完所有的文件。如利用windows的搜索也可以搜索文件内容,只是相当的慢。假如有一个80G硬盘,如果想在上面找到一个内容包含某字符串的文件,可能需要几个小时的时间。Linux下的grep命令也是这一种方式。这是一种比较原始的方法,但对于小数据量的文件,这种方法还是最直接,最方便的。但是对于大量的文件,这种方法的速度就很慢。
另一种是全文检索(Full-text Search):即先建立索引,再对索引进行搜索。索引是从非结构化数据中提取出之后重新组织的信息。
Elasticsearch是一个分布式可扩展的实时搜索和分析引擎,一个建立在全文搜索引擎Apache Lucene™基础上的搜索引擎.当然Elasticsearch并不仅仅是Lucene那么简单,它不仅包括了全文搜索功能,还可以进行以下工作:
kibana相当于是elasticsearch的可视化工具
systemctl stop firewalld.service
systemctl disable firewalld.service
systemctl status firewalld.service
#新建es的config配置文件夹
mkdir -p /data/elasticsearch/config
#新建es的data目录
mkdir -p /data/elasticsearch/data
#给目录设置权限
chmod 777 -R /data/elasticsearch
#写入配置到elasticsearch.yml中
echo "http.host: 0.0.0.0" >>/data/elasticsearch/config/elasticsearch.yml
#安装es
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms128m -Xmx256m" \
-v /data/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /data/elasticsearch/data:/usr/share/elasticsearch/data \
-v /data/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.10.1
将ip改为自己的ip 不能用127.0.0.1 因为它是docker kibana版本号要和elasticsearch版本号一致
docker run -d --name kibana -e ELASTICSEARCH_HOSTS="http://192.168.10.130:9200" -p 5601:5601 kibana:7.10.1
访问ip:5601
es中的type、index、mapping和dsl
快速的用mysql来理解elasticsearch 对应如下
有两个含义:动词(insert) - 名词(表)
Elasticsearch将它的数据存储到一个或者多个索引(index)中,索引就像数据库,可以向索引写入文档或者从索引中读取文档。
PUT /movies/movie/1
{
"title": "The Godfather",
"director": "Francis Ford Coppola",
"year": 1972,
"genres": ["Crime","Drama"]
}
文档(document)是Elasticsearch中的主要实体。所以,所有使用Elasticsearch最终都会归结到文档的搜索上从客户端看,文档就是一个JSON对象,文档由字段构成,每个字段包含字段名以及一个或多个字段值。文档之间可能有各自不同的字段集合,文档没有固定的模式或强制的结构。
Elasticsearch中每个文档都有与之对应的类型(type)定义,允许在一个索引存储多种文档类型,并提供不同映射。
映射做的就是,存储分析链所需的信息。
主要就是设置一些参数,根据这些参数来做过滤还是分割词条。
官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html
因为es的调用是基于Restful的设计风格 所以直接用kibana自带的请求方式 或者用postman、apifox等直接发起get post等请求就可以实现 这里用kibana自带的请求方式 (Dev Tools) 便于观察
在customer
下保存id
为1的数据,这里id
是必须的
在7.x以前一般都是有type的 现在用
_doc
这种固定格式 然后+id
put {index}/_doc/{id} //index可以不用提前建好 会默认创建
put操作必须加id
post操作可以不加 然后返回的值里result
会指明这是新建的还是修改的
GET _cat/indices
PUT /account/_doc/1
{
"name": "bobby",
"age": 18,
"company": [
{
"name":"imooc",
"address":"shanghai"
},
{
"name":"imooc2",
"address":"shanghai"
}
]
}
“_version”、“_seq_no”和“_primary_term”之间的关系:https://discuss.elastic.co/t/relation-between-version-seq-no-and-primary-term/179647
GET _cat/indices //查看所有索引
GET /account //查看index的基本信息
_doc是详情 _source是只获取数据
官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html
Elasticsearch有两种查询方式
查询所有index
所包含的
数据是放在hits
里面的
max_score
是一个得分 数据里哪一个数据和我们搜索的数据匹配度多大
使用_doc
来更新会发现是覆盖操作
得用_update
来进行操作
如果用_update
更新的值和你输入的值相同 那么不会产生任何变化 version
也不会变化 _doc
会更新version
(_doc是愣头青 只会更新 不会查这个值有没有)
可以有不同的操作 index delete update等
index update post put操作的时候是有两行的 第一行是index和id 第二行是数据 其他的是一行
官方给的测试数据:https://github.com/elastic/elasticsearch/blob/7.5/docs/src/test/resources/accounts.json
POST _bulk
{ "index" : { "_index" : "test", "_id" : "1"}}
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_jid": "2"}}
{ "create" : { "_index" : "test", "_id" : "3"}}
{ "field1" : "value3"}
{ "update" : {"_id" : "1", "_index" : "test"}}
{ "doc" : { "field2" : "value2"]}
每一次操作是互不影响的 比如第一次失败了 下一步还是会操作的 没有事务
比如获取user的index
和account的index
里id
为1
的值
es里query的查询和mysl中like查询的逻辑是完全不一样的
比如我要存储
6:“671 Bristol Street”
13:“789 Madison Street”
这时候es就会进行分词 分完词以后进行和文档的整理 整理出你的key和次数 第几个 等等等等
查询的时候 比如我要查询:
6:“Bristol Street”
他会进行分词后查询 并不是全等于才返回 而是有单词等于就返回 类似于百度引擎 然后会有一个“得分”的概念 返回 基于得分来分析返回的数据
查询一般简单查询和复杂查询
简单查询就是拼凑URI
复杂查询是最完整的查询 就是基于request body的查询
官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html
官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html
官方支持的dsl查询方法有:
重点关注全文查询
、复合查询
、术语级查询(term)
即可
match:模糊匹配,需要指定字段名,但是输入会进行分词,比如"hello world"会进行拆分为hello和world,然后匹配,如果字段中包含hello或者world,或者都包含的结果都会被查询出来,也就是说match是一个部分匹配的模糊查询。查询条件相对来说比较宽松。
官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query-phrase.html
match_phase:会对输入做分词,但是需要结果中也包含所有的分词,而且顺序要求一样。以""hello
world"为例,要求结果中必须包含hello和world,而且还要求他们是连着的,顺序也是固定的,hello that word不满足,world hello也不满足条件。
官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html
multi_match 查询提供了一个简便的方法用来对多个字段执行相同的查询,即对指定的多个字段进行
match查询 - 比如我想查找title
或者desc
中包含“go”的字段
GET resume/_search
{
"query":{
"multi_match": {
"query": "go",
"fields": ["title^2","desc"]
}
}
}
官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html
query_string:和match类似,但是match需要指定字段名,query_string是在所有字段中搜索,范围更广泛。
default_field字段如果不用的话默认是所有*
,指定字段的话和上面的差不多 query里可以加连接符:AND
OR
等等
这个query_string在查询的时候多了一步,就是解析query字段里的连接符 这种写法会更严谨一些 更复杂 更自由
这个在开头用过了 也是最简单的一个 直接贴代码吧
官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/term-level-queries.html
term : 这种查询和match在有些时候是等价的,比如我们查询单个的词hello,那么会和match查询结果一
样,但是如果查询"hello world",结果就相差很大,因为这个输入不会进行分词,就是说查询的时候,是
查询字段分词结果中是否有"hello world"的字样,而不是查询字段中包含"hello world"的字样,
elasticsearch会对字段内容进行分词,“hello world"会被分成hello和world,不存在"hello world”,因此这
里的查询结果会为空。这也是term查询和match的区别。
查询所有 有age字段的数据
我在百度想要搜索golang
但是我不知道golang
怎么拼 写成了golanl
百度会有一个纠错的功能 会给我们搜索golang
而不是golanl
这就是fuzzy查询 涉及到一个编辑距离
的原理
编辑距离(一个算法题):https://www.nowcoder.com/questionTerminal/3959837097c7413a961a135d7104c314
理解这道题 自然而然就理解什么是编辑距离了
我一不小心将street
拼写成了streat
如果是多个单词的话 就在match
查询中对内容进行更详细的查询:
官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html
就是运用上述的所有逻辑做一个更复杂的查询
Elasticsearch bool
查询对应Lucene BooleanQuery
, 格式如下
{
"query":{
"bool":{
"must":[
],
"should":[
],
"must_not":[
],
"filter":[
],
}
}
}
得分
会高一点,不满足得分
会低一点 - 应该匹配,查询上下⽂,加分must
逻辑相反 - 必须不匹配,过滤上下⽂,过滤bool
查询采用了一种匹配越多越好的方法,因此每个匹配的must
或should
子句的分数将被加在一起,以提供每个文档的最终得分
官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html
在带你搞定 ElasticSearch
术语中,我们讲到了 Mapping
类似于数据库中的表结构定义
schema
,它有以下几个作用:
定义索引中的字段的名称定义字段的数据类型,比如字符串、数字、布尔字段,倒排索引的相关配置,比如设置某个字段为不被索引、记录 position
等在 ES
早期版本,一个索引下是可以有多个 Type
,从 7.0开始,一个索引只有一个 Type
,也可以说一个 Type
有一个 Mapping
定义。
在了解了什么是 Mapping
之后,接下来对 Mapping
的设置做下介绍:
在创建一个索引的时候,可以对 dynamic
进行设置,可以设成 false
、true
或者 strict
。
比如一个新的文档,这个文档包含一个字段,当 Dynamic
设置为 true
时,这个文档可以被索引进 ES
,这个字段也可以被索引,也就是这个字段可以被搜索,Mapping
也同时被更新;当 dynamic
被设置为false
时候,存在新增字段的数据写入,该数据可以被索引,但是新增字段被丢弃;当设置成 strict
模式时候,数据写入直接出错。
另外还有 index
参数,用来控制当前字段是否被索引,默认为 true
,如果设为 false
,则该字段不可被搜索。
参数 index_options
用于控制倒排索引记录的内容,有如下 4 种配置:
另外,text 类型默认配置为 positions
,其他类型默认为 doc,记录内容越多,占用存储空间越大。
null_value
主要是当字段遇到 null 值时的处理策略,默认为 NULL,即空值,此时 ES 会默认忽略该值,
可以通过设定该值设定字段的默认值,另外只有 KeyWord
类型支持设定 null_value
。
copy_to
作用是将该字段的值复制到目标字段,实现类似 _all
的作用,它不会出现在 _source
中,只用来搜索。
除了上述介绍的参数,还有许多参数,大家感兴趣的可以在官方文档中进行查看。
在学习了 Mapping
的设置之后,让我们来看下字段的数据类型有哪些吧!
ES 字段类型类似于 MySQL 中的字段类型,ES 字段类型主要有:核心类型、复杂类型、地理类型以及特殊类型,
从图中可以看出核心类型可以划分为字符串类型、数字类型、日期类型、布尔类型、基于 BASE64 的二
进制类型、范围类型。
其中,在 ES 7.x 有两种字符串类型:text 和 keyword,在 ES 5.x 之后 string 类型已经不再支持了。
text 类型适用于需要被全文检索的字段,例如新闻正文、邮件内容等比较长的文字,text 类型会被
Lucene 分词器(Analyzer)处理为一个个词项,并使用 Lucene 倒排索引存储,text 字段不能被用于排
序,如果需要使用该类型的字段只需要在定义映射时指定 JSON 中对应字段的 type 为 text。
keyword 适合简短、结构化字符串,例如主机名、姓名、商品名称等,可以用于过滤、排序、聚合检
索,也可以用于精确查询。
数字类型分为 long、integer、short、byte、double、float、half_float、scaled_float。
数字类型的字段在满足需求的前提下应当尽量选择范围较小的数据类型,字段长度越短,搜索效率越高,
对于浮点数,可以优先考虑使用 scaled_float 类型,该类型可以通过缩放因子来精确浮点数,例如 12.34
可以转换为 1234 来存储。
在 ES 中日期可以为以下形式:
格式化的日期字符串,例如 2020-03-17 00:00、2020/03/17
时间戳(和 1970-01-01 00:00:00 UTC 的差值),单位毫秒或者秒即使是格式化的日期字符串,ES 底层
依然采用的是时间戳的形式存储。
JSON 文档中同样存在布尔类型,不过 JSON 字符串类型也可以被 ES 转换为布尔类型存储,前提是字符
串的取值为 true 或者 false,布尔类型常用于检索中的过滤条件。
二进制类型 binary 接受 BASE64 编码的字符串,默认 store 属性为 false,并且不可以被搜索。
范围类型
范围类型可以用来表达一个数据的区间,可以分为5种:
integer_range、float_range、long_range、double_range 以及 date_range。
复合类型主要有对象类型(object)和嵌套类型(nested):
JSON 字符串允许嵌套对象,一个文档可以嵌套多个、多层对象。可以通过对象类型来存储二级文档,不
过由于 Lucene 并没有内部对象的概念,ES 会将原 JSON 文档扁平化,例如文档:
实际上 ES 会将其转换为以下格式,并通过 Lucene 存储,即使 name 是 object 类型:
嵌套类型可以看成是一个特殊的对象类型,可以让对象数组独立检索,例如文档:
username 字段是一个 JSON 数组,并且每个数组对象都是一个 JSON 对象。如果将 username 设置为
对象类型,那么 ES 会将其转换为:
可以看出转换后的 JSON 文档中 first 和 last 的关联丢失了,如果尝试搜索 first 为 wu,last 为 xy 的文
档,那么成功会检索出上述文档,但是 wu 和 xy 在原 JSON 文档中并不属于同一个 JSON 对象,应当是不匹配的,即检索不出任何结果。
嵌套类型就是为了解决这种问题的,嵌套类型将数组中的每个 JSON 对象作为独立的隐藏文档来存储,
每个嵌套的对象都能够独立地被搜索,所以上述案例中虽然表面上只有 1 个文档,但实际上是存储了 4
个文档。
地理类型字段分为两种:经纬度类型和地理区域类型:
经纬度类型字段(geo_point)可以存储经纬度相关信息,通过地理类型的字段,可以用来实现诸如查找
在指定地理区域内相关的文档、根据距离排序、根据地理位置修改评分规则等需求。
经纬度类型可以表达一个点,而 geo_shape 类型可以表达一块地理区域,区域的形状可以是任意多边
形,也可以是点、线、面、多点、多线、多面等几何类型。
特殊类型包括 IP 类型、过滤器类型、Join 类型、别名类型等,在这里简单介绍下 IP 类型和 Join 类型,
其他特殊类型可以查看官方文档。
IP 类型的字段可以用来存储 IPv4 或者 IPv6 地址,如果需要存储 IP 类型的字段,需要手动定义映射:
Join 类型是 ES 6.x 引入的类型,以取代淘汰的 _parent 元字段,用来实现文档的一对一、一对多的关
系,主要用来做父子查询。
Join 类型的 Mapping 如下:
其中,my_join_field 为 Join 类型字段的名称;relations 指定关系:question 是 answer 的父类。
例如定义一个 ID 为 1 的父文档:
接下来定义一个子文档,该文档指定了父文档 ID 为 1:
什么是 Dynamic Mapping?
Dynamic Mapping 机制使我们不需要手动定义 Mapping,ES 会自动根据文档信息来判断字段合适的类
型,但是有时候也会推算的不对,比如地理位置信息有可能会判断为 Text,当类型如果设置不对时,会
导致一些功能无法正常工作,比如 Range 查询。
类型自动识别
ES 类型的自动识别是基于 JSON 的格式,如果输入的是 JSON 是字符串且格式为日期格式,ES 会自动
设置成 Date 类型;当输入的字符串是数字的时候,ES 默认会当成字符串来处理,可以通过设置来转换
成合适的类型;如果输入的是 Text 字段的时候,ES 会自动增加 keyword 子字段,还有一些自动识别
下面我们通过一个例子是看看是怎么类型自动识别的,输入如下请求,创建索引:
然后使用 GET /mapping_test/_mapping 查看,结果如下图所示:
可以从结果中看出,ES 会根据文档信息自动推算出合适的类型。
哦豁,万一我想修改 Mapping 的字段类型,能否更改呢?让我们分以下两种情况来探究下:
修改 Mapping 字段类型?
如果是新增加的字段,根据 Dynamic 的设置分为以下三种状况:
当 Dynamic 设置为 true 时,一旦有新增字段的文档写入,Mapping 也同时被更新。
当 Dynamic 设置为 false 时,索引的 Mapping 是不会被更新的,新增字段的数据无法被索引,也就是无
法被搜索,但是信息会出现在 _source 中。
当 Dynamic 设置为 strict 时,文档写入会失败。
另外一种是字段已经存在,这种情况下,ES 是不允许修改字段的类型的,因为 ES 是根据 Lucene 实现
的倒排索引,一旦生成后就不允许修改,如果希望改变字段类型,必须使用 Reindex API 重建索引。
不能修改的原因是如果修改了字段的数据类型,会导致已被索引的无法被搜索,但是如果是增加新的字
段,就不会有这样的影响。
个人理解
- text和keyword
string(字符串)在es中有text
keyword
两种类型 - 非常重要
比如address字段类型: 你又是text 又是keyword
text
是会分词的
keyword
不会分词
在设置这个字段的时候mapping里可以指定写入的时候用的是哪一个analyze
,查询这个字段的时候用的是哪一个analyze
问题:我用post写入一个不做分词的数据,为什么用match能查询到?
写入:写一个有integer
keyword
text
的index
:
解析:
查询的时候可以直接.keyword
使用term
可以完整的查询到desc字段的数据
使用match
可以查询到name字段的数据
使用match
能不能查询到desc字段的数据???(能
为什么? 之前不是说过match是通过分词来查询的吗? 这不就打脸了?
)
分词规则又分为写入时的分词和查询时的分词
在写入数据的时候你不去手动指明Analyze,es会有一定的策略来生成分词规则
比如在进行match查询的时候默认是你在写入数据设置的规则(如果没设置会自动生成)
这也就导致了match查询的时候会优先使用指定的,如果没指定 会使用创建mapping的时候的策略
Elasticsearch 中文本分析Analysis是把全文本转换成一系列的单词(term/token)的过程,也叫分词。文
本分析是使用分析器 Analyzer 来实现的,Elasticsearch内置了分析器,用户也可以按照自己的需求自定
义分析器。
为了提高搜索准确性,除了在数据写入时转换词条,匹配 Query 语句时候也需要用相同的分析器对查询
语句进行分析。
Analyzer 由三部分组成:Character Filters
、Tokenizer
、Token Filters
Character Filters
Character Filters字符过滤器接收原始文本text的字符流,可以对原始文本增加、删除字段或者对字符做
转换。一个Analyzer 分析器可以有 0-n 个按顺序执行的字符过滤器。
Tokenizer
Tokenizer 分词器接收Character Filters输出的字符流,将字符流分解成的那个的单词,并且输出单词流。
例如空格分词器会将文本按照空格分解,将 “Quick brown fox!” 转换成 [Quick, brown, fox!]。分词器也负
责记录每个单词的顺序和该单词在原始文本中的起始和结束偏移 offsets 。
一个Analyzer 分析器有且只有 1个分词器。
Token Filters
Token Filter单词过滤器接收分词器Tokenizer输出的单词流,可以对单词流中的单词做添加、移除或者转换操作,例如lowercase token filter会将单词全部转换成小写,stop token filter会移除the、and这种通用单词, synonym token filter会往单词流中添加单词的同义词。
Token filters不允许改变单词在原文档的位置以及起始、结束偏移量。
一个Analyzer分析器可以有O-n 个按顺序执行的单词过滤器。
Standard Analyzer - 默认分词器,按词切分,小写处理
Simple Analyzer - 按照非字母切分(符号被过滤),小写处理
Stop Analyzer - 小写处理,停用词过滤(the ,a,is)
Whitespace Analyzer - 按照空格切分,不转小写
Keyword Analyzer - 不分词,直接将输入当做输出
Patter Analyzer - 正则表达式,默认 \W+
Language - 提供了 30 多种常见语言的分词器
Standard Analyzer
#standard
GET _analyze
{
"analyzer": "standard",
"text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}
输出:
[the,2,quick,brown,foxes,a,jumped,over,the,lazy,dog’s,bone]
Simple Analyzer
#simpe
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
GET _analyze
{
"analyzer": "stop",
"text": "The 2 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
#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
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]
Language Analyzer
支持语言: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 QUICK Brown-Foxes jumped over the lazy dog's bone."
}
输出:
[2,quick,brown,fox,jump,over,the,lazy,dog,bone]
中文分词要比英文分词难,英文都以空格分隔,中文理解通常需要上下文理解才能有正确的理解,比如
[苹果,不大好吃]和
[苹果,不大,好吃],这两句意思就不一样。
常用的插件分词器
IK Analyzer - 对中文分词友好,支持远程词典热更新,有ik_smart 、ik_max_word 两种分析器
pinyin Analyzer - 可以对中文进行拼音分析,搜索时使用拼音即可搜索出来对应中文
ICU Analyzer - 提供了 Unicode 的支持,更好的支持亚洲语言
hanLP Analyzer - 基于NLP的中文分析器
单词是语言中重要的基本元素。一个单词可以代表一个信息单元,有着指代名称、功能、动作、性质等作用。在语言的进化史中,不断有新的单词涌现,也有许多单词随着时代的变迁而边缘化直至消失。根据统计,《汉语词典》中包含的汉语单词数目在37万左右,《牛津英语词典》中的词汇约有17万。
理解单词对于分析语言结构和语义具有重要的作用。因此,在机器阅读理解算法中,模型通常需要首先对语句和文本进行单词分拆和解析。
分词(tokenization)的任务是将文本以单词为基本单元进行划分。由于许多词语存在词型的重叠,以及
组合词的运用,解决歧义性是分词任务中的一个挑战。不同的分拆方式可能表示完全不同的语义。如在以下例子中,两种分拆方式代表的语义都有可能:
南京市|⻓江|⼤桥
南京|市⻓|江⼤桥
将复杂问题转化为数学问题
在 机器学习的文章 中讲过,机器学习之所以看上去可以解决很多复杂的问题,是因为它把这些问题都转化为了数学问题。
而 NLP 也是相同的思路,文本都是一些「非结构化数据」,我们需要先将这些数据转化为「结构化数
据」,结构化数据就可以转化为数学问题了,而分词就是转化的第一步。
词是一个比较合适的粒度
词是表达完整含义的最小单位。
字的粒度太小,无法表达完整含义,比如”鼠“可以是”老鼠“,也可以是”鼠标“。
而句子的粒度太大,承载的信息量多,很难复用。比如”传统方法要分词,一个重要原因是传统方法对远距离依赖的建模能力较弱。”
区别1:分词方式不同,中文更难
英文有天然的空格作为分隔符,但是中文没有。所以如何切分是一个难点,再加上中文里一词多意的情况非常多,导致很容易出现歧义。下文中难点部分会详细说明。
区别2:英文单词有多种形态
英文单词存在丰富的变形变换。为了应对这些复杂的变换,英文NLP相比中文存在一些独特的处理步骤,我们称为词形还原(Lemmatization)和词干提取(Stemming)。中文则不需要
词性还原:does,done,doing,did 需要通过词性还原恢复成 do。
词干提取:cities,children,teeth 这些词,需要转换为 city,child,tooth”这些基本形态
区别3:中文分词需要考虑粒度问题
例如「中国科学技术大学」就有很多种分法:
粒度越大,表达的意思就越准确,但是也会导致召回比较少。所以中文需要不同的场景和要求选择不同的粒度。这个在英文中是没有的。
难点 1:没有统一的标准
目前中文分词没有统一的标准,也没有公认的规范。不同的公司和组织各有各的方法和规则。
难点 2:歧义词如何切分
例如「兵乓球拍卖完了」就有2种分词方式表达了2种不同的含义:
难点 3:新词的识别
信息爆炸的时代,三天两头就会冒出来一堆新词,如何快速的识别出这些新词是一大难点。比如当年「蓝瘦香菇」大火,就需要快速识别。
分词的方法大致分为 3 类:
给予词典匹配的分词方式
优点:速度快、成本低
缺点:适应性不强,不同领域效果差异大
基本思想是基于词典匹配,将待分词的中文文本根据一定规则切分和调整,然后跟词典中的词语进行匹
配,匹配成功则按照词典的词分词,匹配失败通过调整或者重新选择,如此反复循环即可。代表方法有基于正向最大匹配和基于逆向最大匹配及双向匹配法。
基于统计的分词方法
优点:适应性较强
缺点:成本较高,速度较慢
这类目前常用的是算法是 **HMM、CRF、SVM、深度学习 **等算法,比如stanford、Hanlp分词工具是基于CRF算法。以CRF为例,基本思路是对汉字进行标注训练,不仅考虑了词语出现的频率,还考虑上下文,具备较好的学习能力,因此其对歧义词和未登录词的识别都具有良好的效果。
基于深度学习
优点:准确率高、适应性强
缺点:成本高,速度慢
例如有人员尝试使用双向LSTM+CRF实现分词器,其本质上是序列标注,所以有通用性,命名实体识别
等都可以使用该模型,据报道其分词器字符准确率可高达97.5%。
常见的分词器都是使用机器学习算法和词典相结合,一方面能够提高分词准确率,另一方面能够改善领域适应性。
下面排名根据 GitHub 上的 star 数排名:
分词就是将句子、段落、文章这种长文本,分解为以字词为单位的数据结构,方便后续的处理分析工作。
分词的原因:
中英文分词的3个典型区别:
中文分词的3大难点
3个典型的分词方式:
下载的版本一定要和es的版本保持一致 查看es版本访问ip:9200
下载地址
之前用docker安装的时候挂载了plugins 直接解压到里面就行
将目录文件夹改名为ik - 必须改名
进入容器后在bin
目录下执行./elasticsearch-plugin list
可以查看安装过哪些插件
cd /data/elasticsearch/plugins
chmod 777 -R ik
ik_smart
和 ik_max_word
ik_max_word:将分词的可能全部列举出来
ik_smart就普通分词:
可以改mappings
里的analyzer
注意 如果这个字段已经存在的话是没办法修改mappings里的analyzer 因为它在初始化的时候已经分词了 新增一个index即可
查询一下:
它是基于词库来分词的
中文中有很多专用名词 他的词库里就没有这些名词 我们得自定义
很多时候也得解决停用词的问题 比如上述的**“的”、“是”、“不”**等等这样的词
就得扩展词库 和 扩展停用词库
自定义库得到ik
的目录下config
添加.dic
文件
cd到ik下的config目录
vim添加一个.dic结尾的文件 比如我添加一个扩展词库和一个停用词库 详细如下:
mydic.dic内容:
慕课网
中华牙膏
extra_stopword.dic内容:
的
是
哟
定义完之后开始配置
回到上级目录编辑IKAnalyzer.cfg.xml
然后重启docker
在查询的时候如果不想指明analyze
就配置mapping
PUT newCN
{
"mappings": {
"properties": {
"name":{
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart"
}
}
}
}