ElasticSearch插件编写-Similarity插件
1.ElasticSearch插件是什么
ElasticSearch可以灵活的支持多种插件的,以及可插拔式的安装卸载和使用。常见的插件包括head,marvel等功能性插件以及类似ik_analyzer分词器也可以通过插件的方式安装。这里主要整理了如何进行自定义插件的开发以及如何根据需求自定义ElasticSearch的打分规则。
2.如何开发自定义Similarity插件
开发版本ElasticSearch2.3.5(不同的版本之间的方式可能不同)。主要步骤就是实现ElasticSearch提供的相关接口。
1. 继承实现org.elasticsearch.plugins.Plugin类,创建onModule()并指定需要添加的Provider
2. 继承实现org.elasticsearch.index.similarity.AbstractSimilarityProvider类,加载自定义的打分机制类
3. 继承实现org.apache.lucene.search.similarities.Similarity实现具体的打分逻辑
4. 打包成jar包放入 es目录/plugins/自定义插件名/ 下,创建plugin-descriptor.properties来配置相关信息
5. 重启elasticsearch后插件生效
以上为开发自定义插件的流程
3.如何实现自定义打分规则
Similarity是ES中的打分模块,所有自定义打分规则和ElasticSearch内部的打分规则都由这个类派生得到。ES默认使用的打分规则是TFIDFSimilarity,建立VSM向量模型后用TF-IDF算法来为搜索结果进行打分。有一个可以替换的预置方案BM25Simlarity,本次的开发主要是要根据特定业务要求定制打分规则。重写以下几个方法来调整计算公式中的参数值。
/**
* qNorm 对qw的归一化处理 不影响排序
*/
@Override
public float queryNorm(float valueForNormalization) {
return 1.0F;
}
/**
* 官方文档中说明了该方法在直接继承Similarity时无法生效,需要继承更高级的Similarity比如TFIDFSimilarity
* coord 当前document包含的搜索的Term词的比率
*/
@Override
public float coord(int overlap, int maxOverlap) {
return (float) 1 / (float) maxOverlap;
}
/**
* 这是一个在索引数据时就会调用的方法 而不是在搜索数据时调用的
* 它的值对应着公式中 boost(t.field in d)×lengthNorm(t.field in d) 的值
* 在索引的时候给field添加一个权重
* @param state
* @return
*/
@Override
public long computeNorm(FieldInvertState state) {
float normValue = lengthNorm(state) * flag;
long norm = (long) normValue;
return norm;
}
/**
* 返回包装的SimWeight对象
*/
@Override
public final SimWeight computeWeight(CollectionStatistics collectionStats, TermStatistics... termStats) {
float numTerms = (float) termStats.length;
//numTerms query词的数量 观察执行发现其实一直是1,大概是每个query的词单独执行一遍本方法
return new OverlapStats(collectionStats.field(), numTerms);
}
/**返回包装的打分对象,依赖于上面的权重计算
*/
@Override
public final SimScorer simScorer(SimWeight stats, LeafReaderContext context) throws IOException {
OverlapStats overlapStats = (OverlapStats) stats;
return new OverlapScorer(overlapStats, context.reader().getNormValues(overlapStats.field));
}
更多细节请参考源代码,主要通过上面的几个方法来影响搜索的打分结果。
4.添加Field长度打分因子
在之前的插件中主要考虑了搜索词的命中情况,缺少对于Field域长度的关联打分。我们认为当query的term所命中的doc对应的field长度越长,其对应的信息价值就越低。和IDF的思路比较类似,所以要在原本命中关键词得分的基础上添加命中时field长度越长,那么其所应该得到的分值越低。
参考TFIDFSimilarity中的计算方法后对field做了如下的权重
$ lengthNorm = boost \times \frac {1} {\sqrt fieldLength} $
要注意的是computeNorm在创建数据索引的时候调用生成权重,所以在测试数据的时候要记得删除旧索引数据插入新数据才能看得到效果
最后在自定义实现的SimScore派生类中完成计算得到新分数
/**
* 加入域名反比相关因子,目前保留5位精度,后续观察调整
* 2018.4.3
* @param doc
* @param freq
* @return
*/
@Override
public float score(int doc, float freq) {
float norm = this.norms == null ? 1F : (float) (norms.get(doc) / (double) flag);
float queryWeight = stats.queryWeight * norm;
return queryWeight;
}
此时重启ElasticsSearch后清空旧数据并加入新数据,搜索“青果”,在两条数据的一元分词的字段“青果阅读”和“青果阅读早上好晚上好中午好”搜索,可以发现前者的分数大于后者,分数可以通过计算得到。