elasticSearch 6.5结合ik分词实现同义词检索

作者: 白云飞 分类: elasticSearch,php 发布时间: 2018-12-21 11:01 阅读:

上一篇 《elasticsearch-php结合ik分词,实现中文检索》 讲解了怎么通过 php 与ik分词结合实现中文检索的功能。这部分功能其实是很简单的,网上,手册也能找到很多的讲解的例子。其实本篇,才是我想讲的重点。在实现了中文检索功能之后,我就在考虑,如果用户输错了,或者输入了相似的词也会联想出想要的词。然而,就是这样简单的需求,我在网上找了好久的资料,也没看到满意的答案,基本上都是介绍的老的版本的,在 6.5已经不可用了,又或者有说是新版本的,但是我实验却不生效。同时,结合 php 的更是没有一个。于是,我决定写下这篇文章,给有同样需求的人提供一些思路。

首先,elasticSearch 提供了同义词过滤器,synonym。但是es 原生并不支持中文的分词,因此,我们就需要自己定义 analysis。其中的 自定义的分析器 analyzer 中的分词器 tokenizer 我们采用ik分词,过滤器采用 synonym。就可以实现同义词的功能。

在这之前,我们先来看看 停止词 stopword 的百度百科定义

如果搜索引擎要将这些词都索引的话,那么几乎每个网站都会被索引,也就是说工作量巨大。可以毫不夸张的说句,只要是个英文网站都会用到a或者是the。那么这些英文的词跟我们中文有什么关系呢? 在中文网站里面其实也存在大量的stopword,我们称它为停止词。比如,我们前面这句话,“在”、“里面”、“也”、“的”、“它”、“为”这些词都是停止词。这些词因为使用频率过高,几乎每个网页上都存在,所以搜索引擎开发人员都将这一类词语全部忽略掉。如果我们的网站上存在大量这样的词语,那么相当于浪费了很多资源。原本可以添加一个关键词,排名就可以上升一名的,为什么不留着添加为关键词呢?停止词对SEO的意义不是越多越好,而是尽量的减少为宜。

废话不多说,我们来看看,怎么定义这个索引吧

$params = [
    'index' => 'smart_talk',
    'body' => [
        'settings' => [
            'number_of_shards' => 1,
            'number_of_replicas' => 0,
            'analysis' => [
                'analyzer' => [
                    'ik_syno_smart' => [
                        'type' => 'custom',
                        'tokenizer' => 'ik_smart',
                        'filter' => ['my_stop_filter', 'my_syno_filter'],
                        'char_filter' => ['my_char_filter']
                    ],
                    'ik_syno_max_word' => [
                        'type' => 'custom',
                        'tokenizer' => 'ik_max_word',
                        'filter' => ['my_stop_filter', 'my_syno_filter'],
                        'char_filter' => ['my_char_filter']
                    ]
                ],
                'filter' => [
                    'my_stop_filter' => [
                        'type' => 'stop',
                        'stopwords' => [' ']
                    ],
                    'my_syno_filter' => [
                        'type' => 'synonym',
                        'synonyms_path' => 'analysis/synonyms.txt'
                    ]
                ],
                'char_filter' => [
                    'my_char_filter' => [
                        'type' => 'mapping',
                        'mappings' => ['| => |']
                    ]
                ]
            ]
        ],
        'mappings' => [
            'answers' => [
                'properties' => [
                    'question' => [
                        'type' => 'text',
                        'analyzer' => 'ik_syno_max_word',
                        'search_analyzer' => 'ik_syno_smart'
                    ],
                    'answer' => [
                        'type' => 'text',
                        'index' => false
                    ]
                ]
            ]
        ]
    ]
];
  •  analyer 分析器,我们自定义了一个 ik_syno_smart 和 ik_syno_max_word。制定了他的 type 类型为 custom,这个表示自定义的意思。还有很多种类型,你可以到官网手册介绍  analyer的地方查看。tokenizer 分词器,我采用了 ik_smart 和 ik_max_word 这是为了方便索引 和 查询。过滤器采用了 一个自定义的 同义词过滤器 my_syno_filter 和 一个自定义的停止词过滤器 my_stop_filter ,字符过滤器 char_filter 我们也采用了一个自定义的,为了结合我们后面的 自定义同义词词典来使用。
  • 看看两个过滤器,my_stop_filter 停止词过滤器,这里定义了一个空格,让搜索引擎过滤无效的空格,提升效率。my_syno_filter 同义词过滤器,这里是重点。首先,type 是 synonym 这个是es自带的同义词类型。synonyms_path 表示同义词词典的配置,这里我写的是
'synonyms_path' => 'analysis/synonyms.txt'

这个路径到底在哪里呢?

这里的路径,是相对一 es的config 目录来的。只要在config 目录下,你可以任意放在哪里,只要给到正确的路径就可以了。当然,我建议你像我一样这样放,有组织性。下面,我们来看看 字典的格式。

字典的编码必须是 utf-8 并且里面的 逗号,一定是英文的

话费, 花费, 化肥 => 话费
充值, 虫值, 重置 => 充值
返回, 返还, 退回 => 返还

这里,我根据我自己的业务,定义了几个同义词 以及一些纠错词。可见,这里分词一旦遇到 话费,话费,化肥 都会被解析成 话费来处理。

  • 字符过滤器 char_filter 根据字典的格式,定义了一个 ‘mappings’ => [‘| => |’]

