ElasticSearch(7)

定制化中文分词器

对于搜索引擎来说,很多词都属于专有名字,比如凯悦,这是一个品牌,而对于分词器来说并不知道,所以对于凯悦的分词会是把两个字分开:

因此,我们需要定制分词词库。


扩展专有名词

首先来到es的config\analysis-ik目录下,新建new_word.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">new_word.dic</entry>
	 <!--用户可以在这里配置自己的扩展停止词字典-->
	<entry key="ext_stopwords"></entry>
	<!--用户可以在这里配置远程扩展字典 -->
	<!-- <entry key="remote_ext_dict">words_location</entry> -->
	<!--用户可以在这里配置远程扩展停止词字典-->
	<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

然后把这两处修改同步到其他节点上。重启es后再测试一下:

虽然配置成功了,但是在搜索的时候会发现是空的,这是因为配置上已经改了过来,但是在es索引中还是两个字分开的,所以需要更新索引,局部更新:

POST /shop/_update_by_query
{
  "query":{
    "bool":{
      "must":[
        {"term":{"name":"凯"}},
        {"term":{"name":"悦"}}
      ]
    }
  }
}

 这样,再搜索“凯悦”,就只会出来含有专有名词“凯悦”的结果了。


扩展同义词

对于中文来说,大多数时候搜索需要同义词,比如搜索苹果,对于买手机的来说可以跟iphone一起搜索出来,这就需要我们来配置同义词了,对于同义词来说,需要我们在创建索引结构的时候就加进去,所以需要把索引删掉重新导入。

首先新建一个synomyms.txt,里面写凯悦,锡伯,红桃

PUT /shop
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1,
    "analysis": {
      "filter": {
        "my_synonym_filter":{
          "type":"synonym",
          "synonyms_path":"analysis-ik/synomyms.txt"
        }
      },
      "analyzer": {
        "ik_syno":{
          "type":"custom",
          "tokenizer":"ik_smart",
          "filter":["my_synonym_filter"]
        },
        "ik_syno_max":{
          "type":"custom",
          "tokenizer":"ik_max_word",
          "filter":["my_synonym_filter"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "id":{"type":"integer"},
      "name":{"type": "text","analyzer": "ik_syno_max","search_analyzer": "ik_syno"},
      "tags":{"type": "text","analyzer": "whitespace","fielddata": true},
      "location":{"type": "geo_point"},
      "remark_score":{"type": "double"},
      "price_per_man":{"type": "integer"},
      "category_id":{"type": "integer"},
      "category_name":{"type": "keyword"},
      "seller_id":{"type": "integer"},
      "seller_remark_score":{"type": "double"},
      "seller_disabled_flag":{"type": "integer"}
    }
  }
}

加了一个过滤器,搜索关键字的时候会查询配置的文件中有没有这个词,有的话就都一起搜索出来。然后在要搜索的索引分词器改成自定义的,然后重新导入索引,先看下分词器的凯悦:

接下来搜索“锡伯”,结果中也可以看到,可以搜索出凯悦的结果。


相关性重塑

什么是相关性?例如搜索凯悦,可以出来很多结果,但是如果搜索住宿,按照人脑的想法,应该会搜出来酒店相关的结果,但是现阶段内,如果标题没有“住宿”是肯定搜索不出来的。要解决这样一个问题,明显需要在查询语句的判断中做些修改,例如:

GET /shop/_search
{
  "_source": "*", 
  "script_fields": {
    "distance": {
      "script": {
        "source": "haversin(lat,lon,doc['location'].lat,doc['location'].lon)",
        "lang": "expression",
        "params": {
          "lat":31.306171,
          "lon":121.525841
        }
      }
    }
  },
  "query": {
    "function_score": {
      "query": {
        "bool": {
          "must": [
            {
              "bool": {
                "should": [
                  {"match": {"name": {"query": "住宿","boost": 0.1}}},
                  {"term": {"category_id": {"value": "2","boost": 0.1}}}
                ]
              }
            },
            {"term": {"seller_disabled_flag": 0}}
          ]
        }
      },
      "functions": [
        {
          "gauss":{
            "location":{
              "origin":"31.306171,121.525841",
              "scale":"100km",
              "offset":"0km",
              "decay":0.5
            }
          },
          "weight": 9
        },
        {
          "field_value_factor": {
            "field": "remark_score"
          },
          "weight": 0.2
        },
        {
          "field_value_factor": {
            "field": "seller_remark_score"
          },
          "weight": 0.1
        }
      ],
      "score_mode": "sum",
      "boost_mode": "sum"
    }
  },
  "sort": [
    {
      "_score": {
        "order": "desc"
      }
    }
  ],
  "aggs": {
    "group_by_tags": {
      "terms": {
        "field": "tags"
      }
    }
  }
}

将关键字和类目放在一起,这样二者任意一个满足就可以出结果了,在java代码中:

    private Map<Integer,List<String>> categoryWorkMap = new HashMap<>();    

    //构造分词函数识别器
    @PostConstruct
    public void init(){
        categoryWorkMap.put(1,new ArrayList<>());
        categoryWorkMap.put(2,new ArrayList<>());

        categoryWorkMap.get(1).add("吃饭");
        categoryWorkMap.get(1).add("下午茶");

        categoryWorkMap.get(2).add("休息");
        categoryWorkMap.get(2).add("睡觉");
        categoryWorkMap.get(2).add("住宿");
    }

    private Integer getCategoryIdByToken(String token){
        for(Integer key : categoryWorkMap.keySet()){
            List<String> tokenList = categoryWorkMap.get(key);
            if(tokenList.contains(token)){
                return key;
            }
        }
        return null;
    }

    //构造分词函数识别器
    private Map<String,Object> analyzeCategoryKeyword(String keyword) throws IOException {
        Map<String,Object> res = new HashMap<>();

        Request request = new Request("GET","/shop/_analyze");
        request.setJsonEntity("{" + "  \"field\": \"name\"," + "  \"text\":\""+keyword+"\"\n" + "}");
        Response response = restHighLevelClient.getLowLevelClient().performRequest(request);
        String responseStr = EntityUtils.toString(response.getEntity());
        JSONObject jsonObject = JSONObject.parseObject(responseStr);
        JSONArray jsonArray = jsonObject.getJSONArray("tokens");
        for(int i = 0; i < jsonArray.size(); i++){
            String token = jsonArray.getJSONObject(i).getString("token");
            Integer categoryId = getCategoryIdByToken(token);
            if(categoryId != null){
                res.put(token,categoryId);
            }
        }
        return res;
    }

代码中的categoryWorkMap 可以写配置文件中,这里主要是讲方法,所以就不写了。这样,当搜索进来的时候,可以先匹配categoryWorkMap ,如果有关键字,就可以按照对应的类目进行搜索。

再说下打分机制,在搜索类目的时候,设置boost就可以了。

不过为了统一起来,可以在这里把数值改为0,不影响打分策略, 然后在function中配置,这样清晰明了,也可以统一管理打分机制。

当然,这是大多数情况。

发布了97 篇原创文章 · 获赞 28 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/haozi_rou/article/details/104821032
今日推荐