接下来看看,我们的“字段”  mapping

  • 类型是 answers,properties 属性,这里由于我是 问答,只牵扯到问题和答案,因此,我定义一个 question,类型 type 是text。旧版本是 string,6.5 已经做出修改。analyzer 分析器,采用了 自定义的 ik_syno_max_word,也就是采用 ik 的 ik_max_word ,就是为了索引的时候,尽可能的拆分成的更细小的词,提高覆盖面。 search_analyzer 搜索分析器,采用的 ik_syno_smart,是基于 ik_smart 为了搜索规避一些关联度较低的问题。index 没有定义,默认的是加入索引。answer 字段,不参与索引,因此 index 定义为 false。这里也是 6.5 新改的,只能是 true 或者 false。

好了,执行这个索引建立程序,同时新加入一些数据。

<?php

use Elasticsearch\ClientBuilder;

require './vendor/autoload.php';

$hosts = [
    '127.0.0.1:9200'
];

$client = ClientBuilder::create()->setHosts($hosts)->build();

$params = [
    'index' => 'smart_talk',
    'body' => [
        'settings' => [
            'number_of_shards' => 1,
            'number_of_replicas' => 0,
            'analysis' => [
                'analyzer' => [
                    'ik_syno_smart' => [
                        'type' => 'custom',
                        'tokenizer' => 'ik_smart',
                        'filter' => ['my_stop_filter', 'my_syno_filter'],
                        'char_filter' => ['my_char_filter']
                    ],
                    'ik_syno_max_word' => [
                        'type' => 'custom',
                        'tokenizer' => 'ik_max_word',
                        'filter' => ['my_stop_filter', 'my_syno_filter'],
                        'char_filter' => ['my_char_filter']
                    ]
                ],
                'filter' => [
                    'my_stop_filter' => [
                        'type' => 'stop',
                        'stopwords' => [' ']
                    ],
                    'my_syno_filter' => [
                        'type' => 'synonym',
                        'synonyms_path' => 'analysis/synonyms.txt'
                    ]
                ],
                'char_filter' => [
                    'my_char_filter' => [
                        'type' => 'mapping',
                        'mappings' => ['| => |']
                    ]
                ]
            ]
        ],
        'mappings' => [
            'answers' => [
                'properties' => [
                    'question' => [
                        'type' => 'text',
                        'analyzer' => 'ik_syno_max_word',
                        'search_analyzer' => 'ik_syno_smart'
                    ],
                    'answer' => [
                        'type' => 'text',
                        'index' => false
                    ]
                ]
            ]
        ]
    ]
];

$response = $client->indices()->create($params);
echo "<pre>";
print_r($response);

// 索引文档
$params = [
    'index' => 'smart_talk',
    'type' => 'answers',
    'id' => 1,
    'body' => [
        'question' => '我每个月怎么充值话费',
        'answer' => '每月的账期,我们系统会自动帮您充值相应的话费'
    ]
];

$response = $client->index($params);

$params = [
    'index' => 'smart_talk',
    'type' => 'answers',
    'id' => 2,
    'body' => [
        'question' => '我的话费怎么返还的',
        'answer' => '具体返回的话费,请查看合同规定'
    ]
];

$response = $client->index($params);

$params = [
    'index' => 'smart_talk',
    'type' => 'answers',
    'id' => 3,
    'body' => [
        'question' => '你们公司的地址在哪里',
        'answer' => '我的地址在南京市雨花区南京软件谷华博科技园'
    ]
];

$response = $client->index($params);

得到如下的画面

Array
(
    [acknowledged] => 1
    [shards_acknowledged] => 1
    [index] => smart_talk
)

接着,我们通过 search.php 进行测试

<?php
/**
 * Created by PhpStorm.
 * User: NickBai
 * Email: 876337011@qq.com
 * Date: 2018/12/18
 * Time: 10:34 AM
 */

use Elasticsearch\ClientBuilder;

require './vendor/autoload.php';

$hosts = [
    '127.0.0.1:9200'
];

$client = ClientBuilder::create()->setHosts($hosts)->build();

$params = [
    'index' => 'smart_talk',
    'type' => 'answers',
    'body' => [
        'query' => [
            'match' => [
                'question' => '化肥'
            ]
        ]
    ]
];

$results = $client->search($params);
print_r($results);

可以看到,虽然我们输入的是化肥,依旧可以匹配到如下的内容

Array
(
    [took] => 26
    [timed_out] => 
    [_shards] => Array
        (
            [total] => 1
            [successful] => 1
            [skipped] => 0
            [failed] => 0
        )

    [hits] => Array
        (
            [total] => 2
            [max_score] => 0.480346
            [hits] => Array
                (
                    [0] => Array
                        (
                            [_index] => smart_talk
                            [_type] => answers
                            [_id] => 2
                            [_score] => 0.480346
                            [_source] => Array
                                (
                                    [question] => 我的话费怎么返还的
                                    [answer] => 具体返回的话费,请查看合同规定
                                )

                        )

                    [1] => Array
                        (
                            [_index] => smart_talk
                            [_type] => answers
                            [_id] => 1
                            [_score] => 0.45059982
                            [_source] => Array
                                (
                                    [question] => 我每个月怎么充值话费
                                    [answer] => 每月的账期,我们系统会自动帮您充值相应的话费
                                )

                        )

                )

        )

)

 注意,本版本演示的 6.5.3 ,不能保证 之前的版本 以及之后的版本依旧可以使用。所以,一定要注意您的版本与我的是否一致。

 

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!